Reflection is overkill for a system like this. its not needed at all. all thats really needed is a look into the design
here is one way of going about it
BuffDefinition: defines the stats AND behaviour of that are immuttable between all instances of the buff (things like what it does, how long it lasts, what can dispel it… data that would always be the same on all instances of that buff type) Buff definition is a ScriptableObject, so think of it as a blueprint for what the buff does in a general case.
BuffInstance: the fact that its a Net object doesn’t matter too much, just that since its not a Unity object you can’t drag and drop it around in the inspector. but thats perfectly fine. BuffInstance should hold data that specific to that instance. things like if its Active, the Source, the Target and the current Duration are some key data points. also to simply all the trouble you’re getting simply give the BuffInstance a Reference to the buff definition that it should use
BuffManager: holds a list buff instances (or dictionary with a string as the key, so you could activate buffs by name, look into SerializableDictionaries) which on update will loop through the instances, find the ones that are active and send that instance to the referenced buffdefintion via a “BuffDefinition.Update(Buffinstance instance, float deltaTime)” and let the BuffDefinition perform the behaviour for that buffinstance
BuffDefinition and BuffInstance are of course abstract so when you have a concrete type of buff, say “Wither” you’d follw Liskov’s rule and pass the data as an abstract, and let the handler (buffdefinition) check if it can cast it.
public interface IDamageReport
{
public float DamageDealt{get;set;}
}
public interface IDamageable
{
// applies damage to target, returns amount of damage actually dealt after resistances
public float Damage(float damageAmount);
}
public interface IDamagePerTick
{
public float Damage{get;}
public float Tick{get;}
public float TickTimer{get;set}
}
public class BuffInstance_Wither:BuffInstance,
IDamageReport, IDamagePerTick
{
private float m_damageDealt =0;
private float m_tickTimer =0;
[Tooltip("How much Damage is applied each tick")]
[SerializeField]private float m_damagePerTick =50f;
[Tooltip("How many seconds each tick is")]
[SerializeField]private float m_tick =0.5f;
public float DamageDealt
{
get{return m_damageDealt;}
set{m_damageDealt = value;}
}
public float Damage
{
get{return m_damagePerTick;}
}
public float Tick
{
get{return m_tick;}
}
public float TickTimer
{
get{return m_tickTimer;}
set{m_tickTimer = value;}
}
public override void Reset()
{
base.Reset();
m_damageDealt = 0;
m_tickTimer = m_tick;
}
}
public class BuffDefinition_Wither:BuffDefinition
{
public override void Update(BuffInstance instance, float deltaTime)
{
if(instance == null) return;
if(!instance.Target)// no target to wither, deactivate buff
{
instance.Active = false;
instance.Duration = 0;
return;
}
base.Update(instance,deltaTime);
IDamagePerTick ticker = instance as IDamagePerTick;
if(ticker== null) return;
ticker.TickTimer -= deltaTime;
if(ticker.TickTimer>0) return;
ticker.TickTimer = ticker.Tick;
IDamageable damageableTarget = instance.Target.GetComponent<IDamageable>();
if(damageableTarget == null) return;
float damageDealt = damageableTarget.Damage(ticker.Damage);
IDamageReport damageReport = instance as IDamageReport;
if(damageReport == null) return;
damageReport.DamageDealt += damageDealt;
}
}
So Buff Manager will reset the state whenever the buff is activated, Buff Definition themselves will be fed the instance states and modify them, deactivating them when their time is up