using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using YachtDice.Categories; using YachtDice.Modifiers.Conditions; using YachtDice.Modifiers.Core; using YachtDice.Modifiers.Definition; using YachtDice.Modifiers.Effects; using YachtDice.Modifiers.Pipeline; using YachtDice.Modifiers.Runtime; using YachtDice.Scoring; namespace YachtDice.Tests { public class ModifierPipelineTests { private ModifierRegistry registry; private ModifierPipeline pipeline; // Тестовые категории private CategoryDefinition chanceCategory; private CategoryDefinition fullHouseCategory; private CategoryDefinition onesCategory; private CategoryDefinition threesCategory; private CategoryDefinition foursCategory; private CategoryDefinition sixesCategory; [SetUp] public void SetUp() { registry = new ModifierRegistry(10); pipeline = new ModifierPipeline(registry); pipeline.TracingEnabled = false; chanceCategory = SumAllCategory.CreateForTest("chance", "Шанс"); fullHouseCategory = FullHouseCategory.CreateForTest("full_house", "Фулл-хаус"); onesCategory = SumOfValueCategory.CreateForTest("ones", "Единицы", 1); threesCategory = SumOfValueCategory.CreateForTest("threes", "Тройки", 3); foursCategory = SumOfValueCategory.CreateForTest("fours", "Четвёрки", 4); sixesCategory = SumOfValueCategory.CreateForTest("sixes", "Шестёрки", 6); } [TearDown] public void TearDown() { Object.DestroyImmediate(chanceCategory); Object.DestroyImmediate(fullHouseCategory); Object.DestroyImmediate(onesCategory); Object.DestroyImmediate(threesCategory); Object.DestroyImmediate(foursCategory); Object.DestroyImmediate(sixesCategory); } private ModifierDefinition CreateDef(string id, TriggerType trigger, List conditions, List effects) { var behavior = ModifierBehavior.CreateForTest(trigger, conditions, effects); return ModifierDefinition.CreateForTest(id, new List { behavior }); } private void RegisterAndActivate(ModifierDefinition def) { var inst = registry.Add(def); registry.TryActivate(inst); } private ModifierContext CreateScoringContext(int baseScore, int[] dice, CategoryDefinition category) { return new ModifierContext { BaseScore = baseScore, DiceValues = dice, Category = category, AllActiveModifiers = registry.Active, }; } // ── Phase Ordering ────────────────────────────────────────── [Test] public void Execute_AdditiveBeforeMultiplicative() { var addEffect = AddFlatScoreEffect.CreateForTest(10, ModifierPhase.Additive); var mulEffect = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative); var addDef = CreateDef("add", TriggerType.OnCategoryScored, null, new List { addEffect }); var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null, new List { mulEffect }); RegisterAndActivate(mulDef); RegisterAndActivate(addDef); var ctx = CreateScoringContext(20, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); // (20 + 10) * 2 = 60 Assert.AreEqual(60, result.FinalScore); } [Test] public void Execute_PostMultiplicativeAfterMultiplicative() { var mulEffect = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative); var postMulEffect = PostMultiplyEffect.CreateForTest(3f, ModifierPhase.PostMultiplicative); var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null, new List { mulEffect }); var postDef = CreateDef("post", TriggerType.OnCategoryScored, null, new List { postMulEffect }); RegisterAndActivate(postDef); RegisterAndActivate(mulDef); var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); // (10 + 0) * 2 * 3 = 60 Assert.AreEqual(60, result.FinalScore); } // ── Condition Filtering ───────────────────────────────────── [Test] public void Execute_ConditionFails_SkipsEffect() { var condition = CategoryCondition.CreateForTest(fullHouseCategory); var effect = AddFlatScoreEffect.CreateForTest(100); var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored, new List { condition }, new List { effect }); RegisterAndActivate(def); // Scoring Ones, not FullHouse — condition should fail var ctx = CreateScoringContext(5, new[] { 1, 1, 1, 1, 1 }, onesCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(0, result.FlatBonus); Assert.AreEqual(5, result.FinalScore); } [Test] public void Execute_ConditionPasses_AppliesEffect() { var condition = CategoryCondition.CreateForTest(fullHouseCategory); var effect = AddFlatScoreEffect.CreateForTest(15); var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored, new List { condition }, new List { effect }); RegisterAndActivate(def); var ctx = CreateScoringContext(25, new[] { 3, 3, 3, 2, 2 }, fullHouseCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(15, result.FlatBonus); Assert.AreEqual(40, result.FinalScore); } // ── Trigger Filtering ─────────────────────────────────────── [Test] public void Execute_WrongTrigger_SkipsModifier() { var effect = AddFlatScoreEffect.CreateForTest(999); var def = CreateDef("turn-bonus", TriggerType.OnTurnStart, null, new List { effect }); RegisterAndActivate(def); var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(0, result.FlatBonus); Assert.AreEqual(10, result.FinalScore); } // ── Multiple Modifiers ────────────────────────────────────── [Test] public void Execute_MultipleModifiers_CorrectOrder() { var perDieAdd = AddPerDiceEffect.CreateForTest(2, targetDiceValue: 3, phase: ModifierPhase.Additive); var perDieMul = MultiplyPerDiceEffect.CreateForTest(1.5f, targetDiceValue: 3, phase: ModifierPhase.Multiplicative); var flatAdd = AddFlatScoreEffect.CreateForTest(10, ModifierPhase.Additive, priority: 10); var finalMul = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative, priority: 10); var def1 = CreateDef("pda", TriggerType.OnCategoryScored, null, new List { perDieAdd }); var def2 = CreateDef("pdm", TriggerType.OnCategoryScored, null, new List { perDieMul }); var def3 = CreateDef("fa", TriggerType.OnCategoryScored, null, new List { flatAdd }); var def4 = CreateDef("fm", TriggerType.OnCategoryScored, null, new List { finalMul }); RegisterAndActivate(def4); RegisterAndActivate(def3); RegisterAndActivate(def2); RegisterAndActivate(def1); // dice: [3, 3, 3, 1, 2] — 3 threes var ctx = CreateScoringContext(9, new[] { 3, 3, 3, 1, 2 }, threesCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(16, result.FlatBonus); Assert.AreEqual(168, result.FinalScore); } // ── Empty / Null Cases ────────────────────────────────────── [Test] public void Execute_NoActiveModifiers_NoChange() { var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(10, result.FinalScore); Assert.AreEqual(0, result.FlatBonus); Assert.AreEqual(1f, result.Multiplier); } [Test] public void Execute_InactiveModifier_Skipped() { var effect = AddFlatScoreEffect.CreateForTest(50); var def = CreateDef("inactive", TriggerType.OnCategoryScored, null, new List { effect }); registry.Add(def); var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(0, result.FlatBonus); Assert.AreEqual(10, result.FinalScore); } // ── Side Effects ──────────────────────────────────────────── [Test] public void Execute_SideEffectsInSideEffectPhase() { var scoreEffect = AddFlatScoreEffect.CreateForTest(10, ModifierPhase.Additive); var currencyEffect = AddCurrencyEffect.CreateForTest(25, ModifierPhase.SideEffect); var def = CreateDef("rewards", TriggerType.OnCategoryScored, null, new List { scoreEffect, currencyEffect }); RegisterAndActivate(def); var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(10, result.FlatBonus); Assert.AreEqual(25, result.CurrencyDelta); Assert.AreEqual(20, result.FinalScore); } // ── Tracing ───────────────────────────────────────────────── [Test] public void Execute_TracingEnabled_PopulatesDebugLog() { pipeline.TracingEnabled = true; var effect = AddFlatScoreEffect.CreateForTest(10); var def = CreateDef("traced", TriggerType.OnCategoryScored, null, new List { effect }); RegisterAndActivate(def); var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.IsNotNull(result.DebugLog); Assert.IsTrue(result.DebugLog.Count > 0); } // ── DiceValue Condition ────────────────────────────────────── [Test] public void Execute_DiceValueCondition_OnlyTriggersOnMatch() { var condition = DiceValueCondition.CreateForTest(6, minCount: 3); var effect = AddFlatScoreEffect.CreateForTest(100); var def = CreateDef("sixes-bonus", TriggerType.OnCategoryScored, new List { condition }, new List { effect }); RegisterAndActivate(def); // Only 2 sixes — condition requires 3 var ctx = CreateScoringContext(12, new[] { 6, 6, 1, 2, 3 }, sixesCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(0, result.FlatBonus); // 3 sixes — condition passes var ctx2 = CreateScoringContext(18, new[] { 6, 6, 6, 1, 2 }, sixesCategory); var result2 = pipeline.Execute(TriggerType.OnCategoryScored, ctx2).GetAwaiter().GetResult(); Assert.AreEqual(100, result2.FlatBonus); } // ── MinScore Condition ────────────────────────────────────── [Test] public void Execute_MinScoreCondition_ThresholdWorks() { var condition = MinScoreCondition.CreateForTest(20); var effect = MultiplyScoreEffect.CreateForTest(2f); var def = CreateDef("high-score-bonus", TriggerType.OnCategoryScored, new List { condition }, new List { effect }); RegisterAndActivate(def); // Below threshold var ctx = CreateScoringContext(15, new[] { 3, 3, 3, 3, 3 }, threesCategory); var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult(); Assert.AreEqual(1f, result.Multiplier); // At threshold var ctx2 = CreateScoringContext(20, new[] { 4, 4, 4, 4, 4 }, foursCategory); var result2 = pipeline.Execute(TriggerType.OnCategoryScored, ctx2).GetAwaiter().GetResult(); Assert.AreEqual(2f, result2.Multiplier); } // ── ToScoreResult ─────────────────────────────────────────── [Test] public void ToScoreResult_ConvertsCorrectly() { var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory); ctx.FlatBonus = 5; ctx.Multiplier = 2f; ctx.PostMultiplier = 1.5f; ScoreResult sr = ctx.ToScoreResult(); Assert.AreEqual(10, sr.BaseScore); Assert.AreEqual(5, sr.FlatBonus); Assert.AreEqual(3f, sr.Multiplier, 0.001f); // 2 * 1.5 Assert.AreEqual(chanceCategory, sr.Category); } } }