Files
GameJamDungeon/Zennysoft.Game.Ma/src/player/Player.cs
2026-03-02 22:28:23 -08:00

948 lines
34 KiB
C#

using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using SimpleInjector;
using System;
using System.Linq;
using Zennysoft.Ma.Adapter;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Game.Ma;
[Meta(typeof(IAutoNode))]
public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
{
#region Dependency Injection
public override void _Notification(int what) => this.Notify(what);
IPlayer IProvide<IPlayer>.Value() => this;
[Dependency] public IGame _game => this.DependOn<IGame>();
private PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
#endregion
public IHealthComponent HealthComponent { get; private set; }
public IVTComponent VTComponent { get; private set; }
public IAttackComponent AttackComponent { get; private set; }
public IDefenseComponent DefenseComponent { get; private set; }
public IExperiencePointsComponent ExperiencePointsComponent { get; private set; }
public ILuckComponent LuckComponent { get; private set; }
public IEquipmentComponent EquipmentComponent { get; private set; }
public IStatusEffectComponent StatusEffectComponent { get; private set; }
public Vector3 CurrentPosition => GlobalPosition;
public Basis CurrentBasis => Transform.Basis;
public IInventory Inventory { get; private set; } = default!;
public event Action PlayerDied;
private PlayerLogic.Settings Settings { get; set; } = default!;
private IPlayerLogic PlayerLogic { get; set; } = default!;
#region Exports
[ExportGroup("Movement")]
[Export(PropertyHint.Range, "0, 60, 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(PropertyHint.Range, "1, 60, 1")] public double RustDuration { get; set; } = 30;
[Export]
private bool HealthTimerIsActive = false;
#endregion
#region Node Dependencies
[Node] public IAnimationPlayer SpellFXAnimations { get; set; } = default!;
[Node] private IAnimationPlayer WeaponAnimations { get; set; } = default!;
[Node] private IAnimationPlayer PlayerFXAnimations { get; set; } = default!;
[Node] private IAnimationPlayer TakeDamageAnimationPlayer { get; set; } = default!;
[Node] private Area3D Hitbox { get; set; } = default!;
[Node] private Area3D CollisionDetector { get; set; } = default!;
[Node] private Timer HealthTimer { get; set; } = default!;
[Node] private RigidBody3D WallCheck { get; set; } = default!;
[Node] private AudioStreamPlayer3D WalkSFX { get; set; } = default!;
[Node] private CollisionShape3D MainCollision { get; set; } = default!;
[Node] private ShakeCamera _camera3D { get; set; } = default!;
[Node] private PlayerProjectile FireReactor { get; set; } = default!;
[Node] private PlayerProjectile AirReactor { get; set; } = default!;
[Node] private PlayerProjectile WaterReactor { get; set; } = default!;
[Node] private PlayerProjectile PersuaderBullet { get; set; } = default!;
[Node] private Sprite2D PersuaderCrosshair { get; set; } = default!;
#endregion
[Export]
public int HealthTimerHPRate { get; set; } = 1;
[Export]
public bool AutoIdentifyItems { get; set; } = false;
[Export]
public float HealthTimerSpeedModifier { get; set; } = 1f;
[Export]
public bool AutoRevive { get; set; } = false;
private bool flipAttack = false;
private float _healthTimerWaitTime = 3.0f;
private bool _healthTimerActive = true;
private bool _debugSprint = false;
private float _knockbackStrength = 0.0f;
private Vector3 _knockbackDirection = Vector3.Zero;
private ItemReroller _itemReroller;
private PlayerEffectService _playerEffectService;
public void Initialize()
{
var container = new SimpleInjector.Container();
container.Register<IPlayerLogic, PlayerLogic>(Lifestyle.Singleton);
PlayerLogic = container.GetInstance<IPlayerLogic>();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
Inventory = new Inventory();
Inventory.InventoryChanged += Inventory_InventoryChanged;
HealthComponent = new HealthComponent(InitialHP);
VTComponent = new VTComponent(InitialVT);
AttackComponent = new AttackComponent(InitialAttack);
DefenseComponent = new DefenseComponent(InitialDefense);
ExperiencePointsComponent = new ExperiencePointsComponent();
LuckComponent = new LuckComponent(InitialLuck);
EquipmentComponent = new EquipmentComponent();
StatusEffectComponent = new StatusEffectComponent(RustDuration);
_itemReroller = new ItemReroller(ItemDatabase.Instance);
_playerEffectService = new PlayerEffectService(this);
Settings = new PlayerLogic.Settings() { RotationSpeed = RotationSpeed, MoveSpeed = MoveSpeed, Acceleration = Acceleration };
PlayerBinding = PlayerLogic.Bind();
PlayerBinding
.Handle((in PlayerLogic.Output.ThrowItem output) =>
{
})
.Handle((in PlayerLogic.Output.Move output) =>
{
Move(output.delta);
});
PlayerLogic.Start();
this.Provide();
}
public void ResetPlayerData()
{
PlayerFXAnimations.Play("RESET");
Inventory.Items.Clear();
HealthComponent.Reset();
VTComponent.Reset();
AttackComponent.Reset();
DefenseComponent.Reset();
ExperiencePointsComponent.Reset();
LuckComponent.Reset();
EquipmentComponent.Reset();
HealthTimer.Timeout += OnHealthTimerTimeout;
}
#region Initialization
public void OnReady()
{
Hitbox.AreaEntered += Hitbox_AreaEntered;
CollisionDetector.AreaEntered += CollisionDetector_AreaEntered;
HealthComponent.CurrentHP.Changed += InverseHPToAttackPowerSync;
HealthComponent.HealthReachedZero += Die;
ExperiencePointsComponent.PlayerLevelUp += OnLevelUp;
ExperiencePointsComponent.PlayerLevelDown += OnLevelDown;
PlayerFXAnimations.AnimationFinished += PlayerFXAnimations_AnimationFinished;
HealthTimer.WaitTime = _healthTimerWaitTime;
SetProcessInput(false);
SetPhysicsProcess(false);
}
#endregion
public void Activate()
{
SetProcessInput(true);
SetPhysicsProcess(true);
SetHealthTimerStatus(HealthTimerIsActive);
Hitbox.SetDeferred(Area3D.PropertyName.Monitoring, true);
Hitbox.SetDeferred(Area3D.PropertyName.Monitorable, true);
}
public void Deactivate()
{
Velocity = Vector3.Zero;
SetProcessInput(false);
SetPhysicsProcess(false);
SetHealthTimerStatus(false);
Hitbox.SetDeferred(Area3D.PropertyName.Monitoring, false);
Hitbox.SetDeferred(Area3D.PropertyName.Monitorable, false);
}
public void SetHealthTimerStatus(bool isActive)
{
if (isActive)
HealthTimer.Start();
else
HealthTimer.Stop();
}
public void ModifyHealthTimerSpeed(float newSpeed)
{
HealthTimerSpeedModifier = newSpeed;
HealthTimer.Stop();
HealthTimer.WaitTime = _healthTimerWaitTime * newSpeed;
HealthTimer.Start();
}
public void ModifyHealthRecoveryAmount(int newAmount)
{
}
public void TeleportPlayer((Vector3 Rotation, Vector3 Position) newTransform)
{
Rotation = newTransform.Rotation;
Position = newTransform.Position;
ResetPhysicsInterpolation();
}
public void TakeDamage(AttackData damage)
{
_camera3D.AddShake(1.0f);
TakeDamageAnimationPlayer.Play("take_damage");
var damageReceived = DamageCalculator.CalculateDamage(damage, TotalDefense, EquipmentComponent.ElementalResistance);
HealthComponent.Damage(damageReceived);
SfxDatabase.Instance.Play(SoundEffect.TakeDamage);
if (EquipmentComponent.EquippedArmor.Value.ArmorTag == ArmorTag.DegradeOnHit)
{
EquipmentComponent.EquippedArmor.Value.DecreaseArmorDefense(1);
EquipmentComponent.UpdateEquipment(EquipmentComponent.EquippedArmor.Value);
}
}
public void Knockback(float impulse)
{
_knockbackStrength = impulse;
_knockbackDirection = GlobalBasis.Z.Normalized();
}
public void PlayJumpScareAnimation() => PlayerFXAnimations.Play("jump_scare");
public IBaseInventoryItem IdentifyItem(IBaseInventoryItem unidentifiedItem) => _itemReroller.RerollItem(unidentifiedItem, Inventory);
public int TotalAttack => AttackComponent.CurrentAttack.Value + EquipmentComponent.BonusAttack;
public int TotalDefense => DefenseComponent.CurrentDefense.Value + EquipmentComponent.BonusDefense;
public int TotalLuck => LuckComponent.Luck.Value + EquipmentComponent.BonusLuck;
public void LevelUp()
{
ExperiencePointsComponent.LevelUp();
}
public void Die()
{
PlayerFXAnimations.Play("death");
if (AutoRevive)
return;
HealthTimer.WaitTime = _healthTimerWaitTime;
HealthTimer.Timeout -= OnHealthTimerTimeout;
SetProcessInput(false);
SetPhysicsProcess(false);
}
public override void _Input(InputEvent @event)
{
if (GetTree().Paused)
return;
if (@event.IsActionPressed(GameInputs.Attack))
Attack();
if (@event.IsActionPressed(GameInputs.Sprint))
_debugSprint = true;
else if (@event.IsActionReleased(GameInputs.Sprint))
_debugSprint = false;
}
public void OnPhysicsProcess(double delta)
{
PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta));
}
public void Equip(IEquipableItem equipable)
{
if (equipable.ItemTag == ItemTag.MysteryItem)
{
var rerolledItem = _itemReroller.RerollItem(equipable, Inventory);
Equip(rerolledItem);
return;
}
if (equipable is Weapon && EquipmentComponent.EquippedWeapon.Value != null && !EquipmentComponent.EquippedWeapon.Value.Glued)
Unequip(EquipmentComponent.EquippedWeapon.Value);
if (equipable is Armor && EquipmentComponent.EquippedArmor.Value != null && !EquipmentComponent.EquippedArmor.Value.Glued)
Unequip(EquipmentComponent.EquippedArmor.Value);
if (equipable is Accessory && EquipmentComponent.EquippedAccessory.Value != null && !EquipmentComponent.EquippedAccessory.Value.Glued)
Unequip(EquipmentComponent.EquippedAccessory.Value);
if (equipable is Ammo && EquipmentComponent.EquippedAmmo.Value != null && !EquipmentComponent.EquippedAmmo.Value.Glued)
Unequip(EquipmentComponent.EquippedAmmo.Value);
if (equipable.ItemTag == ItemTag.GlueOnEquip)
equipable.Glued = true;
HealthComponent.RaiseMaximumHP(equipable.BonusHP, false);
VTComponent.RaiseMaximumVT(equipable.BonusVT, false);
LuckComponent.IncreaseLuck(equipable.BonusLuck);
if (equipable is IAugmentableItem augmentable)
if (augmentable.Augment != null)
augmentable.Augment.AugmentType.Apply();
EquipmentComponent.Equip(equipable);
if (equipable is Weapon weapon)
{
if (weapon.WeaponTag == WeaponTag.KineticProjectile)
PersuaderCrosshair.Show();
if (weapon.WeaponTag == WeaponTag.InverseHPAttackPower)
InverseHPToAttackPowerSync(HealthComponent.CurrentHP.Value);
}
if (equipable is Accessory accessory)
{
if (accessory.AccessoryTag == AccessoryTag.BoostEXPGainRate)
ExperiencePointsComponent.ModifyExpGainRate(ExperiencePointsComponent.ExpGainRate.Value + 0.2f);
}
_game.NotifyInventory($"{equipable.ItemName} equipped.");
}
public void Unequip(IEquipableItem equipable)
{
HealthComponent.SetMaximumHealth(HealthComponent.MaximumHP.Value - equipable.BonusHP);
VTComponent.SetMaximumVT(VTComponent.MaximumVT.Value - equipable.BonusVT);
LuckComponent.DecreaseLuck(equipable.BonusLuck);
if (equipable is IAugmentableItem augmentItem && augmentItem.Augment != null)
augmentItem.Augment.AugmentType.Remove();
EquipmentComponent.Unequip(equipable);
if (equipable is Weapon weapon)
{
if (weapon.WeaponTag == WeaponTag.KineticProjectile)
PersuaderCrosshair.Hide();
}
if (equipable is Accessory accessory)
{
if (accessory.AccessoryTag == AccessoryTag.BoostEXPGainRate)
ExperiencePointsComponent.ModifyExpGainRate(ExperiencePointsComponent.ExpGainRate.Value - 0.2f);
}
_game.NotifyInventory($"{equipable.ItemName} unequipped.");
}
public void ApplyNewAugment(IAugmentItem jewel, IAugmentableItem augmentableItem)
{
Inventory.Remove(jewel);
ApplyNewAugment((dynamic)augmentableItem, jewel as Jewel);
if (augmentableItem.Augment != null && augmentableItem is IEquipableItem equipable && EquipmentComponent.IsItemEquipped(equipable))
augmentableItem.Augment.AugmentType.Apply();
_game.NotifyInventory($"{((IBaseInventoryItem)augmentableItem).ItemName} augmented with {jewel.ItemName}.");
}
private void ApplyNewAugment(Weapon weapon, Jewel jewel)
{
switch (jewel.Stats.JewelTag)
{
case JewelTags.AeolicElement:
weapon.Stats.WeaponElement = ElementType.Aeolic;
weapon.Augment = new Augment(JewelTags.AeolicElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.HydricElement:
weapon.Stats.WeaponElement = ElementType.Hydric;
weapon.Augment = new Augment(JewelTags.HydricElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.SlowVTReduction:
weapon.Augment = new Augment(JewelTags.SlowVTReduction, new SlowVTReductionAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.HastenVT:
weapon.Augment = new Augment(JewelTags.HastenVT, new HastenVTAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.ReviveUserOnce:
weapon.Augment = new Augment(JewelTags.ReviveUserOnce, new RevivePlayerAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseHPRecovery:
weapon.Augment = new Augment(JewelTags.IncreaseHPRecovery, new HPRecoverySpeedAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.LowerEXPGain:
weapon.Augment = new Augment(JewelTags.LowerEXPGain, new LowerEXPRateAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.ItemRescue:
if (!_game.RescuedItems.TryAdd(weapon))
break;
if (EquipmentComponent.IsItemEquipped(weapon))
Unequip(weapon);
Inventory.Remove(weapon);
SfxDatabase.Instance.Play(SoundEffect.Transfer);
break;
case JewelTags.Glue:
if (!EquipmentComponent.IsItemEquipped(weapon))
break;
weapon.Glued = true;
weapon.Augment = new Augment(JewelTags.Glue, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.TelluricElement:
weapon.Stats.WeaponElement = ElementType.Telluric;
weapon.Augment = new Augment(JewelTags.TelluricElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.AutoIdentifyAllItems:
weapon.Augment = new Augment(JewelTags.AutoIdentifyAllItems, new IdentifyAllItemsAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseAtkDefLuck:
weapon.IncreaseAttack(jewel.Stats.BonusAttack);
weapon.IncreaseDefense(jewel.Stats.BonusDefense);
weapon.IncreaseLuck(jewel.Stats.BonusLuck);
weapon.Augment = new Augment(JewelTags.IncreaseAtkDefLuck, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseLuck:
weapon.IncreaseLuck(25);
weapon.Augment = new Augment(JewelTags.IncreaseLuck, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
}
EquipmentComponent.UpdateEquipment(weapon);
}
private void ApplyNewAugment(Armor armor, Jewel jewel)
{
switch (jewel.Stats.JewelTag)
{
case JewelTags.AeolicElement:
armor.Stats.AeolicResistance += jewel.Stats.AeolicResistance;
armor.Augment = new Augment(JewelTags.AeolicElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.HydricElement:
armor.Stats.HydricResistance += jewel.Stats.HydricResistance;
armor.Augment = new Augment(JewelTags.HydricElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.SlowVTReduction:
armor.Augment = new Augment(JewelTags.SlowVTReduction, new SlowVTReductionAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.HastenVT:
armor.Augment = new Augment(JewelTags.HastenVT, new HastenVTAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.ReviveUserOnce:
armor.Augment = new Augment(JewelTags.ReviveUserOnce, new RevivePlayerAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseHPRecovery:
armor.Augment = new Augment(JewelTags.IncreaseHPRecovery, new HPRecoverySpeedAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.LowerEXPGain:
armor.Augment = new Augment(JewelTags.LowerEXPGain, new LowerEXPRateAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.ItemRescue:
if (!_game.RescuedItems.TryAdd(armor))
break;
if (EquipmentComponent.IsItemEquipped(armor))
Unequip(armor);
Inventory.Remove(armor);
SfxDatabase.Instance.Play(SoundEffect.Transfer);
break;
case JewelTags.Glue:
if (!EquipmentComponent.IsItemEquipped(armor))
break;
armor.Glued = true;
armor.Augment = new Augment(JewelTags.Glue, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.TelluricElement:
armor.Stats.TelluricResistance += jewel.Stats.TelluricResistance;
armor.Augment = new Augment(JewelTags.TelluricElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.AutoIdentifyAllItems:
armor.Augment = new Augment(JewelTags.AutoIdentifyAllItems, new IdentifyAllItemsAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseAtkDefLuck:
armor.IncreaseAttack(jewel.Stats.BonusAttack);
armor.IncreaseDefense(jewel.Stats.BonusDefense);
armor.IncreaseLuck(jewel.Stats.BonusLuck);
armor.Augment = new Augment(JewelTags.IncreaseAtkDefLuck, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseLuck:
armor.IncreaseLuck(jewel.Stats.BonusLuck);
armor.Augment = new Augment(JewelTags.IncreaseLuck, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
}
EquipmentComponent.UpdateEquipment(armor);
}
private void ApplyNewAugment(Accessory accessory, Jewel jewel)
{
switch (jewel.Stats.JewelTag)
{
case JewelTags.AeolicElement:
accessory.Stats.AeolicResistance += jewel.Stats.AeolicResistance;
accessory.Augment = new Augment(JewelTags.AeolicElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.HydricElement:
accessory.Stats.HydricResistance += jewel.Stats.HydricResistance;
accessory.Augment = new Augment(JewelTags.HydricElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.SlowVTReduction:
accessory.Augment = new Augment(JewelTags.SlowVTReduction, new SlowVTReductionAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.HastenVT:
accessory.Augment = new Augment(JewelTags.HastenVT, new HastenVTAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.ReviveUserOnce:
accessory.Augment = new Augment(JewelTags.ReviveUserOnce, new RevivePlayerAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseHPRecovery:
accessory.Augment = new Augment(JewelTags.IncreaseHPRecovery, new HPRecoverySpeedAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.LowerEXPGain:
accessory.Augment = new Augment(JewelTags.LowerEXPGain, new LowerEXPRateAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.ItemRescue:
if (!_game.RescuedItems.TryAdd(accessory))
break;
if (EquipmentComponent.IsItemEquipped(accessory))
Unequip(accessory);
Inventory.Remove(accessory);
SfxDatabase.Instance.Play(SoundEffect.Transfer);
break;
case JewelTags.Glue:
if (!EquipmentComponent.IsItemEquipped(accessory))
break;
accessory.Glued = true;
accessory.Augment = new Augment(JewelTags.Glue, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.TelluricElement:
accessory.Stats.TelluricResistance += jewel.Stats.TelluricResistance;
accessory.Augment = new Augment(JewelTags.TelluricElement, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.AutoIdentifyAllItems:
accessory.Augment = new Augment(JewelTags.AutoIdentifyAllItems, new IdentifyAllItemsAugment(this), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseAtkDefLuck:
accessory.IncreaseAttack(jewel.Stats.BonusAttack);
accessory.IncreaseDefense(jewel.Stats.BonusDefense);
accessory.IncreaseLuck(jewel.Stats.BonusLuck);
accessory.Augment = new Augment(JewelTags.IncreaseAtkDefLuck, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseLuck:
accessory.IncreaseLuck(jewel.Stats.BonusLuck);
accessory.Augment = new Augment(JewelTags.IncreaseLuck, new BasicAugment(), jewel.ItemName, jewel.StatDescription, jewel.GetTexture());
break;
}
EquipmentComponent.UpdateEquipment(accessory);
}
private static Vector3 GlobalInputVector
{
get
{
var rawInput = Godot.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 PlaySpellFX(SpellFXEnum spellEnum)
{
SpellFXAnimations.Stop();
SpellFXAnimations.Play(spellEnum.ToString());
}
private void OnLevelUp()
{
BoostPlayerHPFromLevelUp();
}
private void OnLevelDown()
{
LowerPlayerHPFromLevelDown();
}
private void BoostPlayerHPFromLevelUp()
{
var rng = new RandomNumberGenerator();
rng.Randomize();
var hpIncrease = rng.RandiRange(3, 6);
HealthComponent.RaiseMaximumHP(hpIncrease);
}
private void LowerPlayerHPFromLevelDown()
{
var rng = new RandomNumberGenerator();
rng.Randomize();
var hpIncrease = rng.RandiRange(3, 6);
HealthComponent.LowerMaximumHP(hpIncrease);
}
private static float LeftStrafeInputVector => Godot.Input.GetActionStrength(GameInputs.StrafeLeft);
private static float RightStrafeInputVector => Godot.Input.GetActionStrength(GameInputs.StrafeRight);
private async void Attack()
{
var weapon = EquipmentComponent.EquippedWeapon.Value as Weapon;
if (weapon.WeaponTag == WeaponTag.ElementalProjectile || weapon.WeaponTag == WeaponTag.KineticProjectile)
{
HandleProjectile(weapon);
return;
}
if (WeaponAnimations.IsPlaying())
return;
PlayAttackAnimation();
if (PlayerIsHittingGeometry())
{
await ToSignal(GetTree().CreateTimer(0.15f), "timeout");
WeaponAnimations.Stop();
WeaponAnimations.Play("Hit Wall");
return;
}
if (weapon.WeaponTag == WeaponTag.DegradeOnSwing)
_playerEffectService.Degrade();
else if (weapon.WeaponTag == WeaponTag.SelfDamage)
_playerEffectService.TakeSelfDamage(5);
}
private void HandleProjectile(Weapon weapon)
{
var ammo = EquipmentComponent.EquippedAmmo.Value as Ammo;
if (ammo.Count == null || ammo.Count?.Value <= 0)
return;
var fired = false;
if (weapon.WeaponTag == WeaponTag.ElementalProjectile)
{
if (ammo.AmmoElement == ElementType.Igneous)
fired = FireReactor.Fire();
if (ammo.AmmoElement == ElementType.Aeolic)
fired = AirReactor.Fire();
if (ammo.AmmoElement == ElementType.Hydric)
fired = WaterReactor.Fire();
}
if (weapon.WeaponTag == WeaponTag.KineticProjectile)
{
PlayAttackAnimation();
fired = PersuaderBullet.Fire();
}
if (!fired)
return;
ammo.SetCount(ammo.Count.Value - 1);
EquipmentComponent.UpdateEquipment(ammo);
if (ammo.Count.Value <= 0)
{
EquipmentComponent.Unequip(ammo);
Inventory.Remove(ammo);
}
}
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 PlayAttackAnimation()
{
var weapon = (Weapon)EquipmentComponent.EquippedWeapon.Value;
SfxDatabase.Instance.Play(weapon.SoundEffect);
WeaponAnimations.SetSpeedScale((float)weapon.AttackSpeed);
var potentialAnimName = weapon.Stats.Name;
if (WeaponAnimations.HasAnimation(potentialAnimName))
WeaponAnimations.Play(potentialAnimName);
else if (weapon.WeaponElement == ElementType.Aeolic)
WeaponAnimations.Play("Air Slash");
else if (weapon.WeaponElement == ElementType.Hydric)
WeaponAnimations.Play("Water Slash");
else if (weapon.WeaponElement == ElementType.Igneous)
WeaponAnimations.Play("Fire Slash");
else if (weapon.WeaponElement == ElementType.Telluric)
WeaponAnimations.Play("Earth Slash");
else if (string.IsNullOrWhiteSpace(potentialAnimName))
WeaponAnimations.Play("Unarmed");
else
WeaponAnimations.Play("Normal Slash");
}
private void PlayerFXAnimations_AnimationFinished(StringName animName)
{
if (animName == "death")
{
if (AutoRevive)
PlayerFXAnimations.PlayBackwards("revive");
else
PlayerDied?.Invoke();
}
if (animName == "revive")
{
Revive();
}
}
private void Revive()
{
HealthComponent.SetCurrentHealth(HealthComponent.MaximumHP.Value);
VTComponent.SetVT(VTComponent.MaximumVT.Value);
if (EquipmentComponent.EquippedAccessory.Value.Augment?.AugmentTag == JewelTags.ReviveUserOnce)
{
var itemToBreak = EquipmentComponent.EquippedAccessory.Value;
Unequip(EquipmentComponent.EquippedAccessory.Value);
Inventory.Remove(itemToBreak);
}
else if (EquipmentComponent.EquippedArmor.Value.Augment?.AugmentTag == JewelTags.ReviveUserOnce)
{
var itemToBreak = EquipmentComponent.EquippedArmor.Value;
Unequip(EquipmentComponent.EquippedArmor.Value);
Inventory.Remove(itemToBreak);
}
else if (EquipmentComponent.EquippedWeapon.Value.Augment?.AugmentTag == JewelTags.ReviveUserOnce)
{
var itemToBreak = EquipmentComponent.EquippedWeapon.Value;
Unequip(EquipmentComponent.EquippedWeapon.Value);
Inventory.Remove(itemToBreak);
}
else
PlayJumpScareAnimation();
}
private void InverseHPToAttackPowerSync(int obj)
{
var weapon = (Weapon)EquipmentComponent.EquippedWeapon.Value;
if (weapon.WeaponTag == WeaponTag.InverseHPAttackPower)
{
var healthPercentage = (HealthComponent.CurrentHP.Value * 10) / HealthComponent.MaximumHP.Value;
weapon.SetAttack(10 - healthPercentage);
EquipmentComponent.Equip(weapon);
}
}
private void OnExitTree()
{
PlayerLogic.Stop();
PlayerBinding.Dispose();
Hitbox.AreaEntered -= Hitbox_AreaEntered;
CollisionDetector.AreaEntered -= CollisionDetector_AreaEntered;
HealthTimer.Timeout -= OnHealthTimerTimeout;
HealthComponent.CurrentHP.Changed -= InverseHPToAttackPowerSync;
HealthComponent.HealthReachedZero -= Die;
ExperiencePointsComponent.PlayerLevelUp -= OnLevelUp;
PlayerFXAnimations.AnimationFinished -= PlayerFXAnimations_AnimationFinished;
}
private 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).Normalized();
if (moveDirection.Length() > 0.25f)
{
var rng = new RandomNumberGenerator();
rng.Randomize();
WalkSFX.PitchScale = rng.RandfRange(0.5f, 1.5f);
if (!WalkSFX.Playing)
WalkSFX.Play();
}
else if (WalkSFX.Playing)
WalkSFX.Stop();
var velocity = (Basis * moveDirection * Settings.MoveSpeed * Settings.Acceleration);
if (_debugSprint)
velocity *= 2;
_knockbackStrength *= 0.9f;
Transform = Transform with { Basis = transform.Basis };
Velocity = velocity + (_knockbackDirection * _knockbackStrength);
MoveAndSlide();
}
private void OnPlayerPositionUpdated(Vector3 globalPosition) => GlobalPosition = globalPosition;
private void OnHealthTimerTimeout()
{
if (VTComponent.CurrentVT.Value > 0)
{
if (((Accessory)EquipmentComponent.EquippedAccessory.Value).AccessoryTag == AccessoryTag.HalfVTConsumption)
_healthTimerActive = !_healthTimerActive;
HealthComponent.Heal(HealthTimerHPRate);
if (_healthTimerActive)
VTComponent.Reduce(1);
}
else
HealthComponent.Damage(1);
}
private void Hitbox_AreaEntered(Area3D area)
{
var target = area.GetOwner();
if (target is IEnemy enemy)
HitEnemy(enemy);
}
private void HitEnemy(IEnemy enemy)
{
var weapon = EquipmentComponent.EquippedWeapon.Value as Weapon;
var isCriticalHit = BattleExtensions.IsCriticalHit(TotalLuck);
var totalDamage = TotalAttack;
if (isCriticalHit)
{
totalDamage += (int)(totalDamage * 0.5f);
SfxDatabase.Instance.Play(SoundEffect.Crit);
}
var baseAttack = new AttackData(totalDamage, weapon.WeaponElement, weapon.WeaponTag == WeaponTag.IgnoreDefense, weapon.WeaponTag == WeaponTag.IgnoreAffinity);
var damageDealt = DamageCalculator.CalculateDamage(baseAttack, enemy.DefenseComponent.CurrentDefense.Value, enemy.ElementalResistanceSet);
enemy.HealthComponent.Damage(damageDealt);
if (weapon.WeaponTag == WeaponTag.Knockback && enemy is IKnockbackable knockbackable)
knockbackable.Knockback(0.3f, -CurrentBasis.Z.Normalized());
if (weapon.WeaponTag == WeaponTag.SelfDamage)
_playerEffectService.TakeSelfDamage(weapon.Stats.SelfDamage);
if (weapon.WeaponTag == WeaponTag.Instakill)
_playerEffectService.Instakill(enemy);
if (weapon.WeaponTag == WeaponTag.RustChanceSelfAndEnemy)
{
var rustChance = 0.15f;
var rng = new RandomNumberGenerator();
rng.Randomize();
if (rng.Randf() <= rustChance)
{
if (rng.Randf() >= 0.5f && ((Accessory)EquipmentComponent.EquippedAccessory.Value).AccessoryTag != AccessoryTag.StatusEffectImmunity)
StatusEffectComponent.Rust.OnNext(true);
else
enemy.StatusEffectComponent.Rust.OnNext(true);
}
}
}
private async void CollisionDetector_AreaEntered(Area3D area)
{
if (area.GetParent() is IBaseInventoryItem inventoryItem)
{
var isAdded = Inventory.PickUpItem(inventoryItem);
if (isAdded)
((Node3D)inventoryItem).QueueFree();
}
if (area.GetParent() is DroppedItem droppedItem)
{
var isAdded = Inventory.PickUpItem(droppedItem.Item);
if (isAdded)
droppedItem.QueueFree();
}
if (area.GetParent() is ThrownItem thrownItem)
{
var isAdded = Inventory.PickUpItem(thrownItem.ItemThatIsThrown);
if (isAdded)
thrownItem.QueueFree();
}
if (area.GetParent() is Restorative restorative)
{
await ToSignal(GetTree().CreateTimer(0.2f), "timeout");
VTComponent.Restore(restorative.RestoreAmount);
SfxDatabase.Instance.Play(SoundEffect.HealVT);
restorative.QueueFree();
}
}
private bool PlayerIsHittingGeometry()
{
var collisions = WallCheck.GetCollidingBodies();
return collisions.Count > 0;
}
private void WallCheck_BodyEntered(Node body)
{
GD.Print("Hit wall");
WeaponAnimations.Stop();
}
private void Inventory_InventoryChanged()
{
if (AutoIdentifyItems)
{
foreach (var item in Inventory.Items.ToList())
{
if (item.ItemTag == ItemTag.MysteryItem)
IdentifyItem(item);
}
}
}
}