extends Node # StaticTektonManager # Handles spawning and placement of Static Tektons in restricted zones. const STAND_SCENE_PATH = "res://scenes/static_tekton_stand.tscn" const TEKTON_SCENE_PATH = "res://scenes/static_tekton.tscn" const STATIC_CONTROLLER_SCRIPT = "res://scripts/static_tekton_controller.gd" const PERIMETER_BUFFER = 1 # 1-tile safe zone on all sides (matches main.gd) # Zone Definitions based on CameraContextManager logic # 9 Zones in a 3x3 grid (approximate for 14x14 map) # Top-Left, Top-Mid, Top-Right # Mid-Left, Mid-Mid, Mid-Right # Bot-Left, Bot-Mid, Bot-Right func calculate_spawn_points(_count: int, gridmap: Node) -> Array[Vector2i]: """ Calculates the 5 fixed potential spawn positions for static tektons. Now respects 1-tile perimeter buffer to prevent edge spawning. Corners: (2,2), (W-3,2), (2,H-3), (W-3,H-3) Center: (W/2, H/2) Returns exactly 5 spots if possible. """ if not gridmap: return [] var width = gridmap.columns var height = gridmap.rows print("[StaticTektonManager] Calculating static tekton positions (Fixed 5-Spots with Buffer)...") # Fixed Spots with buffer (at least 2 tiles from edge for 3x3 stand + 1 tile buffer) var spots: Array[Vector2i] = [ Vector2i(PERIMETER_BUFFER + 1, PERIMETER_BUFFER + 1), # Top-Left Vector2i(width - PERIMETER_BUFFER - 2, PERIMETER_BUFFER + 1), # Top-Right Vector2i(PERIMETER_BUFFER + 1, height - PERIMETER_BUFFER - 2), # Bottom-Left Vector2i(width - PERIMETER_BUFFER - 2, height - PERIMETER_BUFFER - 2), # Bottom-Right Vector2i(width / 2, height / 2) # Center ] # Validate spots (ensure they are walkable and have room) var valid_spots: Array[Vector2i] = [] for spot in spots: if _is_valid_3x3(spot, gridmap): valid_spots.append(spot) else: print("[StaticTektonManager] Warning: Spot %s is not a valid 3x3 walkable area!" % str(spot)) return valid_spots func _pick_spot_in_zone_biased(zone: Rect2i, gridmap: Node, type: int) -> Vector2i: # type: 0=TL, 1=TR, 2=BL, 3=BR, 4=Center # ideal target relative to map var target = Vector2i.ZERO match type: 0: target = Vector2i(PERIMETER_BUFFER + 1, PERIMETER_BUFFER + 1) 1: target = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, PERIMETER_BUFFER + 1) 2: target = Vector2i(PERIMETER_BUFFER + 1, gridmap.rows - PERIMETER_BUFFER - 2) 3: target = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, gridmap.rows - PERIMETER_BUFFER - 2) 4: target = Vector2i(gridmap.columns / 2, gridmap.rows / 2) # Clamp target to be inside valid area (respecting perimeter buffer + 3x3 margin) var min_x = max(PERIMETER_BUFFER + 1, zone.position.x + 1) var max_x = min(gridmap.columns - PERIMETER_BUFFER - 2, zone.position.x + zone.size.x - 2) var min_y = max(PERIMETER_BUFFER + 1, zone.position.y + 1) var max_y = min(gridmap.rows - PERIMETER_BUFFER - 2, zone.position.y + zone.size.y - 2) if min_x > max_x or min_y > max_y: return Vector2i(-1, -1) var clamped_target = Vector2i( clamp(target.x, min_x, max_x), clamp(target.y, min_y, max_y) ) # BFS to find nearest valid 3x3 spot to clamped_target var queue = [clamped_target] var visited = {clamped_target: true} # Limit search to avoid hanging var checks = 0 while not queue.is_empty() and checks < 200: var current = queue.pop_front() checks += 1 if _is_valid_3x3(current, gridmap): return current var neighbors = [ Vector2i(0, 1), Vector2i(0, -1), Vector2i(1, 0), Vector2i(-1, 0) ] for n in neighbors: var next = current + n if next.x >= min_x and next.x <= max_x and next.y >= min_y and next.y <= max_y: if not visited.has(next): visited[next] = true queue.append(next) return Vector2i(-1, -1) func _is_valid_3x3(center: Vector2i, gridmap: Node) -> bool: for dx in range(-1, 2): for dy in range(-1, 2): var check_pos = Vector3i(center.x + dx, 0, center.y + dy) if gridmap.get_cell_item(check_pos) == -1: return false return true func _pick_spot_in_zone(zone: Rect2i, gridmap: Node, zone_idx: int = -1) -> Vector2i: """ Find a valid 3x3 spot in the zone. The returned position is the CENTER of the 3x3 area. If zone_idx is a corner (0, 2, 6, 8), we snap to corner position with buffer. """ # CORNER SNAPPING: If this is a corner zone, force it to the corner with buffer # to ensure the 3x3 Stand stays away from edges (no 1-tile gaps). if zone_idx == 0: # Top-Left var center = Vector2i(PERIMETER_BUFFER + 1, PERIMETER_BUFFER + 1) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 2: # Top-Right var center = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, PERIMETER_BUFFER + 1) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 6: # Bottom-Left var center = Vector2i(PERIMETER_BUFFER + 1, gridmap.rows - PERIMETER_BUFFER - 2) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 8: # Bottom-Right var center = Vector2i(gridmap.columns - PERIMETER_BUFFER - 2, gridmap.rows - PERIMETER_BUFFER - 2) if _is_valid_3x3(center, gridmap): return center # Fallback/Random logic for non-corner zones or if preferred corner was invalid var attempts = 0 while attempts < 30: attempts += 1 # Ensure center respects perimeter buffer (1 tile from edge + 1 tile for 3x3 center = 2 tiles) var min_x = max(PERIMETER_BUFFER + 1, zone.position.x + 1) var max_x = min(gridmap.columns - PERIMETER_BUFFER - 2, zone.position.x + zone.size.x - 2) var min_y = max(PERIMETER_BUFFER + 1, zone.position.y + 1) var max_y = min(gridmap.rows - PERIMETER_BUFFER - 2, zone.position.y + zone.size.y - 2) if min_x > max_x or min_y > max_y: break # Zone too small var x = randi_range(min_x, max_x) var y = randi_range(min_y, max_y) var center = Vector2i(x, y) # Check 3x3 area validity var valid_area = true for dx in range(-1, 2): for dy in range(-1, 2): var check_pos = Vector3i(center.x + dx, 0, center.y + dy) if gridmap.get_cell_item(check_pos) == -1: valid_area = false # Void/Hole break # Optionally check for other obstacles? if valid_area: return center print("[StaticTektonManager] Failed to find 3x3 spot in zone %s" % zone) return Vector2i(-1, -1)