diff --git a/network/network_profile_service.gd b/network/network_profile_service.gd index aa787af..da26d86 100644 --- a/network/network_profile_service.gd +++ b/network/network_profile_service.gd @@ -625,6 +625,15 @@ func _on_session_state_changed(state: NetworkSession.State) -> void: _latest_check_name = "" _session.set_local_appearance_snapshot(_appearance_store.get_snapshot()) _apply_to_avatar(1, _appearance_store.get_snapshot()) + return + if state in [ + NetworkSession.State.PRIVATE_HOST, + NetworkSession.State.OPEN_HOST, + NetworkSession.State.JOINED_CLIENT, + ]: + var local_peer_id := _session.get_local_peer_id() + _apply_to_avatar(local_peer_id, _appearance_store.get_snapshot()) + _apply_local_voice_to_avatar() func _emit_conflict_result(data: Dictionary) -> void: diff --git a/tests/profile_multiplayer_validation.gd b/tests/profile_multiplayer_validation.gd index fc25665..44fbfe5 100644 --- a/tests/profile_multiplayer_validation.gd +++ b/tests/profile_multiplayer_validation.gd @@ -1,7 +1,9 @@ extends SceneTree const MainScene: PackedScene = preload("res://main/main.tscn") +const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd") const TEST_PORT: int = 18144 +const HOST_VOICE_MESSAGE: String = "remote voice playback" func _initialize() -> void: @@ -23,8 +25,24 @@ func _run() -> void: func _run_host() -> void: var main: Node = await _create_initialized_main() var session := main.get_node("%NetworkSession") as NetworkSession + var profile := main.get_node( + "%NetworkProfilePreferences" + ) as NetworkProfilePreferences + assert(profile.set_profile_identity( + profile.display_name, + "deep", + profile.speech_speed_id, + profile.call_id, + "kim", + )) var save_manager := main.get("_save_manager") as PlayerSaveManager assert(session.start_private_host(TEST_PORT)) + var local_avatar := main.get_node("%PlayerSpawnService").get_avatar( + 1 + ) as Player + assert(local_avatar != null) + assert(local_avatar.get_animalese_voice_id() == "deep") + assert(local_avatar.get_animalese_sample_set_id() == "kim") assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") for _frame: int in 4: @@ -58,6 +76,11 @@ func _run_host() -> void: assert(avatar != null) assert(avatar.appearance_snapshot == changed) assert(record.display_name != "NetworkProfileService") + assert(chat_service.send_local_message( + HOST_VOICE_MESSAGE, + profile.voice_id, + profile.sample_set_id, + )) var voice_message_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < voice_message_deadline @@ -80,6 +103,14 @@ func _run_host() -> void: func _run_client() -> void: var main: Node = await _create_initialized_main() + var chat_ui := main.get_node("%GameUI").get_node("%ChatUI") as ChatUI + var animalese := chat_ui.get("_animalese_voice") as AnimaleseVoiceType + assert(animalese != null) + var remote_voice_samples: Array[String] = [] + animalese.character_sample_played.connect( + func(sample_set_id: String, _character: String) -> void: + remote_voice_samples.append(sample_set_id) + ) main.call("_on_title_join_game_requested", "127.0.0.1:%d" % TEST_PORT) var session := main.get_node("%NetworkSession") as NetworkSession var join_deadline: int = Time.get_ticks_msec() + 20000 @@ -132,6 +163,29 @@ func _run_client() -> void: var chat_service := main.get_node( "%NetworkChatService" ) as NetworkChatService + var host_voice_messages: Array[Dictionary] = [] + for message: Dictionary in chat_service.get_history(): + if str(message.get("body", "")) == HOST_VOICE_MESSAGE: + host_voice_messages.append(message) + var host_voice_deadline: int = Time.get_ticks_msec() + 8000 + while ( + Time.get_ticks_msec() < host_voice_deadline + and ( + host_voice_messages.is_empty() + or remote_voice_samples.is_empty() + ) + ): + await process_frame + for message: Dictionary in chat_service.get_history(): + if ( + str(message.get("body", "")) == HOST_VOICE_MESSAGE + and message not in host_voice_messages + ): + host_voice_messages.append(message) + assert(not host_voice_messages.is_empty()) + assert(str(host_voice_messages[0].get("voice_id", "")) == "deep") + assert(str(host_voice_messages[0].get("sample_set_id", "")) == "kim") + assert(remote_voice_samples.has("kim")) var local_confirmations: Array[Dictionary] = [] chat_service.local_message_confirmed.connect( func(message: Dictionary) -> void: diff --git a/ui/animalese_voice.gd b/ui/animalese_voice.gd index 4562f94..b180838 100644 --- a/ui/animalese_voice.gd +++ b/ui/animalese_voice.gd @@ -1,6 +1,8 @@ class_name AnimaleseVoice extends Node +signal character_sample_played(sample_set_id: String, character: String) + const SUPPORTED_CHARACTERS := "abcdefghijklmnopqrstuvwxyz0123456789" const DEFAULT_CHARACTERS_PER_SECOND: float = 28.0 const POLYPHONY: int = 8 @@ -144,3 +146,4 @@ func _play_character( 1.45, ) _playback.play_stream(sample, 0.0, volume_db, pitch) + character_sample_played.emit(sample_set_id, normalized) diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 8c04775..da3bb59 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -322,10 +322,11 @@ func set_available(value: bool) -> void: _refresh_visibility() -func close_chat() -> void: +func close_chat(preserve_status: bool = false) -> void: var ownership_was_active: bool = _opened or _input_lock_applied _opened = false - _set_status("") + if not preserve_status: + _set_status("") _entry.virtual_keyboard_enabled = false _entry.release_focus() _entry.hide() @@ -860,37 +861,46 @@ func _handle_chat_command(body: String) -> bool: "weather": _handle_weather_command(parts) "": - _set_status("Enter a command after /.") + _show_command_error("Enter a command after /.") _: - _set_status("Unknown command: /%s" % command) + _show_command_error("Unknown command: /%s" % command) _entry.clear() _flush_draft() return true +func _show_command_error(message: String) -> void: + _set_status(message) + close_chat(true) + + func _handle_time_command(parts: PackedStringArray) -> void: if parts.size() != 2: - _set_status("Usage: /time [dawn, day, dusk, night]") + _show_command_error("Usage: /time [dawn, day, dusk, night]") return if _service == null or _world_time == null: - _set_status("World time is unavailable.") + _show_command_error("World time is unavailable.") return var phase_name := String(parts[1]).to_lower() if phase_name not in ["dawn", "day", "dusk", "night"]: - _set_status("Usage: /time [dawn, day, dusk, night]") + _show_command_error("Usage: /time [dawn, day, dusk, night]") return if not _service.request_world_time_change(phase_name): - _set_status("Only the host or an operator can change world time.") + _show_command_error( + "Only the host or an operator can change world time." + ) return close_chat() func _handle_weather_command(parts: PackedStringArray) -> void: if parts.size() != 2: - _set_status("Usage: /weather [clear, cloudy, rainy, foggy]") + _show_command_error( + "Usage: /weather [clear, cloudy, rainy, foggy]" + ) return if _service == null or _world_weather == null: - _set_status("World weather is unavailable.") + _show_command_error("World weather is unavailable.") return var weather_name := String(parts[1]).to_lower() match weather_name: @@ -899,10 +909,14 @@ func _handle_weather_command(parts: PackedStringArray) -> void: "cloudy", "rainy", "foggy": pass _: - _set_status("Usage: /weather [clear, cloudy, rainy, foggy]") + _show_command_error( + "Usage: /weather [clear, cloudy, rainy, foggy]" + ) return if not _service.request_world_weather_change(weather_name): - _set_status("Only the host or an operator can change world weather.") + _show_command_error( + "Only the host or an operator can change world weather." + ) return close_chat() @@ -1009,7 +1023,7 @@ func _show_speech_bubble( speaker_avatar.get_animalese_sample_set_id() ) _animalese_voice.speak_text( - label, + _animalese_voice, label.text, str(peer_id), voice_profile_id,