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.
76 lines
3.6 KiB
Python
76 lines
3.6 KiB
Python
#!/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('|', '|').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()
|