Major Player refactor

This commit is contained in:
2025-02-07 02:29:50 -08:00
parent 0cdae88952
commit fe1a1e61ef
132 changed files with 2554 additions and 2478 deletions

View File

@@ -4,151 +4,149 @@ using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon
namespace GameJamDungeon;
public interface IBoss : ICharacterBody3D
{
public interface IBoss : ICharacterBody3D
public AnimationTree AnimationTree { get; }
public AnimationPlayer HitAnimation { get; }
public Timer AttackTimer { get; }
public void Activate();
public AutoProp<double> CurrentHP { get; }
}
[Meta(typeof(IAutoNode))]
public partial class Boss : CharacterBody3D, IBoss, IProvide<IBossLogic>
{
public override void _Notification(int what) => this.Notify(what);
public IBossLogic BossLogic { get; set; } = default!;
[Export] public EnemyStatResource BossResource { get; set; } = default!;
IBossLogic IProvide<IBossLogic>.Value() => BossLogic;
public BossLogic.IBinding BossBinding { get; set; } = default!;
[Dependency] public IGameRepo GameRepo => this.DependOn<IGameRepo>();
[Dependency] public IGameEventDepot GameEventDepot => this.DependOn<IGameEventDepot>();
[Dependency] public IGame Game => this.DependOn<IGame>();
[Dependency] public IPlayer Player => this.DependOn<IPlayer>();
[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!;
[Node] public Area3D AttackBox { get; set; } = default!;
[Node] public Area3D SecondaryAttackBox { get; set; } = default!;
public AutoProp<double> CurrentHP { get; set; }
public void Setup()
{
public AnimationTree AnimationTree { get; }
BossLogic = new BossLogic();
BossLogic.Set(this as IBoss);
BossLogic.Set(GameRepo);
public AnimationPlayer HitAnimation { get; }
public Timer AttackTimer { get; }
public void Activate();
public AutoProp<double> CurrentHP { get; }
SetPhysicsProcess(false);
Hide();
}
[Meta(typeof(IAutoNode))]
public partial class Boss : CharacterBody3D, IBoss, IProvide<IBossLogic>
public void OnResolved()
{
public override void _Notification(int what) => this.Notify(what);
public IBossLogic BossLogic { get; set; } = default!;
[Export] public EnemyStatResource BossResource { get; set; } = default!;
IBossLogic IProvide<IBossLogic>.Value() => BossLogic;
public BossLogic.IBinding BossBinding { get; set; } = default!;
[Dependency] public IGameRepo GameRepo => this.DependOn<IGameRepo>();
[Dependency] public IGameEventDepot GameEventDepot => this.DependOn<IGameEventDepot>();
[Dependency] public IGame Game => this.DependOn<IGame>();
[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!;
[Node] public Area3D AttackBox { get; set; } = default!;
[Node] public Area3D SecondaryAttackBox { get; set; } = default!;
public AutoProp<double> CurrentHP { get; set; }
public void Setup()
BossBinding = BossLogic.Bind();
BossBinding
.Handle((in BossLogic.Output.Defeated output) =>
{
BossLogic = new BossLogic();
BossLogic.Set(this as IBoss);
BossLogic.Set(GameRepo);
HitAnimation.Play("Defeated");
});
this.Provide();
BossLogic.Start();
CurrentHP = new AutoProp<double>(BossResource.MaximumHP);
CurrentHP.Sync += OnHPChanged;
AttackTimer.Timeout += AttackTimer_Timeout;
Hitbox.AreaEntered += Hitbox_AreaEntered;
HitAnimation.AnimationFinished += HitAnimation_AnimationFinished;
AttackBox.AreaEntered += AttackBox_AreaEntered;
SecondaryAttackBox.AreaEntered += SecondaryAttackBox_AreaEntered;
}
SetPhysicsProcess(false);
Hide();
}
private void AttackBox_AreaEntered(Area3D area)
{
//var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
// BossResource,
// GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
// false);
//GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(bossHitDamage));
}
public void OnResolved()
private void SecondaryAttackBox_AreaEntered(Area3D area)
{
//var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
// BossResource,
// GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
// false);
//var nerfDamage = bossHitDamage *= 0.25f;
//GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(nerfDamage));
Player.Knockback(115f);
}
private void HitAnimation_AnimationFinished(StringName animName)
{
if (animName == "Defeated")
QueueFree();
}
private void Hitbox_AreaEntered(Area3D area)
{
if (area is IHitbox)
{
BossBinding = BossLogic.Bind();
BossBinding
.Handle((in BossLogic.Output.Defeated output) =>
{
HitAnimation.Play("Defeated");
});
this.Provide();
BossLogic.Start();
CurrentHP = new AutoProp<double>(BossResource.MaximumHP);
CurrentHP.Sync += OnHPChanged;
AttackTimer.Timeout += AttackTimer_Timeout;
Hitbox.AreaEntered += Hitbox_AreaEntered;
HitAnimation.AnimationFinished += HitAnimation_AnimationFinished;
AttackBox.AreaEntered += AttackBox_AreaEntered;
SecondaryAttackBox.AreaEntered += SecondaryAttackBox_AreaEntered;
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= Player.Inventory.EquippedWeapon.Value.Luck)
isCriticalHit = true;
}
}
private void AttackBox_AreaEntered(Area3D area)
{
//var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
// BossResource,
// GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
// false);
//GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(bossHitDamage));
}
public void Activate()
{
BossLogic.Input(new BossLogic.Input.Activate());
}
private void SecondaryAttackBox_AreaEntered(Area3D area)
{
//var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
// BossResource,
// GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
// false);
//var nerfDamage = bossHitDamage *= 0.25f;
//GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(nerfDamage));
Game.Player.ApplyCentralImpulseToPlayer(GlobalBasis.Z.Normalized());
}
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());
}
private void HitAnimation_AnimationFinished(StringName animName)
{
if (animName == "Defeated")
QueueFree();
}
public void OnPhysicsProcess(double delta)
{
BossLogic.Input(new BossLogic.Input.PhysicsTick(delta));
MoveAndSlide();
}
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());
}
private void OnHPChanged(double newHP)
{
if (newHP <= 0)
BossLogic.Input(new BossLogic.Input.BossDefeated());
}
}

