using Sandbox.Citizen; using Sandbox.Weapons; using Sasalka; public sealed partial class Dedugan : Component { [Property] public Inventar Inventory { get; private set; } private Dictionary _useableCache = new(); [Sync] private bool InAds { get; set; } = false; private AttachmentSlotResolver _resolver; // Автоматическая стрельба private bool _isShooting = false; private TimeSince _lastShootTime = 0f; // Периодическое сохранение private TimeSince _lastAutoSave = 0f; private const float AUTO_SAVE_INTERVAL = 60f; // Сохраняем каждые 60 секунд void InventoryStart() { if ( !Network.IsOwner ) return; // Создаем инвентарь как компонент Inventory = GameObject.Components.GetOrCreate(); _resolver = new AttachmentSlotResolver( Renderer.GetAttachmentObject ); Inventory.OnEquipped += OnItemEquipped; Inventory.OnUnEquipped += OnItemUnEquipped; Inventory.OnItemAdded += OnItemAdded; Inventory.OnItemRemoved += OnItemRemoved; // Загружаем инвентарь из сохранения при старте LoadInventoryFromSave(); } /// /// Загружает инвентарь из сохранения /// private void LoadInventoryFromSave() { if ( Inventory.HasSaveFile() ) { Log.Info( "Загружаем инвентарь из сохранения..." ); Inventory.LoadFromSave(); } else { Log.Info( "Файл сохранения инвентаря не найден, начинаем с пустым инвентарем" ); } } private void OnItemEquipped( InventoryItem item ) { // Очищаем кэши при экипировке предмета _useableCache.Clear(); _resolver?.ClearCache(); if ( item?.Definition is WeaponItemDefinition weaponDef && weaponDef.Prefab.IsValid() ) { var go = weaponDef.Prefab.Clone(); AnimationHelper.HoldType = weaponDef.HoldType; switch ( weaponDef.Slot ) { case Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand: case Inventar.InventorySlot.RightHand: go.Parent = Renderer.GetAttachmentObject( "hold_R" ); break; case Inventar.InventorySlot.LeftHand: go.Parent = Renderer.GetAttachmentObject( "hold_L" ); break; default: go.Parent = Renderer.GetAttachmentObject( "forward_reference_modelspace" ); break; } go.LocalPosition = weaponDef.WeaponDefinition.Position; go.LocalRotation = weaponDef.WeaponDefinition.Rotation; go.LocalScale = weaponDef.WeaponDefinition.Scale; // Получаем компонент оружия и вызываем экипировку if ( go.Components.TryGet( out var weapon ) ) { // Передаем ссылку на InventoryItem в оружие weapon.SetInventoryItem( item ); weapon.OnEquipped(); _useableCache[weaponDef.Slot] = (go, weapon); } go.NetworkSpawn(); var hand = weaponDef.Slot switch { Inventar.InventorySlot.LeftHand => CitizenAnimationHelper.Hand.Left, Inventar.InventorySlot.RightHand => CitizenAnimationHelper.Hand.Right, Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand => CitizenAnimationHelper.Hand.Both, _ => CitizenAnimationHelper.Hand.Both }; AnimationHelper.Handedness = hand; RpcSetHoldAnimation( weaponDef.HoldType, hand ); InAds = true; } else if ( item?.Definition is ClothingItemDefinition clothingDef ) { WearWorkshop( new List() { clothingDef.ClothUrl } ); } } private void OnItemUnEquipped( InventoryItem item ) { // Очищаем кэши при снятии предмета _useableCache.Clear(); _resolver?.ClearCache(); if ( item?.Definition is WeaponItemDefinition weaponDef && weaponDef.Prefab.IsValid() ) { // Вызываем OnUnEquipped для оружия if ( _useableCache.TryGetValue( weaponDef.Slot, out var cached ) ) { if ( cached.useable is BaseWeapon weapon ) { weapon.OnUnEquipped(); } } switch ( weaponDef.Slot ) { case Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand: case Inventar.InventorySlot.RightHand: case Inventar.InventorySlot.LeftHand: var attachmentName = !weaponDef.Slot.HasFlag( Inventar.InventorySlot.RightHand ) ? "hold_L" : "hold_R"; Renderer.GetAttachmentObject( attachmentName ).Children.ForEach( child => child.Destroy() ); RpcSetHoldAnimation( CitizenAnimationHelper.HoldTypes.None, CitizenAnimationHelper.Hand.Both ); break; default: Renderer.GetAttachmentObject( "forward_reference_modelspace" ).Children .ForEach( child => child.Destroy() ); break; } InAds = false; } else if ( item?.Definition is ClothingItemDefinition clothingDef ) { StripByName( clothingDef.Description ); } } private void OnItemAdded( InventoryItem item ) { // Кэш не очищаем при добавлении предметов, чтобы не нарушить работу оружия } private void OnItemRemoved( InventoryItem item ) { // Кэш не очищаем при удалении предметов, чтобы не нарушить работу оружия } [Rpc.Broadcast] public void RpcSetHoldAnimation( CitizenAnimationHelper.HoldTypes HoldType, CitizenAnimationHelper.Hand hand ) { AnimationHelper.HoldType = HoldType; AnimationHelper.Handedness = hand; } void InventoryUpdate() { if ( !Network.IsOwner ) return; // Обработка автоматической стрельбы HandleAutomaticShooting(); // Обработка перезарядки if ( Input.Pressed( "Reload" ) ) { TryReloadWeapon(); } // Периодическое автоматическое сохранение if ( _lastAutoSave > AUTO_SAVE_INTERVAL ) { if ( Inventory != null ) { Log.Info( "Периодическое автоматическое сохранение инвентаря..." ); Sasalka.InventorySaveSystem.SaveInventory( Inventory ); } _lastAutoSave = 0f; } } /// /// Обработка автоматической стрельбы /// private void HandleAutomaticShooting() { var weapon = GetEquippedWeapon(); if ( weapon == null ) return; var weaponDef = weapon.GetWeaponDefinition(); if ( weaponDef == null ) return; // Начало стрельбы if ( Input.Pressed( "Attack1" ) ) { _isShooting = true; TryUseWeapon(); } // Продолжение стрельбы (только если оружие автоматическое) if ( Input.Down( "Attack1" ) && _isShooting && weaponDef.IsAutomatic ) { TryUseWeapon(); } // Остановка стрельбы if ( Input.Released( "Attack1" ) ) { _isShooting = false; } } /// /// Попытка использования экипированного предмета /// private void TryUseWeapon() { var useable = GetEquippedUseable(); if ( useable != null && useable.CanUse() ) { useable.Use(); Attack(); // Анимация атаки } } /// /// Попытка перезарядки экипированного оружия /// private void TryReloadWeapon() { var weapon = GetEquippedWeapon(); if ( weapon != null && !weapon.IsReloading ) { weapon.StartReload(); // Анимация перезарядки персонажа (скорость уже установлена в weapon.StartReload()) // Дополнительно устанавливаем здесь на случай, если weapon.StartReload() не вызвался var weaponDef = weapon.GetWeaponDefinition(); if ( weaponDef != null ) { float reloadSpeed = weaponDef.ReloadTime > 0 ? 1f / weaponDef.ReloadTime : 1f; Reload( reloadSpeed ); } } } /// /// Получить экипированный используемый предмет /// private IUseable GetEquippedUseable() { // Проверяем правую руку if ( Inventory.EquippedItems.TryGetValue( Inventar.InventorySlot.RightHand, out var rightHandItem ) ) { if ( _useableCache.TryGetValue( Inventar.InventorySlot.RightHand, out var cached ) && cached.useable != null ) { return cached.useable; } } // Проверяем левую руку if ( Inventory.EquippedItems.TryGetValue( Inventar.InventorySlot.LeftHand, out var leftHandItem ) ) { if ( _useableCache.TryGetValue( Inventar.InventorySlot.LeftHand, out var cached ) && cached.useable != null ) { return cached.useable; } } // Проверяем обе руки if ( Inventory.EquippedItems.TryGetValue( Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand, out var bothHandsItem ) ) { if ( _useableCache.TryGetValue( Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand, out var cached ) && cached.useable != null ) { return cached.useable; } } return null; } /// /// Получить экипированное оружие /// private BaseWeapon GetEquippedWeapon() { var useable = GetEquippedUseable(); return useable as BaseWeapon; } /// /// Получить информацию о патронах для UI /// public (int current, int max, int totalInInventory) GetAmmoInfo() { var weapon = GetEquippedWeapon(); if ( weapon == null ) { return (0, 0, 0); } return weapon.GetAmmoInfo(); } /// /// Получить общее количество патронов в инвентаре (для UI) /// public int GetTotalInInventory() { var ammoInfo = GetAmmoInfo(); return ammoInfo.totalInInventory; } /// /// Получить прогресс перезарядки /// public float GetReloadProgress() { var weapon = GetEquippedWeapon(); return weapon?.GetReloadProgress() ?? 1f; } [Rpc.Broadcast] void Attack() { Renderer.Set( "b_attack", true ); } [Rpc.Broadcast] void Reload( float reloadSpeed ) { Renderer.Set( "b_reload", true ); Renderer.Set( "speed_reload", reloadSpeed ); } }