82 lines
4.9 KiB
C#
82 lines
4.9 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLProjectileStart.cs — 투사체 시작 위치 null 타깃 가드 (Q7 D-35 원인 지점)
|
|
//
|
|
// PD 지시 #813 · 발주서 WL-813x2 §1-2 (2026-09-09)
|
|
//
|
|
// ■ 실측 원인 (메인 에디터 Editor.log · Q7 R1 15:08 구간 · R1_13/R1_14 캡처 사이)
|
|
// NullReferenceException: Object reference not set to an instance of an object
|
|
// at ProjectileBase.Set (ProjectileData _data) [0x0026d] in Assets/Script/Character/Projectile/ProjectileBase.cs:233
|
|
// at Select_BodyonFire.Set (ProjectileData _data) in .../SelectSkill/Select_BodyonFire.cs:12
|
|
// at InGameInfo+<>c__DisplayClass31_0.<Load_Skill>b__0 (AsyncOperationHandle`1) in Assets/Script/Info/InGameInfo.cs:339
|
|
// at DelegateList`1[T].Invoke (Addressables · DelayedActionManager.LateUpdate)
|
|
// 2회째(15:23 R2b 구간)는 같은 줄에서 `SelectFairyBase.Set` 경유.
|
|
//
|
|
// ■ 왜 null 인가
|
|
// `InGameInfo.Load_Skill`(:333-340) 은 선택스킬 프리팹이 풀에 없을 때만 도는 **최초 1회 비동기 로드**다.
|
|
// Addressables 콜백이 다음 LateUpdate 로 밀리는 사이에 `_ptd.target`(=발사 시점의 몹)이 사라지면
|
|
// 콜백이 부르는 `ProjectileBase.Set` 안에서 target 이 null 이 된다.
|
|
// `Set` 은 :184 에서 `isNullTarget` 을 이미 계산해 :213 · :238 은 가드하지만,
|
|
// :229-236 의 시작 위치 switch 는 **`default:`(=None/Target)와 `TargetCenter` 를 가드하지 않는다**.
|
|
// → 존 클리어 카드(813o)·레벨업으로 런 도중 선택스킬이 새로 붙는 WL 런에서 특히 잘 걸린다.
|
|
//
|
|
// ■ 가드
|
|
// target 이 (Unity 오버로드 ==) null 이면 shooter 위치로 대체한다. shooter 마저 null 이면 현재 위치 유지.
|
|
// target 이 살아 있으면 **원본과 완전히 동일한 식**이다.
|
|
// SO `projectileNullTargetGuard = 0` 이면 원본 동작 그대로(=역참조)로 되돌린다(C8).
|
|
//
|
|
// 🔴 `??` · `?.` 를 쓰지 않는다 — UnityEngine.Object 의 가짜 null 을 못 거른다(813y 교훈).
|
|
// 🔴 Debug.LogError 금지(813z) — 진단 로그는 Debug.Log 1줄뿐이고 기본 off.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using UnityEngine;
|
|
|
|
namespace WL.Combat.Diagnostics
|
|
{
|
|
/// <summary>ProjectileBase.Set 의 target 기준 시작 위치 계산(원본 2줄 훅이 부른다).</summary>
|
|
public static class WLProjectileStart
|
|
{
|
|
/// <summary>가드가 null target 을 잡은 횟수(프로브·QA 가 읽는다).</summary>
|
|
public static int NullTargetCount;
|
|
/// <summary>그중 shooter 로도 대체하지 못해 현재 위치를 유지한 횟수.</summary>
|
|
public static int NoShooterCount;
|
|
/// <summary>마지막으로 잡은 스킬(진단용).</summary>
|
|
public static string LastSkill = "";
|
|
|
|
/// <summary>카운터 초기화(프로브 전용).</summary>
|
|
public static void ResetCounters() { NullTargetCount = NoShooterCount = 0; LastSkill = ""; }
|
|
|
|
/// <summary>
|
|
/// target 기준 시작 위치. target 이 null 이면 shooter → current 순으로 대체한다.
|
|
/// </summary>
|
|
/// <param name="data">발사 데이터(원본 `_data` 그대로).</param>
|
|
/// <param name="current">대체 실패 시 유지할 현재 위치.</param>
|
|
public static Vector3 TargetPos(ProjectileData data, Vector3 current)
|
|
{
|
|
if (data == null) return current;
|
|
|
|
var cfg = WLErrorGuardSettings.Instance;
|
|
bool guard = cfg == null || (cfg.enabled && !WLErrorGuardSettings.RuntimeDisabled && cfg.projectileNullTargetGuard);
|
|
|
|
// C8: 가드 off 면 원본 그대로 역참조한다(= 원본 동작 100%).
|
|
if (!guard) return data.target.Get_CenterPositionFoward();
|
|
|
|
if (!(data.target == null)) return data.target.Get_CenterPositionFoward(); // Unity 오버로드 == (가짜 null 포함)
|
|
|
|
NullTargetCount++;
|
|
LastSkill = data.m_Skill.ToString();
|
|
|
|
if (!(data.shooter == null))
|
|
{
|
|
if (cfg != null && cfg.projectileGuardLog)
|
|
Debug.Log("[WL813x2] 투사체 타깃 소실 — shooter 위치로 대체 · skill=" + LastSkill + " · 누적 " + NullTargetCount);
|
|
return data.shooter.Get_CenterPositionFoward();
|
|
}
|
|
|
|
NoShooterCount++;
|
|
if (cfg != null && cfg.projectileGuardLog)
|
|
Debug.Log("[WL813x2] 투사체 타깃·발사자 모두 소실 — 현재 위치 유지 · skill=" + LastSkill);
|
|
return current;
|
|
}
|
|
}
|
|
}
|