From 0e4d69f7b9d82c633e5192ff1c2b88b64b622cb8 Mon Sep 17 00:00:00 2001 From: Yogi Wiguna Date: Fri, 20 Feb 2026 17:54:58 +0800 Subject: [PATCH] feat: Introduce `PlayerMovementManager` to manage player movement, rotation, attack mode push mechanics, and grid-based collision detection. --- addons/enhanced_gridmap/enhanced_gridmap.gd | 7 ++ scenes/main.gd | 114 +++++++++++++++++--- scenes/player.gd | 19 ++-- scripts/bot_controller.gd | 23 +++- scripts/managers/game_state_manager.gd | 2 +- scripts/managers/player_movement_manager.gd | 38 ++++--- scripts/managers/stop_n_go_manager.gd | 49 +++++---- scripts/managers/touch_controls.gd | 2 +- 8 files changed, 193 insertions(+), 61 deletions(-) diff --git a/addons/enhanced_gridmap/enhanced_gridmap.gd b/addons/enhanced_gridmap/enhanced_gridmap.gd index 544ab6c..26d8e98 100644 --- a/addons/enhanced_gridmap/enhanced_gridmap.gd +++ b/addons/enhanced_gridmap/enhanced_gridmap.gd @@ -555,6 +555,13 @@ func find_path(start: Vector2, end: Vector2, floor_index: int = 0, clear_path_vi var start_point = start.y * columns + start.x var end_point = end.y * columns + end.x + + if not is_position_valid(Vector2i(int(start.x), int(start.y))) or not is_position_valid(Vector2i(int(end.x), int(end.y))): + return [] + + if not astar.has_point(start_point) or not astar.has_point(end_point): + return [] + path = astar.get_point_path(start_point, end_point) if visualize: diff --git a/scenes/main.gd b/scenes/main.gd index cf62fa3..6999c7c 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -409,10 +409,11 @@ func _setup_host_game(): # Ensure Bots are in the tree before assigning positions await get_tree().process_frame - # NOW assign random spawn positions for EVERYONE (Host, Client, Bots) - # This ensures we respect the static tekton reserved zones for all characters - _assign_random_spawn_positions() + # INITIALIZE ARENA SIZE for Stop n Go BEFORE spawning players, to prevent out-of-bounds + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_manager: + stop_n_go_manager._setup_arena() + # Arena is set up, wait for __start_game to assign positions where Socket is open _start_game() func _spawn_lobby_client_sync(peer_id: int): @@ -442,6 +443,10 @@ func _setup_client_game(): var my_id = multiplayer.get_unique_id() print("Client setup - my peer ID: ", my_id) + # INITIALIZE ARENA SIZE for Stop n Go locally to prevent out-of-bounds before sync arrives + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_manager: + stop_n_go_manager._apply_arena_setup() + # Pre-spawn ALL players known from LobbyManager (including Host ID 1) # This ensures nodes exist to receive RPCs (like 'set_spawn_position') that might arrive before full sync var lobby_players = LobbyManager.get_players() @@ -525,15 +530,29 @@ func _auto_start_from_lobby(): func _start_game(): if multiplayer.is_server(): + # Wait for Nakama websocket to actually be open, up to 5 seconds + var nakama = get_node_or_null("/root/NakamaManager") + if nakama and nakama.has_method("is_connected_to_nakama"): + var wait_time = 0.0 + while not nakama.is_connected_to_nakama() and wait_time < 5.0: + await get_tree().create_timer(0.2).timeout + wait_time += 0.2 + # Allow socket/peer to stabilize before blasting RPCs await get_tree().create_timer(2.0).timeout + # NOW assign random spawn positions for EVERYONE (Host, Client, Bots) + # This safely sends RPCs over the completed socket connection + _assign_random_spawn_positions() + GameStateManager.start_game() - rpc("sync_game_start", GameStateManager.players, TurnManager.turn_based_mode) + if can_rpc(): + 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) + if can_rpc(): + rpc("set_current_turn", next_player) # Start the global match timer (this also starts the first cycle) if LobbyManager.game_mode == "Stop n Go": @@ -565,6 +584,12 @@ func _assign_random_spawn_positions(): var spawns_BR = [] # Bottom-Right var all_spawns = [] # Fallback + # Stop n Go Custom Spawn Logic + if LobbyManager.game_mode == "Stop n Go": + var all_players = get_tree().get_nodes_in_group("Players") + _assign_stop_n_go_spawn_positions(all_players) + return + var mid_x = enhanced_gridmap.columns / 2 var mid_z = enhanced_gridmap.rows / 2 @@ -649,12 +674,49 @@ func _assign_random_spawn_positions(): # Set position and sync to all clients player.current_position = assigned_pos player.position = player.grid_to_world(assigned_pos) + player.is_player_moving = false player.spawn_point_selected = true - player.rpc("set_spawn_position", assigned_pos) + if can_rpc(): + player.rpc("set_spawn_position", assigned_pos) else: print("Critical: No spawn point found for player ", player.name) spawn_index += 1 + print("Assigned spawn %s to player %s" % [assigned_pos, player.name]) + +func _assign_stop_n_go_spawn_positions(all_players: Array): + """Assigns spawns to the far left columns (Start Line) for Stop N Go mode.""" + var enhanced_gridmap = $EnhancedGridMap + var valid_spawns = [] + + # Find all walkable tiles in the ONLY first column (x = 0) + for x in range(1): + for z in range(enhanced_gridmap.rows): + var ground = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) + if ground == 0 or ground == 2 or ground == 6: # Walkable, Start Line, or Safe Zone + valid_spawns.append(Vector2i(x, z)) + + valid_spawns.shuffle() + + # Sort players for deterministic assignment + all_players.sort_custom(func(a, b): return a.name.to_int() < b.name.to_int()) + + var spawn_index = 0 + for player in all_players: + var assigned_pos = Vector2i(0, 0) # Fallback + if spawn_index < valid_spawns.size(): + assigned_pos = valid_spawns[spawn_index] + + # Ensure immediate sync + player.position = player.grid_to_world(assigned_pos) + player.current_position = assigned_pos + player.is_player_moving = false + player.spawn_point_selected = true + if can_rpc(): + player.rpc("set_spawn_position", assigned_pos) + + spawn_index += 1 + print("[StopNGo] Assigned starting block %s to player %s" % [assigned_pos, player.name]) # ============================================================================= # Tekton NPC Management @@ -706,10 +768,8 @@ func spawn_tekton_npc(): # Generate a consistent ID/Name for sync (add index to ensure uniqueness) var tekton_id = Time.get_ticks_msec() + spawned_count _create_tekton(valid_pos, tekton_id) - - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - if multiplayer.get_peers().size() > 0: - rpc("sync_spawn_tekton", valid_pos, tekton_id) + if can_rpc(): + rpc("sync_spawn_tekton", valid_pos, tekton_id) spawned_count += 1 print("[Main] Spawned Tekton %d at %s" % [spawned_count, valid_pos]) @@ -1423,12 +1483,26 @@ func request_full_grid_sync(): print("[Main] Grid sync requested by %d" % sender_id) var enhanced_gridmap = $EnhancedGridMap if enhanced_gridmap: - var grid_data = enhanced_gridmap.get_floor_data(1) # Sync Floor 1 (Items) - print("[Main] Server: Prepared grid data. Size: %d. Sending to %d..." % [grid_data.size(), sender_id]) - - # Force send (Server is usually always connected) - rpc_id(sender_id, "sync_full_grid_data", grid_data) - print("[Main] Server: Sent rpc_id to %d" % sender_id) + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_manager: + # Resync Full Arena for Stop n Go + var floor0_data = enhanced_gridmap.get_floor_data(0) + var floor1_data = enhanced_gridmap.get_floor_data(1) + rpc_id(sender_id, "sync_full_grid_data_stop_n_go", floor0_data, floor1_data, enhanced_gridmap.columns, enhanced_gridmap.rows) + + # Resync Phase and Missions since they might have missed the initial broadcast + var phase_name = "GO" if stop_n_go_manager.current_phase == stop_n_go_manager.Phase.GO else "STOP" + stop_n_go_manager.rpc_id(sender_id, "sync_phase", phase_name, stop_n_go_manager.phase_timer) + + if stop_n_go_manager.player_missions.has(sender_id): + var mission_dict = {sender_id: stop_n_go_manager.player_missions[sender_id]} + stop_n_go_manager.rpc_id(sender_id, "sync_missions", mission_dict) + + else: + var grid_data = enhanced_gridmap.get_floor_data(1) # Sync Floor 1 (Items) + print("[Main] Server: Prepared grid data. Size: %d. Sending to %d..." % [grid_data.size(), sender_id]) + # Force send (Server is usually always connected) + rpc_id(sender_id, "sync_full_grid_data", grid_data) + print("[Main] Server: Sent rpc_id to %d" % sender_id) @rpc("authority", "call_local", "reliable") func sync_full_grid_data(data: PackedInt32Array): @@ -1939,3 +2013,11 @@ func _on_joystick_toggled(enabled: bool): if touch_controls: touch_controls.set_joystick_enabled(enabled) touch_controls._save_settings() + +func can_rpc() -> bool: + if not multiplayer.has_multiplayer_peer() or multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED: + return false + var nakama = get_node_or_null("/root/NakamaManager") + if nakama and nakama.has_method("is_connected_to_nakama") and not nakama.is_connected_to_nakama(): + return false + return true diff --git a/scenes/player.gd b/scenes/player.gd index 4f436a4..578d49e 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -237,9 +237,9 @@ func _ready(): set_process_input(false) set_process_unhandled_input(false) - # Set initial position for bots + # Set initial position for bots (Only if NOT randomized/Stop N Go by server) if enhanced_gridmap: - if is_multiplayer_authority(): + if is_multiplayer_authority() and not LobbyManager.get_randomize_spawn() and LobbyManager.game_mode != "Stop n Go": current_position = _find_random_spawn_position() update_player_position(current_position) spawn_point_selected = true @@ -270,12 +270,12 @@ func _ready(): enhanced_gridmap.set_diagonal_movement(use_diagonal_movement) # Only set position if not using random spawn (host will assign via RPC) - if not LobbyManager.get_randomize_spawn() and not spawn_point_selected: + if not LobbyManager.get_randomize_spawn() and LobbyManager.game_mode != "Stop n Go" and not spawn_point_selected: current_position = find_valid_starting_position() update_player_position(current_position) # Ensure proper initial positioning - if not LobbyManager.get_randomize_spawn() and not spawn_point_selected: + if not LobbyManager.get_randomize_spawn() and LobbyManager.game_mode != "Stop n Go" and not spawn_point_selected: global_position = Vector3( current_position.x * 1 + 1 * 0.5, 1.0, @@ -1888,11 +1888,7 @@ func set_spawn_position(pos: Vector2i): # Clear any spawn highlights clear_spawn_highlights() # Update visual position - var new_pos = Vector3( - current_position.x * cell_size.x + cell_size.x * 0.5, - cell_size.y, - current_position.y * cell_size.z + cell_size.z * 0.5 - ) + cell_offset + var new_pos = grid_to_world(pos) print("[Player %s] set_spawn_position: Grid %s -> World %s (CellSize: %s)" % [name, pos, new_pos, cell_size]) @@ -2068,7 +2064,10 @@ func update_active_player_indicator(): color = Color.YELLOW # Apply visual tint to character model across network - rpc("sync_modulate", color) + if can_rpc(): + rpc("sync_modulate", color) + else: + sync_modulate(color) # Apply locally if offline/not ready func knock_tekton(): # ... legacy or helper function ... diff --git a/scripts/bot_controller.gd b/scripts/bot_controller.gd index abd44da..4a1a827 100644 --- a/scripts/bot_controller.gd +++ b/scripts/bot_controller.gd @@ -116,8 +116,13 @@ func _run_ai_tick(): # Don't make new decisions while moving if actor.is_player_moving: return + + # STOP N GO: Red light freezing logic + if _should_freeze_for_stop_n_go(): + # print("[BotController] %s freezes for STOP phase!" % actor.name) + return - print("[BotController] AI Tick: evaluating priorities...") + # print("[BotController] AI Tick: evaluating priorities...") # Evaluate board status var board_fullness = _get_board_fullness_ratio() @@ -401,7 +406,6 @@ func _try_move() -> bool: else: # PATHFINDING FAILED! (Likely stuck on wall/stand) # Attempt UNSTUCK move to any adjacent valid tile - print("[BotController] Pathfinding failed for %s. Attempting UNSTUCK move." % actor.name) return await _try_unstuck_move() # Execute SINGLE STEP movement using player manager @@ -409,6 +413,7 @@ func _try_move() -> bool: _is_processing_action = true _current_action = "moving" + # Safety timeout to prevent infinite loop var max_wait_time = 2.0 var elapsed = 0.0 @@ -427,6 +432,20 @@ func _try_move() -> bool: return false +func _should_freeze_for_stop_n_go() -> bool: + """Check if the bot should intentionally skip its turn during STOP phase outside of safe zones.""" + var main = get_tree().root.get_node_or_null("Main") + if not main: return false + + var sng_manager = main.get_node_or_null("StopNGoManager") + if sng_manager and sng_manager.get("is_active") and sng_manager.get("current_phase") == 1: # Phase.STOP is 1 + # Check if we are outside the safe zone + var tile = enhanced_gridmap.get_cell_item(Vector3i(actor.current_position.x, 0, actor.current_position.y)) + if tile != sng_manager.TILE_SAFE: + return true # Red Light! Freeze! + + return false + func _try_unstuck_move() -> bool: """Randomly move to ANY adjacent valid tile to escape sticky situations.""" var neighbors = enhanced_gridmap.get_neighbors(actor.current_position, 0) diff --git a/scripts/managers/game_state_manager.gd b/scripts/managers/game_state_manager.gd index 8b43449..dcdf9ac 100644 --- a/scripts/managers/game_state_manager.gd +++ b/scripts/managers/game_state_manager.gd @@ -5,7 +5,7 @@ extends Node signal game_started() signal game_state_changed() -@export var enable_bots: bool = true +@export var enable_bots: bool = false @export var max_players: int = 8 var players: Array = [] diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index a275f37..c2f59d6 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -37,10 +37,16 @@ func rotate_towards_target(target_pos: Vector2i): var direction = Vector3(target_pos.x - player.current_position.x, 0, target_pos.y - player.current_position.y) if direction != Vector3.ZERO: target_rotation = atan2(direction.x, direction.z) - # Sync rotation to other clients - if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): + if player.is_multiplayer_authority() and _can_rpc(): player.rpc("sync_rotation", target_rotation) +func _can_rpc() -> bool: + if not player or not player.is_inside_tree() or not player.multiplayer.has_multiplayer_peer(): + return false + if player.multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED: + return false + return true + func simple_move_to(grid_position: Vector2i) -> bool: if is_moving: var direction = grid_position - player.current_position @@ -49,9 +55,11 @@ func simple_move_to(grid_position: Vector2i) -> bool: return false if not player.is_multiplayer_authority(): + # print("[Move] Failed: Not authority for ", player.name) return false if player.get("is_frozen"): + print("[Move] Failed: Player is frozen") return false # Stop n Go Mode Violation Check @@ -60,6 +68,7 @@ func simple_move_to(grid_position: Vector2i) -> bool: var manager = main.get_node_or_null("StopNGoManager") if manager and manager.has_method("check_movement_violation"): if manager.check_movement_violation(player.name.to_int(), player.current_position, grid_position): + print("[Move] Failed: Stop N Go Violation") return false var distance: int @@ -68,13 +77,12 @@ func simple_move_to(grid_position: Vector2i) -> bool: else: distance = abs(grid_position.x - player.current_position.x) + abs(grid_position.y - player.current_position.y) - if distance != 1: - return false # Only single-step moves allowed - if not enhanced_gridmap.is_position_valid(grid_position): + # print("[Move] Failed: Position not valid on GridMap %s" % grid_position) return false if player.has_method("can_move_to_finish") and not player.can_move_to_finish(grid_position): + print("[Move] Failed: Cannot move to finish yet") return false var cell_item = enhanced_gridmap.get_cell_item(Vector3i(grid_position.x, 0, grid_position.y)) @@ -83,10 +91,12 @@ func simple_move_to(grid_position: Vector2i) -> bool: var is_wall_passable = player.get("is_invisible") and cell_item == 4 if (cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items) and not is_wall_passable: + print("[Move] Failed: Cell Item %d is non-walkable" % cell_item) return false # PHYSICS CHECK: Ensure no static obstacles (like Stands) are blocking the path if _is_position_blocked_by_physics(grid_position): + print("[Move] Failed: Blocked by physics raycast at %s" % grid_position) return false if player.is_position_occupied(grid_position): @@ -110,7 +120,7 @@ func simple_move_to(grid_position: Vector2i) -> bool: rotate_towards_target(grid_position) - if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): + if player.is_multiplayer_authority() and _can_rpc(): if player.has_method("sync_walk_animation"): player.rpc("sync_walk_animation") @@ -119,7 +129,7 @@ func simple_move_to(grid_position: Vector2i) -> bool: current_move_direction = grid_position - player.current_position - if player.is_multiplayer_authority() and player.has_method("can_rpc") and player.can_rpc(): + if player.is_multiplayer_authority() and _can_rpc(): player.rpc("start_movement_along_path", path, not (player.is_bot or player.is_in_group("Bots"))) return true @@ -151,7 +161,7 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: # 1. Drop Victim's Tiles if other_player.has_method("drop_all_tiles"): - if player.has_method("can_rpc") and player.can_rpc(): + if _can_rpc(): other_player.rpc("drop_all_tiles") # Sync drop # 2. Spawn PowerUps around Victim @@ -169,17 +179,17 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: not _is_position_blocked_by_physics(pushed_to_pos): # Valid push var push_path = [Vector2(pushed_to_pos.x, pushed_to_pos.y)] - if player.has_method("can_rpc") and player.can_rpc(): + if _can_rpc(): other_player.rpc("start_movement_along_path", push_path, false) other_player.target_position = pushed_to_pos # Logical update # Apply stun/freeze effect as requested (same as wall stagger) - if player.has_method("can_rpc") and player.can_rpc(): + if _can_rpc(): other_player.rpc("apply_stagger", 1.5) else: # Wall/Blocked -> Stagger in place - if player.has_method("can_rpc") and player.can_rpc(): + if _can_rpc(): other_player.rpc("apply_stagger", 1.5) # 4. Consume Boost (Full) - One hit per charge @@ -453,7 +463,9 @@ func _is_position_blocked_by_physics(target_pos: Vector2i) -> bool: var result = space_state.intersect_ray(query) if result: if result.collider != player: - # print("Movement Blocked by Physics Body: ", result.collider.name) - return true + # ONLY block if it's a Static Tekton Stand + # Ignore GridMap floors/walls, which are handled by get_cell_item rules + if result.collider.name.find("StaticTektonStand") != -1 or result.collider.is_in_group("StaticTektonStands") or result.collider.has_method("is_stand"): + return true return false diff --git a/scripts/managers/stop_n_go_manager.gd b/scripts/managers/stop_n_go_manager.gd index 95bf7db..fc31335 100644 --- a/scripts/managers/stop_n_go_manager.gd +++ b/scripts/managers/stop_n_go_manager.gd @@ -27,6 +27,7 @@ const TILE_OBSTACLE = 4 # Wall var hud_layer: CanvasLayer var phase_label: Label var mission_label: Label +var red_tint_overlay: ColorRect func _ready(): set_process(false) @@ -37,6 +38,13 @@ func _setup_hud(): hud_layer.visible = false add_child(hud_layer) + # Full-screen red tint overlay (below everything else in this layer, but above game) + red_tint_overlay = ColorRect.new() + red_tint_overlay.color = Color(1.0, 0.0, 0.0, 0.25) # Transparent red + red_tint_overlay.set_anchors_preset(Control.PRESET_FULL_RECT) # Cover whole screen + red_tint_overlay.visible = false # Hidden initially + hud_layer.add_child(red_tint_overlay) + var vbox = VBoxContainer.new() vbox.set_anchors_preset(Control.PRESET_TOP_RIGHT) vbox.offset_right = -20 @@ -90,6 +98,10 @@ func _update_hud_visuals(): if phase_label: phase_label.text = "PHASE: %s (%.0fs)" % [phase_name, max(0, phase_timer)] phase_label.add_theme_color_override("font_color", Color.GREEN if current_phase == Phase.GO else Color.RED) + + # Toggle Red Screen Tint + if red_tint_overlay: + red_tint_overlay.visible = (current_phase == Phase.STOP) var my_id = multiplayer.get_unique_id() if mission_label and player_missions.has(my_id): @@ -120,7 +132,7 @@ func start_game_mode(): if multiplayer.is_server(): activate_client_side() # Server also needs local processing - _setup_arena() + # _setup_arena() # REMOVED: Already explicitly called in main.gd _setup_host_game to prepare floor before spawns! _assign_missions() _start_phase(Phase.GO) else: @@ -133,11 +145,15 @@ func _start_phase(phase: Phase): phase_timer = GO_DURATION if phase == Phase.GO else STOP_DURATION var phase_name = "GO" if phase == Phase.GO else "STOP" - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - if multiplayer.get_peers().size() > 0: - rpc("sync_phase", phase_name, phase_timer) + if can_rpc(): + rpc("sync_phase", phase_name, phase_timer) emit_signal("phase_changed", phase_name, phase_timer) +func can_rpc() -> bool: + if not multiplayer.has_multiplayer_peer() or multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED: + return false + return true + @rpc("authority", "call_local", "reliable") func sync_phase(phase_name: String, duration: float): if not is_active: @@ -152,9 +168,8 @@ func _setup_arena(): print("[StopNGo] Setting up 22x10 Arena with Randomized Obstacles...") # Explicitly sync dimensions and clear grid on all clients - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - if multiplayer.get_peers().size() > 0: - rpc("sync_arena_setup") + if can_rpc(): + rpc("sync_arena_setup") # Apply locally for Server (RPC is call_remote) _apply_arena_setup() @@ -169,9 +184,9 @@ func _apply_arena_setup(): var gridmap = get_node("/root/Main/EnhancedGridMap") if not gridmap: return - # Set Size for Stop n Go - gridmap.columns = 22 - gridmap.rows = 10 + # Set Size for Stop n Go explicitly, bypassing setters that wipe the map + gridmap.set("columns", 22) + gridmap.set("rows", 10) # Clear existing items on all layers gridmap.clear() @@ -232,7 +247,7 @@ func _apply_arena_setup(): # Sync the WHOLE grid to all clients to ensure size and stripes are correct var main = get_node("/root/Main") - if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: + if main and can_rpc(): # Gather all floor 0 and floor 1 data var floor0_data = gridmap.get_floor_data(0) var floor1_data = gridmap.get_floor_data(1) @@ -272,9 +287,8 @@ func _assign_missions(): } idx += 1 - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - if multiplayer.get_peers().size() > 0: - rpc("sync_missions", player_missions) + if can_rpc(): + rpc("sync_missions", player_missions) @rpc("authority", "call_local", "reliable") func sync_missions(missions: Dictionary): @@ -306,7 +320,7 @@ func _penalize_player(player_id: int): if player_node: # Don't reset mission progress! # Just Drop All Tiles (which already exist on player) - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: + if can_rpc(): player_node.rpc("drop_all_tiles") # Also send message @@ -331,9 +345,8 @@ func update_mission_progress(player_id: int, tile_id: int): if player_node: NotificationManager.send_message(player_node, "Mission Complete! Reach the Finish!", NotificationManager.MessageType.GOAL) - if multiplayer.is_server() and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - if multiplayer.get_peers().size() > 0: - rpc("sync_mission_progress", player_id, mission["current"]) + if multiplayer.is_server() and can_rpc(): + rpc("sync_mission_progress", player_id, mission["current"]) @rpc("any_peer", "call_local", "reliable") func sync_mission_progress(player_id: int, current: int): diff --git a/scripts/managers/touch_controls.gd b/scripts/managers/touch_controls.gd index bfe96e6..8e2a512 100644 --- a/scripts/managers/touch_controls.gd +++ b/scripts/managers/touch_controls.gd @@ -272,7 +272,7 @@ func _ensure_shortcut_label(btn: Button, button_name: String): btn.add_child(shortcut_lbl) func _on_joystick_direction(direction: Vector2i): - if local_player and local_player.has_method("simple_move_to"): + if local_player and local_player.movement_manager: var target_pos = local_player.current_position + direction local_player.movement_manager.simple_move_to(target_pos)