118 lines
6.4 KiB
C#
118 lines
6.4 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLPierceProjectile.cs — 스킬 투사체 관통 + 겹쳐 있는 동안 일정 간격 다단 피해 (#974 · 2026-10-23)
|
|
//
|
|
// PD 원문: 「한손검 주력 스킬로 발사하는 검기의 크기를 50% 줄이고, 경로 상 적을 관통하며 모든 적에게 피해를 주도록 구현해줘.
|
|
// 0.2m 정도 넉백이 적용되어야 하며, 관통하는 동안 0.2초 간격으로 피해를 더 입힐 수 있어야 해. (만약 관통+밀리는 과정에 판정 범위에 있다면 1회 이상 피해를 받을 수 있음)」
|
|
// ■ 원본 = ProjectileBase.ProcessCollider(Off_byHit 기본 true) — 처음 맞힌 적에서 투사체를 끈다(Skill_EnergySlash = 루트 BoxCollider 1개 · ProjectileHitChecker 없음 · 코드 실측)
|
|
// ■ 처리 = 규칙 스킬(맞힌 투사체의 SkillListTableData.e_Skill)이면 원본 명중 줄의 훅(1줄 · 끄기 조건)이 끄지 않는다(관통) +
|
|
// rehitSeconds > 0 이면 투사체 루트에 WLPierceRehit 을 붙여 겹쳐 있는 적(OnTriggerStay)을 그 간격마다 다시 ProcessCollider(피해 · 명중 이펙트 · 피격 반응 원본 그대로)
|
|
// ■ 넉백 0.2 m = WLSkillHitFeel 규칙 pushMeters · 크기 50 % = WLProjectileVisual fxScale
|
|
// ■ 값 = Resources/WL/WLPierceProjectile.json (C45) · 끄기 = enabled 0
|
|
// 🔴 Debug.LogError 금지(원본 ErrorLogHook 팝업).
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace WL.Combat.Weapon
|
|
{
|
|
[System.Serializable]
|
|
public class WLPierceRule
|
|
{
|
|
public string skill = ""; // eEffect 이름(원본 스킬 클래스)
|
|
public int pierce = 1; // 1 = 맞혀도 끄지 않는다(경로 위 모든 적)
|
|
public float rehitSeconds; // > 0 = 겹쳐 있는 적을 이 간격(초)마다 다시 맞힌다
|
|
public string note = "";
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class WLPierceJson
|
|
{
|
|
public int enabled = 1;
|
|
public WLPierceRule[] rules = new WLPierceRule[0];
|
|
public string note = "";
|
|
}
|
|
|
|
public static class WLPierceProjectile
|
|
{
|
|
public const string ResourcesPath = "WL/WLPierceProjectile";
|
|
public static int Pierced, Rehits, Errors;
|
|
public static string LastLog = "";
|
|
|
|
static WLPierceJson s_cfg;
|
|
static Dictionary<int, WLPierceRule> s_rules;
|
|
|
|
internal static WLPierceJson Cfg
|
|
{
|
|
get
|
|
{
|
|
if (s_cfg != null) return s_cfg;
|
|
s_cfg = new WLPierceJson(); s_rules = new Dictionary<int, WLPierceRule>();
|
|
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; }
|
|
if (s_cfg.rules != null)
|
|
foreach (var r in s_cfg.rules)
|
|
{
|
|
eEffect ef;
|
|
if (r != null && System.Enum.TryParse(r.skill, out ef)) s_rules[(int)ef] = r;
|
|
}
|
|
return s_cfg;
|
|
}
|
|
}
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
|
static void Boot() { s_cfg = null; s_rules = null; }
|
|
|
|
static WLPierceRule RuleFor(ProjectileData d)
|
|
{
|
|
var c = Cfg;
|
|
if (c.enabled == 0 || d == null || d.m_SkillListTableData == null || s_rules == null) return null;
|
|
WLPierceRule r;
|
|
return s_rules.TryGetValue((int)d.m_SkillListTableData.e_Skill, out r) ? r : null;
|
|
}
|
|
|
|
/// <summary>원본 ProjectileBase.ProcessCollider 명중 줄의 훅 — true = 이 명중에서 투사체를 끄지 않는다(관통) · 다단이면 그 적의 마지막 명중 시각을 기록.</summary>
|
|
public static bool Pierces(ProjectileBase pb, ProjectileData d, Collider other)
|
|
{
|
|
try
|
|
{
|
|
var r = RuleFor(d);
|
|
if (r == null || r.pierce == 0 || pb == null) return false;
|
|
if (r.rehitSeconds > 0f)
|
|
{
|
|
var rh = pb.GetComponent<WLPierceRehit>();
|
|
if (rh == null) rh = pb.gameObject.AddComponent<WLPierceRehit>();
|
|
rh.Init(pb, r.rehitSeconds); rh.Mark(other);
|
|
}
|
|
Pierced++;
|
|
return true;
|
|
}
|
|
catch (System.Exception ex) { Errors++; LastLog = "관통 확인 실패 — " + ex.Message; return false; }
|
|
}
|
|
}
|
|
|
|
/// <summary>투사체 루트(충돌체와 같은 오브젝트)에 붙어 겹쳐 있는 적을 간격마다 다시 맞힌다. 처음 닿음은 원본 OnTriggerEnter 가 맡는다.</summary>
|
|
internal sealed class WLPierceRehit : MonoBehaviour
|
|
{
|
|
ProjectileBase _pb;
|
|
float _interval;
|
|
bool _busy;
|
|
readonly Dictionary<int, float> _last = new Dictionary<int, float>();
|
|
|
|
public void Init(ProjectileBase pb, float interval) { _pb = pb; _interval = interval; }
|
|
public void Mark(Collider c) { if (c != null) _last[c.GetInstanceID()] = Time.time; }
|
|
void OnEnable() { _last.Clear(); } // 풀 재사용 — 이전 발사의 기록을 버린다
|
|
|
|
void OnTriggerStay(Collider other)
|
|
{
|
|
if (_busy || _pb == null || _interval <= 0f || other == null) return;
|
|
float t; int id = other.GetInstanceID();
|
|
if (!_last.TryGetValue(id, out t) || Time.time - t < _interval) return;
|
|
_busy = true;
|
|
try { if (_pb.ProcessCollider(other, false)) { _last[id] = Time.time; WLPierceProjectile.Rehits++; } } // 원본 피해 경로(Off_byHit false = 훅을 안 거치므로 여기서 시각 갱신)
|
|
catch (System.Exception ex) { WLPierceProjectile.Errors++; WLPierceProjectile.LastLog = "다단 실패 — " + ex.Message; }
|
|
finally { _busy = false; }
|
|
}
|
|
}
|
|
}
|