Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Inventory.cs
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
11  partial class Inventory
12  {
13  public const int MaxPossibleStackSize = (1 << 6) - 1; //the max value that will fit in 6 bits, i.e 63
14 
15  public const int MaxItemsPerNetworkEvent = 128;
16 
17  public class ItemSlot
18  {
19  private readonly List<Item> items = new List<Item>(MaxPossibleStackSize);
20 
21  public bool HideIfEmpty;
22 
23  public IReadOnlyList<Item> Items => items;
24 
25  private readonly Inventory inventory;
26 
27  public ItemSlot(Inventory inventory)
28  {
29  this.inventory = inventory;
30  }
31 
32  public bool CanBePut(Item item, bool ignoreCondition = false)
33  {
34  if (item == null) { return false; }
35  if (items.Count > 0)
36  {
37  if (!ignoreCondition)
38  {
39  if (item.IsFullCondition)
40  {
41  if (items.Any(it => !it.IsFullCondition)) { return false; }
42  }
43  else if (MathUtils.NearlyEqual(item.Condition, 0.0f))
44  {
45  if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) { return false; }
46  }
47  else
48  {
49  return false;
50  }
51  }
52  if (items[0].Quality != item.Quality) { return false; }
53  if (items[0].Prefab.Identifier != item.Prefab.Identifier || items.Count + 1 > item.Prefab.GetMaxStackSize(inventory))
54  {
55  return false;
56  }
57  }
58  return true;
59  }
60 
61  public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null)
62  {
63  if (itemPrefab == null) { return false; }
64  if (items.Count > 0)
65  {
66  if (condition.HasValue)
67  {
68  if (MathUtils.NearlyEqual(condition.Value, 0.0f))
69  {
70  if (items.Any(it => it.Condition > 0.0f)) { return false; }
71  }
72  else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.Health))
73  {
74  if (items.Any(it => !it.IsFullCondition)) { return false; }
75  }
76  else
77  {
78  return false;
79  }
80  }
81  else
82  {
83  if (items.Any(it => !it.IsFullCondition)) { return false; }
84  }
85 
86  if (quality.HasValue)
87  {
88  if (items[0].Quality != quality.Value) { return false; }
89  }
90 
91  if (items[0].Prefab.Identifier != itemPrefab.Identifier ||
92  items.Count + 1 > itemPrefab.GetMaxStackSize(inventory))
93  {
94  return false;
95  }
96  }
97  return true;
98  }
99 
101  public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null, bool ignoreItemsInSlot = false)
102  {
103  if (itemPrefab == null) { return 0; }
104  maxStackSize ??= itemPrefab.GetMaxStackSize(inventory);
105  if (items.Count > 0 && !ignoreItemsInSlot)
106  {
107  if (condition.HasValue)
108  {
109  if (MathUtils.NearlyEqual(condition.Value, 0.0f))
110  {
111  if (items.Any(it => it.Condition > 0.0f)) { return 0; }
112  }
113  else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.Health))
114  {
115  if (items.Any(it => !it.IsFullCondition)) { return 0; }
116  }
117  else
118  {
119  return 0;
120  }
121  }
122  else
123  {
124  if (items.Any(it => !it.IsFullCondition)) { return 0; }
125  }
126  if (items[0].Prefab.Identifier != itemPrefab.Identifier) { return 0; }
127  return maxStackSize.Value - items.Count;
128  }
129  else
130  {
131  return maxStackSize.Value;
132  }
133  }
134 
135  public void Add(Item item)
136  {
137  if (item == null)
138  {
139  throw new InvalidOperationException("Tried to add a null item to an inventory slot.");
140  }
141  if (items.Count > 0)
142  {
143  if (items[0].Prefab.Identifier != item.Prefab.Identifier)
144  {
145  throw new InvalidOperationException("Tried to stack different types of items.");
146  }
147  else if (items.Count + 1 > item.Prefab.GetMaxStackSize(inventory))
148  {
149  throw new InvalidOperationException($"Tried to add an item to a full inventory slot (stack already full, x{items.Count} {items.First().Prefab.Identifier}).");
150  }
151  }
152  if (items.Contains(item)) { return; }
153 
154  //keep lowest-condition items at the top of the stack
155  int index = 0;
156  for (int i = 0; i < items.Count; i++)
157  {
158  if (items[i].Condition > item.Condition)
159  {
160  break;
161  }
162  index++;
163  }
164  items.Insert(index, item);
165  }
166 
170  public Item RemoveItem()
171  {
172  if (items.Count == 0) { return null; }
173 
174  var item = items[0];
175  items.RemoveAt(0);
176  return item;
177  }
178 
179  public void RemoveItem(Item item)
180  {
181  items.Remove(item);
182  }
183 
187  public void RemoveAllItems()
188  {
189  items.Clear();
190  }
191 
192  public void RemoveWhere(Func<Item, bool> predicate)
193  {
194  items.RemoveAll(it => predicate(it));
195  }
196 
197  public bool Any()
198  {
199  return items.Count > 0;
200  }
201 
202  public bool Empty()
203  {
204  return items.Count == 0;
205  }
206 
207  public Item First()
208  {
209  return items[0];
210  }
211 
213  {
214  return items.FirstOrDefault();
215  }
216 
218  {
219  return items.LastOrDefault();
220  }
221 
222  public bool Contains(Item item)
223  {
224  return items.Contains(item);
225  }
226 
227  }
228 
229  public readonly Entity Owner;
230 
234  protected readonly int capacity;
235  protected readonly ItemSlot[] slots;
236 
237  public bool Locked;
238 
239  protected float syncItemsDelay;
240 
241 
242  private int extraStackSize;
243  public int ExtraStackSize
244  {
245  get => extraStackSize;
246  set => extraStackSize = MathHelper.Max(value, 0);
247  }
248 
252  public virtual IEnumerable<Item> AllItems
253  {
254  get
255  {
256  return GetAllItems(checkForDuplicates: this is CharacterInventory);
257  }
258  }
259 
260  private readonly List<Item> allItemsList = new List<Item>();
264  public IEnumerable<Item> AllItemsMod
265  {
266  get
267  {
268  allItemsList.Clear();
269  allItemsList.AddRange(AllItems);
270  return allItemsList;
271  }
272  }
273 
274  public int Capacity
275  {
276  get { return capacity; }
277  }
278 
279  public static bool IsDragAndDropGiveAllowed
280  {
281  get
282  {
283  // allowed for single player
284  if (GameMain.NetworkMember == null)
285  {
286  return true;
287  }
288 
289  // controlled by server setting in multiplayer
290  return GameMain.NetworkMember.ServerSettings.AllowDragAndDropGive;
291  }
292  }
293 
294  public int EmptySlotCount => slots.Count(i => !i.Empty());
295 
296  public bool AllowSwappingContainedItems = true;
297 
298  public Inventory(Entity owner, int capacity, int slotsPerRow = 5)
299  {
300  this.capacity = capacity;
301 
302  this.Owner = owner;
303 
304  slots = new ItemSlot[capacity];
305  for (int i = 0; i < capacity; i++)
306  {
307  slots[i] = new ItemSlot(this);
308  }
309 
310 #if CLIENT
311  this.slotsPerRow = slotsPerRow;
312 
313  if (DraggableIndicator == null)
314  {
315  DraggableIndicator = GUIStyle.GetComponentStyle("GUIDragIndicator").GetDefaultSprite();
316 
317  slotHotkeySprite = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(258, 7, 120, 120), null, 0);
318 
319  EquippedIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(550, 137, 87, 16), new Vector2(0.5f, 0.5f), 0);
320  EquippedHoverIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(550, 157, 87, 16), new Vector2(0.5f, 0.5f), 0);
321  EquippedClickedIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(550, 177, 87, 16), new Vector2(0.5f, 0.5f), 0);
322 
323  UnequippedIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(550, 197, 87, 16), new Vector2(0.5f, 0.5f), 0);
324  UnequippedHoverIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(550, 217, 87, 16), new Vector2(0.5f, 0.5f), 0);
325  UnequippedClickedIndicator = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(550, 237, 87, 16), new Vector2(0.5f, 0.5f), 0);
326  }
327 #endif
328  }
329 
330  protected IEnumerable<Item> GetAllItems(bool checkForDuplicates)
331  {
332  for (int i = 0; i < capacity; i++)
333  {
334  foreach (var item in slots[i].Items)
335  {
336  if (item == null)
337  {
338 #if DEBUG
339  DebugConsole.ThrowError($"Null item in inventory {Owner.ToString() ?? "null"}, slot {i}!");
340 #endif
341  continue;
342  }
343 
344  if (checkForDuplicates)
345  {
346  bool duplicateFound = false;
347  for (int j = 0; j < i; j++)
348  {
349  if (slots[j].Items.Contains(item))
350  {
351  duplicateFound = true;
352  break;
353  }
354  }
355  if (!duplicateFound) { yield return item; }
356  }
357  else
358  {
359  yield return item;
360  }
361  }
362  }
363  }
364 
365  private void NotifyItemComponentsOfChange()
366  {
367 #if CLIENT
368  if (Owner is Character character && character == Character.Controlled)
369  {
370  character.SelectedItem?.GetComponent<CircuitBox>()?.OnViewUpdateProjSpecific();
371  }
372 #endif
373 
374  if (Owner is not Item it) { return; }
375 
376  foreach (var c in it.Components)
377  {
378  c.OnInventoryChanged();
379  }
380 
381  it.ParentInventory?.NotifyItemComponentsOfChange();
382  }
383 
387  public bool Contains(Item item)
388  {
389  return slots.Any(i => i.Contains(item));
390  }
391 
396  {
397  foreach (var itemSlot in slots)
398  {
399  var item = itemSlot.FirstOrDefault();
400  if (item != null) { return item; }
401  }
402  return null;
403  }
404 
409  {
410  for (int i = slots.Length - 1; i >= 0; i--)
411  {
412  var item = slots[i].LastOrDefault();
413  if (item != null) { return item; }
414  }
415  return null;
416  }
417 
421  public Item GetItemAt(int index)
422  {
423  if (index < 0 || index >= slots.Length) { return null; }
424  return slots[index].FirstOrDefault();
425  }
426 
430  public IEnumerable<Item> GetItemsAt(int index)
431  {
432  if (index < 0 || index >= slots.Length) { return Enumerable.Empty<Item>(); }
433  return slots[index].Items;
434  }
435 
436  public int GetItemStackSlotIndex(Item item, int index)
437  {
438  if (index < 0 || index >= slots.Length) { return -1; }
439 
440  return slots[index].Items.IndexOf(item);
441  }
442 
446  public int FindIndex(Item item)
447  {
448  for (int i = 0; i < capacity; i++)
449  {
450  if (slots[i].Contains(item)) { return i; }
451  }
452  return -1;
453  }
454 
458  public List<int> FindIndices(Item item)
459  {
460  List<int> indices = new List<int>();
461  for (int i = 0; i < capacity; i++)
462  {
463  if (slots[i].Contains(item)) { indices.Add(i); }
464  }
465  return indices;
466  }
467 
471  public virtual bool ItemOwnsSelf(Item item)
472  {
473  if (Owner == null) { return false; }
474  if (!(Owner is Item)) { return false; }
475  Item ownerItem = Owner as Item;
476  if (ownerItem == item) { return true; }
477  if (ownerItem.ParentInventory == null) { return false; }
478  return ownerItem.ParentInventory.ItemOwnsSelf(item);
479  }
480 
481  public virtual int FindAllowedSlot(Item item, bool ignoreCondition = false)
482  {
483  if (ItemOwnsSelf(item)) { return -1; }
484 
485  for (int i = 0; i < capacity; i++)
486  {
487  //item is already in the inventory!
488  if (slots[i].Contains(item)) { return -1; }
489  }
490 
491  for (int i = 0; i < capacity; i++)
492  {
493  if (slots[i].CanBePut(item, ignoreCondition)) { return i; }
494  }
495 
496  return -1;
497  }
498 
502  public bool CanBePut(Item item)
503  {
504  for (int i = 0; i < capacity; i++)
505  {
506  if (CanBePutInSlot(item, i)) { return true; }
507  }
508  return false;
509  }
510 
514  public virtual bool CanBePutInSlot(Item item, int i, bool ignoreCondition = false)
515  {
516  if (ItemOwnsSelf(item)) { return false; }
517  if (i < 0 || i >= slots.Length) { return false; }
518  return slots[i].CanBePut(item, ignoreCondition);
519  }
520 
521  public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null)
522  {
523  for (int i = 0; i < capacity; i++)
524  {
525  if (CanBePutInSlot(itemPrefab, i, condition, quality)) { return true; }
526  }
527  return false;
528  }
529 
530  public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null, int? quality = null)
531  {
532  if (i < 0 || i >= slots.Length) { return false; }
533  return slots[i].CanBePut(itemPrefab, condition);
534  }
535 
536  public int HowManyCanBePut(ItemPrefab itemPrefab, float? condition = null)
537  {
538  int count = 0;
539  for (int i = 0; i < capacity; i++)
540  {
541  count += HowManyCanBePut(itemPrefab, i, condition);
542  }
543  return count;
544  }
545 
546  public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot = false)
547  {
548  if (i < 0 || i >= slots.Length) { return 0; }
549  return slots[i].HowManyCanBePut(itemPrefab, condition: condition, ignoreItemsInSlot: ignoreItemsInSlot);
550  }
551 
555  public virtual bool TryPutItem(Item item, Character user, IEnumerable<InvSlotType> allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false)
556  {
557  int slot = FindAllowedSlot(item, ignoreCondition);
558  if (slot < 0) { return false; }
559 
560  PutItem(item, slot, user, true, createNetworkEvent);
561  return true;
562  }
563 
564  public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false)
565  {
566  if (i < 0 || i >= slots.Length)
567  {
568  string thisItemStr = item?.Prefab.Identifier.Value ?? "null";
569  string ownerStr = "null";
570  if (Owner is Item ownerItem)
571  {
572  ownerStr = ownerItem.Prefab.Identifier.Value;
573  }
574  else if (Owner is Character ownerCharacter)
575  {
576  ownerStr = ownerCharacter.SpeciesName.Value;
577  }
578  string errorMsg = $"Inventory.TryPutItem failed: index was out of range (item: {thisItemStr}, inventory: {ownerStr}).";
579  GameAnalyticsManager.AddErrorEventOnce("Inventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
580 #if DEBUG
581  DebugConsole.ThrowError(errorMsg);
582 #endif
583  return false;
584  }
585 
586  if (Owner == null) return false;
587  //there's already an item in the slot
588  if (slots[i].Any() && allowCombine)
589  {
590  if (slots[i].First().Combine(item, user))
591  {
592  //item in the slot removed as a result of combining -> put this item in the now free slot
593  if (!slots[i].Any())
594  {
595  return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition);
596  }
597  return true;
598  }
599  }
600  if (CanBePutInSlot(item, i, ignoreCondition))
601  {
602  PutItem(item, i, user, true, createNetworkEvent);
603  return true;
604  }
605  else if (slots[i].Any() && item.ParentInventory != null && allowSwapping)
606  {
607  var itemInSlot = slots[i].First();
608  if (itemInSlot.OwnInventory != null &&
609  !itemInSlot.OwnInventory.Contains(item) &&
610  itemInSlot.GetComponent<ItemContainer>()?.GetMaxStackSize(0) == 1 &&
611  itemInSlot.OwnInventory.TrySwapping(0, item, user, createNetworkEvent, swapWholeStack: false))
612  {
613  return true;
614  }
615  return
616  TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) ||
617  TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false);
618  }
619  else
620  {
621 #if CLIENT
622  if (visualSlots != null && createNetworkEvent) { visualSlots[i].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); }
623 #endif
624  return false;
625  }
626  }
627 
628  protected virtual void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true)
629  {
630  if (i < 0 || i >= slots.Length)
631  {
632  string errorMsg = "Inventory.PutItem failed: index was out of range(" + i + ").\n" + Environment.StackTrace.CleanupStackTrace();
633  GameAnalyticsManager.AddErrorEventOnce("Inventory.PutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
634  return;
635  }
636 
637  var should = GameMain.LuaCs.Hook.Call<bool?>("inventoryPutItem", this, item, user, i, removeItem);
638 
639  if (should != null && should.Value)
640  return;
641 
642  if (Owner == null) { return; }
643 
644  Inventory prevInventory = item.ParentInventory;
645  Inventory prevOwnerInventory = item.FindParentInventory(inv => inv is CharacterInventory);
646 
647  if (createNetworkEvent)
648  {
650  //also delay syncing the inventory the item was inside
651  if (prevInventory != null && prevInventory != this) { prevInventory.syncItemsDelay = 1.0f; }
652  }
653 
654  if (removeItem)
655  {
656  item.Drop(user, setTransform: false, createNetworkEvent: createNetworkEvent && GameMain.NetworkMember is { IsServer: true });
657  item.ParentInventory?.RemoveItem(item);
658  }
659 
660  slots[i].Add(item);
661  item.ParentInventory = this;
662 
663 #if CLIENT
664  if (visualSlots != null)
665  {
666  visualSlots[i].ShowBorderHighlight(Color.White, 0.1f, 0.4f);
667  if (selectedSlot?.Inventory == this) { selectedSlot.ForceTooltipRefresh = true; }
668  }
669 #endif
670  CharacterHUD.RecreateHudTextsIfControlling(user);
671 
672  if (item.body != null)
673  {
674  item.body.Enabled = false;
675  item.body.BodyType = FarseerPhysics.BodyType.Dynamic;
676  item.SetTransform(item.SimPosition, rotation: 0.0f, findNewHull: false);
677  //update to refresh the interpolated draw rotation and position (update doesn't run on disabled bodies)
678  item.body.UpdateDrawPosition(interpolate: false);
679  }
680 
681 #if SERVER
682  if (prevOwnerInventory is CharacterInventory characterInventory && characterInventory != this && Owner == user)
683  {
684  var client = GameMain.Server?.ConnectedClients?.Find(cl => cl.Character == user);
685  GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(characterInventory, client, item);
686  }
687 #endif
688  if (this is CharacterInventory)
689  {
690  if (prevInventory != this && prevOwnerInventory != this)
691  {
692  HumanAIController.ItemTaken(item, user);
693  }
694  }
695  else
696  {
697  if (item.FindParentInventory(inv => inv is CharacterInventory) is CharacterInventory currentInventory)
698  {
699  if (currentInventory != prevInventory)
700  {
701  HumanAIController.ItemTaken(item, user);
702  }
703  }
704  }
705 
706  NotifyItemComponentsOfChange();
707  }
708 
709  public bool IsEmpty()
710  {
711  for (int i = 0; i < capacity; i++)
712  {
713  if (slots[i].Any()) { return false; }
714  }
715 
716  return true;
717  }
718 
723  public virtual bool IsFull(bool takeStacksIntoAccount = false)
724  {
725  if (takeStacksIntoAccount)
726  {
727  for (int i = 0; i < capacity; i++)
728  {
729  if (!slots[i].Any()) { return false; }
730  var item = slots[i].FirstOrDefault();
731  if (slots[i].Items.Count < item.Prefab.GetMaxStackSize(this)) { return false; }
732  }
733  }
734  else
735  {
736  for (int i = 0; i < capacity; i++)
737  {
738  if (!slots[i].Any()) { return false; }
739  }
740  }
741 
742  return true;
743  }
744 
745  protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent, bool swapWholeStack)
746  {
747  if (item?.ParentInventory == null || !slots[index].Any()) { return false; }
748  if (slots[index].Items.Any(it => !it.IsInteractable(user))) { return false; }
749  if (!AllowSwappingContainedItems) { return false; }
750 
751  var should = GameMain.LuaCs.Hook.Call<bool?>("inventoryItemSwap", this, item, user, index, swapWholeStack);
752 
753  if (should != null)
754  return should.Value;
755 
756  //swap to InvSlotType.Any if possible
757  Inventory otherInventory = item.ParentInventory;
758  bool otherIsEquipped = false;
759  int otherIndex = -1;
760  for (int i = 0; i < otherInventory.slots.Length; i++)
761  {
762  if (!otherInventory.slots[i].Contains(item)) { continue; }
763  if (otherInventory is CharacterInventory characterInventory)
764  {
765  if (characterInventory.SlotTypes[i] == InvSlotType.Any)
766  {
767  otherIndex = i;
768  break;
769  }
770  else
771  {
772  otherIsEquipped = true;
773  }
774  }
775  }
776  if (otherIndex == -1)
777  {
778  otherIndex = otherInventory.FindIndex(item);
779  if (otherIndex == -1)
780  {
781  DebugConsole.ThrowError("Something went wrong when trying to swap items between inventory slots: couldn't find the source item from it's inventory.\n" + Environment.StackTrace.CleanupStackTrace());
782  return false;
783  }
784  }
785 
786  List<Item> existingItems = new List<Item>();
787  if (swapWholeStack)
788  {
789  existingItems.AddRange(slots[index].Items);
790  for (int j = 0; j < capacity; j++)
791  {
792  if (existingItems.Any(existingItem => slots[j].Contains(existingItem))) { slots[j].RemoveAllItems(); }
793  }
794  }
795  else
796  {
797  existingItems.Add(slots[index].FirstOrDefault());
798  for (int j = 0; j < capacity; j++)
799  {
800  if (existingItems.Any(existingItem => slots[j].Contains(existingItem))) { slots[j].RemoveItem(existingItems.First()); }
801  }
802  }
803 
804  List<Item> stackedItems = new List<Item>();
805  if (swapWholeStack)
806  {
807  for (int j = 0; j < otherInventory.capacity; j++)
808  {
809  if (otherInventory.slots[j].Contains(item))
810  {
811  stackedItems.AddRange(otherInventory.slots[j].Items);
812  otherInventory.slots[j].RemoveAllItems();
813  }
814  }
815  }
816  else
817  {
818  stackedItems.Add(item);
819  otherInventory.slots[otherIndex].RemoveItem(item);
820  }
821 
822 
823  bool swapSuccessful = false;
824  if (otherIsEquipped)
825  {
826  swapSuccessful =
827  stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent))
828  &&
829  (existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
830  existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.AnySlot, createNetworkEvent));
831  }
832  else
833  {
834  swapSuccessful =
835  (existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
836  existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.AnySlot, createNetworkEvent))
837  &&
838  stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
839 
840  if (!swapSuccessful && existingItems.Count == 1 && existingItems[0].AllowDroppingOnSwapWith(item))
841  {
842  if (!(existingItems[0].Container?.ParentInventory is CharacterInventory characterInv) ||
843  !characterInv.TryPutItem(existingItems[0], user, new List<InvSlotType>() { InvSlotType.Any }))
844  {
845  existingItems[0].Drop(user, createNetworkEvent);
846  }
847  swapSuccessful = stackedItems.Distinct().Any(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
848 #if CLIENT
849  if (swapSuccessful)
850  {
851  SoundPlayer.PlayUISound(GUISoundType.DropItem);
852  if (otherInventory.visualSlots != null && otherIndex > -1)
853  {
854  otherInventory.visualSlots[otherIndex].ShowBorderHighlight(Color.Transparent, 0.1f, 0.1f);
855  }
856  }
857 #endif
858  }
859  }
860 
861  //if the item in the slot can be moved to the slot of the moved item
862  if (swapSuccessful)
863  {
864  System.Diagnostics.Debug.Assert(slots[index].Contains(item), "Something when wrong when swapping items, item is not present in the inventory.");
865  System.Diagnostics.Debug.Assert(!existingItems.Any(it => !it.Prefab.AllowDroppingOnSwap && !otherInventory.Contains(it)), "Something when wrong when swapping items, item is not present in the other inventory.");
866 #if CLIENT
867  if (visualSlots != null)
868  {
869  for (int j = 0; j < capacity; j++)
870  {
871  if (slots[j].Contains(item)) { visualSlots[j].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.9f); }
872  }
873  }
874  if (otherInventory.visualSlots != null)
875  {
876  for (int j = 0; j < otherInventory.capacity; j++)
877  {
878  if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.visualSlots[j].ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.9f); }
879  }
880  }
881 #endif
882  return true;
883  }
884  else //swapping the items failed -> move them back to where they were
885  {
886  if (swapWholeStack)
887  {
888  foreach (Item stackedItem in stackedItems)
889  {
890  for (int j = 0; j < capacity; j++)
891  {
892  if (slots[j].Contains(stackedItem)) { slots[j].RemoveItem(stackedItem); };
893  }
894  }
895  foreach (Item existingItem in existingItems)
896  {
897  for (int j = 0; j < otherInventory.capacity; j++)
898  {
899  if (otherInventory.slots[j].Contains(existingItem)) { otherInventory.slots[j].RemoveItem(existingItem); }
900  }
901  }
902  }
903  else
904  {
905  for (int j = 0; j < capacity; j++)
906  {
907  if (slots[j].Contains(item))
908  {
909  slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
910  }
911  }
912  for (int j = 0; j < otherInventory.capacity; j++)
913  {
914  if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault()))
915  {
916  otherInventory.slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
917  }
918  }
919  }
920 
921  if (otherIsEquipped)
922  {
923  TryPutAndForce(existingItems, this, index);
924  TryPutAndForce(stackedItems, otherInventory, otherIndex);
925  }
926  else
927  {
928  TryPutAndForce(stackedItems, otherInventory, otherIndex);
929  TryPutAndForce(existingItems, this, index);
930  }
931 
932  void TryPutAndForce(IEnumerable<Item> items, Inventory inventory, int slotIndex)
933  {
934  foreach (var item in items)
935  {
936  if (!inventory.TryPutItem(item, slotIndex, false, false, user, createNetworkEvent, ignoreCondition: true) &&
937  !inventory.GetItemsAt(slotIndex).Contains(item))
938  {
939  inventory.ForceToSlot(item, slotIndex);
940  }
941  }
942  }
943 
944  if (createNetworkEvent)
945  {
947  otherInventory.CreateNetworkEvent();
948  }
949 
950 #if CLIENT
951  if (visualSlots != null)
952  {
953  for (int j = 0; j < capacity; j++)
954  {
955  if (slots[j].Contains(existingItems.FirstOrDefault()))
956  {
957  visualSlots[j].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
958  }
959  }
960  }
961 #endif
962  return false;
963  }
964  }
965 
966  public void CreateNetworkEvent()
967  {
968  if (GameMain.NetworkMember == null) { return; }
969  if (GameMain.NetworkMember.IsClient) { syncItemsDelay = 1.0f; }
970 
971  //split into multiple events because one might not be enough to fit all the items
972  List<Range> slotRanges = new List<Range>();
973  int startIndex = 0;
974  int itemCount = 0;
975  for (int i = 0; i < capacity; i++)
976  {
977  int count = slots[i].Items.Count;
978  if (itemCount + count > MaxItemsPerNetworkEvent || i == capacity - 1)
979  {
980  slotRanges.Add(new Range(startIndex, i + 1));
981  startIndex = i + 1;
982  itemCount = 0;
983  }
984  itemCount += count;
985  }
986 
987  foreach (var slotRange in slotRanges)
988  {
989  CreateNetworkEvent(slotRange);
990  }
991  }
992 
993  protected virtual void CreateNetworkEvent(Range slotRange) { }
994 
995  public Item FindItem(Func<Item, bool> predicate, bool recursive)
996  {
997  Item match = AllItems.FirstOrDefault(predicate);
998  if (match == null && recursive)
999  {
1000  foreach (var item in AllItems)
1001  {
1002  if (item?.OwnInventory == null) { continue; }
1003 
1004  match = item.OwnInventory.FindItem(predicate, recursive: true);
1005  if (match != null) { return match; }
1006  }
1007  }
1008  return match;
1009  }
1010 
1011  public List<Item> FindAllItems(Func<Item, bool> predicate = null, bool recursive = false, List<Item> list = null)
1012  {
1013  list ??= new List<Item>();
1014  foreach (var item in AllItems)
1015  {
1016  if (predicate == null || predicate(item))
1017  {
1018  list.Add(item);
1019  }
1020  if (recursive)
1021  {
1022  item.OwnInventory?.FindAllItems(predicate, recursive: true, list);
1023  }
1024  }
1025  return list;
1026  }
1027 
1028  public Item FindItemByTag(Identifier tag, bool recursive = false)
1029  {
1030  if (tag.IsEmpty) { return null; }
1031  return FindItem(i => i.HasTag(tag), recursive);
1032  }
1033 
1034  public Item FindItemByIdentifier(Identifier identifier, bool recursive = false)
1035  {
1036  if (identifier.IsEmpty) { return null; }
1037  return FindItem(i => i.Prefab.Identifier == identifier, recursive);
1038  }
1039 
1040  public virtual void RemoveItem(Item item)
1041  {
1042  if (item == null) { return; }
1043 
1044  //go through the inventory and remove the item from all slots
1045  for (int n = 0; n < capacity; n++)
1046  {
1047  if (!slots[n].Contains(item)) { continue; }
1048 
1049  slots[n].RemoveItem(item);
1050  item.ParentInventory = null;
1051 #if CLIENT
1052  if (visualSlots != null)
1053  {
1054  visualSlots[n].ShowBorderHighlight(Color.White, 0.1f, 0.4f);
1055  if (selectedSlot?.Inventory == this) { selectedSlot.ForceTooltipRefresh = true; }
1056  }
1057 #endif
1058  CharacterHUD.RecreateHudTextsIfFocused(item);
1059  }
1060 
1061  NotifyItemComponentsOfChange();
1062  }
1063 
1067  public void ForceToSlot(Item item, int index)
1068  {
1069  slots[index].Add(item);
1070  item.ParentInventory = this;
1071  bool equipped = (this as CharacterInventory)?.Owner is Character character && character.HasEquippedItem(item);
1072  if (item.body != null && !equipped)
1073  {
1074  item.body.Enabled = false;
1075  item.body.BodyType = FarseerPhysics.BodyType.Dynamic;
1076  }
1077  }
1078 
1082  public void ForceRemoveFromSlot(Item item, int index)
1083  {
1084  slots[index].RemoveItem(item);
1085  }
1086 
1087  public bool IsInSlot(Item item, int index)
1088  {
1089  if (index < 0 || index >= slots.Length) { return false; }
1090  return slots[index].Contains(item);
1091  }
1092 
1093  public void SharedRead(IReadMessage msg, List<ushort>[] receivedItemIds, out bool readyToApply)
1094  {
1095  byte start = msg.ReadByte();
1096  byte end = msg.ReadByte();
1097 
1098  //if we received the first chunk of item IDs, clear the rest
1099  //to ensure we don't have anything outdated in the list - we're about to receive the rest of the IDs next
1100  if (start == 0)
1101  {
1102  for (int i = 0; i < capacity; i++)
1103  {
1104  receivedItemIds[i] = null;
1105  }
1106  }
1107 
1108  for (int i = start; i < end; i++)
1109  {
1110  var newItemIds = new List<ushort>();
1111  int itemCount = msg.ReadRangedInteger(0, MaxPossibleStackSize);
1112  for (int j = 0; j < itemCount; j++)
1113  {
1114  newItemIds.Add(msg.ReadUInt16());
1115  }
1116  receivedItemIds[i] = newItemIds;
1117  }
1118 
1119  //if all IDs haven't been received yet (chunked into multiple events?)
1120  //don't apply the state yet
1121  readyToApply = !receivedItemIds.Contains(null);
1122  }
1123 
1124  public void SharedWrite(IWriteMessage msg, Range slotRange)
1125  {
1126  int start = slotRange.Start.Value;
1127  int end = slotRange.End.Value;
1128 
1129  msg.WriteByte((byte)start);
1130  msg.WriteByte((byte)end);
1131  for (int i = start; i < end; i++)
1132  {
1133  msg.WriteRangedInteger(slots[i].Items.Count, 0, MaxPossibleStackSize);
1134  for (int j = 0; j < Math.Min(slots[i].Items.Count, MaxPossibleStackSize); j++)
1135  {
1136  var item = slots[i].Items[j];
1137  msg.WriteUInt16(item?.ID ?? (ushort)0);
1138  }
1139  }
1140  }
1141 
1145  public void DeleteAllItems()
1146  {
1147  for (int i = 0; i < capacity; i++)
1148  {
1149  if (!slots[i].Any()) { continue; }
1150  foreach (Item item in slots[i].Items)
1151  {
1152  foreach (ItemContainer itemContainer in item.GetComponents<ItemContainer>())
1153  {
1154  itemContainer.Inventory.DeleteAllItems();
1155  }
1156  }
1157  slots[i].Items.ForEachMod(it => it.Remove());
1158  slots[i].RemoveAllItems();
1159  }
1160  }
1161  }
1162 }
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
void RemoveAllItems()
Removes all items from the slot
int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize=null, float? condition=null, bool ignoreItemsInSlot=false)
Defaults to ItemPrefab.MaxStackSize if null
bool CanBePut(ItemPrefab itemPrefab, float? condition=null, int? quality=null)
bool CanBePut(Item item, bool ignoreCondition=false)
Item FindItemByTag(Identifier tag, bool recursive=false)
virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent=true, bool ignoreCondition=false)
void ForceRemoveFromSlot(Item item, int index)
Removes an item from a specific slot. Doesn't do any sanity checks, use with caution!
readonly int capacity
Capacity, or the number of slots in the inventory.
virtual bool CanBePutInSlot(Item item, int i, bool ignoreCondition=false)
Can the item be put in the specified slot.
virtual int FindAllowedSlot(Item item, bool ignoreCondition=false)
Item FindItemByIdentifier(Identifier identifier, bool recursive=false)
Item FirstOrDefault()
Return the first item in the inventory, or null if the inventory is empty.
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 SharedRead(IReadMessage msg, List< ushort >[] receivedItemIds, out bool readyToApply)
bool CanBePut(ItemPrefab itemPrefab, float? condition=null, int? quality=null)
virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition=null, int? quality=null)
virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot=false)
bool Contains(Item item)
Is the item contained in this inventory. Does not recursively check items inside items.
virtual bool ItemOwnsSelf(Item item)
Returns true if the item owns any of the parent inventories.
Inventory(Entity owner, int capacity, int slotsPerRow=5)
List< int > FindIndices(Item item)
Find the indices of all the slots the item is contained in (two-hand items for example can be in mult...
virtual void PutItem(Item item, int i, Character user, bool removeItem=true, bool createNetworkEvent=true)
void SharedWrite(IWriteMessage msg, Range slotRange)
bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent, bool swapWholeStack)
Item FindItem(Func< Item, bool > predicate, bool recursive)
void DeleteAllItems()
Deletes all items inside the inventory (and also recursively all items inside the items)
int HowManyCanBePut(ItemPrefab itemPrefab, float? condition=null)
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.
void ForceToSlot(Item item, int index)
Forces an item to a specific slot. Doesn't remove the item from existing slots/inventories or do any ...
List< Item > FindAllItems(Func< Item, bool > predicate=null, bool recursive=false, List< Item > list=null)
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...
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
Item LastOrDefault()
Return the last item in the inventory, or null if the inventory is empty.
IEnumerable< Item > GetAllItems(bool checkForDuplicates)
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...
virtual bool IsFull(bool takeStacksIntoAccount=false)
Is there room to put more items in the inventory. Doesn't take stacking into account by default.
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
Inventory FindParentInventory(Func< Inventory, bool > predicate)
object Call(string name, params object[] args)
readonly Identifier Identifier
Definition: Prefab.cs:34
void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount=0.5f)
int ReadRangedInteger(int min, int max)
void WriteRangedInteger(int val, int min, int max)
GUISoundType
Definition: GUI.cs:21