using System;
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Core.Save;
namespace BaseGames.Inventory
{
///
/// 运行时背包管理器。
/// 挂在 Persistent 场景 [Services] 下,事件驱动记录玩家持有的道具,
/// 实现 持久化。设计对照
/// 与 的服务注册 / 存档模式。
///
[DefaultExecutionOrder(-700)]
public class InventoryManager : MonoBehaviour, ISaveable, IInventoryService
{
[SerializeField] private ItemDatabaseSO _database;
[Header("Event Channels - Listen")]
[Tooltip("道具拾取(itemId)。Collectible 拾取 Item 类型时广播 EVT_ItemPickup。")]
[SerializeField] private StringEventChannelSO _onItemPickup;
[Header("Event Channels - Raise")]
[Tooltip("道具首次获得时广播(itemId),供 HUD Toast / 地图碎片揭示等订阅。")]
[SerializeField] private StringEventChannelSO _onItemAcquired;
[Tooltip("背包内容变化时广播(无负载),供 UI 轻量刷新。")]
[SerializeField] private VoidEventChannelSO _onInventoryChanged;
[Tooltip("地图碎片道具获得时广播揭示区域(regionId)。对应 EVT_RevealRegion。")]
[SerializeField] private StringEventChannelSO _onRevealRegion;
private readonly Dictionary _counts = new();
private readonly HashSet _newItems = new();
private readonly List _view = new();
private bool _viewDirty = true;
private bool _isDuplicate;
private readonly CompositeDisposable _subs = new();
public event Action OnInventoryChanged;
// ── 生命周期 ──────────────────────────────────────────────────────────
private void Awake()
{
if (ServiceLocator.GetOrDefault() != null) { _isDuplicate = true; Destroy(gameObject); return; }
ServiceLocator.Register(this);
Debug.Assert(_database != null, "[InventoryManager] _database 未赋值,请在 Inspector 中指定 ItemDatabaseSO。", this);
}
private void OnEnable()
{
if (_isDuplicate) return;
_onItemPickup?.Subscribe(OnItemPickup).AddTo(_subs);
ServiceLocator.GetOrDefault()?.Register(this);
}
private void OnDisable()
{
if (_isDuplicate) return;
_subs.Clear();
ServiceLocator.GetOrDefault()?.Unregister(this);
}
private void OnDestroy()
{
if (_isDuplicate) return;
ServiceLocator.Unregister(this);
}
// ── 事件驱动拾取 ──────────────────────────────────────────────────────
private void OnItemPickup(string itemId) => AddItem(itemId, 1);
// ── IInventoryService ─────────────────────────────────────────────────
public IReadOnlyList Items
{
get { RebuildViewIfDirty(); return _view; }
}
public int DistinctCount => _counts.Count;
public int GetCount(string itemId)
=> !string.IsNullOrEmpty(itemId) && _counts.TryGetValue(itemId, out var c) ? c : 0;
public bool Has(string itemId) => GetCount(itemId) > 0;
public int AddItem(string itemId, int amount = 1)
{
if (string.IsNullOrEmpty(itemId) || amount <= 0) return 0;
var item = _database != null ? _database.Find(itemId) : null;
if (item == null) { Debug.LogWarning($"[InventoryManager] 找不到道具: {itemId}", this); return 0; }
bool firstTime = !_counts.ContainsKey(itemId);
int current = firstTime ? 0 : _counts[itemId];
int target;
if (!item.stackable)
{
if (current >= 1) return 0; // 非叠加道具已持有,忽略
target = 1;
}
else
{
target = current + amount;
if (item.maxStack > 0) target = Mathf.Min(target, item.maxStack);
}
int added = target - current;
if (added <= 0) return 0;
_counts[itemId] = target;
_newItems.Add(itemId);
_viewDirty = true;
if (firstTime)
{
_onItemAcquired?.Raise(itemId);
// 地图碎片:揭示对应区域
if (item.category == ItemCategory.MapShard && !string.IsNullOrEmpty(item.revealRegionId))
_onRevealRegion?.Raise(item.revealRegionId);
}
RaiseChanged();
return added;
}
public int RemoveItem(string itemId, int amount = 1)
{
if (string.IsNullOrEmpty(itemId) || amount <= 0) return 0;
if (!_counts.TryGetValue(itemId, out var current) || current <= 0) return 0;
int removed = Mathf.Min(current, amount);
int target = current - removed;
if (target <= 0)
{
_counts.Remove(itemId);
_newItems.Remove(itemId);
}
else
{
_counts[itemId] = target;
}
_viewDirty = true;
RaiseChanged();
return removed;
}
public void MarkSeen(string itemId)
{
if (string.IsNullOrEmpty(itemId)) return;
if (_newItems.Remove(itemId))
{
_viewDirty = true;
RaiseChanged();
}
}
// ── 内部 ──────────────────────────────────────────────────────────────
private void RaiseChanged()
{
_onInventoryChanged?.Raise();
OnInventoryChanged?.Invoke();
}
private void RebuildViewIfDirty()
{
if (!_viewDirty) return;
_view.Clear();
foreach (var kv in _counts)
{
var item = _database != null ? _database.Find(kv.Key) : null;
if (item == null) continue;
_view.Add(new InventoryEntry(item, kv.Value, _newItems.Contains(kv.Key)));
}
// 按分类枚举顺序、再按本地化名/ID 稳定排序,供 UI 分组展示
_view.Sort((a, b) =>
{
int c = a.Item.category.CompareTo(b.Item.category);
return c != 0 ? c : string.CompareOrdinal(a.Item.itemId, b.Item.itemId);
});
_viewDirty = false;
}
// ── ISaveable ─────────────────────────────────────────────────────────
public void OnSave(SaveData data)
{
data.Inventory.ItemCounts.Clear();
foreach (var kv in _counts) data.Inventory.ItemCounts[kv.Key] = kv.Value;
data.Inventory.NewItemIds.Clear();
data.Inventory.NewItemIds.AddRange(_newItems);
}
public void OnLoad(SaveData data)
{
_counts.Clear();
_newItems.Clear();
if (data.Inventory?.ItemCounts != null)
foreach (var kv in data.Inventory.ItemCounts)
if (!string.IsNullOrEmpty(kv.Key) && kv.Value > 0)
_counts[kv.Key] = kv.Value;
if (data.Inventory?.NewItemIds != null)
foreach (var id in data.Inventory.NewItemIds)
if (_counts.ContainsKey(id)) _newItems.Add(id);
_viewDirty = true;
RaiseChanged();
}
}
}