225 lines
12 KiB
C#
225 lines
12 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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>";
|
||
}
|
||
}
|
||
}
|