Files
MovementTests/scenes/enemies/Enemy.cs

168 lines
4.3 KiB
C#

using System;
using Godot;
using Movementtests.interfaces;
[GlobalClass]
public partial class Enemy : CharacterBody3D,
IDamageable,
IDamageDealer,
IHealthable,
IKillable,
IMoveable,
ISpawnable,
IKnockbackable,
ITargetable
{
// Signals and events
public event Action<IDamageable, DamageRecord> DamageTaken;
public event Action<IHealthable, float> HealthChanged;
public event Action<IHealthable> HealthDepleted;
// Public export components
[Export]
public Node3D Target { get; set; }
[ExportGroup("Health")]
[Export]
public RHealth RHealth { get; set; }
[Export]
public RDeathEffect[] DeathEffects { get; set; }
public IHealthable CHealth { get; set; }
[ExportGroup("Damage")]
[Export]
public RDamage RDamage { get; set; }
public IDamageable CDamageable { get; set; }
[Export]
public RKnockback RKnockback { get; set; }
public IKnockbackable CKnockback { get; set; }
[ExportGroup("Movement")]
[Export]
public RMovement RMovement { get; set; }
public IMoveable CMovement { get; set; }
// Public stuff
public float CurrentHealth { get; set; }
// Private stuff
private Area3D _damageBox;
public override void _Ready()
{
Initialize();
SetupSignals();
}
public void Initialize()
{
_damageBox = GetNode<Area3D>("DamageBox");
CDamageable = GetNode<Node>("CDamageable") as IDamageable;
CMovement = GetNode<Node>("CMovement") as IMoveable;
CHealth = GetNode<Node>("CHealth") as IHealthable;
CKnockback = GetNode<Node>("CKnockback") as IKnockbackable;
if (CDamageable is null) GD.PrintErr("This node needs a 'CDamage' child of type IDamageable!");
if (CMovement is null) GD.PrintErr("This node needs a 'CMovement' child of type IMoveable!");
if (CHealth is null) GD.PrintErr("This node needs a 'CHealth' child of type IHealthable!");
if (CKnockback is null) GD.PrintErr("This node needs a 'CKnockback' child of type IKnockbackable!");
if (RMovement != null) CMovement!.RMovement = RMovement;
if (RHealth != null)
{
CHealth!.RHealth = RHealth;
CHealth.CurrentHealth = RHealth.StartingHealth;
}
if (RKnockback != null) CKnockback!.RKnockback = RKnockback;
}
public void SetupSignals()
{
CDamageable.DamageTaken += ReduceHealth;
CDamageable.DamageTaken += RegisterKnockback;
CHealth.HealthDepleted += Kill;
}
public override void _PhysicsProcess(double delta)
{
// Only trigger gameplay related effects on specific frames
if(Engine.GetPhysicsFrames() % 10 == 0) ProcessGameplay(delta);
var targetPlanar = new Vector3(Target.GlobalPosition.X, GlobalPosition.Y, Target.GlobalPosition.Z);
LookAt(targetPlanar);
var inputs = new MovementInputs(
Velocity: Velocity,
TargetLocation: Target.GlobalPosition,
isOnFloor: IsOnFloor(),
gravity: GetGravity(),
delta: delta
);
Velocity = ComputeVelocity(inputs);
Velocity += ComputeKnockback();
MoveAndSlide();
}
public void ProcessGameplay(double delta)
{
var bodies = _damageBox.GetOverlappingBodies();
foreach (var body in bodies)
{
if(body is IDamageable spawnable)
spawnable.TakeDamage(new DamageRecord(this, RDamage));
}
}
public Vector3 ComputeVelocity(MovementInputs inputs)
{
if (CMovement is null) return Vector3.Zero;
return CMovement.ComputeVelocity(inputs);
}
public DamageRecord TakeDamage(DamageRecord damageRecord)
{
if (CDamageable is null)
return damageRecord with { Damage = new RDamage(0, damageRecord.Damage.DamageType) };
var finalDamage = CDamageable.TakeDamage(damageRecord);
DamageTaken?.Invoke(this, finalDamage);
return finalDamage;
}
public void ReduceHealth(IDamageable source, DamageRecord damageRecord)
{
if (CHealth is null) return;
CHealth.ReduceHealth(source, damageRecord);
HealthChanged?.Invoke(this, CHealth.CurrentHealth);
}
public void Kill(IHealthable source)
{
foreach (var killable in DeathEffects.ToIKillables())
{
killable.Kill(source);
}
QueueFree();
}
public void RegisterKnockback(IDamageable source, DamageRecord damageRecord)
{
if (CKnockback is null) return;
CKnockback.RegisterKnockback(source, damageRecord);
}
public Vector3 ComputeKnockback()
{
if (CKnockback is null) return Vector3.Zero;
return CKnockback.ComputeKnockback();
}
public Vector3 GetTargetGlobalPosition()
{
var target = GetNode<Node3D>("CTarget");
if (target is null) return GlobalPosition;
return target.GlobalPosition;
}
}