refactor: group scripts into walker/ wikigen/ bundle/

Organize the 16 loose scripts by concern:
  walker/  -- .wbt save tooling (sand, build_wbt, walker_hashes,
              harvest_hashes, recover_key)
  wikigen/ -- MediaWiki page generators (make_*_wiki, render_wiki)
  bundle/  -- Unity/Odin asset extraction (unitybundle, odin_read,
              extract_*, loot_probe, dump_loot_bytes)

The only cross-script imports (build_wbt->walker_hashes,
extract_loot->odin_read) live within the same folder, so each
script's dir on sys.path[0] keeps them resolving with no code
changes. All data paths are absolute, so the moves don't affect
I/O. Named the code dir wikigen/ to avoid colliding with the
generated wiki/ output dir; ignore the regenerable wiki_site/ render.
This commit is contained in:
DownloadPizza
2026-06-11 14:49:33 +02:00
parent 2e886f31f0
commit a44e4db1c3
17 changed files with 3 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Generate a MediaWiki Items page from the authoritative item registry.
Source = items_registry.json, built from CheatItemDefinitionsData.Items
(List<ItemDefinition>). An entry exists iff the game defines it as a carriable
item (it has an ItemDefinition / StorageStack). Damage-type name variants
(_Ranged/_Melee) and world objects are NOT in this list, by design.
"""
import json, os
EX = '/home/downloadpizza/sand_tools/extracted'
WIKI = '/home/downloadpizza/sand_tools/wiki'
OUT = os.path.join(WIKI, 'Items.mediawiki')
reg = json.load(open(os.path.join(EX, 'items_registry.json')))['items']
CAT_LABEL = {
'WEAPON': 'Weapon', 'AMMO': 'Ammo', 'TURRET_AMMO': 'Turret Ammo',
'RESOURCE_T1': 'Resource (T1)', 'RESOURCE_T2': 'Resource (T2)', 'RESOURCE_T3': 'Resource (T3)',
'ARMOR': 'Armor', 'BACKPACK': 'Backpack', 'ENERGY': 'Energy', 'FOOD': 'Food', 'MONEY': 'Money',
'KEY': 'Key', 'SMALL_VALUABLE': 'Small Valuable', 'LARGE_VALUABLE': 'Large Valuable',
'RAID_EXPLOSIVES': 'Raid Explosive', 'WEAPON_BELT': 'Weapon Belt',
'UTILITY_CONSUMABLE': 'Utility Consumable', 'ATTACK_CONSUMABLE': 'Attack Consumable',
}
def esc(s):
return (s or '').replace('\n', ' ').replace('|', '&#124;').strip()
def stack(n):
return '' if n >= 100000 else str(n) # 100000/1000000 = effectively unlimited
def main():
out = []
out.append("Complete list of carriable '''items''' in '''SAND''' — everything the game defines as an "
"actual inventory item (i.e. it has an item definition). Names and descriptions are the "
"in-game English text.")
out.append('')
out.append(f"{len(reg)} items. The table is sortable — click a header to group by category. "
"(Damage-type name variants and world objects are intentionally excluded; this is the "
"real pickup-able item set.)")
out.append('')
out.append('__TOC__')
out.append('')
out.append('== Items ==')
out.append('{| class="wikitable sortable"')
out.append('! Category !! Name !! Item ID !! data-sort-type="number" | Stack !! Short description')
rows = sorted(reg.items(), key=lambda kv: (kv[1]['type'], kv[1]['name'].lower()))
for iid, info in rows:
cat = CAT_LABEL.get(info['type'], info['type'].title())
out.append('|-')
out.append(f"| {cat} || [[{info['name']}]] || <code>{iid}</code> "
f"|| {stack(info['storageStack'])} || {esc(info['shortDescription'])}")
out.append('|}')
out.append('')
out.append('== Notes ==')
out.append('* The item set comes from the game\'s item-definition registry '
'(<code>CheatItemDefinitionsData.Items</code>): an entry is included only if the game '
'defines it as a carriable item. This deliberately excludes damage-type name variants '
'(<code>_Ranged</code>/<code>_Melee</code>) and world objects, which merely have '
'display names but are not inventory items.')
out.append('* "Stack" is the item\'s storage stack limit; "" means effectively unlimited.')
out.append('* Full long-form descriptions exist for most items and can be added per page.')
out.append('')
out.append('[[Category:Items]]')
text = '\n'.join(out)
os.makedirs(WIKI, exist_ok=True)
open(OUT, 'w').write(text)
from collections import Counter
c = Counter(CAT_LABEL.get(v['type'], v['type']) for v in reg.values())
print(f"wrote {OUT} ({len(text)} chars, {len(reg)} items)")
for k, v in c.most_common():
print(f" {v:3d} {k}")
if __name__ == '__main__':
main()