init
This commit is contained in:
commit
e45f121fb9
89 changed files with 336069 additions and 0 deletions
261
demo/scripts/export_map.gd
Normal file
261
demo/scripts/export_map.gd
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
extends SceneTree
|
||||
|
||||
# Usage:
|
||||
# godot --headless --path demo --script scripts/export_map.gd -- SEED DEPTH [LEVELS] OUT.map
|
||||
#
|
||||
# Generates N dungeons (seeds SEED..SEED+N-1, depths DEPTH..DEPTH+N-1),
|
||||
# stacks them vertically, and writes one Standard Quake .map file.
|
||||
#
|
||||
# Chasms on non-bottom levels are real holes — no floor brush, and the
|
||||
# level below has its ceiling drilled out at the same (x,y) so the shaft
|
||||
# stays open all the way down until it hits a non-chasm floor. Chasms on
|
||||
# the bottom level get a local pit floor at CHASM_TOP so you don't fall
|
||||
# into the void.
|
||||
|
||||
const TILE_SIZE := 64
|
||||
const HEIGHT := 128
|
||||
const WALL_THICKNESS := 64
|
||||
const TEXTURE := "__TB_empty"
|
||||
|
||||
const FLOOR_TOP := 0
|
||||
const WATER_TOP := -32
|
||||
const CHASM_TOP := -128
|
||||
const PIT_BOTTOM := CHASM_TOP - WALL_THICKNESS # = -192
|
||||
const WALL_TOP := HEIGHT + WALL_THICKNESS # = 192
|
||||
|
||||
# Per-level Z offset. Each level's brushes get translated by
|
||||
# -level_index * LEVEL_SPACING. Sized so level N+1's ceiling top sits at
|
||||
# or below level N's PIT_BOTTOM — no overlap, no z-fighting.
|
||||
const LEVEL_SPACING := 384
|
||||
|
||||
# terrain_t
|
||||
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
|
||||
|
||||
# liquid_t
|
||||
const L_WATER := 1
|
||||
const L_CHASM := 3
|
||||
|
||||
enum Kind { EMPTY, WALL, FLOOR, WATER, CHASM }
|
||||
|
||||
func _init() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var seed: int
|
||||
var depth: int
|
||||
var levels: int
|
||||
var out_path: String
|
||||
match args.size():
|
||||
3:
|
||||
seed = int(args[0])
|
||||
depth = int(args[1])
|
||||
levels = 1
|
||||
out_path = args[2]
|
||||
4:
|
||||
seed = int(args[0])
|
||||
depth = int(args[1])
|
||||
levels = int(args[2])
|
||||
out_path = args[3]
|
||||
_:
|
||||
push_error("usage: -- SEED DEPTH [LEVELS] OUT.map")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
if levels < 1:
|
||||
push_error("levels must be >= 1")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var grids: Array = []
|
||||
for k in range(levels):
|
||||
var gen := BrogueGen.new()
|
||||
grids.append(gen.generate(seed + k, depth + k))
|
||||
gen.free()
|
||||
|
||||
var f := FileAccess.open(out_path, FileAccess.WRITE)
|
||||
if f == null:
|
||||
push_error("cannot open %s for write" % out_path)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var brush_count := _write_map(f, grids, seed, depth)
|
||||
f.close()
|
||||
print("wrote %s — %d levels, %d brushes" % [out_path, levels, brush_count])
|
||||
quit(0)
|
||||
|
||||
func _write_map(f: FileAccess, grids: Array, seed: int, depth: int) -> int:
|
||||
f.store_string("// Game: FuncGodot\n")
|
||||
f.store_string("// Format: Valve\n")
|
||||
f.store_string("// Generated by brogue-genesis — seed: %d, depth: %d, levels: %d\n" % [
|
||||
seed, depth, grids.size(),
|
||||
])
|
||||
f.store_string("{\n")
|
||||
f.store_string("\"classname\" \"worldspawn\"\n")
|
||||
|
||||
var count := 0
|
||||
var w: int = grids[0]["width"]
|
||||
var h: int = grids[0]["height"]
|
||||
# Per-(x,y) flag: "some level above has a chasm here, so our ceiling
|
||||
# is drilled out so the shaft stays open". Accumulates top-down.
|
||||
var chasm_above := PackedByteArray()
|
||||
chasm_above.resize(w * h)
|
||||
|
||||
for k in range(grids.size()):
|
||||
var grid: Dictionary = grids[k]
|
||||
var is_bottom := (k == grids.size() - 1)
|
||||
var z_offset := -k * LEVEL_SPACING
|
||||
count += _write_level(f, grid, z_offset, chasm_above, is_bottom)
|
||||
# Update chasm_above for the next level using THIS level's chasms.
|
||||
_propagate_chasms(grid, chasm_above)
|
||||
|
||||
f.store_string("}\n")
|
||||
return count
|
||||
|
||||
func _write_level(f: FileAccess, grid: Dictionary, z_off: int,
|
||||
chasm_above: PackedByteArray, is_bottom: bool) -> int:
|
||||
var w: int = grid["width"]
|
||||
var h: int = grid["height"]
|
||||
var terrain: PackedByteArray = grid["terrain"]
|
||||
var liquid: PackedByteArray = grid["liquid"]
|
||||
|
||||
var count := 0
|
||||
var ts := TILE_SIZE
|
||||
|
||||
# Pass 1 — floors / walls, row-merged by kind.
|
||||
for gy in range(h):
|
||||
var run_start := 0
|
||||
var run_kind := _kind(terrain, liquid, w, 0, gy)
|
||||
for gx in range(1, w + 1):
|
||||
var cur: int = Kind.EMPTY
|
||||
if gx < w:
|
||||
cur = _kind(terrain, liquid, w, gx, gy)
|
||||
if cur == run_kind and gx < w:
|
||||
continue
|
||||
var x0 := run_start * ts
|
||||
var x1 := gx * ts
|
||||
var y0 := gy * ts
|
||||
var y1 := (gy + 1) * ts
|
||||
match run_kind:
|
||||
Kind.WALL:
|
||||
count += _emit_box(f, x0, y0, z_off + PIT_BOTTOM,
|
||||
x1, y1, z_off + WALL_TOP)
|
||||
Kind.FLOOR:
|
||||
count += _emit_box(f, x0, y0, z_off + PIT_BOTTOM,
|
||||
x1, y1, z_off + FLOOR_TOP)
|
||||
Kind.WATER:
|
||||
count += _emit_box(f, x0, y0, z_off + PIT_BOTTOM,
|
||||
x1, y1, z_off + WATER_TOP)
|
||||
Kind.CHASM:
|
||||
# Non-bottom chasms are real holes — no floor. Bottom
|
||||
# chasms get a local pit floor so the player doesn't
|
||||
# fall into empty void.
|
||||
if is_bottom:
|
||||
count += _emit_box(f, x0, y0, z_off + PIT_BOTTOM,
|
||||
x1, y1, z_off + CHASM_TOP)
|
||||
_:
|
||||
pass
|
||||
run_start = gx
|
||||
run_kind = cur
|
||||
|
||||
# Pass 2 — ceilings, row-merged by "is ceiling present here?". A
|
||||
# ceiling is present if the cell has floor-like terrain AND no level
|
||||
# above has drilled a chasm through it.
|
||||
for gy in range(h):
|
||||
var run_start := 0
|
||||
var run_has := _has_ceiling(terrain, chasm_above, w, 0, gy)
|
||||
for gx in range(1, w + 1):
|
||||
var cur := false
|
||||
if gx < w:
|
||||
cur = _has_ceiling(terrain, chasm_above, w, gx, gy)
|
||||
if cur == run_has and gx < w:
|
||||
continue
|
||||
if run_has:
|
||||
var x0 := run_start * ts
|
||||
var x1 := gx * ts
|
||||
var y0 := gy * ts
|
||||
var y1 := (gy + 1) * ts
|
||||
count += _emit_box(f, x0, y0, z_off + HEIGHT,
|
||||
x1, y1, z_off + WALL_TOP)
|
||||
run_start = gx
|
||||
run_has = cur
|
||||
|
||||
return count
|
||||
|
||||
# Record any chasm cells at this level into the running mask so the NEXT
|
||||
# level knows to drill its ceiling there.
|
||||
func _propagate_chasms(grid: Dictionary, chasm_above: PackedByteArray) -> void:
|
||||
var w: int = grid["width"]
|
||||
var h: int = grid["height"]
|
||||
var terrain: PackedByteArray = grid["terrain"]
|
||||
var liquid: PackedByteArray = grid["liquid"]
|
||||
for gy in range(h):
|
||||
for gx in range(w):
|
||||
var idx := gy * w + gx
|
||||
if terrain[idx] == T_LIQUID and liquid[idx] == L_CHASM:
|
||||
chasm_above[idx] = 1
|
||||
|
||||
# Draw kind for floor/wall emission.
|
||||
func _kind(terrain: PackedByteArray, liquid: PackedByteArray,
|
||||
w: int, x: int, y: int) -> int:
|
||||
var idx := y * w + x
|
||||
var t: int = terrain[idx]
|
||||
match t:
|
||||
T_NOTHING: return Kind.EMPTY
|
||||
T_WALL: return Kind.WALL
|
||||
T_LIQUID:
|
||||
var liq: int = liquid[idx]
|
||||
if liq == L_CHASM: return Kind.CHASM
|
||||
if liq == L_WATER: return Kind.WATER
|
||||
return Kind.FLOOR # lava / brimstone sit at floor level
|
||||
_: return Kind.FLOOR
|
||||
|
||||
# Ceiling emitted when the cell is floor-like AND no chasm sits above it.
|
||||
# Walls and empty cells don't get ceilings either (wall brush already
|
||||
# reaches WALL_TOP; empty is empty).
|
||||
func _has_ceiling(terrain: PackedByteArray, chasm_above: PackedByteArray,
|
||||
w: int, x: int, y: int) -> bool:
|
||||
var idx := y * w + x
|
||||
if chasm_above[idx] != 0:
|
||||
return false
|
||||
var t: int = terrain[idx]
|
||||
return t != T_NOTHING and t != T_WALL
|
||||
|
||||
# Standard Quake brush: 6 axis-aligned planes, 3 points per plane, inward
|
||||
# normals per TrenchBroom convention. Mirrors libd's emit_solid_box.
|
||||
func _emit_box(f: FileAccess, x0: int, y0: int, z0: int,
|
||||
x1: int, y1: int, z1: int) -> int:
|
||||
if x0 >= x1 or y0 >= y1 or z0 >= z1:
|
||||
return 0
|
||||
f.store_string("{\n")
|
||||
# Valve 220 texture axes per face normal (Quake convention).
|
||||
# X-facing walls: U=Y, V=-Z. Y-facing walls: U=X, V=-Z. Z-facing: U=X, V=-Y.
|
||||
var ax_x := "[ 0 1 0 0 ] [ 0 0 -1 0 ]"
|
||||
var ax_y := "[ 1 0 0 0 ] [ 0 0 -1 0 ]"
|
||||
var ax_z := "[ 1 0 0 0 ] [ 0 -1 0 0 ]"
|
||||
# -X
|
||||
_face(f, x0, y0, z0, x0, y1, z0, x0, y0, z1, ax_x)
|
||||
# +X
|
||||
_face(f, x1, y0, z0, x1, y0, z1, x1, y1, z0, ax_x)
|
||||
# -Y
|
||||
_face(f, x0, y0, z1, x1, y0, z1, x0, y0, z0, ax_y)
|
||||
# +Y
|
||||
_face(f, x0, y1, z0, x1, y1, z0, x0, y1, z1, ax_y)
|
||||
# -Z
|
||||
_face(f, x0, y0, z0, x1, y0, z0, x0, y1, z0, ax_z)
|
||||
# +Z
|
||||
_face(f, x0, y0, z1, x0, y1, z1, x1, y0, z1, ax_z)
|
||||
f.store_string("}\n")
|
||||
return 1
|
||||
|
||||
func _face(f: FileAccess, x1: int, y1: int, z1: int,
|
||||
x2: int, y2: int, z2: int,
|
||||
x3: int, y3: int, z3: int, axes: String) -> void:
|
||||
f.store_string("( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s %s 0 1 1\n" % [
|
||||
x1, y1, z1, x2, y2, z2, x3, y3, z3, TEXTURE, axes,
|
||||
])
|
||||
Loading…
Add table
Add a link
Reference in a new issue