using System.Collections.Generic; using System.Globalization; using TKS.OutGame; using UnityEngine; namespace TKS { public enum SkillTrigger { /// 액티브 · 다음 슬링샷 1회에 붙는다(예약 · 행동 전 다시 탭 = 취소 · 쿨 소모 없음). NextAction, /// 액티브 · 즉시 발동(행동 소모 없음). Instant, /// 패시브 · 상시. Always } /// 효과가 걸리는 대상(skill_effects.csv target 칸). public enum SkillScope { /// 빈칸 = 효과 주인 자신. Self, /// allies = 같은 편 전원(자신 포함). Allies, /// camp:<진영>[:<병종>] = 같은 편 · 그 진영(heroes.csv faction) · (그 병종). Camp, /// camp_male:<진영> = 같은 편 · 그 진영 · 남성. CampMale, /// radius:<r> = 자신 주위 r 안 같은 편(자신 제외 · 매번 다시 봄). Radius, /// range = 시전 순간 자신 슬링 최대 거리 안 같은 편(자신 포함 · 대상 고정 · 즉시형 지속 효과). Range, /// aura = 지금 자신 슬링 최대 거리 안 같은 편(자신 포함 · 매번 다시 봄). Aura } /// 효과 발동 조건(skill_effects.csv cond 칸) — 상대(공격 대상·공격자)·치명타·턴. public enum SkillCondKind { None, Crit, VsStatus, VsBleed, VsConfused, VsStunned, VsType, VsMale, VsMaleHero, VsFemaleHero, TurnLe, Armed } /// 스킬 효과 1줄(skill_effects.csv) — 키 · 대상 · 조건 · 지속 · p1~p3. public class SkillEffect { public string Key; public float P; public float A2; public float A3; public bool HasA2; public bool HasA3; public SkillScope Scope; public string Camp = ""; public bool HasType; public UnitType Type; public float Radius; public SkillCondKind Cond; public UnitType CondType; public int CondN; /// 0 = 상시·장착 중·1회 · n = n 라운드(시한 효과). public int Dur; public float Get(int i, float fallback = 0f) { if (i == 0) return P; if (i == 1) return HasA2 ? A2 : fallback; return HasA3 ? A3 : fallback; } public SkillEffect Clone() { return (SkillEffect)MemberwiseClone(); } public SkillEffect Scaled(float factor) { SkillKeys.Info info; var c = Clone(); if (!SkillKeys.All.TryGetValue(Key, out info) || info.Mode == SkillScale.Fixed) return c; if (info.Idx == 0 || info.Both) c.P = SkillKeys.Apply(info.Mode, P, factor); if ((info.Idx == 1 || info.Both) && HasA2) c.A2 = SkillKeys.Apply(info.Mode, A2, factor); if (info.Idx == 2 && HasA3) c.A3 = SkillKeys.Apply(info.Mode, A3, factor); return c; } /// target 칸 해석(오류면 메시지 · 빈칸 = 자신). public static string ParseTarget(string s, SkillEffect e) { e.Scope = SkillScope.Self; if (string.IsNullOrEmpty(s)) return null; var parts = s.Split(':'); switch (parts[0]) { case "allies": e.Scope = SkillScope.Allies; return null; case "range": e.Scope = SkillScope.Range; return null; case "aura": e.Scope = SkillScope.Aura; return null; case "radius": e.Scope = SkillScope.Radius; if (parts.Length < 2 || !float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out e.Radius)) return "radius 값 없음"; return null; case "camp": case "camp_male": e.Scope = parts[0] == "camp" ? SkillScope.Camp : SkillScope.CampMale; if (parts.Length < 2 || parts[1].Length == 0) return "진영 없음"; e.Camp = parts[1]; if (parts.Length >= 3) { if (!UnitTypeInfo.TryParseKey(parts[2], out e.Type)) return "병종 「" + parts[2] + "」 모름"; e.HasType = true; } return null; } return "대상 「" + s + "」 모름"; } /// cond 칸 해석(오류면 메시지 · 빈칸 = 조건 없음). public static string ParseCond(string s, SkillEffect e) { e.Cond = SkillCondKind.None; if (string.IsNullOrEmpty(s)) return null; var parts = s.Split(':'); switch (parts[0]) { case "crit": e.Cond = SkillCondKind.Crit; return null; case "vs_status": e.Cond = SkillCondKind.VsStatus; return null; case "vs_bleed": e.Cond = SkillCondKind.VsBleed; return null; case "vs_confused": e.Cond = SkillCondKind.VsConfused; return null; case "vs_stunned": e.Cond = SkillCondKind.VsStunned; return null; case "vs_male": e.Cond = SkillCondKind.VsMale; return null; case "vs_male_hero": e.Cond = SkillCondKind.VsMaleHero; return null; case "vs_female_hero": e.Cond = SkillCondKind.VsFemaleHero; return null; case "armed": e.Cond = SkillCondKind.Armed; return null; case "vs_type": e.Cond = SkillCondKind.VsType; if (parts.Length < 2 || !UnitTypeInfo.TryParseKey(parts[1], out e.CondType)) return "vs_type 병종 모름"; return null; case "turn_le": e.Cond = SkillCondKind.TurnLe; if (parts.Length < 2 || !int.TryParse(parts[1], out e.CondN)) return "turn_le 턴 없음"; return null; } return "조건 「" + s + "」 모름"; } } /// skills.csv 1행 + skill_effects.csv 효과 줄의 전투용 정의. public class SkillDef { public string Id; public string HeroId; public string NameKo; public bool IsActive; public SkillTrigger Trigger; public int Cooldown; public int LvMax; public float LvScale; public bool NeedsImpl; public readonly List Effects = new List(); public static SkillDef From(SkillInfoDb.Skill s, SkillInfoDb db) { var d = new SkillDef { Id = s.id, HeroId = s.heroId, NameKo = s.nameKo, IsActive = s.kind == "active", Trigger = s.trigger == "next_action" ? SkillTrigger.NextAction : s.trigger == "instant" ? SkillTrigger.Instant : SkillTrigger.Always, Cooldown = s.cooldown, LvMax = Mathf.Max(1, s.lvMax), LvScale = s.lvScale, NeedsImpl = s.needsImpl, }; if (db != null) foreach (var r in db.effects) { if (r.skillId != s.id) continue; var e = new SkillEffect { Key = r.key, P = r.p1, A2 = r.p2, A3 = r.p3, HasA2 = r.HasP(1), HasA3 = r.HasP(2), Dur = r.dur }; SkillEffect.ParseTarget(r.target, e); SkillEffect.ParseCond(r.cond, e); d.Effects.Add(e); } return d; } /// 레벨 계수 = 1 + lv_scale·(lv−1) (lv 는 1~lv_max 로 자름). public float LevelFactor(int lv) { return 1f + LvScale * (Mathf.Clamp(lv, 1, LvMax) - 1); } /// 레벨·★ 배율(패시브 ★5 = evolve.csv passive_x)을 적용한 효과 목록. public List ScaledEffects(int lv, float starMult) { float f = LevelFactor(lv) * (IsActive ? 1f : starMult); var list = new List(); foreach (var e in Effects) list.Add(e.Scaled(f)); return list; } public SkillEffect First(string key) { foreach (var e in Effects) if (e.Key == key) return e; return null; } public bool Has(string key) { return First(key) != null; } } /// 스킬·영웅 표 조회(Resources/OutGame 의 SkillInfoDb · HeroDb = 임포트 자산 · 전투 시작마다 다시 읽음). public static class SkillCatalog { static Dictionary defs; static SkillInfoDb source; static int sourceCount; public static void Invalidate() { defs = null; } static void Ensure() { var db = OutGameData.Default; var s = db != null ? db.Skills : null; int n = s != null ? s.skills.Count + s.effects.Count * 1000 : 0; if (defs != null && s == source && s != null && n == sourceCount) return; defs = new Dictionary(); source = s; sourceCount = n; if (s == null) return; foreach (var row in s.skills) { if (string.IsNullOrEmpty(row.id) || defs.ContainsKey(row.id)) continue; defs[row.id] = SkillDef.From(row, s); } } public static SkillDef Get(string id) { if (string.IsNullOrEmpty(id)) return null; Ensure(); SkillDef d; return defs.TryGetValue(id, out d) ? d : null; } public static IEnumerable All { get { Ensure(); return defs.Values; } } public static HeroDb.Hero Hero(string id) { var db = OutGameData.Default; return db != null && db.Heroes != null ? db.Heroes.Get(id) : null; } /// 적 무장 → heroes.csv 영웅(아트 id 가 같으면 · 없으면 이름이 같은 영웅) · 병사 유닛 제외 · 없으면 null. public static HeroDb.Hero HeroForEnemy(string art, string name) { var db = OutGameData.Default; if (db == null || db.Heroes == null) return null; HeroDb.Hero byName = null; foreach (var h in db.Heroes.heroes) { if (h.isSoldier) continue; if (!string.IsNullOrEmpty(art) && h.art == art) return h; if (byName == null && !string.IsNullOrEmpty(name) && h.nameKo == name) byName = h; } return byName; } } }