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

323
ui/decor_shop.gd Normal file
View file

@ -0,0 +1,323 @@
class_name DecorShop
extends Control
const INPUT_OWNER: StringName = &"decor_shop"
const DecorCatalogType = preload("res://homes/decor_catalog.gd")
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
signal menu_visibility_changed(is_open: bool)
signal menu_exit_started
var _player: Player
var _wallet: PlayerWallet
var _fishing_spot: FishingSpot
var _interaction: DecorShopInteraction
var _bag: PlayerBag
var _home_state: PlayerHomeState
var _network_shop: NetworkShopService
var _wallet_label: Label
var _owned_label: Label
var _feedback: Label
var _buy_button: Button
var _close_button: Button
var _transaction_pending: bool = false
var _prior_movement_enabled: bool = true
var _prior_camera_enabled: bool = true
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
func _ready() -> void:
UtilityPageStyleType.apply_page(self)
_build_ui()
hide()
func setup(
player: Player,
wallet: PlayerWallet,
fishing_spot: FishingSpot,
interaction: DecorShopInteraction,
bag: PlayerBag,
home_state: PlayerHomeState,
network_shop: NetworkShopService,
) -> void:
_player = player
_wallet = wallet
_fishing_spot = fishing_spot
_interaction = interaction
_bag = bag
_home_state = home_state
_network_shop = network_shop
if not _wallet.balance_changed.is_connected(_on_wallet_changed):
_wallet.balance_changed.connect(_on_wallet_changed)
if not _bag.contents_changed.is_connected(_refresh):
_bag.contents_changed.connect(_refresh)
if not _home_state.changed.is_connected(_refresh):
_home_state.changed.connect(_refresh)
if (
_network_shop != null
and not _network_shop.local_purchase_pending.is_connected(
_on_purchase_pending
)
):
_network_shop.local_purchase_pending.connect(_on_purchase_pending)
_network_shop.local_purchase_finished.connect(_on_purchase_finished)
_refresh()
func set_world_interaction(interaction: DecorShopInteraction) -> void:
_interaction = interaction
func open_shop() -> bool:
if (
visible
or _player == null
or _wallet == null
or _fishing_spot == null
or _interaction == null
or not _interaction.is_local_player_in_range()
or not _fishing_spot.can_open_fishing_shop()
):
return false
_prior_movement_enabled = _player.is_movement_enabled()
_prior_camera_enabled = _player.is_camera_input_enabled()
_prior_mouse_mode = Input.mouse_mode
_player.set_movement_enabled(false)
_player.set_camera_input_enabled(false)
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_transaction_pending = (
_network_shop != null
and _network_shop.is_local_purchase_pending()
)
_feedback.text = ""
_refresh()
show()
_buy_button.call_deferred("grab_focus")
menu_visibility_changed.emit(true)
return true
func close_shop(restore_controls: bool = true) -> void:
if not visible:
return
menu_exit_started.emit()
get_viewport().gui_release_focus()
hide()
if _fishing_spot != null:
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false)
if restore_controls and _player != null:
_player.set_movement_enabled(_prior_movement_enabled)
_player.set_camera_input_enabled(_prior_camera_enabled)
Input.mouse_mode = _prior_mouse_mode
menu_visibility_changed.emit(false)
func consume_escape() -> bool:
if not visible:
return false
close_shop()
return true
func close_for_range_exit() -> void:
close_shop()
func close_for_water_recovery() -> void:
close_shop(false)
func close_for_session_end() -> void:
close_shop(false)
func _unhandled_input(event: InputEvent) -> void:
if not visible or not event.is_action_pressed("ui_cancel"):
return
close_shop()
get_viewport().set_input_as_handled()
func _purchase_cube() -> void:
if _network_shop == null or _transaction_pending:
return
var request_id: String = _network_shop.request_decor(
DecorCatalogType.BASIC_CUBE_ID
)
if request_id.is_empty():
_refresh()
func _on_purchase_pending(_request_id: String) -> void:
_transaction_pending = true
_feedback.text = "purchasing…"
_refresh()
func _on_purchase_finished(
_request_id: String,
accepted: bool,
message: String,
product_id: StringName,
category: int,
_quantity: int,
total_cost: int,
) -> void:
if (
category != NetworkShopProtocol.ProductCategory.DECOR
or product_id != DecorCatalogType.BASIC_CUBE_ID
):
return
_transaction_pending = false
_feedback.text = (
"basic cube purchased · %d fishcoin spent" % total_cost
if accepted
else message.to_lower()
)
_refresh()
func _on_wallet_changed(_balance: int, _delta: int) -> void:
_refresh()
func _refresh() -> void:
if _wallet_label == null:
return
var balance: int = _wallet.get_balance() if _wallet != null else 0
var owned: int = (
_home_state.get_owned_count(DecorCatalogType.BASIC_CUBE_ID)
if _home_state != null else 0
)
var carried: int = (
_bag.get_quantity(DecorCatalogType.BASIC_CUBE_ID)
if _bag != null else 0
)
_wallet_label.text = "%d fishcoins" % balance
_owned_label.text = "owned %d · packed %d" % [owned, carried]
_buy_button.disabled = (
_transaction_pending
or _wallet == null
or not _wallet.can_afford(
DecorCatalogType.get_price(DecorCatalogType.BASIC_CUBE_ID)
)
or _bag == null
or not _bag.can_add_item(DecorCatalogType.BASIC_CUBE_ID, 1)
or owned >= DecorCatalogType.MAX_OWNED_PER_PRODUCT
)
func _build_ui() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
z_index = 83
mouse_filter = Control.MOUSE_FILTER_STOP
var blocker := ColorRect.new()
blocker.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
blocker.color = Color(0.02, 0.08, 0.10, 0.55)
blocker.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(blocker)
var panel := PanelContainer.new()
panel.set_anchors_preset(Control.PRESET_CENTER)
panel.position = Vector2(-440.0, -250.0)
panel.size = Vector2(880.0, 500.0)
panel.add_theme_stylebox_override(
"panel",
UtilityPageStyleType.rounded_style(
UtilityPageStyleType.OCEAN_PANEL_DEEP, 24
),
)
add_child(panel)
var margin := MarginContainer.new()
for side: StringName in [
&"margin_left", &"margin_top", &"margin_right", &"margin_bottom",
]:
margin.add_theme_constant_override(side, 28)
panel.add_child(margin)
var layout := VBoxContainer.new()
layout.add_theme_constant_override("separation", 18)
margin.add_child(layout)
var header := HBoxContainer.new()
header.add_theme_constant_override("separation", 12)
layout.add_child(header)
var title := Label.new()
title.text = "decor"
title.add_theme_font_size_override("font_size", 38)
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(title)
_wallet_label = Label.new()
_wallet_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_wallet_label.add_theme_font_size_override("font_size", 22)
header.add_child(_wallet_label)
var item_panel := PanelContainer.new()
item_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
item_panel.add_theme_stylebox_override(
"panel",
UtilityPageStyleType.rounded_style(
UtilityPageStyleType.OCEAN_FIELD, 18
),
)
layout.add_child(item_panel)
var item_margin := MarginContainer.new()
for side: StringName in [
&"margin_left", &"margin_top", &"margin_right", &"margin_bottom",
]:
item_margin.add_theme_constant_override(side, 24)
item_panel.add_child(item_margin)
var item_row := HBoxContainer.new()
item_row.add_theme_constant_override("separation", 28)
item_margin.add_child(item_row)
var preview := Panel.new()
preview.custom_minimum_size = Vector2(150.0, 150.0)
preview.add_theme_stylebox_override(
"panel",
UtilityPageStyleType.rounded_style(Color("b77a55"), 12),
)
item_row.add_child(preview)
var details := VBoxContainer.new()
details.size_flags_horizontal = Control.SIZE_EXPAND_FILL
details.add_theme_constant_override("separation", 10)
item_row.add_child(details)
var item_name := Label.new()
item_name.text = DecorCatalogType.get_display_name(
DecorCatalogType.BASIC_CUBE_ID
)
item_name.add_theme_font_size_override("font_size", 32)
details.add_child(item_name)
var description := Label.new()
description.text = DecorCatalogType.get_description(
DecorCatalogType.BASIC_CUBE_ID
)
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
description.add_theme_font_size_override("font_size", 20)
description.size_flags_vertical = Control.SIZE_EXPAND_FILL
details.add_child(description)
_owned_label = Label.new()
_owned_label.add_theme_font_size_override("font_size", 18)
details.add_child(_owned_label)
_buy_button = Button.new()
_buy_button.text = "buy · 1 fishcoin"
_buy_button.custom_minimum_size = Vector2(260.0, 58.0)
UtilityPageStyleType.apply_ocean_button(_buy_button)
_buy_button.pressed.connect(_purchase_cube)
details.add_child(_buy_button)
var footer := HBoxContainer.new()
footer.add_theme_constant_override("separation", 12)
layout.add_child(footer)
_feedback = Label.new()
_feedback.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_feedback.add_theme_font_size_override("font_size", 18)
footer.add_child(_feedback)
_close_button = Button.new()
_close_button.text = "close"
_close_button.custom_minimum_size = Vector2(150.0, 50.0)
UtilityPageStyleType.apply_ocean_button(_close_button)
_close_button.pressed.connect(close_shop)
footer.add_child(_close_button)
_buy_button.focus_neighbor_bottom = _buy_button.get_path_to(_close_button)
_close_button.focus_neighbor_top = _close_button.get_path_to(_buy_button)

