[Refactor] Replace enum-driven modifier system with data-driven SO composition
Replace the entire static, enum-based modifier pipeline with a composition-based, data-driven architecture using ScriptableObject polymorphism. New modifiers can now be created by assembling SO building blocks (Conditions + Effects + Behaviors) — no core code edits needed. New architecture: - Core/: TriggerType, ModifierPhase, ModifierContext, ICondition, IEffect - Definition/: ModifierDefinitionSO, ModifierBehaviorSO, ConditionSO, EffectSO, ModifierCatalogSO - Conditions/: DieValueCondition, CategoryCondition, MinScoreCondition, DiceCountCondition - Effects/: AddFlat, AddPerDie, Multiply, MultiplyPerDie, PostMultiply, AddCurrency, ConsumeCharge - Runtime/: ModifierInstance, ModifierRegistry (non-static service) - Pipeline/: async ModifierPipeline with phase ordering, tracing, anti-recursion - Editor/: ModifierDefinitionValidator with menu items - Events/: GameEventBus (non-static typed dispatcher) - DI/: GameLifetimeScope (VContainer composition root) Deleted old system: ModifierData, ModifierEffect, ModifierEnums, ModifierPipeline (static), ModifierRuntime, ModifierTarget, ShopCatalog, ModifierAssetCreator. Updated: ScoringSystem (VContainer + async), InventoryModel (delegates to ModifierRegistry), ShopModel (uses ModifierDefinitionSO), GameController (VContainer injection), SaveData (uses Runtime.ModifierSaveEntry), all views/controllers, and all test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "AddCurrencyEffect", menuName = "YachtDice/Modifiers/Effects/Add Currency")]
|
||||
public class AddCurrencyEffect : EffectSO
|
||||
{
|
||||
[SerializeField] private int amount;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
context.CurrencyDelta += amount * instance.Stacks;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static AddCurrencyEffect CreateForTest(int amount,
|
||||
ModifierPhase phase = ModifierPhase.SideEffect, int priority = 0)
|
||||
{
|
||||
var so = CreateInstance<AddCurrencyEffect>();
|
||||
so.amount = amount;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "AddFlatScoreEffect", menuName = "YachtDice/Modifiers/Effects/Add Flat Score")]
|
||||
public class AddFlatScoreEffect : EffectSO
|
||||
{
|
||||
[SerializeField] private int value;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
context.FlatBonus += value * instance.Stacks;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static AddFlatScoreEffect CreateForTest(int value, ModifierPhase phase = ModifierPhase.Additive, int priority = 0)
|
||||
{
|
||||
var so = CreateInstance<AddFlatScoreEffect>();
|
||||
so.value = value;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "AddPerDieEffect", menuName = "YachtDice/Modifiers/Effects/Add Per Die")]
|
||||
public class AddPerDieEffect : EffectSO
|
||||
{
|
||||
[Tooltip("Points to add per matching die.")]
|
||||
[SerializeField] private int valuePerDie;
|
||||
|
||||
[Tooltip("Die face value to match (1-6). 0 = any/all dice.")]
|
||||
[SerializeField, Range(0, 6)] private int targetDieValue;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
if (context.DiceValues == null) return UniTask.CompletedTask;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < context.DiceValues.Length; i++)
|
||||
{
|
||||
if (targetDieValue == 0 || context.DiceValues[i] == targetDieValue)
|
||||
count++;
|
||||
}
|
||||
|
||||
context.FlatBonus += valuePerDie * count * instance.Stacks;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static AddPerDieEffect CreateForTest(int valuePerDie, int targetDieValue = 0,
|
||||
ModifierPhase phase = ModifierPhase.Additive, int priority = 0)
|
||||
{
|
||||
var so = CreateInstance<AddPerDieEffect>();
|
||||
so.valuePerDie = valuePerDie;
|
||||
so.targetDieValue = targetDieValue;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "ConsumeChargeEffect", menuName = "YachtDice/Modifiers/Effects/Consume Charge")]
|
||||
public class ConsumeChargeEffect : EffectSO
|
||||
{
|
||||
[SerializeField] private int charges = 1;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
instance.ConsumeCharge(charges);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static ConsumeChargeEffect CreateForTest(int charges = 1,
|
||||
ModifierPhase phase = ModifierPhase.SideEffect, int priority = 100)
|
||||
{
|
||||
var so = CreateInstance<ConsumeChargeEffect>();
|
||||
so.charges = charges;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "MultiplyPerDieEffect", menuName = "YachtDice/Modifiers/Effects/Multiply Per Die")]
|
||||
public class MultiplyPerDieEffect : EffectSO
|
||||
{
|
||||
[Tooltip("Multiplier to apply per matching die.")]
|
||||
[SerializeField] private float multiplierPerDie = 1f;
|
||||
|
||||
[Tooltip("Die face value to match (1-6). 0 = any/all dice.")]
|
||||
[SerializeField, Range(0, 6)] private int targetDieValue;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
if (context.DiceValues == null) return UniTask.CompletedTask;
|
||||
|
||||
for (int i = 0; i < context.DiceValues.Length; i++)
|
||||
{
|
||||
if (targetDieValue == 0 || context.DiceValues[i] == targetDieValue)
|
||||
context.Multiplier *= multiplierPerDie;
|
||||
}
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static MultiplyPerDieEffect CreateForTest(float multiplierPerDie, int targetDieValue = 0,
|
||||
ModifierPhase phase = ModifierPhase.Multiplicative, int priority = 0)
|
||||
{
|
||||
var so = CreateInstance<MultiplyPerDieEffect>();
|
||||
so.multiplierPerDie = multiplierPerDie;
|
||||
so.targetDieValue = targetDieValue;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "MultiplyScoreEffect", menuName = "YachtDice/Modifiers/Effects/Multiply Score")]
|
||||
public class MultiplyScoreEffect : EffectSO
|
||||
{
|
||||
[SerializeField] private float multiplier = 1f;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
context.Multiplier *= Mathf.Pow(multiplier, instance.Stacks);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static MultiplyScoreEffect CreateForTest(float multiplier,
|
||||
ModifierPhase phase = ModifierPhase.Multiplicative, int priority = 0)
|
||||
{
|
||||
var so = CreateInstance<MultiplyScoreEffect>();
|
||||
so.multiplier = multiplier;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Modifiers.Effects
|
||||
{
|
||||
[CreateAssetMenu(fileName = "PostMultiplyEffect", menuName = "YachtDice/Modifiers/Effects/Post Multiply")]
|
||||
public class PostMultiplyEffect : EffectSO
|
||||
{
|
||||
[SerializeField] private float postMultiplier = 1f;
|
||||
|
||||
public override UniTask Apply(ModifierContext context, ModifierInstance instance)
|
||||
{
|
||||
context.PostMultiplier *= Mathf.Pow(postMultiplier, instance.Stacks);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static PostMultiplyEffect CreateForTest(float postMultiplier,
|
||||
ModifierPhase phase = ModifierPhase.PostMultiplicative, int priority = 0)
|
||||
{
|
||||
var so = CreateInstance<PostMultiplyEffect>();
|
||||
so.postMultiplier = postMultiplier;
|
||||
so.SetPhaseForTest(phase);
|
||||
so.SetPriorityForTest(priority);
|
||||
return so;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user