extends Node class_name ModularMapGenerator # ModularMapGenerator - Procedural generation for Floor 0 (Ground) const GROUND_ITEM = 0 const WALL_ITEM = 4 # Or Void if using empty cells # Generation Parameters var grid_width: int = 14 var grid_depth: int = 14 var min_walkable_cells: int = 20 # Minimum for 12 players + space var max_walkable_cells: int = 100 var connectivity_guaranteed: bool = true # Shapes / Strategies enum MapShape { RECTANGLE, ISLANDS, MAZE_LIKE, ROOMS, RANDOM_NOISE } func generate_map(gridmap: Node, width: int, depth: int, shape: int = MapShape.ROOMS) -> bool: grid_width = width grid_depth = depth var grid_data = [] # 2D array [z][x] # Initialize with walls/void for z in range(depth): var row = [] for x in range(width): row.append(WALL_ITEM) grid_data.append(row) # Apply generation strategy match shape: MapShape.RECTANGLE: _generate_rectangle(grid_data) MapShape.ISLANDS: _generate_islands(grid_data) MapShape.ROOMS: _generate_rooms(grid_data) MapShape.RANDOM_NOISE: _generate_noise(grid_data) _: _generate_rooms(grid_data) # Default # Ensure connectivity if connectivity_guaranteed: _ensure_connectivity(grid_data) # Ensure minimum walkable count (by growing if needed) _ensure_min_walkable(grid_data) # Apply to GridMap _apply_to_gridmap(gridmap, grid_data) return true func _generate_rectangle(grid: Array): # Simple rectangle with some noise edges? for z in range(1, grid_depth - 1): for x in range(1, grid_width - 1): grid[z][x] = GROUND_ITEM func _generate_noise(grid: Array): var rng = RandomNumberGenerator.new() rng.randomize() for z in range(1, grid_depth - 1): for x in range(1, grid_width - 1): if rng.randf() > 0.4: grid[z][x] = GROUND_ITEM func _generate_rooms(grid: Array): # Random walk rooms var rng = RandomNumberGenerator.new() rng.randomize() var room_count = rng.randi_range(3, 6) var rooms = [] # Rects for i in range(room_count): var w = rng.randi_range(3, 6) var h = rng.randi_range(3, 6) var x = rng.randi_range(1, grid_width - w - 1) var z = rng.randi_range(1, grid_depth - h - 1) # Carve room for rz in range(z, z + h): for rx in range(x, x + w): grid[rz][rx] = GROUND_ITEM # Connect to previous room if i > 0: var prev = rooms[i-1] _connect_points(grid, Vector2i(prev[0], prev[1]), Vector2i(x, z)) rooms.append([x, z, w, h]) func _generate_islands(grid: Array): # Cellular automata smoothing _generate_noise(grid) # Smoothing passes for i in range(3): var new_grid = grid.duplicate(true) for z in range(1, grid_depth - 1): for x in range(1, grid_width - 1): var neighbors = _count_neighbors(grid, x, z) if neighbors > 4: new_grid[z][x] = GROUND_ITEM elif neighbors < 3: new_grid[z][x] = WALL_ITEM grid = new_grid func _count_neighbors(grid: Array, x: int, z: int) -> int: var count = 0 for dz in range(-1, 2): for dx in range(-1, 2): if dz == 0 and dx == 0: continue if grid[z + dz][x + dx] == GROUND_ITEM: count += 1 return count func _connect_points(grid: Array, p1: Vector2i, p2: Vector2i): var curr = p1 while curr != p2: grid[curr.y][curr.x] = GROUND_ITEM if curr.x != p2.x: curr.x += 1 if p2.x > curr.x else -1 elif curr.y != p2.y: curr.y += 1 if p2.y > curr.y else -1 grid[p2.y][p2.x] = GROUND_ITEM func _ensure_connectivity(grid: Array): # Flood fill from first walkable var start = Vector2i(-1, -1) var all_walkable = [] for z in range(grid_depth): for x in range(grid_width): if grid[z][x] == GROUND_ITEM: if start == Vector2i(-1, -1): start = Vector2i(x, z) all_walkable.append(Vector2i(x, z)) if start == Vector2i(-1, -1): return # Empty map var visited = {} var queue = [start] visited[start] = true var connected_count = 0 while not queue.is_empty(): var curr = queue.pop_front() connected_count += 1 # Check neighbors var dirs = [Vector2i(0, 1), Vector2i(0, -1), Vector2i(1, 0), Vector2i(-1, 0)] for d in dirs: var next = curr + d if next.x >= 0 and next.x < grid_width and next.y >= 0 and next.y < grid_depth: if grid[next.y][next.x] == GROUND_ITEM and not visited.has(next): visited[next] = true queue.append(next) # If disconnected parts, keep largest island or removing others? # Better: grow bridges. But for simplicity, let's just use the main island # and turn others to walls? Or connect them? # For "Rooms" geneation, we explicitly connected them. # For "Islands", let's just keep the biggest island or assume automata made big blobs. if connected_count < all_walkable.size(): # Prune disconnected (simple approach) for pos in all_walkable: if not visited.has(pos): grid[pos.y][pos.x] = WALL_ITEM func _ensure_min_walkable(grid: Array): var walkable = [] for z in range(grid_depth): for x in range(grid_width): if grid[z][x] == GROUND_ITEM: walkable.append(Vector2i(x, z)) if walkable.size() < min_walkable_cells: # Force create a central room var center_x = grid_width / 2 var center_z = grid_depth / 2 var radius = int(sqrt(min_walkable_cells)) / 2 + 1 for z in range(center_z - radius, center_z + radius): for x in range(center_x - radius, center_x + radius): if x >= 0 and x < grid_width and z >= 0 and z < grid_depth: grid[z][x] = GROUND_ITEM func _apply_to_gridmap(gridmap: Node, grid: Array): for z in range(grid_depth): for x in range(grid_width): var item = grid[z][x] # Floor 0 logic gridmap.set_cell_item(Vector3i(x, 0, z), item)