Project_WL/Assets/WL/Island/WLBossToken.cs

225 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// WLBossToken.cs — 보스 처치 재화 「보스 증표」 + 땅 확장 가격판 재화 훅 (#825 · 2026-10-09)
//
// PD 2026-10-09: 「보스를 잡으면 땅이 열리는 게 아니라, 보스를 잡아서 얻는 재화로 열릴 수 있는 형태면 충분」
// - 지급: 던전 보스 스테이지 클리어(StageClearResult.isBoss && win) → WLIslandSettings.bossTokenPerBossClear 개
// (PlayerPrefs 저장 · 세이브 초기화(WLIslandStart.Prepare) 때 0)
// - 소비: FI 확장 가격판(Purchaser)이 WLLandCurrency 를 통해 잔액을 읽고 깎는다
// (expansionCurrency 1 = 증표 · 0 = 코인 원본 그대로) — Purchaser.cs 훅 4줄(🔴 WL 훅 (#825) 표시)
// - 가격: expansionBossTokenPrices[열린 땅 수 − expansionBaseUnlockedCount] (표 밖은 마지막 값)
// - 던전 해금 「골드 납품 발판」(WLGoldPlatform)은 같은 Purchaser 를 재사용하므로 코인 그대로(부모 감지)
// - 표시: WLHudBinder 의 특수 재화 칸(specialItemId 0 이면) + 가격판 글자 색·「증표」 접두(WLBossPricePad)
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using UnityEngine;
using WL.Combat.Stage;
using FIIslandManager = CryingSnow.FarmingIsland.IslandManager;
using FIPurchaser = CryingSnow.FarmingIsland.Purchaser;
namespace WL.Island
{
public static class WLBossToken
{
public static event System.Action Changed;
public static int Granted, Spent;
public static string LastLog = "";
static string Key(WLIslandSettings cfg) { return cfg != null && !string.IsNullOrEmpty(cfg.bossTokenPrefsKey) ? cfg.bossTokenPrefsKey : "WL_Island_BossToken"; }
public static string Name { get { var cfg = WLIslandSettings.Instance; return cfg != null && !string.IsNullOrEmpty(cfg.bossTokenName) ? cfg.bossTokenName : "보스 증표"; } }
public static int Get() { return PlayerPrefs.GetInt(Key(WLIslandSettings.Instance), 0); }
public static void Set(int v)
{
PlayerPrefs.SetInt(Key(WLIslandSettings.Instance), Mathf.Max(0, v)); PlayerPrefs.Save();
if (Changed != null) Changed();
}
public static void Add(int n) { if (n <= 0) return; Set(Get() + n); Granted += n; }
public static bool TrySpend(int n)
{
if (n <= 0) return true;
int cur = Get(); if (cur < n) return false;
Set(cur - n); Spent += n; return true;
}
public static void Reset() { Set(0); LastLog = Name + " 0 (세이브 초기화)"; }
/// <summary>던전 스테이지 클리어 훅(WLIslandBridge.OnStageCleared) — 보스 스테이지 승리만 센다.</summary>
public static void OnStageCleared(in StageClearResult r, WLIslandSettings cfg)
{
if (cfg == null || !r.win || !r.isBoss) return;
int n = cfg.bossTokenPerBossClear;
if (n <= 0) return;
Add(n);
LastLog = "보스 처치 — " + Name + " +" + n + " (보유 " + Get() + ")";
WLIslandBridge.Log(cfg, LastLog);
}
}
/// <summary>FI Purchaser 훅 — 가격판이 쓰는 재화(코인 / 보스 증표)와 가격.</summary>
public static class WLLandCurrency
{
static readonly HashSet<int> s_bossPads = new HashSet<int>(); // 증표 모드로 바뀐 Purchaser(instanceID)
public static int PadsConverted;
public static string LastLog = "";
static float s_lastShortLogAt = -99f;
public static bool BossMode(WLIslandSettings cfg) { return cfg != null && cfg.enabled_ != 0 && cfg.expansionCurrency == 1; }
/// <summary>표 가격(열린 땅 수 기준). 증표 모드가 아니면 -1.</summary>
/// <summary>#850 — 아직 안 연 땅부터 count 개를 여는 데 드는 증표 합(표 밖은 마지막 값). 증표 모드가 아니면 0.</summary>
public static int CostForExpansions(WLIslandSettings cfg, int count)
{
if (!BossMode(cfg) || count <= 0) return 0;
int sum = 0;
for (int i = 0; i < count; i++) sum += Mathf.Max(0, TablePrice(cfg, cfg.expansionBaseUnlockedCount + i));
return sum;
}
public static int TablePrice(WLIslandSettings cfg, int unlocked)
{
if (!BossMode(cfg)) return -1;
int idx = Mathf.Max(0, unlocked - cfg.expansionBaseUnlockedCount);
if (cfg.expansionBossTokenPrices == null || cfg.expansionBossTokenPrices.Length == 0) return 1;
return Mathf.Max(0, cfg.expansionBossTokenPrices[Mathf.Min(idx, cfg.expansionBossTokenPrices.Length - 1)]);
}
/// <summary>Purchaser.Init 훅 — 증표 모드면 표 가격으로 치환하고 그 판을 증표 판으로 기억한다. 던전 골드 발판은 코인 그대로.</summary>
public static int PriceFor(FIPurchaser p, int fiPrice)
{
var cfg = WLIslandSettings.Instance;
if (p == null || !BossMode(cfg)) return fiPrice;
if (p.GetComponentInParent<WLGoldPlatform>() != null) return fiPrice; // 던전 해금 발판(WLGoldPlatform) = 코인
var mgr = FIIslandManager.Instance;
int unlocked = mgr != null && mgr.UnlockedIslands != null ? mgr.UnlockedIslands.Count : 0;
int price = TablePrice(cfg, unlocked);
s_bossPads.Add(p.GetInstanceID());
if (p.GetComponent<WLBossPricePad>() == null) p.gameObject.AddComponent<WLBossPricePad>();
PadsConverted++;
LastLog = "확장 가격판 → " + WLBossToken.Name + " " + price + " (열린 땅 " + unlocked + " · 코인가 " + fiPrice + " 대체)";
WLIslandBridge.Log(cfg, LastLog);
return price;
}
public static bool IsBossPad(FIPurchaser p) { return p != null && s_bossPads.Contains(p.GetInstanceID()); }
/// <summary>Purchaser 구매 루프 훅 — 지불 가능한 잔액.</summary>
public static int Balance(FIPurchaser p)
{
if (!IsBossPad(p)) { var mgr = FIIslandManager.Instance; return mgr != null ? mgr.Coin : 0; }
int bal = WLBossToken.Get();
if (bal <= 0 && Time.unscaledTime - s_lastShortLogAt > 3f)
{
s_lastShortLogAt = Time.unscaledTime;
WLIslandBridge.Log(WLIslandSettings.Instance, WLBossToken.Name + " 부족 — 던전 보스를 처치하면 얻는다");
}
return bal;
}
/// <summary>Purchaser 구매 루프 훅 — 지불.</summary>
public static void Spend(FIPurchaser p, int n)
{
if (n <= 0) return;
if (!IsBossPad(p)) { var mgr = FIIslandManager.Instance; if (mgr != null) mgr.Coin -= n; return; }
WLBossToken.TrySpend(n);
}
}
/// <summary>
/// 증표 가격판 표식 — 가격 글자를 증표 색으로 물들이고 「증표」 접두를 붙인다(FI 는 숫자만 쓴다).
/// FI 가격판 폰트에는 한글이 없어 □□ 로 보였다(2026-10-09 캡처 실측) → 장비 UI 와 같은 한글 폰트로 바꾼다. 폰트를 못 찾으면 숫자만 둔다.
/// </summary>
public sealed class WLBossPricePad : MonoBehaviour
{
TMPro.TMP_Text _text; string _last; bool _fontTried; TMPro.TMP_FontAsset _kr;
void LateUpdate()
{
if (_text == null) { _text = GetComponentInChildren<TMPro.TMP_Text>(true); if (_text == null) return; }
if (!_fontTried)
{
_fontTried = true;
try { _kr = WL.Equip.WLEquipUi.ResolveKoreanFont(WL.Equip.WLEquipSettings.Instance); } catch (System.Exception) { _kr = null; }
if (_kr != null) _text.font = _kr;
}
var cfg = WLIslandSettings.Instance;
if (cfg != null) _text.color = cfg.bossTokenPriceColor;
string cur = _text.text;
if (cur == _last) return;
if (_kr != null && !cur.StartsWith("증표")) _text.text = "증표 " + cur;
_last = _text.text;
}
}
/// <summary>섬 화면 좌상단(FI 코인 HUD 아래) 「보스 증표 N」 라벨 — 섬에는 WL 재화 HUD 가 없어 따로 띄운다(2026-10-09 캡처 실측).</summary>
public sealed class WLBossTokenHud : MonoBehaviour
{
public static WLBossTokenHud Instance { get; private set; }
TMPro.TextMeshProUGUI _text; int _last = -1; float _t;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
if (Instance != null) return;
var cfg = WLIslandSettings.Instance;
if (cfg == null || cfg.enabled_ == 0 || cfg.bossTokenPerBossClear <= 0) return;
var go = new GameObject("WL_BossTokenHud", typeof(RectTransform));
go.layer = 5;
DontDestroyOnLoad(go);
Instance = go.AddComponent<WLBossTokenHud>();
}
void Start()
{
var canvas = gameObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 50;
var scaler = gameObject.AddComponent<UnityEngine.UI.CanvasScaler>();
scaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1080f, 1920f); scaler.matchWidthOrHeight = 0.5f;
var box = new GameObject("Box", typeof(RectTransform), typeof(UnityEngine.UI.Image)); box.layer = 5; box.transform.SetParent(transform, false);
var brt = (RectTransform)box.transform;
brt.anchorMin = brt.anchorMax = new Vector2(0f, 1f); brt.pivot = new Vector2(0f, 1f);
brt.anchoredPosition = new Vector2(470f, -40f); brt.sizeDelta = new Vector2(330f, 72f); // FI 「+1m coins」 오른쪽(좌상단 지도 버튼과 겹치지 않게 · 2차 캡처 실측)
var img = box.GetComponent<UnityEngine.UI.Image>(); img.color = new Color(0f, 0f, 0f, 0.45f); img.raycastTarget = false;
var t = new GameObject("Text", typeof(RectTransform)); t.layer = 5; t.transform.SetParent(box.transform, false);
_text = t.AddComponent<TMPro.TextMeshProUGUI>();
var trt = _text.rectTransform; trt.anchorMin = Vector2.zero; trt.anchorMax = Vector2.one; trt.offsetMin = new Vector2(16f, 4f); trt.offsetMax = new Vector2(-12f, -4f);
TryKoreanFont();
_text.fontSize = 32f; _text.alignment = TMPro.TextAlignmentOptions.MidlineLeft; _text.raycastTarget = false; _text.fontStyle = TMPro.FontStyles.Bold;
var cfg = WLIslandSettings.Instance;
_text.color = cfg != null ? cfg.bossTokenPriceColor : Color.white;
Refresh(true);
}
void Update()
{
_t += Time.unscaledDeltaTime;
if (_t < 0.5f) return;
_t = 0f;
Refresh(false);
}
bool _fontOk;
/// <summary>한글 폰트 — 씬 로드 직후엔 설정·폰트가 아직 없을 수 있어(2차 캡처 □□ 실측) 성공할 때까지 0.5초마다 다시 시도한다.</summary>
void TryKoreanFont()
{
if (_fontOk || _text == null) return;
// ResolveKoreanFont 는 한글 폰트를 못 찾으면 아무 폰트나 돌려준다(장비 UI 를 열기 전 섬에는 한글 라벨이 없다 · 3차 캡처 □□ 실측) → 한글 글자 보유를 직접 확인하고, 아니면 다음 틱에 다시
try { var kr = WL.Equip.WLEquipUi.ResolveKoreanFont(WL.Equip.WLEquipSettings.Instance); if (kr != null && kr.HasCharacter('증', true, true)) { _text.font = kr; _fontOk = true; } }
catch (System.Exception) { }
}
void Refresh(bool force)
{
if (_text == null) return;
TryKoreanFont();
int v = WLBossToken.Get();
bool island = CryingSnow.FarmingIsland.IslandManager.Instance != null;
gameObject.SetActive(true);
_text.transform.parent.gameObject.SetActive(island);
if (!force && v == _last) return;
_last = v;
_text.text = WLBossToken.Name + " <b>" + v + "</b>";
}
}
}