122 lines
8.7 KiB
C#
122 lines
8.7 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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);
|
||
}
|
||
}
|
||
}
|