WL #948 마법검(1071 · 지팡이 10302) 4건 — ① 투사체 꼬리(원본 FX_Shoot_06 particle 늘림 빌보드) hideNames ② 1·2타 명중 = 0.5 m 밀림 + 경직 0.3 s(WLStageHitReaction 10302 행) ③ 3타 = 겉모습·명중 이펙트 ×0.5(WLProjectileVisual stageScales) + 0.2 s 뒤 띄움 0.7 m · 반대쪽 1.5 m 밀림(delaySeconds 신설) ④ 3타 동작 = 같은 클립에 상체 아바타 마스크(루트·다리 제외 · 매핑 maskPath 신설 · WL_UpperBody.mask · 도구 CopyFromOther) · ComboTracker 토큰 없는 WL 서브클립(_S_ 앞 숫자) 단계 인식 · 테스트 하네스 = 섬처럼 Set_Obj 동안만 Stage(로비 컨트롤러 방지) · 대상 재겨눔 · 사거리 밖 더미 자동 R
실측(테스트 씬 Play): 1·2·3타 반응 로그 각 적용 · 3타 겉모습 ×0.5 11회 · 비행 중 꼬리 입자 숨김 · 3타 자세 제자리(캡처) · 콘솔 게임 오류 0 · 매핑 재굽기 백업 비교 = 변경 1(CastspellC 메타 마스크) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
3e7215ad34
commit
b19aca15bd
|
|
@ -169,6 +169,18 @@ public static class WL_CombatAnimSetup
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
// #948 — 매핑 행의 maskPath(AvatarMask 에셋)를 임포터 서브클립에 씌운다(CopyFromOther). 비우면 원본 설정 그대로.
|
||||
// 상체 마스크(루트·다리 제외) = 지팡이 3타를 점프 없이 제자리에서 상체만(PD 「하체는 서 있는 상태에서 상체만」).
|
||||
private static void ApplyMask(UnityEditor.ModelImporterClipAnimation c, WL.Combat.StateClipSpec st, StringBuilder sb)
|
||||
{
|
||||
if (c == null || st == null || string.IsNullOrEmpty(st.maskPath)) return;
|
||||
var mask = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.AvatarMask>(st.maskPath);
|
||||
if (mask == null) { sb.AppendLine(" [WARN] 마스크 에셋 없음: " + st.maskPath); return; }
|
||||
c.maskType = UnityEditor.ClipAnimationMaskType.CopyFromOther;
|
||||
c.maskSource = mask;
|
||||
sb.AppendLine(" 마스크 " + mask.name + " → " + c.name);
|
||||
}
|
||||
|
||||
// ── 서브클립 보장 ────────────────────────────────────────────────────────
|
||||
// FBX 임포터의 clipAnimations 에 <clipName> 이 없으면 원본 클립 구간을 복제해 추가하고,
|
||||
// events 를 매핑대로 다시 쓴다. 이미 있으면 events 만 갱신한다.
|
||||
|
|
@ -232,6 +244,7 @@ public static class WL_CombatAnimSetup
|
|||
maskType = src.maskType,
|
||||
events = evs,
|
||||
};
|
||||
ApplyMask(copy, st, sb); // #948
|
||||
list.Add(copy);
|
||||
sb.AppendLine(" + 서브클립 생성 " + clipName + " (" + src.name + " 복제 · " +
|
||||
src.firstFrame + "~" + src.lastFrame + "f · " + clipLength.ToString("0.000") + "s)");
|
||||
|
|
@ -241,6 +254,7 @@ public static class WL_CombatAnimSetup
|
|||
var c = list[idx];
|
||||
c.events = evs;
|
||||
if (rangeOverride) { c.firstFrame = useFirst; c.lastFrame = useLast; }
|
||||
ApplyMask(c, st, sb); // #948
|
||||
list[idx] = c;
|
||||
sb.AppendLine(" ~ 서브클립 이벤트 갱신 " + clipName + (rangeOverride ? " (범위 " + useFirst + "~" + useLast + "f)" : ""));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a9b647a73aef2fb4a83c157be5e4867c
|
||||
ModelImporter:
|
||||
serializedVersion: 22200
|
||||
serializedVersion: 24300
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
1: 100000
|
||||
|
|
@ -1326,6 +1326,7 @@ ModelImporter:
|
|||
materialName: 0
|
||||
materialSearch: 1
|
||||
materialLocation: 0
|
||||
searchTexturesGlobally: 1
|
||||
animations:
|
||||
legacyGenerateAnimations: 4
|
||||
bakeSimulation: 0
|
||||
|
|
@ -1333,8 +1334,6 @@ ModelImporter:
|
|||
optimizeGameObjects: 0
|
||||
removeConstantScaleCurves: 0
|
||||
motionNodeName:
|
||||
rigImportErrors:
|
||||
rigImportWarnings:
|
||||
animationImportErrors:
|
||||
animationImportWarnings:
|
||||
animationRetargetingWarnings:
|
||||
|
|
@ -1352,7 +1351,7 @@ ModelImporter:
|
|||
- serializedVersion: 16
|
||||
name: CastspellC_wing
|
||||
takeName: Anim@CastspellC
|
||||
internalID: 0
|
||||
internalID: 7400000
|
||||
firstFrame: 431
|
||||
lastFrame: 495
|
||||
wrapMode: 0
|
||||
|
|
@ -1801,6 +1800,49 @@ ModelImporter:
|
|||
maskType: 1
|
||||
maskSource: {fileID: 101100000, guid: 61b0253f83d533f49bbc43d584500982, type: 2}
|
||||
additiveReferencePoseFrame: 430.80002
|
||||
- serializedVersion: 16
|
||||
name: StaffJumpCast3_S_10302
|
||||
takeName: Anim@CastspellC
|
||||
internalID: -4485281783395832467
|
||||
firstFrame: 431
|
||||
lastFrame: 487
|
||||
wrapMode: 0
|
||||
orientationOffsetY: 0
|
||||
level: 0
|
||||
cycleOffset: 0
|
||||
loop: 0
|
||||
hasAdditiveReferencePose: 0
|
||||
loopTime: 0
|
||||
loopBlend: 0
|
||||
loopBlendOrientation: 1
|
||||
loopBlendPositionY: 1
|
||||
loopBlendPositionXZ: 1
|
||||
keepOriginalOrientation: 1
|
||||
keepOriginalPositionY: 1
|
||||
keepOriginalPositionXZ: 1
|
||||
heightFromFeet: 0
|
||||
mirror: 0
|
||||
bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000
|
||||
curves: []
|
||||
events:
|
||||
- time: 0
|
||||
functionName: LookAtTarget
|
||||
data:
|
||||
objectReferenceParameter: {instanceID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
- time: 0.43
|
||||
functionName: Projectile
|
||||
data: Projectile_10302
|
||||
objectReferenceParameter: {instanceID: 0}
|
||||
floatParameter: 0
|
||||
intParameter: 0
|
||||
messageOptions: 0
|
||||
transformMask: []
|
||||
maskType: 1
|
||||
maskSource: {fileID: 31900000, guid: 8568a603ff2c2854190b73771710444a, type: 2}
|
||||
additiveReferencePoseFrame: 0
|
||||
isReadable: 1
|
||||
meshes:
|
||||
lODScreenPercentages: []
|
||||
|
|
@ -1827,6 +1869,9 @@ ModelImporter:
|
|||
maxBonesPerVertex: 4
|
||||
minBoneWeight: 0.001
|
||||
optimizeBones: 1
|
||||
generateMeshLods: 0
|
||||
meshLodGenerationFlags: 0
|
||||
maximumMeshLod: -1
|
||||
meshOptimizationFlags: -1
|
||||
indexFormat: 1
|
||||
secondaryUVAngleDistortion: 8
|
||||
|
|
@ -3320,7 +3365,8 @@ ModelImporter:
|
|||
hasTranslationDoF: 0
|
||||
hasExtraRoot: 1
|
||||
skeletonHasParents: 1
|
||||
lastHumanDescriptionAvatarSource: {fileID: 9000000, guid: 91ee43d975a10d245a0eeb62290d5072, type: 3}
|
||||
lastHumanDescriptionAvatarSource: {fileID: 9000000, guid: 91ee43d975a10d245a0eeb62290d5072,
|
||||
type: 3}
|
||||
autoGenerateAvatarMappingIfUnspecified: 1
|
||||
animationType: 3
|
||||
humanoidOversampling: 1
|
||||
|
|
|
|||
|
|
@ -992,6 +992,7 @@ MonoBehaviour:
|
|||
firstFrame: 431
|
||||
lastFrame: 487
|
||||
speed: 0
|
||||
maskPath: Assets/WL/Combat/Masks/WL_UpperBody.mask
|
||||
events:
|
||||
- functionName: LookAtTarget
|
||||
time: 0
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ namespace WL.Combat
|
|||
[Tooltip("#834 상태 재생 속도 배율(AnimatorState.speed). 0 = 건드리지 않음 · 1 = 원속 · 1.3 = 30 % 빠르게. 이벤트는 정규화 시각이라 함께 당겨진다.")]
|
||||
public float speed;
|
||||
|
||||
[Tooltip("#948 서브클립에 씌울 아바타 마스크 에셋 경로(Assets/… .mask). 비우면 원본 클립 설정 그대로. 상체 마스크(루트·다리 제외) = 점프·이동 없이 제자리에서 상체만 움직인다(지팡이 3타)")]
|
||||
public string maskPath = "";
|
||||
|
||||
public List<AnimEventSpec> events = new List<AnimEventSpec>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,14 @@ namespace WL.Combat.Core
|
|||
{
|
||||
if (string.IsNullOrEmpty(clip) || string.IsNullOrEmpty(token)) return 0;
|
||||
int idx = clip.IndexOf(token, System.StringComparison.OrdinalIgnoreCase);
|
||||
if (idx < 0) return 0;
|
||||
if (idx < 0)
|
||||
{
|
||||
// #948 지팡이처럼 토큰이 없는 WL 서브클립(CastFire1_S_10302 · Cast2_S_10302 · StaffJumpCast3_S_10302) — 접미사(_S_) 바로 앞 숫자가 단계. 실측: 2·3타가 0 단계로 읽혀 단계별 피격 반응이 1타에만 걸렸다
|
||||
var cs = WLCombatCoreSettings.Instance; string suf = cs != null ? cs.comboClipDigitSuffix : "";
|
||||
int s = string.IsNullOrEmpty(suf) ? -1 : clip.IndexOf(suf, System.StringComparison.OrdinalIgnoreCase);
|
||||
if (s > 0 && clip[s - 1] >= '0' && clip[s - 1] <= '9') return clip[s - 1] - '0';
|
||||
return 0;
|
||||
}
|
||||
for (int i = idx + token.Length; i < clip.Length; i++)
|
||||
{
|
||||
char c = clip[i];
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ namespace WL.Combat.Core
|
|||
[Tooltip("클립 이름에서 콤보 단계를 읽는 토큰. 토큰 바로 뒤의 첫 숫자가 단계(없으면 1). 예: Attack1_S_10501 → 1 · attack → 1 · Bow_Attack2 → 2.")]
|
||||
public string comboClipToken = "Attack";
|
||||
|
||||
[Tooltip("#948 토큰이 없는 WL 서브클립(<이름><단계>_S_<직업> · CastFire1_S_10302 · Cast2_S_10302 · StaffJumpCast3_S_10302)은 이 접미사 바로 앞 숫자를 단계로 읽는다. 비우면 그런 클립은 0 단계.")]
|
||||
public string comboClipDigitSuffix = "_S_";
|
||||
|
||||
// ─────────────────────────────────────────── 감속 중재자
|
||||
[Header("감속 중재자 (TimeScaleArbiter · 우선순위 Pause > Cinematic > HitStop · D-1(a))")]
|
||||
[Tooltip("끄면 WLHitFeelRunner 가 기존 코루틴 경로로 히트스톱을 건다(중재 없음).")]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3093e91426560754fa2ffc1697fc2562
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!319 &31900000
|
||||
AvatarMask:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: WL_UpperBody
|
||||
m_Mask: 00000000010000000100000000000000000000000100000001000000010000000100000000000000000000000100000001000000
|
||||
m_Elements: []
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8568a603ff2c2854190b73771710444a
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 31900000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -12,9 +12,14 @@
|
|||
"projectile": "Projectile_10302",
|
||||
"visual": "WL/WL_FireBoltVisual",
|
||||
"scale": 1.0,
|
||||
"_note_948": "#948 PD 마법검(1071): ① 「거리가 멀어질수록 꼬리처럼 길게 늘어지는 이펙트」 = 원본 FX_Shoot_06_projectile/particle(늘림 빌보드 · 비행 중 뒤로 남는 입자 · 실측 경계 2.5~3.2 m) → hideNames 추가 ② 「3타 공격의 크기를 절반」 = stageScales 3단계 겉모습·명중 이펙트 × 0.5(판정 그대로)",
|
||||
"hideNames": [
|
||||
"FX_Shoot_06_projectile/trail",
|
||||
"FX_Shoot_06_projectile/back_fire"
|
||||
"FX_Shoot_06_projectile/back_fire",
|
||||
"FX_Shoot_06_projectile/particle"
|
||||
],
|
||||
"stageScales": [
|
||||
{ "stage": 3, "scale": 0.5, "hitScale": 0.5 }
|
||||
],
|
||||
"hitFx": "WL/Impact/WL_Hit_Staff",
|
||||
"hitScale": 0.37,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,46 @@
|
|||
{
|
||||
"enabled": 1,
|
||||
"_note_948": "#948 PD 마법검(1071 · 직업 10302): 1·2타 투사체 명중 = 0.5 m 밀림 + 짧은 경직 · 3타 = 이펙트가 먼저 보이고(delaySeconds) 공중으로 튀어오르며 공격자 반대쪽으로 밀림(Fling · launchHeight)",
|
||||
"rules": [
|
||||
{
|
||||
"classIds": [
|
||||
10302
|
||||
],
|
||||
"stage": 1,
|
||||
"knockbackSeconds": 0.15,
|
||||
"knockbackSpeed": 0.0,
|
||||
"stunSeconds": 0.0,
|
||||
"launchHeight": 0.0,
|
||||
"staggerSeconds": 0.3,
|
||||
"knockbackDistance": 0.5,
|
||||
"delaySeconds": 0.0
|
||||
},
|
||||
{
|
||||
"classIds": [
|
||||
10302
|
||||
],
|
||||
"stage": 2,
|
||||
"knockbackSeconds": 0.15,
|
||||
"knockbackSpeed": 0.0,
|
||||
"stunSeconds": 0.0,
|
||||
"launchHeight": 0.0,
|
||||
"staggerSeconds": 0.3,
|
||||
"knockbackDistance": 0.5,
|
||||
"delaySeconds": 0.0
|
||||
},
|
||||
{
|
||||
"classIds": [
|
||||
10302
|
||||
],
|
||||
"stage": 3,
|
||||
"knockbackSeconds": 0.4,
|
||||
"knockbackSpeed": 0.0,
|
||||
"stunSeconds": 0.3,
|
||||
"launchHeight": 0.7,
|
||||
"staggerSeconds": 0.0,
|
||||
"knockbackDistance": 1.5,
|
||||
"delaySeconds": 0.2
|
||||
},
|
||||
{
|
||||
"classIds": [
|
||||
10501
|
||||
|
|
|
|||
|
|
@ -37,8 +37,13 @@ namespace WL.Combat.Weapon
|
|||
public string hitFx = "";
|
||||
public float hitScale = 1f;
|
||||
public float hitLife = 1.5f;
|
||||
// #948 PD 「마법검 3타 공격의 크기를 절반으로」 — 쏜 PC 의 콤보 단계별 겉모습(보이는 자식)·명중 이펙트 배율. 판정(루트 콜라이더)은 그대로 · 항목 없는 단계 = 1
|
||||
public WLProjectileStageScale[] stageScales = new WLProjectileStageScale[0];
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class WLProjectileStageScale { public int stage; public float scale = 1f; public float hitScale = 1f; }
|
||||
|
||||
[System.Serializable]
|
||||
public class WLProjectileVisualJson
|
||||
{
|
||||
|
|
@ -79,7 +84,6 @@ namespace WL.Combat.Weapon
|
|||
{
|
||||
if (pb == null) return;
|
||||
WLStaffEarth.OnProjectileSet(pb); // #935 지팡이 3타 투사체 표시(쏠 때마다 · 이미 겉모습을 붙인 풀 인스턴스도)
|
||||
if (s_done.Contains(pb.GetInstanceID())) return;
|
||||
if (!s_loaded) Load();
|
||||
WLProjectileVisualRule rule;
|
||||
var n = pb.name;
|
||||
|
|
@ -89,8 +93,33 @@ namespace WL.Combat.Weapon
|
|||
s_rules.TryGetValue(key, out rule); s_byName[n] = rule;
|
||||
}
|
||||
if (rule == null) return;
|
||||
Events++;
|
||||
if (Attach(pb, rule)) s_done.Add(pb.GetInstanceID());
|
||||
if (!s_done.Contains(pb.GetInstanceID())) { Events++; if (Attach(pb, rule)) s_done.Add(pb.GetInstanceID()); }
|
||||
ApplyStageScale(pb, rule); // #948 매 발사(풀 재사용 포함) — 콤보 단계별 배율
|
||||
}
|
||||
|
||||
public static int StageScaled; public static string StageLog = "";
|
||||
static readonly Dictionary<int, Vector3[]> s_baseScales = new Dictionary<int, Vector3[]>(); // 투사체 인스턴스 → 직접 자식들의 기본 localScale
|
||||
|
||||
/// <summary>#948 쏜 PC 의 콤보 단계(ComboTracker.LastStage)에 맞는 stageScales 항목이 있으면 보이는 직접 자식(원본 FX 노드 · ~WL_Visual)의 localScale 과 명중 이펙트 배율을 곱한다. 콜라이더가 달린 자식(판정)은 건드리지 않는다. 항목이 없으면 기본값(풀 재사용 복원).</summary>
|
||||
static void ApplyStageScale(ProjectileBase pb, WLProjectileVisualRule rule)
|
||||
{
|
||||
if (rule.stageScales == null || rule.stageScales.Length == 0) return;
|
||||
var pd = pb.Get_ProjectTileData(); var shooter = pd != null ? pd.shooter as PCActor : null;
|
||||
var ct = shooter != null ? shooter.GetComponent<WL.Combat.Core.ComboTracker>() : null;
|
||||
int stage = ct != null ? ct.LastStage : 0;
|
||||
WLProjectileStageScale e = null;
|
||||
foreach (var s in rule.stageScales) if (s != null && s.stage == stage) { e = s; break; }
|
||||
float mul = e != null ? e.scale : 1f, hitMul = e != null ? e.hitScale : 1f;
|
||||
int id = pb.GetInstanceID(); var t = pb.transform; Vector3[] bases;
|
||||
if (!s_baseScales.TryGetValue(id, out bases) || bases.Length != t.childCount)
|
||||
{
|
||||
bases = new Vector3[t.childCount];
|
||||
for (int i = 0; i < t.childCount; i++) bases[i] = t.GetChild(i).localScale;
|
||||
s_baseScales[id] = bases;
|
||||
}
|
||||
for (int i = 0; i < t.childCount; i++) { var c = t.GetChild(i); if (c.GetComponent<Collider>() != null) continue; c.localScale = bases[i] * mul; }
|
||||
var hf = pb.GetComponent<WLProjectileHitFx>(); if (hf != null) hf.scale = rule.hitScale * hitMul;
|
||||
if (e != null) { StageScaled++; StageLog = pb.name + " 단계 " + stage + " 겉모습 × " + mul + " · 명중 × " + hitMul; }
|
||||
}
|
||||
|
||||
/// <summary>#917 속사(WLRapidShot)가 쏜 직후 부른다 — Set 훅(OnSet)이 이미 붙였으면 아무것도 안 한다(1 회).</summary>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace WL.Combat.Weapon
|
|||
public float knockbackDistance; // #906 0 보다 크면 원본 KnockBack 대신 WLLaunch.Fling 으로 정확히 이만큼(m) knockbackSeconds 초에 밀고 띄운다
|
||||
public float staggerSeconds; // #906 피격 경직(피격 자세 굳힘 + 이동 · 공격 잠금)을 이 시간(초)까지 늘림 — 다음 타가 자연스럽게 맞게
|
||||
public int wallBounce; // #908 1 = knockbackDistance 로 날아가다 벽(이동 불가 경계)에 닿으면 튕겨 남은 거리를 계속
|
||||
public float delaySeconds; // #948 타격 확정 뒤 이만큼(초) 기다렸다 넉백·띄우기·기절을 건다 — PD 「이펙트 등장 후 타이밍에 맞게 몬스터가 튀어오르며 밀려나게(지금은 이펙트 생성과 동시에)」 · 경직은 즉시
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
|
|
@ -69,12 +70,8 @@ namespace WL.Combat.Weapon
|
|||
foreach (var r in rules)
|
||||
{
|
||||
if (r == null || r.stage != stage || r.classIds == null || System.Array.IndexOf(r.classIds, cls) < 0) continue;
|
||||
if (r.knockbackDistance > 0f) WLLaunch.Fling(e.victim, pc, r.knockbackDistance, Mathf.Max(0.05f, r.knockbackSeconds), r.launchHeight, r.stunSeconds, r.wallBounce != 0);
|
||||
else
|
||||
{
|
||||
if (r.knockbackSeconds > 0f || r.stunSeconds > 0f) WLLaunch.KnockbackStun(e.victim, pc, r.knockbackSeconds, r.knockbackSpeed, r.stunSeconds);
|
||||
if (r.launchHeight > 0f && r.knockbackSeconds > 0f) WLLaunch.LiftOnly(e.victim, r.launchHeight, WLLaunch.EffectiveKnockbackSeconds(r.knockbackSeconds));
|
||||
}
|
||||
if (r.delaySeconds > 0f) e.victim.StartCoroutine(CoDelayed(r, e.victim, pc)); // #948 이펙트가 먼저 보이고 몸이 뒤따르게
|
||||
else Push(r, e.victim, pc);
|
||||
if (r.staggerSeconds > 0f) WL.Combat.Reaction.MobStagger.Extend(e.victim, r.staggerSeconds);
|
||||
Applied++; LastLog = "stage " + stage + " → " + e.victim.name + " 넉백 " + (r.knockbackDistance > 0f ? r.knockbackDistance : WLLaunch.EffectiveKnockbackSeconds(r.knockbackSeconds) * r.knockbackSpeed).ToString("0.##") + " m · 띄움 " + r.launchHeight + " m · 기절 " + r.stunSeconds + " s · 경직 " + r.staggerSeconds + " s";
|
||||
return;
|
||||
|
|
@ -82,5 +79,23 @@ namespace WL.Combat.Weapon
|
|||
}
|
||||
catch (System.Exception ex) { Errors++; LastLog = "실패 — " + ex.Message; }
|
||||
}
|
||||
|
||||
static void Push(WLStageHitRule r, Actor victim, PCActor pc)
|
||||
{
|
||||
if (DSUtil.CheckNull(victim) || victim.IsDead()) return;
|
||||
if (r.knockbackDistance > 0f) WLLaunch.Fling(victim, pc, r.knockbackDistance, Mathf.Max(0.05f, r.knockbackSeconds), r.launchHeight, r.stunSeconds, r.wallBounce != 0);
|
||||
else
|
||||
{
|
||||
if (r.knockbackSeconds > 0f || r.stunSeconds > 0f) WLLaunch.KnockbackStun(victim, pc, r.knockbackSeconds, r.knockbackSpeed, r.stunSeconds);
|
||||
if (r.launchHeight > 0f && r.knockbackSeconds > 0f) WLLaunch.LiftOnly(victim, r.launchHeight, WLLaunch.EffectiveKnockbackSeconds(r.knockbackSeconds));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>#948 지연 뒤 밀기·띄우기 — 피해자(Actor) 코루틴이라 그 사이 죽거나 꺼지면 자연히 멈춘다.</summary>
|
||||
static System.Collections.IEnumerator CoDelayed(WLStageHitRule r, Actor victim, PCActor pc)
|
||||
{
|
||||
yield return new WaitForSeconds(r.delaySeconds);
|
||||
try { Push(r, victim, pc); } catch (System.Exception ex) { Errors++; LastLog = "지연 밀기 실패 — " + ex.Message; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
"dummyOffset": [0, 0, 0],
|
||||
"dummyStill": 1,
|
||||
"preloadEffects": 0,
|
||||
"gameMode": "Lobby",
|
||||
"combatFormCheckSeconds": 0.5,
|
||||
"autoResetDummySeconds": 2.0,
|
||||
"moveKeys": 1,
|
||||
"attackKey": "Space",
|
||||
"weaponPrevKey": "O",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ namespace WL.SkillTest
|
|||
public float[] dummyOffset = { 0f, 0f, 0f }; // 전부 0 = WLIslandMobSettings.hitTestDummyOffset
|
||||
public int dummyStill = 1; // 1 = 더미 제자리
|
||||
public int preloadEffects = 0; // 1 = 원본 InGameInfo 이펙트·스킬 예열(514 인스턴스 · 약 18 s) · 0 = 생략(첫 사용 때 로드)
|
||||
public string gameMode = "Lobby"; // InGameInfo 게임 모드 — 섬과 같은 Lobby(자동 전투·대상 찾기가 로비 규칙). Stage 로 두면 PC 가 대상 없이 달린다(#948 실측). 로비 컨트롤러(pcanim_lobby) 문제는 Set_Obj 동안만 Stage 로 바꾸는 전투 형태 적용(섬 WLIslandAvatar.ApplyCombatForm 과 같은 꼴)으로 푼다
|
||||
public float combatFormCheckSeconds = 0.5f; // 이 간격으로 PC 가 로비 컨트롤러면 전투 형태 다시 적용 · 대상이 없으면 더미를 다시 겨눔
|
||||
public float autoResetDummySeconds = 2.0f; // 더미가 공격 사거리 밖(넉백 누적)에 이만큼(초) 머물면 PC 앞 기본 자리로(R 키와 같음) → 콤보가 끊기지 않고 반복된다(#948 · 0 = 끔)
|
||||
public int moveKeys = 1; // 1 = WASD/방향키도 이동(가상패드와 함께 · PD #947 「가상패드 + 키 입력」)
|
||||
public string attackKey = "Space";
|
||||
public string weaponPrevKey = "O"; // PD #947: O = 무기 왼쪽 순환
|
||||
|
|
@ -81,7 +84,7 @@ namespace WL.SkillTest
|
|||
|
||||
static WLSkillTestJson s_cfg;
|
||||
static System.Reflection.FieldInfo s_fEffects, s_fMagic, s_fMagicArr;
|
||||
float _lastRt, _simH, _simV;
|
||||
float _lastRt, _simH, _simV, _formTimer, _outOfRange;
|
||||
bool _moving, _slow;
|
||||
KeyCode _kAttack = KeyCode.Space, _kPrev = KeyCode.O, _kNext = KeyCode.P, _kReset = KeyCode.R, _kSlow = KeyCode.T, _kPause = KeyCode.Backslash;
|
||||
string _msg = "";
|
||||
|
|
@ -132,6 +135,7 @@ namespace WL.SkillTest
|
|||
|
||||
// 예열을 켠 경우만: InGameInfo.Start 의 이펙트 예열 루프(테이블 0.1 s 폴링 뒤 1프레임)가 끝난 다음에 PC 를 만든다(먼저 만들면 PC 피격 이펙트 키가 먼저 등록돼 그 루프의 Add 가 중복 · #947 2차 Play 실측 · 원본에 가드도 넣음)
|
||||
if (cfg.preloadEffects != 0) for (float t = 0f; t < 3f && !EffectsPreloaded(); t += Time.unscaledDeltaTime) yield return null;
|
||||
if (!Try("gamemode", () => SetGameMode(cfg.gameMode))) yield break;
|
||||
Lap("ingameinfo");
|
||||
|
||||
if (pcActor == null || dummyActor == null) { Fail("refs-missing", "pcActor/dummyActor 참조 없음 — Build Scene 다시"); yield break; }
|
||||
|
|
@ -146,7 +150,7 @@ namespace WL.SkillTest
|
|||
cls = ResolveClass(cfg);
|
||||
pcActor.transform.position = NavPos(V3(cfg.spawnPos));
|
||||
pcActor.gameObject.SetActive(true); // 빌더가 꺼 둔 것을 Set 직전에 켠다(원본 Set 도 켜지만 그 전에 콜라이더 bounds 를 읽는다)
|
||||
pcActor.Set(false, null, sdata, cls, true);
|
||||
WithStageMode(() => pcActor.Set(false, null, sdata, cls, true)); // Set 안의 Set_Obj 가 Lobby 면 로비 컨트롤러를 입힌다(#948 실측: 지팡이가 pcanim_lobby 로 서서 투사체 0) → 섬처럼 그 동안만 Stage
|
||||
FillMagicSlots(pcActor, sdata);
|
||||
ActorInfo.Ins.Add_Actor(pcActor, eRole.PC, 0, pcActor.name);
|
||||
})) yield break;
|
||||
|
|
@ -202,6 +206,60 @@ namespace WL.SkillTest
|
|||
|
||||
void Fail(string stage, string msg) { Stage = stage; LastLog = msg; WriteStatus(); Debug.LogWarning("[WLSkillTest] " + stage + " — " + msg); } // LogError 금지(원본 ErrorLogHook 팝업)
|
||||
|
||||
static System.Reflection.FieldInfo s_fMode;
|
||||
public static int CombatForms;
|
||||
|
||||
static System.Reflection.FieldInfo ModeField()
|
||||
{
|
||||
if (s_fMode == null) s_fMode = typeof(InGameInfo).GetField("m_GameMode", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
|
||||
return s_fMode;
|
||||
}
|
||||
|
||||
/// <summary>InGameInfo 의 비공개 m_GameMode 를 JSON 값(기본 Lobby = 섬과 같음)으로.</summary>
|
||||
static void SetGameMode(string mode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(mode)) return;
|
||||
eGameMode m; if (!System.Enum.TryParse(mode, true, out m)) { LastLog = "게임 모드 이름 오류 " + mode; return; }
|
||||
var ig = InGameInfo.Ins; var f = ModeField();
|
||||
if (ig == null || f == null) { LastLog = "InGameInfo/m_GameMode 없음"; return; }
|
||||
f.SetValue(ig, m);
|
||||
}
|
||||
|
||||
/// <summary>act 를 Stage 모드로 돌린다(Lobby 였으면 끝나고 되돌림) — 원본 PCActor.Set_Obj 가 Lobby 면 pcanim_lobby 를 입히는 것을 피한다(섬 WLIslandAvatar.ApplyCombatForm · WLWeaponClasses.RefreshModelIfNeeded 와 같은 꼴).</summary>
|
||||
static void WithStageMode(System.Action act)
|
||||
{
|
||||
var ig = InGameInfo.Ins; var f = ModeField();
|
||||
object prev = ig != null && f != null ? f.GetValue(ig) : null; bool flipped = false;
|
||||
try
|
||||
{
|
||||
if (f != null && prev is eGameMode && (eGameMode)prev == eGameMode.Lobby) { f.SetValue(ig, eGameMode.Stage); flipped = true; }
|
||||
act();
|
||||
}
|
||||
finally { if (flipped) f.SetValue(ig, prev); }
|
||||
}
|
||||
|
||||
static bool PcInLobbyForm(PCActor pc)
|
||||
{
|
||||
var an = pc != null ? pc.GetComponentInChildren<Animator>() : null;
|
||||
var c = an != null ? an.runtimeAnimatorController : null;
|
||||
return c != null && c.name.IndexOf("lobby", System.StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>무기 교체(O/P · 숫자 키 등급) 뒤 원본 Set_Obj 가 로비 컨트롤러를 다시 입혔으면 전투 형태로 되돌리고, 대상을 잃었으면 더미를 다시 겨눈다(0.5 s 마다 · JSON combatFormCheckSeconds).</summary>
|
||||
void CheckCombatForm(PCActor pc)
|
||||
{
|
||||
if (PcInLobbyForm(pc)) { WithStageMode(() => pc.Set_Obj()); CombatForms++; _msg = "전투 형태 다시 적용 " + CombatForms; }
|
||||
if (Dummy != null && pc.Get_Target() == null && !_moving) pc.Change_Target(false, Dummy);
|
||||
// 더미가 사거리 밖에 머물면(넉백 누적) 자동 R — 콤보 반복 관찰용
|
||||
float auto = Cfg.autoResetDummySeconds;
|
||||
if (auto > 0f && Dummy != null)
|
||||
{
|
||||
var d = Dummy.Get_position() - pc.Get_position(); d.y = 0f;
|
||||
if (d.magnitude > pc.WL_AttackRange + 0.3f) { _outOfRange += Mathf.Max(0.1f, Cfg.combatFormCheckSeconds); if (_outOfRange >= auto) { _outOfRange = 0f; _msg = "더미 사거리 밖 " + auto + " s → " + ResetDummy(); } }
|
||||
else _outOfRange = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>InGameInfo.Start 의 예열 루프가 돌았는가 — 비공개 dic_str_Effect 의 키 수 ≥ 이펙트 표(목록 1) 행 수.</summary>
|
||||
static bool EffectsPreloaded()
|
||||
{
|
||||
|
|
@ -374,6 +432,8 @@ namespace WL.SkillTest
|
|||
var pc = MyValue.MyPC as PCActor;
|
||||
if (pc == null) return;
|
||||
var cfg = Cfg;
|
||||
_formTimer += Time.unscaledDeltaTime;
|
||||
if (_formTimer >= Mathf.Max(0.1f, cfg.combatFormCheckSeconds)) { _formTimer = 0f; CheckCombatForm(pc); }
|
||||
|
||||
// 이동 — 섬 조이스틱(WLIslandAvatar.DrivePc · 원본 FloatingJoystick)과 같은 호출 순서. 누름 = Set_AutoBraking(false) → JoystickStatus 1 · 매 프레임 Run_byJoystick(카메라 기준) · 뗌 = JoystickStatus 0 → Set_AutoBraking(true)(Play_Idle · 자동 전투 재개)
|
||||
float h = 0f, v = 0f;
|
||||
|
|
|
|||
Loading…
Reference in New Issue