View File

@@ -1,20 +1,19 @@
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
public static class Input
{
public static class Input
{
public readonly record struct Activate();
public readonly record struct Activate();
public readonly record struct PhysicsTick(double Delta);
public readonly record struct PhysicsTick(double Delta);
public readonly record struct PrimaryAttack();
public readonly record struct PrimaryAttack();
public readonly record struct SecondaryAttack();
public readonly record struct SecondaryAttack();
public readonly record struct HitByPlayer(double Damage);
public readonly record struct HitByPlayer(double Damage);
public readonly record struct BossDefeated();
}
public readonly record struct BossDefeated();
}
}

View File

@@ -1,12 +1,11 @@
namespace GameJamDungeon
{
public partial class BossLogic
{
public static class Output
{
public readonly record struct HitByPlayer(double CurrentHP);
namespace GameJamDungeon;
public readonly record struct Defeated();
}
public partial class BossLogic
{
public static class Output
{
public readonly record struct HitByPlayer(double CurrentHP);
public readonly record struct Defeated();
}
}

View File

@@ -1,16 +1,15 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
[Meta]
public abstract partial record State : StateLogic<State>
{
[Meta]
public abstract partial record State : StateLogic<State>
protected State()
{
protected State()
{
}
}
}
}

View File

@@ -1,14 +1,13 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
namespace GameJamDungeon
{
public interface IBossLogic : ILogicBlock<BossLogic.State>;
namespace GameJamDungeon;
[Meta, Id("boss_logic")]
[LogicBlock(typeof(State), Diagram = true)]
public partial class BossLogic : LogicBlock<BossLogic.State>, IBossLogic
{
public override Transition GetInitialState() => To<State.Idle>();
}
public interface IBossLogic : ILogicBlock<BossLogic.State>;
[Meta, Id("boss_logic")]
[LogicBlock(typeof(State), Diagram = true)]
public partial class BossLogic : LogicBlock<BossLogic.State>, IBossLogic
{
public override Transition GetInitialState() => To<State.Idle>();
}

View File

@@ -1,32 +1,31 @@
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
public partial record State
{
public partial record State
[Meta, Id("boss_logic_state_alive")]
public abstract partial record Alive : State, IGet<Input.HitByPlayer>, IGet<Input.BossDefeated>
{
[Meta, Id("boss_logic_state_alive")]
public abstract partial record Alive : State, IGet<Input.HitByPlayer>, IGet<Input.BossDefeated>
{
}
}
public Transition On(in Input.HitByPlayer input)
{
var enemy = Get<IBoss>();
enemy.HitAnimation.Play("Hit");
enemy.CurrentHP.OnNext(enemy.CurrentHP.Value - input.Damage);
GD.Print("Current HP: " + enemy.CurrentHP.Value);
Output(new Output.HitByPlayer());
return ToSelf();
}
public Transition On(in Input.HitByPlayer input)
{
var enemy = Get<IBoss>();
enemy.HitAnimation.Play("Hit");
enemy.CurrentHP.OnNext(enemy.CurrentHP.Value - input.Damage);
GD.Print("Current HP: " + enemy.CurrentHP.Value);
Output(new Output.HitByPlayer());
return ToSelf();
}
public Transition On(in Input.BossDefeated input)
{
Output(new Output.Defeated());
return To<Defeated>();
}
public Transition On(in Input.BossDefeated input)
{
Output(new Output.Defeated());
return To<Defeated>();
}
}
}

View File

@@ -1,34 +1,33 @@
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
public partial record State
{
public partial record State
[Meta, Id("boss_logic_state_approach_player")]
public partial record ApproachPlayer : Alive, IGet<Input.PhysicsTick>
{
[Meta, Id("boss_logic_state_approach_player")]
public partial record ApproachPlayer : Alive, IGet<Input.PhysicsTick>
public Transition On(in Input.PhysicsTick input)
{
public Transition On(in Input.PhysicsTick input)
{
var gameRepo = Get<IGameRepo>();
var boss = Get<IBoss>();
var delta = (float)input.Delta;
var boss = Get<IBoss>();
var player = Get<IPlayer>();
var delta = (float)input.Delta;
var playerPosition = new Vector3(gameRepo.PlayerGlobalPosition.Value.X, boss.GlobalPosition.Y, gameRepo.PlayerGlobalPosition.Value.Z);
var playerPosition = new Vector3(player.CurrentPosition.X, boss.GlobalPosition.Y, player.CurrentPosition.Z);
if (boss.GlobalPosition.DistanceTo(gameRepo.PlayerGlobalPosition.Value) <= 5.0f)
return To<EngagePlayer>();
if (boss.GlobalPosition.DistanceTo(player.CurrentPosition) <= 5.0f)
return To<EngagePlayer>();
var moveToward = boss.GlobalPosition.MoveToward(playerPosition, (float)delta * 3f);
boss.GlobalPosition = moveToward;
var moveToward = boss.GlobalPosition.MoveToward(playerPosition, (float)delta * 3f);
boss.GlobalPosition = moveToward;
var targetDirection = boss.GlobalPosition - gameRepo.PlayerGlobalPosition.Value;
boss.GlobalRotation = new Vector3(boss.GlobalRotation.X, Mathf.LerpAngle(boss.GlobalRotation.Y, Mathf.Atan2(-targetDirection.X, -targetDirection.Z), delta * 3f), boss.GlobalRotation.Z);
boss.AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("Walk");
return ToSelf();
}
var targetDirection = boss.GlobalPosition - player.CurrentPosition;
boss.GlobalRotation = new Vector3(boss.GlobalRotation.X, Mathf.LerpAngle(boss.GlobalRotation.Y, Mathf.Atan2(-targetDirection.X, -targetDirection.Z), delta * 3f), boss.GlobalRotation.Z);
boss.AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("Walk");
return ToSelf();
}
}
}

View File

@@ -1,15 +1,14 @@
using Chickensoft.Introspection;
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
public partial record State
{
public partial record State
[Meta, Id("boss_logic_state_defeated")]
public partial record Defeated : State
{
[Meta, Id("boss_logic_state_defeated")]
public partial record Defeated : State
{
}
}
}
}

