using System; using System.Linq; using Cirno.Scripts.Components.FSM; using Cirno.Scripts.Enums; using Cirno.Scripts.Resources; using Godot; using Godot.Collections; namespace Cirno.Scripts.Components.Actors; public partial class PlayerDamageReceiver : Area2D { [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 ShieldDamageResistances { get; set; } = []; [Export] public Array 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 _stateMachine; public void Init(IStateMachine 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(Area2D area) { if (!Enabled) return; if (Invulnerable) return; if (area.IsInGroup(AcidGroupName)) { // Handle acid death AcidDeath(); return; } if (area is not Bullet 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); } } // return; // if (CurrentShield > 0 && type is not DamageType.Explosive or DamageType.Acid) // { // // Reduce shield // //PlayShieldAnimation(); // Let this be handled by event // CurrentShield -= damage; // if (CurrentShield < 0) // { // CurrentHealth -= Math.Abs(CurrentShield); // CurrentShield = 0; // } // } // else // { // if (type is DamageType.Fire) // { // CurrentHealth -= damage * 2; // } // else // { // CurrentHealth -= damage; // } // // //Blink(); // Let this be handled by event // } // // if (!(CurrentHealth <= 0)) return; } }