From cc207bca3d3ab09542fd9603b9817aa5a8cf8007 Mon Sep 17 00:00:00 2001 From: Yogi Wiguna Date: Tue, 31 Mar 2026 12:40:57 +0800 Subject: [PATCH] feat: implement global SFXManager singleton and integrate into main scene initialization --- scenes/main.gd | 10 +++++++--- scenes/player.gd | 20 +++++++++++++++++++- scripts/managers/sfx_manager.gd | 14 ++++++++++---- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/scenes/main.gd b/scenes/main.gd index 2487368..16f37ef 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -720,8 +720,12 @@ func _auto_start_from_lobby(): func _start_game(): if multiplayer.is_server(): + # Delay spawn assignment to allow clients to finish instantiating Player nodes + # via MultiplayerSpawner. If called too early, the RPC is dropped and clients + # are left misplaced at the default starting position. + await get_tree().create_timer(1.0).timeout + # NOW assign spawn positions for EVERYONE (Host, Client, Bots) - # We do this BEFORE the stabilization delay so players are moved away from (0,0) immediately. _assign_random_spawn_positions() # Wait for Nakama websocket to actually be open, up to 5 seconds @@ -735,9 +739,9 @@ func _start_game(): wait_time += 0.2 # Stabilization delay to allow clients to finish loading and spawning - # We wait 1.5s to ensure the 1.2s loading screen buffer has finished + # We wait 0.5s to ensure the remainder of the 1.2s loading screen buffer has finished # before the countdown starts. - await get_tree().create_timer(1.5).timeout + await get_tree().create_timer(0.5).timeout # PRE-GAME COUNTDOWN (3s) # Spawn static obstacles before countdown starts (Stop n Go only) diff --git a/scenes/player.gd b/scenes/player.gd index 1079c9c..0f645d3 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -76,6 +76,8 @@ var is_attack_mode: bool = false: return # Prevent infinite recursion / redundant updates is_attack_mode = value + if is_attack_mode: + attack_mode_timer = MAX_ATTACK_MODE_TIME _refresh_player_visuals() # Sync to others if we are the authority @@ -836,6 +838,8 @@ var slow_timer: float = 0.0 var tekton_carry_timer: float = 0.0 const MAX_TEKTON_CARRY_TIME: float = 3.0 +var attack_mode_timer: float = 0.0 +const MAX_ATTACK_MODE_TIME: float = 5.0 @rpc("any_peer", "call_local") func apply_stagger(duration: float = 1.5): @@ -1184,12 +1188,24 @@ func _process(delta): if movement_manager: movement_manager._process(delta) + # Attack/Knock Mode Expiration Timer + if is_multiplayer_authority() and (is_attack_mode or is_knock_mode): + if attack_mode_timer > 0: + attack_mode_timer -= delta + if attack_mode_timer <= 0: + attack_mode_timer = 0.0 + is_attack_mode = false + is_knock_mode = false + NotificationManager.send_message(self, "Knock Mode Expired!", NotificationManager.MessageType.WARNING) + if powerup_manager: + powerup_manager.reset_boost() + # Immunity Timer Logic if immunity_timer > 0: immunity_timer -= delta if immunity_timer <= 0: immunity_timer = 0 - _apply_tint_recursive(self , Color.WHITE) # Remove immunity tint + _apply_tint_recursive(self, Color.WHITE) # Remove immunity tint # Slow Timer Logic if slow_timer > 0: @@ -2445,6 +2461,8 @@ var is_knock_mode: bool = false: set(value): if is_knock_mode == value: return is_knock_mode = value + if is_knock_mode: + attack_mode_timer = MAX_ATTACK_MODE_TIME _refresh_player_visuals() func enter_attack_mode(): diff --git a/scripts/managers/sfx_manager.gd b/scripts/managers/sfx_manager.gd index d25ddbd..c485bc8 100644 --- a/scripts/managers/sfx_manager.gd +++ b/scripts/managers/sfx_manager.gd @@ -15,10 +15,16 @@ func _load_sounds(): dir.list_dir_begin() var file_name = dir.get_next() while file_name != "": - if not dir.current_is_dir() and (file_name.ends_with(".mp3") or file_name.ends_with(".wav") or file_name.ends_with(".ogg")): - var base_name = file_name.get_basename() - sounds[base_name] = load(sfx_path + file_name) - # print("[SfxManager] Loaded: ", base_name) + if not dir.current_is_dir(): + # Remove .import extension if it exists (Godot adds this to imported resources in exported builds) + var clean_name = file_name.replace(".import", "") + + if clean_name.ends_with(".mp3") or clean_name.ends_with(".wav") or clean_name.ends_with(".ogg"): + var base_name = clean_name.get_basename() # Ex: "jump.wav" -> "jump" + # Only load the original extension, Godot's resource loader handles the .import remapping automatically. + if not sounds.has(base_name): + sounds[base_name] = load(sfx_path + clean_name) + file_name = dir.get_next() dir.list_dir_end() else: