#!/usr/bin/env python3 """Generate dragon_drops_table.md — one big markdown table. Rows: NPC + Item pair. Each NPC's drops are grouped together; dragon items are listed first within each NPC, then other items below. Columns: NPC | Lvl | Item | Amount | Rate (1/x) | Percentage """ import sys, os, re sys.path.insert(0, '/Users/tomassimkus/VS/openrsc-develop') os.chdir('/Users/tomassimkus/VS/openrsc-develop') from update_drops import parse_update_sql from compare_drops import load_enums from collections import defaultdict npc_name_to_id, npc_id_to_name, item_name_to_id, item_id_to_name = load_enums() _, rows, _ = parse_update_sql() sql_data = defaultdict(list) for nid, amt, iid, w, _ in rows: sql_data[nid].append((iid, int(amt), w)) def parse_combat_levels(): levels = {} with open('pk_npcdef.sql') as f: content = f.read() for ins in re.finditer(r"INSERT INTO `pk_npcdef`[^;]*?VALUES\s*(.+?);", content, re.DOTALL): body = ins.group(1) i, n = 0, len(body) while i < n: while i < n and body[i] in ' \t\n\r,': i += 1 if i >= n or body[i] != '(': break depth, j, in_str = 1, i + 1, False while j < n and depth > 0: c = body[j] if in_str: if c == '\\' and j + 1 < n: j += 2; continue if c == "'": if j + 1 < n and body[j + 1] == "'": j += 2; continue in_str = False elif c == "'": in_str = True elif c == '(': depth += 1 elif c == ')': depth -= 1 j += 1 tb = body[i + 1:j - 1] i = j cols, k, m, cur, in_str = [], 0, len(tb), [], False while k < m: c = tb[k] if in_str: if c == '\\' and k + 1 < m: cur.append(c); cur.append(tb[k+1]); k += 2; continue if c == "'": if k + 1 < m and tb[k+1] == "'": cur.append("''"); k += 2; continue in_str = False; cur.append(c) else: cur.append(c) else: if c == "'": in_str = True; cur.append(c) elif c == ',': cols.append(''.join(cur).strip()); cur = [] else: cur.append(c) k += 1 if cur: cols.append(''.join(cur).strip()) if len(cols) >= 9: try: nid = int(cols[0]); lvl = int(cols[8]) levels[nid] = lvl except ValueError: pass return levels combat_levels = parse_combat_levels() def is_dragon_item(iid, iname): if iname == "DRAGONSTONE": return True if iid in (1276, 1277): return True if iname and "DRAGON" in iname and not iname.endswith("_BONES"): return True return False # Find NPCs that drop at least one dragon item npcs_with_dragon = set() for nid, drops in sql_data.items(): for iid, _, _ in drops: if is_dragon_item(iid, item_id_to_name.get(iid, "")): npcs_with_dragon.add(nid) break # Primary bosses first PRIMARY = [477, 291, 290, 344, 201, 202, 184, 22, 181] others = sorted(npcs_with_dragon - set(PRIMARY)) ORDER = PRIMARY + others def fmt_rate(weight, total): if weight == 0: return "guaranteed", "100.0000%" odds = total / weight pct = weight / total * 100 return f"1/{round(odds):,}", f"{pct:.4f}%" # Build markdown lines = [] lines.append("# Dragon-item-dropping NPCs — full drop tables\n") lines.append("Generated from `pk_npcdrops_update.sql` (current state, includes recent edits).\n") lines.append(f"**Total NPCs covered:** {len(ORDER)} ({len(PRIMARY)} primary bosses + {len(others)} other RDT monsters)\n") lines.append("\nWithin each NPC, dragon items are listed first, then other weighted drops, then any guaranteed/nothing drops.\n") lines.append("\n---\n") lines.append("| NPC | Lvl | Item | Amt | Rate (1/x) | Percentage |") lines.append("|---|---|---|---|---|---|") for nid in ORDER: name = npc_id_to_name.get(nid, f"#{nid}") lvl = combat_levels.get(nid, "?") drops = sql_data.get(nid, []) if not drops: continue total_w = sum(w for _, _, w in drops if w > 0) # Partition guaranteed = [] dragon = [] other = [] empty_w = 0 for iid, amt, weight in drops: iname = item_id_to_name.get(iid, f"Item#{iid}") if iid == -1 and amt == -1: empty_w += weight continue if weight == 0: guaranteed.append((iid, amt, iname)) elif is_dragon_item(iid, iname): dragon.append((iid, amt, weight, iname)) else: other.append((iid, amt, weight, iname)) # Aggregate by (iid, amt) def agg(items): m = defaultdict(int) for iid, amt, weight, iname in items: m[(iid, amt, iname)] += weight return [(iid, amt, w, iname) for (iid, amt, iname), w in sorted(m.items(), key=lambda x: -x[1])] dragon_sorted = agg(dragon) other_sorted = agg(other) state = {"first": True} def cells_npc(): if state["first"]: state["first"] = False return f"**{name}** (id={nid})", str(lvl) return "", "" # Dragon items for iid, amt, w, iname in dragon_sorted: rate, pct = fmt_rate(w, total_w) n_c, l_c = cells_npc() amt_s = str(amt) lines.append(f"| {n_c} | {l_c} | **{iname}** | {amt_s} | {rate} | {pct} |") # Other items for iid, amt, w, iname in other_sorted: rate, pct = fmt_rate(w, total_w) n_c, l_c = cells_npc() amt_s = str(amt) lines.append(f"| {n_c} | {l_c} | {iname} | {amt_s} | {rate} | {pct} |") # Guaranteed for iid, amt, iname in guaranteed: n_c, l_c = cells_npc() amt_s = str(amt) # Mark Dragon Bones quirk suffix = " *(= Dragon Bones in game)*" if iid == 274 else "" lines.append(f"| {n_c} | {l_c} | _{iname}_{suffix} | {amt_s} | guaranteed | 100.0000% |") # Empty if empty_w > 0: rate, pct = fmt_rate(empty_w, total_w) n_c, l_c = cells_npc() lines.append(f"| {n_c} | {l_c} | _(Nothing — empty drop)_ | — | {rate} | {pct} |") # Blank row between NPCs (markdown allows empty cells) lines.append("| | | | | | |") out_path = os.path.join(os.getcwd(), "dragon_drops_table.md") with open(out_path, "w") as f: f.write("\n".join(lines)) print(f"Wrote {len(lines)} table rows to {out_path}") print(f"NPCs covered: {len(ORDER)}")