Files
GameJamDungeon/src/player/Player.cs
2024-09-15 02:17:40 -07:00

286 lines
9.3 KiB
C#

using Chickensoft.AutoInject;
using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
using Chickensoft.SaveFileBuilder;
using Godot;
namespace GameJamDungeon
{
public interface IPlayer : ICharacterBody3D, IKillable
{
PlayerLogic PlayerLogic { get; }
public Vector3 GetGlobalInputVector();
public float GetLeftStrafeInputVector();
public float GetRightStrafeInputVector();
event Player.InventoryButtonPressedEventHandler InventoryButtonPressed;
event Player.MinimapButtonHeldEventHandler MinimapButtonHeld;
event Player.PauseButtonPressedEventHandler PauseButtonPressed;
}
[Meta(typeof(IAutoNode))]
public partial class Player : CharacterBody3D, IPlayer, IProvide<PlayerLogic>
{
public override void _Notification(int what) => this.Notify(what);
PlayerLogic IProvide<PlayerLogic>.Value() => PlayerLogic;
[Dependency]
public IAppRepo AppRepo => this.DependOn<IAppRepo>();
[Dependency]
public IGameRepo GameRepo => this.DependOn<IGameRepo>();
[Dependency]
public ISaveChunk<GameData> GameChunk => this.DependOn<ISaveChunk<GameData>>();
[Signal]
public delegate void InventoryButtonPressedEventHandler();
[Signal]
public delegate void MinimapButtonHeldEventHandler();
[Signal]
public delegate void PauseButtonPressedEventHandler();
[Export]
public PlayerStatResource PlayerStatResource { get; set; } = default!;
public PlayerLogic.Settings Settings { get; set; } = default!;
public PlayerLogic PlayerLogic { get; set; } = default!;
public PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
[Node] public IAnimationPlayer AnimationPlayer { get; set; } = default!;
[Node] public AnimatedSprite2D SwordSlashAnimation { get; set; } = default!;
[Node] public IHitbox Hitbox { get; set; } = default!;
[Node] public Timer HealthTimer { get; set; } = default!;
[Node] public IArea3D CollisionDetector { get; set; } = default!;
private PlayerData PlayerData { get; set; } = default!;
public void Initialize()
{
AnimationPlayer.AnimationFinished += OnAnimationFinished;
}
public void Setup()
{
Settings = new PlayerLogic.Settings() { RotationSpeed = PlayerStatResource.RotationSpeed, MoveSpeed = PlayerStatResource.MoveSpeed, Acceleration = PlayerStatResource.Acceleration };
PlayerData = new PlayerData()
{
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);
PlayerLogic = new PlayerLogic();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
PlayerLogic.Set(AppRepo);
PlayerLogic.Set(GameRepo);
PlayerLogic.Set(PlayerData);
PlayerData.Inventory.EquippedAccessory.Sync += EquippedAccessory_Sync;
PlayerData.CurrentHP.Sync += CurrentHP_Sync;
}
public void OnResolved()
{
PlayerBinding = PlayerLogic.Bind();
PlayerBinding
.Handle((in PlayerLogic.Output.MovementComputed output) =>
{
Transform = Transform with { Basis = output.Rotation };
Velocity = output.Velocity;
})
.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);
SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2;
GlobalPosition = GameRepo.PlayerGlobalPosition.Value;
GameRepo.PlayerGlobalPosition.Sync += PlayerGlobalPosition_Sync;
HealthTimer.Timeout += OnHealthTimerTimeout;
CollisionDetector.AreaEntered += OnEnemyHitBoxEntered;
PlayerData.Inventory.AccessoryUnequipped += Inventory_AccessoryUnequipped;
}
public void OnReady()
{
SetPhysicsProcess(true);
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed(GameInputs.Inventory))
{
GD.Print("Inventory button pressed");
EmitSignal(SignalName.InventoryButtonPressed);
}
if (@event.IsActionPressed(GameInputs.MiniMap))
{
GD.Print("MiniMap button pressed");
EmitSignal(SignalName.MinimapButtonHeld);
}
if (@event.IsActionPressed(GameInputs.Pause))
{
GD.Print("Pause button pressed");
EmitSignal(SignalName.PauseButtonPressed);
}
if (@event.IsActionPressed(GameInputs.Attack))
PlayerLogic.Input(new PlayerLogic.Input.Attack());
if (@event.IsActionPressed(GameInputs.Sprint))
Settings.MoveSpeed *= 4;
if (@event.IsActionReleased(GameInputs.Sprint))
Settings.MoveSpeed /= 4;
}
public void OnPhysicsProcess(double delta)
{
PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta));
MoveAndSlide();
PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition, GlobalTransform));
}
public Vector3 GetGlobalInputVector()
{
var rawInput = Input.GetVector(GameInputs.MoveLeft, GameInputs.MoveRight, GameInputs.MoveUp, GameInputs.MoveDown);
var input = new Vector3
{
X = rawInput.X,
Z = rawInput.Y
};
return input with { Y = 0f };
}
public float GetLeftStrafeInputVector()
{
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;
throwItem.AnimationPlayer.Play("throw");
}
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)
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 OnEnemyHitBoxEntered(Area3D area)
{
if (area is IHitbox hitBox)
{
var enemy = hitBox.GetParent<IEnemy>();
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= enemy.EnemyStatResource.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateEnemyDamage(PlayerData.CurrentDefense.Value + PlayerData.BonusDefense, enemy.EnemyStatResource, GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats, isCriticalHit);
PlayerData.SetCurrentHP(PlayerData.CurrentHP.Value - Mathf.RoundToInt(damage));
GD.Print($"Player hit for {damage} damage.");
}
}
private void CurrentHP_Sync(int newHealth)
{
if (newHealth <= 0)
Kill();
}
}
}