[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,24 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using YachtDice.Modifiers;
|
||||
|
||||
namespace YachtDice.Shop
|
||||
{
|
||||
[CreateAssetMenu(fileName = "ShopCatalog", menuName = "YachtDice/Shop Catalog")]
|
||||
public sealed class ShopCatalog : ScriptableObject
|
||||
{
|
||||
[SerializeField] private List<ModifierData> availableModifiers = new();
|
||||
|
||||
public IReadOnlyList<ModifierData> AvailableModifiers => availableModifiers;
|
||||
|
||||
public ModifierData FindById(string id)
|
||||
{
|
||||
for (int i = 0; i < availableModifiers.Count; i++)
|
||||
{
|
||||
if (availableModifiers[i] != null && availableModifiers[i].Id == id)
|
||||
return availableModifiers[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
using UnityEngine;
|
||||
using YachtDice.Economy;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
|
||||
namespace YachtDice.Shop
|
||||
{
|
||||
public class ShopController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private ShopCatalog catalog;
|
||||
[SerializeField] private ModifierCatalogSO catalog;
|
||||
[SerializeField] private ShopView shopView;
|
||||
[SerializeField] private CurrencyBank currencyBank;
|
||||
|
||||
private ShopModel model;
|
||||
|
||||
public ShopCatalog Catalog => catalog;
|
||||
public ModifierCatalogSO Catalog => catalog;
|
||||
|
||||
public void Initialize(ShopModel shopModel)
|
||||
{
|
||||
@@ -25,7 +25,7 @@ namespace YachtDice.Shop
|
||||
|
||||
model.OnItemPurchased += HandleItemPurchased;
|
||||
|
||||
shopView.Populate(catalog.AvailableModifiers, model);
|
||||
shopView.Populate(catalog.All, model);
|
||||
shopView.UpdateCurrencyDisplay(currencyBank != null ? currencyBank.Balance : 0);
|
||||
}
|
||||
|
||||
@@ -41,20 +41,20 @@ namespace YachtDice.Shop
|
||||
model.OnItemPurchased -= HandleItemPurchased;
|
||||
}
|
||||
|
||||
private void HandleBuyClicked(ModifierData data)
|
||||
private void HandleBuyClicked(ModifierDefinitionSO def)
|
||||
{
|
||||
model.TryPurchase(data);
|
||||
model.TryPurchase(def);
|
||||
}
|
||||
|
||||
private void HandleCurrencyChanged(int newBalance)
|
||||
{
|
||||
shopView.UpdateCurrencyDisplay(newBalance);
|
||||
shopView.RefreshStates(catalog.AvailableModifiers, model);
|
||||
shopView.RefreshStates(catalog.All, model);
|
||||
}
|
||||
|
||||
private void HandleItemPurchased(ModifierData data)
|
||||
private void HandleItemPurchased(ModifierDefinitionSO def)
|
||||
{
|
||||
shopView.RefreshStates(catalog.AvailableModifiers, model);
|
||||
shopView.RefreshStates(catalog.All, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Core;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
|
||||
namespace YachtDice.Shop
|
||||
{
|
||||
@@ -23,9 +24,9 @@ namespace YachtDice.Shop
|
||||
[SerializeField] private Color rareColor = new(0.4f, 0.6f, 1f);
|
||||
[SerializeField] private Color epicColor = new(0.8f, 0.4f, 1f);
|
||||
|
||||
private ModifierData data;
|
||||
private ModifierDefinitionSO data;
|
||||
|
||||
public event Action<ModifierData> OnBuyClicked;
|
||||
public event Action<ModifierDefinitionSO> OnBuyClicked;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -33,9 +34,9 @@ namespace YachtDice.Shop
|
||||
buyButton.onClick.AddListener(() => OnBuyClicked?.Invoke(data));
|
||||
}
|
||||
|
||||
public void Setup(ModifierData modifierData, ShopItemState state)
|
||||
public void Setup(ModifierDefinitionSO modifierDef, ShopItemState state)
|
||||
{
|
||||
data = modifierData;
|
||||
data = modifierDef;
|
||||
|
||||
if (nameText != null) nameText.text = data.DisplayName;
|
||||
if (descriptionText != null) descriptionText.text = data.Description;
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using YachtDice.Economy;
|
||||
using YachtDice.Inventory;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
|
||||
namespace YachtDice.Shop
|
||||
{
|
||||
@@ -12,7 +12,7 @@ namespace YachtDice.Shop
|
||||
private readonly InventoryModel inventoryModel;
|
||||
private readonly HashSet<string> purchasedPermanentIds = new();
|
||||
|
||||
public event Action<ModifierData> OnItemPurchased;
|
||||
public event Action<ModifierDefinitionSO> OnItemPurchased;
|
||||
|
||||
public ShopModel(CurrencyBank currencyBank, InventoryModel inventoryModel)
|
||||
{
|
||||
@@ -20,25 +20,24 @@ namespace YachtDice.Shop
|
||||
this.inventoryModel = inventoryModel;
|
||||
}
|
||||
|
||||
public bool CanPurchase(ModifierData modifier)
|
||||
public bool CanPurchase(ModifierDefinitionSO modifier)
|
||||
{
|
||||
if (modifier == null) return false;
|
||||
if (!currencyBank.CanAfford(modifier.ShopPrice)) return false;
|
||||
|
||||
if (modifier.Durability == ModifierDurability.Permanent &&
|
||||
purchasedPermanentIds.Contains(modifier.Id))
|
||||
if (!modifier.HasLimitedUses && purchasedPermanentIds.Contains(modifier.Id))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryPurchase(ModifierData modifier)
|
||||
public bool TryPurchase(ModifierDefinitionSO modifier)
|
||||
{
|
||||
if (!CanPurchase(modifier)) return false;
|
||||
|
||||
if (!currencyBank.Spend(modifier.ShopPrice)) return false;
|
||||
|
||||
if (modifier.Durability == ModifierDurability.Permanent)
|
||||
if (!modifier.HasLimitedUses)
|
||||
purchasedPermanentIds.Add(modifier.Id);
|
||||
|
||||
inventoryModel.AddModifier(modifier);
|
||||
@@ -48,18 +47,17 @@ namespace YachtDice.Shop
|
||||
|
||||
public bool IsPermanentOwned(string modifierId) => purchasedPermanentIds.Contains(modifierId);
|
||||
|
||||
public ShopItemState GetItemState(ModifierData modifier)
|
||||
public ShopItemState GetItemState(ModifierDefinitionSO modifier)
|
||||
{
|
||||
if (modifier == null) return ShopItemState.TooExpensive;
|
||||
|
||||
if (modifier.Durability == ModifierDurability.Permanent &&
|
||||
purchasedPermanentIds.Contains(modifier.Id))
|
||||
if (!modifier.HasLimitedUses && purchasedPermanentIds.Contains(modifier.Id))
|
||||
return ShopItemState.Owned;
|
||||
|
||||
if (!currencyBank.CanAfford(modifier.ShopPrice))
|
||||
return ShopItemState.TooExpensive;
|
||||
|
||||
return modifier.Durability == ModifierDurability.LimitedUses
|
||||
return modifier.HasLimitedUses
|
||||
? ShopItemState.RepurchaseAvailable
|
||||
: ShopItemState.Available;
|
||||
}
|
||||
@@ -79,6 +77,6 @@ namespace YachtDice.Shop
|
||||
Available,
|
||||
TooExpensive,
|
||||
Owned,
|
||||
RepurchaseAvailable
|
||||
RepurchaseAvailable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using YachtDice.Modifiers;
|
||||
using YachtDice.Modifiers.Definition;
|
||||
|
||||
namespace YachtDice.Shop
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace YachtDice.Shop
|
||||
|
||||
private readonly List<ShopItemView> spawnedItems = new();
|
||||
|
||||
public event Action<ModifierData> OnBuyClicked;
|
||||
public event Action<ModifierDefinitionSO> OnBuyClicked;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -34,24 +34,24 @@ namespace YachtDice.Shop
|
||||
public void Hide() => gameObject.SetActive(false);
|
||||
public bool IsVisible => gameObject.activeSelf;
|
||||
|
||||
public void Populate(IReadOnlyList<ModifierData> catalog, ShopModel model)
|
||||
public void Populate(IReadOnlyList<ModifierDefinitionSO> catalog, ShopModel model)
|
||||
{
|
||||
ClearItems();
|
||||
|
||||
for (int i = 0; i < catalog.Count; i++)
|
||||
{
|
||||
var data = catalog[i];
|
||||
if (data == null) continue;
|
||||
var def = catalog[i];
|
||||
if (def == null) continue;
|
||||
|
||||
var item = Instantiate(itemPrefab, itemContainer);
|
||||
var state = model.GetItemState(data);
|
||||
item.Setup(data, state);
|
||||
var state = model.GetItemState(def);
|
||||
item.Setup(def, state);
|
||||
item.OnBuyClicked += HandleBuy;
|
||||
spawnedItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshStates(IReadOnlyList<ModifierData> catalog, ShopModel model)
|
||||
public void RefreshStates(IReadOnlyList<ModifierDefinitionSO> catalog, ShopModel model)
|
||||
{
|
||||
for (int i = 0; i < spawnedItems.Count && i < catalog.Count; i++)
|
||||
{
|
||||
@@ -76,6 +76,6 @@ namespace YachtDice.Shop
|
||||
spawnedItems.Clear();
|
||||
}
|
||||
|
||||
private void HandleBuy(ModifierData data) => OnBuyClicked?.Invoke(data);
|
||||
private void HandleBuy(ModifierDefinitionSO def) => OnBuyClicked?.Invoke(def);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user