88 lines
3.5 KiB
Python
88 lines
3.5 KiB
Python
"""Generate the tileset palette `twu01palstd.itp` from tiles.py.
|
|
|
|
python make_itp.py [out.itp] # default: twu01palstd.itp
|
|
|
|
The toolset loads `<set>palstd.itp` when it registers a tileset; without it the
|
|
set fails to register (blank name in the New Area wizard, "could not create
|
|
area"). It's a GFF `ITP ` file: MAIN = a list of palette *category* folders, each
|
|
{ID byte, STRREF (dialog.tlk folder label), LIST of leaves}.
|
|
|
|
We emit TWO folders (the verified structure — AGENTS.md §2.3; wut01palstd.itp):
|
|
* Features (STRREF 63261): one leaf per NON-paint tile, {NAME, RESREF=tile
|
|
resref}. Each leaf is placeable ONLY because make_set.py backs it with a 1x1
|
|
[GROUP#] whose Name == this NAME and Tile0 == that tile; the toolset finds the
|
|
group by the RESREF (its first tile). Paint tiles (tiles.is_paint) are omitted
|
|
here on purpose — they're brush-laid terrain, not click-placed features.
|
|
* Terrain (STRREF 8282): the paintable vocabulary — `cobble` and `canal` (each
|
|
RESREF = terrain name) plus the magic `eraser` leaf (RESREF `eraser`, STRREF
|
|
63291, no NAME). `raised` is NOT here: it's a height on cobble (Raise/Lower),
|
|
deferred to the next pass with HasHeightTransition=1 + a cobble-height matrix.
|
|
Multi-tile groups (folder 63262) would go in a third folder; we have none yet.
|
|
Built as JSON and converted to GFF with neverwinter.nim's nwn_gff (on PATH).
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import tiles
|
|
|
|
STRREF_FEATURES = 63261 # dialog.tlk label for the "Features" palette folder
|
|
STRREF_TERRAIN = 8282 # dialog.tlk label for the "Terrain" palette folder
|
|
STRREF_ERASER = 63291 # the magic eraser leaf (no NAME)
|
|
|
|
|
|
def leaf(name, resref):
|
|
return {"__struct_id": 0,
|
|
"NAME": {"type": "cexostring", "value": name},
|
|
"RESREF": {"type": "resref", "value": resref}}
|
|
|
|
|
|
def eraser_leaf():
|
|
return {"__struct_id": 0,
|
|
"RESREF": {"type": "resref", "value": "eraser"},
|
|
"STRREF": {"type": "dword", "value": STRREF_ERASER}}
|
|
|
|
|
|
def category(cid, strref, leaves):
|
|
return {"__struct_id": 0,
|
|
"ID": {"type": "byte", "value": cid},
|
|
"STRREF": {"type": "dword", "value": strref},
|
|
"LIST": {"type": "list", "value": leaves}}
|
|
|
|
|
|
def build():
|
|
features = [leaf(tiles.group_name(s), tiles.resref(s))
|
|
for s in tiles.TILES if not tiles.is_paint(s)]
|
|
# Paintable terrains = every .set terrain EXCEPT 'raised' (which is a height on
|
|
# cobble, not a terrain — the Raise/Lower tool, deferred to the next pass).
|
|
paintable = [t for t in tiles.TERRAINS if t != "raised"] # -> cobble, canal
|
|
terrain = [leaf(t, t) for t in paintable] + [eraser_leaf()]
|
|
return {
|
|
"__data_type": "ITP ",
|
|
"MAIN": {"type": "list", "value": [
|
|
category(0, STRREF_FEATURES, features), # ID/STRREF mirror wut01palstd.itp
|
|
category(2, STRREF_TERRAIN, terrain),
|
|
]},
|
|
}
|
|
|
|
|
|
def main():
|
|
out = sys.argv[1] if len(sys.argv) > 1 else f"{tiles.PREFIX}palstd.itp"
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as tf:
|
|
json.dump(build(), tf)
|
|
js = tf.name
|
|
try:
|
|
subprocess.run(["nwn_gff", "-i", js, "-l", "json", "-k", "gff", "-o", out],
|
|
check=True)
|
|
finally:
|
|
os.unlink(js)
|
|
nfeat = sum(1 for s in tiles.TILES if not tiles.is_paint(s))
|
|
print(f"wrote {out} ({nfeat} features + paint:cobble,canal + eraser)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|