574 lines
36 KiB
C#
574 lines
36 KiB
C#
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;
|
||
if (UISkin.On) UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false); // 목록형 바탕(Fable §5-3)
|
||
Header("내정", "훈련 · 순찰 · 개발", true);
|
||
var body = Body();
|
||
for (int i = 0; i < PageNames.Length; i++)
|
||
{
|
||
int k = i;
|
||
tabs.Add(UISkin.On
|
||
? UiKit.SubTab(body, "Tab" + i, PageNames[i], 24f + i * 348f, 18f, 332f, 84f, () => SetPage(k))
|
||
: 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;
|
||
if (UISkin.On) UiKit.SetSubTab(tabs[i], on);
|
||
else
|
||
{
|
||
tabs[i].GetComponent<Image>().color = on ? UiKit.Red : 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)
|
||
{
|
||
if (UISkin.Has("panel_field") && UISkin.Has("btn_l_green")) BuildPatrolKit(pages[1]);
|
||
else 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 ? UiKit.Purple : UiKit.Paper);
|
||
UiKit.Border(card, UiKit.Line, 3f);
|
||
var head = UiKit.TopBand("Head", card.transform, 0f, 72f);
|
||
UiKit.Paint(head, max ? UiKit.Purple : UiKit.Dark);
|
||
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, UiKit.Slot);
|
||
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), UiKit.Slot, 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) };
|
||
string[] rIcon = { "cur_gold", "cur_exp", "cur_gear", "chest_bronze" };
|
||
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), UiKit.Dark);
|
||
if (UiKit.Icon(line.transform, rIcon[i], new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(12f, 0f), 58f) == null)
|
||
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 };
|
||
string[] sn = { y.gold.ToString("N0"), y.exp.ToString("N0"), y.gear.ToString(), y.chests.ToString() };
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
if (UISkin.Has("item_bg") && UISkin.Icon(rIcon[i]) != null)
|
||
{
|
||
// 쌓인 보상 칸(설계서 §3⑦) = 팩 ItemFrame 5층 + 아이콘 + 수량
|
||
var cell = UiKit.Rect("Chip" + i, got, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f + i * 250f, 16f), new Vector2(232f, 130f));
|
||
UiKit.ItemSlot(cell, "gear_blue_", true);
|
||
UiKit.DropShadow(UiKit.Icon(cell, rIcon[i], new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(14f, 4f), 84f), 4f);
|
||
var v = UiKit.Title(UiKit.Label(cell, sn[i], 38, UiKit.White, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-16f, 0f), new Vector2(130f, 60f)), true);
|
||
v.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
continue;
|
||
}
|
||
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), UiKit.Dark);
|
||
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);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 순찰(Fable 제안서 §4-7 · #1104) — 섹션 「순찰」(기준 줄 + 레벨형 게이지 군량색 + 배지 「7h」) → 「시간당 수확」 스탯 칸 4(아이콘 64 · 라벨 · 값 Sen) →
|
||
/// 「쌓인 보상」 타일 4(ItemFrame 132 + 수량 칩) → 수확 Primary 녹 · 광고 2배 Secondary 하늘 + 광고 글리프 · 즉시 Secondary 보라 + 보옥 비용.
|
||
/// </summary>
|
||
void BuildPatrolKit(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 soft = UiKit.Col("text_soft", UiKit.InkSoft);
|
||
const float W = 1032f, Fw = W - 12f, GaugeBar = 800f;
|
||
|
||
// ① 순찰
|
||
RectTransform ar;
|
||
var a = UiKit.SectionPanel(p, "Patrol", 24f, 6f, W, 236f, false, false, out ar);
|
||
UiKit.SectionTitle(ar, "순찰");
|
||
var basis = UiKit.Label(ar, "기준 = 마지막 클리어 " + (row != null ? row.chapter : 0) + "장 · 오프라인 누적", 26, soft, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -12f), new Vector2(620f, 48f), false);
|
||
basis.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
Text badge;
|
||
var fill = UiKit.Gauge(a, "Gauge", 16f, 66f, GaugeBar, "lv_badge", "grain", out badge);
|
||
UiKit.SetGauge(fill, cap > 0f ? h / cap : 0f, GaugeBar);
|
||
badge.text = Mathf.FloorToInt(h) + "h";
|
||
badge.fontSize = 30;
|
||
var cur = UiKit.Title(UiKit.Label(a, FormatHours(h) + " / " + FormatHours(cap), 30, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -164f), new Vector2(260f, 52f)), true);
|
||
cur.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
UiKit.Label(a, "성장 패스 24시간(준비 중) · 농업 개발 +" + Mathf.RoundToInt(DevRules.Mod(db, d, DevRules.PatrolCap)) + "분", 24, soft, TextAnchor.MiddleLeft, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -168f), new Vector2(Fw - 320f, 44f), false).alignment = TextAnchor.MiddleRight;
|
||
|
||
// ② 시간당 수확
|
||
RectTransform br;
|
||
var b = UiKit.SectionPanel(p, "Rate", 24f, 258f, W, 448f, false, false, out br);
|
||
UiKit.SectionTitle(br, "시간당 수확");
|
||
string[] rn = { "골드", "병사 경험", "승급 장비", "보물 청동 상자" };
|
||
string[] rv =
|
||
{
|
||
PatrolRules.GoldPerHour(db, d).ToString("N0"),
|
||
PatrolRules.ExpPerHour(db, d).ToString("N0"),
|
||
PatrolRules.GearPerHour(db, d).ToString("0.##"),
|
||
PatrolRules.ChestPerHour(db, d).ToString("0.##"),
|
||
};
|
||
string[] rs = { "/ 시간", "/ 시간 · 출전 편성", "/ 시간 · " + UiKit.GradeKo(PatrolRules.GearGrade(db, d)) + " 등급", "/ 시간" };
|
||
string[] rIcon = { "cur_gold", "cur_exp", "cur_gear", "chest_bronze" };
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
var cell = UiKit.Skin(UiKit.At(b, "Row" + i, 14f, 76f + i * 90f, Fw - 28f, 82f), "panel12", UiKit.Col("row_on_field", UiKit.Paper)).transform;
|
||
var ic = UiKit.Icon(cell, rIcon[i], new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(50f, 0f), 64f);
|
||
if (ic != null) UiKit.DropShadow(ic, 3f);
|
||
UiKit.Label(cell, rn[i], 30, soft, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(98f, 0f), new Vector2(300f, 60f), false);
|
||
var sub = UiKit.Label(cell, rs[i], 24, soft, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-24f, -2f), new Vector2(300f, 50f), false);
|
||
sub.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
var val = UiKit.Title(UiKit.Label(cell, rv[i], 44, UiKit.White, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-24f - Mathf.Ceil(sub.preferredWidth) - 14f, 2f), new Vector2(260f, 70f)), true);
|
||
val.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
}
|
||
|
||
// ③ 쌓인 보상
|
||
var y = PatrolRules.Preview(db, d, h, 1f);
|
||
RectTransform cr;
|
||
var c = UiKit.SectionPanel(p, "Stored", 24f, 722f, W, 262f, false, false, out cr);
|
||
UiKit.SectionTitle(cr, "쌓인 보상");
|
||
string[] sn = { y.gold.ToString("N0"), y.exp.ToString("N0"), y.gear.ToString(), y.chests.ToString() };
|
||
const float Tile = 132f, Gap = 64f;
|
||
float tx0 = (Fw - 4f * Tile - 3f * Gap) * 0.5f;
|
||
for (int i = 0; i < 4; i++)
|
||
UiKit.RewardTile(c, "Tile" + i, tx0 + i * (Tile + Gap), 90f, Tile, i == 2 ? "gear_" + PatrolRules.GearGrade(db, d) + "_" : "gear_white_", rIcon[i], sn[i], i == 2);
|
||
|
||
// ④ 버튼
|
||
EconomyRules.RollDaily(d, S.Now);
|
||
var hb = UiKit.SkinBtn(p, "Harvest", h > 0f ? "btn_l_green" : "btn_gray", UiKit.White, "수확", 52, UiKit.Col("text_on_green", UiKit.White), (1080f - 600f) * 0.5f, 1004f, 600f, 128f, () => DoHarvest(false));
|
||
hb.interactable = h > 0f;
|
||
bool adOk = h > 0f && d.adsUsed.patrol < e.PatrolAdDaily;
|
||
var ad = UiKit.SkinBtn(p, "Ad", adOk ? "btn_sky" : "btn_gray", UiKit.White, "광고 2배", 38, UiKit.White, 24f, 1152f, 505f, 112f, () => DoHarvest(true));
|
||
ad.interactable = adOk;
|
||
UiKit.BtnCost(ad, "ad", d.adsUsed.patrol + "/" + e.PatrolAdDaily, 34f, 28);
|
||
var ins = UiKit.SkinBtn(p, "Instant", "btn_purple", UiKit.White, "즉시 " + FormatHours(e.PatrolInstantHours), 38, UiKit.White, 1056f - 505f, 1152f, 505f, 112f, () =>
|
||
{
|
||
string why;
|
||
var r = S.InstantHarvest(out why);
|
||
if (r == null) Root.Toast(why);
|
||
else ShowYield("즉시 수확", r);
|
||
});
|
||
UiKit.BtnCost(ins, "cur_gem", e.PatrolInstantGem.ToString(), 40f, 32);
|
||
}
|
||
|
||
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) : UiKit.InkSoft;
|
||
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);
|
||
UiKit.IconOrDot(foot.transform, "cur_blueprint", "도", new Color(0.33f, 0.45f, 0.62f), new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(20f, 0f), 76f);
|
||
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));
|
||
UiKit.IconOrDot(foot.transform, "cur_gold", "골", UiKit.Coin, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(380f, 0f), 76f);
|
||
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;
|
||
}
|
||
}
|
||
}
|