Files
GameJamDungeon/Zennysoft.Game.Ma/src/enemy/Enemy.cs

247 lines
6.9 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());
[Dependency] protected IGameRepo _gameRepo => this.DependOn<IGameRepo>();
#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;
[Export] public double AeolicResistance { get; set; }
[Export] public double HydricResistance { get; set; }
[Export] public double IgenousResistance { get; set; }
[Export] public double FerrumResistance { get; set; }
[Export] public double TelluricResistance { get; set; }
[Export] public double HolyResistance { get; set; }
[Export] public double CurseResistance { get; set; }
public ElementalResistanceSet ElementalResistanceSet => new ElementalResistanceSet(AeolicResistance, HydricResistance, IgenousResistance, FerrumResistance, TelluricResistance, HolyResistance, CurseResistance);
[Node] private AudioStreamPlayer3D _absorbSFX { get; set; } = default!;
[Node] private AudioStreamPlayer3D _hitSFX { get; set; } = default!;
[Node] private AudioStreamPlayer3D _morphSFX { get; set; } = default!;
[Node] private AudioStreamPlayer3D _dieSFX { get; set; } = default!;
[Node] private AudioStreamPlayer3D _aggroSFX { get; set; } = default!;
protected bool _activated = false;
private Vector3 _previousPosition = Vector3.Zero;
#region Godot methods
public void Setup()
{
_enemyLogic = new EnemyLogic();
_enemyLogic.Set(this as IEnemy);
_enemyLogic.Set(_player);
SetPhysicsProcess(true);
EnemyModelView.HitPlayer += EnemyModelView_HitPlayer;
EnemyBinding = _enemyLogic.Bind();
HealthComponent = new HealthComponent(InitialHP);
HealthComponent.HealthReachedZero += Die;
HealthComponent.DamageTaken += TakeHit;
AttackComponent = new AttackComponent(InitialAttack);
DefenseComponent = new DefenseComponent(InitialDefense);
EnemyBinding
.Handle((in EnemyLogic.Output.Activate _) =>
{
if (!_activated)
{
Activate();
_activated = true;
_aggroSFX.Play();
if (this is IHaveFollowBehavior)
_enemyLogic.Input(new EnemyLogic.Input.Follow());
if (this is IHaveFleeBehavior)
_enemyLogic.Input(new EnemyLogic.Input.Flee());
}
})
.Handle((in EnemyLogic.Output.Idle _) =>
{
Idle();
})
.Handle((in EnemyLogic.Output.Move _) =>
{
Move();
})
.Handle((in EnemyLogic.Output.ReturnToDefaultState _) =>
{
ReturnToDefaultState();
});
this.Provide();
_enemyLogic.Start();
_previousPosition = GlobalPosition;
}
#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();
_hitSFX.Play();
}
public virtual void Die()
{
SetPhysicsProcess(false);
_enemyLogic.Input(new EnemyLogic.Input.Defeated());
_player.ExperiencePointsComponent.Gain(ExpGiven);
EnemyModelView.PlayDeathAnimation();
_hitSFX.Play();
_dieSFX.Play();
_gameRepo.OnEnemyDied(this);
var tweener = CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
}
public void OnAbsorb()
{
_absorbSFX.Play();
}
public void OnMorph()
{
_morphSFX.Play();
SetPhysicsProcess(false);
_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();
HealthComponent.HealthReachedZero -= Die;
HealthComponent.DamageTaken -= TakeHit;
EnemyModelView.HitPlayer -= EnemyModelView_HitPlayer;
}
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 = new Vector3(randomSpawnPoint.GlobalPosition.X, GlobalPosition.Y, randomSpawnPoint.GlobalPosition.Z);
_previousPosition = GlobalPosition;
if (this is IHavePatrolBehavior patrolEnemy)
patrolEnemy.PatrolBehavior.HomePosition = GlobalPosition;
_enemyLogic.Input(new EnemyLogic.Input.Reset());
}
else
throw new NotImplementedException($"Only {nameof(MonsterRoom)} types are currently supported.");
}
protected void LookAtTarget(Vector3 targetPosition)
{
if (GlobalPosition.IsEqualApprox(targetPosition))
return;
var lookDirection = GlobalPosition - targetPosition;
var look = new Vector3(lookDirection.X, GlobalPosition.Y, lookDirection.Z);
if (GlobalPosition.DistanceTo(look) > 0.01)
LookAt(look, Vector3.Up);
}
protected void SetTarget(Vector3 targetPosition) => TargetPosition = targetPosition;
private void EnemyModelView_HitPlayer(object sender, EventArgs e)
{
_player.TakeDamage(new AttackData(AttackComponent.CurrentAttack.Value, ElementType.None));
}
}