Update enemy move/attack logic

This commit is contained in:
2025-09-23 23:21:02 -07:00
parent e526fdd1c3
commit 7c2d61a36a
19 changed files with 82 additions and 136 deletions

View File

@@ -167,6 +167,11 @@ public partial class BossTypeA : CharacterBody3D, IEnemy, IHasPrimaryAttack, IHa
_attackTimer.Timeout += OnAttackTimeout;
}
public void StopAttackTimer()
{
_attackTimer.Timeout -= OnAttackTimeout;
}
public void SetTarget(Vector3 target) => _target = target;
private void Hitbox_AreaEntered(Area3D area)

View File

@@ -0,0 +1,25 @@
using Godot;
namespace Zennysoft.Game.Ma;
public abstract partial class FollowsPlayerEnemy : Enemy
{
[Export]
public double StopMovingAndAttackDistance = 2.5f;
[Export]
public double StopFollowingPlayerDistance = 30f;
[Export]
public double StopAttackingAndMoveDistance = 3f;
public virtual void FollowPlayerAndAttack(IEnemyLogic enemyLogic, Vector3 currentPosition, Vector3 targetPosition)
{
if (enemyLogic.Value is EnemyLogic.State.FollowPlayer && currentPosition.DistanceTo(targetPosition) < StopMovingAndAttackDistance)
enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
else if (enemyLogic.Value is EnemyLogic.State.FollowPlayer && currentPosition.DistanceTo(targetPosition) > StopFollowingPlayerDistance)
enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
else if (enemyLogic.Value is EnemyLogic.State.Attacking && currentPosition.DistanceTo(targetPosition) > StopAttackingAndMoveDistance)
enemyLogic.Input(new EnemyLogic.Input.Alerted());
}
}

View File

@@ -0,0 +1 @@
uid://bh7g8gb2gdkdp

View File

@@ -23,6 +23,8 @@ public interface IEnemy : IKillable
public void StartAttackTimer();
public void StopAttackTimer();
public abstract void SetTarget(Vector3 target);
public void SetEnemyGlobalPosition(Vector3 target);

View File

@@ -12,3 +12,4 @@ path_max_distance = 3.01
avoidance_enabled = true
radius = 1.5
time_horizon_obstacles = 1.0
debug_enabled = true

View File

@@ -7,7 +7,7 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Sproingy : Enemy, IHasPrimaryAttack, ICanPatrol
public partial class Sproingy : FollowsPlayerEnemy, IHasPrimaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -27,19 +27,8 @@ public partial class Sproingy : Enemy, IHasPrimaryAttack, ICanPatrol
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is not EnemyLogic.State.Activated)
return;
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 3f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
else if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 30f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
else if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -28,7 +28,7 @@ radius = 0.106078
height = 1.23076
[sub_resource type="SphereShape3D" id="SphereShape3D_8vcnq"]
radius = 0.57308
radius = 0.365183
[sub_resource type="CylinderShape3D" id="CylinderShape3D_jbgmx"]
height = 5.0

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,7 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Michael : Enemy, IHasPrimaryAttack, ICanPatrol
public partial class Michael : FollowsPlayerEnemy, IHasPrimaryAttack, ICanPatrol
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
@@ -25,16 +25,8 @@ public partial class Michael : Enemy, IHasPrimaryAttack, ICanPatrol
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 3f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 3f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}
@@ -48,6 +40,7 @@ public partial class Michael : Enemy, IHasPrimaryAttack, ICanPatrol
{
PrimaryAttack();
}
public void PrimaryAttack()
{
_enemyModelView.PlayPrimaryAttackAnimation();

View File

@@ -3,12 +3,13 @@ using Chickensoft.Introspection;
using Godot;
using System;
using System.Collections.Generic;
using Zennysoft.Game.Abstractions;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class FilthEater : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, IHasRangedAttack
public partial class FilthEater : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, IHasRangedAttack, ICanPatrol
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
@@ -20,6 +21,8 @@ public partial class FilthEater : Enemy, IHasPrimaryAttack, IHasSecondaryAttack,
[Export]
public double SecondaryAttackElementalDamageBonus { get; set; } = 1.0;
[Node] private INavigationAgentClient _navigationAgentClient { get; set; } = default!;
public void OnReady()
{
SetPhysicsProcess(true);
@@ -29,13 +32,9 @@ public partial class FilthEater : Enemy, IHasPrimaryAttack, IHasSecondaryAttack,
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}
public override void TakeAction()
@@ -60,6 +59,15 @@ public partial class FilthEater : Enemy, IHasPrimaryAttack, IHasSecondaryAttack,
_enemyModelView.PlaySecondaryAttackAnimation();
}
public void Patrol()
{
var rng = new RandomNumberGenerator();
rng.Randomize();
var randomizedSpot = new Vector3(rng.RandfRange(-5.0f, 5.0f), 0, rng.RandfRange(-5.0f, 5.0f));
_enemyLogic.Input(new EnemyLogic.Input.PatrolToRandomSpot(GlobalPosition + randomizedSpot));
_enemyLogic.Input(new EnemyLogic.Input.StartPatrol());
}
private void Hitbox_AreaEntered(Area3D area)
{
var target = area.GetOwner();

View File

@@ -9,7 +9,7 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Sara : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
public partial class Sara : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -33,19 +33,8 @@ public partial class Sara : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanP
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is not EnemyLogic.State.Activated)
return;
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 1.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 30f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 3f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -9,7 +9,7 @@ using Zennysoft.Game.Abstractions;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Ballos : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
public partial class Ballos : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -33,16 +33,8 @@ public partial class Ballos : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICa
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -7,7 +7,7 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Chariot : Enemy, IHasPrimaryAttack, ICanActivate, ICanPatrol
public partial class Chariot : FollowsPlayerEnemy, IHasPrimaryAttack, ICanActivate, ICanPatrol
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
@@ -23,13 +23,7 @@ public partial class Chariot : Enemy, IHasPrimaryAttack, ICanActivate, ICanPatro
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 4f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 25f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
if (_activated)
_movementSpeed = 0;

