302 lines
18 KiB
C#
302 lines
18 KiB
C#
using System.Collections.Generic;
|
||
using System.Text;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
namespace TKS.OutGame
|
||
{
|
||
/// <summary>
|
||
/// 전투 결과 패널(기획서 §6 · 비주얼 = Fable 제안서 §4-2 #1104) — 별 3(받침 원 + 등장 튀김) · 조건 행 3 · 보상 타일(골드 · 경험 · 보옥 · 장비 · 상자) ·
|
||
/// 버튼 = 「다시」(Tertiary · 같은 스테이지 재도전) + 「확인」(Primary 노랑) · 승리면 리본 뒤 금빛.
|
||
/// 전투 HUD 의 승리/패배 판(sortingOrder 100) 위에 덮는다 · 화면 아무 데나 탭 = 기존 그대로(SlingshotInput → 월드맵 복귀 · 「다시」 위만 제외).
|
||
/// </summary>
|
||
public class BattleResultPanel
|
||
{
|
||
// 패널 배치(UI px · 패널 왼쪽 위 기준)
|
||
const float PW = 920f;
|
||
const float PH = 1080f;
|
||
const float Side = 60f;
|
||
const float RowW = PW - Side * 2f;
|
||
const float RowH = 64f;
|
||
const float Tile = 132f;
|
||
const float BtnY = PH - 40f - 110f;
|
||
|
||
readonly OutGameRoot root;
|
||
readonly GameObject go;
|
||
readonly Text title;
|
||
readonly Image ribbon;
|
||
readonly Image glow;
|
||
readonly RectTransform starRow;
|
||
readonly Image[] starImg = new Image[3];
|
||
readonly RectTransform body;
|
||
readonly Text plain;
|
||
readonly Button retry;
|
||
readonly Button ok;
|
||
readonly Text retryCost;
|
||
string rewardsText = "";
|
||
|
||
public bool Visible
|
||
{
|
||
get { return go.activeSelf; }
|
||
}
|
||
|
||
public string RewardsText
|
||
{
|
||
get { return rewardsText; }
|
||
}
|
||
|
||
public BattleResultPanel(OutGameRoot r, RectTransform canvas)
|
||
{
|
||
root = r;
|
||
var dim = UiKit.Fill("Result", canvas);
|
||
UiKit.Paint(dim, UISkin.Col("dim", new Color(0f, 0f, 0f, 0.55f)), false);
|
||
go = dim.gameObject;
|
||
Image panel = null;
|
||
if (UISkin.Has("popup") && UISkin.Has("panel_frame"))
|
||
{
|
||
// 리본 뒤 금빛(승리만 · 팩 Glow_Circle01 · 호흡) → 팩 팝업 틀 → 제목 리본(승리 붉은 · 패배 회색)
|
||
if (UISkin.Has("glow_circle"))
|
||
{
|
||
glow = UiKit.Skin(UiKit.Rect("Glow", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f + PH * 0.5f + 90f), new Vector2(1240f, 1240f)), "glow_circle", UiKit.Col("ribbon_glow", Color.clear));
|
||
var pulse = glow.gameObject.AddComponent<UiPulse>();
|
||
pulse.min = 0.65f;
|
||
pulse.period = 1.6f;
|
||
// 안쪽 한 겹 더(그림 가운데 α 0.5 라 한 장으로는 후광이 약함 · 검수2 보충)
|
||
var core = UiKit.Skin(UiKit.Rect("Core", glow.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(720f, 720f)), "glow_circle", UiKit.Col("ribbon_glow", Color.clear));
|
||
core.raycastTarget = false;
|
||
}
|
||
// 패널 = 섹션 패널 층(그림자 · 밝은 프레임 #3F4262 · 필드 #262840 · 윗면 광택 · 검수 결함 10)
|
||
var holder = UiKit.Rect("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f), new Vector2(PW, PH));
|
||
RectTransform froot;
|
||
UiKit.SectionPanel(holder, "Frame", 0f, 0f, PW, PH, false, false, out froot);
|
||
var pt = (Transform)holder;
|
||
var rib = UiKit.Rect("Ribbon", pt, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), new Vector2(0f, 10f), new Vector2(1000f, 200f));
|
||
ribbon = UiKit.Skin(rib, "ribbon", Color.white);
|
||
title = UiKit.Title(UiKit.FillLabel(rib, "", 88, UiKit.White, TextAnchor.MiddleCenter, true, 150f), true);
|
||
title.rectTransform.offsetMin = new Vector2(150f, 34f);
|
||
// 별 3 = 받침 원(Circle78 chip_dark) + 별(가운데 크게 · 위로) · 0.15 s 간격 튀김
|
||
starRow = UiKit.At(pt, "StarRow", 0f, 120f, PW, 190f);
|
||
UiLayer.Nest(starRow.gameObject, UiRate.Frame, false); // 별 3 튀김 = 한 Frame 층(별마다 캔버스를 만들지 않음 · PD 2026-11-10)
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
bool mid = i == 1;
|
||
float plate = mid ? 150f : 120f;
|
||
var pos = new Vector2((i - 1) * 190f, mid ? 12f : -14f);
|
||
UiKit.Skin(UiKit.Rect("Plate" + i, starRow, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos, new Vector2(plate, plate)), "circle_frame", UiKit.Col("chip_dark", UiKit.Dark));
|
||
starImg[i] = UiKit.Icon(starRow, "star_on_l", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos + new Vector2(0f, 4f), mid ? 132f : 106f);
|
||
if (starImg[i] == null) continue;
|
||
UiKit.DropShadow(starImg[i], 5f);
|
||
starImg[i].gameObject.AddComponent<UiPop>().delay = 0.15f * i;
|
||
}
|
||
body = UiKit.Fill("Body", pt);
|
||
retry = UiKit.SkinBtn(pt, "Retry", "round08", UiKit.Col("menu_btn", UiKit.Dark), "다시", 34, UiKit.White, Side, BtnY, 260f, 110f, () => root.RetryStage());
|
||
retryCost = UiKit.BtnCost(retry, "cur_stamina", "", 36f, 28); // 재도전 = 군량 소모 표시(검수 결함 2)
|
||
ok = UiKit.SkinBtn(pt, "Ok", UISkin.Has("btn_l_yellow") ? "btn_l_yellow" : "button", UISkin.Has("btn_l_yellow") ? UiKit.White : UiKit.Coin, "확인", 46, UiKit.Col("text_on_yellow", UiKit.Ink), PW - Side - 520f, BtnY, 520f, 110f, null);
|
||
}
|
||
else
|
||
{
|
||
panel = UiKit.Box("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 40f), new Vector2(PW, PH), UiKit.Paper);
|
||
UiKit.Border(panel, UiKit.Line, 4f);
|
||
var head = UiKit.TopBand("Head", panel.transform, 0f, 170f);
|
||
UiKit.Paint(head, UiKit.Dark);
|
||
title = UiKit.FillLabel(head, "", 96, UiKit.White);
|
||
plain = UiKit.Label(panel.transform, "", 36, UiKit.Ink, TextAnchor.UpperLeft, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -200f), new Vector2(760f, 860f), false);
|
||
plain.lineSpacing = 1.15f;
|
||
UiKit.Label(panel.transform, "화면을 탭하면 계속", 34, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 36f), new Vector2(800f, 60f), false);
|
||
}
|
||
go.SetActive(false);
|
||
}
|
||
|
||
public void SetVisible(bool on)
|
||
{
|
||
if (go.activeSelf != on) go.SetActive(on);
|
||
}
|
||
|
||
/// <summary>화면 좌표가 「다시」 버튼 위인가 — 화면 탭(월드맵 복귀)을 이 버튼에서는 건너뛴다.</summary>
|
||
public bool PointerOnRetry(Vector2 screen)
|
||
{
|
||
if (retry == null || !go.activeInHierarchy || !retry.gameObject.activeSelf) return false;
|
||
var c = go.GetComponentInParent<Canvas>();
|
||
return RectTransformUtility.RectangleContainsScreenPoint((RectTransform)retry.transform, screen, c != null ? c.worldCamera : null);
|
||
}
|
||
|
||
public void Fill(StageReward r)
|
||
{
|
||
var s = root.Session;
|
||
rewardsText = r.win ? RewardText(s, r) : "보상 없음";
|
||
title.text = (r.win ? "승리" : "패배") + " <size=44>" + r.chapter + "-" + r.stage + (r.boss ? " 보스" : "") + "</size>";
|
||
ShowStars(r.win ? r.stars : 0, r.win);
|
||
SetButtons(true);
|
||
if (retryCost != null && root.Battle != null && root.Battle.Config != null && root.Battle.StageIndex >= 0 && root.Battle.StageIndex < root.Battle.Config.stages.Length)
|
||
{
|
||
int cost = EconomyRules.StageCost(s.Db, s.Save, root.Battle.Config.stages[root.Battle.StageIndex]);
|
||
retryCost.text = cost.ToString();
|
||
retry.GetComponent<Image>().color = s.Save.stamina >= cost ? UiKit.Col("menu_btn", UiKit.Dark) : UiKit.Col("disabled", UiKit.Disabled);
|
||
}
|
||
if (body == null)
|
||
{
|
||
plain.text = (r.win ? "별 " + UiKit.Stars(r.stars, 3) + "\n\n" : "아군이 전멸했습니다\n\n") + rewardsText;
|
||
return;
|
||
}
|
||
UiKit.Clear(body);
|
||
float y = 330f;
|
||
if (r.win)
|
||
{
|
||
string[] cond = { "스테이지 클리어", "아군 전원 생존", r.starTurns + "턴 이내 클리어 <size=26>(" + r.turns + "턴)</size>" };
|
||
for (int i = 0; i < 3; i++)
|
||
UiKit.CondRow(body, "Cond" + i, Side, y + i * (RowH + 8f), RowW, RowH, r.conds[i] ? "check" : "x_small", r.conds[i], cond[i], 30);
|
||
}
|
||
else
|
||
{
|
||
UiKit.CondRow(body, "Cond0", Side, y, RowW, RowH, "x_small", false, "아군이 전멸했습니다", 30);
|
||
UiKit.CondRow(body, "Cond1", Side, y + RowH + 8f, RowW, RowH, "cur_stamina", false, "군량 " + r.stamina + " 소모 · 별 없음", 30, false);
|
||
}
|
||
y = 566f;
|
||
UiKit.SubTitle(body, "보상", Side, y, RowW);
|
||
y += 64f;
|
||
if (!r.win)
|
||
{
|
||
var none = UiKit.Title(UiKit.Label(body, "보상 없음", 40, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y - 10f), new Vector2(RowW, 70f)));
|
||
none.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
UiKit.Label(body, "영웅 레벨업 · 승급 · 편성을 바꿔 다시 도전", 30, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y - 90f), new Vector2(RowW, 50f), false);
|
||
return;
|
||
}
|
||
Tiles(s, r, y);
|
||
Notes(s, r, y + Tile + 62f);
|
||
}
|
||
|
||
/// <summary>보상 타일 행(가운데 정렬 · 칸 아래 이름) — 골드 · 경험 · 보옥(있을 때) · 장비(등급 칸 · 있을 때) · 상자(있을 때).</summary>
|
||
void Tiles(OutGameSession s, StageReward r, float y)
|
||
{
|
||
var list = new List<string[]>(); // 아이콘 · 수량 · 칸 색 접두 · 이름
|
||
list.Add(new[] { "cur_gold", r.gold.ToString("N0"), "gear_white_", r.first ? "골드" : "골드 ×" + s.Db.Economy.RepeatRewardMult });
|
||
list.Add(new[] { "cur_exp", r.exp.ToString("N0"), "gear_white_", "경험 · " + r.expTo.Count + "명" });
|
||
if (r.gem > 0) list.Add(new[] { "cur_gem", r.gem.ToString("N0"), "gear_blue_", "보옥" });
|
||
if (!string.IsNullOrEmpty(r.gearId))
|
||
{
|
||
var g = s.Db.Gear.Get(r.gearId);
|
||
string grade = g != null && !string.IsNullOrEmpty(g.grade) ? g.grade : "white";
|
||
list.Add(new[] { g != null ? "gear_" + g.kind : "cur_gear", "", "gear_" + grade + "_", g != null ? g.nameKo : r.gearId });
|
||
}
|
||
if (!string.IsNullOrEmpty(r.chest) && s.Db.Chests != null && s.Db.Chests.Get(r.chest) != null)
|
||
list.Add(new[] { "chest_" + r.chest, "", "gear_purple_", s.Db.Chests.Get(r.chest).nameKo });
|
||
float gap = list.Count >= 5 ? 22f : 30f;
|
||
float x = (PW - list.Count * Tile - (list.Count - 1) * gap) * 0.5f;
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var e = list[i];
|
||
float tx = x + i * (Tile + gap);
|
||
var cell = UiKit.RewardTile(body, "Tile" + i, tx, y, Tile, e[2], e[0], e[1], e[2] != "gear_white_");
|
||
if (r.first && (e[0] == "cur_gold" || e[0] == "cur_gem")) FirstBadge(cell); // 첫 클리어 = 뱃지(스테이지 팝업과 같은 표현 · 검수 결함 8)
|
||
var n = UiKit.Label(body, e[3], 26, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.UpperCenter, new Vector2(0f, 1f), new Vector2(0.5f, 1f), new Vector2(tx + Tile * 0.5f, -y - Tile - 12f), new Vector2(Tile + gap, 36f), false);
|
||
n.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
}
|
||
}
|
||
|
||
/// <summary>「첫」 뱃지(Label_Badge01_Yellow · 칸 오른쪽 위).</summary>
|
||
public static void FirstBadge(Transform cell)
|
||
{
|
||
var b = UiKit.Icon(cell, "badge_first", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-8f, -6f), 58f);
|
||
if (b != null) UiKit.Title(UiKit.FillLabel(b.transform, "첫", 24, UiKit.White), true);
|
||
}
|
||
|
||
/// <summary>타일 아래 = 레벨업 가능 칩(위 화살표 + 노란 글자 · 검수 결함 8) · 없으면 비움.</summary>
|
||
void Notes(OutGameSession s, StageReward r, float y)
|
||
{
|
||
var ready = ReadyNames(s, r);
|
||
if (ready.Length == 0) return;
|
||
string text = "레벨업 가능 " + ready;
|
||
var t = UiKit.InfoChip(body, "LevelUp", Side, y, RowW, 56f, "up_green", text, 28);
|
||
t.color = UiKit.Col("select_line", UiKit.Coin);
|
||
float w = Mathf.Min(RowW, Mathf.Ceil(t.preferredWidth) + 110f);
|
||
var c = (RectTransform)t.transform.parent;
|
||
c.anchoredPosition = new Vector2((PW - w) * 0.5f, -y);
|
||
c.sizeDelta = new Vector2(w, 56f);
|
||
t.rectTransform.sizeDelta = new Vector2(w - 80f, 56f);
|
||
}
|
||
|
||
/// <summary>연무장 결과(탑 · 던전 · 투기장 · 적장 토벌 · PD #1093 C) — 별 없음 · 결과 행 · 탭 = 연무장.</summary>
|
||
public void FillMode(ModeResult r)
|
||
{
|
||
SetButtons(false);
|
||
rewardsText = r != null ? string.Join("\n", r.lines.ToArray()) : "";
|
||
if (r == null)
|
||
{
|
||
title.text = "연무장";
|
||
ShowStars(-1, true);
|
||
if (body != null) UiKit.Clear(body);
|
||
else plain.text = "";
|
||
return;
|
||
}
|
||
bool win = r.win || r.kind == ArenaRules.Raid;
|
||
title.text = r.kind == ArenaRules.Raid ? "토벌 종료" : r.win ? "승리" : "패배";
|
||
ShowStars(-1, win);
|
||
if (body == null)
|
||
{
|
||
plain.text = "<size=44>" + r.title + "</size>\n\n" + rewardsText;
|
||
return;
|
||
}
|
||
UiKit.Clear(body);
|
||
var head = UiKit.Title(UiKit.Label(body, r.title, 44, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -150f), new Vector2(RowW, 110f)), true);
|
||
head.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
UiKit.SubTitle(body, "결과", Side, 290f, RowW);
|
||
float y = 354f;
|
||
for (int i = 0; i < r.lines.Count && y + RowH <= BtnY - 20f; i++, y += RowH + 8f)
|
||
UiKit.CondRow(body, "Line" + i, Side, y, RowW, RowH, null, true, r.lines[i], 30);
|
||
}
|
||
|
||
/// <summary>스테이지 = 「다시」 + 「확인」 · 연무장 = 「확인」 가운데.</summary>
|
||
void SetButtons(bool stage)
|
||
{
|
||
if (retry == null) return;
|
||
retry.gameObject.SetActive(stage);
|
||
((RectTransform)ok.transform).anchoredPosition = new Vector2(stage ? PW - Side - 520f : (PW - 520f) * 0.5f, -BtnY);
|
||
}
|
||
|
||
/// <summary>별 아이콘(n < 0 = 숨김) · 리본 색(승리 붉은 · 패배 회색) · 금빛 = 승리만.</summary>
|
||
void ShowStars(int n, bool win)
|
||
{
|
||
if (glow != null) glow.gameObject.SetActive(win);
|
||
if (starRow == null) return;
|
||
starRow.gameObject.SetActive(n >= 0);
|
||
for (int i = 0; i < 3; i++)
|
||
if (starImg[i] != null) starImg[i].sprite = UISkin.Icon(i < n ? "star_on_l" : "star_off") ?? starImg[i].sprite;
|
||
if (ribbon != null && !UISkin.Apply(ribbon, win ? "ribbon" : "ribbon_gray")) ribbon.color = win ? Color.white : UiKit.Disabled;
|
||
}
|
||
|
||
static string ReadyNames(OutGameSession s, StageReward r)
|
||
{
|
||
var ready = new StringBuilder();
|
||
foreach (var id in r.expTo)
|
||
{
|
||
var lv = s.Save.Level(id);
|
||
if (HeroRules.CanLevelUp(s.Db, s.Save, lv) != null) continue;
|
||
if (ready.Length > 0) ready.Append(" · ");
|
||
ready.Append(s.Def(id).nameKo);
|
||
}
|
||
return ready.ToString();
|
||
}
|
||
|
||
/// <summary>보상 줄(소탕 팝업 · 테마 없을 때 결과 화면 공용).</summary>
|
||
public static string RewardText(OutGameSession s, StageReward r)
|
||
{
|
||
var e = s.Db.Economy;
|
||
var sb = new StringBuilder();
|
||
sb.Append("골드 +").Append(r.gold.ToString("N0")).Append(r.first ? " <size=28>(첫 클리어)</size>" : " <size=28>(반복 ×" + e.RepeatRewardMult + ")</size>");
|
||
sb.Append("\n경험 +").Append(r.exp).Append(" <size=28>출전 ").Append(r.expTo.Count).Append("명</size>");
|
||
if (r.gem > 0) sb.Append("\n보옥 +").Append(r.gem).Append(r.boss ? " <size=28>(첫 클리어 + 보스)</size>" : " <size=28>(첫 클리어)</size>");
|
||
if (!string.IsNullOrEmpty(r.gearId))
|
||
{
|
||
var g = s.Db.Gear.Get(r.gearId);
|
||
sb.Append("\n장비 ").Append(g != null ? g.nameKo + " (" + UiKit.GradeKo(g.grade) + ")" : r.gearId).Append(" 획득");
|
||
}
|
||
else sb.Append("\n<size=30>장비 드랍 없음</size>");
|
||
if (!string.IsNullOrEmpty(r.chest) && s.Db.Chests != null && s.Db.Chests.Get(r.chest) != null) sb.Append("\n").Append(s.Db.Chests.Get(r.chest).nameKo).Append(" 획득 <size=28>(보물 > 상자)</size>");
|
||
if (r.sweep) sb.Append("\n<size=30>군량 ").Append(r.stamina).Append(" 소모 · 소탕(전투 없음)</size>");
|
||
var ready = ReadyNames(s, r);
|
||
if (ready.Length > 0) sb.Append("\n<color=#a03a22><size=30>레벨업 가능: ").Append(ready).Append("</size></color>");
|
||
return sb.ToString();
|
||
}
|
||
}
|
||
}
|