Improve UI scaling and fishing commerce

This commit is contained in:
Alexander Sellite 2026-07-31 21:20:23 -04:00
parent 2de96dd7bb
commit 6b8c3eb2f7
29 changed files with 1515 additions and 817 deletions

View file

@ -60,6 +60,7 @@ var _send_pending: bool = false
var _pending_send_body: String = ""
var _prior_movement: bool = true
var _prior_camera: bool = true
var _output_scale: float = 1.0
func _ready() -> void:
@ -563,6 +564,10 @@ func _set_presentation_state(
_refresh_visibility()
func set_output_scale(output_scale: float) -> void:
_output_scale = maxf(output_scale, 0.001)
func _refresh_handle_labels(state: PresentationState) -> void:
var collapsed := state == PresentationState.COLLAPSED
var height_state := _visible_state_before_collapse if collapsed else state
@ -632,7 +637,9 @@ func _update_speech() -> void:
if camera.is_position_behind(world_position):
bubble.hide()
continue
var screen_position := camera.unproject_position(world_position)
var screen_position := (
camera.unproject_position(world_position) / _output_scale
)
var viewport_size := get_viewport_rect().size
if (
screen_position.x < -80.0

View file

@ -8,7 +8,8 @@ const CONTENT_MARGIN_RIGHT := 18.0
const CONTENT_MARGIN_BOTTOM := 16.0
const HANDWRITTEN_FONT: Font = preload("res://ui/fonts/seattle_avenue.otf")
const NOTEPAD_TEXTURE: Texture2D = preload("res://art/ui/ui_notepad.png")
const NOTEPAD_ART_SCALE := 1.1
const NOTEPAD_CANONICAL_OVERSCAN: float = 1.2
const NOTEPAD_ART_OFFSET := Vector2(10.0, 20.0)
@export var title_text := "notes"
@export var inset_content := true
@ -34,17 +35,7 @@ static func apply_handwritten_to(root: Control) -> void:
func _draw() -> void:
var texture_size := NOTEPAD_TEXTURE.get_size()
var uniform_scale := minf(
size.x / texture_size.x,
size.y / texture_size.y,
) * NOTEPAD_ART_SCALE
var rendered_size := texture_size * uniform_scale
var rendered_rect := Rect2(
(size - rendered_size) * 0.5,
rendered_size,
)
draw_texture_rect(NOTEPAD_TEXTURE, rendered_rect, false)
draw_texture_rect(NOTEPAD_TEXTURE, get_art_rect(), false)
var font: Font = HANDWRITTEN_FONT
draw_string(
@ -58,6 +49,34 @@ func _draw() -> void:
)
func get_art_rect() -> Rect2:
return get_art_rect_for_host(size)
static func get_art_rect_for_host(host_size: Vector2) -> Rect2:
# Source pixels establish the artwork aspect ratio and sampling quality. The
# canonical host rect, not the source dimensions, owns its displayed size.
var source_size: Vector2 = NOTEPAD_TEXTURE.get_size()
if (
host_size.x <= 0.0
or host_size.y <= 0.0
or source_size.x <= 0.0
or source_size.y <= 0.0
):
return Rect2()
var fit_scale: float = minf(
host_size.x / source_size.x,
host_size.y / source_size.y,
)
var rendered_size: Vector2 = (
source_size * fit_scale * NOTEPAD_CANONICAL_OVERSCAN
)
return Rect2(
(host_size - rendered_size) * 0.5 + NOTEPAD_ART_OFFSET,
rendered_size,
)
static func make_layout_style(with_content_margins: bool = true) -> StyleBoxFlat:
var paper := StyleBoxFlat.new()
paper.bg_color = Color.TRANSPARENT

View file

@ -4,10 +4,6 @@ extends Control
const INPUT_OWNER: StringName = &"fishing_shop"
const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd")
const FishCatchType = preload("res://fish/fish_catch.gd")
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
const FishSaleResultType = preload("res://economy/fish_sale_result.gd")
const FishSaleServiceType = preload("res://economy/fish_sale_service.gd")
const PlayerWalletType = preload("res://economy/player_wallet.gd")
const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd")
const ItemCatalogType = preload("res://items/item_catalog.gd")
@ -24,11 +20,12 @@ const PlayerCoolerCapacityType = preload(
const ShopInteractionType = preload(
"res://world/fishing_shop_interaction.gd"
)
const FishBatchSelectionType = preload(
"res://ui/fish_batch_selection.gd"
)
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
signal menu_visibility_changed(is_open: bool)
signal sell_fish_requested
signal shop_cooler_return_requested
signal shop_cooler_confirmation_cancel_requested
enum CloseReason {
USER,
@ -39,17 +36,12 @@ enum CloseReason {
}
@onready var _wallet_label: Label = %WalletLabel
@onready var _fish_sales: VBoxContainer = (
$ShopPanel/Margin/Layout/Body/FishSales
)
@onready var _fish_list: ItemList = %FishList
@onready var _sales_title: Label = %SalesTitle
@onready var _fish_empty: Label = %FishEmpty
@onready var _fish_texture: TextureRect = %FishTexture
@onready var _fish_name: Label = %FishName
@onready var _fish_details: Label = %FishDetails
@onready var _selection_summary: Label = %SelectionSummary
@onready var _sell_button: Button = %SellButton
@onready var _shop_panel: PanelContainer = %ShopPanel
@onready var _shop_cooler_page: Control = %ShopCoolerPage
@onready var _shop_cooler_mount: Control = %ShopCoolerMount
@onready var _back_to_shop_button: Button = %BackToShopButton
@onready var _buy_mode_button: Button = %BuyModeButton
@onready var _sell_mode_button: Button = %SellModeButton
@onready var _feedback: Label = %Feedback
@onready var _reel_level: Label = %ReelLevel
@onready var _reel_effect: Label = %ReelEffect
@ -64,15 +56,9 @@ enum CloseReason {
@onready var _cooler_effect: Label = %CoolerEffect
@onready var _cooler_cost: Label = %CoolerCost
@onready var _cooler_purchase: Button = %CoolerPurchase
@onready var _confirmation: PanelContainer = %SaleConfirmation
@onready var _confirmation_text: Label = %ConfirmationText
@onready var _confirm_sale: Button = %ConfirmSale
@onready var _cancel_sale: Button = %CancelSale
var _player: PlayerType
var _inventory: FishInventoryType
var _wallet: PlayerWalletType
var _sale_service: FishSaleServiceType
var _buyer: FishBuyerProfileType
var _upgrades: PlayerFishingUpgradesType
var _fishing_spot: FishingSpotType
@ -80,42 +66,93 @@ var _interaction: ShopInteractionType
var _bag: PlayerBagType
var _item_catalog: ItemCatalogType
var _cooler_capacity: PlayerCoolerCapacityType
var _network_session: NetworkSession
var _network_shop: NetworkShopService
var _fish_selection := FishBatchSelectionType.new()
var _confirmation_catch_ids: Array[StringName] = []
var _confirmation_generation: int = -1
var _prior_movement_enabled: bool = true
var _prior_camera_enabled: bool = true
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
var _snapshot_stored: bool = false
var _mouse_snapshot_stored: bool = false
var _generation: int = 0
var _selection_input_generation: int = 0
var _transaction_in_progress: bool = false
var _closing: bool = false
var _cooler_page_active: bool = false
var _cooler_modal_open: bool = false
func _ready() -> void:
# Catch selling belongs to the Pelican action in the Player Menu. Keep the
# legacy shop sales controls out of the active interface.
_fish_sales.hide()
UtilityPageStyleType.apply_page(self)
_apply_shop_styles()
%CloseButton.pressed.connect(close_shop)
_fish_list.item_selected.connect(_on_fish_selected)
_fish_list.item_clicked.connect(_on_fish_clicked)
_sell_button.pressed.connect(_open_sale_confirmation)
_confirm_sale.pressed.connect(_on_confirm_sale)
_cancel_sale.pressed.connect(_close_sale_confirmation)
_buy_mode_button.pressed.connect(_focus_buy_page)
_sell_mode_button.pressed.connect(_request_shop_cooler)
_back_to_shop_button.pressed.connect(shop_cooler_return_requested.emit)
_reel_purchase.pressed.connect(_purchase_reel_speed)
_barrier_purchase.pressed.connect(_purchase_barrier_power)
_cooler_purchase.pressed.connect(_purchase_cooler_capacity)
func _request_shop_cooler() -> void:
if _transaction_in_progress:
_feedback.text = "Finish the current transaction first."
return
if not _is_transaction_context_valid():
_feedback.text = "The fishing shop is no longer available."
return
sell_fish_requested.emit()
func _focus_buy_page() -> void:
_feedback.text = ""
if _supplies_list.get_child_count() > 0:
var first_supply := _supplies_list.get_child(0) as Button
if first_supply != null and not first_supply.disabled:
first_supply.grab_focus()
return
_reel_purchase.grab_focus()
func _apply_shop_styles() -> void:
var shop_panel := $ShopPanel as PanelContainer
var shop_panel_style := UtilityPageStyleType.panel_style()
shop_panel_style.set_corner_radius_all(24)
shop_panel_style.content_margin_left = 0.0
shop_panel_style.content_margin_top = 0.0
shop_panel_style.content_margin_right = 0.0
shop_panel_style.content_margin_bottom = 0.0
shop_panel.add_theme_stylebox_override(
"panel", shop_panel_style
)
for panel: PanelContainer in [
$ShopPanel/Margin/Layout/Body/Upgrades/ReelCard,
$ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard,
$ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard,
]:
panel.add_theme_stylebox_override(
"panel", UtilityPageStyleType.row_style()
)
for button: BaseButton in [
%CloseButton,
_buy_mode_button,
_sell_mode_button,
_reel_purchase,
_barrier_purchase,
_cooler_purchase,
_back_to_shop_button,
]:
UtilityPageStyleType.apply_ocean_button(button)
_buy_mode_button.add_theme_stylebox_override(
"normal",
UtilityPageStyleType.ocean_button_style(
UtilityPageStyleType.OCEAN_SELECTED
),
)
_feedback.add_theme_color_override(
"font_color", UtilityPageStyleType.OCEAN_TEXT_SECONDARY
)
func setup(
player: PlayerType,
inventory: FishInventoryType,
wallet: PlayerWalletType,
sale_service: FishSaleServiceType,
buyer: FishBuyerProfileType,
upgrades: PlayerFishingUpgradesType,
fishing_spot: FishingSpotType,
@ -123,13 +160,10 @@ func setup(
bag: PlayerBagType,
item_catalog: ItemCatalogType,
cooler_capacity: PlayerCoolerCapacityType,
network_session: NetworkSession,
network_shop: NetworkShopService,
) -> void:
_player = player
_inventory = inventory
_wallet = wallet
_sale_service = sale_service
_buyer = buyer
_upgrades = upgrades
_fishing_spot = fishing_spot
@ -137,7 +171,6 @@ func setup(
_bag = bag
_item_catalog = item_catalog
_cooler_capacity = cooler_capacity
_network_session = network_session
_network_shop = network_shop
if (
_network_shop != null
@ -151,9 +184,6 @@ func setup(
_network_shop.local_purchase_finished.connect(
_on_network_purchase_finished
)
_fish_selection.clear()
if not _inventory.catches_changed.is_connected(_on_inventory_changed):
_inventory.catches_changed.connect(_on_inventory_changed)
if not _wallet.balance_changed.is_connected(_on_wallet_changed):
_wallet.balance_changed.connect(_on_wallet_changed)
if not _upgrades.upgrades_changed.is_connected(_on_upgrades_changed):
@ -180,7 +210,6 @@ func open_shop() -> bool:
or _buyer.id != MAIN_SHOP_BUYER_ID
):
return false
_generation += 1
_closing = false
_transaction_in_progress = (
_network_shop != null
@ -196,10 +225,10 @@ func open_shop() -> bool:
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_feedback.text = ""
_close_sale_confirmation()
deactivate_shop_cooler_page()
show()
_refresh_all()
%CloseButton.grab_focus()
_buy_mode_button.grab_focus()
menu_visibility_changed.emit(true)
return true
@ -207,13 +236,42 @@ func open_shop() -> bool:
func consume_escape() -> bool:
if not visible:
return false
if _confirmation.visible:
_close_sale_confirmation()
else:
close_shop()
if _cooler_page_active:
if _cooler_modal_open:
shop_cooler_confirmation_cancel_requested.emit()
else:
shop_cooler_return_requested.emit()
return true
close_shop()
return true
func get_shop_cooler_mount() -> Control:
return _shop_cooler_mount
func activate_shop_cooler_page() -> void:
_cooler_page_active = true
_shop_panel.hide()
_shop_cooler_page.show()
_back_to_shop_button.disabled = false
func deactivate_shop_cooler_page() -> void:
_cooler_page_active = false
_cooler_modal_open = false
_back_to_shop_button.disabled = false
_shop_cooler_page.hide()
_shop_panel.show()
if visible:
_buy_mode_button.grab_focus()
func set_shop_cooler_modal_open(is_open: bool) -> void:
_cooler_modal_open = is_open
_back_to_shop_button.disabled = is_open
func close_shop(
reason: CloseReason = CloseReason.USER,
restore_controls: bool = true,
@ -221,10 +279,7 @@ func close_shop(
if not visible:
return
_closing = true
_generation += 1
_transaction_in_progress = false
_close_sale_confirmation()
_fish_selection.clear()
hide()
get_viewport().gui_release_focus()
if _fishing_spot != null and is_instance_valid(_fishing_spot):
@ -241,6 +296,7 @@ func close_shop(
_apply_mouse_close_policy(reason)
_closing = false
menu_visibility_changed.emit(false)
deactivate_shop_cooler_page()
func close_for_range_exit() -> void:
@ -268,8 +324,6 @@ func _refresh_all() -> void:
if not is_node_ready():
return
_refresh_wallet()
_refresh_fish_list()
_refresh_selected_fish()
_refresh_upgrades()
_refresh_supplies()
_refresh_cooler_capacity()
@ -283,144 +337,6 @@ func _refresh_wallet() -> void:
)
func _refresh_fish_list() -> void:
_fish_list.clear()
var catches: Array[FishCatchType] = (
_inventory.get_all_catches() if _inventory != null else []
)
_sales_title.text = "cooler %d / %d • full base value" % [
catches.size(),
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
]
catches.sort_custom(
func(a: FishCatchType, b: FishCatchType) -> bool:
return a.catch_sequence > b.catch_sequence
)
var visible_ids: Array[StringName] = []
for fish_catch: FishCatchType in catches:
if fish_catch != null and fish_catch.is_valid():
visible_ids.append(fish_catch.catch_id)
_fish_selection.set_visible_order(visible_ids)
for fish_catch: FishCatchType in catches:
if fish_catch == null or not fish_catch.is_valid():
continue
var marker: String = ""
if _fish_selection.is_selected(fish_catch.catch_id):
marker += ""
if fish_catch.catch_id == _fish_selection.get_focused_id():
marker += ""
if fish_catch.is_favorited:
marker += ""
var index: int = _fish_list.add_item(
"%s%s%.2f lb"
% [marker, fish_catch.fish.display_name, fish_catch.weight_lb],
fish_catch.fish.display_texture
)
_fish_list.set_item_metadata(index, String(fish_catch.catch_id))
_fish_list.set_item_tooltip(
index,
"%s%s"
% [
fish_catch.fish.get_rarity_name(),
"favorited" if fish_catch.is_favorited else "available",
]
)
_fish_empty.visible = _fish_list.item_count == 0
_fish_list.deselect_all()
for index: int in range(_fish_list.item_count):
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
if _fish_selection.is_selected(catch_id):
_fish_list.select(index, false)
func _refresh_selected_fish() -> void:
var fish_catch: FishCatchType = _get_selected_catch()
_fish_texture.texture = (
fish_catch.fish.display_texture if fish_catch != null else null
)
_fish_name.text = (
fish_catch.fish.display_name if fish_catch != null else "select a fish"
)
if fish_catch == null:
_fish_details.text = "choose one individual fish from your cooler."
_update_sale_summary()
return
var individual_preview: FishSaleResultType = (
_sale_service.preview_batch([fish_catch.catch_id], _buyer)
if _sale_service != null
else null
)
var offer: int = (
individual_preview.payout if individual_preview != null else -1
)
_fish_details.text = (
"%.2f lb • %s\nbase value: $%d\nmain-shop offer: $%d%s"
% [
fish_catch.weight_lb,
fish_catch.fish.get_rarity_name(),
fish_catch.sale_value,
offer,
"\nfavorited fish cannot be sold."
if fish_catch.is_favorited
else "",
]
)
_update_sale_summary()
func _update_sale_summary() -> void:
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
var selected_count: int = selected_ids.size()
_sell_button.text = (
"sell fish"
if selected_count <= 1
else "sell %d fish" % selected_count
)
if selected_count == 0:
_selection_summary.text = "no fish selected"
_sell_button.disabled = true
return
if _network_session != null and _network_session.is_joined_client():
_selection_summary.text = (
"1 fish selected"
if selected_count == 1
else "%d fish selected" % selected_count
)
_selection_summary.text += (
"\nSell catches to the nearby pelicans."
)
_sell_button.disabled = true
return
var preview: FishSaleResultType = (
_sale_service.preview_batch(selected_ids, _buyer)
if _sale_service != null
else null
)
_selection_summary.text = (
"1 fish selected"
if selected_count == 1
else "%d fish selected" % selected_count
)
if (
preview != null
and (
preview.is_success()
or preview.status == FishSaleResultType.Status.FAVORITED
)
):
_selection_summary.text += "\ntotal offer: $%d" % preview.payout
if preview != null and preview.status == FishSaleResultType.Status.FAVORITED:
_selection_summary.text += (
"\nfavorited fish must be removed from the selection."
)
_sell_button.disabled = (
preview == null
or not preview.is_success()
or _transaction_in_progress
or _closing
)
func _refresh_upgrades() -> void:
if _upgrades == null:
return
@ -516,6 +432,7 @@ func _refresh_supplies() -> void:
if _network_shop == null or not _network_shop.can_request_purchase()
else item.description
)
UtilityPageStyleType.apply_ocean_button(button)
button.pressed.connect(_purchase_supply.bind(item_id))
_supplies_list.add_child(button)
@ -549,142 +466,6 @@ func _refresh_cooler_capacity() -> void:
_cooler_purchase.text = "purchase"
func _on_fish_selected(index: int) -> void:
if index < 0 or index >= _fish_list.item_count:
return
var input_generation: int = _selection_input_generation
call_deferred(
"_apply_keyboard_fish_selection",
index,
input_generation
)
func _apply_keyboard_fish_selection(
index: int,
input_generation: int,
) -> void:
if (
input_generation != _selection_input_generation
or index < 0
or index >= _fish_list.item_count
):
return
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
_fish_selection.select_only(catch_id)
_feedback.text = ""
_refresh_fish_list()
_refresh_selected_fish()
func _on_fish_clicked(
index: int,
_position: Vector2,
mouse_button_index: int,
) -> void:
if (
mouse_button_index != MOUSE_BUTTON_LEFT
or index < 0
or index >= _fish_list.item_count
):
return
_selection_input_generation += 1
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
_fish_selection.apply_click(
catch_id,
Input.is_key_pressed(KEY_CTRL),
Input.is_key_pressed(KEY_SHIFT)
)
_feedback.text = ""
_refresh_fish_list()
_refresh_selected_fish()
func _open_sale_confirmation() -> void:
if _network_session != null and _network_session.is_joined_client():
_feedback.text = "Sell catches to the nearby pelicans."
return
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
if not _is_transaction_context_valid() or selected_ids.is_empty():
_feedback.text = "the fish selection is no longer available."
_refresh_all()
return
var preview: FishSaleResultType = _sale_service.preview_batch(
selected_ids,
_buyer
)
if not preview.is_success():
_feedback.text = (
"favorited fish cannot be sold. "
+ "remove them from the selection first."
if preview.status == FishSaleResultType.Status.FAVORITED
else preview.get_message()
)
return
_confirmation_catch_ids = selected_ids.duplicate()
_confirmation_generation = _generation
_confirmation_text.text = (
"sell %d fish to the fishing shop for $%d?"
% [preview.fish_count, preview.payout]
)
_confirmation.show()
_cancel_sale.grab_focus()
func _close_sale_confirmation() -> void:
_confirmation_catch_ids.clear()
_confirmation_generation = -1
_confirmation.hide()
func _on_confirm_sale() -> void:
if (
_transaction_in_progress
or (
_network_session != null
and _network_session.is_joined_client()
)
):
return
var transaction_generation: int = _generation
var catch_ids: Array[StringName] = _confirmation_catch_ids.duplicate()
var confirmation_generation: int = _confirmation_generation
_close_sale_confirmation()
if (
catch_ids.is_empty()
or not _is_transaction_context_valid()
or transaction_generation != _generation
or confirmation_generation != transaction_generation
or _buyer.id != MAIN_SHOP_BUYER_ID
):
_feedback.text = "unable to complete sale."
return
_transaction_in_progress = true
var result: FishSaleResultType = _sale_service.sell_batch(
catch_ids,
_buyer
)
_transaction_in_progress = false
if (
transaction_generation != _generation
or not visible
):
return
if result.is_success():
_feedback.text = (
"fish sold for $%d."
if result.fish_count == 1
else "%d fish sold for $%d." % [
result.fish_count,
result.payout,
]
)
_fish_selection.remove_ids(catch_ids)
else:
_feedback.text = result.get_message()
_refresh_all()
func _purchase_reel_speed() -> void:
_purchase_upgrade(true)
@ -777,13 +558,6 @@ func _on_network_purchase_finished(
_refresh_all()
func _get_selected_catch() -> FishCatchType:
var focused_id: StringName = _fish_selection.get_focused_id()
if _inventory == null or focused_id.is_empty():
return null
return _inventory.get_catch_by_id(focused_id)
func _is_transaction_context_valid() -> bool:
return (
visible
@ -798,27 +572,6 @@ func _is_transaction_context_valid() -> bool:
)
func _on_inventory_changed() -> void:
_refresh_fish_list()
if not _confirmation_catch_ids.is_empty():
var preview: FishSaleResultType = _sale_service.preview_batch(
_confirmation_catch_ids,
_buyer
)
if (
_confirmation_generation != _generation
or not preview.is_success()
):
_close_sale_confirmation()
_feedback.text = (
"favorited fish cannot be sold. "
+ "remove them from the selection first."
if preview.status == FishSaleResultType.Status.FAVORITED
else preview.get_message()
)
_refresh_selected_fish()
func _on_wallet_changed(_balance: int, _delta: int) -> void:
_refresh_wallet()
_refresh_upgrades()
@ -839,7 +592,6 @@ func _on_bag_changed() -> void:
func _on_cooler_capacity_changed(_level: int, _capacity: int) -> void:
_refresh_cooler_capacity()
_refresh_fish_list()
func _apply_mouse_close_policy(reason: CloseReason) -> void:

View file

@ -19,9 +19,10 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme = ExtResource("2_theme")
theme_override_font_sizes/font_size = 20
script = ExtResource("1_script")
[node name="Dimmer" type="ColorRect" parent="."]
[node name="InputBlocker" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
@ -29,28 +30,60 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 0
color = Color(0.015, 0.02, 0.03, 0.64)
[node name="ShopCoolerPage" type="Control" parent="."]
unique_name_in_owner = true
visible = false
z_index = 90
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
[node name="ShopCoolerMount" type="Control" parent="ShopCoolerPage"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
[node name="BackToShopButton" type="Button" parent="ShopCoolerPage"]
unique_name_in_owner = true
z_index = 110
layout_mode = 0
offset_left = 1052.0
offset_top = 54.0
offset_right = 1218.0
offset_bottom = 104.0
text = "back to shop"
[node name="ShopPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -560.0
offset_top = -320.0
offset_right = 560.0
offset_bottom = 320.0
offset_left = -490.0
offset_top = -300.0
offset_right = 490.0
offset_bottom = 300.0
grow_horizontal = 2
grow_vertical = 2
[node name="Margin" type="MarginContainer" parent="ShopPanel"]
layout_mode = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 14
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 14
theme_override_constants/margin_bottom = 12
[node name="Layout" type="VBoxContainer" parent="ShopPanel/Margin"]
layout_mode = 2
@ -64,7 +97,7 @@ theme_override_constants/separation = 8
layout_mode = 2
size_flags_horizontal = 3
text = "fishing shop"
theme_override_font_sizes/font_size = 36
theme_override_font_sizes/font_size = 32
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
[node name="CoinIcon" type="TextureRect" parent="ShopPanel/Margin/Layout/Header"]
@ -84,7 +117,24 @@ text = "wallet: $0"
unique_name_in_owner = true
layout_mode = 2
text = "close"
custom_minimum_size = Vector2(110, 58)
custom_minimum_size = Vector2(104, 48)
[node name="ModeTabs" type="HBoxContainer" parent="ShopPanel/Margin/Layout"]
layout_mode = 2
alignment = 1
theme_override_constants/separation = 10
[node name="BuyModeButton" type="Button" parent="ShopPanel/Margin/Layout/ModeTabs"]
unique_name_in_owner = true
custom_minimum_size = Vector2(180, 42)
layout_mode = 2
text = "buy gear & supplies"
[node name="SellModeButton" type="Button" parent="ShopPanel/Margin/Layout/ModeTabs"]
unique_name_in_owner = true
custom_minimum_size = Vector2(180, 42)
layout_mode = 2
text = "sell fish"
[node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"]
unique_name_in_owner = true
@ -98,123 +148,27 @@ layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 12
[node name="FishSales" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
custom_minimum_size = Vector2(420, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
[node name="SalesTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales"]
unique_name_in_owner = true
layout_mode = 2
text = "sell individual cooler fish • full base value"
theme_override_font_sizes/font_size = 26
[node name="SalesBody" type="HSplitContainer" parent="ShopPanel/Margin/Layout/Body/FishSales"]
layout_mode = 2
size_flags_vertical = 3
split_offset = 220
theme_override_constants/separation = 8
[node name="ListPanel" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody"]
custom_minimum_size = Vector2(205, 0)
layout_mode = 2
[node name="ListMargin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel"]
layout_mode = 2
theme_override_constants/margin_left = 7
theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 7
theme_override_constants/margin_bottom = 7
[node name="ListStack" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin"]
layout_mode = 2
[node name="FishEmpty" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin/ListStack"]
unique_name_in_owner = true
layout_mode = 2
text = "your cooler is empty."
horizontal_alignment = 1
[node name="FishList" type="ItemList" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin/ListStack"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
fixed_icon_size = Vector2i(48, 36)
icon_mode = 1
select_mode = 1
same_column_width = true
[node name="FishDetail" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody"]
custom_minimum_size = Vector2(185, 0)
layout_mode = 2
[node name="DetailMargin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail"]
layout_mode = 2
theme_override_constants/margin_left = 9
theme_override_constants/margin_top = 9
theme_override_constants/margin_right = 9
theme_override_constants/margin_bottom = 9
[node name="DetailStack" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin"]
layout_mode = 2
theme_override_constants/separation = 6
[node name="FishTexture" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
unique_name_in_owner = true
custom_minimum_size = Vector2(160, 90)
layout_mode = 2
expand_mode = 1
stretch_mode = 5
texture_filter = 1
[node name="FishName" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
unique_name_in_owner = true
layout_mode = 2
text = "select a fish"
horizontal_alignment = 1
theme_override_font_sizes/font_size = 29
[node name="FishDetails" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
unique_name_in_owner = true
layout_mode = 2
text = "choose one individual fish from your cooler."
horizontal_alignment = 1
autowrap_mode = 2
[node name="SelectionSummary" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
unique_name_in_owner = true
layout_mode = 2
text = "no fish selected"
horizontal_alignment = 1
autowrap_mode = 2
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
[node name="SellButton" type="Button" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
text = "sell selected"
[node name="Upgrades" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
custom_minimum_size = Vector2(270, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8
[node name="UpgradeTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
layout_mode = 2
text = "fishing upgrades"
theme_override_font_sizes/font_size = 26
theme_override_font_sizes/font_size = 23
[node name="Supplies" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
custom_minimum_size = Vector2(210, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
[node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"]
layout_mode = 2
text = "supplies"
theme_override_font_sizes/font_size = 26
theme_override_font_sizes/font_size = 23
[node name="Scroll" type="ScrollContainer" parent="ShopPanel/Margin/Layout/Body/Supplies"]
layout_mode = 2
@ -233,16 +187,16 @@ layout_mode = 2
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard"]
layout_mode = 2
theme_override_constants/margin_left = 9
theme_override_constants/margin_top = 9
theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 9
theme_override_constants/margin_bottom = 9
theme_override_constants/margin_bottom = 7
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin"]
layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
custom_minimum_size = Vector2(48, 48)
custom_minimum_size = Vector2(44, 44)
layout_mode = 2
texture = ExtResource("3_reel")
expand_mode = 1
@ -256,22 +210,25 @@ size_flags_horizontal = 3
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
layout_mode = 2
text = "reel speed"
theme_override_font_sizes/font_size = 26
theme_override_font_sizes/font_size = 22
[node name="ReelLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "level 0"
theme_override_font_sizes/font_size = 18
[node name="ReelEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "1.00× → 1.10×"
theme_override_font_sizes/font_size = 18
[node name="ReelCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "$50"
theme_override_font_sizes/font_size = 18
[node name="ReelPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
unique_name_in_owner = true
@ -284,16 +241,16 @@ layout_mode = 2
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard"]
layout_mode = 2
theme_override_constants/margin_left = 9
theme_override_constants/margin_top = 9
theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 9
theme_override_constants/margin_bottom = 9
theme_override_constants/margin_bottom = 7
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin"]
layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
custom_minimum_size = Vector2(48, 48)
custom_minimum_size = Vector2(44, 44)
layout_mode = 2
texture = ExtResource("4_barrier")
expand_mode = 1
@ -307,22 +264,25 @@ size_flags_horizontal = 3
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
layout_mode = 2
text = "barrier power"
theme_override_font_sizes/font_size = 26
theme_override_font_sizes/font_size = 22
[node name="BarrierLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "level 0"
theme_override_font_sizes/font_size = 18
[node name="BarrierEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "1 damage → 2 damage"
theme_override_font_sizes/font_size = 18
[node name="BarrierCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "$100"
theme_override_font_sizes/font_size = 18
[node name="BarrierPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
unique_name_in_owner = true
@ -335,16 +295,16 @@ layout_mode = 2
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard"]
layout_mode = 2
theme_override_constants/margin_left = 9
theme_override_constants/margin_top = 9
theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 9
theme_override_constants/margin_bottom = 9
theme_override_constants/margin_bottom = 7
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin"]
layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
custom_minimum_size = Vector2(48, 48)
custom_minimum_size = Vector2(44, 44)
layout_mode = 2
texture = ExtResource("6_cooler")
expand_mode = 1
@ -358,74 +318,27 @@ size_flags_horizontal = 3
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
layout_mode = 2
text = "cooler capacity"
theme_override_font_sizes/font_size = 26
theme_override_font_sizes/font_size = 22
[node name="CoolerLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "level 0"
theme_override_font_sizes/font_size = 18
[node name="CoolerEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "12 → 18 fish"
theme_override_font_sizes/font_size = 18
[node name="CoolerCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
unique_name_in_owner = true
layout_mode = 2
text = "$75"
theme_override_font_sizes/font_size = 18
[node name="CoolerPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
unique_name_in_owner = true
layout_mode = 2
text = "purchase"
[node name="SaleConfirmation" type="PanelContainer" parent="."]
unique_name_in_owner = true
visible = false
z_index = 10
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -320.0
offset_top = -150.0
offset_right = 320.0
offset_bottom = 150.0
grow_horizontal = 2
grow_vertical = 2
[node name="Margin" type="MarginContainer" parent="SaleConfirmation"]
layout_mode = 2
theme_override_constants/margin_left = 18
theme_override_constants/margin_top = 16
theme_override_constants/margin_right = 18
theme_override_constants/margin_bottom = 16
[node name="Stack" type="VBoxContainer" parent="SaleConfirmation/Margin"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="ConfirmationText" type="Label" parent="SaleConfirmation/Margin/Stack"]
unique_name_in_owner = true
layout_mode = 2
autowrap_mode = 2
horizontal_alignment = 1
[node name="Buttons" type="HBoxContainer" parent="SaleConfirmation/Margin/Stack"]
layout_mode = 2
alignment = 1
theme_override_constants/separation = 8
[node name="ConfirmSale" type="Button" parent="SaleConfirmation/Margin/Stack/Buttons"]
unique_name_in_owner = true
layout_mode = 2
text = "sell fish"
theme_type_variation = &"DangerButton"
[node name="CancelSale" type="Button" parent="SaleConfirmation/Margin/Stack/Buttons"]
unique_name_in_owner = true
layout_mode = 2
text = "cancel"

View file

@ -41,6 +41,7 @@ signal crisp_reset_focus_requested
signal interactive_pointer_ui_changed(is_open: bool)
signal passive_pointer_ui_changed(is_enabled: bool)
signal player_menu_backdrop_visibility_changed(is_visible: bool)
signal shop_backdrop_visibility_changed(is_visible: bool)
@onready var _status_label: Label = %StatusLabel
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
@ -65,7 +66,7 @@ signal player_menu_backdrop_visibility_changed(is_visible: bool)
$UIRoot/TitleScreen/ResponsiveTitleStage/TitlePresentationScaleRoot/SettingsPanel
)
@onready var _pause_settings_panel: SettingsPanelType = (
$UIRoot/PauseMenu/ResponsivePauseStage/PausePresentationScaleRoot/SettingsCenter/SettingsPanel
$UIRoot/CanonicalStage/PauseMenu/ResponsivePauseStage/PausePresentationScaleRoot/SettingsCenter/SettingsPanel
)
var _showcase_active: bool = false
@ -77,6 +78,8 @@ var _shop_open: bool = false
var _chat_input_open: bool = false
var _player_menu_hotbar_visible: bool = false
var _item_effects: PlayerItemEffectsType
var _main_shop_buyer: FishBuyerProfileType
var _shop_interaction: ShopInteractionType
func _ready() -> void:
@ -151,6 +154,9 @@ func setup(
_player_menu.inventory_hotbar_context_changed.connect(
_on_inventory_hotbar_context_changed
)
_player_menu.shop_cooler_modal_changed.connect(
_on_shop_cooler_modal_changed
)
_player_menu.setup(
player,
inventory,
@ -174,9 +180,7 @@ func setup(
_hotbar_ui.setup(hotbar, bag, item_catalog, fishing_spot)
_fishing_shop.setup(
player,
inventory,
wallet,
sale_service,
main_shop_buyer,
fishing_upgrades,
fishing_spot,
@ -184,10 +188,18 @@ func setup(
bag,
item_catalog,
cooler_capacity,
network_session,
network_shop_service
network_shop_service,
)
_fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
_fishing_shop.sell_fish_requested.connect(_on_shop_sell_fish_requested)
_fishing_shop.shop_cooler_return_requested.connect(
_on_shop_cooler_return_requested
)
_fishing_shop.shop_cooler_confirmation_cancel_requested.connect(
_player_menu.cancel_shop_cooler_confirmation
)
_main_shop_buyer = main_shop_buyer
_shop_interaction = shop_interaction
func setup_data_and_identity(
@ -554,6 +566,9 @@ func _on_player_menu_exit_started() -> void:
func _on_shop_visibility_changed(is_open: bool) -> void:
_shop_open = is_open
shop_backdrop_visibility_changed.emit(is_open)
if not is_open and _player_menu.is_shop_cooler_mounted():
_player_menu.unmount_shop_cooler()
_refresh_chat_availability()
_refresh_hotbar_visibility()
_hotbar_ui.set_gameplay_input_enabled(
@ -567,6 +582,33 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
_emit_interactive_pointer_ui_changed()
func _on_shop_sell_fish_requested() -> void:
if (
not _fishing_shop.visible
or _main_shop_buyer == null
or not _main_shop_buyer.is_valid()
or _shop_interaction == null
or not _shop_interaction.is_local_player_in_range()
or _fishing_spot == null
or not _fishing_spot.is_ready_for_shop_transaction()
):
return
if _player_menu.mount_shop_cooler(
_fishing_shop.get_shop_cooler_mount(),
_main_shop_buyer,
):
_fishing_shop.activate_shop_cooler_page()
func _on_shop_cooler_return_requested() -> void:
_player_menu.unmount_shop_cooler()
_fishing_shop.deactivate_shop_cooler_page()
func _on_shop_cooler_modal_changed(is_open: bool) -> void:
_fishing_shop.set_shop_cooler_modal_open(is_open)
func _on_inventory_hotbar_context_changed(show_hotbar: bool) -> void:
var context_changed: bool = _player_menu_hotbar_visible != show_hotbar
_player_menu_hotbar_visible = show_hotbar

View file

@ -41,7 +41,11 @@ script = ExtResource("1_ui")
unique_name_in_owner = true
mouse_filter = 2
[node name="GameplayTransientHUD" type="Control" parent="UIRoot"]
[node name="CanonicalStage" type="Control" parent="UIRoot"]
unique_name_in_owner = true
mouse_filter = 2
[node name="GameplayTransientHUD" type="Control" parent="UIRoot/CanonicalStage"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -51,7 +55,7 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="FishingPanel" type="PanelContainer" parent="UIRoot/GameplayTransientHUD"]
[node name="FishingPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
z_index = 60
@ -69,33 +73,33 @@ grow_vertical = 0
mouse_filter = 2
theme = ExtResource("3_theme")
[node name="MarginContainer" type="MarginContainer" parent="UIRoot/GameplayTransientHUD/FishingPanel"]
[node name="MarginContainer" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel"]
theme_override_constants/margin_left = 14
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 14
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer"]
[node name="VBoxContainer" type="VBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer"]
theme_override_constants/separation = 4
[node name="StatusLabel" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="StatusLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
text = ""
horizontal_alignment = 1
theme_override_font_sizes/font_size = 16
[node name="ShowcaseDetails" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="ShowcaseDetails" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
horizontal_alignment = 1
[node name="CatchTrack" type="Control" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="CatchTrack" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(0, 20)
[node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
[node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -109,7 +113,7 @@ theme_override_styles/fill = SubResource("StyleBox_catch_fill")
value = 0.0
show_percentage = false
[node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
[node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -123,7 +127,7 @@ theme_override_styles/fill = SubResource("StyleBox_chase_fill")
value = 0.0
show_percentage = false
[node name="BarrierMarkers" type="Control" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
[node name="BarrierMarkers" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -133,23 +137,23 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="BarrierSummary" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="BarrierSummary" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
horizontal_alignment = 1
[node name="BarrierHealth" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="BarrierHealth" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
horizontal_alignment = 1
[node name="PlayerMenu" parent="UIRoot" instance=ExtResource("2_menu")]
[node name="PlayerMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("2_menu")]
unique_name_in_owner = true
[node name="Hotbar" parent="UIRoot" instance=ExtResource("7_hotbar")]
[node name="Hotbar" parent="UIRoot/CanonicalStage" instance=ExtResource("7_hotbar")]
unique_name_in_owner = true
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/GameplayTransientHUD"]
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
z_index = 55
@ -167,22 +171,22 @@ grow_vertical = 0
mouse_filter = 2
theme = ExtResource("3_theme")
[node name="Margin" type="MarginContainer" parent="UIRoot/GameplayTransientHUD/ShopPrompt"]
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 7
[node name="Label" type="Label" parent="UIRoot/GameplayTransientHUD/ShopPrompt/Margin"]
[node name="Label" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/Margin"]
layout_mode = 2
text = "press e to open fishing shop"
horizontal_alignment = 1
[node name="FishingShop" parent="UIRoot" instance=ExtResource("8_shop")]
[node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")]
unique_name_in_owner = true
[node name="EffectStatus" type="Label" parent="UIRoot/GameplayTransientHUD"]
[node name="EffectStatus" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
z_index = 54
@ -223,5 +227,5 @@ script = ExtResource("4_fade")
[node name="TitleScreen" parent="UIRoot" instance=ExtResource("5_title")]
unique_name_in_owner = true
[node name="PauseMenu" parent="UIRoot" instance=ExtResource("6_pause")]
[node name="PauseMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("6_pause")]
unique_name_in_owner = true

View file

@ -6,7 +6,6 @@ const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
const COMPACT_HEIGHT_THRESHOLD: float = 560.0
const VISIBILITY_FADE_DURATION: float = 0.55
const BACKDROP_TARGET_ALPHA: float = 0.68
const PlayerType = preload("res://player/player.gd")
const SaveManagerType = preload("res://save/player_save_manager.gd")
const SettingsManagerType = preload(
@ -48,7 +47,6 @@ enum CloseReason {
@onready var _presentation_scale_root: Control = %PausePresentationScaleRoot
@onready var _root_page: SettingsBubblePage = %RootPage
@onready var _settings_panel: SettingsPanelType = %SettingsPanel
@onready var _dim_background: ColorRect = %DimBackground
@onready var _transition_flurry: BubbleTransitionFlurry = (
%TransitionBubbleLayer
)
@ -75,8 +73,6 @@ var _action_in_progress: bool = false
var _root_transition_active: bool = false
var _root_transition_generation: int = 0
var _closing_menu: bool = false
var _backdrop_fade: Tween
var _backdrop_fade_generation: int = 0
var _pending_join_endpoint: String = ""
@ -98,7 +94,6 @@ func _ready() -> void:
)
_join_game_page.join_requested.connect(_on_join_game_requested)
_join_game_page.back_requested.connect(_close_join_game)
_dim_background.color.a = 0.0
_confirmation_page.hide_page()
resized.connect(_update_responsive_pause_stage)
call_deferred("_update_responsive_pause_stage")
@ -144,7 +139,6 @@ func open_menu() -> void:
_action_in_progress = false
_root_page.hide_page()
show()
_fade_backdrop(true)
_update_responsive_pause_stage()
_begin_root_entry(false)
menu_visibility_changed.emit(true)
@ -159,7 +153,6 @@ func resume() -> void:
or _confirmation_action != ConfirmationAction.NONE
):
return
_fade_backdrop(false)
_begin_root_exit(_finish_user_close)
@ -268,7 +261,6 @@ func _finish_open_settings() -> void:
_settings_panel.open_panel(
_settings_manager,
SettingsPanelType.PresentationMode.GAMEPLAY_MODAL,
true,
true
)
@ -493,9 +485,6 @@ func _finish_close(reason: CloseReason, restore_controls: bool) -> void:
_closing_menu = false
_root_transition_generation += 1
_root_transition_active = false
_cancel_backdrop_fade()
_dim_background.color.a = 0.0
_dim_background.hide()
_settings_panel.hide()
_join_game_page.close_page()
_root_page.hide_page()
@ -519,45 +508,6 @@ func _emit_transition_flurry() -> void:
_transition_flurry.emit_flurry()
func _fade_backdrop(fade_in: bool) -> void:
_backdrop_fade_generation += 1
_cancel_backdrop_fade()
_dim_background.show()
var target_alpha: float = BACKDROP_TARGET_ALPHA if fade_in else 0.0
if is_equal_approx(_dim_background.color.a, target_alpha):
_finish_backdrop_fade(_backdrop_fade_generation, fade_in)
return
var generation: int = _backdrop_fade_generation
_backdrop_fade = create_tween()
_backdrop_fade.tween_property(
_dim_background,
"color:a",
target_alpha,
VISIBILITY_FADE_DURATION
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
_backdrop_fade.finished.connect(
_finish_backdrop_fade.bind(generation, fade_in),
CONNECT_ONE_SHOT
)
func _finish_backdrop_fade(generation: int, faded_in: bool) -> void:
if generation != _backdrop_fade_generation:
return
_backdrop_fade = null
_dim_background.color.a = (
BACKDROP_TARGET_ALPHA if faded_in else 0.0
)
if not faded_in:
_dim_background.hide()
func _cancel_backdrop_fade() -> void:
if _backdrop_fade != null:
_backdrop_fade.kill()
_backdrop_fade = null
func _update_responsive_pause_stage() -> void:
if not is_node_ready():
return

View file

@ -25,17 +25,6 @@ mouse_filter = 0
theme = ExtResource("3_theme")
script = ExtResource("1_script")
[node name="DimBackground" type="ColorRect" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 0
color = Color(0.025, 0.12, 0.19, 0.68)
[node name="TransitionBubbleLayer" type="Control" parent="."]
unique_name_in_owner = true
z_index = 1

View file

@ -79,10 +79,12 @@ const COOLER_RARITY_UNCOMMON := Color("64c87c")
const COOLER_RARITY_RARE := Color("6098dd")
const COOLER_RARITY_EPIC := Color("a979cf")
const COOLER_RARITY_LEGENDARY := Color("db78a7")
const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
signal menu_visibility_changed(is_open: bool)
signal inventory_hotbar_context_changed(show_hotbar: bool)
signal menu_exit_started
signal shop_cooler_modal_changed(is_open: bool)
enum Section {
COOLER,
@ -131,6 +133,7 @@ const INVENTORY_HEADER_INSET := Vector2(24.0, 20.0)
const INVENTORY_HEADER_HEIGHT := 40.0
const INVENTORY_MAIN_CORNER_RADIUS: int = 58
const INVENTORY_INNER_CORNER_RADIUS: int = 45
const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
@onready var _navigation_cluster: BubbleClusterType = %NavigationCluster
@onready var _presentation_scale_root: Control = %PlayerMenuPresentationScaleRoot
@ -276,6 +279,12 @@ var _reservations: PlayerAssetReservationService
var _network_profile_service: NetworkProfileService
var _network_player_list: NetworkPlayerListService
var _default_buyer: FishBuyerProfileType
var _sale_buyer_override: FishBuyerProfileType
var _shop_cooler_context_active: bool = false
var _cooler_original_parent: Node
var _cooler_original_index: int = -1
var _confirmation_original_parent: Node
var _confirmation_original_index: int = -1
var _catalog: FishPoolType
var _fishing_spot: FishingSpotType
var _bag: PlayerBagType
@ -336,6 +345,10 @@ var _logbook_page_tween: Tween
func _ready() -> void:
_configure_inventory_transition_group()
_cooler_original_parent = _cooler_page.get_parent()
_cooler_original_index = _cooler_page.get_index()
_confirmation_original_parent = _sale_confirmation.get_parent()
_confirmation_original_index = _sale_confirmation.get_index()
_inventory_sub_tabs.move_child(_tackle_sub_tab, 1)
_inventory_sub_tabs.move_child(_bag_sub_tab, 2)
_inventory_tab.pressed.connect(_show_last_inventory_section)
@ -652,6 +665,7 @@ func consume_escape() -> bool:
func open_menu() -> void:
if (
visible
or _shop_cooler_context_active
or _player == null
or _fishing_spot == null
or not _fishing_spot.can_open_player_menu()
@ -678,6 +692,99 @@ func open_menu() -> void:
menu_visibility_changed.emit(true)
func mount_shop_cooler(
host: Control,
buyer: FishBuyerProfileType,
) -> bool:
if (
visible
or _shop_cooler_context_active
or host == null
or not is_instance_valid(host)
or buyer == null
or not buyer.is_valid()
or buyer.id != MAIN_SHOP_BUYER_ID
):
return false
_menu_generation += 1
_cancel_page_tween()
_transitioning = false
_page_transitioning = false
_sale_buyer_override = buyer
_shop_cooler_context_active = true
_current_section = Section.COOLER
_last_inventory_section = Section.COOLER
_cooler_page.reparent(host, false)
_sale_confirmation.reparent(host, false)
_cooler_page.position = Vector2.ZERO
_cooler_page.size = DESKTOP_REFERENCE_SIZE
_cooler_page.scale = Vector2.ONE
_cooler_page.modulate = Color.WHITE
_cooler_page.visible = true
_sale_confirmation.visible = false
_update_shell_layout()
_refresh_inventory()
_set_content_interactive(true)
_update_cooler_water_mask()
set_process(true)
call_deferred("_focus_shop_cooler")
return true
func unmount_shop_cooler() -> void:
if not _shop_cooler_context_active:
return
_close_sale_confirmation()
_set_content_interactive(false)
_cooler_page.visible = false
_cooler_page.reparent(_cooler_original_parent, false)
_cooler_original_parent.move_child(
_cooler_page,
mini(_cooler_original_index, _cooler_original_parent.get_child_count() - 1),
)
_sale_confirmation.reparent(_confirmation_original_parent, false)
_confirmation_original_parent.move_child(
_sale_confirmation,
mini(
_confirmation_original_index,
_confirmation_original_parent.get_child_count() - 1,
),
)
_sale_buyer_override = null
_shop_cooler_context_active = false
_menu_generation += 1
if not visible:
set_process(false)
func is_shop_cooler_mounted() -> bool:
return _shop_cooler_context_active
func cancel_shop_cooler_confirmation() -> void:
if _shop_cooler_context_active and _sale_confirmation.visible:
_close_sale_confirmation()
func _focus_shop_cooler() -> void:
if not _shop_cooler_context_active or not _cooler_page.is_visible_in_tree():
return
var focused_node := _fish_nodes.get(
_fish_selection.get_focused_id()
) as CoolerFishSpriteType
if focused_node != null and focused_node.focus_mode != Control.FOCUS_NONE:
focused_node.grab_focus()
return
for fish_catch: FishCatchType in _sorted_catches:
var fish_node := _fish_nodes.get(
fish_catch.catch_id
) as CoolerFishSpriteType
if fish_node != null and fish_node.focus_mode != Control.FOCUS_NONE:
fish_node.grab_focus()
return
_cooler_sort_option.grab_focus()
func close_menu(
reason: CloseReason = CloseReason.USER,
restore_controls: bool = true,
@ -886,6 +993,9 @@ func _show_last_inventory_section() -> void:
func _focus_current_section() -> void:
if _shop_cooler_context_active:
_focus_shop_cooler()
return
if _current_section == Section.COOLER:
_cooler_sub_tab.grab_focus()
elif _current_section == Section.BAG:
@ -903,12 +1013,15 @@ func _focus_current_section() -> void:
func _process(delta: float) -> void:
if visible:
if visible or _shop_cooler_context_active:
_motion_elapsed += delta
_navigation_cluster.advance_motion(delta)
_apply_navigation_selection_presentation()
if visible:
_navigation_cluster.advance_motion(delta)
_apply_navigation_selection_presentation()
for fish_node: CoolerFishSpriteType in _fish_nodes.values():
fish_node.advance_presentation(delta, _motion_elapsed)
if not visible:
return
var bag_motion_enabled: bool = (
not _bag_drag_active and not get_viewport().gui_is_dragging()
)
@ -1040,6 +1153,20 @@ func _apply_inventory_styles() -> void:
"font_color",
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
)
_sale_confirmation.add_theme_stylebox_override(
"panel",
UtilityPageStyle.panel_style(),
)
_confirmation_message.add_theme_font_override(
"font",
UtilityPageStyle.TuffyFont,
)
_confirmation_message.add_theme_color_override(
"font_color",
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
)
UtilityPageStyle.apply_ocean_button(_confirm_sale_button)
UtilityPageStyle.apply_ocean_button(_cancel_sale_button)
func _set_bag_view(view: BagView) -> void:
@ -1531,11 +1658,9 @@ func _update_shell_layout() -> void:
_players_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
_players_page.position = Vector2.ZERO
_players_page.size = reference_size
_sale_confirmation.size = SALE_CONFIRMATION_SIZE
_sale_confirmation.position = (
Vector2(70.0, 128.0) if compact else Vector2(320.0, 200.0)
)
_sale_confirmation.size = (
Vector2(500.0, 250.0) if compact else Vector2(640.0, 320.0)
(reference_size - SALE_CONFIRMATION_SIZE) * 0.5
)
_cooler_host.custom_minimum_size = Vector2(
520.0 if compact else 800.0,
@ -2592,11 +2717,18 @@ func _configure_cooler_fish_focus() -> void:
controls.append(fish_node)
var columns: int = 3 if _compact_layout else 9
if controls.is_empty():
_cooler_sub_tab.focus_neighbor_bottom = NodePath()
if not _shop_cooler_context_active:
_cooler_sub_tab.focus_neighbor_bottom = NodePath()
return
_cooler_sub_tab.focus_neighbor_bottom = _cooler_sub_tab.get_path_to(
controls.front()
var top_control: Control = (
_cooler_sort_option
if _shop_cooler_context_active
else _cooler_sub_tab
)
if not _shop_cooler_context_active:
_cooler_sub_tab.focus_neighbor_bottom = _cooler_sub_tab.get_path_to(
controls.front()
)
for index: int in controls.size():
var control: CoolerFishSpriteType = controls[index]
control.focus_neighbor_left = control.get_path_to(
@ -2606,7 +2738,7 @@ func _configure_cooler_fish_focus() -> void:
controls[mini(index + 1, controls.size() - 1)]
)
control.focus_neighbor_top = (
control.get_path_to(_cooler_sub_tab)
control.get_path_to(top_control)
if index < columns
else control.get_path_to(controls[index - columns])
)
@ -2776,9 +2908,10 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
_cooler_detail_texture.texture = fish_catch.fish.display_texture
_cooler_detail_texture.visible = fish_catch.fish.display_texture != null
_cooler_detail_name.text = fish_catch.fish.display_name
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
var individual_preview: FishSaleResultType = (
_sale_service.preview_batch([fish_catch.catch_id], _default_buyer)
if _sale_service != null
_sale_service.preview_batch([fish_catch.catch_id], active_buyer)
if _sale_service != null and active_buyer != null
else null
)
var buyer_offer: int = (
@ -2793,8 +2926,8 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
):
_cooler_detail_name.text += " • reserved in mail"
_cooler_weight_unit.text = "lb • %s" % fish_catch.fish.get_rarity_name()
if buyer_offer >= 0 and _default_buyer != null:
_cooler_offer_label.text = "%s offer" % _default_buyer.display_name
if buyer_offer >= 0 and active_buyer != null:
_cooler_offer_label.text = _get_offer_label(active_buyer)
_cooler_offer_value.text = "$%d" % buyer_offer
else:
_cooler_offer_label.text = "buyer unavailable"
@ -2831,6 +2964,11 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
func _update_sale_summary() -> void:
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
var buyer_id: StringName = (
active_buyer.id if active_buyer != null else StringName()
)
var offer_label: String = _get_offer_label(active_buyer)
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
var selected_count: int = selected_ids.size()
_sell_button.text = (
@ -2842,7 +2980,7 @@ func _update_sale_summary() -> void:
if selected_count == 0:
_selection_summary.text = "no fish selected"
_selection_status.set_content("selected", "none")
_offer_status.set_content("pelican offer", "")
_offer_status.set_content(offer_label, "")
_sell_button.text = "sell fish"
_sell_button.disabled = true
_sell_bubble.disabled = true
@ -2866,7 +3004,7 @@ func _update_sale_summary() -> void:
else "%d fish selected" % selected_count
)
_selection_status.set_content("selected", str(selected_count))
_offer_status.set_content("pelican offer", "pending")
_offer_status.set_content(offer_label, "pending")
_sell_button.disabled = true
_sell_bubble.disabled = true
_sale_unavailable.text = "Selling…"
@ -2874,7 +3012,8 @@ func _update_sale_summary() -> void:
return
if (
_network_sale_service == null
or not _network_sale_service.can_request_sale()
or buyer_id.is_empty()
or not _network_sale_service.can_request_sale(buyer_id)
):
_selection_summary.text = (
"1 fish selected"
@ -2882,7 +3021,7 @@ func _update_sale_summary() -> void:
else "%d fish selected" % selected_count
)
_selection_status.set_content("selected", str(selected_count))
_offer_status.set_content("pelican offer", "unavailable")
_offer_status.set_content(offer_label, "unavailable")
_sell_button.disabled = true
_sell_bubble.disabled = true
_sell_bubble.persistent_mark = false
@ -2893,8 +3032,8 @@ func _update_sale_summary() -> void:
_sale_unavailable.visible = true
return
var preview: FishSaleResultType = (
_sale_service.preview_batch(selected_ids, _default_buyer)
if _sale_service != null
_sale_service.preview_batch(selected_ids, active_buyer)
if _sale_service != null and active_buyer != null
else null
)
var count_text: String = (
@ -2907,21 +3046,21 @@ func _update_sale_summary() -> void:
if (
preview != null
and preview.payout >= 0
and _default_buyer != null
and active_buyer != null
and (
preview.is_success()
or preview.status == FishSaleResultType.Status.FAVORITED
)
):
_selection_summary.text += "\n%s total offer: $%d" % [
_default_buyer.display_name,
active_buyer.display_name,
preview.payout,
]
_set_cooler_selection_summary(selected_count, preview.payout)
_offer_status.set_content("pelican offer", "$%d" % preview.payout)
_offer_status.set_content(offer_label, "$%d" % preview.payout)
else:
_set_cooler_selection_summary(selected_count, -1)
_offer_status.set_content("pelican offer", "unavailable")
_offer_status.set_content(offer_label, "unavailable")
_sell_button.disabled = preview == null or not preview.is_success()
_sell_bubble.disabled = _sell_button.disabled
_sell_bubble.persistent_mark = false
@ -2962,6 +3101,10 @@ func _on_favorite_pressed() -> void:
func _on_sell_pressed() -> void:
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
var buyer_id: StringName = (
active_buyer.id if active_buyer != null else StringName()
)
if (
_sale_in_progress
or (
@ -2973,7 +3116,8 @@ func _on_sell_pressed() -> void:
return
if (
_network_sale_service == null
or not _network_sale_service.can_request_sale()
or buyer_id.is_empty()
or not _network_sale_service.can_request_sale(buyer_id)
):
_transaction_feedback.text = (
"Selling is not supported by this server."
@ -2983,13 +3127,13 @@ func _on_sell_pressed() -> void:
if (
_inventory == null
or _sale_service == null
or _default_buyer == null
or active_buyer == null
or selected_ids.is_empty()
):
return
var preview: FishSaleResultType = _sale_service.preview_batch(
selected_ids,
_default_buyer
active_buyer
)
if not preview.is_success():
_transaction_feedback.text = (
@ -3001,8 +3145,8 @@ func _on_sell_pressed() -> void:
_refresh_inventory()
return
_confirmation_catch_ids = selected_ids.duplicate()
_confirmation_buyer = _default_buyer
_confirmation_buyer_id = _default_buyer.id
_confirmation_buyer = active_buyer
_confirmation_buyer_id = active_buyer.id
_confirmation_generation = _menu_generation
var fish_label: String = "fish"
_confirmation_message.text = (
@ -3010,12 +3154,14 @@ func _on_sell_pressed() -> void:
% [
preview.fish_count,
fish_label,
_get_buyer_display_group(_default_buyer),
_get_buyer_display_group(active_buyer),
preview.payout,
preview.base_value,
]
)
_sale_confirmation.visible = true
if _shop_cooler_context_active:
shop_cooler_modal_changed.emit(true)
_set_shell_interactive(false)
for button: Button in [_confirm_sale_button, _cancel_sale_button]:
button.focus_mode = Control.FOCUS_ALL
@ -3028,7 +3174,9 @@ func _on_confirm_sale_pressed() -> void:
if (
_sale_in_progress
or _network_sale_service == null
or not _network_sale_service.can_request_sale()
or not _network_sale_service.can_request_sale(
_confirmation_buyer_id
)
or _sale_service == null
or _confirmation_catch_ids.is_empty()
or _confirmation_buyer == null
@ -3040,11 +3188,13 @@ func _on_confirm_sale_pressed() -> void:
var requested_catch_ids: Array[StringName] = (
_confirmation_catch_ids.duplicate()
)
var requested_buyer_id: StringName = _confirmation_buyer_id
var transaction_generation: int = _menu_generation
_confirm_sale_button.disabled = true
_close_sale_confirmation()
var request_id: String = _network_sale_service.request_local_sale(
requested_catch_ids
requested_catch_ids,
requested_buyer_id,
)
if request_id.is_empty():
_sale_in_progress = false
@ -3052,7 +3202,7 @@ func _on_confirm_sale_pressed() -> void:
if (
_network_sale_service.is_local_sale_pending()
and transaction_generation == _menu_generation
and visible
and (visible or _shop_cooler_context_active)
):
_transaction_feedback.text = "Selling…"
@ -3066,7 +3216,7 @@ func _can_use_shared_world_actions() -> bool:
func _on_network_sale_pending(_request_id: String) -> void:
_sale_in_progress = true
if visible:
if visible or _shop_cooler_context_active:
_transaction_feedback.text = "Selling…"
_update_sale_summary()
@ -3081,19 +3231,26 @@ func _on_network_sale_finished(
_sale_in_progress = false
if accepted:
_fish_selection.remove_ids(catch_ids)
if not visible:
if not visible and not _shop_cooler_context_active:
return
_transaction_feedback.text = (
_refresh_all()
var feedback_message: String = (
"Sale complete. $%d received." % payout
if accepted
else message
)
_refresh_all()
_transaction_feedback.text = feedback_message
if not accepted:
_sale_unavailable.text = feedback_message
_sale_unavailable.visible = true
if _current_section == Section.COOLER:
call_deferred("_restore_inventory_tab_focus")
func _restore_inventory_tab_focus() -> void:
if _shop_cooler_context_active:
_focus_shop_cooler()
return
if (
visible
and _current_section == Section.COOLER
@ -3110,6 +3267,8 @@ func _close_sale_confirmation() -> void:
_confirmation_buyer_id = StringName()
_confirmation_generation = -1
_sale_confirmation.visible = false
if was_visible and _shop_cooler_context_active:
shop_cooler_modal_changed.emit(false)
_confirmation_message.text = ""
_confirm_sale_button.disabled = false
for button: Button in [_confirm_sale_button, _cancel_sale_button]:
@ -3117,7 +3276,7 @@ func _close_sale_confirmation() -> void:
button.mouse_filter = Control.MOUSE_FILTER_IGNORE
if (
was_visible
and visible
and (visible or _shop_cooler_context_active)
and not _transitioning
and not _page_transitioning
):
@ -3171,6 +3330,22 @@ func _get_buyer_display_group(
return "buyer"
func _get_active_sale_buyer() -> FishBuyerProfileType:
if _sale_buyer_override != null and _sale_buyer_override.is_valid():
return _sale_buyer_override
return _default_buyer
func _get_offer_label(buyer: FishBuyerProfileType) -> String:
if buyer == null:
return "buyer offer"
if buyer.id == MAIN_SHOP_BUYER_ID:
return "shop offer"
if buyer.id == NetworkSaleService.PELICAN_BUYER_ID:
return "pelican offer"
return "buyer offer"
func _refresh_logbook() -> void:
if not is_node_ready():
return

View file

@ -487,7 +487,7 @@ mouse_filter = 1
[node name="BagFilterTabs" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
unique_name_in_owner = true
z_index = 5
z_index = 55
layout_mode = 0
offset_left = 122.0
offset_top = 166.0
@ -1330,12 +1330,12 @@ columns = 4
[node name="SaleConfirmation" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
unique_name_in_owner = true
visible = false
z_index = 40
z_index = 120
layout_mode = 0
offset_left = 320.0
offset_top = 200.0
offset_right = 960.0
offset_bottom = 520.0
offset_left = 380.0
offset_top = 265.0
offset_right = 840.0
offset_bottom = 455.0
[node name="ConfirmationMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation"]
layout_mode = 2
@ -1347,29 +1347,31 @@ theme_override_constants/margin_bottom = 20
[node name="ConfirmationLayout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin"]
layout_mode = 2
theme_override_constants/separation = 12
alignment = 1
[node name="ConfirmationMessage" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 22
autowrap_mode = 2
horizontal_alignment = 1
[node name="ConfirmationButtons" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
layout_mode = 2
theme_override_constants/separation = 10
alignment = 1
[node name="ConfirmSaleButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
unique_name_in_owner = true
layout_mode = 2
text = "confirm"
theme_type_variation = &"DangerButton"
custom_minimum_size = Vector2(170, 64)
custom_minimum_size = Vector2(140, 44)
[node name="CancelSaleButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
unique_name_in_owner = true
layout_mode = 2
text = "cancel"
custom_minimum_size = Vector2(170, 64)
custom_minimum_size = Vector2(140, 44)
[node name="NavigationCluster" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
unique_name_in_owner = true

View file

@ -32,7 +32,6 @@ enum PresentationMode {
GAMEPLAY_MODAL,
}
@onready var _backdrop: ColorRect = $Backdrop
@onready var _root_page: SettingsBubblePage = %RootPage
@onready var _display_page: SettingsBubblePage = %DisplayPage
@onready var _controls_page: SettingsBubblePage = %ControlsPage
@ -210,15 +209,10 @@ func open_panel(
settings_manager: SettingsManagerType,
presentation_mode: PresentationMode = PresentationMode.GAMEPLAY_MODAL,
animate_gameplay_host_transitions: bool = false,
uses_external_backdrop: bool = false,
) -> void:
_cancel_page_transition()
_presentation_mode = presentation_mode
_animate_gameplay_host_transitions = animate_gameplay_host_transitions
_backdrop.visible = (
presentation_mode == PresentationMode.GAMEPLAY_MODAL
and not uses_external_backdrop
)
_settings_manager = settings_manager
_load_controls()
_feedback.text = ""

View file

@ -15,16 +15,6 @@ anchors_preset = 0
theme = ExtResource("2_theme")
script = ExtResource("1_panel")
[node name="Backdrop" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0.025, 0.085, 0.115, 0.72)
[node name="SettingsFeedback" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 1

View file

@ -1541,12 +1541,12 @@ func _spawn_decorative_fish(generation: int) -> void:
CROSSING_DURATION_MIN,
CROSSING_DURATION_MAX
)
fish_control.position = Vector2(
-presentation_size.x - FISH_EDGE_MARGIN
if direction > 0.0
else layer_size.x + FISH_EDGE_MARGIN,
base_y
var horizontal_bounds: Vector2 = get_decorative_fish_crossing_bounds(
layer_size.x,
presentation_size.x,
direction,
)
fish_control.position = Vector2(horizontal_bounds.x, base_y)
_decorative_fish_layer.add_child(fish_control)
_decorative_fish = fish_control
_decorative_fish_tween = create_tween()
@ -1586,17 +1586,14 @@ func _update_decorative_fish(
or not is_instance_valid(fish_control)
):
return
var start_x: float = (
-fish_control.size.x - FISH_EDGE_MARGIN
if direction > 0.0
else _decorative_fish_layer.size.x + FISH_EDGE_MARGIN
var horizontal_bounds: Vector2 = get_decorative_fish_crossing_bounds(
_decorative_fish_layer.size.x,
fish_control.size.x,
direction,
)
var end_x: float = (
_decorative_fish_layer.size.x + FISH_EDGE_MARGIN
if direction > 0.0
else -fish_control.size.x - FISH_EDGE_MARGIN
fish_control.position.x = lerpf(
horizontal_bounds.x, horizontal_bounds.y, progress
)
fish_control.position.x = lerpf(start_x, end_x, progress)
var bobbed_y: float = (
base_y
+ sin(progress * crossing_duration * bob_speed) * bob_amplitude
@ -1609,6 +1606,20 @@ func _update_decorative_fish(
fish_control.position = _snap_world_preview(fish_control.position)
static func get_decorative_fish_crossing_bounds(
layer_width: float,
fish_width: float,
direction: float,
) -> Vector2:
var outside_left: float = -fish_width - FISH_EDGE_MARGIN
var outside_right: float = layer_width + FISH_EDGE_MARGIN
return (
Vector2(outside_left, outside_right)
if direction > 0.0
else Vector2(outside_right, outside_left)
)
func _on_decorative_fish_finished(
fish_control: TextureRect,
generation: int,

View file

@ -13,6 +13,13 @@ signal effective_pixel_size_changed(
@onready var _ui_viewport: SubViewport = $UIViewport
@onready var _ui_root: Control = $UIViewport/GameUI/UIRoot
@onready var _canonical_stage: Control = (
$UIViewport/GameUI/UIRoot/CanonicalStage
)
@onready var _chat_ui: ChatUI = $UIViewport/GameUI/UIRoot/ChatUI
@onready var _title_content_stage: Control = (
$UIViewport/GameUI/UIRoot/TitleScreen/ResponsiveTitleStage
)
var _requested_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
var _effective_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
@ -94,21 +101,29 @@ func _resize_presentation() -> void:
float(render_height)
/ UIReferencePresentationType.REFERENCE_SIZE.y
)
var visible_reference_size: Vector2 = (
UIReferencePresentationType.get_visible_reference_size(display_size)
)
var viewport_size := Vector2i(
roundi(
UIReferencePresentationType.REFERENCE_SIZE.x
* render_scale
),
render_height,
roundi(visible_reference_size.x * render_scale),
roundi(visible_reference_size.y * render_scale),
)
set_anchors_preset(Control.PRESET_TOP_LEFT)
position = UIReferencePresentationType.get_offset(display_size)
position = Vector2.ZERO
size = Vector2(viewport_size)
scale = Vector2.ONE * (reference_scale / render_scale)
stretch_shrink = 1
_ui_root.position = Vector2.ZERO
_ui_root.scale = Vector2.ONE * render_scale
_ui_root.size = UIReferencePresentationType.REFERENCE_SIZE
_ui_root.size = visible_reference_size
_canonical_stage.position = (
UIReferencePresentationType.get_stage_position(display_size)
)
_canonical_stage.size = UIReferencePresentationType.REFERENCE_SIZE
_title_content_stage.set_anchors_preset(Control.PRESET_TOP_LEFT)
_title_content_stage.position = _canonical_stage.position
_title_content_stage.size = UIReferencePresentationType.REFERENCE_SIZE
_chat_ui.set_output_scale(reference_scale)
if (
previous_effective_size != _effective_pixel_size
or _requested_pixel_size != _effective_pixel_size

View file

@ -1,6 +1,15 @@
class_name UIReferencePresentation
extends RefCounted
# All functional UI is authored in this coordinate system and receives one
# centered, uniform transform at presentation time. Texture source dimensions
# describe sampling quality and aspect ratio; a canonical Control rect owns
# every texture's on-screen layout size.
#
# Edge-docked overlays use the same canonical units and uniform scale, but may
# use the extra logical canvas outside the centered stage on non-16:9 windows.
# This keeps deliberate window-edge attachment without letting individual
# controls calculate monitor- or DPI-specific geometry.
const REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
@ -18,6 +27,14 @@ static func get_offset(display_size: Vector2) -> Vector2:
) * 0.5
static func get_visible_reference_size(display_size: Vector2) -> Vector2:
return display_size / get_scale(display_size)
static func get_stage_position(display_size: Vector2) -> Vector2:
return (get_visible_reference_size(display_size) - REFERENCE_SIZE) * 0.5
static func get_rect(display_size: Vector2) -> Rect2:
var presentation_scale: float = get_scale(display_size)
return Rect2(