using System.Collections.Generic; using UnityEngine; namespace TKS.OutGame { /// /// 내정 개발 트리 규칙(기획서 §7 · S11 · PD #1093 C) — 노드 레벨 · 선행 조건 · 비용(골드 = gold_base × gold_growth^lv · 도면 = blueprint_from_lv 이상 Lv 마다 blueprint_per_lv) · /// 초기화(보옥 dev_reset_gem · 쓴 도면 환급 · 골드 환급 없음) · 효과 = Mod(키) = Σ pct_per_lv × Lv 를 각 계산 지점이 읽는다(아래 Keys = 키 → 적용 지점). /// public static class DevRules { public const string GoldGain = "gold_gain_pct"; public const string GemBonus = "gem_bonus_pct"; public const string TradeDiscount = "trade_discount_pct"; public const string ChestPoint = "chest_point_pct"; public const string TradeFreeRefresh = "trade_free_refresh"; public const string PatrolGold = "patrol_gold_pct"; public const string SellPrice = "sell_price_pct"; public const string FirstClearGem = "first_clear_gem_pct"; public const string StarReward = "star_reward_pct"; public const string FoodCap = "food_cap_add"; public const string FoodRegen = "food_regen_cut_sec"; public const string PatrolYield = "patrol_yield_pct"; public const string PatrolCap = "patrol_cap_min"; public const string PatrolGearGrade = "patrol_gear_grade_up"; public const string ExpGain = "exp_gain_pct"; public const string FoodCost = "food_cost_cut"; public const string AdFood = "ad_food_add"; public const string PatrolGearChance = "patrol_gear_chance_pct"; public const string GearDrop = "gear_drop_pct"; public const string ChestHigh = "chest_high_grade_pct"; public const string IronGain = "iron_gain_pct"; public const string CraftCost = "craft_cost_cut_pct"; public const string CraftBulk = "craft_bulk"; public const string CraftBlueprint = "craft_blueprint_cut"; public const string GearSell = "gear_sell_pct"; public const string BlueprintGain = "blueprint_gain_pct"; public const string DungeonGear = "dungeon_gear_pct"; public const string ChestTier = "chest_tier_up_pct"; /// 효과 키 → 적용 지점(화면 설명 · 「미구현」 = 판매·합성·별 보상이 아직 없어 적용 지점 없음 · PD 확인 대상). public static readonly Dictionary Keys = new Dictionary { { GoldGain, "스테이지·군량고 골드 +%" }, { GemBonus, "스테이지·탑 보옥 +%" }, { TradeDiscount, "상점 장비·상자 가격 −%" }, { ChestPoint, "상자 포인트 +%" }, { TradeFreeRefresh, "상점 무료 새로고침 +회/일" }, { PatrolGold, "순찰 골드 +%" }, { SellPrice, "판매가 +% (판매 미구현)" }, { FirstClearGem, "첫 클리어 보옥 +%" }, { StarReward, "별 보상 +% (별 보상 미구현)" }, { FoodCap, "군량 상한 +" }, { FoodRegen, "군량 회복 −초" }, { PatrolYield, "순찰 수확 +%" }, { PatrolCap, "순찰 상한 +분" }, { PatrolGearGrade, "순찰 장비 등급 +" }, { ExpGain, "스테이지·순찰 경험 +%" }, { FoodCost, "스테이지 군량 −(1 단위 내림)" }, { AdFood, "광고 군량 +" }, { PatrolGearChance, "순찰 장비 +%" }, { GearDrop, "스테이지 장비 드랍 +%p" }, { ChestHigh, "상자 상위 등급 확률 +%" }, { IronGain, "운철 +%" }, { CraftCost, "합성 비용 −% (합성 미구현)" }, { CraftBulk, "합성 일괄 (합성 미구현)" }, { CraftBlueprint, "합성 도면 − (합성 미구현)" }, { GearSell, "장비 판매 +% (판매 미구현)" }, { BlueprintGain, "도면 획득 +%" }, { DungeonGear, "대장간 장비 +%" }, { ChestTier, "상자 한 단계 위 확률 +%" }, }; public static bool Known(string key) { return key != null && Keys.ContainsKey(key); } public static int Level(SaveData d, string node) { return Counts.Get(d.dev.nodes, node); } /// 효과 합 = Σ(키를 가진 노드) pct_per_lv × Lv(단위는 키마다 · Keys). public static float Mod(OutGameData db, SaveData d, string key) { if (db == null || db.Dev == null || d == null) return 0f; float s = 0f; foreach (var n in db.Dev.nodes) { int lv = Level(d, n.id); if (lv <= 0) continue; foreach (var k in n.EffectKeys) if (k == key) s += n.pctPerLv * lv; } return s; } /// % 효과의 배율 = 1 + Mod/100. public static float Mult(OutGameData db, SaveData d, string key) { return 1f + Mod(db, d, key) / 100f; } /// Lv → Lv+1 비용(골드 · 도면). public static void NextCost(DevTree.Node n, int lv, out int gold, out int blueprint) { gold = Mathf.RoundToInt(n.goldBase * Mathf.Pow(n.goldGrowth, lv)); blueprint = lv + 1 >= n.blueprintFromLv ? n.blueprintPerLv : 0; } public static bool Unlocked(SaveData d, DevTree.Node n) { foreach (var r in n.requires) if (Level(d, r.node) < r.lv) return false; return true; } public static string CanUpgrade(OutGameData db, SaveData d, string id) { var n = db.Dev.Get(id); if (n == null) return "없는 노드"; int lv = Level(d, id); if (lv >= n.lvMax) return "최고 레벨"; if (!Unlocked(d, n)) { var sb = new List(); foreach (var r in n.requires) if (Level(d, r.node) < r.lv) sb.Add(db.Dev.Get(r.node).nameKo + " Lv " + r.lv); return "선행: " + string.Join(" · ", sb.ToArray()); } int gold, bp; NextCost(n, lv, out gold, out bp); if (d.gold < gold) return "골드 부족 " + d.gold.ToString("N0") + " / " + gold.ToString("N0"); if (d.inv.blueprint < bp) return "도면 부족 " + d.inv.blueprint + " / " + bp; return null; } public static string Upgrade(OutGameData db, SaveData d, string id) { var why = CanUpgrade(db, d, id); if (why != null) return why; var n = db.Dev.Get(id); int gold, bp; NextCost(n, Level(d, id), out gold, out bp); d.gold -= gold; d.inv.blueprint -= bp; d.dev.blueprintSpent += bp; Counts.Add(d.dev.nodes, id, 1); return null; } public static int TotalLevels(SaveData d) { int n = 0; foreach (var c in d.dev.nodes) n += c.n; return n; } /// 트리 초기화 = 보옥 dev_reset_gem · 쓴 도면 환급 · 골드 환급 없음. public static string Reset(OutGameData db, SaveData d) { if (TotalLevels(d) == 0) return "초기화할 노드 없음"; int cost = db.Economy.DevResetGem; if (d.gem < cost) return "보옥 부족 " + d.gem + " / " + cost; d.gem -= cost; d.inv.blueprint += d.dev.blueprintSpent; d.dev.blueprintSpent = 0; d.dev.nodes.Clear(); return null; } } }