Add portable RV homes and decor prototype

This commit is contained in:
Alexander Sellite 2026-08-30 21:37:26 -04:00
parent 81decf2b71
commit fe099f47dd
56 changed files with 5459 additions and 112 deletions

View file

@ -19,9 +19,15 @@ func _run() -> void:
for _frame: int in 8:
await process_frame
assert(bool(main.get("_application_initialized")))
assert(bool(main.call(
"_apply_world", WorldLayout.STARTER_ISLAND, 1, true
)))
assert(bool(main.call(
"_prepare_host_world", WorldLayout.STARTER_ISLAND, 1
)))
assert(bool(main.call("_prepare_private_host")))
var save_manager := main.get("_save_manager") as PlayerSaveManager
assert(save_manager.initialize_new_game())
assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND))
main.call("_enter_gameplay")
await physics_frame
await physics_frame
@ -94,11 +100,7 @@ func _run() -> void:
assert(keyboard_mapping.reset_mapping())
await process_frame
var shop_prompt_key := game_ui.get_node("%ShopPromptKey") as Label
var storage_prompt_message := game_ui.get_node(
"%StoragePromptMessage"
) as Label
assert(shop_prompt_key.text == "E")
assert(storage_prompt_message.text == "E open storage")
var remapped_interact := InputEventKey.new()
remapped_interact.physical_keycode = KEY_F
remapped_interact.pressed = true
@ -108,7 +110,6 @@ func _run() -> void:
))
await process_frame
assert(shop_prompt_key.text == "F")
assert(storage_prompt_message.text == "F open storage")
assert(keyboard_mapping.reset_mapping())
await process_frame
assert(toolbar.get_parent() == chat_ui.get_parent())

View file

@ -104,7 +104,10 @@ func _run() -> void:
var hotbar_data: Dictionary = parsed["hotbar"]
assert(typeof(hotbar_data.get("fish_slots")) == TYPE_ARRAY)
assert(str((hotbar_data["fish_slots"] as Array)[1]) == fish_catch.catch_id)
assert(int(parsed["save_version"]) == 10)
assert(int(parsed["save_version"]) == 11)
assert(not PlayerHomeState.sanitize_save_data(
parsed.get("home", {})
).is_empty())
assert(
int(parsed["experience"]["total_experience"])
== 125

View file

@ -485,7 +485,7 @@ func _validate_version_four_migration() -> void:
version_four,
4,
)
assert(int(migrated.get("save_version", -1)) == 10)
assert(int(migrated.get("save_version", -1)) == 11)
assert(int((migrated["experience"] as Dictionary)["total_experience"]) == 0)
assert(
is_equal_approx(
@ -509,4 +509,7 @@ func _validate_version_four_migration() -> void:
assert(
PlayerJobService.validate_save_data(migrated.get("jobs", {}))
)
assert(not PlayerHomeState.sanitize_save_data(
migrated.get("home", {})
).is_empty())
manager.queue_free()

View file

@ -0,0 +1,178 @@
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
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(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))
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)
var remote_spawn: Transform3D = home_world.get_exterior_spawn_transform(
remote_fingerprint
)
assert(_same_door_position(
remote_avatar.global_position, remote_spawn.origin
))
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
))
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 _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()

View file

@ -0,0 +1 @@
uid://bndtkip5p6xma

View file

@ -205,8 +205,11 @@ func _run() -> void:
)
assert(bool(decoded.get("ok", false)))
var save_data: Dictionary = decoded["data"]
assert(int(save_data.get("save_version", -1)) == 10)
assert(int(save_data.get("save_version", -1)) == 11)
assert(PlayerJobService.validate_save_data(save_data.get("jobs", {})))
assert(not PlayerHomeState.sanitize_save_data(
save_data.get("home", {})
).is_empty())
_validate_pause_session_switch(main, session)
main.queue_free()

View file

@ -143,7 +143,7 @@ func _validate_save_migration() -> void:
version_five,
5,
)
assert(int(migrated.get("save_version", -1)) == 10)
assert(int(migrated.get("save_version", -1)) == 11)
var experience_data: Dictionary = migrated.get("experience", {})
assert(int(experience_data.get("total_experience", -1)) == 0)
var world_data: Dictionary = migrated.get("world", {})
@ -155,6 +155,9 @@ func _validate_save_migration() -> void:
assert(
PlayerJobService.validate_save_data(migrated.get("jobs", {}))
)
assert(not PlayerHomeState.sanitize_save_data(
migrated.get("home", {})
).is_empty())
manager.queue_free()

