feat: implement obstacle manager for randomized static wall spawning, integrate into main scene, and add stop-n-go manager.
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
extends Node
|
||||
class_name ObstacleManager
|
||||
|
||||
# ObstacleManager - Handles static spawning of walls/blocks on the arena
|
||||
|
||||
const TILE_OBSTACLE = 4 # Wall
|
||||
const TILE_GROUND = 0 # Standard Floor
|
||||
|
||||
var main: Node
|
||||
var gridmap: Node
|
||||
|
||||
func initialize(p_main: Node, p_gridmap: Node):
|
||||
main = p_main
|
||||
gridmap = p_gridmap
|
||||
print("[ObstacleManager] Initialized")
|
||||
|
||||
func spawn_random_obstacles(count: int = 15):
|
||||
if not multiplayer.is_server(): return
|
||||
print("[ObstacleManager] Attempting to spawn %d obstacles (Guaranteed types first)" % count)
|
||||
|
||||
var shapes = _get_all_shapes()
|
||||
var successful_spawns = 0
|
||||
|
||||
# 1. First, try to spawn each shape type at least once
|
||||
for shape_idx in range(shapes.size()):
|
||||
var shape_attempts = 0
|
||||
var shape_placed = false
|
||||
while not shape_placed and shape_attempts < 20: # Give each type a fair chance
|
||||
shape_attempts += 1
|
||||
if spawn_random_wall(shape_idx):
|
||||
shape_placed = true
|
||||
successful_spawns += 1
|
||||
|
||||
if successful_spawns >= count: break
|
||||
|
||||
# 2. Then, fill the remaining count with random shapes
|
||||
var total_attempts = 0
|
||||
var max_attempts = count * 5
|
||||
while successful_spawns < count and total_attempts < max_attempts:
|
||||
total_attempts += 1
|
||||
if spawn_random_wall():
|
||||
successful_spawns += 1
|
||||
|
||||
print("[ObstacleManager] Final: Spawned %d obstacles" % successful_spawns)
|
||||
|
||||
# Force AStar update after all spawns
|
||||
if gridmap and gridmap.has_method("initialize_astar"):
|
||||
gridmap.initialize_astar()
|
||||
|
||||
func _get_all_shapes() -> Array:
|
||||
return [
|
||||
# L (3 blocks) - 4 Rotations
|
||||
[Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, 1)], # L - Corner Bottom Left
|
||||
[Vector2i(0, 0), Vector2i(-1, 0), Vector2i(0, 1)], # L - Corner Bottom Right
|
||||
[Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, -1)], # L - Corner Top Left
|
||||
[Vector2i(0, 0), Vector2i(-1, 0), Vector2i(0, -1)], # L - Corner Top Right
|
||||
|
||||
# 2 Vertical
|
||||
[Vector2i(0, 0), Vector2i(0, 1)],
|
||||
[Vector2i(0, 0), Vector2i(0, -1)],
|
||||
|
||||
# 2 Horizontal
|
||||
[Vector2i(0, 0), Vector2i(1, 0)],
|
||||
[Vector2i(0, 0), Vector2i(-1, 0)],
|
||||
|
||||
# Single
|
||||
[Vector2i(0, 0)]
|
||||
]
|
||||
|
||||
func spawn_random_wall(forced_shape_idx: int = -1) -> bool:
|
||||
if not gridmap or not main: return false
|
||||
|
||||
var cols = gridmap.get("columns") if "columns" in gridmap else 14
|
||||
var rows = gridmap.get("rows") if "rows" in gridmap else 14
|
||||
|
||||
# User Request: "spawn on columns 3 and so on"
|
||||
if cols <= 3: return false
|
||||
|
||||
var x = randi_range(3, cols - 1)
|
||||
var z = randi_range(0, rows - 1)
|
||||
|
||||
var shapes = _get_all_shapes()
|
||||
var shape_idx = forced_shape_idx if forced_shape_idx != -1 else randi() % shapes.size()
|
||||
var shape = shapes[shape_idx]
|
||||
var wall_positions: Array[Vector3i] = []
|
||||
|
||||
# Validate position and check for ground
|
||||
for offset in shape:
|
||||
var target_x = x + offset.x
|
||||
var target_z = z + offset.y
|
||||
|
||||
# Bounds check
|
||||
if target_x >= 3 and target_x < cols and target_z >= 0 and target_z < rows:
|
||||
var current_item = gridmap.get_cell_item(Vector3i(target_x, 0, target_z))
|
||||
# Only spawn on ground (0) to avoid replacing other obstacles or special tiles
|
||||
if current_item == TILE_GROUND:
|
||||
# PLAYER CHECK: Ensure no player is on this tile
|
||||
var is_occupied = false
|
||||
for player in get_tree().get_nodes_in_group("Players"):
|
||||
if player.get("current_position") == Vector2i(target_x, target_z):
|
||||
is_occupied = true
|
||||
break
|
||||
if is_occupied: return false
|
||||
|
||||
# ADJACENCY CHECK: Ensure no existing walls are nearby
|
||||
if not _is_position_isolated(target_x, target_z, shape, x, z):
|
||||
return false
|
||||
wall_positions.append(Vector3i(target_x, 0, target_z))
|
||||
else:
|
||||
# If any block of the shape hits a non-ground tile, fail the whole shape for clean placement
|
||||
return false
|
||||
else:
|
||||
return false
|
||||
|
||||
# Only proceed if we can place the full shape
|
||||
if wall_positions.is_empty():
|
||||
return false
|
||||
|
||||
# Create walls on all clients (Permanent, no despawn)
|
||||
for pos in wall_positions:
|
||||
main.rpc("sync_grid_item", pos.x, pos.y, pos.z, TILE_OBSTACLE)
|
||||
|
||||
return true
|
||||
|
||||
func _is_position_isolated(target_x: int, target_z: int, shape_offsets: Array, origin_x: int, origin_z: int) -> bool:
|
||||
# Check 3x3 area
|
||||
for dx in range(-1, 2):
|
||||
for dz in range(-1, 2):
|
||||
if dx == 0 and dz == 0: continue
|
||||
|
||||
var nx = target_x + dx
|
||||
var nz = target_z + dz
|
||||
|
||||
# Check if this neighbor is part of the shape we are currently placing
|
||||
var is_part_of_shape = false
|
||||
for offset in shape_offsets:
|
||||
if nx == origin_x + offset.x and nz == origin_z + offset.y:
|
||||
is_part_of_shape = true
|
||||
break
|
||||
|
||||
if is_part_of_shape: continue
|
||||
|
||||
# Check if gridmap has a wall at this neighbor
|
||||
if gridmap.get_cell_item(Vector3i(nx, 0, nz)) == TILE_OBSTACLE:
|
||||
return false
|
||||
return true
|
||||
@@ -286,52 +286,16 @@ func _apply_arena_setup():
|
||||
for z in range(gridmap.rows):
|
||||
gridmap.set_cell_item(Vector3i(x, 0, z), tile_id)
|
||||
|
||||
# --- SPECIFIC OBSTACLES (Black Walls) ---
|
||||
# Note: Specific obstacles removed as per user request to replace with random ones.
|
||||
# MISSION TILES: Moved to start_game_mode() to ensure they spawn AFTER walls.
|
||||
|
||||
# Left Obstacles (Column 4)
|
||||
# Top Vertical Bar
|
||||
for z in range(0, 4): # z=0, 1, 2, 3
|
||||
gridmap.set_cell_item(Vector3i(4, 0, z), TILE_OBSTACLE)
|
||||
# Bottom Vertical Bar
|
||||
for z in range(6, 10): # z=6, 7, 8, 9
|
||||
gridmap.set_cell_item(Vector3i(4, 0, z), TILE_OBSTACLE)
|
||||
|
||||
# Center Obstacles (Column 11 area)
|
||||
# Top Middle Vertical Bar (Offset slightly down)
|
||||
for z in range(1, 5): # z=1, 2, 3, 4
|
||||
gridmap.set_cell_item(Vector3i(11, 0, z), TILE_OBSTACLE)
|
||||
|
||||
# Bottom Middle L-Shape (Vertical + Horizontal hook)
|
||||
for z in range(6, 9): # z=6, 7, 8 (Vertical part)
|
||||
gridmap.set_cell_item(Vector3i(11, 0, z), TILE_OBSTACLE)
|
||||
# Horizontal part of L (at z=6 to right?) - Image looks like inverted L or T?
|
||||
# Let's assume right hook at top of bottom part
|
||||
gridmap.set_cell_item(Vector3i(12, 0, 6), TILE_OBSTACLE)
|
||||
|
||||
# Right Obstacles (Column 18 area)
|
||||
# Top Right L-Shape (Horizontal hook + Vertical down)
|
||||
# Vertical
|
||||
for z in range(0, 3): # z=0, 1, 2
|
||||
gridmap.set_cell_item(Vector3i(18, 0, z), TILE_OBSTACLE)
|
||||
# Horizontal hook to right
|
||||
gridmap.set_cell_item(Vector3i(19, 0, 2), TILE_OBSTACLE)
|
||||
gridmap.set_cell_item(Vector3i(20, 0, 2), TILE_OBSTACLE)
|
||||
|
||||
# Bottom Right Vertical Bar
|
||||
for z in range(5, 9): # z=5, 6, 7, 8
|
||||
gridmap.set_cell_item(Vector3i(18, 0, z), TILE_OBSTACLE)
|
||||
|
||||
gridmap.update_grid_data()
|
||||
gridmap.initialize_astar()
|
||||
|
||||
# Spawn tiles for missions
|
||||
func setup_mission_tiles():
|
||||
"""Public wrapper to trigger mission tile spawning before game start."""
|
||||
if multiplayer.is_server():
|
||||
_spawn_mission_tiles()
|
||||
# Client already constructs the base arena locally via _apply_arena_setup()
|
||||
# So no need to blast huge 5KB arrays across the network.
|
||||
|
||||
# For any specifically spawned tiles (like missions), they are sent individually
|
||||
# by sync_grid_item inside _spawn_mission_tiles.
|
||||
|
||||
func _spawn_mission_tiles():
|
||||
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
||||
|
||||
Reference in New Issue
Block a user