4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
23 public IReadOnlyList<Item>
Items => items;
29 this.inventory = inventory;
34 if (item ==
null) {
return false; }
41 if (items.Any(it => !it.IsFullCondition)) {
return false; }
43 else if (MathUtils.NearlyEqual(item.
Condition, 0.0f))
45 if (items.Any(it => !MathUtils.NearlyEqual(it.Condition, 0.0f))) {
return false; }
63 if (itemPrefab ==
null) {
return false; }
66 if (condition.HasValue)
68 if (MathUtils.NearlyEqual(condition.Value, 0.0f))
70 if (items.Any(it => it.Condition > 0.0f)) {
return false; }
72 else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.
Health))
74 if (items.Any(it => !it.IsFullCondition)) {
return false; }
83 if (items.Any(it => !it.IsFullCondition)) {
return false; }
88 if (items[0].
Quality != quality.Value) {
return false; }
103 if (itemPrefab ==
null) {
return 0; }
105 if (items.Count > 0 && !ignoreItemsInSlot)
107 if (condition.HasValue)
109 if (MathUtils.NearlyEqual(condition.Value, 0.0f))
111 if (items.Any(it => it.Condition > 0.0f)) {
return 0; }
113 else if (MathUtils.NearlyEqual(condition.Value, itemPrefab.
Health))
115 if (items.Any(it => !it.IsFullCondition)) {
return 0; }
124 if (items.Any(it => !it.IsFullCondition)) {
return 0; }
127 return maxStackSize.Value - items.Count;
131 return maxStackSize.Value;
139 throw new InvalidOperationException(
"Tried to add a null item to an inventory slot.");
145 throw new InvalidOperationException(
"Tried to stack different types of items.");
149 throw new InvalidOperationException($
"Tried to add an item to a full inventory slot (stack already full, x{items.Count} {items.First().Prefab.Identifier}).");
152 if (items.Contains(item)) {
return; }
156 for (
int i = 0; i < items.Count; i++)
164 items.Insert(index, item);
172 if (items.Count == 0) {
return null; }
194 items.RemoveAll(it => predicate(it));
199 return items.Count > 0;
204 return items.Count == 0;
214 return items.FirstOrDefault();
219 return items.LastOrDefault();
224 return items.Contains(item);
242 private int extraStackSize;
245 get => extraStackSize;
246 set => extraStackSize = MathHelper.Max(value, 0);
260 private readonly List<Item> allItemsList =
new List<Item>();
268 allItemsList.Clear();
311 this.slotsPerRow = slotsPerRow;
315 DraggableIndicator = GUIStyle.GetComponentStyle(
"GUIDragIndicator").GetDefaultSprite();
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);
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);
334 foreach (var item
in slots[i].Items)
339 DebugConsole.ThrowError($
"Null item in inventory {Owner.ToString() ?? "null"}, slot {i}!");
344 if (checkForDuplicates)
346 bool duplicateFound =
false;
347 for (
int j = 0; j < i; j++)
351 duplicateFound =
true;
355 if (!duplicateFound) { yield
return item; }
365 private void NotifyItemComponentsOfChange()
370 character.SelectedItem?.GetComponent<CircuitBox>()?.OnViewUpdateProjSpecific();
374 if (
Owner is not Item it) {
return; }
376 foreach (var c
in it.Components)
378 c.OnInventoryChanged();
381 it.ParentInventory?.NotifyItemComponentsOfChange();
389 return slots.
Any(i => i.Contains(item));
397 foreach (var itemSlot
in slots)
399 var item = itemSlot.FirstOrDefault();
400 if (item !=
null) {
return item; }
410 for (
int i =
slots.Length - 1; i >= 0; i--)
413 if (item !=
null) {
return item; }
423 if (index < 0 || index >=
slots.Length) {
return null; }
432 if (index < 0 || index >=
slots.Length) {
return Enumerable.Empty<
Item>(); }
438 if (index < 0 || index >=
slots.Length) {
return -1; }
460 List<int> indices =
new List<int>();
473 if (
Owner ==
null) {
return false; }
476 if (ownerItem == item) {
return true; }
517 if (i < 0 || i >=
slots.Length) {
return false; }
525 if (
CanBePutInSlot(itemPrefab, i, condition, quality)) {
return true; }
532 if (i < 0 || i >=
slots.Length) {
return false; }
548 if (i < 0 || i >=
slots.Length) {
return 0; }
549 return slots[i].
HowManyCanBePut(itemPrefab, condition: condition, ignoreItemsInSlot: ignoreItemsInSlot);
555 public virtual bool TryPutItem(
Item item,
Character user, IEnumerable<InvSlotType> allowedSlots =
null,
bool createNetworkEvent =
true,
bool ignoreCondition =
false)
558 if (slot < 0) {
return false; }
560 PutItem(item, slot, user,
true, createNetworkEvent);
564 public virtual bool TryPutItem(
Item item,
int i,
bool allowSwapping,
bool allowCombine,
Character user,
bool createNetworkEvent =
true,
bool ignoreCondition =
false)
566 if (i < 0 || i >=
slots.Length)
569 string ownerStr =
"null";
572 ownerStr = ownerItem.Prefab.Identifier.Value;
576 ownerStr = ownerCharacter.SpeciesName.Value;
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);
581 DebugConsole.ThrowError(errorMsg);
586 if (
Owner ==
null)
return false;
590 if (
slots[i].First().Combine(item, user))
595 return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition);
602 PutItem(item, i, user,
true, createNetworkEvent);
608 if (itemInSlot.OwnInventory !=
null &&
609 !itemInSlot.OwnInventory.Contains(item) &&
611 itemInSlot.OwnInventory.TrySwapping(0, item, user, createNetworkEvent, swapWholeStack:
false))
616 TrySwapping(i, item, user, createNetworkEvent, swapWholeStack:
true) ||
617 TrySwapping(i, item, user, createNetworkEvent, swapWholeStack:
false);
628 protected virtual void PutItem(
Item item,
int i,
Character user,
bool removeItem =
true,
bool createNetworkEvent =
true)
630 if (i < 0 || i >=
slots.Length)
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);
639 if (should !=
null && should.Value)
642 if (
Owner ==
null) {
return; }
647 if (createNetworkEvent)
651 if (prevInventory !=
null && prevInventory !=
this) { prevInventory.
syncItemsDelay = 1.0f; }
656 item.
Drop(user, setTransform:
false, createNetworkEvent: createNetworkEvent &&
GameMain.
NetworkMember is { IsServer: true });
672 if (item.
body !=
null)
682 if (prevOwnerInventory is
CharacterInventory characterInventory && characterInventory !=
this &&
Owner == user)
684 var client =
GameMain.Server?.ConnectedClients?.Find(cl => cl.Character == user);
685 GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(characterInventory, client, item);
690 if (prevInventory !=
this && prevOwnerInventory !=
this)
699 if (currentInventory != prevInventory)
706 NotifyItemComponentsOfChange();
713 if (
slots[i].
Any()) {
return false; }
723 public virtual bool IsFull(
bool takeStacksIntoAccount =
false)
725 if (takeStacksIntoAccount)
729 if (!
slots[i].
Any()) {
return false; }
731 if (
slots[i].Items.Count < item.Prefab.GetMaxStackSize(
this)) {
return false; }
738 if (!
slots[i].
Any()) {
return false; }
747 if (item?.ParentInventory ==
null || !
slots[index].
Any()) {
return false; }
748 if (
slots[index].Items.
Any(it => !it.IsInteractable(user))) {
return false; }
751 var should =
GameMain.
LuaCs.
Hook.
Call<
bool?>(
"inventoryItemSwap",
this, item, user, index, swapWholeStack);
758 bool otherIsEquipped =
false;
760 for (
int i = 0; i < otherInventory.
slots.Length; i++)
765 if (characterInventory.SlotTypes[i] ==
InvSlotType.Any)
772 otherIsEquipped =
true;
776 if (otherIndex == -1)
778 otherIndex = otherInventory.
FindIndex(item);
779 if (otherIndex == -1)
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());
786 List<Item> existingItems =
new List<Item>();
789 existingItems.AddRange(
slots[index].Items);
804 List<Item> stackedItems =
new List<Item>();
807 for (
int j = 0; j < otherInventory.
capacity; j++)
811 stackedItems.AddRange(otherInventory.
slots[j].
Items);
818 stackedItems.Add(item);
823 bool swapSuccessful =
false;
827 stackedItems.Distinct().All(stackedItem =>
TryPutItem(stackedItem, index,
false,
false, user, createNetworkEvent))
829 (existingItems.All(existingItem => otherInventory.
TryPutItem(existingItem, otherIndex,
false,
false, user, createNetworkEvent)) ||
835 (existingItems.All(existingItem => otherInventory.
TryPutItem(existingItem, otherIndex,
false,
false, user, createNetworkEvent)) ||
838 stackedItems.Distinct().All(stackedItem =>
TryPutItem(stackedItem, index,
false,
false, user, createNetworkEvent));
840 if (!swapSuccessful && existingItems.Count == 1 && existingItems[0].AllowDroppingOnSwapWith(item))
842 if (!(existingItems[0].Container?.ParentInventory is
CharacterInventory characterInv) ||
843 !characterInv.TryPutItem(existingItems[0], user,
new List<InvSlotType>() {
InvSlotType.Any }))
845 existingItems[0].
Drop(user, createNetworkEvent);
847 swapSuccessful = stackedItems.Distinct().Any(stackedItem =>
TryPutItem(stackedItem, index,
false,
false, user, createNetworkEvent));
852 if (otherInventory.
visualSlots !=
null && otherIndex > -1)
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.");
876 for (
int j = 0; j < otherInventory.
capacity; j++)
888 foreach (
Item stackedItem
in stackedItems)
895 foreach (
Item existingItem
in existingItems)
897 for (
int j = 0; j < otherInventory.
capacity; j++)
909 slots[j].
RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
912 for (
int j = 0; j < otherInventory.
capacity; j++)
914 if (otherInventory.
slots[j].
Contains(existingItems.FirstOrDefault()))
916 otherInventory.
slots[j].
RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
923 TryPutAndForce(existingItems,
this, index);
924 TryPutAndForce(stackedItems, otherInventory, otherIndex);
928 TryPutAndForce(stackedItems, otherInventory, otherIndex);
929 TryPutAndForce(existingItems,
this, index);
932 void TryPutAndForce(IEnumerable<Item> items,
Inventory inventory,
int slotIndex)
934 foreach (var item
in items)
936 if (!inventory.
TryPutItem(item, slotIndex,
false,
false, user, createNetworkEvent, ignoreCondition:
true) &&
937 !inventory.
GetItemsAt(slotIndex).Contains(item))
944 if (createNetworkEvent)
972 List<Range> slotRanges =
new List<Range>();
980 slotRanges.Add(
new Range(startIndex, i + 1));
987 foreach (var slotRange
in slotRanges)
998 if (match ==
null && recursive)
1002 if (item?.OwnInventory ==
null) {
continue; }
1005 if (match !=
null) {
return match; }
1011 public List<Item>
FindAllItems(Func<Item, bool> predicate =
null,
bool recursive =
false, List<Item> list =
null)
1013 list ??=
new List<Item>();
1016 if (predicate ==
null || predicate(item))
1022 item.OwnInventory?.FindAllItems(predicate, recursive:
true, list);
1030 if (tag.IsEmpty) {
return null; }
1031 return FindItem(i => i.HasTag(tag), recursive);
1036 if (identifier.IsEmpty) {
return null; }
1037 return FindItem(i => i.Prefab.Identifier == identifier, recursive);
1042 if (item ==
null) {
return; }
1061 NotifyItemComponentsOfChange();
1072 if (item.
body !=
null && !equipped)
1089 if (index < 0 || index >=
slots.Length) {
return false; }
1104 receivedItemIds[i] =
null;
1108 for (
int i = start; i < end; i++)
1110 var newItemIds =
new List<ushort>();
1112 for (
int j = 0; j < itemCount; j++)
1116 receivedItemIds[i] = newItemIds;
1121 readyToApply = !receivedItemIds.Contains(
null);
1126 int start = slotRange.Start.Value;
1127 int end = slotRange.End.Value;
1131 for (
int i = start; i < end; i++)
1157 slots[i].
Items.ForEachMod(it => it.Remove());
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
static Character? Controlled
static readonly List< InvSlotType > AnySlot
static NetworkMember NetworkMember
static void ItemTaken(Item item, Character thief)
void RemoveWhere(Func< Item, bool > predicate)
void RemoveAllItems()
Removes all items from the slot
void RemoveItem(Item item)
int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize=null, float? condition=null, bool ignoreItemsInSlot=false)
Defaults to ItemPrefab.MaxStackSize if null
IReadOnlyList< Item > Items
Item RemoveItem()
Removes one item from the slot
bool CanBePut(ItemPrefab itemPrefab, float? condition=null, int? quality=null)
bool CanBePut(Item item, bool ignoreCondition=false)
ItemSlot(Inventory inventory)
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.
readonly ItemSlot[] slots
virtual void CreateNetworkEvent(Range slotRange)
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)
int GetItemStackSlotIndex(Item item, int index)
virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot=false)
const int MaxItemsPerNetworkEvent
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)
static SlotReference selectedSlot
static bool IsDragAndDropGiveAllowed
static Sprite DraggableIndicator
void CreateNetworkEvent()
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)
static Sprite UnequippedIndicator
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....
virtual void RemoveItem(Item item)
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 ...
static Sprite slotHotkeySprite
bool AllowSwappingContainedItems
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.
bool IsInSlot(Item item, int index)
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.
const int MaxPossibleStackSize
override Vector2? SimPosition
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
ItemInventory OwnInventory
Inventory ParentInventory
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
Inventory FindParentInventory(Func< Inventory, bool > predicate)
int GetMaxStackSize(Inventory inventory)
readonly ItemInventory Inventory
int GetMaxStackSize(int slotIndex)
object Call(string name, params object[] args)
void UpdateDrawPosition(bool interpolate=true)
readonly Identifier Identifier
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)
void WriteUInt16(UInt16 val)