Project_WL/Assets/WL/Combat/Weapon/WLSummonMelee.cs

122 lines
8.7 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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();
/// <summary>소환 콜백(같은 프레임)에서 부른다 — 값 묶기 · 몸 불꽃(life 초).</summary>
public static void Arm(SummonActor a, WLStaffEarthSkill r, float life)
{
if (a == null || r == null) return;
var m = a.GetComponent<WLSummonMelee>(); if (m == null) m = a.gameObject.AddComponent<WLSummonMelee>();
m._a = a; m._r = r; m._col = a.GetComponent<Collider>(); 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";
}
/// <summary>#1004 골렘 공격 판정 상자 앞 끝(몸 중심에서 m) — WLHitboxFit 이 읽는다(0 = 원본 상자).</summary>
public float HitReach { get { return _r != null && isActiveAndEnabled ? _r.summonHitReach : 0f; } }
readonly System.Collections.Generic.List<GameObject> _burn = new System.Collections.Generic.List<GameObject>();
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; }
}
/// <summary>몸(충돌체 경계 · 수평)에서 summonAuraRadius m 안 적(몸 반경 뺌)에게 골렘 공격력 × summonAuraRate.</summary>
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);
}
}
}