extends SceneTree const MainScene: PackedScene = preload("res://main/main.tscn") const TEST_PORT: int = 18222 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("Home spawn 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 network_home := main.get_node( "%NetworkHomeService" ) as NetworkHomeService var home_world := main.get_node("%HomeWorldService") as HomeWorldService var spawn_service := main.get_node( "%PlayerSpawnService" ) as PlayerSpawnService var save_manager := main.get("_save_manager") as PlayerSaveManager var home_state := main.get_node("%PlayerHomeState") as PlayerHomeState assert(bool(main.call( "_apply_world", WorldLayout.STARTER_ISLAND, 1, true ))) assert(bool(main.call( "_prepare_host_world", WorldLayout.STARTER_ISLAND, 1 ))) assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) assert(session.start_private_host(TEST_PORT)) main.call("_enter_gameplay") assert(session.set_host_open(true)) var host_fingerprint: String = session.get_local_identity_fingerprint() assert(await _wait_for_home(network_home, host_fingerprint)) var host_avatar: Player = spawn_service.get_avatar(1) var host_spawn: Transform3D = home_world.get_exterior_spawn_transform( host_fingerprint ) assert(_same_door_position(host_avatar.global_position, host_spawn.origin)) assert(_faces_away_from_home(host_avatar, home_world, host_fingerprint)) assert(network_home.request_enter(host_fingerprint)) await physics_frame host_avatar.global_position = home_world.get_interior_exit_position( host_fingerprint ) assert(network_home.request_exit()) await physics_frame assert(_same_door_position(host_avatar.global_position, host_spawn.origin)) assert(_faces_away_from_home(host_avatar, home_world, host_fingerprint)) var upgrade_result_id := "home-test-upgrade" network_home.authorize_home_upgrade( session.get_local_peer_id(), PlayerHomeState.STARTER_RV_TIER, upgrade_result_id, ) assert(home_state.upgrade_to_next_tier()) var rv_deadline: int = Time.get_ticks_msec() + 5000 while ( Time.get_ticks_msec() < rv_deadline and home_world.get_interior_exit_positions(host_fingerprint).size() < 2 ): await process_frame var rv_exits: Array[Vector3] = home_world.get_interior_exit_positions( host_fingerprint ) assert(rv_exits.size() == 2) network_home.finalize_home_upgrade( session.get_local_peer_id(), upgrade_result_id, true ) assert(rv_exits[0].x < rv_exits[1].x) for exit_position: Vector3 in rv_exits: host_avatar.global_position = ( exit_position + home_world.get_interior_exit_direction_near( host_fingerprint, exit_position ) * 0.05 ) assert(home_world.has_avatar_crossed_interior_exit_threshold( host_avatar, host_fingerprint )) host_avatar.global_position = rv_exits[0] + Vector3(0.0, 0.0, 1.1) assert(not home_world.has_avatar_crossed_interior_exit_threshold( host_avatar, host_fingerprint )) var presentations: Dictionary = home_world.get( "_presentations_by_fingerprint" ) var host_presentation: Dictionary = presentations.get(host_fingerprint, {}) var host_interior := host_presentation.get("interior") as Node3D assert(host_interior != null) var wall_collision := host_interior.find_child( "WallWestCollision", true, false ) as StaticBody3D assert(wall_collision != null) assert(bool(wall_collision.get_meta( HomeWorldService.INTERIOR_SHELL_COLLISION_META, false ))) var wall_shape := wall_collision.get_child(0) as CollisionShape3D assert(wall_shape != null) assert((wall_shape.shape as ConcavePolygonShape3D).backface_collision) host_avatar.global_position = host_spawn.origin assert(network_home.request_enter(host_fingerprint)) await physics_frame var remote_peer_id: int = await _wait_for_remote_peer(session) assert(remote_peer_id > 1) var remote_record := session.get_peer_record(remote_peer_id) var remote_fingerprint: String = remote_record.identity_fingerprint assert(await _wait_for_home(network_home, remote_fingerprint)) var remote_avatar: Player = spawn_service.get_avatar(remote_peer_id) assert(not remote_avatar.is_remote_presentation_visible()) var chat_ui := main.get_node("%GameUI").get_node("%ChatUI") as ChatUI chat_ui.call( "_show_speech_bubble", remote_peer_id, "outside", remote_fingerprint ) var speech_states: Dictionary = chat_ui.get("_speech") assert(not speech_states.has(remote_peer_id)) var toolbar := main.get_node("%GameUI").get_node( "%HomeDecorToolbar" ) as HomeDecorToolbar assert(toolbar.visible) var exit_button := toolbar.find_child( "ExitRVButton", true, false ) as Button assert(exit_button != null and exit_button.text == "Exit RV") var storage := main.get_node("%GameUI").get_node( "%PlayerStorage" ) as PlayerStorage assert(storage.open_storage_from_home(true)) var close_storage_event := InputEventAction.new() close_storage_event.action = &"interact" close_storage_event.pressed = true storage._unhandled_input(close_storage_event) assert(not storage.visible) exit_button.pressed.emit() var exit_deadline: int = Time.get_ticks_msec() + 5000 while ( Time.get_ticks_msec() < exit_deadline and not network_home.get_local_home_owner_fingerprint().is_empty() ): await process_frame assert(network_home.get_local_home_owner_fingerprint().is_empty()) var visibility_deadline: int = Time.get_ticks_msec() + 1000 while ( Time.get_ticks_msec() < visibility_deadline and is_instance_valid(remote_avatar) and not remote_avatar.is_remote_presentation_visible() ): await process_frame assert(is_instance_valid(remote_avatar)) assert(remote_avatar.is_remote_presentation_visible()) chat_ui.call( "_show_speech_bubble", remote_peer_id, "inside", remote_fingerprint ) speech_states = chat_ui.get("_speech") assert(speech_states.has(remote_peer_id)) chat_ui.call("_on_peer_removed", remote_peer_id) var remote_spawn: Transform3D = home_world.get_exterior_spawn_transform( remote_fingerprint ) assert(_same_door_position( remote_avatar.global_position, remote_spawn.origin )) assert(_faces_away_from_home( remote_avatar, home_world, remote_fingerprint )) var disconnect_deadline: int = Time.get_ticks_msec() + 12000 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("Home spawn multiplayer host validation: PASS") await _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 network_home := main.get_node( "%NetworkHomeService" ) as NetworkHomeService var home_world := main.get_node("%HomeWorldService") as HomeWorldService var deadline: int = Time.get_ticks_msec() + 20000 while Time.get_ticks_msec() < 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 fingerprint: String = session.get_local_identity_fingerprint() assert(await _wait_for_home(network_home, fingerprint)) var player := main.get("_player") as Player var exterior_spawn: Transform3D = ( home_world.get_exterior_spawn_transform(fingerprint) ) deadline = Time.get_ticks_msec() + 5000 while ( Time.get_ticks_msec() < deadline and not _same_door_position( player.global_position, exterior_spawn.origin ) ): await physics_frame assert(_same_door_position( player.global_position, exterior_spawn.origin )) assert(_faces_away_from_home(player, home_world, fingerprint)) # Keep the peer present long enough for the host to rebuild the upgraded RV, # exercise late-spawn visibility, and use the toolbar transition back to the # shared world. A two-second grace period raced the host on slower machines. await create_timer(10.0).timeout print("Home spawn multiplayer client validation: PASS") await _cleanup(main, session) 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_home( network_home: NetworkHomeService, fingerprint: String, ) -> bool: var deadline: int = Time.get_ticks_msec() + 10000 while Time.get_ticks_msec() < deadline: await process_frame var manifests: Dictionary = network_home.get( "_manifests_by_fingerprint" ) if manifests.has(fingerprint): return true return false func _faces_away_from_home( avatar: Player, home_world: HomeWorldService, fingerprint: String, ) -> bool: var exterior_basis: Basis = home_world.get_exterior_transform( fingerprint ).basis var outward_direction := exterior_basis.z outward_direction.y = 0.0 if outward_direction.is_zero_approx(): return false return avatar.get_facing_direction().dot( outward_direction.normalized() ) >= 0.99 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 _same_door_position(first: Vector3, second: Vector3) -> bool: return ( Vector2(first.x, first.z).distance_to(Vector2(second.x, second.z)) < 0.08 and absf(first.y - second.y) < 0.3 ) func _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()