sasalka/Code/Inventory/Inventar.cs
2025-06-29 15:20:07 +03:00

404 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<InventoryItem> Items { get; set; } = new List<InventoryItem>();
[Sync]
public IDictionary<InventorySlot, InventoryItem> EquippedItems { get; set; } =
new Dictionary<InventorySlot, InventoryItem>();
// Настройка вместимости инвентаря
[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<InventoryItem> OnEquipped;
public event Action<InventoryItem> OnUnEquipped;
public event Action<InventoryItem> OnItemAdded;
public event Action<InventoryItem> OnItemRemoved;
// Кэш для оптимизации поиска предметов
private Dictionary<BaseItemDefinition, InventoryItem> _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;
}
/// <summary>
/// Обновляет кэш предметов
/// </summary>
private void UpdateItemCache()
{
_itemCache.Clear();
foreach ( var item in Items )
{
if ( item.Definition != null )
{
_itemCache[item.Definition] = item;
}
}
_cacheDirty = false;
}
/// <summary>
/// Добавляет предмет в инвентарь, распределяя по существующим и новым стекам. Возвращает остаток, который не удалось добавить (или 0, если всё добавлено).
/// </summary>
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. Возвращаем остаток, если не всё удалось добавить
AutoSave(); // Автоматическое сохранение
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 );
AutoSave(); // Автоматическое сохранение
return true;
}
else
{
// Уменьшаем количество
item.Count -= count;
_cacheDirty = true; // Помечаем кэш как устаревший
OnChanged?.Invoke();
AutoSave(); // Автоматическое сохранение
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();
AutoSave(); // Автоматическое сохранение
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<InventoryItem>( out var inventoryItem ) )
{
inventoryItem.Count = item.Count;
inventoryItem.Definition = item.Definition;
// Копируем патроны из оригинального предмета
inventoryItem.MagazineAmmo = item.MagazineAmmo;
}
gO.NetworkSpawn();
// Удаляем весь предмет из инвентаря
RemoveItem( item, item.Count );
}
/// <summary>
/// Получает отображаемое название слота
/// </summary>
private string GetSlotDisplayName( InventorySlot slot )
{
return slot switch
{
InventorySlot.LeftHand => "Л.Рука",
InventorySlot.RightHand => "П.Рука",
InventorySlot.Head => "Голова",
InventorySlot.Body => "Тело",
InventorySlot.Hands => "Руки",
InventorySlot.Bottom => "Ноги",
InventorySlot.Feet => "Обувь",
_ => "Неизвестно"
};
}
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();
AutoSave(); // Автоматическое сохранение
}
}
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;
}
#region Сохранение и загрузка
/// <summary>
/// Автоматически сохраняет инвентарь при изменениях
/// </summary>
public void AutoSave()
{
if ( Network.IsOwner )
{
InventorySaveSystem.SaveInventory( this );
}
}
/// <summary>
/// Загружает инвентарь из файла сохранения
/// </summary>
public void LoadFromSave()
{
if ( Network.IsOwner )
{
InventorySaveSystem.LoadInventory( this );
}
}
/// <summary>
/// Проверяет, существует ли файл сохранения
/// </summary>
public bool HasSaveFile()
{
return InventorySaveSystem.HasSaveFile();
}
/// <summary>
/// Удаляет файл сохранения
/// </summary>
public void DeleteSaveFile()
{
if ( Network.IsOwner )
{
InventorySaveSystem.DeleteSaveFile();
}
}
/// <summary>
/// Выбрасывает все предметы из инвентаря
/// </summary>
/// <param name="dropPosition">Базовая позиция для выбрасывания</param>
/// <param name="scatterRadius">Радиус разброса предметов</param>
/// <returns>Количество выброшенных предметов</returns>
public int DropAllItems( Vector3 dropPosition, float scatterRadius = 50f )
{
if ( !Network.IsOwner ) return 0;
Log.Info( "Выбрасываем все предметы из инвентаря..." );
// Создаем копию списка предметов для безопасной итерации
var itemsToDrop = Items.ToList();
var equippedItemsToDrop = EquippedItems.Values.ToList();
int droppedCount = 0;
// Сначала снимаем все экипированные предметы
foreach ( var equippedItem in equippedItemsToDrop )
{
if ( equippedItem != null )
{
UnEquipItem( equippedItem );
}
}
// Затем выбрасываем все предметы
foreach ( var item in itemsToDrop )
{
if ( item != null && item.Count > 0 )
{
// Выбираем случайную позицию в радиусе разброса
var scatteredPosition = dropPosition + Vector3.Random * scatterRadius + Vector3.Up * 100f;
DropItem( item, scatteredPosition );
droppedCount++;
}
}
Log.Info( $"Выброшено {droppedCount} предметов из инвентаря" );
return droppedCount;
}
#endregion
}