Polish inventory shop and utility interfaces
Split inventory items and tackle views, standardize bait supply presentation, add status-effect icons, and align shop, logbook, art-kit, and shared utility layouts.
This commit is contained in:
parent
84695c473b
commit
a60925bf7b
21 changed files with 1150 additions and 385 deletions
|
|
@ -151,9 +151,6 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
)
|
||||
else:
|
||||
_service.cancel_local_interaction(_request_id)
|
||||
status_changed.emit(
|
||||
"Sneak close, pull the net all the way back, and line up the marker."
|
||||
)
|
||||
_reset_charge_state()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ const ENERGY_DRINK_ID: StringName = &"energy_drink"
|
|||
const SNACK_ID: StringName = &"snack"
|
||||
const FISH_FINDER_ID: StringName = &"fish_finder"
|
||||
const BATTERIES_ID: StringName = &"batteries"
|
||||
const EFFECT_IDS: Array[StringName] = [
|
||||
COFFEE_ID,
|
||||
ENERGY_DRINK_ID,
|
||||
SNACK_ID,
|
||||
FISH_FINDER_ID,
|
||||
]
|
||||
const FISH_FINDER_DEAD_MESSAGE: String = "hmm... batteries are dead..."
|
||||
const COFFEE_DURATION: float = 90.0
|
||||
const ENERGY_DRINK_DURATION: float = 90.0
|
||||
|
|
@ -100,6 +106,10 @@ func get_remaining_snapshot() -> Dictionary[StringName, float]:
|
|||
return _remaining.duplicate()
|
||||
|
||||
|
||||
func get_registered_effect_ids() -> Array[StringName]:
|
||||
return EFFECT_IDS.duplicate()
|
||||
|
||||
|
||||
func get_effect_duration(item_id: StringName) -> float:
|
||||
return _get_duration(item_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -399,6 +399,33 @@ func _run() -> void:
|
|||
assert(hide_button.button_group.allow_unpress)
|
||||
assert(brush_option.item_count == 4)
|
||||
assert(grid_option.item_count == 4)
|
||||
var expected_brush_icons: Array[String] = [
|
||||
"art_kit_marker_tip_fine.png",
|
||||
"art_kit_marker_tip_thin.png",
|
||||
"art_kit_marker_tip_mid.png",
|
||||
"art_kit_marker_tip_thick.png",
|
||||
]
|
||||
var expected_grid_icons: Array[String] = [
|
||||
"art_kit_grid_small_light.png",
|
||||
"art_kit_grid_medium_light.png",
|
||||
"art_kit_grid_large_light.png",
|
||||
"art_kit_grid_xl_light.png",
|
||||
]
|
||||
for index: int in 4:
|
||||
assert(brush_option.get_item_text(index).is_empty())
|
||||
assert(grid_option.get_item_text(index).is_empty())
|
||||
assert(brush_option.get_item_icon(index) != null)
|
||||
assert(grid_option.get_item_icon(index) != null)
|
||||
assert(
|
||||
brush_option.get_item_icon(index).resource_path.ends_with(
|
||||
expected_brush_icons[index]
|
||||
)
|
||||
)
|
||||
assert(
|
||||
grid_option.get_item_icon(index).resource_path.ends_with(
|
||||
expected_grid_icons[index]
|
||||
)
|
||||
)
|
||||
assert(not brush_option.get_popup().is_item_disabled(0))
|
||||
assert(brush_option.get_popup().is_item_disabled(1))
|
||||
assert(not grid_option.get_popup().is_item_disabled(0))
|
||||
|
|
|
|||
|
|
@ -570,6 +570,53 @@ func _test_fishing_shop_sale_ui(
|
|||
and shop_backdrop != null
|
||||
and ui_viewport != null
|
||||
)
|
||||
var item_catalog := main.get("item_catalog") as ItemCatalog
|
||||
var worms: ItemData = item_catalog.get_item_by_id(&"worms")
|
||||
assert(worms != null and worms.max_stack == 10)
|
||||
if player.bag.get_quantity(worms.item_id) == 0:
|
||||
assert(player.bag.add_item(worms.item_id, 4))
|
||||
assert(player.equip_bait(worms))
|
||||
await process_frame
|
||||
var expected_bait_supply := UtilityPageStyle.supply_quantity_text(
|
||||
player.bag.get_quantity(worms.item_id),
|
||||
worms.max_stack,
|
||||
)
|
||||
var hud_bait_quantity := game_ui.get_node(
|
||||
"%ActiveBaitQuantity"
|
||||
) as Label
|
||||
assert(hud_bait_quantity.text == expected_bait_supply)
|
||||
var hud_bait_badge := hud_bait_quantity.get_parent() as Panel
|
||||
assert(
|
||||
is_equal_approx(
|
||||
hud_bait_badge.rotation_degrees,
|
||||
UtilityPageStyle.SUPPLY_BADGE_ROTATION_DEGREES,
|
||||
)
|
||||
)
|
||||
player_menu.call("_refresh_tackle_box")
|
||||
await process_frame
|
||||
var tackle_bait_button: Button
|
||||
for child: Node in player_menu.get_node("%BaitItemList").get_children():
|
||||
var candidate := child as Button
|
||||
if (
|
||||
candidate != null
|
||||
and candidate.get_meta(&"controller_tackle_item_id", StringName())
|
||||
== worms.item_id
|
||||
):
|
||||
tackle_bait_button = candidate
|
||||
break
|
||||
assert(tackle_bait_button != null)
|
||||
var tackle_quantity := tackle_bait_button.get_node(
|
||||
"QuantityBadge/Quantity"
|
||||
) as Label
|
||||
assert(tackle_quantity.text == expected_bait_supply)
|
||||
assert(tackle_bait_button.tooltip_text.contains(expected_bait_supply))
|
||||
var tackle_badge := tackle_quantity.get_parent() as Panel
|
||||
assert(
|
||||
is_equal_approx(
|
||||
tackle_badge.rotation_degrees,
|
||||
UtilityPageStyle.SUPPLY_BADGE_ROTATION_DEGREES,
|
||||
)
|
||||
)
|
||||
player.global_position = interaction.global_position
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
|
|
@ -596,6 +643,14 @@ func _test_fishing_shop_sale_ui(
|
|||
assert(shop_panel_style.corner_radius_top_left == 24)
|
||||
var upgrade_grid := shop.get_node("%UpgradeGrid") as GridContainer
|
||||
assert(upgrade_grid != null and upgrade_grid.columns == 3)
|
||||
assert(
|
||||
upgrade_grid.get_theme_constant("h_separation")
|
||||
== FishingShop.SHOP_SLOT_SEPARATION
|
||||
)
|
||||
assert(
|
||||
upgrade_grid.get_theme_constant("v_separation")
|
||||
== FishingShop.SHOP_SLOT_SEPARATION
|
||||
)
|
||||
assert((shop.get_node("%Upgrades") as Control).visible)
|
||||
assert(not (shop.get_node("%Supplies") as Control).visible)
|
||||
for upgrade_name: String in [
|
||||
|
|
@ -638,6 +693,89 @@ func _test_fishing_shop_sale_ui(
|
|||
assert(sell_mode != null and sell_mode.text == "Sell Fish")
|
||||
var equipment_tab := shop_tabs[3] as Button
|
||||
assert(equipment_tab != null and equipment_tab.text == "Equipment")
|
||||
var supplies_list := shop.get_node("%SuppliesList") as VBoxContainer
|
||||
var bait_tab := shop_tabs[1] as Button
|
||||
assert(bait_tab != null and bait_tab.text == "Bait and Lures")
|
||||
await _activate_pointer_control(bait_tab, ui_viewport)
|
||||
await process_frame
|
||||
var worms_button := supplies_list.find_child(
|
||||
"Supply_worms", true, false
|
||||
) as Button
|
||||
var snails_button := supplies_list.find_child(
|
||||
"Supply_snails", true, false
|
||||
) as Button
|
||||
assert(worms_button != null and snails_button != null)
|
||||
var bait_grid := worms_button.get_parent().get_parent() as GridContainer
|
||||
assert(bait_grid != null)
|
||||
assert(
|
||||
bait_grid.get_theme_constant("h_separation")
|
||||
== FishingShop.SHOP_SLOT_SEPARATION
|
||||
)
|
||||
assert(
|
||||
bait_grid.get_theme_constant("v_separation")
|
||||
== FishingShop.SHOP_SLOT_SEPARATION
|
||||
)
|
||||
var shop_bait_quantity := worms_button.get_node(
|
||||
"SupplyQuantityBadge/Quantity"
|
||||
) as Label
|
||||
assert(shop_bait_quantity.text == expected_bait_supply)
|
||||
var shop_bait_badge := shop_bait_quantity.get_parent() as Panel
|
||||
assert(shop_bait_badge.position.y == FishingShop.BAIT_SUPPLY_BADGE_Y)
|
||||
assert(
|
||||
is_equal_approx(
|
||||
shop_bait_badge.rotation_degrees,
|
||||
UtilityPageStyle.SUPPLY_BADGE_ROTATION_DEGREES,
|
||||
)
|
||||
)
|
||||
assert(
|
||||
shop_bait_badge.position.y + shop_bait_badge.size.y
|
||||
<= FishingShop.SUPPLY_PRICE_Y
|
||||
)
|
||||
var worms_unit_price: int = FishingShopStock.get_price(worms.item_id)
|
||||
assert(
|
||||
worms_button.tooltip_text.contains(
|
||||
"%d fish coin%s per bait piece" % [
|
||||
worms_unit_price,
|
||||
"" if worms_unit_price == 1 else "s",
|
||||
]
|
||||
)
|
||||
)
|
||||
assert(not worms_button.tooltip_text.contains("owned"))
|
||||
var worms_restock_quantity: int = FishingShopStock.get_purchase_quantity(
|
||||
worms.item_id,
|
||||
player.bag.get_quantity(worms.item_id),
|
||||
worms.max_stack,
|
||||
)
|
||||
var worms_restock_cost: int = FishingShopStock.get_purchase_cost(
|
||||
worms.item_id,
|
||||
worms_restock_quantity,
|
||||
player.bag.is_bait_unlocked(worms.item_id),
|
||||
)
|
||||
var worms_price := worms_button.get_parent().get_node(
|
||||
"PriceBubble/CurrencyAmount/Price"
|
||||
) as Label
|
||||
assert(worms_price.text == str(maxi(worms_restock_cost, 0)))
|
||||
var bait_receipt: String = shop.call(
|
||||
"_purchase_success_feedback",
|
||||
worms.item_id,
|
||||
NetworkShopProtocol.ProductCategory.SUPPLY,
|
||||
maxi(worms_restock_quantity, 1),
|
||||
maxi(worms_restock_cost, worms_unit_price),
|
||||
)
|
||||
assert(bait_receipt.begins_with("Bait Restocked\n"))
|
||||
assert(bait_receipt.contains(CurrencyPresentation.ICON_PATH))
|
||||
assert(bait_receipt.contains(" spent on "))
|
||||
assert(bait_receipt.ends_with(" worms"))
|
||||
assert(snails_button.get_node_or_null("SupplyQuantityBadge") == null)
|
||||
assert(worms_button.get_node_or_null("UnlockStateIcon") == null)
|
||||
var snails_lock_icon := snails_button.get_node(
|
||||
"UnlockStateIcon"
|
||||
) as TextureRect
|
||||
assert(not bool(snails_lock_icon.get_meta(&"unlocked")))
|
||||
assert(snails_lock_icon.texture.resource_path.ends_with(
|
||||
"/lock_light.png"
|
||||
))
|
||||
assert(is_equal_approx(snails_lock_icon.modulate.a, 0.75))
|
||||
await _activate_pointer_control(equipment_tab, ui_viewport)
|
||||
await process_frame
|
||||
var equipment_sections: Array[String] = []
|
||||
|
|
@ -645,17 +783,21 @@ func _test_fishing_shop_sale_ui(
|
|||
if child is Label:
|
||||
equipment_sections.append((child as Label).text)
|
||||
assert(equipment_sections == ["rods", "equipment"])
|
||||
var supplies_list := shop.get_node("%SuppliesList") as VBoxContainer
|
||||
var rod_carousel := supplies_list.get_node("RodCarousel") as HBoxContainer
|
||||
assert(rod_carousel != null)
|
||||
var rod_cards := rod_carousel.get_node("RodScroll/RodCards") as HBoxContainer
|
||||
assert(rod_cards != null and rod_cards.get_child_count() == 1)
|
||||
assert(
|
||||
rod_cards.get_theme_constant("separation")
|
||||
== FishingShop.SHOP_SLOT_SEPARATION
|
||||
)
|
||||
var basic_rod_button := rod_cards.find_child(
|
||||
"Rod_basic_fishing_rod", true, false
|
||||
) as Button
|
||||
assert(basic_rod_button != null)
|
||||
assert(basic_rod_button.custom_minimum_size == Vector2(144.0, 144.0))
|
||||
assert(basic_rod_button.icon != null and basic_rod_button.disabled)
|
||||
assert(basic_rod_button.get_node_or_null("UnlockStateIcon") == null)
|
||||
assert(rod_cards.find_child("Rod_aurora_rod", true, false) == null)
|
||||
await _activate_pointer_control(art_supplies_tab, ui_viewport)
|
||||
await process_frame
|
||||
|
|
@ -681,6 +823,42 @@ func _test_fishing_shop_sale_ui(
|
|||
marker_material.get_shader_parameter("marker_color")
|
||||
== SurfaceDrawingPalette.get_color(color_id)
|
||||
)
|
||||
var expected_art_upgrade_icons: Dictionary[StringName, String] = {
|
||||
&"brush_2x": "art_kit_marker_tip_thin.png",
|
||||
&"brush_3x": "art_kit_marker_tip_mid.png",
|
||||
&"brush_4x": "art_kit_marker_tip_thick.png",
|
||||
&"grid_32x": "art_kit_grid_medium_light.png",
|
||||
&"grid_64x": "art_kit_grid_large_light.png",
|
||||
&"grid_128x": "art_kit_grid_xl_light.png",
|
||||
}
|
||||
var found_art_upgrade_icons: int = 0
|
||||
for upgrade_node: Node in shop.find_children("*", "Button", true, false):
|
||||
var upgrade_button := upgrade_node as Button
|
||||
if not upgrade_button.has_meta(&"art_product_id"):
|
||||
continue
|
||||
var product_id := StringName(
|
||||
str(upgrade_button.get_meta(&"art_product_id"))
|
||||
)
|
||||
if not expected_art_upgrade_icons.has(product_id):
|
||||
continue
|
||||
found_art_upgrade_icons += 1
|
||||
assert(upgrade_button.text.is_empty())
|
||||
assert(upgrade_button.icon != null)
|
||||
var product_unlocked: bool = (
|
||||
player.art_unlocks.owns_product(product_id)
|
||||
)
|
||||
var state_icon := upgrade_button.get_node_or_null(
|
||||
"UnlockStateIcon"
|
||||
) as TextureRect
|
||||
assert((state_icon == null) == product_unlocked)
|
||||
if state_icon != null:
|
||||
assert(state_icon.texture.resource_path.ends_with("/lock_light.png"))
|
||||
assert(
|
||||
upgrade_button.icon.resource_path.ends_with(
|
||||
expected_art_upgrade_icons[product_id]
|
||||
)
|
||||
)
|
||||
assert(found_art_upgrade_icons == expected_art_upgrade_icons.size())
|
||||
var price_bubbles := shop.find_children(
|
||||
"PriceBubble", "PanelContainer", true, false
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,25 @@ func _initialize() -> void:
|
|||
var shop := FishingShopType.new()
|
||||
assert(shop.call("_unlock_status", false) == "locked")
|
||||
assert(shop.call("_unlock_status", true) == "unlocked")
|
||||
var locked_button := Button.new()
|
||||
shop.call(
|
||||
"_add_unlock_state_icon", locked_button, false, Vector2(72.0, 72.0)
|
||||
)
|
||||
var locked_icon := locked_button.get_node(
|
||||
"UnlockStateIcon"
|
||||
) as TextureRect
|
||||
assert(locked_icon.texture.resource_path.ends_with("/lock_light.png"))
|
||||
assert(locked_icon.size == Vector2(48.0, 48.0))
|
||||
assert(locked_icon.position == Vector2(12.0, 6.0))
|
||||
assert(is_equal_approx(locked_icon.modulate.a, 0.75))
|
||||
assert(not bool(locked_icon.get_meta(&"unlocked")))
|
||||
locked_button.free()
|
||||
var unlocked_button := Button.new()
|
||||
shop.call(
|
||||
"_add_unlock_state_icon", unlocked_button, true, Vector2(72.0, 72.0)
|
||||
)
|
||||
assert(unlocked_button.get_node_or_null("UnlockStateIcon") == null)
|
||||
unlocked_button.free()
|
||||
shop.free()
|
||||
|
||||
var magnet: ItemDataType = ItemCatalogResource.get_available_item_by_id(
|
||||
|
|
@ -30,6 +49,12 @@ func _initialize() -> void:
|
|||
assert(FishingShopStockType.get_price(&"magnet") == 250)
|
||||
assert(FishingShopStockType.get_stock_item_ids().has(&"magnet"))
|
||||
assert(FishingShopStockType.is_permanent_unlock(&"magnet", magnet))
|
||||
var crab_net: ItemDataType = ItemCatalogResource.get_available_item_by_id(
|
||||
&"crab_net"
|
||||
)
|
||||
assert(crab_net != null)
|
||||
assert(crab_net.icon != null)
|
||||
assert(crab_net.icon.resource_path.ends_with("/equipment/temp_net.png"))
|
||||
|
||||
var wallet := PlayerWalletType.new()
|
||||
wallet.current_balance = 250
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ func _run() -> void:
|
|||
))
|
||||
_validate_shared_inventory_geometry(player_menu)
|
||||
_validate_inventory_layering(player_menu)
|
||||
_validate_tackle_to_items_transition(player_menu)
|
||||
_validate_cooler_notepad_typography(player_menu)
|
||||
_validate_resolution_matrix()
|
||||
await _capture_inventory_pages(player_menu)
|
||||
|
|
@ -101,6 +102,15 @@ func _apply_stage_layout(stage: Control, display_size: Vector2) -> void:
|
|||
|
||||
|
||||
func _validate_shared_inventory_geometry(player_menu: PlayerMenu) -> void:
|
||||
for node_name: StringName in [
|
||||
&"CoolerOuterWall",
|
||||
&"BagOuterWall",
|
||||
&"TackleMainPanel",
|
||||
]:
|
||||
var main_panel := player_menu.get_node("%%%s" % node_name) as Control
|
||||
assert(main_panel != null)
|
||||
assert(main_panel.position.is_equal_approx(INVENTORY_PANEL_RECT.position))
|
||||
assert(main_panel.size.is_equal_approx(INVENTORY_PANEL_RECT.size))
|
||||
for node_name: StringName in [
|
||||
&"DetailConstellation",
|
||||
&"BagDetailConstellation",
|
||||
|
|
@ -113,17 +123,24 @@ func _validate_shared_inventory_geometry(player_menu: PlayerMenu) -> void:
|
|||
|
||||
|
||||
func _validate_inventory_layering(player_menu: PlayerMenu) -> void:
|
||||
var bag_filters := player_menu.get_node("%BagFilterTabs") as Control
|
||||
var bag_panel := player_menu.get_node("%BagOuterWall") as Control
|
||||
var sale_confirmation := player_menu.get_node("%SaleConfirmation") as Control
|
||||
var cooler_panel := player_menu.get_node("%CoolerOuterWall") as Control
|
||||
var tackle_panel := player_menu.get_node("%TackleMainPanel") as Control
|
||||
assert(bag_filters != null)
|
||||
var inventory_tabs := player_menu.get_node("%InventorySubTabs") as Control
|
||||
var items_tab := player_menu.get_node("%ItemsSubTab") as Button
|
||||
var bait_list := player_menu.get_node("%BaitItemList") as GridContainer
|
||||
var lure_list := player_menu.get_node("%LureItemList") as GridContainer
|
||||
assert(bag_panel != null)
|
||||
assert(sale_confirmation != null)
|
||||
assert(cooler_panel != null)
|
||||
assert(tackle_panel != null)
|
||||
assert(bag_filters.z_index > bag_panel.z_index)
|
||||
assert(inventory_tabs != null)
|
||||
assert(items_tab != null and items_tab.text == "Items")
|
||||
assert(bait_list != null and bait_list.columns == 3)
|
||||
assert(lure_list != null and lure_list.columns == 3)
|
||||
assert(bait_list.get_theme_constant("h_separation") == 28)
|
||||
assert(lure_list.get_theme_constant("h_separation") == 28)
|
||||
assert(sale_confirmation.z_index > cooler_panel.z_index)
|
||||
assert(sale_confirmation.z_index > tackle_panel.z_index)
|
||||
# The contextual Hotbar uses z=90 while the Player Menu is open.
|
||||
|
|
@ -141,6 +158,28 @@ func _validate_inventory_layering(player_menu: PlayerMenu) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _validate_tackle_to_items_transition(player_menu: PlayerMenu) -> void:
|
||||
var empty_state := player_menu.get_node("%BagEmptyState") as Label
|
||||
player_menu.set("_bag_view", PlayerMenu.BagView.EQUIPMENT)
|
||||
player_menu.call("_refresh_bag")
|
||||
assert(empty_state.text == "No equipment in your Bag.")
|
||||
player_menu.call(
|
||||
"_show_section_immediate", PlayerMenu.Section.TACKLE_BOX
|
||||
)
|
||||
player_menu.call("_show_bag_view", PlayerMenu.BagView.CONSUMABLES)
|
||||
assert(
|
||||
player_menu.get("_current_section") == PlayerMenu.Section.BAG
|
||||
)
|
||||
assert(
|
||||
player_menu.get("_bag_view") == PlayerMenu.BagView.CONSUMABLES
|
||||
)
|
||||
assert(empty_state.text == "No items in your Bag.")
|
||||
player_menu.call("_cancel_page_tween")
|
||||
player_menu.call(
|
||||
"_show_section_immediate", PlayerMenu.Section.COOLER
|
||||
)
|
||||
|
||||
|
||||
func _validate_cooler_notepad_typography(player_menu: PlayerMenu) -> void:
|
||||
var sort_choice := player_menu.get_node("%CoolerSortOption") as Control
|
||||
var sort_direction := player_menu.get_node(
|
||||
|
|
@ -276,10 +315,10 @@ func _capture_inventory_pages(player_menu: PlayerMenu) -> void:
|
|||
player_menu.visible = true
|
||||
var sections: Array[PlayerMenu.Section] = [
|
||||
PlayerMenu.Section.COOLER,
|
||||
PlayerMenu.Section.TACKLE_BOX,
|
||||
PlayerMenu.Section.BAG,
|
||||
PlayerMenu.Section.TACKLE_BOX,
|
||||
]
|
||||
var suffixes: Array[String] = ["cooler", "tackle", "equipment"]
|
||||
var suffixes: Array[String] = ["cooler", "equipment", "tackle"]
|
||||
for index: int in sections.size():
|
||||
player_menu.call("_show_section_immediate", sections[index])
|
||||
await process_frame
|
||||
|
|
|
|||
|
|
@ -82,6 +82,34 @@ func _validate_page() -> void:
|
|||
page.get("_category") == LogbookCatalog.Category.FRESH_WATER
|
||||
)
|
||||
assert((page.get("_catalog_grid") as GridContainer).columns == 4)
|
||||
var category_tabs: Array = page.get("_category_tabs") as Array
|
||||
var category_tab_categories: Array = (
|
||||
page.get("_category_tab_categories") as Array
|
||||
)
|
||||
assert(category_tab_categories == [
|
||||
LogbookCatalog.Category.FRESH_WATER,
|
||||
LogbookCatalog.Category.SALT_WATER,
|
||||
LogbookCatalog.Category.SHELLFISH,
|
||||
LogbookCatalog.Category.OTHER,
|
||||
])
|
||||
assert(category_tabs.size() == category_tab_categories.size())
|
||||
for tab_node: Variant in category_tabs:
|
||||
var category_tab := tab_node as Button
|
||||
assert(category_tab.size == LogbookPage.LOGBOOK_TAB_SIZE)
|
||||
var text_width: float = UtilityPageStyle.TuffyFont.get_string_size(
|
||||
category_tab.text,
|
||||
HORIZONTAL_ALIGNMENT_LEFT,
|
||||
-1.0,
|
||||
OrganizerTab.FONT_SIZE,
|
||||
).x
|
||||
assert(
|
||||
text_width + LogbookPage.LOGBOOK_TAB_TEXT_SIDE_INSET * 2.0
|
||||
<= category_tab.size.x
|
||||
)
|
||||
assert(
|
||||
(category_tabs[0] as Button).get_parent().position.x
|
||||
== LogbookPage.LOGBOOK_TAB_LEFT_INSET
|
||||
)
|
||||
var initial_detail_body := page.get("_detail_body") as VBoxContainer
|
||||
assert(not initial_detail_body.get_parent() is ScrollContainer)
|
||||
assert(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ func _run() -> void:
|
|||
var hotbar := game_ui.get_node(
|
||||
"UIRoot/CanonicalStage/Hotbar"
|
||||
) as HotbarUI
|
||||
assert(game_ui.get_node_or_null(
|
||||
"UIRoot/CanonicalStage/GameplayTransientHUD/EffectStatus"
|
||||
) == null)
|
||||
var screen_fade := game_ui.get_node("UIRoot/ScreenFade") as Control
|
||||
var title_screen := game_ui.get_node("UIRoot/TitleScreen") as TitleScreen
|
||||
var title_content_stage := title_screen.get_node(
|
||||
|
|
@ -154,9 +157,13 @@ func _run() -> void:
|
|||
assert(presenter.size.is_equal_approx(Vector2(expected_viewport_size)))
|
||||
var chat_panel := chat_ui.get_node("ChatPanel") as Control
|
||||
var clock_panel := chat_ui.get_node("WorldClockPanel") as Control
|
||||
var status_effect_column := chat_ui.get_node(
|
||||
"StatusEffectColumn"
|
||||
) as Control
|
||||
var weather_icon := chat_ui.get_node("WorldWeatherIcon") as Control
|
||||
assert(chat_panel != null)
|
||||
assert(clock_panel != null)
|
||||
assert(status_effect_column != null)
|
||||
assert(weather_icon != null)
|
||||
assert(chat_panel.visible)
|
||||
assert(is_equal_approx(chat_panel.position.x, 0.0))
|
||||
|
|
@ -168,6 +175,17 @@ func _run() -> void:
|
|||
ChatUI.CLOCK_EDGE_MARGIN,
|
||||
ChatUI.CLOCK_EDGE_MARGIN,
|
||||
)))
|
||||
assert(is_equal_approx(
|
||||
status_effect_column.position.x,
|
||||
clock_panel.position.x
|
||||
+ (ChatUI.CLOCK_SIZE.x - ChatUI.STATUS_EFFECT_ICON_SIZE.x) * 0.5,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
status_effect_column.position.y,
|
||||
clock_panel.position.y
|
||||
+ ChatUI.CLOCK_SIZE.y
|
||||
+ ChatUI.STATUS_EFFECT_TOP_GAP,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
weather_icon.position.y, ChatUI.CLOCK_EDGE_MARGIN
|
||||
))
|
||||
|
|
|
|||
128
ui/chat_ui.gd
128
ui/chat_ui.gd
|
|
@ -36,10 +36,12 @@ const CLOCK_FONT_SIZE: int = 27
|
|||
const CLOCK_EDGE_MARGIN: float = 10.0
|
||||
const WEATHER_ICON_SIZE := Vector2(51.0, 51.0)
|
||||
const WEATHER_ICON_GAP: float = 6.0
|
||||
const FISH_FINDER_EFFECT_ICON_SIZE := Vector2(18.0, 18.0)
|
||||
const FISH_FINDER_EFFECT_ICON: Texture2D = preload(
|
||||
"res://items/icons/consumables/64_consumable_fishfinder.png"
|
||||
)
|
||||
const STATUS_EFFECT_ICON_SIZE := Vector2(40.0, 40.0)
|
||||
const STATUS_EFFECT_TOP_GAP: float = 6.0
|
||||
const STATUS_EFFECT_ICON_GAP: int = 4
|
||||
const STATUS_EFFECT_WARNING_RATIO: float = 0.10
|
||||
const STATUS_EFFECT_PULSE_PERIOD: float = 1.0
|
||||
const STATUS_EFFECT_PULSE_MIN_ALPHA: float = 0.25
|
||||
const CHAT_SHOW_ICON: Texture2D = preload(
|
||||
"res://ui/icons/pictograms/arrow_light_right_more.png"
|
||||
)
|
||||
|
|
@ -57,6 +59,8 @@ const WorldWeatherServiceType = preload(
|
|||
"res://world/world_weather_service.gd"
|
||||
)
|
||||
const WeatherIconType = preload("res://ui/weather_icon.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
|
||||
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
|
||||
const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd")
|
||||
|
|
@ -92,7 +96,8 @@ var _animalese_voice: AnimaleseVoiceType
|
|||
var _clock_panel: PanelContainer
|
||||
var _clock_label: Label
|
||||
var _weather_icon: WeatherIconType
|
||||
var _fish_finder_effect_icon: TextureRect
|
||||
var _status_effect_column: VBoxContainer
|
||||
var _status_effect_icons: Dictionary[StringName, TextureRect] = {}
|
||||
var _speech: Dictionary[int, Dictionary] = {}
|
||||
var _draft_save_timer: Timer
|
||||
var _opacity_tween: Tween
|
||||
|
|
@ -117,6 +122,7 @@ var _mobile_mode: bool = false
|
|||
var _world_time: WorldTimeServiceType
|
||||
var _world_weather: WorldWeatherServiceType
|
||||
var _item_effects: PlayerItemEffects
|
||||
var _item_catalog: ItemCatalogType
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -137,6 +143,7 @@ func setup(
|
|||
world_time: WorldTimeServiceType,
|
||||
world_weather: WorldWeatherServiceType,
|
||||
item_effects: PlayerItemEffects,
|
||||
item_catalog: ItemCatalogType,
|
||||
) -> void:
|
||||
_service = service
|
||||
_session = session
|
||||
|
|
@ -147,6 +154,8 @@ func setup(
|
|||
_world_time = world_time
|
||||
_world_weather = world_weather
|
||||
_item_effects = item_effects
|
||||
_item_catalog = item_catalog
|
||||
_rebuild_status_effect_icons()
|
||||
if (
|
||||
_fishing_spot != null
|
||||
and not _fishing_spot.local_speech_requested.is_connected(
|
||||
|
|
@ -195,6 +204,77 @@ func setup(
|
|||
_refresh_history()
|
||||
|
||||
|
||||
func _rebuild_status_effect_icons() -> void:
|
||||
for child: Node in _status_effect_column.get_children():
|
||||
_status_effect_column.remove_child(child)
|
||||
child.queue_free()
|
||||
_status_effect_icons.clear()
|
||||
if _item_effects == null or _item_catalog == null:
|
||||
_status_effect_column.hide()
|
||||
return
|
||||
for item_id: StringName in _item_effects.get_registered_effect_ids():
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||
if item == null or item.icon == null:
|
||||
continue
|
||||
var icon := TextureRect.new()
|
||||
icon.name = "StatusEffect_%s" % String(item_id)
|
||||
icon.texture = item.icon
|
||||
icon.custom_minimum_size = STATUS_EFFECT_ICON_SIZE
|
||||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
icon.hide()
|
||||
_status_effect_column.add_child(icon)
|
||||
_status_effect_icons[item_id] = icon
|
||||
var icon_count: int = _status_effect_icons.size()
|
||||
_status_effect_column.size = Vector2(
|
||||
STATUS_EFFECT_ICON_SIZE.x,
|
||||
STATUS_EFFECT_ICON_SIZE.y * icon_count
|
||||
+ STATUS_EFFECT_ICON_GAP * maxi(icon_count - 1, 0),
|
||||
)
|
||||
|
||||
|
||||
func _update_status_effect_icons() -> void:
|
||||
if _status_effect_column == null:
|
||||
return
|
||||
var has_active_effect: bool = false
|
||||
for item_id: StringName in _status_effect_icons:
|
||||
var icon: TextureRect = _status_effect_icons[item_id]
|
||||
var remaining: float = (
|
||||
_item_effects.get_remaining(item_id)
|
||||
if _item_effects != null
|
||||
else 0.0
|
||||
)
|
||||
icon.visible = remaining > 0.0
|
||||
icon.modulate = Color.WHITE
|
||||
if remaining <= 0.0:
|
||||
continue
|
||||
has_active_effect = true
|
||||
var warning_duration: float = (
|
||||
_item_effects.get_effect_duration(item_id)
|
||||
* STATUS_EFFECT_WARNING_RATIO
|
||||
)
|
||||
if warning_duration <= 0.0 or remaining > warning_duration:
|
||||
continue
|
||||
var warning_elapsed: float = maxf(
|
||||
warning_duration - remaining, 0.0
|
||||
)
|
||||
var pulse_phase: float = (
|
||||
fmod(warning_elapsed, STATUS_EFFECT_PULSE_PERIOD)
|
||||
/ STATUS_EFFECT_PULSE_PERIOD
|
||||
)
|
||||
var pulse_weight: float = 0.5 + 0.5 * cos(pulse_phase * TAU)
|
||||
icon.modulate.a = lerpf(
|
||||
STATUS_EFFECT_PULSE_MIN_ALPHA, 1.0, pulse_weight
|
||||
)
|
||||
_status_effect_column.visible = (
|
||||
has_active_effect
|
||||
and _available
|
||||
and (not _hud_hidden or _opened)
|
||||
)
|
||||
|
||||
|
||||
func open_chat() -> void:
|
||||
if (
|
||||
_opened or not _available or _service == null
|
||||
|
|
@ -359,6 +439,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
|
||||
func _process(_delta: float) -> void:
|
||||
_refresh_visibility()
|
||||
_update_status_effect_icons()
|
||||
_update_panel_opacity()
|
||||
_update_speech()
|
||||
|
||||
|
|
@ -391,15 +472,13 @@ func _build_ui() -> void:
|
|||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_clock_panel.add_child(_clock_label)
|
||||
_fish_finder_effect_icon = TextureRect.new()
|
||||
_fish_finder_effect_icon.name = "FishFinderEffectIcon"
|
||||
_fish_finder_effect_icon.texture = FISH_FINDER_EFFECT_ICON
|
||||
_fish_finder_effect_icon.size = FISH_FINDER_EFFECT_ICON_SIZE
|
||||
_fish_finder_effect_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_fish_finder_effect_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_fish_finder_effect_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_fish_finder_effect_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_fish_finder_effect_icon)
|
||||
_status_effect_column = VBoxContainer.new()
|
||||
_status_effect_column.name = "StatusEffectColumn"
|
||||
_status_effect_column.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_status_effect_column.add_theme_constant_override(
|
||||
"separation", STATUS_EFFECT_ICON_GAP
|
||||
)
|
||||
add_child(_status_effect_column)
|
||||
_weather_icon = WeatherIconType.new()
|
||||
_weather_icon.name = "WorldWeatherIcon"
|
||||
_weather_icon.size = WEATHER_ICON_SIZE
|
||||
|
|
@ -968,7 +1047,7 @@ func _refresh_visibility() -> void:
|
|||
_panel.hide()
|
||||
_clock_panel.hide()
|
||||
_weather_icon.hide()
|
||||
_fish_finder_effect_icon.hide()
|
||||
_status_effect_column.hide()
|
||||
_collapse_button.hide()
|
||||
_height_button.hide()
|
||||
_unread_indicator.hide()
|
||||
|
|
@ -978,7 +1057,7 @@ func _refresh_visibility() -> void:
|
|||
_panel.hide()
|
||||
_clock_panel.hide()
|
||||
_weather_icon.hide()
|
||||
_fish_finder_effect_icon.hide()
|
||||
_status_effect_column.hide()
|
||||
_collapse_button.hide()
|
||||
_height_button.hide()
|
||||
_unread_indicator.hide()
|
||||
|
|
@ -986,10 +1065,6 @@ func _refresh_visibility() -> void:
|
|||
return
|
||||
_clock_panel.show()
|
||||
_weather_icon.show()
|
||||
_fish_finder_effect_icon.visible = (
|
||||
_item_effects != null
|
||||
and _item_effects.is_active(PlayerItemEffects.FISH_FINDER_ID)
|
||||
)
|
||||
_collapse_button.show()
|
||||
var collapsed := _presentation_state == PresentationState.COLLAPSED
|
||||
_panel.show()
|
||||
|
|
@ -1365,10 +1440,10 @@ func _layout_presentation(animate: bool) -> void:
|
|||
clock_x,
|
||||
CLOCK_EDGE_MARGIN,
|
||||
)
|
||||
var fish_finder_effect_position := Vector2(
|
||||
var status_effect_column_position := Vector2(
|
||||
clock_position.x
|
||||
+ (CLOCK_SIZE.x - FISH_FINDER_EFFECT_ICON_SIZE.x) * 0.5,
|
||||
clock_position.y + CLOCK_SIZE.y + 2.0,
|
||||
+ (CLOCK_SIZE.x - STATUS_EFFECT_ICON_SIZE.x) * 0.5,
|
||||
clock_position.y + CLOCK_SIZE.y + STATUS_EFFECT_TOP_GAP,
|
||||
)
|
||||
var weather_icon_x: float = (
|
||||
clock_position.x - WEATHER_ICON_GAP - WEATHER_ICON_SIZE.x
|
||||
|
|
@ -1393,8 +1468,7 @@ func _layout_presentation(animate: bool) -> void:
|
|||
_panel.size = target_size
|
||||
_clock_panel.position = clock_position
|
||||
_clock_panel.size = CLOCK_SIZE
|
||||
_fish_finder_effect_icon.position = fish_finder_effect_position
|
||||
_fish_finder_effect_icon.size = FISH_FINDER_EFFECT_ICON_SIZE
|
||||
_status_effect_column.position = status_effect_column_position
|
||||
_weather_icon.position = weather_icon_position
|
||||
_weather_icon.size = WEATHER_ICON_SIZE
|
||||
_collapse_button.position = collapse_position
|
||||
|
|
@ -1416,9 +1490,9 @@ func _layout_presentation(animate: bool) -> void:
|
|||
UIMotion.CHAT_RESIZE_DURATION,
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
_height_tween.tween_property(
|
||||
_fish_finder_effect_icon,
|
||||
_status_effect_column,
|
||||
"position",
|
||||
fish_finder_effect_position,
|
||||
status_effect_column_position,
|
||||
UIMotion.CHAT_RESIZE_DURATION,
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
_height_tween.tween_property(
|
||||
|
|
|
|||
|
|
@ -37,9 +37,32 @@ const ART_KIT_MARKER_ICON: Texture2D = preload(
|
|||
const ART_KIT_MARKER_SHADER: Shader = preload(
|
||||
"res://ui/art_kit_marker_icon.gdshader"
|
||||
)
|
||||
const ART_UPGRADE_ICONS: Dictionary[StringName, Texture2D] = {
|
||||
&"brush_2x": preload(
|
||||
"res://items/icons/art/art_kit_marker_tip_thin.png"
|
||||
),
|
||||
&"brush_3x": preload(
|
||||
"res://items/icons/art/art_kit_marker_tip_mid.png"
|
||||
),
|
||||
&"brush_4x": preload(
|
||||
"res://items/icons/art/art_kit_marker_tip_thick.png"
|
||||
),
|
||||
&"grid_32x": preload(
|
||||
"res://items/icons/art/art_kit_grid_medium_light.png"
|
||||
),
|
||||
&"grid_64x": preload(
|
||||
"res://items/icons/art/art_kit_grid_large_light.png"
|
||||
),
|
||||
&"grid_128x": preload(
|
||||
"res://items/icons/art/art_kit_grid_xl_light.png"
|
||||
),
|
||||
}
|
||||
const CURRENCY_ICON: Texture2D = preload(
|
||||
"res://items/icons/shop/32_currency.png"
|
||||
)
|
||||
const LOCKED_ITEM_ICON: Texture2D = preload(
|
||||
"res://ui/icons/pictograms/lock_light.png"
|
||||
)
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
signal menu_exit_started
|
||||
|
|
@ -75,7 +98,7 @@ const SHOP_SECTION_LABELS: Array[String] = [
|
|||
const SUPPLY_ICON_GRID_COLUMNS: int = 9
|
||||
const SUPPLY_ICON_TILE_SIZE := Vector2(72.0, 72.0)
|
||||
const SUPPLY_ICON_HOST_SIZE := Vector2(72.0, 84.0)
|
||||
const SUPPLY_ICON_GRID_SEPARATION: int = 8
|
||||
const SHOP_SLOT_SEPARATION: int = 28
|
||||
const SUPPLY_PRICE_COLOR := Color("c3dfe6")
|
||||
const SUPPLY_PRICE_FONT_SIZE: int = 15
|
||||
const SUPPLY_PRICE_Y: float = 60.0
|
||||
|
|
@ -83,10 +106,15 @@ const SUPPLY_PRICE_HEIGHT: float = 24.0
|
|||
const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0
|
||||
const SUPPLY_PRICE_ICON_SIZE: float = 18.0
|
||||
const SUPPLY_PRICE_ICON_GAP: float = 3.0
|
||||
const BAIT_SUPPLY_BADGE_Y: float = (
|
||||
UtilityPageStyleType.SUPPLY_BADGE_EDGE_MARGIN
|
||||
)
|
||||
const LOCKED_ITEM_ICON_SIZE := Vector2(48.0, 48.0)
|
||||
const LOCKED_ITEM_ICON_ALPHA: float = 0.75
|
||||
const ROD_CARD_TILE_SIZE := Vector2(144.0, 144.0)
|
||||
const ROD_CARD_HOST_SIZE := Vector2(152.0, 198.0)
|
||||
const ROD_CAROUSEL_HEIGHT: float = 206.0
|
||||
const ROD_CAROUSEL_SEPARATION: int = 12
|
||||
const ROD_CAROUSEL_SEPARATION: int = SHOP_SLOT_SEPARATION
|
||||
const ROD_CAROUSEL_STEP: float = 328.0
|
||||
const ROD_PRICE_Y: float = 128.0
|
||||
const ROD_PRICE_HEIGHT: float = 30.0
|
||||
|
|
@ -680,6 +708,8 @@ func _refresh_supplies() -> void:
|
|||
current_stock_grid = _add_stock_icon_grid()
|
||||
current_stock_group = stock_group
|
||||
var button := Button.new()
|
||||
button.name = "Supply_%s" % str(item_id)
|
||||
button.set_meta(&"item_id", item_id)
|
||||
button.custom_minimum_size = Vector2(195, 54)
|
||||
button.icon = item.icon if item.icon != null else FALLBACK_SUPPLY_ICON
|
||||
button.expand_icon = true
|
||||
|
|
@ -709,6 +739,11 @@ func _refresh_supplies() -> void:
|
|||
)
|
||||
var item_tooltip_text: String = item.description
|
||||
if bait_topoff:
|
||||
var unit_price: int = FishingShopStockType.get_price(item_id)
|
||||
var unit_price_description: String = (
|
||||
"%d fish coin%s per bait piece"
|
||||
% [unit_price, "" if unit_price == 1 else "s"]
|
||||
)
|
||||
if use_icon_tile:
|
||||
button.custom_minimum_size = SUPPLY_ICON_TILE_SIZE
|
||||
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
|
|
@ -725,17 +760,15 @@ func _refresh_supplies() -> void:
|
|||
]
|
||||
)
|
||||
item_tooltip_text = (
|
||||
"%s\nunlock and fill to %d/%d\n%s" % [
|
||||
"%s\nunlock and fill\n%s\n%s" % [
|
||||
item.display_name,
|
||||
item.max_stack,
|
||||
item.max_stack,
|
||||
unit_price_description,
|
||||
item.description,
|
||||
]
|
||||
if not bait_unlocked
|
||||
else "%s\n%d/%d owned\n%s" % [
|
||||
else "%s\n%s\n%s" % [
|
||||
item.display_name,
|
||||
owned,
|
||||
item.max_stack,
|
||||
unit_price_description,
|
||||
item.description,
|
||||
]
|
||||
)
|
||||
|
|
@ -773,14 +806,32 @@ func _refresh_supplies() -> void:
|
|||
else item_tooltip_text
|
||||
)
|
||||
UtilityPageStyleType.apply_ocean_button(button)
|
||||
if permanent_unlock or bait_topoff:
|
||||
_add_unlock_state_icon(
|
||||
button,
|
||||
bait_unlocked if bait_topoff else owned > 0,
|
||||
(
|
||||
SUPPLY_ICON_TILE_SIZE
|
||||
if use_icon_tile
|
||||
else button.custom_minimum_size
|
||||
),
|
||||
)
|
||||
if bait_topoff and bait_unlocked:
|
||||
UtilityPageStyleType.add_supply_quantity_badge(
|
||||
button,
|
||||
owned,
|
||||
item.max_stack,
|
||||
&"SupplyQuantityBadge",
|
||||
BAIT_SUPPLY_BADGE_Y,
|
||||
)
|
||||
button.pressed.connect(_purchase_supply.bind(item_id))
|
||||
if current_stock_grid != null:
|
||||
_add_supply_icon_tile(
|
||||
current_stock_grid,
|
||||
button,
|
||||
(
|
||||
total_cost
|
||||
if bait_topoff and not bait_unlocked
|
||||
maxi(total_cost, 0)
|
||||
if bait_topoff
|
||||
else FishingShopStockType.get_price(item_id)
|
||||
),
|
||||
)
|
||||
|
|
@ -836,10 +887,10 @@ func _add_stock_icon_grid() -> GridContainer:
|
|||
grid.columns = SUPPLY_ICON_GRID_COLUMNS
|
||||
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
grid.add_theme_constant_override(
|
||||
"h_separation", SUPPLY_ICON_GRID_SEPARATION
|
||||
"h_separation", SHOP_SLOT_SEPARATION
|
||||
)
|
||||
grid.add_theme_constant_override(
|
||||
"v_separation", SUPPLY_ICON_GRID_SEPARATION
|
||||
"v_separation", SHOP_SLOT_SEPARATION
|
||||
)
|
||||
_supplies_list.add_child(grid)
|
||||
return grid
|
||||
|
|
@ -919,6 +970,9 @@ func _add_rod_card(parent: HBoxContainer, rod: FishingRodDataType) -> void:
|
|||
button.expand_icon = true
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_add_unlock_state_icon(
|
||||
button, owned, ROD_CARD_TILE_SIZE, ROD_PRICE_Y
|
||||
)
|
||||
button.disabled = (
|
||||
owned
|
||||
or level_locked
|
||||
|
|
@ -999,6 +1053,7 @@ func _add_art_kit_button(parent: GridContainer) -> void:
|
|||
],
|
||||
)
|
||||
_configure_supply_icon_tile(button, item.icon)
|
||||
_add_unlock_state_icon(button, owned, SUPPLY_ICON_TILE_SIZE)
|
||||
_add_supply_icon_tile(
|
||||
parent,
|
||||
button,
|
||||
|
|
@ -1035,16 +1090,23 @@ func _add_art_upgrade_button(
|
|||
ArtShopStockType.get_description(product_id),
|
||||
],
|
||||
)
|
||||
button.accessibility_name = ArtShopStockType.get_display_name(product_id)
|
||||
button.set_meta(&"art_product_id", product_id)
|
||||
var marker_color_id: StringName = (
|
||||
PlayerArtUnlocksType.color_id_for_product(product_id)
|
||||
)
|
||||
if marker_color_id.is_empty():
|
||||
_configure_supply_icon_tile(button, FALLBACK_SUPPLY_ICON)
|
||||
var upgrade_icon: Texture2D = ART_UPGRADE_ICONS.get(
|
||||
product_id,
|
||||
FALLBACK_SUPPLY_ICON,
|
||||
)
|
||||
_configure_supply_icon_tile(button, upgrade_icon)
|
||||
else:
|
||||
_configure_marker_icon_tile(
|
||||
button,
|
||||
SurfaceDrawingPalette.get_color(marker_color_id),
|
||||
)
|
||||
_add_unlock_state_icon(button, unlocked, SUPPLY_ICON_TILE_SIZE)
|
||||
_add_supply_icon_tile(
|
||||
parent,
|
||||
button,
|
||||
|
|
@ -1110,6 +1172,32 @@ func _configure_marker_icon_tile(
|
|||
button.add_child(icon)
|
||||
|
||||
|
||||
func _add_unlock_state_icon(
|
||||
button: Button,
|
||||
unlocked: bool,
|
||||
tile_size: Vector2,
|
||||
price_bubble_y: float = SUPPLY_PRICE_Y,
|
||||
) -> void:
|
||||
if unlocked:
|
||||
return
|
||||
var icon := TextureRect.new()
|
||||
icon.name = "UnlockStateIcon"
|
||||
icon.texture = LOCKED_ITEM_ICON
|
||||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
icon.z_index = 3
|
||||
icon.set_meta(&"unlocked", false)
|
||||
icon.size = LOCKED_ITEM_ICON_SIZE
|
||||
icon.position = Vector2(
|
||||
(tile_size.x - LOCKED_ITEM_ICON_SIZE.x) * 0.5,
|
||||
(price_bubble_y - LOCKED_ITEM_ICON_SIZE.y) * 0.5,
|
||||
)
|
||||
icon.modulate.a = LOCKED_ITEM_ICON_ALPHA
|
||||
button.add_child(icon)
|
||||
|
||||
|
||||
func _add_supply_icon_tile(
|
||||
parent: Container,
|
||||
button: Button,
|
||||
|
|
@ -1353,18 +1441,20 @@ func _on_network_purchase_finished(
|
|||
_request_id: String,
|
||||
accepted: bool,
|
||||
message: String,
|
||||
_product_id: StringName,
|
||||
_category: int,
|
||||
_quantity: int,
|
||||
product_id: StringName,
|
||||
category: int,
|
||||
quantity: int,
|
||||
total_cost: int,
|
||||
) -> void:
|
||||
_transaction_in_progress = false
|
||||
if not visible:
|
||||
return
|
||||
_set_feedback(
|
||||
(
|
||||
"Purchase complete • %s spent."
|
||||
% CurrencyPresentation.bbcode_amount(total_cost, 18)
|
||||
_purchase_success_feedback(
|
||||
product_id,
|
||||
category,
|
||||
quantity,
|
||||
total_cost,
|
||||
)
|
||||
if accepted
|
||||
else message
|
||||
|
|
@ -1372,6 +1462,31 @@ func _on_network_purchase_finished(
|
|||
_refresh_all()
|
||||
|
||||
|
||||
func _purchase_success_feedback(
|
||||
product_id: StringName,
|
||||
category: int,
|
||||
quantity: int,
|
||||
total_cost: int,
|
||||
) -> String:
|
||||
if (
|
||||
category == NetworkShopProtocol.ProductCategory.SUPPLY
|
||||
and FishingShopStockType.is_bait_topoff(product_id)
|
||||
):
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(product_id)
|
||||
var item_name: String = (
|
||||
item.display_name if item != null else str(product_id).capitalize()
|
||||
)
|
||||
return "Bait Restocked\n%s spent on %d %s" % [
|
||||
CurrencyPresentation.bbcode_amount(total_cost, 18),
|
||||
quantity,
|
||||
item_name,
|
||||
]
|
||||
return (
|
||||
"Purchase complete • %s spent."
|
||||
% CurrencyPresentation.bbcode_amount(total_cost, 18)
|
||||
)
|
||||
|
||||
|
||||
func _set_feedback(message: String) -> void:
|
||||
_feedback.text = "[center]%s[/center]" % message
|
||||
|
||||
|
|
|
|||
|
|
@ -198,8 +198,8 @@ horizontal_scroll_mode = 0
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 20
|
||||
theme_override_constants/v_separation = 20
|
||||
theme_override_constants/h_separation = 28
|
||||
theme_override_constants/v_separation = 28
|
||||
columns = 3
|
||||
|
||||
[node name="ReelTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"]
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ var _shop_animalese_voice: AnimaleseVoiceType
|
|||
var _shop_npc_player_in_range: bool = false
|
||||
var _shop_npc_spoken_for_current_visit: bool = false
|
||||
var _shop_npc_next_speech_msec: int = 0
|
||||
@onready var _effect_status: Label = %EffectStatus
|
||||
@onready var _chat_ui: ChatUIType = %ChatUI
|
||||
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
|
||||
@onready var _quick_radial_menu: QuickRadialMenuType = %QuickRadialMenu
|
||||
|
|
@ -174,7 +173,6 @@ var _system_menu_open: bool = false
|
|||
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
|
||||
var _surface_drawing: NetworkSurfaceDrawingService
|
||||
|
|
@ -304,7 +302,6 @@ func setup(
|
|||
_item_catalog = item_catalog
|
||||
_settings_manager = settings_manager
|
||||
_fishing_spot = fishing_spot
|
||||
_item_effects = item_effects
|
||||
_experience = experience
|
||||
if (
|
||||
_player != null
|
||||
|
|
@ -339,7 +336,8 @@ func setup(
|
|||
_experience.experience_awarded.connect(_on_experience_awarded)
|
||||
_chat_ui.setup(
|
||||
network_chat_service, network_session, spawn_service, player,
|
||||
fishing_spot, settings_manager, world_time, world_weather, item_effects
|
||||
fishing_spot, settings_manager, world_time, world_weather, item_effects,
|
||||
item_catalog,
|
||||
)
|
||||
_title_settings_panel.setup_network_profile(
|
||||
network_profile, network_session
|
||||
|
|
@ -1195,31 +1193,6 @@ func _process(delta: float) -> void:
|
|||
_update_virtual_mouse(delta)
|
||||
_update_controller_menu_scroll(delta)
|
||||
_update_experience_bubble_position()
|
||||
if _item_effects == null or not _gameplay_ui_enabled:
|
||||
_effect_status.hide()
|
||||
return
|
||||
var parts: Array[String] = []
|
||||
for item_id: StringName in [
|
||||
PlayerItemEffectsType.COFFEE_ID,
|
||||
PlayerItemEffectsType.ENERGY_DRINK_ID,
|
||||
PlayerItemEffectsType.SNACK_ID,
|
||||
PlayerItemEffectsType.FISH_FINDER_ID,
|
||||
]:
|
||||
var remaining: float = _item_effects.get_remaining(item_id)
|
||||
if remaining <= 0.0:
|
||||
continue
|
||||
var label: String = {
|
||||
PlayerItemEffectsType.COFFEE_ID: "coffee",
|
||||
PlayerItemEffectsType.ENERGY_DRINK_ID: "energy",
|
||||
PlayerItemEffectsType.SNACK_ID: "snack",
|
||||
PlayerItemEffectsType.FISH_FINDER_ID: "finder",
|
||||
}.get(item_id, "")
|
||||
parts.append(
|
||||
"%s %d:%02d"
|
||||
% [label, floori(remaining / 60.0), int(remaining) % 60]
|
||||
)
|
||||
_effect_status.text = " ".join(parts)
|
||||
_effect_status.visible = not parts.is_empty()
|
||||
|
||||
|
||||
func _update_controller_menu_scroll(delta: float) -> void:
|
||||
|
|
@ -1363,14 +1336,6 @@ func _apply_active_bait_indicator_style() -> void:
|
|||
&"icon_pressed_color",
|
||||
]:
|
||||
_active_bait_button.add_theme_color_override(state, Color.WHITE)
|
||||
var badge_style := StyleBoxFlat.new()
|
||||
badge_style.bg_color = Color("0b5558")
|
||||
badge_style.set_corner_radius_all(12)
|
||||
badge_style.anti_aliasing = false
|
||||
_active_bait_quantity_badge.add_theme_stylebox_override(
|
||||
"panel",
|
||||
badge_style,
|
||||
)
|
||||
|
||||
|
||||
func _refresh_active_bait_indicator() -> void:
|
||||
|
|
@ -1399,14 +1364,16 @@ func _refresh_active_bait_indicator() -> void:
|
|||
if _bag != null
|
||||
else 0
|
||||
)
|
||||
_active_bait_quantity.text = str(quantity)
|
||||
_active_bait_quantity.add_theme_font_size_override(
|
||||
"font_size",
|
||||
13 if quantity >= 100 else 15,
|
||||
UtilityPageStyle.configure_supply_quantity_badge(
|
||||
_active_bait_quantity_badge,
|
||||
_active_bait_quantity,
|
||||
quantity,
|
||||
item.max_stack,
|
||||
)
|
||||
_active_bait_button.tooltip_text = "%s ×%d" % [
|
||||
_active_bait_button.tooltip_text = "%s • %d/%d" % [
|
||||
item.display_name,
|
||||
quantity,
|
||||
item.max_stack,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ layout_mode = 1
|
|||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -27.0
|
||||
offset_left = -49.0
|
||||
offset_top = 3.0
|
||||
offset_right = -3.0
|
||||
offset_bottom = 27.0
|
||||
|
|
@ -136,14 +136,12 @@ layout_mode = 1
|
|||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 1.0
|
||||
offset_right = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_colors/font_color = Color(0.905882, 0.960784, 0.956863, 1)
|
||||
theme_override_font_sizes/font_size = 15
|
||||
text = "0"
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "0/10"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
|
|
@ -473,20 +471,6 @@ 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="EffectStatus" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 54
|
||||
offset_left = 18.0
|
||||
offset_top = 18.0
|
||||
offset_right = 500.0
|
||||
offset_bottom = 44.0
|
||||
mouse_filter = 2
|
||||
theme = ExtResource("3_theme")
|
||||
theme_override_colors/font_shadow_color = Color(0, 0, 0, 0.9)
|
||||
theme_override_constants/shadow_offset_x = 2
|
||||
theme_override_constants/shadow_offset_y = 2
|
||||
|
||||
[node name="ChatUI" type="Control" parent="UIRoot"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ const SCROLL_DOWN_TEXTURE: Texture2D = preload(
|
|||
const LOGBOOK_ARTWORK_SOURCE_SIZE := Vector2(512.0, 247.0)
|
||||
const LEFT_PAGE_CONTENT_RECT := Rect2(56.0, 26.0, 190.0, 198.0)
|
||||
const RIGHT_PAGE_CONTENT_RECT := Rect2(267.0, 26.0, 210.0, 198.0)
|
||||
const LOGBOOK_TAB_LEFT_INSET: float = 122.0
|
||||
const LOGBOOK_TAB_LEFT_INSET: float = 92.0
|
||||
const LOGBOOK_TAB_SIZE := Vector2(108.0, 38.0)
|
||||
const LOGBOOK_TAB_SEPARATION: float = 6.0
|
||||
const LOGBOOK_TAB_TEXT_SIDE_INSET: float = 10.0
|
||||
const PAGE_CONTENT_SCALE: float = 0.97
|
||||
const CATALOG_PORTRAIT_SIZE := Vector2(78.0, 36.0)
|
||||
const CATALOG_ENTRY_SIZE := Vector2(92.0, 92.0)
|
||||
|
|
@ -67,6 +70,7 @@ var _snapping_catalog_scroll: bool = false
|
|||
var _catalog_scroll_snap_timer: Timer
|
||||
|
||||
var _category_tabs: Array[Button] = []
|
||||
var _category_tab_categories: Array[LogbookCatalog.Category] = []
|
||||
var _catalog_scroll: ScrollContainer
|
||||
var _catalog_grid: GridContainer
|
||||
var _catalog_scroll_up_indicator: TextureRect
|
||||
|
|
@ -163,7 +167,9 @@ func focus_initial() -> void:
|
|||
var first := _entry_buttons.values().front() as Button
|
||||
first.grab_focus()
|
||||
else:
|
||||
_category_tabs[int(_category)].grab_focus()
|
||||
var category_tab_index: int = _category_tab_categories.find(_category)
|
||||
if category_tab_index >= 0:
|
||||
_category_tabs[category_tab_index].grab_focus()
|
||||
|
||||
|
||||
func _build_interface() -> void:
|
||||
|
|
@ -183,19 +189,26 @@ func _build_interface() -> void:
|
|||
var tabs := HBoxContainer.new()
|
||||
# Align the first flange with the authored left paper edge.
|
||||
tabs.position = Vector2(LOGBOOK_TAB_LEFT_INSET, 37.0)
|
||||
tabs.size = Vector2(384.0, 38.0)
|
||||
tabs.z_index = 10
|
||||
tabs.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
tabs.add_theme_constant_override("separation", 6)
|
||||
stack.add_child(tabs)
|
||||
for category: LogbookCatalog.Category in [
|
||||
var category_order: Array[LogbookCatalog.Category] = [
|
||||
LogbookCatalog.Category.FRESH_WATER,
|
||||
LogbookCatalog.Category.SALT_WATER,
|
||||
LogbookCatalog.Category.OTHER,
|
||||
LogbookCatalog.Category.SHELLFISH,
|
||||
]:
|
||||
LogbookCatalog.Category.OTHER,
|
||||
]
|
||||
tabs.size = Vector2(
|
||||
LOGBOOK_TAB_SIZE.x * float(category_order.size())
|
||||
+ LOGBOOK_TAB_SEPARATION * float(category_order.size() - 1),
|
||||
LOGBOOK_TAB_SIZE.y,
|
||||
)
|
||||
tabs.z_index = 10
|
||||
tabs.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
tabs.add_theme_constant_override(
|
||||
"separation", roundi(LOGBOOK_TAB_SEPARATION)
|
||||
)
|
||||
stack.add_child(tabs)
|
||||
for category: LogbookCatalog.Category in category_order:
|
||||
var tab := OrganizerTabType.new()
|
||||
tab.custom_minimum_size = Vector2(90, 38)
|
||||
tab.custom_minimum_size = LOGBOOK_TAB_SIZE
|
||||
tab.toggle_mode = true
|
||||
tab.text = LogbookCatalog.category_label(category)
|
||||
tab.palette_index = int(category)
|
||||
|
|
@ -203,6 +216,7 @@ func _build_interface() -> void:
|
|||
tab.pressed.connect(_select_category.bind(category))
|
||||
tabs.add_child(tab)
|
||||
_category_tabs.append(tab)
|
||||
_category_tab_categories.append(category)
|
||||
_configure_category_focus()
|
||||
|
||||
var book := Control.new()
|
||||
|
|
@ -529,7 +543,7 @@ func _select_category(category: LogbookCatalog.Category) -> void:
|
|||
_selected_entry_key = StringName()
|
||||
for index: int in _category_tabs.size():
|
||||
(_category_tabs[index] as OrganizerTabType).set_selected(
|
||||
index == int(_category)
|
||||
_category_tab_categories[index] == _category
|
||||
)
|
||||
_begin_category_transition()
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@ func setup(
|
|||
|
||||
func activate() -> void:
|
||||
_show_inbox()
|
||||
UtilityPageStyle.animate_in(self)
|
||||
call_deferred("_focus_first")
|
||||
|
||||
|
||||
|
|
@ -217,7 +216,7 @@ func _build_compose() -> Control:
|
|||
_attachment_kind = OptionButton.new()
|
||||
_attachment_kind.position = Vector2(688, 48)
|
||||
_attachment_kind.size = Vector2(280, 46)
|
||||
for label: String in ["No gift", "Currency", "Fish", "Consumable"]:
|
||||
for label: String in ["No gift", "Currency", "Fish", "Item"]:
|
||||
_attachment_kind.add_item(label)
|
||||
_attachment_kind.item_selected.connect(_refresh_attachment_choices)
|
||||
page.add_child(_attachment_kind)
|
||||
|
|
@ -770,7 +769,7 @@ func _attachment_text(attachment: Dictionary) -> String:
|
|||
StringName(str(attachment.get("item_id", "")))
|
||||
)
|
||||
return "%s ×%d" % [
|
||||
item.display_name if item != null else "Consumable",
|
||||
item.display_name if item != null else "Item",
|
||||
int(attachment.get("quantity", 0)),
|
||||
]
|
||||
return "Invalid gift."
|
||||
|
|
|
|||
|
|
@ -115,11 +115,6 @@ enum BagView {
|
|||
CONSUMABLES,
|
||||
}
|
||||
|
||||
enum TackleView {
|
||||
BAIT,
|
||||
LURES,
|
||||
}
|
||||
|
||||
enum SortMode {
|
||||
CATCH_ORDER,
|
||||
NAME,
|
||||
|
|
@ -150,10 +145,9 @@ const INVENTORY_NOTEPAD_POSITION := Vector2(
|
|||
INVENTORY_MAIN_POSITION.y,
|
||||
)
|
||||
const INVENTORY_NOTEPAD_SIZE := Vector2(278.0, 484.0)
|
||||
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 TACKLE_GRID_COLUMNS: int = 3
|
||||
const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||
|
||||
@onready var _navigation_cluster: BubbleClusterType = %NavigationCluster
|
||||
|
|
@ -198,18 +192,18 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
|||
@onready var _inventory_sub_tabs: HBoxContainer = %InventorySubTabs
|
||||
@onready var _cooler_sub_tab: Button = %CoolerSubTab
|
||||
@onready var _bag_sub_tab: Button = %BagSubTab
|
||||
@onready var _items_sub_tab: Button = %ItemsSubTab
|
||||
@onready var _tackle_sub_tab: Button = %TackleSubTab
|
||||
@onready var _bag_filter_tabs: HBoxContainer = %BagFilterTabs
|
||||
@onready var _equipment_filter: Button = %EquipmentFilter
|
||||
@onready var _consumables_filter: Button = %ConsumablesFilter
|
||||
@onready var _tackle_main_panel: PanelContainer = %TackleMainPanel
|
||||
@onready var _tackle_detail_panel: PanelContainer = %TackleDetailPanel
|
||||
@onready var _bait_filter: Button = %BaitFilter
|
||||
@onready var _lures_filter: Button = %LuresFilter
|
||||
@onready var _tackle_empty: Label = %TackleEmpty
|
||||
@onready var _bait_heading: Label = %BaitHeading
|
||||
@onready var _lure_heading: Label = %LureHeading
|
||||
@onready var _bait_empty: Label = %BaitEmpty
|
||||
@onready var _lure_empty: Label = %LureEmpty
|
||||
@onready var _tackle_detail_text: Label = %TackleDetailText
|
||||
@onready var _tackle_equip_button: NotepadInkActionType = %TackleEquipButton
|
||||
@onready var _tackle_item_list: VBoxContainer = %TackleItemList
|
||||
@onready var _bait_item_list: GridContainer = %BaitItemList
|
||||
@onready var _lure_item_list: GridContainer = %LureItemList
|
||||
@onready var _bag_outer_wall: PanelContainer = %BagOuterWall
|
||||
@onready var _bag_inner_liner: PanelContainer = %BagInnerLiner
|
||||
@onready var _bag_scroll: ScrollContainer = %BagScroll
|
||||
|
|
@ -312,8 +306,8 @@ var _cooler_capacity: PlayerCoolerCapacityType
|
|||
var _current_section: Section = Section.COOLER
|
||||
var _last_inventory_section: Section = Section.COOLER
|
||||
var _bag_view: BagView = BagView.EQUIPMENT
|
||||
var _tackle_view: TackleView = TackleView.BAIT
|
||||
var _selected_tackle_item_id: StringName
|
||||
var _tackle_item_buttons: Dictionary[StringName, Button] = {}
|
||||
var _controller_ownership: ControllerOwnership = ControllerOwnership.ITEM_LIST
|
||||
var _controller_source_section: Section = Section.COOLER
|
||||
var _controller_source_identity: StringName
|
||||
|
|
@ -377,18 +371,11 @@ func _ready() -> void:
|
|||
_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)
|
||||
_cooler_sub_tab.pressed.connect(_show_section.bind(Section.COOLER))
|
||||
_bag_sub_tab.pressed.connect(_show_section.bind(Section.BAG))
|
||||
_bag_sub_tab.pressed.connect(_show_bag_view.bind(BagView.EQUIPMENT))
|
||||
_items_sub_tab.pressed.connect(_show_bag_view.bind(BagView.CONSUMABLES))
|
||||
_tackle_sub_tab.pressed.connect(_show_section.bind(Section.TACKLE_BOX))
|
||||
_equipment_filter.pressed.connect(_set_bag_view.bind(BagView.EQUIPMENT))
|
||||
_consumables_filter.pressed.connect(
|
||||
_set_bag_view.bind(BagView.CONSUMABLES)
|
||||
)
|
||||
_bait_filter.pressed.connect(_set_tackle_view.bind(TackleView.BAIT))
|
||||
_lures_filter.pressed.connect(_set_tackle_view.bind(TackleView.LURES))
|
||||
_tackle_equip_button.pressed.connect(_toggle_active_tackle)
|
||||
_logbook_tab.pressed.connect(
|
||||
_show_section.bind(Section.LOGBOOK)
|
||||
|
|
@ -434,7 +421,6 @@ func _ready() -> void:
|
|||
# Keep the exposed tab hitboxes above the full-page roots. The individual
|
||||
# main panels retain a higher z-index and cover the tab bodies below y=166.
|
||||
_inventory_sub_tabs.move_to_front()
|
||||
_bag_filter_tabs.move_to_front()
|
||||
_apply_cooler_control_styles()
|
||||
_apply_cooler_wall_styles()
|
||||
_apply_cooler_notepad_style()
|
||||
|
|
@ -947,17 +933,7 @@ func _restore_controller_item_focus(
|
|||
elif section == Section.BAG:
|
||||
target = _bag_item_nodes.get(identity) as BagItemSpriteType
|
||||
elif section == Section.TACKLE_BOX:
|
||||
for child: Node in _tackle_item_list.get_children():
|
||||
var button := child as BaseButton
|
||||
if (
|
||||
button != null
|
||||
and button.has_meta(&"controller_tackle_item_id")
|
||||
and StringName(str(button.get_meta(
|
||||
&"controller_tackle_item_id"
|
||||
))) == identity
|
||||
):
|
||||
target = button
|
||||
break
|
||||
target = _tackle_item_buttons.get(identity) as Button
|
||||
if (
|
||||
target != null
|
||||
and is_instance_valid(target)
|
||||
|
|
@ -1087,23 +1063,14 @@ func _handle_controller_secondary_switch(event: InputEvent) -> bool:
|
|||
):
|
||||
return true
|
||||
if _is_inventory_section(_current_section):
|
||||
var inventory_index: int = _get_inventory_tab_index()
|
||||
if (
|
||||
(direction > 0 and _current_section == Section.BAG)
|
||||
or (direction < 0 and _current_section == Section.COOLER)
|
||||
(direction > 0 and inventory_index == 3)
|
||||
or (direction < 0 and inventory_index == 0)
|
||||
):
|
||||
_begin_controller_hotbar_management()
|
||||
return true
|
||||
var inventory_sections: Array[Section] = [
|
||||
Section.COOLER,
|
||||
Section.TACKLE_BOX,
|
||||
Section.BAG,
|
||||
]
|
||||
var inventory_index: int = inventory_sections.find(_current_section)
|
||||
_show_section(inventory_sections[wrapi(
|
||||
inventory_index + direction,
|
||||
0,
|
||||
inventory_sections.size(),
|
||||
)])
|
||||
_show_inventory_tab(inventory_index + direction)
|
||||
return true
|
||||
_cycle_visible_secondary_tabs(direction)
|
||||
return true
|
||||
|
|
@ -1129,13 +1096,31 @@ func _begin_controller_hotbar_management() -> void:
|
|||
|
||||
func _cycle_from_controller_hotbar_management(direction: int) -> void:
|
||||
_release_controller_ownership(false, false)
|
||||
var target_section: Section = (
|
||||
Section.COOLER if direction > 0 else Section.BAG
|
||||
)
|
||||
if target_section == _current_section:
|
||||
var target_index: int = 0 if direction > 0 else 3
|
||||
if target_index == _get_inventory_tab_index():
|
||||
call_deferred("_focus_current_section")
|
||||
else:
|
||||
_show_section(target_section)
|
||||
_show_inventory_tab(target_index)
|
||||
|
||||
|
||||
func _get_inventory_tab_index() -> int:
|
||||
if _current_section == Section.COOLER:
|
||||
return 0
|
||||
if _current_section == Section.BAG:
|
||||
return 2 if _bag_view == BagView.CONSUMABLES else 1
|
||||
return 3
|
||||
|
||||
|
||||
func _show_inventory_tab(index: int) -> void:
|
||||
match clampi(index, 0, 3):
|
||||
0:
|
||||
_show_section(Section.COOLER)
|
||||
1:
|
||||
_show_bag_view(BagView.EQUIPMENT)
|
||||
2:
|
||||
_show_bag_view(BagView.CONSUMABLES)
|
||||
_:
|
||||
_show_section(Section.TACKLE_BOX)
|
||||
|
||||
|
||||
func _cycle_visible_secondary_tabs(direction: int) -> bool:
|
||||
|
|
@ -1575,7 +1560,9 @@ func _show_section_immediate(section: Section) -> void:
|
|||
if section != Section.BAG:
|
||||
_close_bag_detail()
|
||||
else:
|
||||
_update_bag_detail()
|
||||
# BagView can change while entering from another Inventory page. Rebuild
|
||||
# the filtered contents here so the visible list always matches its tab.
|
||||
_refresh_bag()
|
||||
_refresh_tackle_box()
|
||||
if section != Section.LOGBOOK:
|
||||
_cancel_logbook_page_transition(true)
|
||||
|
|
@ -1600,7 +1587,12 @@ func _show_section_immediate(section: Section) -> void:
|
|||
_players_page.deactivate()
|
||||
_inventory_tab.button_pressed = _is_inventory_section(section)
|
||||
_cooler_sub_tab.set_selected(section == Section.COOLER)
|
||||
_bag_sub_tab.set_selected(section == Section.BAG)
|
||||
_bag_sub_tab.set_selected(
|
||||
section == Section.BAG and _bag_view == BagView.EQUIPMENT
|
||||
)
|
||||
_items_sub_tab.set_selected(
|
||||
section == Section.BAG and _bag_view == BagView.CONSUMABLES
|
||||
)
|
||||
_tackle_sub_tab.set_selected(section == Section.TACKLE_BOX)
|
||||
_refresh_inventory_organizer_tabs()
|
||||
_logbook_tab.button_pressed = section == Section.LOGBOOK
|
||||
|
|
@ -1622,8 +1614,9 @@ func _is_inventory_section(section: Section) -> bool:
|
|||
func _refresh_inventory_organizer_tabs() -> void:
|
||||
for tab: OrganizerTabType in [
|
||||
_cooler_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
_bag_sub_tab,
|
||||
_items_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
]:
|
||||
tab.refresh_state()
|
||||
|
||||
|
|
@ -1633,8 +1626,9 @@ func _animate_inventory_tab_entry() -> void:
|
|||
return
|
||||
var tabs: Array[OrganizerTabType] = [
|
||||
_cooler_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
_bag_sub_tab,
|
||||
_items_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
]
|
||||
for index: int in tabs.size():
|
||||
tabs[index].animate_entrance(float(index) * 0.025)
|
||||
|
|
@ -1643,8 +1637,9 @@ func _animate_inventory_tab_entry() -> void:
|
|||
func _settle_inventory_tabs_for_close() -> void:
|
||||
for tab: OrganizerTabType in [
|
||||
_cooler_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
_bag_sub_tab,
|
||||
_items_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
]:
|
||||
tab.settle_for_close()
|
||||
|
||||
|
|
@ -1779,8 +1774,9 @@ func _reserve_visible_secondary_navigation() -> void:
|
|||
call_deferred("_focus_current_section")
|
||||
var inventory_tabs: Array[Button] = [
|
||||
_cooler_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
_bag_sub_tab,
|
||||
_items_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
]
|
||||
for index: int in inventory_tabs.size():
|
||||
var tab: Button = inventory_tabs[index]
|
||||
|
|
@ -1794,9 +1790,7 @@ func _reserve_visible_secondary_navigation() -> void:
|
|||
_inventory_tab.focus_neighbor_bottom = _inventory_tab.get_path_to(
|
||||
_inventory_tab_for_section(_last_inventory_section)
|
||||
)
|
||||
_tackle_sub_tab.focus_neighbor_bottom = _tackle_sub_tab.get_path_to(
|
||||
_bait_filter
|
||||
)
|
||||
_configure_tackle_item_focus()
|
||||
|
||||
|
||||
func _inventory_tab_for_section(section: Section) -> Button:
|
||||
|
|
@ -1804,7 +1798,11 @@ func _inventory_tab_for_section(section: Section) -> Button:
|
|||
Section.COOLER:
|
||||
return _cooler_sub_tab
|
||||
Section.BAG:
|
||||
return _bag_sub_tab
|
||||
return (
|
||||
_items_sub_tab
|
||||
if _bag_view == BagView.CONSUMABLES
|
||||
else _bag_sub_tab
|
||||
)
|
||||
_:
|
||||
return _tackle_sub_tab
|
||||
|
||||
|
|
@ -1847,7 +1845,7 @@ func _configure_active_page_focus() -> void:
|
|||
Section.BAG:
|
||||
_configure_bag_item_focus()
|
||||
Section.TACKLE_BOX:
|
||||
_bait_filter.grab_focus()
|
||||
_configure_tackle_item_focus()
|
||||
Section.LOGBOOK:
|
||||
_catalog_logbook.focus_initial()
|
||||
Section.NET:
|
||||
|
|
@ -1859,13 +1857,6 @@ func _configure_active_page_focus() -> void:
|
|||
|
||||
|
||||
func _apply_inventory_styles() -> void:
|
||||
for button: Button in [
|
||||
_equipment_filter,
|
||||
_consumables_filter,
|
||||
_bait_filter,
|
||||
_lures_filter,
|
||||
]:
|
||||
UtilityPageStyle.apply_ocean_button(button)
|
||||
for panel: PanelContainer in [_tackle_main_panel]:
|
||||
var panel_style := UtilityPageStyle.panel_style()
|
||||
panel_style.set_corner_radius_all(INVENTORY_MAIN_CORNER_RADIUS)
|
||||
|
|
@ -1873,7 +1864,12 @@ func _apply_inventory_styles() -> void:
|
|||
"panel",
|
||||
panel_style,
|
||||
)
|
||||
for label: Label in [_tackle_empty]:
|
||||
for label: Label in [
|
||||
_bait_heading,
|
||||
_lure_heading,
|
||||
_bait_empty,
|
||||
_lure_empty,
|
||||
]:
|
||||
label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
label.add_theme_color_override(
|
||||
"font_color",
|
||||
|
|
@ -1895,124 +1891,180 @@ func _apply_inventory_styles() -> void:
|
|||
UtilityPageStyle.apply_ocean_button(_cancel_sale_button)
|
||||
|
||||
|
||||
func _set_bag_view(view: BagView) -> void:
|
||||
func _show_bag_view(view: BagView) -> void:
|
||||
if (
|
||||
_transitioning
|
||||
or _page_transitioning
|
||||
or _sale_confirmation.visible
|
||||
or get_viewport().gui_is_dragging()
|
||||
):
|
||||
return
|
||||
if _current_section != Section.BAG:
|
||||
_bag_view = view
|
||||
_selected_bag_item_id = StringName()
|
||||
_show_section(Section.BAG)
|
||||
return
|
||||
if _bag_view == view:
|
||||
return
|
||||
_release_controller_ownership(false, true)
|
||||
_bag_view = view
|
||||
_selected_bag_item_id = StringName()
|
||||
_refresh_bag()
|
||||
|
||||
|
||||
func _set_tackle_view(view: TackleView) -> void:
|
||||
_tackle_view = view
|
||||
_selected_tackle_item_id = StringName()
|
||||
_refresh_tackle_box()
|
||||
_bag_sub_tab.set_selected(view == BagView.EQUIPMENT)
|
||||
_items_sub_tab.set_selected(view == BagView.CONSUMABLES)
|
||||
_refresh_inventory_organizer_tabs()
|
||||
_reserve_visible_secondary_navigation()
|
||||
_set_content_interactive(true)
|
||||
call_deferred("_focus_current_section")
|
||||
|
||||
|
||||
func _refresh_tackle_box() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
_bait_filter.button_pressed = _tackle_view == TackleView.BAIT
|
||||
_lures_filter.button_pressed = _tackle_view == TackleView.LURES
|
||||
for child: Node in _tackle_item_list.get_children():
|
||||
child.queue_free()
|
||||
var matching_items: Array[OwnedItemType] = []
|
||||
for item_list: GridContainer in [_bait_item_list, _lure_item_list]:
|
||||
for child: Node in item_list.get_children():
|
||||
child.queue_free()
|
||||
_tackle_item_buttons.clear()
|
||||
var bait_items: Array[OwnedItemType] = []
|
||||
var lure_items: Array[OwnedItemType] = []
|
||||
if _bag != null and _item_catalog != null:
|
||||
var available_items: Array[OwnedItemType] = (
|
||||
_bag.get_unlocked_bait_items()
|
||||
if _tackle_view == TackleView.BAIT
|
||||
else _bag.get_all_items()
|
||||
)
|
||||
for owned: OwnedItemType in available_items:
|
||||
for owned: OwnedItemType in _bag.get_unlocked_bait_items():
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(
|
||||
owned.item_id
|
||||
)
|
||||
if item == null:
|
||||
continue
|
||||
if (
|
||||
_tackle_view == TackleView.BAIT
|
||||
and item.category == ItemDataType.Category.BAIT
|
||||
):
|
||||
matching_items.append(owned)
|
||||
elif (
|
||||
_tackle_view == TackleView.LURES
|
||||
and item.category == ItemDataType.Category.LURE
|
||||
):
|
||||
matching_items.append(owned)
|
||||
matching_items.sort_custom(_sort_bag_items)
|
||||
for owned: OwnedItemType in matching_items:
|
||||
if item != null and item.category == ItemDataType.Category.BAIT:
|
||||
bait_items.append(owned)
|
||||
for owned: OwnedItemType in _bag.get_all_items():
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(
|
||||
owned.item_id
|
||||
)
|
||||
if item != null and item.category == ItemDataType.Category.LURE:
|
||||
lure_items.append(owned)
|
||||
bait_items.sort_custom(_sort_bag_items)
|
||||
lure_items.sort_custom(_sort_bag_items)
|
||||
_populate_tackle_column(bait_items, _bait_item_list)
|
||||
_populate_tackle_column(lure_items, _lure_item_list)
|
||||
if (
|
||||
not _selected_tackle_item_id.is_empty()
|
||||
and not _tackle_item_buttons.has(_selected_tackle_item_id)
|
||||
):
|
||||
_selected_tackle_item_id = StringName()
|
||||
_bait_empty.visible = bait_items.is_empty()
|
||||
_lure_empty.visible = lure_items.is_empty()
|
||||
_configure_tackle_item_focus()
|
||||
_update_tackle_detail()
|
||||
_apply_tackle_interactivity(_tackle_is_interactive())
|
||||
|
||||
|
||||
func _populate_tackle_column(
|
||||
owned_items: Array[OwnedItemType],
|
||||
item_list: GridContainer,
|
||||
) -> void:
|
||||
for owned: OwnedItemType in owned_items:
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(owned.item_id)
|
||||
var row := Button.new()
|
||||
row.icon = item.icon
|
||||
row.tooltip_text = (
|
||||
"%s ×%d" % [item.display_name, owned.quantity]
|
||||
if item == null:
|
||||
continue
|
||||
var button := Button.new()
|
||||
button.icon = item.icon
|
||||
button.tooltip_text = (
|
||||
"%s • %d/%d" % [
|
||||
item.display_name,
|
||||
owned.quantity,
|
||||
item.max_stack,
|
||||
]
|
||||
if item.is_bait()
|
||||
else item.display_name
|
||||
)
|
||||
var compact_tackle_item: bool = item.is_bait() or item.is_lure()
|
||||
if compact_tackle_item:
|
||||
row.custom_minimum_size = Vector2(72, 72)
|
||||
row.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
row.expand_icon = item.icon != null
|
||||
row.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
row.text = "" if item.icon != null else item.display_name
|
||||
if item.icon == null:
|
||||
row.add_theme_font_size_override("font_size", 12)
|
||||
else:
|
||||
row.text = "%s ×%d" % [item.display_name, owned.quantity]
|
||||
row.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
row.toggle_mode = true
|
||||
row.set_meta(&"controller_tackle_item_id", owned.item_id)
|
||||
row.button_pressed = owned.item_id == _selected_tackle_item_id
|
||||
if compact_tackle_item:
|
||||
_apply_tackle_bait_button_style(row)
|
||||
if item.is_bait():
|
||||
_add_tackle_quantity_badge(row, owned.quantity)
|
||||
else:
|
||||
UtilityPageStyle.apply_ocean_button(row)
|
||||
row.pressed.connect(_select_tackle_item.bind(owned.item_id))
|
||||
_tackle_item_list.add_child(row)
|
||||
_tackle_empty.text = (
|
||||
"No bait collected."
|
||||
if _tackle_view == TackleView.BAIT
|
||||
else "No lures collected."
|
||||
)
|
||||
_tackle_empty.visible = matching_items.is_empty()
|
||||
_update_tackle_detail()
|
||||
button.custom_minimum_size = Vector2(72, 72)
|
||||
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
button.expand_icon = item.icon != null
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
button.text = "" if item.icon != null else item.display_name
|
||||
if item.icon == null:
|
||||
button.add_theme_font_size_override("font_size", 12)
|
||||
button.toggle_mode = true
|
||||
button.set_meta(&"controller_tackle_item_id", owned.item_id)
|
||||
button.button_pressed = owned.item_id == _selected_tackle_item_id
|
||||
_apply_tackle_bait_button_style(button)
|
||||
if item.is_bait():
|
||||
UtilityPageStyle.add_supply_quantity_badge(
|
||||
button,
|
||||
owned.quantity,
|
||||
item.max_stack,
|
||||
&"QuantityBadge",
|
||||
)
|
||||
button.pressed.connect(_select_tackle_item.bind(owned.item_id))
|
||||
item_list.add_child(button)
|
||||
_tackle_item_buttons[owned.item_id] = button
|
||||
|
||||
|
||||
func _add_tackle_quantity_badge(row: Button, quantity: int) -> void:
|
||||
var badge := Panel.new()
|
||||
badge.name = "QuantityBadge"
|
||||
badge.anchor_left = 1.0
|
||||
badge.anchor_right = 1.0
|
||||
badge.offset_left = -27.0
|
||||
badge.offset_top = 3.0
|
||||
badge.offset_right = -3.0
|
||||
badge.offset_bottom = 27.0
|
||||
badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
badge.z_index = 2
|
||||
var badge_style := StyleBoxFlat.new()
|
||||
badge_style.bg_color = Color("0b5558")
|
||||
badge_style.set_corner_radius_all(12)
|
||||
badge_style.anti_aliasing = false
|
||||
badge.add_theme_stylebox_override("panel", badge_style)
|
||||
row.add_child(badge)
|
||||
var quantity_label := Label.new()
|
||||
quantity_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
quantity_label.offset_left = 1.0
|
||||
quantity_label.offset_right = 1.0
|
||||
quantity_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
quantity_label.text = str(quantity)
|
||||
quantity_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
quantity_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
quantity_label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
quantity_label.add_theme_font_size_override(
|
||||
"font_size",
|
||||
13 if quantity >= 100 else 15,
|
||||
func _configure_tackle_item_focus() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var bait_buttons: Array[Button] = _get_tackle_column_buttons(
|
||||
_bait_item_list
|
||||
)
|
||||
quantity_label.add_theme_color_override("font_color", Color("e7f5f4"))
|
||||
badge.add_child(quantity_label)
|
||||
var lure_buttons: Array[Button] = _get_tackle_column_buttons(
|
||||
_lure_item_list
|
||||
)
|
||||
var first_button: Button = (
|
||||
bait_buttons.front()
|
||||
if not bait_buttons.is_empty()
|
||||
else (lure_buttons.front() if not lure_buttons.is_empty() else null)
|
||||
)
|
||||
_tackle_sub_tab.focus_neighbor_bottom = (
|
||||
_tackle_sub_tab.get_path_to(first_button)
|
||||
if first_button != null
|
||||
else NodePath()
|
||||
)
|
||||
_configure_tackle_column_focus(bait_buttons, lure_buttons, true)
|
||||
_configure_tackle_column_focus(lure_buttons, bait_buttons, false)
|
||||
|
||||
|
||||
func _get_tackle_column_buttons(item_list: GridContainer) -> Array[Button]:
|
||||
var buttons: Array[Button] = []
|
||||
for child: Node in item_list.get_children():
|
||||
var button := child as Button
|
||||
if button != null and not button.is_queued_for_deletion():
|
||||
buttons.append(button)
|
||||
return buttons
|
||||
|
||||
|
||||
func _configure_tackle_column_focus(
|
||||
buttons: Array[Button],
|
||||
other_buttons: Array[Button],
|
||||
is_left_column: bool,
|
||||
) -> void:
|
||||
for index: int in buttons.size():
|
||||
var button: Button = buttons[index]
|
||||
var column: int = index % TACKLE_GRID_COLUMNS
|
||||
var row: int = floori(float(index) / float(TACKLE_GRID_COLUMNS))
|
||||
var left_target: Button = button
|
||||
var right_target: Button = button
|
||||
if column > 0:
|
||||
left_target = buttons[index - 1]
|
||||
elif not is_left_column and not other_buttons.is_empty():
|
||||
left_target = other_buttons[mini(
|
||||
row * TACKLE_GRID_COLUMNS + TACKLE_GRID_COLUMNS - 1,
|
||||
other_buttons.size() - 1,
|
||||
)]
|
||||
if column < TACKLE_GRID_COLUMNS - 1 and index + 1 < buttons.size():
|
||||
right_target = buttons[index + 1]
|
||||
elif is_left_column and not other_buttons.is_empty():
|
||||
right_target = other_buttons[mini(
|
||||
row * TACKLE_GRID_COLUMNS,
|
||||
other_buttons.size() - 1,
|
||||
)]
|
||||
button.focus_neighbor_left = button.get_path_to(left_target)
|
||||
button.focus_neighbor_right = button.get_path_to(right_target)
|
||||
button.focus_neighbor_top = (
|
||||
button.get_path_to(_tackle_sub_tab)
|
||||
if index < TACKLE_GRID_COLUMNS
|
||||
else button.get_path_to(buttons[index - TACKLE_GRID_COLUMNS])
|
||||
)
|
||||
button.focus_neighbor_bottom = button.get_path_to(
|
||||
buttons[mini(index + TACKLE_GRID_COLUMNS, buttons.size() - 1)]
|
||||
)
|
||||
|
||||
|
||||
func _apply_tackle_bait_button_style(button: Button) -> void:
|
||||
|
|
@ -2045,7 +2097,10 @@ func _apply_tackle_bait_button_style(button: Button) -> void:
|
|||
|
||||
func _select_tackle_item(item_id: StringName) -> void:
|
||||
_selected_tackle_item_id = item_id
|
||||
_refresh_tackle_box()
|
||||
for candidate_id: StringName in _tackle_item_buttons:
|
||||
var button: Button = _tackle_item_buttons[candidate_id]
|
||||
button.button_pressed = candidate_id == item_id
|
||||
_update_tackle_detail()
|
||||
|
||||
|
||||
func _update_tackle_detail() -> void:
|
||||
|
|
@ -2057,11 +2112,8 @@ func _update_tackle_detail() -> void:
|
|||
if item == null:
|
||||
_tackle_equip_button.visible = false
|
||||
_tackle_equip_button.persistent_mark = false
|
||||
_tackle_detail_text.text = (
|
||||
"Select bait for details."
|
||||
if _tackle_view == TackleView.BAIT
|
||||
else "Select a lure for details."
|
||||
)
|
||||
_tackle_detail_text.text = "Select bait or a lure for details."
|
||||
_apply_tackle_interactivity(_tackle_is_interactive())
|
||||
return
|
||||
_tackle_equip_button.visible = item.is_bait() or item.is_lure()
|
||||
var quantity: int = _bag.get_quantity(item.item_id) if _bag != null else 0
|
||||
|
|
@ -2073,7 +2125,7 @@ func _update_tackle_detail() -> void:
|
|||
break
|
||||
var detail_lines: Array[String] = [item.display_name, ""]
|
||||
if item.is_bait():
|
||||
detail_lines.append("quantity: %d" % quantity)
|
||||
detail_lines.append("quantity: %d/%d" % [quantity, item.max_stack])
|
||||
if assigned_slot >= 0:
|
||||
detail_lines.append("hotbar slot %d" % (assigned_slot + 1))
|
||||
detail_lines.append("")
|
||||
|
|
@ -2096,6 +2148,42 @@ func _update_tackle_detail() -> void:
|
|||
_tackle_equip_button.persistent_mark = is_equipped
|
||||
_tackle_equip_button.disabled = quantity <= 0
|
||||
_tackle_equip_button.refresh_ink_state()
|
||||
_apply_tackle_interactivity(_tackle_is_interactive())
|
||||
|
||||
|
||||
func _tackle_is_interactive() -> bool:
|
||||
return (
|
||||
visible
|
||||
and _current_section == Section.TACKLE_BOX
|
||||
and not _transitioning
|
||||
and not _page_transitioning
|
||||
and not _sale_confirmation.visible
|
||||
)
|
||||
|
||||
|
||||
func _apply_tackle_interactivity(interactive: bool) -> void:
|
||||
for button: Button in _tackle_item_buttons.values():
|
||||
button.focus_mode = (
|
||||
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
|
||||
)
|
||||
button.mouse_filter = (
|
||||
Control.MOUSE_FILTER_STOP
|
||||
if interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
var action_interactive: bool = (
|
||||
interactive
|
||||
and _tackle_equip_button.visible
|
||||
and not _tackle_equip_button.disabled
|
||||
)
|
||||
_tackle_equip_button.focus_mode = (
|
||||
Control.FOCUS_ALL if action_interactive else Control.FOCUS_NONE
|
||||
)
|
||||
_tackle_equip_button.mouse_filter = (
|
||||
Control.MOUSE_FILTER_STOP
|
||||
if action_interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
|
||||
|
||||
func _toggle_active_tackle() -> void:
|
||||
|
|
@ -2489,10 +2577,6 @@ func _update_shell_layout() -> void:
|
|||
_tackle_rest_position = Vector2.ZERO
|
||||
_bag_outer_wall.position = INVENTORY_MAIN_POSITION
|
||||
_bag_outer_wall.size = INVENTORY_MAIN_SIZE
|
||||
_bag_filter_tabs.position = (
|
||||
INVENTORY_MAIN_POSITION + INVENTORY_HEADER_INSET
|
||||
)
|
||||
_bag_filter_tabs.size = Vector2(320.0, INVENTORY_HEADER_HEIGHT)
|
||||
_bag_host.custom_minimum_size = (
|
||||
Vector2(520.0, 152.0) if compact else Vector2(788.0, 358.0)
|
||||
)
|
||||
|
|
@ -2932,7 +3016,10 @@ func _set_content_interactive(interactive: bool) -> void:
|
|||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
for tab: Button in [
|
||||
_cooler_sub_tab, _bag_sub_tab, _tackle_sub_tab,
|
||||
_cooler_sub_tab,
|
||||
_bag_sub_tab,
|
||||
_items_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
]:
|
||||
var tab_interactive: bool = (
|
||||
interactive and _is_inventory_section(_current_section)
|
||||
|
|
@ -3014,6 +3101,10 @@ func _set_content_interactive(interactive: bool) -> void:
|
|||
if bag_interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
var tackle_interactive: bool = (
|
||||
interactive and _current_section == Section.TACKLE_BOX
|
||||
)
|
||||
_apply_tackle_interactivity(tackle_interactive)
|
||||
|
||||
|
||||
func _cancel_presentation_tween() -> void:
|
||||
|
|
@ -3117,9 +3208,9 @@ func _on_hotbar_changed() -> void:
|
|||
func _refresh_bag() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var owned_items: Array[OwnedItemType] = (
|
||||
_bag.get_all_items() if _bag != null else []
|
||||
)
|
||||
var owned_items: Array[OwnedItemType] = []
|
||||
if _bag != null:
|
||||
owned_items = _bag.get_all_items()
|
||||
var filtered_items: Array[OwnedItemType] = []
|
||||
for owned: OwnedItemType in owned_items:
|
||||
var item: ItemDataType = (
|
||||
|
|
@ -3145,16 +3236,12 @@ func _refresh_bag() -> void:
|
|||
owned_items = filtered_items
|
||||
owned_items.sort_custom(_sort_bag_items)
|
||||
_sorted_bag_items = owned_items
|
||||
_equipment_filter.button_pressed = _bag_view == BagView.EQUIPMENT
|
||||
_consumables_filter.button_pressed = (
|
||||
_bag_view == BagView.CONSUMABLES
|
||||
)
|
||||
_bag_empty.visible = owned_items.is_empty()
|
||||
_bag_empty_state.visible = owned_items.is_empty()
|
||||
_bag_empty_state.text = (
|
||||
"No equipment in your Bag."
|
||||
if _bag_view == BagView.EQUIPMENT
|
||||
else "No consumables in your Bag."
|
||||
else "No items in your Bag."
|
||||
)
|
||||
if (
|
||||
not _selected_bag_item_id.is_empty()
|
||||
|
|
@ -3209,7 +3296,7 @@ func _sync_bag_item_nodes(owned_items: Array[OwnedItemType]) -> void:
|
|||
continue
|
||||
var removed := _bag_item_nodes[item_id] as BagItemSpriteType
|
||||
_bag_item_nodes.erase(item_id)
|
||||
_release_focus_from(removed, _bag_sub_tab)
|
||||
_release_focus_from(removed, _active_bag_tab())
|
||||
removed.queue_free()
|
||||
_layout_bag_items()
|
||||
_configure_bag_item_focus()
|
||||
|
|
@ -3270,6 +3357,7 @@ func _layout_bag_items() -> void:
|
|||
func _configure_bag_item_focus() -> void:
|
||||
if _current_section != Section.BAG:
|
||||
return
|
||||
var active_tab: Button = _active_bag_tab()
|
||||
var controls: Array[BagItemSpriteType] = []
|
||||
for owned: OwnedItemType in _sorted_bag_items:
|
||||
var item_node := _bag_item_nodes.get(
|
||||
|
|
@ -3278,9 +3366,9 @@ func _configure_bag_item_focus() -> void:
|
|||
if item_node != null:
|
||||
controls.append(item_node)
|
||||
if controls.is_empty():
|
||||
_bag_sub_tab.focus_neighbor_bottom = NodePath()
|
||||
active_tab.focus_neighbor_bottom = NodePath()
|
||||
return
|
||||
_bag_sub_tab.focus_neighbor_bottom = _bag_sub_tab.get_path_to(
|
||||
active_tab.focus_neighbor_bottom = active_tab.get_path_to(
|
||||
controls.front()
|
||||
)
|
||||
for index: int in controls.size():
|
||||
|
|
@ -3296,7 +3384,7 @@ func _configure_bag_item_focus() -> void:
|
|||
controls[right_index]
|
||||
)
|
||||
control.focus_neighbor_top = (
|
||||
control.get_path_to(_bag_sub_tab)
|
||||
control.get_path_to(active_tab)
|
||||
if index < 3
|
||||
else control.get_path_to(controls[index - 3])
|
||||
)
|
||||
|
|
@ -3305,6 +3393,14 @@ func _configure_bag_item_focus() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _active_bag_tab() -> Button:
|
||||
return (
|
||||
_items_sub_tab
|
||||
if _bag_view == BagView.CONSUMABLES
|
||||
else _bag_sub_tab
|
||||
)
|
||||
|
||||
|
||||
func _sort_bag_items(a: OwnedItemType, b: OwnedItemType) -> bool:
|
||||
var item_a: ItemDataType = _item_catalog.get_item_by_id(a.item_id)
|
||||
var item_b: ItemDataType = _item_catalog.get_item_by_id(b.item_id)
|
||||
|
|
@ -3355,7 +3451,7 @@ func _update_bag_detail() -> void:
|
|||
_bag_detail_data.text = (
|
||||
"%s\nquantity: %d\n%s\n%s\n%s"
|
||||
% [
|
||||
item.get_category_name(),
|
||||
_item_category_display_name(item),
|
||||
quantity,
|
||||
item_state,
|
||||
hotbar_assignment,
|
||||
|
|
@ -3374,7 +3470,7 @@ func _update_bag_detail() -> void:
|
|||
_bag_sprite_detail_data.text = (
|
||||
"%s • quantity: %d\n%s • %s\n%s"
|
||||
% [
|
||||
item.get_category_name(),
|
||||
_item_category_display_name(item),
|
||||
quantity,
|
||||
item_state,
|
||||
hotbar_assignment,
|
||||
|
|
@ -3388,6 +3484,12 @@ func _update_bag_detail() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _item_category_display_name(item: ItemDataType) -> String:
|
||||
if item.category == ItemDataType.Category.CONSUMABLE:
|
||||
return "item"
|
||||
return item.get_category_name()
|
||||
|
||||
|
||||
func _close_bag_detail() -> void:
|
||||
_bag_detail_constellation.visible = false
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ z_index = 40
|
|||
layout_mode = 0
|
||||
offset_left = 150.0
|
||||
offset_top = 136.0
|
||||
offset_right = 534.0
|
||||
offset_right = 664.0
|
||||
offset_bottom = 174.0
|
||||
mouse_filter = 1
|
||||
theme_override_constants/separation = 6
|
||||
|
|
@ -103,6 +103,16 @@ text = "Equipment"
|
|||
script = ExtResource("23_organizer_tab")
|
||||
palette_index = 2
|
||||
|
||||
[node name="ItemsSubTab" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/InventorySubTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(124, 38)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = null
|
||||
text = "Items"
|
||||
script = ExtResource("23_organizer_tab")
|
||||
palette_index = 2
|
||||
|
||||
[node name="TackleSubTab" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/InventorySubTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(124, 38)
|
||||
|
|
@ -485,30 +495,6 @@ offset_right = 1280.0
|
|||
offset_bottom = 720.0
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="BagFilterTabs" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 55
|
||||
layout_mode = 0
|
||||
offset_left = 122.0
|
||||
offset_top = 166.0
|
||||
offset_right = 442.0
|
||||
offset_bottom = 206.0
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="EquipmentFilter" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagFilterTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Equipment"
|
||||
|
||||
[node name="ConsumablesFilter" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagFilterTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Consumables"
|
||||
|
||||
[node name="BagOuterWall" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 50
|
||||
|
|
@ -532,7 +518,7 @@ layout_mode = 2
|
|||
[node name="BagInnerMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall/BagOuterMargin/BagInnerLiner"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 18
|
||||
theme_override_constants/margin_top = 58
|
||||
theme_override_constants/margin_top = 18
|
||||
theme_override_constants/margin_right = 18
|
||||
theme_override_constants/margin_bottom = 18
|
||||
|
||||
|
|
@ -646,47 +632,114 @@ theme_override_constants/margin_bottom = 20
|
|||
|
||||
[node name="Layout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 14
|
||||
|
||||
[node name="TackleFilterTabs" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="BaitFilter" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleFilterTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Bait"
|
||||
|
||||
[node name="LuresFilter" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleFilterTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 40)
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Lures"
|
||||
|
||||
[node name="TackleScroll" type="ScrollContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout"]
|
||||
[node name="TackleColumns" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
theme_override_constants/separation = 18
|
||||
|
||||
[node name="TackleItemList" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleScroll"]
|
||||
unique_name_in_owner = true
|
||||
[node name="BaitColumn" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="TackleEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout"]
|
||||
[node name="BaitHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/BaitColumn"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "bait"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BaitBody" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/BaitColumn"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="BaitScroll" type="ScrollContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/BaitColumn/BaitBody"]
|
||||
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
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="BaitItemList" type="GridContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/BaitColumn/BaitBody/BaitScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 28
|
||||
theme_override_constants/v_separation = 12
|
||||
columns = 3
|
||||
|
||||
[node name="BaitEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/BaitColumn/BaitBody"]
|
||||
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 = 2
|
||||
text = "No bait collected."
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="TackleDivider" type="ColorRect" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns"]
|
||||
custom_minimum_size = Vector2(2, 0)
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
color = Color(0.204, 0.482, 0.553, 0.65)
|
||||
|
||||
[node name="LureColumn" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="LureHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/LureColumn"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "lures"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LureBody" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/LureColumn"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="LureScroll" type="ScrollContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/LureColumn/LureBody"]
|
||||
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
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="LureItemList" type="GridContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/LureColumn/LureBody/LureScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 28
|
||||
theme_override_constants/v_separation = 12
|
||||
columns = 3
|
||||
|
||||
[node name="LureEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage/TackleMainPanel/Margin/Layout/TackleColumns/LureColumn/LureBody"]
|
||||
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 = 2
|
||||
text = "No lures collected."
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="TackleDetailPanel" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/TackleBoxPage"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ func setup(
|
|||
|
||||
func activate() -> void:
|
||||
_refresh()
|
||||
UtilityPageStyle.animate_in(self)
|
||||
_focus_first()
|
||||
|
||||
|
||||
|
|
@ -82,7 +81,6 @@ func _build() -> void:
|
|||
tab_row.add_child(_count_label)
|
||||
_build_host_settings(root)
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.custom_minimum_size = Vector2(0, 310)
|
||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
root.add_child(scroll)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,18 @@ const MARKER_MODE_SHADER: Shader = preload(
|
|||
const GRID_MODE_ICON: Texture2D = preload(
|
||||
"res://items/icons/art/art_kit_grid_light.png"
|
||||
)
|
||||
const BRUSH_SIZE_ICONS: Dictionary[int, Texture2D] = {
|
||||
1: preload("res://items/icons/art/art_kit_marker_tip_fine.png"),
|
||||
2: preload("res://items/icons/art/art_kit_marker_tip_thin.png"),
|
||||
3: preload("res://items/icons/art/art_kit_marker_tip_mid.png"),
|
||||
4: preload("res://items/icons/art/art_kit_marker_tip_thick.png"),
|
||||
}
|
||||
const GRID_SIZE_ICONS: Dictionary[int, Texture2D] = {
|
||||
16: preload("res://items/icons/art/art_kit_grid_small_light.png"),
|
||||
32: preload("res://items/icons/art/art_kit_grid_medium_light.png"),
|
||||
64: preload("res://items/icons/art/art_kit_grid_large_light.png"),
|
||||
128: preload("res://items/icons/art/art_kit_grid_xl_light.png"),
|
||||
}
|
||||
const TOOLBAR_WIDTH: float = 510.0
|
||||
const TOOLBAR_HEIGHT: float = 373.0
|
||||
const COLOR_RAIL_WIDTH: float = 46.0
|
||||
|
|
@ -41,6 +53,12 @@ func _ready() -> void:
|
|||
UtilityPageStyleType.apply_ocean_button(_mode_button)
|
||||
UtilityPageStyleType.apply_ocean_button(_brush_option)
|
||||
UtilityPageStyleType.apply_ocean_button(_grid_option)
|
||||
for size_option: OptionButton in [_brush_option, _grid_option]:
|
||||
size_option.expand_icon = true
|
||||
size_option.add_theme_constant_override("icon_max_width", 40)
|
||||
size_option.get_popup().add_theme_constant_override(
|
||||
"icon_max_width", 40
|
||||
)
|
||||
for button: Button in _action_buttons():
|
||||
UtilityPageStyleType.apply_ocean_button(button)
|
||||
button.custom_minimum_size = Vector2(48, 44)
|
||||
|
|
@ -188,10 +206,26 @@ func owns_pointer_event(event: InputEvent) -> bool:
|
|||
func _build_options() -> void:
|
||||
_brush_option.clear()
|
||||
for brush_size: int in PlayerArtUnlocks.BRUSH_SIZES:
|
||||
_brush_option.add_item("%d×" % brush_size, brush_size)
|
||||
_brush_option.add_icon_item(
|
||||
BRUSH_SIZE_ICONS[brush_size],
|
||||
"",
|
||||
brush_size,
|
||||
)
|
||||
_brush_option.set_item_tooltip(
|
||||
_brush_option.item_count - 1,
|
||||
"%d× marker size" % brush_size,
|
||||
)
|
||||
_grid_option.clear()
|
||||
for grid_size: int in PlayerArtUnlocks.GRID_SIZES:
|
||||
_grid_option.add_item("%d×" % grid_size, grid_size)
|
||||
_grid_option.add_icon_item(
|
||||
GRID_SIZE_ICONS[grid_size],
|
||||
"",
|
||||
grid_size,
|
||||
)
|
||||
_grid_option.set_item_tooltip(
|
||||
_grid_option.item_count - 1,
|
||||
"%d×%d grid" % [grid_size, grid_size],
|
||||
)
|
||||
for child: Node in _color_list.get_children():
|
||||
child.queue_free()
|
||||
_color_buttons.clear()
|
||||
|
|
|
|||
|
|
@ -41,13 +41,21 @@ theme_override_constants/separation = 5
|
|||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(80, 48)
|
||||
layout_mode = 2
|
||||
tooltip_text = "brush size"
|
||||
texture_filter = 1
|
||||
tooltip_text = "marker size"
|
||||
accessibility_name = "marker size"
|
||||
expand_icon = true
|
||||
fit_to_longest_item = false
|
||||
|
||||
[node name="GridOption" type="OptionButton" parent="TopPanel/Top"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(92, 48)
|
||||
layout_mode = 2
|
||||
texture_filter = 1
|
||||
tooltip_text = "grid size"
|
||||
accessibility_name = "grid size"
|
||||
expand_icon = true
|
||||
fit_to_longest_item = false
|
||||
|
||||
[node name="EraserButton" type="Button" parent="TopPanel/Top"]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
|
|
@ -27,12 +27,97 @@ const LIGHT_TEXT: Color = Color("f5eed9")
|
|||
const DISABLED_TEXT: Color = Color(0.33, 0.36, 0.35, 0.72)
|
||||
const MOTION_TWEEN_META: StringName = &"utility_page_motion_tween"
|
||||
const LAPTOP_RECT: Rect2 = Rect2(66.0, 132.0, 1148.0, 520.0)
|
||||
const SUPPLY_BADGE_MIN_WIDTH: float = 46.0
|
||||
const SUPPLY_BADGE_HEIGHT: float = 24.0
|
||||
const SUPPLY_BADGE_EDGE_MARGIN: float = 3.0
|
||||
const SUPPLY_BADGE_X_OFFSET: float = 18.0
|
||||
const SUPPLY_BADGE_ROTATION_DEGREES: float = 15.0
|
||||
const SUPPLY_BADGE_HORIZONTAL_PADDING: float = 10.0
|
||||
const SUPPLY_BADGE_FONT_SIZE: int = 13
|
||||
const SUPPLY_BADGE_COLOR: Color = Color("0b5558")
|
||||
const SUPPLY_BADGE_TEXT_COLOR: Color = Color("e7f5f4")
|
||||
|
||||
|
||||
static func apply_page(root: Control) -> void:
|
||||
root.add_theme_font_override("font", TuffyFont)
|
||||
|
||||
|
||||
static func supply_quantity_text(current: int, total: int) -> String:
|
||||
return "%d/%d" % [maxi(current, 0), maxi(total, 0)]
|
||||
|
||||
|
||||
static func add_supply_quantity_badge(
|
||||
parent: Control,
|
||||
current: int,
|
||||
total: int,
|
||||
badge_name: StringName = &"SupplyQuantityBadge",
|
||||
top_margin: float = SUPPLY_BADGE_EDGE_MARGIN,
|
||||
) -> Panel:
|
||||
var badge := Panel.new()
|
||||
badge.name = badge_name
|
||||
badge.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
badge.z_index = 3
|
||||
parent.add_child(badge)
|
||||
var quantity_label := Label.new()
|
||||
quantity_label.name = "Quantity"
|
||||
quantity_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
quantity_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
quantity_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
quantity_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
badge.add_child(quantity_label)
|
||||
configure_supply_quantity_badge(
|
||||
badge, quantity_label, current, total, top_margin
|
||||
)
|
||||
return badge
|
||||
|
||||
|
||||
static func configure_supply_quantity_badge(
|
||||
badge: Panel,
|
||||
quantity_label: Label,
|
||||
current: int,
|
||||
total: int,
|
||||
top_margin: float = SUPPLY_BADGE_EDGE_MARGIN,
|
||||
) -> void:
|
||||
var supply_text := supply_quantity_text(current, total)
|
||||
var text_width: float = TuffyFont.get_string_size(
|
||||
supply_text,
|
||||
HORIZONTAL_ALIGNMENT_LEFT,
|
||||
-1.0,
|
||||
SUPPLY_BADGE_FONT_SIZE,
|
||||
).x
|
||||
var badge_width: float = maxf(
|
||||
SUPPLY_BADGE_MIN_WIDTH,
|
||||
ceilf(text_width + SUPPLY_BADGE_HORIZONTAL_PADDING),
|
||||
)
|
||||
badge.offset_left = (
|
||||
-badge_width - SUPPLY_BADGE_EDGE_MARGIN + SUPPLY_BADGE_X_OFFSET
|
||||
)
|
||||
badge.offset_top = top_margin
|
||||
badge.offset_right = -SUPPLY_BADGE_EDGE_MARGIN + SUPPLY_BADGE_X_OFFSET
|
||||
badge.offset_bottom = top_margin + SUPPLY_BADGE_HEIGHT
|
||||
badge.pivot_offset = Vector2(
|
||||
badge_width * 0.5,
|
||||
SUPPLY_BADGE_HEIGHT * 0.5,
|
||||
)
|
||||
badge.rotation_degrees = SUPPLY_BADGE_ROTATION_DEGREES
|
||||
var badge_style := rounded_style(
|
||||
SUPPLY_BADGE_COLOR,
|
||||
roundi(SUPPLY_BADGE_HEIGHT * 0.5),
|
||||
)
|
||||
badge_style.anti_aliasing = false
|
||||
badge.add_theme_stylebox_override("panel", badge_style)
|
||||
quantity_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
quantity_label.text = supply_text
|
||||
quantity_label.add_theme_font_override("font", TuffyFont)
|
||||
quantity_label.add_theme_font_size_override(
|
||||
"font_size", SUPPLY_BADGE_FONT_SIZE
|
||||
)
|
||||
quantity_label.add_theme_color_override(
|
||||
"font_color", SUPPLY_BADGE_TEXT_COLOR
|
||||
)
|
||||
|
||||
|
||||
static func rounded_style(color: Color, radius: int) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = color
|
||||
|
|
@ -46,6 +131,7 @@ static func build_laptop_screen(
|
|||
laptop_rect: Rect2 = LAPTOP_RECT,
|
||||
) -> MarginContainer:
|
||||
var laptop := PanelContainer.new()
|
||||
laptop.name = "UtilityMainBox"
|
||||
laptop.position = laptop_rect.position
|
||||
laptop.size = laptop_rect.size
|
||||
laptop.add_theme_stylebox_override(
|
||||
|
|
@ -53,12 +139,21 @@ static func build_laptop_screen(
|
|||
)
|
||||
root.add_child(laptop)
|
||||
|
||||
# Page-specific minimum sizes must not resize the shared outer artwork.
|
||||
# Keep the shell in a fixed Control so every utility page retains the same
|
||||
# canonical rectangle even as its internal controls evolve.
|
||||
var fixed_layout := Control.new()
|
||||
fixed_layout.name = "UtilityMainBoxLayout"
|
||||
fixed_layout.clip_contents = true
|
||||
laptop.add_child(fixed_layout)
|
||||
|
||||
var shell_margin := MarginContainer.new()
|
||||
shell_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
shell_margin.add_theme_constant_override("margin_left", 20)
|
||||
shell_margin.add_theme_constant_override("margin_top", 18)
|
||||
shell_margin.add_theme_constant_override("margin_right", 20)
|
||||
shell_margin.add_theme_constant_override("margin_bottom", 24)
|
||||
laptop.add_child(shell_margin)
|
||||
fixed_layout.add_child(shell_margin)
|
||||
|
||||
var screen := PanelContainer.new()
|
||||
screen.add_theme_stylebox_override(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue