using Godot; using Godot.Collections; using Newlon.Components.Level; using Newlon.Systems.Effects; namespace Newlon.Components; [GlobalClass] public partial class Entity : Node2D { #region Health points [Export] public float MaxHP; [Export]public float HP; [Signal] public delegate void OnHPChangedEventHandler(EntitySignalContext context); [Signal] public delegate void OnDamagedEventHandler(); public virtual void TakeDamage(float amount, Node origin) { if(amount > 0) EmitSignal(SignalName.OnDamaged); var context = new EntitySignalContext() { target = this, source = (Entity)origin, actionAmount = amount }; if (HP - amount <= 0) { context.deltaHP = -HP; EmitSignal(SignalName.OnHPChanged, context); HP = 0; KillByDamage(); } else { context.deltaHP = -amount; HP -= amount; EmitSignal(SignalName.OnHPChanged, context); } } public virtual void Heal(float amount, Node origin) { var context = new EntitySignalContext() { target = this, source = (Entity)origin, actionAmount = amount }; if (HP + amount > MaxHP) { context.deltaHP = MaxHP - HP; EmitSignal(SignalName.OnHPChanged, context); HP = MaxHP; } else { context.deltaHP = amount; HP += amount; EmitSignal(SignalName.OnHPChanged, context); } } public virtual void KillByDamage() { Kill(); } public virtual void Kill() { QueueFree(); } #endregion #region Brain [Export] private AnimationPlayer _player; [Export] private AnimationTree _tree; private bool forceToggledBrain = false; private bool brainEnabled = true; public virtual void DisableBrain(bool force = true) { if (brainEnabled == false) return; if (_player != null) _player.ProcessMode = ProcessModeEnum.Pausable; if (_tree != null) _tree.ProcessMode = ProcessModeEnum.Pausable; ProcessMode = ProcessModeEnum.Disabled; forceToggledBrain = force; brainEnabled = false; } public virtual void EnableBrain(bool force = true) { if (brainEnabled) return; if (_player != null) _player.ProcessMode = ProcessModeEnum.Inherit; if (_tree != null) _tree.ProcessMode = ProcessModeEnum.Inherit; ProcessMode = ProcessModeEnum.Inherit; forceToggledBrain = force; brainEnabled = true; } #endregion #region Effects [Export] private Array _effectImmunities = new(); [Export] private bool completeInvulnerability = false; private readonly Dictionary _activeEffectSlots = new(); private readonly Dictionary _effectSlotTimers = new(); [Signal] public delegate void EffectStartedEventHandler(Effect what); [Signal] public delegate void EffectEndedEventHandler(Effect what); [Signal] public delegate void EffectContinuedEventHandler(Effect what); public virtual void GiveEffect(Effect what) { if (_effectImmunities.Contains(what) || completeInvulnerability) { return; } string slot = what.Slot; if (_activeEffectSlots.ContainsKey(slot) == false) { InitSlot(slot); } if (what == _activeEffectSlots[slot]) { EmitSignal(SignalName.EffectContinued, what); } else { EmitSignal(SignalName.EffectStarted, what); } if (_activeEffectSlots[slot] != null) { _effectSlotTimers[slot].Stop(); _activeEffectSlots[slot].Exit(this); } _effectSlotTimers[slot].WaitTime = what.Duration; _effectSlotTimers[slot].Start(); what.Enter(this); _activeEffectSlots[slot] = what; } private void InitSlot(string key) { _activeEffectSlots.Add(key, null); var timer = new Timer() { Autostart = false, OneShot = true }; AddChild(timer); timer.Timeout += () => { EndEffectAtSlot(key); }; _effectSlotTimers.Add(key, timer); } public void EndEffect(Effect what) { EndEffectAtSlot(what.Slot); } public void ProcessEffects() { foreach (string key in _activeEffectSlots.Keys) _activeEffectSlots[key]?.Process(this); } private void EndEffectAtSlot(string slot) { _activeEffectSlots[slot].Exit(this); _activeEffectSlots[slot] = null; EmitSignal(SignalName.EffectEnded, _activeEffectSlots[slot]); } #endregion #region LocalTimescale private float _localTimescale = 1.0f; [Signal] public delegate void OnLocalTimescaleChangedEventHandler(float scale); public float LocalTimescale { get => _localTimescale; set { _localTimescale = value; EmitSignal(SignalName.OnLocalTimescaleChanged, _localTimescale); } } #endregion #region Godot overrides public override void _Ready() { HP = MaxHP; if (RuntimeLevelData.Instance != null) { if (RuntimeLevelData.Instance.GetLevelState() != RuntimeLevelData.LevelStates.Game) DisableBrain(false); } } #endregion }