// ─────────────────────────────────────────────────────────────────────────────
// PotionButton.cs — 전투 패드 물약 버튼(잔량 + 쿨 링) (WL-813tj · 813j · #813)
//
// 기준서 v1 §D-1 813j 행: "813c 의 빈 자리에 물약 버튼(잔량 · 쿨 링)"
// §B 요소 8: 물약 3개/런 · 40 % 회복 · 쿨 5 s.
//
// ■ 자리 (813c 소유)
// 813c 가 전투 패드 6슬롯 중 4를 스킬로 쓰고 **2를 예약**(물약 · 회피)으로 비활성화했다.
// 이 버튼은 그 예약 좌표(`WLHudLayoutSettings.ReserveSlotPx(index)`)에 **자기 노드**로 앉는다.
// 비활성 SkillCard 를 되살려 쓰지 않는 이유: `WLBattlePadLayout.Apply()` 가 매번 예약 슬롯을
// SetActive(false) 로 되돌리기 때문(813c 실측) — 켜 두면 두 코드가 서로 끈다.
//
// ■ 🔴 데이터는 813i(Gameplay) 가 주인 — 이 파일은 표시만 한다
// `WL.Combat.Survival.PotionUse` 는 **아직 없다**(브랜치 `wl/gameplay/WL-813i-survival` 은
// 2026-09-09 01:4x 기준 main 대비 커밋 0 · `Assets/WL/Combat/Survival/*.cs` 없음 = 실측).
// 존재하지 않는 타입을 참조하면 게이트가 깨지므로, **정적 델리게이트 자리**만 열어 둔다.
// 813i 는 자기 파일에서 아래 4개를 채우면 끝난다(UI 수정 0):
// PotionButton.CountProvider = () => PotionUse.Remain;
// PotionButton.CooldownRemainProvider = () => PotionUse.CooldownRemain;
// PotionButton.CooldownTotalProvider = () => PotionUse.CooldownTotal;
// PotionButton.UseHandler = () => PotionUse.TryUse();
// Provider 가 없으면 설정의 표시용 기본값(3개 · 5 s)을 그리고, 클릭은 무동작이다(오동작 방지).
//
// ■ 값 = WLSurvivalUiSettings.asset + 자리 = WLHudLayoutSettings.asset (둘 다 C45)
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using System.Text;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace WL.UI
{
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public class PotionButton : MonoBehaviour
{
// ── 813i 연결점 (「후속」 · 존재하지 않는 타입을 참조하지 않는다) ─────
/// 남은 물약 개수. 813i 가 채운다.
public static System.Func CountProvider;
/// 남은 쿨(초). 813i 가 채운다.
public static System.Func CooldownRemainProvider;
/// 쿨 전체 길이(초). 813i 가 채운다.
public static System.Func CooldownTotalProvider;
/// 실제 사용. true = 소비 성공. 813i 가 채운다.
public static System.Func UseHandler;
/// 버튼을 눌렀다는 통지(로그·튜토리얼용 · 선택).
public static System.Action UseRequested;
/// 813i 가 연결됐는가(진단·완료보고 근거).
public static bool HasProvider { get { return CountProvider != null || UseHandler != null; } }
[Header("구성 요소 (BuildIfNeeded 가 만든다)")]
[SerializeField] private Image background;
[SerializeField] private Image cooldownRing;
[SerializeField] private TextMeshProUGUI iconLabel;
[SerializeField] private TextMeshProUGUI countLabel;
[SerializeField] private Button button;
[SerializeField] private CanvasGroup group;
[Header("폰트 (에디터가 주입)")]
[SerializeField] private TMP_FontAsset font;
private RectTransform _rt;
private Canvas _canvas;
private int _lastCount = -1;
private float _lastFill = -1f;
// ── 진단 ──────────────────────────────────────────────────────────────
public int ClickCount { get; private set; }
public int UsedCount { get; private set; }
public int BlockedCount { get; private set; }
public int ShownCount { get { return _lastCount; } }
public float ShownFill { get { return _lastFill; } }
private void Awake() { _rt = GetComponent(); }
private void OnEnable() { Initialize(); }
public string Initialize()
{
if (_rt == null) _rt = GetComponent();
BuildIfNeeded();
string layout = ApplyLayout();
Refresh();
return "initialized · " + layout + " · provider=" + HasProvider;
}
// ── 값 읽기 (813i 있으면 그쪽 · 없으면 표시용 기본값) ────────────────
public int ReadCount()
{
if (CountProvider != null) { try { return CountProvider(); } catch { } }
var s = WLSurvivalUiSettings.Instance;
return s != null ? s.potionFallbackCount : 0;
}
public float ReadCooldownFill()
{
var s = WLSurvivalUiSettings.Instance;
float total = 0f, remain = 0f;
if (CooldownTotalProvider != null) { try { total = CooldownTotalProvider(); } catch { } }
if (CooldownRemainProvider != null) { try { remain = CooldownRemainProvider(); } catch { } }
if (total <= 0f) total = s != null ? s.potionFallbackCooldownSeconds : 0f;
if (total <= 0f) return 0f;
return Mathf.Clamp01(remain / total);
}
private void Update() { Refresh(); }
/// 잔량·쿨 링·흐림을 현재 값에 맞춘다(값이 그대로면 아무 것도 하지 않는다).
public string Refresh()
{
var s = WLSurvivalUiSettings.Instance;
if (s == null) return "WLSurvivalUiSettings 에셋 없음";
int count = ReadCount();
float fill = ReadCooldownFill();
bool ready = count > 0 && fill <= 0.0001f;
if (count != _lastCount)
{
_lastCount = count;
if (countLabel != null) countLabel.text = string.Format(s.potionCountFormat, count);
}
if (!Mathf.Approximately(fill, _lastFill))
{
_lastFill = fill;
if (cooldownRing != null)
{
cooldownRing.fillAmount = fill;
if (cooldownRing.enabled != (fill > 0.0001f)) cooldownRing.enabled = fill > 0.0001f;
}
}
var tint = ready ? s.potionReadyColor : s.potionDimColor;
if (iconLabel != null) iconLabel.color = tint;
if (countLabel != null) countLabel.color = tint;
if (group != null) group.alpha = ready ? 1f : 0.75f;
return "count=" + count + " fill=" + fill.ToString("F3") + " ready=" + ready;
}
/// 버튼 클릭. 813i Provider 가 없으면 아무 일도 하지 않는다(설정으로 완화 가능).
public string OnClickPotion()
{
ClickCount++;
var s = WLSurvivalUiSettings.Instance;
if (s == null) return "설정 없음 — 무동작";
if (UseRequested != null) { try { UseRequested(); } catch { } }
if (UseHandler == null)
{
BlockedCount++;
if (s.verboseLog) Debug.Log("[PotionButton] 813i(PotionUse) 미연결 — 표시만 (후속)");
return s.potionRequireProvider ? "813i 미연결 — 무동작(후속)" : "813i 미연결 — 무동작";
}
bool ok = false;
try { ok = UseHandler(); } catch { ok = false; }
if (ok) UsedCount++; else BlockedCount++;
Refresh();
return ok ? "사용 성공" : "사용 불가(잔량 0 또는 쿨)";
}
// ── 구성 · 배치 ───────────────────────────────────────────────────────
public bool BuildIfNeeded()
{
if (_rt == null) _rt = GetComponent();
bool made = false;
if (group == null) { group = GetComponent(); if (group == null) { group = gameObject.AddComponent(); made = true; } }
if (background == null)
{
background = GetComponent();
if (background == null) { background = gameObject.AddComponent(); made = true; }
background.raycastTarget = true;
}
if (button == null)
{
button = GetComponent