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

168 lines
11 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLBlackHole.cs — 맹약(오브) 각성 「블랙홀」(Skill_BlackHole): 범위 안 몬스터가 중심으로 빨려 들어가며 다단 피해 → 폭파 때 강한 피해 1회 (#978 · 2026-10-24)
//
// PD 원문: 「오브 각성 스킬이 발동되면, 해당 위치로 몬스터들이 블랙홀과 같이 빨려들어가며 다단 히트 피해를 받고, 폭파할 때 강력한 피해 1회가 발동되면 좋겠어.」
// ■ 원본(Skill_BlackHole.Co_BlackHole): 수명 동안 간격마다 범위 안 적 Attract + 피해 → 0.5 초 뒤 같은 피해 1회(+ 각성 2단계 기절)
// ■ WL — 원본 Set 끝 훅 1줄(Take)이 넘겨받는다: 매 프레임 범위 안 적을 중심 쪽으로 pullSpeed(m/s)로 끌어당김(pullMinDist 까지 · 길찾기 에이전트면 Move) ·
// 원본 간격마다 원본 투사체 피해(ProcessCollider = 피격 반응 · 이펙트 그대로) · 수명 + explodeDelay 뒤 폭파 = 피해 배율 explodeDamageMul 로 1회 + 폭발 이펙트 + 원본 기절
// ■ #979 PD 「블랙홀의 색상이 너무 밝아서 블랙홀 컨셉이 아닌 느낌이야」 — 블랙홀 동안 원본 렌더러를 끄고(hideOriginal) 검은 코어(GA BlackHole01 사본) ·
// 가장자리에서 중심으로 빨려 드는 줄기(범위 배율)를 띄우고(lifeFx) 폭파 = 보라 폭발 사본(explodeFx)
// ■ 값 = Resources/WL/WLBlackHole.json (C45) · enabled 0 = 원본 그대로
// 🔴 Debug.LogError 금지(원본 팝업).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace WL.Combat.Weapon
{
[System.Serializable]
public class WLBlackHoleFx { public string path = ""; public float scale = 1f, perRange = 0f; } // 배율 = scale × (perRange > 0 ? 범위 × perRange : 1)
[System.Serializable]
public class WLBlackHoleJson
{
public int enabled = 0;
public float pullSpeed = 3f, pullMinDist = 0.4f;
public float explodeDelay = 0.5f, explodeDamageMul = 5f;
public string explodeFx = "WL/Impact/WL_Impact_Explosion";
public float explodeFxScale = 0.5f, explodeFxLife = 2f;
public int hideOriginal = 0; // #979 1 = 블랙홀 동안 원본 이펙트 렌더러를 끔(끝나 풀로 돌아갈 때 되살림)
public WLBlackHoleFx[] lifeFx = new WLBlackHoleFx[0]; // #979 블랙홀 동안 중심에 띄울 WL 이펙트(검은 코어 · 빨려 드는 줄기)
public float lifeFxFade = 0.25f; // 폭파 때 방출을 멈춘 뒤 지우기까지(s)
public float collapseTo = 1f; // #979 폭파 전 explodeDelay 동안 블랙홀 이펙트가 이 배율까지 줄어듦(응축 → 폭발 · 1 = 그대로)
public string note = "";
}
public static class WLBlackHole
{
public const string ResourcesPath = "WL/WLBlackHole";
public static int Taken, Ticks, TickHits, ExplodeHits; public static float MaxPulled; public static string LastLog = "";
static WLBlackHoleJson s_cfg;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Reset() { s_cfg = null; }
internal static WLBlackHoleJson Cfg
{
get
{
if (s_cfg != null) return s_cfg;
s_cfg = new WLBlackHoleJson();
var ta = Resources.Load<TextAsset>(ResourcesPath);
if (ta == null) { LastLog = "설정 없음 " + ResourcesPath; return s_cfg; }
try { JsonUtility.FromJsonOverwrite(ta.text, s_cfg); } catch (System.Exception ex) { LastLog = "설정 파싱 실패 — " + ex.Message; }
return s_cfg;
}
}
/// <summary>원본 Skill_BlackHole.Set 훅 — true 면 WL 이 진행(원본 Co_BlackHole 건너뜀). 값은 원본이 각성 단계까지 계산한 것.</summary>
public static bool Take(ProjectileBase pb, float range, float life, float gap, float stun)
{
try
{
var c = Cfg; if (c.enabled == 0 || pb == null) return false;
var pd = pb.Get_ProjectTileData(); if (pd == null || DSUtil.CheckNull(pd.shooter)) return false;
var run = pb.GetComponent<WLBlackHoleRunner>(); if (run == null) run = pb.gameObject.AddComponent<WLBlackHoleRunner>();
run.Begin(pb, pd, range, life, Mathf.Max(0.05f, gap), stun, c); Taken++;
return true;
}
catch (System.Exception ex) { LastLog = "넘겨받기 실패 — " + ex.Message; return false; }
}
}
internal sealed class WLBlackHoleRunner : MonoBehaviour
{
readonly List<Renderer> _hidden = new List<Renderer>(); readonly List<GameObject> _fx = new List<GameObject>();
public void Begin(ProjectileBase pb, ProjectileData pd, float range, float life, float gap, float stun, WLBlackHoleJson c)
{
StopAllCoroutines(); EndLifeFx(0f); RestoreOriginal(); StartCoroutine(Run(pb, pd, range, life, gap, stun, c));
}
void HideOriginal(ProjectileBase pb) { foreach (var r in pb.GetComponentsInChildren<Renderer>(true)) if (r.enabled) { r.enabled = false; _hidden.Add(r); } }
void RestoreOriginal() { foreach (var r in _hidden) if (r != null) r.enabled = true; _hidden.Clear(); }
void SpawnLifeFx(WLBlackHoleJson c, Vector3 center, float range)
{
if (c.lifeFx == null) return;
foreach (var f in c.lifeFx)
{
if (f == null || string.IsNullOrEmpty(f.path)) continue;
var p = Resources.Load<GameObject>(f.path); if (p == null) { WLBlackHole.LastLog = "블랙홀 이펙트 없음 " + f.path; continue; }
var g = Instantiate(p, center, Quaternion.identity); g.transform.localScale = p.transform.localScale * (f.scale * (f.perRange > 0f ? range * f.perRange : 1f));
foreach (var ps in g.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; }
_fx.Add(g);
}
}
void EndLifeFx(float fade)
{
foreach (var g in _fx)
{
if (g == null) continue;
foreach (var ps in g.GetComponentsInChildren<ParticleSystem>(true)) ps.Stop(true, ParticleSystemStopBehavior.StopEmitting);
Destroy(g, Mathf.Max(0f, fade));
}
_fx.Clear();
}
void OnDisable() { EndLifeFx(0f); RestoreOriginal(); } // 풀로 돌아갈 때 원본 렌더러를 되살려 다음 사용(enabled 0 포함)에 그대로
static List<Actor> Enemies(ProjectileData pd, Vector3 center, float range)
{
var res = new List<Actor>(); var info = ActorInfo.Ins; if (info == null || DSUtil.CheckNull(pd.shooter)) return res;
var list = info.Get_EnemyActors(pd.shooter.IsEnemy(), center, range + 3f); if (list == null) return res;
try { for (int k = 0; k < list.Count; k++) { var a = list[k]; if (a == null || a.IsDead()) continue; var v = a.Get_position() - center; v.y = 0f; if (v.magnitude <= range + WL.Combat.WLCombatMotionSettings.BodyRadius(a)) res.Add(a); } }
finally { info.Release_Actors(list); }
return res;
}
static int HitAll(ProjectileBase pb, List<Actor> list)
{
int n = 0; foreach (var a in list) { if (a == null || a.IsDead()) continue; var col = a.GetComponent<Collider>(); if (col != null && col.enabled && pb.ProcessCollider(col, false)) n++; }
return n;
}
IEnumerator Run(ProjectileBase pb, ProjectileData pd, float range, float life, float gap, float stun, WLBlackHoleJson c)
{
Vector3 center = pb.transform.position; float t0 = Time.time, nextTick = 0f, pulled = 0f;
if (c.hideOriginal != 0) HideOriginal(pb); // #979 PD 「블랙홀 색이 너무 밝아」 — 원본(밝은 보라 · 흰 섬광)을 끄고 WL 검은 코어 · 빨려 드는 줄기로
SpawnLifeFx(c, center, range);
while (Time.time - t0 < life)
{
float t = Time.time - t0, step = c.pullSpeed * Time.deltaTime;
foreach (var a in Enemies(pd, center, range)) // 빨려 들어감 — 중심 쪽 수평 이동(pullMinDist 까지)
{
var v = center - a.Get_position(); v.y = 0f; float d = v.magnitude - c.pullMinDist; if (d <= 0f) continue;
var delta = v.normalized * Mathf.Min(step, d);
var ag = a.GetComponent<UnityEngine.AI.NavMeshAgent>();
if (ag != null && ag.enabled && ag.isOnNavMesh) ag.Move(delta); else a.transform.position += delta;
pulled += delta.magnitude;
}
if (t >= nextTick) { nextTick += gap; WLBlackHole.Ticks++; WLBlackHole.TickHits += HitAll(pb, Enemies(pd, center, range)); }
yield return null;
}
if (c.explodeDelay > 0f) // 응축 — 코어 · 줄기가 collapseTo 배까지 줄어든 뒤 폭파
{
var s0 = new List<Vector3>(); foreach (var g in _fx) s0.Add(g != null ? g.transform.localScale : Vector3.one);
for (float t = 0f; t < c.explodeDelay; t += Time.deltaTime)
{
float k = Mathf.Lerp(1f, c.collapseTo, t / c.explodeDelay);
for (int i = 0; i < _fx.Count; i++) if (_fx[i] != null) _fx[i].transform.localScale = s0[i] * k;
yield return null;
}
}
EndLifeFx(c.lifeFxFade);
// 폭파 — 피해 배율을 잠시 올려 1회 · 기절 · 폭발 이펙트
var targets = Enemies(pd, center, range); float mul0 = pd.AddDmgPercent;
pd.AddDmgPercent = mul0 * Mathf.Max(1f, c.explodeDamageMul);
try { WLBlackHole.ExplodeHits += HitAll(pb, targets); } finally { pd.AddDmgPercent = mul0; }
if (stun > 0f) foreach (var a in targets) if (a != null && !a.IsDead()) a.Stun(stun, true);
var fx = !string.IsNullOrEmpty(c.explodeFx) ? Resources.Load<GameObject>(c.explodeFx) : null;
if (fx != null) { var g = Instantiate(fx, center, Quaternion.identity); g.transform.localScale = fx.transform.localScale * c.explodeFxScale; foreach (var ps in g.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; } Destroy(g, Mathf.Max(0.5f, c.explodeFxLife)); }
WLBlackHole.MaxPulled = Mathf.Max(WLBlackHole.MaxPulled, pulled);
WLBlackHole.LastLog = "범위 " + range.ToString("F1") + " m · 수명 " + life.ToString("F1") + " s · 간격 " + gap.ToString("F2") + " · 다단 누적 " + WLBlackHole.TickHits + " · 폭파 명중 " + WLBlackHole.ExplodeHits + " × 배율 " + c.explodeDamageMul + (fx == null ? " · 폭발 이펙트 없음 " + c.explodeFx : "");
WLStaffEarth.OffOriginal(pb);
}
}
}