diff --git a/scenes/main.gd b/scenes/main.gd index 0499dec..36e7a30 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -12,6 +12,8 @@ var touch_controls var camera_context_manager var stop_n_go_manager var stop_n_go_winner_id: int = -1 # Track who finished first in Stop n Go mode +var portal_mode_winner_id: int = -1 +var is_match_ended: bool = false var obstacle_manager var portal_mode_manager @@ -1758,6 +1760,27 @@ func sync_game_end_stop_n_go(winner_id: int): # Trigger match end _on_match_ended() +@rpc("any_peer", "call_local", "reliable") +func sync_game_end_portal_mode(winner_id: int): + print("[TEKTON DOORS] Game ended! Winner: ", winner_id) + portal_mode_winner_id = winner_id + + var winner_name = "Player " + str(winner_id) + var player_node = get_node_or_null(str(winner_id)) + if player_node: + winner_name = player_node.display_name + + # Broadcast win + add_message_to_bar("MATCH COMPLETE", winner_name + " Wins with 8 Missions!", MessageType.GOAL) + + # Stop logic + if portal_mode_manager: + if portal_mode_manager.swap_timer: portal_mode_manager.swap_timer.stop() + if portal_mode_manager.tile_refresh_timer: portal_mode_manager.tile_refresh_timer.stop() + + # Trigger match end + _on_match_ended() + func _on_match_ended(): """Called when the global match timer ends - show game over screen.""" print("[Main] Match ended! Showing game over screen...") @@ -1846,13 +1869,19 @@ func _show_game_over_panel(): "score": goals_cycle_manager.get_player_score(p.name.to_int()) if goals_cycle_manager else 0 }) - # Custom Sort for Stop n Go: Winner always first + # Custom Sort for Stop n Go and Tekton Doors: Winner always first if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1: player_scores.sort_custom(func(a, b): if a.peer_id == stop_n_go_winner_id: return true if b.peer_id == stop_n_go_winner_id: return false return a.score > b.score ) + elif LobbyManager.game_mode == "Tekton Doors" and portal_mode_winner_id != -1: + player_scores.sort_custom(func(a, b): + if a.peer_id == portal_mode_winner_id: return true + if b.peer_id == portal_mode_winner_id: return false + return a.score > b.score + ) else: player_scores.sort_custom(func(a, b): return a.score > b.score) diff --git a/scenes/player.gd b/scenes/player.gd index 93a250c..5d2367e 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -1345,6 +1345,13 @@ func start_movement_along_path(path: Array, clear_visual: bool = true): if sng_manager.check_win_condition(name.to_int(), current_position): sng_main.rpc("sync_game_end_stop_n_go", name.to_int()) + # Tekton Doors Win Check + elif LobbyManager.game_mode == "Tekton Doors": + var main_node = get_tree().root.get_node_or_null("Main") + if main_node and main_node.portal_mode_manager: + if main_node.portal_mode_manager.check_win_condition(name.to_int(), current_position): + main_node.rpc("sync_game_end_portal_mode", name.to_int()) + # FORCE SNAP: Update target visual position to the perfect grid center # This ensures that when interpolation resumes (in _process), it pulls to the correct spot target_visual_position = grid_to_world(current_position) diff --git a/scenes/portal_door.tscn b/scenes/portal_door.tscn index 9f2a9b8..6223aa6 100644 --- a/scenes/portal_door.tscn +++ b/scenes/portal_door.tscn @@ -21,7 +21,7 @@ emission = Color(0.0, 0.4, 1.0, 1) emission_energy_multiplier = 2.0 [sub_resource type="BoxShape3D" id="BoxShape3D_trigger"] -size = Vector3(1.4, 2.1, 0.6) +size = Vector3(1.4, 2.1, 0.5) [sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_portal"] properties/0/path = NodePath(":target_room_id") diff --git a/scripts/managers/portal_mode_manager.gd b/scripts/managers/portal_mode_manager.gd index 718236d..165f2df 100644 --- a/scripts/managers/portal_mode_manager.gd +++ b/scripts/managers/portal_mode_manager.gd @@ -17,10 +17,14 @@ 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 +var missions_required: int = 8 var arena_setup_done: bool = false var player_portal_cooldowns: Dictionary = {} +var hud_layer: CanvasLayer +var mission_label: Label +var _has_notified_mission_complete: bool = false + func initialize(p_main: Node, p_gridmap: Node): main = p_main gridmap = p_gridmap @@ -52,6 +56,9 @@ func initialize(p_main: Node, p_gridmap: Node): var gcm = main.get_node_or_null("GoalsCycleManager") if gcm: gcm.global_timer_updated.connect(_on_global_timer_updated) + gcm.goal_count_updated.connect(_on_goal_count_updated) + + _setup_hud() func _on_global_timer_updated(time_remaining: float): if not multiplayer.is_server(): return @@ -78,6 +85,14 @@ func start_game_mode(): # Initial Tile Spawn _refresh_tiles() + + # Show HUD + _activate_hud() + +func _activate_hud(): + if hud_layer: + hud_layer.visible = true + _update_hud_visuals() func setup_arena_locally(): """Sets up GridMap size and walls. Called on host and clients.""" @@ -119,6 +134,69 @@ func get_spawn_points() -> Array[Vector2i]: Vector2i(10, 10) # Room 3 ] +func _setup_hud(): + hud_layer = CanvasLayer.new() + hud_layer.layer = 5 + hud_layer.visible = false + add_child(hud_layer) + + var bottom_container = CenterContainer.new() + bottom_container.set_anchors_preset(Control.PRESET_CENTER_BOTTOM) + bottom_container.grow_horizontal = Control.GROW_DIRECTION_BOTH + bottom_container.grow_vertical = Control.GROW_DIRECTION_BEGIN + bottom_container.offset_bottom = -50 + hud_layer.add_child(bottom_container) + + mission_label = Label.new() + mission_label.text = "GOALS (0/8)" + mission_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + + var custom_font = load("res://assets/fonts/Nougat-ExtraBlack.ttf") + if custom_font: mission_label.add_theme_font_override("font", custom_font) + + mission_label.add_theme_font_size_override("font_size", 28) + mission_label.add_theme_color_override("font_outline_color", Color.BLACK) + mission_label.add_theme_constant_override("outline_size", 8) + bottom_container.add_child(mission_label) + + # Initial update + _update_hud_visuals() + +func _update_hud_visuals(): + if not mission_label: return + + var my_id = multiplayer.get_unique_id() + var gcm = main.get_node_or_null("GoalsCycleManager") + var completed_count = gcm.player_goal_counts.get(my_id, 0) if gcm else 0 + + mission_label.text = "GOALS (%d/%d)" % [completed_count, missions_required] + + if completed_count >= missions_required: + mission_label.text = "ALL GOALS COMPLETE!\nFIND THE FINISH ROOM!" + mission_label.add_theme_color_override("font_color", Color.GOLD) + + if not _has_notified_mission_complete: + _has_notified_mission_complete = true + var player_node = main.get_node_or_null(str(my_id)) + if player_node: + NotificationManager.send_message(player_node, "ALL GOALS COMPLETE!", NotificationManager.MessageType.GOAL) + else: + mission_label.add_theme_color_override("font_color", Color.WHITE) + _has_notified_mission_complete = false + +func is_mission_complete(peer_id: int) -> bool: + var gcm = main.get_node_or_null("GoalsCycleManager") + if not gcm: return false + return gcm.player_goal_counts.get(peer_id, 0) >= missions_required + +func check_win_condition(player_id: int, pos: Vector2i) -> bool: + # 1. Check if on finish tile + var tile = gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y)) + if tile != 3: return false + + # 2. Check missions + return is_mission_complete(player_id) + func _setup_room_partitions(): for i in range(GRID_SIZE): # Vertical wall (middle columns) @@ -308,28 +386,47 @@ func _on_global_goal_count_updated(_peer_id: int, _count: int): # Mission requirement removed in favor of time-based finish reveal pass +func _on_goal_count_updated(peer_id: int, _count: int): + if peer_id == multiplayer.get_unique_id(): + _update_hud_visuals() + func _spawn_finish_room(): print("[PortalModeManager] Time is running out! Revealing Finish Room...") finish_spawned = true - # Choose a random center room spawn point to place the finish tile - var room_centers = get_spawn_points() - var center = room_centers[randi() % room_centers.size()] + # Choose a random room quadrant index (0 to 3) + var room_idx = randi() % 4 - # Place finish tile (ID 3) on Floor 1 (Y=1) - # We use current_position as spawn point centers are guaranteed to be walkable - # IMPORTANT: Change the floor tile (Floor 0) to item ID 3 (Goal/Finish) - main.rpc("sync_grid_item", center.x, 0, center.y, 3) + # Determine center for the selected room quadrant (7x7 rooms) + var x_center = 3 if (room_idx == 0 or room_idx == 2) else 10 + var z_center = 3 if (room_idx == 0 or room_idx == 1) else 10 - # Clear any item on Floor 1 above the finish tile - main.rpc("sync_grid_item", center.x, 1, center.y, -1) + # Determine 3x3 bounds around the center + var x_start = x_center - 1 + var x_end = x_center + 2 # exclusive for range() + var z_start = z_center - 1 + var z_end = z_center + 2 # exclusive for range() + + print("[PortalModeManager] Converting 3x3 area in Room %d (X:%d-%d, Z:%d-%d) to Finish Tiles" % [room_idx, x_start, x_end-1, z_start, z_end-1]) + + # Iterate through the 3x3 area + for x in range(x_start, x_end): + for z in range(z_start, z_end): + # Only convert walkable floor tiles (Item ID 0) on Floor 0 + var floor_0_item = gridmap.get_cell_item(Vector3i(x, 0, z)) + if floor_0_item == 0: + # Change Floor 0 tile to Finish Tile (ID 3) + main.rpc("sync_grid_item", x, 0, z, 3) + + # Clear any item on Floor 1 above this tile + main.rpc("sync_grid_item", x, 1, z, -1) # Visual update for server if gridmap.has_method("update_grid_data"): gridmap.update_grid_data() main.rpc("display_message", "[ALARM] THE FINISH ROOM HAS APPEARED!") - main.rpc("broadcast_message", "SYSTEM", "The Finish Tile is in Room %d!" % _get_room_index(center), 4) # 4 = MessageType.WARNING + main.rpc("broadcast_message", "SYSTEM", "A 3x3 Finish Zone has appeared in Room %d!" % room_idx, 4) # 4 = MessageType.WARNING func _get_room_index(pos: Vector2i) -> int: if pos.x < 7 and pos.y < 7: return 0 @@ -393,7 +490,7 @@ func handle_portal_interaction(player, door): var current_time = Time.get_ticks_msec() if player_portal_cooldowns.has(player.name): - if current_time - player_portal_cooldowns[player.name] < 3000: + if current_time - player_portal_cooldowns[player.name] < 1000: return player_portal_cooldowns[player.name] = current_time diff --git a/scripts/portal_door.gd b/scripts/portal_door.gd index b7528f6..e7146f7 100644 --- a/scripts/portal_door.gd +++ b/scripts/portal_door.gd @@ -35,7 +35,7 @@ func _on_body_entered(body: Node3D): if body.is_in_group("Players") or body.get("is_bot"): var current_time = Time.get_ticks_msec() if body.has_meta("last_portal_time"): - if current_time - body.get_meta("last_portal_time") < 3000: + if current_time - body.get_meta("last_portal_time") < 1000: return body.set_meta("last_portal_time", current_time)