1
ui/decor_shop.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://810ck3mvbn5v

14
ui/decor_shop.tscn Normal file
View file

@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://ui/decor_shop.gd" id="1_script"]
[node name="DecorShop" type="Control"]
unique_name_in_owner = true
visible = false
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_script")

View file

@ -20,6 +20,7 @@ const TitleScreenType = preload("res://ui/title_screen.gd")
const PauseMenuType = preload("res://ui/pause_menu.gd")
const NetworkSessionType = preload("res://network/network_session.gd")
const FishingShopType = preload("res://ui/fishing_shop.gd")
const DecorShopType = preload("res://ui/decor_shop.gd")
const SettingsPanelType = preload("res://ui/settings_panel.gd")
const PlayerFishingUpgradesType = preload(
"res://progression/player_fishing_upgrades.gd"
@ -27,7 +28,11 @@ const PlayerFishingUpgradesType = preload(
const ShopInteractionType = preload(
"res://world/fishing_shop_interaction.gd"
)
const DecorShopInteractionType = preload(
"res://world/decor_shop_interaction.gd"
)
const PlayerStorageType = preload("res://ui/player_storage.gd")
const HomeDecorToolbarType = preload("res://ui/home_decor_toolbar.gd")
const PlayerStorageInteractionType = preload(
"res://world/player_storage_interaction.gd"
)
@ -71,6 +76,9 @@ const MAIN_BUBBLE_PROFILE: BubbleMenuProfile = preload(
const ACTIVE_BAIT_EMPTY_ICON: Texture2D = preload(
"res://ui/icons/pictograms/x_dark.png"
)
const RV_STORAGE_ICON: Texture2D = preload(
"res://ui/icons/tab_menu/inventory_simple.png"
)
signal pixelation_settings_visibility_changed(is_visible: bool)
signal crisp_reset_focus_requested
@ -82,6 +90,7 @@ signal virtual_pointer_mode_changed(is_active: bool)
signal social_prompt_accepted
signal social_prompt_declined
signal data_root_changed
signal rv_storage_requested
const VIRTUAL_MOUSE_INPUT_OWNER: StringName = &"controller_virtual_mouse"
const EMOTE_RADIAL_CAMERA_OWNER: StringName = &"emote_radial_menu"
@ -145,7 +154,10 @@ const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000
@onready var _social_prompt: BubbleConfirmationPage = %SocialPrompt
@onready var _hotbar_ui: HotbarUIType = %Hotbar
@onready var _fishing_shop: FishingShopType = %FishingShop
@onready var _decor_shop: DecorShopType = %DecorShop
@onready var _player_storage: PlayerStorageType = %PlayerStorage
@onready var _home_decor_toolbar: HomeDecorToolbarType = %HomeDecorToolbar
@onready var _rv_storage_button: Button = %RVStorageButton
@onready var _storage_prompt: PanelContainer = %StoragePrompt
@onready var _storage_prompt_message: Label = %StoragePromptMessage
@onready var _shop_prompt: Control = %ShopPrompt
@ -195,6 +207,7 @@ var _chat_input_open: bool = false
var _player_menu_hotbar_visible: bool = false
var _main_shop_buyer: FishBuyerProfileType
var _shop_interaction: ShopInteractionType
var _decor_shop_interaction: DecorShopInteractionType
var _storage_interaction: PlayerStorageInteractionType
var _surface_drawing: NetworkSurfaceDrawingService
var _surface_drawing_hotbar_selected: bool = false
@ -221,14 +234,17 @@ var _controller_text_entry_request: Callable
var _controller_text_entry_is_open: Callable
var _controller_text_entry_close: Callable
var _restore_chat_keyboard_after_fishing: bool = false
var _rv_storage_button_requested_visible: bool = false
func _ready() -> void:
_prioritize_surface_drawing_pointer_input()
_bite_prompt_button.pressed.connect(_on_bite_prompt_pressed)
_rv_storage_button.pressed.connect(_on_rv_storage_button_pressed)
_social_prompt.confirmed.connect(_accept_social_prompt)
_social_prompt.cancelled.connect(_decline_social_prompt)
_apply_active_bait_indicator_style()
_apply_rv_storage_button_style()
_refresh_active_bait_indicator()
_refresh_interaction_prompt_bindings()
# Reward feedback must remain above full-screen canonical menus. Keeping the
@ -346,11 +362,13 @@ func setup(
fishing_upgrades: PlayerFishingUpgradesType,
shop_interaction: ShopInteractionType,
storage_interaction: PlayerStorageInteractionType,
decor_shop_interaction: DecorShopInteractionType,
item_effects: PlayerItemEffectsType,
cooler_capacity: PlayerCoolerCapacityType,
network_session: NetworkSessionType,
network_sale_service: NetworkSaleService,
network_shop_service: NetworkShopService,
home_state: PlayerHomeState,
network_chat_service: NetworkChatService,
network_profile: NetworkProfilePreferences,
spawn_service: PlayerSpawnService,
@ -494,6 +512,15 @@ func setup(
network_sale_service,
reservations,
)
_decor_shop.setup(
player,
wallet,
fishing_spot,
decor_shop_interaction,
bag,
home_state,
network_shop_service,
)
_player_storage.setup(
player,
fishing_spot,
@ -509,6 +536,8 @@ func setup(
)
_fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
_fishing_shop.menu_exit_started.connect(_on_shop_exit_started)
_decor_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
_decor_shop.menu_exit_started.connect(_on_shop_exit_started)
_fishing_shop.sell_fish_requested.connect(_on_shop_sell_fish_requested)
_fishing_shop.shop_cooler_return_requested.connect(
_on_shop_cooler_return_requested
@ -518,6 +547,7 @@ func setup(
)
_main_shop_buyer = main_shop_buyer
_shop_interaction = shop_interaction
_decor_shop_interaction = decor_shop_interaction
_storage_interaction = storage_interaction
_surface_drawing = surface_drawing
if (
@ -540,11 +570,14 @@ func setup(
func set_world_interactions(
shop_interaction: ShopInteractionType,
storage_interaction: PlayerStorageInteractionType,
decor_shop_interaction: DecorShopInteractionType,
) -> void:
_shop_interaction = shop_interaction
_storage_interaction = storage_interaction
_decor_shop_interaction = decor_shop_interaction
_fishing_shop.set_world_interaction(shop_interaction)
_player_storage.set_world_interaction(storage_interaction)
_decor_shop.set_world_interaction(decor_shop_interaction)
func _prioritize_surface_drawing_pointer_input() -> void:
@ -1655,6 +1688,34 @@ func _apply_active_bait_indicator_style() -> void:
_active_bait_button.add_theme_color_override(state, Color.WHITE)
func _apply_rv_storage_button_style() -> void:
var normal_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_normal_style()
var hover_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_hover_style()
var pressed_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_pressed_style()
for style: StyleBoxFlat in [normal_style, hover_style, pressed_style]:
style.set_corner_radius_all(29)
style.content_margin_left = 9.0
style.content_margin_top = 9.0
style.content_margin_right = 9.0
style.content_margin_bottom = 9.0
_rv_storage_button.icon = RV_STORAGE_ICON
_rv_storage_button.add_theme_stylebox_override("normal", normal_style)
_rv_storage_button.add_theme_stylebox_override("hover", hover_style)
_rv_storage_button.add_theme_stylebox_override("focus", hover_style)
_rv_storage_button.add_theme_stylebox_override("pressed", pressed_style)
for state: StringName in [
&"icon_normal_color",
&"icon_hover_color",
&"icon_focus_color",
&"icon_pressed_color",
]:
_rv_storage_button.add_theme_color_override(state, Color.WHITE)
func _on_rv_storage_button_pressed() -> void:
rv_storage_requested.emit()
func _refresh_active_bait_indicator() -> void:
_refresh_active_bait_indicator_visibility()
if not is_node_ready():
@ -1718,17 +1779,20 @@ func set_gameplay_ui_enabled(enabled: bool) -> void:
if not enabled:
_gameplay_hud_hidden = false
_refresh_active_bait_indicator_visibility()
_refresh_rv_storage_button_visibility()
if enabled:
_try_start_shop_npc_speech()
_refresh_gameplay_hud_visibility()
_refresh_chat_availability()
if not enabled:
_home_decor_toolbar.set_active(false)
_end_virtual_mouse()
_close_radial_menus()
if _surface_drawing != null:
_surface_drawing.deactivate()
close_player_menu_for_session_end()
_fishing_shop.close_for_session_end()
_decor_shop.close_for_session_end()
_player_storage.close_for_session_end()
_fishing_panel.visible = false
_shop_prompt.hide()
@ -1749,6 +1813,7 @@ func set_gameplay_hud_hidden(hidden: bool) -> void:
_quick_radial_menu.set_hud_hidden(hidden)
_refresh_gameplay_hud_visibility()
_refresh_active_bait_indicator_visibility()
_refresh_rv_storage_button_visibility()
_refresh_hotbar_visibility()
_refresh_chat_availability()
@ -1864,13 +1929,43 @@ func get_fishing_shop() -> FishingShopType:
return _fishing_shop
func get_decor_shop() -> DecorShopType:
return _decor_shop
func get_player_storage() -> PlayerStorageType:
return _player_storage
func get_home_decor_toolbar() -> HomeDecorToolbarType:
return _home_decor_toolbar
func set_rv_interior_camera_mode(enabled: bool) -> void:
_hotbar_ui.set_rv_interior_camera_mode(enabled)
func set_rv_storage_button_visible(requested_visible: bool) -> void:
_rv_storage_button_requested_visible = requested_visible
_refresh_rv_storage_button_visibility()
func _refresh_rv_storage_button_visibility() -> void:
_rv_storage_button.visible = (
_rv_storage_button_requested_visible
and _gameplay_ui_enabled
and not _gameplay_hud_hidden
and not _system_menu_open
and not _player_menu_open
and not _shop_open
and not _storage_open
)
func set_storage_prompt_visible(
requested_visible: bool,
world_anchor: Vector3 = Vector3(0.0, INF, 0.0),
action_text: String = "open storage",
) -> void:
if not _storage_prompt.has_meta(&"styled"):
_storage_prompt.set_meta(&"styled", true)
@ -1879,6 +1974,7 @@ func set_storage_prompt_visible(
)
style.anti_aliasing = false
_storage_prompt.add_theme_stylebox_override("panel", style)
_storage_prompt_message.text = action_text
_storage_prompt.visible = (
requested_visible
and _gameplay_ui_enabled
@ -2762,6 +2858,7 @@ func _refresh_hotbar_visibility() -> void:
func _emit_interactive_pointer_ui_changed() -> void:
_refresh_active_bait_indicator_visibility()
_refresh_rv_storage_button_visibility()
interactive_pointer_ui_changed.emit(
_system_menu_open
or _player_menu_open

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=22 format=3]
[gd_scene load_steps=24 format=3]
[ext_resource type="Script" path="res://ui/game_ui.gd" id="1_ui"]
[ext_resource type="PackedScene" path="res://ui/player_menu.tscn" id="2_menu"]
@ -15,6 +15,8 @@
[ext_resource type="Script" path="res://ui/controller_virtual_cursor.gd" id="13_cursor"]
[ext_resource type="PackedScene" path="res://ui/player_storage.tscn" id="14_storage"]
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="15_social_prompt"]
[ext_resource type="PackedScene" path="res://ui/decor_shop.tscn" id="16_decor_shop"]
[ext_resource type="PackedScene" path="res://ui/home_decor_toolbar.tscn" id="17_decor_toolbar"]
[sub_resource type="StyleBoxFlat" id="StyleBox_chase_background"]
bg_color = Color(0.032, 0.118, 0.15, 1)
@ -472,9 +474,37 @@ polygon = PackedVector2Array(-8, 0, 8, 0, 0, 10)
[node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")]
unique_name_in_owner = true
[node name="DecorShop" parent="UIRoot/CanonicalStage" instance=ExtResource("16_decor_shop")]
unique_name_in_owner = true
[node name="HomeDecorToolbar" parent="UIRoot/CanonicalStage" instance=ExtResource("17_decor_toolbar")]
unique_name_in_owner = true
[node name="PlayerStorage" parent="UIRoot/CanonicalStage" instance=ExtResource("14_storage")]
unique_name_in_owner = true
[node name="RVStorageButton" type="Button" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
z_index = 57
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -74.0
offset_top = 16.0
offset_right = -16.0
offset_bottom = 74.0
grow_horizontal = 0
mouse_default_cursor_shape = 2
focus_mode = 0
tooltip_text = "open RV storage"
theme = ExtResource("3_theme")
texture_filter = 1
expand_icon = true
icon_alignment = 1
vertical_icon_alignment = 1
[node name="StoragePrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false

126
ui/home_decor_toolbar.gd Normal file
View file

@ -0,0 +1,126 @@
class_name HomeDecorToolbar
extends PanelContainer
signal placement_mode_requested(mode: int)
signal rotate_requested
signal store_requested
signal cancel_requested
var _mode_button: Button
var _selection_label: Label
var _rotate_button: Button
var _store_button: Button
var _mode: PlayerHomeState.PlacementMode = PlayerHomeState.PlacementMode.GRID
func _ready() -> void:
_build_ui()
hide()
func set_active(active: bool) -> void:
if visible == active:
return
visible = active
if active and _mode_button != null:
_mode_button.call_deferred("grab_focus")
func set_mode(mode: PlayerHomeState.PlacementMode) -> void:
_mode = mode
if _mode_button != null:
_mode_button.text = (
"placement: grid"
if mode == PlayerHomeState.PlacementMode.GRID
else "placement: free"
)
func set_selection(instance_id: String, display_name: String = "") -> void:
var selected: bool = not instance_id.is_empty()
if _selection_label != null:
_selection_label.text = (
"selected: %s" % display_name
if selected else "click decor to select; drag to move"
)
if _rotate_button != null:
_rotate_button.disabled = not selected
if _store_button != null:
_store_button.disabled = not selected
func report_status(message: String) -> void:
if _selection_label != null:
_selection_label.text = message
func _build_ui() -> void:
custom_minimum_size = Vector2(330.0, 0.0)
position = Vector2(925.0, 540.0)
z_index = 58
mouse_filter = Control.MOUSE_FILTER_STOP
add_theme_stylebox_override(
"panel",
UtilityPageStyle.rounded_style(
Color(UtilityPageStyle.OCEAN_PANEL_DEEP, 0.96), 16
),
)
var margin := MarginContainer.new()
for side: StringName in [
&"margin_left", &"margin_top", &"margin_right", &"margin_bottom",
]:
margin.add_theme_constant_override(side, 12)
add_child(margin)
var layout := VBoxContainer.new()
layout.add_theme_constant_override("separation", 8)
margin.add_child(layout)
var title := Label.new()
title.text = "RV decor"
title.add_theme_font_size_override("font_size", 24)
layout.add_child(title)
_selection_label = Label.new()
_selection_label.text = "click decor to select; drag to move"
_selection_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_selection_label.add_theme_font_size_override("font_size", 17)
layout.add_child(_selection_label)
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 8)
layout.add_child(row)
_mode_button = Button.new()
_mode_button.text = "placement: grid"
_mode_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
UtilityPageStyle.apply_ocean_button(_mode_button)
_mode_button.pressed.connect(_toggle_mode)
row.add_child(_mode_button)
_rotate_button = Button.new()
_rotate_button.text = "rotate"
_rotate_button.disabled = true
UtilityPageStyle.apply_ocean_button(_rotate_button)
_rotate_button.pressed.connect(rotate_requested.emit)
row.add_child(_rotate_button)
var action_row := HBoxContainer.new()
action_row.add_theme_constant_override("separation", 8)
layout.add_child(action_row)
_store_button = Button.new()
_store_button.text = "pack up"
_store_button.disabled = true
_store_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
UtilityPageStyle.apply_ocean_button(_store_button)
_store_button.pressed.connect(store_requested.emit)
action_row.add_child(_store_button)
var cancel_button := Button.new()
cancel_button.text = "clear selection"
cancel_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
UtilityPageStyle.apply_ocean_button(cancel_button)
cancel_button.pressed.connect(cancel_requested.emit)
action_row.add_child(cancel_button)
func _toggle_mode() -> void:
_mode = (
PlayerHomeState.PlacementMode.FREE
if _mode == PlayerHomeState.PlacementMode.GRID
else PlayerHomeState.PlacementMode.GRID
)
set_mode(_mode)
placement_mode_requested.emit(int(_mode))

View file

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

View file

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://ui/home_decor_toolbar.gd" id="1_script"]
[node name="HomeDecorToolbar" type="PanelContainer"]
unique_name_in_owner = true
visible = false
script = ExtResource("1_script")

View file

@ -37,6 +37,7 @@ var _slots: Array[BubbleHotbarSlotType] = []
var _gameplay_input_enabled: bool = false
var _drag_enabled: bool = false
var _swap_hotbar_camera_scroll: bool = false
var _rv_interior_camera_mode: bool = false
var _motion_elapsed: float = 0.0
var _compact_layout: bool = false
var _player_menu_context: bool = false
@ -99,6 +100,10 @@ func set_swap_hotbar_camera_scroll(enabled: bool) -> void:
_swap_hotbar_camera_scroll = enabled
func set_rv_interior_camera_mode(enabled: bool) -> void:
_rv_interior_camera_mode = enabled
func set_drag_enabled(enabled: bool) -> void:
_drag_enabled = enabled
for slot: BubbleHotbarSlotType in _slots:
@ -308,7 +313,11 @@ func _unhandled_input(event: InputEvent) -> void:
if (
event is InputEventMouseButton
and event.pressed
and event.shift_pressed == _swap_hotbar_camera_scroll
and (
event.shift_pressed
if _rv_interior_camera_mode
else event.shift_pressed == _swap_hotbar_camera_scroll
)
):
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_hotbar.cycle_selection(-1)

View file

@ -4,6 +4,7 @@ extends Control
const INPUT_OWNER: StringName = &"player_storage"
signal menu_visibility_changed(is_open: bool)
signal decorate_requested
var _player: Player
var _fishing_spot: FishingSpot
@ -14,6 +15,9 @@ var _storage_grid: GeneralInventoryGrid
var _inventory_count: Label
var _storage_count: Label
var _feedback: Label
var _title: Label
var _decorate_button: Button
var _home_access: bool = false
var _prior_movement_enabled: bool = true
var _prior_camera_enabled: bool = true
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
@ -75,15 +79,34 @@ func set_world_interaction(interaction: PlayerStorageInteraction) -> void:
func open_storage() -> bool:
return _open_storage(false)
func open_storage_from_home(allow_decorating: bool = true) -> bool:
return _open_storage(true, allow_decorating)
func _open_storage(
home_access: bool,
allow_decorating: bool = false,
) -> bool:
if (
visible
or _player == null
or _interaction == null
or not _interaction.is_local_player_in_range()
or _fishing_spot == null
or not _fishing_spot.can_open_fishing_shop()
or (
not home_access
and (
_interaction == null
or not _interaction.is_local_player_in_range()
)
)
):
return false
_home_access = home_access
_title.text = "RV storage" if home_access else "private storage"
_decorate_button.visible = home_access and allow_decorating
_prior_movement_enabled = _player.is_movement_enabled()
_prior_camera_enabled = _player.is_camera_input_enabled()
_prior_mouse_mode = Input.mouse_mode
@ -112,10 +135,13 @@ func close_storage(restore_controls: bool = true) -> void:
_player.set_movement_enabled(_prior_movement_enabled)
_player.set_camera_input_enabled(_prior_camera_enabled)
Input.mouse_mode = _prior_mouse_mode
_home_access = false
menu_visibility_changed.emit(false)
func close_for_range_exit() -> void:
if _home_access:
return
close_storage()
@ -248,12 +274,20 @@ func _build_ui() -> void:
var header := HBoxContainer.new()
layout.add_child(header)
var title := Label.new()
title.text = "private storage"
title.add_theme_font_size_override("font_size", 32)
title.add_theme_color_override("font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY)
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(title)
_title = Label.new()
_title.text = "private storage"
_title.add_theme_font_size_override("font_size", 32)
_title.add_theme_color_override("font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY)
_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(_title)
_decorate_button = Button.new()
_decorate_button.text = "decorate RV"
_decorate_button.tooltip_text = "Place decor inside your RV"
_decorate_button.custom_minimum_size = Vector2(160.0, 48.0)
UtilityPageStyle.apply_ocean_button(_decorate_button)
_decorate_button.hide()
_decorate_button.pressed.connect(_request_decorating)
header.add_child(_decorate_button)
var close := Button.new()
close.text = "×"
close.tooltip_text = "Close storage"
@ -285,6 +319,13 @@ func _build_ui() -> void:
layout.add_child(_feedback)
func _request_decorating() -> void:
if not _home_access:
return
close_storage()
decorate_requested.emit()
func _build_column(parent: HBoxContainer, title_text: String) -> Dictionary:
var panel := PanelContainer.new()
panel.custom_minimum_size = Vector2(550.0, 0.0)