using UnityEngine;
using UnityEngine.Serialization;
using TMPro;
using System.Collections.Generic;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Localization;
namespace BaseGames.World.Map
{
///
/// 探索进度百分比 UI 组件。
/// 显示全局探索进度和当前区域探索进度,随 OnExplorationChanged 实时更新。
/// 挂在 HUD 或地图面板下;需配置两个 TMP_Text 字段和可选的 StringEventChannelSO。
///
public class MapProgressDisplay : MonoBehaviour
{
[Header("文本组件")]
[Tooltip("全局探索进度文本,例如 '已探索 42%'。留空则不更新。")]
[SerializeField] private TMP_Text _globalProgressText;
[Tooltip("当前区域进度文本,例如 '区域:67%'。留空则不更新。")]
[SerializeField] private TMP_Text _regionProgressText;
[Header("格式字符串({0:P0} = 百分比,{1} = 区域显示名)")]
[SerializeField] private string _globalFormat = "已探索 {0:P0}";
[SerializeField] private string _regionFormat = "{1}:{0:P0}";
[Header("区域名映射(与 RegionNameDisplay 共用同一机制)")]
[Tooltip("RegionId → 本地化显示名映射。未配置时直接显示 RegionId。")]
[FormerlySerializedAs("_regionNameEntries")]
[SerializeField] private RegionNameEntry[] _regionNames;
[Header("Event Channels")]
[Tooltip("区域切换事件;订阅后区域切换时自动刷新区域进度。")]
[SerializeField] private StringEventChannelSO _onRegionChanged;
private IMapService _mapSvc;
private string _currentRegionId;
private readonly CompositeDisposable _subs = new();
// R15-N2 RegionId → Entry 字典,将 ResolveRegionDisplayName 从 O(N) 降至 O(1)
private Dictionary _regionDict;
private void Awake()
{
BuildRegionDict();
}
private void OnValidate() => BuildRegionDict();
private void OnEnable()
{
_mapSvc = ServiceLocator.GetOrDefault();
if (_mapSvc != null)
{
_mapSvc.OnExplorationChanged += Refresh;
_currentRegionId = _mapSvc.CurrentRegionId;
}
_onRegionChanged?.Subscribe(OnRegionChanged).AddTo(_subs);
Refresh();
}
private void OnDisable()
{
if (_mapSvc != null)
{
_mapSvc.OnExplorationChanged -= Refresh;
_mapSvc = null;
}
_subs.Clear();
}
private void OnRegionChanged(string regionId)
{
_currentRegionId = regionId;
Refresh();
}
/// 立即刷新进度文本。可由外部调用(例如地图面板打开时)。
public void Refresh()
{
if (_mapSvc == null)
_mapSvc = ServiceLocator.GetOrDefault();
if (_mapSvc == null) return;
// 全局探索进度
if (_globalProgressText != null)
{
float progress = _mapSvc.GetExplorationProgress();
try
{
_globalProgressText.text = string.Format(ResolveFormat(_globalFormat, "{0:P0}"), progress);
}
catch (System.FormatException)
{
_globalProgressText.text = $"{progress:P0}";
Debug.LogWarning($"[MapProgressDisplay] _globalFormat 格式字符串配置有误:'{_globalFormat}',已回退到默认显示。", this);
}
}
// 当前区域进度
if (_regionProgressText != null && !string.IsNullOrEmpty(_currentRegionId))
{
var rooms = _mapSvc.GetRoomsByRegion(_currentRegionId);
if (rooms != null && rooms.Length > 0)
{
int exploredCount = 0;
foreach (var r in rooms)
if (r != null && _mapSvc.IsExplored(r.RoomId)) exploredCount++;
float regionProgress = (float)exploredCount / rooms.Length;
// R15-N2 解析区域显示名(本地化 Key → DisplayName → RegionId 回退)
string regionDisplayName = ResolveRegionDisplayName(_currentRegionId);
try
{
_regionProgressText.text = string.Format(ResolveFormat(_regionFormat, "{1} {0:P0}"), regionProgress, regionDisplayName);
}
catch (System.FormatException)
{
_regionProgressText.text = $"{regionDisplayName}:{regionProgress:P0}";
Debug.LogWarning($"[MapProgressDisplay] _regionFormat 格式字符串配置有误:'{_regionFormat}',已回退到默认显示。", this);
}
}
}
}
// ── 辅助方法 ──────────────────────────────────────────────────────────
///
/// 将格式串字段解析为本地化格式串,并保证始终含 {0} 占位符:
/// ① 字段填本地化 Key(如 "MAP_PROGRESS_GLOBAL")→ 返回本地化值(如 "已探索 {0:P0}");
/// ② 字段直接是格式串 → Get 原样返回(向后兼容);
/// ③ 本地化服务缺失 / Key 未命中(返回值无 {0})→ 回退到 ,
/// 确保即使本地化未就绪也不丢失百分比数值。
///
private static string ResolveFormat(string keyOrFormat, string fallback)
{
if (string.IsNullOrEmpty(keyOrFormat)) return fallback;
string s = LocalizationManager.Get(keyOrFormat, LocalizationTable.UI);
// 检测 "{0"(兼容 "{0}"、"{0:P0}"、"{0:F1}" 等格式说明符);缺占位符视为未解析,用回退
return (!string.IsNullOrEmpty(s) && s.Contains("{0")) ? s : fallback;
}
/// 预建 RegionId → Entry 字典,O(1) 查询。
private void BuildRegionDict()
=> _regionDict = MapServiceExtensions.BuildRegionDict(_regionNames);
///
/// 将 regionId 解析为玩家可读的显示名。
/// 优先读 LocKey(本地化),其次 DisplayName,最后回退到 regionId 本身。
///
private string ResolveRegionDisplayName(string regionId)
=> MapServiceExtensions.ResolveRegionDisplayName(_regionDict, regionId);
}
}