starfighter/MB_FYP/script/ai_fighter.cs

237 lines
7.2 KiB
C#

using Godot;
using System;
public partial class ai_fighter : ship
{
[Signal]
public delegate void OnDeathEventHandler(int factionInt);
[Export]
public float AggroRange = 500f;
[Export]
public int ScorePayout = 50;
[Export]
public float EngageDistance = 300f;
public bool SignalsConnected { get; set; } = false;
private Label _healthLabel;
private Label _hpLabel;
//retreat logic
private float _previousDistance;
private float _stuckTime;
private const float StuckThreshold = 0.5f;
private const float DistanceTolerance = 5f;
private Node2D _currentTarget;
public override void _Ready()
{
SetShipStats();
SetupVisual();
Sprite.Texture = GD.Load<Texture2D>(SpritePath);
_healthLabel = GetNode<Label>("HealthDisplay/HealthLabel");
UpdateHealthLabel(Health);
LaserSpawn = GetNode<Node2D>("LaserSpawn");
}
public override void _Process(double delta)
{
base._Process(delta);
_healthLabel.GetParent<Node2D>().GlobalRotation = 0f;
}
public override void _PhysicsProcess(double delta)
{
UpdateMovement(delta);
HandleFiring(delta);
//GD.Print(Name, ": MainSpeed = ", MainSpeed, " Velocity = ", Velocity.Length());
}
// SETUP
private void SetupAI()
{
return;
}
private void UpdateHealthLabel(int health)
{
_healthLabel.Text = health.ToString();
float percentage = (float)Health / MaxHealth;
_healthLabel.Modulate = percentage switch
{
<= 0.25f => new Color(1f, 0f, 0f),
<= 0.5f => new Color(1f, 0.5f, 0f),
<= 0.75f => new Color(1f, 1f, 0f),
_ => new Color(0.07f,0.6f, 0.07f)
}; // thank you rider
}
// COMBAT
public override void ShipDamage(int damage)
{
base.ShipDamage(damage);
UpdateHealthLabel(Health);
}
protected override void Explode()
{
int factionInt = (int)Faction;
EmitSignal(SignalName.OnDeath, factionInt);
QueueFree();
}
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/WaveController/Ships/Enemy");
}
else if (Faction == ShipFaction.ENEMY || Faction == ShipFaction.ACE)
{
shipParent = GetTree().Root.GetNode("Game/WaveController/Ships/Friendly");
}
if (shipParent == null)
{
GD.Print("No shipParent found");
return null;
}
foreach (Node node in shipParent.GetChildren())
{
if (node is ship { Health: > 0 } target)
{
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();
}
}