|
|
|
|
@ -13,6 +13,8 @@ extends Node3D |
|
|
|
|
var _weapon_spawn_counter: int = 0 |
|
|
|
|
# Track active weapons for late-join sync (server-side only) |
|
|
|
|
var _active_weapons: Dictionary = {} # weapon_id -> WorldWeapon reference |
|
|
|
|
# Track if we've already initialized to prevent double-spawning |
|
|
|
|
var _multiplayer_initialized: bool = false |
|
|
|
|
|
|
|
|
|
# multiplayer chat |
|
|
|
|
@onready var message: LineEdit = $MultiplayerChat/VBoxContainer/HBoxContainer/Message |
|
|
|
|
@ -23,6 +25,8 @@ var _active_weapons: Dictionary = {} # weapon_id -> WorldWeapon reference |
|
|
|
|
var chat_visible = false |
|
|
|
|
|
|
|
|
|
func _ready(): |
|
|
|
|
print("[Level] _ready() called. Peer ID: ", multiplayer.get_unique_id(), " Is server: ", multiplayer.is_server()) |
|
|
|
|
|
|
|
|
|
multiplayer_chat.hide() |
|
|
|
|
menu.show() |
|
|
|
|
multiplayer_chat.set_process_input(true) |
|
|
|
|
@ -39,11 +43,28 @@ func _ready(): |
|
|
|
|
add_child(weapons_container) |
|
|
|
|
print("Created WeaponsContainer") |
|
|
|
|
|
|
|
|
|
# Clients: Remove manually placed weapons (server will sync them via RPC) |
|
|
|
|
if not multiplayer.is_server(): |
|
|
|
|
# Don't initialize weapons in _ready() - wait for Host/Join to be pressed |
|
|
|
|
# This is handled in initialize_multiplayer() which is called after the |
|
|
|
|
# multiplayer peer is properly set up |
|
|
|
|
print("[Level] _ready() complete - waiting for Host/Join") |
|
|
|
|
|
|
|
|
|
func _on_connected_to_server(): |
|
|
|
|
print("[Level] Connected to server! Cleaning up manual weapons") |
|
|
|
|
_cleanup_manual_weapons_on_client() |
|
|
|
|
|
|
|
|
|
## Called by Network when multiplayer peer is set up |
|
|
|
|
func initialize_multiplayer(): |
|
|
|
|
print("[Level] initialize_multiplayer called. is_server: ", multiplayer.is_server()) |
|
|
|
|
|
|
|
|
|
# Prevent double initialization |
|
|
|
|
if _multiplayer_initialized: |
|
|
|
|
print("[Level] Already initialized, skipping") |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
_multiplayer_initialized = true |
|
|
|
|
|
|
|
|
|
if multiplayer.is_server(): |
|
|
|
|
print("[Level] Running server initialization") |
|
|
|
|
Network.connect("player_connected", Callable(self, "_on_player_connected")) |
|
|
|
|
multiplayer.peer_disconnected.connect(_remove_player) |
|
|
|
|
|
|
|
|
|
@ -53,19 +74,40 @@ func _ready(): |
|
|
|
|
# Spawn initial weapons when server starts |
|
|
|
|
_spawn_initial_weapons() |
|
|
|
|
|
|
|
|
|
# Spawn the host player (peer ID 1) |
|
|
|
|
print("[Level] Spawning host player") |
|
|
|
|
var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"}) |
|
|
|
|
_add_player(1, host_info) |
|
|
|
|
else: |
|
|
|
|
# Client initialization - clean up manual weapons immediately |
|
|
|
|
print("[Level] Running client initialization - cleaning up manual weapons") |
|
|
|
|
_cleanup_manual_weapons_on_client() |
|
|
|
|
|
|
|
|
|
func _cleanup_manual_weapons_on_client(): |
|
|
|
|
"""Remove manually placed weapons on clients (server will sync them via RPC)""" |
|
|
|
|
print("[Client ", multiplayer.get_unique_id(), "] _cleanup_manual_weapons_on_client called") |
|
|
|
|
|
|
|
|
|
if not weapons_container: |
|
|
|
|
print("[Client] No weapons_container found!") |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
print("[Client] WeaponsContainer has ", weapons_container.get_child_count(), " children") |
|
|
|
|
|
|
|
|
|
var weapons_to_remove = [] |
|
|
|
|
for child in weapons_container.get_children(): |
|
|
|
|
if child is WorldWeapon and child.weapon_id == -1: |
|
|
|
|
print("[Client] Checking child: ", child.name, " (type: ", child.get_class(), ")") |
|
|
|
|
if child is WorldWeapon: |
|
|
|
|
print("[Client] - Is WorldWeapon with weapon_id: ", child.weapon_id) |
|
|
|
|
if child.weapon_id == -1: |
|
|
|
|
weapons_to_remove.append(child) |
|
|
|
|
print("[Client] - Marked for removal") |
|
|
|
|
|
|
|
|
|
print("[Client] Found ", weapons_to_remove.size(), " weapons to remove") |
|
|
|
|
for weapon in weapons_to_remove: |
|
|
|
|
print("[Client] Removing manually placed weapon: ", weapon.name) |
|
|
|
|
weapon.queue_free() |
|
|
|
|
# Use immediate removal to prevent RPC errors |
|
|
|
|
weapons_container.remove_child(weapon) |
|
|
|
|
weapon.free() |
|
|
|
|
|
|
|
|
|
func _initialize_manual_weapons(): |
|
|
|
|
"""Initialize any WorldWeapon nodes manually placed in the level scene""" |
|
|
|
|
@ -144,16 +186,29 @@ func _spawn_initial_weapons(): |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func _on_player_connected(peer_id, player_info): |
|
|
|
|
print("[Server] _on_player_connected called for peer ", peer_id, " with info: ", player_info) |
|
|
|
|
_add_player(peer_id, player_info) |
|
|
|
|
|
|
|
|
|
# Sync existing players to the newly joined player |
|
|
|
|
if multiplayer.is_server() and peer_id != 1: |
|
|
|
|
print("[Server] Syncing existing players to newly connected peer: ", peer_id) |
|
|
|
|
# Wait a frame to ensure new player is fully initialized |
|
|
|
|
await get_tree().process_frame |
|
|
|
|
for existing_player in players_container.get_children(): |
|
|
|
|
var existing_id = int(existing_player.name) |
|
|
|
|
if existing_id != peer_id: # Don't sync the player to themselves |
|
|
|
|
print("[Server] Syncing existing player ", existing_id, " to peer ", peer_id) |
|
|
|
|
rpc_id(peer_id, "_spawn_player_local", existing_id, existing_player.position) |
|
|
|
|
|
|
|
|
|
# Sync existing weapons to the newly joined player (but not to server itself) |
|
|
|
|
if multiplayer.is_server() and peer_id != 1: |
|
|
|
|
print("[Server] Syncing weapons to newly connected peer: ", peer_id) |
|
|
|
|
print("[Server] Active weapons in _active_weapons: ", _active_weapons.keys()) |
|
|
|
|
print("[Server] Active weapons count: ", _active_weapons.size()) |
|
|
|
|
for weapon_id in _active_weapons.keys(): |
|
|
|
|
var weapon = _active_weapons[weapon_id] |
|
|
|
|
if is_instance_valid(weapon) and weapon.weapon_data: |
|
|
|
|
print("[Server] Sending weapon ", weapon_id, " to peer ", peer_id) |
|
|
|
|
print("[Server] Sending weapon ", weapon_id, " (", weapon.weapon_data.weapon_name, ") at position ", weapon.global_position, " to peer ", peer_id) |
|
|
|
|
# Send current position and zero velocity for syncing |
|
|
|
|
rpc_id(peer_id, "_client_spawn_weapon", |
|
|
|
|
weapon.weapon_data.resource_path, |
|
|
|
|
@ -161,24 +216,83 @@ func _on_player_connected(peer_id, player_info): |
|
|
|
|
Vector3.ZERO, |
|
|
|
|
weapon_id |
|
|
|
|
) |
|
|
|
|
else: |
|
|
|
|
print("[Server] Skipping invalid weapon ", weapon_id) |
|
|
|
|
|
|
|
|
|
# Sync equipped weapons for all existing players to the newly joined player |
|
|
|
|
print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id) |
|
|
|
|
for player_node in players_container.get_children(): |
|
|
|
|
var player = player_node as Character |
|
|
|
|
if player and is_instance_valid(player): |
|
|
|
|
# Skip the newly joined player (they don't have weapons yet) |
|
|
|
|
if int(player.name) == peer_id: |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
# Sync main hand weapon |
|
|
|
|
if player.equipped_weapon and player.equipped_weapon.weapon_data: |
|
|
|
|
print("[Server] Syncing main hand weapon for player ", player.name, " to peer ", peer_id) |
|
|
|
|
player.rpc_id(peer_id, "equip_weapon_from_world", |
|
|
|
|
player.equipped_weapon.weapon_data.resource_path) |
|
|
|
|
|
|
|
|
|
# Sync off-hand weapon |
|
|
|
|
if player.equipped_offhand and player.equipped_offhand.weapon_data: |
|
|
|
|
print("[Server] Syncing off-hand weapon for player ", player.name, " to peer ", peer_id) |
|
|
|
|
player.rpc_id(peer_id, "equip_weapon_from_world", |
|
|
|
|
player.equipped_offhand.weapon_data.resource_path) |
|
|
|
|
|
|
|
|
|
func _on_host_pressed(): |
|
|
|
|
print("[Level] Host button pressed") |
|
|
|
|
menu.hide() |
|
|
|
|
print("[Level] Calling Network.start_host()") |
|
|
|
|
Network.start_host(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower()) |
|
|
|
|
print("[Level] Waiting one frame...") |
|
|
|
|
await get_tree().process_frame |
|
|
|
|
print("[Level] Calling initialize_multiplayer()") |
|
|
|
|
initialize_multiplayer() |
|
|
|
|
|
|
|
|
|
func _on_join_pressed(): |
|
|
|
|
print("[Level] Join button pressed") |
|
|
|
|
menu.hide() |
|
|
|
|
print("[Level] Calling Network.join_game()") |
|
|
|
|
Network.join_game(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower(), address_input.text.strip_edges()) |
|
|
|
|
print("[Level] Waiting one frame...") |
|
|
|
|
await get_tree().process_frame |
|
|
|
|
print("[Level] Calling initialize_multiplayer()") |
|
|
|
|
initialize_multiplayer() |
|
|
|
|
|
|
|
|
|
func _add_player(id: int, player_info : Dictionary): |
|
|
|
|
print("[Level] _add_player called for peer ", id, " with info: ", player_info) |
|
|
|
|
|
|
|
|
|
# Server spawns player and replicates to all clients via RPC |
|
|
|
|
if multiplayer.is_server(): |
|
|
|
|
if players_container.has_node(str(id)): |
|
|
|
|
print("[Level] Player ", id, " already exists, skipping") |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
var spawn_pos = get_spawn_point() |
|
|
|
|
print("[Level] Server spawning player ", id, " at ", spawn_pos) |
|
|
|
|
|
|
|
|
|
# Spawn on server and all clients (call_local does both) |
|
|
|
|
rpc("_spawn_player_local", id, spawn_pos) |
|
|
|
|
|
|
|
|
|
@rpc("any_peer", "call_local", "reliable") |
|
|
|
|
func _spawn_player_local(id: int, spawn_pos: Vector3): |
|
|
|
|
if players_container.has_node(str(id)): |
|
|
|
|
print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " already exists, skipping") |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
print("[Peer ", multiplayer.get_unique_id(), "] Creating player instance for peer ", id) |
|
|
|
|
var player = player_scene.instantiate() |
|
|
|
|
player.name = str(id) |
|
|
|
|
player.position = get_spawn_point() |
|
|
|
|
player.position = spawn_pos |
|
|
|
|
print("[Peer ", multiplayer.get_unique_id(), "] Adding player to PlayersContainer") |
|
|
|
|
players_container.add_child(player, true) |
|
|
|
|
print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " spawned at ", player.position) |
|
|
|
|
|
|
|
|
|
# Get player info from Network |
|
|
|
|
var player_info = Network.players.get(id, {"nick": "Player", "skin": "blue"}) |
|
|
|
|
|
|
|
|
|
var nick = Network.players[id]["nick"] |
|
|
|
|
var nick = player_info["nick"] |
|
|
|
|
# Access nickname directly via node path since @onready hasn't loaded yet |
|
|
|
|
var nickname_label = player.get_node_or_null("PlayerNick/Nickname") |
|
|
|
|
if nickname_label: |
|
|
|
|
@ -359,6 +473,14 @@ func remove_world_weapon(weapon_id: int): |
|
|
|
|
print("[ERROR] remove_world_weapon called on client!") |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
# Immediately remove from active weapons to prevent late-join sync issues |
|
|
|
|
if _active_weapons.has(weapon_id): |
|
|
|
|
_active_weapons.erase(weapon_id) |
|
|
|
|
print("[Server] Removed weapon ", weapon_id, " from _active_weapons. Remaining: ", _active_weapons.size()) |
|
|
|
|
print("[Server] Remaining weapon IDs: ", _active_weapons.keys()) |
|
|
|
|
else: |
|
|
|
|
print("[Server] WARNING: Weapon ", weapon_id, " not found in _active_weapons!") |
|
|
|
|
|
|
|
|
|
# Broadcast removal to all clients |
|
|
|
|
print("[Server] Broadcasting removal RPC to all clients") |
|
|
|
|
rpc("_remove_weapon_on_clients", weapon_id) |
|
|
|
|
|