994f9f9610
`reference/` held benilla's own copies of the reference's tables, some derived by its own scripts from older inputs: the fire-site table kept one event id where a site fires two, and credited glue-screen sites to in-game events. Now `reference/` is benilla-re's tables at the commit `reference/PIN` names, byte for byte: bindings, events, glue events, messages, event arguments, verb events, the glue surface, the Lua globals, chat types and groups, and CVars. `scripts/sync-reference.py` copies them, and its `--check` runs in the gates and in CI. Four generators and nine old files go, and every reader moves to the new columns. New tests hold the chat type, chat group and CVar tables to the copies; the CVar rows already filed are listed as known. Fixes #245 Fixes #960 Part of #244 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
104 lines
3.8 KiB
Python
Executable File
104 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
r"""Generate `crates/benilla-ui/src/messages/catalog.rs` from `reference/messages.tsv`.
|
|
|
|
The table is the reference's message registry at `0xb4b498`, which `CGGameUI::DisplayError`
|
|
`0x496720` indexes by message id; this re-shapes it into a Rust static that ships with benilla.
|
|
|
|
python3 scripts/gen-message-catalog.py # the vendored table -> the .rs, in place
|
|
python3 scripts/gen-message-catalog.py --check # fail when the .rs is not what it generates
|
|
python3 scripts/gen-message-catalog.py <tsv> # another table
|
|
|
|
It refuses rather than emits on a non-dense id range, a duplicate key or an unknown kind.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
TABLE = os.path.join(REPO, "reference", "messages.tsv")
|
|
OUT = os.path.join(REPO, "crates", "benilla-ui", "src", "messages", "catalog.rs")
|
|
|
|
KINDS = {0: "Chat", 1: "Info", 2: "Error"}
|
|
|
|
HEADER = """\
|
|
//! The 1.12 client's message catalog, row for row.
|
|
//!
|
|
//! @generated by `scripts/gen-message-catalog.py` from `reference/messages.tsv`.
|
|
//! Do not edit by hand; regenerate.
|
|
|
|
use super::{MessageRecord, MsgKind};
|
|
|
|
/// Every row of `0xb4b498`, indexed by message id: the ids are dense `0..465`, so `CATALOG[id]`
|
|
/// is the reference's `0xb4b498 + 20*id`.
|
|
///
|
|
/// `rustfmt` is off: the generator emits one row per line, and a rewrap would drift from it.
|
|
#[rustfmt::skip]
|
|
pub static CATALOG: [MessageRecord; {count}] = [
|
|
"""
|
|
|
|
|
|
def main(path: str, check: bool = False) -> int:
|
|
if not os.path.exists(path):
|
|
raise SystemExit(f"no table at {path}")
|
|
rows = []
|
|
for line in open(path):
|
|
if line.startswith("id\t"):
|
|
continue
|
|
f = line.rstrip("\n").split("\t")
|
|
if len(f) != 7:
|
|
raise SystemExit(f"malformed row (expected 7 fields, got {len(f)}): {line!r}")
|
|
rows.append(
|
|
{
|
|
"id": int(f[0]),
|
|
"key": f[2],
|
|
"kind": int(f[3]),
|
|
"sound": f[4],
|
|
"type_tag": int(f[5], 16),
|
|
"chat_type": int(f[6]),
|
|
}
|
|
)
|
|
|
|
for want, r in enumerate(rows):
|
|
if r["id"] != want:
|
|
raise SystemExit(f"ids are not dense: expected {want}, got {r['id']}")
|
|
keys = [r["key"] for r in rows]
|
|
if len(set(keys)) != len(keys):
|
|
raise SystemExit("duplicate key in the catalog — by_key would be ambiguous")
|
|
for r in rows:
|
|
if r["kind"] not in KINDS:
|
|
raise SystemExit(
|
|
f"row {r['id']} ({r['key']}) has kind {r['kind']}, which MsgKind does not model"
|
|
)
|
|
|
|
out = [HEADER.replace("{count}", str(len(rows)))]
|
|
for r in rows:
|
|
sound = "None" if r["sound"] == "NONE" else f'Some("{r["sound"]}")'
|
|
out.append(
|
|
f' MessageRecord {{ id: {r["id"]}, key: "{r["key"]}", '
|
|
f'kind: MsgKind::{KINDS[r["kind"]]}, sound: {sound}, '
|
|
f'type_tag: {r["type_tag"]:#04x}, chat_type: {r["chat_type"]} }},\n'
|
|
)
|
|
out.append("];\n")
|
|
text = "".join(out)
|
|
if check:
|
|
have = open(OUT).read() if os.path.exists(OUT) else None
|
|
if have != text:
|
|
raise SystemExit(
|
|
f"{os.path.relpath(OUT, REPO)} is not what {os.path.relpath(path, REPO)} "
|
|
"generates; run scripts/gen-message-catalog.py"
|
|
)
|
|
print(f"{os.path.relpath(OUT, REPO)} matches its table ({len(rows)} rows)")
|
|
return 0
|
|
with open(OUT, "w") as f:
|
|
f.write(text)
|
|
print(f"wrote {os.path.relpath(OUT, REPO)} ({len(rows)} rows)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = sys.argv[1:]
|
|
check = "--check" in args
|
|
args = [a for a in args if a != "--check"]
|
|
if len(args) > 1 or (check and args):
|
|
raise SystemExit(__doc__)
|
|
sys.exit(main(args[0] if args else TABLE, check))
|