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/tekton.tscn" const STATIC_CONTROLLER_SCRIPT = "res://scripts/static_tekton_controller.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. Corners: (1,1), (W-2,1), (1,H-2), (W-2,H-2) 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)...") # Fixed Spots var spots: Array[Vector2i] = [ Vector2i(1, 1), # Top-Left Vector2i(width - 2, 1), # Top-Right Vector2i(1, height - 2), # Bottom-Left Vector2i(width - 2, height - 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(0, 0) 1: target = Vector2i(gridmap.columns, 0) 2: target = Vector2i(0, gridmap.rows) 3: target = Vector2i(gridmap.columns, gridmap.rows) 4: target = Vector2i(gridmap.columns / 2, gridmap.rows / 2) # Clamp target to be inside valid area (taking 3x3 margin into account) # Center of 3x3 must be at least 1 tile from edge var min_x = max(1, zone.position.x + 1) var max_x = min(gridmap.columns - 2, zone.position.x + zone.size.x - 2) var min_y = max(1, zone.position.y + 1) var max_y = min(gridmap.rows - 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 the absolute map corner. """ # CORNER SNAPPING: If this is a corner zone, force it to the extreme corner # to ensure the 3x3 Stand fills the corner completely (no 1-tile gaps). if zone_idx == 0: # Top-Left var center = Vector2i(1, 1) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 2: # Top-Right var center = Vector2i(gridmap.columns - 2, 1) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 6: # Bottom-Left var center = Vector2i(1, gridmap.rows - 2) if _is_valid_3x3(center, gridmap): return center elif zone_idx == 8: # Bottom-Right var center = Vector2i(gridmap.columns - 2, gridmap.rows - 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 is at least 1 tile away from edges of the map to fit 3x3 # zone.position might be 0,0. var min_x = max(1, zone.position.x + 1) var max_x = min(gridmap.columns - 2, zone.position.x + zone.size.x - 2) var min_y = max(1, zone.position.y + 1) var max_y = min(gridmap.rows - 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)