573 lines
25 KiB
C#
573 lines
25 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
namespace TKS.OutGame
|
||
{
|
||
/// <summary>아웃게임 화면 1장(본영 로비 · 영웅 · 편성 · 거래). OutGameRoot 가 만들고 전환한다.</summary>
|
||
public abstract class OutGameScreen
|
||
{
|
||
public OutGameRoot Root;
|
||
public RectTransform Rt;
|
||
/// <summary>하단 탭 강조(0 영웅 · 4 거래 · −1 없음).</summary>
|
||
public int Tab = -1;
|
||
|
||
public OutGameSession S
|
||
{
|
||
get { return Root.Session; }
|
||
}
|
||
|
||
public abstract void Build();
|
||
public virtual void Refresh() { }
|
||
public virtual void OnShow() { }
|
||
/// <summary>뒤로 가기를 화면 안에서 처리했으면 true.</summary>
|
||
public virtual bool OnBack() { return false; }
|
||
|
||
/// <summary>제목 띠(재화 바 아래) · back = 「◀」 버튼.</summary>
|
||
protected void Header(string title, string sub, bool back)
|
||
{
|
||
var band = UiKit.TopBand("Header", Rt, OutGameRoot.TopBarH, OutGameRoot.HeaderH);
|
||
UiKit.Paint(band, UiKit.Header, false);
|
||
float x = 36f;
|
||
if (back)
|
||
{
|
||
UiKit.Btn("Back", band, "◀", 44, UiKit.Dark, UiKit.White, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(24f, 0f), new Vector2(100f, 76f), () => Root.Back());
|
||
x = 146f;
|
||
}
|
||
var t = UiKit.Label(band, title, 52, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(x, 0f), new Vector2(360f, 90f));
|
||
t.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
UiKit.Label(band, sub, 30, UiKit.InkSoft, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(x + t.preferredWidth + 18f, -4f), new Vector2(700f, 60f), false);
|
||
}
|
||
|
||
/// <summary>제목 띠 아래 ~ 하단 탭 위 본문.</summary>
|
||
protected RectTransform Body(string name = "Body")
|
||
{
|
||
return UiKit.Fill(name, Rt, 0f, 0f, OutGameRoot.TopBarH + OutGameRoot.HeaderH, OutGameRoot.TabBarH);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 아웃게임 루트(2026-11-05 #1093 시스템 기획 §1 · §10 A) — 게임 시작 = 본영 로비 · 출정 → 기존 챕터 월드맵 · 편성 · 영웅 · 거래 · 준비 중 팝업 ·
|
||
/// 상단 재화 바(골드 · 보옥 · 군량 n/최대 + 회복 타이머 · 월드맵에도 표시) · 하단 탭 5 · 뒤로 가기(◀ · Esc) · 전투 결과 보상 패널.
|
||
/// 데이터 = OutGameSession(표 + 세이브) · 화면은 도형 + 글자 와이어프레임(목업 img03·06·08·11 배치).
|
||
/// </summary>
|
||
public class OutGameRoot : MonoBehaviour
|
||
{
|
||
public static OutGameRoot Instance { get; private set; }
|
||
|
||
public const float TopBarH = 120f;
|
||
public const float HeaderH = 104f;
|
||
public const float TabBarH = 150f;
|
||
public static readonly Vector2 Reference = new Vector2(1080f, 1920f);
|
||
|
||
public OutGameSession Session { get; private set; }
|
||
public BattleManager Battle { get; private set; }
|
||
public WorldMapScreen Map { get; private set; }
|
||
public Camera Cam { get; private set; }
|
||
|
||
BattleHUD hud;
|
||
SlingshotInput sling;
|
||
|
||
Canvas mainCanvas;
|
||
Canvas topCanvas;
|
||
Canvas resultCanvas;
|
||
public RectTransform TopRoot { get; private set; }
|
||
|
||
public LobbyScreen Lobby { get; private set; }
|
||
public HeroListScreen HeroList { get; private set; }
|
||
public HeroDetailScreen HeroDetail { get; private set; }
|
||
public FormationScreen Formation { get; private set; }
|
||
public TradeScreen Trade { get; private set; }
|
||
readonly List<OutGameScreen> screens = new List<OutGameScreen>();
|
||
OutGameScreen current;
|
||
|
||
RectTransform barRoot;
|
||
Text goldText;
|
||
Text gemText;
|
||
Text staminaText;
|
||
Text staminaTimer;
|
||
readonly List<Image> tabImages = new List<Image>();
|
||
readonly List<Text> tabTexts = new List<Text>();
|
||
|
||
GameObject modal;
|
||
GameObject toast;
|
||
Text toastText;
|
||
float toastUntil;
|
||
float nextTick;
|
||
|
||
BattleResultPanel result;
|
||
bool resultDone;
|
||
|
||
public OutGameScreen Current
|
||
{
|
||
get { return current; }
|
||
}
|
||
|
||
/// <summary>아웃게임 화면(로비 등)이 보이는가(월드맵·전투 중 = false).</summary>
|
||
public bool ScreensVisible
|
||
{
|
||
get { return mainCanvas != null && mainCanvas.gameObject.activeSelf; }
|
||
}
|
||
|
||
public bool LobbyVisible
|
||
{
|
||
get { return ScreensVisible && current == Lobby; }
|
||
}
|
||
|
||
public bool ModalVisible
|
||
{
|
||
get { return modal != null; }
|
||
}
|
||
|
||
public bool ResultVisible
|
||
{
|
||
get { return result != null && result.Visible; }
|
||
}
|
||
|
||
void Awake()
|
||
{
|
||
Instance = this;
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
if (Instance == this) Instance = null;
|
||
}
|
||
|
||
public void Build(BattleManager b, BattleHUD h, SlingshotInput s, WorldMapScreen wm, Camera cam)
|
||
{
|
||
Battle = b;
|
||
hud = h;
|
||
sling = s;
|
||
Map = wm;
|
||
Cam = cam;
|
||
Session = OutGameSession.Start(OutGameData.Default, b.Config);
|
||
Session.Changed += RefreshAll;
|
||
|
||
// 전투 연결: 출전 = 편성 프리셋 · 결과 = 보상
|
||
b.LineupProvider = Session.BuildLineup;
|
||
b.StageStarted += () => resultDone = false;
|
||
b.PhaseChanged += OnPhase;
|
||
|
||
// 월드맵 연결: 팝업 줄 · 군량 · 별 · 소탕 · 본영
|
||
if (wm != null)
|
||
{
|
||
wm.TopInset = TopBarH;
|
||
wm.PopupInfo = PopupInfo;
|
||
wm.TryEnter = TryEnter;
|
||
wm.StarsOf = si => EconomyRules.StarsOf(Session.Save, b.Config.stages[si]);
|
||
wm.CanSweep = si => EconomyRules.CanSweep(Session.Save, b.Config.stages[si]);
|
||
wm.Sweep = DoSweep;
|
||
wm.BackRequested = ShowLobby;
|
||
}
|
||
|
||
mainCanvas = MakeCanvas("OutGameCanvas", 350, 0.45f);
|
||
resultCanvas = MakeCanvas("BattleResultCanvas", 200, 0.8f);
|
||
topCanvas = MakeCanvas("OutGameTopCanvas", 450, 0.4f);
|
||
TopRoot = (RectTransform)topCanvas.transform;
|
||
|
||
var bg = UiKit.Fill("Bg", mainCanvas.transform);
|
||
UiKit.Paint(bg, UiKit.Bg, false, true);
|
||
|
||
Lobby = Add(new LobbyScreen());
|
||
HeroList = Add(new HeroListScreen());
|
||
HeroDetail = Add(new HeroDetailScreen());
|
||
Formation = Add(new FormationScreen());
|
||
Trade = Add(new TradeScreen());
|
||
BuildTabBar();
|
||
BuildCurrencyBar();
|
||
result = new BattleResultPanel(this, (RectTransform)resultCanvas.transform);
|
||
mainCanvas.gameObject.SetActive(false);
|
||
}
|
||
|
||
T Add<T>(T scr) where T : OutGameScreen
|
||
{
|
||
scr.Root = this;
|
||
scr.Rt = UiKit.Fill(typeof(T).Name, mainCanvas.transform);
|
||
scr.Build();
|
||
scr.Rt.gameObject.SetActive(false);
|
||
screens.Add(scr);
|
||
return scr;
|
||
}
|
||
|
||
Canvas MakeCanvas(string name, int order, float plane)
|
||
{
|
||
var go = new GameObject(name);
|
||
go.transform.SetParent(transform, false);
|
||
var c = go.AddComponent<Canvas>();
|
||
c.renderMode = RenderMode.ScreenSpaceCamera; // 카메라 캡처(검증)에도 찍히게
|
||
c.worldCamera = Cam;
|
||
c.planeDistance = plane;
|
||
c.sortingOrder = order;
|
||
var sc = go.AddComponent<CanvasScaler>();
|
||
sc.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
sc.referenceResolution = Reference;
|
||
sc.matchWidthOrHeight = 0.5f;
|
||
go.AddComponent<GraphicRaycaster>();
|
||
return c;
|
||
}
|
||
|
||
// ---------- 화면 전환 ----------
|
||
|
||
/// <summary>본영 로비(게임 시작 화면) — 월드맵·전투 HUD·조작을 끄고 로비를 연다.</summary>
|
||
public void ShowLobby()
|
||
{
|
||
if (Map != null) Map.Hide();
|
||
if (Battle != null && Battle.Phase != BattlePhase.Setup) Battle.GoIdle();
|
||
Show(Lobby);
|
||
}
|
||
|
||
public void Show(OutGameScreen scr)
|
||
{
|
||
if (hud != null) hud.gameObject.SetActive(false);
|
||
if (sling != null) sling.enabled = false;
|
||
if (Map != null && Map.IsVisible) Map.Hide();
|
||
mainCanvas.gameObject.SetActive(true);
|
||
foreach (var s in screens) s.Rt.gameObject.SetActive(s == scr);
|
||
current = scr;
|
||
for (int i = 0; i < tabImages.Count; i++)
|
||
{
|
||
bool on = scr.Tab == i;
|
||
tabImages[i].color = on ? UiKit.Red : UiKit.Dark;
|
||
}
|
||
scr.OnShow();
|
||
scr.Refresh();
|
||
RefreshBar();
|
||
}
|
||
|
||
public void ShowHero(string id)
|
||
{
|
||
HeroDetail.Select(id);
|
||
Show(HeroDetail);
|
||
}
|
||
|
||
/// <summary>출정 = 챕터 월드맵(도전 중인 마지막 열린 챕터).</summary>
|
||
public void OpenWorldMap()
|
||
{
|
||
if (Map == null || !Map.HasData) return;
|
||
CloseModal();
|
||
mainCanvas.gameObject.SetActive(false);
|
||
Map.Show(CurrentChapter());
|
||
}
|
||
|
||
public int CurrentChapter()
|
||
{
|
||
if (Map == null) return 0;
|
||
int ch = 0;
|
||
for (int i = 0; i < Map.ChapterCount; i++)
|
||
if (Map.ChapterUnlocked(i)) ch = i;
|
||
return ch;
|
||
}
|
||
|
||
/// <summary>뒤로 가기(◀ · Esc): 팝업 닫기 → 화면 안 처리 → 영웅 상세 = 목록 → 나머지 = 로비 · 월드맵 = 로비.</summary>
|
||
public void Back()
|
||
{
|
||
if (modal != null)
|
||
{
|
||
CloseModal();
|
||
return;
|
||
}
|
||
if (Map != null && Map.IsVisible)
|
||
{
|
||
if (Map.PopupVisible) Map.ClosePopup();
|
||
else ShowLobby();
|
||
return;
|
||
}
|
||
if (!ScreensVisible || current == null) return;
|
||
if (current.OnBack()) return;
|
||
if (current == HeroDetail) Show(HeroList);
|
||
else if (current != Lobby) Show(Lobby);
|
||
}
|
||
|
||
/// <summary>「준비 중」(연무장·보물·내정) · 「추후 업데이트」(동맹) 안내.</summary>
|
||
public void ComingSoon(string what, bool later = false)
|
||
{
|
||
Modal(what, later ? "추후 업데이트 예정입니다." : "준비 중입니다.\n(2차 개발 — 시스템 기획 §7~§9)", new ModalButton("확인", UiKit.Dark, null));
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (Battle == null || Session == null) return;
|
||
bool inBattle = Battle.Phase != BattlePhase.Setup;
|
||
bool onMap = Map != null && Map.IsVisible;
|
||
if (inBattle && ScreensVisible) mainCanvas.gameObject.SetActive(false); // 테스트 등이 바로 StartStage 를 불러도 화면이 전투를 가리지 않게
|
||
bool barOn = !inBattle;
|
||
if (barRoot.gameObject.activeSelf != barOn) barRoot.gameObject.SetActive(barOn);
|
||
result.SetVisible(inBattle && resultDone && (Battle.Phase == BattlePhase.StageClear || Battle.Phase == BattlePhase.Defeat));
|
||
|
||
if (Time.unscaledTime >= nextTick)
|
||
{
|
||
nextTick = Time.unscaledTime + 0.5f;
|
||
Session.TickStamina();
|
||
RefreshBar();
|
||
}
|
||
if (toast != null && Time.unscaledTime > toastUntil) toast.SetActive(false);
|
||
if (Input.GetKeyDown(KeyCode.Escape) && !inBattle) Back();
|
||
}
|
||
|
||
void RefreshAll()
|
||
{
|
||
RefreshBar();
|
||
if (ScreensVisible && current != null) current.Refresh();
|
||
}
|
||
|
||
// ---------- 상단 재화 바 ----------
|
||
|
||
void BuildCurrencyBar()
|
||
{
|
||
barRoot = UiKit.TopBand("CurrencyBar", TopRoot, 0f, TopBarH);
|
||
UiKit.Paint(barRoot, new Color(0.15f, 0.11f, 0.09f), false, true);
|
||
goldText = Pill(0, "골", "골드", UiKit.Coin, () => Toast("골드 = 스테이지 보상 · 거래 > 상점 · (구매 상점 준비 중)"));
|
||
gemText = Pill(1, "보", "보옥", UiKit.Jade, () => Toast("보옥 구매(과금) = 준비 중 · 첫 클리어 · 보스 보상"));
|
||
staminaText = Pill(2, "군", "군량", UiKit.Grain, StaminaModal);
|
||
staminaTimer = staminaText.transform.parent.Find("Name").GetComponent<Text>();
|
||
}
|
||
|
||
Text Pill(int i, string mark, string name, Color c, Action plus)
|
||
{
|
||
float w = 336f;
|
||
var p = UiKit.Box("Pill" + i, barRoot, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(18f + i * (w + 18f), 0f), new Vector2(w, 84f), new Color(0.27f, 0.21f, 0.18f));
|
||
var dot = UiKit.Dot("Icon", p.transform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(8f, 0f), 64f, c);
|
||
UiKit.FillLabel(dot.transform, mark, 32, UiKit.White);
|
||
var n = UiKit.Label(p.transform, name, 26, new Color(0.85f, 0.8f, 0.75f), TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(80f, 0f), new Vector2(110f, 60f), false);
|
||
n.gameObject.name = "Name";
|
||
var v = UiKit.Label(p.transform, "", 34, UiKit.White, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-70f, 0f), new Vector2(170f, 60f));
|
||
v.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
UiKit.Btn("Plus", p.transform, "+", 40, new Color(0.42f, 0.35f, 0.31f), UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-8f, 0f), new Vector2(56f, 56f), plus);
|
||
return v;
|
||
}
|
||
|
||
public void RefreshBar()
|
||
{
|
||
if (Session == null || goldText == null) return;
|
||
var d = Session.Save;
|
||
goldText.text = d.gold.ToString("N0");
|
||
gemText.text = d.gem.ToString("N0");
|
||
staminaText.text = d.stamina + "/" + Session.Db.Economy.StaminaMax;
|
||
int left = Session.StaminaSecondsToNext();
|
||
staminaTimer.text = left > 0 ? (left / 60).ToString("00") + ":" + (left % 60).ToString("00") : "군량";
|
||
}
|
||
|
||
// ---------- 하단 탭 ----------
|
||
|
||
void BuildTabBar()
|
||
{
|
||
var bar = UiKit.Rect("TabBar", mainCanvas.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), Vector2.zero, new Vector2(Reference.x, TabBarH));
|
||
bar.anchorMin = new Vector2(0f, 0f);
|
||
bar.anchorMax = new Vector2(1f, 0f);
|
||
bar.offsetMin = new Vector2(0f, 0f);
|
||
bar.offsetMax = new Vector2(0f, TabBarH);
|
||
UiKit.Paint(bar, UiKit.Dark, false, true);
|
||
string[] names = { "영웅", "보물", "내정", "동맹", "거래" };
|
||
string[] marks = { "영", "보", "내", "잠", "거" };
|
||
for (int i = 0; i < names.Length; i++)
|
||
{
|
||
int k = i;
|
||
var cell = UiKit.Rect("Tab" + i, bar, new Vector2(0f, 0f), new Vector2(0f, 0f), Vector2.zero, Vector2.zero);
|
||
cell.anchorMin = new Vector2(i / 5f, 0f);
|
||
cell.anchorMax = new Vector2((i + 1) / 5f, 1f);
|
||
cell.offsetMin = cell.offsetMax = Vector2.zero;
|
||
var img = UiKit.Paint(cell, UiKit.Dark, false, true);
|
||
var btn = cell.gameObject.AddComponent<Button>();
|
||
btn.onClick.AddListener(() => OnTab(k));
|
||
var dot = UiKit.Dot("Icon", cell, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -18f), 66f, i == 3 ? new Color(0.35f, 0.3f, 0.28f) : new Color(0.38f, 0.31f, 0.28f));
|
||
UiKit.FillLabel(dot.transform, marks[i], 32, i == 3 ? new Color(0.7f, 0.66f, 0.62f) : UiKit.White);
|
||
var t = UiKit.Label(cell, names[i], 32, i == 3 ? new Color(0.62f, 0.58f, 0.55f) : UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 14f), new Vector2(200f, 48f));
|
||
tabImages.Add(img);
|
||
tabTexts.Add(t);
|
||
}
|
||
}
|
||
|
||
public void OnTab(int i)
|
||
{
|
||
switch (i)
|
||
{
|
||
case 0: Show(HeroList); break;
|
||
case 1: ComingSoon("보물"); break;
|
||
case 2: ComingSoon("내정 (훈련 · 순찰 · 개발)"); break;
|
||
case 3: ComingSoon("동맹", true); break;
|
||
case 4: Show(Trade); break;
|
||
}
|
||
}
|
||
|
||
// ---------- 팝업 · 알림 ----------
|
||
|
||
public struct ModalButton
|
||
{
|
||
public string label;
|
||
public Color color;
|
||
public Action action;
|
||
public bool keepOpen;
|
||
|
||
public ModalButton(string label, Color color, Action action, bool keepOpen = false)
|
||
{
|
||
this.label = label;
|
||
this.color = color;
|
||
this.action = action;
|
||
this.keepOpen = keepOpen;
|
||
}
|
||
}
|
||
|
||
public void Modal(string title, string body, params ModalButton[] buttons)
|
||
{
|
||
var content = ModalCustom(title, 0f, buttons);
|
||
var t = UiKit.FillLabel(content, body, 36, UiKit.Ink, TextAnchor.MiddleCenter, false, 10f);
|
||
t.lineSpacing = 1.1f;
|
||
var rt = (RectTransform)content.parent;
|
||
float h = Mathf.Clamp(t.preferredHeight + 330f, 480f, 1500f);
|
||
rt.sizeDelta = new Vector2(rt.sizeDelta.x, h);
|
||
}
|
||
|
||
/// <summary>제목 + 내용 칸 + 아래 버튼 줄 · 반환 = 내용 칸(높이 = height · 0 이면 Modal 이 맞춤).</summary>
|
||
public RectTransform ModalCustom(string title, float height, params ModalButton[] buttons)
|
||
{
|
||
CloseModal();
|
||
var dim = UiKit.Fill("Modal", TopRoot);
|
||
var dimImg = UiKit.Paint(dim, new Color(0f, 0f, 0f, 0.6f), false, true);
|
||
dimImg.raycastTarget = true;
|
||
modal = dim.gameObject;
|
||
float h = height > 0f ? height : 600f;
|
||
var panel = UiKit.Box("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f), new Vector2(940f, h), UiKit.Paper);
|
||
panel.raycastTarget = true;
|
||
UiKit.Border(panel, UiKit.Line, 4f);
|
||
var head = UiKit.TopBand("Head", panel.transform, 0f, 110f);
|
||
UiKit.Paint(head, UiKit.Dark);
|
||
UiKit.FillLabel(head, title, 44, UiKit.White);
|
||
var content = UiKit.Fill("Content", panel.transform, 40f, 40f, 130f, 160f);
|
||
int n = Mathf.Max(1, buttons.Length);
|
||
float bw = Mathf.Min(400f, (860f - (n - 1) * 20f) / n);
|
||
for (int i = 0; i < buttons.Length; i++)
|
||
{
|
||
var mb = buttons[i];
|
||
float x = (i - (n - 1) / 2f) * (bw + 20f);
|
||
UiKit.Btn("B" + i, panel.transform, mb.label, 34, mb.color, UiKit.White, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(x, 36f), new Vector2(bw, 100f), () =>
|
||
{
|
||
if (!mb.keepOpen) CloseModal();
|
||
if (mb.action != null) mb.action();
|
||
});
|
||
}
|
||
return content;
|
||
}
|
||
|
||
public void CloseModal()
|
||
{
|
||
if (modal == null) return;
|
||
modal.SetActive(false);
|
||
Destroy(modal);
|
||
modal = null;
|
||
}
|
||
|
||
public void Toast(string msg)
|
||
{
|
||
if (toast == null)
|
||
{
|
||
var img = UiKit.Box("Toast", TopRoot, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, TabBarH + 40f), new Vector2(940f, 110f), new Color(0.1f, 0.08f, 0.07f, 0.92f));
|
||
toast = img.gameObject;
|
||
toastText = UiKit.FillLabel(img.transform, "", 32, UiKit.White, TextAnchor.MiddleCenter, false, 16f);
|
||
}
|
||
toast.transform.SetAsLastSibling();
|
||
toastText.text = msg;
|
||
toast.SetActive(true);
|
||
toastUntil = Time.unscaledTime + 2.2f;
|
||
}
|
||
|
||
public string LastToast
|
||
{
|
||
get { return toast != null && toast.activeSelf ? toastText.text : ""; }
|
||
}
|
||
|
||
// ---------- 군량 ----------
|
||
|
||
/// <summary>군량 충전(보옥 구매 · 광고 스텁) 팝업.</summary>
|
||
public void StaminaModal()
|
||
{
|
||
StaminaModal(null);
|
||
}
|
||
|
||
void StaminaModal(string why)
|
||
{
|
||
var e = Session.Db.Economy;
|
||
var d = Session.Save;
|
||
EconomyRules.RollDaily(d, Session.Now);
|
||
string body = (why != null ? why + "\n\n" : "") +
|
||
"군량 " + d.stamina + " / " + e.StaminaMax + " · " + (e.StaminaRegenSec / 60) + "분마다 1 회복(오프라인 포함)\n" +
|
||
"스테이지 " + e.StageCost + " · 보스 " + e.BossCost + " 소모";
|
||
Modal("군량 충전", body,
|
||
new ModalButton("보옥 " + e.GemStaminaCost + " → +" + e.GemStaminaAmount + "\n(오늘 " + d.adsUsed.gemStamina + "/" + e.GemStaminaDaily + ")", UiKit.Jade, () =>
|
||
{
|
||
var w = Session.BuyStamina();
|
||
StaminaModal(w ?? "군량 +" + e.GemStaminaAmount);
|
||
if (Map != null) Map.RefreshPopup();
|
||
}, true),
|
||
new ModalButton("광고(시험) +" + e.AdStamina + "\n(오늘 " + d.adsUsed.stamina + "/" + e.AdStaminaDaily + ")", UiKit.Grain, () =>
|
||
{
|
||
var w = Session.AdStamina();
|
||
StaminaModal(w ?? "군량 +" + e.AdStamina + " (광고 SDK 미연동 · 시험 지급)");
|
||
if (Map != null) Map.RefreshPopup();
|
||
}, true),
|
||
new ModalButton("닫기", UiKit.Dark, null));
|
||
}
|
||
|
||
// ---------- 월드맵 연결 ----------
|
||
|
||
string PopupInfo(int si)
|
||
{
|
||
var s = Battle.Config.stages[si];
|
||
var db = Session.Db;
|
||
var d = Session.Save;
|
||
var e = db.Economy;
|
||
var row = db.Rewards.For(s.chapter);
|
||
if (row == null) return "";
|
||
var st = d.Stage(s.chapter, s.stage, false);
|
||
bool first = st == null || st.clears == 0;
|
||
int cost = EconomyRules.StageCost(db, s);
|
||
float baseGold = row.goldBase + s.stage * e.GoldPerStage;
|
||
int gold = Mathf.RoundToInt(baseGold * (first ? e.FirstClearGoldMult : e.RepeatRewardMult));
|
||
int exp = Mathf.RoundToInt(row.expBase * (first ? 1f : e.RepeatRewardMult));
|
||
bool boss = s.kind == StageKind.Boss;
|
||
var sb = new System.Text.StringBuilder();
|
||
sb.Append("<color=#8a3a22>군량 ").Append(cost).Append(" 소모</color> (보유 ").Append(d.stamina).Append("/").Append(e.StaminaMax).Append(")");
|
||
sb.Append("\n보상 골드 ").Append(gold).Append(first ? "(첫 클리어)" : "(반복 ×" + e.RepeatRewardMult + ")").Append(" · 경험 ").Append(exp).Append("(출전 전원)");
|
||
if (first) sb.Append(" · 보옥 ").Append(row.gemFirst + (boss ? row.gemBoss : 0));
|
||
float drop = boss ? e.BossGearDropPct : row.gearDropPct;
|
||
sb.Append("\n장비 드랍 ").Append(Mathf.RoundToInt(drop * 100f)).Append("% (").Append(UiKit.GradeKo(row.gearGradeMax)).Append(" 등급 이하)");
|
||
int stars = st != null ? st.stars : 0;
|
||
sb.Append("\n별 ").Append(UiKit.Stars(stars, 3)).Append(" 클리어 · 전원 생존 · ").Append(row.starTurns).Append("턴 이내");
|
||
if (stars >= 3) sb.Append("\n<color=#3a6a30>별 3 = 소탕 가능(군량만 · 전투 없음)</color>");
|
||
return sb.ToString();
|
||
}
|
||
|
||
bool TryEnter(int si)
|
||
{
|
||
if (Session.TryPayEntry(Battle.Config.stages[si])) return true;
|
||
StaminaModal("군량이 부족합니다 — 필요 " + EconomyRules.StageCost(Session.Db, Battle.Config.stages[si]));
|
||
return false;
|
||
}
|
||
|
||
void DoSweep(int si)
|
||
{
|
||
var s = Battle.Config.stages[si];
|
||
var r = Session.Sweep(s);
|
||
if (r == null)
|
||
{
|
||
StaminaModal("군량이 부족합니다 — 필요 " + EconomyRules.StageCost(Session.Db, s));
|
||
return;
|
||
}
|
||
Modal("소탕 " + s.chapter + "-" + s.stage, BattleResultPanel.RewardText(Session, r), new ModalButton("확인", UiKit.Dark, null));
|
||
if (Map != null) Map.RefreshPopup();
|
||
}
|
||
|
||
// ---------- 전투 결과 ----------
|
||
|
||
void OnPhase(BattlePhase p)
|
||
{
|
||
if (p != BattlePhase.StageClear && p != BattlePhase.Defeat) return;
|
||
if (resultDone) return;
|
||
resultDone = true;
|
||
var r = Session.OnBattleEnd(Battle, p == BattlePhase.StageClear);
|
||
result.Fill(r);
|
||
}
|
||
}
|
||
}
|