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; [Export] public EnemyStorage3D Storage { get; private set; } [Export] public PlayerDetection3D PlayerDetection { get; private set; } [Export] public Weapon3D EquippedWeapon; [Export] public NavigationProvider3D NavigationModule { get; private set; } [Export] public GravityProvider GravityProvider { get; private set; } private bool _isPlayerInRange = false; private Vector3? _currentStrafeTarget = null; private float _strafeSpeed => Storage.EnemyData.StrafeSpeed; private double _responseTimer = 0; public override void EnterState() { base.EnterState(); //GD.Print("Entering Shooting"); PlayerDetection.PlayerOutOfRange += PlayerDetectionOnPlayerOutOfRange; //DamageReceiver.ChangeState(true); //DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted; 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; //DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted; _currentStrafeTarget = null; //DamageReceiver.ChangeState(false); } public override void PhysicsProcessState(double delta) { base.PhysicsProcessState(delta); 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() { 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 (Storage.EnemyData.PredictPlayer && PlayerDetection.LastKnowPlayerVelocity.HasValue) // { // var predictedDirection = MathFunctions.PredictInterceptPosition(MainObject.GlobalPosition, // PlayerDetection.LastKnownPlayerPosition.Value, PlayerDetection.LastKnowPlayerVelocity.Value, // EquippedWeapon.WeaponData.BulletData.BulletSpeed); // if (predictedDirection.HasValue) return (predictedDirection.Value - MainObject.GlobalPosition).Normalized(); // // } 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(); } }