View File

@@ -7,14 +7,13 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Chinthe : Enemy, IHasPrimaryAttack, ICanPatrol, ICanActivate
public partial class Chinthe : FollowsPlayerEnemy, IHasPrimaryAttack, ICanPatrol, ICanActivate
{
public override void _Notification(int what) => this.Notify(what);
private const string PARAMETERS_PLAYBACK = "parameters/playback";
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
[Export]
public double PrimaryAttackElementalDamageBonus { get; set; } = 1.0;
@@ -30,19 +29,13 @@ public partial class Chinthe : Enemy, IHasPrimaryAttack, ICanPatrol, ICanActivat
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is not EnemyLogic.State.Activated)
return;
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 4.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
_navigationAgentClient.CalculateVelocity(GlobalPosition, EnemyModelView.CanMove);
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}
public override void Die()

View File

@@ -9,7 +9,7 @@ using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Ambassador : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
public partial class Ambassador : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -33,19 +33,8 @@ public partial class Ambassador : Enemy, IHasPrimaryAttack, IHasSecondaryAttack,
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is not EnemyLogic.State.Activated)
return;
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 1.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 30f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 3f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -9,7 +9,7 @@ using Zennysoft.Game.Abstractions;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class AgniDemon : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
public partial class AgniDemon : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -33,16 +33,8 @@ public partial class AgniDemon : Enemy, IHasPrimaryAttack, IHasSecondaryAttack,
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -9,7 +9,7 @@ using Zennysoft.Game.Abstractions;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Palan : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
public partial class Palan : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -33,16 +33,8 @@ public partial class Palan : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICan
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -9,7 +9,7 @@ using Zennysoft.Game.Abstractions;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class ShieldOfHeaven : Enemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
public partial class ShieldOfHeaven : FollowsPlayerEnemy, IHasPrimaryAttack, IHasSecondaryAttack, ICanPatrol
{
public override void _Notification(int what) => this.Notify(what);
@@ -33,16 +33,8 @@ public partial class ShieldOfHeaven : Enemy, IHasPrimaryAttack, IHasSecondaryAtt
public void OnPhysicsProcess(double delta)
{
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) < 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.StartAttacking());
if (_enemyLogic.Value is EnemyLogic.State.FollowPlayer && GlobalPosition.DistanceTo(_player.CurrentPosition) > 45f)
_enemyLogic.Input(new EnemyLogic.Input.LostPlayer());
if (_enemyLogic.Value is EnemyLogic.State.Attacking && GlobalPosition.DistanceTo(_player.CurrentPosition) > 2.5f)
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
FollowPlayerAndAttack(_enemyLogic, GlobalPosition, _player.CurrentPosition);
_navigationAgentClient.CalculateVelocity(GlobalPosition, true);
base._PhysicsProcess(delta);
}

View File

@@ -12,6 +12,7 @@ public partial class EnemyLogic
public Attacking()
{
OnAttach(() => Get<IEnemy>().StartAttackTimer());
OnDetach(() => Get<IEnemy>().StopAttackTimer());
}
}
}