Files
GameJamDungeon/Zennysoft.Game.Ma/src/player/Player.cs
T
zenny 2ab6158849 Make adjustments to bad end map
Modify debug menu slightly to pause while open (need to close the menu to load floors, etc)
Clear map when game over screen appears
2026-06-08 00:08:25 -07:00

1016 lines
36 KiB
C#

using Chickensoft.AutoInject;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using SimpleInjector;
using System;
using System.Linq;
using Zennysoft.Ma;
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 ISigilComponent SigilComponent { get; 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 Barrier Barrier { get; set; } = default!;
[Node] private ProjectileSystem _airReactorProjectileSystem { get; set; } = default!;
[Node] private ProjectileSystem _fireReactorProjectileSystem { get; set; } = default!;
[Node] private ProjectileSystem _waterReactorProjectileSystem { get; set; } = default!;
[Node] private ProjectileSystem _kineticProjectileSystem { 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 bool BriefImmunity { get; set; } = false;
[Export]
private float _immunityTime { get; set; } = 30f;
[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;
private Timer _projectileCooldownTimer;
private Timer _immunityTimer;
private bool _fired;
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);
SigilComponent = new SigilComponent();
_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();
SigilComponent.Reset();
}
#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;
HealthTimer.Timeout += OnHealthTimerTimeout;
_projectileCooldownTimer = new Timer();
AddChild(_projectileCooldownTimer);
_projectileCooldownTimer.WaitTime = 1.8f;
_projectileCooldownTimer.Timeout += ProjectileCooldown;
_immunityTimer = new Timer();
AddChild(_immunityTimer);
_immunityTimer.WaitTime = _immunityTime;
_immunityTimer.Timeout += _immunityTimer_Timeout;
SetProcessInput(false);
SetPhysicsProcess(false);
}
private void _immunityTimer_Timeout()
{
BriefImmunity = false;
Barrier.FadeOut();
SfxDatabase.Instance.Play(SoundEffect.Debuff);
}
#endregion
public void Activate()
{
SetProcessInput(true);
SetPhysicsProcess(true);
SetHealthTimerStatus(HealthTimerIsActive);
ShowCamera(true);
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 ShowCamera(bool showCamera) => _camera3D.Current = showCamera;
public void TakeDamage(AttackData damage)
{
if (BriefImmunity)
return;
ShakePlayerCamera(1, 1);
TakeDamageAnimationPlayer.Play("take_damage");
var defense = TotalDefense;
var elementalResistance = EquipmentComponent.ElementalResistance + SigilComponent.Sigil.ElementalResistanceSet;
var damageReceived = DamageCalculator.CalculateDamage(damage, defense, elementalResistance);
damageReceived *= Mathf.RoundToInt(1 + SigilComponent.Sigil.DamageModifier);
GD.Print($"Damage dealt: {damageReceived}");
HealthComponent.Damage(damageReceived, damage.ElementType);
SfxDatabase.Instance.Play(SoundEffect.TakeDamage);
if (EquipmentComponent.EquippedArmor.Value.ArmorTag == ArmorTag.DegradeOnHit)
{
EquipmentComponent.EquippedArmor.Value.DecreaseArmorDefense(1);
EquipmentComponent.UpdateEquipment(EquipmentComponent.EquippedArmor.Value);
}
}
public void SetSigil(ISigil sigil)
{
SigilComponent.Sigil = sigil;
_game.SetAffinity(sigil.ElementType);
}
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 void ShakePlayerCamera(float shakeIntensity, float shakeAmount)
{
_camera3D._shakeIntensity = shakeIntensity;
_camera3D.AddShake(shakeAmount);
}
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 || SigilComponent.Sigil.AutoRevive)
return;
SetSigil(new NoneSigil());
Deactivate();
}
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.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.HydricElement:
weapon.Stats.WeaponElement = ElementType.Hydric;
weapon.Augment = new Augment(JewelTags.HydricElement, new BasicAugment(), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.SlowVTReduction:
weapon.Augment = new Augment(JewelTags.SlowVTReduction, new SlowVTReductionAugment(this), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.HastenVT:
weapon.Augment = new Augment(JewelTags.HastenVT, new HastenVTAugment(this), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.ReviveUserOnce:
weapon.Augment = new Augment(JewelTags.ReviveUserOnce, new RevivePlayerAugment(this), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseHPRecovery:
weapon.Augment = new Augment(JewelTags.IncreaseHPRecovery, new HPRecoverySpeedAugment(this), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.LowerEXPGain:
weapon.Augment = new Augment(JewelTags.LowerEXPGain, new LowerEXPRateAugment(this), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, 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.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.TelluricElement:
weapon.Stats.WeaponElement = ElementType.Telluric;
weapon.Augment = new Augment(JewelTags.TelluricElement, new BasicAugment(), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.AutoIdentifyAllItems:
weapon.Augment = new Augment(JewelTags.AutoIdentifyAllItems, new IdentifyAllItemsAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, 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.Stats.WeaponAugmentDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseLuck:
weapon.IncreaseLuck(25);
weapon.Augment = new Augment(JewelTags.IncreaseLuck, new BasicAugment(), jewel.ItemName, jewel.Stats.WeaponAugmentDescription, 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.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.HydricElement:
armor.Stats.HydricResistance += jewel.Stats.HydricResistance;
armor.Augment = new Augment(JewelTags.HydricElement, new BasicAugment(), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.SlowVTReduction:
armor.Augment = new Augment(JewelTags.SlowVTReduction, new SlowVTReductionAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.HastenVT:
armor.Augment = new Augment(JewelTags.HastenVT, new HastenVTAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.ReviveUserOnce:
armor.Augment = new Augment(JewelTags.ReviveUserOnce, new RevivePlayerAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseHPRecovery:
armor.Augment = new Augment(JewelTags.IncreaseHPRecovery, new HPRecoverySpeedAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.LowerEXPGain:
armor.Augment = new Augment(JewelTags.LowerEXPGain, new LowerEXPRateAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, 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.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.TelluricElement:
armor.Stats.TelluricResistance += jewel.Stats.TelluricResistance;
armor.Augment = new Augment(JewelTags.TelluricElement, new BasicAugment(), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.AutoIdentifyAllItems:
armor.Augment = new Augment(JewelTags.AutoIdentifyAllItems, new IdentifyAllItemsAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, 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.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseLuck:
armor.IncreaseLuck(jewel.Stats.BonusLuck);
armor.Augment = new Augment(JewelTags.IncreaseLuck, new BasicAugment(), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, 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.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.HydricElement:
accessory.Stats.HydricResistance += jewel.Stats.HydricResistance;
accessory.Augment = new Augment(JewelTags.HydricElement, new BasicAugment(), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.SlowVTReduction:
accessory.Augment = new Augment(JewelTags.SlowVTReduction, new SlowVTReductionAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.HastenVT:
accessory.Augment = new Augment(JewelTags.HastenVT, new HastenVTAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.ReviveUserOnce:
accessory.Augment = new Augment(JewelTags.ReviveUserOnce, new RevivePlayerAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseHPRecovery:
accessory.Augment = new Augment(JewelTags.IncreaseHPRecovery, new HPRecoverySpeedAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.LowerEXPGain:
accessory.Augment = new Augment(JewelTags.LowerEXPGain, new LowerEXPRateAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, 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.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.TelluricElement:
accessory.Stats.TelluricResistance += jewel.Stats.TelluricResistance;
accessory.Augment = new Augment(JewelTags.TelluricElement, new BasicAugment(), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.AutoIdentifyAllItems:
accessory.Augment = new Augment(JewelTags.AutoIdentifyAllItems, new IdentifyAllItemsAugment(this), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, 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.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
case JewelTags.IncreaseLuck:
accessory.IncreaseLuck(jewel.Stats.BonusLuck);
accessory.Augment = new Augment(JewelTags.IncreaseLuck, new BasicAugment(), jewel.ItemName, jewel.Stats.ArmorAugmentDescription, jewel.GetTexture());
break;
}
EquipmentComponent.UpdateEquipment(accessory);
}
private void ProjectileCooldown() => _fired = false;
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);
}
public void EnactBriefImmunity()
{
SfxDatabase.Instance.Play(SoundEffect.Buff);
BriefImmunity = true;
Barrier.FadeIn();
_immunityTimer.Start();
}
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 (weapon.WeaponTag == WeaponTag.DegradeOnSwing)
_playerEffectService.Degrade();
else if (weapon.WeaponTag == WeaponTag.SelfDamage)
_playerEffectService.TakeSelfDamage(5);
}
private void HandleProjectile(Weapon weapon)
{
if (_fired)
return;
var ammo = EquipmentComponent.EquippedAmmo.Value as Ammo;
if (ammo.Count == null || ammo.Count?.Value <= 0)
return;
if (weapon.WeaponTag == WeaponTag.ElementalProjectile)
{
if (ammo.AmmoElement == ElementType.Aeolic)
_airReactorProjectileSystem.Fire();
if (ammo.AmmoElement == ElementType.Igneous)
_fireReactorProjectileSystem.Fire();
if (ammo.AmmoElement == ElementType.Hydric)
_waterReactorProjectileSystem.Fire();
}
if (weapon.WeaponTag == WeaponTag.KineticProjectile)
{
PlayAttackAnimation();
_kineticProjectileSystem.Fire();
}
ammo.SetCount(ammo.Count.Value - 1);
EquipmentComponent.UpdateEquipment(ammo);
if (ammo.Count.Value <= 0)
{
EquipmentComponent.Unequip(ammo);
Inventory.Remove(ammo);
}
_fired = true;
_projectileCooldownTimer.Start();
}
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 || SigilComponent.Sigil.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 moveSpeed = Settings.MoveSpeed * (1 + SigilComponent.Sigil.MoveSpeedModifier);
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 * (float)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, ElementType.None);
}
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 (SigilComponent.Sigil.ElementType == weapon.WeaponElement)
totalDamage = Mathf.RoundToInt(totalDamage * 1.15f);
totalDamage = Mathf.RoundToInt(totalDamage * (1 + SigilComponent.Sigil.DamageModifier));
if (isCriticalHit)
{
totalDamage += (int)(totalDamage * 0.5f);
SfxDatabase.Instance.Play(SoundEffect.Crit);
}
GD.Print($"Damage dealt: {totalDamage}");
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, weapon.WeaponElement);
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 && ((Armor)EquipmentComponent.EquippedArmor.Value).ArmorTag != ArmorTag.ImmuneToRust)
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);
}
}
}
}