From a43a17ed10628fb184b1869c13bdba3483c41df5 Mon Sep 17 00:00:00 2001 From: Elrinth Date: Sun, 11 Jan 2026 04:02:21 +0100 Subject: [PATCH] add hud to joiners... sync alittle better, so double damage isn't done. --- src/scripts/enemy_base.gd | 10 ++++++++++ src/scripts/game_world.gd | 17 +++++++++++++++-- src/scripts/level_complete_ui.gd | 4 ++-- src/scripts/player.gd | 24 ++++++++++++++++++++++-- src/scripts/sword_projectile.gd | 14 +++++++------- 5 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/scripts/enemy_base.gd b/src/scripts/enemy_base.gd index 399380e..cd074ae 100644 --- a/src/scripts/enemy_base.gd +++ b/src/scripts/enemy_base.gd @@ -422,6 +422,16 @@ func _die(): if killer_player and is_instance_valid(killer_player) and killer_player.character_stats: killer_player.character_stats.kills += 1 print(name, " kill credited to ", killer_player.name, " (total kills: ", killer_player.character_stats.kills, ")") + + # Sync kill update to client if this player belongs to a client + # Only sync if we're on the server and the killer is a client's player + if multiplayer.has_multiplayer_peer() and is_multiplayer_authority(): + var killer_peer_id = killer_player.get_multiplayer_authority() + # Only sync if killer is a client (not server's own player) + if killer_peer_id != 0 and killer_peer_id != multiplayer.get_unique_id() and killer_player.has_method("_sync_stats_update"): + # Server is updating a client's player stats - sync to the client + var coins = killer_player.character_stats.coin if "coin" in killer_player.character_stats else 0 + killer_player._sync_stats_update.rpc_id(killer_peer_id, killer_player.character_stats.kills, coins) # Spawn loot immediately (before death animation) _spawn_loot() diff --git a/src/scripts/game_world.gd b/src/scripts/game_world.gd index 05ff7a9..8345d39 100644 --- a/src/scripts/game_world.gd +++ b/src/scripts/game_world.gd @@ -997,6 +997,9 @@ func _sync_dungeon(dungeon_data_sync: Dictionary, seed_value: int, level: int, h # Note: Level number is shown via _sync_show_level_number RPC, not here # This prevents duplicate displays and ensures consistent timing + # Load HUD on client (same as server does) + call_deferred("_load_hud") + # Sync existing dungeon to newly connected clients if multiplayer.is_server(): # This shouldn't happen, but just in case @@ -1441,7 +1444,17 @@ func _clear_level(): # Clear previous level data print("GameWorld: Clearing previous level...") - # Clear tilemap layers + # Clear tilemap layers - ensure we get references from scene if they exist + var environment = get_node_or_null("Environment") + if environment: + var layer0 = environment.get_node_or_null("DungeonLayer0") + if layer0: + layer0.clear() + var layer_above = environment.get_node_or_null("TileMapLayerAbove") + if layer_above: + layer_above.clear() + + # Also clear via stored references if they exist if dungeon_tilemap_layer: dungeon_tilemap_layer.clear() if dungeon_tilemap_layer_above: @@ -2414,7 +2427,7 @@ func _spawn_blocking_doors(): func _spawn_floor_switch(i_position: Vector2, required_weight: float, tile_x: int, tile_y: int, switch_type: String = "walk", switch_room: Dictionary = {}) -> Node: # Spawn a floor switch using the scene file # switch_type: "walk" for walk-on switch (weight 1), "pillar" for pillar switch (weight 5) - var switch_scene = preload("res://scenes/floor_switch.tscn") + var switch_scene = load("res://scenes/floor_switch.tscn") if not switch_scene: push_error("ERROR: Could not load floor_switch scene!") return null diff --git a/src/scripts/level_complete_ui.gd b/src/scripts/level_complete_ui.gd index 463487b..2a7ac6b 100644 --- a/src/scripts/level_complete_ui.gd +++ b/src/scripts/level_complete_ui.gd @@ -71,8 +71,8 @@ func show_stats(enemies_defeated: int, times_downed: int, exp_collected: float, # Format level time as MM:SS if time_label: - var minutes = int(level_time) / 60 - var seconds = int(level_time) % 60 + var minutes = int(level_time / 60.0) + var seconds = int(fmod(level_time, 60.0)) time_label.text = "Finish time: %02d:%02d" % [minutes, seconds] time_label.visible = true else: diff --git a/src/scripts/player.gd b/src/scripts/player.gd index 651a5a4..f854a41 100644 --- a/src/scripts/player.gd +++ b/src/scripts/player.gd @@ -1878,6 +1878,11 @@ func _sync_release(obj_path: NodePath): obj.set_collision_mask_value(2, true) if "is_frozen" in obj: obj.is_frozen = false + # CRITICAL: Clear is_being_held so object can be grabbed again and switches can detect it + if "is_being_held" in obj: + obj.is_being_held = false + if "held_by_player" in obj: + obj.held_by_player = null elif _is_player(obj): obj.set_collision_layer_value(1, true) obj.set_collision_mask_value(1, true) @@ -2384,12 +2389,27 @@ func add_coins(amount: int): if character_stats: character_stats.add_coin(amount) print(name, " picked up ", amount, " coin(s)! Total coins: ", character_stats.coin) + + # Sync coins to client if this is server-side coin collection + # (e.g., when loot is collected on server, sync to client) + if multiplayer.has_multiplayer_peer() and multiplayer.is_server() and not is_multiplayer_authority() and can_send_rpcs and is_inside_tree(): + # Server is adding coins to a client's player - sync to the client + var peer_id = get_multiplayer_authority() + if peer_id != 0: + _sync_stats_update.rpc_id(peer_id, character_stats.kills, character_stats.coin) else: coins += amount print(name, " picked up ", amount, " coin(s)! Total coins: ", coins) - # Sync coins to other players if needed (for UI display) - # This can be expanded later if coins need to be synced across network + +@rpc("authority", "reliable") +func _sync_stats_update(kills_count: int, coins_count: int): + # Client receives stats update from server (for kills and coins) + # Update local stats to match server + if character_stats: + character_stats.kills = kills_count + character_stats.coin = coins_count + print(name, " stats synced from server: kills=", kills_count, " coins=", coins_count) func heal(amount: float): if is_dead: diff --git a/src/scripts/sword_projectile.gd b/src/scripts/sword_projectile.gd index 51c92ad..c833405 100644 --- a/src/scripts/sword_projectile.gd +++ b/src/scripts/sword_projectile.gd @@ -70,12 +70,12 @@ func _on_body_entered(body): if body in hit_targets: return - # For players: only the projectile owner (authority) should register hits - # This prevents duplicate damage from multiple clients - # For enemies: clients can also send RPCs to server to deal damage - if body.is_in_group("player"): - if player_owner and not player_owner.is_multiplayer_authority(): - return # Only server players can damage other players + # CRITICAL: Only the projectile owner (authority) should deal damage + # This prevents duplicate damage when the projectile exists on both server and clients + # Server creates the projectile first, then clients create it via _sync_attack + # Without this check, both server and client projectiles would deal damage + if player_owner and not player_owner.is_multiplayer_authority(): + return # Only the authority (creator) of the projectile can deal damage # Add to hit_targets IMMEDIATELY to prevent multiple hits (mark as hit before processing) hit_targets[body] = true @@ -100,7 +100,7 @@ func _on_body_entered(body): body.rpc_take_damage.rpc(damage, attacker_pos) print("Sword projectile hit player: ", body.name, " for ", damage, " damage!") - # Deal damage to enemies - clients can send RPC to server to deal damage + # Deal damage to enemies - only authority (creator) deals damage elif body.is_in_group("enemy") and body.has_method("rpc_take_damage"): $SfxImpact.play() var attacker_pos = player_owner.global_position if player_owner else global_position