using System;
using System.Collections;
using System.Collections.Generic;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Skills;
using BrewMonster.UI;
using UnityEngine;
using UnityEngine.UI;
using BrewMonster.Managers;
using BrewMonster.Network;
namespace BrewMonster
{
///
/// Debug skill trigger panel.
/// 调试技能触发面板。
///
/// Populates a UI GridLayoutGroup with one button per usable skill on the host player.
/// Clicking a button fires the skill locally: animation + VFX only, no network, no cooldown, no resource check.
/// 用主角可用技能填充 UI GridLayoutGroup,每个技能一个按钮。
/// 点击按钮本地触发技能:仅播放动画 + 特效,无网络、无冷却、无资源消耗。
///
/// Scene setup:
/// 1. Assign Player (CECHostPlayer reference).
/// 2. Assign Target Marker (a Transform ~5 units in front of the player — used for projectile/fly-VFX skills).
/// 3. Assign Skill Grid Container (RectTransform with GridLayoutGroup).
/// 4. Assign Skill Button Prefab (GameObject with a Button root, child Image for icon, child Text for label).
/// 5. Enter Play Mode; the grid is populated automatically. Call Refresh() again after role/weapon changes.
///
public class SkillTriggerPanel : MonoBehaviour
{
///
/// Sentinel entity ID used to represent the draggable target marker in the GFX system.
/// 用于在特效系统中表示可拖动目标标记的实体 ID。
///
/// Local id for debug NPC marker; encoded with for GFX lookups.
public const int DEBUG_TARGET_ID = 1200;
/// Encode a local index as a server-style NPC id (high bit set).
public static int EncodeDebugNpcId(int localId = DEBUG_TARGET_ID) =>
unchecked((int)(0x80000000u | (uint)localId));
public static SkillTriggerPanel Instance { get; private set; }
[Header("References")]
[Tooltip("The host player to read skills from and trigger cast on.")]
[SerializeField] private CECHostPlayer player;
[Tooltip("World-space Transform used as target for projectile (non-self) skills. Place a Sphere ~5 units in front of the player.")]
[SerializeField] public CECMonsterTest targetMarker;
[Tooltip("RectTransform that has a GridLayoutGroup component. Buttons are instantiated here.")]
[SerializeField] private RectTransform skillGridContainer;
[Tooltip("Prefab with: root Button, child Image (icon), child Text (skill name/ID).")]
[SerializeField] private GameObject skillButtonPrefab;
[Header("Filter")]
[Tooltip("When enabled, hides skills that fail weapon/form/move-env restrictions. Disable to show ALL positive skills regardless of restrictions.")]
[SerializeField] private bool filterByRestrictions = false;
///
/// World position of the draggable target marker, read by CECSkillGfxMan.get_pos_by_id.
/// 可拖动目标标记的世界坐标,由 CECSkillGfxMan.get_pos_by_id 读取。
///
public static Vector3 DebugTargetPosition =>
Instance != null && Instance.targetMarker != null
? Instance.targetMarker.transform.position
: Vector3.zero;
private void Awake()
{
Instance = this;
}
private void OnDestroy()
{
if (Instance == this)
Instance = null;
}
private void Start()
{
RegisterSceneHostWithGameRun();
StartCoroutine(RefreshWhenReady());
}
///
/// Animation test scene: in-scene is not created via .
/// GFX get_pos_by_id resolves host through → .
///
private void RegisterSceneHostWithGameRun()
{
if (player == null)
return;
var gameRun = EC_Game.GetGameRun();
if (gameRun == null)
return;
gameRun.RegisterAnimSceneHostPlayer(player);
}
/// Target id for / GFX composer (self = host cid, else marker NPC id).
private int ResolveLocalCastTargetId(CECSkill skill)
{
if (skill.GetTargetType() == 0)
return player.GetCharacterID();
if (targetMarker != null)
return targetMarker.GetNPCID();
return EncodeDebugNpcId();
}
///
/// Polls every 0.5 s until the host player has skills and CECGameUIMan is initialized,
/// then calls Refresh() once. If no server skill data arrives within
/// seconds, injects all config skills directly from SkillStub.map so the panel works offline.
///
/// 每 0.5 秒轮询,直到主角已有技能且 CECGameUIMan 初始化完毕,再调用一次 Refresh()。
/// 若 k_InjectFallbackSecs 秒内未收到服务端技能数据,则直接从 SkillStub.map 注入所有配置技能,以便离线使用。
///
private const float k_InjectFallbackSecs = 2f;
private IEnumerator RefreshWhenReady()
{
var wait = new WaitForSeconds(0.5f);
float elapsed = 0f;
// Wait for UI atlas first — required for icons regardless of skill source.
// 先等待 UI 图集加载完毕,图标渲染必须。
while (CECUIManager.Instance?.GetInGameUIMan() == null)
{
elapsed += 0.5f;
yield return wait;
}
// Wait for skills, but fall back to config injection after timeout.
// 等待技能数据,超时后回退到配置注入。
while (player != null && player.GetPositiveSkillNum() == 0)
{
if (elapsed >= k_InjectFallbackSecs)
{
Debug.Log("[SkillTriggerPanel] No server skills after " +
$"{k_InjectFallbackSecs}s — injecting all config skills for offline debug.");
player.InjectDebugSkillsFromConfig();
break;
}
elapsed += 0.5f;
yield return wait;
}
Refresh();
}
///
/// Loads every skill defined in SkillStub.map (config) directly into the player's skill list,
/// bypassing the server skill message. Use this when testing locally without a server.
/// 将 SkillStub.map(配置文件)中所有技能直接注入玩家技能列表,绕过服务端消息。
/// 适用于无服务端的本地测试场景。
///
[ContextMenu("Debug: Inject All Config Skills")]
public void InjectConfigSkills()
{
if (player == null)
{
Debug.LogWarning("[SkillTriggerPanel] player is not assigned.");
return;
}
player.InjectDebugSkillsFromConfig();
Refresh();
}
///
/// Clears and repopulates the skill grid from the host player's current positive skill list.
/// Call again after equipping a different weapon or changing shape/form.
/// Right-click this component in the Inspector to call Refresh manually.
/// 从主角当前正向技能列表清空并重新填充技能格。
/// 在更换武器或变身后再次调用;右键此组件可手动调用。
///
[ContextMenu("Refresh")]
public void Refresh()
{
if (player == null)
{
Debug.LogWarning("[SkillTriggerPanel] player is not assigned.");
return;
}
if (skillGridContainer == null)
{
Debug.LogWarning("[SkillTriggerPanel] skillGridContainer is not assigned.");
return;
}
if (skillButtonPrefab == null)
{
Debug.LogWarning("[SkillTriggerPanel] skillButtonPrefab is not assigned.");
return;
}
// Clear existing buttons / 清空已有按钮
foreach (Transform child in skillGridContainer)
Destroy(child.gameObject);
int total = player.GetPositiveSkillNum();
if (total == 0)
{
Debug.LogWarning("[SkillTriggerPanel] GetPositiveSkillNum() = 0. " +
"Skills are loaded via OnMsgHstSkillData (server message). " +
"Make sure the host player has received skill data before calling Refresh.");
return;
}
CECGameUIMan uiMan = CECUIManager.Instance?.GetInGameUIMan();
if (uiMan == null)
{
Debug.LogWarning("[SkillTriggerPanel] CECGameUIMan not ready — icon atlas not loaded. " +
"Call Refresh() after the game UI finishes initializing.");
return;
}
int added = 0;
int filtered = 0;
for (int i = 0; i < total; i++)
{
CECSkill skill = player.GetPositiveSkillByIndex(i);
if (skill == null)
continue;
if (filterByRestrictions && !IsSkillUsable(skill))
{
filtered++;
continue;
}
GameObject cell = Instantiate(skillButtonPrefab, skillGridContainer);
// --- Icon ---
string iconName = skill.GetIconFile();
if (!string.IsNullOrEmpty(iconName))
{
Sprite icon = uiMan.GetIcon(iconName, EC_GAMEUI_ICONS.ICONS_SKILL);
Image cellImage = GetIconImage(cell);
Debug.Log("[SkillTriggerPanel] icon = " + (icon == null) + "cellImage = " + (cellImage == null));
if (cellImage != null && icon != null)
cellImage.sprite = icon;
}
else{
Debug.LogWarning("[SkillTriggerPanel] skill.GetIconFile() is empty for skill " + skill.GetSkillID());
}
// --- Label ---
Text cellText = cell.GetComponentInChildren();
if (cellText != null)
{
string skillName = skill.GetName();
cellText.text = string.IsNullOrEmpty(skillName)
? $"[{skill.GetSkillID()}]\nLv{skill.GetSkillLevel()}"
: $"{skillName}\nLv{skill.GetSkillLevel()}";
}
// --- Button ---
CECSkill captured = skill;
Button btn = cell.GetComponentInChildren