using System; using System.Collections.Generic; using System.Linq; using Sandbox; namespace Sasalka; public class Inventar : Component { [Flags] public enum InventorySlot { None = 0, LeftHand = 1 << 0, // 1 RightHand = 1 << 1, // 2 Head = 1 << 2, // 4 Body = 1 << 3, // 8 Hands = 1 << 4, // 16 Bottom = 1 << 5, // 32 Feet = 1 << 6 // 64 } [Sync] public IList Items { get; set; } = new List(); [Sync] public IDictionary EquippedItems { get; set; } = new Dictionary(); // Настройка вместимости инвентаря [Property] public int MaxInventorySlots { get; set; } = 20; // Максимальное количество слотов [Property] public bool UnlimitedSlots { get; set; } = false; // Безлимитный инвентарь public static bool IsInventoryOpen = false; public event Action OnChanged; public event Action OnEquipped; public event Action OnUnEquipped; public event Action OnItemAdded; public event Action OnItemRemoved; // Кэш для оптимизации поиска предметов private Dictionary _itemCache = new(); private bool _cacheDirty = true; public bool CanAddItem( InventoryItem item ) { if ( item == null || item.Definition == null ) return false; // Обновляем кэш при необходимости if ( _cacheDirty ) { UpdateItemCache(); } // Проверяем, есть ли уже такой предмет в инвентаре if ( _itemCache.TryGetValue( item.Definition, out var existingItem ) ) { // Если предмет уже есть, проверяем, можно ли добавить к нему количество return existingItem.Count + item.Count <= item.Definition.MaxCount; } // Если предмета нет, проверяем вместимость инвентаря if ( !UnlimitedSlots && Items.Count >= MaxInventorySlots ) { return false; // Инвентарь полон } // Проверяем только MaxCount для нового предмета return item.Count <= item.Definition.MaxCount; } /// /// Обновляет кэш предметов /// private void UpdateItemCache() { _itemCache.Clear(); foreach ( var item in Items ) { if ( item.Definition != null ) { _itemCache[item.Definition] = item; } } _cacheDirty = false; } /// /// Добавляет предмет в инвентарь, распределяя по существующим и новым стекам. Возвращает остаток, который не удалось добавить (или 0, если всё добавлено). /// public int AddItem( InventoryItem item ) { if ( item == null || item.Definition == null || item.Count <= 0 ) return item.Count; int toAdd = item.Count; // 1. Заполняем существующие стаки foreach ( var stack in Items.Where( x => x.Definition == item.Definition && x.Count < x.Definition.MaxCount ) ) { int canAdd = Math.Min( toAdd, stack.Definition.MaxCount - stack.Count ); if ( canAdd > 0 ) { stack.Count += canAdd; toAdd -= canAdd; _cacheDirty = true; // Помечаем кэш как устаревший OnChanged?.Invoke(); OnItemAdded?.Invoke( stack ); } if ( toAdd <= 0 ) return 0; } // 2. Добавляем новые стаки, если есть место while ( toAdd > 0 && (UnlimitedSlots || Items.Count < MaxInventorySlots) ) { int stackCount = Math.Min( toAdd, item.Definition.MaxCount ); var newStack = new InventoryItem { Definition = item.Definition, Count = stackCount, MagazineAmmo = item.MagazineAmmo }; Items.Add( newStack ); toAdd -= stackCount; _cacheDirty = true; // Помечаем кэш как устаревший OnChanged?.Invoke(); OnItemAdded?.Invoke( newStack ); } // 3. Возвращаем остаток, если не всё удалось добавить return toAdd; } public bool RemoveItem( InventoryItem item, int count = 1 ) { if ( item == null || !Items.Contains( item ) ) return false; if ( count >= item.Count ) { // Удаляем весь предмет UnEquipItem( item ); Items.Remove( item ); _cacheDirty = true; // Помечаем кэш как устаревший OnChanged?.Invoke(); OnItemRemoved?.Invoke( item ); return true; } else { // Уменьшаем количество item.Count -= count; _cacheDirty = true; // Помечаем кэш как устаревший OnChanged?.Invoke(); return true; } } public bool EquipItem( InventoryItem item ) { if ( item?.Definition is not IEquipable equipable ) return false; var slot = item.Definition.GetSlot(); if ( slot == null ) return false; // Если уже экипирован этот же предмет — снять его if ( EquippedItems.Values.Contains( item ) ) { UnEquipItem( item ); return true; } // Если на этом слоте уже что-то есть — снять старый предмет if ( EquippedItems.TryGetValue( slot.Value, out var oldItem ) ) { UnEquipItem( oldItem ); } // Экипировать новый предмет EquippedItems[slot.Value] = item; item.Equipped = true; item.OnEquipped(); OnEquipped?.Invoke( item ); OnChanged?.Invoke(); return true; } public void DropItem( InventoryItem item, Vector3 position ) { if ( item == null || !Items.Contains( item ) ) return; GameObject gO = item.Definition.Prefab.Clone( position ); if ( gO.Components.TryGet( out var inventoryItem ) ) { inventoryItem.Count = item.Count; inventoryItem.Definition = item.Definition; // Копируем патроны из оригинального предмета inventoryItem.MagazineAmmo = item.MagazineAmmo; } gO.NetworkSpawn(); // Удаляем весь предмет из инвентаря RemoveItem( item, item.Count ); } public void UnEquipItem( InventoryItem item ) { if ( item == null ) return; var slotToRemove = EquippedItems.FirstOrDefault( kvp => kvp.Value == item ).Key; if ( EquippedItems.ContainsKey( slotToRemove ) ) { EquippedItems.Remove( slotToRemove ); item.Equipped = false; item.OnUnEquipped(); OnUnEquipped?.Invoke( item ); OnChanged?.Invoke(); } } public InventoryItem GetEquippedItem( InventorySlot slot ) { return EquippedItems.TryGetValue( slot, out var item ) ? item : null; } public bool IsSlotOccupied( InventorySlot slot ) { return EquippedItems.ContainsKey( slot ); } public void ClearInventory() { // Снимаем все экипированные предметы foreach ( var item in EquippedItems.Values.ToList() ) { UnEquipItem( item ); } Items.Clear(); OnChanged?.Invoke(); } // Публичный метод для уведомления об изменениях извне класса public void NotifyChanged() { OnChanged?.Invoke(); } // Публичный метод для уведомления о добавлении предмета извне класса public void NotifyItemAdded( InventoryItem item ) { OnItemAdded?.Invoke( item ); } // Методы для получения информации о вместимости public int GetUsedSlots() { return Items.Count; } public int GetAvailableSlots() { if ( UnlimitedSlots ) return int.MaxValue; return Math.Max( 0, MaxInventorySlots - Items.Count ); } public bool IsInventoryFull() { if ( UnlimitedSlots ) return false; return Items.Count >= MaxInventorySlots; } public float GetInventoryUsagePercentage() { if ( UnlimitedSlots ) return 0f; return (float)Items.Count / MaxInventorySlots * 100f; } }