293 lines
11 KiB
C#
293 lines
11 KiB
C#
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using TKS.OutGame;
|
||
using UnityEngine;
|
||
|
||
namespace TKS
|
||
{
|
||
public enum SkillTrigger
|
||
{
|
||
/// <summary>액티브 · 다음 슬링샷 1회에 붙는다(예약 · 행동 전 다시 탭 = 취소 · 쿨 소모 없음).</summary>
|
||
NextAction,
|
||
/// <summary>액티브 · 즉시 발동(행동 소모 없음).</summary>
|
||
Instant,
|
||
/// <summary>패시브 · 상시.</summary>
|
||
Always
|
||
}
|
||
|
||
/// <summary>효과가 걸리는 대상(skill_effects.csv target 칸).</summary>
|
||
public enum SkillScope
|
||
{
|
||
/// <summary>빈칸 = 효과 주인 자신.</summary>
|
||
Self,
|
||
/// <summary>allies = 같은 편 전원(자신 포함).</summary>
|
||
Allies,
|
||
/// <summary>camp:<진영>[:<병종>] = 같은 편 · 그 진영(heroes.csv faction) · (그 병종).</summary>
|
||
Camp,
|
||
/// <summary>camp_male:<진영> = 같은 편 · 그 진영 · 남성.</summary>
|
||
CampMale,
|
||
/// <summary>radius:<r> = 자신 주위 r 안 같은 편(자신 제외 · 매번 다시 봄).</summary>
|
||
Radius,
|
||
/// <summary>range = 시전 순간 자신 슬링 최대 거리 안 같은 편(자신 포함 · 대상 고정 · 즉시형 지속 효과).</summary>
|
||
Range,
|
||
/// <summary>aura = 지금 자신 슬링 최대 거리 안 같은 편(자신 포함 · 매번 다시 봄).</summary>
|
||
Aura
|
||
}
|
||
|
||
/// <summary>효과 발동 조건(skill_effects.csv cond 칸) — 상대(공격 대상·공격자)·치명타·턴.</summary>
|
||
public enum SkillCondKind
|
||
{
|
||
None,
|
||
Crit,
|
||
VsStatus,
|
||
VsBleed,
|
||
VsConfused,
|
||
VsStunned,
|
||
VsType,
|
||
VsMale,
|
||
VsMaleHero,
|
||
VsFemaleHero,
|
||
TurnLe,
|
||
Armed
|
||
}
|
||
|
||
/// <summary>스킬 효과 1줄(skill_effects.csv) — 키 · 대상 · 조건 · 지속 · p1~p3.</summary>
|
||
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;
|
||
/// <summary>0 = 상시·장착 중·1회 · n = n 라운드(시한 효과).</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>target 칸 해석(오류면 메시지 · 빈칸 = 자신).</summary>
|
||
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 + "」 모름";
|
||
}
|
||
|
||
/// <summary>cond 칸 해석(오류면 메시지 · 빈칸 = 조건 없음).</summary>
|
||
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 + "」 모름";
|
||
}
|
||
}
|
||
|
||
/// <summary>skills.csv 1행 + skill_effects.csv 효과 줄의 전투용 정의.</summary>
|
||
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<SkillEffect> Effects = new List<SkillEffect>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>레벨 계수 = 1 + lv_scale·(lv−1) (lv 는 1~lv_max 로 자름).</summary>
|
||
public float LevelFactor(int lv)
|
||
{
|
||
return 1f + LvScale * (Mathf.Clamp(lv, 1, LvMax) - 1);
|
||
}
|
||
|
||
/// <summary>레벨·★ 배율(패시브 ★5 = evolve.csv passive_x)을 적용한 효과 목록.</summary>
|
||
public List<SkillEffect> ScaledEffects(int lv, float starMult)
|
||
{
|
||
float f = LevelFactor(lv) * (IsActive ? 1f : starMult);
|
||
var list = new List<SkillEffect>();
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>스킬·영웅 표 조회(Resources/OutGame 의 SkillInfoDb · HeroDb = 임포트 자산 · 전투 시작마다 다시 읽음).</summary>
|
||
public static class SkillCatalog
|
||
{
|
||
static Dictionary<string, SkillDef> 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<string, SkillDef>();
|
||
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<SkillDef> 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;
|
||
}
|
||
|
||
/// <summary>적 무장 → heroes.csv 영웅(아트 id 가 같으면 · 없으면 이름이 같은 영웅) · 병사 유닛 제외 · 없으면 null.</summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|