diff --git a/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta b/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta index 6820d642c..df11b6bad 100644 --- a/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta +++ b/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta @@ -694,6 +694,49 @@ ModelImporter: maskType: 0 maskSource: {instanceID: 0} additiveReferencePoseFrame: 0 + - serializedVersion: 16 + name: Cast2_S_10303 + takeName: Take 001 + internalID: -998496362607748373 + firstFrame: 16 + lastFrame: 46 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 0 + loopBlend: 0 + loopBlendOrientation: 1 + loopBlendPositionY: 1 + loopBlendPositionXZ: 0 + 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.35 + functionName: Projectile + data: Projectile_10303 + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + transformMask: [] + maskType: 0 + maskSource: {instanceID: 0} + additiveReferencePoseFrame: 0 isReadable: 1 meshes: lODScreenPercentages: [] diff --git a/Assets/Res_Addr/Animations/pcanim_summoner.controller b/Assets/Res_Addr/Animations/pcanim_summoner.controller index 1b41f347e..a25795226 100644 --- a/Assets/Res_Addr/Animations/pcanim_summoner.controller +++ b/Assets/Res_Addr/Animations/pcanim_summoner.controller @@ -2349,7 +2349,7 @@ AnimatorState: m_MirrorParameterActive: 0 m_CycleOffsetParameterActive: 0 m_TimeParameterActive: 0 - m_Motion: {fileID: 6641085478485924818, guid: e0919932c9c179142ad06cf4ee11f204, + m_Motion: {fileID: -998496362607748373, guid: 7a1612ec6fd9fbf4b917575b7c1ecdcb, type: 3} m_Tag: m_SpeedParameter: diff --git a/Assets/Script/Character/Actor.cs b/Assets/Script/Character/Actor.cs index 53b090974..2cf4e8420 100644 --- a/Assets/Script/Character/Actor.cs +++ b/Assets/Script/Character/Actor.cs @@ -753,7 +753,7 @@ public class Actor : MyCoroutine if (!testskill) m_Stat.Set_Stat(false, eStat.MP, skilldata.n_MP); else m_SkillData.SetSkillExtraType = true; Set_MagicCoolTime(skilldata.Get_CoolTime(m_Stat.Get_Stat(eStat.COOLTIME_Multiplier)), index); - if (IsMainPC() && !testskill) ServerInfo.Ins.m_ServerData.Add_MissionCount(ePurpose.USE_SKILL_ALL); + if (IsMainPC() && !testskill && !DSUtil.CheckNull(ServerInfo.Ins) && ServerInfo.Ins.m_ServerData != null) ServerInfo.Ins.m_ServerData.Add_MissionCount(ePurpose.USE_SKILL_ALL); // WL #949 — 스킬 테스트 씬(서버 객체 없음)에서 NRE 로 스킬이 안 나가던 것(실측 Actor.cs:756) · 서버 있으면 원본 그대로 return Play_Skill(skilldata.s_Animation); } public virtual void Shoot_Skill() diff --git a/Assets/WL/Combat/CombatAnimMapping.asset b/Assets/WL/Combat/CombatAnimMapping.asset index 404d1b222..2589be23d 100644 --- a/Assets/WL/Combat/CombatAnimMapping.asset +++ b/Assets/WL/Combat/CombatAnimMapping.asset @@ -1153,26 +1153,29 @@ MonoBehaviour: intParameter: 0 floatParameter: 0 - stateName: attack2 - sourceFbxPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@Attack3_S.FBX + sourceFbxPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX subClipName: Cast2_S_{CID} - sourceClipName: + sourceClipName: incomingExitTime: 0.854 createIfMissing: 0 - firstFrame: -1 - lastFrame: -1 - speed: 0 + firstFrame: 16 + lastFrame: 46 + speed: 1 + maskPath: + bakeRootY: -1 + mirror: 0 events: - functionName: LookAtTarget time: 0 useNormalized: 1 normalizedTime: 0 - stringParameter: + stringParameter: intParameter: 0 floatParameter: 0 - functionName: Projectile time: 0 useNormalized: 1 - normalizedTime: 0.407 + normalizedTime: 0.35 stringParameter: Projectile_{CID} intParameter: 0 floatParameter: 0 diff --git a/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json b/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json index 5a4a3ca90..af625005d 100644 --- a/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json +++ b/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json @@ -7,9 +7,16 @@ "prefab": "WL/Book/WL_Book_Magic", "count": 1, "scale": 0.9, - "side": 0.55, - "height": 0.68, - "forward": 0.05, + "side": 0.35, + "height": 0.72, + "forward": 0.45, + "cameraSpace": 1, + "castStates": [ "Cast1_S", "Cast2_S", "BookCharge", "BookCast" ], + "castSide": 0.25, + "castHeight": 0.62, + "castForward": 0.5, + "hideHandWeapon": 1, + "_note_949": "#949 PD 「책이 캐릭터를 가리지 않게 가능한 캐릭터보다 뒤쪽에(추적 로직 수정) · 시전 동작 중엔 책이 캐릭터 모션 앞쪽으로 유지」 — 평소 자리 = 카메라 기준(cameraSpace 1 · forward + = 카메라에서 먼 쪽 = 화면 위 = 캐릭터 뒤 0.45 m · 옆 0.35 · 높이 0.72) · 시전 중(클립 이름 Cast1/Cast2/BookCharge/BookCast) = PC 정면 0.5 m · 옆 0.25 · 높이 0.62 · followLerp 로 오간다. 이전 = PC 오른쪽 0.55 · 앞 0.05(카메라에선 몸 앞을 가림)", "euler": [ 40.0, 0.0, diff --git a/Assets/WL/Combat/Weapon/WLFloatingBook.cs b/Assets/WL/Combat/Weapon/WLFloatingBook.cs index 6ce57e472..c78c3afad 100644 --- a/Assets/WL/Combat/Weapon/WLFloatingBook.cs +++ b/Assets/WL/Combat/Weapon/WLFloatingBook.cs @@ -36,6 +36,11 @@ namespace WL.Combat.Weapon public float animLoopStartSec, animLoopEndSec;// 책 클립 반복 구간(초 · 끝 ≤ 시작 = 반복 안 함) public int matchPcLayer; // 1 = 책 레이어를 PC 와 같게(같은 카메라 경로로 그려지게) public float pollSec = 0.25f; + // #949 PD 「책이 캐릭터를 가리지 않게 가능한 캐릭터보다 뒤쪽에 · 시전 동작 중엔 캐릭터 모션 앞쪽으로 유지」 + public int cameraSpace; // 1 = 평소 자리(side · forward)를 카메라 yaw 기준으로 — forward + = 카메라에서 먼 쪽(화면 위 · 캐릭터 뒤) · 0 = PC 정면 기준 + public string[] castStates = new string[0]; // PC 현재 클립 이름에 이 조각이 들면 「시전 중」 → castSide · castHeight · castForward(PC 정면 기준) 로 + public float castSide, castHeight, castForward; + public int hideHandWeapon; // #949 1 = 책이 떠 있는 동안 PC 손 소켓(w_*) 아래 무기 렌더러를 끈다(지팡이·셉터 숨김 · 책이 사라지면 되돌림) — 직업 표(수정 행)로 비우면 Set_Obj 재부착이 로비 컨트롤러를 끼워 실패(실측) public string note = ""; } @@ -98,6 +103,28 @@ namespace WL.Combat.Weapon for (int i = 0; i < _books.Count; i++) if (_books[i] == null) { missing = true; break; } if (_books.Count > 0 && (!want || missing || !ReferenceEquals(pc, _pc))) Clear(!want ? "조건 해제" : missing ? "씬 정리됨" : "PC 교체"); if (want && _books.Count == 0) Spawn(c, pc); + if (want && c.hideHandWeapon != 0 && _books.Count > 0) HideHandWeapon(pc); // 매 폴링 — 원본이 무기 모델을 다시 붙여도(Set_Obj) 다시 끈다 + } + + // #949 손 소켓(w_*) 아래 무기 렌더러 끄기 · 되돌리기 + readonly List _hiddenWeapon = new List(); + public static int WeaponHidden; + void HideHandWeapon(PCActor pc) + { + foreach (var t in pc.GetComponentsInChildren(true)) + { + if (!t.name.StartsWith("w_")) continue; + foreach (var r in t.GetComponentsInChildren(true)) + { + if (r == null || !r.enabled || r is ParticleSystemRenderer) continue; + r.enabled = false; _hiddenWeapon.Add(r); WeaponHidden++; + } + } + } + void RestoreHandWeapon() + { + for (int i = 0; i < _hiddenWeapon.Count; i++) if (_hiddenWeapon[i] != null) _hiddenWeapon[i].enabled = true; + _hiddenWeapon.Clear(); } /// 장착 무기(FR GroupNo)가 weaponIds 에 들거나, 그 무기의 공격표 class_id 가 classIds 에 드는가. @@ -139,6 +166,7 @@ namespace WL.Combat.Weapon if (_books.Count == 0) return; for (int i = 0; i < _books.Count; i++) if (_books[i] != null) Destroy(_books[i].gameObject); _books.Clear(); _anims.Clear(); _pc = null; + RestoreHandWeapon(); // #949 Cleared++; LastLog = "책 정리 — " + why; } @@ -166,6 +194,12 @@ namespace WL.Combat.Weapon Quaternion faceYaw = pcYaw; if (c.faceCamera != 0) { var cam = Camera.main; if (cam != null) { var f = cam.transform.forward; f.y = 0f; if (f.sqrMagnitude > 1e-4f) faceYaw = Quaternion.LookRotation(f.normalized, Vector3.up); } } var e = c.euler != null && c.euler.Length >= 3 ? Quaternion.Euler(c.euler[0], c.euler[1], c.euler[2]) : Quaternion.identity; + // #949 시전 중(현재 클립 이름에 castStates 조각) = PC 정면 기준 cast 자리 · 평소 = cameraSpace 면 카메라 yaw 기준(캐릭터 뒤 = 화면 위) + bool casting = IsCasting(c); + Casting = casting; + float side = casting ? c.castSide : c.side, height = casting ? c.castHeight : c.height, forward = casting ? c.castForward : c.forward; + Quaternion basis = casting || c.cameraSpace == 0 ? pcYaw : (c.faceCamera != 0 ? faceYaw : pcYaw); + if (!casting && c.cameraSpace != 0 && c.faceCamera == 0) { var cam = Camera.main; if (cam != null) { var f = cam.transform.forward; f.y = 0f; if (f.sqrMagnitude > 1e-4f) basis = Quaternion.LookRotation(f.normalized, Vector3.up); } } int n = _books.Count; float kp = snap || c.followLerp <= 0f ? 1f : 1f - Mathf.Exp(-c.followLerp * Time.deltaTime); float kr = snap || c.turnLerp <= 0f ? 1f : 1f - Mathf.Exp(-c.turnLerp * Time.deltaTime); @@ -176,13 +210,26 @@ namespace WL.Combat.Weapon var ring = Quaternion.Euler(0f, _orbit + 360f * share, 0f); float bob = Mathf.Sin((t * c.bobHz + share) * Mathf.PI * 2f) * c.bobAmp; float sw = Mathf.Sin((t * c.swayHz + share) * Mathf.PI * 2f); - var pos = pt.position + pcYaw * (ring * new Vector3(c.side, 0f, c.forward)) + Vector3.up * (c.height + bob); + var pos = pt.position + basis * (ring * new Vector3(side, 0f, forward)) + Vector3.up * (height + bob); var rot = faceYaw * ring * e * Quaternion.Euler(0f, sw * c.swayYawDeg, sw * c.swayRollDeg); if (kp >= 1f && kr >= 1f) b.SetPositionAndRotation(pos, rot); else b.SetPositionAndRotation(Vector3.Lerp(b.position, pos, kp), Quaternion.Slerp(b.rotation, rot, kr)); } } + public static bool Casting; // 프로브용 — 마지막 배치가 시전 자리였는가 + + /// #949 PC 애니메이터 0층 현재 클립 이름에 castStates 조각이 들면 시전 중. + bool IsCasting(WLFloatingBookJson c) + { + if (c.castStates == null || c.castStates.Length == 0 || _pc == null) return false; + var an = _pc.m_animation; if (an == null) return false; + var ci = an.GetCurrentAnimatorClipInfo(0); string name = ci.Length > 0 && ci[0].clip != null ? ci[0].clip.name : ""; + if (name.Length == 0) return false; + for (int i = 0; i < c.castStates.Length; i++) if (!string.IsNullOrEmpty(c.castStates[i]) && name.Contains(c.castStates[i])) return true; + return false; + } + /// 펼친 뒤 넘김 구간 반복 — 상태 시간이 끝 초를 넘으면 시작 초로 되돌린다. void LoopAnim(WLFloatingBookJson c) {