Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Wearable.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using Barotrauma.IO;
5 using System.Linq;
6 using System.Xml.Linq;
10 using System.Collections.Immutable;
11 
12 namespace Barotrauma
13 {
14  public enum WearableType
15  {
16  Item,
17  Hair,
18  Beard,
19  Moustache,
21  Husk,
22  Herpes
23  }
24 
26  {
27  public ContentPath UnassignedSpritePath { get; private set; }
28  public string SpritePath { get; private set; }
29  public ContentXElement SourceElement { get; private set; }
30 
31  public WearableType Type { get; private set; }
32  private Sprite _sprite;
33  public Sprite Sprite
34  {
35  get { return _sprite; }
36  private set
37  {
38  if (value == _sprite) { return; }
39  if (_sprite != null)
40  {
41  _sprite.Remove();
42  }
43  _sprite = value;
44  }
45  }
46  public LimbType Limb { get; private set; }
47  public bool HideLimb { get; private set; }
48 
49  public enum ObscuringMode
50  {
51  None,
52  Hide,
53  AlphaClip
54  }
55  public ObscuringMode ObscureOtherWearables { get; private set; }
58  public bool CanBeHiddenByOtherWearables { get; private set; }
59 
63  public ImmutableHashSet<Identifier> CanBeHiddenByItem { get; private set; }
64  public List<WearableType> HideWearablesOfType { get; private set; }
65  public bool InheritLimbDepth { get; private set; }
69  public bool InheritScale { get; private set; }
70  public bool IgnoreRagdollScale { get; private set; }
71  public bool IgnoreLimbScale { get; private set; }
72  public bool IgnoreTextureScale { get; private set; }
73  public bool InheritOrigin { get; private set; }
74  public bool InheritSourceRect { get; private set; }
75 
76  public float Scale { get; private set; }
77 
78  public float Rotation { get; private set; }
79 
80  public LimbType DepthLimb { get; private set; }
81  private Wearable _wearableComponent;
83  {
84  get { return _wearableComponent; }
85  set
86  {
87  if (value == _wearableComponent) { return; }
88  if (_wearableComponent != null)
89  {
90  _wearableComponent.Remove();
91  }
92  _wearableComponent = value;
93  }
94  }
95  public string Sound { get; private set; }
96  public Point? SheetIndex { get; private set; }
97 
98  public LightComponent LightComponent => LightComponents?.FirstOrDefault();
99 
100  public List<LightComponent> LightComponents
101  {
102  get
103  {
104  if (_lightComponents == null)
105  {
106  _lightComponents = new List<LightComponent>();
107  }
108  return _lightComponents;
109  }
110  }
111  private List<LightComponent> _lightComponents;
112 
113  public int Variant { get; set; }
114 
115  private Character _picker;
121  {
122  get { return _picker; }
123  set
124  {
125  if (value == _picker) { return; }
126  _picker = value;
127  IsInitialized = false;
128  UnassignedSpritePath = ParseSpritePath(SourceElement);
129  Init(_picker);
130  }
131  }
132 
133  public WearableSprite(ContentXElement subElement, WearableType type)
134  {
135  Type = type;
136  SourceElement = subElement;
138  Init();
139  switch (type)
140  {
141  case WearableType.Hair:
142  case WearableType.Beard:
143  case WearableType.Moustache:
144  case WearableType.FaceAttachment:
145  case WearableType.Husk:
146  case WearableType.Herpes:
147  Limb = LimbType.Head;
148  break;
149  }
150  }
151 
155  public WearableSprite(ContentXElement subElement, Wearable wearable, int variant = 0)
156  {
157  Type = WearableType.Item;
158  WearableComponent = wearable;
159  Variant = Math.Max(variant, 0);
160  UnassignedSpritePath = ParseSpritePath(subElement);
161  SourceElement = subElement;
162  }
163 
164  private ContentPath ParseSpritePath(ContentXElement element)
165  {
166  if (element.DoesAttributeReferenceFileNameAlone("texture"))
167  {
168  string textureName = element.GetAttributeString("texture", "");
169  return ContentPath.FromRaw(
170  element.ContentPackage,
171  $"{Path.GetDirectoryName(WearableComponent.Item.Prefab.FilePath)}/{textureName}");
172  }
173  else
174  {
175  return element.GetAttributeContentPath("texture") ?? ContentPath.Empty;
176  }
177  }
178 
179  public void ParsePath(bool parseSpritePath)
180  {
181 #if SERVER
182  // Server doesn't care about texture paths at all
183  return;
184 #endif
186  if (_picker?.Info != null)
187  {
189  }
190  SpritePath = SpritePath.Replace("[VARIANT]", Variant.ToString());
191  if (!File.Exists(SpritePath))
192  {
193  // If the variant does not exist, parse the path so that it uses first variant.
194  SpritePath = SpritePath.Replace("[VARIANT]", "1");
195  }
196  if (!File.Exists(SpritePath) && _picker?.Info == null)
197  {
198  // If there's no character info is defined, try to use first tagset from CharacterInfoPrefab
199  var charInfoPrefab = CharacterPrefab.HumanPrefab.CharacterInfoPrefab;
200  SpritePath = charInfoPrefab.ReplaceVars(SpritePath, charInfoPrefab.Heads.First());
201  }
202  if (parseSpritePath)
203  {
205  }
206  }
207 
208  public bool IsInitialized { get; private set; }
209  public void Init(Character picker = null)
210  {
211  if (IsInitialized) { return; }
212 
213  _picker = picker;
214  ParsePath(false);
215  Sprite?.Remove();
216  Sprite = new Sprite(SourceElement, file: SpritePath);
217  Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
218  HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
219 
220  foreach (var mode in Enum.GetValues<ObscuringMode>())
221  {
222  if (mode == ObscuringMode.None) { continue; }
223  if (SourceElement.GetAttributeBool($"{mode}OtherWearables", false))
224  {
225  ObscureOtherWearables = mode;
226  }
227  }
228 
229  CanBeHiddenByOtherWearables = SourceElement.GetAttributeBool("canbehiddenbyotherwearables", true);
230  CanBeHiddenByItem = SourceElement.GetAttributeIdentifierArray(nameof(CanBeHiddenByItem), Array.Empty<Identifier>()).ToImmutableHashSet();
231  InheritLimbDepth = SourceElement.GetAttributeBool("inheritlimbdepth", true);
232  var scale = SourceElement.GetAttribute("inheritscale");
233  if (scale != null)
234  {
235  InheritScale = scale.GetAttributeBool(Type != WearableType.Item);
236  }
237  else
238  {
239  InheritScale = SourceElement.GetAttributeBool("inherittexturescale", Type != WearableType.Item);
240  }
241  IgnoreLimbScale = SourceElement.GetAttributeBool("ignorelimbscale", false);
242  IgnoreTextureScale = SourceElement.GetAttributeBool("ignoretexturescale", false);
243  IgnoreRagdollScale = SourceElement.GetAttributeBool("ignoreragdollscale", false);
244  SourceElement.GetAttributeBool("inherittexturescale", false);
245  InheritOrigin = SourceElement.GetAttributeBool("inheritorigin", Type != WearableType.Item);
246  InheritSourceRect = SourceElement.GetAttributeBool("inheritsourcerect", Type != WearableType.Item);
247  DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true);
248  Sound = SourceElement.GetAttributeString("sound", "");
249  Scale = SourceElement.GetAttributeFloat("scale", 1.0f);
250  Rotation = MathHelper.ToRadians(SourceElement.GetAttributeFloat("rotation", 0.0f));
251  var index = SourceElement.GetAttributePoint("sheetindex", new Point(-1, -1));
252  if (index.X > -1 && index.Y > -1)
253  {
254  SheetIndex = index;
255  }
256 
257  HideWearablesOfType = new List<WearableType>();
258  var wearableTypes = SourceElement.GetAttributeStringArray("hidewearablesoftype", null);
259  if (wearableTypes != null && wearableTypes.Length > 0)
260  {
261  foreach (var value in wearableTypes)
262  {
263  if (Enum.TryParse(value, ignoreCase: true, out WearableType wearableType))
264  {
265  HideWearablesOfType.Add(wearableType);
266  }
267  }
268  }
269 
270  IsInitialized = true;
271  }
272  }
273 }
274 
276 {
278  {
279  private readonly ContentXElement[] wearableElements;
280  private readonly WearableSprite[] wearableSprites;
281  private readonly LimbType[] limbType;
282  private readonly Limb[] limb;
283 
284  private readonly List<DamageModifier> damageModifiers;
285  public readonly Dictionary<Identifier, float> SkillModifiers;
286 
287  public readonly Dictionary<StatTypes, float> WearableStatValues = new Dictionary<StatTypes, float>();
288 
289  public IEnumerable<DamageModifier> DamageModifiers
290  {
291  get { return damageModifiers; }
292  }
293 
294  public bool AutoEquipWhenFull { get; private set; }
295  public bool DisplayContainedStatus { get; private set; }
296 
297  [Serialize(false, IsPropertySaveable.No, description: "Can the item be used (assuming it has components that are usable in some way) when worn.")]
298  public bool AllowUseWhenWorn { get; set; }
299 
300  public readonly int Variants;
301 
302  private int variant;
303  public int Variant
304  {
305  get { return variant; }
306  set
307  {
308  if (variant == value) { return; }
309 #if SERVER
310  variant = value;
311  if (!item.Submarine?.Loading ?? true)
312  {
313  item.CreateServerEvent(this);
314  }
315 #elif CLIENT
316 
317  Character character = picker;
318  if (character != null)
319  {
320  Unequip(character);
321  }
322 
323  for (int i = 0; i < wearableSprites.Length; i++)
324  {
325  var subElement = wearableElements[i];
326 
327  wearableSprites[i]?.Sprite?.Remove();
328  wearableSprites[i] = new WearableSprite(subElement, this, value);
329  }
330 
331  if (character != null)
332  {
333  Equip(character);
334  }
335 
336  variant = value;
337 #endif
338  }
339  }
340 
344  public readonly float PressureProtection;
345 
346  public Wearable(Item item, ContentXElement element) : base(item, element)
347  {
348  this.item = item;
349 
350  damageModifiers = new List<DamageModifier>();
351  SkillModifiers = new Dictionary<Identifier, float>();
352 
353  int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
354  Variants = element.GetAttributeInt("variants", 0);
355  variant = Rand.Range(1, Variants + 1, Rand.RandSync.ServerAndClient);
356  wearableSprites = new WearableSprite[spriteCount];
357  wearableElements = new ContentXElement[spriteCount];
358  limbType = new LimbType[spriteCount];
359  limb = new Limb[spriteCount];
360  AutoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
361  DisplayContainedStatus = element.GetAttributeBool("displaycontainedstatus", false);
362  int i = 0;
363  foreach (var subElement in element.Elements())
364  {
365  switch (subElement.Name.ToString().ToLowerInvariant())
366  {
367  case "sprite":
368  if (subElement.GetAttribute("texture") == null)
369  {
370  DebugConsole.ThrowError("Item \"" + item.Name + "\" doesn't have a texture specified!",
371  contentPackage: element.ContentPackage);
372  return;
373  }
374 
375  limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
376  subElement.GetAttributeString("limb", "Head"), true);
377 
378  wearableSprites[i] = new WearableSprite(subElement, this, variant);
379  wearableElements[i] = subElement;
380 
381  foreach (var lightElement in subElement.Elements())
382  {
383  if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
384  wearableSprites[i].LightComponents.Add(new LightComponent(item, lightElement)
385  {
386  Parent = this
387  });
388  foreach (var light in wearableSprites[i].LightComponents)
389  {
390  item.AddComponent(light);
391  }
392  }
393 
394  i++;
395  break;
396  case "damagemodifier":
397  damageModifiers.Add(new DamageModifier(subElement, item.Name + ", Wearable"));
398  break;
399  case "skillmodifier":
400  Identifier skillIdentifier = subElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
401  float skillValue = subElement.GetAttributeFloat("skillvalue", 0f);
402  if (SkillModifiers.ContainsKey(skillIdentifier))
403  {
404  SkillModifiers[skillIdentifier] += skillValue;
405  }
406  else
407  {
408  SkillModifiers.TryAdd(skillIdentifier, skillValue);
409  }
410  break;
411  case "statvalue":
412  StatTypes statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), Name);
413  float statValue = subElement.GetAttributeFloat("value", 0f);
414  if (WearableStatValues.ContainsKey(statType))
415  {
416  WearableStatValues[statType] += statValue;
417  }
418  else
419  {
420  WearableStatValues.TryAdd(statType, statValue);
421  }
422  break;
423  case "statuseffect":
424  if (subElement.GetAttributeString("Target", string.Empty).ToLowerInvariant().Contains("character"))
425  {
426  PressureProtection = Math.Max(subElement.GetAttributeFloat(nameof(PressureProtection), 0), PressureProtection);
427  }
428  break;
429  }
430  }
431  }
432 
433  public override void Equip(Character character)
434  {
435  foreach (var allowedSlot in allowedSlots)
436  {
437  if (allowedSlot == InvSlotType.Any) { continue; }
438  foreach (Enum value in Enum.GetValues(typeof(InvSlotType)))
439  {
440  var slotType = (InvSlotType)value;
441  if (slotType == InvSlotType.Any || slotType == InvSlotType.None) { continue; }
442  if (allowedSlot.HasFlag(slotType) && !character.Inventory.IsInLimbSlot(item, slotType))
443  {
444  return;
445  }
446  }
447  }
448 
449  picker = character;
450 
451  for (int i = 0; i < wearableSprites.Length; i++ )
452  {
453  var wearableSprite = wearableSprites[i];
454  if (!wearableSprite.IsInitialized) { wearableSprite.Init(picker); }
455  // If the item is gender specific (it has a different textures for male and female), we have to change the gender here so that the texture is updated.
456  wearableSprite.Picker = picker;
457 
458  Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
459  if (equipLimb == null) { continue; }
460 
461  if (item.body != null)
462  {
463  item.body.Enabled = false;
464  }
465  IsActive = true;
466  if (wearableSprite.LightComponent != null)
467  {
468  foreach (var light in wearableSprite.LightComponents)
469  {
470  light.ParentBody = equipLimb.body;
471  }
472  }
473 
474  limb[i] = equipLimb;
475  if (!equipLimb.WearingItems.Contains(wearableSprite))
476  {
477  equipLimb.WearingItems.Add(wearableSprite);
478  equipLimb.WearingItems.Sort((wearable, nextWearable) =>
479  {
480  float depth = wearable?.Sprite?.Depth ?? 0;
481  float nextDepth = nextWearable?.Sprite?.Depth ?? 0;
482  return nextDepth.CompareTo(depth);
483  });
484  equipLimb.WearingItems.Sort((wearable, nextWearable) =>
485  {
486  var wearableComponent = wearable?.WearableComponent;
487  var nextWearableComponent = nextWearable?.WearableComponent;
488  if (wearableComponent == null && nextWearableComponent == null) { return 0; }
489  if (wearableComponent == null) { return -1; }
490  if (nextWearableComponent == null) { return 1; }
491  return wearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes).CompareTo(nextWearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes));
492  });
493  }
494 #if CLIENT
495  equipLimb.UpdateWearableTypesToHide();
496 #endif
497  }
498  character.OnWearablesChanged();
499  }
500 
501  public override void Drop(Character dropper, bool setTransform = true)
502  {
503  Character previousPicker = picker;
504  Unequip(picker);
505  base.Drop(dropper, setTransform);
506  previousPicker?.OnWearablesChanged();
507  picker = null;
508  IsActive = false;
509  }
510 
511  public override void Unequip(Character character)
512  {
513  if (character == null || character.Removed) { return; }
514  if (picker == null) { return; }
515 
516  for (int i = 0; i < wearableSprites.Length; i++)
517  {
518  Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
519  if (equipLimb == null) { continue; }
520 
521  if (wearableSprites[i].LightComponent != null)
522  {
523  foreach (var light in wearableSprites[i].LightComponents)
524  {
525  light.ParentBody = null;
526  }
527  }
528 
529  equipLimb.WearingItems.RemoveAll(w => w != null && w == wearableSprites[i]);
530 #if CLIENT
531  equipLimb.UpdateWearableTypesToHide();
532 #endif
533  limb[i] = null;
534  }
535 
536  IsActive = false;
537  }
538 
539  public override void UpdateBroken(float deltaTime, Camera cam)
540  {
541  Update(deltaTime, cam);
542  }
543 
544  public override void Update(float deltaTime, Camera cam)
545  {
546  if (picker == null || picker.Removed)
547  {
548  IsActive = false;
549  return;
550  }
551 
552  //if the item is also being held, let the Holdable component control the position
553  if (item.GetComponent<Holdable>() is not { IsActive: true })
554  {
556  }
557  item.ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
558 
559 #if CLIENT
560  PlaySound(ActionType.OnWearing, picker);
561 #endif
562  }
563 
564  protected override void RemoveComponentSpecific()
565  {
566  base.RemoveComponentSpecific();
567 
568  Unequip(picker);
569 
570  foreach (WearableSprite wearableSprite in wearableSprites)
571  {
572  wearableSprite?.Sprite?.Remove();
573  wearableSprite.Picker = null;
574  }
575  }
576 
577  public override XElement Save(XElement parentElement)
578  {
579  XElement componentElement = base.Save(parentElement);
580  componentElement.Add(new XAttribute("variant", variant));
581  return componentElement;
582  }
583 
584  private int loadedVariant = -1;
585  public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
586  {
587  base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
588  loadedVariant = componentElement.GetAttributeInt("variant", -1);
589  }
590  public override void OnItemLoaded()
591  {
592  base.OnItemLoaded();
593  //do this here to prevent creating a network event before the item has been fully initialized
594  if (loadedVariant > 0 && loadedVariant < Variants + 1)
595  {
596  Variant = loadedVariant;
597  }
598  }
599  public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
600  {
601  msg.WriteByte((byte)Variant);
602  base.ServerEventWrite(msg, c, extraData);
603  }
604 
605  public override void ClientEventRead(IReadMessage msg, float sendingTime)
606  {
607  Variant = (int)msg.ReadByte();
608  base.ClientEventRead(msg, sendingTime);
609  }
610 
611  }
612 }
static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
CharacterInfoPrefab CharacterInfoPrefab
static CharacterPrefab HumanPrefab
static ContentPath FromRaw(string? rawValue)
static readonly ContentPath Empty
Definition: ContentPath.cs:12
string???????????? Value
Definition: ContentPath.cs:27
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
Point GetAttributePoint(string key, in Point def)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
IEnumerable< ContentXElement > Elements()
bool DoesAttributeReferenceFileNameAlone(string key)
ContentPath? GetAttributeContentPath(string key)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
string?[] GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant=false)
XAttribute? GetAttribute(string name)
Submarine Submarine
Definition: Entity.cs:53
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb limb=null, Entity useTarget=null, bool isNetworkEvent=false, Vector2? worldPosition=null)
Executes all StatusEffects of the specified type. Note that condition checks are ignored here: that s...
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
void AddComponent(ItemComponent component)
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
List< InvSlotType > allowedSlots
Definition: Pickable.cs:14
override void ClientEventRead(IReadMessage msg, float sendingTime)
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
readonly float PressureProtection
How deep down does the item protect from pressure? Determined by status effects.
override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
readonly List< WearableSprite > WearingItems
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
bool ParseTexturePath(string path="", string file="")
ImmutableHashSet< Identifier > CanBeHiddenByItem
Tags or identifiers of items that can hide this one.
bool InheritScale
Does the wearable inherit all the scalings of the wearer? Also the wearable's own scale is used!
Character Picker
None = Any/Not Defined -> no effect. Changing the gender forces re-initialization,...
WearableSprite(ContentXElement subElement, Wearable wearable, int variant=0)
Note: this constructor cannot initialize automatically, because the gender is unknown at this point....
WearableSprite(ContentXElement subElement, WearableType type)
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180