View File

@@ -1,47 +1,46 @@
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
public partial record State
{
public partial record State
[Meta, Id("boss_logic_state_engage_player")]
public partial record EngagePlayer : Alive, IGet<Input.PhysicsTick>, IGet<Input.PrimaryAttack>, IGet<Input.SecondaryAttack>
{
[Meta, Id("boss_logic_state_engage_player")]
public partial record EngagePlayer : Alive, IGet<Input.PhysicsTick>, IGet<Input.PrimaryAttack>, IGet<Input.SecondaryAttack>
public EngagePlayer()
{
public EngagePlayer()
{
OnAttach(() => Get<IBoss>().AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("Idle"));
}
OnAttach(() => Get<IBoss>().AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("Idle"));
}
public Transition On(in Input.PhysicsTick input)
{
var gameRepo = Get<IGameRepo>();
var boss = Get<IBoss>();
var delta = (float)input.Delta;
var playerPosition = new Vector3(gameRepo.PlayerGlobalPosition.Value.X, boss.GlobalPosition.Y, gameRepo.PlayerGlobalPosition.Value.Z);
var targetDirection = boss.GlobalPosition - gameRepo.PlayerGlobalPosition.Value;
boss.GlobalRotation = new Vector3(boss.GlobalRotation.X, Mathf.LerpAngle(boss.GlobalRotation.Y, Mathf.Atan2(-targetDirection.X, -targetDirection.Z), delta * 3f), boss.GlobalRotation.Z);
if (boss.GlobalPosition.DistanceTo(gameRepo.PlayerGlobalPosition.Value) > 5.0f)
return To<ApproachPlayer>();
public Transition On(in Input.PhysicsTick input)
{
var boss = Get<IBoss>();
var player = Get<IPlayer>();
var delta = (float)input.Delta;
var playerPosition = new Vector3(player.CurrentPosition.X, boss.GlobalPosition.Y, player.CurrentPosition.Z);
var targetDirection = boss.GlobalPosition - player.CurrentPosition;
boss.GlobalRotation = new Vector3(boss.GlobalRotation.X, Mathf.LerpAngle(boss.GlobalRotation.Y, Mathf.Atan2(-targetDirection.X, -targetDirection.Z), delta * 3f), boss.GlobalRotation.Z);
if (boss.GlobalPosition.DistanceTo(player.CurrentPosition) > 5.0f)
return To<ApproachPlayer>();
return ToSelf();
}
return ToSelf();
}
public Transition On(in Input.PrimaryAttack input)
{
var boss = Get<IBoss>();
boss.AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("PrimaryAttack");
return ToSelf();
}
public Transition On(in Input.PrimaryAttack input)
{
var boss = Get<IBoss>();
boss.AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("PrimaryAttack");
return ToSelf();
}
public Transition On(in Input.SecondaryAttack input)
{
var boss = Get<IBoss>();
boss.AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("SecondaryAttack");
return ToSelf();
}
public Transition On(in Input.SecondaryAttack input)
{
var boss = Get<IBoss>();
boss.AnimationTree.Get("parameters/playback").As<AnimationNodeStateMachinePlayback>().Travel("SecondaryAttack");
return ToSelf();
}
}
}

View File

@@ -1,29 +1,28 @@
using Chickensoft.Introspection;
namespace GameJamDungeon
namespace GameJamDungeon;
public partial class BossLogic
{
public partial class BossLogic
public partial record State
{
public partial record State
[Meta, Id("boss_logic_state_idle")]
public partial record Idle : Alive, IGet<Input.Activate>
{
[Meta, Id("boss_logic_state_idle")]
public partial record Idle : Alive, IGet<Input.Activate>
public Idle()
{
public Idle()
OnDetach(() =>
{
OnDetach(() =>
{
var boss = Get<IBoss>();
boss.SetPhysicsProcess(true);
boss.Show();
boss.AttackTimer.Start();
}
);
}
public Transition On(in Input.Activate input)
{
return To<ApproachPlayer>();
var boss = Get<IBoss>();
boss.SetPhysicsProcess(true);
boss.Show();
boss.AttackTimer.Start();
}
);
}
public Transition On(in Input.Activate input)
{
return To<ApproachPlayer>();
}
}
}