Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/CharacterInventory.cs
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Xml.Linq;
7 
8 namespace Barotrauma
9 {
10  [Flags]
11  public enum InvSlotType
12  {
13  None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, InnerClothes = 16, OuterClothes = 32, Headset = 64, Card = 128, Bag = 256, HealthInterface = 512
14  };
15 
16  partial class CharacterInventory : Inventory
17  {
24  public enum AccessLevel
25  {
26  Restricted,
27  Limited,
28  Allowed
29  }
30 
31  private readonly Character character;
32 
34  {
35  get;
36  private set;
37  }
38 
39  public static readonly List<InvSlotType> AnySlot = new List<InvSlotType>() { InvSlotType.Any };
40 
41  public static bool IsHandSlotType(InvSlotType s) => s.HasFlag(InvSlotType.LeftHand) || s.HasFlag(InvSlotType.RightHand);
42 
43  protected bool[] IsEquipped;
44 
48  public bool AccessibleWhenAlive
49  {
50  get;
51  private set;
52  }
53 
57  public bool AccessibleByOwner
58  {
59  get;
60  private set;
61  }
62 
63  private static string[] ParseSlotTypes(ContentXElement element)
64  {
65  string slotString = element.GetAttributeString("slots", null);
66  return slotString == null ? Array.Empty<string>() : slotString.Split(',');
67  }
68 
69  public CharacterInventory(ContentXElement element, Character character, bool spawnInitialItems)
70  : base(character, ParseSlotTypes(element).Length)
71  {
72  this.character = character;
73  IsEquipped = new bool[capacity];
75 
76  AccessibleWhenAlive = element.GetAttributeBool("accessiblewhenalive", character.Info != null);
77  AccessibleByOwner = element.GetAttributeBool("accessiblebyowner", AccessibleWhenAlive);
78 
79  string[] slotTypeNames = ParseSlotTypes(element);
80  System.Diagnostics.Debug.Assert(slotTypeNames.Length == capacity);
81 
82  for (int i = 0; i < capacity; i++)
83  {
84  InvSlotType parsedSlotType = InvSlotType.Any;
85  slotTypeNames[i] = slotTypeNames[i].Trim();
86  if (!Enum.TryParse(slotTypeNames[i], out parsedSlotType))
87  {
88  DebugConsole.ThrowError("Error in the inventory config of \"" + character.SpeciesName + "\" - " + slotTypeNames[i] + " is not a valid inventory slot type.",
89  contentPackage: element.ContentPackage);
90  }
91  SlotTypes[i] = parsedSlotType;
92  switch (SlotTypes[i])
93  {
94  case InvSlotType.LeftHand:
95  case InvSlotType.RightHand:
96  slots[i].HideIfEmpty = true;
97  break;
98  }
99  }
100 
101  InitProjSpecific(element);
102 
103  var itemElements = element.Elements().Where(e => e.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase));
104  int itemCount = itemElements.Count();
105  if (itemCount > capacity)
106  {
107  DebugConsole.ThrowError($"Character \"{character.SpeciesName}\" is configured to spawn with more items than it has inventory capacity for.");
108  }
109 #if DEBUG
110  else if (itemCount > capacity - 2 && !character.IsPet && capacity > 0)
111  {
112  DebugConsole.ThrowError(
113  $"Character \"{character.SpeciesName}\" is configured to spawn with so many items it will have less than 2 free inventory slots. " +
114  "This can cause issues with talents that spawn extra loot in monsters' inventories."
115  + " Consider increasing the inventory size.",
116  contentPackage: element.ContentPackage);
117  }
118 #endif
119 
120  if (!spawnInitialItems) { return; }
121 
122 #if CLIENT
123  //clients don't create items until the server says so
124  if (GameMain.Client != null) { return; }
125 #endif
126 
127  foreach (var subElement in itemElements)
128  {
129  string itemIdentifier = subElement.GetAttributeString("identifier", "");
130  if (!ItemPrefab.Prefabs.TryGet(itemIdentifier, out var itemPrefab))
131  {
132  DebugConsole.ThrowError("Error in character inventory \"" + character.SpeciesName + "\" - item \"" + itemIdentifier + "\" not found.",
133  contentPackage: element.ContentPackage);
134  continue;
135  }
136 
137  string slotString = subElement.GetAttributeString("slot", "None");
138  InvSlotType slot = Enum.TryParse(slotString, ignoreCase: true, out InvSlotType s) ? s : InvSlotType.None;
139 
140  bool forceToSlot = subElement.GetAttributeBool("forcetoslot", false);
141  int amount = subElement.GetAttributeInt("amount", 1);
142  for (int i = 0; i < amount; i++)
143  {
144  Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, this, ignoreLimbSlots: forceToSlot, slot: slot, onSpawned: (Item item) =>
145  {
146  if (item != null && item.ParentInventory != this)
147  {
148  string errorMsg = $"Failed to spawn the initial item \"{item.Prefab.Identifier}\" in the inventory of \"{character.SpeciesName}\".";
149  DebugConsole.ThrowError(errorMsg, contentPackage: element.ContentPackage);
150  GameAnalyticsManager.AddErrorEventOnce("CharacterInventory:FailedToSpawnInitialItem", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
151  }
152  else if (!character.Enabled)
153  {
154  foreach (var heldItem in character.HeldItems)
155  {
156  if (item.body != null) { item.body.Enabled = false; }
157  }
158  }
159  });
160  }
161  }
162  }
163 
164  partial void InitProjSpecific(XElement element);
165 
166  public Item FindEquippedItemByTag(Identifier tag)
167  {
168  if (tag.IsEmpty) { return null; }
169  for (int i = 0; i < slots.Length; i++)
170  {
171  if (SlotTypes[i] == InvSlotType.Any) { continue; }
172 
173  var item = slots[i].FirstOrDefault();
174  if (item != null && item.HasTag(tag)) { return item; }
175  }
176  return null;
177  }
178 
179  public int FindLimbSlot(InvSlotType limbSlot)
180  {
181  for (int i = 0; i < slots.Length; i++)
182  {
183  if (SlotTypes[i] == limbSlot) { return i; }
184  }
185  return -1;
186  }
187 
189  {
190  for (int i = 0; i < slots.Length; i++)
191  {
192  if (SlotTypes[i] == limbSlot) { return slots[i].FirstOrDefault(); }
193  }
194  return null;
195  }
196 
197 
198  public bool IsInLimbSlot(Item item, InvSlotType limbSlot)
199  {
200  if (limbSlot == (InvSlotType.LeftHand | InvSlotType.RightHand))
201  {
202  int rightHandSlot = FindLimbSlot(InvSlotType.RightHand);
203  int leftHandSlot = FindLimbSlot(InvSlotType.LeftHand);
204  if (rightHandSlot > -1 && slots[rightHandSlot].Contains(item) &&
205  leftHandSlot > -1 && slots[leftHandSlot].Contains(item))
206  {
207  return true;
208  }
209  }
210 
211  for (int i = 0; i < slots.Length; i++)
212  {
213  if (SlotTypes[i] == limbSlot && slots[i].Contains(item)) { return true; }
214  }
215  return false;
216  }
217 
218  public override bool CanBePutInSlot(Item item, int i, bool ignoreCondition = false)
219  {
220  return
221  base.CanBePutInSlot(item, i, ignoreCondition) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) &&
222  (SlotTypes[i] == InvSlotType.Any || slots[i].Items.Count < 1);
223  }
224 
225  public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
226  {
227  return
228  base.CanBePutInSlot(itemPrefab, i, condition, quality) &&
229  (SlotTypes[i] == InvSlotType.Any || slots[i].Items.Count < 1);
230  }
231 
232  public override void RemoveItem(Item item)
233  {
234  RemoveItem(item, tryEquipFromSameStack: false);
235  }
236 
237  public void RemoveItem(Item item, bool tryEquipFromSameStack)
238  {
239  if (!Contains(item)) { return; }
240 
241  bool wasEquipped = character.HasEquippedItem(item);
242  var indices = FindIndices(item);
243 
244  base.RemoveItem(item);
245 #if CLIENT
246  CreateSlots();
247  if (character == Character.Controlled)
248  {
249  character.SelectedItem?.GetComponent<CircuitBox>()?.OnViewUpdateProjSpecific();
250  }
251 #endif
252  CharacterHUD.RecreateHudTextsIfControlling(character);
253  //if the item was equipped and there are more items in the same stack, equip one of those items
254  if (tryEquipFromSameStack && wasEquipped)
255  {
256  int limbSlot = indices.Find(j => SlotTypes[j] != InvSlotType.Any);
257  foreach (int i in indices)
258  {
259  var itemInSameSlot = GetItemAt(i);
260  if (itemInSameSlot != null)
261  {
262  if (TryPutItem(itemInSameSlot, limbSlot, allowSwapping: false, allowCombine: false, character))
263  {
264 #if CLIENT
265  visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.412f);
266 #endif
267  }
268  break;
269  }
270  }
271  }
272  }
273 
274 
278  public bool TryPutItemWithAutoEquipCheck(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
279  {
280  // Does not auto-equip the item if specified and no suitable any slot found (for example handcuffs are not auto-equipped)
281  if (item.AllowedSlots.Contains(InvSlotType.Any))
282  {
283  var wearable = item.GetComponent<Wearable>();
284  if (wearable != null && !wearable.AutoEquipWhenFull && !IsAnySlotAvailable(item))
285  {
286  return false;
287  }
288  }
289 
290  if (allowedSlots != null && allowedSlots.Any() && !allowedSlots.Contains(InvSlotType.Any))
291  {
292  bool allSlotsTaken = true;
293  foreach (var allowedSlot in allowedSlots)
294  {
295  if (allowedSlot == (InvSlotType.RightHand | InvSlotType.LeftHand))
296  {
297  int rightHandSlot = FindLimbSlot(InvSlotType.RightHand);
298  int leftHandSlot = FindLimbSlot(InvSlotType.LeftHand);
299  if (rightHandSlot > -1 && slots[rightHandSlot].CanBePut(item) &&
300  leftHandSlot > -1 && slots[leftHandSlot].CanBePut(item))
301  {
302  allSlotsTaken = false;
303  break;
304  }
305  }
306  else
307  {
308  int slot = FindLimbSlot(allowedSlot);
309  if (slot > -1 && slots[slot].CanBePut(item))
310  {
311  allSlotsTaken = false;
312  break;
313  }
314  }
315 
316  }
317  if (allSlotsTaken)
318  {
319  int slot = FindLimbSlot(allowedSlots.First());
320  if (slot > -1 && slots[slot].Items.Any(it => it != item) && slots[slot].First().AllowDroppingOnSwapWith(item))
321  {
322  foreach (Item existingItem in slots[slot].Items.ToList())
323  {
324  existingItem.Drop(user);
325  if (existingItem.ParentInventory != null) { existingItem.ParentInventory.RemoveItem(existingItem); }
326  }
327  }
328  }
329  }
330 
331  return TryPutItem(item, user, allowedSlots, createNetworkEvent);
332  }
333 
337  public override bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false)
338  {
339  if (allowedSlots == null || !allowedSlots.Any()) { return false; }
340  if (item == null)
341  {
342 #if DEBUG
343  throw new Exception("item null");
344 #else
345  return false;
346 #endif
347  }
348  if (item.Removed)
349  {
350 #if DEBUG
351  throw new Exception("Tried to put a removed item (" + item.Name + ") in an inventory");
352 #else
353  DebugConsole.ThrowError("Tried to put a removed item (" + item.Name + ") in an inventory.\n" + Environment.StackTrace.CleanupStackTrace());
354  return false;
355 #endif
356  }
357 
358  if (item.GetComponent<Pickable>() == null || item.AllowedSlots.None()) { return false; }
359 
360  bool inSuitableSlot = false;
361  bool inWrongSlot = false;
362  int currentSlot = -1;
363  for (int i = 0; i < capacity; i++)
364  {
365  if (slots[i].Contains(item))
366  {
367  currentSlot = i;
368  if (allowedSlots.Any(a => a.HasFlag(SlotTypes[i])))
369  {
370  if ((SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) && !allowedSlots.Contains(SlotTypes[i]))
371  {
372  //allowed slot = InvSlotType.RightHand | InvSlotType.LeftHand
373  // -> make sure the item is in both hand slots
374  inSuitableSlot = IsInLimbSlot(item, InvSlotType.RightHand) && IsInLimbSlot(item, InvSlotType.LeftHand);
375  }
376  else
377  {
378  inSuitableSlot = true;
379  }
380  }
381  else if (!allowedSlots.Any(a => a.HasFlag(SlotTypes[i])))
382  {
383  inWrongSlot = true;
384  }
385  }
386  }
387 
388  //all good
389  if (inSuitableSlot && !inWrongSlot) { return true; }
390 
391  //try to place the item in a LimbSlot.Any slot if that's allowed
392  if (allowedSlots.Contains(InvSlotType.Any) && item.AllowedSlots.Contains(InvSlotType.Any))
393  {
394  int freeIndex = GetFreeAnySlot(item, inWrongSlot);
395  if (freeIndex > -1)
396  {
397  PutItem(item, freeIndex, user, true, createNetworkEvent);
398  item.Unequip(character);
399  return true;
400  }
401  }
402 
403  int placedInSlot = -1;
404  foreach (InvSlotType allowedSlot in allowedSlots)
405  {
406  if (allowedSlot.HasFlag(InvSlotType.RightHand) && character.AnimController.GetLimb(LimbType.RightHand) == null) { continue; }
407  if (allowedSlot.HasFlag(InvSlotType.LeftHand) && character.AnimController.GetLimb(LimbType.LeftHand) == null) { continue; }
408 
409  //check if all the required slots are free
410  bool free = true;
411  for (int i = 0; i < capacity; i++)
412  {
413  if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && slots[i].Items.Any(it => it != item))
414  {
415  if (!slots[i].First().AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(slots[i].FirstOrDefault(), character, new List<InvSlotType> { InvSlotType.Any }, true, ignoreCondition))
416  {
417  free = false;
418 #if CLIENT
419  for (int j = 0; j < capacity; j++)
420  {
421  if (visualSlots != null && slots[j] == slots[i]) { visualSlots[j].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); }
422  }
423 #endif
424  }
425  }
426  }
427 
428  if (!free) { continue; }
429 
430  for (int i = 0; i < capacity; i++)
431  {
432  if (allowedSlot.HasFlag(SlotTypes[i]) && item.GetComponents<Pickable>().Any(p => p.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i]))) && slots[i].Empty())
433  {
434  bool removeFromOtherSlots = item.ParentInventory != this;
435  if (placedInSlot == -1 && inWrongSlot)
436  {
437  if (!slots[i].HideIfEmpty || SlotTypes[currentSlot] != InvSlotType.Any) { removeFromOtherSlots = true; }
438  }
439 
440  PutItem(item, i, user, removeFromOtherSlots, createNetworkEvent);
441  item.Equip(character);
442  placedInSlot = i;
443  }
444  }
445  if (placedInSlot > -1) { break; }
446  }
447 
448  return placedInSlot > -1;
449  }
450 
451  public bool IsAnySlotAvailable(Item item) => GetFreeAnySlot(item, inWrongSlot: false) > -1;
452 
453  private int GetFreeAnySlot(Item item, bool inWrongSlot)
454  {
455  //attempt to stack first
456  for (int i = 0; i < capacity; i++)
457  {
458  if (SlotTypes[i] != InvSlotType.Any) { continue; }
459  if (!slots[i].Empty() && CanBePutInSlot(item, i))
460  {
461  return i;
462  }
463  }
464  for (int i = 0; i < capacity; i++)
465  {
466  if (SlotTypes[i] != InvSlotType.Any) { continue; }
467  if (slots[i].Contains(item))
468  {
469  return i;
470  }
471  }
472  for (int i = 0; i < capacity; i++)
473  {
474  if (SlotTypes[i] != InvSlotType.Any) { continue; }
475  if (CanBePutInSlot(item, i))
476  {
477  return i;
478  }
479  }
480  for (int i = 0; i < capacity; i++)
481  {
482  if (SlotTypes[i] != InvSlotType.Any) { continue; }
483  if (inWrongSlot)
484  {
485  //another item already in the slot
486  if (slots[i].Any() && slots[i].Items.Any(it => it != item)) { continue; }
487  }
488  else
489  {
490  if (!CanBePutInSlot(item, i)) { continue; }
491  }
492  return i;
493  }
494  return -1;
495  }
496 
497  public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false)
498  {
499  if (index < 0 || index >= slots.Length)
500  {
501  string errorMsg = "CharacterInventory.TryPutItem failed: index was out of range(" + index + ").\n" + Environment.StackTrace.CleanupStackTrace();
502  GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
503  return false;
504  }
505  //there's already an item in the slot
506  if (slots[index].Any())
507  {
508  if (slots[index].Contains(item)) { return false; }
509  return base.TryPutItem(item, index, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition);
510  }
511 
512  if (SlotTypes[index] == InvSlotType.Any)
513  {
514  if (!item.GetComponents<Pickable>().Any(p => p.AllowedSlots.Contains(InvSlotType.Any))) { return false; }
515  if (slots[index].Any()) { return slots[index].Contains(item); }
516  PutItem(item, index, user, true, createNetworkEvent);
517  return true;
518  }
519 
520  InvSlotType placeToSlots = InvSlotType.None;
521 
522  bool slotsFree = true;
523  foreach (Pickable pickable in item.GetComponents<Pickable>())
524  {
525  foreach (InvSlotType allowedSlot in pickable.AllowedSlots)
526  {
527  if (!allowedSlot.HasFlag(SlotTypes[index])) { continue; }
528  for (int i = 0; i < capacity; i++)
529  {
530  if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Any() && !slots[i].Contains(item))
531  {
532  slotsFree = false;
533  break;
534  }
535  placeToSlots = allowedSlot;
536  }
537  }
538  }
539 
540  if (!slotsFree) { return false; }
541 
542  return TryPutItem(item, user, new List<InvSlotType>() { placeToSlots }, createNetworkEvent, ignoreCondition);
543  }
544 
545  protected override void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true)
546  {
547  base.PutItem(item, i, user, removeItem, createNetworkEvent);
548 #if CLIENT
549  CreateSlots();
550  if (character == Character.Controlled)
551  {
552  HintManager.OnObtainedItem(character, item);
553  character.SelectedItem?.GetComponent<CircuitBox>()?.OnViewUpdateProjSpecific();
554  }
555 #endif
556  CharacterHUD.RecreateHudTextsIfControlling(character);
558  {
560  }
561  item.Equipper = user;
562  }
563 
564  protected override void CreateNetworkEvent(Range slotRange)
565  {
566  GameMain.NetworkMember?.CreateEntityEvent(character, new Character.InventoryStateEventData(slotRange));
567  }
568 
569  }
570 }
bool IsAnySlotAvailable(Item item)
override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality=null)
override 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
AccessLevel
How much access other characters have to the inventory? Restricted = Only accessible when character i...
bool AccessibleWhenAlive
Can the inventory be accessed when the character is still alive
static bool IsHandSlotType(InvSlotType s)
bool AccessibleByOwner
Can the inventory be accessed by the character itself when the character is still alive (only has an ...
override void PutItem(Item item, int i, Character user, bool removeItem=true, bool createNetworkEvent=true)
bool TryPutItemWithAutoEquipCheck(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true)
If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equippe...
override bool CanBePutInSlot(Item item, int i, bool ignoreCondition=false)
Can the item be put in the specified slot.
override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent=true, bool ignoreCondition=false)
CharacterInventory(ContentXElement element, Character character, bool spawnInitialItems)
string? GetAttributeString(string key, string? def)
ContentPackage? ContentPackage
IEnumerable< ContentXElement > Elements()
bool GetAttributeBool(string key, bool def)
static EntitySpawner Spawner
Definition: Entity.cs:31
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition=null, int? quality=null, Action< Item > onSpawned=null)
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static GameClient Client
Definition: GameMain.cs:188
readonly int capacity
Capacity, or the number of slots in the inventory.
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
IEnumerable< InvSlotType > AllowedSlots
void AssignCampaignInteractionType(CampaignMode.InteractionType interactionType, IEnumerable< Client > targetClients=null)
Character Equipper
Which character equipped this item? May not be the same character as the one who it's equipped on (yo...
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
CampaignMode.InteractionType CampaignInteractionType
static readonly PrefabCollection< ItemPrefab > Prefabs
List< InvSlotType > AllowedSlots
Definition: Pickable.cs:25