Client LuaCsForBarotrauma
RelatedItem.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Collections.Immutable;
4 using System.Linq;
5 using System.Xml.Linq;
6 using Microsoft.Xna.Framework;
8 
9 namespace Barotrauma
10 {
17  {
18  public enum RelationType
19  {
20  None,
25  Contained,
29  Equipped,
33  Picked,
38  Container,
42  Invalid
43  }
44 
48  public bool MatchOnEmpty { get; set; }
49 
53  public bool RequireEmpty { get; set; }
54 
55  private bool RequireOrMatchOnEmpty => MatchOnEmpty || RequireEmpty;
56 
61  public bool IgnoreInEditor { get; set; }
62 
63 
68  public ImmutableHashSet<Identifier> ExcludedIdentifiers { get; private set; }
69 
70  private readonly RelationType type;
71 
72  public List<StatusEffect> StatusEffects = new List<StatusEffect>();
73 
78 
82  public Identifier MsgTag;
83 
87  public bool ExcludeBroken { get; private set; }
88 
92  public bool ExcludeFullCondition { get; private set; }
93 
97  public bool AllowVariants { get; private set; } = true;
98 
100  {
101  get { return type; }
102  }
103 
107  public int TargetSlot = -1;
108 
113 
117  public Vector2? ItemPos;
118 
123  public bool Hide;
124 
129  public float Rotation;
130 
135  public bool SetActive;
136 
142 
147  public bool IsOptional { get; set; }
148 
149  public string JoinedIdentifiers
150  {
151  get => Identifiers.ConvertToString();
152  set => Identifiers = value.ToIdentifiers().ToImmutableHashSet();
153  }
154 
158  public ImmutableHashSet<Identifier> Identifiers { get; private set; }
159 
161  {
162  get => ExcludedIdentifiers.ConvertToString();
163  set => ExcludedIdentifiers = value.ToIdentifiers().ToImmutableHashSet();
164  }
165 
166  public bool MatchesItem(Item item)
167  {
168  if (item == null) { return false; }
169  if (ExcludedIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
170  foreach (var excludedIdentifier in ExcludedIdentifiers)
171  {
172  if (item.HasTag(excludedIdentifier)) { return false; }
173  }
174  if (item.ParentInventory?.Owner is Character character && CharacterInventorySlotType != InvSlotType.None)
175  {
176  if (!character.HasEquippedItem(item, CharacterInventorySlotType)) { return false; }
177  }
178  if (Identifiers.Contains(item.Prefab.Identifier)) { return true; }
179  foreach (var identifier in Identifiers)
180  {
181  if (item.HasTag(identifier)) { return true; }
182  }
183  if (AllowVariants && !item.Prefab.VariantOf.IsEmpty)
184  {
185  if (Identifiers.Contains(item.Prefab.VariantOf)) { return true; }
186  }
187  return false;
188  }
189  public bool MatchesItem(ItemPrefab itemPrefab)
190  {
191  if (itemPrefab == null) { return false; }
192  if (ExcludedIdentifiers.Contains(itemPrefab.Identifier)) { return false; }
193  foreach (var excludedIdentifier in ExcludedIdentifiers)
194  {
195  if (itemPrefab.Tags.Contains(excludedIdentifier)) { return false; }
196  }
197  if (Identifiers.Contains(itemPrefab.Identifier)) { return true; }
198  foreach (var identifier in Identifiers)
199  {
200  if (itemPrefab.Tags.Contains(identifier)) { return true; }
201  }
202  if (AllowVariants && !itemPrefab.VariantOf.IsEmpty)
203  {
204  if (Identifiers.Contains(itemPrefab.VariantOf)) { return true; }
205  }
206  return false;
207  }
208 
209  public RelatedItem(Identifier[] identifiers, Identifier[] excludedIdentifiers)
210  {
211  this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
212  this.ExcludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
213  }
214 
215  public RelatedItem(ContentXElement element, string parentDebugName)
216  {
217  Identifier[] identifiers;
218  if (element.GetAttribute("name") != null)
219  {
220  //backwards compatibility + a console warning
221  DebugConsole.ThrowError($"Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names.", contentPackage: element.ContentPackage);
222  Identifier[] itemNames = element.GetAttributeIdentifierArray("name", Array.Empty<Identifier>());
223  //attempt to convert to identifiers and tags
224  List<Identifier> convertedIdentifiers = new List<Identifier>();
225  foreach (Identifier itemName in itemNames)
226  {
227  var matchingItem = ItemPrefab.Prefabs.Find(me => me.Name == itemName.Value);
228  if (matchingItem != null)
229  {
230  convertedIdentifiers.Add(matchingItem.Identifier);
231  }
232  else
233  {
234  //no matching item found, this must be a tag
235  convertedIdentifiers.Add(itemName);
236  }
237  }
238  identifiers = convertedIdentifiers.ToArray();
239  }
240  else
241  {
242  identifiers = element.GetAttributeIdentifierArray("items", null) ?? element.GetAttributeIdentifierArray("item", null);
243  if (identifiers == null)
244  {
245  identifiers = element.GetAttributeIdentifierArray("identifiers", null) ?? element.GetAttributeIdentifierArray("tags", null);
246  if (identifiers == null)
247  {
248  identifiers = element.GetAttributeIdentifierArray("identifier", null) ?? element.GetAttributeIdentifierArray("tag", Array.Empty<Identifier>());
249  }
250  }
251  }
252  this.Identifiers = identifiers.ToImmutableHashSet();
253 
254  Identifier[] excludedIdentifiers = element.GetAttributeIdentifierArray("excludeditems", null) ?? element.GetAttributeIdentifierArray("excludeditem", null);
255  if (excludedIdentifiers == null)
256  {
257  excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifiers", null) ?? element.GetAttributeIdentifierArray("excludedtags", null);
258  if (excludedIdentifiers == null)
259  {
260  excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifier", null) ?? element.GetAttributeIdentifierArray("excludedtag", Array.Empty<Identifier>());
261  }
262  }
263  this.ExcludedIdentifiers = excludedIdentifiers.ToImmutableHashSet();
264 
265  ExcludeBroken = element.GetAttributeBool("excludebroken", true);
266  RequireEmpty = element.GetAttributeBool("requireempty", false);
267  ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false);
268  AllowVariants = element.GetAttributeBool("allowvariants", true);
269  Rotation = element.GetAttributeFloat("rotation", 0f);
270  SetActive = element.GetAttributeBool("setactive", false);
272 
273  CharacterInventorySlotType = element.GetAttributeEnum(nameof(CharacterInventorySlotType), InvSlotType.None);
274 
275  if (element.GetAttribute(nameof(Hide)) != null)
276  {
277  Hide = element.GetAttributeBool(nameof(Hide), false);
278  }
279  if (element.GetAttribute(nameof(ItemPos)) != null)
280  {
281  ItemPos = element.GetAttributeVector2(nameof(ItemPos), Vector2.Zero);
282  }
283  string typeStr = element.GetAttributeString("type", "");
284  if (string.IsNullOrEmpty(typeStr))
285  {
286  switch (element.Name.ToString().ToLowerInvariant())
287  {
288  case "containable":
289  typeStr = "Contained";
290  break;
291  case "suitablefertilizer":
292  case "suitableseed":
293  typeStr = "None";
294  break;
295  }
296  }
297  if (!Enum.TryParse(typeStr, true, out type))
298  {
299  DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type.", contentPackage: element.ContentPackage);
300  type = RelationType.Invalid;
301  }
302 
303  MsgTag = element.GetAttributeIdentifier("msg", Identifier.Empty);
304  LocalizedString msg = TextManager.Get(MsgTag);
305  if (!msg.Loaded)
306  {
307  Msg = MsgTag.Value;
308  }
309  else
310  {
311 #if CLIENT
312  foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
313  {
314  string inputTag = $"[{inputType.ToString().ToLowerInvariant()}]";
315  if (!msg.Contains(inputTag)) { continue; }
316  msg = msg.Replace(inputTag, GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType));
317  }
318  Msg = msg;
319 #endif
320  }
321 
322  foreach (var subElement in element.Elements())
323  {
324  if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
325  StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
326  }
327 
328  IsOptional = element.GetAttributeBool("optional", false);
329  IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
330  MatchOnEmpty = element.GetAttributeBool("matchonempty", false);
331  TargetSlot = element.GetAttributeInt("targetslot", -1);
332 
333  }
334 
335  public bool CheckRequirements(Character character, Item parentItem)
336  {
337  switch (type)
338  {
339  case RelationType.Contained:
340  if (parentItem == null) { return false; }
341  return CheckContained(parentItem);
342  case RelationType.Container:
343  if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty || RequireEmpty; }
344  return CheckItem(parentItem.Container, this);
345  case RelationType.Equipped:
346  if (character == null) { return false; }
347  var heldItems = character.HeldItems;
348  if (RequireOrMatchOnEmpty && heldItems.None()) { return true; }
349  foreach (Item equippedItem in heldItems)
350  {
351  if (equippedItem == null) { continue; }
352  if (CheckItem(equippedItem, this))
353  {
354  if (RequireEmpty && equippedItem.Condition > 0) { return false; }
355  return true;
356  }
357  }
358  break;
359  case RelationType.Picked:
360  if (character == null) { return false; }
361  if (character.Inventory == null) { return MatchOnEmpty || RequireEmpty; }
362  var allItems = character.Inventory.AllItems;
363  if (RequireOrMatchOnEmpty && allItems.None()) { return true; }
364  foreach (Item pickedItem in allItems)
365  {
366  if (pickedItem == null) { continue; }
367  if (CheckItem(pickedItem, this))
368  {
369  if (RequireEmpty && pickedItem.Condition > 0) { return false; }
370  return true;
371  }
372  }
373  break;
374  default:
375  return true;
376  }
377 
378  static bool CheckItem(Item i, RelatedItem ri) => (!ri.ExcludeBroken || ri.RequireEmpty || i.Condition > 0.0f) && (!ri.ExcludeFullCondition || !i.IsFullCondition) && ri.MatchesItem(i);
379 
380  return false;
381  }
382 
383  private bool CheckContained(Item parentItem)
384  {
385  if (parentItem.OwnInventory == null) { return false; }
386 
387  if (TargetSlot == -1 && RequireOrMatchOnEmpty)
388  {
389  bool isEmpty = parentItem.OwnInventory.IsEmpty();
390  if (RequireEmpty) { return isEmpty; }
391  if (MatchOnEmpty && isEmpty) { return true; }
392  }
393  foreach (var container in parentItem.GetComponents<Items.Components.ItemContainer>())
394  {
395  if (TargetSlot > -1 && RequireOrMatchOnEmpty)
396  {
397  var itemInSlot = container.Inventory.GetItemAt(TargetSlot);
398  if (RequireEmpty) { return itemInSlot == null; }
399  if (MatchOnEmpty && itemInSlot == null) { return true; }
400  }
401  foreach (Item contained in container.Inventory.AllItems)
402  {
403  if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
404  if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; }
405  if (CheckContained(contained)) { return true; }
406  }
407  }
408  return false;
409  }
410 
411  public void Save(XElement element)
412  {
413  element.Add(
414  new XAttribute("items", JoinedIdentifiers),
415  new XAttribute("type", type.ToString()),
416  new XAttribute("characterinventoryslottype", CharacterInventorySlotType.ToString()),
417  new XAttribute("optional", IsOptional),
418  new XAttribute("ignoreineditor", IgnoreInEditor),
419  new XAttribute("excludebroken", ExcludeBroken),
420  new XAttribute("requireempty", RequireEmpty),
421  new XAttribute("excludefullcondition", ExcludeFullCondition),
422  new XAttribute("targetslot", TargetSlot),
423  new XAttribute("allowvariants", AllowVariants),
424  new XAttribute("rotation", Rotation),
425  new XAttribute("setactive", SetActive));
426 
427  if (Hide)
428  {
429  element.Add(new XAttribute(nameof(Hide), true));
430  }
431  if (ItemPos.HasValue)
432  {
433  element.Add(new XAttribute(nameof(ItemPos), ItemPos.Value));
434  }
435 
436  if (ExcludedIdentifiers.Count > 0)
437  {
438  element.Add(new XAttribute("excludedidentifiers", JoinedExcludedIdentifiers));
439  }
440 
441  if (!Msg.IsNullOrWhiteSpace()) { element.Add(new XAttribute("msg", MsgTag.IsEmpty ? Msg : MsgTag.Value)); }
442  }
443 
444  public static RelatedItem Load(ContentXElement element, bool returnEmpty, string parentDebugName)
445  {
446  RelatedItem ri = new RelatedItem(element, parentDebugName);
447  if (ri.Type == RelationType.Invalid) { return null; }
448  if (ri.Identifiers.None() && ri.ExcludedIdentifiers.None() && !returnEmpty) { return null; }
449  return ri;
450  }
451  }
452 }
IEnumerable< Item >?? HeldItems
Items the character has in their hand slots. Doesn't return nulls and only returns items held in both...
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
Vector2 GetAttributeVector2(string key, in Vector2 def)
bool GetAttributeBool(string key, bool def)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
int FindIndex(Item item)
Find the index of the first slot the item is contained in.
static readonly PrefabCollection< ItemPrefab > Prefabs
bool Contains(string subStr, StringComparison comparison=StringComparison.Ordinal)
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
readonly Identifier Identifier
Definition: Prefab.cs:34
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
static StatusEffect Load(ContentXElement element, string parentDebugName)