Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/ItemPrefab.cs
3 using FarseerPhysics;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Collections.Generic;
8 using System.Collections.Immutable;
9 using System.Linq;
10 
11 namespace Barotrauma
12 {
14  {
15  //sprite will be rendered if the condition of the item is below this
16  public readonly float MaxConditionPercentage;
17  public readonly Sprite Sprite;
18  public readonly bool FadeIn;
19  public readonly Point Offset;
20 
21  public BrokenItemSprite(Sprite sprite, float maxCondition, bool fadeIn, Point offset)
22  {
23  Sprite = sprite;
24  MaxConditionPercentage = MathHelper.Clamp(maxCondition, 0.0f, 100.0f);
25  FadeIn = fadeIn;
26  Offset = offset;
27  }
28  }
29 
31  {
33  {
34  None, HideWhenVisible, HideWhenNotVisible
35  }
36 
37  public readonly Sprite Sprite;
38  public readonly bool UseWhenAttached;
40  public readonly ImmutableHashSet<Identifier> AllowedContainerIdentifiers;
41  public readonly ImmutableHashSet<Identifier> AllowedContainerTags;
42 
43  public ContainedItemSprite(ContentXElement element, string path = "", bool lazyLoad = false)
44  {
45  Sprite = new Sprite(element, path, lazyLoad: lazyLoad);
46  UseWhenAttached = element.GetAttributeBool("usewhenattached", false);
47  Enum.TryParse(element.GetAttributeString("decorativespritebehavior", "None"), ignoreCase: true, out DecorativeSpriteBehavior);
48  AllowedContainerIdentifiers = element.GetAttributeIdentifierArray("allowedcontaineridentifiers", Array.Empty<Identifier>()).ToImmutableHashSet();
49  AllowedContainerTags = element.GetAttributeIdentifierArray("allowedcontainertags", Array.Empty<Identifier>()).ToImmutableHashSet();
50  }
51 
52  public bool MatchesContainer(Item container)
53  {
54  if (container == null) { return false; }
55  return AllowedContainerIdentifiers.Contains(container.Prefab.Identifier) ||
56  AllowedContainerTags.Any(t => container.Prefab.Tags.Contains(t));
57  }
58  }
59 
60  partial class ItemPrefab : MapEntityPrefab, IImplementsVariants<ItemPrefab>
61  {
62  public ImmutableDictionary<Identifier, ImmutableArray<DecorativeSprite>> UpgradeOverrideSprites { get; private set; }
63  public ImmutableArray<BrokenItemSprite> BrokenSprites { get; private set; }
64  public ImmutableArray<DecorativeSprite> DecorativeSprites { get; private set; }
65  public ImmutableArray<ContainedItemSprite> ContainedSprites { get; private set; }
66  public ImmutableDictionary<int, ImmutableArray<DecorativeSprite>> DecorativeSpriteGroups { get; private set; }
67  public Sprite InventoryIcon { get; private set; }
68  public Sprite MinimapIcon { get; private set; }
69  public Sprite UpgradePreviewSprite { get; private set; }
70  public Sprite InfectedSprite { get; private set; }
71  public Sprite DamagedInfectedSprite { get; private set; }
72 
73  public float UpgradePreviewScale = 1.0f;
74 
75  private IReadOnlyList<DamageModifier> wearableDamageModifiers;
76  private IReadOnlyDictionary<Identifier, float> wearableSkillModifiers;
77 
78  //only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis
79  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)]
80  public Color InventoryIconColor { get; protected set; }
81 
83  public string ImpactSoundTag { get; private set; }
84 
85  [Serialize(true, IsPropertySaveable.No)]
86  public bool ShowInStatusMonitor
87  {
88  get;
89  private set;
90  }
91  private void ParseSubElementsClient(ContentXElement element, ItemPrefab variantOf)
92  {
93  UpgradePreviewSprite = null;
95  InventoryIcon = null;
96  MinimapIcon = null;
97  InfectedSprite = null;
98  DamagedInfectedSprite = null;
99 
100  var upgradeOverrideSprites = new Dictionary<Identifier, List<DecorativeSprite>>();
101  var brokenSprites = new List<BrokenItemSprite>();
102  var decorativeSprites = new List<DecorativeSprite>();
103  var containedSprites = new List<ContainedItemSprite>();
104  var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
105 
106  var wearableDamageModifiers = new List<DamageModifier>();
107  var wearableSkillModifiers = new Dictionary<Identifier, float>();
108 
109  foreach (var subElement in element.Elements())
110  {
111  switch (subElement.Name.LocalName.ToLowerInvariant())
112  {
113  case "upgradeoverride":
114  {
115 
116  var sprites = new List<DecorativeSprite>();
117  foreach (var decorSprite in subElement.Elements())
118  {
119  if (decorSprite.NameAsIdentifier() == "decorativesprite")
120  {
121  sprites.Add(new DecorativeSprite(decorSprite));
122  }
123  }
124  upgradeOverrideSprites.Add(subElement.GetAttributeIdentifier("identifier", Identifier.Empty), sprites);
125  break;
126  }
127  case "upgradepreviewsprite":
128  {
129  string iconFolder = GetTexturePath(subElement, variantOf);
130  UpgradePreviewSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
131  UpgradePreviewScale = subElement.GetAttributeFloat("scale", 1.0f);
132  }
133  break;
134  case "inventoryicon":
135  {
136  string iconFolder = GetTexturePath(subElement, variantOf);
137  InventoryIcon = new Sprite(subElement, iconFolder, lazyLoad: true);
138  }
139  break;
140  case "minimapicon":
141  {
142  string iconFolder = GetTexturePath(subElement, variantOf);
143  MinimapIcon = new Sprite(subElement, iconFolder, lazyLoad: true);
144  }
145  break;
146  case "infectedsprite":
147  {
148  string iconFolder = GetTexturePath(subElement, variantOf);
149 
150  InfectedSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
151  }
152  break;
153  case "damagedinfectedsprite":
154  {
155  string iconFolder = GetTexturePath(subElement, variantOf);
156 
157  DamagedInfectedSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
158  }
159  break;
160  case "brokensprite":
161  string brokenSpriteFolder = GetTexturePath(subElement, variantOf);
162 
163  var brokenSprite = new BrokenItemSprite(
164  new Sprite(subElement, brokenSpriteFolder, lazyLoad: true),
165  subElement.GetAttributeFloat("maxcondition", 0.0f),
166  subElement.GetAttributeBool("fadein", false),
167  subElement.GetAttributePoint("offset", Point.Zero));
168 
169  int spriteIndex = 0;
170  for (int i = 0; i < brokenSprites.Count && brokenSprites[i].MaxConditionPercentage < brokenSprite.MaxConditionPercentage; i++)
171  {
172  spriteIndex = i;
173  }
174  brokenSprites.Insert(spriteIndex, brokenSprite);
175  break;
176  case "decorativesprite":
177  string decorativeSpriteFolder = GetTexturePath(subElement, variantOf);
178 
179  int groupID = 0;
180  DecorativeSprite decorativeSprite = null;
181  if (subElement.GetAttribute("texture") == null)
182  {
183  groupID = subElement.GetAttributeInt("randomgroupid", 0);
184  }
185  else
186  {
187  decorativeSprite = new DecorativeSprite(subElement, decorativeSpriteFolder, lazyLoad: true);
188  decorativeSprites.Add(decorativeSprite);
189  groupID = decorativeSprite.RandomGroupID;
190  }
191  if (!decorativeSpriteGroups.ContainsKey(groupID))
192  {
193  decorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
194  }
195  decorativeSpriteGroups[groupID].Add(decorativeSprite);
196 
197  break;
198  case "containedsprite":
199  string containedSpriteFolder = GetTexturePath(subElement, variantOf);
200  var containedSprite = new ContainedItemSprite(subElement, containedSpriteFolder, lazyLoad: true);
201  if (containedSprite.Sprite != null)
202  {
203  containedSprites.Add(containedSprite);
204  }
205  break;
206  case "wearable":
207  foreach (ContentXElement wearableSubElement in subElement.Elements())
208  {
209  switch (wearableSubElement.Name.LocalName.ToLowerInvariant())
210  {
211  case "damagemodifier":
212  wearableDamageModifiers.Add(new DamageModifier(wearableSubElement, Name.Value + ", Wearable", checkErrors: false));
213  break;
214  case "skillmodifier":
215  Identifier skillIdentifier = wearableSubElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
216  float skillValue = wearableSubElement.GetAttributeFloat("skillvalue", 0f);
217  if (wearableSkillModifiers.ContainsKey(skillIdentifier))
218  {
219  wearableSkillModifiers[skillIdentifier] += skillValue;
220  }
221  else
222  {
223  wearableSkillModifiers.TryAdd(skillIdentifier, skillValue);
224  }
225  break;
226  }
227  }
228  break;
229  }
230  }
231  this.wearableDamageModifiers = wearableDamageModifiers.ToImmutableList();
232  this.wearableSkillModifiers = wearableSkillModifiers.ToImmutableDictionary();
233 
234  UpgradeOverrideSprites = upgradeOverrideSprites.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
235  BrokenSprites = brokenSprites.ToImmutableArray();
236  DecorativeSprites = decorativeSprites.ToImmutableArray();
237  ContainedSprites = containedSprites.ToImmutableArray();
238  DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
239 
240 #if CLIENT
241  foreach (Item item in Item.ItemList)
242  {
243  if (item.Prefab == this)
244  {
245  item.InitSpriteStates();
246  }
247  }
248 #endif
249  }
250 
251  public bool CanCharacterBuy()
252  {
253  if (DefaultPrice == null) { return false; }
254  if (!DefaultPrice.RequiresUnlock) { return true; }
255  return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
256  }
258  {
259  LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖";
260  if (!Description.IsNullOrEmpty())
261  {
262  tooltip += $"\n{Description}";
263  }
264  if (wearableDamageModifiers.Any() || wearableSkillModifiers.Any())
265  {
266  Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
267  }
268  if (SkillRequirementHints != null && SkillRequirementHints.Any())
269  {
270  tooltip += GetSkillRequirementHints(character);
271  }
272  return tooltip;
273  }
274 
275  public override void UpdatePlacing(Camera cam)
276  {
277 
279  {
280  Selected = null;
281  return;
282  }
283 
285 
286  Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
288  {
289  if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
290  {
291  var item = new Item(new Rectangle((int)position.X, (int)position.Y, (int)(Sprite.size.X * Scale), (int)(Sprite.size.Y * Scale)), this, Submarine.MainSub)
292  {
294  };
295  item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
296  item.GetComponent<Items.Components.Door>()?.RefreshLinkedGap();
297  item.FindHull();
298  item.Submarine = Submarine.MainSub;
299 
300  if (PlayerInput.IsShiftDown())
301  {
302  if (potentialContainer?.OwnInventory?.TryPutItem(item, Character.Controlled) ?? false)
303  {
304  SoundPlayer.PlayUISound(GUISoundType.PickItem);
305  }
306  }
307 
308  SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> {item}, false));
309 
310  placePosition = Vector2.Zero;
311  return;
312  }
313  }
314  else
315  {
316  Vector2 placeSize = Size * Scale;
317 
318  if (placePosition == Vector2.Zero)
319  {
320  if (PlayerInput.PrimaryMouseButtonHeld() && GUI.MouseOn == null) { placePosition = position; }
321  }
322  else
323  {
324  if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, Size.X); }
325  if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, Size.Y); }
326 
328  {
329  var item = new Item(new Rectangle((int)placePosition.X, (int)placePosition.Y, (int)placeSize.X, (int)placeSize.Y), this, Submarine.MainSub);
330  placePosition = Vector2.Zero;
331 
332  item.Submarine = Submarine.MainSub;
333  item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
334  item.FindHull();
335 
336  SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { item }, false));
337 
338  return;
339  }
340  }
341  }
342 
343  if (potentialContainer != null)
344  {
345  potentialContainer.IsHighlighted = true;
346  }
347  }
348 
349  public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
350  {
351  Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
352 
354  {
355  Sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y) + Sprite.size / 2.0f * Scale, SpriteColor, scale: Scale);
356  }
357  else
358  {
359  Vector2 placeSize = Size * Scale;
360  if (placePosition != Vector2.Zero)
361  {
362  if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); }
363  if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); }
364  position = placePosition;
365  }
366  Sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
367  }
368  }
369 
370  public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
371  {
373  {
374  sprite.Draw(
375  spriteBatch: spriteBatch,
376  pos: new Vector2(placeRect.Center.X,
377  -(placeRect.Y - placeRect.Height / 2)),
378  color: SpriteColor * 0.8f,
379  scale: scale,
380  rotate: rotation,
381  spriteEffect: spriteEffects ^ sprite.effects);
382  }
383  else
384  {
385  Vector2 position = placeRect.Location.ToVector2();
386  Vector2 placeSize = placeRect.Size.ToVector2();
387  sprite?.DrawTiled(
388  spriteBatch: spriteBatch,
389  position: new Vector2(position.X, -position.Y),
390  targetSize: placeSize,
391  rotation: rotation,
392  textureScale: Vector2.One * scale,
393  color: SpriteColor * 0.8f,
394  spriteEffects: spriteEffects ^ sprite.effects);
395  }
396  }
397 
399  {
400  LocalizedString text = "";
401  if (SkillRequirementHints != null && SkillRequirementHints.Any() && character != null)
402  {
403  Color orange = GUIStyle.Orange;
404  // Reuse an existing, localized, text because it's what we want here: "Required skills:"
405  text = "\n\n" + $"‖color:{orange.ToStringHex()}‖{TextManager.Get("requiredrepairskills")}‖color:end‖";
406  foreach (var hint in SkillRequirementHints)
407  {
408  int skillLevel = (int)character.GetSkillLevel(hint.Skill);
409  Color levelColor = GUIStyle.Yellow;
410  if (skillLevel >= hint.Level)
411  {
412  levelColor = GUIStyle.Green;
413  }
414  else if (skillLevel < hint.Level / 2)
415  {
416  levelColor = GUIStyle.Red;
417  }
418  text += "\n" + hint.GetFormattedText(skillLevel, levelColor.ToStringHex());
419  }
420  }
421  return text;
422  }
423  }
424 }
BrokenItemSprite(Sprite sprite, float maxCondition, bool fadeIn, Point offset)
Vector2 ScreenToWorld(Vector2 coords)
Definition: Camera.cs:410
float GetSkillLevel(string skillIdentifier)
readonly ImmutableHashSet< Identifier > AllowedContainerTags
readonly DecorativeSpriteBehaviorType DecorativeSpriteBehavior
ContainedItemSprite(ContentXElement element, string path="", bool lazyLoad=false)
readonly ImmutableHashSet< Identifier > AllowedContainerIdentifiers
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
IEnumerable< ContentXElement > Elements()
bool GetAttributeBool(string key, bool def)
ImmutableArray< DecorativeSprite > DecorativeSprites
ImmutableDictionary< int, ImmutableArray< DecorativeSprite > > DecorativeSpriteGroups
ImmutableArray< ContainedItemSprite > ContainedSprites
override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
ImmutableArray< SkillRequirementHint > SkillRequirementHints
override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale=1.0f, float rotation=0.0f, SpriteEffects spriteEffects=SpriteEffects.None)
LocalizedString GetTooltip(Character character)
ImmutableDictionary< Identifier, ImmutableArray< DecorativeSprite > > UpgradeOverrideSprites
LocalizedString GetSkillRequirementHints(Character character)
override ImmutableHashSet< Identifier > Tags
ImmutableArray< BrokenItemSprite > BrokenSprites
override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
static Item GetPotentialContainer(Vector2 position, HashSet< MapEntity > entities=null)
readonly Identifier Identifier
Definition: Prefab.cs:34
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation=0f, Vector2? origin=null, Color? color=null, Vector2? startOffset=null, Vector2? textureScale=null, float? depth=null, SpriteEffects? spriteEffects=null)
static void StoreCommand(Command command)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, Vector2? mousePos=null, bool round=false)
GUISoundType
Definition: GUI.cs:21