286 lines
9.3 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|