extends Node # PortalModeManager - Handles "Tekton Doors" mode logic # Manages room partitioning, portal connections, and mode-specific timers. var main: Node var gridmap: Node # Room layout config const ROOM_COUNT = 4 const GRID_SIZE = 14 const ROOM_DIM = 7 # State var connections = {} # room_id -> {door_id -> {target_room, target_door}} var doors = [] # List of PortalDoor nodes var swap_timer: Timer var tile_refresh_timer: Timer var finish_spawned: bool = false var missions_required: int = 3 func initialize(p_main: Node, p_gridmap: Node): main = p_main gridmap = p_gridmap print("[PortalModeManager] Initialized") # Connection Swap Timer (15s) swap_timer = Timer.new() swap_timer.name = "PortalSwapTimer" swap_timer.wait_time = 15.0 swap_timer.timeout.connect(_on_swap_timer_timeout) add_child(swap_timer) # Tile Refresh Timer (25s) tile_refresh_timer = Timer.new() tile_refresh_timer.name = "TileRefreshTimer" tile_refresh_timer.wait_time = 25.0 tile_refresh_timer.timeout.connect(_on_tile_refresh_timer_timeout) add_child(tile_refresh_timer) # Connect to mission tracking var gcm = main.get_node_or_null("GoalsCycleManager") if gcm: gcm.goal_count_updated.connect(_on_goal_count_updated) func start_game_mode(): if not multiplayer.is_server(): return print("[PortalModeManager] Starting Portal Game Mode...") # 1. Setup Arena Size _setup_arena_size() # 2. Setup Room Partitions (visual/physical walls between rooms) _setup_room_partitions() # 3. Spawn Portal Doors _spawn_portal_doors() # 4. Initialize Connections _randomize_connections() # 5. Start Timers swap_timer.start() tile_refresh_timer.start() # 6. Initial Tile Spawn _refresh_tiles() func _setup_arena_size(): if not gridmap: return gridmap.columns = GRID_SIZE gridmap.rows = GRID_SIZE gridmap.clear() # Fill floor for x in range(GRID_SIZE): for z in range(GRID_SIZE): gridmap.set_cell_item(Vector3i(x, 0, z), 0) # Normal floor func get_spawn_points() -> Array[Vector2i]: # One point per quadrant return [ Vector2i(3, 3), # Room 0 Vector2i(10, 3), # Room 1 Vector2i(3, 10), # Room 2 Vector2i(10, 10) # Room 3 ] func _setup_room_partitions(): for i in range(GRID_SIZE): # Vertical wall (middle columns) gridmap.set_cell_item(Vector3i(6, 0, i), 4) # Wall item gridmap.set_cell_item(Vector3i(7, 0, i), 4) # Horizontal wall (middle rows) gridmap.set_cell_item(Vector3i(i, 0, 6), 4) gridmap.set_cell_item(Vector3i(i, 0, 7), 4) func _spawn_portal_doors(): var portal_scene = load("res://scenes/portal_door.tscn") var stands_container = main.get_node_or_null("Stands") if not stands_container: return var door_configs = [ # Room 0 {"room": 0, "pos": Vector2i(6, 2), "rot": PI/2, "offset": Vector2i(-1, 0)}, # East {"room": 0, "pos": Vector2i(2, 6), "rot": 0, "offset": Vector2i(0, -1)}, # South # Room 1 {"room": 1, "pos": Vector2i(7, 2), "rot": PI/2, "offset": Vector2i(1, 0)}, # West {"room": 1, "pos": Vector2i(11, 6), "rot": 0, "offset": Vector2i(0, -1)}, # South # Room 2 {"room": 2, "pos": Vector2i(2, 7), "rot": 0, "offset": Vector2i(0, 1)}, # North {"room": 2, "pos": Vector2i(6, 11), "rot": PI/2, "offset": Vector2i(-1, 0)},# East # Room 3 {"room": 3, "pos": Vector2i(11, 7), "rot": 0, "offset": Vector2i(0, 1)}, # North {"room": 3, "pos": Vector2i(7, 11), "rot": PI/2, "offset": Vector2i(1, 0)} # West ] for i in range(door_configs.size()): var cfg = door_configs[i] var door = portal_scene.instantiate() door.name = "Portal_%d" % i door.room_id = cfg["room"] door.door_id = i door.set_meta("spawn_offset", cfg["offset"]) # Store offset for teleport # Position var world_pos = gridmap.map_to_local(Vector3i(cfg["pos"].x, 0, cfg["pos"].y)) door.transform.origin = world_pos door.rotation.y = cfg["rot"] stands_container.add_child(door, true) doors.append(door) door.player_entered_portal.connect(handle_portal_interaction) gridmap.set_cell_item(Vector3i(cfg["pos"].x, 0, cfg["pos"].y), 0) # Normal floor const PORTAL_COLORS = [ Color(0, 1, 1), # Cyan Color(1, 0, 1), # Magenta Color(1, 1, 0), # Yellow Color(0, 1, 0) # Green ] func _randomize_connections(): if not multiplayer.is_server(): return print("[PortalModeManager] Swapping portal connections...") connections.clear() var door_indices = [] for i in range(doors.size()): door_indices.append(i) # Shuffle and Validate: ensure no pairs are in the same room var valid_pairing = false var attempts = 0 while not valid_pairing and attempts < 100: attempts += 1 door_indices.shuffle() valid_pairing = true for i in range(0, door_indices.size(), 2): var a = door_indices[i] var b = door_indices[i+1] if doors[a].room_id == doors[b].room_id: valid_pairing = false break # Pair them up and assign colors for i in range(0, door_indices.size(), 2): var a = door_indices[i] var b = door_indices[i+1] connections[a] = b connections[b] = a var color = PORTAL_COLORS[i/2 % PORTAL_COLORS.size()] doors[a].target_door_id = b doors[a].portal_color = color doors[b].target_door_id = a doors[b].portal_color = color main.rpc("display_message", "PORTALS SWITCHED!") func _on_goal_count_updated(peer_id: int, count: int): if not multiplayer.is_server(): return if count >= missions_required and not finish_spawned: _spawn_finish_room() func _spawn_finish_room(): print("[PortalModeManager] Missions complete! Spawning Finish Room...") finish_spawned = true # Choose a random center room tile (X=3, Z=10 or similar in any room) var room_centers = get_spawn_points() var center = room_centers[randi() % room_centers.size()] # Place finish tile (ID 3) gridmap.set_cell_item(Vector3i(center.x, 0, center.y), 3) main.get_node("EnhancedGridMap").update_grid_data() main.rpc("display_message", "FINISH ROOM REVEALED!") func _on_swap_timer_timeout(): _randomize_connections() func _on_tile_refresh_timer_timeout(): _refresh_tiles() main.rpc("display_message", "TILES REPLENISHED!") func _refresh_tiles(): # Simple tile fill for each quadrant for x in range(GRID_SIZE): for z in range(GRID_SIZE): # Skip walls if gridmap.get_cell_item(Vector3i(x, 0, z)) == 4: continue # Low chance to spawn a tile if empty if randf() < 0.1: var weights = ScarcityModel.get_tile_weights() var tile_id = _pick_weighted_tile(weights) # 1. Update GridMap gridmap.set_cell_item(Vector3i(x, 0, z), tile_id) func _pick_weighted_tile(weights: Dictionary) -> int: var total_weight = 0 for w in weights.values(): total_weight += w var r = randi() % total_weight var cumulative = 0 for tile in weights: cumulative += weights[tile] if r < cumulative: return tile return 7 # Default Heart func handle_portal_interaction(player, door): if not multiplayer.is_server(): return var source_id = door.door_id if not connections.has(source_id): return var target_id = connections[source_id] var target_door = doors[target_id] # Use stored offset to avoid infinite loop (spawn inside the target room) var offset = target_door.get_meta("spawn_offset") if target_door.has_meta("spawn_offset") else Vector2i(0,0) # Convert world pos back to grid var target_world = target_door.global_position var target_grid_3d = gridmap.local_to_map(target_world) var target_grid = Vector2i(target_grid_3d.x, target_grid_3d.z) + offset print("[Portal] Teleporting %s to Room %d, Pos %s (via Door %d)" % [player.name, target_door.room_id, target_grid, target_id]) # Snap player if player.has_method("set_spawn_position"): player.rpc("set_spawn_position", target_grid)