feat: Introduce PlayerMovementManager to manage player movement, rotation, attack mode push mechanics, and grid-based collision detection.
This commit is contained in:
+98
-16
@@ -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
|
||||
|
||||
+9
-10
@@ -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 ...
|
||||
|
||||
Reference in New Issue
Block a user