Effects handling rework

This commit is contained in:
Rendo 2025-07-07 17:29:10 +05:00
commit 63930450a3
21 changed files with 100 additions and 228 deletions

View file

@ -6,13 +6,13 @@ namespace Newlon.Components;
public partial class Armor : Node
{
[Signal]
public delegate void ArmorDamagedEventHandler(int hp);
public delegate void ArmorDamagedEventHandler(float hp);
[Signal]
public delegate void ArmorLostEventHandler();
[Export]
public int MaxHP { get; private set; }
private int _hp;
public float MaxHP { get; private set; }
private float _hp;
private bool _lost = false;
public override void _Ready()
@ -20,11 +20,11 @@ public partial class Armor : Node
_hp = MaxHP;
}
public int RecieveDamage(int damage)
public float RecieveDamage(float damage)
{
if(_lost)
return damage;
int returnAmount = 0;
float returnAmount = 0;
_hp -= damage;
if(_hp <= 0)
{
@ -37,11 +37,11 @@ public partial class Armor : Node
return returnAmount;
}
public int Heal(int amount)
public float Heal(float amount)
{
if(_lost)
return amount;
int returnAmount = 0;
float returnAmount = 0;
_hp += amount;
if (_hp >= MaxHP)
{

View file

@ -14,9 +14,9 @@ public partial class DegradingSprite : Sprite2D
armor.ArmorDamaged += OnZombieHPChanged;
}
private void OnZombieHPChanged(int hp)
private void OnZombieHPChanged(float hp)
{
float percent = (float)hp / (float)armor.MaxHP;
float percent = hp / armor.MaxHP;
for (int i = 0; i < degradationStages.Count; i++)
{
if (percent <= thresholdPercentage[i])

View file

@ -7,8 +7,8 @@ namespace Newlon.Components;
public partial class Entity : Node2D
{
#region Health points
[Export] public float MaxHP { get; private set; }
public float HP { get; private set; }
[Export] public float MaxHP;
public float HP;
[Signal] public delegate void OnHPChangedEventHandler(int deltaHP, Node origin);
[Signal] public delegate void OnDamagedEventHandler();
@ -55,9 +55,14 @@ public partial class Entity : Node2D
}
#endregion
#region Effects
[Export] private Array<Effect> _effectImmunities;
private readonly Effect[] _activeEffectSlots = new Effect[Utility.EffectSlotCount];
private readonly Timer[] _effectSlotTimers = new Timer[Utility.EffectSlotCount];
[Export] private Array<Effect> _effectImmunities = new();
private readonly Dictionary<string,Effect> _activeEffectSlots = new();
private readonly Dictionary<string,Timer> _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)
{
@ -65,7 +70,22 @@ public partial class Entity : Node2D
{
return;
}
int slot = (int)what.Slot;
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();
@ -76,24 +96,39 @@ public partial class Entity : Node2D
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)
{
what.Exit(this);
_activeEffectSlots[(int)what.Slot] = null;
EndEffectAtSlot(what.Slot);
}
public void ProcessEffects()
{
for (int i = 0; i < Utility.EffectSlotCount; i++)
_activeEffectSlots[i]?.Process(this);
foreach(string key in _activeEffectSlots.Keys)
_activeEffectSlots[key]?.Process(this);
}
private void EndEffectAtSlot(int slot)
private void EndEffectAtSlot(string slot)
{
_activeEffectSlots[slot].Exit(this);
_activeEffectSlots[slot] = null;
EmitSignal(SignalName.EffectEnded, _activeEffectSlots[slot]);
}
#endregion
#region LocalTimescale
@ -113,16 +148,6 @@ public partial class Entity : Node2D
public override void _Ready()
{
HP = MaxHP;
// Effect timers setup
for(int i = 0; i < Utility.EffectSlotCount; i++)
{
var timer = new Timer() {Autostart = false, OneShot = true};
_effectSlotTimers[i] = timer;
AddChild(timer);
int current_index = i;
timer.Timeout += () => {EndEffectAtSlot(current_index);};
}
}
#endregion
}

View file

@ -1,17 +0,0 @@
using Godot;
namespace Newlon.Components;
//
// Base interface for entities
//
public interface IEntity
{
public int Hp { get; }
public int MaxHp { get; }
public void TakeDamage(int amount, Node origin, Utility.DamageTypes damageType = Utility.DamageTypes.PHYSICAL);
public void Heal(int amount, Node origin);
public void DisableBrain();
public void EnableBrain();
}

View file

@ -1 +0,0 @@
uid://cjdeq452vk2ll

View file

@ -1,6 +0,0 @@
namespace Newlon.Components;
public interface ILocalTimescale
{
public float LocalTimescale { get; set; }
}

View file

@ -1 +0,0 @@
uid://bxu6ljkmmlgd5

View file

@ -17,8 +17,6 @@ public partial class LinearProjectile : Area2D, IProjectile
[Export]
private Effect _impactEffect;
[Export]
private Utility.DamageTypes _damageType = Utility.DamageTypes.PHYSICAL;
[Export]
private PackedScene particles;
private int _line;
private bool used = false;
@ -33,13 +31,13 @@ public partial class LinearProjectile : Area2D, IProjectile
public void OnAreaEntered(Area2D area)
{
if (used == true) return;
var entity = area.GetParent<IEntity>();
var entity = area.GetParent<Entity>();
if (entity != null)
{
entity.TakeDamage(_damage,this,_damageType);
entity.TakeDamage(_damage,this);
used = true;
if (entity is IEffectHandler effectHandler && _impactEffect != null)
effectHandler.GiveEffect(_impactEffect);
if (_impactEffect != null)
entity.GiveEffect(_impactEffect);
PoolContainer.Instance.SpawnParticles(particles, GlobalPosition);

View file

@ -44,7 +44,7 @@ public partial class Previewport : SubViewport
title.Text = Tr(resource.name_key);
description.Text = Tr(resource.description_key);
AddChild(current_display);
if (current_display is IEntity entity)
if (current_display is Entity entity)
entity.DisableBrain();
}

View file

@ -7,7 +7,7 @@ public partial class Eyesight : Area2D
{
private bool _enemyDetected;
public bool EnemyDetected => _enemyDetected;
private readonly List<IEntity> _detectedEntities = new List<IEntity>();
private readonly List<Entity> _detectedEntities = new List<Entity>();
private RuntimePlantData _plantData;
public override void _Ready()
@ -19,7 +19,7 @@ public partial class Eyesight : Area2D
public void OnAreaEntered(Area2D area)
{
var entity = area.GetParent<IEntity>();
var entity = area.GetParent<Entity>();
if (entity != null)
{
_detectedEntities.Add(entity);
@ -30,7 +30,7 @@ public partial class Eyesight : Area2D
public void OnAreaExited(Area2D area)
{
var entity = area.GetParent<IEntity>();
var entity = area.GetParent<Entity>();
if (entity != null)
{
if (_detectedEntities.Contains(entity))

View file

@ -1,162 +1,48 @@
using System.Collections.Generic;
using Godot;
using Newlon.Systems.Effects;
namespace Newlon.Components.Zombies;
public partial class RuntimeZombieData : Node2D, IEntity, ILocalTimescale, IEffectHandler
public partial class RuntimeZombieData : Entity
{
[Signal]
public delegate void OnHPChangedEventHandler(int deltaHP, Node origin);
[Signal]
public delegate void OnDamagedEventHandler();
[Signal]
public delegate void OnLocalTimescaleChangedEventHandler(int currentTimescale);
[Export]
private int _hp;
[Export]
private int _maxHP;
[Export]
private Armor _armor;
[Export]
private AudioStream garlicSound;
[Export]
private AudioStream freezeSound;
private float _localTimescale = 1.0f;
public int Hp => _hp;
public int MaxHp => _maxHP;
public bool AbleToEat = true;
public float LocalTimescale
{
get => _localTimescale;
set
{
_localTimescale = value;
EmitSignal(SignalName.OnLocalTimescaleChanged,_localTimescale);
}
}
public override void _Ready()
{
_hp = _maxHP;
// Effect timers setup
for(int i = 0; i < Utility.EffectSlotCount; i++)
{
var timer = new Timer() {Autostart = false, OneShot = true};
_effectSlotTimers[i] = timer;
AddChild(timer);
int current_index = i;
timer.Timeout += () => {EndEffectAtSlot(current_index);};
}
}
public void Heal(int amount,Node origin)
{
if(_armor != null)
{
_hp += _armor.Heal(amount);
}
else
_hp += amount;
EmitSignal(SignalName.OnHPChanged,amount,origin);
if (MaxHp > 0)
{
_hp = MaxHp;
}
}
public void TakeDamage(int amount, Node origin, Utility.DamageTypes damageType = Utility.DamageTypes.PHYSICAL)
public override void Heal(float amount, Node origin)
{
if (_armor != null)
{
_hp -= _armor.RecieveDamage(amount);
HP += _armor.Heal(amount);
}
else
_hp -= amount;
EmitSignal(SignalName.OnHPChanged,-amount, origin);
HP += amount;
EmitSignal(SignalName.OnHPChanged, amount, origin);
if (HP > MaxHP)
{
HP = MaxHP;
}
}
public override void TakeDamage(float amount, Node origin)
{
if (_armor != null)
{
HP -= _armor.RecieveDamage(amount);
}
else
HP -= amount;
EmitSignal(SignalName.OnHPChanged, -amount, origin);
EmitSignal(SignalName.OnDamaged);
if (_hp <= 0)
if (HP <= 0)
{
QueueFree();
KillByDamage();
}
}
#region Effects system
private readonly Effect[] _activeEffectSlots = new Effect[Utility.EffectSlotCount];
private readonly Timer[] _effectSlotTimers = new Timer[Utility.EffectSlotCount];
// Effect handling
public void GiveEffect(Effect what)
{
int slot = (int)what.Slot;
if (_activeEffectSlots[slot] == null)
{
switch (what.Slot)
{
case Utility.EffectSlots.FREEZE:
AudioSequencer.Play("zombie_freeze", freezeSound);
var settings = new ChannelSettings();
settings.restartTreshold = -1;
AudioSequencer.ChangeSettings("zombie_freeze",settings);
break;
case Utility.EffectSlots.GARLIC:
AudioSequencer.Play("zombie_garlic", garlicSound);
break;
}
}
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;
}
public void EndEffect(Effect what)
{
what.Exit(this);
_activeEffectSlots[(int)what.Slot] = null;
}
public void ProcessEffects()
{
for(int i = 0; i < Utility.EffectSlotCount; i++)
_activeEffectSlots[i]?.Process(this);
}
private void EndEffectAtSlot(int slot)
{
_activeEffectSlots[slot].Exit(this);
_activeEffectSlots[slot] = null;
}
public void DisableBrain()
{
GetNode<AnimationPlayer>("AnimationPlayer").ProcessMode = ProcessModeEnum.Pausable;
ProcessMode = ProcessModeEnum.Disabled;
}
public void EnableBrain()
{
GetNode<AnimationPlayer>("AnimationPlayer").ProcessMode = ProcessModeEnum.Inherit;
ProcessMode = ProcessModeEnum.Inherit;
}
#endregion
}