Files
GameJamDungeon/Zennysoft.Game.Ma/src/enemy/Enemy.cs
2025-10-22 16:24:07 -07:00

188 lines
4.7 KiB
C#

using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using System;
using System.Collections.Immutable;
using System.Linq;
using Zennysoft.Ma.Adapter;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
{
#region Registration
public override void _Notification(int what) => this.Notify(what);
protected IEnemyLogic _enemyLogic { get; set; } = default!;
IEnemyLogic IProvide<IEnemyLogic>.Value() => _enemyLogic;
public EnemyLogic.IBinding EnemyBinding { get; set; } = default!;
#endregion
#region Dependencies
[Dependency] protected IPlayer _player => this.DependOn(() => GetParent().GetChildren().OfType<IPlayer>().Single());
#endregion
public IHealthComponent HealthComponent { get; private set; }
public IAttackComponent AttackComponent { get; private set; }
public IDefenseComponent DefenseComponent { get; private set; }
public virtual IEnemyModelView EnemyModelView { get; set; } = default!;
public Vector3 TargetPosition { get; private set; }
[ExportGroup("Enemy Stats")]
[Export] public int InitialHP { get; set; } = 50;
[Export] public int InitialAttack { get; set; } = 8;
[Export] public int InitialDefense { get; set; } = 8;
[Export] public int ExpGiven { get; set; } = 10;
public Enemy()
{
HealthComponent = new HealthComponent(InitialHP);
HealthComponent.HealthReachedZero += Die;
HealthComponent.DamageTaken += TakeHit;
AttackComponent = new AttackComponent(InitialAttack);
DefenseComponent = new DefenseComponent(InitialDefense);
}
#region Godot methods
public void Setup()
{
_enemyLogic = new EnemyLogic();
_enemyLogic.Set(this as IEnemy);
_enemyLogic.Set(_player);
SetPhysicsProcess(true);
EnemyModelView.HitPlayer += EnemyModelView_HitPlayer;
}
public void OnResolved()
{
EnemyBinding = _enemyLogic.Bind();
EnemyBinding
.Handle((in EnemyLogic.Output.Activate _) =>
{
Activate();
})
.Handle((in EnemyLogic.Output.Idle _) =>
{
Idle();
})
.Handle((in EnemyLogic.Output.Move _) =>
{
Move();
})
.Handle((in EnemyLogic.Output.ReturnToDefaultState _) =>
{
ReturnToDefaultState();
});
this.Provide();
_enemyLogic.Start();
}
#endregion
public virtual void Activate()
{
}
public virtual void Idle()
{
EnemyModelView.PlayIdleAnimation();
}
public virtual void Move()
{
EnemyModelView.PlayWalkAnimation();
}
public virtual void PerformAction()
{
EnemyModelView.PlayPrimaryAttackAnimation();
}
public virtual void ReturnToDefaultState()
{
}
public virtual void TakeHit()
{
_enemyLogic.Input(new EnemyLogic.Input.Alert());
EnemyModelView.PlayHitAnimation();
}
public virtual void Die()
{
SetPhysicsProcess(false);
_enemyLogic.Input(new EnemyLogic.Input.Defeated());
_player.ExperiencePointsComponent.Gain(ExpGiven);
EnemyModelView.PlayDeathAnimation();
var tweener = CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
}
public IDungeonRoom GetCurrentRoom(ImmutableList<IDungeonRoom> roomList)
{
foreach (var room in roomList)
{
var enemiesInCurrentRoom = room.EnemiesInRoom;
if (enemiesInCurrentRoom.Contains(this))
return room;
}
return null;
}
public void OnExitTree()
{
_enemyLogic.Stop();
EnemyBinding.Dispose();
}
public virtual void MoveEnemyToNewRoom(IDungeonRoom newRoom)
{
if (newRoom is MonsterRoom monsterRoom)
{
var spawnPoints = monsterRoom.EnemySpawnPoints.GetChildren().OfType<Marker3D>().ToList();
var spawnPointsGodotCollection = new Godot.Collections.Array<Marker3D>(spawnPoints);
var randomSpawnPoint = spawnPointsGodotCollection.PickRandom();
GlobalPosition = randomSpawnPoint.GlobalPosition;
if (this is IHavePatrolBehavior patrolEnemy)
patrolEnemy.PatrolBehavior.HomePosition = GlobalPosition;
_enemyLogic.Input(new EnemyLogic.Input.Reset());
}
throw new NotImplementedException($"Only {nameof(MonsterRoom)} types are currently supported.");
}
protected void LookAtTarget(Vector3 targetPosition)
{
var lookDirection = GlobalPosition - targetPosition;
if (lookDirection != GlobalPosition)
LookAt(new Vector3(lookDirection.X, GlobalPosition.Y, lookDirection.Z), Vector3.Up);
}
protected void SetTarget(Vector3 targetPosition) => TargetPosition = targetPosition;
private void EnemyModelView_HitPlayer(object sender, System.EventArgs e)
{
_player.TakeDamage(new Damage(AttackComponent.TotalAttack, ElementType.None, false, false, false));
}
}