View file

@ -22,11 +22,24 @@ func _run() -> void:
var save_manager := main.get("_save_manager") as PlayerSaveManager
var player := main.get("_player") as Player
var data_root := main.get("_data_root") as PlayerDataRoot
assert(save_manager != null and player != null and data_root != null)
var home_state := main.get("_home_state") as PlayerHomeState
assert(
save_manager != null
and player != null
and data_root != null
and home_state != null
)
assert(save_manager.initialize_new_game(864209))
save_manager.set_autosave_enabled(true)
assert(player.wallet.restore_balance(4321))
assert(player.bag.add_item(&"coffee"))
assert(home_state.add_owned_decor(&"basic_cube"))
assert(not home_state.place_decor(
&"basic_cube",
Vector3.ZERO,
0.0,
PlayerHomeState.PlacementMode.GRID,
).is_empty())
assert(save_manager.save_now())
var save_path: String = str(save_manager.get("_save_path"))
@ -38,7 +51,14 @@ func _run() -> void:
_assert_opaque(save_path)
var decoded: Dictionary = ProgressionSaveCodec.read_local_save(save_path)
assert(bool(decoded.get("ok", false)))
assert(int((decoded["data"] as Dictionary)["save_version"]) == 10)
assert(int((decoded["data"] as Dictionary)["save_version"]) == 11)
assert(not PlayerHomeState.sanitize_save_data(
(decoded["data"] as Dictionary).get("home", {})
).is_empty())
assert(
((decoded["data"] as Dictionary)["home"] as Dictionary)
.get("placements", []).size() == 1
)
var legacy_container_path: String = data_root.progression_backup_directory().path_join(
"netfishing-container.nfsave"
)
@ -74,6 +94,8 @@ func _run() -> void:
assert(save_manager.load_player_data())
assert(player.wallet.get_balance() == 4321)
assert(save_manager.get_world_seed() == 864209)
assert(home_state.get_owned_count(&"basic_cube") == 1)
assert(home_state.get_placed_count(&"basic_cube") == 1)
var legacy_data: Dictionary = (
ProgressionSaveCodec.read_local_save(save_path)["data"]

View file

@ -35,6 +35,41 @@ func _run() -> void:
hotbar.setup(bag, ItemCatalogResource, catches, layout)
assert(PlayerInventoryLayout.INVENTORY_CAPACITIES == [9, 18, 27, 36])
assert(PlayerCoolerCapacity.CAPACITIES == [9, 18, 27, 36, 45, 54, 63, 72])
var basic_cube: ItemData = ItemCatalogResource.get_item_by_id(&"basic_cube")
assert(basic_cube != null)
assert(basic_cube.icon != null)
assert(basic_cube.hotbar_allowed)
assert(bag.add_item(&"basic_cube"))
assert(layout.is_item_in_inventory(&"basic_cube"))
assert(hotbar.assign_item(0, &"basic_cube"))
assert(hotbar.get_item_id(0) == &"basic_cube")
assert(
layout.get_container(
PlayerInventoryLayout.EntryKind.ITEM,
&"basic_cube",
) == PlayerInventoryLayout.InventoryContainer.HOTBAR
)
assert(hotbar.clear_slot(0))
assert(layout.is_item_in_inventory(&"basic_cube"))
assert(bag.remove_item(&"basic_cube"))
var packed_decor_slot: int = hotbar.find_first_empty_slot()
assert(packed_decor_slot == 0)
assert(layout.prepare_new_item_placement(
&"basic_cube",
PlayerInventoryLayout.InventoryContainer.HOTBAR,
packed_decor_slot,
))
assert(bag.add_item(&"basic_cube"))
assert(
layout.get_container(
PlayerInventoryLayout.EntryKind.ITEM,
&"basic_cube",
) == PlayerInventoryLayout.InventoryContainer.HOTBAR
)
assert(hotbar.assign_item(packed_decor_slot, &"basic_cube"))
assert(hotbar.select_slot(packed_decor_slot))
assert(hotbar.get_selected_item_id() == &"basic_cube")
assert(bag.remove_item(&"basic_cube"))
assert(bag.add_item(&"coffee", 3))
assert(layout.get_inventory_count() == 1)