bgen/demo/scripts/bake_dungeon.gd
saarsena@gmail.com e45f121fb9 init
2026-04-16 21:04:50 -04:00

135 lines
4 KiB
GDScript

extends SceneTree
# Usage:
# godot --headless --path demo --script scripts/bake_dungeon.gd -- SEED DEPTH OUT_DIR
#
# Shells out to bin/genesis --emit=json and bakes the result into two sibling
# assets under OUT_DIR:
# - mesh_library.tres (tile catalog, same content MLB.build()
# produces at runtime — shareable across bakes)
# - dungeon_seed<S>_depth<D>.tscn (PackedScene with a GridMap referencing
# the MeshLibrary, populated per the
# generated dungeon)
#
# Override the genesis binary path with the GENESIS_BIN env var; default is
# "../bin/genesis" relative to the Godot project.
const MESH_LIB_NAME := "mesh_library.tres"
const MLB = preload("res://scripts/mesh_library_builder.gd")
func _init() -> void:
var args := OS.get_cmdline_user_args()
if args.size() != 3:
push_error("usage: -- SEED DEPTH OUT_DIR")
quit(1)
return
var seed := int(args[0])
var depth := int(args[1])
var out_dir := args[2]
var bin_path := OS.get_environment("GENESIS_BIN")
if bin_path == "":
bin_path = ProjectSettings.globalize_path("res://") + "../bin/genesis"
bin_path = bin_path.simplify_path()
if not DirAccess.dir_exists_absolute(out_dir):
var err := DirAccess.make_dir_recursive_absolute(out_dir)
if err != OK:
push_error("cannot create %s (err %d)" % [out_dir, err])
quit(1)
return
var stdout: Array = []
var exit_code := OS.execute(bin_path, [
"--seed", str(seed), "--depth", str(depth), "--emit=json",
], stdout, true)
if exit_code != 0:
push_error("genesis exited %d (bin=%s)" % [exit_code, bin_path])
quit(1)
return
var grid: Dictionary = JSON.parse_string(stdout[0])
if grid.is_empty():
push_error("genesis produced invalid JSON")
quit(1)
return
# Step 1 — MeshLibrary.tres.
var lib_path := "%s/%s" % [out_dir, MESH_LIB_NAME]
var save_err := MLB.save_resource(lib_path, true)
if save_err != OK:
push_error("failed to save %s (err %d)" % [lib_path, save_err])
quit(1)
return
# Step 2 — PackedScene with populated GridMap.
var lib: MeshLibrary = load(lib_path)
var root := Node3D.new()
root.name = "Dungeon_seed%d_depth%d" % [seed, depth]
var gm := GridMap.new()
gm.name = "GridMap"
gm.mesh_library = lib
gm.cell_size = Vector3(1, 1, 1)
root.add_child(gm)
gm.owner = root
var cells_set := _populate(gm, grid)
var packed := PackedScene.new()
var pack_err := packed.pack(root)
if pack_err != OK:
push_error("failed to pack scene (err %d)" % pack_err)
quit(1)
return
var scn_path := "%s/dungeon_seed%d_depth%d.tscn" % [out_dir, seed, depth]
var scn_err := ResourceSaver.save(packed, scn_path)
if scn_err != OK:
push_error("failed to save %s (err %d)" % [scn_path, scn_err])
quit(1)
return
print("wrote %s + %s%d cells" % [scn_path, lib_path, cells_set])
quit(0)
# Decode the base64 layers and stamp tile IDs into the GridMap. Mirrors
# arcade_scene.gd _populate_grid_map so the CLI produces the same geometry
# as the in-engine path for the same seed/depth. Chasms stay as empty cells.
func _populate(gm: GridMap, grid: Dictionary) -> int:
var w := int(grid["width"])
var h := int(grid["height"])
var terrain := Marshalls.base64_to_raw(grid["terrain"])
var liquid := Marshalls.base64_to_raw(grid["liquid"])
var count := 0
for y in range(h):
for x in range(w):
var idx := y * w + x
var t := terrain[idx]
var liq := liquid[idx]
if t == 5 and liq == 3: # T_LIQUID + L_CHASM
continue
var tile := _tile_for(t, liq)
if tile < 0:
continue
gm.set_cell_item(Vector3i(x, 0, y), tile)
count += 1
return count
func _tile_for(terrain: int, liquid: int) -> int:
match terrain:
1: return MLB.TILE_FLOOR
4: return MLB.TILE_CORRIDOR
3: return MLB.TILE_DOOR
2: return MLB.TILE_WALL
6: return MLB.TILE_BRIDGE
7: return MLB.TILE_STAIRS_UP
8: return MLB.TILE_STAIRS_DOWN
5:
match liquid:
1: return MLB.TILE_WATER
2: return MLB.TILE_LAVA
4: return MLB.TILE_BRIMSTONE
_: return MLB.TILE_WATER
_: return -1