using System.Collections.Generic; using System.Linq; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Core.Save; namespace BaseGames.World.Shop { /// /// 商店流程控制器(架构 15_MapShopModule §2.3)。 /// 管理库存可见性、购买逻辑、补货策略,并通过 ISaveable 持久化购买记录。 /// public class ShopController : MonoBehaviour, ISaveable { [SerializeField] private ShopInventorySO _inventory; [SerializeField] private ShopPanel _shopPanel; // UI 面板(P4 UI 模块实现) [Header("Event Channels(广播)")] [SerializeField] private StringEventChannelSO _onShopOpen; // EVT_ShopOpened(shopId) [SerializeField] private VoidEventChannelSO _onShopClosed; // EVT_ShopClosed [SerializeField] private ShopPurchaseEventChannelSO _onItemPurchased; // EVT_ItemPurchased [Header("Event Channels(订阅补货触发)")] [SerializeField] private StringEventChannelSO _onBossDefeated; // EVT_BossDefeated [SerializeField] private VoidEventChannelSO _onSavePointActivated; // EVT_SavePointActivated // key = itemId,value = 已购次数 private Dictionary _purchaseCounts = new(); private HashSet _soldUniqueItems = new(); private List _availableItemsCache; private bool _isDirty = true; private readonly CompositeDisposable _subs = new(); private void OnEnable() { if (_inventory == null) return; if (_inventory.RestockPolicy == RestockPolicy.OnBossDefeat) _onBossDefeated?.Subscribe(OnBossDefeated).AddTo(_subs); if (_inventory.RestockPolicy == RestockPolicy.OnSavePoint) _onSavePointActivated?.Subscribe(OnSavePointActivated).AddTo(_subs); ServiceLocator.GetOrDefault()?.Register(this); } private void OnDisable() { _subs.Clear(); ServiceLocator.GetOrDefault()?.Unregister(this); } private void OnBossDefeated(string _) => Restock(); private void OnSavePointActivated() => Restock(); // ── 公共 API ────────────────────────────────────────────────────────── public void Open() { _shopPanel?.Show(GetAvailableItems(), this); _onShopOpen?.Raise(_inventory.ShopId); } public void Close() { _shopPanel?.Hide(); _onShopClosed?.Raise(); } /// 返回当前可购买的商品列表(过滤已售唯一品及超限商品)。结果在库存变更时才重建。 public List GetAvailableItems() { if (!_isDirty) return _availableItemsCache; if (_inventory?.DefaultInventory == null) { _availableItemsCache = new List(); _isDirty = false; return _availableItemsCache; } _availableItemsCache = _inventory.DefaultInventory .Where(item => item != null && !_soldUniqueItems.Contains(item.ItemId) && (item.MaxPurchaseCount < 0 || GetPurchaseCount(item.ItemId) < item.MaxPurchaseCount)) .Take(_inventory.MaxDisplaySlots) .ToList(); _isDirty = false; return _availableItemsCache; } /// /// 按 RestockPolicy 补货:重置非唯一商品的购买次数(已售唯一品不恢复)。 /// public void Restock() { if (_inventory?.DefaultInventory == null) return; var nonUniqueIds = _inventory.DefaultInventory .Where(i => i != null && !i.IsUnique) .Select(i => i.ItemId); foreach (var id in nonUniqueIds) _purchaseCounts.Remove(id); _isDirty = true; } /// /// 尝试购买商品。由 ShopPanel 的购买按钮调用。 /// /// 购买成功返回 true;资金不足或商品不可购返回 false。 public bool TryPurchase(ShopItemSO item, int playerGeo) { if (item == null) return false; int effectivePrice = GetEffectivePrice(item); if (playerGeo < effectivePrice) return false; if (_soldUniqueItems.Contains(item.ItemId)) return false; if (item.MaxPurchaseCount >= 0 && GetPurchaseCount(item.ItemId) >= item.MaxPurchaseCount) return false; // 通过事件频道扣 Geo(PlayerStats 监听 EVT_ItemPurchased) _onItemPurchased?.Raise(new ShopPurchaseEvent { ItemId = item.ItemId, Price = effectivePrice, }); // 更新库存状态 _purchaseCounts[item.ItemId] = GetPurchaseCount(item.ItemId) + 1; if (item.IsUnique) _soldUniqueItems.Add(item.ItemId); _isDirty = true; return true; } /// /// 返回应用难度乘数后的实际价格。UI 层可通过此方法显示正确标价。 /// public int GetEffectivePrice(ShopItemSO item) { var scaler = ServiceLocator.GetOrDefault()?.CurrentScaler; if (scaler == null) return item.BasePrice; return Mathf.Max(1, Mathf.RoundToInt(item.BasePrice * scaler.ShopPriceMultiplier)); } private int GetPurchaseCount(string id) => _purchaseCounts.TryGetValue(id, out var c) ? c : 0; // ── ISaveable ───────────────────────────────────────────────────────── public void OnSave(SaveData data) { if (_inventory == null) return; if (!data.Shops.ShopRecords.ContainsKey(_inventory.ShopId)) data.Shops.ShopRecords[_inventory.ShopId] = new ShopRecord(); var record = data.Shops.ShopRecords[_inventory.ShopId]; record.SoldUniqueItems = _soldUniqueItems.ToList(); record.PurchaseCounts = new Dictionary(_purchaseCounts); } public void OnLoad(SaveData data) { if (_inventory == null) return; if (data.Shops.ShopRecords.TryGetValue(_inventory.ShopId, out var record)) { _soldUniqueItems = new HashSet(record.SoldUniqueItems ?? new List()); _purchaseCounts = record.PurchaseCounts ?? new Dictionary(); _isDirty = true; } } } // ─── ShopPanel ────────────────────────────────────────────────────────── /// /// 商店 UI 面板基类。 /// ShopController 通过此接口调用面板显示/隐藏,已解耦具体 UI 实现。 /// public class ShopPanel : MonoBehaviour { public virtual void Show(List items, ShopController controller) { } public virtual void Hide() { } } }