Major Player refactor
This commit is contained in:
34
src/player/IPlayer.cs
Normal file
34
src/player/IPlayer.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public interface IPlayer : IKillable
|
||||
{
|
||||
public void Attack();
|
||||
|
||||
public void ToggleInventory();
|
||||
|
||||
public void ToggleMinimap();
|
||||
|
||||
public void PlayerPause();
|
||||
|
||||
public void TakeDamage(double damage, ElementType elementType = ElementType.None, bool isCriticalHit = false);
|
||||
|
||||
public void Knockback(float impulse);
|
||||
|
||||
public void GainExp(int expGained);
|
||||
|
||||
public void LevelUp();
|
||||
|
||||
public void Move(float delta);
|
||||
|
||||
public void TeleportPlayer(Vector3 newPosition);
|
||||
|
||||
public IInventory Inventory { get; }
|
||||
|
||||
public PlayerStats Stats { get; }
|
||||
|
||||
public Vector3 CurrentPosition { get; }
|
||||
|
||||
public Basis CurrentBasis { get; }
|
||||
}
|
||||
@@ -5,248 +5,273 @@ using Chickensoft.SaveFileBuilder;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class Player : CharacterBody3D, IPlayer
|
||||
{
|
||||
public interface IPlayer : ICharacterBody3D, IKillable
|
||||
#region Dependency Injection
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
|
||||
private PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
public double CurrentHP => Stats.CurrentHP.Value;
|
||||
|
||||
public Vector3 CurrentPosition => GlobalPosition;
|
||||
|
||||
public Basis CurrentBasis => Transform.Basis;
|
||||
public PlayerStats Stats { get; set; } = default!;
|
||||
|
||||
public IInventory Inventory { get; private set; } = default!;
|
||||
|
||||
private PlayerLogic.Settings Settings { get; set; } = default!;
|
||||
|
||||
private PlayerLogic PlayerLogic { get; set; } = default!;
|
||||
|
||||
|
||||
#region Dependencies
|
||||
[Dependency]
|
||||
public IAppRepo AppRepo => this.DependOn<IAppRepo>();
|
||||
|
||||
[Dependency]
|
||||
public IGame Game => this.DependOn<IGame>();
|
||||
|
||||
[Dependency]
|
||||
public ISaveChunk<GameData> GameChunk => this.DependOn<ISaveChunk<GameData>>();
|
||||
#endregion
|
||||
|
||||
#region Event Signals
|
||||
[Signal]
|
||||
public delegate void InventoryButtonPressedEventHandler();
|
||||
[Signal]
|
||||
public delegate void MinimapButtonHeldEventHandler();
|
||||
[Signal]
|
||||
public delegate void PauseButtonPressedEventHandler();
|
||||
#endregion
|
||||
|
||||
#region Exports
|
||||
[Export]
|
||||
public PlayerStatResource PlayerStatResource { get; set; } = default!;
|
||||
|
||||
[Export]
|
||||
private WeaponStats _defaultWeapon { get; set; } = default!;
|
||||
[Export]
|
||||
private ArmorStats _defaultArmor { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
#region Node Dependencies
|
||||
[Node] private IAnimationPlayer AnimationPlayer { get; set; } = default!;
|
||||
|
||||
[Node] private AnimatedSprite2D SwordSlashAnimation { get; set; } = default!;
|
||||
|
||||
[Node] private IHitbox Hitbox { get; set; } = default!;
|
||||
|
||||
[Node] private Timer HealthTimer { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
|
||||
private bool flipAttack = false;
|
||||
|
||||
private float _healthTimerWaitTime = 3.0f;
|
||||
|
||||
private bool reduceOnTick = true;
|
||||
|
||||
private float _knockbackStrength = 0.0f;
|
||||
private Vector3 _knockbackDirection = Vector3.Zero;
|
||||
|
||||
private Dictionary<int, int> _expToNextLevel;
|
||||
|
||||
#region Initialization
|
||||
public void Initialize()
|
||||
{
|
||||
public Vector3 GetGlobalInputVector();
|
||||
|
||||
public float GetLeftStrafeInputVector();
|
||||
|
||||
public float GetRightStrafeInputVector();
|
||||
|
||||
public void ApplyCentralImpulseToPlayer(Vector3 velocity);
|
||||
|
||||
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false);
|
||||
|
||||
public void LevelUp();
|
||||
|
||||
event Player.InventoryButtonPressedEventHandler InventoryButtonPressed;
|
||||
event Player.MinimapButtonHeldEventHandler MinimapButtonHeld;
|
||||
event Player.PauseButtonPressedEventHandler PauseButtonPressed;
|
||||
AnimationPlayer.AnimationFinished += OnAnimationFinished;
|
||||
_expToNextLevel = new Dictionary<int, int>
|
||||
{
|
||||
{ 2, 12 },
|
||||
{ 3, 39 },
|
||||
{ 4, 87 },
|
||||
{ 5, 162 },
|
||||
{ 6, 270 },
|
||||
{ 7, 417 },
|
||||
{ 8, 609 }
|
||||
};
|
||||
}
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class Player : CharacterBody3D, IPlayer, IProvide<PlayerLogic>
|
||||
public void Setup()
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
Settings = new PlayerLogic.Settings() { RotationSpeed = PlayerStatResource.RotationSpeed, MoveSpeed = PlayerStatResource.MoveSpeed, Acceleration = PlayerStatResource.Acceleration };
|
||||
|
||||
PlayerLogic IProvide<PlayerLogic>.Value() => PlayerLogic;
|
||||
Stats = new PlayerStats();
|
||||
Inventory = new Inventory();
|
||||
|
||||
public PlayerLogic.Settings Settings { get; set; } = default!;
|
||||
Stats.SetCurrentHP(PlayerStatResource.CurrentHP);
|
||||
Stats.SetMaximumHP(PlayerStatResource.MaximumHP);
|
||||
Stats.SetCurrentVT(PlayerStatResource.CurrentVT);
|
||||
Stats.SetMaximumVT(PlayerStatResource.MaximumVT);
|
||||
Stats.SetCurrentAttack(PlayerStatResource.CurrentAttack);
|
||||
Stats.SetBonusAttack(PlayerStatResource.BonusAttack);
|
||||
Stats.SetMaxAttack(PlayerStatResource.MaxAttack);
|
||||
Stats.SetCurrentDefense(PlayerStatResource.CurrentDefense);
|
||||
Stats.SetBonusDefense(PlayerStatResource.BonusDefense);
|
||||
Stats.SetMaxDefense(PlayerStatResource.MaxDefense);
|
||||
Stats.SetCurrentExp(PlayerStatResource.CurrentExp);
|
||||
Stats.SetCurrentLevel(PlayerStatResource.CurrentLevel);
|
||||
Stats.SetExpToNextLevel(PlayerStatResource.ExpToNextLevel);
|
||||
Stats.SetLuck(PlayerStatResource.Luck);
|
||||
|
||||
private PlayerLogic PlayerLogic { get; set; } = default!;
|
||||
PlayerLogic = new PlayerLogic();
|
||||
PlayerLogic.Set(this as IPlayer);
|
||||
PlayerLogic.Set(Settings);
|
||||
PlayerLogic.Set(AppRepo);
|
||||
PlayerLogic.Set(Stats);
|
||||
|
||||
private PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
|
||||
var defaultWeapon = new Weapon();
|
||||
defaultWeapon.SetItemStats(_defaultWeapon);
|
||||
var defaultArmor = new Armor();
|
||||
defaultArmor.SetItemStats(_defaultArmor);
|
||||
Inventory.TryAdd(defaultWeapon);
|
||||
Inventory.TryAdd(defaultArmor);
|
||||
|
||||
#region Dependencies
|
||||
[Dependency]
|
||||
public IAppRepo AppRepo => this.DependOn<IAppRepo>();
|
||||
Inventory.Equip(defaultWeapon);
|
||||
Inventory.Equip(defaultArmor);
|
||||
|
||||
[Dependency]
|
||||
public IGameRepo GameRepo => this.DependOn<IGameRepo>();
|
||||
Inventory.EquippedAccessory.Sync += EquippedAccessory_Sync;
|
||||
Stats.CurrentHP.Sync += CurrentHP_Sync;
|
||||
Stats.CurrentExp.Sync += CurrentEXP_Sync;
|
||||
|
||||
[Dependency]
|
||||
public ISaveChunk<GameData> GameChunk => this.DependOn<ISaveChunk<GameData>>();
|
||||
#endregion
|
||||
HealthTimer.WaitTime = _healthTimerWaitTime;
|
||||
}
|
||||
|
||||
#region Event Signals
|
||||
[Signal]
|
||||
public delegate void InventoryButtonPressedEventHandler();
|
||||
[Signal]
|
||||
public delegate void MinimapButtonHeldEventHandler();
|
||||
[Signal]
|
||||
public delegate void PauseButtonPressedEventHandler();
|
||||
#endregion
|
||||
public void OnResolved()
|
||||
{
|
||||
PlayerBinding = PlayerLogic.Bind();
|
||||
|
||||
#region Exports
|
||||
[Export]
|
||||
public PlayerStatResource PlayerStatResource { get; set; } = default!;
|
||||
|
||||
[Export]
|
||||
private WeaponStats _defaultWeapon { get; set; } = default!;
|
||||
[Export]
|
||||
private ArmorStats _defaultArmor { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
#region Node Dependencies
|
||||
[Node] private IAnimationPlayer AnimationPlayer { get; set; } = default!;
|
||||
|
||||
[Node] private AnimatedSprite2D SwordSlashAnimation { get; set; } = default!;
|
||||
|
||||
[Node] private IHitbox Hitbox { get; set; } = default!;
|
||||
|
||||
[Node] private Timer HealthTimer { get; set; } = default!;
|
||||
#endregion
|
||||
|
||||
private PlayerData PlayerData { get; set; } = default!;
|
||||
|
||||
private bool flipAttack = false;
|
||||
|
||||
private float _healthTimerWaitTime = 3.0f;
|
||||
|
||||
private bool reduceOnTick = true;
|
||||
|
||||
private float _knockbackStrength = 0.0f;
|
||||
private Vector3 _knockbackDirection = Vector3.Zero;
|
||||
|
||||
private Dictionary<int, int> _expToNextLevel;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
AnimationPlayer.AnimationFinished += OnAnimationFinished;
|
||||
_expToNextLevel = new Dictionary<int, int>
|
||||
PlayerBinding
|
||||
.Handle((in PlayerLogic.Output.Animations.Attack output) =>
|
||||
{
|
||||
{ 2, 12 },
|
||||
{ 3, 39 },
|
||||
{ 4, 87 },
|
||||
{ 5, 162 },
|
||||
{ 6, 270 },
|
||||
{ 7, 417 },
|
||||
{ 8, 609 }
|
||||
};
|
||||
}
|
||||
var attackSpeed = Inventory.EquippedWeapon.Value.AttackSpeed;
|
||||
AnimationPlayer.SetSpeedScale((float)attackSpeed);
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
Settings = new PlayerLogic.Settings() { RotationSpeed = PlayerStatResource.RotationSpeed, MoveSpeed = PlayerStatResource.MoveSpeed, Acceleration = PlayerStatResource.Acceleration };
|
||||
|
||||
PlayerData = new PlayerData()
|
||||
AnimationPlayer.Play("attack");
|
||||
})
|
||||
.Handle((in PlayerLogic.Output.ThrowItem output) =>
|
||||
{
|
||||
GlobalTransform = GlobalTransform,
|
||||
StateMachine = PlayerLogic,
|
||||
Velocity = Velocity,
|
||||
Inventory = new Inventory(),
|
||||
};
|
||||
});
|
||||
|
||||
PlayerData.SetCurrentHP(PlayerStatResource.CurrentHP);
|
||||
PlayerData.SetMaximumHP(PlayerStatResource.MaximumHP);
|
||||
PlayerData.SetCurrentVT(PlayerStatResource.CurrentVT);
|
||||
PlayerData.SetMaximumVT(PlayerStatResource.MaximumVT);
|
||||
PlayerData.SetCurrentAttack(PlayerStatResource.CurrentAttack);
|
||||
PlayerData.SetMaxAttack(PlayerStatResource.MaxAttack);
|
||||
PlayerData.SetCurrentDefense(PlayerStatResource.CurrentDefense);
|
||||
PlayerData.SetMaxDefense(PlayerStatResource.MaxDefense);
|
||||
PlayerData.SetCurrentExp(PlayerStatResource.CurrentExp);
|
||||
PlayerData.SetCurrentLevel(PlayerStatResource.CurrentLevel);
|
||||
PlayerData.SetExpToNextLevel(PlayerStatResource.ExpToNextLevel);
|
||||
PlayerData.SetLuck(PlayerStatResource.Luck);
|
||||
this.Provide();
|
||||
PlayerLogic.Start();
|
||||
HealthTimer.Timeout += OnHealthTimerTimeout;
|
||||
Inventory.AccessoryUnequipped += Inventory_AccessoryUnequipped;
|
||||
Hitbox.AreaEntered += Hitbox_AreaEntered;
|
||||
}
|
||||
|
||||
PlayerLogic = new PlayerLogic();
|
||||
PlayerLogic.Set(this as IPlayer);
|
||||
PlayerLogic.Set(Settings);
|
||||
PlayerLogic.Set(AppRepo);
|
||||
PlayerLogic.Set(GameRepo);
|
||||
PlayerLogic.Set(PlayerData);
|
||||
public void OnReady()
|
||||
{
|
||||
SetPhysicsProcess(true);
|
||||
SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2;
|
||||
}
|
||||
#endregion
|
||||
|
||||
var defaultWeapon = new Weapon() { WeaponStats = _defaultWeapon };
|
||||
var defaultArmor = new Armor() { ArmorStats = _defaultArmor };
|
||||
PlayerData.Inventory.TryAdd(defaultWeapon);
|
||||
PlayerData.Inventory.TryAdd(defaultArmor);
|
||||
public void Attack()
|
||||
{
|
||||
PlayerLogic.Input(new PlayerLogic.Input.Attack());
|
||||
}
|
||||
|
||||
PlayerData.Inventory.Equip(defaultWeapon);
|
||||
PlayerData.Inventory.Equip(defaultArmor);
|
||||
public void ToggleInventory()
|
||||
{
|
||||
Game.ToggleInventory();
|
||||
}
|
||||
|
||||
PlayerData.Inventory.EquippedAccessory.Sync += EquippedAccessory_Sync;
|
||||
PlayerData.CurrentHP.Sync += CurrentHP_Sync;
|
||||
PlayerData.CurrentExp.Sync += CurrentEXP_Sync;
|
||||
public void ToggleMinimap()
|
||||
{
|
||||
Game.ToggleMinimap();
|
||||
}
|
||||
|
||||
HealthTimer.WaitTime = _healthTimerWaitTime;
|
||||
}
|
||||
public void PlayerPause()
|
||||
{
|
||||
EmitSignal(SignalName.PauseButtonPressed);
|
||||
}
|
||||
|
||||
public void OnResolved()
|
||||
public void Move(float delta)
|
||||
{
|
||||
var rawInput = GlobalInputVector;
|
||||
var strafeLeftInput = LeftStrafeInputVector;
|
||||
var strafeRightInput = RightStrafeInputVector;
|
||||
|
||||
var transform = Transform;
|
||||
transform.Basis = new Basis(Vector3.Up, Settings.RotationSpeed * -rawInput.X * delta) * transform.Basis;
|
||||
var moveDirection = new Vector3(strafeRightInput - strafeLeftInput, 0, rawInput.Z);
|
||||
var velocity = Basis * moveDirection * Settings.MoveSpeed * Settings.Acceleration;
|
||||
_knockbackStrength = _knockbackStrength * 0.9f;
|
||||
Transform = Transform with { Basis = transform.Basis };
|
||||
Velocity = velocity + (_knockbackDirection * _knockbackStrength);
|
||||
MoveAndSlide();
|
||||
}
|
||||
|
||||
public void TeleportPlayer(Vector3 newPosition)
|
||||
{
|
||||
GlobalPosition = newPosition;
|
||||
}
|
||||
|
||||
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false)
|
||||
{
|
||||
if (Stats.CurrentHP.Value > 0)
|
||||
{
|
||||
PlayerBinding = PlayerLogic.Bind();
|
||||
|
||||
PlayerBinding
|
||||
.Handle((in PlayerLogic.Output.MovementComputed output) =>
|
||||
{
|
||||
_knockbackStrength = _knockbackStrength * 0.9f;
|
||||
Transform = Transform with { Basis = output.Rotation };
|
||||
Velocity = output.Velocity + (_knockbackDirection * _knockbackStrength);
|
||||
MoveAndSlide();
|
||||
})
|
||||
.Handle((in PlayerLogic.Output.Animations.Attack output) =>
|
||||
{
|
||||
var attackSpeed = PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.AttackSpeed;
|
||||
AnimationPlayer.SetSpeedScale((float)attackSpeed);
|
||||
|
||||
AnimationPlayer.Play("attack");
|
||||
})
|
||||
.Handle((in PlayerLogic.Output.ThrowItem output) =>
|
||||
{
|
||||
});
|
||||
|
||||
this.Provide();
|
||||
PlayerLogic.Start();
|
||||
GameRepo.SetPlayerData(PlayerData);
|
||||
GlobalPosition = GameRepo.PlayerGlobalPosition.Value;
|
||||
GameRepo.PlayerGlobalPosition.Sync += PlayerGlobalPosition_Sync;
|
||||
HealthTimer.Timeout += OnHealthTimerTimeout;
|
||||
PlayerData.Inventory.AccessoryUnequipped += Inventory_AccessoryUnequipped;
|
||||
Hitbox.AreaEntered += Hitbox_AreaEntered;
|
||||
damage = CalculateDefenseResistance(damage);
|
||||
if (isCriticalHit)
|
||||
damage *= 2;
|
||||
Stats.SetCurrentHP(Stats.CurrentHP.Value - (int)damage);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReady()
|
||||
{
|
||||
SetPhysicsProcess(true);
|
||||
}
|
||||
public void Knockback(float impulse)
|
||||
{
|
||||
_knockbackStrength = impulse;
|
||||
_knockbackDirection = GlobalBasis.Z.Normalized();
|
||||
}
|
||||
|
||||
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false)
|
||||
{
|
||||
if (PlayerData.CurrentHP.Value > 0)
|
||||
{
|
||||
damage = CalculateDefenseResistance(damage);
|
||||
if (isCriticalHit)
|
||||
damage *= 2;
|
||||
PlayerData.SetCurrentHP(PlayerData.CurrentHP.Value - (int)damage);
|
||||
}
|
||||
}
|
||||
public void GainExp(int expGained)
|
||||
{
|
||||
Stats.SetCurrentExp(Stats.CurrentExp.Value + expGained);
|
||||
}
|
||||
|
||||
public void LevelUp()
|
||||
{
|
||||
var nextLevel = PlayerData.CurrentLevel.Value + 1;
|
||||
var expToNextLevel = _expToNextLevel[nextLevel];
|
||||
var newCurrentExp = Mathf.Max(PlayerData.CurrentExp.Value - PlayerData.ExpToNextLevel.Value, 0);
|
||||
PlayerData.SetCurrentLevel(nextLevel);
|
||||
PlayerData.SetExpToNextLevel(expToNextLevel);
|
||||
PlayerData.SetCurrentExp(newCurrentExp);
|
||||
}
|
||||
public void LevelUp()
|
||||
{
|
||||
var nextLevel = Stats.CurrentLevel.Value + 1;
|
||||
var expToNextLevel = _expToNextLevel[nextLevel];
|
||||
var newCurrentExp = Mathf.Max(Stats.CurrentExp.Value - Stats.ExpToNextLevel.Value, 0);
|
||||
Stats.SetCurrentLevel(nextLevel);
|
||||
Stats.SetExpToNextLevel(expToNextLevel);
|
||||
Stats.SetCurrentExp(newCurrentExp);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed(GameInputs.Inventory))
|
||||
{
|
||||
GD.Print("Inventory button pressed");
|
||||
EmitSignal(SignalName.InventoryButtonPressed);
|
||||
}
|
||||
public void Die() => PlayerLogic.Input(new PlayerLogic.Input.Killed());
|
||||
|
||||
if (@event.IsActionPressed(GameInputs.MiniMap))
|
||||
{
|
||||
GD.Print("MiniMap button pressed");
|
||||
EmitSignal(SignalName.MinimapButtonHeld);
|
||||
}
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed(GameInputs.Inventory))
|
||||
ToggleInventory();
|
||||
|
||||
if (@event.IsActionPressed(GameInputs.Pause))
|
||||
{
|
||||
GD.Print("Pause button pressed");
|
||||
EmitSignal(SignalName.PauseButtonPressed);
|
||||
}
|
||||
if (@event.IsActionPressed(GameInputs.MiniMap))
|
||||
ToggleMinimap();
|
||||
|
||||
if (@event.IsActionPressed(GameInputs.Attack))
|
||||
PlayerLogic.Input(new PlayerLogic.Input.Attack());
|
||||
}
|
||||
if (@event.IsActionPressed(GameInputs.Pause))
|
||||
PlayerPause();
|
||||
|
||||
public void OnPhysicsProcess(double delta)
|
||||
{
|
||||
PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta));
|
||||
if (@event.IsActionPressed(GameInputs.Attack))
|
||||
Attack();
|
||||
}
|
||||
|
||||
SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2;
|
||||
public void OnPhysicsProcess(double delta)
|
||||
{
|
||||
PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta));
|
||||
PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition, GlobalTransform));
|
||||
}
|
||||
|
||||
PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition, GlobalTransform));
|
||||
}
|
||||
|
||||
public Vector3 GetGlobalInputVector()
|
||||
private static Vector3 GlobalInputVector
|
||||
{
|
||||
get
|
||||
{
|
||||
var rawInput = Input.GetVector(GameInputs.MoveLeft, GameInputs.MoveRight, GameInputs.MoveUp, GameInputs.MoveDown);
|
||||
var input = new Vector3
|
||||
@@ -256,113 +281,117 @@ namespace GameJamDungeon
|
||||
};
|
||||
return input with { Y = 0f };
|
||||
}
|
||||
}
|
||||
|
||||
public float GetLeftStrafeInputVector()
|
||||
private static float LeftStrafeInputVector
|
||||
{
|
||||
get
|
||||
{
|
||||
var leftStrafe = Input.GetActionStrength(GameInputs.StrafeLeft);
|
||||
return leftStrafe;
|
||||
}
|
||||
|
||||
public float GetRightStrafeInputVector()
|
||||
{
|
||||
var rightStrafe = Input.GetActionStrength(GameInputs.StrafeRight);
|
||||
return rightStrafe;
|
||||
}
|
||||
|
||||
public void ThrowItem()
|
||||
{
|
||||
var itemScene = GD.Load<PackedScene>("res://src/items/throwable/ThrowableItem.tscn");
|
||||
var throwItem = itemScene.Instantiate<ThrowableItem>();
|
||||
GetTree().Root.AddChildEx(throwItem);
|
||||
throwItem.GlobalPosition = GameRepo.PlayerGlobalPosition.Value;
|
||||
throwItem.GlobalRotation = GlobalRotation;
|
||||
}
|
||||
|
||||
public void ApplyCentralImpulseToPlayer(Vector3 velocity)
|
||||
{
|
||||
_knockbackStrength = 115.0f;
|
||||
_knockbackDirection = velocity;
|
||||
}
|
||||
|
||||
public void OnAnimationFinished(StringName animation)
|
||||
{
|
||||
GD.Print("Attack finished");
|
||||
PlayerLogic.Input(new PlayerLogic.Input.AttackAnimationFinished());
|
||||
}
|
||||
|
||||
public void OnExitTree()
|
||||
{
|
||||
PlayerLogic.Stop();
|
||||
PlayerBinding.Dispose();
|
||||
AnimationPlayer.AnimationFinished -= OnAnimationFinished;
|
||||
}
|
||||
|
||||
public void Kill() => PlayerLogic.Input(new PlayerLogic.Input.Killed());
|
||||
|
||||
private void PlayerGlobalPosition_Sync(Vector3 newPlayerPosition)
|
||||
{
|
||||
GlobalPosition = newPlayerPosition;
|
||||
}
|
||||
|
||||
private void OnPlayerPositionUpdated(Vector3 globalPosition) => GlobalPosition = globalPosition;
|
||||
|
||||
private void OnHealthTimerTimeout()
|
||||
{
|
||||
if (PlayerData.CurrentVT.Value > 0)
|
||||
{
|
||||
if (PlayerData.Inventory.EquippedAccessory.Value.AccessoryStats.AccessoryTags.Contains(AccessoryTag.HalfVTConsumption))
|
||||
{
|
||||
reduceOnTick = !reduceOnTick;
|
||||
}
|
||||
PlayerData.SetCurrentHP(PlayerData.CurrentHP.Value + 1);
|
||||
if (reduceOnTick)
|
||||
PlayerData.SetCurrentVT(PlayerData.CurrentVT.Value - 1);
|
||||
}
|
||||
else
|
||||
PlayerData.SetCurrentHP(PlayerData.CurrentHP.Value - 1);
|
||||
}
|
||||
|
||||
private void EquippedAccessory_Sync(Accessory equippedItem)
|
||||
{
|
||||
PlayerData.SetMaximumHP(PlayerData.MaximumHP.Value + equippedItem.AccessoryStats.MaxHPUp);
|
||||
PlayerData.SetMaximumVT(PlayerData.MaximumVT.Value + equippedItem.AccessoryStats.MaxVTUp);
|
||||
PlayerData.SetLuck(PlayerData.Luck.Value + equippedItem.AccessoryStats.LUCKUp);
|
||||
}
|
||||
|
||||
private void Inventory_AccessoryUnequipped(AccessoryStats unequippedAccessory)
|
||||
{
|
||||
PlayerData.SetMaximumHP(PlayerData.MaximumHP.Value - unequippedAccessory.MaxHPUp);
|
||||
PlayerData.SetMaximumVT(PlayerData.MaximumVT.Value - unequippedAccessory.MaxVTUp);
|
||||
PlayerData.SetLuck(PlayerData.Luck.Value - unequippedAccessory.LUCKUp);
|
||||
}
|
||||
|
||||
private void CurrentHP_Sync(int newHealth)
|
||||
{
|
||||
if (newHealth <= 0)
|
||||
Kill();
|
||||
}
|
||||
|
||||
private void CurrentEXP_Sync(int newExp)
|
||||
{
|
||||
if (PlayerData.CurrentExp.Value >= PlayerData.ExpToNextLevel.Value)
|
||||
LevelUp();
|
||||
}
|
||||
|
||||
private double CalculateDefenseResistance(double incomingDamage)
|
||||
{
|
||||
return Mathf.Max(incomingDamage - (PlayerData.CurrentDefense.Value + PlayerData.BonusDefense), 0.0);
|
||||
}
|
||||
|
||||
private void Hitbox_AreaEntered(Area3D area)
|
||||
{
|
||||
var target = area.GetOwner();
|
||||
if (target is IEnemy enemy)
|
||||
{
|
||||
enemy.TakeDamage(
|
||||
(PlayerStatResource.CurrentAttack + PlayerStatResource.BonusAttack + PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.Damage) * PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.ElementalDamageBonus,
|
||||
PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponElement,
|
||||
ignoreElementalResistance: PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.IgnoreAffinity));
|
||||
}
|
||||
return Input.GetActionStrength(GameInputs.StrafeLeft);
|
||||
}
|
||||
}
|
||||
|
||||
private static float RightStrafeInputVector
|
||||
{
|
||||
get
|
||||
{
|
||||
return Input.GetActionStrength(GameInputs.StrafeRight);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowItem()
|
||||
{
|
||||
var itemScene = GD.Load<PackedScene>("res://src/items/throwable/ThrowableItem.tscn");
|
||||
var throwItem = itemScene.Instantiate<ThrowableItem>();
|
||||
GetTree().Root.AddChildEx(throwItem);
|
||||
throwItem.GlobalPosition = CurrentPosition;
|
||||
throwItem.GlobalRotation = GlobalRotation;
|
||||
}
|
||||
|
||||
private void OnAnimationFinished(StringName animation)
|
||||
{
|
||||
GD.Print("Attack finished");
|
||||
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.CurrentVT.Value > 0)
|
||||
{
|
||||
if (Inventory.EquippedAccessory.Value.AccessoryTags.Contains(AccessoryTag.HalfVTConsumption))
|
||||
{
|
||||
reduceOnTick = !reduceOnTick;
|
||||
}
|
||||
Stats.SetCurrentHP(Stats.CurrentHP.Value + 1);
|
||||
if (reduceOnTick)
|
||||
Stats.SetCurrentVT(Stats.CurrentVT.Value - 1);
|
||||
}
|
||||
else
|
||||
Stats.SetCurrentHP(Stats.CurrentHP.Value - 1);
|
||||
}
|
||||
|
||||
private void EquippedAccessory_Sync(Accessory accessory)
|
||||
{
|
||||
Stats.SetMaximumHP(Stats.MaximumHP.Value + accessory.MaxHPUp);
|
||||
Stats.SetMaximumVT(Stats.MaximumVT.Value + accessory.MaxVTUp);
|
||||
Stats.SetLuck(Stats.Luck.Value + accessory.LuckUp);
|
||||
}
|
||||
|
||||
private void Inventory_AccessoryUnequipped(Accessory accessory)
|
||||
{
|
||||
Stats.SetMaximumHP(Stats.MaximumHP.Value - accessory.MaxHPUp);
|
||||
Stats.SetMaximumVT(Stats.MaximumVT.Value - accessory.MaxVTUp);
|
||||
Stats.SetLuck(Stats.Luck.Value - accessory.LuckUp);
|
||||
}
|
||||
|
||||
private void CurrentHP_Sync(int newHealth)
|
||||
{
|
||||
if (newHealth <= 0)
|
||||
Die();
|
||||
}
|
||||
|
||||
private void CurrentEXP_Sync(int newExp)
|
||||
{
|
||||
if (Stats.CurrentExp.Value >= Stats.ExpToNextLevel.Value)
|
||||
LevelUp();
|
||||
}
|
||||
|
||||
private double CalculateDefenseResistance(double incomingDamage)
|
||||
{
|
||||
return Mathf.Max(incomingDamage - Stats.CurrentDefense.Value, 0.0);
|
||||
}
|
||||
|
||||
private void Hitbox_AreaEntered(Area3D area)
|
||||
{
|
||||
var target = area.GetOwner();
|
||||
if (target is IEnemy enemy)
|
||||
HitEnemy(enemy);
|
||||
}
|
||||
|
||||
private void HitEnemy(IEnemy enemy)
|
||||
{
|
||||
var attackValue = PlayerStatResource.CurrentAttack + Inventory.EquippedWeapon.Value.Damage;
|
||||
var ignoreElementalResistance = Inventory.EquippedWeapon.Value.WeaponTags.Contains(WeaponTag.IgnoreAffinity);
|
||||
var isCriticalHit = BattleExtensions.IsCriticalHit(Stats.Luck.Value);
|
||||
var element = Inventory.EquippedWeapon.Value.WeaponElement;
|
||||
|
||||
enemy.TakeDamage(
|
||||
attackValue * Inventory.EquippedWeapon.Value.ElementalDamageBonus,
|
||||
element,
|
||||
isCriticalHit,
|
||||
false,
|
||||
ignoreElementalResistance);
|
||||
|
||||
if (Inventory.EquippedWeapon.Value.WeaponTags.Contains(WeaponTag.Knockback))
|
||||
enemy.Knockback(0.3f, -CurrentBasis.Z.Normalized());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ CurrentAttack = 8
|
||||
CurrentDefense = 12
|
||||
MaxAttack = 8
|
||||
MaxDefense = 12
|
||||
BonusAttack = 0
|
||||
BonusDefense = 0
|
||||
Luck = 0.05
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_wedu3"]
|
||||
@@ -344,7 +342,6 @@ animations = [{
|
||||
}]
|
||||
|
||||
[node name="Player" type="CharacterBody3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2, 0)
|
||||
collision_layer = 806
|
||||
collision_mask = 775
|
||||
script = ExtResource("1_xcol5")
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
using Chickensoft.Collections;
|
||||
using Chickensoft.Serialization;
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial record PlayerData
|
||||
{
|
||||
// TODO: Implement save system
|
||||
[Save("global_transform")]
|
||||
public required Transform3D GlobalTransform { get; init; }
|
||||
[Save("state_machine")]
|
||||
public required PlayerLogic StateMachine { get; init; }
|
||||
[Save("velocity")]
|
||||
public required Vector3 Velocity { get; init; }
|
||||
|
||||
[Save("inventory")]
|
||||
public required Inventory Inventory { get; init; }
|
||||
[Save("currentHP")]
|
||||
public IAutoProp<int> CurrentHP => _currentHP;
|
||||
[Save("maximumHP")]
|
||||
public IAutoProp<int> MaximumHP => _maximumHP;
|
||||
[Save("currentVT")]
|
||||
public IAutoProp<int> CurrentVT => _currentVT;
|
||||
[Save("maximumVT")]
|
||||
public IAutoProp<int> MaximumVT => _maximumVT;
|
||||
[Save("currentExp")]
|
||||
public IAutoProp<int> CurrentExp => _currentExp;
|
||||
[Save("currentLevel")]
|
||||
public IAutoProp<int> CurrentLevel => _currentLevel;
|
||||
[Save("currentAttack")]
|
||||
public IAutoProp<int> CurrentAttack => _currentAttack;
|
||||
[Save("currentDefense")]
|
||||
public IAutoProp<int> CurrentDefense => _currentDefense;
|
||||
[Save("maxAttack")]
|
||||
public IAutoProp<int> MaxAttack => _maxAttack;
|
||||
[Save("maxDefense")]
|
||||
public IAutoProp<int> MaxDefense => _maxDefense;
|
||||
public int BonusAttack => Inventory.EquippedWeapon.Value.WeaponStats.Damage + Inventory.EquippedAccessory.Value.AccessoryStats.ATKUp;
|
||||
public int BonusDefense => Inventory.EquippedArmor.Value.ArmorStats.Defense + Inventory.EquippedAccessory.Value.AccessoryStats.DEFUp;
|
||||
[Save("expToNextLevel")]
|
||||
public IAutoProp<int> ExpToNextLevel => _expToNextLevel;
|
||||
[Save("luck")]
|
||||
public IAutoProp<double> Luck => _luck;
|
||||
|
||||
public void SetCurrentHP(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaximumHP.Value);
|
||||
_currentHP.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaximumHP(int newValue)
|
||||
{
|
||||
_maximumHP.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentVT(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaximumVT.Value);
|
||||
_currentVT.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaximumVT(int newValue)
|
||||
{
|
||||
_maximumVT.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentExp(int newValue)
|
||||
{
|
||||
_currentExp.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentLevel(int newValue)
|
||||
{
|
||||
_currentLevel.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentAttack(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaxAttack.Value);
|
||||
_currentAttack.OnNext(clampedValue);
|
||||
}
|
||||
public void SetCurrentDefense(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaxDefense.Value);
|
||||
_currentDefense.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaxAttack(int newValue)
|
||||
{
|
||||
_maxAttack.OnNext(newValue);
|
||||
}
|
||||
public void SetMaxDefense(int newValue)
|
||||
{
|
||||
_maxDefense.OnNext(newValue);
|
||||
}
|
||||
public void SetExpToNextLevel(int newValue)
|
||||
{
|
||||
_expToNextLevel.OnNext(newValue);
|
||||
}
|
||||
public void SetLuck(double newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, 1.0);
|
||||
_luck.OnNext(clampedValue);
|
||||
}
|
||||
|
||||
private readonly AutoProp<int> _currentHP = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maximumHP = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentVT = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maximumVT = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentExp = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentLevel = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentAttack = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentDefense = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maxAttack = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maxDefense = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _bonusAttack = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _bonusDefense = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _expToNextLevel = new(int.MaxValue);
|
||||
private readonly AutoProp<double> _luck = new(double.MaxValue);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,43 @@
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class PlayerStatResource : Resource
|
||||
{
|
||||
[GlobalClass]
|
||||
public partial class PlayerStatResource : Resource
|
||||
{
|
||||
/// <summary>Rotation speed (quaternions?/sec).</summary>
|
||||
[Export(PropertyHint.Range, "0, 100, 0.1")]
|
||||
public float RotationSpeed { get; set; } = 12.0f;
|
||||
/// <summary>Rotation speed (quaternions?/sec).</summary>
|
||||
[Export(PropertyHint.Range, "0, 100, 0.1")]
|
||||
public float RotationSpeed { get; set; } = 12.0f;
|
||||
|
||||
/// <summary>Player speed (meters/sec).</summary>
|
||||
[Export(PropertyHint.Range, "0, 100, 0.1")]
|
||||
public float MoveSpeed { get; set; } = 8f;
|
||||
/// <summary>Player speed (meters/sec).</summary>
|
||||
[Export(PropertyHint.Range, "0, 100, 0.1")]
|
||||
public float MoveSpeed { get; set; } = 8f;
|
||||
|
||||
/// <summary>Player speed (meters^2/sec).</summary>
|
||||
[Export(PropertyHint.Range, "0, 100, 0.1")]
|
||||
public float Acceleration { get; set; } = 4f;
|
||||
/// <summary>Player speed (meters^2/sec).</summary>
|
||||
[Export(PropertyHint.Range, "0, 100, 0.1")]
|
||||
public float Acceleration { get; set; } = 4f;
|
||||
|
||||
[Export] public int CurrentHP { get; set; }
|
||||
[Export] public int MaximumHP { get; set; }
|
||||
[Export] public int CurrentHP { get; set; }
|
||||
[Export] public int MaximumHP { get; set; }
|
||||
|
||||
[Export] public int CurrentVT { get; set; }
|
||||
[Export] public int MaximumVT { get; set; }
|
||||
[Export] public int CurrentVT { get; set; }
|
||||
[Export] public int MaximumVT { get; set; }
|
||||
|
||||
[Export] public int CurrentExp { get; set; }
|
||||
[Export] public int ExpToNextLevel { get; set; }
|
||||
[Export] public int CurrentLevel { get; set; }
|
||||
[Export] public int CurrentExp { get; set; }
|
||||
[Export] public int ExpToNextLevel { get; set; }
|
||||
[Export] public int CurrentLevel { get; set; }
|
||||
|
||||
[Export] public int CurrentAttack { get; set; }
|
||||
[Export] public int CurrentDefense { get; set; }
|
||||
[Export] public int CurrentAttack { get; set; }
|
||||
[Export] public int BonusAttack { get; set; }
|
||||
|
||||
[Export] public int MaxAttack { get; set; }
|
||||
[Export] public int MaxDefense { get; set; }
|
||||
[Export] public int MaxAttack { get; set; }
|
||||
|
||||
[Export] public int BonusAttack { get; set; }
|
||||
[Export] public int BonusDefense { get; set; }
|
||||
[Export] public int CurrentDefense { get; set; }
|
||||
|
||||
[Export(PropertyHint.Range, "0, 1, 0.01")]
|
||||
public double Luck { get; set; }
|
||||
}
|
||||
[Export] public int BonusDefense { get; set; }
|
||||
|
||||
[Export] public int MaxDefense { get; set; }
|
||||
|
||||
[Export(PropertyHint.Range, "0, 1, 0.01")]
|
||||
public double Luck { get; set; }
|
||||
}
|
||||
|
||||
114
src/player/PlayerStats.cs
Normal file
114
src/player/PlayerStats.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Chickensoft.Collections;
|
||||
using Chickensoft.Serialization;
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial record PlayerStats
|
||||
{
|
||||
[Save("currentHP")]
|
||||
public IAutoProp<int> CurrentHP => _currentHP;
|
||||
[Save("maximumHP")]
|
||||
public IAutoProp<int> MaximumHP => _maximumHP;
|
||||
[Save("currentVT")]
|
||||
public IAutoProp<int> CurrentVT => _currentVT;
|
||||
[Save("maximumVT")]
|
||||
public IAutoProp<int> MaximumVT => _maximumVT;
|
||||
[Save("currentExp")]
|
||||
public IAutoProp<int> CurrentExp => _currentExp;
|
||||
[Save("currentLevel")]
|
||||
public IAutoProp<int> CurrentLevel => _currentLevel;
|
||||
[Save("currentAttack")]
|
||||
public IAutoProp<int> CurrentAttack => _currentAttack;
|
||||
[Save("bonusAttack")]
|
||||
public IAutoProp<int> BonusAttack => _bonusAttack;
|
||||
[Save("maxAttack")]
|
||||
public IAutoProp<int> MaxAttack => _maxAttack;
|
||||
[Save("currentDefense")]
|
||||
public IAutoProp<int> CurrentDefense => _currentDefense;
|
||||
[Save("bonusDefense")]
|
||||
public IAutoProp<int> BonusDefense => _bonusDefense;
|
||||
[Save("maxDefense")]
|
||||
public IAutoProp<int> MaxDefense => _maxDefense;
|
||||
[Save("expToNextLevel")]
|
||||
public IAutoProp<int> ExpToNextLevel => _expToNextLevel;
|
||||
[Save("luck")]
|
||||
public IAutoProp<double> Luck => _luck;
|
||||
|
||||
public void SetCurrentHP(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaximumHP.Value);
|
||||
_currentHP.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaximumHP(int newValue)
|
||||
{
|
||||
_maximumHP.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentVT(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaximumVT.Value);
|
||||
_currentVT.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaximumVT(int newValue)
|
||||
{
|
||||
_maximumVT.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentExp(int newValue)
|
||||
{
|
||||
_currentExp.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentLevel(int newValue)
|
||||
{
|
||||
_currentLevel.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentAttack(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaxAttack.Value);
|
||||
_currentAttack.OnNext(clampedValue);
|
||||
}
|
||||
public void SetBonusAttack(int newValue)
|
||||
{
|
||||
_bonusAttack.OnNext(newValue);
|
||||
}
|
||||
public void SetMaxAttack(int newValue)
|
||||
{
|
||||
_maxAttack.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentDefense(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaxDefense.Value);
|
||||
_currentDefense.OnNext(clampedValue);
|
||||
}
|
||||
public void SetBonusDefense(int newValue)
|
||||
{
|
||||
_bonusDefense.OnNext(newValue);
|
||||
}
|
||||
public void SetMaxDefense(int newValue)
|
||||
{
|
||||
_maxDefense.OnNext(newValue);
|
||||
}
|
||||
public void SetExpToNextLevel(int newValue)
|
||||
{
|
||||
_expToNextLevel.OnNext(newValue);
|
||||
}
|
||||
public void SetLuck(double newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, 1.0);
|
||||
_luck.OnNext(clampedValue);
|
||||
}
|
||||
|
||||
private readonly AutoProp<int> _currentHP = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maximumHP = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentVT = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maximumVT = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentExp = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentLevel = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentAttack = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _bonusAttack = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maxAttack = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _currentDefense = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _bonusDefense = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _maxDefense = new(int.MaxValue);
|
||||
private readonly AutoProp<int> _expToNextLevel = new(int.MaxValue);
|
||||
private readonly AutoProp<double> _luck = new(double.MaxValue);
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
public static class Input
|
||||
{
|
||||
public static class Input
|
||||
{
|
||||
public readonly record struct PhysicsTick(double Delta);
|
||||
public readonly record struct PhysicsTick(double Delta);
|
||||
|
||||
public readonly record struct Moved(Vector3 GlobalPosition, Transform3D GlobalTransform);
|
||||
public readonly record struct Moved(Vector3 GlobalPosition, Transform3D GlobalTransform);
|
||||
|
||||
public readonly record struct Enable;
|
||||
public readonly record struct Enable;
|
||||
|
||||
public readonly record struct Attack;
|
||||
public readonly record struct Attack;
|
||||
|
||||
public readonly record struct AttackAnimationFinished;
|
||||
public readonly record struct AttackAnimationFinished;
|
||||
|
||||
public readonly record struct Killed;
|
||||
}
|
||||
public readonly record struct Killed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
using Godot;
|
||||
namespace GameJamDungeon;
|
||||
|
||||
namespace GameJamDungeon
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
public static class Output
|
||||
{
|
||||
public static class Output
|
||||
public static class Animations
|
||||
{
|
||||
public static class Animations
|
||||
{
|
||||
public readonly record struct Attack;
|
||||
}
|
||||
|
||||
public readonly record struct MovementComputed(Basis Rotation, Vector3 Velocity);
|
||||
|
||||
public readonly record struct ThrowItem;
|
||||
public readonly record struct Attack;
|
||||
}
|
||||
|
||||
public readonly record struct ThrowItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
public record Settings
|
||||
{
|
||||
public record Settings
|
||||
{
|
||||
public float MoveSpeed { get; set; }
|
||||
public float MoveSpeed { get; set; }
|
||||
|
||||
public float RotationSpeed { get; set; }
|
||||
public float RotationSpeed { get; set; }
|
||||
|
||||
public float Acceleration { get; set; }
|
||||
}
|
||||
public float Acceleration { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
[Meta]
|
||||
public abstract partial record State : StateLogic<State>;
|
||||
}
|
||||
[Meta]
|
||||
public abstract partial record State : StateLogic<State>;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public interface IPlayerLogic : ILogicBlock<PlayerLogic.State>;
|
||||
namespace GameJamDungeon;
|
||||
|
||||
[Meta, Id("player_logic")]
|
||||
[LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial class PlayerLogic : LogicBlock<PlayerLogic.State>, IPlayerLogic
|
||||
{
|
||||
public override Transition GetInitialState() => To<State.Idle>();
|
||||
}
|
||||
public interface IPlayerLogic : ILogicBlock<PlayerLogic.State>;
|
||||
|
||||
[Meta, Id("player_logic")]
|
||||
[LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial class PlayerLogic : LogicBlock<PlayerLogic.State>, IPlayerLogic
|
||||
{
|
||||
public override Transition GetInitialState() => To<State.Idle>();
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@ state "PlayerLogic State" as GameJamDungeon_PlayerLogic_State {
|
||||
state "Disabled" as GameJamDungeon_PlayerLogic_State_Disabled
|
||||
}
|
||||
|
||||
GameJamDungeon_PlayerLogic_State_Alive --> GameJamDungeon_PlayerLogic_State_Alive : Moved
|
||||
GameJamDungeon_PlayerLogic_State_Alive --> GameJamDungeon_PlayerLogic_State_Alive : PhysicsTick
|
||||
GameJamDungeon_PlayerLogic_State_Alive --> GameJamDungeon_PlayerLogic_State_Dead : Killed
|
||||
GameJamDungeon_PlayerLogic_State_Attacking --> GameJamDungeon_PlayerLogic_State_Idle : AttackAnimationFinished
|
||||
GameJamDungeon_PlayerLogic_State_Disabled --> GameJamDungeon_PlayerLogic_State_Idle : Enable
|
||||
GameJamDungeon_PlayerLogic_State_Idle --> GameJamDungeon_PlayerLogic_State_Attacking : Attack
|
||||
|
||||
GameJamDungeon_PlayerLogic_State_Alive : OnPhysicsTick → MovementComputed
|
||||
GameJamDungeon_PlayerLogic_State_Idle : OnAttack → Attack
|
||||
|
||||
[*] --> GameJamDungeon_PlayerLogic_State_Idle
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using Chickensoft.Introspection;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
public partial record State
|
||||
{
|
||||
public partial record State
|
||||
[Meta]
|
||||
public partial record Attacking : Alive, IGet<Input.AttackAnimationFinished>
|
||||
{
|
||||
[Meta]
|
||||
public partial record Attacking : Alive, IGet<Input.AttackAnimationFinished>
|
||||
public Transition On(in Input.AttackAnimationFinished input)
|
||||
{
|
||||
public Transition On(in Input.AttackAnimationFinished input)
|
||||
{
|
||||
return To<Idle>();
|
||||
}
|
||||
return To<Idle>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public abstract partial record State
|
||||
{
|
||||
[Meta, Id("player_logic_state_alive_idle")]
|
||||
public partial record Idle : Alive, IGet<Input.Attack>
|
||||
{
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public virtual Transition On(in Input.Attack input)
|
||||
{
|
||||
GD.Print("Attacking...");
|
||||
Output(new Output.Animations.Attack());
|
||||
return To<Attacking>();
|
||||
}
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public abstract partial record State
|
||||
{
|
||||
[Meta, Id("player_logic_state_alive_idle")]
|
||||
public partial record Idle : Alive, IGet<Input.Attack>
|
||||
{
|
||||
|
||||
public virtual Transition On(in Input.Attack input)
|
||||
{
|
||||
GD.Print("Attacking...");
|
||||
Output(new Output.Animations.Attack());
|
||||
return To<Attacking>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,27 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
public partial record State
|
||||
{
|
||||
public partial record State
|
||||
[Meta, Id("player_logic_alive")]
|
||||
public abstract partial record Alive : State, IGet<Input.PhysicsTick>, IGet<Input.Killed>
|
||||
{
|
||||
[Meta, Id("player_logic_alive")]
|
||||
public abstract partial record Alive : State, IGet<Input.PhysicsTick>, IGet<Input.Moved>, IGet<Input.Killed>
|
||||
public virtual Transition On(in Input.PhysicsTick input)
|
||||
{
|
||||
public virtual Transition On(in Input.PhysicsTick input)
|
||||
{
|
||||
var delta = input.Delta;
|
||||
var player = Get<IPlayer>();
|
||||
var settings = Get<Settings>();
|
||||
var delta = input.Delta;
|
||||
var player = Get<IPlayer>();
|
||||
player.Move((float)delta);
|
||||
return ToSelf();
|
||||
}
|
||||
|
||||
var rawInput = player.GetGlobalInputVector();
|
||||
var strafeLeftInput = player.GetLeftStrafeInputVector();
|
||||
var strafeRightInput = player.GetRightStrafeInputVector();
|
||||
|
||||
var transform = player.Transform;
|
||||
transform.Basis = new Basis(Vector3.Up, settings.RotationSpeed * -rawInput.X * (float)delta) * transform.Basis;
|
||||
var moveDirection = new Vector3(strafeRightInput - strafeLeftInput, 0, rawInput.Z);
|
||||
var velocity = player.Basis * moveDirection * settings.MoveSpeed * settings.Acceleration;
|
||||
|
||||
Output(new Output.MovementComputed(transform.Basis, velocity));
|
||||
|
||||
return ToSelf();
|
||||
}
|
||||
|
||||
public virtual Transition On(in Input.Moved input)
|
||||
{
|
||||
var gameRepo = Get<IGameRepo>();
|
||||
gameRepo.SetPlayerGlobalPosition(input.GlobalPosition);
|
||||
gameRepo.SetPlayerGlobalTransform(input.GlobalTransform);
|
||||
return ToSelf();
|
||||
}
|
||||
|
||||
public Transition On(in Input.Killed input)
|
||||
{
|
||||
GD.Print("Player died");
|
||||
return To<Dead>();
|
||||
}
|
||||
public Transition On(in Input.Killed input)
|
||||
{
|
||||
GD.Print("Player died");
|
||||
return To<Dead>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
using Chickensoft.Introspection;
|
||||
|
||||
namespace GameJamDungeon
|
||||
namespace GameJamDungeon;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial class PlayerLogic
|
||||
public abstract partial record State
|
||||
{
|
||||
public abstract partial record State
|
||||
[Meta, Id("player_logic_state_disabled")]
|
||||
public partial record Disabled : State, IGet<Input.Enable>
|
||||
{
|
||||
[Meta, Id("player_logic_state_disabled")]
|
||||
public partial record Disabled : State, IGet<Input.Enable>
|
||||
public Disabled()
|
||||
{
|
||||
public Disabled()
|
||||
{
|
||||
OnAttach(() => Get<IAppRepo>().GameEntered += OnGameEntered);
|
||||
OnDetach(() => Get<IAppRepo>().GameEntered -= OnGameEntered);
|
||||
}
|
||||
|
||||
public Transition On(in Input.Enable input) => To<Idle>();
|
||||
|
||||
public void OnGameEntered() => Input(new Input.Enable());
|
||||
OnAttach(() => Get<IAppRepo>().GameEntered += OnGameEntered);
|
||||
OnDetach(() => Get<IAppRepo>().GameEntered -= OnGameEntered);
|
||||
}
|
||||
|
||||
public Transition On(in Input.Enable input) => To<Idle>();
|
||||
|
||||
public void OnGameEntered() => Input(new Input.Enable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user