Refactor Player class to use components, also use components in Enemy class types and fiddle with boss structure

This commit is contained in:
2025-10-22 02:41:08 -07:00
parent 44fd8c82b0
commit 6ec45c4805
85 changed files with 941 additions and 1449 deletions

View File

@@ -6,6 +6,7 @@ using Chickensoft.SaveFileBuilder;
using Godot;
using SimpleInjector;
using System;
using Zennysoft.Game.Implementation.Components;
using Zennysoft.Ma.Adapter;
using Zennysoft.Ma.Adapter.Entity;
@@ -26,14 +27,22 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
public ISaveChunk<PlayerData> PlayerChunk { get; set; } = default!;
#endregion
public double CurrentHP => Stats.CurrentHP.Value;
public HealthComponent HealthComponent { get; private set; }
public VTComponent VTComponent { get; private set; }
public AttackComponent AttackComponent { get; private set; }
public DefenseComponent DefenseComponent { get; private set; }
public ExperiencePointsComponent ExperiencePointsComponent { get; private set; }
public LuckComponent LuckComponent { get; private set; }
public Vector3 CurrentPosition => GlobalPosition;
public Basis CurrentBasis => Transform.Basis;
public PlayerStats Stats { get; set; } = default!;
public IInventory Inventory { get; private set; } = default!;
public AutoProp<EquipableItem> EquippedWeapon => _equippedWeapon;
@@ -61,8 +70,25 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
#endregion
#region Exports
[Export]
private PlayerStatResource _playerStatResource { get; set; } = default!;
[ExportGroup("Movement")]
[Export(PropertyHint.Range, "0, 100, 0.1")]
public float RotationSpeed { get; set; } = 1.5f;
[Export(PropertyHint.Range, "0, 100, 0.1")]
public float MoveSpeed { get; set; } = 4f;
[Export(PropertyHint.Range, "0, 100, 0.1")]
public float Acceleration { get; set; } = 2f;
[ExportGroup("Player Stats")]
[Export(PropertyHint.Range, "1, 1000, 1")] public int InitialHP { get; set; } = 212;
[Export(PropertyHint.Range, "0, 1000, 1")] public int InitialVT { get; set; } = 116;
[Export(PropertyHint.Range, "1, 100, 1")] public int InitialAttack { get; set; } = 16;
[Export(PropertyHint.Range, "1, 100, 1")] public int InitialDefense { get; set; } = 12;
[Export(PropertyHint.Range, "1, 100, 1")] public int InitialLuck { get; set; } = 8;
[Export]
private bool HealthTimerIsActive = false;
@@ -96,15 +122,9 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
public void InitializePlayerState()
{
Inventory = new Inventory();
Stats = InitializePlayerStats();
SetProcessInput(false);
SetPhysicsProcess(false);
EquippedWeapon.Changed += EquippedWeapon_Sync;
EquippedArmor.Changed += EquippedArmor_Sync;
EquippedAccessory.Changed += EquippedAccessory_Sync;
Stats.CurrentHP.Changed += CurrentHP_Sync;
Stats.CurrentExp.Changed += CurrentEXP_Sync;
HealthTimer.WaitTime = _healthTimerWaitTime;
HealthTimer.Timeout += OnHealthTimerTimeout;
}
@@ -113,46 +133,27 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
{
var container = new SimpleInjector.Container();
container.Register<IPlayerLogic, PlayerLogic>(Lifestyle.Singleton);
//container.Verify();
PlayerLogic = container.GetInstance<IPlayerLogic>();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
PlayerLogic.Set(Stats);
PlayerLogic.Set(_gameRepo);
Hitbox.AreaEntered += Hitbox_AreaEntered;
CollisionDetector.AreaEntered += CollisionDetector_AreaEntered;
AnimationPlayer.AnimationFinished += OnAnimationFinished;
}
public void OnResolved()
{
Settings = new PlayerLogic.Settings() { RotationSpeed = _playerStatResource.RotationSpeed, MoveSpeed = _playerStatResource.MoveSpeed, Acceleration = _playerStatResource.Acceleration };
Settings = new PlayerLogic.Settings() { RotationSpeed = RotationSpeed, MoveSpeed = MoveSpeed, Acceleration = Acceleration };
PlayerChunk = new SaveChunk<PlayerData>(
onSave: (chunk) => new PlayerData()
{
PlayerStats = Stats,
Inventory = Inventory
},
onLoad: (chunk, data) =>
{
Stats = new PlayerStats(
data.PlayerStats.CurrentHP,
data.PlayerStats.MaximumHP,
data.PlayerStats.CurrentVT,
data.PlayerStats.MaximumVT,
data.PlayerStats.CurrentAttack,
data.PlayerStats.BonusAttack,
data.PlayerStats.MaxAttack,
data.PlayerStats.CurrentDefense,
data.PlayerStats.BonusDefense,
data.PlayerStats.MaxDefense,
data.PlayerStats.CurrentExp,
data.PlayerStats.CurrentLevel,
data.PlayerStats.ExpToNextLevel,
data.PlayerStats.Luck);
Inventory = data.Inventory;
}
);
@@ -160,18 +161,6 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
PlayerBinding = PlayerLogic.Bind();
PlayerBinding
.Handle((in PlayerLogic.Output.Animations.Attack output) =>
{
if (PlayerIsHittingGeometry())
{
AnimationPlayer.Play("hit_wall");
_gameRepo.OnPlayerAttackedWall();
}
else
{
PlayAttackAnimation();
}
})
.Handle((in PlayerLogic.Output.ThrowItem output) =>
{
})
@@ -192,6 +181,14 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
public void OnReady()
{
SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2;
HealthComponent = new HealthComponent(InitialHP);
HealthComponent.HealthReachedZero += Die;
VTComponent = new VTComponent(InitialVT);
AttackComponent = new AttackComponent(InitialAttack);
DefenseComponent = new DefenseComponent(InitialDefense);
ExperiencePointsComponent = new ExperiencePointsComponent();
LuckComponent = new LuckComponent(InitialLuck);
}
#endregion
@@ -211,63 +208,15 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
public void Attack()
{
PlayerLogic.Input(new PlayerLogic.Input.Attack());
}
public void RaiseHP(int amountToRaise)
{
Stats.SetMaximumHP(Stats.MaximumHP.Value + amountToRaise);
Stats.SetCurrentHP(Stats.MaximumHP.Value);
_gameRepo.AnnounceMessageInInventory($"{amountToRaise}MAXHP Up.");
}
public void HealHP(int amountToRestore)
{
Stats.SetCurrentHP(Stats.CurrentHP.Value + amountToRestore);
var raiseString = amountToRestore == 1000 ? "MAX" : $"{amountToRestore}";
_gameRepo.AnnounceMessageInInventory($"{raiseString}HP Restored.");
}
public void RaiseVT(int amountToRaise)
{
if (Stats.CurrentVT == Stats.MaximumVT)
if (PlayerIsHittingGeometry())
{
Stats.SetMaximumVT(Stats.MaximumVT.Value + amountToRaise);
Stats.SetCurrentVT(Stats.MaximumVT.Value);
_gameRepo.AnnounceMessageInInventory($"{amountToRaise}MAXVT Up.");
AnimationPlayer.Play("hit_wall");
_gameRepo.OnPlayerAttackedWall();
}
else
{
PlayAttackAnimation();
}
}
public void HealVT(int amountToRestore)
{
Stats.SetCurrentVT(Stats.CurrentVT.Value + amountToRestore);
var raiseString = amountToRestore == 1000 ? "MAX" : $"{amountToRestore}";
_gameRepo.AnnounceMessageInInventory($"{raiseString}VT Restored.");
}
public void ModifyBonusAttack(int amount)
{
Stats.SetBonusAttack(Stats.BonusAttack.Value + amount);
}
public void ModifyBonusDefense(int amount)
{
Stats.SetBonusDefense(Stats.BonusDefense.Value + amount);
}
public void ModifyMaximumHP(int amount)
{
Stats.SetMaximumHP(Stats.MaximumHP.Value + amount);
}
public void ModifyMaximumVT(int amount)
{
Stats.SetMaximumVT(Stats.MaximumVT.Value + amount);
}
public void ModifyBonusLuck(double amount)
{
Stats.SetLuck(Stats.Luck.Value + amount);
}
public void SetHealthTimerStatus(bool isActive)
@@ -301,11 +250,8 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
public void TakeDamage(Damage damage)
{
if (Stats.CurrentHP.Value > 0)
{
var damageDone = DamageCalculator.CalculateDamage(damage, Stats.CurrentDefense.Value + Stats.BonusDefense.Value, ((Armor)_equippedArmor.Value).Stats.ElementalResistanceSet);
Stats.SetCurrentHP(Stats.CurrentHP.Value - damageDone);
}
var damageReceived = DamageCalculator.CalculateDamage(damage, DefenseComponent.TotalDefense, ((Armor)_equippedArmor.Value).Stats.ElementalResistanceSet);
HealthComponent.Damage(damageReceived);
}
public void Knockback(float impulse)
@@ -314,42 +260,22 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
_knockbackDirection = GlobalBasis.Z.Normalized();
}
public void GainExp(double expGained)
{
Stats.SetCurrentExp(Stats.CurrentExp.Value + expGained);
}
public void LevelUp()
{
var rng = new RandomNumberGenerator();
rng.Randomize();
var hpIncrease = rng.RandiRange(3, 6);
Stats.SetMaximumHP(Stats.MaximumHP.Value + hpIncrease);
var nextLevel = Stats.CurrentLevel.Value + 1;
var expToNextLevel = (int)(6.5 * nextLevel + 4.5 * Mathf.Pow(nextLevel, 2) + Mathf.Pow(nextLevel, 3));
var newCurrentExp = Mathf.Max(Stats.CurrentExp.Value - Stats.ExpToNextLevel.Value, 0);
Stats.SetCurrentLevel(nextLevel);
Stats.SetExpToNextLevel(expToNextLevel);
Stats.SetCurrentExp(newCurrentExp);
HealthComponent.RaiseMaximumHP(hpIncrease);
ExperiencePointsComponent.LevelUp();
}
public void Die()
{
EquippedWeapon.Sync -= EquippedWeapon_Sync;
EquippedArmor.Sync -= EquippedArmor_Sync;
EquippedAccessory.Sync -= EquippedAccessory_Sync;
Stats.CurrentHP.Sync -= CurrentHP_Sync;
Stats.CurrentExp.Sync -= CurrentEXP_Sync;
HealthTimer.WaitTime = _healthTimerWaitTime;
HealthTimer.Timeout -= OnHealthTimerTimeout;
SwordSlashAnimation.Stop();
SetProcessInput(false);
SetPhysicsProcess(false);
//Hitbox.AreaEntered -= Hitbox_AreaEntered;
//CollisionDetector.AreaEntered -= CollisionDetector_AreaEntered;
//AnimationPlayer.AnimationFinished -= OnAnimationFinished;
Game.GameOver();
}
@@ -405,23 +331,16 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
if (equipable is Weapon weapon)
{
weapon.IsEquipped = false;
ModifyBonusAttack(-weapon.Damage);
_equippedWeapon.OnNext(new Weapon());
}
else if (equipable is Armor armor)
{
armor.IsEquipped = false;
ModifyBonusDefense(-armor.Defense);
_equippedArmor.OnNext(new Armor());
}
else if (equipable is Accessory accessory)
{
accessory.IsEquipped = false;
ModifyMaximumHP(-accessory.MaxHPUp);
ModifyMaximumVT(-accessory.MaxVTUp);
ModifyBonusAttack(-accessory.ATKUp);
ModifyBonusDefense(-accessory.DEFUp);
ModifyBonusLuck(-accessory.LuckUp);
_equippedAccessory.OnNext(new Accessory());
}
else
@@ -442,21 +361,9 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
}
}
private static float LeftStrafeInputVector
{
get
{
return Input.GetActionStrength(GameInputs.StrafeLeft);
}
}
private static float LeftStrafeInputVector => Input.GetActionStrength(GameInputs.StrafeLeft);
private static float RightStrafeInputVector
{
get
{
return Input.GetActionStrength(GameInputs.StrafeRight);
}
}
private static float RightStrafeInputVector => Input.GetActionStrength(GameInputs.StrafeRight);
private void ThrowItem()
{
@@ -475,68 +382,28 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
_gameRepo.OnPlayerAttack();
}
private void OnAnimationFinished(StringName animation)
{
PlayerLogic.Input(new PlayerLogic.Input.AttackAnimationFinished());
}
private void OnExitTree()
{
PlayerLogic.Stop();
PlayerBinding.Dispose();
AnimationPlayer.AnimationFinished -= OnAnimationFinished;
}
private void OnPlayerPositionUpdated(Vector3 globalPosition) => GlobalPosition = globalPosition;
private void OnHealthTimerTimeout()
{
if (Stats.CurrentHP.Value <= 0)
return;
if (Stats.CurrentVT.Value > 0)
if (VTComponent.CurrentVT.Value > 0)
{
if (((Accessory)EquippedAccessory.Value).AccessoryTag == AccessoryTag.HalfVTConsumption)
{
reduceOnTick = !reduceOnTick;
}
Stats.SetCurrentHP(Stats.CurrentHP.Value + 1);
HealthComponent.Heal(1);
if (reduceOnTick)
Stats.SetCurrentVT(Stats.CurrentVT.Value - 1);
VTComponent.Reduce(1);
}
else
Stats.SetCurrentHP(Stats.CurrentHP.Value - 1);
}
private void EquippedWeapon_Sync(EquipableItem obj)
{
ModifyBonusAttack(((Weapon)obj).Damage);
}
private void EquippedArmor_Sync(EquipableItem obj)
{
ModifyBonusDefense(((Armor)obj).Defense);
}
private void EquippedAccessory_Sync(EquipableItem accessory)
{
ModifyMaximumHP(((Accessory)accessory).MaxHPUp);
ModifyMaximumVT(((Accessory)accessory).MaxVTUp);
ModifyBonusAttack(((Accessory)accessory).ATKUp);
ModifyBonusDefense(((Accessory)accessory).DEFUp);
ModifyBonusLuck(((Accessory)accessory).LuckUp);
}
private void CurrentHP_Sync(int newHealth)
{
if (newHealth <= 0)
Die();
}
private void CurrentEXP_Sync(double newExp)
{
if (Stats.CurrentExp.Value >= Stats.ExpToNextLevel.Value)
LevelUp();
HealthComponent.Damage(1);
}
private void Hitbox_AreaEntered(Area3D area)
@@ -548,17 +415,16 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
private void HitEnemy(IEnemy enemy)
{
var attackValue = Stats.CurrentAttack.Value + Stats.BonusAttack.Value;
var ignoreElementalResistance = ((Weapon)EquippedWeapon.Value).WeaponTag == WeaponTag.IgnoreAffinity;
var isCriticalHit = BattleExtensions.IsCriticalHit(Stats.Luck.Value);
var isCriticalHit = BattleExtensions.IsCriticalHit(LuckComponent.Luck.Value);
var element = ((Weapon)EquippedWeapon.Value).WeaponElement;
var baseAttack = new Damage(
(int)(attackValue * ((Weapon)EquippedWeapon.Value).ElementalDamageBonus),
(int)(AttackComponent.TotalAttack * ((Weapon)EquippedWeapon.Value).ElementalDamageBonus),
element,
isCriticalHit,
false,
false,
ignoreElementalResistance);
var damageDealt = DamageCalculator.CalculateDamage(baseAttack, 10f, new ElementalResistanceSet(0, 0, 0, 0, 0));
var damageDealt = DamageCalculator.CalculateDamage(baseAttack, enemy.DefenseComponent.TotalDefense, new ElementalResistanceSet(0, 0, 0, 0, 0));
enemy.TakeDamage(damageDealt);
if (((Weapon)EquippedWeapon.Value).WeaponTag == WeaponTag.Knockback && enemy is IKnockbackable knockbackable)
@@ -611,27 +477,6 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
}
}
private PlayerStats InitializePlayerStats()
{
var playerStats = new PlayerStats(
currentHP: new AutoProp<int>(_playerStatResource.CurrentHP),
maximumHP: new AutoProp<int>(_playerStatResource.MaximumHP),
currentVT: new AutoProp<int>(_playerStatResource.CurrentVT),
maximumVT: new AutoProp<int>(_playerStatResource.MaximumVT),
currentAttack: new AutoProp<int>(_playerStatResource.CurrentAttack),
currentDefense: new AutoProp<int>(_playerStatResource.CurrentDefense),
maxAttack: new AutoProp<int>(_playerStatResource.MaxAttack),
maxDefense: new AutoProp<int>(_playerStatResource.MaxDefense),
bonusAttack: new AutoProp<int>(_playerStatResource.BonusAttack),
bonusDefense: new AutoProp<int>(_playerStatResource.BonusDefense),
currentExp: new AutoProp<double>(_playerStatResource.CurrentExp),
expToNextLevel: new AutoProp<int>(_playerStatResource.ExpToNextLevel),
currentLevel: new AutoProp<int>(_playerStatResource.CurrentLevel),
luck: new AutoProp<double>(_playerStatResource.Luck));
return playerStats;
}
private bool PlayerIsHittingGeometry()
{
var collisions = WallCheck.GetCollidingBodies();
@@ -640,7 +485,6 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<ISaveChunk<Play
private void WallCheck_BodyEntered(Node body)
{
PlayerLogic.Input(new PlayerLogic.Input.AttackAnimationFinished());
GD.Print("Hit wall");
AnimationPlayer.Stop();
}