extends SceneTree const MainScene: PackedScene = preload("res://main/main.tscn") const TEST_PORT: int = 18152 const PREJOIN_MESSAGE: String = "private prejoin message" const LIVE_MESSAGE: String = "live message after join" const HOST_ROLEPLAY: String = "/me grabs [b]his fishing rod[/b]" const CLIENT_ROLEPLAY: String = "/me waves [color=red]hello[/color]" func _initialize() -> void: call_deferred("_run") func _run() -> void: var arguments: PackedStringArray = OS.get_cmdline_user_args() if arguments.has("host"): await _run_host() return if arguments.has("client"): await _run_client() return push_error("Chat privacy validation needs host or client mode.") quit(1) func _run_host() -> void: var main: Node = await _create_initialized_main() var session := main.get_node("%NetworkSession") as NetworkSession var save_manager := main.get("_save_manager") as PlayerSaveManager assert(bool(main.call( "_apply_world", WorldLayout.STARTER_ISLAND, 1, true ))) assert(session.set_host_world(WorldLayout.STARTER_ISLAND, 1)) assert(session.start_private_host(TEST_PORT)) assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") for _frame: int in 4: await physics_frame assert(session.set_host_open(true)) var chat_service := main.get_node( "%NetworkChatService" ) as NetworkChatService assert(not chat_service.send_local_message("/me")) assert(chat_service.send_local_message(PREJOIN_MESSAGE)) var remote_peer_id: int = await _wait_for_remote_peer(session) assert(remote_peer_id > 1) await create_timer(1.0).timeout assert(chat_service.send_local_message(LIVE_MESSAGE)) assert(chat_service.send_local_message(HOST_ROLEPLAY)) var client_roleplay_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < client_roleplay_deadline and not _history_contains(chat_service, CLIENT_ROLEPLAY) ): await process_frame assert(_history_contains(chat_service, CLIENT_ROLEPLAY)) var client_roleplay: Dictionary = _history_message( chat_service, CLIENT_ROLEPLAY ) assert(int(client_roleplay.get("sender_peer_id", 0)) == remote_peer_id) assert( str(client_roleplay.get("sender_display_name", "")) == session.get_peer_record(remote_peer_id).display_name ) _assert_roleplay_rendered( main, str(client_roleplay["sender_display_name"]), "waves [color=red]hello[/color]", ) _assert_no_world_speech(main, remote_peer_id) var disconnect_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < disconnect_deadline and session.is_authenticated_peer(remote_peer_id) ): await process_frame assert(not session.is_authenticated_peer(remote_peer_id)) print("Chat privacy multiplayer host validation: PASS") await _session_cleanup(main, session) func _run_client() -> void: var main: Node = await _create_initialized_main() 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 while Time.get_ticks_msec() < join_deadline: await process_frame if session.state == NetworkSession.State.VERIFYING_SERVER_IDENTITY: main.call("_confirm_server_trust") if session.is_joined_client() and bool(main.get("_gameplay_started")): break assert(session.is_joined_client()) var chat_service := main.get_node( "%NetworkChatService" ) as NetworkChatService await create_timer(0.5).timeout assert(not _history_contains(chat_service, PREJOIN_MESSAGE)) var live_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < live_deadline and ( not _history_contains(chat_service, LIVE_MESSAGE) or not _history_contains(chat_service, HOST_ROLEPLAY) ) ): await process_frame assert(_history_contains(chat_service, LIVE_MESSAGE)) assert(_history_contains(chat_service, HOST_ROLEPLAY)) assert(not _history_contains(chat_service, PREJOIN_MESSAGE)) var host_roleplay: Dictionary = _history_message( chat_service, HOST_ROLEPLAY ) var host_peer_id: int = int(host_roleplay.get("sender_peer_id", 0)) assert(host_peer_id > 0) assert( str(host_roleplay.get("sender_display_name", "")) == session.get_peer_record(host_peer_id).display_name ) _assert_roleplay_rendered( main, str(host_roleplay["sender_display_name"]), "grabs [b]his fishing rod[/b]", ) _assert_world_speech_body(main, host_peer_id, LIVE_MESSAGE) assert(chat_service.send_local_message(CLIENT_ROLEPLAY)) var confirmation_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < confirmation_deadline and not _history_contains(chat_service, CLIENT_ROLEPLAY) ): await process_frame assert(_history_contains(chat_service, CLIENT_ROLEPLAY)) _assert_no_world_speech(main, session.get_local_peer_id()) print("Chat privacy multiplayer client validation: PASS") await _session_cleanup(main, session) func _history_contains(service: NetworkChatService, body: String) -> bool: return service.get_history().any( func(message: Dictionary) -> bool: return str(message.get("body", "")) == body ) func _history_message( service: NetworkChatService, body: String, ) -> Dictionary: for message: Dictionary in service.get_history(): if str(message.get("body", "")) == body: return message return {} func _assert_roleplay_rendered( main: Node, display_name: String, action: String, ) -> void: var chat_ui := main.get_node("%GameUI").get_node("%ChatUI") as ChatUI var history := chat_ui.get("_history") as RichTextLabel assert(history != null) var expected: String = "%s %s" % [display_name, action] assert(history.get_parsed_text().contains(expected)) assert(not history.get_parsed_text().contains( "%s: /me" % display_name )) assert(not history.bbcode_enabled) var italic_font := history.get_theme_font("italics_font") as FontVariation assert(italic_font != null) assert(not is_zero_approx(italic_font.variation_transform.y.x)) func _assert_no_world_speech(main: Node, peer_id: int) -> void: var chat_ui := main.get_node("%GameUI").get_node("%ChatUI") as ChatUI var speech: Dictionary = chat_ui.get("_speech") assert(not speech.has(peer_id)) func _assert_world_speech_body( main: Node, peer_id: int, expected_body: String, ) -> void: var chat_ui := main.get_node("%GameUI").get_node("%ChatUI") as ChatUI var speech: Dictionary = chat_ui.get("_speech") assert(speech.has(peer_id)) var state: Dictionary = speech[peer_id] var bubble := state.get("bubble") as PanelContainer assert(bubble != null) for child: Node in bubble.get_children(): if child is Label: assert((child as Label).text == expected_body) return assert(false, "Speech bubble label is missing.") func _create_initialized_main() -> Node: root.size = Vector2i(1280, 720) var main: Node = MainScene.instantiate() root.add_child(main) for _frame: int in 4: await process_frame if not bool(main.get("_application_initialized")): main.call("_activate_selected_data_path", "", true) for _frame: int in 8: await process_frame assert(bool(main.get("_application_initialized"))) return main func _wait_for_remote_peer(session: NetworkSession) -> int: var deadline: int = Time.get_ticks_msec() + 20000 while Time.get_ticks_msec() < deadline: await process_frame for peer_id: int in session.get_authenticated_peer_ids(): if peer_id != session.get_local_peer_id(): return peer_id return 0 func _session_cleanup(main: Node, session: NetworkSession) -> void: session.disconnect_session("") main.queue_free() for _frame: int in 4: await process_frame await create_timer(0.1).timeout quit()