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

168 lines
11 KiB
C#
Raw 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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);
}
}
}