using Cirno.Scripts.Enums; using Cirno.Scripts.Utils; using Cirno.Scripts.Weapons; using Godot; namespace Cirno.Scripts.Components.FSM.Enemy._3D; public partial class Shooting : EnemyStateBase3D { public override EnemyState StateId => EnemyState.Shooting; private EnemyStorage3D Storage { get; set; } private PlayerDetection3D PlayerDetection { get; set; } [Export] public Weapon3D EquippedWeapon; private NavigationProvider3D NavigationModule { get; set; } private GravityProvider GravityProvider { get; set; } private bool _isPlayerInRange; private Vector3? _currentStrafeTarget; private float StrafeSpeed => Storage.EnemyData.StrafeSpeed; private double _responseTimer; public override void Init(IStateMachine machine) { base.Init(machine); Storage ??= StateMachine.GetModule(); PlayerDetection ??= StateMachine.GetModule(); NavigationModule ??= StateMachine.GetModule(); GravityProvider ??= StateMachine.GetModule(); } public override void EnterState() { base.EnterState(); //GD.Print("Entering Shooting"); PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange; if (Storage is null || PlayerDetection is null) return; if (EquippedWeapon != null) { EquippedWeapon.WeaponData = Storage.Root.EnemyResource.Weapon; EquippedWeapon.Init(); } _currentStrafeTarget = null; _responseTimer = 0; } private void PlayerDetectionOnPlayerOutOfRange() { GD.Print("Player out of range, returning to alert"); StateMachine.SetState(EnemyState.Alert); } public override void ExitState() { base.ExitState(); PlayerDetection.PlayerOutOfRange -= PlayerDetectionOnPlayerOutOfRange; _currentStrafeTarget = null; } public override void PhysicsProcessState(double delta) { base.PhysicsProcessState(delta); if (Storage is null || PlayerDetection is null || NavigationModule is null || GravityProvider is null) return; if (PlayerDetection.IsPlayerInRange(Storage.Root.EnemyResource.ViewRange) && PlayerDetection.IsPlayerInSight()) { // SHOOT Shoot(); if (StrafeSpeed > 0) { // Check if a strafe position is needed if (!_currentStrafeTarget.HasValue || NavigationModule.IsNavigationFinished()) { if (_responseTimer < Storage.EnemyData.ResponseTime) { _responseTimer += delta; } else { _currentStrafeTarget = CalculateStrafePosition(); _responseTimer = 0; } } } } else { GD.Print("Back to alert because the player could not be detected anymore"); StateMachine.SetState(EnemyState.Alert); return; } if (_currentStrafeTarget.HasValue) { NavigationModule.SetTarget(_currentStrafeTarget.Value); NavigationModule.Move(StrafeSpeed); } // Calculate gravity MainObject.Velocity = new Vector3(MainObject.Velocity.X, GravityProvider.CalculateGravityVelocity(MainObject.Velocity.Y, delta), MainObject.Velocity.Z); MainObject.MoveAndSlide(); } private Vector3? CalculateStrafePosition() { if (!PlayerDetection.LastKnownPlayerPosition.HasValue) return null; var playerPos = PlayerDetection.LastKnownPlayerPosition.Value; var enemyPos = MainObject.GlobalPosition; // Calculate direction to player var directionToPlayer = (playerPos - enemyPos).Normalized(); // Get perpendicular vectors (left and right strafing directions) var leftStrafe = directionToPlayer.Rotated(Vector3.Up, -Mathf.Pi / 2); var rightStrafe = directionToPlayer.Rotated(Vector3.Up,Mathf.Pi / 2); // Randomly decide left or right first bool tryLeftFirst = GD.Randf() < 0.5f; for (float factor = 1f; factor > 0.25f; factor *= 0.75f) { float strafeDistance = Mathf.Lerp(Storage.EnemyData.MinStrafeDistance, Storage.EnemyData.MaxStrafeDistance, factor); var newPos = enemyPos + (tryLeftFirst ? leftStrafe : rightStrafe) * strafeDistance; if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos)) { return newPos; } } for (float factor = 1f; factor > 0.25f; factor *= 0.75f) { float strafeDistance = Mathf.Lerp(Storage.EnemyData.MinStrafeDistance, Storage.EnemyData.MaxStrafeDistance, factor); var newPos = enemyPos + (tryLeftFirst ? rightStrafe : leftStrafe) * strafeDistance; if (NavigationModule.IsNavigable(newPos) && PlayerDetection.HasLineOfSight(newPos, playerPos)) { return newPos; } } return null; // No valid strafe position found } private Vector2 GetShootDirection() { if (!PlayerDetection.LastKnownPlayerPosition.HasValue) return Vector2.Zero; return (PlayerDetection.LastKnownPlayerPosition.Value - MainObject.GlobalPosition).ToVector2().Normalized(); } private void Shoot() { if (EquippedWeapon == null) return; if (!PlayerDetection.LastKnownPlayerPosition.HasValue) return; var direction = GetShootDirection(); // Shoot at the player's last known position EquippedWeapon.ShootDirection = direction; //StorageModule.FacingDirection = direction; Storage.FacingDirection = direction.SnapToCardinal().Normalized(); Storage.AimingDirection = Storage.FacingDirection; EquippedWeapon.Shoot(); } }