using System; using System.Globalization; using UnityEngine; namespace TKS.OutGame { /// /// 스킬 성장 규칙(시스템 기획 §3-5·§5 · PD #1093 B): 레벨 1~lv_max · 상한 = 승급 등급(HeroRules.SkillCap) + ★ 보정(evolve.csv skill_unlock 「active_lv+n」 = 액티브 상한 +n) · /// ★ 패시브 배율(「passive_x1.5」) · 레벨업 비용 = skill_levels.csv(스킬서 + 골드) · 스킬서 임시 판매(economy.csv skill_book_* · 획득처 사당 던전은 C 단계). /// public static class SkillRules { const string ActiveCapToken = "active_lv+"; const string PassiveMultToken = "passive_x"; /// ★ star 까지 해금된 액티브 상한 보정 합(evolve.csv skill_unlock). public static int ActiveCapBonus(OutGameData db, int star) { int add = 0; if (db == null || db.Evolve == null) return 0; foreach (var r in db.Evolve.rows) { if (r.star > star || string.IsNullOrEmpty(r.skillUnlock) || !r.skillUnlock.StartsWith(ActiveCapToken)) continue; int n; if (int.TryParse(r.skillUnlock.Substring(ActiveCapToken.Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) add += n; } return add; } /// ★ star 까지 해금된 패시브 효과 배율(곱 · 없으면 1). public static float PassiveMult(OutGameData db, int star) { float m = 1f; if (db == null || db.Evolve == null) return m; foreach (var r in db.Evolve.rows) { if (r.star > star || string.IsNullOrEmpty(r.skillUnlock) || !r.skillUnlock.StartsWith(PassiveMultToken)) continue; float f; if (float.TryParse(r.skillUnlock.Substring(PassiveMultToken.Length), NumberStyles.Float, CultureInfo.InvariantCulture, out f)) m *= f; } return m; } /// 스킬 i(0 = 액티브 · 1 = 패시브) 레벨 상한 = 등급 순서 + (액티브면 ★ 보정) · 최대 lv_max. public static int Cap(OutGameData db, HeroDb.Hero h, HeroState s, int i) { var sk = Skill(db, h, i); if (sk == null || s == null) return 1; int cap = HeroRules.SkillCap(db, s) + (i == 0 ? ActiveCapBonus(db, s.star) : 0); return Mathf.Clamp(cap, 1, Mathf.Min(sk.lvMax, db.Skills.MaxLevel)); } public static SkillInfoDb.Skill Skill(OutGameData db, HeroDb.Hero h, int i) { if (db == null || db.Skills == null || h == null) return null; return db.Skills.Get(i == 0 ? h.skillActive : h.skillPassive); } public static int Level(HeroState s, int i) { return s != null && s.skillLv != null && s.skillLv.Length > i ? Mathf.Max(1, s.skillLv[i]) : 1; } /// 레벨업 가능하면 null · 아니면 이유. public static string CanLevelUp(OutGameData db, SaveData d, HeroDb.Hero h, HeroState s, int i) { if (h == null || s == null || h.isSoldier) return "병사 유닛은 스킬 없음"; var sk = Skill(db, h, i); if (sk == null) return "스킬 없음"; int lv = Level(s, i); int cap = Cap(db, h, s, i); if (lv >= sk.lvMax) return "최고 레벨"; if (lv >= cap) return "상한 Lv " + cap + " — 승급" + (i == 0 ? "·★3" : "") + "으로 해금"; var c = db.Skills.CostTo(lv + 1); if (c == null) return "비용 표 없음"; if (d.inv.books.skill < c.books) return "스킬서 " + d.inv.books.skill + " / " + c.books; if (d.gold < c.gold) return "골드 " + d.gold.ToString("N0") + " / " + c.gold.ToString("N0"); return null; } public static string LevelUp(OutGameData db, SaveData d, HeroDb.Hero h, HeroState s, int i) { var why = CanLevelUp(db, d, h, s, i); if (why != null) return why; var c = db.Skills.CostTo(Level(s, i) + 1); d.inv.books.skill -= c.books; d.gold -= c.gold; if (s.skillLv == null || s.skillLv.Length < 2) s.skillLv = new[] { 1, 1 }; s.skillLv[i] = Level(s, i) + 1; return null; } /// 출전 칸에 스킬(id · 레벨 = 상한 안 · ★ 패시브 배율 · 무장)을 채운다. public static void FillLineup(OutGameData db, SaveData d, HeroDb.Hero h, LineupEntry e) { if (h == null || e == null || h.isSoldier) return; var s = d.Hero(h.id); e.general = true; e.skillActive = h.skillActive; e.skillPassive = h.skillPassive; e.activeLv = Mathf.Min(Level(s, 0), Cap(db, h, s, 0)); e.passiveLv = Mathf.Min(Level(s, 1), Cap(db, h, s, 1)); e.passiveMult = PassiveMult(db, s != null ? s.star : h.rarity); } // ---------- 스킬서 임시 판매(거래 > 상점 · PD 확인 대상) ---------- public static int BooksBoughtToday(SaveData d, DateTime nowUtc) { return d.shop.bookDay == EconomyRules.DayKey(nowUtc) ? d.shop.bookBought : 0; } public static string CanBuyBook(OutGameData db, SaveData d, DateTime nowUtc) { var e = db.Economy; if (BooksBoughtToday(d, nowUtc) >= e.SkillBookShopDaily) return "오늘 구매 " + e.SkillBookShopDaily + "회 완료"; if (d.gold < e.SkillBookPrice) return "골드 " + d.gold.ToString("N0") + " / " + e.SkillBookPrice.ToString("N0"); return null; } public static string BuyBook(OutGameData db, SaveData d, DateTime nowUtc) { var why = CanBuyBook(db, d, nowUtc); if (why != null) return why; var day = EconomyRules.DayKey(nowUtc); if (d.shop.bookDay != day) { d.shop.bookDay = day; d.shop.bookBought = 0; } d.shop.bookBought++; d.gold -= db.Economy.SkillBookPrice; d.inv.books.skill++; return null; } } }