#!/usr/bin/env python3 """Resolve the arsenal item -> hardpoint slot -> weapon record chain. An Arsenal item in GP_HANGAR_ARSENAL.pak names a HARDPOINT SLOT on the player craft (`PlayerWeapon = Turret_050`), not a weapon. The slot's `WeaponID` is what points into the 131-record `Weapon` datasheet. Regenerates docs/re/data/arsenal-chain.txt. """ import sys, os, glob, re, collections HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) import unitgroup as U from unit_substructures import pak_entries ITEMF = {'Dependency', 'MissionObjective', 'Model', 'Package', 'PlayerWeapon', 'Points', 'Power', 'Range'} def main(): ars = glob.glob('/work/sylph_extract/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0] mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0] slots = collections.defaultdict(dict); wids = set() for h, b in pak_entries(mg): if b[:4] != b'IDXD': continue try: recs = U.parse(b) except Exception: continue for r in recs: if r['squadron'] == 'Weapon': i = U.named(r).get('ID') if i: wids.add(i) g = [r for r in recs if r['squadron'] == 'Generic'] if not (g and 'HP' in U.named(g[0])): continue uid = U.named(g[0]).get('ID') for r in recs: if re.fullmatch(r'Turret_\d{3}', r['squadron']): slots[uid][r['squadron']] = U.named(r).get('WeaponID') items = {}; roster = None for h, b in pak_entries(ars): if b[:4] != b'IDXD': continue try: recs = U.parse(b) except Exception: continue for r in recs: if r['squadron'] == 'WEAPONS' and roster is None: roster = r if ITEMF <= set(U.named(r)): items.setdefault(r['squadron'], r) print("# The Arsenal item -> hardpoint slot -> Weapon record chain") print("# Regenerate: python3 tools/re-capture/arsenal_chain.py") print("# See docs/re/structures/arsenal-item-weapon-chain.md") rset = {v for _, _, v in roster['fields']} print("\n## the roster and the item records") print(" WEAPONS roster values %3d" % len(rset)) print(" arsenal ITEM records %3d" % len(items)) print(" item names that are roster values %3d" % len(set(items) & rset)) print(" roster values with no item record: %s" % sorted(rset - set(items))) print(" Weapon.ID values in the datasheet %3d" % len(wids)) print("\n## CONTROL item.PlayerWeapon is NOT a Weapon.ID") pw = {U.named(r).get('PlayerWeapon', '').strip() for r in items.values()} print(" distinct PlayerWeapon values %3d ; how many are a Weapon.ID: %d" % (len(pw), len(pw & wids))) print("\n## CONTROL item.PlayerWeapon IS a Turret_NNN slot id") for craft in sorted(slots): if 'Saber' not in craft: continue s = slots[craft] hit = sum(1 for r in items.values() if U.named(r).get('PlayerWeapon', '').strip() in s) wing = sum(1 for r in items.values() if U.named(r).get('WingmanWeapon', '').strip() in s) print(" %-40s slots %2d Player %2d/%d Wingman %2d/%d" % (craft, len(s), hit, len(items), wing, len(items))) print("\n## THE CHAIN, per craft variant: item -> slot -> WeaponID -> Weapon.ID") for craft in sorted(slots): if 'Saber' not in craft: continue s = slots[craft] reach = [s.get(U.named(r).get('PlayerWeapon', '').strip()) for r in items.values()] land = [w for w in reach if w in wids] print(" %-40s %3d/%3d land on a Weapon.ID, %2d distinct" % (craft, len(land), len(items), len(set(land)))) vs = [v for v in s.values() if v and v != 'Weapon_NULL'] print(" slot WeaponIDs: %d non-NULL, %d distinct, e.g. %s" % (len(vs), len(set(vs)), sorted(set(vs))[:2])) s = slots['UN_f001_TCAF_DeltaSaber_T_Player'] used = {U.named(r).get('PlayerWeapon', '').strip() for r in items.values()} print("\n player craft slots not referenced by any item: %s" % sorted(set(s) - used)) k = sorted(items)[0] print("\n## a full item record -- %s" % k) for kk, vv in sorted(U.named(items[k]).items()): print(" %-22s %s" % (kk, vv)) main()