Refactor Enemy
This commit is contained in:
@@ -10,119 +10,123 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
|
||||
public IEnemyLogic EnemyLogic { get; set; } = default!;
|
||||
private IEnemyLogic _enemyLogic { get; set; } = default!;
|
||||
|
||||
IEnemyLogic IProvide<IEnemyLogic>.Value() => EnemyLogic;
|
||||
IEnemyLogic IProvide<IEnemyLogic>.Value() => _enemyLogic;
|
||||
|
||||
public EnemyLogic.IBinding EnemyBinding { get; set; } = default!;
|
||||
|
||||
#region Dependencies
|
||||
|
||||
[Dependency] IGameRepo GameRepo => this.DependOn<IGameRepo>();
|
||||
|
||||
[Dependency] IGameEventDepot GameEventDepot => this.DependOn<IGameEventDepot>();
|
||||
#endregion
|
||||
|
||||
[Export]
|
||||
public EnemyStatResource EnemyStatResource { get; set; } = default!;
|
||||
#region Exports
|
||||
[Export] private EnemyStatResource _enemyStatResource { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
public static PackedScene CollisionDetectorScene => GD.Load<PackedScene>("res://src/enemy/CollisionDetector.tscn");
|
||||
#region Node Dependencies
|
||||
[Node] private NavigationAgent3D NavAgent { get; set; } = default!;
|
||||
|
||||
public static Area3D CollisionDetector { get; set; } = default!;
|
||||
[Node] private Area3D LineOfSight { get; set; } = default!;
|
||||
|
||||
public AutoProp<double> CurrentHP { get; set; }
|
||||
[Node] private Timer PatrolTimer { get; set; } = default!;
|
||||
|
||||
[Node] public NavigationAgent3D NavAgent { get; set; } = default!;
|
||||
[Node] private Timer AttackTimer { get; set; } = default!;
|
||||
|
||||
[Node] public Area3D LineOfSight { get; set; } = default!;
|
||||
[Node] private RayCast3D Raycast { get; set; } = default!;
|
||||
|
||||
[Node] public Timer PatrolTimer { get; set; } = default!;
|
||||
[Node] protected EnemyModelView EnemyModelView { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
[Node] public Timer AttackTimer { get; set; } = default!;
|
||||
public double CurrentHP => _currentHP.Value;
|
||||
|
||||
[Node] public RayCast3D Raycast { get; set; } = default!;
|
||||
|
||||
[Node] public EnemyModelView EnemyModelView { get; set; } = default!;
|
||||
private AutoProp<double> _currentHP { get; set; }
|
||||
|
||||
private float _knockbackStrength = 0.0f;
|
||||
|
||||
private Vector3 _knockbackDirection = Vector3.Zero;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
EnemyLogic = new EnemyLogic();
|
||||
EnemyLogic.Set(EnemyStatResource);
|
||||
EnemyLogic.Set(this as IEnemy);
|
||||
EnemyLogic.Set(GameRepo);
|
||||
_enemyLogic = new EnemyLogic();
|
||||
_enemyLogic.Set(_enemyStatResource);
|
||||
_enemyLogic.Set(this as IEnemy);
|
||||
_enemyLogic.Set(GameRepo);
|
||||
}
|
||||
|
||||
public void OnReady()
|
||||
{
|
||||
SetPhysicsProcess(true);
|
||||
CollisionDetector = CollisionDetectorScene.Instantiate<Area3D>();
|
||||
CollisionDetector.AreaEntered += OnPlayerHitboxEntered;
|
||||
AddChild(CollisionDetector);
|
||||
EnemyModelView.Hitbox.AreaEntered += Hitbox_AreaEntered;
|
||||
}
|
||||
|
||||
private void Hitbox_AreaEntered(Area3D area)
|
||||
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false, bool ignoreDefense = false, bool ignoreElementalResistance = false)
|
||||
{
|
||||
if (area.GetParent().GetParent() is IPlayer player)
|
||||
if (_currentHP.Value > 0)
|
||||
{
|
||||
var isCriticalHit = false;
|
||||
var rng = new RandomNumberGenerator();
|
||||
rng.Randomize();
|
||||
var roll = rng.Randf();
|
||||
if (roll <= EnemyStatResource.Luck)
|
||||
isCriticalHit = true;
|
||||
var damage = DamageCalculator.CalculateEnemyAttackDamage(
|
||||
GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
|
||||
EnemyStatResource,
|
||||
GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
|
||||
isCriticalHit);
|
||||
GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(damage));
|
||||
GD.Print($"Player hit for {damage} damage.");
|
||||
ApplyKnockback();
|
||||
|
||||
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);
|
||||
EnemyModelView.PlayHitAnimation();
|
||||
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToLocation(Vector3 target, float delta)
|
||||
{
|
||||
NavAgent.TargetPosition = target;
|
||||
var targetPosition = NavAgent.GetNextPathPosition();
|
||||
|
||||
var velocity = (targetPosition - GlobalTransform.Origin).Normalized() * 2f * delta;
|
||||
var lookAtDir = GlobalTransform.Origin - velocity;
|
||||
var lookAtPosition = new Vector3(lookAtDir.X, GlobalPosition.Y, lookAtDir.Z);
|
||||
if (GlobalPosition.DistanceTo(target) > 1.0f && !velocity.IsEqualApprox(Vector3.Zero) && !GlobalPosition.IsEqualApprox(lookAtPosition))
|
||||
LookAt(lookAtPosition);
|
||||
EnemyModelView.RotateModel(GlobalTransform.Basis, -GameRepo.PlayerGlobalTransform.Value.Basis.Z);
|
||||
_knockbackStrength = _knockbackStrength * 0.9f;
|
||||
MoveAndCollide(velocity + (_knockbackDirection * _knockbackStrength));
|
||||
}
|
||||
|
||||
public void Die()
|
||||
{
|
||||
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
|
||||
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();
|
||||
EnemyBinding = _enemyLogic.Bind();
|
||||
|
||||
EnemyBinding
|
||||
.Handle((in EnemyLogic.Output.MovementComputed output) =>
|
||||
.Handle((in EnemyLogic.Output.TakeAction _) =>
|
||||
{
|
||||
EnemyModelView.RotateModel(GlobalTransform.Basis, -GameRepo.PlayerGlobalTransform.Value.Basis.Z);
|
||||
_knockbackStrength = _knockbackStrength * 0.9f;
|
||||
MoveAndCollide(output.LinearVelocity + (_knockbackDirection * _knockbackStrength));
|
||||
})
|
||||
.Handle((in EnemyLogic.Output.HitByPlayer output) =>
|
||||
{
|
||||
EnemyModelView.PlayHitAnimation();
|
||||
// TODO: Make this an event to notify game that player hit someone
|
||||
if (GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.SelfDamage))
|
||||
GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - 5);
|
||||
if (GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.Knockback))
|
||||
{
|
||||
_knockbackDirection = -GameRepo.PlayerGlobalTransform.Value.Basis.Z.Normalized();
|
||||
_knockbackStrength = 0.3f;
|
||||
}
|
||||
})
|
||||
.Handle((in EnemyLogic.Output.Attack _) =>
|
||||
{
|
||||
EnemyModelView.PlayAttackAnimation();
|
||||
TakeAction();
|
||||
})
|
||||
.Handle((in EnemyLogic.Output.Defeated output) =>
|
||||
{
|
||||
EnemyModelView.PlayDeathAnimation();
|
||||
var tweener = GetTree().CreateTween();
|
||||
tweener.TweenInterval(1.0f);
|
||||
tweener.TweenCallback(Callable.From(QueueFree));
|
||||
GameEventDepot.OnEnemyDefeated(GlobalPosition, EnemyStatResource);
|
||||
Die();
|
||||
});
|
||||
|
||||
this.Provide();
|
||||
|
||||
EnemyLogic.Start();
|
||||
_enemyLogic.Start();
|
||||
|
||||
CurrentHP = new AutoProp<double>(EnemyStatResource.MaximumHP);
|
||||
CurrentHP.Sync += OnHPChanged;
|
||||
_currentHP = new AutoProp<double>(_enemyStatResource.MaximumHP);
|
||||
_currentHP.Sync += OnHPChanged;
|
||||
LineOfSight.BodyEntered += LineOfSight_BodyEntered;
|
||||
PatrolTimer.Timeout += OnPatrolTimeout;
|
||||
AttackTimer.Timeout += OnAttackTimeout;
|
||||
@@ -133,7 +137,7 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
|
||||
|
||||
public void OnExitTree()
|
||||
{
|
||||
EnemyLogic.Stop();
|
||||
_enemyLogic.Stop();
|
||||
EnemyBinding.Dispose();
|
||||
}
|
||||
|
||||
@@ -142,32 +146,13 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
|
||||
var rng = new RandomNumberGenerator();
|
||||
rng.Randomize();
|
||||
var randomizedSpot = new Vector3(rng.RandfRange(-7.0f, 7.0f), 0, rng.RandfRange(-7.0f, 7.0f));
|
||||
EnemyLogic.Input(new EnemyLogic.Input.PatrolToRandomSpot(GlobalPosition + randomizedSpot));
|
||||
_enemyLogic.Input(new EnemyLogic.Input.PatrolToRandomSpot(GlobalPosition + randomizedSpot));
|
||||
PatrolTimer.WaitTime = rng.RandfRange(5.0f, 10.0f);
|
||||
}
|
||||
|
||||
public void OnPhysicsProcess(double delta)
|
||||
{
|
||||
EnemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
|
||||
}
|
||||
|
||||
public void OnPlayerHitboxEntered(Area3D body)
|
||||
{
|
||||
if (body is IHitbox hitBox)
|
||||
{
|
||||
if (CurrentHP.Value > 0)
|
||||
{
|
||||
var isCriticalHit = false;
|
||||
var rng = new RandomNumberGenerator();
|
||||
rng.Randomize();
|
||||
var roll = rng.Randf();
|
||||
if (roll <= GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.Luck)
|
||||
isCriticalHit = true;
|
||||
var damage = DamageCalculator.CalculateWeaponAttackDamage(GameRepo.PlayerData.CurrentAttack.Value + GameRepo.PlayerData.BonusAttack, EnemyStatResource, GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats, isCriticalHit);
|
||||
GD.Print($"Enemy Hit for {damage} damage.");
|
||||
EnemyLogic.Input(new EnemyLogic.Input.HitByPlayer(damage));
|
||||
}
|
||||
}
|
||||
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
|
||||
}
|
||||
|
||||
private void OnAttackTimeout()
|
||||
@@ -177,7 +162,7 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
|
||||
|
||||
var rng = new RandomNumberGenerator();
|
||||
rng.Randomize();
|
||||
EnemyLogic.Input(new EnemyLogic.Input.AttackTimer());
|
||||
_enemyLogic.Input(new EnemyLogic.Input.AttackTimer());
|
||||
AttackTimer.Stop();
|
||||
AttackTimer.WaitTime = rng.RandfRange(2f, 5.0f);
|
||||
AttackTimer.Start();
|
||||
@@ -197,20 +182,49 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
|
||||
if (collider is IPlayer player)
|
||||
{
|
||||
Raycast.DebugShapeCustomColor = Color.FromString("Purple", Colors.Purple);
|
||||
EnemyLogic.Input(new EnemyLogic.Input.Alerted());
|
||||
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyKnockback()
|
||||
{
|
||||
if (GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.Knockback))
|
||||
{
|
||||
_knockbackDirection = -GameRepo.PlayerGlobalTransform.Value.Basis.Z.Normalized();
|
||||
_knockbackStrength = 0.3f;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHPChanged(double newHP)
|
||||
{
|
||||
if (newHP <= 0)
|
||||
EnemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
|
||||
Die();
|
||||
}
|
||||
|
||||
private void DoNothing()
|
||||
private double CalculateElementalResistance(double incomingDamage, ElementType incomingElementType)
|
||||
{
|
||||
if (incomingElementType == ElementType.Aeolic)
|
||||
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.AeolicResistance), 0.0);
|
||||
if (incomingElementType == ElementType.Hydric)
|
||||
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.HydricResistance), 0.0);
|
||||
if (incomingElementType == ElementType.Igneous)
|
||||
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.IgneousResistance), 0.0);
|
||||
if (incomingElementType == ElementType.Ferrum)
|
||||
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.FerrumResistance), 0.0);
|
||||
if (incomingElementType == ElementType.Telluric)
|
||||
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.TelluricResistance), 0.0);
|
||||
|
||||
return Mathf.Max(incomingDamage, 0.0);
|
||||
}
|
||||
|
||||
private double CalculateDefenseResistance(double incomingDamage)
|
||||
{
|
||||
return Mathf.Max(incomingDamage - _enemyStatResource.CurrentDefense, 0.0);
|
||||
}
|
||||
|
||||
public virtual void TakeAction()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user