AGENTS.md: NWN:EE tileset construction reference (constraints, .mdl/.wok walkmesh rules, .set format, Blender->NWN pipeline, prototype->NWN mapping). wgt01/: Westgate Urban Foundation MVP kit. - generator/tiles.py: single source of truth (40-tile table, materials, 12x12 test map, pure paint/height/walkmesh/.set derivations). - generator/build.py: Blender 4.0.2 scene generator (VIS_/WOK_/BLOCK_ per tile, edge metadata, library grid + 12x12 district previews). - generator/make_wok.py: ASCII walkmesh emitter (verts+faces+surfacemat+AABB tree) from the same geometry. - generator/make_set.py: first-pass twu01.set (CRLF) from the tile grammar. - flake.nix: build/wok/set apps + dev shell (pinned Blender 4.0.2, NWN tools). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
"""Generate the first-pass NWN tileset definition `twu01.set` from tiles.py.
|
|
|
|
python make_set.py [out.set] # default: twu01.set
|
|
|
|
Pure Python, no Blender. Emits a valid, DOS/CRLF `.set` (AGENTS.md §4/§9: Unix
|
|
line endings crash the toolset). Corners/heights/crossers derive from each tile's
|
|
edge grammar; this is a DRAFT to load and hand-tune in the toolset, not a final
|
|
set. See CAVEATS below.
|
|
|
|
CAVEATS (first pass — tune in the toolset):
|
|
* PathNode is a placeholder ('A' walkable / 'N' non-walkable). Real pathnode
|
|
letters govern creature routing and must be verified against the toolset.
|
|
* ImageMap2D references minimaps (twu01_<code>.tga) that are not rendered yet.
|
|
* Groups/features Count=0; add multi-tile groups later.
|
|
"""
|
|
|
|
import sys
|
|
|
|
import tiles
|
|
|
|
PREFIX = tiles.PREFIX
|
|
|
|
|
|
def tile_block(i, spec):
|
|
cor = tiles.nwn_corners(spec["edges"])
|
|
n, e, s, w = spec["edges"]
|
|
tl, tr, bl, br = cor["TopLeft"], cor["TopRight"], cor["BottomLeft"], cor["BottomRight"]
|
|
ref = tiles.resref(spec)
|
|
walkable = spec["kind"] != "canal"
|
|
L = [f"[TILE{i}]"]
|
|
L += [
|
|
f"Model={ref}",
|
|
"WalkMesh=", # legacy field; per-model .wok is matched by name
|
|
f"ImageMap2D={ref}",
|
|
f"TopLeft={tl[0]}", f"TopLeftHeight={tl[1]}",
|
|
f"TopRight={tr[0]}", f"TopRightHeight={tr[1]}",
|
|
f"BottomLeft={bl[0]}", f"BottomLeftHeight={bl[1]}",
|
|
f"BottomRight={br[0]}", f"BottomRightHeight={br[1]}",
|
|
f"Top={tiles.nwn_crosser(n)}",
|
|
f"Right={tiles.nwn_crosser(e)}",
|
|
f"Bottom={tiles.nwn_crosser(s)}",
|
|
f"Left={tiles.nwn_crosser(w)}",
|
|
"MainLight1=0", "MainLight2=0", "SourceLight1=0", "SourceLight2=0",
|
|
"AnimLoop1=0", "AnimLoop2=0", "AnimLoop3=0",
|
|
"Orientation=0",
|
|
f"PathNode={'A' if walkable else 'N'}", # placeholder — verify in toolset
|
|
"VisibilityNode=",
|
|
"Doors=0",
|
|
"Sounds=0",
|
|
]
|
|
return L
|
|
|
|
|
|
def build():
|
|
L = []
|
|
L += [
|
|
"[GENERAL]",
|
|
f"Name={PREFIX}",
|
|
"Type=SET",
|
|
"Interior=0",
|
|
"HasHeightTransition=1",
|
|
"Transition=1.5", # metres per height step = Z=1.5 raised tier
|
|
"Border=cobble",
|
|
"Default=cobble",
|
|
"Floor=cobble",
|
|
"DisplayName=-1",
|
|
"UnlocalizedName=Westgate Urban Foundation",
|
|
"Version=1",
|
|
"EnvMap=",
|
|
"SelectorHeight=3",
|
|
"",
|
|
"[GRASS]",
|
|
"Grass=0",
|
|
"GrassTextureName=",
|
|
"Density=0.0",
|
|
"Height=0.0",
|
|
"AmbientRed=0.0", "AmbientGreen=0.0", "AmbientBlue=0.0",
|
|
"DiffuseRed=0.0", "DiffuseGreen=0.0", "DiffuseBlue=0.0",
|
|
"",
|
|
"[TERRAIN TYPES]",
|
|
f"Count={len(tiles.TERRAINS)}",
|
|
]
|
|
for idx, t in enumerate(tiles.TERRAINS):
|
|
L += ["", f"[TERRAIN{idx}]", f"Name={t}", "StrRef=-1"]
|
|
L += ["", "[CROSSER TYPES]", f"Count={len(tiles.CROSSERS)}"]
|
|
for idx, cr in enumerate(tiles.CROSSERS):
|
|
L += ["", f"[CROSSER{idx}]", f"Name={cr}", "StrRef=-1"]
|
|
L += ["", "[PRIMARY RULES]", "Count=0",
|
|
"", "[SECONDARY RULES]", "Count=0",
|
|
"", "[TILES]", f"Count={len(tiles.TILES)}"]
|
|
for i, spec in enumerate(tiles.TILES):
|
|
L.append("")
|
|
L += tile_block(i, spec)
|
|
L += ["", "[GROUPS]", "Count=0", ""]
|
|
return L
|
|
|
|
|
|
def main():
|
|
out = sys.argv[1] if len(sys.argv) > 1 else f"{PREFIX}.set"
|
|
with open(out, "w", newline="") as fh: # newline='' + explicit \r\n = CRLF
|
|
fh.write("\r\n".join(build()))
|
|
print(f"wrote {out} ({len(tiles.TILES)} tiles, "
|
|
f"{len(tiles.TERRAINS)} terrains, {len(tiles.CROSSERS)} crossers, CRLF)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|