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; public bool CanAddItem( InventoryItem item ) { if ( item == null || item.Definition == null ) return false; // Проверяем, есть ли уже такой предмет в инвентаре var existingItem = Items.FirstOrDefault( x => x.Definition == item.Definition ); if ( existingItem != null ) { // Если предмет уже есть, проверяем, можно ли добавить к нему количество return existingItem.Count + item.Count <= item.Definition.MaxCount; } // Если предмета нет, проверяем вместимость инвентаря if ( !UnlimitedSlots && Items.Count >= MaxInventorySlots ) { return false; // Инвентарь полон } // Проверяем только MaxCount для нового предмета return item.Count <= item.Definition.MaxCount; } /// /// Добавляет предмет в инвентарь, распределяя по существующим и новым стекам. Возвращает остаток, который не удалось добавить (или 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; 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 }; Items.Add(newStack); toAdd -= stackCount; 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 ); OnChanged?.Invoke(); OnItemRemoved?.Invoke( item ); return true; } else { // Уменьшаем количество item.Count -= count; 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; OnEquipped?.Invoke( item ); OnChanged?.Invoke(); return true; } public void DropItem( InventoryItem item, Vector3 position ) { if ( item == null || !Items.Contains( item ) ) return; // Создаем копию предмета для выбрасывания var droppedItem = new InventoryItem { Definition = item.Definition, Count = item.Count // Выбрасываем всю стопку }; GameObject gO = item.Definition.Prefab.Clone( position ); if ( gO.Components.TryGet( out var inventoryItem ) ) { inventoryItem.Count = droppedItem.Count; inventoryItem.Definition = droppedItem.Definition; } 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; 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; } }