using Godot; using System; using System.Diagnostics; using Cirno.Scripts; using Cirno.Scripts.Components; using Cirno.Scripts.Resources; using Cirno.Scripts.Utils; public partial class Weapon : Node2D { [Export] public WeaponResource WeaponData { get; set; } [Export] public PackedScene BulletScene { get; set; } [Export] public Marker2D Muzzle { get; set; } [Signal] public delegate void ShootingEventHandler(); [Signal] public delegate void ReloadingEventHandler(); [Signal] public delegate void EmptyEventHandler(); public int Ammo { get; set; } = 0; private int _loadedAmmo; public int LoadedAmmo { get => _loadedAmmo; private set { _loadedAmmo = value; _inventoryManager?.NotifyLoadedAmmoChange(WeaponData?.ItemKey, _loadedAmmo); } } public Vector2 ShootDirection { get; set; } = Vector2.Zero; private Timer _cooldownTimer; private Marker2D _muzzle; private Node2D _bulletsContainer; private GameManager _gameManager; private InventoryManager _inventoryManager; private readonly StringName _shieldAmmoType = "SHIELD"; private bool UsesBattery => WeaponData.AmmoKey == _shieldAmmoType; // Called when the node enters the scene tree for the first time. public override void _Ready() { _muzzle = GetNode("./Muzzle"); _cooldownTimer = GetNode("./ShootTimer"); _gameManager = this.GetGameManager(); _inventoryManager = this.GetInventoryManager(); // Start full if (WeaponData != null) { LoadedAmmo = WeaponData.BulletCapacity; } } public void Reload() { EmitSignalReloading(); _cooldownTimer.Start(WeaponData.ReloadTime); if (WeaponData.InfiniteAmmo || string.IsNullOrWhiteSpace(WeaponData.AmmoKey)) { LoadedAmmo = WeaponData.BulletCapacity; } else { var ammoToLoad = _inventoryManager.RemoveItem(WeaponData.AmmoKey, WeaponData.BulletCapacity - LoadedAmmo); if (ammoToLoad > 0) { LoadedAmmo = ammoToLoad; } else { EmitSignalEmpty(); //GD.Print("Out of ammo"); } } } public void Shoot(BulletOwner? ownerOverride = null) { // Waiting on reload or Rate of Fire cooldown? if (!_cooldownTimer.IsStopped()) { return; } // Check for battery if it's used if (UsesBattery) { if (GameManager.Instance.Player.Shield.CurrentResource >= WeaponData.AmmoPerShot) { GameManager.Instance.Player.Shield.CurrentResource -= WeaponData.AmmoPerShot; } else { return; } } // Out of ammo? if (LoadedAmmo < WeaponData.AmmoPerShot) { if (WeaponData.AutoReload) { Reload(); } return; } EmitSignalShooting(); // TODO: Shoot at muzzle position, need to provide a way to turn it, on a radius? float halfSpread = WeaponData.SpreadAngle / 2f; float spreadStep = WeaponData.BulletsPerShot > 1 ? WeaponData.SpreadAngle / (WeaponData.BulletsPerShot - 1) : 0; for (int i = 0; i < WeaponData.BulletsPerShot; i++) { // Calculate angle offset for this bullet float spreadOffset = -halfSpread + (spreadStep * i); // Add random spread if (WeaponData.RandomSpread > 0) { // Gaussian with mean = 0, stddev = WeaponData.RandomSpread spreadOffset += RandomStuff.GaussianClamped( mean: 0f, stdDev: WeaponData.RandomSpread, // tuning knob min: -halfSpread, max: halfSpread ); } // Rotate the ShootDirection by the spread angle Vector2 spreadDirection = ShootDirection.Rotated(Mathf.DegToRad(spreadOffset)); var bullet = this.CreateChildOf(_gameManager.BulletsContainer, WeaponData.BulletData.BulletScene, _muzzle.GlobalPosition); if (bullet == null) { GD.PrintErr("Bullet is null, not shooting"); return; }; var bulletData = WeaponData.MakeBullet(_muzzle.GlobalPosition); if (ownerOverride.HasValue) { bulletData.Owner = ownerOverride.Value; } bullet.Initialize(bulletData, _gameManager); //bullet.SetDirection(ShootDirection); bullet.SetDirection(spreadDirection); bullet.Speed = WeaponData.BulletData.BulletSpeed; } if (!UsesBattery) { LoadedAmmo -= WeaponData.AmmoPerShot; } //_inventoryManager.NotifyLoadedAmmoChange(WeaponData.ItemKey, LoadedAmmo); // if (!string.IsNullOrWhiteSpace(WeaponData?.AmmoKey)) // { // // Notify hud to decrease weapon // // } _cooldownTimer.Start(WeaponData?.RateOfFire ?? 0); } }