using Chickensoft.AutoInject; using Chickensoft.Collections; using Chickensoft.GodotNodeInterfaces; using Chickensoft.Introspection; using Godot; namespace GameJamDungeon { public interface IBoss : ICharacterBody3D { public AnimationTree AnimationTree { get; } public AnimationPlayer HitAnimation { get; } public Timer AttackTimer { get; } public void Activate(); public AutoProp CurrentHP { get; } } [Meta(typeof(IAutoNode))] public partial class Boss : CharacterBody3D, IBoss, IProvide { public override void _Notification(int what) => this.Notify(what); public IBossLogic BossLogic { get; set; } = default!; [Export] public EnemyStatResource BossResource { get; set; } = default!; IBossLogic IProvide.Value() => BossLogic; public BossLogic.IBinding BossBinding { get; set; } = default!; [Dependency] public IGameRepo GameRepo => this.DependOn(); [Node] public AnimationTree AnimationTree { get; set; } = default!; [Node] public Timer AttackTimer { get; set; } = default!; [Node] public AnimationPlayer HitAnimation { get; set; } = default!; [Node] public Area3D Hitbox { get; set; } = default!; public AutoProp CurrentHP { get; set; } public void Setup() { BossLogic = new BossLogic(); BossLogic.Set(this as IBoss); BossLogic.Set(GameRepo); SetPhysicsProcess(false); Hide(); } public void OnResolved() { BossBinding = BossLogic.Bind(); BossBinding .Handle((in BossLogic.Output.Defeated output) => { HitAnimation.Play("Defeated"); }); this.Provide(); BossLogic.Start(); CurrentHP = new AutoProp(BossResource.MaximumHP); CurrentHP.Sync += OnHPChanged; AttackTimer.Timeout += AttackTimer_Timeout; Hitbox.AreaEntered += Hitbox_AreaEntered; HitAnimation.AnimationFinished += HitAnimation_AnimationFinished; } private void HitAnimation_AnimationFinished(StringName animName) { if (animName == "Defeated") QueueFree(); } private void Hitbox_AreaEntered(Area3D area) { if (area is IHitbox) { var isCriticalHit = false; var rng = new RandomNumberGenerator(); rng.Randomize(); var roll = rng.Randf(); if (roll <= GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.Luck) isCriticalHit = true; var damage = DamageCalculator.CalculateWeaponAttackDamage(GameRepo.PlayerData.CurrentAttack.Value + GameRepo.PlayerData.BonusAttack, BossResource, GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats, isCriticalHit); GD.Print($"Enemy Hit for {damage} damage."); BossLogic.Input(new BossLogic.Input.HitByPlayer(damage)); } } public void Activate() { BossLogic.Input(new BossLogic.Input.Activate()); } private void AttackTimer_Timeout() { var random = new RandomNumberGenerator(); random.Randomize(); var selection = random.RandWeighted([0.2f, 0.8f]); if (selection == 0) BossLogic.Input(new BossLogic.Input.SecondaryAttack()); else BossLogic.Input(new BossLogic.Input.PrimaryAttack()); } public void OnPhysicsProcess(double delta) { BossLogic.Input(new BossLogic.Input.PhysicsTick(delta)); MoveAndSlide(); } private void OnHPChanged(double newHP) { if (newHP <= 0) BossLogic.Input(new BossLogic.Input.BossDefeated()); } } }