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;
}
}
}