An Arsenal item does not reference a Weapon record. It references a Turret_NNN HARDPOINT SLOT on the player craft's own unit table, and the slot is what carries the WeaponID. Three hops: Arbalest_155KG.PlayerWeapon -> Turret_050 (a slot on UN_f001_TCAF_DeltaSaber_T_Player) -> .WeaponID -> Weapon_DSaber_P_wep_50_Cannon Controls, both in the same loop: 0/59 distinct PlayerWeapon values are a Weapon.ID; 59/59 are a Turret_NNN slot id; the full chain lands on a Weapon.ID 59/59. WingmanWeapon resolves identically. The WEAPONS roster's 59 = 55 item names + 4 empty-slot sentinels. Wingmen fly a cheaper gun: following the same 59 slots across craft variants, the _Player tables give each item its own weapon record (59 distinct) while the AI tables collapse all 59 onto 10 generic classes. That is most of the 131. Upgrades yesterday's 'hardpoint catalogue' reading from 21 to adopted, proved from an independent file, and corrects its '10 distinct WeaponID' figure -- that was the AI variant, not the player's. Also adds an __main__ guard to unit_substructures.py so importing pak_entries from it does not run its report; its artefact is unchanged and still byte-identical.
94 lines
4.2 KiB
Python
94 lines
4.2 KiB
Python
#!/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()
|