Server LuaCsForBarotrauma
ItemContainer.cs
3 using FarseerPhysics;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
8 using System.Globalization;
9 using System.Linq;
10 using System.Xml.Linq;
11 
13 {
15  {
16  readonly record struct ActiveContainedItem(Item Item, StatusEffect StatusEffect, bool ExcludeBroken, bool ExcludeFullCondition, bool BlameEquipperForDeath);
17 
18  readonly record struct ContainedItem(Item Item, bool Hide, Vector2? ItemPos, float Rotation);
19 
20  class SlotRestrictions
21  {
22  public int MaxStackSize;
23  public List<RelatedItem> ContainableItems;
24  public readonly bool AutoInject;
25 
26  public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems, bool autoInject)
27  {
28  MaxStackSize = maxStackSize;
29  ContainableItems = containableItems;
30  AutoInject = autoInject;
31  }
32 
33  public bool MatchesItem(Item item)
34  {
35  return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(item));
36  }
37 
38  public bool MatchesItem(ItemPrefab itemPrefab)
39  {
40  return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(itemPrefab));
41  }
42 
43  public bool MatchesItem(Identifier identifierOrTag)
44  {
45  return
46  ContainableItems == null || ContainableItems.Count == 0 ||
47  ContainableItems.Any(c => c.Identifiers.Contains(identifierOrTag) && !c.ExcludedIdentifiers.Contains(identifierOrTag));
48  }
49  }
50 
51  public readonly NamedEvent<ItemContainer> OnContainedItemsChanged = new NamedEvent<ItemContainer>();
52 
53  private bool alwaysContainedItemsSpawned;
54 
55  public readonly ItemInventory Inventory;
56 
57  private readonly List<ActiveContainedItem> activeContainedItems = new List<ActiveContainedItem>();
58 
59  private readonly List<ContainedItem> containedItems = new List<ContainedItem>();
60 
61  private List<ushort>[] itemIds;
62 
63  //how many items can be contained
64  private int capacity;
65  [Serialize(5, IsPropertySaveable.No, description: "How many items can be contained inside this item.")]
66  public int Capacity
67  {
68  get { return capacity; }
69  private set
70  {
71  capacity = Math.Max(value, 0);
72  MainContainerCapacity = value;
73  }
74  }
78  public int MainContainerCapacity { get; private set; }
79 
80  //how many items can be contained
81  private int maxStackSize;
82  [Serialize(64, IsPropertySaveable.No, description: "How many items can be stacked in one slot. Does not increase the maximum stack size of the items themselves, e.g. a stack of bullets could have a maximum size of 8 but the number of bullets in a specific weapon could be restricted to 6.")]
83  public int MaxStackSize
84  {
85  get { return maxStackSize; }
86  set { maxStackSize = Math.Max(value, 1); }
87  }
88 
89  private bool hideItems;
90  [Serialize(true, IsPropertySaveable.No, description: "Should the items contained inside this item be hidden."
91  + " If set to false, you should use the ItemPos and ItemInterval properties to determine where the items get rendered.")]
92  public bool HideItems
93  {
94  get { return hideItems; }
95  set
96  {
97  hideItems = value;
98  Drawable = !hideItems;
99  }
100  }
101 
102  [Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")]
103  public Vector2 ItemPos { get; set; }
104 
105  [Serialize("0.0,0.0", IsPropertySaveable.No, description: "The interval at which the contained items are spaced apart from each other (in pixels).")]
106  public Vector2 ItemInterval { get; set; }
107 
108  [Serialize(100, IsPropertySaveable.No, description: "How many items are placed in a row before starting a new row.")]
109  public int ItemsPerRow { get; set; }
110 
111  [Serialize(true, IsPropertySaveable.No, description: "Should the inventory of this item be visible when the item is selected.")]
112  public bool DrawInventory
113  {
114  get;
115  set;
116  }
117 
118  [Serialize(true, IsPropertySaveable.No, "Allow dragging and dropping items to deposit items into this inventory.")]
119  public bool AllowDragAndDrop
120  {
121  get;
122  set;
123  }
124 
125  [Serialize(true, IsPropertySaveable.No)]
127  {
128  get;
129  set;
130  }
131 
132  [Serialize(true, IsPropertySaveable.Yes, description: "When this item is equipped, and you 'quick use' (double click / equip button) another equippable item, should the game attempt to move that item inside this one?")]
133  public bool QuickUseMovesItemsInside { get; set; }
134 
135  [Serialize(false, IsPropertySaveable.No, description: "If set to true, interacting with this item will make the character interact with the contained item(s), automatically picking them up if they can be picked up.")]
137  {
138  get;
139  set;
140  }
141 
142  private ImmutableHashSet<Identifier> autoInteractWithContainedTags = ImmutableHashSet<Identifier>.Empty;
143  [Serialize("", IsPropertySaveable.Yes, description: $"Interacting with this container will autointeract with contained items that have one of these tags. Only valid if {nameof(AutoInteractWithContained)} is set to true.")]
145  {
146  get { return autoInteractWithContainedTags.ConvertToString(); }
147  set
148  {
149  autoInteractWithContainedTags = value.ToIdentifiers().ToImmutableHashSet();
150  }
151  }
152 
153  [Serialize(true, IsPropertySaveable.No, description: "Is the container accessible in general.")]
154  public bool AllowAccess { get; set; }
155 
156  [Serialize(false, IsPropertySaveable.No, description: "Is the container only accessible when it's broken. Doesn't apply to editors.")]
157  public bool AccessOnlyWhenBroken { get; set; }
158 
159  [Serialize(true, IsPropertySaveable.No, description: "Is the container accessible when dropped.")]
160  public bool AllowAccessWhenDropped { get; set; }
161 
162  [Serialize(5, IsPropertySaveable.No, description: "How many inventory slots the inventory has per row.")]
163  public int SlotsPerRow { get; set; }
164 
165  private readonly HashSet<Identifier> containableRestrictions = new HashSet<Identifier>();
166  [Editable, Serialize("", IsPropertySaveable.Yes, description: "Define items (by identifiers or tags) that bots should place inside this container. If empty, no restrictions are applied.")]
168  {
169  get { return string.Join(",", containableRestrictions); }
170  set
171  {
172  containableRestrictions.Clear();
173  if (!value.IsNullOrEmpty())
174  {
175  foreach (var str in value.Split(','))
176  {
177  if (str.IsNullOrWhiteSpace()) { continue; }
178  containableRestrictions.Add(str.ToIdentifier());
179  }
180  }
181  }
182  }
183 
184  [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should this container be automatically filled with items?")]
185  public bool AutoFill { get; set; }
186 
187  private float itemRotation;
188  [Serialize(0.0f, IsPropertySaveable.No, description: "The rotation in which the contained sprites are drawn (in degrees).")]
189  public float ItemRotation
190  {
191  get { return MathHelper.ToDegrees(itemRotation); }
192  set { itemRotation = MathHelper.ToRadians(value); }
193  }
194 
195  [Serialize("", IsPropertySaveable.No, description: "Specify an item for the container to spawn with.")]
196  public string SpawnWithId
197  {
198  get;
199  set;
200  }
201 
202  [Serialize(false, IsPropertySaveable.No, description: "Should the items configured using SpawnWithId spawn if this item is broken.")]
204  {
205  get;
206  set;
207  }
208 
209  [Serialize(false, IsPropertySaveable.No, description: "Should the items be injected into the user.")]
210  public bool AutoInject
211  {
212  get;
213  set;
214  }
215 
216  [Serialize(0.5f, IsPropertySaveable.No, description: "The health threshold that the user must reach in order to activate the autoinjection.")]
217  public float AutoInjectThreshold
218  {
219  get;
220  set;
221  }
222 
223  [Serialize(false, IsPropertySaveable.No)]
224  public bool RemoveContainedItemsOnDeconstruct { get; set; }
225 
229  public bool Locked
230  {
231  get { return Inventory.Locked; }
232  set { Inventory.Locked = value; }
233  }
234 
239  {
240  get => Inventory.AllItems.Count();
241  }
242 
247  {
248  get => Inventory.AllItems.Count(it => it.Condition > 0.0f);
249  }
250 
251  public int ExtraStackSize
252  {
253  get => Inventory.ExtraStackSize;
254  set => Inventory.ExtraStackSize = value;
255  }
256 
257  private readonly ImmutableArray<SlotRestrictions> slotRestrictions;
258 
259  readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
260 
261  private float prevContainedItemRefreshRotation;
262  private Vector2 prevContainedItemRefreshPosition;
263 
264  private float autoInjectCooldown = 1.0f;
265  const float AutoInjectInterval = 1.0f;
266 
267  private bool subContainersCanAutoInject;
268 
269 
270  public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
271  {
272  isRestrictionsDefined = containableRestrictions.Any();
273  if (slotRestrictions.None(s => s.MatchesItem(item))) { return false; }
274  if (!isRestrictionsDefined) { return true; }
275  return identifiersOrTags.Any(id => containableRestrictions.Any(r => r == id));
276  }
277 
278  public bool ShouldBeContained(Item item, out bool isRestrictionsDefined)
279  {
280  isRestrictionsDefined = containableRestrictions.Any();
281  if (slotRestrictions.None(s => s.MatchesItem(item))) { return false; }
282  if (!isRestrictionsDefined) { return true; }
283  return containableRestrictions.Any(id => item.Prefab.Identifier == id || item.HasTag(id));
284  }
285 
286  private ImmutableHashSet<Identifier> containableItemIdentifiers;
287  public ImmutableHashSet<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
288 
289  public List<RelatedItem> ContainableItems { get; }
290  public List<RelatedItem> AllSubContainableItems { get; }
291 
292  public readonly bool HasSubContainers;
293 
294  public bool hasSignalConnections;
295 
296  private string totalConditionValueString = "", totalConditionPercentageString = "", totalItemsString = "";
297  private float prevTotalConditionValue = 0, prevTotalConditionPercentage = 0; int prevTotalItems = 0;
298 
300  : base(item, element)
301  {
302  int totalCapacity = capacity;
303 
304  foreach (var subElement in element.Elements())
305  {
306  switch (subElement.Name.ToString().ToLowerInvariant())
307  {
308  case "containable":
309  RelatedItem containable = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: item.Name);
310  if (containable == null)
311  {
312  DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.",
313  contentPackage: element.ContentPackage);
314  continue;
315  }
316  ContainableItems ??= new List<RelatedItem>();
317  ContainableItems.Add(containable);
318  break;
319  case "subcontainer":
320  totalCapacity += subElement.GetAttributeInt("capacity", 1);
321  HasSubContainers = true;
322  break;
323  }
324  }
325  Inventory = new ItemInventory(item, this, totalCapacity, SlotsPerRow);
326 
327  // we have to assign this here because the fields are serialized before the inventory is created otherwise
328  ExtraStackSize = element.GetAttributeInt(nameof(ExtraStackSize), 0);
329 
330  List<SlotRestrictions> newSlotRestrictions = new List<SlotRestrictions>(totalCapacity);
331  for (int i = 0; i < capacity; i++)
332  {
333  newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems, autoInject: false));
334  }
335 
336  int subContainerIndex = capacity;
337  foreach (var subElement in element.Elements())
338  {
339  if (subElement.Name.ToString().ToLowerInvariant() != "subcontainer") { continue; }
340 
341  int subCapacity = subElement.GetAttributeInt("capacity", 1);
342  int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
343  bool autoInject = subElement.GetAttributeBool("autoinject", false);
344 
345  subContainersCanAutoInject |= autoInject;
346 
347  var subContainableItems = new List<RelatedItem>();
348  foreach (var subSubElement in subElement.Elements())
349  {
350  if (subSubElement.Name.ToString().ToLowerInvariant() != "containable") { continue; }
351 
352  RelatedItem containable = RelatedItem.Load(subSubElement, returnEmpty: false, parentDebugName: item.Name);
353  if (containable == null)
354  {
355  DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.",
356  contentPackage: element.ContentPackage);
357  continue;
358  }
359  subContainableItems.Add(containable);
360  AllSubContainableItems ??= new List<RelatedItem>();
361  AllSubContainableItems.Add(containable);
362  }
363 
364  for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
365  {
366  newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems, autoInject));
367  }
368  subContainerIndex += subCapacity;
369  }
370  capacity = totalCapacity;
371  slotRestrictions = newSlotRestrictions.ToImmutableArray();
372  System.Diagnostics.Debug.Assert(totalCapacity == slotRestrictions.Length);
373  InitProjSpecific(element);
374  }
375 
377  {
378  int containableIndex = 0;
379  foreach (var subElement in element.GetChildElements("containable"))
380  {
381  RelatedItem containable = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: item.Name);
382  if (containable == null)
383  {
384  DebugConsole.ThrowError("Error when loading containable restrictions for \"" + item.Name + "\" - containable with no identifiers.",
385  contentPackage: element.ContentPackage);
386  continue;
387  }
388  ContainableItems[containableIndex] = containable;
389  containableIndex++;
390  if (containableIndex >= ContainableItems.Count) { break; }
391  }
392  for (int i = 0; i < capacity; i++)
393  {
394  slotRestrictions[i].ContainableItems = ContainableItems;
395  }
396 #if CLIENT
397  if (element.GetChildElement("clearsubcontainerrestrictions") != null)
398  {
399  for (int i = capacity - MainContainerCapacity; i < capacity; i++)
400  {
401  slotRestrictions[i].MaxStackSize = MaxStackSize;
402  slotIcons[i] = null;
403  }
404  }
405 #endif
406  }
407 
408  public int GetMaxStackSize(int slotIndex)
409  {
410  if (slotIndex < 0 || slotIndex >= capacity)
411  {
412  return 0;
413  }
414  return slotRestrictions[slotIndex].MaxStackSize;
415  }
416 
417  partial void InitProjSpecific(ContentXElement element);
418 
419  public void OnItemContained(Item containedItem)
420  {
421  int index = Inventory.FindIndex(containedItem);
422  RelatedItem relatedItem = null;
423  if (index >= 0 && index < slotRestrictions.Length)
424  {
425  if (slotRestrictions[index].ContainableItems != null)
426  {
427  activeContainedItems.RemoveAll(i => i.Item == containedItem);
428  foreach (var containableItem in slotRestrictions[index].ContainableItems)
429  {
430  if (!containableItem.MatchesItem(containedItem)) { continue; }
431  //the 1st matching ContainableItem of the slot determines the hiding, position and rotation of the item
432  relatedItem ??= containableItem;
433  foreach (StatusEffect effect in containableItem.StatusEffects)
434  {
435  ActiveContainedItem activeContainedItem = new(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition, containableItem.BlameEquipperForDeath);
436  activeContainedItems.Add(activeContainedItem);
437 
438  if (!ShouldApplyEffects(activeContainedItem)) { continue; }
439  activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets);
440  }
441  }
442  }
443  }
444 
445  var containedItemInfo = new ContainedItem(containedItem,
446  Hide: relatedItem?.Hide ?? false,
447  ItemPos: relatedItem?.ItemPos,
448  Rotation: relatedItem?.Rotation ?? 0.0f);
449  containedItems.RemoveAll(d => d.Item == containedItem);
450 
451  if (hideItems)
452  {
453  //if the items aren't visible, the draw order doesn't matter and we can skip the sorting
454  containedItems.Add(containedItemInfo);
455  }
456  else
457  {
458  int containedIndex = 0;
459  while (containedIndex < containedItems.Count)
460  {
461  if (index <= Inventory.FindIndex(containedItems[containedIndex].Item))
462  {
463  break;
464  }
465  containedIndex++;
466  }
467  //sort drawables by their order in the inventory
468  containedItems.Insert(containedIndex, containedItemInfo);
469  }
470 
471  if (item.GetComponent<Planter>() != null)
472  {
473  GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":GardeningPlanted:" + containedItem.Prefab.Identifier);
474  }
475 
476  //no need to Update() if this item has no statuseffects and no physics body, and if there are no signal connections.
477  IsActive = hasSignalConnections || activeContainedItems.Count > 0 || Inventory.AllItems.Any(static it => it.body != null);
478 
479  if (IsActive && item.GetRootInventoryOwner() is Character owner &&
480  owner.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
481  {
482  // Set the contained items active if there's an item inserted inside the container. Enables e.g. the rifle flashlight when it's attached to the rifle (put inside of it).
483  SetContainedActive(true);
484  }
485  if (containedItem.FlippedX != item.FlippedX)
486  {
487  containedItem.FlipX(relativeToSub: false);
488  }
489  if (containedItem.FlippedY != item.FlippedY)
490  {
491  containedItem.FlipY(relativeToSub: false);
492  }
495  OnContainedItemsChanged.Invoke(this);
496  }
497 
498  public override void Move(Vector2 amount, bool ignoreContacts = false)
499  {
501  }
502 
503  public void OnItemRemoved(Item containedItem)
504  {
505  foreach (ActiveContainedItem activeContainedItem in activeContainedItems)
506  {
507  if (activeContainedItem.Item != containedItem || !ShouldApplyEffects(activeContainedItem)) { continue; }
508  activeContainedItem.StatusEffect.Apply(ActionType.OnRemoved, deltaTime: 1, item, targets);
509  }
510 
511  activeContainedItems.RemoveAll(i => i.Item == containedItem);
512  containedItems.RemoveAll(i => i.Item == containedItem);
514  //deactivate if the inventory is empty
515  IsActive = hasSignalConnections || activeContainedItems.Count > 0 || Inventory.AllItems.Any(static it => it.body != null);
517  OnContainedItemsChanged.Invoke(this);
518  }
519 
520  public bool BlameEquipperForDeath()
521  {
522  return activeContainedItems.Any(c => c.BlameEquipperForDeath);
523  }
524 
525  public bool CanBeContained(Item item)
526  {
527  if (!AllowAccessWhenDropped && this.item.body is { Enabled: true }) { return false; }
528  return slotRestrictions.Any(s => s.MatchesItem(item));
529  }
530 
531  public bool CanBeContained(Item item, int index)
532  {
533  if (index < 0 || index >= capacity) { return false; }
534  if (!AllowAccessWhenDropped && this.item.body is { Enabled: true }) { return false; }
535  return slotRestrictions[index].MatchesItem(item);
536  }
537 
538  public bool CanBeContained(ItemPrefab itemPrefab)
539  {
540  return slotRestrictions.Any(s => s.MatchesItem(itemPrefab));
541  }
542 
543  public bool CanBeContained(ItemPrefab itemPrefab, int index)
544  {
545  if (index < 0 || index >= capacity) { return false; }
546  return slotRestrictions[index].MatchesItem(itemPrefab);
547  }
548 
550  {
551  if (item == null) { return false; }
552  foreach (var containedItem in Inventory.AllItems)
553  {
554  if (containedItem.Prefab.Identifier == item.Prefab.Identifier)
555  {
556  return true;
557  }
558  }
559  return false;
560  }
561 
562  public override void FlipX(bool relativeToSub)
563  {
564  base.FlipX(relativeToSub);
565  if (HideItems) { return; }
566  if (item.body == null) { return; }
567  foreach (Item containedItem in Inventory.AllItems)
568  {
569  if (containedItem.body != null && containedItem.body.Enabled && containedItem.body.Dir != item.body.Dir)
570  {
571  containedItem.FlipX(relativeToSub);
572  }
573  }
574  }
575 
576  public override void Update(float deltaTime, Camera cam)
577  {
578  if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
579  {
580  SpawnAlwaysContainedItems();
581  alwaysContainedItemsSpawned = true;
582  }
583 
585  {
586  float totalConditionValue = 0, totalConditionPercentage = 0; int totalItems = 0;
587  foreach (var item in Inventory.AllItems)
588  {
589  if (!MathUtils.NearlyEqual(item.Condition, 0))
590  {
591  totalConditionValue += item.Condition;
592  totalConditionPercentage += item.ConditionPercentage;
593  totalItems++;
594  }
595  }
596 
597  if (!MathUtils.NearlyEqual(totalConditionValue, prevTotalConditionValue))
598  {
599  totalConditionValueString = ((int)totalConditionValue).ToString(CultureInfo.InvariantCulture);
600  prevTotalConditionValue = totalConditionValue;
601  }
602 
603  if (!MathUtils.NearlyEqual(totalConditionPercentage, prevTotalConditionPercentage))
604  {
605  totalConditionPercentageString = ((int)totalConditionPercentage).ToString(CultureInfo.InvariantCulture);
606  prevTotalConditionPercentage = totalConditionPercentage;
607  }
608 
609  if (totalItems != prevTotalItems)
610  {
611  totalItemsString = totalItems.ToString(CultureInfo.InvariantCulture);
612  prevTotalItems = totalItems;
613  }
614 
615  item.SendSignal(totalConditionValueString, "contained_conditions");
616  item.SendSignal(totalConditionPercentageString, "contained_conditions_percentage");
617  item.SendSignal(totalItemsString, "contained_items");
618  }
619 
620  if (item.ParentInventory is CharacterInventory ownerInventory)
621  {
622  SetContainedItemPositionsIfNeeded();
623 
624  if (AutoInject || subContainersCanAutoInject)
625  {
626  //normally autoinjection should delete the (medical) item, so it only gets applied once
627  //but in multiplayer clients aren't allowed to remove items themselves, so they may be able to trigger this dozens of times
628  //before the server notifies them of the item being removed, leading to a sharp lag spike.
629  //this can also happen with mods, if there's a way to autoinject something that doesn't get removed On Use.
630  //so let's ensure the item is only applied once per second at most.
631 
632  autoInjectCooldown -= deltaTime;
633  if (autoInjectCooldown <= 0.0f &&
634  ownerInventory?.Owner is Character ownerCharacter &&
635  ownerCharacter.HealthPercentage / 100f <= AutoInjectThreshold &&
636  ownerCharacter.HasEquippedItem(item))
637  {
638  if (AutoInject)
639  {
640  Inventory.AllItemsMod.ForEach(i => Inject(i));
641  }
642  else
643  {
644  for (int i = 0; i < slotRestrictions.Length; i++)
645  {
646  if (slotRestrictions[i].AutoInject)
647  {
648  Inventory.GetItemsAt(i).ForEachMod(i => Inject(i));
649  }
650  }
651  }
652  void Inject(Item item)
653  {
654  item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter, useTarget: ownerCharacter);
655  item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter, useTarget: ownerCharacter);
656  item.GetComponent<GeneticMaterial>()?.Equip(ownerCharacter);
657  }
658  autoInjectCooldown = AutoInjectInterval;
659  }
660  }
661 
662  }
663  else if (item.body != null && item.body.Enabled)
664  {
665  if (item.body.FarseerBody.Awake)
666  {
667  SetContainedItemPositionsIfNeeded();
668  }
669  }
670  else if (!hasSignalConnections && activeContainedItems.Count == 0)
671  {
672  IsActive = false;
673  return;
674  }
675 
676  foreach (ActiveContainedItem activeContainedItem in activeContainedItems)
677  {
678  if (!ShouldApplyEffects(activeContainedItem)) continue;
679 
680  StatusEffect effect = activeContainedItem.StatusEffect;
681  effect.Apply(ActionType.OnActive, deltaTime, item, targets);
682  effect.Apply(ActionType.OnContaining, deltaTime, item, targets);
683  if (item.GetComponent<Wearable>() is Wearable { IsActive: true })
684  {
685  effect.Apply(ActionType.OnWearing, deltaTime, item, targets);
686  }
687  }
688  }
689 
690  private bool ShouldApplyEffects(ActiveContainedItem activeContainedItem)
691  {
692  Item contained = activeContainedItem.Item;
693  if (activeContainedItem.ExcludeBroken && contained.Condition <= 0) { return false; }
694  if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { return false; }
695  StatusEffect effect = activeContainedItem.StatusEffect;
696 
697  targets.Clear();
698  if (effect.HasTargetType(StatusEffect.TargetType.This))
699  {
700  targets.AddRange(item.AllPropertyObjects);
701  }
702  if (effect.HasTargetType(StatusEffect.TargetType.Contained))
703  {
704  targets.AddRange(contained.AllPropertyObjects);
705  }
706  if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
707  {
708  targets.Add(character);
709  }
710  if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
711  {
712  effect.AddNearbyTargets(item.WorldPosition, targets);
713  }
714  return true;
715  }
716 
720  private void SetContainedItemPositionsIfNeeded()
721  {
722  if (Vector2.DistanceSquared(prevContainedItemRefreshPosition, item.Position) > 10.0f ||
723  Math.Abs(prevContainedItemRefreshRotation - item.body?.Rotation ?? item.RotationRad) > 0.01f)
724  {
726  prevContainedItemRefreshPosition = item.Position;
727  prevContainedItemRefreshRotation = item.body?.Rotation ?? item.RotationRad;
728  }
729  }
730 
731  public override void UpdateBroken(float deltaTime, Camera cam)
732  {
733  //update when the item is broken too to get OnContaining effects to execute and contained item positions to update
734  if (IsActive)
735  {
736  Update(deltaTime, cam);
737  }
738  }
739 
740  public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
741  {
742  return IsAccessible() && base.HasRequiredItems(character, addMessage, msg);
743  }
744 
749  public bool IsAccessible()
750  {
751  if (!AllowAccess) { return false; }
753  {
754  if (Screen.Selected is { IsEditor: true })
755  {
756  // AccessOnlyWhenBroken doesn't apply to editors.
757  return true;
758  }
759  return item.Condition <= 0;
760  }
761  return true;
762  }
763 
764  public override bool Select(Character character)
765  {
766  if (item.Container != null) { return false; }
767  if (!IsAccessible()) { return false; }
768  if (AutoInteractWithContained && character.SelectedItem == null && Screen.Selected is not { IsEditor: true })
769  {
770  foreach (Item contained in Inventory.AllItems)
771  {
772  if (CanAutoInteractWithContained(contained) && contained.TryInteract(character))
773  {
774  character.FocusedItem = contained;
775  return false;
776  }
777  }
778  }
779  var abilityItem = new AbilityItemContainer(item);
780  character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem);
781 
782  if (item.ParentInventory?.Owner == character)
783  {
784  //can't select ItemContainers in the character's inventory (the inventory is drawn by hovering the cursor over the inventory slot, not as a GUIFrame)
785  return false;
786  }
787  else
788  {
789  return base.Select(character);
790  }
791  }
792 
793  public override bool Pick(Character picker)
794  {
795  if (!IsAccessible()) { return false; }
796  if (AutoInteractWithContained && Screen.Selected is not { IsEditor: true })
797  {
798  foreach (Item contained in Inventory.AllItems)
799  {
800  if (CanAutoInteractWithContained(contained) && contained.TryInteract(picker))
801  {
802  picker.FocusedItem = contained;
803  return true;
804  }
805  }
806  }
807 
808  IsActive = true;
809 
810  return picker != null;
811  }
812 
813  public override bool Combine(Item item, Character user)
814  {
815  if (!AllowDragAndDrop && user != null) { return false; }
816  if (!slotRestrictions.Any(s => s.MatchesItem(item))) { return false; }
817  if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
818  //genetic materials use special logic for combining, don't allow doing it by placing them inside each other here
819  if (this.Item.GetComponent<GeneticMaterial>() != null) { return false; }
820 
821  if (Inventory.TryPutItem(item, user))
822  {
823  IsActive = true;
824  if (hideItems && item.body != null) { item.body.Enabled = false; }
825 
826  return true;
827  }
828 
829  return false;
830  }
831 
832  public override void Drop(Character dropper, bool setTransform = true)
833  {
834  IsActive = true;
835  SetContainedActive(false);
836  }
837 
838  public override void Equip(Character character)
839  {
840  IsActive = true;
841  if (character != null && character.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
842  {
843  SetContainedActive(true);
844  }
845  else
846  {
847  SetContainedActive(false);
848  }
849  }
850 
851  private bool CanAutoInteractWithContained(Item containedItem)
852  {
853  return AutoInteractWithContained && autoInteractWithContainedTags.Any(t => containedItem.HasTag(t));
854  }
855 
856  private void SetContainedActive(bool active)
857  {
858  if ((ContainableItems == null || !ContainableItems.Any(c => c.SetActive)) &&
859  (AllSubContainableItems == null || !AllSubContainableItems.Any(c => c.SetActive)))
860  {
861  return;
862  }
863  foreach (Item containedItem in Inventory.AllItems)
864  {
865  RelatedItem containableItem = FindContainableItem(containedItem);
866  if (containableItem != null && containableItem.SetActive)
867  {
868  foreach (var ic in containedItem.Components)
869  {
870  ic.IsActive = active;
871  }
872  if (containedItem.body != null)
873  {
874  containedItem.body.Enabled = active;
875  if (active)
876  {
877  containedItem.body.PhysEnabled = false;
878  }
879  }
880  }
881  }
882  if (active)
883  {
884  FlipX(false);
885  }
886  }
887 
888  private RelatedItem FindContainableItem(Item item)
889  {
890  int index = Inventory.FindIndex(item);
891  if (index == -1 ) { return null; }
892  return slotRestrictions[index]?.ContainableItems?.FirstOrDefault(ci => ci.MatchesItem(item));
893  }
894 
898  public int? FindSuitableSubContainerIndex(Identifier itemTagOrIdentifier)
899  {
900  for (int i = 0; i < slotRestrictions.Length; i++)
901  {
902  if (slotRestrictions[i].MatchesItem(itemTagOrIdentifier)) { return i; }
903  }
904  return null;
905  }
906 
907  public override void ReceiveSignal(Signal signal, Connection connection)
908  {
909  switch (connection.Name)
910  {
911  case "activate":
912  case "use":
913  case "trigger_in":
914  if (signal.value != "0")
915  {
916  item.Use(1.0f, user: signal.sender);
917  }
918  break;
919  }
920  }
921 
922 #warning There's some code duplication here and in DrawContainedItems() method, but it's not straightforward to get rid of it, because of slightly different logic and the usage of draw positions vs. positions etc. Should probably be splitted into smaller methods.
924  {
925  var rootBody = item.RootContainer?.body ?? item.body;
926 
927  Vector2 transformedItemPos = GetContainedPosition(
928  drawPosition: false,
929  out Vector2 transformedItemIntervalHorizontal,
930  out Vector2 transformedItemIntervalVertical,
931  out bool flippedX,
932  out bool flippedY);
933 
934  int i = 0;
935  Vector2 currentItemPos = transformedItemPos;
936  foreach (ContainedItem contained in containedItems)
937  {
938  Vector2 itemPos = currentItemPos;
939  if (contained.ItemPos.HasValue)
940  {
941  Vector2 pos = contained.ItemPos.Value;
942  if (item.body != null)
943  {
944  Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
945  pos.X *= rootBody.Dir;
946  itemPos = Vector2.Transform(pos, transform) + item.body.Position;
947  }
948  else
949  {
950  itemPos = pos;
951  // This code is aped based on above. Not tested.
952  if (flippedX)
953  {
954  itemPos.X = -itemPos.X;
955  itemPos.X += item.Rect.Width;
956  }
957  if (flippedY)
958  {
959  itemPos.Y = -itemPos.Y;
960  itemPos.Y -= item.Rect.Height;
961  }
962  itemPos += new Vector2(item.Rect.X, item.Rect.Y);
963  if (Math.Abs(item.RotationRad) > 0.01f)
964  {
965  Matrix transform = Matrix.CreateRotationZ(item.RotationRad);
966  itemPos = Vector2.Transform(itemPos - item.Position, transform) + item.Position;
967  }
968  }
969  }
970 
971  if (contained.Item.body != null)
972  {
973  try
974  {
975  Vector2 simPos = ConvertUnits.ToSimUnits(itemPos);
976  float rotation = itemRotation;
977  if (contained.Rotation != 0)
978  {
979  rotation = MathHelper.ToRadians(contained.Rotation);
980  }
981  if (item.body != null)
982  {
983  rotation *= rootBody.Dir;
984  rotation += item.body.Rotation;
985  }
986  else
987  {
988  rotation += -item.RotationRad;
989  }
990  contained.Item.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
991  contained.Item.body.SetPrevTransform(contained.Item.body.SimPosition, contained.Item.body.Rotation);
992  contained.Item.body.UpdateDrawPosition();
993  }
994  catch (Exception e)
995  {
996  DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace());
997  GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Item.Name,
998  GameAnalyticsManager.ErrorSeverity.Error,
999  "SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace());
1000  }
1001  contained.Item.body.Submarine = item.Submarine;
1002  }
1003 
1004  contained.Item.Rect =
1005  new Rectangle(
1006  (int)(itemPos.X - contained.Item.Rect.Width / 2.0f),
1007  (int)(itemPos.Y + contained.Item.Rect.Height / 2.0f),
1008  contained.Item.Rect.Width, contained.Item.Rect.Height);
1009 
1010  contained.Item.Submarine = item.Submarine;
1011  contained.Item.CurrentHull = item.CurrentHull;
1012  contained.Item.SetContainedItemPositions();
1013 
1014  i++;
1015  if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
1016  {
1017  //interval set on both axes -> use a grid layout
1018  currentItemPos += transformedItemIntervalHorizontal;
1019  if (i % ItemsPerRow == 0)
1020  {
1021  currentItemPos = transformedItemPos;
1022  currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow);
1023  }
1024  }
1025  else
1026  {
1027  currentItemPos += transformedItemIntervalHorizontal + transformedItemIntervalVertical;
1028  }
1029  }
1030  }
1031 
1032  private Vector2 GetContainedPosition(bool drawPosition,
1033  out Vector2 transformedItemIntervalHorizontal, out Vector2 transformedItemIntervalVertical,
1034  out bool flippedX, out bool flippedY)
1035  {
1036  Vector2 transformedItemPos = ItemPos * item.Scale;
1037  Vector2 transformedItemInterval = ItemInterval * item.Scale;
1038  transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
1039  transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
1040 
1041  flippedX = item.RootContainer?.FlippedX ?? item.FlippedX;
1042  flippedY = item.RootContainer?.FlippedY ?? item.FlippedY;
1043  var rootBody = item.RootContainer?.body ?? item.body;
1044  bool bodyFlipped = rootBody is { Dir: -1 };
1045 
1046  if (ItemPos == Vector2.Zero && ItemInterval == Vector2.Zero && !drawPosition)
1047  {
1048  transformedItemPos = item.Position;
1049  }
1050  else
1051  {
1052  if (item.body == null)
1053  {
1054  if (flippedX)
1055  {
1056  transformedItemPos.X = -transformedItemPos.X;
1057  transformedItemPos.X += item.Rect.Width;
1058  transformedItemInterval.X = -transformedItemInterval.X;
1059  transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
1060  }
1061  if (flippedY)
1062  {
1063  transformedItemPos.Y = -transformedItemPos.Y;
1064  transformedItemPos.Y -= item.Rect.Height;
1065  transformedItemInterval.Y = -transformedItemInterval.Y;
1066  transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
1067  }
1068  transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
1069  if (drawPosition)
1070  {
1071  if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
1072  }
1073  if (Math.Abs(item.RotationRad) > 0.01f)
1074  {
1075  Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
1076  transformedItemPos =
1077  drawPosition ?
1078  Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition :
1079  Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position;
1080  transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
1081  transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
1082  }
1083  }
1084  else
1085  {
1086  Matrix transform = Matrix.CreateRotationZ(drawPosition ? item.body.DrawRotation : item.body.Rotation);
1087  if (bodyFlipped)
1088  {
1089  transformedItemPos.X = -transformedItemPos.X;
1090  transformedItemInterval.X = -transformedItemInterval.X;
1091  transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
1092  }
1093 
1094  transformedItemPos = Vector2.Transform(transformedItemPos, transform);
1095  transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
1096  transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
1097  transformedItemPos += drawPosition ? item.body.DrawPosition : item.body.Position;
1098  }
1099  }
1100  return transformedItemPos;
1101  }
1102 
1103  public override void OnItemLoaded()
1104  {
1106  containableItemIdentifiers = slotRestrictions.SelectMany(s => s.ContainableItems?.SelectMany(ri => ri.Identifiers) ?? Enumerable.Empty<Identifier>()).ToImmutableHashSet();
1107  hasSignalConnections = item.Connections?.Any(c => c.Name is "contained_conditions" or "contained_conditions_percentage" or "contained_items") ?? false;
1108  if (item.Submarine == null || !item.Submarine.Loading)
1109  {
1110  SpawnAlwaysContainedItems();
1111  }
1112  }
1113 
1114  public override void OnMapLoaded()
1115  {
1116  if (itemIds != null)
1117  {
1118  for (ushort i = 0; i < itemIds.Length; i++)
1119  {
1120  if (i >= Inventory.Capacity)
1121  {
1122  //legacy support: before item stacking was implemented, revolver for example had a separate slot for each bullet
1123  //now there's just one, try to put the extra items where they fit (= stack them)
1124  Inventory.TryPutItem(item, user: null, createNetworkEvent: false);
1125  continue;
1126  }
1127  foreach (ushort id in itemIds[i])
1128  {
1129  if (!(Entity.FindEntityByID(id) is Item item)) { continue; }
1130  Inventory.TryPutItem(item, i, false, false, null, createNetworkEvent: false, ignoreCondition: true);
1131  }
1132  }
1133  itemIds = null;
1134  }
1135 
1136  //outpost and ruins are loaded in multiple stages (each module is loaded separately)
1137  //spawning items at this point during the generation will cause ID overlaps with the entities in the modules loaded afterwards
1138  //so let's not spawn them at this point, but in the 1st Update()
1140  {
1141  if (SpawnWithId.Length > 0)
1142  {
1143  IsActive = true;
1144  }
1145  }
1146  else
1147  {
1148  SpawnAlwaysContainedItems();
1149  }
1150 
1152  }
1153 
1154  private void SpawnAlwaysContainedItems()
1155  {
1156  if (SpawnWithId.Length > 0 && (item.Condition > 0.0f || SpawnWithIdWhenBroken))
1157  {
1158  string[] splitIds = SpawnWithId.Split(',');
1159  foreach (string id in splitIds)
1160  {
1161  ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == id);
1162  if (prefab != null && Inventory != null && Inventory.CanBePut(prefab))
1163  {
1164  bool isEditor = false;
1165 #if CLIENT
1166  isEditor = Screen.Selected == GameMain.SubEditorScreen;
1167 #endif
1168  if (!isEditor && (Entity.Spawner == null || Entity.Spawner.Removed) && GameMain.NetworkMember == null)
1169  {
1170  var spawnedItem = new Item(prefab, Vector2.Zero, null);
1171  Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots, createNetworkEvent: false);
1172  alwaysContainedItemsSpawned = true;
1173  }
1174  else
1175  {
1176  IsActive = true;
1177  Entity.Spawner?.AddItemToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false, onSpawned: (Item item) => { alwaysContainedItemsSpawned = true; });
1178  }
1179  }
1180  }
1181  }
1182  }
1183 
1184  protected override void ShallowRemoveComponentSpecific()
1185  {
1186  }
1187 
1188  protected override void RemoveComponentSpecific()
1189  {
1190  base.RemoveComponentSpecific();
1191 #if CLIENT
1192  inventoryTopSprite?.Remove();
1193  inventoryBackSprite?.Remove();
1194  inventoryBottomSprite?.Remove();
1195  ContainedStateIndicator?.Remove();
1196 
1197  if (SubEditorScreen.IsSubEditor())
1198  {
1200  return;
1201  }
1202 #endif
1203  //if we're unloading the whole sub, no need to drop anything (everything's going to be removed anyway)
1204  if (!Submarine.Unloading)
1205  {
1206  Inventory.AllItemsMod.ForEach(it => it.Drop(null));
1207  }
1208  }
1209 
1210  public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
1211  {
1212  base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
1213 
1214  string containedString = componentElement.GetAttributeString("contained", "");
1215  string[] itemIdStrings = containedString.Split(',');
1216  itemIds = new List<ushort>[itemIdStrings.Length];
1217  for (int i = 0; i < itemIdStrings.Length; i++)
1218  {
1219  itemIds[i] ??= new List<ushort>();
1220  foreach (string idStr in itemIdStrings[i].Split(';'))
1221  {
1222  if (!int.TryParse(idStr, out int id)) { continue; }
1223  itemIds[i].Add(idRemap.GetOffsetId(id));
1224  }
1225  }
1226  ExtraStackSize = componentElement.GetAttributeInt(nameof(ExtraStackSize), 0);
1227  }
1228 
1229  public override XElement Save(XElement parentElement)
1230  {
1231  XElement componentElement = base.Save(parentElement);
1232  string[] itemIdStrings = new string[Inventory.Capacity];
1233  for (int i = 0; i < Inventory.Capacity; i++)
1234  {
1235  var items = Inventory.GetItemsAt(i);
1236  itemIdStrings[i] = string.Join(';', items.Select(it => it.ID.ToString()));
1237  }
1238  componentElement.Add(new XAttribute("contained", string.Join(',', itemIdStrings)));
1239  componentElement.Add(new XAttribute(nameof(ExtraStackSize), ExtraStackSize));
1240  return componentElement;
1241  }
1242  }
1243 
1245  {
1247  {
1248  Item = item;
1249  }
1250  public Item Item { get; set; }
1251  }
1252 }
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel=CharacterInventory.AccessLevel.Limited)
Item? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
readonly? ContentPackage ContentPackage
Definition: ContentPath.cs:21
string? GetAttributeString(string key, string? def)
int GetAttributeInt(string key, int def)
static EntitySpawner Spawner
Definition: Entity.cs:31
virtual Vector2 DrawPosition
Definition: Entity.cs:51
Submarine Submarine
Definition: Entity.cs:53
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition=null, int? quality=null, Action< Item > onSpawned=null)
static NetworkMember NetworkMember
Definition: GameMain.cs:41
static GameSession GameSession
Definition: GameMain.cs:45
static readonly Screen SubEditorScreen
Definition: GameMain.cs:62
GameModePreset Preset
Definition: GameMode.cs:43
readonly Identifier Identifier
ushort GetOffsetId(XElement element)
Definition: IdRemap.cs:86
virtual bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
void DeleteAllItems()
Deletes all items inside the inventory (and also recursively all items inside the items)
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.
IEnumerable< Item > GetItemsAt(int index)
Get all the item stored in the specified inventory slot. Can return more than one item if the slot co...
IEnumerable< Item > AllItemsMod
All items contained in the inventory. Allows modifying the contents of the inventory while being enum...
bool CanBePut(Item item)
Can the item be put in the inventory (i.e. is there a suitable free slot or a stack the item can be p...
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...
IReadOnlyList< ISerializableEntity > AllPropertyObjects
void Use(float deltaTime, Character user=null, Limb targetLimb=null, Entity useTarget=null, Character userForOnUsedEvent=null)
override void FlipX(bool relativeToSub)
Flip the entity horizontally
override void FlipY(bool relativeToSub)
Flip the entity vertically
bool TryInteract(Character user, bool ignoreRequiredItems=false, bool forceSelectKey=false, bool forceUseKey=false)
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id=Entity.NullEntityID, bool callOnItemLoaded=true)
void SendSignal(string signal, string connectionName)
static readonly PrefabCollection< ItemPrefab > Prefabs
Definition: ItemPrefab.cs:483
The base class for components holding the different functionalities of the item
override void Update(float deltaTime, Camera cam)
bool CanBeContained(ItemPrefab itemPrefab, int index)
void OnItemRemoved(Item containedItem)
int ContainedItemCount
Can be used by status effects
override void Equip(Character character)
bool ShouldBeContained(Item item, out bool isRestrictionsDefined)
readonly NamedEvent< ItemContainer > OnContainedItemsChanged
ItemContainer(Item item, ContentXElement element)
bool IsAccessible()
Is the container currently accessible. Use this method for checking the accessibility logic,...
override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
override void Move(Vector2 amount, bool ignoreContacts=false)
ImmutableHashSet< Identifier > ContainableItemIdentifiers
void OnItemContained(Item containedItem)
override void OnMapLoaded()
Called when all items have been loaded. Use to initialize connections between items.
bool CanBeContained(ItemPrefab itemPrefab)
bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
override bool Combine(Item item, Character user)
override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
bool CanBeContained(Item item, int index)
int ContainedNonBrokenItemCount
Can be used by status effects
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override void FlipX(bool relativeToSub)
override XElement Save(XElement parentElement)
override bool Pick(Character picker)
a Character has picked the item
override bool Select(Character character)
override void ReceiveSignal(Signal signal, Connection connection)
int? FindSuitableSubContainerIndex(Identifier itemTagOrIdentifier)
Returns the index of the first slot whose restrictions match the specified tag or identifier
void ReloadContainableRestrictions(ContentXElement element)
override void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
int MainContainerCapacity
The capacity of the main container without taking the sub containers into account....
bool Locked
Can be used by status effects to lock the inventory
override void UpdateBroken(float deltaTime, Camera cam)
virtual Rectangle Rect
Definition: MapEntity.cs:99
readonly Identifier Identifier
Definition: Prefab.cs:34
static Screen Selected
Definition: Screen.cs:5
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
Definition: StatusEffect.cs:72
bool HasTargetType(TargetType targetType)
void AddNearbyTargets(Vector2 worldPosition, List< ISerializableEntity > targets)
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
static void RecreateHudTextsIfFocused(params Item[] items)
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26
AbilityEffectType
Definition: Enums.cs:140