[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:
@@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers;
|
||||
using VContainer;
|
||||
using YachtDice.Events;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Scoring
|
||||
{
|
||||
@@ -13,7 +17,16 @@ namespace YachtDice.Scoring
|
||||
|
||||
private readonly Dictionary<YachtCategory, int> scorecard = new();
|
||||
private readonly HashSet<YachtCategory> usedCategories = new();
|
||||
private List<ModifierData> activeModifierData = new();
|
||||
|
||||
private GameEventBus eventBus;
|
||||
private ModifierRegistry modifierRegistry;
|
||||
|
||||
[Inject]
|
||||
public void Construct(GameEventBus eventBus, ModifierRegistry modifierRegistry)
|
||||
{
|
||||
this.eventBus = eventBus;
|
||||
this.modifierRegistry = modifierRegistry;
|
||||
}
|
||||
|
||||
public bool IsCategoryUsed(YachtCategory category) => usedCategories.Contains(category);
|
||||
|
||||
@@ -38,19 +51,62 @@ namespace YachtDice.Scoring
|
||||
|
||||
public bool IsComplete => CategoriesFilledCount >= TotalCategoryCount;
|
||||
|
||||
public void SetActiveModifiers(List<ModifierData> modifiers)
|
||||
{
|
||||
activeModifierData = modifiers ?? new List<ModifierData>();
|
||||
}
|
||||
|
||||
public IReadOnlyList<ModifierData> ActiveModifiers => activeModifierData;
|
||||
|
||||
public ScoreResult PreviewScore(int[] diceValues, YachtCategory category)
|
||||
public ScoreResult PreviewScore(int[] diceValues, YachtCategory category,
|
||||
int currentRoll = 0, int currentTurn = 0, int playerCurrency = 0)
|
||||
{
|
||||
int baseScore = CategoryScorer.Calculate(diceValues, category);
|
||||
ScoreResult result = ScoreResult.Create(baseScore, diceValues, category);
|
||||
|
||||
ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.SelectedCategory);
|
||||
if (eventBus == null || modifierRegistry == null)
|
||||
return ScoreResult.Create(baseScore, diceValues, category);
|
||||
|
||||
var context = ModifierContext.CreateForScoring(
|
||||
baseScore, diceValues, category,
|
||||
currentRoll, currentTurn, playerCurrency,
|
||||
modifierRegistry.Active);
|
||||
|
||||
eventBus.Fire(TriggerType.OnCategoryScored, context).Forget();
|
||||
|
||||
return context.ToScoreResult();
|
||||
}
|
||||
|
||||
public async UniTask<ScoreResult> ScoreCategoryAsync(int[] diceValues, YachtCategory category,
|
||||
int currentRoll, int currentTurn, int playerCurrency)
|
||||
{
|
||||
if (usedCategories.Contains(category))
|
||||
throw new InvalidOperationException($"Category {category} has already been scored.");
|
||||
|
||||
int baseScore = CategoryScorer.Calculate(diceValues, category);
|
||||
|
||||
ModifierContext context;
|
||||
if (eventBus != null && modifierRegistry != null)
|
||||
{
|
||||
context = ModifierContext.CreateForScoring(
|
||||
baseScore, diceValues, category,
|
||||
currentRoll, currentTurn, playerCurrency,
|
||||
modifierRegistry.Active);
|
||||
|
||||
await eventBus.Fire(TriggerType.OnCategoryScored, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
context = new ModifierContext
|
||||
{
|
||||
BaseScore = baseScore,
|
||||
DiceValues = diceValues,
|
||||
Category = category,
|
||||
};
|
||||
}
|
||||
|
||||
var result = context.ToScoreResult();
|
||||
int finalScore = result.FinalScore;
|
||||
scorecard[category] = finalScore;
|
||||
usedCategories.Add(category);
|
||||
|
||||
OnCategoryScored?.Invoke(category, finalScore);
|
||||
OnCategoryConfirmed?.Invoke(category, result);
|
||||
|
||||
if (IsComplete)
|
||||
OnAllCategoriesScored?.Invoke(TotalScore);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -61,10 +117,23 @@ namespace YachtDice.Scoring
|
||||
throw new InvalidOperationException($"Category {category} has already been scored.");
|
||||
|
||||
int baseScore = CategoryScorer.Calculate(diceValues, category);
|
||||
ScoreResult result = ScoreResult.Create(baseScore, diceValues, category);
|
||||
|
||||
ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.SelectedCategory);
|
||||
ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.AnyCategoryClosed);
|
||||
ModifierContext context = null;
|
||||
if (eventBus != null && modifierRegistry != null)
|
||||
{
|
||||
context = ModifierContext.CreateForScoring(
|
||||
baseScore, diceValues, category,
|
||||
0, 0, 0,
|
||||
modifierRegistry.Active);
|
||||
|
||||
eventBus.Fire(TriggerType.OnCategoryScored, context).Forget();
|
||||
}
|
||||
|
||||
ScoreResult result;
|
||||
if (context != null)
|
||||
result = context.ToScoreResult();
|
||||
else
|
||||
result = ScoreResult.Create(baseScore, diceValues, category);
|
||||
|
||||
int finalScore = result.FinalScore;
|
||||
scorecard[category] = finalScore;
|
||||
|
||||
Reference in New Issue
Block a user