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 { return string.Join(",", Identifiers); }
152  set
153  {
154  if (value == null) return;
155 
156  Identifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet();
157  }
158  }
159 
163  public ImmutableHashSet<Identifier> Identifiers { get; private set; }
164 
166  {
167  get { return string.Join(",", ExcludedIdentifiers); }
168  set
169  {
170  if (value == null) return;
171 
172  ExcludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet();
173  }
174  }
175 
176  public bool MatchesItem(Item item)
177  {
178  if (item == null) { return false; }
179  if (ExcludedIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
180  foreach (var excludedIdentifier in ExcludedIdentifiers)
181  {
182  if (item.HasTag(excludedIdentifier)) { return false; }
183  }
184  if (item.ParentInventory?.Owner is Character character && CharacterInventorySlotType != InvSlotType.None)
185  {
186  if (!character.HasEquippedItem(item, CharacterInventorySlotType)) { return false; }
187  }
188  if (Identifiers.Contains(item.Prefab.Identifier)) { return true; }
189  foreach (var identifier in Identifiers)
190  {
191  if (item.HasTag(identifier)) { return true; }
192  }
193  if (AllowVariants && !item.Prefab.VariantOf.IsEmpty)
194  {
195  if (Identifiers.Contains(item.Prefab.VariantOf)) { return true; }
196  }
197  return false;
198  }
199  public bool MatchesItem(ItemPrefab itemPrefab)
200  {
201  if (itemPrefab == null) { return false; }
202  if (ExcludedIdentifiers.Contains(itemPrefab.Identifier)) { return false; }
203  foreach (var excludedIdentifier in ExcludedIdentifiers)
204  {
205  if (itemPrefab.Tags.Contains(excludedIdentifier)) { return false; }
206  }
207  if (Identifiers.Contains(itemPrefab.Identifier)) { return true; }
208  foreach (var identifier in Identifiers)
209  {
210  if (itemPrefab.Tags.Contains(identifier)) { return true; }
211  }
212  if (AllowVariants && !itemPrefab.VariantOf.IsEmpty)
213  {
214  if (Identifiers.Contains(itemPrefab.VariantOf)) { return true; }
215  }
216  return false;
217  }
218 
219  public RelatedItem(Identifier[] identifiers, Identifier[] excludedIdentifiers)
220  {
221  this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
222  this.ExcludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
223  }
224 
225  public RelatedItem(ContentXElement element, string parentDebugName)
226  {
227  Identifier[] identifiers;
228  if (element.GetAttribute("name") != null)
229  {
230  //backwards compatibility + a console warning
231  DebugConsole.ThrowError($"Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names.", contentPackage: element.ContentPackage);
232  Identifier[] itemNames = element.GetAttributeIdentifierArray("name", Array.Empty<Identifier>());
233  //attempt to convert to identifiers and tags
234  List<Identifier> convertedIdentifiers = new List<Identifier>();
235  foreach (Identifier itemName in itemNames)
236  {
237  var matchingItem = ItemPrefab.Prefabs.Find(me => me.Name == itemName.Value);
238  if (matchingItem != null)
239  {
240  convertedIdentifiers.Add(matchingItem.Identifier);
241  }
242  else
243  {
244  //no matching item found, this must be a tag
245  convertedIdentifiers.Add(itemName);
246  }
247  }
248  identifiers = convertedIdentifiers.ToArray();
249  }
250  else
251  {
252  identifiers = element.GetAttributeIdentifierArray("items", null) ?? element.GetAttributeIdentifierArray("item", null);
253  if (identifiers == null)
254  {
255  identifiers = element.GetAttributeIdentifierArray("identifiers", null) ?? element.GetAttributeIdentifierArray("tags", null);
256  if (identifiers == null)
257  {
258  identifiers = element.GetAttributeIdentifierArray("identifier", null) ?? element.GetAttributeIdentifierArray("tag", Array.Empty<Identifier>());
259  }
260  }
261  }
262  this.Identifiers = identifiers.ToImmutableHashSet();
263 
264  Identifier[] excludedIdentifiers = element.GetAttributeIdentifierArray("excludeditems", null) ?? element.GetAttributeIdentifierArray("excludeditem", null);
265  if (excludedIdentifiers == null)
266  {
267  excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifiers", null) ?? element.GetAttributeIdentifierArray("excludedtags", null);
268  if (excludedIdentifiers == null)
269  {
270  excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifier", null) ?? element.GetAttributeIdentifierArray("excludedtag", Array.Empty<Identifier>());
271  }
272  }
273  this.ExcludedIdentifiers = excludedIdentifiers.ToImmutableHashSet();
274 
275  ExcludeBroken = element.GetAttributeBool("excludebroken", true);
276  RequireEmpty = element.GetAttributeBool("requireempty", false);
277  ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false);
278  AllowVariants = element.GetAttributeBool("allowvariants", true);
279  Rotation = element.GetAttributeFloat("rotation", 0f);
280  SetActive = element.GetAttributeBool("setactive", false);
282 
283  CharacterInventorySlotType = element.GetAttributeEnum(nameof(CharacterInventorySlotType), InvSlotType.None);
284 
285  if (element.GetAttribute(nameof(Hide)) != null)
286  {
287  Hide = element.GetAttributeBool(nameof(Hide), false);
288  }
289  if (element.GetAttribute(nameof(ItemPos)) != null)
290  {
291  ItemPos = element.GetAttributeVector2(nameof(ItemPos), Vector2.Zero);
292  }
293  string typeStr = element.GetAttributeString("type", "");
294  if (string.IsNullOrEmpty(typeStr))
295  {
296  switch (element.Name.ToString().ToLowerInvariant())
297  {
298  case "containable":
299  typeStr = "Contained";
300  break;
301  case "suitablefertilizer":
302  case "suitableseed":
303  typeStr = "None";
304  break;
305  }
306  }
307  if (!Enum.TryParse(typeStr, true, out type))
308  {
309  DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type.", contentPackage: element.ContentPackage);
310  type = RelationType.Invalid;
311  }
312 
313  MsgTag = element.GetAttributeIdentifier("msg", Identifier.Empty);
314  LocalizedString msg = TextManager.Get(MsgTag);
315  if (!msg.Loaded)
316  {
317  Msg = MsgTag.Value;
318  }
319  else
320  {
321 #if CLIENT
322  foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
323  {
324  msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType));
325  }
326  Msg = msg;
327 #endif
328  }
329 
330  foreach (var subElement in element.Elements())
331  {
332  if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
333  StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
334  }
335 
336  IsOptional = element.GetAttributeBool("optional", false);
337  IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
338  MatchOnEmpty = element.GetAttributeBool("matchonempty", false);
339  TargetSlot = element.GetAttributeInt("targetslot", -1);
340 
341  }
342 
343  public bool CheckRequirements(Character character, Item parentItem)
344  {
345  switch (type)
346  {
347  case RelationType.Contained:
348  if (parentItem == null) { return false; }
349  return CheckContained(parentItem);
350  case RelationType.Container:
351  if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty || RequireEmpty; }
352  return CheckItem(parentItem.Container, this);
353  case RelationType.Equipped:
354  if (character == null) { return false; }
355  var heldItems = character.HeldItems;
356  if (RequireOrMatchOnEmpty && heldItems.None()) { return true; }
357  foreach (Item equippedItem in heldItems)
358  {
359  if (equippedItem == null) { continue; }
360  if (CheckItem(equippedItem, this))
361  {
362  if (RequireEmpty && equippedItem.Condition > 0) { return false; }
363  return true;
364  }
365  }
366  break;
367  case RelationType.Picked:
368  if (character == null) { return false; }
369  if (character.Inventory == null) { return MatchOnEmpty || RequireEmpty; }
370  var allItems = character.Inventory.AllItems;
371  if (RequireOrMatchOnEmpty && allItems.None()) { return true; }
372  foreach (Item pickedItem in allItems)
373  {
374  if (pickedItem == null) { continue; }
375  if (CheckItem(pickedItem, this))
376  {
377  if (RequireEmpty && pickedItem.Condition > 0) { return false; }
378  return true;
379  }
380  }
381  break;
382  default:
383  return true;
384  }
385 
386  static bool CheckItem(Item i, RelatedItem ri) => (!ri.ExcludeBroken || ri.RequireEmpty || i.Condition > 0.0f) && (!ri.ExcludeFullCondition || !i.IsFullCondition) && ri.MatchesItem(i);
387 
388  return false;
389  }
390 
391  private bool CheckContained(Item parentItem)
392  {
393  if (parentItem.OwnInventory == null) { return false; }
394 
395  if (TargetSlot == -1 && RequireOrMatchOnEmpty)
396  {
397  bool isEmpty = parentItem.OwnInventory.IsEmpty();
398  if (RequireEmpty) { return isEmpty; }
399  if (MatchOnEmpty && isEmpty) { return true; }
400  }
401  foreach (var container in parentItem.GetComponents<Items.Components.ItemContainer>())
402  {
403  if (TargetSlot > -1 && RequireOrMatchOnEmpty)
404  {
405  var itemInSlot = container.Inventory.GetItemAt(TargetSlot);
406  if (RequireEmpty) { return itemInSlot == null; }
407  if (MatchOnEmpty && itemInSlot == null) { return true; }
408  }
409  foreach (Item contained in container.Inventory.AllItems)
410  {
411  if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
412  if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; }
413  if (CheckContained(contained)) { return true; }
414  }
415  }
416  return false;
417  }
418 
419  public void Save(XElement element)
420  {
421  element.Add(
422  new XAttribute("items", JoinedIdentifiers),
423  new XAttribute("type", type.ToString()),
424  new XAttribute("characterinventoryslottype", CharacterInventorySlotType.ToString()),
425  new XAttribute("optional", IsOptional),
426  new XAttribute("ignoreineditor", IgnoreInEditor),
427  new XAttribute("excludebroken", ExcludeBroken),
428  new XAttribute("requireempty", RequireEmpty),
429  new XAttribute("excludefullcondition", ExcludeFullCondition),
430  new XAttribute("targetslot", TargetSlot),
431  new XAttribute("allowvariants", AllowVariants),
432  new XAttribute("rotation", Rotation),
433  new XAttribute("setactive", SetActive));
434 
435  if (Hide)
436  {
437  element.Add(new XAttribute(nameof(Hide), true));
438  }
439  if (ItemPos.HasValue)
440  {
441  element.Add(new XAttribute(nameof(ItemPos), ItemPos.Value));
442  }
443 
444  if (ExcludedIdentifiers.Count > 0)
445  {
446  element.Add(new XAttribute("excludedidentifiers", JoinedExcludedIdentifiers));
447  }
448 
449  if (!Msg.IsNullOrWhiteSpace()) { element.Add(new XAttribute("msg", MsgTag.IsEmpty ? Msg : MsgTag.Value)); }
450  }
451 
452  public static RelatedItem Load(ContentXElement element, bool returnEmpty, string parentDebugName)
453  {
454  RelatedItem ri = new RelatedItem(element, parentDebugName);
455  if (ri.Type == RelationType.Invalid) { return null; }
456  if (ri.Identifiers.None() && ri.ExcludedIdentifiers.None() && !returnEmpty) { return null; }
457  return ri;
458  }
459  }
460 }
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
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)