2025-04-18 01:07:51 +00:00
|
|
|
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;
|
2025-04-18 23:21:19 +00:00
|
|
|
private const float stuckThreshold = 0.5f;
|
|
|
|
|
private const float distanceTolerance = 5f;
|
2025-04-18 01:07:51 +00:00
|
|
|
|
|
|
|
|
private Node2D currentTarget;
|
|
|
|
|
|
|
|
|
|
public override void _Ready()
|
|
|
|
|
{
|
|
|
|
|
SetShipStats();
|
|
|
|
|
SetupVisual();
|
|
|
|
|
Sprite.Texture = GD.Load<Texture2D>(spritePath);
|
|
|
|
|
|
|
|
|
|
LaserSpawn = GetNode<Node2D>("LaserSpawn");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void _PhysicsProcess(double delta)
|
|
|
|
|
{
|
|
|
|
|
UpdateMovement(delta);
|
2025-04-18 23:21:19 +00:00
|
|
|
HandleFiring(delta);
|
|
|
|
|
//GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length());
|
2025-04-18 01:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SETUP
|
|
|
|
|
private void SetupAI()
|
|
|
|
|
{
|
2025-04-18 23:21:19 +00:00
|
|
|
return;
|
2025-04-18 01:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// COMBAT
|
2025-04-18 23:21:19 +00:00
|
|
|
private void HandleFiring(double delta)
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
2025-04-18 23:21:19 +00:00
|
|
|
//GD.Print(Name, ": checking fire");
|
|
|
|
|
//GD.Print(Name, ": fireTimer = ", fireTimer);
|
2025-04-18 01:07:51 +00:00
|
|
|
|
|
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2025-04-18 01:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TARGETING
|
2025-04-18 23:21:19 +00:00
|
|
|
private Node2D FindClosestTarget() // returns nearest valid ship depending on faction
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
private bool EnsureTarget() // finds a new target if no current or if invalid (dead)
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
|
|
|
|
if (currentTarget == null || !IsInstanceValid(currentTarget))
|
|
|
|
|
{
|
|
|
|
|
currentTarget = FindClosestTarget();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
private void UpdateDistanceTracking(float distance) // tracks how long AI has been stuck in same spot
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
|
|
|
|
if (MathF.Abs(distance - previousDistance) < distanceTolerance)
|
|
|
|
|
{
|
|
|
|
|
stuckTime += (float)GetProcessDeltaTime();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stuckTime = 0f;
|
|
|
|
|
}
|
|
|
|
|
previousDistance = distance;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
private void HandleThrust(float angleDiff, float distance)
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
|
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
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
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
|
|
|
|
if (distance > EngageDistance)
|
|
|
|
|
{
|
2025-04-18 23:21:19 +00:00
|
|
|
Velocity += -Transform.Y * MainSpeed; // approach target
|
2025-04-18 01:07:51 +00:00
|
|
|
}
|
|
|
|
|
else if (distance < retreatThreshold)
|
|
|
|
|
{
|
|
|
|
|
if (stuckTime > stuckThreshold)
|
|
|
|
|
{
|
2025-04-18 23:21:19 +00:00
|
|
|
// reposition if stuck
|
2025-04-18 01:07:51 +00:00
|
|
|
Vector2 away = (GlobalPosition - currentTarget.GlobalPosition).Normalized();
|
|
|
|
|
Velocity += away * MainSpeed;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-04-18 23:21:19 +00:00
|
|
|
Velocity += Transform.X * StrafeSpeed; // try and orbit target laterally (strafe)
|
2025-04-18 01:07:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-18 23:21:19 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Velocity += Transform.X * StrafeSpeed * 0.5f; // lateral drift in hold range
|
|
|
|
|
}
|
2025-04-18 01:07:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
private void RotateToTarget(Vector2 direction, double delta) // rotates ship toward angle using rotation speed
|
2025-04-18 01:07:51 +00:00
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
2025-04-18 23:21:19 +00:00
|
|
|
if (!EnsureTarget()) return; // skip if no valid target
|
2025-04-18 01:07:51 +00:00
|
|
|
|
2025-04-18 23:21:19 +00:00
|
|
|
// math for direction, distance and rotation
|
2025-04-18 01:07:51 +00:00
|
|
|
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);
|
2025-04-18 23:21:19 +00:00
|
|
|
HandleThrust(angleDiff, distance);
|
2025-04-18 01:07:51 +00:00
|
|
|
RotateToTarget(direction, delta);
|
|
|
|
|
|
|
|
|
|
Velocity = Velocity.MoveToward(Vector2.Zero, 2.5f); //2.5f FA value (on for AI)
|
|
|
|
|
Velocity = Velocity.LimitLength(MaxSpeed);
|
|
|
|
|
|
|
|
|
|
MoveAndSlide();
|
|
|
|
|
}
|
|
|
|
|
}
|