ThreeKingdomsSlingshot/Assets/Scripts/Runtime/Skills/SkillDef.cs

293 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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:&lt;진영&gt;[:&lt;병종&gt;] = 같은 편 · 그 진영(heroes.csv faction) · (그 병종).</summary>
Camp,
/// <summary>camp_male:&lt;진영&gt; = 같은 편 · 그 진영 · 남성.</summary>
CampMale,
/// <summary>radius:&lt;r&gt; = 자신 주위 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;
}
}
}