using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace TKS.OutGame { /// /// 연무장(로비 출정 우측 · 기획 S13 · 목업 img09 · PD #1093 C) — 첫 페이지 = 모드 카드 4(규칙 · 보상 · 입장) → 카드 탭 = 모드 페이지: /// ① 천하제일 탑(다음 층 · 적 템플릿 · 배율 · 보상 · 입장권 + 광고) ② 일일 던전 3(요일 · 남은 입장 · 보상) ③ 투기장(메달 · 주간 순위 · 샘플 상대 10 · 메달 상점) /// ④ 적장 토벌(이번 주 보스 · 누적 피해 · 하루 1). 도전 = OutGameRoot.StartMode → BattleManager.StartMode(적 편성 · 배율 · 제한 턴) → 결과 패널 → 탭 = 이 페이지로. /// public class ArenaScreen : OutGameScreen { public static readonly string[] ModeNames = { "천하제일 탑", "일일 던전 3", "투기장", "적장 토벌" }; int page; RectTransform root; /// 0 = 카드 · 1 탑 · 2 던전 · 3 투기장 · 4 적장 토벌. public int Page { get { return page; } } public override void Build() { Header("연무장", "탑 · 일일 던전 · 투기장 · 적장 토벌", true); root = Body(); } public void SetPage(int p) { page = Mathf.Clamp(p, 0, 4); Refresh(); } public override void OnShow() { S.Do(() => { ArenaRules.RollDaily(S.Db, S.Save, S.Now); ArenaRules.EnsureSeason(S.Db, S.Save, S.Now, S.Rng); ArenaRules.RaidBoss(S.Db, S.Save, S.Now); return null; }); } public override bool OnBack() { if (page == 0) return false; SetPage(0); return true; } public override void Refresh() { UiKit.Clear(root); switch (page) { case 0: BuildCards(); break; case 1: BuildTower(); break; case 2: BuildDungeons(); break; case 3: BuildArena(); break; case 4: BuildRaid(); break; } } // ---------- 카드 4 ---------- void BuildCards() { var db = S.Db; var d = S.Save; var e = db.Economy; var now = S.Now; string boss = db.Heroes.Get(ArenaRules.RaidBoss(db, d, now)) != null ? db.Heroes.Get(ArenaRules.RaidBoss(db, d, now)).nameKo : ""; string[,] text = { { "100층 · 층마다 고정 편성 적 · 10층마다 보스", "보옥 · 도면 · 10층마다 보물 금 상자", "입장권 하루 " + e.TowerTicketsDaily + "(광고 +" + e.TowerAdTickets + ") · 진행 저장", "다음 " + ArenaRules.TowerNext(db, d) + "층 · 입장권 " + d.arena.towerTickets }, { "군량고(골드) · 대장간(승급 장비·운철) · 사당(스킬서·영웅 조각) — 요일 순환", "던전별 재화", "각 하루 " + (db.Dungeons.dungeons.Count > 0 ? db.Dungeons.dungeons[0].entriesPerDay : 0) + "(패스 +1 준비 중)", "오늘 열림 " + OpenCount(now) + "곳" }, { "비동기 5v5 · 상대 편성은 AI 가 조작(적 AI 재사용) · 주간 랭킹", "투기장 메달 → 영웅 조각 상점", "하루 " + db.Arena.EntriesPerDay, "메달 " + d.arena.medals + " · " + ArenaRules.Rank(d) + "위 · 남은 " + ArenaRules.ArenaLeft(db, d, now) }, { "주간 보스(여포·조조 …) · 누적 피해", "보물 명장 상자", "하루 " + e.RaidDaily, "이번 주 " + boss + " · 누적 " + d.arena.raidDamage.ToString("N0") }, }; float w = 508f, h = 700f; for (int i = 0; i < 4; i++) { int k = i; float x = 24f + (i % 2) * (w + 16f); float y = -18f - (i / 2) * (h + 20f); var card = UiKit.Btn("Card" + i, root, "", 30, UiKit.Paper, UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, y), new Vector2(w, h), () => SetPage(k + 1)); UiKit.Border(card.GetComponent(), UiKit.Line, 3f); var head = UiKit.TopBand("Head", card.transform, 0f, 110f); UiKit.Paint(head, UiKit.Red); UiKit.FillLabel(head, ModeNames[i] + (i == 3 ? "\n(이벤트)" : ""), 44, UiKit.White); string[] tags = { "규칙", "보상", "입장" }; for (int t = 0; t < 3; t++) { float ty = -128f - t * 160f; var tag = UiKit.Box("Tag" + t, card.transform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(20f, ty), new Vector2(96f, 44f), UiKit.Dark); tag.raycastTarget = false; UiKit.FillLabel(tag.transform, tags[t], 24, UiKit.White); UiKit.Label(card.transform, text[i, t], 28, UiKit.Ink, TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(20f, ty - 52f), new Vector2(w - 40f, 104f), false); } var st = UiKit.Box("Status", card.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 16f), new Vector2(w - 32f, 70f), new Color(0.95f, 0.9f, 0.8f)); st.raycastTarget = false; UiKit.FillLabel(st.transform, text[i, 3], 26, UiKit.Red); } } int OpenCount(System.DateTime now) { int n = 0; foreach (var dg in S.Db.Dungeons.dungeons) if (ArenaRules.OpenToday(dg, now)) n++; return n; } RectTransform Panel(string name, float top, float h) { var band = UiKit.TopBand(name, root, top, h, 24f, 24f); var img = UiKit.Paint(band, UiKit.Paper); UiKit.Border(img, UiKit.Line, 3f); return band; } void Title(string t) { var band = UiKit.TopBand("Title", root, 12f, 96f, 24f, 24f); UiKit.Paint(band, UiKit.Red); UiKit.FillLabel(band, t, 48, UiKit.White); } // ---------- ① 천하제일 탑 ---------- void BuildTower() { var db = S.Db; var d = S.Save; Title("천하제일 탑"); int next = ArenaRules.TowerNext(db, d); var f = db.Tower.Get(next); var stage = ArenaRules.TowerStage(db, Root.Battle.Config, next); var info = Panel("Floor", 124f, 520f); UiKit.Label(info, next + "층", 110, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -20f), new Vector2(600f, 140f)); var sb = new System.Text.StringBuilder(); sb.Append("클리어 최고 ").Append(d.arena.towerFloor).Append("층 / ").Append(db.Tower.Top).Append("층\n"); if (f != null) { sb.Append("적 편성 = ").Append(f.templateStage).Append(stage != null ? " 「" + stage.subtitle + "」 · " + stage.enemies.Count + "기" : " (템플릿 없음)").Append("\n"); sb.Append("적 HP ×").Append(f.hpMult.ToString("0.00")).Append(" · 공격 ×").Append(f.atkMult.ToString("0.00")).Append("\n"); sb.Append("보상 보옥 ").Append(f.rewardGem).Append(" · 도면 ").Append(f.rewardBlueprint); if (f.chest.Length > 0) sb.Append(" · ").Append(db.Chests.Get(f.chest).nameKo); } UiKit.Label(info, sb.ToString(), 34, UiKit.Ink, TextAnchor.UpperCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -180f), new Vector2(980f, 320f), false).lineSpacing = 1.2f; var tk = Panel("Tickets", 664f, 150f); UiKit.Label(tk, "입장권 " + d.arena.towerTickets + " (매일 " + db.Economy.TowerTicketsDaily + "장까지 채움 · 입장 = 1장)", 40, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(24f, 0f), new Vector2(720f, 80f)); var ad = UiKit.Btn("Ad", tk, "광고 +" + db.Economy.TowerAdTickets + "\n(시험 · " + d.dailyC.towerAd + "/1)", 32, UiKit.Grain, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-16f, 0f), new Vector2(240f, 110f), () => Root.Toast(S.Do(() => ArenaRules.TowerAd(S.Db, S.Save, S.Now)) ?? "입장권 +" + S.Db.Economy.TowerAdTickets + " (광고 SDK 미연동 · 시험 지급)")); ad.interactable = d.dailyC.towerAd < 1; var why = ArenaRules.CanTower(db, d, S.Now); if (stage == null && why == null) why = "템플릿 스테이지 없음"; var go = UiKit.Btn("Go", root, why == null ? "도전 — " + next + "층" : why, 48, why == null ? UiKit.Red : UiKit.Disabled, UiKit.White, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -840f), new Vector2(1032f, 150f), () => { var w = S.Do(() => ArenaRules.EnterTower(S.Db, S.Save, S.Now)); if (w != null) { Root.Toast(w); return; } Root.StartMode(ArenaRules.Tower, "", next, ArenaRules.TowerStage(S.Db, Root.Battle.Config, next), 0, 1); }); go.interactable = why == null; UiKit.Label(root, "진행 저장 · 패배해도 층 유지(입장권만 소모)", 28, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -1010f), new Vector2(1000f, 50f), false); } // ---------- ② 일일 던전 ---------- void BuildDungeons() { var db = S.Db; var d = S.Save; var now = S.Now; Title("일일 던전"); var today = DungeonDb.Weekdays[(int)now.ToLocalTime().DayOfWeek]; UiKit.Label(root, "오늘 = " + DayKo(today) + "요일 · 요일 순환 · 승리 시 입장 1회 소모", 30, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -116f), new Vector2(1000f, 50f), false); for (int i = 0; i < db.Dungeons.dungeons.Count; i++) { var dg = db.Dungeons.dungeons[i]; bool open = ArenaRules.OpenToday(dg, now); int left = ArenaRules.DungeonLeft(db, d, dg, now); var box = Panel("Dungeon_" + dg.id, 180f + i * 330f, 310f); box.GetComponent().color = open ? UiKit.Paper : new Color(0.88f, 0.85f, 0.8f); UiKit.Label(box, dg.nameKo, 52, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -16f), new Vector2(400f, 76f)); var days = new List(); foreach (var w in dg.weekdays) days.Add(w == today ? "" + DayKo(w) + "" : DayKo(w)); UiKit.Label(box, "요일 " + string.Join(" · ", days.ToArray()), 30, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -100f), new Vector2(600f, 50f), false); var rw = new List(); foreach (var k in dg.rewardKinds) rw.Add(RewardKo(k) + " " + dg.Amount(k)); UiKit.Label(box, "보상 " + string.Join(" · ", rw.ToArray()), 30, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -152f), new Vector2(640f, 50f), false); UiKit.Label(box, "적 = " + dg.templateStage + " 편성 · HP ×" + dg.hpMult.ToString("0.0#"), 28, UiKit.InkSoft, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -204f), new Vector2(640f, 50f), false); string id = dg.id; var why = ArenaRules.CanDungeon(db, d, id, now); var b = UiKit.Btn("Go", box, open ? "입장\n남은 " + left + "/" + dg.entriesPerDay + "" : "닫힘", 40, why == null ? UiKit.Red : UiKit.Disabled, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-24f, 0f), new Vector2(300f, 170f), () => { var w = ArenaRules.CanDungeon(S.Db, S.Save, id, S.Now); if (w != null) { Root.Toast(w); return; } Root.StartMode(ArenaRules.Dungeon, id, 0, ArenaRules.DungeonStage(S.Db, Root.Battle.Config, id), 0, 2); }); b.interactable = why == null; } } static string DayKo(string w) { switch (w) { case "sun": return "일"; case "mon": return "월"; case "tue": return "화"; case "wed": return "수"; case "thu": return "목"; case "fri": return "금"; case "sat": return "토"; } return w; } static string RewardKo(string k) { switch (k) { case "gold": return "골드"; case "gear": return "승급 장비"; case "iron": return "운철"; case "skill_book": return "스킬서"; case "shards": return "영웅 조각"; } return k; } // ---------- ③ 투기장 ---------- void BuildArena() { var db = S.Db; var d = S.Save; var now = S.Now; Title("투기장"); var me = Panel("Me", 124f, 200f); int left = ArenaRules.ArenaLeft(db, d, now); UiKit.Label(me, "메달 " + d.arena.medals + " · 주간 점수 " + d.arena.seasonScore + " · " + ArenaRules.Rank(d) + "위 / " + (d.arena.samples.Count + 1), 36, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -14f), new Vector2(980f, 56f)); var start = SaveStore.ParseIso(d.arena.seasonStart, now); int daysLeft = Mathf.Max(0, db.Arena.SeasonDays - (int)(now - start).TotalDays); UiKit.Label(me, "전적 " + d.arena.wins + "승 " + d.arena.losses + "패 · 오늘 남은 도전 " + left + "/" + db.Arena.EntriesPerDay + " · 시즌 " + daysLeft + "일 남음 · 승 +" + db.Arena.MedalWin + " / 패 +" + db.Arena.MedalLose + "\n" + "순위 보상 1~10위 보옥 " + db.Arena.RewardTop10Gem + " · 그 밖 " + db.Arena.RewardTop100Gem + (d.arena.lastSeason.Length > 0 ? " · " + d.arena.lastSeason : "") + "", 30, UiKit.InkSoft, TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -76f), new Vector2(980f, 110f), false); UiKit.Btn("Shop", me, "메달 상점", 30, UiKit.Jade, UiKit.White, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-16f, -12f), new Vector2(200f, 60f), MedalShop); ScrollRect sr; var list = UiKit.ScrollGrid("Opponents", root, 24f, 24f, 344f, 16f, new Vector2(1032f, 130f), new Vector2(0f, 12f), 1, out sr); for (int i = 0; i < d.arena.samples.Count; i++) { var s = d.arena.samples[i]; int k = i; var cell = UiKit.Cell(list, "Opp" + i, UiKit.Paper, null); UiKit.Border(cell, UiKit.Line, 2f); int power = 0; for (int j = 0; j < s.heroes.Count; j++) { var h = db.Heroes.Get(s.heroes[j]); if (h == null) continue; power += HeroRules.Compute(db, Root.Battle.Config, h, new HeroState { id = h.id, lv = s.lv, grade = db.Promotion.FirstGrade, star = Mathf.Clamp(h.rarity, 1, db.Evolve.MaxStar) }).power; UiKit.Portrait("H" + j, cell.transform, h, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(16f + j * 92f, 0f), new Vector2(84f, 104f), false, 1.8f, new Color(0.9f, 0.86f, 0.8f)); } UiKit.Label(cell.transform, s.name + "\n전투력 " + power.ToString("N0") + " · 점수 " + s.score + "", 30, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(490f, 0f), new Vector2(330f, 110f)); var why = ArenaRules.CanArena(db, d, k, now); var b = UiKit.Btn("Go", cell.transform, "도전", 34, why == null ? UiKit.Red : UiKit.Disabled, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-16f, 0f), new Vector2(180f, 96f), () => { var w = ArenaRules.CanArena(S.Db, S.Save, k, S.Now); if (w != null) { Root.Toast(w); return; } Root.StartMode(ArenaRules.Arena, "", k, ArenaRules.ArenaStage(S.Db, Root.Battle.Config, S.Save.arena.samples[k]), 0, 3); }); b.interactable = why == null; } } public void MedalShop() { var db = S.Db; var d = S.Save; var content = Root.ModalCustom("메달 상점 — 영웅 조각", 1500f, new OutGameRoot.ModalButton("닫기", UiKit.Dark, null)); UiKit.Label(content, "메달 " + d.arena.medals + " · " + db.Arena.ShopPrice + "메달 = 조각 " + db.Arena.ShopShards + " · 오늘 " + d.dailyC.arenaShop + "/" + db.Arena.ShopDaily, 30, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, new Vector2(860f, 50f)); ScrollRect sr; var grid = UiKit.ScrollGrid("Heroes", content, 0f, 0f, 60f, 0f, new Vector2(200f, 260f), new Vector2(12f, 12f), 4, out sr); foreach (var hs in d.heroes) { var h = db.Heroes.Get(hs.id); if (h == null) continue; string id = h.id; var cell = UiKit.Cell(grid, "Buy_" + id, UiKit.Paper, () => { var w = S.Do(() => ArenaRules.BuyShards(S.Db, S.Save, id, S.Now)); Root.Toast(w ?? h.nameKo + " 조각 +" + S.Db.Arena.ShopShards); if (w == null) MedalShop(); }); UiKit.Portrait("Face", cell.transform, h, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -8f), new Vector2(150f, 150f), false, 1.8f, new Color(0.9f, 0.86f, 0.8f)); UiKit.Label(cell.transform, h.nameKo + "\n조각 " + hs.shards + " · ★" + hs.star + "", 26, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 8f), new Vector2(190f, 90f)); } } // ---------- ④ 적장 토벌 ---------- void BuildRaid() { var db = S.Db; var d = S.Save; var now = S.Now; var e = db.Economy; Title("적장 토벌 (이벤트)"); string bossId = ArenaRules.RaidBoss(db, d, now); var h = db.Heroes.Get(bossId); var stage = ArenaRules.RaidStage(db, Root.Battle.Config, bossId); var box = Panel("Boss", 124f, 640f); UiKit.Portrait("Boss", box, h, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -20f), new Vector2(320f, 320f), false, 1f, new Color(0.85f, 0.8f, 0.75f)); var sb = new System.Text.StringBuilder(); sb.Append("이번 주 보스 — ").Append(h != null ? h.nameKo : bossId).Append("\n"); sb.Append("보스 HP ×").Append(e.RaidHpMult.ToString("0.#")).Append(" · ").Append(e.RaidTurns).Append("턴 동안 피해를 누적").Append(stage != null ? " · 적 " + stage.enemies.Count + "기" : "").Append("\n"); sb.Append("이번 주 누적 피해 ").Append(d.arena.raidDamage.ToString("N0")).Append(" (").Append(d.arena.raidWeek).Append(" 주)\n"); sb.Append("보상 ").Append(db.Chests.Get(e.RaidChest).nameKo).Append(" · 천기 보물 확률 ").Append((e.RaidArcanaPct * 100f).ToString("0.#")).Append("%"); UiKit.Label(box, sb.ToString(), 32, UiKit.Ink, TextAnchor.UpperCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -360f), new Vector2(980f, 270f), false).lineSpacing = 1.15f; var why = ArenaRules.CanRaid(db, d, now); if (stage == null && why == null) why = "보스 편성 없음"; var go = UiKit.Btn("Go", root, why == null ? "토벌 — 남은 " + ArenaRules.RaidLeft(db, d, now) + "/" + e.RaidDaily : why, 48, why == null ? UiKit.Red : UiKit.Disabled, UiKit.White, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -790f), new Vector2(1032f, 150f), () => { var w = ArenaRules.CanRaid(S.Db, S.Save, S.Now); if (w != null) { Root.Toast(w); return; } Root.StartMode(ArenaRules.Raid, bossId, 0, ArenaRules.RaidStage(S.Db, Root.Battle.Config, bossId), S.Db.Economy.RaidTurns, 4); }); go.interactable = why == null; UiKit.Label(root, "주간 보스 = 여포·조조 순환 · 주가 바뀌면 누적 초기화 · 천기 = 적장 토벌 전용", 28, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -960f), new Vector2(1000f, 50f), false); } } }