diff --git a/Assets/Scripts/Inventory/InventoryController.cs b/Assets/Scripts/Inventory/InventoryController.cs index 02490fa..c0fafa7 100644 --- a/Assets/Scripts/Inventory/InventoryController.cs +++ b/Assets/Scripts/Inventory/InventoryController.cs @@ -1,9 +1,7 @@ using UnityEngine; using VContainer; -using YachtDice.Categories; using YachtDice.Economy; using YachtDice.Modifiers.Runtime; -using YachtDice.Scoring; namespace YachtDice.Inventory { @@ -12,16 +10,14 @@ namespace YachtDice.Inventory [SerializeField] private InventoryView inventoryView; private InventoryModel _model; - private ScoringSystem _scoringSystem; private CurrencyBank _currencyBank; public InventoryModel Model => _model; [Inject] - public void Construct(InventoryModel model, ScoringSystem scoringSystem, CurrencyBank currencyBank) + public void Construct(InventoryModel model, CurrencyBank currencyBank) { this._model = model; - this._scoringSystem = scoringSystem; this._currencyBank = currencyBank; } @@ -32,7 +28,6 @@ namespace YachtDice.Inventory inventoryView.OnSellClicked += HandleSell; _model.OnInventoryChanged += HandleInventoryChanged; - _scoringSystem.OnCategoryConfirmed += HandleCategoryConfirmed; RefreshView(); } @@ -48,9 +43,6 @@ namespace YachtDice.Inventory if (_model != null) _model.OnInventoryChanged -= HandleInventoryChanged; - - if (_scoringSystem != null) - _scoringSystem.OnCategoryConfirmed -= HandleCategoryConfirmed; } public void ToggleVisibility() @@ -89,11 +81,6 @@ namespace YachtDice.Inventory RefreshView(); } - private void HandleCategoryConfirmed(CategoryDefinition category, ScoreResult result) - { - _model.ConsumeUseOnActive(); - } - private void RefreshView() { if (inventoryView != null && _model != null) diff --git a/Assets/Scripts/Modifiers/Core/ModifierContext.cs b/Assets/Scripts/Modifiers/Core/ModifierContext.cs index c7d1827..3e3e3a0 100644 --- a/Assets/Scripts/Modifiers/Core/ModifierContext.cs +++ b/Assets/Scripts/Modifiers/Core/ModifierContext.cs @@ -38,6 +38,7 @@ namespace YachtDice.Modifiers.Core // Trigger info public TriggerType Trigger; + public bool IsPreview; // Side-effect accumulators public int CurrencyDelta; diff --git a/Assets/Scripts/Modifiers/Pipeline/ModifierPipeline.cs b/Assets/Scripts/Modifiers/Pipeline/ModifierPipeline.cs index 6a22c1c..61267c8 100644 --- a/Assets/Scripts/Modifiers/Pipeline/ModifierPipeline.cs +++ b/Assets/Scripts/Modifiers/Pipeline/ModifierPipeline.cs @@ -90,10 +90,13 @@ namespace YachtDice.Modifiers.Pipeline var effects = behavior.Effects; for (int e = 0; e < effects.Count; e++) { - if (effects[e] == null) continue; + var effect = effects[e]; + if (effect == null) continue; + if (context.IsPreview && effect.Phase == ModifierPhase.SideEffect) continue; + _effectBuffer.Add(new EffectEntry { - Effect = effects[e], + Effect = effect, Instance = inst, }); } @@ -113,6 +116,8 @@ namespace YachtDice.Modifiers.Pipeline trace.AddEffectApplied(entry.Instance.Definition.Id, entry.Effect.name, entry.Effect.Phase); } + _registry.RemoveExpired(); + if (trace != null) { string traceStr = trace.ToString(); diff --git a/Assets/Scripts/Modifiers/Runtime/ModifierRegistry.cs b/Assets/Scripts/Modifiers/Runtime/ModifierRegistry.cs index a47d2dc..2ff8207 100644 --- a/Assets/Scripts/Modifiers/Runtime/ModifierRegistry.cs +++ b/Assets/Scripts/Modifiers/Runtime/ModifierRegistry.cs @@ -195,6 +195,36 @@ namespace YachtDice.Modifiers.Runtime OnChanged?.Invoke(); } + public void RemoveExpired() + { + var changed = false; + var activeChanged = false; + + for (var i = _instances.Count - 1; i >= 0; i--) + { + var instance = _instances[i]; + if (!instance.IsExpired) + continue; + + if (instance.IsActive) + activeChanged = true; + + instance.IsActive = false; + _instances.RemoveAt(i); + changed = true; + } + + if (!changed) + return; + + _activeCacheDirty = true; + + if (activeChanged) + OnActiveModifiersChanged?.Invoke(Active); + + OnChanged?.Invoke(); + } + private void RebuildActiveCache() { _activeCache.Clear(); diff --git a/Assets/Scripts/Scoring/ScoringSystem.cs b/Assets/Scripts/Scoring/ScoringSystem.cs index f976326..aba6454 100644 --- a/Assets/Scripts/Scoring/ScoringSystem.cs +++ b/Assets/Scripts/Scoring/ScoringSystem.cs @@ -76,8 +76,9 @@ namespace YachtDice.Scoring baseScore, dice, category, currentRoll, currentTurn, playerCurrency, _modifierRegistry.Active); + context.IsPreview = true; - _eventBus.Fire(TriggerType.OnCategoryScored, context).Forget(); + _eventBus.Fire(TriggerType.OnCategoryScored, context).GetAwaiter().GetResult(); return context.ToScoreResult(); } @@ -143,7 +144,7 @@ namespace YachtDice.Scoring 0, 0, 0, _modifierRegistry.Active); - _eventBus.Fire(TriggerType.OnCategoryScored, context).Forget(); + _eventBus.Fire(TriggerType.OnCategoryScored, context).GetAwaiter().GetResult(); } ScoreResult result; diff --git a/Assets/Scripts/Tests/Editor/LimitedUseModifierFlowTests.cs b/Assets/Scripts/Tests/Editor/LimitedUseModifierFlowTests.cs new file mode 100644 index 0000000..b457fc7 --- /dev/null +++ b/Assets/Scripts/Tests/Editor/LimitedUseModifierFlowTests.cs @@ -0,0 +1,146 @@ +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using YachtDice.Categories; +using YachtDice.Dice; +using YachtDice.Economy; +using YachtDice.Events; +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 LimitedUseModifierFlowTests + { + private readonly List _createdAssets = new(); + + private CategoryDefinition _onesCategory; + private CategoryDefinition _twosCategory; + private CategoryDefinition _chanceCategory; + private DiceDefinition _standardDice; + private CategoryCatalog _catalog; + private CurrencyBank _currencyBank; + private ModifierRegistry _registry; + private ScoringSystem _scoringSystem; + private ModifierInstance _instance; + + [SetUp] + public void SetUp() + { + _standardDice = DiceDefinition.CreateForTest("d6", "d6"); + _onesCategory = SumOfValueCategory.CreateForTest("ones", "Единицы", 1); + _twosCategory = SumOfValueCategory.CreateForTest("twos", "Двойки", 2); + _chanceCategory = SumAllCategory.CreateForTest("chance", "Шанс"); + _catalog = CategoryCatalog.CreateForTest(new List + { + _onesCategory, + _twosCategory, + _chanceCategory, + }); + + _createdAssets.Add(_standardDice); + _createdAssets.Add(_onesCategory); + _createdAssets.Add(_twosCategory); + _createdAssets.Add(_chanceCategory); + _createdAssets.Add(_catalog); + + var bankGo = new GameObject("CurrencyBank"); + _currencyBank = bankGo.AddComponent(); + _currencyBank.SetBalance(100); + + _registry = new ModifierRegistry(5); + var pipeline = new ModifierPipeline(_registry) + { + TracingEnabled = false, + }; + + var eventBus = new GameEventBus(pipeline); + + var scoringGo = new GameObject("ScoringSystem"); + _scoringSystem = scoringGo.AddComponent(); + _scoringSystem.Construct(eventBus, _registry, _catalog, _currencyBank); + + var multiply = AddCreatedAsset(MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative)); + var consume = AddCreatedAsset(ConsumeChargeEffect.CreateForTest(1, ModifierPhase.SideEffect)); + var behavior = AddCreatedAsset(ModifierBehavior.CreateForTest( + TriggerType.OnCategoryScored, + null, + new List { multiply, consume })); + var definition = AddCreatedAsset(ModifierDefinition.CreateForTest( + "mod_multiplier_2x_limited", + new List { behavior }, + hasLimitedUses: true, + maxUses: 3)); + + _instance = _registry.Add(definition); + _registry.TryActivate(_instance); + } + + [TearDown] + public void TearDown() + { + foreach (var scoring in Object.FindObjectsByType(FindObjectsSortMode.None)) + Object.DestroyImmediate(scoring.gameObject); + + foreach (var bank in Object.FindObjectsByType(FindObjectsSortMode.None)) + Object.DestroyImmediate(bank.gameObject); + + for (var i = 0; i < _createdAssets.Count; i++) + { + if (_createdAssets[i] != null) + Object.DestroyImmediate(_createdAssets[i]); + } + + _createdAssets.Clear(); + } + + [Test] + public void PreviewScore_DoesNotConsumeCharges() + { + _scoringSystem.PreviewScore(CreateDice(1, 1, 2, 3, 4), _onesCategory); + _scoringSystem.PreviewScore(CreateDice(2, 2, 2, 3, 4), _twosCategory); + + Assert.AreEqual(3, _instance.RemainingUses); + Assert.AreEqual(1, _registry.All.Count); + Assert.IsTrue(_instance.IsActive); + } + + [Test] + public void ScoreCategory_ConsumesExactlyOneCharge() + { + _scoringSystem.ScoreCategory(CreateDice(1, 1, 2, 3, 4), _onesCategory); + + Assert.AreEqual(2, _instance.RemainingUses); + Assert.AreEqual(1, _registry.All.Count); + } + + [Test] + public void ScoreCategory_RemovesModifier_WhenChargesExhausted() + { + _scoringSystem.ScoreCategory(CreateDice(1, 1, 2, 3, 4), _onesCategory); + _scoringSystem.ScoreCategory(CreateDice(2, 2, 2, 3, 4), _twosCategory); + _scoringSystem.ScoreCategory(CreateDice(1, 2, 3, 4, 5), _chanceCategory); + + Assert.AreEqual(0, _registry.All.Count); + Assert.AreEqual(0, _registry.ActiveCount); + } + + private IReadOnlyList CreateDice(params int[] values) + { + var dice = new DiceInstance[values.Length]; + for (var i = 0; i < values.Length; i++) + dice[i] = new DiceInstance(_standardDice, values[i]); + return dice; + } + + private T AddCreatedAsset(T asset) where T : Object + { + _createdAssets.Add(asset); + return asset; + } + } +} diff --git a/Assets/Scripts/Tests/Editor/LimitedUseModifierFlowTests.cs.meta b/Assets/Scripts/Tests/Editor/LimitedUseModifierFlowTests.cs.meta new file mode 100644 index 0000000..60acfee --- /dev/null +++ b/Assets/Scripts/Tests/Editor/LimitedUseModifierFlowTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0dbdebf1eec04ff9af5ca19c0bc5f9b3