// WL826_WarriorPackMapping.cs — 무기 계열별 공격 모션을 ExplosiveLLC Warrior Pack Super Bundle 로 교체 (매핑 에셋 재작성 · 에디터 1회)
// PD 2026-10-09 「Warrior Pack Super Bundle 을 설치했어 — 무기별 모션을 이 에셋 모션으로 교체」
// 검 = Knight · 망치 = Hammer · 단검 = Ninja · 활 = Archer · 지팡이 = Mage (창 = 번들에 창 팩이 없어 Shinabro 유지 · 2Handed 는 대안)
// v2(23:5x · PD 「일부 모션이 제대로 안 보인다」): 타격 시점 = 팩 FBX 에 구워진 Hit 이벤트 시각(정규화 · 실측) · 전이 exit = 앞 클립 Hit + 0.35(0.55~0.90)
// 이 스크립트는 CombatAnimMapping.asset 의 행만 다시 쓴다. 실제 서브클립·이벤트·컨트롤러 교체는 기존 도구가 한다:
// unity command run_script --file AgentScripts/WL826_WarriorPackMapping.cs --entry WL826_WarriorPackMapping.Run --timeout 120
// unity command run_script --file AgentScripts/WL_CombatAnimSetup.cs --entry WL_CombatAnimSetup.Run --args '[["apply"]]' --timeout 500
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using WL.Combat;
public static class WL826_WarriorPackMapping
{
const string MappingPath = "Assets/WL/Combat/CombatAnimMapping.asset";
const string Root = "Assets/ExplosiveLLC/";
const string K = Root + "Knight Warrior Mecanim Animation Pack/Animations/Knight@";
const string H = Root + "Hammer Warrior Mecanim Animation Pack/Animations/Hammer@";
const string N = Root + "Ninja Warrior Mecanim Animation Pack/Animations/Ninja@";
const string A = Root + "Archer Warrior Mecanim Animation Pack/Animations/Archer@";
const string M = Root + "Mage Warrior Mecanim Animation Pack/Animations/Mage@";
// #838 대검 = 2Handed Warrior 팩 · 활 = FR(FirstRising) 휴머노이드 .anim 사본(Assets/WL/Combat/Anim/FR/ · 원본 C:\BurningTimes\FirstRising\Assets\Animation\Player\Weapon\Bow0~4.anim)
const string T = Root + "2 Handed Warrior Mecanim Animation Pack/Animations/2Handed@";
const string FR = "Assets/WL/Combat/Anim/FR/";
public const string GreatswordCtrl = "Assets/Res_Addr/Animations/pcanim_greatsword.controller";
// 팩 FBX 임포터의 원본 클립 Hit 이벤트 시각(정규화 · 2026-10-09 23:5x 실측 · 없는 클립은 0.45 가정)
const float K1 = 0.449f, K2 = 0.350f, K3 = 0.374f;
// #838 2Handed 팩 Attack1_S/2_S/3_S Hit 실측(임포터 원본 이벤트 · 2026-10-11)
const float T1 = 0.479f, T2 = 0.504f, T3 = 0.468f;
// #838 FR 활 클립(Bow0~4 = 동일 모션 · 0.8 s) — 양손 거리 실측(WL838_BowRelease): 당김 최대 f21(0.87) → 시위 놓음 f22~23(0.92) → 발사 0.92 · 다음 타 진입은 클립 끝(0.98)
const float B1 = 0.92f, BExit = 0.98f;
// #839 활 3타 — 기 모으기 이벤트 0.40(당김 구간 f6~f21 안). #840 PD 「발사 전까지 활시위 당김 유지」 → 이벤트부터 1.2 s 동안 애니메이터 0.35배(당김 자세 유지) 뒤 원속 → 발사 0.92 는 그 뒤. 3타 상태 속도는 원속(1.0).
// #843 PD 「조준이 끝난 직후 바로 발사」 — 유지(0.6 s · 속도 0.35)는 클립을 0.21 s = 0.2625 만큼 진행시키므로 발사 0.92 에서 역산한 0.66 에 시작 → 유지가 끝나는 프레임이 곧 발사 프레임(이전 0.40 은 0.2 s 더 기다렸다)
const float BCharge = 0.66f, BChargeDur = 0.6f, BSpeed3 = 1.0f; const string BHoldSpeed = "0.35"; // #841 PD 「모아쏘기 딜레이 절반」 유지 1.2 → 0.6 s · BSpeed3 1.0 = 원속(유지는 WLBowCharge)
// #839 해머 1타 재생 배율(PD 「첫 타 공격 속도를 좀 더 빠르게」) · #840 3타 진입 exit 0.55 + 3타 재생 1.3(2→3 딜레이 단축)
const float HS1 = 1.3f, HX3 = 0.55f, HS3 = 1.3f;
// #839 마법(소서리스) — Mage@Attack2_S 18~64f · 투사체 f41(0.504): 발사 구간 34~64f(31f · 1.03 s · 투사체 (41-34)/30 = 0.23) · 3타 진입 exit 0.85
const int MFirst = 34, MLast = 64; const float MF = 0.23f, MExit = 0.85f;
// #839 2Handed 4타·스킬 — MoveAttack1_S Hit 0.568 · SpecialAttack1(Hit 이벤트 없음 · 포즈 띠 찌르기 f5~9/10 → 0.50) · Run2-Attack1(0.53 s · 0.40 추정)
const float T4 = 0.568f, TSk1 = 0.50f, TSk2 = 0.40f;
// #839 Spearman 팩 Hit 실측(임포터 원본 이벤트): Attack1 0.439 · Attack2_S 0.425 · Attack3_S 0.523(Land 0.48)
const string SP = Root + "Spearman Warrior Mecanim Animation Pack/Animations/Spearman@";
const float SP1 = 0.439f, SP2 = 0.425f, SP3 = 0.523f;
// #840 창 2타 = Attack2_S(23~60f · Hit f38.7) 찌르기 구간 33~46f(14f · 0.47 s) → 타격 (38.7-33)/13 = 0.44 · 3연속(Attack2·2b·2c) · 재생 1.2배 · 다음 진입 exit 0.9
const int SPcFirst = 33, SPcLast = 46; const float SPc = 0.44f, SPcSpeed = 1.5f, SPcExit = 0.9f; // PD 「찌르기 모션을 매우 빠르게 3회」 → 1.5배(한 번 0.31 s · 3번 ≈ 0.95 s)
const float H1 = 0.370f, H2 = 0.422f, H3 = 0.448f;
const float N1 = 0.237f, N2 = 0.407f, N3 = 0.203f;
const float A1 = 0.393f, A2 = 0.45f, A3 = 0.45f;
const float M1 = 0.504f, M2 = 0.407f;
static AnimEventSpec Ev(string fn, float t, string s = "", int i = 0, float f = 0f)
{
return new AnimEventSpec { functionName = fn, useNormalized = true, normalizedTime = Mathf.Clamp01(t), time = 0f, stringParameter = s, intParameter = i, floatParameter = f };
}
// #828 검 — 베기 이펙트(ShowEffect)는 「실측 스윙 시작 프레임」에 둔다(2026-09-07 #804 · 09-15 #816v 와 같은 규칙).
// WL828_TrailBake 실측(60 Hz · 속도 트림 창 시작): Attack1 f12/50 → 0.24 · Attack2 f14/52 → 0.269 · Attack3 f16/64 → 0.25
// (Hit−0.12 규칙은 2타를 스윙보다 4프레임 앞서 띄웠다 → 호가 칼보다 먼저 돈다)
// #841 1타 정정: 60 Hz 전수 덤프(WL828 ProbeDump)로 보면 f12~f17 은 칼을 머리 뒤로 감아올리는 구간이고 실제 내려베기는 f16→f22(0.267→0.367 s · 한 샘플당 0.83~0.93 m 이동).
// ShowEffect = 베기 시작 f16/50 = 0.32 (Hit 0.449 = 베기 끝 f22 직후).
const float KS1 = 0.32f, KS2 = 0.269f, KS3 = 0.25f;
// #834 비수 3타 — 2타 타격(0.407) 직후 진입 · 재생 1.3배
const float DS3 = 0.5f, DS3Speed = 1.3f;
// #838 비수 4타(DualBlades_DodgeAttack · 원본 힘민지 클립) — 원본 이벤트 시각 유지(ShowEffect 0.155 · Projectile 0.186) · 진입 exit 0.55 · 재생 1.5배
const string AS = "Assets/Res_Addr/Animations/Animation/Assassin/";
const float D4 = 0.186f, D4Show = 0.155f, D4Exit = 0.55f, D4Speed = 1.5f;
/// 근접 — 팩 Hit 시각 기준: 베기 이펙트 = Hit−0.12(showAt 지정 시 그 값) · 전진 = Hit−0.06 · 판정 = Hit · 2·3타는 LookAtTarget.
static List Melee(int step, float hit, float move, float showAt = -1f, int effectCid = 0)
{
var l = new List();
if (step > 1) l.Add(Ev("LookAtTarget", 0f));
// effectCid > 0 = 베기 이펙트·타격 상자 이름을 **템플릿 직업** 것으로(#838 대검 10801 → Effect_Slash_10101_N · Projectile_10101 · 창 10701 과 같은 재사용 규칙).
// WL 추가 직업은 템플릿 행을 복제해 들어가므로 투사체 사전(dic_projectile)에는 템플릿 이름만 있다 — Projectile_10801 로 쓰면 KeyNotFound(2026-10-11 실측 18건).
string cid = effectCid > 0 ? effectCid.ToString() : "{CID}";
l.Add(Ev("ShowEffect", showAt >= 0f ? showAt : Mathf.Max(0.05f, hit - 0.12f), "Effect_Slash_" + cid + "_" + step, 1));
if (move > 0f) l.Add(Ev("Move", Mathf.Max(0.05f, hit - 0.06f), "", 0, move));
l.Add(Ev("Projectile", hit, "Projectile_" + cid));
return l;
}
/// 원거리(활·지팡이) — 조준 뒤 발사만.
static List Ranged(float hit)
{
return new List { Ev("LookAtTarget", 0f), Ev("Projectile", hit, "Projectile_{CID}") };
}
/// #839 활 3타 — 조준 → 기 모으기(WLBowCharge · float = 유지 초 · string = 유지 중 속도 #840) → 발사(당김 유지가 끝난 뒤 클립 진행대로).
static List RangedCharged(float hit)
{
return new List { Ev("LookAtTarget", 0f), Ev("WLBowCharge", BCharge, BHoldSpeed, 0, BChargeDur), Ev("Projectile", hit, "Projectile_{CID}") };
}
/// #838 대기 클립 — 힘민지 원본 대기 클립은 첫 프레임에 Event_Idle(논리 CurAnim 초기화)을 갖는다. 팩 Idle(Knight@Idle·Mage@Idle)로 바꾼 직업은 이 이벤트가 없어
/// 콤보 뒤 CurAnim 이 Attack 에 굳고(Play_Attack 가드) 두 번째 콤보가 안 나간다(2026-10-11 해머 실측: 애니메이터 idle · 논리 Attack). 서브클립에 되살린다.
static List IdleEv() { return new List { Ev("Event_Idle", 0f) }; }
/// 앞 클립의 Hit 뒤 0.35 지점에서 다음 타로 넘어간다(0.55~0.90) — 타격과 뒤따르는 동작이 보이게.
static float Exit(float prevHit) { return Mathf.Clamp(prevHit + 0.35f, 0.55f, 0.90f); }
static StateClipSpec St(string state, string fbx, string sub, List ev, float incomingExit = 0f, float speed = 0f)
{
return new StateClipSpec { stateName = state, sourceFbxPath = fbx, subClipName = sub, sourceClipName = "", incomingExitTime = incomingExit, createIfMissing = false, speed = speed, events = ev };
}
/// #839 프레임 범위 지정(마법 발사 구간만 등) + 상태 신설(create) + 원본 클립 이름 지정.
static StateClipSpec StR(string state, string fbx, string sub, List ev, int first, int last, float incomingExit = 0f, float speed = 0f, bool create = false, string srcClip = "")
{
var s = St(state, fbx, sub, ev, incomingExit, speed);
s.firstFrame = first; s.lastFrame = last; s.createIfMissing = create; s.sourceClipName = srcClip;
return s;
}
/// #839 스킬 상태 — 힘민지 원본 스킬 클립 규약: SkillEvent(int 0) = 타격/발사 · (dashslash) ShowEffect Effect_Teleport i3 @0 · (combo01) SkillEvent@0 + Del_Magic@0.67.
static List Skill(float hit, bool teleport = false, bool delMagicAt067 = false)
{
var l = new List();
if (teleport) l.Add(Ev("ShowEffect", 0f, "Effect_Teleport", 3));
l.Add(Ev("SkillEvent", hit));
if (delMagicAt067) l.Add(Ev("Del_Magic", 0.67f));
return l;
}
static ClassCombatAnimSpec Row(int cid, string note, params StateClipSpec[] states)
{
var r = new ClassCombatAnimSpec { classId = cid, note = note, enabled = true, controllerPathOverride = "" };
r.states = new List(states);
return r;
}
/// ClassConfig 에 없는 WL 추가 직업(대검 10801 등) — 컨트롤러 경로를 직접 준다.
static ClassCombatAnimSpec RowC(int cid, string ctrlPath, string note, params StateClipSpec[] states)
{
var r = Row(cid, note, states);
r.controllerPathOverride = ctrlPath;
return r;
}
public static string Run()
{
var mapping = AssetDatabase.LoadAssetAtPath(MappingPath);
if (mapping == null) return "매핑 에셋 없음 " + MappingPath;
foreach (var p in new[] { K + "Attack1_S.FBX", H + "Attack1_S.FBX", N + "Attack1_S.FBX", A + "Attack1_S.FBX", M + "Attack2_S.FBX" })
if (AssetDatabase.LoadAssetAtPath