#!/usr/bin/env python3 """ constants.py - reverse tables for the `.data` keys authored as symbolic names. Four `[GameData]` keys are written in the source as a symbol rather than a literal, and the record stores only the resolved integer. Emitting a faithful `.data` therefore needs the int -> symbol direction: MechID $(M_Annihilator) MechLabHeaders.h #define M_Annihilator 0 TechType $(Tech_IS) MechLabHeaders.h #define Tech_IS 0 NameIndex $(IDS_Annihilator) MissionLang.defines #define IDS_ANNIHILATOR 576 MoveTypeFlag LEGJUMPMOVETYPE MWObject.hpp enum + MWObject_Tool.cpp:20 MoveTypeFlag is the odd one out: a bare token, not `$(...)`, matched by the `stricmp` chain in `MWObject__GameModel::ConvertStringToMoveType`. Its values come from the anonymous enum at MWObject.hpp:136, where LEG is 0 and the order is NOT the same as the stricmp chain, so the enum is the authority. The `.defines` and `.h` files spell symbols in mixed case while sources reference them in any case, so lookups here are case-insensitive. """ import re REPO = "/home/rich/Repositories/firestorm/Gameleap" MECHLAB_HEADERS = REPO + "/mw4/Content/ShellScripts/MechLabHeaders.h" MISSIONLANG_DEFINES = REPO + "/mw4/Content/Defines/MissionLang.defines" # MWObject.hpp:136. Declaration order is the value order. MOVE_TYPE = [ "LEGMOVETYPE", "LEGJUMPMOVETYPE", "TRACKMOVETYPE", "WHEELMOVETYPE", "FLYERMOVETYPE", "HOVERMOVETYPE", "HELIMOVETYPE", "NONEMOVETYPE", "DROPSHIPMOVETYPE", "WATERMOVETYPE", ] _DEFINE = re.compile(r'^\s*#define\s+(\w+)\s+(-?\d+)\s*$', re.M) def defines(path, prefix): """-> {value: [symbols]} for every `#define NAME ` in a file. Values are not unique: IDS_FIRSTSKIN and IDS_WOLFHOUND are both 501, so a single winner cannot be picked here without knowing which mech is being emitted. """ txt = open(path, encoding="latin-1", errors="replace").read() out = {} for name, val in _DEFINE.findall(txt): if name.lower().startswith(prefix.lower()): out.setdefault(int(val), []).append(name) return out def tables(): """-> {sourceKey: {intValue: [sourceToken]}} for the four symbolic keys.""" wrap = lambda d: {v: [f"$({s})" for s in syms] for v, syms in d.items()} return { "MechID": wrap(defines(MECHLAB_HEADERS, "M_")), "TechType": wrap(defines(MECHLAB_HEADERS, "Tech_")), "NameIndex": wrap(defines(MISSIONLANG_DEFINES, "IDS_")), "MoveTypeFlag": {i: [s] for i, s in enumerate(MOVE_TYPE)}, } def symbol(tbl, key, value, hint=None): """Pick the source token for a value, preferring one naming the chassis.""" syms = tbl.get(key, {}).get(value) if not syms: return None if hint and len(syms) > 1: h = norm(hint) for s in syms: if h and h in norm(s): return s return syms[0] def norm(tok): """Compare symbols ignoring case and $() wrapping.""" return re.sub(r'[^a-z0-9_]', '', tok.lower()) if __name__ == "__main__": for key, tbl in tables().items(): print(f"{key:14s} {len(tbl):5d} values e.g. {list(tbl.items())[:2]}")