378 lines
24 KiB
C#
378 lines
24 KiB
C#
// 발주서 WL-813f — 전리품 연출(산포 · 등급 빔 · 자동 줍기 · 폭발 드랍) 검증 프로브
|
|
//
|
|
// 실행 (CLAUDE.md §1: using 금지 · 네임스페이스 풀어 쓰기)
|
|
// 에디트 모드 : unity command run_script --file AgentScripts/WL813f_Probe.cs --entry WL813f_Probe.Edit --timeout 300
|
|
// Play(인게임): unity command run_script --file AgentScripts/WL813f_Probe.cs --entry WL813f_Probe.Status
|
|
// unity command run_script --file AgentScripts/WL813f_Probe.cs --entry WL813f_Probe.Watch --args '[12.0]'
|
|
//
|
|
// Edit() 가 재는 것 — 씬/프리팹/에셋 변경 0(가짜 오브젝트는 전부 DestroyImmediate 로 회수):
|
|
// (a) WLLootSettings 로드 · 값 전수 · 등급 빔 프리팹 참조 해소(등급 1~3 null · 4~9 실물)
|
|
// (b) 산포 표집 1000회 — 반경 min/max/mean(설정 0.6~1.2 준수) · 부채꼴 각 분포
|
|
// (c) 등급별 튀는 높이 1~9
|
|
// (d) 폭발 건수 분포 — 보스 3~5 · 엘리트 1~2 · 일반 0 (2000회)
|
|
// (e) 가짜 Killed(비활성 MobActor) → 구독 동작 · BurstPlanned · C8 off 시 0
|
|
// (f) 자석 리플렉션 — 원본 private 필드 3종 + ProjectileData.Speed 를 찾았는가 · EngageMagnet 전/후 상태
|
|
// (g) 자동 줍기 반경 경계 — 1.5 m 안/밖 · 콜라이더 꺼짐(착지 전) 제외
|
|
// (h) 등급 빔 + EffectBudget.Heavy 상한(코어 값) — 초과분 스킵 · CleanupAll 후 슬롯 0
|
|
// (i) 동시 드랍 상한 · 원본 풀 상한(DropItemInfo: 코인 30 · 아이템 2)
|
|
// (j) C8 — RuntimeDisabled 시 훅 2개가 원본 값을 그대로 두는가
|
|
// (k) 데이터 — RandomBag 새 백 8130001 5행 총확률 · MonsterAppear 813e 6행 참조
|
|
//
|
|
// 수치는 전부 SO/테이블에서 읽는다(C45). 프로브 상수는 "어디를 보나" 뿐이다.
|
|
|
|
public static class WL813f_Probe
|
|
{
|
|
const string kBagJson = "Assets/ResWork/Table/Export/RandomBag.json";
|
|
const string kAppearJson = "Assets/ResWork/Table/Export/MonsterAppear.json";
|
|
const string kOutDir = "AgentScripts/staging/WL813f";
|
|
const int kNewBagId = 8130001;
|
|
|
|
static System.Text.StringBuilder s;
|
|
static void L(string t) { s.AppendLine(t); UnityEngine.Debug.Log("[WL813f] " + t); }
|
|
|
|
static string Abs(string rel)
|
|
{
|
|
return System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(),
|
|
rel.Replace('/', System.IO.Path.DirectorySeparatorChar));
|
|
}
|
|
|
|
static string Flush(string name)
|
|
{
|
|
try
|
|
{
|
|
System.IO.Directory.CreateDirectory(Abs(kOutDir));
|
|
System.IO.File.WriteAllText(Abs(kOutDir + "/" + name), s.ToString(), new System.Text.UTF8Encoding(true));
|
|
}
|
|
catch (System.Exception e) { UnityEngine.Debug.LogWarning("[WL813f] 로그 저장 실패: " + e.Message); }
|
|
return s.ToString();
|
|
}
|
|
|
|
// ── 아주 얕은 JSON 배열 파서(행 = {"k": "v", ...}) ─────────────────────────
|
|
static System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, string>> ReadRows(string path)
|
|
{
|
|
var list = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, string>>();
|
|
string txt = System.IO.File.ReadAllText(Abs(path));
|
|
foreach (System.Text.RegularExpressions.Match m in
|
|
System.Text.RegularExpressions.Regex.Matches(txt, "\\{[^{}]*\\}"))
|
|
{
|
|
var d = new System.Collections.Generic.Dictionary<string, string>();
|
|
foreach (System.Text.RegularExpressions.Match kv in
|
|
System.Text.RegularExpressions.Regex.Matches(m.Value, "\"([^\"]+)\"\\s*:\\s*\"([^\"]*)\""))
|
|
d[kv.Groups[1].Value] = kv.Groups[2].Value;
|
|
list.Add(d);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
// ── 가짜 드랍(비활성 GO → Awake 실행 안 됨 · 콜라이더는 직접 붙인다) ────────
|
|
static DropItem MakeFakeDrop(string name, UnityEngine.Vector3 pos, bool colliderOn)
|
|
{
|
|
var go = new UnityEngine.GameObject(name);
|
|
go.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|
go.SetActive(false);
|
|
var di = go.AddComponent<DropItem>();
|
|
var col = go.AddComponent<UnityEngine.SphereCollider>();
|
|
col.isTrigger = true;
|
|
col.enabled = colliderOn;
|
|
di.m_Collider = col;
|
|
go.transform.position = pos;
|
|
go.SetActive(true); // Awake 는 이미 지나갔다(비활성 상태로 AddComponent)
|
|
return di;
|
|
}
|
|
|
|
static Actor MakeFakeMob(string name, eSubRol subRole, UnityEngine.Vector3 pos)
|
|
{
|
|
var go = new UnityEngine.GameObject(name);
|
|
go.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
|
go.SetActive(false); // 비활성으로 만들어 Awake/Start 를 태우지 않는다
|
|
var a = go.AddComponent<MobActor>();
|
|
a.m_SubRole = subRole;
|
|
go.transform.position = pos;
|
|
return a;
|
|
}
|
|
|
|
static void Kill(UnityEngine.Object o) { if (o != null) UnityEngine.Object.DestroyImmediate(o); }
|
|
|
|
// ═══════════════════════════════════════════════════════════════ Edit
|
|
public static string Edit()
|
|
{
|
|
s = new System.Text.StringBuilder();
|
|
L("=== WL-813f 전리품 연출 프로브(에디트 모드) " + System.DateTime.Now.ToString("HH:mm:ss") + " ===");
|
|
|
|
WL.Combat.Loot.WLLootSettings.ClearCache();
|
|
var cfg = WL.Combat.Loot.WLLootSettings.Instance;
|
|
if (cfg == null) { L("!! WLLootSettings.asset 로드 실패 — Resources/WL/WLLootSettings"); return Flush("EDIT.txt"); }
|
|
|
|
WL.Combat.Loot.LootBurst.EnsureSubscribed();
|
|
WL.Combat.Loot.LootBurst.ResetDiagnostics();
|
|
WL.Combat.Core.EffectBudget.ResetAll();
|
|
|
|
// (a) 설정 · 빔 프리팹 참조
|
|
L("");
|
|
L("[a] WLLootSettings — Enabled=" + WL.Combat.Loot.WLLootSettings.Enabled + " verbose=" + cfg.verboseLog);
|
|
L(" 산포 " + cfg.scatterRadiusMin + "~" + cfg.scatterRadiusMax + " m · navSnap " + cfg.scatterNavSnapRadius +
|
|
" · 튀기 base " + cfg.bounceHeightBase + " +" + cfg.bounceHeightPerGrade + "/등급 · max " + cfg.bounceHeightMax);
|
|
L(" 빔 minGrade " + cfg.beamMinGrade + " · scale " + cfg.beamScale + " · maxLife " + cfg.beamMaxLifetimeSeconds +
|
|
" · budget " + cfg.beamUseEffectBudget + " · sound " + cfg.beamSound + " vol " + cfg.beamSoundVolume);
|
|
L(" 줍기 " + cfg.autoPickupEnabled + " r=" + cfg.autoPickupRadius + " m · scan " + cfg.autoPickupScanIntervalSeconds +
|
|
" s · magnet rise " + cfg.magnetRiseHeight + "/" + cfg.magnetRiseSpeed);
|
|
L(" 폭발 elite " + cfg.eliteBurstMin + "~" + cfg.eliteBurstMax + " · boss " + cfg.bossBurstMin + "~" + cfg.bossBurstMax +
|
|
" · normal " + cfg.normalBurstCount + " · 간격 " + cfg.burstIntervalSeconds + " s · bagRoll " + cfg.burstRollsBag +
|
|
" · goldShare " + cfg.burstGoldShare + " · cluster " + cfg.burstUseClusterBudget);
|
|
L(" 상한 maxConcurrentDrops " + cfg.maxConcurrentDrops + " · beamSuppressAbove " + cfg.beamSuppressAboveDrops);
|
|
var sb = new System.Text.StringBuilder();
|
|
for (int g = 1; g <= WL.Combat.Loot.WLLootSettings.GradeSlots; g++)
|
|
{
|
|
var pf = WL.Combat.Loot.LootBurst.BeamPrefab(cfg, g);
|
|
sb.Append(g).Append("=").Append(pf != null ? pf.name : "-").Append(" ");
|
|
}
|
|
L(" 등급별 빔: " + sb.ToString().Trim());
|
|
|
|
// (b) 산포 표집
|
|
L("");
|
|
var origin = new UnityEngine.Vector3(10f, 0f, 10f);
|
|
float mn = 999f, mx = 0f, sum = 0f; int n = 1000;
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
var p = WL.Combat.Loot.LootBurst.SampleScatter(cfg, origin);
|
|
float d = UnityEngine.Vector3.Distance(new UnityEngine.Vector3(origin.x, 0f, origin.z),
|
|
new UnityEngine.Vector3(p.x, 0f, p.z));
|
|
if (d < mn) mn = d; if (d > mx) mx = d; sum += d;
|
|
}
|
|
L("[b] 산포 " + n + "회 — min " + mn.ToString("F3") + " · max " + mx.ToString("F3") +
|
|
" · mean " + (sum / n).ToString("F3") + " m (설정 " + cfg.scatterRadiusMin + "~" + cfg.scatterRadiusMax +
|
|
") · NavMesh 없음 → 링 좌표 그대로");
|
|
|
|
// (c) 등급별 튀는 높이
|
|
var hb = new System.Text.StringBuilder();
|
|
for (int g = 1; g <= 9; g++)
|
|
{
|
|
float h = UnityEngine.Mathf.Min(cfg.bounceHeightMax,
|
|
cfg.bounceHeightBase + UnityEngine.Mathf.Max(0, g - 1) * cfg.bounceHeightPerGrade);
|
|
hb.Append(g).Append(":").Append(h.ToString("F2")).Append(" ");
|
|
}
|
|
L("[c] 튀는 높이(m) — " + hb.ToString().Trim());
|
|
|
|
// (d) 폭발 건수 분포
|
|
int[] boss = new int[8], elite = new int[8]; int normal = 0;
|
|
for (int i = 0; i < 2000; i++)
|
|
{
|
|
boss[UnityEngine.Mathf.Clamp(WL.Combat.Loot.LootBurst.BurstCount(cfg, eSubRol.Boss), 0, 7)]++;
|
|
elite[UnityEngine.Mathf.Clamp(WL.Combat.Loot.LootBurst.BurstCount(cfg, eSubRol.Elite), 0, 7)]++;
|
|
normal += WL.Combat.Loot.LootBurst.BurstCount(cfg, eSubRol.None);
|
|
}
|
|
L("[d] 폭발 건수 2000회 — boss " + Hist(boss) + " · elite " + Hist(elite) + " · normal 합계 " + normal);
|
|
|
|
// (e) 가짜 Killed
|
|
L("");
|
|
var mobB = MakeFakeMob("__fakeBoss", eSubRol.Boss, new UnityEngine.Vector3(5f, 0f, 5f));
|
|
var mobE = MakeFakeMob("__fakeElite", eSubRol.Elite, new UnityEngine.Vector3(6f, 0f, 5f));
|
|
var mobN = MakeFakeMob("__fakeNormal", eSubRol.None, new UnityEngine.Vector3(7f, 0f, 5f));
|
|
WL.Combat.Loot.LootBurst.ResetDiagnostics();
|
|
int coreOn = WL.Combat.Core.WLCombatCoreSettings.Enabled ? 1 : 0;
|
|
WL.Combat.Core.CombatEvents.RaiseKilled(mobB);
|
|
int p1 = WL.Combat.Loot.LootBurst.BurstPlanned; int c1 = WL.Combat.Loot.LootBurst.LastBurstCount;
|
|
WL.Combat.Core.CombatEvents.RaiseKilled(mobE);
|
|
int p2 = WL.Combat.Loot.LootBurst.BurstPlanned; int c2 = WL.Combat.Loot.LootBurst.LastBurstCount;
|
|
WL.Combat.Core.CombatEvents.RaiseKilled(mobN);
|
|
int p3 = WL.Combat.Loot.LootBurst.BurstPlanned; int c3 = WL.Combat.Loot.LootBurst.LastBurstCount;
|
|
L("[e] 가짜 Killed — core Enabled=" + coreOn + " · 구독 " + WL.Combat.Loot.LootBurst.Subscribed +
|
|
" · Killed 처리 " + WL.Combat.Loot.LootBurst.KilledHandled +
|
|
" · boss 계획 " + c1 + "(누적 " + p1 + ") · elite " + c2 + "(누적 " + p2 + ") · normal " + c3 + "(누적 " + p3 + ")" +
|
|
" · 실제 드랍 " + WL.Combat.Loot.LootBurst.BurstDropped +
|
|
" (DropItemInfo.Ins=" + (DropItemInfo.Ins != null) + " · table_randombag.Ins=" + (table_randombag.Ins != null) + ")");
|
|
|
|
// (f) 자석 리플렉션
|
|
L("");
|
|
var d1 = MakeFakeDrop("__fakeDrop1", new UnityEngine.Vector3(0f, 0f, 0f), true);
|
|
int before = WL.Combat.Loot.LootBurst.MagnetState(d1);
|
|
bool eng = WL.Combat.Loot.LootBurst.EngageMagnet(d1);
|
|
int after = WL.Combat.Loot.LootBurst.MagnetState(d1);
|
|
bool again = WL.Combat.Loot.LootBurst.EngageMagnet(d1);
|
|
L("[f] 자석 리플렉션 OK=" + WL.Combat.Loot.LootBurst.ReflectionOk +
|
|
" · m_Magnet " + before + "→" + after + " (engage " + eng + " · 재호출 " + again + " = 중복 방지)" +
|
|
" · MagnetEngaged " + WL.Combat.Loot.LootBurst.MagnetEngaged);
|
|
|
|
// (g) 자동 줍기 반경 경계
|
|
var pc = new UnityEngine.Vector3(0f, 0f, 0f);
|
|
float r = cfg.autoPickupRadius;
|
|
var din = MakeFakeDrop("__in", new UnityEngine.Vector3(r - 0.01f, 0f, 0f), true);
|
|
var dout = MakeFakeDrop("__out", new UnityEngine.Vector3(r + 0.01f, 0f, 0f), true);
|
|
var dair = MakeFakeDrop("__air", new UnityEngine.Vector3(r - 0.5f, 0f, 0f), false); // 아직 착지 전(콜라이더 off)
|
|
L("[g] 줍기 반경 " + r + " m — " + (r - 0.01f).ToString("F2") + "m " +
|
|
WL.Combat.Loot.LootBurst.InPickupRadius(din, pc, cfg) + " · " + (r + 0.01f).ToString("F2") + "m " +
|
|
WL.Combat.Loot.LootBurst.InPickupRadius(dout, pc, cfg) + " · 착지 전(콜라이더 off) " +
|
|
WL.Combat.Loot.LootBurst.InPickupRadius(dair, pc, cfg));
|
|
|
|
// (h) 등급 빔 + EffectBudget
|
|
// 에디트 모드에는 table_itemlist.Ins 가 없어 ResolveGrade 가 항상 1 이다(런타임 등급 판정은 QA).
|
|
// 그래서 **메모리에서만** beamMinGrade 를 1 로 내리고 등급 1 칸에 등급 4 빔을 꽂아 스폰 경로를 실제로 태운다.
|
|
// 에셋 파일은 저장하지 않는다(SetDirty·SaveAssets 호출 0) — 끝나면 원래 값으로 되돌리고 git status 로 확인한다.
|
|
L("");
|
|
WL.Combat.Loot.LootBurst.ResetDiagnostics();
|
|
WL.Combat.Core.EffectBudget.ResetAll();
|
|
int heavyLimit = WL.Combat.Core.EffectBudget.Limit(WL.Combat.Core.EffectBudgetKind.Heavy);
|
|
int probeItemId = FindItemIdOfGrade(cfg.beamMinGrade);
|
|
L("[h] 등급 판정 — 아이템 " + probeItemId + " → ResolveGrade " + WL.Combat.Loot.LootBurst.ResolveGrade(probeItemId) +
|
|
" (table_itemlist.Ins=" + (table_itemlist.Ins != null) + " · 에디트 모드는 테이블 미로드 → 항상 1)");
|
|
|
|
int savedMinGrade = cfg.beamMinGrade;
|
|
var savedSlot0 = cfg.beamByGrade[0];
|
|
cfg.beamByGrade[0] = WL.Combat.Loot.LootBurst.BeamPrefab(cfg, savedMinGrade); // 등급 1 칸 = 등급 4 빔(임시)
|
|
cfg.beamMinGrade = 1;
|
|
|
|
var drops = new System.Collections.Generic.List<DropItem>();
|
|
for (int i = 0; i < heavyLimit + 2; i++)
|
|
{
|
|
var di = MakeFakeDrop("__beam" + i, new UnityEngine.Vector3(i, 0f, 0f), true);
|
|
drops.Add(di);
|
|
var sp = new UnityEngine.Vector3(i, 0f, 0f);
|
|
var tp = sp;
|
|
WL.Combat.Loot.LootBurst.OnDropSpawn(di, probeItemId, 1, ref sp, ref tp);
|
|
}
|
|
L(" 빔 강제(minGrade 1 · 프리팹 " + (cfg.beamByGrade[0] != null ? cfg.beamByGrade[0].name : "-") + ") " +
|
|
(heavyLimit + 2) + "건 — Heavy 상한 " + heavyLimit + " · 스폰 " + WL.Combat.Loot.LootBurst.BeamSpawned +
|
|
" · 예산 스킵 " + WL.Combat.Loot.LootBurst.BeamSkippedBudget +
|
|
" · 프리팹 없음 " + WL.Combat.Loot.LootBurst.BeamSkippedNoPrefab +
|
|
" · Heavy Count " + WL.Combat.Core.EffectBudget.Count(WL.Combat.Core.EffectBudgetKind.Heavy) +
|
|
" · 사운드 " + WL.Combat.Loot.LootBurst.SoundPlayed + "(SoundInfo.Ins=" + (SoundInfo.Ins != null) + ")" +
|
|
" · 등록 드랍 " + WL.Combat.Loot.LootBurst.ActiveDropCount() +
|
|
" · 산포 " + WL.Combat.Loot.LootBurst.Scattered);
|
|
int beamChildren = 0;
|
|
for (int i = 0; i < drops.Count; i++) beamChildren += drops[i].transform.childCount;
|
|
L(" 빔 자식 수 " + beamChildren + " (drop 에 붙었는가 · beamParentToDrop=" + cfg.beamParentToDrop + ")");
|
|
WL.Combat.Loot.LootBurst.CleanupAll();
|
|
int beamAfter = 0;
|
|
for (int i = 0; i < drops.Count; i++) beamAfter += drops[i].transform.childCount;
|
|
L(" CleanupAll 후 Heavy Count " + WL.Combat.Core.EffectBudget.Count(WL.Combat.Core.EffectBudgetKind.Heavy) +
|
|
" · 등록 드랍 " + WL.Combat.Loot.LootBurst.ActiveDropCount() + " · 남은 빔 자식 " + beamAfter);
|
|
|
|
cfg.beamMinGrade = savedMinGrade;
|
|
cfg.beamByGrade[0] = savedSlot0;
|
|
L(" 설정 복원 — beamMinGrade " + cfg.beamMinGrade + " · 등급 1 칸 " +
|
|
(cfg.beamByGrade[0] == null ? "null(원본)" : cfg.beamByGrade[0].name) + " · 에셋 저장 0(SetDirty/SaveAssets 미호출)");
|
|
|
|
// (j) C8 — 꺼짐이면 원본 값 그대로
|
|
L("");
|
|
WL.Combat.Loot.LootBurst.ResetDiagnostics();
|
|
WL.Combat.Loot.WLLootSettings.RuntimeDisabled = true;
|
|
var dC8 = MakeFakeDrop("__c8", new UnityEngine.Vector3(0f, 0f, 0f), true);
|
|
var sp0 = new UnityEngine.Vector3(1f, 2f, 3f); var tp0 = new UnityEngine.Vector3(4f, 5f, 6f);
|
|
var spK = sp0; var tpK = tp0;
|
|
WL.Combat.Loot.LootBurst.OnDropSpawn(dC8, probeItemId, 1, ref spK, ref tpK);
|
|
WL.Combat.Loot.LootBurst.OnDropPicked(dC8, probeItemId, 1, null);
|
|
WL.Combat.Core.CombatEvents.RaiseKilled(mobB);
|
|
L("[j] C8 RuntimeDisabled — start 불변 " + (spK == sp0) + " · target 불변 " + (tpK == tp0) +
|
|
" · DropsSeen " + WL.Combat.Loot.LootBurst.DropsSeen + " · Picked " + WL.Combat.Loot.LootBurst.PickedCount +
|
|
" · Killed 처리 " + WL.Combat.Loot.LootBurst.KilledHandled + " · 빔 " + WL.Combat.Loot.LootBurst.BeamSpawned +
|
|
" · Heavy Count " + WL.Combat.Core.EffectBudget.Count(WL.Combat.Core.EffectBudgetKind.Heavy));
|
|
WL.Combat.Loot.WLLootSettings.RuntimeDisabled = false;
|
|
var spR = sp0; var tpR = tp0;
|
|
WL.Combat.Loot.LootBurst.OnDropSpawn(dC8, probeItemId, 1, ref spR, ref tpR);
|
|
L(" 복구 후 — start 변경 " + (spR != sp0) + " · target 변경 " + (tpR != tp0) +
|
|
" · DropsSeen " + WL.Combat.Loot.LootBurst.DropsSeen + " · 빔 " + WL.Combat.Loot.LootBurst.BeamSpawned);
|
|
WL.Combat.Loot.LootBurst.CleanupAll();
|
|
|
|
// (i) 원본 풀 상한
|
|
L("");
|
|
L("[i] 원본 풀 상한(DropItemInfo.Start 고정) — 코인(itemid 2) 30 · 아이템(-1) 2 · 초과 요청은 조용히 무시" +
|
|
" · 우리 상한 maxConcurrentDrops " + cfg.maxConcurrentDrops + " (폭발 추가분만 막고 기본 드랍은 막지 않는다)");
|
|
|
|
// (k) 데이터
|
|
L("");
|
|
var bagRows = ReadRows(kBagJson);
|
|
double pMiss = 1.0; int newRows = 0;
|
|
var bagDetail = new System.Text.StringBuilder();
|
|
for (int i = 0; i < bagRows.Count; i++)
|
|
{
|
|
if (bagRows[i]["n_DropIndex"] != kNewBagId.ToString()) continue;
|
|
newRows++;
|
|
double rate = double.Parse(bagRows[i]["f_DropRate"], System.Globalization.CultureInfo.InvariantCulture);
|
|
pMiss *= (1.0 - rate);
|
|
bagDetail.Append("g").Append(bagRows[i]["n_ItemGrade"]).Append("/").Append(bagRows[i]["n_ItemId"])
|
|
.Append("@").Append(bagRows[i]["f_DropRate"]).Append(" ");
|
|
}
|
|
var appearRows = ReadRows(kAppearJson);
|
|
int pointed = 0, old = 0;
|
|
for (int i = 0; i < appearRows.Count; i++)
|
|
{
|
|
if (appearRows[i]["n_DropReward"] == kNewBagId.ToString()) pointed++;
|
|
else old++;
|
|
}
|
|
L("[k] 데이터 — RandomBag 총 " + bagRows.Count + "행 · 새 백 " + kNewBagId + " " + newRows + "행 " + bagDetail.ToString().Trim());
|
|
L(" 새 백 총 드랍 확률 = " + ((1.0 - pMiss) * 100.0).ToString("F2") + "% (기준서 15~25%) · 원본 순차 추첨(첫 성공에서 break) 기준");
|
|
L(" MonsterAppear " + appearRows.Count + "행 — 새 백 참조 " + pointed + "행(813e 존 5 + 보스 1) · 9700001 유지 " + old + "행");
|
|
|
|
// 정리
|
|
for (int i = 0; i < drops.Count; i++) Kill(drops[i] != null ? drops[i].gameObject : null);
|
|
Kill(d1 != null ? d1.gameObject : null); Kill(din != null ? din.gameObject : null);
|
|
Kill(dout != null ? dout.gameObject : null); Kill(dair != null ? dair.gameObject : null);
|
|
Kill(dC8 != null ? dC8.gameObject : null);
|
|
Kill(mobB != null ? mobB.gameObject : null); Kill(mobE != null ? mobE.gameObject : null);
|
|
Kill(mobN != null ? mobN.gameObject : null);
|
|
var runner = WL.Combat.Loot.LootBurst.RunnerOrNull;
|
|
if (runner != null) Kill(runner.gameObject);
|
|
WL.Combat.Core.EffectBudget.ResetAll();
|
|
L("");
|
|
L("정리 완료 — Heavy Count " + WL.Combat.Core.EffectBudget.Count(WL.Combat.Core.EffectBudgetKind.Heavy) +
|
|
" · ClusterBurst Count " + WL.Combat.Core.EffectBudget.Count(WL.Combat.Core.EffectBudgetKind.ClusterBurst));
|
|
return Flush("EDIT.txt");
|
|
}
|
|
|
|
static string Hist(int[] h)
|
|
{
|
|
var b = new System.Text.StringBuilder();
|
|
for (int i = 0; i < h.Length; i++) if (h[i] > 0) b.Append(i).Append(":").Append(h[i]).Append(" ");
|
|
return b.ToString().Trim();
|
|
}
|
|
|
|
/// <summary>테이블에서 해당 등급의 아이템 ID 를 하나 찾는다(없으면 새 백의 첫 행 · 그것도 없으면 골드).</summary>
|
|
static int FindItemIdOfGrade(int grade)
|
|
{
|
|
var rows = ReadRows(kBagJson);
|
|
for (int i = 0; i < rows.Count; i++)
|
|
if (rows[i]["n_DropIndex"] == kNewBagId.ToString() && rows[i]["n_ItemGrade"] == grade.ToString())
|
|
return int.Parse(rows[i]["n_ItemId"]);
|
|
return 2;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════ Play
|
|
/// <summary>Play 중 카운터 스냅샷(실전투 QA 용).</summary>
|
|
public static string Status()
|
|
{
|
|
s = new System.Text.StringBuilder();
|
|
L("=== WL-813f Status " + System.DateTime.Now.ToString("HH:mm:ss") + " ===");
|
|
L("Killed 처리 " + WL.Combat.Loot.LootBurst.KilledHandled + " · 드랍 " + WL.Combat.Loot.LootBurst.DropsSeen +
|
|
" · 산포 " + WL.Combat.Loot.LootBurst.Scattered + " · 획득 " + WL.Combat.Loot.LootBurst.PickedCount);
|
|
L("빔 " + WL.Combat.Loot.LootBurst.BeamSpawned + " · 예산 스킵 " + WL.Combat.Loot.LootBurst.BeamSkippedBudget +
|
|
" · 프리팹 없음 " + WL.Combat.Loot.LootBurst.BeamSkippedNoPrefab + " · 과밀 스킵 " + WL.Combat.Loot.LootBurst.BeamSkippedCrowded +
|
|
" · 사운드 " + WL.Combat.Loot.LootBurst.SoundPlayed);
|
|
L("자석 " + WL.Combat.Loot.LootBurst.MagnetEngaged + " · 폭발 계획 " + WL.Combat.Loot.LootBurst.BurstPlanned +
|
|
" · 실드랍 " + WL.Combat.Loot.LootBurst.BurstDropped + " · 상한 스킵 " + WL.Combat.Loot.LootBurst.BurstSkippedCap);
|
|
L("살아 있는 드랍 " + WL.Combat.Loot.LootBurst.ActiveDropCount() + " · Heavy " +
|
|
WL.Combat.Core.EffectBudget.Count(WL.Combat.Core.EffectBudgetKind.Heavy) + "/" +
|
|
WL.Combat.Core.EffectBudget.Limit(WL.Combat.Core.EffectBudgetKind.Heavy) +
|
|
" · 마지막 등급 " + WL.Combat.Loot.LootBurst.LastGrade +
|
|
" · 마지막 산포 " + WL.Combat.Loot.LootBurst.LastScatterDistance.ToString("F2") + " m");
|
|
return Flush("STATUS.txt");
|
|
}
|
|
|
|
/// <summary>Play 중 N초 감시 — 드랍/획득 수와 줍기 완료 시간을 잰다.</summary>
|
|
public static string Watch(double seconds)
|
|
{
|
|
s = new System.Text.StringBuilder();
|
|
if (!UnityEngine.Application.isPlaying) { L("Play 중에만 쓸 수 있다."); return Flush("WATCH.txt"); }
|
|
L("=== WL-813f Watch " + seconds.ToString("F0") + "s ===");
|
|
int d0 = WL.Combat.Loot.LootBurst.DropsSeen, p0 = WL.Combat.Loot.LootBurst.PickedCount,
|
|
k0 = WL.Combat.Loot.LootBurst.KilledHandled, b0 = WL.Combat.Loot.LootBurst.BeamSpawned,
|
|
m0 = WL.Combat.Loot.LootBurst.MagnetEngaged, r0 = WL.Combat.Loot.LootBurst.BurstDropped;
|
|
double t0 = UnityEngine.Time.realtimeSinceStartupAsDouble;
|
|
while (UnityEngine.Time.realtimeSinceStartupAsDouble - t0 < seconds)
|
|
System.Threading.Thread.Sleep(50);
|
|
L("처치 " + (WL.Combat.Loot.LootBurst.KilledHandled - k0) + " · 드랍 " + (WL.Combat.Loot.LootBurst.DropsSeen - d0) +
|
|
" · 획득 " + (WL.Combat.Loot.LootBurst.PickedCount - p0) + " · 빔 " + (WL.Combat.Loot.LootBurst.BeamSpawned - b0) +
|
|
" · 자석 " + (WL.Combat.Loot.LootBurst.MagnetEngaged - m0) + " · 폭발 드랍 " + (WL.Combat.Loot.LootBurst.BurstDropped - r0));
|
|
return Flush("WATCH.txt");
|
|
}
|
|
}
|