using Godot; using System; public partial class ai_fighter : ship { [Export] public float AggroRange = 500f; [Export] public int ScorePayout = 50; [Export] public float EngageDistance = 300f; //retreat logic private float previousDistance = 0f; private float stuckTime = 0f; private const float stuckThreshold = 0.5f; private const float distanceTolerance = 5f; private Node2D currentTarget; public override void _Ready() { SetShipStats(); SetupVisual(); Sprite.Texture = GD.Load(spritePath); LaserSpawn = GetNode("LaserSpawn"); } public override void _PhysicsProcess(double delta) { UpdateMovement(delta); HandleFiring(delta); //GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length()); } // SETUP private void SetupAI() { return; } // COMBAT private void HandleFiring(double delta) { //GD.Print(Name, ": checking fire"); //GD.Print(Name, ": fireTimer = ", fireTimer); fireTimer -= (float)delta; // countdown to next shot available if (fireTimer > 0f) return; // if countdown not finished then no shoot if (currentTarget == null || !IsInstanceValid(currentTarget)) return; // checks for valid target, returns if not // determining direction and distance to target Vector2 toTarget = (currentTarget.GlobalPosition - GlobalPosition).Normalized(); float angleToTarget = toTarget.Angle() + Mathf.Pi / 2; float angleDiff = Mathf.Abs(Mathf.AngleDifference(Rotation, angleToTarget)); float distance = GlobalPosition.DistanceTo(currentTarget.GlobalPosition); //GD.Print(Name, ": angleDiff = ", Mathf.RadToDeg(angleDiff)); if (angleDiff < 0.4f && distance <= EngageDistance + 200f) // fire when in range and mostly aligned { //GD.Print(Name, ": angleDiff = ", angleDiff, ", distance = ", distance); ShootLaser(); fireTimer = FireCooldown; // resets cooldown } } // TARGETING private Node2D FindClosestTarget() // returns nearest valid ship depending on faction { Node2D closest = null; float closestDistance = Mathf.Inf; Node shipParent = null; if (faction == ShipFaction.FRIENDLY) { shipParent = GetTree().Root.GetNode("Game/Ships/Enemy"); } else if (faction == ShipFaction.ENEMY || faction == ShipFaction.ACE) { shipParent = GetTree().Root.GetNode("Game/Ships/Friendly"); } if (shipParent == null) { GD.Print("No shipParent found"); return null; } foreach (Node node in shipParent.GetChildren()) { if (node is ship target && target.Health > 0) { float dist = GlobalPosition.DistanceTo(target.GlobalPosition); if (dist < closestDistance) { closest = target; closestDistance = dist; } } } return closest; } private bool EnsureTarget() // finds a new target if no current or if invalid (dead) { if (currentTarget == null || !IsInstanceValid(currentTarget)) { currentTarget = FindClosestTarget(); return false; } return true; } private void UpdateDistanceTracking(float distance) // tracks how long AI has been stuck in same spot { if (MathF.Abs(distance - previousDistance) < distanceTolerance) { stuckTime += (float)GetProcessDeltaTime(); } else { stuckTime = 0f; } previousDistance = distance; } private void HandleThrust(float angleDiff, float distance) { GD.Print(Name, " | angleDiff: ", Mathf.RadToDeg(angleDiff), " | velocity: ", Velocity.Length()); float retreatThreshold = EngageDistance * 0.75f; // retreat distance if (Mathf.Abs(angleDiff) < 1f) // only move forward when facing the target roughly { if (distance > EngageDistance) { Velocity += -Transform.Y * MainSpeed; // approach target } else if (distance < retreatThreshold) { if (stuckTime > stuckThreshold) { // reposition if stuck Vector2 away = (GlobalPosition - currentTarget.GlobalPosition).Normalized(); Velocity += away * MainSpeed; } else { Velocity += Transform.X * StrafeSpeed; // try and orbit target laterally (strafe) } } else { Velocity += Transform.X * StrafeSpeed * 0.5f; // lateral drift in hold range } } } private void RotateToTarget(Vector2 direction, double delta) // rotates ship toward angle using rotation speed { float targetAngle = direction.Angle() + Mathf.Pi / 2; float angleDiff = Mathf.AngleDifference(Rotation, targetAngle); Rotation += Mathf.Clamp(angleDiff, -RotationSpeed * (float)delta, RotationSpeed * (float)delta); } private void UpdateMovement(double delta) { if (!EnsureTarget()) return; // skip if no valid target // math for direction, distance and rotation Vector2 direction = (currentTarget.GlobalPosition - GlobalPosition).Normalized(); float angleToTarget = direction.Angle() + MathF.PI / 2; float angleDiff = Mathf.AngleDifference(Rotation, angleToTarget); float distance = GlobalPosition.DistanceTo(currentTarget.GlobalPosition); UpdateDistanceTracking(distance); HandleThrust(angleDiff, distance); RotateToTarget(direction, delta); Velocity = Velocity.MoveToward(Vector2.Zero, 2.5f); //2.5f FA value (on for AI) Velocity = Velocity.LimitLength(MaxSpeed); MoveAndSlide(); } }