fluid-stats icon indicating copy to clipboard operation
fluid-stats copied to clipboard

Multiple instances of the same modifier

Open Nixellion opened this issue 4 years ago • 4 comments

Hello!

First of all - this is a great project! Has basically everything I was trying to make myself, especially love how easy it is to add and manage stats.

One thing I'm not certain about however, how would I go about adding multiple instances of the same modifier? To stack them. I tried

statModifier = Instantiate(statModifier);
characterStats = transform.root.gameObject.GetComponentInChildren<CharacterStats>();
statModifier.ApplyAdjustment(characterStats.runtimeStats);

But it only adds 1 single instance of a modifier.

Thanks! :) And sorry if it's in the docs somewhere, I could not find it

I suppose I should be giving each instance unique IDs and keeping track of them for removal?

Nixellion avatar Feb 01 '22 18:02 Nixellion

For now here's how I did it, but I don't think it's the best way to approach it:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using CleverCrow.Fluid.StatsSystem;


[System.Serializable]
public class ModifierMemory
{
    public string id;
    public OperatorType operatorType;
    public StatDefinition definition;

}
public class StatusEffect_StatModifier : StatusEffect
{
    public StatsAdjustment statModifier;
    CharacterStats characterStats;

    private List<ModifierMemory> modifierIds = new List<ModifierMemory>();

    public override void ApplyEffect()
    {   
        characterStats = transform.root.gameObject.GetComponentInChildren<CharacterStats>();
        foreach (ModifierGroup modifierGroup in statModifier.Adjustments)
        {   
            string randomId =  System.Guid.NewGuid().ToString();
            characterStats.runtimeStats.SetModifier(modifierGroup.operatorType, modifierGroup.definition, randomId, modifierGroup.GetValue(0));

            ModifierMemory mm = new ModifierMemory();
            mm.id = randomId;
            mm.operatorType = modifierGroup.operatorType;
            mm.definition = modifierGroup.definition;
            modifierIds.Add(mm);
        }
    }

    public override void OnRemoveEffect()
    {   
        foreach (ModifierMemory mm in modifierIds)
        {
            characterStats.runtimeStats.RemoveModifier(mm.operatorType, mm.definition, mm.id);
        }
        Destroy(gameObject);
    }
}

ApplyEffect is called whenever gameobject is spawned, OnRemoveEffect is called OnDestroy. This way I can simply attach this gameobject to a character anywhere in the hierarchy and it will apply the modifier and remember it's ID and values required to remove it (why are operatorType and Definition required for RemoveModifier function? I thought it would work with just an ID but ah well). And whenever game object is removed it will remove these modifiers.

Overall I think that the stat system itself should allow stacking in some way, it's the norm anyway, isn't it? Being able to equip multiple instances of the same item. Even in diablo you could do that by equipping multiple rings, for example. Risk of Rain 2 also brings this concept of stacking to the limits. As of now it seems like the system is designed in a way that assumes that character can only equip one of each item?

Nixellion avatar Feb 01 '22 19:02 Nixellion

Hmmm, yeah with all the caching going on I'm not sure how you'd share a single modifier instance 🤔 Would require some refactoring at the very least. There would probably need to be a separate API end point to add a shared instance and the caching requirements would be tricky. If it works how you wrote it power to you for sure.

Would probably need to generate the modifier externally with a custom class (kind of like what you did). Something like new StatsAdjustmentInstance(adjustment). Then call runtimeStats.ApplyAdjustment(instance);. But removing the adjustment would be a little tricky and the caching would become far more complicated.

Outside of a pretty big API rework I'm kinda stumped on this one.

ashblue avatar Feb 01 '22 20:02 ashblue

I did try to do Instantiate(statModifier) (or I should rather call it Instantiate(statAdjustment)) and use that. But it works the same, as it seems like whatever you're doing is entirely based on Id strings?

What about creating unique statAdjustment instances internally with unique Ids on them? Or something along the lines of adding a checkbox like "stackable", which sometime at runtime clones\instantiates an SO and adjusts it's ID to be unique?

Just thinking out loud.

I honestly thing that this is crucial feature for this package to be production ready. I can't imagine many games would be fine without stacking?

Nixellion avatar Feb 01 '22 20:02 Nixellion

Using it in production and it's been working fine for me. It sounds like a cool feature, but I haven't had any sort of need for it. Stat adjustments do overlay just fine by using IDs to overlap and applying multiple stat adjustments. Can I ask what your use case is? It might already do what you need with a small tweak.

If you're trying to do this it could be fixed by adding a unique ID onto the stat IDs.

statModifier.ApplyAdjustment(sameStatsInstance);
// This would probably just overwrite the pre-existing stats
statModifier.ApplyAdjustment(sameStatsInstance);

The system tracks all modifiers with a unique ID associated with the adjustment. It helps tremendously with tracking and debugging.

ashblue avatar Feb 02 '22 02:02 ashblue

Above solution should work as a workaround. I can expand the system to allow multiple adjustments but would require a refactor and a bit of planning. Closing for now but happy to chat more in the discussions on how a refactor might look.

ashblue avatar Mar 21 '23 17:03 ashblue