Change all enemy types to CharacterBody3D

This commit is contained in:
2025-02-25 19:55:04 -08:00
parent 0d7242c96d
commit 282a2eddb0
13 changed files with 179 additions and 207 deletions

View File

@@ -30,6 +30,9 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
#region Exports
[Export] protected EnemyStatResource _enemyStatResource { get; set; } = default!;
[Export]
private float _movementSpeed = 2f;
#endregion
#region Node Dependencies
@@ -60,6 +63,7 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
private Timer _thinkTimer;
#region Godot methods
public void Setup()
{
_enemyLogic = new EnemyLogic();
@@ -81,93 +85,6 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
_thinkTimer.Start();
}
private void NavAgent_TargetReached()
{
NavAgent.TargetPosition = _currentTarget;
}
private void NavAgent_VelocityComputed(Vector3 safeVelocity)
{
if (CurrentHP <= 0)
return;
_knockbackStrength = _knockbackStrength * 0.9f;
Velocity = safeVelocity + (_knockbackDirection * _knockbackStrength);
MoveAndSlide();
}
public void SetTarget(Vector3 target)
{
Task.Delay(TimeSpan.FromSeconds(1.5)).ContinueWith(_ => _currentTarget = new Vector3(target.X, -1.75f, target.Z));
}
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false, bool ignoreDefense = false, bool ignoreElementalResistance = false)
{
if (_currentHP.Value > 0)
{
if (!ignoreElementalResistance)
damage = CalculateElementalResistance(damage, elementType);
if (!ignoreDefense)
damage = CalculateDefenseResistance(damage);
if (isCriticalHit)
damage *= 2;
GD.Print($"Enemy Hit for {damage} damage.");
_currentHP.OnNext(_currentHP.Value - damage);
GD.Print("Current HP: " + _currentHP.Value);
if (_currentHP.Value <= 0)
return;
EnemyModelView.PlayHitAnimation();
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
NavAgent_TargetReached();
if (Player.EquippedWeapon.Value.WeaponTags.Contains(WeaponTag.SelfDamage))
Player.Stats.SetCurrentHP(Player.Stats.CurrentHP.Value - 5);
}
}
public override void _PhysicsProcess(double delta)
{
if (CurrentHP <= 0)
return;
if (_enemyLogic.Value is EnemyLogic.State.Attacking)
SetTarget(GlobalPosition);
var nextPathPosition = NavAgent.GetNextPathPosition();
var newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * 2f;
if (NavAgent.AvoidanceEnabled)
NavAgent.Velocity = newVelocity;
else
NavAgent_VelocityComputed(newVelocity);
var lookDir = GlobalPosition + Velocity;
if (_enemyLogic.Value is not EnemyLogic.State.Attacking && (!lookDir.IsEqualApprox(GlobalPosition) || !Velocity.IsZeroApprox()))
LookAt(lookDir, Vector3.Up, true);
var isWalking = _enemyLogic.Value is EnemyLogic.State.Patrolling or EnemyLogic.State.FollowPlayer;
EnemyModelView.RotateModel(GlobalTransform.Basis, -Player.CurrentBasis.Z, isWalking);
}
public void Knockback(float impulse, Vector3 direction)
{
_knockbackDirection = direction;
_knockbackStrength = 0.3f;
}
public void Die()
{
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
CollisionShape.SetDeferred("disabled", true);
EnemyModelView.PlayDeathAnimation();
var tweener = GetTree().CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
GameEventDepot.OnEnemyDefeated(GlobalPosition, _enemyStatResource);
}
public void OnResolved()
{
EnemyBinding = _enemyLogic.Bind();
@@ -194,10 +111,77 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
PatrolTimer.WaitTime = rng.RandfRange(7.0f, 15.0f);
}
public void OnExitTree()
public override void _PhysicsProcess(double delta)
{
_enemyLogic.Stop();
EnemyBinding.Dispose();
CalculateVelocity();
}
#endregion
public virtual void TakeAction()
{
}
public void SetTarget(Vector3 target)
{
Task.Delay(TimeSpan.FromSeconds(1.5)).ContinueWith(_ => _currentTarget = new Vector3(target.X, -1.75f, target.Z));
}
public virtual void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false, bool ignoreDefense = false, bool ignoreElementalResistance = false)
{
if (_currentHP.Value > 0)
{
if (!ignoreElementalResistance)
damage = CalculateElementalResistance(damage, elementType);
if (!ignoreDefense)
damage = CalculateDefenseResistance(damage);
if (isCriticalHit)
damage *= 2;
GD.Print($"Enemy Hit for {damage} damage.");
_currentHP.OnNext(_currentHP.Value - damage);
GD.Print("Current HP: " + _currentHP.Value);
if (_currentHP.Value <= 0)
return;
EnemyModelView.PlayHitAnimation();
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
NavAgent_TargetReached();
if (Player.EquippedWeapon.Value.WeaponTags.Contains(WeaponTag.SelfDamage))
Player.Stats.SetCurrentHP(Player.Stats.CurrentHP.Value - 5);
}
}
public void Knockback(float impulse, Vector3 direction)
{
_knockbackDirection = direction;
_knockbackStrength = 0.3f;
}
public void Die()
{
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
CollisionShape.SetDeferred("disabled", true);
EnemyModelView.PlayDeathAnimation();
var tweener = GetTree().CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
GameEventDepot.OnEnemyDefeated(GlobalPosition, _enemyStatResource);
}
private void NavAgent_TargetReached()
{
NavAgent.TargetPosition = _currentTarget;
}
private void NavAgent_VelocityComputed(Vector3 safeVelocity)
{
if (CurrentHP <= 0)
return;
_knockbackStrength = _knockbackStrength * 0.9f;
Velocity = safeVelocity + (_knockbackDirection * _knockbackStrength);
MoveAndSlide();
}
private void OnPatrolTimeout()
@@ -221,6 +205,30 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
}
private void CalculateVelocity()
{
if (CurrentHP <= 0)
return;
if (_enemyLogic.Value is EnemyLogic.State.Attacking)
SetTarget(GlobalPosition);
var nextPathPosition = NavAgent.GetNextPathPosition();
var newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * 2f;
if (NavAgent.AvoidanceEnabled)
NavAgent.Velocity = newVelocity;
else
NavAgent_VelocityComputed(newVelocity);
var lookDir = GlobalPosition + Velocity;
if (_enemyLogic.Value is not EnemyLogic.State.Attacking && (!lookDir.IsEqualApprox(GlobalPosition) || !Velocity.IsZeroApprox()))
LookAt(lookDir, Vector3.Up, true);
var isWalking = _enemyLogic.Value is EnemyLogic.State.Patrolling or EnemyLogic.State.FollowPlayer;
EnemyModelView.RotateModel(GlobalTransform.Basis, -Player.CurrentBasis.Z, isWalking);
}
private void OnAttackTimeout()
{
if (GlobalPosition.DistanceTo(Player.CurrentPosition) > 5f)
@@ -284,7 +292,9 @@ public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
return Mathf.Max(incomingDamage - _enemyStatResource.CurrentDefense, 0.0);
}
public virtual void TakeAction()
public void OnExitTree()
{
_enemyLogic.Stop();
EnemyBinding.Dispose();
}
}