362 lines
10 KiB
GDScript
362 lines
10 KiB
GDScript
class_name PlayerStorage
|
||
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
|
||
var _interaction: PlayerStorageInteraction
|
||
var _layout: PlayerInventoryLayout
|
||
var _inventory_grid: GeneralInventoryGrid
|
||
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
|
||
|
||
|
||
func _ready() -> void:
|
||
UtilityPageStyle.apply_page(self)
|
||
_build_ui()
|
||
hide()
|
||
|
||
|
||
func setup(
|
||
player: Player,
|
||
fishing_spot: FishingSpot,
|
||
interaction: PlayerStorageInteraction,
|
||
layout: PlayerInventoryLayout,
|
||
bag: PlayerBag,
|
||
fish_inventory: FishInventory,
|
||
hotbar: PlayerHotbar,
|
||
item_catalog: ItemCatalog,
|
||
) -> void:
|
||
_player = player
|
||
_fishing_spot = fishing_spot
|
||
_interaction = interaction
|
||
_layout = layout
|
||
_inventory_grid.setup(
|
||
layout,
|
||
bag,
|
||
fish_inventory,
|
||
hotbar,
|
||
item_catalog,
|
||
PlayerInventoryLayout.InventoryContainer.INVENTORY,
|
||
)
|
||
_storage_grid.setup(
|
||
layout,
|
||
bag,
|
||
fish_inventory,
|
||
hotbar,
|
||
item_catalog,
|
||
PlayerInventoryLayout.InventoryContainer.STORAGE,
|
||
)
|
||
_inventory_grid.entry_selected.connect(
|
||
_on_entry_selected.bind(
|
||
PlayerInventoryLayout.InventoryContainer.STORAGE
|
||
)
|
||
)
|
||
_storage_grid.entry_selected.connect(
|
||
_on_entry_selected.bind(
|
||
PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||
)
|
||
)
|
||
if _layout != null and not _layout.layout_changed.is_connected(_refresh):
|
||
_layout.layout_changed.connect(_refresh)
|
||
_refresh()
|
||
|
||
|
||
func set_world_interaction(interaction: PlayerStorageInteraction) -> void:
|
||
_interaction = interaction
|
||
|
||
|
||
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 _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
|
||
_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
|
||
_feedback.text = "select an item to move it · drag to choose a slot"
|
||
_refresh()
|
||
show()
|
||
call_deferred("_focus_first_slot")
|
||
menu_visibility_changed.emit(true)
|
||
return true
|
||
|
||
|
||
func close_storage(restore_controls: bool = true) -> void:
|
||
if not visible:
|
||
return
|
||
var viewport := get_viewport()
|
||
if viewport != null:
|
||
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
|
||
_home_access = false
|
||
menu_visibility_changed.emit(false)
|
||
|
||
|
||
func close_for_range_exit() -> void:
|
||
if _home_access:
|
||
return
|
||
close_storage()
|
||
|
||
|
||
func close_for_water_recovery() -> void:
|
||
close_storage(false)
|
||
|
||
|
||
func close_for_session_end() -> void:
|
||
close_storage(false)
|
||
|
||
|
||
func consume_escape() -> bool:
|
||
if not visible:
|
||
return false
|
||
close_storage()
|
||
return true
|
||
|
||
|
||
func _unhandled_input(event: InputEvent) -> void:
|
||
if not visible or not event.is_action_pressed("ui_cancel"):
|
||
return
|
||
close_storage()
|
||
get_viewport().set_input_as_handled()
|
||
|
||
|
||
func _on_entry_selected(
|
||
kind: int,
|
||
identity: StringName,
|
||
target_container: int,
|
||
) -> void:
|
||
if kind < 0 or identity.is_empty() or _layout == null:
|
||
return
|
||
if _layout.move_entry_to_first_free(kind, identity, target_container):
|
||
_feedback.text = (
|
||
"moved to storage"
|
||
if target_container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||
else "moved to inventory"
|
||
)
|
||
else:
|
||
_feedback.text = (
|
||
"storage is full"
|
||
if target_container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||
else "inventory is full"
|
||
)
|
||
|
||
|
||
func _refresh() -> void:
|
||
if _layout == null:
|
||
return
|
||
_inventory_grid.refresh()
|
||
_storage_grid.refresh()
|
||
_inventory_count.text = "%d / %d" % [
|
||
_layout.get_inventory_count(),
|
||
_layout.get_inventory_capacity(),
|
||
]
|
||
_storage_count.text = "%d / %d" % [
|
||
_layout.get_storage_count(),
|
||
_layout.get_storage_capacity(),
|
||
]
|
||
call_deferred("_connect_grid_focus")
|
||
|
||
|
||
func _focus_first_slot() -> void:
|
||
var slot := _inventory_grid.get_first_occupied_slot()
|
||
if slot == null:
|
||
slot = _storage_grid.get_first_occupied_slot()
|
||
if slot != null:
|
||
slot.grab_focus()
|
||
|
||
|
||
func _connect_grid_focus() -> void:
|
||
var inventory_slots := _inventory_grid.get_slots()
|
||
var storage_slots := _storage_grid.get_slots()
|
||
if inventory_slots.is_empty() or storage_slots.is_empty():
|
||
return
|
||
var row_count := mini(
|
||
ceili(
|
||
float(inventory_slots.size())
|
||
/ float(PlayerInventoryLayout.INVENTORY_COLUMNS)
|
||
),
|
||
ceili(
|
||
float(storage_slots.size())
|
||
/ float(PlayerInventoryLayout.INVENTORY_COLUMNS)
|
||
),
|
||
)
|
||
for row: int in row_count:
|
||
var inventory_index := mini(
|
||
row * PlayerInventoryLayout.INVENTORY_COLUMNS
|
||
+ PlayerInventoryLayout.INVENTORY_COLUMNS - 1,
|
||
inventory_slots.size() - 1,
|
||
)
|
||
var storage_index := mini(
|
||
row * PlayerInventoryLayout.INVENTORY_COLUMNS,
|
||
storage_slots.size() - 1,
|
||
)
|
||
inventory_slots[inventory_index].focus_neighbor_right = (
|
||
inventory_slots[inventory_index].get_path_to(storage_slots[storage_index])
|
||
)
|
||
storage_slots[storage_index].focus_neighbor_left = (
|
||
storage_slots[storage_index].get_path_to(inventory_slots[inventory_index])
|
||
)
|
||
|
||
|
||
func _build_ui() -> void:
|
||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
z_index = 82
|
||
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(-580.0, -310.0)
|
||
panel.size = Vector2(1160.0, 620.0)
|
||
panel.add_theme_stylebox_override(
|
||
"panel",
|
||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_PANEL_DEEP, 22),
|
||
)
|
||
add_child(panel)
|
||
var outer := MarginContainer.new()
|
||
for side: StringName in [&"margin_left", &"margin_top", &"margin_right", &"margin_bottom"]:
|
||
outer.add_theme_constant_override(side, 20)
|
||
panel.add_child(outer)
|
||
var layout := VBoxContainer.new()
|
||
layout.add_theme_constant_override("separation", 12)
|
||
outer.add_child(layout)
|
||
|
||
var header := HBoxContainer.new()
|
||
layout.add_child(header)
|
||
_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"
|
||
close.custom_minimum_size = Vector2(48.0, 48.0)
|
||
close.flat = true
|
||
close.add_theme_font_size_override("font_size", 30)
|
||
close.pressed.connect(close_storage)
|
||
header.add_child(close)
|
||
|
||
var columns := HBoxContainer.new()
|
||
columns.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
columns.add_theme_constant_override("separation", 18)
|
||
layout.add_child(columns)
|
||
var inventory_column := _build_column(columns, "inventory")
|
||
_inventory_count = inventory_column["count"]
|
||
_inventory_grid = GeneralInventoryGrid.new()
|
||
(inventory_column["scroll"] as ScrollContainer).add_child(_inventory_grid)
|
||
var storage_column := _build_column(columns, "storage")
|
||
_storage_count = storage_column["count"]
|
||
_storage_grid = GeneralInventoryGrid.new()
|
||
(storage_column["scroll"] as ScrollContainer).add_child(_storage_grid)
|
||
|
||
_feedback = Label.new()
|
||
_feedback.text = "select an item to move it · drag to choose a slot"
|
||
_feedback.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_feedback.add_theme_color_override(
|
||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||
)
|
||
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)
|
||
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
panel.add_theme_stylebox_override(
|
||
"panel",
|
||
UtilityPageStyle.rounded_style(UtilityPageStyle.OCEAN_PANEL_MID, 16),
|
||
)
|
||
parent.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, 12)
|
||
panel.add_child(margin)
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 8)
|
||
margin.add_child(column)
|
||
var header := HBoxContainer.new()
|
||
column.add_child(header)
|
||
var title := Label.new()
|
||
title.text = title_text
|
||
title.add_theme_font_size_override("font_size", 22)
|
||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
header.add_child(title)
|
||
var count := Label.new()
|
||
count.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||
count.add_theme_color_override(
|
||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||
)
|
||
header.add_child(count)
|
||
var scroll := ScrollContainer.new()
|
||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
column.add_child(scroll)
|
||
return {"count": count, "scroll": scroll}
|