ThreeKingdomsSlingshot/Assets/Scripts/OutGame/UI/BattleResultPanel.cs

302 lines
18 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.

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 &lt; 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();
}
}
}