cirnogodot/Scripts/Components/Actors/3D/PlayerDamageReceiver3D.cs
2025-06-26 13:17:21 +02:00

211 lines
No EOL
7.1 KiB
C#

using System.Linq;
using Cirno.Scripts.Components.FSM;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Weapons;
using Godot;
using Godot.Collections;
namespace Cirno.Scripts.Components.Actors._3D;
public partial class PlayerDamageReceiver3D : Area3D
{
[Export]
public bool Enabled { get; set; } = false;
[Export]
public bool Invulnerable { get; private set; } = false;
[Export] public BulletOwner BulletGroup { get; set; } = BulletOwner.Player;
[ExportCategory("Extensions")]
[Export] public StringName HealthExtendName { get; private set; } = "HEALTH_EXTEND";
[Export] public StringName ShieldExtendName { get; private set; } = "SHIELD_EXTEND";
[Export] public float BaseHealth { get; private set; } = 32f;
[Export] public float BaseShield { get; private set; } = 32f;
[Export] public float BaseMotivation { get; private set; } = 100f;
[Export] public float HealthExtendAmount { get; private set; } = 4f;
[Export] public float ShieldExtendAmount { get; private set; } = 4f;
[ExportCategory("Providers")]
[Export]
private ActorResourceProvider _healthProvider;
[Export]
private ActorResourceProvider _shieldProvider;
[Export] private ActorResourceProvider _motivationProvider;
[ExportCategory("Damage Types")]
[Export] public StringName AcidGroupName { get; private set; } = "Acid";
[Export] public Array<DamageResistance> ShieldDamageResistances { get; set; } = [];
[Export] public Array<DamageResistance> HealthDamageResistances { get; set; } = [];
[Signal]
public delegate void HealthChangedEventHandler(float newValue, float maxValue);
[Signal]
public delegate void HealthDecreasedEventHandler(float value, float newValue, float maxValue);
[Signal]
public delegate void ShieldChangedEventHandler(float newValue, float maxValue);
[Signal]
public delegate void ShieldDecreasedEventHandler(float value, float newValue, float maxValue);
[Signal]
public delegate void DeathEventHandler();
public float CurrentHealth
{
get => _healthProvider.CurrentResource;
set => _healthProvider.CurrentResource = value;
}
public float CurrentShield
{
get => _shieldProvider.CurrentResource;
set => _shieldProvider.CurrentResource = value;
}
public float CurrentMotivation
{
get => _motivationProvider.CurrentResource;
set => _motivationProvider.CurrentResource = value;
}
private IStateMachine<PlayerState, CharacterBody3D> _stateMachine;
public void Init(IStateMachine<PlayerState, CharacterBody3D> machine)
{
_stateMachine = machine;
Invulnerable = GlobalState.Instance.SessionSettings.GodMode;
_healthProvider.ResourceChanged += ((value, maxValue) =>
{
//if (!Enabled) return;
Hud.Instance?.UpdateHealth(value, maxValue);
EmitSignal(SignalName.HealthChanged, value, maxValue);
});
_healthProvider.ResourceDecreased += (value, newValue, maxValue) =>
{
EmitSignal(SignalName.HealthDecreased, value, newValue, maxValue);
};
_shieldProvider.ResourceDecreased += (value, newValue, maxValue) =>
{
EmitSignal(SignalName.ShieldDecreased, value, newValue, maxValue);
};
_shieldProvider.ResourceChanged += ((value, maxValue) =>
{
//if (!Enabled) return;
Hud.Instance?.UpdateShield(value, maxValue);
EmitSignal(SignalName.ShieldChanged, value, maxValue);
});
_healthProvider.ResourceDepleted += () =>
{
//if (!Enabled) return;
EmitSignal(SignalName.Death);
};
// Set max resources based on inventory
SetMaxResources();
InventoryManager.Instance.ItemAdded += (item, amount) =>
{
if (item.ItemKey != HealthExtendName && item.ItemKey != ShieldExtendName) return;
SetMaxResources();
};
_motivationProvider.ResourceChanged += (value, maxValue) =>
{
Hud.Instance?.UpdateMotivation(value, maxValue);
};
}
private void SetMaxResources()
{
var healthExtends = InventoryManager.Instance.GetItemCount(HealthExtendName);
var shieldExtends = InventoryManager.Instance.GetItemCount(ShieldExtendName);
_healthProvider.MaxResource = BaseHealth + (healthExtends * HealthExtendAmount);
_shieldProvider.MaxResource = BaseShield + (shieldExtends * ShieldExtendAmount);
_motivationProvider.CurrentResource = BaseMotivation;
}
public void RefillHealth()
{
_healthProvider.FillResource();
}
public void RefillShield()
{
_shieldProvider.FillResource();
}
private void _on_damage_hitbox_area_entered(Area3D area)
{
if (!Enabled) return;
if (Invulnerable) return;
if (area.IsInGroup(AcidGroupName))
{
// Handle acid death
AcidDeath();
return;
}
if (area is not Bullet3D bullet || bullet.BulletOwner == BulletGroup) return;
this.Hit(bullet.Damage, bullet.DamageType);
bullet.RequestCollisionDestruction();
}
private void AcidDeath()
{
if (!Enabled) return;
GD.Print("Acid death");
_stateMachine.SetState(PlayerState.Drowning);
//_healthProvider.CurrentResource = 0;
}
private void ApplyDamageToHealth(float damage, DamageType type = DamageType.Neutral)
{
if (HealthDamageResistances.Where(x => x.DamageType == type)
.Any(x => x.Attribute is DamageAttribute.Skip)) return;
var dmg = HealthDamageResistances.Aggregate(damage, (current, resistance) => current * resistance.CalculateDamage(current, type));
CurrentHealth -= dmg;
}
public void Hit(float damage, DamageType type = DamageType.Neutral)
{
if (!Enabled) return;
// Change value based on difficulty
float difficultyReducedDmg = damage / GlobalState.Instance.SessionSettings.DifficultyDamageMultiplier;
// Check if the shield is empty or damage has skip attributes
if (CurrentShield <= 0 || ShieldDamageResistances.Where(x => x.DamageType == type).Any(x => x.Attribute is DamageAttribute.Skip))
{
// do not apply, go to health
ApplyDamageToHealth(difficultyReducedDmg, type);
}
else
{
var shieldDmg = ShieldDamageResistances.Aggregate(difficultyReducedDmg, (current, resistance) => current * resistance.CalculateDamage(current, type));
// apply and get remainder
var remainder = CurrentShield - shieldDmg;
CurrentShield = remainder; // Let the resource's self-balancing take care of any remainders
if (remainder < 0)
{
// Apply remainder to health
ApplyDamageToHealth(-remainder, type);
}
}
}
}