ThreeKingdomsSlingshot/Assets/Scripts/OutGame/Domestic/DomesticScreen.cs

467 lines
28 KiB
C#
Raw 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 UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
/// <summary>
/// 내정(하단 탭 · 기획 S9~S11 · 목업 img13·05·14 · PD #1093 C) — 상단 탭 3(한 항목 = 한 페이지):
/// ① 훈련 = 병종 탭 5 · 랭크 바(현재 랭크 마커 + 랭크 보너스 · 다음 랭크 승급 골드 · 남은 레벨) · 카드 3(HP·공·방 Lv n/Max · 골드) · 3항목 Max → 랭크 승급
/// ② 순찰 = 기준 챕터 · 누적 n/최대 시간 · 시간당 수확 4줄 · 쌓인 보상 4칸 · 수확 · 광고 2배(스텁) · 즉시 수확(보옥)
/// ③ 개발 = 갈래 ◀ 상업·농업·공업 ▶ 한 페이지씩 · 세로 트리(선행 조건 깊이로 줄 · 노드 n/Max · 큰 노드) · 노드 탭 = 강화 팝업 · 도면·골드 · 트리 초기화.
/// </summary>
public class DomesticScreen : OutGameScreen
{
public static readonly string[] PageNames = { "훈련", "순찰", "개발" };
int page;
int typeIdx;
int branchIdx;
readonly List<Button> tabs = new List<Button>();
readonly List<RectTransform> pages = new List<RectTransform>();
public int Page
{
get { return page; }
}
/// <summary>병종 탭 순서 = training.csv 행 순서(보·창·기·궁·책).</summary>
public List<UnitType> TrainTypes
{
get
{
var l = new List<UnitType>();
foreach (var r in S.Db.Training.rows)
if (!l.Contains(r.type)) l.Add(r.type);
return l;
}
}
public UnitType TrainType
{
get
{
var l = TrainTypes;
return l.Count > 0 ? l[Mathf.Clamp(typeIdx, 0, l.Count - 1)] : UnitType.Infantry;
}
}
public string Branch
{
get
{
var b = S.Db.Dev.Branches;
return b.Count > 0 ? b[Mathf.Clamp(branchIdx, 0, b.Count - 1)] : "";
}
}
/// <summary>개발 페이지에 그려진 노드 수(테스트).</summary>
public int NodeCount { get; private set; }
public override void Build()
{
Tab = 2;
Header("내정", "훈련 · 순찰 · 개발", true);
var body = Body();
for (int i = 0; i < PageNames.Length; i++)
{
int k = i;
tabs.Add(UiKit.Btn("Tab" + i, body, PageNames[i], 36, UiKit.Paper, UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * 348f, -18f), new Vector2(332f, 84f), () => SetPage(k)));
pages.Add(UiKit.Fill("Page" + i, body, 0f, 0f, 118f, 0f));
}
}
public void SetPage(int p)
{
page = Mathf.Clamp(p, 0, pages.Count - 1);
Refresh();
}
public void SetTrainType(UnitType t)
{
typeIdx = Mathf.Max(0, TrainTypes.IndexOf(t));
Refresh();
}
public void SetBranch(int i)
{
int n = Mathf.Max(1, S.Db.Dev.Branches.Count);
branchIdx = ((i % n) + n) % n;
Refresh();
}
public override void Refresh()
{
for (int i = 0; i < tabs.Count; i++)
{
bool on = i == page;
tabs[i].GetComponent<Image>().color = on ? UiKit.Dark : UiKit.Paper;
UiKit.LabelOf(tabs[i]).color = on ? UiKit.White : UiKit.Ink;
pages[i].gameObject.SetActive(on);
}
UiKit.Clear(pages[page]);
if (page == 0) BuildTraining(pages[0]);
else if (page == 1) BuildPatrol(pages[1]);
else BuildDev(pages[2]);
}
// ---------- ① 훈련 ----------
void BuildTraining(RectTransform p)
{
var db = S.Db;
var d = S.Save;
var t = TrainType;
var types = TrainTypes;
for (int i = 0; i < types.Count; i++)
{
var ut = types[i];
bool on = ut == t;
UiKit.Btn("Type" + i, p, UnitTypeInfo.Mark(ut), 40, on ? UiKit.Red : UiKit.Paper, on ? UiKit.White : UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * 208f, -6f), new Vector2(196f, 78f), () => SetTrainType(ut));
}
int rank = TrainingRules.Rank(d, t);
var stage = UiKit.TopBand("Unit", p, 100f, 470f, 24f, 24f);
UiKit.Paint(stage, new Color(0.24f, 0.19f, 0.25f));
UiKit.Label(stage, TrainingRules.RankName(db, rank) + " <size=40>랭크 " + rank + "/" + db.Training.MaxRank + "</size>", 60, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -20f), new Vector2(900f, 90f));
var halo = UiKit.Dot("Halo", stage, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -40f), 300f, new Color(1f, 1f, 1f, 0.1f));
halo.raycastTarget = false;
var face = UiKit.Dot("Face", stage, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -40f), 220f, UiKit.TypeColor(t));
UiKit.FillLabel(face.transform, UnitTypeInfo.Mark(t), 110, UiKit.White);
UiKit.Label(stage, UnitTypeInfo.Korean(t) + " 영웅·병사 전원", 30, new Color(0.85f, 0.8f, 0.86f), TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 14f), new Vector2(900f, 50f), false);
// 랭크 바: 현재 랭크 마커 + 랭크 보너스 · 다음 랭크 승급 비용 · 남은 레벨
var bar = UiKit.TopBand("RankBar", p, 586f, 230f, 24f, 24f);
UiKit.Paint(bar, new Color(0.17f, 0.14f, 0.19f));
UiKit.Label(bar, "랭크 진행", 30, new Color(0.85f, 0.8f, 0.86f), TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -12f), new Vector2(300f, 50f));
int left = TrainingRules.Remaining(db, d, t);
int total = db.Training.MaxLevel * TrainingTable.Stats.Length;
var fill = UiKit.Bar("Progress", bar, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(24f, 6f), new Vector2(760f, 30f), new Color(0.32f, 0.28f, 0.35f), new Color(0.95f, 0.68f, 0.2f));
float v = total > 0 ? (total - left) / (float)total : 0f;
UiKit.SetBar(fill, v);
var marker = UiKit.Dot("Marker", bar, new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(24f + 760f * v, 6f), 70f, new Color(0.95f, 0.68f, 0.2f));
UiKit.FillLabel(marker.transform, "▲", 34, UiKit.Ink);
var r = db.Training.RankOf(rank);
UiKit.Label(bar, TrainingRules.RankName(db, rank) + " · 랭크 보너스 +" + (r != null ? r.rankPct : 0f).ToString("0.#") + "%", 32, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f, 16f), new Vector2(700f, 54f));
string promo;
if (rank >= db.Training.MaxRank) promo = "최종 랭크";
else promo = "다음 랭크 승급 비용\n<size=40>골드 ×" + TrainingRules.PromoteCost(db, d, t).ToString("N0") + "</size>";
UiKit.Label(bar, promo, 26, new Color(0.85f, 0.8f, 0.86f), TextAnchor.UpperRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -12f), new Vector2(300f, 110f), false);
UiKit.Label(bar, left + "\n<size=24>남은 레벨</size>", 40, UiKit.White, TextAnchor.LowerRight, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-24f, 12f), new Vector2(200f, 90f));
// 카드 3: HP · 공 · 방
string[] names = { "HP", "공격력", "방어력" };
string[] marks = { "H", "공", "방" };
float cw = 330f;
for (int i = 0; i < 3; i++)
{
int k = i;
int lv = TrainingRules.Lv(d, t, i);
bool max = lv >= db.Training.MaxLevel;
var card = UiKit.Box("Card" + i, p, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * (cw + 21f), -838f), new Vector2(cw, 360f), max ? new Color(0.85f, 0.81f, 0.9f) : UiKit.Paper);
UiKit.Border(card, UiKit.Line, 3f);
var head = UiKit.TopBand("Head", card.transform, 0f, 72f);
UiKit.Paint(head, max ? UiKit.Purple : new Color(0.93f, 0.87f, 0.7f));
UiKit.FillLabel(head, max ? "Max" : "LV." + lv, 36, max ? UiKit.White : UiKit.Ink);
var dot = UiKit.Dot("Icon", card.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -86f), 96f, new Color(0.9f, 0.86f, 0.8f));
UiKit.FillLabel(dot.transform, marks[i], 40, UiKit.Ink);
UiKit.Label(card.transform, names[i], 38, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -190f), new Vector2(310f, 50f));
UiKit.Label(card.transform, "LV " + lv + "/" + db.Training.MaxLevel + " +" + TrainingRules.Bonus(db, d, t, TrainingTable.Stats[i]).ToString("0.#") + "%", 26, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -232f), new Vector2(310f, 36f), false);
if (max) UiKit.Label(card.transform, "<color=#3a8a30>✔</color>", 48, UiKit.Ok, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 20f), new Vector2(300f, 70f));
else
{
var b = UiKit.Btn("Up", card.transform, TrainingRules.Cost(db, d, t, i).ToString("N0") + " 골드", 30, UiKit.Coin, UiKit.Ink, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 14f), new Vector2(280f, 66f), () =>
{
var why = S.Do(() => TrainingRules.Level(S.Db, S.Save, TrainType, k));
if (why != null) Root.Toast(why);
});
b.interactable = TrainingRules.CanLevel(db, d, t, i) == null;
}
}
if (left == 0 && rank < db.Training.MaxRank)
{
var pb = UiKit.Btn("Promote", p, "랭크 승급 → " + TrainingRules.RankName(db, rank + 1) + " · 골드 " + TrainingRules.PromoteCost(db, d, t).ToString("N0"), 36, UiKit.Red, UiKit.White, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 90f), new Vector2(900f, 110f), () =>
{
var why = S.Do(() => TrainingRules.Promote(S.Db, S.Save, TrainType));
Root.Toast(why ?? UnitTypeInfo.Korean(TrainType) + " 랭크 승급 → " + TrainingRules.RankName(S.Db, TrainingRules.Rank(S.Save, TrainType)));
});
pb.interactable = TrainingRules.CanPromote(db, d, t) == null;
}
UiKit.Label(p, UnitTypeInfo.Korean(t) + " 전원 HP +" + TrainingRules.Bonus(db, d, t, "hp").ToString("0.#") + "% · 공 +" + TrainingRules.Bonus(db, d, t, "atk").ToString("0.#") + "% · 방 +" +
TrainingRules.Bonus(db, d, t, "def").ToString("0.#") + "% (3항목 Max → 랭크 승급 · 레벨 리셋 · 랭크 보너스 유지)", 26, UiKit.InkSoft, TextAnchor.MiddleCenter,
new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 20f), new Vector2(1030f, 60f), false);
}
// ---------- ② 순찰 ----------
void BuildPatrol(RectTransform p)
{
var db = S.Db;
var d = S.Save;
var e = db.Economy;
var row = PatrolRules.Row(db, d);
float cap = PatrolRules.CapHours(db, d);
float h = PatrolRules.Hours(db, d, S.Now);
var basis = Panel(p, "Basis", 0f, 110f);
UiKit.Label(basis, "기준: 마지막 클리어 챕터 " + (row != null ? row.chapter : 0) + "장", 38, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -8f), new Vector2(900f, 54f));
UiKit.Label(basis, "제" + Mathf.Max(1, d.chapterMax) + "장 · 시간당 수확 · 오프라인 누적", 28, UiKit.InkSoft, TextAnchor.MiddleLeft, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f, 8f), new Vector2(900f, 44f), false);
var acc = Panel(p, "Accum", 126f, 170f);
UiKit.Label(acc, "누적 " + FormatHours(h) + " / 최대 " + FormatHours(cap), 40, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -8f), new Vector2(900f, 56f));
var fill = UiKit.Bar("Bar", acc, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -6f), new Vector2(980f, 34f), new Color(0.88f, 0.84f, 0.77f), UiKit.Red);
UiKit.SetBar(fill, cap > 0f ? h / cap : 0f);
UiKit.Label(acc, "성장 패스 24시간(준비 중) · 농업 개발로 +" + Mathf.RoundToInt(DevRules.Mod(db, d, DevRules.PatrolCap)) + "분", 26, UiKit.Red, TextAnchor.MiddleLeft, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f, 8f), new Vector2(900f, 44f), false);
var rate = Panel(p, "Rate", 312f, 400f);
UiKit.Label(rate, "시간당 수확", 38, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -8f), new Vector2(600f, 56f));
string[] rn = { "골드", "병사 경험", "승급 장비", "보물 청동 상자" };
string[] rv =
{
PatrolRules.GoldPerHour(db, d).ToString("N0") + " / 시간",
PatrolRules.ExpPerHour(db, d).ToString("N0") + " / 시간(출전 편성)",
UiKit.GradeKo(PatrolRules.GearGrade(db, d)) + " · " + PatrolRules.GearPerHour(db, d).ToString("0.##") + " / 시간",
PatrolRules.ChestPerHour(db, d).ToString("0.##") + " / 시간",
};
Color[] rc = { UiKit.Coin, new Color(0.8f, 0.48f, 0.38f), UiKit.Ok, new Color(0.7f, 0.48f, 0.3f) };
for (int i = 0; i < 4; i++)
{
var line = UiKit.Box("Row" + i, rate, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -76f - i * 78f), new Vector2(980f, 68f), new Color(0.95f, 0.92f, 0.86f));
UiKit.Dot("Dot", line.transform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(16f, 0f), 50f, rc[i]);
UiKit.Label(line.transform, rn[i], 34, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(86f, 0f), new Vector2(400f, 56f));
UiKit.Label(line.transform, rv[i], 30, UiKit.Ink, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-20f, 0f), new Vector2(520f, 56f), false);
}
var y = PatrolRules.Preview(db, d, h, 1f);
var got = Panel(p, "Stored", 728f, 220f);
UiKit.Label(got, "쌓인 보상", 38, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -8f), new Vector2(600f, 56f));
string[] sv = { "골드\n" + y.gold.ToString("N0"), "병사 경험\n" + y.exp.ToString("N0"), "승급 장비\n" + y.gear, "청동 상자\n" + y.chests };
for (int i = 0; i < 4; i++)
{
var chip = UiKit.Box("Chip" + i, got, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f + i * 250f, 16f), new Vector2(232f, 130f), new Color(0.95f, 0.92f, 0.86f));
UiKit.Border(chip, rc[i], 3f);
UiKit.FillLabel(chip.transform, sv[i], 30, UiKit.Ink);
}
var hb = UiKit.Btn("Harvest", p, "수확", 64, UiKit.Red, UiKit.White, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -966f), new Vector2(1030f, 140f), () => DoHarvest(false));
hb.interactable = h > 0f;
EconomyRules.RollDaily(d, S.Now);
var ad = UiKit.Btn("Ad", p, "광고 2배 수확\n<size=26>" + d.adsUsed.patrol + "/" + e.PatrolAdDaily + "회/일 (시험)</size>", 36, UiKit.Dark, UiKit.White, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -1124f), new Vector2(505f, 130f), () => DoHarvest(true));
ad.interactable = h > 0f && d.adsUsed.patrol < e.PatrolAdDaily;
UiKit.Btn("Instant", p, "즉시 수확 " + FormatHours(e.PatrolInstantHours) + "분\n<size=26>보옥 " + e.PatrolInstantGem + "</size>", 36, UiKit.Jade, UiKit.White, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -1124f), new Vector2(505f, 130f), () =>
{
string why;
var r = S.InstantHarvest(out why);
if (r == null) Root.Toast(why);
else ShowYield("즉시 수확", r);
});
}
void DoHarvest(bool ad)
{
string why;
var r = S.Harvest(ad, out why);
if (r == null) Root.Toast(why);
else ShowYield(ad ? "광고 2배 수확" : "수확", r);
}
void ShowYield(string title, PatrolYield y)
{
var sb = new System.Text.StringBuilder();
sb.Append(FormatHours(y.hours)).Append(y.mult > 1f ? " × " + y.mult : "").Append(" 분량\n\n");
sb.Append("골드 +").Append(y.gold.ToString("N0")).Append("\n병사 경험 +").Append(y.exp).Append(" (").Append(y.expTo.Count).Append("명)");
if (y.gearIds.Count > 0)
{
var names = new List<string>();
foreach (var g in y.gearIds) names.Add(S.Db.Gear.Get(g).nameKo);
sb.Append("\n장비 ").Append(string.Join(" · ", names.ToArray()));
}
if (y.chestIds.Count > 0) sb.Append("\n").Append(S.Db.Chests.Get(y.chestIds[0]).nameKo).Append(" ×").Append(y.chestIds.Count);
Root.Modal(title, sb.ToString(), new OutGameRoot.ModalButton("확인", UiKit.Dark, null));
}
public static string FormatHours(float h)
{
int min = Mathf.FloorToInt(h * 60f + 1e-3f);
return (min / 60) + "시간" + (min % 60 > 0 ? " " + (min % 60) + "분" : "");
}
static RectTransform Panel(RectTransform p, string name, float top, float h)
{
var band = UiKit.TopBand(name, p, top, h, 24f, 24f);
var img = UiKit.Paint(band, UiKit.Paper);
UiKit.Border(img, UiKit.Line, 3f);
return band;
}
// ---------- ③ 개발 ----------
void BuildDev(RectTransform p)
{
var db = S.Db;
var d = S.Save;
var branches = db.Dev.Branches;
if (branches.Count == 0) return;
int n = branches.Count;
string br = Branch;
UiKit.Btn("Prev", p, "◀", 44, UiKit.Paper, UiKit.Ink, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-330f, -6f), new Vector2(100f, 90f), () => SetBranch(branchIdx - 1));
UiKit.Btn("Next", p, "▶", 44, UiKit.Paper, UiKit.Ink, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(330f, -6f), new Vector2(100f, 90f), () => SetBranch(branchIdx + 1));
UiKit.Label(p, BranchKo(branches[(branchIdx + n - 1) % n]), 24, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-330f, -96f), new Vector2(140f, 34f), false);
UiKit.Label(p, BranchKo(branches[(branchIdx + 1) % n]), 24, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(330f, -96f), new Vector2(140f, 34f), false);
var title = UiKit.Box("Branch", p, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -6f), new Vector2(420f, 90f), UiKit.Red);
UiKit.FillLabel(title.transform, BranchKo(br), 48, UiKit.White);
for (int i = 0; i < n; i++) UiKit.Dot("Page" + i, p, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2((i - (n - 1) / 2f) * 30f, -106f), 16f, i == branchIdx ? UiKit.Red : UiKit.Slot);
UiKit.Label(p, BranchKo(br) + " — " + BranchGoal(br), 30, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -136f), new Vector2(900f, 44f), false);
// 깊이(선행 조건) = 줄 · 같은 줄 = 가로로
var nodes = db.Dev.InBranch(br);
var depth = new Dictionary<string, int>();
int maxDepth = 0;
foreach (var nd in nodes)
{
int dp = 0;
foreach (var r in nd.requires)
{
int x;
if (depth.TryGetValue(r.node, out x)) dp = Mathf.Max(dp, x + 1);
}
depth[nd.id] = dp;
maxDepth = Mathf.Max(maxDepth, dp);
}
var tree = UiKit.Fill("Tree", p, 24f, 24f, 190f, 190f);
float rowH = Mathf.Min(190f, 1050f / (maxDepth + 1));
var pos = new Dictionary<string, Vector2>();
for (int dp = 0; dp <= maxDepth; dp++)
{
var row = new List<DevTree.Node>();
foreach (var nd in nodes)
if (depth[nd.id] == dp) row.Add(nd);
for (int i = 0; i < row.Count; i++)
pos[row[i].id] = new Vector2((i - (row.Count - 1) / 2f) * (row.Count > 1 ? 840f / (row.Count - 1) : 0f), -rowH * (dp + 0.5f));
}
foreach (var nd in nodes)
foreach (var r in nd.requires)
if (pos.ContainsKey(r.node)) Line(tree, pos[r.node], pos[nd.id], DevRules.Level(d, r.node) >= r.lv ? new Color(0.45f, 0.36f, 0.3f) : UiKit.Slot);
NodeCount = 0;
foreach (var nd in nodes)
{
var node = nd;
int lv = DevRules.Level(d, nd.id);
bool open = DevRules.Unlocked(d, nd);
bool max = lv >= nd.lvMax;
float size = nd.Big ? Mathf.Min(150f, rowH * 0.78f) : Mathf.Min(110f, rowH * 0.6f);
var c = UiKit.Btn("Node_" + nd.id, tree, "", 30, max ? UiKit.Coin : open ? UiKit.Paper : new Color(0.55f, 0.51f, 0.48f), UiKit.Ink, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), pos[nd.id], new Vector2(size, size), () => NodePopup(node.id));
var img = c.GetComponent<Image>();
img.sprite = UiKit.Circle;
img.type = Image.Type.Simple;
UiKit.Border(img, nd.Big ? new Color(0.78f, 0.62f, 0.25f) : UiKit.Line, nd.Big ? 6f : 3f);
UiKit.LabelOf(c).text = open ? nd.nameKo.Substring(0, 1) : "잠";
UiKit.LabelOf(c).fontSize = nd.Big ? 60 : 44;
UiKit.LabelOf(c).color = open ? (max ? UiKit.White : UiKit.Ink) : new Color(0.85f, 0.82f, 0.78f);
var pill = UiKit.Box("Lv", c.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0.5f), new Vector2(0f, 0f), new Vector2(110f, 40f), UiKit.Dark);
UiKit.FillLabel(pill.transform, lv + "/" + nd.lvMax, 24, UiKit.White);
float side = pos[nd.id].x <= 0f ? 1f : -1f;
var lab = UiKit.Label(tree, nd.nameKo, 28, open ? UiKit.Ink : UiKit.InkSoft, side > 0 ? TextAnchor.MiddleLeft : TextAnchor.MiddleRight, new Vector2(0.5f, 1f),
new Vector2(side > 0 ? 0f : 1f, 0.5f), pos[nd.id] + new Vector2(side * (size / 2f + 10f), 0f), new Vector2(240f, 50f));
lab.horizontalOverflow = HorizontalWrapMode.Overflow;
NodeCount++;
}
var foot = UiKit.Box("Foot", p, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 16f), new Vector2(1032f, 150f), UiKit.Paper);
UiKit.Border(foot, UiKit.Line, 3f);
var bp = UiKit.Dot("Bp", foot.transform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(20f, 0f), 76f, new Color(0.33f, 0.45f, 0.62f));
UiKit.FillLabel(bp.transform, "도", 36, UiKit.White);
UiKit.Label(foot.transform, "도면 ×" + d.inv.blueprint, 38, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(110f, 0f), new Vector2(260f, 60f));
var gd = UiKit.Dot("Gold", foot.transform, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(380f, 0f), 76f, UiKit.Coin);
UiKit.FillLabel(gd.transform, "골", 36, UiKit.White);
UiKit.Label(foot.transform, d.gold.ToString("N0"), 38, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(470f, 0f), new Vector2(220f, 60f));
UiKit.Btn("Reset", foot.transform, "트리 초기화\n<size=24>보옥 " + db.Economy.DevResetGem + " · 도면 환급</size>", 34, UiKit.Dark, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-16f, 0f), new Vector2(320f, 120f), () =>
{
Root.Modal("트리 초기화", "모든 개발 노드를 0 으로 되돌립니다.\n보옥 " + db.Economy.DevResetGem + " 소모 · 쓴 도면 " + S.Save.dev.blueprintSpent + " 환급 · 골드 환급 없음",
new OutGameRoot.ModalButton("초기화", UiKit.Red, () => Root.Toast(S.Do(() => DevRules.Reset(S.Db, S.Save)) ?? "개발 트리 초기화")),
new OutGameRoot.ModalButton("취소", UiKit.Dark, null));
});
}
static void Line(RectTransform parent, Vector2 a, Vector2 b, Color c)
{
var mid = (a + b) / 2f;
var dir = b - a;
var rt = UiKit.Rect("Link", parent, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), mid, new Vector2(dir.magnitude, 8f));
rt.localRotation = Quaternion.Euler(0f, 0f, Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg);
UiKit.Paint(rt, c, false);
}
public void NodePopup(string id)
{
var db = S.Db;
var d = S.Save;
var n = db.Dev.Get(id);
if (n == null) return;
int lv = DevRules.Level(d, id);
var sb = new System.Text.StringBuilder();
sb.Append(BranchKo(n.branch)).Append(n.Big ? " · 큰 노드" : "").Append(" Lv ").Append(lv).Append(" / ").Append(n.lvMax).Append("\n\n");
foreach (var k in n.EffectKeys)
{
string what;
DevRules.Keys.TryGetValue(k, out what);
sb.Append(what).Append(" ").Append((n.pctPerLv * lv).ToString("0.##")).Append(" → ").Append((n.pctPerLv * Mathf.Min(n.lvMax, lv + 1)).ToString("0.##")).Append("\n");
}
if (n.requires.Count > 0)
{
sb.Append("\n선행: ");
for (int i = 0; i < n.requires.Count; i++)
{
var r = n.requires[i];
bool ok = DevRules.Level(d, r.node) >= r.lv;
sb.Append(i > 0 ? " · " : "").Append(ok ? "<color=#3a6a30>" : "<color=#a03a22>").Append(db.Dev.Get(r.node).nameKo).Append(" Lv ").Append(r.lv).Append("</color>");
}
sb.Append("\n");
}
if (lv < n.lvMax)
{
int gold, bp;
DevRules.NextCost(n, lv, out gold, out bp);
sb.Append("\n다음 비용 골드 ").Append(gold.ToString("N0")).Append(bp > 0 ? " · 도면 " + bp : "");
}
var why = DevRules.CanUpgrade(db, d, id);
Root.Modal(n.nameKo, sb.ToString(),
new OutGameRoot.ModalButton(why == null ? "개발 Lv+1" : why, why == null ? UiKit.Red : UiKit.Disabled, () =>
{
var w = S.Do(() => DevRules.Upgrade(S.Db, S.Save, id));
if (w == null) NodePopup(id);
else Root.Toast(w);
}, true),
new OutGameRoot.ModalButton("닫기", UiKit.Dark, null));
}
public static string BranchKo(string b)
{
switch (b)
{
case "commerce": return "상업";
case "agri": return "농업";
case "industry": return "공업";
}
return b;
}
static string BranchGoal(string b)
{
switch (b)
{
case "commerce": return "재화 획득 효율";
case "agri": return "자원 획득 효율";
case "industry": return "장비 획득";
}
return "";
}
public override bool OnBack()
{
return false;
}
}
}