Project_WL/Assets/WL/Combat/Loot/LootBurstRunner.cs

180 lines
8.5 KiB
C#
Raw Permalink Normal View History

[WL-813f] 전리품 연출 + 드랍률 (#813) CombatEvents.Killed 구독 → 드랍 산포(0.6~1.2 m · 등급별 튀는 높이 0.35+0.08/등급) · 등급 4 이상 빔(보유 FX_LootDrop_Blue/Purple/Gold) + s015_GoldPowerUp · 자동 줍기 반경 1.5 m(원본 자석 상태기를 리플렉션으로 켜기만) · 보스 3~5 · 엘리트 1~2 폭발 드랍(RandomBag 재추첨 · 빈손이면 골드 25%). 값은 전부 WLLootSettings.asset(C45) · 동시 빔은 EffectBudget.Heavy 등록. 원본 수정 = DropItem.cs 2줄(Set 산포 훅 · ItemTouch 획득 통지 훅) 뿐. Actor.cs · MobActor.cs · DropItemInfo.cs · Assets/WL/Combat/Core/** 0줄. 데이터(S): RandomBag.json 새 백 8130001 5행(총 드랍 20.95% · 기준서 15~25%) · MonsterAppear.json 813e 존 6행(813001~813005 · 813010)의 n_DropReward 만 새 백으로 · 나머지 46행 · RandomBag 기존 197행 바이트 무변경. ⚠ butler.xlsm 동기화 필요. 검증: 게이트 PASS(oneshot 1.4분 · error CS 0 / resident up_to_date · 콘솔 error 0) · 에디트 모드 프로브(AgentScripts/WL813f_Probe.cs · 덤프 WL813f_PROBE.txt): 산포 1000회 0.601~1.199 m · 폭발 2000회 boss 3~5 · elite 1~2 · normal 0 · 가짜 Killed 3건 구독 동작 · 자석 리플렉션 OK(0→1 · 중복 방지) · 줍기 경계 1.49 in / 1.51 out / 착지 전 제외 · 빔 4건 → Heavy 상한 2 스폰 · 2 스킵 → CleanupAll 후 슬롯 0 · C8(RuntimeDisabled) 시 산포/빔/획득/폭발 전부 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:19:48 +00:00
// ─────────────────────────────────────────────────────────────────────────────
// LootBurstRunner.cs — LootBurst 의 프레임 루프 · 코루틴 호스트(LevelUpBurstRunner 선례)
//
// PD 지시 #813 · 발주서 WL-813f §1 (2026-09-09)
//
// LootBurst 가 런타임에 1개만 만들고 DontDestroyOnLoad 로 들고 있는다. 씬·프리팹을 건드리지 않는다.
// ① 자동 줍기 — 살아 있는 드랍을 주기적으로 훑어 PC 반경 안이면 원본 자석 상태기를 켠다(LootBurst.EngageMagnet).
// 판정 대상은 **착지가 끝난**(원본이 m_Collider 를 켠) 드랍만 — 날아가는 중에 빨려 들어가지 않게 한다.
// ② 등급 빔 — 드랍이 사라지면(획득·20 s 소멸·AllOff) 빔을 파괴하고 EffectBudget.Heavy 를 정확히 1회 반납한다.
// 러너가 사라져도 OnDisable 이 남은 슬롯을 정산한다.
// ③ 폭발 드랍 — 보스/엘리트 추가 드랍을 간격을 두고 낸다.
//
// 모든 순회는 사전 할당 배열이다(GC 0). 배열은 Register 때만 성장한다.
// ─────────────────────────────────────────────────────────────────────────────
using System;
using System.Collections;
using UnityEngine;
using WL.Combat.Core;
namespace WL.Combat.Loot
{
public class LootBurstRunner : MonoBehaviour
{
struct Entry
{
public DropItem item;
public GameObject beam;
public bool heldBudget;
public float beamDeadline; // unscaled 시각(빔이 있을 때만)
}
Entry[] _entries = new Entry[32];
int _count;
float _nextScan;
/// <summary>살아 있는(등록된) 드랍 수 — 동시 드랍 상한 판정의 기준.</summary>
public int ActiveCount { get { return _count; } }
/// <summary>반납하지 않은 EffectBudget.Heavy 슬롯 수.</summary>
public int HeldBudget
{
get { int n = 0; for (int i = 0; i < _count; i++) if (_entries[i].heldBudget) n++; return n; }
}
/// <summary>진단: 마지막 스캔에서 반경 안이라 자석을 켠 수.</summary>
public int LastScanEngaged;
// ───────────────────────────────────────────────── 등록 · 해제
/// <summary>드랍을 추적 목록에 넣는다(이미 있으면 무시). DropItem.Set 훅에서 부른다.</summary>
public void Register(DropItem item)
{
if (item == null) return;
for (int i = 0; i < _count; i++) if (_entries[i].item == item) return;
if (_count == _entries.Length) Array.Resize(ref _entries, _entries.Length * 2);
_entries[_count++] = new Entry { item = item, beam = null, heldBudget = false, beamDeadline = 0f };
}
/// <summary>이 드랍에 빔을 매단다(같은 드랍에 이미 빔이 있으면 이전 것을 정리한다).</summary>
public void RegisterBeam(DropItem item, GameObject beam, bool heldBudget, float maxLifetime)
{
if (item == null) { DestroyBeam(beam, heldBudget); return; }
Register(item);
for (int i = 0; i < _count; i++)
{
if (_entries[i].item != item) continue;
if (_entries[i].beam != null) DestroyBeam(_entries[i].beam, _entries[i].heldBudget);
_entries[i].beam = beam;
_entries[i].heldBudget = heldBudget;
_entries[i].beamDeadline = Time.unscaledTime + Mathf.Max(0.1f, maxLifetime);
return;
}
}
/// <summary>드랍이 획득·소멸했다. 빔을 파괴하고 슬롯을 반납한 뒤 목록에서 뺀다.</summary>
public void Unregister(DropItem item)
{
for (int i = 0; i < _count; i++)
{
if (_entries[i].item != item) continue;
DestroyBeam(_entries[i].beam, _entries[i].heldBudget);
_entries[i] = _entries[--_count];
_entries[_count] = default(Entry);
return;
}
}
/// <summary>전부 정리(씬 전환 · 프로브 종료). 슬롯을 남기지 않는다.</summary>
public void CleanupAll()
{
for (int i = 0; i < _count; i++)
{
DestroyBeam(_entries[i].beam, _entries[i].heldBudget);
_entries[i] = default(Entry);
}
_count = 0;
}
void DestroyBeam(GameObject beam, bool heldBudget)
{
if (beam != null)
{
if (Application.isPlaying) UnityEngine.Object.Destroy(beam);
else UnityEngine.Object.DestroyImmediate(beam);
}
if (heldBudget) EffectBudget.Release(EffectBudgetKind.Heavy);
}
void OnDisable() { CleanupAll(); }
// ───────────────────────────────────────────────── 프레임 루프
void Update()
{
var s = WLLootSettings.Instance;
if (s == null) { if (_count > 0) CleanupAll(); return; }
float now = Time.unscaledTime;
bool scan = s.autoPickupEnabled && !WLLootSettings.RuntimeDisabled && s.enabled &&
(s.autoPickupScanIntervalSeconds <= 0f || now >= _nextScan);
if (scan) _nextScan = now + Mathf.Max(0f, s.autoPickupScanIntervalSeconds);
Vector3 pcPos = Vector3.zero;
bool pcOk = false;
if (scan)
{
var pc = MyValue.MyPC;
if (pc != null && !pc.IsDead()) { pcPos = pc.Get_Center_position(); pcOk = true; }
}
int engaged = 0;
for (int i = _count - 1; i >= 0; i--)
{
var item = _entries[i].item;
// ① 사라진 드랍(획득 · 20 s 소멸 · AllOff · 풀 파괴) → 빔 파괴 + 슬롯 반납 + 목록에서 제거
if (item == null || !item.gameObject.activeInHierarchy)
{
DestroyBeam(_entries[i].beam, _entries[i].heldBudget);
_entries[i] = _entries[--_count];
_entries[_count] = default(Entry);
continue;
}
// ② 빔 수명 상한(드랍이 어떤 이유로든 오래 남아 있을 때의 2차 방어)
if (_entries[i].beam != null && now >= _entries[i].beamDeadline)
{
DestroyBeam(_entries[i].beam, _entries[i].heldBudget);
_entries[i].beam = null;
_entries[i].heldBudget = false;
}
// ③ 자동 줍기 — 착지가 끝난(콜라이더가 켜진) 드랍만 · 아직 자석이 아닌 것만
if (!scan || !pcOk) continue;
if (!LootBurst.InPickupRadius(item, pcPos, s)) continue;
if (LootBurst.EngageMagnet(item)) engaged++;
}
if (scan) LastScanEngaged = engaged;
}
// ───────────────────────────────────────────────── 폭발 드랍
/// <summary>보스/엘리트 추가 드랍을 간격을 두고 낸다. ClusterBurst 슬롯을 잡았으면 마지막에 반납한다.</summary>
public void RunBurst(int count, int bagId, int baseGold, Vector3 position, float interval, bool heldCluster, eSubRol subRole)
[WL-813f] 전리품 연출 + 드랍률 (#813) CombatEvents.Killed 구독 → 드랍 산포(0.6~1.2 m · 등급별 튀는 높이 0.35+0.08/등급) · 등급 4 이상 빔(보유 FX_LootDrop_Blue/Purple/Gold) + s015_GoldPowerUp · 자동 줍기 반경 1.5 m(원본 자석 상태기를 리플렉션으로 켜기만) · 보스 3~5 · 엘리트 1~2 폭발 드랍(RandomBag 재추첨 · 빈손이면 골드 25%). 값은 전부 WLLootSettings.asset(C45) · 동시 빔은 EffectBudget.Heavy 등록. 원본 수정 = DropItem.cs 2줄(Set 산포 훅 · ItemTouch 획득 통지 훅) 뿐. Actor.cs · MobActor.cs · DropItemInfo.cs · Assets/WL/Combat/Core/** 0줄. 데이터(S): RandomBag.json 새 백 8130001 5행(총 드랍 20.95% · 기준서 15~25%) · MonsterAppear.json 813e 존 6행(813001~813005 · 813010)의 n_DropReward 만 새 백으로 · 나머지 46행 · RandomBag 기존 197행 바이트 무변경. ⚠ butler.xlsm 동기화 필요. 검증: 게이트 PASS(oneshot 1.4분 · error CS 0 / resident up_to_date · 콘솔 error 0) · 에디트 모드 프로브(AgentScripts/WL813f_Probe.cs · 덤프 WL813f_PROBE.txt): 산포 1000회 0.601~1.199 m · 폭발 2000회 boss 3~5 · elite 1~2 · normal 0 · 가짜 Killed 3건 구독 동작 · 자석 리플렉션 OK(0→1 · 중복 방지) · 줍기 경계 1.49 in / 1.51 out / 착지 전 제외 · 빔 4건 → Heavy 상한 2 스폰 · 2 스킵 → CleanupAll 후 슬롯 0 · C8(RuntimeDisabled) 시 산포/빔/획득/폭발 전부 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:19:48 +00:00
{
StartCoroutine(CoBurst(count, bagId, baseGold, position, interval, heldCluster, subRole));
[WL-813f] 전리품 연출 + 드랍률 (#813) CombatEvents.Killed 구독 → 드랍 산포(0.6~1.2 m · 등급별 튀는 높이 0.35+0.08/등급) · 등급 4 이상 빔(보유 FX_LootDrop_Blue/Purple/Gold) + s015_GoldPowerUp · 자동 줍기 반경 1.5 m(원본 자석 상태기를 리플렉션으로 켜기만) · 보스 3~5 · 엘리트 1~2 폭발 드랍(RandomBag 재추첨 · 빈손이면 골드 25%). 값은 전부 WLLootSettings.asset(C45) · 동시 빔은 EffectBudget.Heavy 등록. 원본 수정 = DropItem.cs 2줄(Set 산포 훅 · ItemTouch 획득 통지 훅) 뿐. Actor.cs · MobActor.cs · DropItemInfo.cs · Assets/WL/Combat/Core/** 0줄. 데이터(S): RandomBag.json 새 백 8130001 5행(총 드랍 20.95% · 기준서 15~25%) · MonsterAppear.json 813e 존 6행(813001~813005 · 813010)의 n_DropReward 만 새 백으로 · 나머지 46행 · RandomBag 기존 197행 바이트 무변경. ⚠ butler.xlsm 동기화 필요. 검증: 게이트 PASS(oneshot 1.4분 · error CS 0 / resident up_to_date · 콘솔 error 0) · 에디트 모드 프로브(AgentScripts/WL813f_Probe.cs · 덤프 WL813f_PROBE.txt): 산포 1000회 0.601~1.199 m · 폭발 2000회 boss 3~5 · elite 1~2 · normal 0 · 가짜 Killed 3건 구독 동작 · 자석 리플렉션 OK(0→1 · 중복 방지) · 줍기 경계 1.49 in / 1.51 out / 착지 전 제외 · 빔 4건 → Heavy 상한 2 스폰 · 2 스킵 → CleanupAll 후 슬롯 0 · C8(RuntimeDisabled) 시 산포/빔/획득/폭발 전부 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:19:48 +00:00
}
IEnumerator CoBurst(int count, int bagId, int baseGold, Vector3 position, float interval, bool heldCluster, eSubRol subRole)
[WL-813f] 전리품 연출 + 드랍률 (#813) CombatEvents.Killed 구독 → 드랍 산포(0.6~1.2 m · 등급별 튀는 높이 0.35+0.08/등급) · 등급 4 이상 빔(보유 FX_LootDrop_Blue/Purple/Gold) + s015_GoldPowerUp · 자동 줍기 반경 1.5 m(원본 자석 상태기를 리플렉션으로 켜기만) · 보스 3~5 · 엘리트 1~2 폭발 드랍(RandomBag 재추첨 · 빈손이면 골드 25%). 값은 전부 WLLootSettings.asset(C45) · 동시 빔은 EffectBudget.Heavy 등록. 원본 수정 = DropItem.cs 2줄(Set 산포 훅 · ItemTouch 획득 통지 훅) 뿐. Actor.cs · MobActor.cs · DropItemInfo.cs · Assets/WL/Combat/Core/** 0줄. 데이터(S): RandomBag.json 새 백 8130001 5행(총 드랍 20.95% · 기준서 15~25%) · MonsterAppear.json 813e 존 6행(813001~813005 · 813010)의 n_DropReward 만 새 백으로 · 나머지 46행 · RandomBag 기존 197행 바이트 무변경. ⚠ butler.xlsm 동기화 필요. 검증: 게이트 PASS(oneshot 1.4분 · error CS 0 / resident up_to_date · 콘솔 error 0) · 에디트 모드 프로브(AgentScripts/WL813f_Probe.cs · 덤프 WL813f_PROBE.txt): 산포 1000회 0.601~1.199 m · 폭발 2000회 boss 3~5 · elite 1~2 · normal 0 · 가짜 Killed 3건 구독 동작 · 자석 리플렉션 OK(0→1 · 중복 방지) · 줍기 경계 1.49 in / 1.51 out / 착지 전 제외 · 빔 4건 → Heavy 상한 2 스폰 · 2 스킵 → CleanupAll 후 슬롯 0 · C8(RuntimeDisabled) 시 산포/빔/획득/폭발 전부 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:19:48 +00:00
{
for (int i = 0; i < count; i++)
{
LootBurst.SpawnBurstOne(LootBurst.BagForBurst(WLLootSettings.Instance, subRole, i, bagId), baseGold, position);
[WL-813f] 전리품 연출 + 드랍률 (#813) CombatEvents.Killed 구독 → 드랍 산포(0.6~1.2 m · 등급별 튀는 높이 0.35+0.08/등급) · 등급 4 이상 빔(보유 FX_LootDrop_Blue/Purple/Gold) + s015_GoldPowerUp · 자동 줍기 반경 1.5 m(원본 자석 상태기를 리플렉션으로 켜기만) · 보스 3~5 · 엘리트 1~2 폭발 드랍(RandomBag 재추첨 · 빈손이면 골드 25%). 값은 전부 WLLootSettings.asset(C45) · 동시 빔은 EffectBudget.Heavy 등록. 원본 수정 = DropItem.cs 2줄(Set 산포 훅 · ItemTouch 획득 통지 훅) 뿐. Actor.cs · MobActor.cs · DropItemInfo.cs · Assets/WL/Combat/Core/** 0줄. 데이터(S): RandomBag.json 새 백 8130001 5행(총 드랍 20.95% · 기준서 15~25%) · MonsterAppear.json 813e 존 6행(813001~813005 · 813010)의 n_DropReward 만 새 백으로 · 나머지 46행 · RandomBag 기존 197행 바이트 무변경. ⚠ butler.xlsm 동기화 필요. 검증: 게이트 PASS(oneshot 1.4분 · error CS 0 / resident up_to_date · 콘솔 error 0) · 에디트 모드 프로브(AgentScripts/WL813f_Probe.cs · 덤프 WL813f_PROBE.txt): 산포 1000회 0.601~1.199 m · 폭발 2000회 boss 3~5 · elite 1~2 · normal 0 · 가짜 Killed 3건 구독 동작 · 자석 리플렉션 OK(0→1 · 중복 방지) · 줍기 경계 1.49 in / 1.51 out / 착지 전 제외 · 빔 4건 → Heavy 상한 2 스폰 · 2 스킵 → CleanupAll 후 슬롯 0 · C8(RuntimeDisabled) 시 산포/빔/획득/폭발 전부 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:19:48 +00:00
if (i < count - 1 && interval > 0f) yield return new WaitForSeconds(interval);
}
if (heldCluster) EffectBudget.Release(EffectBudgetKind.ClusterBurst);
}
}
}