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

@ -37,6 +37,9 @@ const FishingShopType = preload("res://ui/fishing_shop.gd")
const FishingShopInteractionType = preload(
"res://world/fishing_shop_interaction.gd"
)
const DecorShopInteractionType = preload(
"res://world/decor_shop_interaction.gd"
)
const PlayerStorageInteractionType = preload(
"res://world/player_storage_interaction.gd"
)
@ -70,6 +73,7 @@ const NetworkFishingServiceType = preload(
"res://network/network_fishing_service.gd"
)
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
const DecorCatalogType = preload("res://homes/decor_catalog.gd")
const NetworkSaleServiceType = preload(
"res://network/network_sale_service.gd"
)
@ -123,6 +127,14 @@ const GatheringControllerType = preload(
const NetworkWorldSpawnServiceType = preload(
"res://network/network_world_spawn_service.gd"
)
const PlayerHomeStateType = preload("res://homes/player_home_state.gd")
const HomeWorldServiceType = preload("res://homes/home_world_service.gd")
const NetworkHomeServiceType = preload(
"res://network/network_home_service.gd"
)
const HomeDecorControllerType = preload(
"res://homes/home_decor_controller.gd"
)
const TITLE_MUSIC_SILENCE_DB: float = -80.0
const TITLE_MUSIC_PATH: String = (
@ -153,6 +165,7 @@ const DEDICATED_ACTIVE_PHYSICS_TICKS_PER_SECOND: int = 30
@onready var _game_ui: GameUIType = %GameUI
@onready var _water_recovery: WaterRecoveryControllerType = %WaterRecovery
@onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager
@onready var _home_state: PlayerHomeStateType = %PlayerHomeState
@onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager
@onready var _controller_mapping_manager: ControllerMappingManagerType = (
%ControllerMappingManager
@ -212,6 +225,10 @@ const DEDICATED_ACTIVE_PHYSICS_TICKS_PER_SECOND: int = 30
)
@onready var _network_sale: NetworkSaleServiceType = %NetworkSaleService
@onready var _network_shop: NetworkShopServiceType = %NetworkShopService
@onready var _network_home: NetworkHomeServiceType = %NetworkHomeService
@onready var _home_decor_controller: HomeDecorControllerType = (
%HomeDecorController
)
@onready var _network_item_use: NetworkItemUseServiceType = (
%NetworkItemUseService
)
@ -234,6 +251,7 @@ const DEDICATED_ACTIVE_PHYSICS_TICKS_PER_SECOND: int = 30
@onready var _players_root: Node3D = $Players
@onready var _surface_drawings_root: Node3D = $SurfaceDrawings
@onready var _world_gatherables_root: Node3D = $WorldGatherables
@onready var _home_world: HomeWorldServiceType = %HomeWorldService
@onready var _title_background: ColorRect = %TitleBackground
@onready var _player_menu_backdrop: ColorRect = %PlayerMenuBackdrop
@onready var _shop_backdrop: ColorRect = %ShopBackdrop
@ -241,6 +259,7 @@ const DEDICATED_ACTIVE_PHYSICS_TICKS_PER_SECOND: int = 30
var _gameplay_started: bool = false
var _save_slots := PlayerSaveSlotCatalogType.new()
var _shop_interaction: FishingShopInteractionType
var _decor_shop_interaction: DecorShopInteractionType
var _storage_interaction: PlayerStorageInteractionType
var _title_music_tween: Tween
var _title_music_transition_generation: int = 0
@ -740,6 +759,7 @@ func _initialize_application(dedicated: bool) -> void:
_player.inventory_layout,
)
_shop_interaction = _test_world.get_fishing_shop()
_decor_shop_interaction = _test_world.get_decor_shop()
_storage_interaction = _test_world.get_player_storage()
if not dedicated:
_shop_interaction.setup_local_player(_player)
@ -749,6 +769,10 @@ func _initialize_application(dedicated: bool) -> void:
_game_ui.set_shop_npc_player_in_range(
_shop_interaction.is_local_player_in_range()
)
_decor_shop_interaction.setup_local_player(_player)
_decor_shop_interaction.local_player_range_changed.connect(
_on_decor_shop_range_changed
)
_storage_interaction.setup_local_player(_player)
_storage_interaction.local_player_range_changed.connect(
_on_storage_range_changed
@ -770,6 +794,23 @@ func _initialize_application(dedicated: bool) -> void:
_world_weather,
_player_jobs,
_player,
_home_state,
)
_home_world.setup(
_test_world,
_player_spawn_service,
_network_session,
dedicated,
)
_network_home.setup(
_network_session,
_player_spawn_service,
_home_state,
_save_manager,
_home_world,
_player.bag,
_player.inventory_layout,
_player.hotbar,
)
_player_jobs.set_save_manager(_save_manager)
_save_manager.set_autosave_enabled(false)
@ -903,6 +944,9 @@ func _initialize_application(dedicated: bool) -> void:
_save_manager,
_asset_reservations,
_player.inventory_layout,
_home_state,
_network_home,
_decor_shop_interaction,
)
_fishing_spot.setup(
_player,
@ -942,11 +986,13 @@ func _initialize_application(dedicated: bool) -> void:
_player.fishing_upgrades,
_shop_interaction,
_storage_interaction,
_decor_shop_interaction,
_player.item_effects,
_player.cooler_capacity,
_network_session,
_network_sale,
_network_shop,
_home_state,
_network_chat,
_network_profile,
_player_spawn_service,
@ -964,6 +1010,21 @@ func _initialize_application(dedicated: bool) -> void:
_test_world.get_world_environment(),
_test_world.get_sun(),
)
_home_decor_controller.setup(
_player,
_player.hotbar,
_player.bag,
_home_state,
_network_home,
_home_world,
_fishing_spot,
_game_ui.get_home_decor_toolbar(),
)
_game_ui.get_player_storage().decorate_requested.connect(
_home_decor_controller.begin_decorating
)
_network_home.local_space_changed.connect(_on_local_home_space_changed)
_game_ui.rv_storage_requested.connect(_on_rv_storage_requested)
_game_ui.setup_data_and_identity(
_data_root,
_identity_backups,
@ -1475,6 +1536,9 @@ func _input(event: InputEvent) -> void:
if _game_ui.get_player_storage().consume_escape():
get_viewport().set_input_as_handled()
return
if _game_ui.get_decor_shop().consume_escape():
get_viewport().set_input_as_handled()
return
if fishing_shop.consume_escape():
get_viewport().set_input_as_handled()
return
@ -1573,19 +1637,50 @@ func _is_pause_open_request(event: InputEvent) -> bool:
func _unhandled_input(event: InputEvent) -> void:
if (
_gameplay_started
and event.is_action_pressed("fish_primary")
and not (event is InputEventKey and event.echo)
and _fishing_spot.try_use_active_non_fishing_item()
):
get_viewport().set_input_as_handled()
return
if (
not _gameplay_started
or not event.is_action_pressed("interact")
or (event is InputEventKey and event.echo)
):
return
var home_owner: String = _network_home.get_local_home_owner_fingerprint()
if not home_owner.is_empty():
if _home_world.is_avatar_near_interior_exit(_player, home_owner):
if _network_home.request_exit():
_game_ui.set_storage_prompt_visible(false)
get_viewport().set_input_as_handled()
return
if (
_network_home.can_local_decorate()
and _fishing_spot.can_open_fishing_shop()
and _game_ui.get_player_storage().open_storage_from_home(true)
):
_game_ui.set_storage_prompt_visible(false)
get_viewport().set_input_as_handled()
return
var nearby_home_owner: String = _home_world.find_nearby_exterior_owner(
_player
)
if not nearby_home_owner.is_empty():
if _network_home.request_enter(nearby_home_owner):
_game_ui.set_storage_prompt_visible(false)
get_viewport().set_input_as_handled()
return
if (
_storage_interaction != null
and _storage_interaction.is_local_player_in_range()
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
and _fishing_spot.can_open_fishing_shop()
and _game_ui.get_player_storage().open_storage()
and _game_ui.get_decor_shop().open_shop()
):
_game_ui.set_storage_prompt_visible(false)
_game_ui.set_shop_prompt_visible(false)
get_viewport().set_input_as_handled()
return
if (
@ -1608,25 +1703,28 @@ func _process(delta: float) -> void:
)
_print_dedicated_metrics()
return
var show_shop_prompt := _can_show_shop_prompt()
var show_decor_shop_prompt := _can_show_decor_shop_prompt()
var show_shop_prompt := show_decor_shop_prompt or _can_show_shop_prompt()
var shop_prompt_anchor := (
_shop_interaction.get_prompt_anchor_position()
if _shop_interaction != null
else Vector3.ZERO
_decor_shop_interaction.get_prompt_anchor_position()
if show_decor_shop_prompt
else (
_shop_interaction.get_prompt_anchor_position()
if _shop_interaction != null else Vector3.ZERO
)
)
_game_ui.set_shop_prompt_visible(
show_shop_prompt,
shop_prompt_anchor,
)
var show_storage_prompt := _can_show_storage_prompt()
var storage_prompt_anchor := (
_storage_interaction.get_prompt_anchor_position()
if _storage_interaction != null
else Vector3.ZERO
)
var home_prompt := _get_home_prompt()
_game_ui.set_storage_prompt_visible(
show_storage_prompt,
storage_prompt_anchor,
not home_prompt.is_empty(),
home_prompt.get("anchor", Vector3.ZERO),
str(home_prompt.get("label", "enter RV")),
)
_game_ui.set_rv_storage_button_visible(
_can_show_rv_storage_button()
)
@ -2421,6 +2519,7 @@ func _apply_world(
if _application_initialized:
_refresh_active_world_bindings()
_network_world_spawns.refresh_world_context()
_network_home.refresh_world_presentations()
if _application_initialized and not _dedicated_runtime:
_water_recovery.update_world_context(
spawn_transform,
@ -2450,10 +2549,19 @@ func _refresh_active_world_bindings() -> void:
_storage_interaction.local_player_range_changed.disconnect(
_on_storage_range_changed
)
if is_instance_valid(_decor_shop_interaction):
if _decor_shop_interaction.local_player_range_changed.is_connected(
_on_decor_shop_range_changed
):
_decor_shop_interaction.local_player_range_changed.disconnect(
_on_decor_shop_range_changed
)
_shop_interaction = _test_world.get_fishing_shop()
_decor_shop_interaction = _test_world.get_decor_shop()
_storage_interaction = _test_world.get_player_storage()
_network_sale.set_shop_interaction(_shop_interaction)
_network_shop.set_shop_interaction(_shop_interaction)
_network_shop.set_decor_shop_interaction(_decor_shop_interaction)
if _dedicated_runtime:
return
if _shop_interaction != null:
@ -2466,9 +2574,15 @@ func _refresh_active_world_bindings() -> void:
_storage_interaction.local_player_range_changed.connect(
_on_storage_range_changed
)
if _decor_shop_interaction != null:
_decor_shop_interaction.setup_local_player(_player)
_decor_shop_interaction.local_player_range_changed.connect(
_on_decor_shop_range_changed
)
_game_ui.set_world_interactions(
_shop_interaction,
_storage_interaction,
_decor_shop_interaction,
)
_on_shop_range_changed(
_shop_interaction != null
@ -2478,6 +2592,10 @@ func _refresh_active_world_bindings() -> void:
_storage_interaction != null
and _storage_interaction.is_local_player_in_range()
)
_on_decor_shop_range_changed(
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
)
func _on_water_recovery_starting() -> void:
@ -2491,6 +2609,7 @@ func _on_water_recovery_starting() -> void:
_game_ui.close_player_menu_for_water_recovery()
_game_ui.get_pause_menu().close_for_water_recovery()
_game_ui.get_fishing_shop().close_for_water_recovery()
_game_ui.get_decor_shop().close_for_water_recovery()
_game_ui.get_player_storage().close_for_water_recovery()
@ -2552,6 +2671,12 @@ func _on_active_hotbar_item_changed(
and item.is_available()
and _player.bag.owns_item(item_id)
)
var active_is_decor: bool = (
DecorCatalogType.has_product(item_id)
and item != null
and item.is_available()
and _player.bag.owns_item(item_id)
)
_player.set_active_fishing_rod(
item as FishingRodDataType if active_is_rod else null,
true,
@ -2562,6 +2687,7 @@ func _on_active_hotbar_item_changed(
)
_player.set_active_catching_net(active_is_catching_net)
_player.set_active_shovel(active_is_shovel)
_player.set_active_decor_package(active_is_decor)
_game_ui.set_surface_drawing_hotbar_selected(active_is_art_kit)
_network_item_use.submit_local_equipped(item_id, active_is_rod or (
item != null and _player.bag.owns_item(item_id)
@ -2698,7 +2824,13 @@ func _exit_tree() -> void:
func _on_shop_range_changed(in_range: bool) -> void:
_game_ui.set_shop_npc_player_in_range(in_range)
_game_ui.set_shop_npc_player_in_range(
in_range
or (
_decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
)
)
if not in_range:
_game_ui.get_fishing_shop().close_for_range_exit()
_game_ui.set_shop_prompt_visible(_can_show_shop_prompt())
@ -2707,7 +2839,50 @@ func _on_shop_range_changed(in_range: bool) -> void:
func _on_storage_range_changed(in_range: bool) -> void:
if not in_range:
_game_ui.get_player_storage().close_for_range_exit()
_game_ui.set_storage_prompt_visible(_can_show_storage_prompt())
var prompt := _get_home_prompt()
_game_ui.set_storage_prompt_visible(
not prompt.is_empty(),
prompt.get("anchor", Vector3.ZERO),
str(prompt.get("label", "enter RV")),
)
_game_ui.set_rv_storage_button_visible(
_can_show_rv_storage_button()
)
func _on_decor_shop_range_changed(in_range: bool) -> void:
_game_ui.set_shop_npc_player_in_range(
in_range
or (
_shop_interaction != null
and _shop_interaction.is_local_player_in_range()
)
)
if not in_range:
_game_ui.get_decor_shop().close_for_range_exit()
_game_ui.set_shop_prompt_visible(
_can_show_shop_prompt() or _can_show_decor_shop_prompt()
)
func _on_local_home_space_changed(
space_id: StringName,
_owner_fingerprint: String,
) -> void:
var inside_rv: bool = String(space_id).begins_with("rv:")
_fishing_spot.set_fishing_environment_enabled(not inside_rv)
_game_ui.set_rv_interior_camera_mode(
inside_rv
)
_game_ui.set_rv_storage_button_visible(
_can_show_rv_storage_button()
)
func _on_rv_storage_requested() -> void:
if not _can_show_rv_storage_button():
return
_game_ui.get_player_storage().open_storage_from_home(true)
func _can_show_shop_prompt() -> bool:
@ -2716,18 +2891,66 @@ func _can_show_shop_prompt() -> bool:
and _shop_interaction != null
and _shop_interaction.is_local_player_in_range()
and not _game_ui.get_fishing_shop().visible
and not _game_ui.get_decor_shop().visible
and not _water_recovery.is_recovery_active()
and _fishing_spot.can_open_fishing_shop()
)
func _can_show_decor_shop_prompt() -> bool:
return (
_gameplay_started
and _decor_shop_interaction != null
and _decor_shop_interaction.is_local_player_in_range()
and not _game_ui.get_decor_shop().visible
and not _game_ui.get_fishing_shop().visible
and not _water_recovery.is_recovery_active()
and _fishing_spot.can_open_fishing_shop()
)
func _can_show_storage_prompt() -> bool:
return not _get_home_prompt().is_empty()
func _can_show_rv_storage_button() -> bool:
return (
_gameplay_started
and _storage_interaction != null
and _storage_interaction.is_local_player_in_range()
and not _game_ui.get_player_storage().visible
and not _game_ui.get_fishing_shop().visible
and not _network_home.get_local_home_owner_fingerprint().is_empty()
and _network_home.can_local_decorate()
and not _water_recovery.is_recovery_active()
and _fishing_spot.can_open_fishing_shop()
)
func _get_home_prompt() -> Dictionary:
if (
not _gameplay_started
or _game_ui.get_player_storage().visible
or _game_ui.get_fishing_shop().visible
or _game_ui.get_decor_shop().visible
or _water_recovery.is_recovery_active()
or not _fishing_spot.can_open_fishing_shop()
):
return {}
var owner_fingerprint: String = (
_network_home.get_local_home_owner_fingerprint()
)
if not owner_fingerprint.is_empty():
if _home_world.is_avatar_near_interior_exit(
_player, owner_fingerprint
):
return {
"label": "leave RV",
"anchor": _home_world.get_interior_prompt_position(
owner_fingerprint
),
}
return {}
var nearby_owner: String = _home_world.find_nearby_exterior_owner(_player)
if nearby_owner.is_empty():
return {}
return {
"label": "enter RV",
"anchor": _home_world.get_exterior_prompt_position(nearby_owner),
}

View file

@ -58,6 +58,10 @@
[ext_resource type="Resource" path="res://gathering/catalog/gatherable_catalog.tres" id="58_gatherable_catalog"]
[ext_resource type="Script" path="res://network/network_world_spawn_service.gd" id="59_world_spawns"]
[ext_resource type="Script" path="res://gathering/gathering_controller.gd" id="60_gathering_controller"]
[ext_resource type="Script" path="res://homes/player_home_state.gd" id="61_home_state"]
[ext_resource type="Script" path="res://homes/home_world_service.gd" id="62_home_world"]
[ext_resource type="Script" path="res://network/network_home_service.gd" id="63_network_home"]
[ext_resource type="Script" path="res://homes/home_decor_controller.gd" id="64_home_decor_controller"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_grass_native"]
shader = ExtResource("42_menu_pattern")
@ -259,6 +263,14 @@ script = ExtResource("22_network_sale")
unique_name_in_owner = true
script = ExtResource("23_network_shop")
[node name="NetworkHomeService" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("63_network_home")
[node name="HomeDecorController" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("64_home_decor_controller")
[node name="NetworkItemUseService" type="Node" parent="." unique_id=255154670]
unique_name_in_owner = true
script = ExtResource("24_network_item")
@ -322,6 +334,10 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.3999481, 12.07081)
[node name="WorldGatherables" type="Node3D" parent="."]
[node name="HomeWorldService" type="Node3D" parent="."]
unique_name_in_owner = true
script = ExtResource("62_home_world")
[node name="GatheringController" type="Node3D" parent="."]
unique_name_in_owner = true
script = ExtResource("60_gathering_controller")
@ -338,6 +354,10 @@ script = ExtResource("8_recovery")
unique_name_in_owner = true
script = ExtResource("9_save")
[node name="PlayerHomeState" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("61_home_state")
[node name="PlayerSettingsManager" type="Node" parent="." unique_id=906991991]
unique_name_in_owner = true
script = ExtResource("10_settings")