Files
Station-0/scripts/rooms/floor_generator.gd
T

126 lines
3.3 KiB
GDScript

class_name FloorGenerator
const GRID_START := Vector2i(5, 5)
const FLOOR_ROOM_COUNTS := {
1: {"min": 8, "max": 9}, # 4-5 combat + start + boss + item + shop
2: {"min": 9, "max": 10}, # 5-6 combat + start + boss + item + shop
3: {"min": 6, "max": 7}, # 2-3 combat + start + boss + item + shop
}
const DIRS := {
"up": Vector2i( 0, -1),
"down": Vector2i( 0, 1),
"left": Vector2i(-1, 0),
"right": Vector2i( 1, 0),
}
const OPPOSITE := {
"up": "down", "down": "up", "left": "right", "right": "left",
}
# Returns Array of RoomData covering one floor layout.
static func generate(floor_number: int) -> Array:
var rng := RandomNumberGenerator.new()
rng.randomize()
var rooms: Dictionary = {} # Vector2i -> RoomData
# --- Start room ---
var start := RoomData.new()
start.type = RoomData.RoomType.START
start.grid_pos = GRID_START
start.id = "%d_%d" % [GRID_START.x, GRID_START.y]
rooms[GRID_START] = start
var queue: Array = [GRID_START]
var counts: Dictionary = FLOOR_ROOM_COUNTS.get(floor_number, {"min": 5, "max": 6})
var target: int = rng.randi_range(counts["min"], counts["max"])
# --- Random walk ---
while rooms.size() < target and queue.size() > 0:
var pos: Vector2i = queue[rng.randi() % queue.size()]
for dir_name in _shuffled(rng, ["up", "down", "left", "right"]):
if rooms.size() >= target:
break
var next: Vector2i = pos + DIRS[dir_name]
if rooms.has(next):
continue
# Skip if the candidate cell already has >1 existing neighbor
# (prevents overly dense clusters)
var neighbor_count := 0
for d in DIRS.values():
if rooms.has(next + d):
neighbor_count += 1
if neighbor_count > 1:
continue
var r := RoomData.new()
r.type = RoomData.RoomType.COMBAT
r.grid_pos = next
r.id = "%d_%d" % [next.x, next.y]
rooms[next] = r
queue.append(next)
# Bidirectional connection
rooms[pos].connections[dir_name] = next
r.connections[OPPOSITE[dir_name]] = pos
# --- Boss: farthest room from start ---
var boss_pos := _farthest(GRID_START, rooms)
rooms[boss_pos].type = RoomData.RoomType.BOSS
# --- Item + Shop: dead-end COMBAT rooms ---
var dead_ends: Array = []
for p in rooms:
var rd: RoomData = rooms[p]
if rd.type == RoomData.RoomType.COMBAT and rd.connections.size() == 1:
dead_ends.append(p)
dead_ends.shuffle()
if dead_ends.size() >= 1:
rooms[dead_ends[0]].type = RoomData.RoomType.ITEM
if dead_ends.size() >= 2:
rooms[dead_ends[1]].type = RoomData.RoomType.SHOP
return rooms.values()
static func _shuffled(rng: RandomNumberGenerator, arr: Array) -> Array:
var result := arr.duplicate()
for i in range(result.size() - 1, 0, -1):
var j := rng.randi() % (i + 1)
var tmp = result[i]
result[i] = result[j]
result[j] = tmp
return result
static func _farthest(start: Vector2i, rooms: Dictionary) -> Vector2i:
var visited: Dictionary = {}
var queue: Array = [[start, 0]]
var farthest := start
var max_dist := 0
while queue.size() > 0:
var entry = queue.pop_front()
var pos: Vector2i = entry[0]
var dist: int = entry[1]
if visited.has(pos):
continue
visited[pos] = true
if dist > max_dist:
max_dist = dist
farthest = pos
var rd: RoomData = rooms[pos]
for connected in rd.connections.values():
if not visited.has(connected):
queue.append([connected, dist + 1])
return farthest