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 random spawn positions for static tektons. Returns an Array of Vector2i positions. """ if count <= 0 or not gridmap: return [] print("[StaticTektonManager] Calculating static tekton positions (Fixed 5-Zone + Center)...") # 1. Define Zones (3x3 Grid) var width = gridmap.columns var depth = gridmap.rows var zone_w = width / 3 var zone_d = depth / 3 var zones = [] for z in range(3): for x in range(3): zones.append(Rect2i(x * zone_w, z * zone_d, zone_w, zone_d)) # 2. Select Fixed Targets: TL(0), TR(2), Center(4), BL(6), BR(8) # This ensures they are never adjacent (always separated by a middle zone) var target_indices = [0, 2, 6, 8, 4] target_indices.shuffle() # Randomize which of the 5 zones we pick # If count < 5, we prioritize corners then center # If count > 5, we only return 5 because that's the max safe non-adjacent set in 3x3 var spawn_points: Array[Vector2i] = [] for zone_idx in target_indices: if spawn_points.size() >= count: break var zone = zones[zone_idx] # Determine Position Type for Bias # 0:TL, 1:TR, 2:BL, 3:BR, 4:Center # We pass zone_idx to _pick_spot_in_zone to snap corners var pos = _pick_spot_in_zone(zone, gridmap, zone_idx) if pos != Vector2i(-1, -1): spawn_points.append(pos) return spawn_points 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)