#!/usr/bin/env python3
"""Generate restore_plan.md — detailed per-NPC breakdown of every stackable
amount difference between pk_npcdrops.sql (new, authentic) and
pk_npcdrops_production.sql (your live server with custom buffs).
Output format: one section per NPC, showing
- Production amounts and rates
- New amounts and rates
- Suggested action per item (restore/keep)
"""
import sys, os, re
sys.path.insert(0, '/Users/tomassimkus/VS/openrsc-develop')
os.chdir('/Users/tomassimkus/VS/openrsc-develop')
from compare_drops import load_enums, BONE_ITEM_IDS
from collections import defaultdict
_, npc_id_to_name, _, item_id_to_name = load_enums()
def parse(path):
with open(path) as f:
content = f.read()
out = defaultdict(list)
for m in re.finditer(r"\((\d+),\s*'(-?\d+)',\s*(-?\d+),\s*(\d+),\s*\d+\)", content):
out[int(m.group(1))].append((int(m.group(3)), int(m.group(2)), int(m.group(4))))
return out
new = parse("pk_npcdrops.sql")
prod = parse("pk_npcdrops_production.sql")
def total_weight(drops):
return sum(w for _, _, w in drops if w > 0)
def format_rate(w, tw):
if w == 0:
return "guaranteed (100%)"
if tw == 0:
return f"w={w}"
pct = w / tw * 100
return f"1/{round(tw/w):,} ({pct:.3f}%)"
def is_stackable(iid, iname):
"""Heuristic: items that typically appear in different stack sizes."""
if iid == -1: return False
if iid in BONE_ITEM_IDS: return False
return True
lines = []
def w(s=""): lines.append(s)
w("# Production-vs-New Drop Amount Differences")
w("")
w("This report compares `pk_npcdrops_production.sql` (your live custom values) ")
w("with `pk_npcdrops.sql` (newly synced to authentic OpenRSC `NpcDrops.java`).")
w("")
w("For each NPC with stackable amount differences, you can mark which production ")
w("buffs to restore on the new file. Decision options per item:")
w("- **RESTORE**: keep production's custom amount")
w("- **KEEP_NEW**: use authentic Java amount")
w("- **HYBRID**: pick specific amounts to keep/discard")
w("")
# Find NPCs with amount diffs
diff_npcs = []
for nid in set(new) | set(prod):
if nid not in new or nid not in prod:
continue
new_drops = new[nid]
prod_drops = prod[nid]
# Aggregate by item: set of amounts
new_amts_per_item = defaultdict(set)
for iid, amt, w_ in new_drops:
if is_stackable(iid, item_id_to_name.get(iid, "")):
new_amts_per_item[iid].add(amt)
prod_amts_per_item = defaultdict(set)
for iid, amt, w_ in prod_drops:
if is_stackable(iid, item_id_to_name.get(iid, "")):
prod_amts_per_item[iid].add(amt)
common_items = set(new_amts_per_item) & set(prod_amts_per_item)
has_diff = False
for iid in common_items:
if new_amts_per_item[iid] != prod_amts_per_item[iid]:
has_diff = True
break
if has_diff:
diff_npcs.append(nid)
w(f"**NPCs with differences: {len(diff_npcs)}**")
w("")
# Categorize: high-tier bosses, mid-tier, others
HIGH_TIER = [477, 291, 290, 344, 201, 202, 184, 22, 181, 254, 254]
diff_high = [n for n in diff_npcs if n in HIGH_TIER]
diff_mid = [n for n in diff_npcs if n not in HIGH_TIER]
ordered = sorted(diff_high) + sorted(diff_mid)
for nid in ordered:
name = npc_id_to_name.get(nid, f"#{nid}")
is_boss = nid in HIGH_TIER
badge = " 🔱 BOSS" if is_boss else ""
new_drops = new[nid]
prod_drops = prod[nid]
new_tw = total_weight(new_drops)
prod_tw = total_weight(prod_drops)
w(f"## {name} (id={nid}){badge}")
w("")
w(f"- Production total weight: {prod_tw:,}")
w(f"- New total weight: {new_tw:,}")
w("")
# Build aggregations: {item: {amount: weight}} for both
new_map = defaultdict(lambda: defaultdict(int))
for iid, amt, w_ in new_drops:
new_map[iid][amt] += w_
prod_map = defaultdict(lambda: defaultdict(int))
for iid, amt, w_ in prod_drops:
prod_map[iid][amt] += w_
# Items with amount diffs
common_items = set(new_map) & set(prod_map)
items_with_diff = []
for iid in common_items:
n_amts = set(new_map[iid])
p_amts = set(prod_map[iid])
if n_amts != p_amts:
items_with_diff.append(iid)
# Sort by max weight (most impactful first)
items_with_diff.sort(
key=lambda iid: -max(max(prod_map[iid].values(), default=0), max(new_map[iid].values(), default=0))
)
w("| Item | Production | New (Java) | Decision |")
w("|---|---|---|---|")
for iid in items_with_diff:
iname = item_id_to_name.get(iid, f"#{iid}")
# Sort amounts by weight desc for readability
p_entries = sorted(prod_map[iid].items(), key=lambda x: -x[1])
n_entries = sorted(new_map[iid].items(), key=lambda x: -x[1])
p_strs = [f"x{amt} {format_rate(wt, prod_tw)}" for amt, wt in p_entries]
n_strs = [f"x{amt} {format_rate(wt, new_tw)}" for amt, wt in n_entries]
p_cell = "
".join(p_strs) if p_strs else "—"
n_cell = "
".join(n_strs) if n_strs else "—"
w(f"| **{iname}** | {p_cell} | {n_cell} | ☐ RESTORE ☐ KEEP_NEW |")
w("")
# Write to file
out_path = "/Users/tomassimkus/VS/openrsc-develop/restore_plan.md"
with open(out_path, "w") as f:
f.write("\n".join(lines))
print(f"Wrote restore plan to: {out_path}")
print(f"NPCs with diffs: {len(diff_npcs)}")
print(f" Bosses (priority): {len(diff_high)}")
print(f" Other NPCs: {len(diff_mid)}")