@tool extends Node3D class_name DungeonGenerator const T_NOTHING := 0 const T_FLOOR := 1 const T_WALL := 2 const T_DOOR := 3 const T_CORRIDOR := 4 const T_LIQUID := 5 const T_BRIDGE := 6 const T_STAIRS_UP := 7 const T_STAIRS_DOWN := 8 const L_NONE := 0 const L_WATER := 1 const L_LAVA := 2 const L_CHASM := 3 const L_BRIMSTONE := 4 # Match the tile IDs exposed by MeshLibraryBuilder. const TILE_FLOOR := 0 const TILE_WALL := 1 const TILE_DOOR := 2 const TILE_CORRIDOR := 3 const TILE_WATER := 4 const TILE_LAVA := 5 const TILE_BRIMSTONE := 6 const TILE_BRIDGE := 7 const TILE_STAIRS_UP := 8 const TILE_STAIRS_DOWN := 9 @export var base_seed: int = 2321 @export var depth_start: int = 20 @export var level_count: int = 10 @export var level_spacing: float = 6.0 @export var generate: bool = false: set(value): if value and Engine.is_editor_hint(): generate_dungeon() generate = false @export var clear_levels: bool = false: set(value): if value and Engine.is_editor_hint(): _clear_levels() clear_levels = false @onready var levels_root: Node3D = %Levels var _mesh_library: MeshLibrary # Populate one level's GridMap from the grid dict. Returns the count of # chasm cells that were deliberately skipped (for reporting). func _populate_level(grid_map: GridMap, grid: Dictionary) -> int: var w: int = grid["width"] var h: int = grid["height"] var terrain: PackedByteArray = grid["terrain"] var liquid: PackedByteArray = grid["liquid"] var chasm_cells := 0 for y in range(h): for x in range(w): var idx := y * w + x var t: int = terrain[idx] var liq: int = liquid[idx] # Chasm liquid renders as an actual see-through pit. if t == T_LIQUID and liq == L_CHASM: chasm_cells += 1 continue var tile_id := _tile_for(t, liq) if tile_id == -1: continue # T_NOTHING or other empty — leave unrendered # GridMap coords: (X, Y, Z). We want dungeon x → X, dungeon y → Z. grid_map.set_cell_item(Vector3i(x, 0, y), tile_id) return chasm_cells func _tile_for(terrain: int, liquid: int) -> int: match terrain: T_FLOOR: return TILE_FLOOR T_CORRIDOR: return TILE_CORRIDOR T_DOOR: return TILE_DOOR T_WALL: return TILE_WALL T_BRIDGE: return TILE_BRIDGE T_STAIRS_UP: return TILE_STAIRS_UP T_STAIRS_DOWN: return TILE_STAIRS_DOWN T_LIQUID: match liquid: L_WATER: return TILE_WATER L_LAVA: return TILE_LAVA L_BRIMSTONE: return TILE_BRIMSTONE L_CHASM: return -1 # empty cell — see through to level below _: return TILE_WATER _: return -1 # T_NOTHING or unknown func _clear_levels() -> void: if levels_root == null: levels_root = %Levels for child in levels_root.get_children(): levels_root.remove_child(child) child.queue_free() func generate_dungeon(): if levels_root == null: levels_root = %Levels if levels_root.get_child_count() > 0: push_warning("Levels already generated — toggle clear_levels first to regenerate.") return _mesh_library = MeshLibraryBuilder.build() var edited_root: Node = get_tree().edited_scene_root if Engine.is_editor_hint() else null var total_chasm_cells := 0 for level_index in range(level_count): var level_seed := base_seed + level_index var depth := depth_start + level_index var gen := BrogueGen.new() var grid: Dictionary = gen.generate(level_seed, depth) gen.free() var grid_map := GridMap.new() grid_map.name = "Level%d" % level_index grid_map.mesh_library = _mesh_library grid_map.cell_size = Vector3(1, 1, 1) grid_map.position.y = -level_index * level_spacing levels_root.add_child(grid_map) if edited_root: grid_map.owner = edited_root var chasm_cells := _populate_level(grid_map, grid) total_chasm_cells += chasm_cells print("Level %d: seed=%d depth=%d rooms=%d machines=%d chasms=%d" % [level_index, level_seed, depth, (grid["rooms"] as Array).size(), (grid["machines"] as Array).size(), chasm_cells])