diff --git a/assets/sfx/attack_mode.mp3 b/assets/sfx/attack_mode.mp3 new file mode 100644 index 0000000..3d2baa3 Binary files /dev/null and b/assets/sfx/attack_mode.mp3 differ diff --git a/assets/sfx/attack_mode.mp3.import b/assets/sfx/attack_mode.mp3.import new file mode 100644 index 0000000..1c80ce1 --- /dev/null +++ b/assets/sfx/attack_mode.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://eus0u884fbx0" +path="res://.godot/imported/attack_mode.mp3-0b135ca9e2806fb808e84a27495143a5.mp3str" + +[deps] + +source_file="res://assets/sfx/attack_mode.mp3" +dest_files=["res://.godot/imported/attack_mode.mp3-0b135ca9e2806fb808e84a27495143a5.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/complete_mission.mp3 b/assets/sfx/complete_mission.mp3 new file mode 100644 index 0000000..2d1f7c0 Binary files /dev/null and b/assets/sfx/complete_mission.mp3 differ diff --git a/assets/sfx/complete_mission.mp3.import b/assets/sfx/complete_mission.mp3.import new file mode 100644 index 0000000..8d53cbc --- /dev/null +++ b/assets/sfx/complete_mission.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://c6fpsw4tpjw1q" +path="res://.godot/imported/complete_mission.mp3-cb0f023b70f39023e3479885699ef187.mp3str" + +[deps] + +source_file="res://assets/sfx/complete_mission.mp3" +dest_files=["res://.godot/imported/complete_mission.mp3-cb0f023b70f39023e3479885699ef187.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/freeze.mp3 b/assets/sfx/freeze.mp3 new file mode 100644 index 0000000..d32b785 Binary files /dev/null and b/assets/sfx/freeze.mp3 differ diff --git a/assets/sfx/freeze.mp3.import b/assets/sfx/freeze.mp3.import new file mode 100644 index 0000000..b73a635 --- /dev/null +++ b/assets/sfx/freeze.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://pygys4jw3su5" +path="res://.godot/imported/freeze.mp3-3b15793bd5b06d0cac66fb3ec33dc577.mp3str" + +[deps] + +source_file="res://assets/sfx/freeze.mp3" +dest_files=["res://.godot/imported/freeze.mp3-3b15793bd5b06d0cac66fb3ec33dc577.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/generate_tile.mp3 b/assets/sfx/generate_tile.mp3 new file mode 100644 index 0000000..5727d22 Binary files /dev/null and b/assets/sfx/generate_tile.mp3 differ diff --git a/assets/sfx/generate_tile.mp3.import b/assets/sfx/generate_tile.mp3.import new file mode 100644 index 0000000..e02b606 --- /dev/null +++ b/assets/sfx/generate_tile.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://dneck8vnrnile" +path="res://.godot/imported/generate_tile.mp3-4ed133faf48f1eb79df726744beaa7cf.mp3str" + +[deps] + +source_file="res://assets/sfx/generate_tile.mp3" +dest_files=["res://.godot/imported/generate_tile.mp3-4ed133faf48f1eb79df726744beaa7cf.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/ghost.mp3 b/assets/sfx/ghost.mp3 new file mode 100644 index 0000000..92bb5b8 Binary files /dev/null and b/assets/sfx/ghost.mp3 differ diff --git a/assets/sfx/ghost.mp3.import b/assets/sfx/ghost.mp3.import new file mode 100644 index 0000000..82c3e26 --- /dev/null +++ b/assets/sfx/ghost.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://de43014odrwp3" +path="res://.godot/imported/ghost.mp3-bd8b4d0f9a077f1b19cffed2cae4c5ab.mp3str" + +[deps] + +source_file="res://assets/sfx/ghost.mp3" +dest_files=["res://.godot/imported/ghost.mp3-bd8b4d0f9a077f1b19cffed2cae4c5ab.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/pick_up_power_tile.mp3 b/assets/sfx/pick_up_power_tile.mp3 new file mode 100644 index 0000000..90bf449 Binary files /dev/null and b/assets/sfx/pick_up_power_tile.mp3 differ diff --git a/assets/sfx/pick_up_power_tile.mp3.import b/assets/sfx/pick_up_power_tile.mp3.import new file mode 100644 index 0000000..a997549 --- /dev/null +++ b/assets/sfx/pick_up_power_tile.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://nlec4kxa0p48" +path="res://.godot/imported/pick_up_power_tile.mp3-b6e12284e41b442fab8687001c2a15d1.mp3str" + +[deps] + +source_file="res://assets/sfx/pick_up_power_tile.mp3" +dest_files=["res://.godot/imported/pick_up_power_tile.mp3-b6e12284e41b442fab8687001c2a15d1.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/pick_up_tekton_roaming.mp3 b/assets/sfx/pick_up_tekton_roaming.mp3 new file mode 100644 index 0000000..19f8e12 Binary files /dev/null and b/assets/sfx/pick_up_tekton_roaming.mp3 differ diff --git a/assets/sfx/pick_up_tekton_roaming.mp3.import b/assets/sfx/pick_up_tekton_roaming.mp3.import new file mode 100644 index 0000000..3a21de1 --- /dev/null +++ b/assets/sfx/pick_up_tekton_roaming.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://8ctpkvemgvmt" +path="res://.godot/imported/pick_up_tekton_roaming.mp3-fcbc7bdfbf724091b491495057817c9b.mp3str" + +[deps] + +source_file="res://assets/sfx/pick_up_tekton_roaming.mp3" +dest_files=["res://.godot/imported/pick_up_tekton_roaming.mp3-fcbc7bdfbf724091b491495057817c9b.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/speed.mp3 b/assets/sfx/speed.mp3 new file mode 100644 index 0000000..345df59 Binary files /dev/null and b/assets/sfx/speed.mp3 differ diff --git a/assets/sfx/speed.mp3.import b/assets/sfx/speed.mp3.import new file mode 100644 index 0000000..fbfc1e1 --- /dev/null +++ b/assets/sfx/speed.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://cilkb27sd2431" +path="res://.godot/imported/speed.mp3-476d426c4b0e9131451d3b90adb24b49.mp3str" + +[deps] + +source_file="res://assets/sfx/speed.mp3" +dest_files=["res://.godot/imported/speed.mp3-476d426c4b0e9131451d3b90adb24b49.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/tile_scatter.mp3 b/assets/sfx/tile_scatter.mp3 new file mode 100644 index 0000000..463e977 Binary files /dev/null and b/assets/sfx/tile_scatter.mp3 differ diff --git a/assets/sfx/tile_scatter.mp3.import b/assets/sfx/tile_scatter.mp3.import new file mode 100644 index 0000000..394154e --- /dev/null +++ b/assets/sfx/tile_scatter.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://cpgg54uaogaaq" +path="res://.godot/imported/tile_scatter.mp3-73bb39bcbdf43b4782c4b8f6a040f991.mp3str" + +[deps] + +source_file="res://assets/sfx/tile_scatter.mp3" +dest_files=["res://.godot/imported/tile_scatter.mp3-73bb39bcbdf43b4782c4b8f6a040f991.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/sfx/wall.mp3 b/assets/sfx/wall.mp3 new file mode 100644 index 0000000..d6c4d5b Binary files /dev/null and b/assets/sfx/wall.mp3 differ diff --git a/assets/sfx/wall.mp3.import b/assets/sfx/wall.mp3.import new file mode 100644 index 0000000..1939a57 --- /dev/null +++ b/assets/sfx/wall.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://wf533jrlmq74" +path="res://.godot/imported/wall.mp3-bb14cf5f4b11510e8b6a52b9795ed55d.mp3str" + +[deps] + +source_file="res://assets/sfx/wall.mp3" +dest_files=["res://.godot/imported/wall.mp3-bb14cf5f4b11510e8b6a52b9795ed55d.mp3str"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..1709c53 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,15 @@ +[gd_resource type="AudioBusLayout" format=3 uid="uid://c6xk1v1m4o5r4"] + +[resource] +bus/1/name = &"Music" +bus/1/solo = false +bus/1/mute = false +bus/1/bypass_fx = false +bus/1/volume_db = 0.0 +bus/1/send = &"Master" +bus/2/name = &"SFX" +bus/2/solo = false +bus/2/mute = false +bus/2/bypass_fx = false +bus/2/volume_db = 0.0 +bus/2/send = &"Master" diff --git a/project.godot b/project.godot index b6032df..f6e06bf 100644 --- a/project.godot +++ b/project.godot @@ -12,6 +12,10 @@ config_version=5 compatibility/default_parent_skeleton_in_mesh_instance_3d=true +[audio] + +buses/default_bus_layout="res://default_bus_layout.tres" + [application] config/name="tekton-local" @@ -33,6 +37,7 @@ PlayerManager="*res://scripts/managers/player_manager.gd" GoalsCycleManager="*res://scripts/managers/goals_cycle_manager.gd" Satori="*uid://b8vev00s34b7" SettingsManager="*uid://c1ouaaqnn0lrc" +SfxManager="*res://scripts/managers/sfx_manager.gd" [display] diff --git a/scenes/lobby.gd b/scenes/lobby.gd index f471831..99e780d 100644 --- a/scenes/lobby.gd +++ b/scenes/lobby.gd @@ -238,19 +238,29 @@ func _on_server_option_selected(index: int) -> void: if server_ip_input: server_ip_input.visible = false NakamaManager.set_server("localhost") LobbyManager.is_lan_mode = false + connection_status.text = "Mode: Local Testing (Nakama)" elif index == 1: # Nakama Remote - if server_ip_input: server_ip_input.visible = true + if server_ip_input: + server_ip_input.visible = true + server_ip_input.placeholder_text = "IP (100.x) or Tailscale Funnel URL..." if server_ip_input: NakamaManager.set_server(server_ip_input.text) LobbyManager.is_lan_mode = false + connection_status.text = "Mode: Online (Nakama Remote)" else: # LAN Direct if server_ip_input: server_ip_input.visible = false LobbyManager.is_lan_mode = true + connection_status.text = "Mode: LAN Direct (No Server)" func _on_server_ip_submitted(new_text: String) -> void: if server_option and server_option.selected == 1: - NakamaManager.set_server(new_text.strip_edges()) + var host = new_text.strip_edges() + NakamaManager.set_server(host) + if host.ends_with(".ts.net"): + connection_status.text = "Using Tailscale Funnel: " + host + else: + connection_status.text = "Server IP updated: " + host func _setup_game_modes() -> void: if not game_mode_option: return diff --git a/scenes/player.gd b/scenes/player.gd index f4e59ad..8ccd5d7 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -2099,6 +2099,7 @@ func sync_grab_tekton(tekton_path: NodePath): if is_attack_mode: is_attack_mode = false + SfxManager.play("pick_up_tekton_roaming") print("[Player %s] Grabbed Tekton %s" % [name, tekton.name]) func throw_tekton(): @@ -2333,6 +2334,7 @@ func sync_knock_tekton(tekton_path: NodePath): # Intensity 2.0 for knock (drops 200% tiles) + Shrink/Recover # Use on_thrown_landing to trigger shrink animation and floor freeze tekton.on_thrown_landing(self , 2.0) + SfxManager.play("attack_mode") print("[Player %s] Knocked Tekton %s" % [name, tekton.name]) # Visual feedback (Juice) diff --git a/scripts/managers/goals_cycle_manager.gd b/scripts/managers/goals_cycle_manager.gd index 3ee1aa3..c79c3dc 100644 --- a/scripts/managers/goals_cycle_manager.gd +++ b/scripts/managers/goals_cycle_manager.gd @@ -254,6 +254,7 @@ func on_goal_completed(player: Node, time_remaining: float): # Randomize 9 tiles around player _randomize_tiles_around_player(player) + SfxManager.rpc("play_rpc", "complete_mission") print("[GoalsCycle] Player %d completed goal! +%d points (base: %d, time bonus: %d)" % [peer_id, score_earned, BASE_SCORE, time_bonus]) @rpc("authority", "call_local", "reliable") diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index e556751..172fa27 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -211,8 +211,10 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: # Visual Feedback: Attack Bump if _can_rpc(): player.rpc("sync_bump", target_pos, false) # Attack bump + SfxManager.rpc("play_rpc", "attack_mode") elif player.has_method("sync_bump"): player.sync_bump(target_pos, false) + SfxManager.play("attack_mode") # 1. 3-Floor Knockback towards Starting Line (X=0) var push_direction = Vector2i(-1, 0) # Backwards diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index 4859193..323d533 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -95,6 +95,7 @@ func grab_item(grid_position: Vector2i) -> bool: if special_tiles_manager: special_tiles_manager.add_powerup_from_item(item) + SfxManager.play("pick_up_power_tile") # Animation for powerup? # ... diff --git a/scripts/managers/sfx_manager.gd b/scripts/managers/sfx_manager.gd new file mode 100644 index 0000000..d25ddbd --- /dev/null +++ b/scripts/managers/sfx_manager.gd @@ -0,0 +1,45 @@ +extends Node + +# SFXManager - Global singleton for playing sound effects +# Autoloaded as "SfxManager" + +var sounds: Dictionary = {} + +func _ready(): + _load_sounds() + +func _load_sounds(): + var sfx_path = "res://assets/sfx/" + var dir = DirAccess.open(sfx_path) + if dir: + 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) + file_name = dir.get_next() + dir.list_dir_end() + else: + push_error("[SfxManager] Could not open sfx directory: " + sfx_path) + +func play(sound_name: String, pitch_range: float = 0.0): + if not sounds.has(sound_name): + # push_warning("[SfxManager] Sound not found: " + sound_name) + return + + var player = AudioStreamPlayer.new() + add_child(player) + player.stream = sounds[sound_name] + player.bus = "SFX" + + if pitch_range > 0: + player.pitch_scale = 1.0 + randf_range(-pitch_range, pitch_range) + + player.play() + player.finished.connect(player.queue_free) + +@rpc("any_peer", "call_local", "unreliable") +func play_rpc(sound_name: String, pitch_range: float = 0.0): + play(sound_name, pitch_range) diff --git a/scripts/managers/sfx_manager.gd.uid b/scripts/managers/sfx_manager.gd.uid new file mode 100644 index 0000000..bb007c4 --- /dev/null +++ b/scripts/managers/sfx_manager.gd.uid @@ -0,0 +1 @@ +uid://dqq36uawrnpwd diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index cb81805..b997020 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -262,6 +262,7 @@ func _execute_faster_speed(): if player.movement_manager: player.movement_manager.set_speed_multiplier(1.5) # 50% faster active_buffs[SpecialEffect.FASTER_SPEED] = FASTER_DURATION + SfxManager.rpc("play_rpc", "speed") NotificationManager.send_message(player, "Speed Boost! (5s)", NotificationManager.MessageType.POWERUP) func _execute_area_freeze(target_pos: Vector2i = Vector2i.ZERO): @@ -317,6 +318,8 @@ func _execute_area_freeze(target_pos: Vector2i = Vector2i.ZERO): NotificationManager.send_message(p, "Caught in Freeze Zone!", NotificationManager.MessageType.WARNING) if p != player: # Don't score for freezing self (unless desired?) - Assuming enemies hit_count += 1 + + SfxManager.rpc("play_rpc", "freeze") if hit_count > 0 and player.is_multiplayer_authority(): var is_sng = LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO) @@ -418,6 +421,7 @@ func _execute_block_floor(target_pos: Vector2i = Vector2i.ZERO): main.rpc("sync_grid_items_batch", batch_data) # Notify + SfxManager.rpc("play_rpc", "wall") NotificationManager.send_message(player, "Defensive Wall Deployed!", NotificationManager.MessageType.POWERUP) func _execute_invisible_mode(target: Node3D): @@ -428,6 +432,7 @@ func _execute_invisible_mode(target: Node3D): if target.has_method("sync_modulate"): target.rpc("sync_modulate", Color(1.0, 1.0, 1.0, 0.4)) # 40% Opacity + SfxManager.rpc("play_rpc", "ghost") NotificationManager.send_message(target, "Invisible Mode!", NotificationManager.MessageType.POWERUP) @@ -438,6 +443,7 @@ func _execute_invisible_mode(target: Node3D): func spawn_powerups_around(center: Vector2i, force_powerups: bool = true, only_common: bool = false, full_density: bool = false): # "spawn / replace your nearby tiles into power up ( special tiles )" # New PowerUp Tiles are 11, 12, 13, 14 + SfxManager.rpc("play_rpc", "generate_tile") var radius = 2 for x in range(-radius, radius + 1): for y in range(-radius, radius + 1): diff --git a/scripts/managers/stop_n_go_manager.gd b/scripts/managers/stop_n_go_manager.gd index 6cd27dd..1275b2d 100644 --- a/scripts/managers/stop_n_go_manager.gd +++ b/scripts/managers/stop_n_go_manager.gd @@ -586,6 +586,7 @@ func _scatter_player_tiles(player_node: Node): main.rpc("sync_playerboard", peer_id, playerboard) # Notify the player + SfxManager.rpc("play_rpc", "tile_scatter") NotificationManager.send_message(player_node, "Not in Safe Zone! Tiles scattered!", NotificationManager.MessageType.WARNING) # Screen shake diff --git a/scripts/nakama_manager.gd b/scripts/nakama_manager.gd index b7c740c..78a9625 100644 --- a/scripts/nakama_manager.gd +++ b/scripts/nakama_manager.gd @@ -32,29 +32,48 @@ func _init_client(): client = Nakama.create_client(nakama_server_key, nakama_host, nakama_port, nakama_scheme) func set_server(host: String, port: int = 7350): - nakama_host = host + # Clean up the host string + var clean_host = host.strip_edges() - # Auto-detect secure tunnels (Tailscale, ngrok, playit, cloudflare) - if host.ends_with(".ts.net") or host.ends_with(".gg") or host.begins_with("https://"): - if host.begins_with("https://"): - nakama_host = host.replace("https://", "") - if host.begins_with("http://"): - nakama_host = host.replace("http://", "") - if nakama_host.ends_with("/"): - nakama_host = nakama_host.substr(0, nakama_host.length() - 1) - - nakama_port = 443 + # Extract protocol if present (override everything else) + var forced_scheme = "" + if clean_host.begins_with("https://"): + forced_scheme = "https" + clean_host = clean_host.replace("https://", "") + elif clean_host.begins_with("http://"): + forced_scheme = "http" + clean_host = clean_host.replace("http://", "") + + # Handle trailing slashes + if clean_host.ends_with("/"): + clean_host = clean_host.substr(0, clean_host.length() - 1) + + # Extract port if explicitly provided in host string (e.g. host:port) + var explicit_port = -1 + if ":" in clean_host: + var parts = clean_host.split(":") + clean_host = parts[0] + explicit_port = parts[1].to_int() + + # DETECT SETTINGS + nakama_host = clean_host + + if forced_scheme != "": + nakama_scheme = forced_scheme + nakama_port = explicit_port if explicit_port != -1 else (443 if forced_scheme == "https" else port) + elif clean_host.ends_with(".ts.net") and explicit_port == -1: + # Tailscale Funnel Case (No port provided, .ts.net domain) nakama_scheme = "https" - else: - # Extract port if they typed something like 192.168.1.1:7350 - if ":" in host and not host.begins_with("http"): - var parts = host.split(":") - nakama_host = parts[0] - nakama_port = parts[1].to_int() - else: - nakama_port = port + nakama_port = 443 + elif clean_host.begins_with("100."): + # Standard Tailscale IP Case nakama_scheme = "http" - + nakama_port = explicit_port if explicit_port != -1 else port + else: + # Generic Case (e.g. localhost, public IP) + nakama_scheme = "http" + nakama_port = explicit_port if explicit_port != -1 else port + _init_client() print("[NakamaManager] Server updated to: ", nakama_scheme, "://", nakama_host, ":", nakama_port)