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 { #region Registration public override void _Notification(int what) => this.Notify(what); protected IEnemyLogic _enemyLogic { get; set; } = default!; IEnemyLogic IProvide.Value() => _enemyLogic; public EnemyLogic.IBinding EnemyBinding { get; set; } = default!; #endregion #region Dependencies [Dependency] protected IPlayer _player => this.DependOn(() => GetParent().GetChildren().OfType().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 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().ToList(); var spawnPointsGodotCollection = new Godot.Collections.Array(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)); } }