using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Core.Save;
namespace BaseGames.World.Map
{
///
/// 运行时地图管理器(架构 15_MapShopModule §1.2)。
/// 挂在 Persistent 场景 [GameManagers] 下,通过事件驱动记录已探索/已完整地图的房间。
/// 实现 ISaveable 持久化探索进度。
///
[DefaultExecutionOrder(-700)]
public class MapManager : MonoBehaviour, ISaveable, IMapService
{
[SerializeField] private MapDatabaseSO _database;
[Header("Event Channels")]
[SerializeField] private StringEventChannelSO _onRoomEntered; // 订阅 EVT_RoomEntered
[Tooltip("房间被探索/标记时广播(Explored/Mapped);地图 UI 已改用 C# 事件,此通道保留供地图外部系统订阅(如成就、音效)。")]
[SerializeField] private StringEventChannelSO _onMapUpdated; // 发布:房间发现时(地图 UI 内当前无订阅者)
[SerializeField] private StringEventChannelSO _onRegionChanged; // 发布:玩家首次进入新区域时(EVT_RegionChanged)
// 三级可见性:
// Unknown → 未进入过(默认)
// Explored → 进入过(显示轮廓/格子)
// Mapped → 完整地图信息(购买 MapFragment 或存档点揭示)
private HashSet _exploredRooms = new();
private HashSet _mappedRooms = new();
private string _currentRegionId;
private int _totalRoomCount = -1; // -1 = 未缓存;OnLoad 后重置
private Dictionary _regionCache; // R11-N6 GetRoomsByRegion 结果缓存
private bool _isDuplicate; // Awake 检测到重复实例时置位,OnEnable/OnDisable 提前 return
private readonly CompositeDisposable _subs = new();
private void Awake()
{
if (ServiceLocator.GetOrDefault() != null) { _isDuplicate = true; Destroy(gameObject); return; }
ServiceLocator.Register(this);
}
private void OnEnable()
{
if (_isDuplicate) return;
_onRoomEntered?.Subscribe(OnRoomEntered).AddTo(_subs);
ServiceLocator.GetOrDefault()?.Register(this);
}
private void OnDisable()
{
if (_isDuplicate) return;
_subs.Clear();
ServiceLocator.GetOrDefault()?.Unregister(this);
}
// ── ISaveable ─────────────────────────────────────────────────────────
public void OnSave(SaveData data)
{
data.Map.ExploredRooms = new HashSet(_exploredRooms);
data.Map.MappedRooms = new HashSet(_mappedRooms);
data.Map.LastRegionId = _currentRegionId;
}
public void OnLoad(SaveData data)
{
_exploredRooms = data.Map.ExploredRooms != null ? new HashSet(data.Map.ExploredRooms) : new HashSet();
_mappedRooms = data.Map.MappedRooms != null ? new HashSet(data.Map.MappedRooms) : new HashSet();
_currentRegionId = data.Map.LastRegionId; // 恢复区域 ID,避免读档后首次进房误触发 EVT_RegionChanged
_totalRoomCount = -1; // 强制下次调用 GetExplorationProgress 时重新计数
// 读档后广播:UI 仅需轻量刷新(不重建结构);订阅 OnExplorationChanged 的 UI 会 RefreshAllCells
OnExplorationChanged?.Invoke();
}
// ── 事件驱动房间发现 ──────────────────────────────────────────────────
private void OnRoomEntered(string roomId)
{
if (string.IsNullOrEmpty(roomId)) return;
bool changed = _exploredRooms.Add(roomId);
if (changed)
{
_onMapUpdated?.Raise(roomId);
OnExplorationChanged?.Invoke();
}
// 区域变化检测:RegionId 非空且与上一次不同时广播 EVT_RegionChanged
var regionId = _database?.GetRoom(roomId)?.RegionId;
if (!string.IsNullOrEmpty(regionId) && regionId != _currentRegionId)
{
_currentRegionId = regionId;
_onRegionChanged?.Raise(regionId);
}
}
///
/// 标记为已获取地图碎片信息(购买 MapFragment SO 触发)。
/// 仅写入 _mappedRooms;若玩家尚未亲自踏入该房间则显示为灰色轮廓(Mapped),
/// 亲自踏入后(Explored)优先级更高,显示为白色。
///
public void SetMapped(string roomId)
{
if (string.IsNullOrEmpty(roomId)) return;
if (_mappedRooms.Add(roomId))
{
_onMapUpdated?.Raise(roomId);
OnRoomMapped?.Invoke(roomId);
OnExplorationChanged?.Invoke();
}
}
///
public void SetMappedBatch(IEnumerable roomIds)
{
if (roomIds == null) return;
bool anyAdded = false;
foreach (var roomId in roomIds)
{
if (string.IsNullOrEmpty(roomId)) continue;
if (_mappedRooms.Add(roomId))
{
_onMapUpdated?.Raise(roomId);
OnRoomMapped?.Invoke(roomId);
anyAdded = true;
}
}
if (anyAdded) OnExplorationChanged?.Invoke();
}
// ── 查询 API ──────────────────────────────────────────────────────────
public bool IsExplored(string roomId) => _exploredRooms.Contains(roomId);
public bool IsMapped(string roomId) => _mappedRooms.Contains(roomId);
public string CurrentRegionId => _currentRegionId;
public MapDatabaseSO Database => _database;
public int ExploredRoomCount => _exploredRooms.Count;
public float GetExplorationProgress()
{
if (_database?.AllRooms == null || _database.AllRooms.Length == 0) return 0f;
if (_totalRoomCount < 0)
_totalRoomCount = _database.AllRooms.Count(r => r != null);
return _totalRoomCount > 0 ? Mathf.Clamp01((float)_exploredRooms.Count / _totalRoomCount) : 0f;
}
public MapRoomDataSO[] GetRoomsByRegion(string regionId)
{
if (string.IsNullOrEmpty(regionId) || _database?.AllRooms == null)
return System.Array.Empty();
// R11-N6 懒加载缓存,避免每帧 LINQ 扫描全量房间数组
_regionCache ??= new Dictionary();
if (!_regionCache.TryGetValue(regionId, out var cached))
{
cached = _database.AllRooms.Where(r => r != null && r.RegionId == regionId).ToArray();
_regionCache[regionId] = cached;
}
return cached;
}
// ── 数据库热更事件 ────────────────────────────────────────────────────
///
public event Action OnDatabaseChanged;
///
public event Action OnExplorationChanged;
///
public event Action OnRoomMapped;
///
public void NotifyDatabaseChanged()
{
_database?.InvalidateIndex();
_totalRoomCount = -1;
_regionCache = null; // R11-N6 数据库变更时清空区域缓存
OnDatabaseChanged?.Invoke();
}
private void OnDestroy()
{
if (_isDuplicate) return;
ServiceLocator.Unregister(this);
}
}
}