# ------------------------------------------------------------------------------------- # Tekton Dash - Multiplayer Board Game - 2024 # ------------------------------------------------------------------------------------- extends Node3D # Manager references var ui_manager var obstacle_manager # Minimal local state var match_id_input: LineEdit var _connection_check_timer: float = 0.0 func _ready(): # Initialize scene managers _init_managers() # Connect to multiplayer signals multiplayer.peer_connected.connect(_on_peer_connected) multiplayer.peer_disconnected.connect(_on_peer_disconnected) # Connect to Nakama signals NakamaManager.match_joined.connect(_on_match_joined) # Setup UI ui_manager.setup_action_buttons(_set_action_state_callback) ui_manager.setup_playerboard_ui() _setup_obstacle_ui() _setup_match_input() func _init_managers(): # Create and attach scene managers ui_manager = load("res://scripts/managers/ui_manager.gd").new() ui_manager.name = "UIManager" add_child(ui_manager) ui_manager.initialize(self) obstacle_manager = load("res://scripts/managers/obstacle_manager.gd").new() obstacle_manager.name = "ObstacleManager" add_child(obstacle_manager) obstacle_manager.initialize($EnhancedGridMap) func _setup_match_input(): match_id_input = LineEdit.new() match_id_input.placeholder_text = "Enter Match ID to Join" match_id_input.custom_minimum_size = Vector2(200, 30) match_id_input.position = Vector2(10, 50) $Menu.add_child(match_id_input) func _setup_obstacle_ui(): var obstacle_button = Button.new() obstacle_button.text = "Place Obstacle" obstacle_button.pressed.connect(func(): _set_action_state(ui_manager.ActionState.PLACING_OBSTACLE)) $ActionMenu/ActionButtonContainer.add_child(obstacle_button) var orientation_button = Button.new() orientation_button.text = "Direction: North" orientation_button.pressed.connect(func(): orientation_button.text = obstacle_manager.cycle_obstacle_orientation() ) $ActionMenu/ActionButtonContainer.add_child(orientation_button) var type_button = Button.new() type_button.text = "Type: 1" type_button.pressed.connect(func(): type_button.text = obstacle_manager.cycle_obstacle_type() ) $ActionMenu/ActionButtonContainer.add_child(type_button) func _process(delta): if multiplayer.is_server() and GameStateManager.is_game_started(): if TurnManager.turn_based_mode: rpc("sync_turn_index", TurnManager.current_turn_index) update_all_players_goals() _connection_check_timer += delta if _connection_check_timer >= 5.0: _connection_check_timer = 0.0 verify_all_connections() # ============================================================================= # Network Button Handlers # ============================================================================= func _on_host_pressed(): $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Server (Creating Match...)" $Menu.visible = false var success = await NakamaManager.connect_to_nakama_async() if not success: $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Connection Failed" $Menu.visible = true return NakamaManager.host_game() func _on_join_pressed(): var match_id = match_id_input.text.strip_edges() if match_id.is_empty(): print("Please enter a Match ID") return $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Client (Joining...)" $Menu.visible = false var success = await NakamaManager.connect_to_nakama_async() if not success: $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Connection Failed" $Menu.visible = true return NakamaManager.join_game(match_id) func _on_match_joined(match_id: String): $NetworkPanel/NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id()) if multiplayer.is_server(): $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Server (Match: %s)" % match_id _setup_host_game() else: $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Client" _setup_client_game() # ============================================================================= # Game Setup # ============================================================================= func _setup_host_game(): # Generate goals GoalManager.generate_preset_goals(GameStateManager.max_players) # Add host player var player_id = 1 var player_character = PlayerManager.add_player_character(player_id) add_child(player_character) player_character.add_to_group("Players", true) GameStateManager.add_player(player_id) GameStateManager.local_player_character = player_character ui_manager.set_local_player(player_character) # Set host goals player_character.goals = GoalManager.get_goals_for_player(0) rpc("sync_player_goals", player_id, player_character.goals) rpc("sync_preset_goals", GoalManager.preset_goals) # Add bots if GameStateManager.enable_bots: for i in range(2, GameStateManager.max_players + 1): _add_bot(i) _start_game() func _setup_client_game(): await get_tree().create_timer(2.0).timeout rpc_id(1, "request_full_player_sync", multiplayer.get_unique_id()) func _start_game(): if multiplayer.is_server(): GameStateManager.start_game() rpc("sync_game_start", GameStateManager.players, TurnManager.turn_based_mode) if TurnManager.turn_based_mode: TurnManager.reset_turn() var next_player = TurnManager.next_turn(GameStateManager.players) rpc("set_current_turn", next_player) # ============================================================================= # Player Management # ============================================================================= func _add_bot(bot_id: int): rpc("create_bot", bot_id) @rpc("call_local") func create_bot(bot_id: int): if not GameStateManager.enable_bots: return if has_node(str(bot_id)): return var bot_character = PlayerManager.create_bot(bot_id) call_deferred("add_child", bot_character) bot_character.add_to_group("Players", true) bot_character.add_to_group("Bots", true) if multiplayer.is_server(): GameStateManager.add_bot(bot_id) var goal_index = bot_id - 1 if goal_index < GoalManager.preset_goals.size(): bot_character.goals = GoalManager.preset_goals[goal_index].duplicate() rpc("sync_player_goals", bot_id, bot_character.goals) await get_tree().create_timer(0.1).timeout bot_character.rpc("sync_bot_status", true) @rpc("any_peer", "call_local") func add_player_character(peer_id: int): if has_node(str(peer_id)): return var player_character = PlayerManager.add_player_character(peer_id) add_child(player_character) player_character.add_to_group("Players", true) GameStateManager.add_player(peer_id) if peer_id == multiplayer.get_unique_id(): GameStateManager.local_player_character = player_character ui_manager.set_local_player(player_character) ui_manager.update_button_states() ui_manager.update_playerboard_ui() func _on_peer_connected(new_peer_id: int): if multiplayer.is_server(): await get_tree().create_timer(1.5).timeout add_player_character(new_peer_id) rpc("add_newly_connected_player_character", new_peer_id) @rpc func add_newly_connected_player_character(new_peer_id: int): add_player_character(new_peer_id) func _on_peer_disconnected(peer_id: int): if multiplayer.is_server(): GameStateManager.remove_player(peer_id) if GameStateManager.enable_bots: var next_id = PlayerManager.get_next_available_bot_id(GameStateManager.max_players, GameStateManager.players) if next_id != -1: _add_bot(next_id) # ============================================================================= # Turn Management (RPC Handlers) # ============================================================================= @rpc("reliable") func sync_turn_index(index: int): TurnManager.current_turn_index = index @rpc("any_peer", "call_local") func set_current_turn(player_id: int): if not TurnManager.turn_based_mode: return for player in get_tree().get_nodes_in_group("Players"): var is_current_turn = player.name == str(player_id) player.is_my_turn = is_current_turn if is_current_turn and not (player.is_bot or player.is_in_group("Bots")): player.action_points = 2 player.has_moved_this_turn = false player.has_performed_action = false player.start_turn() player.clear_highlights() player.clear_playerboard_highlights() else: player.is_my_turn = false @rpc("call_local") func sync_game_start(player_list: Array, is_turn_based: bool): GameStateManager.players = player_list TurnManager.turn_based_mode = is_turn_based GameStateManager.start_game() # ============================================================================= # UI / Action State Management # ============================================================================= func _set_action_state_callback(new_state): _set_action_state(new_state) func _set_action_state(new_state): var local_player = GameStateManager.local_player_character if not local_player or not local_player.is_multiplayer_authority(): return if local_player.is_bot or local_player.is_in_group("Bots"): ui_manager.current_action_state = new_state return if ui_manager.current_action_state == new_state or local_player.action_points <= 0: return ui_manager.current_action_state = new_state local_player.clear_highlights() local_player.clear_playerboard_highlights() match new_state: ui_manager.ActionState.MOVING: local_player.highlight_movement_range() ui_manager.ActionState.GRABBING: local_player.highlight_adjacent_cells() ui_manager.ActionState.PUTTING: local_player.highlight_occupied_playerboard_slots() ui_manager.ActionState.RANDOMIZING: local_player.highlight_random_valid_cells() ui_manager.ActionState.ARRANGING: _show_arrangement_ui() local_player.highlight_occupied_playerboard_slots() ui_manager.ActionState.PLACING_OBSTACLE: local_player.highlight_valid_obstacle_cells() func _show_arrangement_ui(): if ui_manager.playerboard_ui: ui_manager.playerboard_ui.visible = true ui_manager.update_playerboard_ui() func _on_playerboard_slot_clicked(event, slot_index): if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: var local_player = GameStateManager.local_player_character if not local_player: return match ui_manager.current_action_state: ui_manager.ActionState.ARRANGING: local_player.arrange_playerboard_item(slot_index) # ============================================================================= # Obstacle Management # ============================================================================= func place_obstacle(grid_position: Vector2i) -> bool: var local_player = GameStateManager.local_player_character var success = obstacle_manager.place_obstacle(grid_position, local_player) if success: local_player.clear_highlights() _set_action_state(ui_manager.ActionState.NONE) if is_multiplayer_authority(): rpc("sync_place_obstacle", grid_position.x, grid_position.y, 3, obstacle_manager.current_obstacle_item, obstacle_manager.current_obstacle_orientation) return success @rpc("any_peer", "call_local") func sync_place_obstacle(x: int, y: int, floor_index: int, item_index: int, orientation: int): $EnhancedGridMap.place_obstacle(Vector3i(x, floor_index, y), item_index, orientation) # ============================================================================= # Goal & Playerboard Sync # ============================================================================= @rpc("reliable") func sync_preset_goals(goals_list: Array): GoalManager.preset_goals = goals_list @rpc("any_peer", "call_local") func sync_player_goals(player_id: int, goals: Array): var player = get_node_or_null(str(player_id)) if player: player.goals = goals.duplicate() if multiplayer.is_server(): update_all_players_goals() @rpc("any_peer", "call_local") func sync_playerboard(player_id: int, new_playerboard: Array): if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character: ui_manager.update_playerboard_ui() update_all_players_boards() # ============================================================================= # UI Update Functions # ============================================================================= func update_all_players_goals(): if not GameStateManager.is_game_started(): return var all_players = get_tree().get_nodes_in_group("Players") all_players.sort_custom(func(a, b): var a_id = int(String(a.name).get_slice("@", 0)) var b_id = int(String(b.name).get_slice("@", 0)) return a_id < b_id ) for i in range($AllPlayerGoals.get_child_count()): $AllPlayerGoals.get_child(i).visible = false var max_panels = $AllPlayerGoals.get_child_count() for i in range(min(all_players.size(), max_panels)): var player = all_players[i] if player and player.goals.size() > 0: $AllPlayerGoals.get_child(i).visible = true _update_player_goals_ui(i, player.goals) func _update_player_goals_ui(player_idx: int, goals: Array): if player_idx < 0 or player_idx >= $AllPlayerGoals.get_child_count(): return var panel = $AllPlayerGoals.get_child(player_idx) if not panel.has_node("MarginContainer/Playergoals"): return var goals_grid = panel.get_node("MarginContainer/Playergoals") for slot_idx in range(9): if slot_idx >= goals_grid.get_child_count(): break var slot = goals_grid.get_child(slot_idx) var goal_value = goals[slot_idx] if slot_idx < goals.size() else -1 for tile_name in ["TileHeart", "TileDiamond", "TileStar", "TileCoin"]: if slot.has_node(tile_name): slot.get_node(tile_name).hide() match goal_value: 7: if slot.has_node("TileHeart"): slot.get_node("TileHeart").show() 8: if slot.has_node("TileDiamond"): slot.get_node("TileDiamond").show() 9: if slot.has_node("TileStar"): slot.get_node("TileStar").show() 10: if slot.has_node("TileCoin"): slot.get_node("TileCoin").show() func update_all_players_boards(): if not GameStateManager.is_game_started(): return var all_players = get_tree().get_nodes_in_group("Players") var all_player_boards = $AllPlayerBoards # Update boards (simplified version - full implementation would mirror original) pass # ============================================================================= # Connection Verification # ============================================================================= func verify_all_connections(): if multiplayer.is_server(): for peer_id in GameStateManager.players: if peer_id != 1: rpc_id(peer_id, "connection_verify", GameStateManager.players) @rpc func connection_verify(expected_players: Array): for peer_id in expected_players: if peer_id != multiplayer.get_unique_id() and not has_node(str(peer_id)): rpc_id(1, "request_specific_player_data", peer_id) @rpc("any_peer") func request_specific_player_data(requested_peer_id: int): if multiplayer.is_server(): var player = get_node_or_null(str(requested_peer_id)) if player: var player_data = { "peer_id": requested_peer_id, "position": player.current_position, "goals": player.goals, "playerboard": player.playerboard, "is_bot": player.is_bot || player.is_in_group("Bots") } rpc_id(multiplayer.get_remote_sender_id(), "create_specific_player", player_data) @rpc("any_peer") func request_full_player_sync(requesting_peer_id: int): if multiplayer.is_server(): for peer_id in GameStateManager.players: var player = get_node_or_null(str(peer_id)) if player: var player_data = { "peer_id": peer_id, "position": player.current_position, "goals": player.goals, "playerboard": player.playerboard, "is_bot": player.is_bot || player.is_in_group("Bots") } rpc_id(requesting_peer_id, "create_specific_player", player_data) await get_tree().create_timer(0.1).timeout @rpc("reliable") func create_specific_player(data: Dictionary): var peer_id = data["peer_id"] if has_node(str(peer_id)): return var player_character = PlayerManager.add_player_character(peer_id) player_character.current_position = data["position"] add_child(player_character) player_character.add_to_group("Players", true) if data["is_bot"]: player_character.add_to_group("Bots", true) player_character.is_bot = true player_character.rpc("sync_bot_status", true) player_character.goals = data["goals"].duplicate() player_character.playerboard = data["playerboard"].duplicate() player_character.global_position = Vector3( data["position"].x * 2 + 1, 1.0, data["position"].y * 2 + 1 ) player_character.rpc("sync_position", data["position"]) # ============================================================================= # Grid Item Randomization # ============================================================================= func randomize_item_at_position(grid_position: Vector2i): if not multiplayer.is_server(): rpc_id(1, "request_randomize_item", grid_position) return var enhanced_gridmap = $EnhancedGridMap if enhanced_gridmap: var cell = Vector3i(grid_position.x, 1, grid_position.y) var current_item = enhanced_gridmap.get_cell_item(cell) if current_item != -1: var rng = RandomNumberGenerator.new() rng.randomize() var new_item = rng.randi_range(7, 10) while new_item == current_item: new_item = rng.randi_range(7, 10) sync_grid_item(cell.x, cell.y, cell.z, new_item) rpc("sync_grid_item", cell.x, cell.y, cell.z, new_item) @rpc("any_peer") func request_randomize_item(grid_position: Vector2i): if multiplayer.is_server(): randomize_item_at_position(grid_position) @rpc("any_peer", "call_local", "reliable") func sync_grid_item(x: int, y: int, z: int, item: int): var enhanced_gridmap = $EnhancedGridMap if enhanced_gridmap: enhanced_gridmap.set_cell_item(Vector3i(x, y, z), item)