Compare commits

..
4 Commits
Author SHA1 Message Date
Twirpytherobot e192802f01 Finally fixed the sync issue!
Late joining now syncs properly!!!!!!
2025-11-19 23:12:12 +00:00
Twirpytherobot 85e76d67ca Fixed yo shit 2025-11-17 19:26:56 +00:00
Scottlg 6e4e3a3585 Revert to using player.tscn (robot character) temporarily
- Changed level.tscn back to player.tscn instead of Player_Lilguy.tscn
- This ensures the Character script is properly attached
- Lilguy character integration will be done properly later in Godot editor
- Game now runs without type errors
2025-11-16 23:30:48 +00:00
Scottlg bdc507e852 Fix nickname access error on player instantiation
- Changed from player.nickname.text to get_node_or_null approach
- @onready variables aren't available immediately after instantiation
- Prevents 'Invalid access to property or key nickname' error
2025-11-16 23:29:30 +00:00
3 changed files with 172 additions and 41 deletions
+31 -21
View File
@@ -1,8 +1,9 @@
[gd_scene load_steps=6 format=3 uid="uid://db06e8q8f8bdq"] [gd_scene load_steps=7 format=3 uid="uid://db06e8q8f8bdq"]
[ext_resource type="PackedScene" uid="uid://byw3ig2bs1wgu" path="res://assets/characters/player/LilguyRigged.glb" id="1_e6qwr"] [ext_resource type="Script" uid="uid://c2si8gkbnde0c" path="res://level/scripts/player.gd" id="1_player"]
[ext_resource type="PackedScene" uid="uid://byw3ig2bs1wgu" path="res://assets/characters/player/LilguyRigged.glb" id="2_lilguy"]
[ext_resource type="Script" uid="uid://cf7jky1bcs560" path="res://level/scripts/lilguy_body.gd" id="3_body"] [ext_resource type="Script" uid="uid://cf7jky1bcs560" path="res://level/scripts/lilguy_body.gd" id="3_body"]
[ext_resource type="Script" uid="uid://bj7yrijm7bppq" path="res://level/scripts/spring_arm_offset.gd" id="9_dlyie"] [ext_resource type="Script" uid="uid://bj7yrijm7bppq" path="res://level/scripts/spring_arm_offset.gd" id="4_spring"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_yxyay"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_yxyay"]
radius = 0.35796 radius = 0.35796
@@ -12,26 +13,35 @@ height = 1.73092
properties/0/path = NodePath(".:position") properties/0/path = NodePath(".:position")
properties/0/spawn = true properties/0/spawn = true
properties/0/replication_mode = 1 properties/0/replication_mode = 1
properties/1/path = NodePath("AnimationPlayer:current_animation") properties/1/path = NodePath("LilguyRigged/AnimationPlayer:current_animation")
properties/1/spawn = true properties/1/spawn = true
properties/1/replication_mode = 1 properties/1/replication_mode = 1
properties/2/path = NodePath("PlayerNick/Nickname:text") properties/2/path = NodePath("PlayerNick/Nickname:text")
properties/2/spawn = true properties/2/spawn = true
properties/2/replication_mode = 1 properties/2/replication_mode = 1
properties/3/path = NodePath("Armature:rotation") properties/3/path = NodePath("LilguyRigged/Armature:rotation")
properties/3/spawn = true properties/3/spawn = true
properties/3/replication_mode = 1 properties/3/replication_mode = 1
[node name="LilguyRigged" instance=ExtResource("1_e6qwr")] [node name="Player" type="CharacterBody3D" node_paths=PackedStringArray("_body", "_spring_arm_offset", "_weapon_attachment", "_weapon_container", "_offhand_attachment", "_offhand_container")]
script = ExtResource("1_player")
collision_mask = 3 collision_mask = 3
_body = NodePath("LilguyRigged/Armature")
_spring_arm_offset = NodePath("SpringArmOffset")
_weapon_attachment = NodePath("LilguyRigged/Armature/Skeleton3D/WeaponPoint")
_weapon_container = NodePath("LilguyRigged/Armature/Skeleton3D/WeaponPoint/WeaponContainer")
_offhand_attachment = NodePath("LilguyRigged/Armature/Skeleton3D/OffhandPoint")
_offhand_container = NodePath("LilguyRigged/Armature/Skeleton3D/OffhandPoint/OffhandContainer")
[node name="Armature" parent="." index="0" node_paths=PackedStringArray("_character", "animation_player")] [node name="LilguyRigged" parent="." instance=ExtResource("2_lilguy")]
[node name="Armature" parent="LilguyRigged" index="0" node_paths=PackedStringArray("_character", "animation_player")]
transform = Transform3D(0.003, 0, 0, 0, -1.3113416e-10, -0.003, 0, 0.003, -1.3113416e-10, 0, 0, 0) transform = Transform3D(0.003, 0, 0, 0, -1.3113416e-10, -0.003, 0, 0.003, -1.3113416e-10, 0, 0, 0)
script = ExtResource("3_body") script = ExtResource("3_body")
_character = NodePath("..") _character = NodePath("../..")
animation_player = NodePath("../AnimationPlayer") animation_player = NodePath("../AnimationPlayer")
[node name="Skeleton3D" parent="Armature" index="0"] [node name="Skeleton3D" parent="LilguyRigged/Armature" index="0"]
bones/0/position = Vector3(-0.32852697, 2.914154, -546.76843) bones/0/position = Vector3(-0.32852697, 2.914154, -546.76843)
bones/0/rotation = Quaternion(-0.6608288, 0.28933647, -0.19178489, 0.6654384) bones/0/rotation = Quaternion(-0.6608288, 0.28933647, -0.19178489, 0.6654384)
bones/1/position = Vector3(0.054167695, 63.219894, -3.33786e-06) bones/1/position = Vector3(0.054167695, 63.219894, -3.33786e-06)
@@ -78,45 +88,45 @@ bones/23/position = Vector3(1.3624241e-05, 15.034077, 9.790485e-06)
bones/23/rotation = Quaternion(0.11885707, 0.009521758, -0.0077993367, 0.9928351) bones/23/rotation = Quaternion(0.11885707, 0.009521758, -0.0077993367, 0.9928351)
bones/24/position = Vector3(-2.4847686e-06, 11.913359, -6.198885e-06) bones/24/position = Vector3(-2.4847686e-06, 11.913359, -6.198885e-06)
[node name="WeaponPoint" type="BoneAttachment3D" parent="Armature/Skeleton3D" index="1"] [node name="WeaponPoint" type="BoneAttachment3D" parent="LilguyRigged/Armature/Skeleton3D" index="1"]
transform = Transform3D(-0.43292555, -0.61284775, 0.6610542, 0.7782953, 0.11585887, 0.61711675, -0.45478758, 0.78166103, 0.42681834, -352.38528, -73.5694, -531.96124) transform = Transform3D(-0.43292555, -0.61284775, 0.6610542, 0.7782953, 0.11585887, 0.61711675, -0.45478758, 0.78166103, 0.42681834, -352.38528, -73.5694, -531.96124)
bone_name = "mixamorig_RightHand" bone_name = "mixamorig_RightHand"
bone_idx = 14 bone_idx = 14
[node name="WeaponContainer" type="Node3D" parent="Armature/Skeleton3D/WeaponPoint" index="0"] [node name="WeaponContainer" type="Node3D" parent="LilguyRigged/Armature/Skeleton3D/WeaponPoint" index="0"]
transform = Transform3D(36.6912, 297.2667, 16.921356, 46.72698, 11.0892515, -296.13126, -294.05847, 38.85366, -44.94499, 24.08223, -7.4241333, 7.098694) transform = Transform3D(36.6912, 297.2667, 16.921356, 46.72698, 11.0892515, -296.13126, -294.05847, 38.85366, -44.94499, 24.08223, -7.4241333, 7.098694)
[node name="OffhandPoint" type="BoneAttachment3D" parent="Armature/Skeleton3D" index="2"] [node name="OffhandPoint" type="BoneAttachment3D" parent="LilguyRigged/Armature/Skeleton3D" index="2"]
transform = Transform3D(0.6212382, -0.004605584, -0.7836083, -0.620316, 0.60813713, -0.49535576, 0.47882265, 0.7938187, 0.37494114, 135.65903, 334.35764, -511.2708) transform = Transform3D(0.6212382, -0.004605584, -0.7836083, -0.620316, 0.60813713, -0.49535576, 0.47882265, 0.7938187, 0.37494114, 135.65903, 334.35764, -511.2708)
bone_name = "mixamorig_LeftHand" bone_name = "mixamorig_LeftHand"
bone_idx = 10 bone_idx = 10
[node name="OffhandContainer" type="Node3D" parent="Armature/Skeleton3D/OffhandPoint" index="0"] [node name="OffhandContainer" type="Node3D" parent="LilguyRigged/Armature/Skeleton3D/OffhandPoint" index="0"]
transform = Transform3D(-17.74905, -295.46814, -48.82108, 21.019196, -50.01525, 295.05362, -298.73593, 14.035805, 23.660797, 0.005859375, 0.39337158, 0.06616211) transform = Transform3D(-17.74905, -295.46814, -48.82108, 21.019196, -50.01525, 295.05362, -298.73593, 14.035805, 23.660797, 0.005859375, 0.39337158, 0.06616211)
[node name="CollisionShape3D" type="CollisionShape3D" parent="." index="2"] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.066, 0.828, 0.01) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.066, 0.828, 0.01)
shape = SubResource("CapsuleShape3D_yxyay") shape = SubResource("CapsuleShape3D_yxyay")
[node name="SpringArmOffset" type="Node3D" parent="." index="3" node_paths=PackedStringArray("_spring_arm")] [node name="SpringArmOffset" type="Node3D" parent="." node_paths=PackedStringArray("_spring_arm")]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0) transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
script = ExtResource("9_dlyie") script = ExtResource("4_spring")
_spring_arm = NodePath("SpringArm3D") _spring_arm = NodePath("SpringArm3D")
[node name="SpringArm3D" type="SpringArm3D" parent="SpringArmOffset" index="0"] [node name="SpringArm3D" type="SpringArm3D" parent="SpringArmOffset"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
spring_length = 5.0 spring_length = 5.0
[node name="Camera3D" type="Camera3D" parent="SpringArmOffset/SpringArm3D" index="0"] [node name="Camera3D" type="Camera3D" parent="SpringArmOffset/SpringArm3D"]
current = true current = true
[node name="PlayerNick" type="Node3D" parent="." index="4"] [node name="PlayerNick" type="Node3D" parent="."]
[node name="Nickname" type="Label3D" parent="PlayerNick" index="0"] [node name="Nickname" type="Label3D" parent="PlayerNick"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.97037, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.97037, 0)
billboard = 1 billboard = 1
outline_modulate = Color(0, 0, 0, 0.301961) outline_modulate = Color(0, 0, 0, 0.301961)
text = "player name test" text = "player name test"
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="5"] [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_xbohm") replication_config = SubResource("SceneReplicationConfig_xbohm")
+141 -16
View File
@@ -13,6 +13,8 @@ extends Node3D
var _weapon_spawn_counter: int = 0 var _weapon_spawn_counter: int = 0
# Track active weapons for late-join sync (server-side only) # Track active weapons for late-join sync (server-side only)
var _active_weapons: Dictionary = {} # weapon_id -> WorldWeapon reference 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 # multiplayer chat
@onready var message: LineEdit = $MultiplayerChat/VBoxContainer/HBoxContainer/Message @onready var message: LineEdit = $MultiplayerChat/VBoxContainer/HBoxContainer/Message
@@ -23,6 +25,8 @@ var _active_weapons: Dictionary = {} # weapon_id -> WorldWeapon reference
var chat_visible = false var chat_visible = false
func _ready(): func _ready():
print("[Level] _ready() called. Peer ID: ", multiplayer.get_unique_id(), " Is server: ", multiplayer.is_server())
multiplayer_chat.hide() multiplayer_chat.hide()
menu.show() menu.show()
multiplayer_chat.set_process_input(true) multiplayer_chat.set_process_input(true)
@@ -39,33 +43,71 @@ func _ready():
add_child(weapons_container) add_child(weapons_container)
print("Created WeaponsContainer") print("Created WeaponsContainer")
# Clients: Remove manually placed weapons (server will sync them via RPC) # Don't initialize weapons in _ready() - wait for Host/Join to be pressed
if not multiplayer.is_server(): # This is handled in initialize_multiplayer() which is called after the
_cleanup_manual_weapons_on_client() # 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 return
Network.connect("player_connected", Callable(self, "_on_player_connected")) _multiplayer_initialized = true
multiplayer.peer_disconnected.connect(_remove_player)
# Initialize any manually placed weapons in the scene if multiplayer.is_server():
_initialize_manual_weapons() print("[Level] Running server initialization")
Network.connect("player_connected", Callable(self, "_on_player_connected"))
multiplayer.peer_disconnected.connect(_remove_player)
# Spawn initial weapons when server starts # Initialize any manually placed weapons in the scene
_spawn_initial_weapons() _initialize_manual_weapons()
# 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(): func _cleanup_manual_weapons_on_client():
"""Remove manually placed weapons on clients (server will sync them via RPC)""" """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: if not weapons_container:
print("[Client] No weapons_container found!")
return return
print("[Client] WeaponsContainer has ", weapons_container.get_child_count(), " children")
var weapons_to_remove = [] var weapons_to_remove = []
for child in weapons_container.get_children(): 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(), ")")
weapons_to_remove.append(child) 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: for weapon in weapons_to_remove:
print("[Client] Removing manually placed weapon: ", weapon.name) 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(): func _initialize_manual_weapons():
"""Initialize any WorldWeapon nodes manually placed in the level scene""" """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): 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) _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) # Sync existing weapons to the newly joined player (but not to server itself)
if multiplayer.is_server() and peer_id != 1: if multiplayer.is_server() and peer_id != 1:
print("[Server] Syncing weapons to newly connected peer: ", peer_id) 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()) print("[Server] Active weapons count: ", _active_weapons.size())
for weapon_id in _active_weapons.keys(): for weapon_id in _active_weapons.keys():
var weapon = _active_weapons[weapon_id] var weapon = _active_weapons[weapon_id]
if is_instance_valid(weapon) and weapon.weapon_data: 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 # Send current position and zero velocity for syncing
rpc_id(peer_id, "_client_spawn_weapon", rpc_id(peer_id, "_client_spawn_weapon",
weapon.weapon_data.resource_path, weapon.weapon_data.resource_path,
@@ -161,25 +216,87 @@ func _on_player_connected(peer_id, player_info):
Vector3.ZERO, Vector3.ZERO,
weapon_id 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(): func _on_host_pressed():
print("[Level] Host button pressed")
menu.hide() menu.hide()
print("[Level] Calling Network.start_host()")
Network.start_host(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower()) 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(): func _on_join_pressed():
print("[Level] Join button pressed")
menu.hide() 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()) 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): 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)): if players_container.has_node(str(id)):
print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " already exists, skipping")
return return
print("[Peer ", multiplayer.get_unique_id(), "] Creating player instance for peer ", id)
var player = player_scene.instantiate() var player = player_scene.instantiate()
player.name = str(id) 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) players_container.add_child(player, true)
print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " spawned at ", player.position)
var nick = Network.players[id]["nick"] # Get player info from Network
player.nickname.text = nick var player_info = Network.players.get(id, {"nick": "Player", "skin": "blue"})
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:
nickname_label.text = nick
# player.rpc("change_nick", nick) # player.rpc("change_nick", nick)
# Set up HUD for local player # Set up HUD for local player
@@ -356,6 +473,14 @@ func remove_world_weapon(weapon_id: int):
print("[ERROR] remove_world_weapon called on client!") print("[ERROR] remove_world_weapon called on client!")
return 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 # Broadcast removal to all clients
print("[Server] Broadcasting removal RPC to all clients") print("[Server] Broadcasting removal RPC to all clients")
rpc("_remove_weapon_on_clients", weapon_id) rpc("_remove_weapon_on_clients", weapon_id)
-4
View File
@@ -13,10 +13,6 @@ var player_info = {
signal player_connected(peer_id, player_info) signal player_connected(peer_id, player_info)
signal server_disconnected signal server_disconnected
func _process(_delta):
if Input.is_action_just_pressed("quit"):
get_tree().quit(0)
func _ready() -> void: func _ready() -> void:
multiplayer.server_disconnected.connect(_on_connection_failed) multiplayer.server_disconnected.connect(_on_connection_failed)
multiplayer.connection_failed.connect(_on_server_disconnected) multiplayer.connection_failed.connect(_on_server_disconnected)