// ───────────────────────────────────────────────────────────────────────────── // WLSummonMelee.cs — 마법책 주력 용암 골렘(소환체) 근접 · 공격 간격 · 몸 불꽃 · 둘레 지속 피해 (#1003 · 2026-10-31) // // PD 원문(#1003): 「기존 골렘과 같이 근접 형태의 골렘 중 용암 골렘은 없는거야? → 만약 대체 골렘이 없다면 지금 골렘의 공격 사거리를 근접처럼 줄이고, // 몸 주변을 불타는 것처럼 연출해줘. (골렘의 공격 속도를 2초에 한번 정도 공격할 만큼 느리게 하되, 주위 0.5m이내 근접한 적에게 골렘 공격력의 1/10의 // 지속적인 피해를 입히는 기능을 추가해줘)」 // // ■ 근접 — summonMeleeGap ≥ 0 이면 원본 소환체 정지 거리(SummonActor.Set 이 1.5 m 로 박음 · 중심 거리)를 매 프레임 「골렘 + 대상 에이전트 반경 + summonMeleeGap」 으로 // · #1004 PD 「다가가 멈추는 거리가 아니라 공격 판정 범위(팔이 늘어나 공격하는 거리)」 → 음수(원본 그대로) + 판정 상자 앞 끝 HitReach(WLHitboxFit) + 공격 클립 주먹 이동 축소(1004_arm.cs) // ■ 공격 간격 — 공격이 시작되면 summonAttackInterval 초 동안 원본 IsStop(보호 속성 · 리플렉션)으로 다음 공격 · 추적을 묶는다(공격 동작 속도는 그대로) // ■ 몸 불꽃 — 원본 Effect_Burn(원본 골렘 각성 3단계가 쓰는 이펙트)의 반복 불꽃 층만 남긴 WL 사본 WL_Fx_GolemBurn 을 소환 시간 동안 발밑에 붙여 따라감 // (원본 풀로 띄우면 EffectScaleGuard 가 키 0.6 규칙으로 × 0.23 = 발밑 작은 불 — 1차 Play 실측) // ■ 둘레 지속 피해 — summonAuraTick 초마다 몸(충돌체 경계)에서 summonAuraRadius m 안(적 몸 반경 빼고) 적에게 골렘 공격력 × summonAuraRate // (원본 Skill_Golem 각성 3단계 화염과 같은 Get_DamageInfo · Get_Damage 경로) // ■ 값 = Resources/WL/WLStaffEarth.json Skill_Golem 행(summon*) · 붙임 = WLStaffEarth.AfterSummon(마법책 중력 진행이 예약한 소환만 · 셉터 골렘은 원본 그대로) // 🔴 Debug.LogError 금지(원본 ErrorLogHook 팝업) — 예외는 LastLog 로만 // ───────────────────────────────────────────────────────────────────────────── using System.Reflection; using UnityEngine; namespace WL.Combat.Weapon { [DisallowMultipleComponent] public sealed class WLSummonMelee : MonoBehaviour { public static int Armed, Holds, AuraTicks, AuraHits, Errors; public static string LastLog = "", AuraLog = ""; static PropertyInfo s_isStop; static FieldInfo s_navStop; SummonActor _a; WLStaffEarthSkill _r; Collider _col; float _armedAt, _holdUntil, _nextTick; bool _held; eAnim _prev; readonly ProjectileData _pd = new ProjectileData(); /// 소환 콜백(같은 프레임)에서 부른다 — 값 묶기 · 몸 불꽃(life 초). public static void Arm(SummonActor a, WLStaffEarthSkill r, float life) { if (a == null || r == null) return; var m = a.GetComponent(); if (m == null) m = a.gameObject.AddComponent(); m._a = a; m._r = r; m._col = a.GetComponent(); m._armedAt = Time.time; m._held = false; m._holdUntil = 0f; m._prev = eAnim.Idle; m._nextTick = Time.time + Mathf.Max(0.05f, r.summonAuraTick); m.enabled = true; m.ClearBurn(); // #1004 뼈마다(몸 주변에서 입자가 피어오름) · 뼈가 없으면 발밑 1개 — 월드 크기 · 자리 유지한 채 그 뼈를 따라감 var bones = r.summonBurnBones != null && r.summonBurnBones.Length > 0 ? r.summonBurnBones : new[] { "" }; foreach (var path in bones) { var at = string.IsNullOrEmpty(path) ? a.transform : a.transform.Find(path); if (at == null) continue; var go = WLStaffEarth.SpawnAt(r.summonBurn, a, at.position, a.transform.forward, Mathf.Max(0f, life)); if (go != null) { go.transform.SetParent(at, true); m._burn.Add(go); } } Armed++; LastLog = "묶음 " + a.name + " · 불꽃 " + r.summonBurn.prefab + " × " + m._burn.Count + " · " + life.ToString("F0") + " s"; } /// #1004 골렘 공격 판정 상자 앞 끝(몸 중심에서 m) — WLHitboxFit 이 읽는다(0 = 원본 상자). public float HitReach { get { return _r != null && isActiveAndEnabled ? _r.summonHitReach : 0f; } } readonly System.Collections.Generic.List _burn = new System.Collections.Generic.List(); void ClearBurn() { foreach (var g in _burn) if (g != null) Destroy(g); _burn.Clear(); } void OnDisable() { if (_held) SetStop(false); _held = false; ClearBurn(); } void Update() { if (_a == null || _r == null || _a.IsDead()) return; try { var t = _a.Get_Target(); if (_r.summonMeleeGap >= 0f) SetNavStop(AgentR(_a) + (DSUtil.CheckNull(t) ? 0f : AgentR(t)) + _r.summonMeleeGap); // #1004 음수 = 원본 정지 거리 그대로 if (_held && Time.time >= _holdUntil) { SetStop(false); _held = false; } var cur = _a.Get_CurAnim(); if (!_held && _r.summonAttackInterval > 0f && cur == eAnim.Attack && _prev != eAnim.Attack) // 공격 시작 = 간격 동안 다음 공격 · 추적 묶음 { _holdUntil = Time.time + _r.summonAttackInterval; SetStop(true); _held = true; Holds++; LastLog = "공격 " + Time.time.ToString("F2") + " · 대상까지 " + (DSUtil.CheckNull(t) ? "-" : Flat(_a.Get_position(), t.Get_position()).ToString("F2")) + " m · 정지 거리 " + _a.Get_Agent().stoppingDistance.ToString("F2"); } _prev = cur; if (_r.summonAuraRate > 0f && Time.time >= _nextTick) { _nextTick = Time.time + Mathf.Max(0.05f, _r.summonAuraTick); Aura(); } } catch (System.Exception ex) { Errors++; LastLog = "실패 — " + ex.Message; } } /// 몸(충돌체 경계 · 수평)에서 summonAuraRadius m 안 적(몸 반경 뺌)에게 골렘 공격력 × summonAuraRate. void Aura() { var info = ActorInfo.Ins; if (info == null) return; var b = _col != null && _col.enabled ? _col.bounds : new Bounds(_a.Get_Center_position(), Vector3.one); float ext = Mathf.Max(b.extents.x, b.extents.z); var list = info.Get_EnemyActors(_a.IsEnemy(), _a.Get_position(), ext + _r.summonAuraRadius + 3f); if (list == null) return; AuraTicks++; int n = 0; double dmg = 0; try { for (int k = 0; k < list.Count; k++) { var e = list[k]; if (e == null || e.IsDead()) continue; var p = e.Get_position(); var cp = b.ClosestPoint(new Vector3(p.x, b.center.y, p.z)); if (Flat(cp, p) - WL.Combat.WLCombatMotionSettings.BodyRadius(e) > _r.summonAuraRadius) continue; _pd.shooter = _a; _pd.AddDmgPercent = _r.summonAuraRate; var di = _a.Get_DamageInfo(_pd); dmg = di.Damage; e.Get_Damage(di); n++; AuraHits++; } } finally { info.Release_Actors(list); } if (n > 0) AuraLog = "틱 " + Time.time.ToString("F2") + " · 맞음 " + n + " · 피해 " + dmg.ToString("F0") + " · 골렘 공격력 " + _a.Get_StatInfo().Get_Stat(eStat.BaseDmg).ToString("F0"); } static float Flat(Vector3 a, Vector3 b) { a.y = 0f; b.y = 0f; return Vector3.Distance(a, b); } static float AgentR(Actor a) { var ag = a != null ? a.Get_Agent() : null; if (ag == null) return 0f; var s = a.transform.lossyScale; return ag.radius * Mathf.Max(Mathf.Abs(s.x), Mathf.Abs(s.z)); } void SetNavStop(float v) { if (s_navStop == null) s_navStop = typeof(Actor).GetField("m_NavStopDistance", BindingFlags.Instance | BindingFlags.NonPublic); if (s_navStop != null) s_navStop.SetValue(_a, v); } void SetStop(bool v) { if (s_isStop == null) s_isStop = typeof(Actor).GetProperty("IsStop", BindingFlags.Instance | BindingFlags.NonPublic); if (s_isStop != null && _a != null) s_isStop.SetValue(_a, v); } } }