BurningTimesAi/공유/기획/FirstRising/몬스터보스/tools/unity_eval/summarize.py

132 lines
6.0 KiB
Python
Raw Permalink Normal View History

# monitor.log 요약 — 스킬별 시전 수, 시전 뒤 window 초 안에 들어온 플레이어 피해, 예고 시간(설정) 대비 실제 첫 타격 시각
import io
import json
import re
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
log_path = sys.argv[1]
names_path = sys.argv[2]
window = float(sys.argv[3]) if len(sys.argv) > 3 else 5.0
since = float(sys.argv[4]) if len(sys.argv) > 4 else 0.0
config_path = sys.argv[5] if len(sys.argv) > 5 else None
names = {}
for row in json.load(open(names_path, encoding="utf-8-sig")):
names[row["Index"]] = row.get("Desc", "").replace("[BT] ", "")
telegraphs = {}
if config_path:
for boss in json.load(open(config_path, encoding="utf-8-sig")).get("bosses", []):
for t in boss.get("timings") or []:
telegraphs[t["skillId"]] = t
casts = [] # (t, skill, owner, dist)
hits = [] # (t, count delta)
damages = [] # (t, delta)
states = []
positions = []
notices = []
anims = []
for line in open(log_path, encoding="utf-8", errors="replace"):
m = re.match(r"([\d.]+) CAST (\d+) by (\d+) play=(\d+) dist=([\d.]+)", line)
if m:
t = float(m.group(1))
if t >= since:
casts.append((t, int(m.group(2)), int(m.group(3)), float(m.group(5))))
continue
m = re.match(r"([\d.]+) PLAYERHIT count (-?\d+) -> (-?\d+)", line)
if m:
t = float(m.group(1))
if t >= since:
hits.append((t, int(m.group(3)) - int(m.group(2))))
continue
m = re.match(r"([\d.]+) PLAYER hp (-?\d+) -> (-?\d+) \((-?\d+)\)", line)
if m:
t = float(m.group(1))
if t >= since:
damages.append((t, int(m.group(4))))
continue
m = re.match(r"([\d.]+) PLAYERPOS (.*)", line)
if m and float(m.group(1)) >= since:
positions.append((float(m.group(1)), m.group(2).strip()))
continue
m = re.match(r"([\d.]+) NOTICE (.*)", line)
if m and float(m.group(1)) >= since:
notices.append((float(m.group(1)), m.group(2).strip()))
continue
m = re.match(r"([\d.]+) ANIM (\d+) id=(-?\d+) speed=([\d.]+) at=([\d.]+)", line)
if m and float(m.group(1)) >= since:
anims.append((float(m.group(1)), int(m.group(3)), float(m.group(4)), float(m.group(5))))
continue
m = re.match(r"([\d.]+) BOSS#(\d+) hp=(-?\d+) idx=(\d+) init=(\d) dead=(\d) phase=(\d+) inv=(\d+) abn=(\d) gauge=(\d+) groggy=(\d) stun=(\d)(.*)", line)
if m and float(m.group(1)) >= since:
rest = m.group(13)
out = re.search(r" out=(\d)", rest)
air = re.search(r" air=(\d)", rest)
mask = re.search(r" mask=(\d+)", rest)
states.append((float(m.group(1)), int(m.group(4)), int(m.group(7)), int(m.group(8)), int(m.group(10)), int(m.group(11)), int(m.group(12)), int(m.group(3)),
int(out.group(1)) if out else 0, int(air.group(1)) if air else 0, int(mask.group(1)) if mask else 0))
per = {}
order = []
for i, (t, skill, owner, dist) in enumerate(casts):
# 같은 스킬의 다음 시전 전까지만, 그리고 window 안
nxt = min([c[0] for c in casts[i + 1:] if c[1] == skill] + [t + window])
end = min(t + window, nxt)
mine = [(ht, d) for (ht, d) in hits if t <= ht < end]
n_hits = sum(d for (_, d) in mine)
dmg = [d for (dt, d) in damages if t <= dt < end and d < 0]
if skill not in per:
per[skill] = {"casts": 0, "hit_casts": 0, "requests": 0, "damage": [], "dist": [], "first": [], "speeds": []}
order.append(skill)
p = per[skill]
p["casts"] += 1
p["requests"] += n_hits
if n_hits > 0 or dmg:
p["hit_casts"] += 1
if mine:
p["first"].append(mine[0][0] - t)
p["damage"] += dmg
p["dist"].append(dist)
# 시전 뒤 예고 시간 동안의 동작 속도 변화(박자)
tele = telegraphs.get(skill, {}).get("telegraph", 0)
if tele:
p["speeds"].append([f"{a[0] - t:.2f}s:{a[2]:.2f}@{a[3]:.2f}" for a in anims if t <= a[0] <= t + tele + 0.6 and 3 <= a[1] <= 7][:5])
print("스킬 | 이름 | 시전 | 맞은 시전 | 피해 요청 | 체력 변화(최대 6개) | 시전 거리 | 예고(설정) | 첫 타격(실측 평균) | 차이")
for skill in sorted(order):
p = per[skill]
dist = sum(p["dist"]) / len(p["dist"])
tele = telegraphs.get(skill, {}).get("telegraph")
first = sum(p["first"]) / len(p["first"]) if p["first"] else None
tele_s = f"{tele:.2f}" if tele is not None else "-"
first_s = f"{first:.2f}" if first is not None else "-"
diff = f"{first - tele:+.2f}" if (tele is not None and first is not None) else "-"
print(f"{skill} | {names.get(skill, '?')} | {p['casts']} | {p['hit_casts']} | {p['requests']} | {p['damage'][:6]} | {dist:.1f} | {tele_s} | {first_s} | {diff}")
if p["speeds"] and p["speeds"][0]:
print(f" 박자(시전 뒤 시각:재생 속도@동작 위치): {p['speeds'][0]}")
if states:
phases = []
last = None
for s in states:
key = (s[1], s[2])
if key != last:
phases.append(f"{s[0]:.1f}s idx {s[1]} P{s[2]} hp={s[7]}")
last = key
print("페이즈 흐름:", " → ".join(phases))
inv = [s for s in states if s[3] > 0]
if inv:
print(f"전환 무적 관측: {inv[0][0]:.1f}s ~ (무적 표시 {len(inv)}회)")
gauges = sorted(set(s[4] for s in states))
stun = [s[0] for s in states if s[5] == 1 or s[6] == 1]
print("그로기 게이지 관측값:", gauges, "· 기절/그로기 표시 시각:", [f"{x:.1f}" for x in stun[:6]])
print("전장 밖:", sum(1 for s in states if s[8]), "· 공중:", sum(1 for s in states if s[9]), "· 안전 지점 마스크:", sorted(set(s[10] for s in states)))
if notices:
print("안내:", " / ".join(f"{t:.0f}s {text}" for t, text in notices[:12]), f"(총 {len(notices)}건)")
if positions:
print("플레이어 위치 변화:", " → ".join(f"{t:.0f}s({p})" for t, p in positions[:12]), f"(총 {len(positions)}회)")
total_damage = sum(d for _, d in damages if d < 0)
print(f"플레이어 피해 합계 {total_damage} · 피해 요청 합계 {sum(d for _, d in hits)}")