#!/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)}")