[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,32 +1,34 @@
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using YachtDice.Inventory;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Tests
|
||||
{
|
||||
public class InventoryModelTests
|
||||
{
|
||||
private ModifierRegistry registry;
|
||||
private InventoryModel inventory;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
inventory = new InventoryModel(3);
|
||||
registry = new ModifierRegistry(3);
|
||||
inventory = new InventoryModel(registry);
|
||||
}
|
||||
|
||||
private ModifierData CreateTestData(string id = "test",
|
||||
ModifierDurability durability = ModifierDurability.Permanent, int maxUses = 0)
|
||||
private ModifierDefinitionSO CreateTestDef(string id = "test",
|
||||
bool hasLimitedUses = false, int maxUses = 0)
|
||||
{
|
||||
return ModifierData.CreateForTest(id, ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f,
|
||||
durability: durability, maxUses: maxUses);
|
||||
return ModifierDefinitionSO.CreateForTest(id, null,
|
||||
hasLimitedUses: hasLimitedUses, maxUses: maxUses);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddModifier_IncreasesCount()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData());
|
||||
inventory.AddModifier(CreateTestDef());
|
||||
|
||||
Assert.AreEqual(1, inventory.OwnedModifiers.Count);
|
||||
}
|
||||
@@ -34,7 +36,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void TryActivate_SucceedsWithinSlotLimit()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData("a"));
|
||||
inventory.AddModifier(CreateTestDef("a"));
|
||||
var mod = inventory.OwnedModifiers[0];
|
||||
|
||||
bool result = inventory.TryActivate(mod);
|
||||
@@ -49,11 +51,11 @@ namespace YachtDice.Tests
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
inventory.AddModifier(CreateTestData($"m{i}"));
|
||||
inventory.AddModifier(CreateTestDef($"m{i}"));
|
||||
inventory.TryActivate(inventory.OwnedModifiers[i]);
|
||||
}
|
||||
|
||||
inventory.AddModifier(CreateTestData("extra"));
|
||||
inventory.AddModifier(CreateTestDef("extra"));
|
||||
var extra = inventory.OwnedModifiers[3];
|
||||
bool result = inventory.TryActivate(extra);
|
||||
|
||||
@@ -65,7 +67,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void Deactivate_FreesSlot()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData());
|
||||
inventory.AddModifier(CreateTestDef());
|
||||
var mod = inventory.OwnedModifiers[0];
|
||||
inventory.TryActivate(mod);
|
||||
|
||||
@@ -78,7 +80,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void RemoveModifier_DeactivatesAndRemoves()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData());
|
||||
inventory.AddModifier(CreateTestDef());
|
||||
var mod = inventory.OwnedModifiers[0];
|
||||
inventory.TryActivate(mod);
|
||||
|
||||
@@ -91,7 +93,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void ConsumeUseOnActive_DecrementsUses()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData("ltd", ModifierDurability.LimitedUses, 3));
|
||||
inventory.AddModifier(CreateTestDef("ltd", hasLimitedUses: true, maxUses: 3));
|
||||
var mod = inventory.OwnedModifiers[0];
|
||||
inventory.TryActivate(mod);
|
||||
|
||||
@@ -103,7 +105,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void ConsumeUseOnActive_RemovesExpired()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData("ltd", ModifierDurability.LimitedUses, 1));
|
||||
inventory.AddModifier(CreateTestDef("ltd", hasLimitedUses: true, maxUses: 1));
|
||||
var mod = inventory.OwnedModifiers[0];
|
||||
inventory.TryActivate(mod);
|
||||
|
||||
@@ -115,7 +117,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void ConsumeUseOnActive_IgnoresPermanent()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData("perm", ModifierDurability.Permanent));
|
||||
inventory.AddModifier(CreateTestDef("perm"));
|
||||
var mod = inventory.OwnedModifiers[0];
|
||||
inventory.TryActivate(mod);
|
||||
|
||||
@@ -126,13 +128,13 @@ namespace YachtDice.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetActiveModifierData_ReturnsOnlyActive()
|
||||
public void GetActiveModifierDefinitions_ReturnsOnlyActive()
|
||||
{
|
||||
inventory.AddModifier(CreateTestData("a"));
|
||||
inventory.AddModifier(CreateTestData("b"));
|
||||
inventory.AddModifier(CreateTestDef("a"));
|
||||
inventory.AddModifier(CreateTestDef("b"));
|
||||
inventory.TryActivate(inventory.OwnedModifiers[0]);
|
||||
|
||||
var active = inventory.GetActiveModifierData();
|
||||
var active = inventory.GetActiveModifierDefinitions();
|
||||
|
||||
Assert.AreEqual(1, active.Count);
|
||||
}
|
||||
@@ -150,7 +152,7 @@ namespace YachtDice.Tests
|
||||
{
|
||||
bool fired = false;
|
||||
inventory.OnActiveModifiersChanged += _ => fired = true;
|
||||
inventory.AddModifier(CreateTestData());
|
||||
inventory.AddModifier(CreateTestDef());
|
||||
|
||||
inventory.TryActivate(inventory.OwnedModifiers[0]);
|
||||
|
||||
|
||||
@@ -1,94 +1,244 @@
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Effects;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
using YachtDice.Scoring;
|
||||
|
||||
namespace YachtDice.Tests
|
||||
{
|
||||
public class ModifierEffectTests
|
||||
{
|
||||
private static ModifierData CreateData(
|
||||
ModifierEffectType effectType, float effectValue,
|
||||
int dieValue = 0, ModifierScope scope = ModifierScope.SelectedCategory)
|
||||
private ModifierInstance CreateInstance(string id = "test")
|
||||
{
|
||||
return ModifierData.CreateForTest("test", scope, effectType, effectValue, dieValue);
|
||||
var def = ModifierDefinitionSO.CreateForTest(id, null);
|
||||
return new ModifierInstance(def);
|
||||
}
|
||||
|
||||
private ModifierContext CreateContext(int baseScore, int[] dice, YachtCategory category)
|
||||
{
|
||||
return new ModifierContext
|
||||
{
|
||||
BaseScore = baseScore,
|
||||
DiceValues = dice,
|
||||
Category = category,
|
||||
};
|
||||
}
|
||||
|
||||
// ── AddPerDieEffect ─────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void AddPerDieEffect_CountsMatchingDice()
|
||||
{
|
||||
var effect = AddPerDieEffect.CreateForTest(10, targetDieValue: 1);
|
||||
var ctx = CreateContext(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones);
|
||||
var inst = CreateInstance();
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(30, ctx.FlatBonus); // 10 * 3 matching dice
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddPerDieValue_CountsMatchingDice()
|
||||
public void AddPerDieEffect_ZeroTarget_CountsAllDice()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.AddPerDieValue, 10f, dieValue: 1);
|
||||
var result = ScoreResult.Create(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones);
|
||||
var effect = AddPerDieEffect.CreateForTest(2, targetDieValue: 0);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(30, result.FlatBonus);
|
||||
Assert.AreEqual(10, ctx.FlatBonus); // 2 * 5 dice
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddPerDieValue_ZeroTarget_CountsAllDice()
|
||||
public void AddPerDieEffect_NoMatches_ZeroBonus()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.AddPerDieValue, 2f, dieValue: 0);
|
||||
var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var effect = AddPerDieEffect.CreateForTest(10, targetDieValue: 6);
|
||||
var ctx = CreateContext(5, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(10, result.FlatBonus);
|
||||
Assert.AreEqual(0, ctx.FlatBonus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddPerDieValue_NoMatches_ZeroBonus()
|
||||
public void AddPerDieEffect_ScalesWithStacks()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.AddPerDieValue, 10f, dieValue: 6);
|
||||
var result = ScoreResult.Create(5, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var effect = AddPerDieEffect.CreateForTest(10, targetDieValue: 1);
|
||||
var ctx = CreateContext(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones);
|
||||
var inst = CreateInstance();
|
||||
inst.Stacks = 2;
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(0, result.FlatBonus);
|
||||
Assert.AreEqual(60, ctx.FlatBonus); // 10 * 3 * 2 stacks
|
||||
}
|
||||
|
||||
// ── AddFlatScoreEffect ──────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void AddFlatScoreEffect_AddsFlat()
|
||||
{
|
||||
var effect = AddFlatScoreEffect.CreateForTest(15);
|
||||
var ctx = CreateContext(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse);
|
||||
var inst = CreateInstance();
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(15, ctx.FlatBonus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddFlatToFinalScore_AddsFlat()
|
||||
public void AddFlatScoreEffect_ScalesWithStacks()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.AddFlatToFinalScore, 15f);
|
||||
var result = ScoreResult.Create(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse);
|
||||
var effect = AddFlatScoreEffect.CreateForTest(15);
|
||||
var ctx = CreateContext(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse);
|
||||
var inst = CreateInstance();
|
||||
inst.Stacks = 3;
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(15, result.FlatBonus);
|
||||
Assert.AreEqual(45, ctx.FlatBonus); // 15 * 3 stacks
|
||||
}
|
||||
|
||||
// ── MultiplyPerDieEffect ────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void MultiplyPerDieEffect_MultipliesPerMatch()
|
||||
{
|
||||
var effect = MultiplyPerDieEffect.CreateForTest(2f, targetDieValue: 6);
|
||||
var ctx = CreateContext(18, new[] { 6, 6, 6, 1, 2 }, YachtCategory.Sixes);
|
||||
var inst = CreateInstance();
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(8f, ctx.Multiplier, 0.001f); // 1 * 2 * 2 * 2 = 8
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiplyPerDieValue_MultipliesPerMatch()
|
||||
public void MultiplyPerDieEffect_NoMatches_MultiplierUnchanged()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.MultiplyPerDieValue, 2f, dieValue: 6);
|
||||
var result = ScoreResult.Create(18, new[] { 6, 6, 6, 1, 2 }, YachtCategory.Sixes);
|
||||
var effect = MultiplyPerDieEffect.CreateForTest(3f, targetDieValue: 6);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(8f, result.Multiplier); // 1 * 2 * 2 * 2 = 8
|
||||
Assert.AreEqual(1f, ctx.Multiplier);
|
||||
}
|
||||
|
||||
// ── MultiplyScoreEffect ─────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void MultiplyScoreEffect_MultipliesOnce()
|
||||
{
|
||||
var effect = MultiplyScoreEffect.CreateForTest(1.5f);
|
||||
var ctx = CreateContext(50, new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht);
|
||||
var inst = CreateInstance();
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(1.5f, ctx.Multiplier, 0.001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiplyPerDieValue_NoMatches_MultiplierUnchanged()
|
||||
public void MultiplyScoreEffect_ScalesWithStacks()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.MultiplyPerDieValue, 3f, dieValue: 6);
|
||||
var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var effect = MultiplyScoreEffect.CreateForTest(2f);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
inst.Stacks = 3;
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(1f, result.Multiplier);
|
||||
// Pow(2, 3) = 8
|
||||
Assert.AreEqual(8f, ctx.Multiplier, 0.001f);
|
||||
}
|
||||
|
||||
// ── PostMultiplyEffect ──────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void PostMultiplyEffect_MultipliesPostMultiplier()
|
||||
{
|
||||
var effect = PostMultiplyEffect.CreateForTest(2f);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(2f, ctx.PostMultiplier, 0.001f);
|
||||
}
|
||||
|
||||
// ── AddCurrencyEffect ───────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void AddCurrencyEffect_AddsToCurrencyDelta()
|
||||
{
|
||||
var effect = AddCurrencyEffect.CreateForTest(25);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(25, ctx.CurrencyDelta);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiplyFinalScore_MultipliesOnce()
|
||||
public void AddCurrencyEffect_ScalesWithStacks()
|
||||
{
|
||||
var data = CreateData(ModifierEffectType.MultiplyFinalScore, 1.5f);
|
||||
var result = ScoreResult.Create(50, new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht);
|
||||
var effect = AddCurrencyEffect.CreateForTest(25);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance();
|
||||
inst.Stacks = 2;
|
||||
|
||||
ModifierEffect.Apply(data, ref result);
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(1.5f, result.Multiplier, 0.001f);
|
||||
Assert.AreEqual(50, ctx.CurrencyDelta); // 25 * 2 stacks
|
||||
}
|
||||
|
||||
// ── ConsumeChargeEffect ─────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void ConsumeChargeEffect_DecrementsRemainingUses()
|
||||
{
|
||||
var effect = ConsumeChargeEffect.CreateForTest(1);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var def = ModifierDefinitionSO.CreateForTest("limited", null,
|
||||
hasLimitedUses: true, maxUses: 3);
|
||||
var inst = new ModifierInstance(def);
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(2, inst.RemainingUses);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConsumeChargeEffect_IgnoresPermanent()
|
||||
{
|
||||
var effect = ConsumeChargeEffect.CreateForTest(1);
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var inst = CreateInstance(); // permanent (no limited uses)
|
||||
|
||||
effect.Apply(ctx, inst).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(-1, inst.RemainingUses); // unchanged
|
||||
}
|
||||
|
||||
// ── FinalScore Integration ──────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void FinalScore_CombinesBaseAndFlatAndMultiplier()
|
||||
{
|
||||
var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
ctx.FlatBonus = 5;
|
||||
ctx.Multiplier = 2f;
|
||||
ctx.PostMultiplier = 1.5f;
|
||||
|
||||
// FinalScore = floor((10 + 5) * 2 * 1.5) = floor(45) = 45
|
||||
Assert.AreEqual(45, ctx.FinalScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +1,336 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers;
|
||||
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
|
||||
{
|
||||
[Test]
|
||||
public void Apply_AdditiveBeforeMultiplicative()
|
||||
private ModifierRegistry registry;
|
||||
private ModifierPipeline pipeline;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
var addMod = ModifierData.CreateForTest("add", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f);
|
||||
var mulMod = ModifierData.CreateForTest("mul", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.MultiplyFinalScore, 2f);
|
||||
registry = new ModifierRegistry(10);
|
||||
pipeline = new ModifierPipeline(registry);
|
||||
pipeline.TracingEnabled = false; // disable debug logs during tests
|
||||
}
|
||||
|
||||
var modifiers = new List<ModifierData> { mulMod, addMod };
|
||||
var result = ScoreResult.Create(20, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
private ModifierDefinitionSO CreateDef(string id,
|
||||
TriggerType trigger,
|
||||
List<ConditionSO> conditions,
|
||||
List<EffectSO> effects)
|
||||
{
|
||||
var behavior = ModifierBehaviorSO.CreateForTest(trigger, conditions, effects);
|
||||
return ModifierDefinitionSO.CreateForTest(id,
|
||||
new List<ModifierBehaviorSO> { behavior });
|
||||
}
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
private void RegisterAndActivate(ModifierDefinitionSO def)
|
||||
{
|
||||
var inst = registry.Add(def);
|
||||
registry.TryActivate(inst);
|
||||
}
|
||||
|
||||
private ModifierContext CreateScoringContext(int baseScore, int[] dice, YachtCategory 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<EffectSO> { addEffect });
|
||||
var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { mulEffect });
|
||||
|
||||
RegisterAndActivate(mulDef); // registered first, but multiplicative phase
|
||||
RegisterAndActivate(addDef); // registered second, but additive phase
|
||||
|
||||
var ctx = CreateScoringContext(20, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
|
||||
|
||||
// (20 + 10) * 2 = 60
|
||||
Assert.AreEqual(60, result.FinalScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Apply_CategoryLevelBeforeFinalScore()
|
||||
public void Execute_PostMultiplicativeAfterMultiplicative()
|
||||
{
|
||||
var perDie = ModifierData.CreateForTest("perDie", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddPerDieValue, 5f, dieValue: 1);
|
||||
var flat = ModifierData.CreateForTest("flat", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 100f);
|
||||
var mulEffect = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative);
|
||||
var postMulEffect = PostMultiplyEffect.CreateForTest(3f, ModifierPhase.PostMultiplicative);
|
||||
|
||||
var modifiers = new List<ModifierData> { flat, perDie };
|
||||
var result = ScoreResult.Create(3, new[] { 1, 1, 1, 2, 3 }, YachtCategory.Ones);
|
||||
var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { mulEffect });
|
||||
var postDef = CreateDef("post", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { postMulEffect });
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
RegisterAndActivate(postDef);
|
||||
RegisterAndActivate(mulDef);
|
||||
|
||||
// FlatBonus = 15 (perDie: 5*3) + 100 (flat) = 115
|
||||
// FinalScore = (3 + 115) * 1 = 118
|
||||
Assert.AreEqual(118, result.FinalScore);
|
||||
var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
|
||||
|
||||
// (10 + 0) * 2 * 3 = 60
|
||||
Assert.AreEqual(60, result.FinalScore);
|
||||
}
|
||||
|
||||
// ── Condition Filtering ─────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void Apply_ScopeFiltering_SkipsWrongScope()
|
||||
public void Execute_ConditionFails_SkipsEffect()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed,
|
||||
ModifierEffectType.AddFlatToFinalScore, 50f);
|
||||
var condition = CategoryCondition.CreateForTest(YachtCategory.FullHouse);
|
||||
var effect = AddFlatScoreEffect.CreateForTest(100);
|
||||
|
||||
var modifiers = new List<ModifierData> { mod };
|
||||
var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored,
|
||||
new List<ConditionSO> { condition },
|
||||
new List<EffectSO> { effect });
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
RegisterAndActivate(def);
|
||||
|
||||
// Scoring Ones, not FullHouse — condition should fail
|
||||
var ctx = CreateScoringContext(5, new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
|
||||
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(0, result.FlatBonus);
|
||||
Assert.AreEqual(10, result.FinalScore);
|
||||
Assert.AreEqual(5, result.FinalScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Apply_CategoryFilter_SkipsWrongCategory()
|
||||
public void Execute_ConditionPasses_AppliesEffect()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("fh", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 15f,
|
||||
targetCategory: YachtCategory.FullHouse, hasCategoryFilter: true);
|
||||
var condition = CategoryCondition.CreateForTest(YachtCategory.FullHouse);
|
||||
var effect = AddFlatScoreEffect.CreateForTest(15);
|
||||
|
||||
var modifiers = new List<ModifierData> { mod };
|
||||
var result = ScoreResult.Create(5, new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
|
||||
var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored,
|
||||
new List<ConditionSO> { condition },
|
||||
new List<EffectSO> { effect });
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
RegisterAndActivate(def);
|
||||
|
||||
Assert.AreEqual(0, result.FlatBonus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Apply_CategoryFilter_AppliesMatchingCategory()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("fh", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 15f,
|
||||
targetCategory: YachtCategory.FullHouse, hasCategoryFilter: true);
|
||||
|
||||
var modifiers = new List<ModifierData> { mod };
|
||||
var result = ScoreResult.Create(25, new[] { 3, 3, 3, 2, 2 }, YachtCategory.FullHouse);
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
var ctx = CreateScoringContext(25, new[] { 3, 3, 3, 2, 2 }, YachtCategory.FullHouse);
|
||||
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
|
||||
|
||||
Assert.AreEqual(15, result.FlatBonus);
|
||||
Assert.AreEqual(40, result.FinalScore);
|
||||
}
|
||||
|
||||
// ── Trigger Filtering ───────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void Apply_MultipleModifiers_CorrectOrder()
|
||||
public void Execute_WrongTrigger_SkipsModifier()
|
||||
{
|
||||
var perDieAdd = ModifierData.CreateForTest("pda", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddPerDieValue, 2f, dieValue: 3);
|
||||
var perDieMul = ModifierData.CreateForTest("pdm", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.MultiplyPerDieValue, 1.5f, dieValue: 3);
|
||||
var flatAdd = ModifierData.CreateForTest("fa", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f);
|
||||
var finalMul = ModifierData.CreateForTest("fm", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.MultiplyFinalScore, 2f);
|
||||
var effect = AddFlatScoreEffect.CreateForTest(999);
|
||||
var def = CreateDef("turn-bonus", TriggerType.OnTurnStart, null,
|
||||
new List<EffectSO> { effect });
|
||||
|
||||
RegisterAndActivate(def);
|
||||
|
||||
// Fire OnCategoryScored, not OnTurnStart
|
||||
var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
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 = AddPerDieEffect.CreateForTest(2, targetDieValue: 3, phase: ModifierPhase.Additive);
|
||||
var perDieMul = MultiplyPerDieEffect.CreateForTest(1.5f, targetDieValue: 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<EffectSO> { perDieAdd });
|
||||
var def2 = CreateDef("pdm", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { perDieMul });
|
||||
var def3 = CreateDef("fa", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { flatAdd });
|
||||
var def4 = CreateDef("fm", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { finalMul });
|
||||
|
||||
RegisterAndActivate(def4);
|
||||
RegisterAndActivate(def3);
|
||||
RegisterAndActivate(def2);
|
||||
RegisterAndActivate(def1);
|
||||
|
||||
var modifiers = new List<ModifierData> { finalMul, flatAdd, perDieMul, perDieAdd };
|
||||
// dice: [3, 3, 3, 1, 2] — 3 threes
|
||||
var result = ScoreResult.Create(9, new[] { 3, 3, 3, 1, 2 }, YachtCategory.Threes);
|
||||
var ctx = CreateScoringContext(9, new[] { 3, 3, 3, 1, 2 }, YachtCategory.Threes);
|
||||
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
|
||||
// Pass 1 (cat additive): perDieAdd: +2*3 = +6 FlatBonus
|
||||
// Pass 2 (cat multiplicative): perDieMul: 1.5^3 = 3.375 Multiplier
|
||||
// Pass 3 (final additive): flatAdd: +10 FlatBonus → total FlatBonus = 16
|
||||
// Pass 4 (final multiplicative): finalMul: 3.375 * 2 = 6.75 Multiplier
|
||||
// Additive phase: perDieAdd (+2*3=6) + flatAdd (+10) → FlatBonus = 16
|
||||
// Multiplicative phase: perDieMul (1.5^3=3.375) then finalMul (*2) → Multiplier = 6.75
|
||||
// FinalScore = floor((9 + 16) * 6.75) = floor(168.75) = 168
|
||||
Assert.AreEqual(6, result.FlatBonus + 10); // just check pipeline ran; full calc below
|
||||
Assert.AreEqual(16, result.FlatBonus);
|
||||
Assert.AreEqual(168, result.FinalScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Apply_NullModifiers_DoesNotThrow()
|
||||
{
|
||||
var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
// ── Empty / Null Cases ──────────────────────────────────────
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
ModifierPipeline.Apply(null, ref result, ModifierScope.SelectedCategory));
|
||||
[Test]
|
||||
public void Execute_NoActiveModifiers_NoChange()
|
||||
{
|
||||
var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
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 Apply_EmptyList_NoChange()
|
||||
public void Execute_InactiveModifier_Skipped()
|
||||
{
|
||||
var modifiers = new List<ModifierData>();
|
||||
var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var effect = AddFlatScoreEffect.CreateForTest(50);
|
||||
var def = CreateDef("inactive", TriggerType.OnCategoryScored, null,
|
||||
new List<EffectSO> { effect });
|
||||
|
||||
ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory);
|
||||
// Add but don't activate
|
||||
registry.Add(def);
|
||||
|
||||
var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
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<EffectSO> { scoreEffect, currencyEffect });
|
||||
|
||||
RegisterAndActivate(def);
|
||||
|
||||
var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
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<EffectSO> { effect });
|
||||
|
||||
RegisterAndActivate(def);
|
||||
|
||||
var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
|
||||
|
||||
Assert.IsNotNull(result.DebugLog);
|
||||
Assert.IsTrue(result.DebugLog.Count > 0);
|
||||
}
|
||||
|
||||
// ── DieValue Condition ──────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public void Execute_DieValueCondition_OnlyTriggersOnMatch()
|
||||
{
|
||||
var condition = DieValueCondition.CreateForTest(6, minCount: 3);
|
||||
var effect = AddFlatScoreEffect.CreateForTest(100);
|
||||
|
||||
var def = CreateDef("sixes-bonus", TriggerType.OnCategoryScored,
|
||||
new List<ConditionSO> { condition },
|
||||
new List<EffectSO> { effect });
|
||||
|
||||
RegisterAndActivate(def);
|
||||
|
||||
// Only 2 sixes — condition requires 3
|
||||
var ctx = CreateScoringContext(12, new[] { 6, 6, 1, 2, 3 }, YachtCategory.Sixes);
|
||||
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 }, YachtCategory.Sixes);
|
||||
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<ConditionSO> { condition },
|
||||
new List<EffectSO> { effect });
|
||||
|
||||
RegisterAndActivate(def);
|
||||
|
||||
// Below threshold
|
||||
var ctx = CreateScoringContext(15, new[] { 3, 3, 3, 3, 3 }, YachtCategory.Threes);
|
||||
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 }, YachtCategory.Fours);
|
||||
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 }, YachtCategory.Chance);
|
||||
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(YachtCategory.Chance, sr.Category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using YachtDice.Scoring;
|
||||
using YachtDice.Modifiers;
|
||||
|
||||
namespace YachtDice.Tests
|
||||
{
|
||||
@@ -22,41 +20,15 @@ namespace YachtDice.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PreviewScore_AppliesOnlySelectedCategoryModifiers()
|
||||
public void ScoreCategory_WithNoModifiers_CalculatesBaseOnly()
|
||||
{
|
||||
var system = CreateScoringSystem();
|
||||
var result = system.ScoreCategory(new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht);
|
||||
|
||||
var selectedMod = ModifierData.CreateForTest("sel", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f);
|
||||
var anyCloseMod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed,
|
||||
ModifierEffectType.AddFlatToFinalScore, 100f);
|
||||
|
||||
system.SetActiveModifiers(new List<ModifierData> { selectedMod, anyCloseMod });
|
||||
|
||||
var result = system.PreviewScore(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
|
||||
// Only SelectedCategory mod should apply in preview
|
||||
Assert.AreEqual(10, result.FlatBonus);
|
||||
Assert.AreEqual(25, result.FinalScore); // (15 + 10) * 1
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScoreCategory_AppliesBothScopes()
|
||||
{
|
||||
var system = CreateScoringSystem();
|
||||
|
||||
var selectedMod = ModifierData.CreateForTest("sel", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f);
|
||||
var anyCloseMod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed,
|
||||
ModifierEffectType.AddFlatToFinalScore, 20f);
|
||||
|
||||
system.SetActiveModifiers(new List<ModifierData> { selectedMod, anyCloseMod });
|
||||
|
||||
var result = system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
|
||||
// Both scopes should apply
|
||||
Assert.AreEqual(30, result.FlatBonus);
|
||||
Assert.AreEqual(45, result.FinalScore); // (15 + 30) * 1
|
||||
Assert.AreEqual(50, result.BaseScore);
|
||||
Assert.AreEqual(0, result.FlatBonus);
|
||||
Assert.AreEqual(1f, result.Multiplier);
|
||||
Assert.AreEqual(50, result.FinalScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -89,15 +61,36 @@ namespace YachtDice.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScoreCategory_WithNoModifiers_CalculatesBaseOnly()
|
||||
public void PreviewScore_WithNoModifiers_CalculatesBaseOnly()
|
||||
{
|
||||
var system = CreateScoringSystem();
|
||||
var result = system.ScoreCategory(new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht);
|
||||
var result = system.PreviewScore(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
|
||||
|
||||
Assert.AreEqual(50, result.BaseScore);
|
||||
Assert.AreEqual(15, result.BaseScore);
|
||||
Assert.AreEqual(0, result.FlatBonus);
|
||||
Assert.AreEqual(1f, result.Multiplier);
|
||||
Assert.AreEqual(50, result.FinalScore);
|
||||
Assert.AreEqual(15, result.FinalScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TotalScore_SumsAllScoredCategories()
|
||||
{
|
||||
var system = CreateScoringSystem();
|
||||
system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
|
||||
system.ScoreCategory(new[] { 2, 2, 2, 2, 2 }, YachtCategory.Twos);
|
||||
|
||||
Assert.AreEqual(15, system.TotalScore); // 5 + 10
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResetScorecard_ClearsAll()
|
||||
{
|
||||
var system = CreateScoringSystem();
|
||||
system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
|
||||
|
||||
system.ResetScorecard();
|
||||
|
||||
Assert.AreEqual(0, system.TotalScore);
|
||||
Assert.IsFalse(system.IsCategoryUsed(YachtCategory.Ones));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,15 @@ using UnityEngine;
|
||||
using YachtDice.Economy;
|
||||
using YachtDice.Inventory;
|
||||
using YachtDice.Shop;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
using YachtDice.Modifiers.Runtime;
|
||||
|
||||
namespace YachtDice.Tests
|
||||
{
|
||||
public sealed class ShopModelTests
|
||||
{
|
||||
private CurrencyBank bank;
|
||||
private ModifierRegistry registry;
|
||||
private InventoryModel inventory;
|
||||
private ShopModel shop;
|
||||
|
||||
@@ -20,7 +22,8 @@ namespace YachtDice.Tests
|
||||
bank = go.AddComponent<CurrencyBank>();
|
||||
bank.SetBalance(500);
|
||||
|
||||
inventory = new InventoryModel(5);
|
||||
registry = new ModifierRegistry(5);
|
||||
inventory = new InventoryModel(registry);
|
||||
shop = new ShopModel(bank, inventory);
|
||||
}
|
||||
|
||||
@@ -31,11 +34,19 @@ namespace YachtDice.Tests
|
||||
Object.DestroyImmediate(go.gameObject);
|
||||
}
|
||||
|
||||
private ModifierDefinitionSO CreateDef(string id = "test",
|
||||
bool hasLimitedUses = false, int maxUses = 0,
|
||||
int shopPrice = 100, int sellPrice = 50)
|
||||
{
|
||||
return ModifierDefinitionSO.CreateForTest(id, null,
|
||||
hasLimitedUses: hasLimitedUses, maxUses: maxUses,
|
||||
shopPrice: shopPrice, sellPrice: sellPrice);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryPurchase_SucceedsWithSufficientCurrency()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100);
|
||||
var mod = CreateDef("test", shopPrice: 100);
|
||||
|
||||
bool result = shop.TryPurchase(mod);
|
||||
|
||||
@@ -48,8 +59,7 @@ namespace YachtDice.Tests
|
||||
public void TryPurchase_FailsWhenBroke()
|
||||
{
|
||||
bank.SetBalance(10);
|
||||
var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100);
|
||||
var mod = CreateDef("test", shopPrice: 100);
|
||||
|
||||
bool result = shop.TryPurchase(mod);
|
||||
|
||||
@@ -61,9 +71,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void TryPurchase_PermanentCannotBeBoughtTwice()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("perm", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f,
|
||||
durability: ModifierDurability.Permanent, shopPrice: 100);
|
||||
var mod = CreateDef("perm", shopPrice: 100);
|
||||
|
||||
shop.TryPurchase(mod);
|
||||
bool secondResult = shop.TryPurchase(mod);
|
||||
@@ -76,9 +84,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void TryPurchase_LimitedCanBeReBought()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("limited", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f,
|
||||
durability: ModifierDurability.LimitedUses, maxUses: 3, shopPrice: 100);
|
||||
var mod = CreateDef("limited", hasLimitedUses: true, maxUses: 3, shopPrice: 100);
|
||||
|
||||
shop.TryPurchase(mod);
|
||||
bool secondResult = shop.TryPurchase(mod);
|
||||
@@ -91,11 +97,10 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void TryPurchase_FiresPurchaseEvent()
|
||||
{
|
||||
ModifierData purchased = null;
|
||||
shop.OnItemPurchased += data => purchased = data;
|
||||
ModifierDefinitionSO purchased = null;
|
||||
shop.OnItemPurchased += def => purchased = def;
|
||||
|
||||
var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100);
|
||||
var mod = CreateDef("test", shopPrice: 100);
|
||||
|
||||
shop.TryPurchase(mod);
|
||||
|
||||
@@ -106,8 +111,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void GetItemState_Available_WhenCanAfford()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100);
|
||||
var mod = CreateDef("test", shopPrice: 100);
|
||||
|
||||
Assert.AreEqual(ShopItemState.Available, shop.GetItemState(mod));
|
||||
}
|
||||
@@ -116,8 +120,7 @@ namespace YachtDice.Tests
|
||||
public void GetItemState_TooExpensive_WhenCannotAfford()
|
||||
{
|
||||
bank.SetBalance(10);
|
||||
var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100);
|
||||
var mod = CreateDef("test", shopPrice: 100);
|
||||
|
||||
Assert.AreEqual(ShopItemState.TooExpensive, shop.GetItemState(mod));
|
||||
}
|
||||
@@ -125,9 +128,7 @@ namespace YachtDice.Tests
|
||||
[Test]
|
||||
public void GetItemState_Owned_WhenPermanentPurchased()
|
||||
{
|
||||
var mod = ModifierData.CreateForTest("perm", ModifierScope.SelectedCategory,
|
||||
ModifierEffectType.AddFlatToFinalScore, 10f,
|
||||
durability: ModifierDurability.Permanent, shopPrice: 100);
|
||||
var mod = CreateDef("perm", shopPrice: 100);
|
||||
|
||||
shop.TryPurchase(mod);
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner",
|
||||
"YachtDice.Runtime"
|
||||
"YachtDice.Runtime",
|
||||
"UniTask"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
|
||||
Reference in New Issue
Block a user