68c4abace3
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>
158 lines
5.2 KiB
C#
158 lines
5.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using VContainer;
|
|
using YachtDice.Events;
|
|
using YachtDice.Modifiers.Core;
|
|
using YachtDice.Modifiers.Runtime;
|
|
|
|
namespace YachtDice.Scoring
|
|
{
|
|
public class ScoringSystem : MonoBehaviour
|
|
{
|
|
public event Action<YachtCategory, int> OnCategoryScored;
|
|
public event Action<int> OnAllCategoriesScored;
|
|
public event Action<YachtCategory, ScoreResult> OnCategoryConfirmed;
|
|
|
|
private readonly Dictionary<YachtCategory, int> scorecard = new();
|
|
private readonly HashSet<YachtCategory> usedCategories = 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);
|
|
|
|
public int GetCategoryScore(YachtCategory category)
|
|
{
|
|
return scorecard.TryGetValue(category, out int score) ? score : -1;
|
|
}
|
|
|
|
public int TotalScore
|
|
{
|
|
get
|
|
{
|
|
int total = 0;
|
|
foreach (var kvp in scorecard) total += kvp.Value;
|
|
return total;
|
|
}
|
|
}
|
|
|
|
public int CategoriesFilledCount => usedCategories.Count;
|
|
|
|
public int TotalCategoryCount => Enum.GetValues(typeof(YachtCategory)).Length;
|
|
|
|
public bool IsComplete => CategoriesFilledCount >= TotalCategoryCount;
|
|
|
|
public ScoreResult PreviewScore(int[] diceValues, YachtCategory category,
|
|
int currentRoll = 0, int currentTurn = 0, int playerCurrency = 0)
|
|
{
|
|
int baseScore = CategoryScorer.Calculate(diceValues, category);
|
|
|
|
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;
|
|
}
|
|
|
|
public ScoreResult ScoreCategory(int[] diceValues, YachtCategory category)
|
|
{
|
|
if (usedCategories.Contains(category))
|
|
throw new InvalidOperationException($"Category {category} has already been scored.");
|
|
|
|
int baseScore = CategoryScorer.Calculate(diceValues, category);
|
|
|
|
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;
|
|
usedCategories.Add(category);
|
|
|
|
OnCategoryScored?.Invoke(category, finalScore);
|
|
OnCategoryConfirmed?.Invoke(category, result);
|
|
|
|
if (IsComplete)
|
|
OnAllCategoriesScored?.Invoke(TotalScore);
|
|
|
|
return result;
|
|
}
|
|
|
|
public void ResetScorecard()
|
|
{
|
|
scorecard.Clear();
|
|
usedCategories.Clear();
|
|
}
|
|
}
|
|
}
|