145 lines
3.7 KiB
C#
145 lines
3.7 KiB
C#
using Chickensoft.AutoInject;
|
|
using Chickensoft.Collections;
|
|
using Chickensoft.GodotNodeInterfaces;
|
|
using Chickensoft.Introspection;
|
|
using Godot;
|
|
using System;
|
|
using System.Linq;
|
|
|
|
namespace GameJamDungeon;
|
|
|
|
public interface IEnemy : ICharacterBody3D
|
|
{
|
|
public IEnemyLogic EnemyLogic { get; }
|
|
|
|
public AutoProp<double> CurrentHP { get; set; }
|
|
|
|
public EnemyStatInfo EnemyStatInfo { get; set; }
|
|
|
|
public NavigationAgent3D NavAgent { get; set; }
|
|
}
|
|
|
|
[Meta(typeof(IAutoNode))]
|
|
public partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLogic>
|
|
{
|
|
public override void _Notification(int what) => this.Notify(what);
|
|
|
|
public IEnemyLogic EnemyLogic { get; set; } = default!;
|
|
|
|
IEnemyLogic IProvide<IEnemyLogic>.Value() => EnemyLogic;
|
|
|
|
public EnemyLogic.IBinding EnemyBinding { get; set; } = default!;
|
|
|
|
[Dependency] IGameRepo GameRepo => this.DependOn<IGameRepo>();
|
|
|
|
[Export]
|
|
public EnemyStatInfo EnemyStatInfo { get; set; } = new();
|
|
|
|
public static PackedScene CollisionDetectorScene => GD.Load<PackedScene>("res://src/enemy/CollisionDetector.tscn");
|
|
|
|
public static Area3D CollisionDetector { get; set; } = default!;
|
|
|
|
public AutoProp<double> CurrentHP { get; set; }
|
|
|
|
[Node] public NavigationAgent3D NavAgent { get; set; } = default!;
|
|
|
|
public void Setup()
|
|
{
|
|
EnemyLogic = new EnemyLogic();
|
|
EnemyLogic.Set(EnemyStatInfo);
|
|
EnemyLogic.Set(this as IEnemy);
|
|
EnemyLogic.Set(GameRepo);
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
CurrentHP = new AutoProp<double>(EnemyStatInfo.MaximumHP);
|
|
CurrentHP.Sync += OnHPChanged;
|
|
}
|
|
|
|
public void OnResolved()
|
|
{
|
|
EnemyBinding = EnemyLogic.Bind();
|
|
|
|
EnemyBinding
|
|
.Handle((in EnemyLogic.Output.MovementComputed output) =>
|
|
{
|
|
var spriteNode = GetChildren().OfType<AnimatedSprite3D>();
|
|
|
|
if (spriteNode.Any())
|
|
PlayMovementAnimations(spriteNode.Single(), Velocity);
|
|
|
|
MoveAndCollide(output.Velocity);
|
|
})
|
|
.Handle((in EnemyLogic.Output.Die output) =>
|
|
{
|
|
QueueFree();
|
|
})
|
|
.Handle((in EnemyLogic.Output.HitByPlayer output) =>
|
|
{
|
|
});
|
|
|
|
this.Provide();
|
|
|
|
EnemyLogic.Start();
|
|
}
|
|
|
|
public void PlayMovementAnimations(AnimatedSprite3D sprite, Vector3 velocity)
|
|
{
|
|
if (sprite != null && velocity.Length() > 0.2f)
|
|
{
|
|
var lookdir = (GlobalPosition).Normalized();
|
|
var sign = lookdir.Sign();
|
|
if (lookdir.MaxAxisIndex() == Vector3.Axis.X && sign.X == 1)
|
|
sprite.Play("walk_right");
|
|
if (lookdir.MaxAxisIndex() == Vector3.Axis.X && sign.X == -1)
|
|
sprite.Play("walk_left");
|
|
if (lookdir.MaxAxisIndex() == Vector3.Axis.Z && sign.Z == 1)
|
|
sprite.Play("walk_forward");
|
|
if (lookdir.MaxAxisIndex() == Vector3.Axis.Z && sign.Z == -1)
|
|
sprite.Play("walk_backward");
|
|
}
|
|
if (sprite != null && velocity.IsZeroApprox())
|
|
sprite.Stop();
|
|
}
|
|
|
|
|
|
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 damage = DamageCalculator.CalculatePlayerDamage(hitBox.Damage, hitBox.GetParent<IPlayer>().PlayerStatInfo, EnemyStatInfo);
|
|
GD.Print($"Enemy Hit for {damage} damage.");
|
|
EnemyLogic.Input(new EnemyLogic.Input.HitByPlayer(damage));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnHPChanged(double newHP)
|
|
{
|
|
if (newHP <= 0)
|
|
EnemyLogic.Input(new EnemyLogic.Input.Killed());
|
|
}
|
|
|
|
public void OnReady()
|
|
{
|
|
SetPhysicsProcess(true);
|
|
CollisionDetector = CollisionDetectorScene.Instantiate<Area3D>();
|
|
CollisionDetector.AreaEntered += OnPlayerHitboxEntered;
|
|
AddChild(CollisionDetector);
|
|
}
|
|
|
|
public void OnExitTree()
|
|
{
|
|
EnemyLogic.Stop();
|
|
EnemyBinding.Dispose();
|
|
}
|
|
}
|