Rework shop to support multiple item types and add unified PlayerModel
Generalize the shop from selling only ModifierDefinition to any IShopItem (modifiers + dice). Add purchase condition checks, hover tooltips, and fixed prices. Introduce PlayerModel as a facade over CurrencyBank, InventoryModel, and DiceCollection for centralized state and save/load. New files: - IShopItem interface for purchasable items - ShopCatalog SO (unified catalog for all item types) - ShopTooltipView (description on hover) - PlayerModel (aggregates player state) - DiceCollection (owned dice tracking) - DiceCatalog SO (dice definition registry) - DiceCollectionTests Modified: - ModifierDefinition/DieDefinitionSO implement IShopItem - ShopModel/ShopView/ShopItemView/ShopController use IShopItem - SaveData v3 with OwnedDiceIds - GameController uses PlayerModel for save/load - GameLifetimeScope registers new services Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using UnityEngine;
|
|||||||
using VContainer;
|
using VContainer;
|
||||||
using VContainer.Unity;
|
using VContainer.Unity;
|
||||||
using YachtDice.Categories;
|
using YachtDice.Categories;
|
||||||
|
using YachtDice.Dice;
|
||||||
using YachtDice.Economy;
|
using YachtDice.Economy;
|
||||||
using YachtDice.Events;
|
using YachtDice.Events;
|
||||||
using YachtDice.Game;
|
using YachtDice.Game;
|
||||||
@@ -9,6 +10,7 @@ using YachtDice.Inventory;
|
|||||||
using YachtDice.Modifiers.Definition;
|
using YachtDice.Modifiers.Definition;
|
||||||
using YachtDice.Modifiers.Pipeline;
|
using YachtDice.Modifiers.Pipeline;
|
||||||
using YachtDice.Modifiers.Runtime;
|
using YachtDice.Modifiers.Runtime;
|
||||||
|
using YachtDice.Player;
|
||||||
using YachtDice.Scoring;
|
using YachtDice.Scoring;
|
||||||
using YachtDice.Shop;
|
using YachtDice.Shop;
|
||||||
using YachtDice.UI;
|
using YachtDice.UI;
|
||||||
@@ -19,6 +21,8 @@ namespace YachtDice.DI
|
|||||||
{
|
{
|
||||||
[SerializeField] private ModifierCatalog modifierCatalog;
|
[SerializeField] private ModifierCatalog modifierCatalog;
|
||||||
[SerializeField] private CategoryCatalog categoryCatalog;
|
[SerializeField] private CategoryCatalog categoryCatalog;
|
||||||
|
[SerializeField] private DiceCatalog diceCatalog;
|
||||||
|
[SerializeField] private ShopCatalog shopCatalog;
|
||||||
|
|
||||||
[Header("Scene References")]
|
[Header("Scene References")]
|
||||||
[SerializeField] private ScoringSystem scoringSystem;
|
[SerializeField] private ScoringSystem scoringSystem;
|
||||||
@@ -37,6 +41,8 @@ namespace YachtDice.DI
|
|||||||
// SO catalogs
|
// SO catalogs
|
||||||
builder.RegisterInstance(modifierCatalog);
|
builder.RegisterInstance(modifierCatalog);
|
||||||
builder.RegisterInstance(categoryCatalog);
|
builder.RegisterInstance(categoryCatalog);
|
||||||
|
builder.RegisterInstance(diceCatalog);
|
||||||
|
builder.RegisterInstance(shopCatalog);
|
||||||
|
|
||||||
// Core modifier services
|
// Core modifier services
|
||||||
builder.Register<ModifierRegistry>(Lifetime.Singleton)
|
builder.Register<ModifierRegistry>(Lifetime.Singleton)
|
||||||
@@ -44,8 +50,14 @@ namespace YachtDice.DI
|
|||||||
builder.Register<ModifierPipeline>(Lifetime.Singleton);
|
builder.Register<ModifierPipeline>(Lifetime.Singleton);
|
||||||
builder.Register<GameEventBus>(Lifetime.Singleton);
|
builder.Register<GameEventBus>(Lifetime.Singleton);
|
||||||
|
|
||||||
// Domain models
|
// Player subsystems
|
||||||
|
builder.Register<DiceCollection>(Lifetime.Singleton);
|
||||||
builder.Register<InventoryModel>(Lifetime.Singleton);
|
builder.Register<InventoryModel>(Lifetime.Singleton);
|
||||||
|
|
||||||
|
// Unified player model
|
||||||
|
builder.Register<PlayerModel>(Lifetime.Singleton);
|
||||||
|
|
||||||
|
// Shop
|
||||||
builder.Register<ShopModel>(Lifetime.Singleton);
|
builder.Register<ShopModel>(Lifetime.Singleton);
|
||||||
|
|
||||||
// Scene MonoBehaviour components
|
// Scene MonoBehaviour components
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace YachtDice.Dice
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(fileName = "DiceCatalog", menuName = "YachtDice/Dice/Catalog")]
|
||||||
|
public class DiceCatalog : ScriptableObject
|
||||||
|
{
|
||||||
|
[SerializeField] private List<DieDefinitionSO> dice = new();
|
||||||
|
|
||||||
|
public IReadOnlyList<DieDefinitionSO> All => dice;
|
||||||
|
|
||||||
|
public DieDefinitionSO FindById(string id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dice.Count; i++)
|
||||||
|
{
|
||||||
|
if (dice[i] != null && dice[i].Id == id)
|
||||||
|
return dice[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
public static DiceCatalog CreateForTest(List<DieDefinitionSO> defs)
|
||||||
|
{
|
||||||
|
var catalog = CreateInstance<DiceCatalog>();
|
||||||
|
catalog.dice = defs ?? new List<DieDefinitionSO>();
|
||||||
|
return catalog;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using YachtDice.Shop;
|
||||||
|
|
||||||
namespace YachtDice.Dice
|
namespace YachtDice.Dice
|
||||||
{
|
{
|
||||||
@@ -6,16 +7,23 @@ namespace YachtDice.Dice
|
|||||||
/// Абстрактное определение типа дайса.
|
/// Абстрактное определение типа дайса.
|
||||||
/// Наследники описывают конкретные виды (стандартный d6, специальные и т.д.).
|
/// Наследники описывают конкретные виды (стандартный d6, специальные и т.д.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class DieDefinitionSO : ScriptableObject
|
public abstract class DieDefinitionSO : ScriptableObject, IShopItem
|
||||||
{
|
{
|
||||||
[Header("Identity")]
|
[Header("Identity")]
|
||||||
[SerializeField] private string id;
|
[SerializeField] private string id;
|
||||||
[SerializeField] private string displayName;
|
[SerializeField] private string displayName;
|
||||||
|
[SerializeField, TextArea] private string description;
|
||||||
[SerializeField] private Sprite icon;
|
[SerializeField] private Sprite icon;
|
||||||
|
|
||||||
|
[Header("Economy")]
|
||||||
|
[SerializeField] private int shopPrice;
|
||||||
|
|
||||||
public string Id => id;
|
public string Id => id;
|
||||||
public string DisplayName => displayName;
|
public string DisplayName => displayName;
|
||||||
|
public string Description => description;
|
||||||
public Sprite Icon => icon;
|
public Sprite Icon => icon;
|
||||||
|
public int ShopPrice => shopPrice;
|
||||||
|
public bool IsRepurchasable => false;
|
||||||
|
|
||||||
/// <summary>Количество граней.</summary>
|
/// <summary>Количество граней.</summary>
|
||||||
public abstract int FaceCount { get; }
|
public abstract int FaceCount { get; }
|
||||||
@@ -24,11 +32,14 @@ namespace YachtDice.Dice
|
|||||||
public abstract int[] GetFaceValues();
|
public abstract int[] GetFaceValues();
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
public static T CreateForTest<T>(string id, string displayName = null) where T : DieDefinitionSO
|
public static T CreateForTest<T>(string id, string displayName = null,
|
||||||
|
int shopPrice = 0, string description = null) where T : DieDefinitionSO
|
||||||
{
|
{
|
||||||
var so = CreateInstance<T>();
|
var so = CreateInstance<T>();
|
||||||
so.id = id;
|
so.id = id;
|
||||||
so.displayName = displayName ?? id;
|
so.displayName = displayName ?? id;
|
||||||
|
so.description = description ?? id;
|
||||||
|
so.shopPrice = shopPrice;
|
||||||
return so;
|
return so;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using YachtDice.Modifiers.Core;
|
using YachtDice.Modifiers.Core;
|
||||||
|
using YachtDice.Shop;
|
||||||
|
|
||||||
namespace YachtDice.Modifiers.Definition
|
namespace YachtDice.Modifiers.Definition
|
||||||
{
|
{
|
||||||
[CreateAssetMenu(fileName = "NewModifier", menuName = "YachtDice/Modifiers/Definition")]
|
[CreateAssetMenu(fileName = "NewModifier", menuName = "YachtDice/Modifiers/Definition")]
|
||||||
public class ModifierDefinition : ScriptableObject
|
public class ModifierDefinition : ScriptableObject, IShopItem
|
||||||
{
|
{
|
||||||
[Header("Identity")]
|
[Header("Identity")]
|
||||||
[SerializeField] private string id;
|
[SerializeField] private string id;
|
||||||
@@ -36,6 +37,7 @@ namespace YachtDice.Modifiers.Definition
|
|||||||
public bool HasLimitedUses => hasLimitedUses;
|
public bool HasLimitedUses => hasLimitedUses;
|
||||||
public int MaxUses => maxUses;
|
public int MaxUses => maxUses;
|
||||||
public int MaxStacks => maxStacks;
|
public int MaxStacks => maxStacks;
|
||||||
|
public bool IsRepurchasable => hasLimitedUses;
|
||||||
public IReadOnlyList<ModifierBehavior> Behaviors => behaviors;
|
public IReadOnlyList<ModifierBehavior> Behaviors => behaviors;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ namespace YachtDice.Persistence
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class SaveData
|
public sealed class SaveData
|
||||||
{
|
{
|
||||||
public int Version = 2;
|
public int Version = 3;
|
||||||
public int Currency;
|
public int Currency;
|
||||||
public List<ModifierSaveEntry> OwnedModifiers = new();
|
public List<ModifierSaveEntry> OwnedModifiers = new();
|
||||||
|
public List<string> OwnedDiceIds = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using YachtDice.Dice;
|
||||||
|
|
||||||
|
namespace YachtDice.Player
|
||||||
|
{
|
||||||
|
public class DiceCollection
|
||||||
|
{
|
||||||
|
private readonly List<DieDefinitionSO> ownedDice = new();
|
||||||
|
|
||||||
|
public event Action OnChanged;
|
||||||
|
|
||||||
|
public IReadOnlyList<DieDefinitionSO> OwnedDice => ownedDice;
|
||||||
|
|
||||||
|
public void Add(DieDefinitionSO definition)
|
||||||
|
{
|
||||||
|
if (definition == null) return;
|
||||||
|
if (OwnsById(definition.Id)) return;
|
||||||
|
|
||||||
|
ownedDice.Add(definition);
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(DieDefinitionSO definition)
|
||||||
|
{
|
||||||
|
if (ownedDice.Remove(definition))
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OwnsById(string id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ownedDice.Count; i++)
|
||||||
|
{
|
||||||
|
if (ownedDice[i] != null && ownedDice[i].Id == id)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetSaveData()
|
||||||
|
{
|
||||||
|
var ids = new List<string>();
|
||||||
|
for (int i = 0; i < ownedDice.Count; i++)
|
||||||
|
ids.Add(ownedDice[i].Id);
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadSaveData(List<string> diceIds, DiceCatalog catalog)
|
||||||
|
{
|
||||||
|
ownedDice.Clear();
|
||||||
|
|
||||||
|
if (diceIds == null)
|
||||||
|
{
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < diceIds.Count; i++)
|
||||||
|
{
|
||||||
|
var def = catalog.FindById(diceIds[i]);
|
||||||
|
if (def != null)
|
||||||
|
ownedDice.Add(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
ownedDice.Clear();
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using YachtDice.Economy;
|
||||||
|
using YachtDice.Inventory;
|
||||||
|
|
||||||
|
namespace YachtDice.Player
|
||||||
|
{
|
||||||
|
public class PlayerModel
|
||||||
|
{
|
||||||
|
public CurrencyBank Currency { get; }
|
||||||
|
public InventoryModel Inventory { get; }
|
||||||
|
public DiceCollection Dice { get; }
|
||||||
|
|
||||||
|
public event Action OnChanged;
|
||||||
|
|
||||||
|
public PlayerModel(
|
||||||
|
CurrencyBank currencyBank,
|
||||||
|
InventoryModel inventory,
|
||||||
|
DiceCollection diceCollection)
|
||||||
|
{
|
||||||
|
Currency = currencyBank;
|
||||||
|
Inventory = inventory;
|
||||||
|
Dice = diceCollection;
|
||||||
|
|
||||||
|
currencyBank.OnBalanceChanged += _ => OnChanged?.Invoke();
|
||||||
|
inventory.OnInventoryChanged += () => OnChanged?.Invoke();
|
||||||
|
diceCollection.OnChanged += () => OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace YachtDice.Shop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Any item that can appear in the shop.
|
||||||
|
/// Implemented by ScriptableObject definitions (ModifierDefinition, DieDefinitionSO).
|
||||||
|
/// </summary>
|
||||||
|
public interface IShopItem
|
||||||
|
{
|
||||||
|
string Id { get; }
|
||||||
|
string DisplayName { get; }
|
||||||
|
string Description { get; }
|
||||||
|
Sprite Icon { get; }
|
||||||
|
int ShopPrice { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this item can be repurchased after being owned (e.g. consumable modifiers).
|
||||||
|
/// If false, the shop marks it as "Owned" once purchased.
|
||||||
|
/// </summary>
|
||||||
|
bool IsRepurchasable { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace YachtDice.Shop
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(fileName = "ShopCatalog", menuName = "YachtDice/Shop/Catalog")]
|
||||||
|
public class ShopCatalog : ScriptableObject
|
||||||
|
{
|
||||||
|
[SerializeField] private List<ScriptableObject> items = new();
|
||||||
|
|
||||||
|
private List<IShopItem> cachedItems;
|
||||||
|
|
||||||
|
public IReadOnlyList<IShopItem> All
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (cachedItems == null) RebuildCache();
|
||||||
|
return cachedItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IShopItem FindById(string id)
|
||||||
|
{
|
||||||
|
var all = All;
|
||||||
|
for (int i = 0; i < all.Count; i++)
|
||||||
|
{
|
||||||
|
if (all[i] != null && all[i].Id == id)
|
||||||
|
return all[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildCache()
|
||||||
|
{
|
||||||
|
cachedItems = new List<IShopItem>();
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
if (items[i] is IShopItem shopItem)
|
||||||
|
cachedItems.Add(shopItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValidate() => cachedItems = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VContainer;
|
using VContainer;
|
||||||
using YachtDice.Economy;
|
using YachtDice.Economy;
|
||||||
using YachtDice.Modifiers.Definition;
|
|
||||||
|
|
||||||
namespace YachtDice.Shop
|
namespace YachtDice.Shop
|
||||||
{
|
{
|
||||||
@@ -9,14 +8,14 @@ namespace YachtDice.Shop
|
|||||||
{
|
{
|
||||||
[SerializeField] private ShopView shopView;
|
[SerializeField] private ShopView shopView;
|
||||||
|
|
||||||
private ModifierCatalog catalog;
|
private ShopCatalog catalog;
|
||||||
private CurrencyBank currencyBank;
|
private CurrencyBank currencyBank;
|
||||||
private ShopModel model;
|
private ShopModel model;
|
||||||
|
|
||||||
public ModifierCatalog Catalog => catalog;
|
public ShopCatalog Catalog => catalog;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public void Construct(ModifierCatalog catalog, CurrencyBank currencyBank, ShopModel model)
|
public void Construct(ShopCatalog catalog, CurrencyBank currencyBank, ShopModel model)
|
||||||
{
|
{
|
||||||
this.catalog = catalog;
|
this.catalog = catalog;
|
||||||
this.currencyBank = currencyBank;
|
this.currencyBank = currencyBank;
|
||||||
@@ -55,9 +54,9 @@ namespace YachtDice.Shop
|
|||||||
shopView.Show();
|
shopView.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleBuyClicked(ModifierDefinition def)
|
private void HandleBuyClicked(IShopItem item)
|
||||||
{
|
{
|
||||||
model.TryPurchase(def);
|
model.TryPurchase(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCurrencyChanged(int newBalance)
|
private void HandleCurrencyChanged(int newBalance)
|
||||||
@@ -66,7 +65,7 @@ namespace YachtDice.Shop
|
|||||||
shopView.RefreshStates(catalog.All, model);
|
shopView.RefreshStates(catalog.All, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleItemPurchased(ModifierDefinition def)
|
private void HandleItemPurchased(IShopItem item)
|
||||||
{
|
{
|
||||||
shopView.RefreshStates(catalog.All, model);
|
shopView.RefreshStates(catalog.All, model);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using YachtDice.Modifiers.Core;
|
using YachtDice.Modifiers.Core;
|
||||||
using YachtDice.Modifiers.Definition;
|
using YachtDice.Modifiers.Definition;
|
||||||
|
|
||||||
namespace YachtDice.Shop
|
namespace YachtDice.Shop
|
||||||
{
|
{
|
||||||
public class ShopItemView : MonoBehaviour
|
public class ShopItemView : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||||
{
|
{
|
||||||
[SerializeField] private Image iconImage;
|
[SerializeField] private Image iconImage;
|
||||||
[SerializeField] private TMP_Text nameText;
|
[SerializeField] private TMP_Text nameText;
|
||||||
@@ -24,9 +25,11 @@ namespace YachtDice.Shop
|
|||||||
[SerializeField] private Color rareColor = new(0.4f, 0.6f, 1f);
|
[SerializeField] private Color rareColor = new(0.4f, 0.6f, 1f);
|
||||||
[SerializeField] private Color epicColor = new(0.8f, 0.4f, 1f);
|
[SerializeField] private Color epicColor = new(0.8f, 0.4f, 1f);
|
||||||
|
|
||||||
private ModifierDefinition data;
|
private IShopItem data;
|
||||||
|
|
||||||
public event Action<ModifierDefinition> OnBuyClicked;
|
public event Action<IShopItem> OnBuyClicked;
|
||||||
|
public event Action<IShopItem, RectTransform> OnHoverEnter;
|
||||||
|
public event Action OnHoverExit;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@@ -34,9 +37,9 @@ namespace YachtDice.Shop
|
|||||||
buyButton.onClick.AddListener(() => OnBuyClicked?.Invoke(data));
|
buyButton.onClick.AddListener(() => OnBuyClicked?.Invoke(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Setup(ModifierDefinition modifierDef, ShopItemState state)
|
public void Setup(IShopItem item, ShopItemState state)
|
||||||
{
|
{
|
||||||
data = modifierDef;
|
data = item;
|
||||||
|
|
||||||
if (nameText != null) nameText.text = data.DisplayName;
|
if (nameText != null) nameText.text = data.DisplayName;
|
||||||
if (descriptionText != null) descriptionText.text = data.Description;
|
if (descriptionText != null) descriptionText.text = data.Description;
|
||||||
@@ -45,8 +48,16 @@ namespace YachtDice.Shop
|
|||||||
|
|
||||||
if (rarityText != null)
|
if (rarityText != null)
|
||||||
{
|
{
|
||||||
rarityText.text = data.Rarity.ToString();
|
if (data is ModifierDefinition mod)
|
||||||
rarityText.color = GetRarityColor(data.Rarity);
|
{
|
||||||
|
rarityText.gameObject.SetActive(true);
|
||||||
|
rarityText.text = mod.Rarity.ToString();
|
||||||
|
rarityText.color = GetRarityColor(mod.Rarity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rarityText.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetState(state);
|
SetState(state);
|
||||||
@@ -77,6 +88,16 @@ namespace YachtDice.Shop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnPointerEnter(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
OnHoverEnter?.Invoke(data, transform as RectTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPointerExit(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
OnHoverExit?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
private Color GetRarityColor(ModifierRarity rarity)
|
private Color GetRarityColor(ModifierRarity rarity)
|
||||||
{
|
{
|
||||||
return rarity switch
|
return rarity switch
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using YachtDice.Dice;
|
||||||
using YachtDice.Economy;
|
using YachtDice.Economy;
|
||||||
using YachtDice.Inventory;
|
using YachtDice.Inventory;
|
||||||
using YachtDice.Modifiers.Definition;
|
using YachtDice.Modifiers.Definition;
|
||||||
|
using YachtDice.Player;
|
||||||
|
|
||||||
namespace YachtDice.Shop
|
namespace YachtDice.Shop
|
||||||
{
|
{
|
||||||
@@ -10,54 +12,65 @@ namespace YachtDice.Shop
|
|||||||
{
|
{
|
||||||
private readonly CurrencyBank currencyBank;
|
private readonly CurrencyBank currencyBank;
|
||||||
private readonly InventoryModel inventoryModel;
|
private readonly InventoryModel inventoryModel;
|
||||||
|
private readonly DiceCollection diceCollection;
|
||||||
private readonly HashSet<string> purchasedPermanentIds = new();
|
private readonly HashSet<string> purchasedPermanentIds = new();
|
||||||
|
|
||||||
public event Action<ModifierDefinition> OnItemPurchased;
|
public event Action<IShopItem> OnItemPurchased;
|
||||||
|
|
||||||
public ShopModel(CurrencyBank currencyBank, InventoryModel inventoryModel)
|
public ShopModel(CurrencyBank currencyBank, InventoryModel inventoryModel, DiceCollection diceCollection)
|
||||||
{
|
{
|
||||||
this.currencyBank = currencyBank;
|
this.currencyBank = currencyBank;
|
||||||
this.inventoryModel = inventoryModel;
|
this.inventoryModel = inventoryModel;
|
||||||
|
this.diceCollection = diceCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanPurchase(ModifierDefinition modifier)
|
public bool CanPurchase(IShopItem item)
|
||||||
{
|
{
|
||||||
if (modifier == null) return false;
|
if (item == null) return false;
|
||||||
if (!currencyBank.CanAfford(modifier.ShopPrice)) return false;
|
if (!currencyBank.CanAfford(item.ShopPrice)) return false;
|
||||||
|
|
||||||
if (!modifier.HasLimitedUses && purchasedPermanentIds.Contains(modifier.Id))
|
if (!item.IsRepurchasable && purchasedPermanentIds.Contains(item.Id))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryPurchase(ModifierDefinition modifier)
|
public bool TryPurchase(IShopItem item)
|
||||||
{
|
{
|
||||||
if (!CanPurchase(modifier)) return false;
|
if (!CanPurchase(item)) return false;
|
||||||
|
|
||||||
if (!currencyBank.Spend(modifier.ShopPrice)) return false;
|
if (!currencyBank.Spend(item.ShopPrice)) return false;
|
||||||
|
|
||||||
if (!modifier.HasLimitedUses)
|
if (!item.IsRepurchasable)
|
||||||
purchasedPermanentIds.Add(modifier.Id);
|
purchasedPermanentIds.Add(item.Id);
|
||||||
|
|
||||||
inventoryModel.AddModifier(modifier);
|
switch (item)
|
||||||
OnItemPurchased?.Invoke(modifier);
|
{
|
||||||
|
case ModifierDefinition modifier:
|
||||||
|
inventoryModel.AddModifier(modifier);
|
||||||
|
break;
|
||||||
|
case DieDefinitionSO die:
|
||||||
|
diceCollection.Add(die);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnItemPurchased?.Invoke(item);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPermanentOwned(string modifierId) => purchasedPermanentIds.Contains(modifierId);
|
public bool IsPermanentOwned(string itemId) => purchasedPermanentIds.Contains(itemId);
|
||||||
|
|
||||||
public ShopItemState GetItemState(ModifierDefinition modifier)
|
public ShopItemState GetItemState(IShopItem item)
|
||||||
{
|
{
|
||||||
if (modifier == null) return ShopItemState.TooExpensive;
|
if (item == null) return ShopItemState.TooExpensive;
|
||||||
|
|
||||||
if (!modifier.HasLimitedUses && purchasedPermanentIds.Contains(modifier.Id))
|
if (!item.IsRepurchasable && purchasedPermanentIds.Contains(item.Id))
|
||||||
return ShopItemState.Owned;
|
return ShopItemState.Owned;
|
||||||
|
|
||||||
if (!currencyBank.CanAfford(modifier.ShopPrice))
|
if (!currencyBank.CanAfford(item.ShopPrice))
|
||||||
return ShopItemState.TooExpensive;
|
return ShopItemState.TooExpensive;
|
||||||
|
|
||||||
return modifier.HasLimitedUses
|
return item.IsRepurchasable
|
||||||
? ShopItemState.RepurchaseAvailable
|
? ShopItemState.RepurchaseAvailable
|
||||||
: ShopItemState.Available;
|
: ShopItemState.Available;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace YachtDice.Shop
|
||||||
|
{
|
||||||
|
public class ShopTooltipView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private TMP_Text titleText;
|
||||||
|
[SerializeField] private TMP_Text descriptionText;
|
||||||
|
[SerializeField] private TMP_Text priceText;
|
||||||
|
[SerializeField] private RectTransform panelRect;
|
||||||
|
[SerializeField] private Vector2 offset = new(10f, -10f);
|
||||||
|
|
||||||
|
public void Show(IShopItem item, RectTransform anchor)
|
||||||
|
{
|
||||||
|
if (titleText != null) titleText.text = item.DisplayName;
|
||||||
|
if (descriptionText != null) descriptionText.text = item.Description;
|
||||||
|
if (priceText != null) priceText.text = $"Price: {item.ShopPrice}";
|
||||||
|
|
||||||
|
PositionNear(anchor);
|
||||||
|
gameObject.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PositionNear(RectTransform anchor)
|
||||||
|
{
|
||||||
|
if (panelRect == null || anchor == null) return;
|
||||||
|
|
||||||
|
Vector3 worldPos = anchor.position;
|
||||||
|
panelRect.position = worldPos + (Vector3)offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using YachtDice.Modifiers.Definition;
|
|
||||||
|
|
||||||
namespace YachtDice.Shop
|
namespace YachtDice.Shop
|
||||||
{
|
{
|
||||||
@@ -13,10 +12,11 @@ namespace YachtDice.Shop
|
|||||||
[SerializeField] private ShopItemView itemPrefab;
|
[SerializeField] private ShopItemView itemPrefab;
|
||||||
[SerializeField] private TMP_Text currencyText;
|
[SerializeField] private TMP_Text currencyText;
|
||||||
[SerializeField] private Button closeButton;
|
[SerializeField] private Button closeButton;
|
||||||
|
[SerializeField] private ShopTooltipView tooltipView;
|
||||||
|
|
||||||
private readonly List<ShopItemView> spawnedItems = new();
|
private readonly List<ShopItemView> spawnedItems = new();
|
||||||
|
|
||||||
public event Action<ModifierDefinition> OnBuyClicked;
|
public event Action<IShopItem> OnBuyClicked;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,7 @@ namespace YachtDice.Shop
|
|||||||
public void Hide() => gameObject.SetActive(false);
|
public void Hide() => gameObject.SetActive(false);
|
||||||
public bool IsVisible => gameObject.activeSelf;
|
public bool IsVisible => gameObject.activeSelf;
|
||||||
|
|
||||||
public void Populate(IReadOnlyList<ModifierDefinition> catalog, ShopModel model)
|
public void Populate(IReadOnlyList<IShopItem> catalog, ShopModel model)
|
||||||
{
|
{
|
||||||
ClearItems();
|
ClearItems();
|
||||||
|
|
||||||
@@ -47,11 +47,13 @@ namespace YachtDice.Shop
|
|||||||
var state = model.GetItemState(def);
|
var state = model.GetItemState(def);
|
||||||
item.Setup(def, state);
|
item.Setup(def, state);
|
||||||
item.OnBuyClicked += HandleBuy;
|
item.OnBuyClicked += HandleBuy;
|
||||||
|
item.OnHoverEnter += HandleHoverEnter;
|
||||||
|
item.OnHoverExit += HandleHoverExit;
|
||||||
spawnedItems.Add(item);
|
spawnedItems.Add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshStates(IReadOnlyList<ModifierDefinition> catalog, ShopModel model)
|
public void RefreshStates(IReadOnlyList<IShopItem> catalog, ShopModel model)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < spawnedItems.Count && i < catalog.Count; i++)
|
for (int i = 0; i < spawnedItems.Count && i < catalog.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -71,11 +73,25 @@ namespace YachtDice.Shop
|
|||||||
for (int i = 0; i < spawnedItems.Count; i++)
|
for (int i = 0; i < spawnedItems.Count; i++)
|
||||||
{
|
{
|
||||||
spawnedItems[i].OnBuyClicked -= HandleBuy;
|
spawnedItems[i].OnBuyClicked -= HandleBuy;
|
||||||
|
spawnedItems[i].OnHoverEnter -= HandleHoverEnter;
|
||||||
|
spawnedItems[i].OnHoverExit -= HandleHoverExit;
|
||||||
Destroy(spawnedItems[i].gameObject);
|
Destroy(spawnedItems[i].gameObject);
|
||||||
}
|
}
|
||||||
spawnedItems.Clear();
|
spawnedItems.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleBuy(ModifierDefinition def) => OnBuyClicked?.Invoke(def);
|
private void HandleBuy(IShopItem item) => OnBuyClicked?.Invoke(item);
|
||||||
|
|
||||||
|
private void HandleHoverEnter(IShopItem item, RectTransform anchor)
|
||||||
|
{
|
||||||
|
if (tooltipView != null)
|
||||||
|
tooltipView.Show(item, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleHoverExit()
|
||||||
|
{
|
||||||
|
if (tooltipView != null)
|
||||||
|
tooltipView.Hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using YachtDice.Dice;
|
||||||
|
using YachtDice.Player;
|
||||||
|
|
||||||
|
namespace YachtDice.Tests
|
||||||
|
{
|
||||||
|
public sealed class DiceCollectionTests
|
||||||
|
{
|
||||||
|
private DiceCollection collection;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
collection = new DiceCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Add_IncreasesCount()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
|
||||||
|
collection.Add(die);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, collection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Add_DuplicateId_Ignored()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
|
||||||
|
collection.Add(die);
|
||||||
|
collection.Add(die);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, collection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Add_Null_Ignored()
|
||||||
|
{
|
||||||
|
collection.Add(null);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, collection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OwnsById_ReturnsTrueWhenOwned()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
|
||||||
|
collection.Add(die);
|
||||||
|
|
||||||
|
Assert.IsTrue(collection.OwnsById("standard_d6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OwnsById_ReturnsFalseWhenNotOwned()
|
||||||
|
{
|
||||||
|
Assert.IsFalse(collection.OwnsById("standard_d6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Remove_DecreasesCount()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
|
||||||
|
collection.Add(die);
|
||||||
|
collection.Remove(die);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, collection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetSaveData_ReturnsIds()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
|
||||||
|
collection.Add(die);
|
||||||
|
|
||||||
|
var ids = collection.GetSaveData();
|
||||||
|
Assert.AreEqual(1, ids.Count);
|
||||||
|
Assert.AreEqual("standard_d6", ids[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void LoadSaveData_RestoresDice()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
var catalog = DiceCatalog.CreateForTest(new List<DieDefinitionSO> { die });
|
||||||
|
var ids = new List<string> { "standard_d6" };
|
||||||
|
|
||||||
|
collection.LoadSaveData(ids, catalog);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, collection.OwnedDice.Count);
|
||||||
|
Assert.AreEqual("standard_d6", collection.OwnedDice[0].Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void LoadSaveData_SkipsMissingIds()
|
||||||
|
{
|
||||||
|
var catalog = DiceCatalog.CreateForTest(new List<DieDefinitionSO>());
|
||||||
|
var ids = new List<string> { "nonexistent" };
|
||||||
|
|
||||||
|
collection.LoadSaveData(ids, catalog);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, collection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Clear_RemovesAll()
|
||||||
|
{
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
|
||||||
|
collection.Add(die);
|
||||||
|
collection.Clear();
|
||||||
|
|
||||||
|
Assert.AreEqual(0, collection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Add_FiresOnChanged()
|
||||||
|
{
|
||||||
|
bool fired = false;
|
||||||
|
collection.OnChanged += () => fired = true;
|
||||||
|
|
||||||
|
var die = StandardDieSO.CreateStandardD6ForTest();
|
||||||
|
collection.Add(die);
|
||||||
|
|
||||||
|
Assert.IsTrue(fired);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,5 +89,22 @@ namespace YachtDice.Tests
|
|||||||
Assert.IsNotNull(loaded);
|
Assert.IsNotNull(loaded);
|
||||||
Assert.AreEqual(0, loaded.Currency);
|
Assert.AreEqual(0, loaded.Currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SaveAndLoad_RoundTrip_PreservesDiceIds()
|
||||||
|
{
|
||||||
|
var data = new SaveData
|
||||||
|
{
|
||||||
|
Currency = 100,
|
||||||
|
OwnedDiceIds = new List<string> { "standard_d6", "chaos_d6" }
|
||||||
|
};
|
||||||
|
|
||||||
|
SaveSystem.Save(data);
|
||||||
|
var loaded = SaveSystem.Load();
|
||||||
|
|
||||||
|
Assert.AreEqual(2, loaded.OwnedDiceIds.Count);
|
||||||
|
Assert.AreEqual("standard_d6", loaded.OwnedDiceIds[0]);
|
||||||
|
Assert.AreEqual("chaos_d6", loaded.OwnedDiceIds[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using YachtDice.Dice;
|
||||||
using YachtDice.Economy;
|
using YachtDice.Economy;
|
||||||
using YachtDice.Inventory;
|
using YachtDice.Inventory;
|
||||||
|
using YachtDice.Player;
|
||||||
using YachtDice.Shop;
|
using YachtDice.Shop;
|
||||||
using YachtDice.Modifiers.Definition;
|
using YachtDice.Modifiers.Definition;
|
||||||
using YachtDice.Modifiers.Runtime;
|
using YachtDice.Modifiers.Runtime;
|
||||||
@@ -13,6 +15,7 @@ namespace YachtDice.Tests
|
|||||||
private CurrencyBank bank;
|
private CurrencyBank bank;
|
||||||
private ModifierRegistry registry;
|
private ModifierRegistry registry;
|
||||||
private InventoryModel inventory;
|
private InventoryModel inventory;
|
||||||
|
private DiceCollection diceCollection;
|
||||||
private ShopModel shop;
|
private ShopModel shop;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@@ -24,7 +27,8 @@ namespace YachtDice.Tests
|
|||||||
|
|
||||||
registry = new ModifierRegistry(5);
|
registry = new ModifierRegistry(5);
|
||||||
inventory = new InventoryModel(registry);
|
inventory = new InventoryModel(registry);
|
||||||
shop = new ShopModel(bank, inventory);
|
diceCollection = new DiceCollection();
|
||||||
|
shop = new ShopModel(bank, inventory, diceCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
@@ -97,8 +101,8 @@ namespace YachtDice.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TryPurchase_FiresPurchaseEvent()
|
public void TryPurchase_FiresPurchaseEvent()
|
||||||
{
|
{
|
||||||
ModifierDefinition purchased = null;
|
IShopItem purchased = null;
|
||||||
shop.OnItemPurchased += def => purchased = def;
|
shop.OnItemPurchased += item => purchased = item;
|
||||||
|
|
||||||
var mod = CreateDef("test", shopPrice: 100);
|
var mod = CreateDef("test", shopPrice: 100);
|
||||||
|
|
||||||
@@ -134,5 +138,40 @@ namespace YachtDice.Tests
|
|||||||
|
|
||||||
Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(mod));
|
Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TryPurchase_DieItem_AddsToDiceCollection()
|
||||||
|
{
|
||||||
|
var die = DieDefinitionSO.CreateForTest<StandardDieSO>("test_die", shopPrice: 100);
|
||||||
|
|
||||||
|
bool result = shop.TryPurchase(die);
|
||||||
|
|
||||||
|
Assert.IsTrue(result);
|
||||||
|
Assert.AreEqual(400, bank.Balance);
|
||||||
|
Assert.AreEqual(1, diceCollection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TryPurchase_DieItem_CannotBeBoughtTwice()
|
||||||
|
{
|
||||||
|
var die = DieDefinitionSO.CreateForTest<StandardDieSO>("unique_die", shopPrice: 100);
|
||||||
|
|
||||||
|
shop.TryPurchase(die);
|
||||||
|
bool secondResult = shop.TryPurchase(die);
|
||||||
|
|
||||||
|
Assert.IsFalse(secondResult);
|
||||||
|
Assert.AreEqual(400, bank.Balance);
|
||||||
|
Assert.AreEqual(1, diceCollection.OwnedDice.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetItemState_Die_Owned_AfterPurchase()
|
||||||
|
{
|
||||||
|
var die = DieDefinitionSO.CreateForTest<StandardDieSO>("die1", shopPrice: 50);
|
||||||
|
|
||||||
|
shop.TryPurchase(die);
|
||||||
|
|
||||||
|
Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(die));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using YachtDice.Economy;
|
|||||||
using YachtDice.Shop;
|
using YachtDice.Shop;
|
||||||
using YachtDice.Inventory;
|
using YachtDice.Inventory;
|
||||||
using YachtDice.Persistence;
|
using YachtDice.Persistence;
|
||||||
|
using YachtDice.Player;
|
||||||
using YachtDice.Modifiers.Definition;
|
using YachtDice.Modifiers.Definition;
|
||||||
using YachtDice.Modifiers.Runtime;
|
using YachtDice.Modifiers.Runtime;
|
||||||
|
|
||||||
@@ -36,7 +37,9 @@ namespace YachtDice.UI
|
|||||||
private ModifierRegistry modifierRegistry;
|
private ModifierRegistry modifierRegistry;
|
||||||
private CategoryCatalog categoryCatalog;
|
private CategoryCatalog categoryCatalog;
|
||||||
private ModifierCatalog modifierCatalog;
|
private ModifierCatalog modifierCatalog;
|
||||||
|
private DiceCatalog diceCatalog;
|
||||||
private ShopModel shopModel;
|
private ShopModel shopModel;
|
||||||
|
private PlayerModel playerModel;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public void Construct(
|
public void Construct(
|
||||||
@@ -49,7 +52,9 @@ namespace YachtDice.UI
|
|||||||
ModifierRegistry modifierRegistry,
|
ModifierRegistry modifierRegistry,
|
||||||
CategoryCatalog categoryCatalog,
|
CategoryCatalog categoryCatalog,
|
||||||
ModifierCatalog modifierCatalog,
|
ModifierCatalog modifierCatalog,
|
||||||
ShopModel shopModel)
|
DiceCatalog diceCatalog,
|
||||||
|
ShopModel shopModel,
|
||||||
|
PlayerModel playerModel)
|
||||||
{
|
{
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
this.scoringSystem = scoringSystem;
|
this.scoringSystem = scoringSystem;
|
||||||
@@ -60,7 +65,9 @@ namespace YachtDice.UI
|
|||||||
this.modifierRegistry = modifierRegistry;
|
this.modifierRegistry = modifierRegistry;
|
||||||
this.categoryCatalog = categoryCatalog;
|
this.categoryCatalog = categoryCatalog;
|
||||||
this.modifierCatalog = modifierCatalog;
|
this.modifierCatalog = modifierCatalog;
|
||||||
|
this.diceCatalog = diceCatalog;
|
||||||
this.shopModel = shopModel;
|
this.shopModel = shopModel;
|
||||||
|
this.playerModel = playerModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Lifecycle ──────────────────────────────────────────────
|
// ── Lifecycle ──────────────────────────────────────────────
|
||||||
@@ -82,9 +89,9 @@ namespace YachtDice.UI
|
|||||||
gameInfoView.OnShopClicked += HandleShopClicked;
|
gameInfoView.OnShopClicked += HandleShopClicked;
|
||||||
gameInfoView.OnInventoryClicked += HandleInventoryClicked;
|
gameInfoView.OnInventoryClicked += HandleInventoryClicked;
|
||||||
|
|
||||||
// Currency & Modifiers
|
// Currency & Player state
|
||||||
currencyBank.OnBalanceChanged += HandleCurrencyChanged;
|
currencyBank.OnBalanceChanged += HandleCurrencyChanged;
|
||||||
modifierRegistry.OnChanged += HandleInventoryChangedForSave;
|
playerModel.OnChanged += HandlePlayerChangedForSave;
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
scoreCardView.Initialize(categoryCatalog);
|
scoreCardView.Initialize(categoryCatalog);
|
||||||
@@ -112,8 +119,8 @@ namespace YachtDice.UI
|
|||||||
|
|
||||||
currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
|
currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
|
||||||
|
|
||||||
if (modifierRegistry != null)
|
if (playerModel != null)
|
||||||
modifierRegistry.OnChanged -= HandleInventoryChangedForSave;
|
playerModel.OnChanged -= HandlePlayerChangedForSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Save / Load ─────────────────────────────────────────
|
// ── Save / Load ─────────────────────────────────────────
|
||||||
@@ -157,13 +164,25 @@ namespace YachtDice.UI
|
|||||||
modifierRegistry.LoadSaveData(entries, modifierCatalog);
|
modifierRegistry.LoadSaveData(entries, modifierCatalog);
|
||||||
shopModel.LoadPurchasedPermanentIds(permanentIds);
|
shopModel.LoadPurchasedPermanentIds(permanentIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (diceCatalog != null && save.OwnedDiceIds != null && save.OwnedDiceIds.Count > 0)
|
||||||
|
{
|
||||||
|
playerModel.Dice.LoadSaveData(save.OwnedDiceIds, diceCatalog);
|
||||||
|
|
||||||
|
var dicePermIds = new HashSet<string>(save.OwnedDiceIds);
|
||||||
|
var existingIds = shopModel.GetPurchasedPermanentIds();
|
||||||
|
foreach (var id in dicePermIds)
|
||||||
|
existingIds.Add(id);
|
||||||
|
shopModel.LoadPurchasedPermanentIds(existingIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PerformSave()
|
private void PerformSave()
|
||||||
{
|
{
|
||||||
var save = new SaveData
|
var save = new SaveData
|
||||||
{
|
{
|
||||||
Currency = currencyBank.Balance
|
Currency = currencyBank.Balance,
|
||||||
|
OwnedDiceIds = playerModel.Dice.GetSaveData(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var entries = modifierRegistry.GetSaveData();
|
var entries = modifierRegistry.GetSaveData();
|
||||||
@@ -274,10 +293,9 @@ namespace YachtDice.UI
|
|||||||
private void HandleCurrencyChanged(int newBalance)
|
private void HandleCurrencyChanged(int newBalance)
|
||||||
{
|
{
|
||||||
gameInfoView.SetCurrencyText(newBalance);
|
gameInfoView.SetCurrencyText(newBalance);
|
||||||
PerformSave();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleInventoryChangedForSave()
|
private void HandlePlayerChangedForSave()
|
||||||
{
|
{
|
||||||
PerformSave();
|
PerformSave();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user