Files
GameJamDungeon/src/player/Player.cs
2024-09-08 14:19:30 -07:00

247 lines
7.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
{
PlayerStatInfo PlayerStatInfo { get; }
PlayerLogic PlayerLogic { get; }
PlayerData PlayerData { get; }
public Vector3 GetGlobalInputVector();
}
[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>>();
/// <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^2/sec).</summary>
[Export(PropertyHint.Range, "0, 100, 0.1")]
public float Acceleration { get; set; } = 4f;
[Export]
public PlayerStatInfo PlayerStatInfo { get; set; }
public PlayerLogic.Settings Settings { get; set; } = default!;
public PlayerLogic PlayerLogic { get; set; } = default!;
public PlayerData PlayerData { 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 Label HPNumber { get; set; } = default!;
[Node] public Label VTNumber { get; set; } = default!;
[Node] public ProgressBar HPBar { get; set; } = default!;
[Node] public ProgressBar VTBar { get; set; } = default!;
[Node] public IArea3D CollisionDetector { get; set; } = default!;
private AutoProp<double> _currentHP { get; set; } = default!;
private AutoProp<int> _currentVT { get; set; } = default!;
public void Initialize()
{
AnimationPlayer.AnimationFinished += OnAnimationFinished;
}
public void Setup()
{
Settings = new PlayerLogic.Settings(RotationSpeed, MoveSpeed);
PlayerLogic = new PlayerLogic();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
PlayerLogic.Set(AppRepo);
PlayerLogic.Set(GameRepo);
PlayerLogic.Set(PlayerData);
GameRepo.SetPlayerGlobalPosition(GlobalPosition);
GameRepo.PlayerGlobalPosition.Sync += OnPlayerPositionUpdated;
_currentHP = new AutoProp<double>(PlayerStatInfo.MaximumHP);
_currentVT = new AutoProp<int>(PlayerStatInfo.MaximumVT);
_currentHP.Sync += OnHPChanged;
_currentVT.Sync += OnVTChanged;
HPBar.MaxValue = PlayerStatInfo.MaximumHP;
HPBar.Value = _currentHP.Value;
VTBar.MaxValue = PlayerStatInfo.MaximumVT;
VTBar.Value = _currentVT.Value;
HealthTimer.Timeout += OnHealthTimerTimeout;
CollisionDetector.AreaEntered += OnEnemyHitBoxEntered;
}
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 weaponInfo = (WeaponInfo)GameRepo.EquippedWeapon.Info;
var attackSpeed = (float)weaponInfo.AttackSpeed;
AnimationPlayer.SetSpeedScale(attackSpeed);
AnimationPlayer.Play("attack");
if (weaponInfo.WeaponTags.Contains(WeaponTag.SelfDamage))
_currentHP.OnNext(_currentHP.Value - 5);
})
.Handle((in PlayerLogic.Output.ThrowItem output) =>
{
ThrowItem();
});
this.Provide();
PlayerLogic.Start();
SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2;
}
public void OnReady()
{
SetPhysicsProcess(true);
}
private void OnEnemyHitBoxEntered(Area3D area)
{
if (area is IHitbox hitBox)
{
if (_currentHP.Value > 0)
{
var enemy = hitBox.GetParent<IEnemy>();
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= enemy.EnemyStatInfo.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateEnemyDamage(hitBox.Damage, PlayerStatInfo, enemy.EnemyStatInfo, GameRepo.EquippedArmor.ArmorInfo, isCriticalHit);
_currentHP.OnNext(_currentHP.Value - damage);
GD.Print($"Player hit for {damage} damage.");
}
}
}
public void OnPhysicsProcess(double delta)
{
PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta));
var attackIsPressed = Input.IsActionJustPressed(GameInputs.Attack);
if (attackIsPressed && !GameRepo.IsWithinDialogueSpace)
PlayerLogic.Input(new PlayerLogic.Input.Attack());
MoveAndSlide();
PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition));
}
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 void ThrowItem()
{
var itemScene = GD.Load<PackedScene>("res://src/items/throwable/ThrowableItem.tscn");
var throwItem = itemScene.Instantiate<IThrowableItem>();
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;
}
private void OnEquippedWeaponChanged(Weapon info) => Hitbox.Damage = ((WeaponInfo)info.Info).Damage;
private void OnHPChanged(double newHP)
{
HPNumber.Text = Mathf.RoundToInt(newHP).ToString();
HPBar.Value = newHP;
if (newHP <= 0.0)
PlayerLogic.Input(new PlayerLogic.Input.Killed());
}
private void OnVTChanged(int newVT)
{
VTNumber.Text = newVT.ToString();
VTBar.Value = newVT;
}
private void OnPlayerPositionUpdated(Vector3 globalPosition) => GlobalPosition = globalPosition;
private void OnHealthTimerTimeout()
{
if (_currentVT.Value > 0)
_currentVT.OnNext(_currentVT.Value - 1);
else
_currentHP.OnNext(_currentHP.Value - 1);
}
}
}