using System.Threading.Tasks;
using Sandbox;
using Sandbox.Citizen;
using Sasalka;
using System.Collections.Generic;
namespace Sandbox.Weapons;
///
/// Базовый класс для всех видов оружия
/// Содержит всю логику стрельбы и управления патронами
///
public class BaseWeapon : InventoryItem, IUseable
{
[Property] public SkinnedModelRenderer GunRenderer { get; private set; }
[Property] public GameObject MuzzleLight { get; private set; }
[Property] public GameObject ParticlePrefab { get; set; }
[Property] public GameObject BloodParticle { get; set; }
[Property] public GameObject MuzzleFlashPrefab { get; set; }
[Property] public GameObject EjectShellPrefab { get; set; }
[Property] public GameObject MuzzleTransform { get; set; }
[Property] public GameObject EjectTransform { get; set; }
// Состояние оружия
[Sync] public int CurrentAmmo { get; set; }
[Sync] public bool IsReloading { get; set; }
[Sync] public TimeSince TimeSinceLastShot { get; set; }
[Sync] public TimeSince TimeSinceReloadStart { get; set; }
// Компоненты
private SoundPointComponent _sound;
private Rigidbody _rigidbody;
private PickupItem _pickupItem;
// Кэш для производительности
private WeaponItemDefinition _weaponDefinition;
private bool _isInitialized;
// IUseable реализация - Cooldown вычисляется на основе FireRate
public float Cooldown
{
get
{
var weaponDef = GetWeaponDefinition();
return weaponDef != null ? 1f / weaponDef.FireRate : 0.1f;
}
}
protected override void OnStart()
{
base.OnStart();
// Получаем компоненты
_sound = GameObject.GetComponent( true );
_pickupItem = GameObject.Components.Get();
// Инициализируем оружие
InitializeWeapon();
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( !IsValid || !Equipped ) return;
// Проверяем завершение перезарядки
if ( IsReloading && TimeSinceReloadStart >= GetWeaponDefinition()?.ReloadTime )
{
FinishReload();
}
// Обновляем эффекты
UpdateEffects();
}
///
/// Инициализация оружия
///
private void InitializeWeapon()
{
if ( _isInitialized ) return;
_weaponDefinition = Definition as WeaponItemDefinition;
if ( _weaponDefinition == null )
{
Log.Error( $"BaseWeapon: Definition is not WeaponItemDefinition for {GameObject.Name}" );
return;
}
// Оружие начинается с пустым магазином - патроны нужно зарядить
CurrentAmmo = 0;
_isInitialized = true;
}
///
/// Получить определение оружия
///
public WeaponItemDefinition GetWeaponDefinition()
{
return _weaponDefinition ??= Definition as WeaponItemDefinition;
}
///
/// Экипировка оружия
///
public override void OnEquipped()
{
base.OnEquipped();
// Получаем Rigidbody в момент экипировки
_rigidbody = GameObject.Components.Get();
if ( _rigidbody != null )
_rigidbody.Enabled = false;
if ( _pickupItem != null )
_pickupItem.Enabled = false;
}
///
/// Снятие оружия
///
public override void OnUnEquipped()
{
base.OnUnEquipped();
// Получаем Rigidbody в момент снятия
_rigidbody = GameObject.Components.Get();
if ( _rigidbody != null )
_rigidbody.Enabled = true;
if ( _pickupItem != null )
_pickupItem.Enabled = true;
}
// IUseable реализация
///
/// Проверка возможности использования
///
public virtual bool CanUse()
{
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return false;
return IsValid && Equipped && !IsReloading && TimeSinceLastShot >= 1f / weaponDef.FireRate;
}
///
/// Использование оружия (выстрел)
///
public virtual void Use()
{
if ( !CanUse() ) return;
TryShoot();
}
///
/// Попытка выстрела
///
public bool TryShoot()
{
if ( !CanShoot() ) return false;
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return false;
// Проверяем время между выстрелами из определения
if ( TimeSinceLastShot < 1f / weaponDef.FireRate ) return false;
// Проверяем патроны в магазине
if ( CurrentAmmo <= 0 )
{
return false;
}
// Выполняем выстрел
PerformShot();
return true;
}
///
/// Проверка возможности выстрела
///
public bool CanShoot()
{
if ( !IsValid || !Equipped || IsReloading ) return false;
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return false;
// Проверяем только патроны в магазине
return CurrentAmmo > 0 && TimeSinceLastShot >= 1f / weaponDef.FireRate;
}
///
/// Проверка наличия патронов в инвентаре
///
private bool HasAmmoInInventory()
{
var player = Dedugan.Local;
if ( player?.Inventory == null ) return false;
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return false;
foreach ( var item in player.Inventory.Items )
{
if ( item?.Definition is AmmoItemDefinition ammoDef && ammoDef.IsCompatibleWith( weaponDef.AmmoType ) )
{
if ( item.Count > 0 ) return true;
}
}
return false;
}
///
/// Выполнение выстрела
///
protected virtual void PerformShot()
{
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return;
// Уменьшаем патроны в магазине
CurrentAmmo--;
TimeSinceLastShot = 0;
// Эффекты выстрела
PlayShootEffects();
// Трассировка пули
PerformBulletTrace( weaponDef );
}
///
/// Трассировка пули
///
protected virtual void PerformBulletTrace( WeaponItemDefinition weaponDef )
{
var startPos = Scene.Camera.WorldPosition;
var direction = Scene.Camera.WorldRotation.Forward;
// Добавляем разброс
if ( weaponDef.Spread > 0 )
{
direction += Vector3.Random * weaponDef.Spread;
direction = direction.Normal;
}
var tr = Scene.Trace
.Ray( startPos, startPos + direction * weaponDef.Range )
.IgnoreGameObjectHierarchy( Dedugan.Local.GameObject )
.WithoutTags( "weapon" )
.UseHitboxes()
.Run();
if ( tr.Hit )
{
HandleHit( tr, weaponDef );
}
}
///
/// Обработка попадания
///
protected virtual void HandleHit( SceneTraceResult tr, WeaponItemDefinition weaponDef )
{
if ( tr.Hitbox != null )
{
var boneIndex = tr.Hitbox.Bone.Index;
var components = tr.GameObject.Components;
// Кэшируем компоненты для избежания повторных поисков
var dedugan = components.Get();
var enemy = components.Get();
var hasTarget = dedugan.IsValid() || enemy.IsValid();
if ( hasTarget )
{
CreateHitEffects( tr.EndPosition, tr.Normal, true );
}
if ( dedugan.IsValid() )
{
dedugan.ReportHit( tr.Direction, boneIndex, (int)weaponDef.Damage );
}
if ( enemy.IsValid() )
{
enemy.ReportHit( tr.Direction, boneIndex, (int)weaponDef.Damage );
}
}
else
{
CreateHitEffects( tr.EndPosition, tr.Normal );
}
}
///
/// Начать перезарядку
///
public virtual void StartReload()
{
if ( IsReloading || CurrentAmmo >= GetWeaponDefinition()?.MagazineSize ) return;
IsReloading = true;
TimeSinceReloadStart = 0;
// Эффекты перезарядки
PlayReloadEffects();
}
///
/// Завершить перезарядку
///
protected virtual void FinishReload()
{
IsReloading = false;
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return;
// Загружаем патроны из инвентаря
LoadAmmoFromInventory( weaponDef.MagazineSize );
}
///
/// Загрузить патроны из инвентаря
///
private void LoadAmmoFromInventory( int maxAmmo )
{
var player = Dedugan.Local;
if ( player?.Inventory == null )
{
Log.Warning( "LoadAmmoFromInventory: Player or Inventory is null" );
return;
}
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null )
{
Log.Warning( "LoadAmmoFromInventory: Weapon definition is null" );
return;
}
// Проверяем, есть ли патроны в инвентаре
if ( !HasAmmoInInventory() )
{
return;
}
int ammoToLoad = maxAmmo - CurrentAmmo;
if ( ammoToLoad <= 0 )
{
return;
}
// Создаем копию списка для безопасной итерации
var itemsToProcess = player.Inventory.Items.ToList();
var itemsToRemove = new List();
int totalLoaded = 0;
foreach ( var item in itemsToProcess )
{
if ( item?.Definition is AmmoItemDefinition ammoDef && ammoDef.IsCompatibleWith( weaponDef.AmmoType ) )
{
if ( item.Count > 0 )
{
int canLoad = Math.Min( ammoToLoad, item.Count );
if ( item.TryRemoveCount( canLoad ) )
{
CurrentAmmo += canLoad;
ammoToLoad -= canLoad;
totalLoaded += canLoad;
// Если патроны закончились, помечаем для удаления
if ( item.Count <= 0 )
{
itemsToRemove.Add( item );
}
if ( ammoToLoad <= 0 )
{
break;
}
}
}
}
}
// Удаляем пустые предметы после итерации
foreach ( var item in itemsToRemove )
{
try
{
player.Inventory.RemoveItem( item );
}
catch ( System.Exception ex )
{
Log.Warning( $"LoadAmmoFromInventory: Failed to remove item {item.Definition.Name}: {ex.Message}" );
}
}
}
///
/// Воспроизведение эффектов выстрела
///
protected virtual void PlayShootEffects()
{
// Звук
_sound?.StartSound();
// Свет
if ( MuzzleLight != null )
{
MuzzleLight.Enabled = true;
GameTask.DelaySeconds( 0.05f ).ContinueWith( _ => MuzzleLight.Enabled = false );
}
// Анимация
if ( GunRenderer != null )
{
GunRenderer.Set( "Fire", true );
}
// Вспышка
if ( MuzzleFlashPrefab != null && MuzzleTransform != null )
{
var flash = MuzzleFlashPrefab.Clone( MuzzleTransform.WorldPosition, MuzzleTransform.WorldRotation );
DestroyAsync( flash, 0.1f );
}
// Гильза
if ( EjectShellPrefab != null && EjectTransform != null )
{
var shell = EjectShellPrefab.Clone( EjectTransform.WorldPosition, EjectTransform.WorldRotation );
DestroyAsync( shell, 3f );
}
}
///
/// Воспроизведение эффектов перезарядки
///
protected virtual void PlayReloadEffects()
{
// Можно добавить звук перезарядки
}
///
/// Создание эффектов попадания
///
[Rpc.Broadcast]
protected virtual void CreateHitEffects( Vector3 position, Vector3 normal, bool blood = false )
{
var rot = Rotation.LookAt( normal );
var effectPrefab = blood ? BloodParticle : ParticlePrefab;
if ( effectPrefab != null )
{
var effect = effectPrefab.Clone( position, rot );
DestroyAsync( effect, 0.5f );
}
}
///
/// Обновление эффектов
///
protected virtual void UpdateEffects()
{
// Можно добавить дополнительные эффекты
}
///
/// Асинхронное уничтожение объекта
///
protected async void DestroyAsync( GameObject go, float delay )
{
if ( go == null ) return;
await GameTask.DelaySeconds( delay );
if ( go.IsValid )
go.Destroy();
}
///
/// Получить информацию о патронах для UI
///
public (int current, int max, int totalInInventory) GetAmmoInfo()
{
var weaponDef = GetWeaponDefinition();
var totalInInventory = GetTotalAmmoInInventory();
return (CurrentAmmo, weaponDef?.MagazineSize ?? 0, totalInInventory);
}
///
/// Получить общее количество патронов в инвентаре
///
private int GetTotalAmmoInInventory()
{
var player = Dedugan.Local;
if ( player?.Inventory == null ) return 0;
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return 0;
int total = 0;
foreach ( var item in player.Inventory.Items )
{
if ( item?.Definition is AmmoItemDefinition ammoDef && ammoDef.IsCompatibleWith( weaponDef.AmmoType ) )
{
total += item.Count;
}
}
return total;
}
///
/// Получить прогресс перезарядки (0-1)
///
public float GetReloadProgress()
{
if ( !IsReloading ) return 1f;
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return 1f;
return MathX.Clamp( TimeSinceReloadStart / weaponDef.ReloadTime, 0f, 1f );
}
}