Improve UI scaling and fishing commerce
This commit is contained in:
parent
2de96dd7bb
commit
6b8c3eb2f7
29 changed files with 1515 additions and 817 deletions
BIN
art/patterns/pattern_moneyfish.png
Normal file
BIN
art/patterns/pattern_moneyfish.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
40
art/patterns/pattern_moneyfish.png.import
Normal file
40
art/patterns/pattern_moneyfish.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6xws1d2dnbnn"
|
||||
path="res://.godot/imported/pattern_moneyfish.png-c20edef3e829648b3e6aae4e190efaed.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/patterns/pattern_moneyfish.png"
|
||||
dest_files=["res://.godot/imported/pattern_moneyfish.png-c20edef3e829648b3e6aae4e190efaed.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
@ -496,7 +496,7 @@ func _begin_aiming(player: PlayerType) -> void:
|
|||
_cast_charge = 0.0
|
||||
_cast_target = _calculate_cast_target(minimum_cast_distance)
|
||||
state = FishingState.AIMING_CAST
|
||||
status_changed.emit("hold left click to aim • release to cast")
|
||||
status_changed.emit("")
|
||||
var target_is_fishable: bool = is_target_fishable(_cast_target)
|
||||
var preview_position: Vector3 = _resolve_preview_surface_position(_cast_target)
|
||||
_presentation.begin_aim(
|
||||
|
|
@ -603,7 +603,7 @@ func _confirm_cast() -> void:
|
|||
return
|
||||
|
||||
state = FishingState.CASTING
|
||||
status_changed.emit("casting...")
|
||||
status_changed.emit("")
|
||||
_presentation.begin_cast(_cast_target)
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
|
||||
|
|
@ -618,7 +618,7 @@ func _on_cast_completed() -> void:
|
|||
_cast_charge,
|
||||
_build_network_evidence()
|
||||
)
|
||||
status_changed.emit("checking the water...")
|
||||
status_changed.emit("")
|
||||
return
|
||||
|
||||
_selected_water_region = get_fishable_water_region(_cast_target)
|
||||
|
|
|
|||
62
main/main.gd
62
main/main.gd
|
|
@ -74,6 +74,7 @@ const NetworkProfileServiceType = preload(
|
|||
const TITLE_MUSIC_SILENCE_DB: float = -80.0
|
||||
const PLAYER_MENU_PATTERN_SCALE: float = 0.85
|
||||
const PLAYER_MENU_PATTERN_SCROLL_VELOCITY := Vector2(-7.0, -5.0)
|
||||
const SHOP_PATTERN_SCALE: float = 1.75
|
||||
|
||||
@export var fish_catalog: FishPoolType
|
||||
@export var pelican_buyer_profile: FishBuyerProfileType
|
||||
|
|
@ -136,6 +137,7 @@ const PLAYER_MENU_PATTERN_SCROLL_VELOCITY := Vector2(-7.0, -5.0)
|
|||
@onready var _players_root: Node3D = $Players
|
||||
@onready var _title_background: ColorRect = %TitleBackground
|
||||
@onready var _player_menu_backdrop: ColorRect = %PlayerMenuBackdrop
|
||||
@onready var _shop_backdrop: ColorRect = %ShopBackdrop
|
||||
|
||||
var _gameplay_started: bool = false
|
||||
var _shop_interaction: FishingShopInteractionType
|
||||
|
|
@ -146,6 +148,7 @@ var _quit_in_progress: bool = false
|
|||
var _join_requested_from_title: bool = false
|
||||
var _join_requested_from_pause: bool = false
|
||||
var _player_menu_backdrop_tween: Tween
|
||||
var _shop_backdrop_tween: Tween
|
||||
var _pending_join_endpoint: String = ""
|
||||
var _server_trust_dialog: ConfirmationDialog
|
||||
var _pending_trust_changed: bool = false
|
||||
|
|
@ -173,6 +176,16 @@ func _ready() -> void:
|
|||
"scroll_velocity_pixels",
|
||||
PLAYER_MENU_PATTERN_SCROLL_VELOCITY,
|
||||
)
|
||||
var shop_pattern_material := _shop_backdrop.material as ShaderMaterial
|
||||
if shop_pattern_material != null:
|
||||
shop_pattern_material.set_shader_parameter(
|
||||
"display_scale",
|
||||
SHOP_PATTERN_SCALE,
|
||||
)
|
||||
shop_pattern_material.set_shader_parameter(
|
||||
"scroll_velocity_pixels",
|
||||
PLAYER_MENU_PATTERN_SCROLL_VELOCITY,
|
||||
)
|
||||
get_window().size_changed.connect(_resize_native_overlays)
|
||||
_resize_native_overlays()
|
||||
if not _settings_manager.settings_changed.is_connected(
|
||||
|
|
@ -338,17 +351,21 @@ func _initialize_after_data_root() -> void:
|
|||
fish_catalog,
|
||||
_network_item_use
|
||||
)
|
||||
var sale_buyers: Array[FishBuyerProfileType] = [
|
||||
pelican_buyer_profile,
|
||||
main_shop_buyer_profile,
|
||||
]
|
||||
_network_sale.setup(
|
||||
_network_session,
|
||||
_player_spawn_service,
|
||||
_network_fishing,
|
||||
_shop_interaction,
|
||||
_player.inventory,
|
||||
_player.wallet,
|
||||
_player.fish_sale_service,
|
||||
_save_manager,
|
||||
fish_catalog,
|
||||
pelican_buyer_profile,
|
||||
_test_world.get_pelican_convenience_landmark(),
|
||||
sale_buyers,
|
||||
_asset_reservations
|
||||
)
|
||||
_network_shop.setup(
|
||||
|
|
@ -455,6 +472,9 @@ func _initialize_after_data_root() -> void:
|
|||
_game_ui.player_menu_backdrop_visibility_changed.connect(
|
||||
_set_player_menu_backdrop_visible
|
||||
)
|
||||
_game_ui.shop_backdrop_visibility_changed.connect(
|
||||
_set_shop_backdrop_visible
|
||||
)
|
||||
_pixelation_reset.return_to_settings_requested.connect(
|
||||
_game_ui.focus_open_settings_back_button
|
||||
)
|
||||
|
|
@ -916,6 +936,7 @@ func _set_gameplay_active(active: bool) -> void:
|
|||
_refresh_active_hotbar_item()
|
||||
else:
|
||||
_set_player_menu_backdrop_visible(false)
|
||||
_set_shop_backdrop_visible(false)
|
||||
|
||||
|
||||
func _set_player_menu_backdrop_visible(is_visible: bool) -> void:
|
||||
|
|
@ -953,11 +974,48 @@ func _set_player_menu_backdrop_visible(is_visible: bool) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _set_shop_backdrop_visible(is_visible: bool) -> void:
|
||||
if _shop_backdrop_tween != null:
|
||||
_shop_backdrop_tween.kill()
|
||||
_shop_backdrop_tween = null
|
||||
var should_show: bool = is_visible and _gameplay_started
|
||||
if should_show:
|
||||
var was_visible: bool = _shop_backdrop.visible
|
||||
_shop_backdrop.visible = true
|
||||
if not was_visible:
|
||||
_shop_backdrop.modulate.a = 0.0
|
||||
_shop_backdrop_tween = create_tween()
|
||||
_shop_backdrop_tween.tween_property(
|
||||
_shop_backdrop,
|
||||
"modulate:a",
|
||||
1.0,
|
||||
UIMotion.PLAYER_MENU_ENTER_DURATION,
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
return
|
||||
if not _shop_backdrop.visible:
|
||||
return
|
||||
_shop_backdrop_tween = create_tween()
|
||||
_shop_backdrop_tween.tween_property(
|
||||
_shop_backdrop,
|
||||
"modulate:a",
|
||||
0.0,
|
||||
UIMotion.PLAYER_MENU_EXIT_DURATION,
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||||
_shop_backdrop_tween.finished.connect(
|
||||
func() -> void:
|
||||
_shop_backdrop.visible = false
|
||||
_shop_backdrop.modulate.a = 1.0
|
||||
_shop_backdrop_tween = null
|
||||
)
|
||||
|
||||
|
||||
func _resize_native_overlays() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
_player_menu_backdrop.position = Vector2.ZERO
|
||||
_player_menu_backdrop.size = Vector2(get_window().size)
|
||||
_shop_backdrop.position = Vector2.ZERO
|
||||
_shop_backdrop.size = Vector2(get_window().size)
|
||||
|
||||
|
||||
func _on_new_game_requested() -> void:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=45 format=3]
|
||||
[gd_scene load_steps=47 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"]
|
||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"]
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
[ext_resource type="Shader" path="res://ui/title_water_background.gdshader" id="40_title_water"]
|
||||
[ext_resource type="Texture2D" path="res://art/patterns/pattern_tealdot.png" id="41_menu_pattern"]
|
||||
[ext_resource type="Shader" path="res://ui/player_menu_pattern.gdshader" id="42_menu_pattern"]
|
||||
[ext_resource type="Texture2D" path="res://art/patterns/pattern_moneyfish.png" id="43_shop_pattern"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"]
|
||||
shader = ExtResource("40_title_water")
|
||||
|
|
@ -53,6 +54,13 @@ shader_parameter/source_tile_size = Vector2(128, 128)
|
|||
shader_parameter/display_scale = 0.85
|
||||
shader_parameter/scroll_velocity_pixels = Vector2(-7, -5)
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_shop_pattern"]
|
||||
shader = ExtResource("42_menu_pattern")
|
||||
shader_parameter/pattern_texture = ExtResource("43_shop_pattern")
|
||||
shader_parameter/source_tile_size = Vector2(64, 64)
|
||||
shader_parameter/display_scale = 1.75
|
||||
shader_parameter/scroll_velocity_pixels = Vector2(-7, -5)
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("3_main")
|
||||
fish_catalog = ExtResource("6_pool")
|
||||
|
|
@ -82,6 +90,14 @@ mouse_filter = 2
|
|||
material = SubResource("ShaderMaterial_player_menu_pattern")
|
||||
color = Color(1, 1, 1, 1)
|
||||
|
||||
[node name="ShopBackdrop" type="ColorRect" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 10
|
||||
mouse_filter = 2
|
||||
material = SubResource("ShaderMaterial_shop_pattern")
|
||||
color = Color(1, 1, 1, 1)
|
||||
|
||||
[node name="InterfaceFontController" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("39_fonts")
|
||||
|
|
|
|||
|
|
@ -101,6 +101,12 @@ static func validate_result(data: Variant) -> bool:
|
|||
and str(payload["message"]).length() <= MAX_MESSAGE_LENGTH
|
||||
):
|
||||
return false
|
||||
if payload.has("buyer_id") and (
|
||||
typeof(payload["buyer_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]
|
||||
or str(payload["buyer_id"]).is_empty()
|
||||
or str(payload["buyer_id"]).length() > MAX_ID_LENGTH
|
||||
):
|
||||
return false
|
||||
var seen: Dictionary[String, bool] = {}
|
||||
for value: Variant in payload["catch_ids"]:
|
||||
if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ const FishSaleServiceType = preload("res://economy/fish_sale_service.gd")
|
|||
const FishSaleResultType = preload("res://economy/fish_sale_result.gd")
|
||||
|
||||
const MAX_LEDGER_ENTRIES_PER_PEER: int = 64
|
||||
const PELICAN_INTERACTION_RANGE: float = 7.5
|
||||
const PELICAN_BUYER_ID: StringName = &"pelicans"
|
||||
const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
|
||||
|
||||
signal local_sale_pending(request_id: String)
|
||||
signal local_sale_finished(
|
||||
|
|
@ -23,13 +24,13 @@ signal local_sale_finished(
|
|||
var _session: NetworkSession
|
||||
var _spawn_service: PlayerSpawnService
|
||||
var _network_fishing: NetworkFishingService
|
||||
var _shop_interaction: FishingShopInteraction
|
||||
var _inventory: FishInventory
|
||||
var _wallet: PlayerWallet
|
||||
var _sale_service: FishSaleServiceType
|
||||
var _save_manager: PlayerSaveManager
|
||||
var _fish_catalog: FishPoolType
|
||||
var _buyer: FishBuyerProfileType
|
||||
var _pelican_landmark: Node3D
|
||||
var _buyers: Dictionary[StringName, FishBuyerProfileType] = {}
|
||||
var _request_ledgers: Dictionary[int, Dictionary] = {}
|
||||
var _pending_by_peer: Dictionary[int, String] = {}
|
||||
var _result_owners: Dictionary[String, int] = {}
|
||||
|
|
@ -38,6 +39,7 @@ var _applied_results: Dictionary[String, bool] = {}
|
|||
var _received_results: Dictionary[String, bool] = {}
|
||||
var _pending_local_request_id: String = ""
|
||||
var _pending_local_catch_ids: Array[StringName] = []
|
||||
var _pending_local_buyer_id: StringName
|
||||
var _reservations: PlayerAssetReservationService
|
||||
|
||||
|
||||
|
|
@ -45,25 +47,28 @@ func setup(
|
|||
session: NetworkSession,
|
||||
spawn_service: PlayerSpawnService,
|
||||
network_fishing: NetworkFishingService,
|
||||
shop_interaction: FishingShopInteraction,
|
||||
inventory: FishInventory,
|
||||
wallet: PlayerWallet,
|
||||
sale_service: FishSaleServiceType,
|
||||
save_manager: PlayerSaveManager,
|
||||
fish_catalog: FishPoolType,
|
||||
buyer: FishBuyerProfileType,
|
||||
pelican_landmark: Node3D,
|
||||
buyers: Array[FishBuyerProfileType],
|
||||
reservations: PlayerAssetReservationService,
|
||||
) -> void:
|
||||
_session = session
|
||||
_spawn_service = spawn_service
|
||||
_network_fishing = network_fishing
|
||||
_shop_interaction = shop_interaction
|
||||
_inventory = inventory
|
||||
_wallet = wallet
|
||||
_sale_service = sale_service
|
||||
_save_manager = save_manager
|
||||
_fish_catalog = fish_catalog
|
||||
_buyer = buyer
|
||||
_pelican_landmark = pelican_landmark
|
||||
_buyers.clear()
|
||||
for buyer: FishBuyerProfileType in buyers:
|
||||
if buyer != null and buyer.is_valid():
|
||||
_buyers[buyer.id] = buyer
|
||||
_reservations = reservations
|
||||
if not _session.peer_removed.is_connected(_on_peer_removed):
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
|
|
@ -71,9 +76,18 @@ func setup(
|
|||
_session.state_changed.connect(_on_session_state_changed)
|
||||
|
||||
|
||||
func can_request_sale() -> bool:
|
||||
func is_local_sale_pending() -> bool:
|
||||
return not _pending_local_request_id.is_empty()
|
||||
|
||||
|
||||
func can_request_sale(
|
||||
buyer_id: StringName = PELICAN_BUYER_ID,
|
||||
) -> bool:
|
||||
var buyer: FishBuyerProfileType = _buyers.get(buyer_id)
|
||||
return (
|
||||
_session != null
|
||||
buyer != null
|
||||
and buyer.is_valid()
|
||||
and _session != null
|
||||
and _session.is_gameplay_session_active()
|
||||
and (
|
||||
_session.is_host()
|
||||
|
|
@ -84,11 +98,10 @@ func can_request_sale() -> bool:
|
|||
)
|
||||
|
||||
|
||||
func is_local_sale_pending() -> bool:
|
||||
return not _pending_local_request_id.is_empty()
|
||||
|
||||
|
||||
func request_local_sale(catch_ids: Array[StringName]) -> String:
|
||||
func request_local_sale(
|
||||
catch_ids: Array[StringName],
|
||||
buyer_id: StringName = PELICAN_BUYER_ID,
|
||||
) -> String:
|
||||
for catch_id: StringName in catch_ids:
|
||||
if _reservations != null and _reservations.is_fish_reserved(catch_id):
|
||||
local_sale_finished.emit(
|
||||
|
|
@ -100,7 +113,7 @@ func request_local_sale(catch_ids: Array[StringName]) -> String:
|
|||
"", false, "Selling…", _empty_catch_ids(), 0
|
||||
)
|
||||
return ""
|
||||
if not can_request_sale():
|
||||
if not can_request_sale(buyer_id):
|
||||
local_sale_finished.emit(
|
||||
"",
|
||||
false,
|
||||
|
|
@ -114,6 +127,12 @@ func request_local_sale(catch_ids: Array[StringName]) -> String:
|
|||
"", false, "Sale could not be completed.", _empty_catch_ids(), 0
|
||||
)
|
||||
return ""
|
||||
var buyer: FishBuyerProfileType = _buyers.get(buyer_id)
|
||||
if buyer == null or not buyer.is_valid():
|
||||
local_sale_finished.emit(
|
||||
"", false, "The buyer is unavailable.", _empty_catch_ids(), 0
|
||||
)
|
||||
return ""
|
||||
var evidence: Array[Dictionary] = []
|
||||
for catch_id: StringName in catch_ids:
|
||||
var fish_catch: FishCatch = _inventory.get_catch_by_id(catch_id)
|
||||
|
|
@ -127,11 +146,12 @@ func request_local_sale(catch_ids: Array[StringName]) -> String:
|
|||
var request: Dictionary = {
|
||||
"request_id": request_id,
|
||||
"session_id": _session.get_session_id(),
|
||||
"buyer_id": str(_buyer.id) if _buyer != null else "",
|
||||
"buyer_id": str(buyer.id),
|
||||
"catches": evidence,
|
||||
}
|
||||
_pending_local_request_id = request_id
|
||||
_pending_local_catch_ids = catch_ids.duplicate()
|
||||
_pending_local_buyer_id = buyer.id
|
||||
local_sale_pending.emit(request_id)
|
||||
if _session.is_host():
|
||||
_handle_sale_request(_session.get_local_peer_id(), request)
|
||||
|
|
@ -182,27 +202,23 @@ func _handle_sale_request(peer_id: int, data: Dictionary) -> void:
|
|||
request_id, "A sale is already pending."
|
||||
))
|
||||
return
|
||||
if not _is_buyer_available_for_peer(peer_id):
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
var message: String = (
|
||||
"The buyer is unavailable."
|
||||
if avatar == null or _pelican_landmark == null
|
||||
else "Move closer to the buyer."
|
||||
)
|
||||
_record_and_send(peer_id, _rejected_result(request_id, message))
|
||||
return
|
||||
if _buyer == null or not _buyer.is_valid():
|
||||
var buyer_id := StringName(str(data["buyer_id"]))
|
||||
var buyer: FishBuyerProfileType = _buyers.get(buyer_id)
|
||||
if buyer == null or not buyer.is_valid():
|
||||
_record_and_send(peer_id, _rejected_result(
|
||||
request_id, "The buyer is unavailable."
|
||||
))
|
||||
return
|
||||
if StringName(str(data["buyer_id"])) != _buyer.id:
|
||||
if (
|
||||
buyer_id == MAIN_SHOP_BUYER_ID
|
||||
and not _is_shop_available_for_peer(peer_id)
|
||||
):
|
||||
_record_and_send(peer_id, _rejected_result(
|
||||
request_id, "The buyer is unavailable."
|
||||
request_id, "Move closer to the fishing shop."
|
||||
))
|
||||
return
|
||||
var result: Dictionary = _build_authoritative_result(
|
||||
peer_id, request_id, data["catches"]
|
||||
peer_id, request_id, data["catches"], buyer
|
||||
)
|
||||
_pending_by_peer[peer_id] = request_id
|
||||
_record_and_send(peer_id, result)
|
||||
|
|
@ -212,8 +228,9 @@ func _build_authoritative_result(
|
|||
peer_id: int,
|
||||
request_id: String,
|
||||
evidence_values: Array,
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> Dictionary:
|
||||
if _fish_catalog == null or _buyer == null:
|
||||
if _fish_catalog == null or buyer == null:
|
||||
return _rejected_result(
|
||||
request_id, "The buyer is unavailable."
|
||||
)
|
||||
|
|
@ -245,7 +262,7 @@ func _build_authoritative_result(
|
|||
return _rejected_result(
|
||||
request_id, "Favorite catches cannot be sold."
|
||||
)
|
||||
var offer: int = _buyer.get_offer(decoded.sale_value)
|
||||
var offer: int = buyer.get_offer(decoded.sale_value)
|
||||
if (
|
||||
offer < 0
|
||||
or base_value > 9223372036854775807 - decoded.sale_value
|
||||
|
|
@ -262,6 +279,7 @@ func _build_authoritative_result(
|
|||
"request_id": request_id,
|
||||
"session_id": _session.get_session_id(),
|
||||
"target_peer_id": peer_id,
|
||||
"buyer_id": str(buyer.id),
|
||||
"accepted": true,
|
||||
"catch_ids": catch_ids,
|
||||
"payout": payout,
|
||||
|
|
@ -338,6 +356,12 @@ func _apply_sale_result(data: Dictionary) -> void:
|
|||
return
|
||||
if str(data["request_id"]) != _pending_local_request_id:
|
||||
return
|
||||
var result_buyer_id := StringName(str(
|
||||
data.get("buyer_id", _pending_local_buyer_id)
|
||||
))
|
||||
if result_buyer_id != _pending_local_buyer_id:
|
||||
_fail_local_apply(data, "Sale could not be completed.")
|
||||
return
|
||||
if not bool(data["accepted"]):
|
||||
_received_results[result_id] = true
|
||||
_bound_dictionary(_received_results)
|
||||
|
|
@ -359,8 +383,12 @@ func _apply_sale_result(data: Dictionary) -> void:
|
|||
if _reservations != null and _reservations.is_fish_reserved(catch_id):
|
||||
_fail_local_apply(data, "Reserved in a letter.")
|
||||
return
|
||||
var buyer: FishBuyerProfileType = _buyers.get(_pending_local_buyer_id)
|
||||
if buyer == null or not buyer.is_valid():
|
||||
_fail_local_apply(data, "The buyer is unavailable.")
|
||||
return
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
catch_ids, _buyer
|
||||
catch_ids, buyer
|
||||
)
|
||||
if not preview.is_success():
|
||||
var message: String = (
|
||||
|
|
@ -380,7 +408,7 @@ func _apply_sale_result(data: Dictionary) -> void:
|
|||
var sequence_snapshot: int = _inventory.get_next_catch_sequence()
|
||||
var wallet_snapshot: int = _wallet.get_balance()
|
||||
var local_result: FishSaleResultType = _sale_service.sell_batch(
|
||||
catch_ids, _buyer
|
||||
catch_ids, buyer
|
||||
)
|
||||
if not local_result.is_success() or not _save_manager.save_if_dirty():
|
||||
_inventory.replace_all_catches(
|
||||
|
|
@ -420,6 +448,7 @@ func _finish_local_sale(
|
|||
) -> void:
|
||||
_pending_local_request_id = ""
|
||||
_pending_local_catch_ids.clear()
|
||||
_pending_local_buyer_id = StringName()
|
||||
local_sale_finished.emit(
|
||||
request_id, accepted, message, catch_ids, payout
|
||||
)
|
||||
|
|
@ -494,33 +523,6 @@ func _handle_acknowledgement(
|
|||
_pending_by_peer.erase(peer_id)
|
||||
|
||||
|
||||
func _is_buyer_available_for_peer(peer_id: int) -> bool:
|
||||
if (
|
||||
_session == null
|
||||
or not _session.is_host()
|
||||
or not _session.is_gameplay_session_active()
|
||||
or _spawn_service == null
|
||||
or _pelican_landmark == null
|
||||
or not is_instance_valid(_pelican_landmark)
|
||||
):
|
||||
return false
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if (
|
||||
avatar == null
|
||||
or avatar.is_water_recovery_active()
|
||||
or (
|
||||
_network_fishing != null
|
||||
and _network_fishing.has_peer_attempt(peer_id)
|
||||
)
|
||||
):
|
||||
return false
|
||||
return (
|
||||
avatar.global_position.distance_to(
|
||||
_pelican_landmark.global_position
|
||||
) <= PELICAN_INTERACTION_RANGE
|
||||
)
|
||||
|
||||
|
||||
func _on_peer_removed(peer_id: int) -> void:
|
||||
_request_ledgers.erase(peer_id)
|
||||
_pending_by_peer.erase(peer_id)
|
||||
|
|
@ -530,6 +532,28 @@ func _on_peer_removed(peer_id: int) -> void:
|
|||
_acknowledged_results.erase(result_id)
|
||||
|
||||
|
||||
func _is_shop_available_for_peer(peer_id: int) -> bool:
|
||||
if (
|
||||
_session == null
|
||||
or not _session.is_host()
|
||||
or not _session.is_gameplay_session_active()
|
||||
or _spawn_service == null
|
||||
or _shop_interaction == null
|
||||
or not is_instance_valid(_shop_interaction)
|
||||
):
|
||||
return false
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
return (
|
||||
avatar != null
|
||||
and not avatar.is_water_recovery_active()
|
||||
and (
|
||||
_network_fishing == null
|
||||
or not _network_fishing.has_peer_attempt(peer_id)
|
||||
)
|
||||
and _shop_interaction.is_avatar_in_range(avatar)
|
||||
)
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if state in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
|
|
@ -558,6 +582,7 @@ func _clear_session_state() -> void:
|
|||
_received_results.clear()
|
||||
_pending_local_request_id = ""
|
||||
_pending_local_catch_ids.clear()
|
||||
_pending_local_buyer_id = StringName()
|
||||
|
||||
|
||||
func _bound_dictionary(values: Dictionary) -> void:
|
||||
|
|
|
|||
|
|
@ -47,26 +47,22 @@ func _run() -> void:
|
|||
main.get_node("%PlayerAssetReservationService")
|
||||
as PlayerAssetReservationService
|
||||
)
|
||||
var pelican := (
|
||||
main.get("_test_world") as TestWorld
|
||||
).get_pelican_convenience_landmark()
|
||||
assert(player != null)
|
||||
assert(catalog != null and catalog.candidates.size() == 8)
|
||||
assert(sale_service != null)
|
||||
assert(shop_service != null)
|
||||
assert(session != null and session.is_host())
|
||||
assert(reservations != null)
|
||||
assert(pelican != null)
|
||||
sale_service.local_sale_finished.connect(_on_sale_finished)
|
||||
shop_service.local_purchase_finished.connect(_on_shop_finished)
|
||||
player.global_position = pelican.global_position
|
||||
await process_frame
|
||||
|
||||
_test_each_species(player, catalog, sale_service)
|
||||
_test_multi_sale(player, catalog, sale_service)
|
||||
_test_reservations(player, catalog, sale_service, reservations)
|
||||
_test_rejection_cleanup(player, catalog, sale_service, pelican)
|
||||
await _test_player_menu_sale(main, player, catalog, sale_service, pelican)
|
||||
_test_anywhere_sale(player, catalog, sale_service)
|
||||
await _test_player_menu_sale(main, player, catalog, sale_service)
|
||||
await _test_host_shop_sale(main, player, catalog, sale_service)
|
||||
|
||||
assert(session.set_host_open(true))
|
||||
assert(session.state == NetworkSession.State.OPEN_HOST)
|
||||
|
|
@ -76,6 +72,9 @@ func _run() -> void:
|
|||
assert(session.set_host_open(false))
|
||||
|
||||
await _test_host_shop_purchase(main, player, shop_service)
|
||||
await _test_fishing_shop_sale_ui(
|
||||
main, player, catalog, sale_service, reservations
|
||||
)
|
||||
assert(not sale_service.is_local_sale_pending())
|
||||
assert(not shop_service.is_local_purchase_pending())
|
||||
|
||||
|
|
@ -111,14 +110,9 @@ func _run_multiplayer_host() -> void:
|
|||
assert(remote_peer_id > 1)
|
||||
var spawn_service := main.get("_player_spawn_service") as PlayerSpawnService
|
||||
var remote_avatar := spawn_service.get_avatar(remote_peer_id)
|
||||
var pelican := (
|
||||
main.get("_test_world") as TestWorld
|
||||
).get_pelican_convenience_landmark()
|
||||
assert(remote_avatar != null and pelican != null)
|
||||
# The client cannot teleport its authoritative avatar. Place the host-owned
|
||||
# test avatar at each interaction so this fixture validates the remote
|
||||
# transaction path without conflating it with the movement suite.
|
||||
remote_avatar.global_position = pelican.global_position
|
||||
assert(remote_avatar != null)
|
||||
# Pelican sales are available from the Cooler anywhere. The host-owned
|
||||
# avatar only moves when the later Fishing Shop purchase needs proximity.
|
||||
await create_timer(5.0).timeout
|
||||
var interaction := main.get("_shop_interaction") as FishingShopInteraction
|
||||
assert(interaction != null)
|
||||
|
|
@ -156,10 +150,6 @@ func _run_multiplayer_client() -> void:
|
|||
var shop_service := main.get_node("%NetworkShopService") as NetworkShopService
|
||||
sale_service.local_sale_finished.connect(_on_sale_finished)
|
||||
shop_service.local_purchase_finished.connect(_on_shop_finished)
|
||||
var pelican := (
|
||||
main.get("_test_world") as TestWorld
|
||||
).get_pelican_convenience_landmark()
|
||||
player.global_position = pelican.global_position
|
||||
var catch_ids: Array[StringName] = []
|
||||
for fish: FishData in catalog.candidates:
|
||||
var fish_catch := _make_catch(fish)
|
||||
|
|
@ -182,6 +172,28 @@ func _run_multiplayer_client() -> void:
|
|||
var interaction := main.get("_shop_interaction") as FishingShopInteraction
|
||||
player.global_position = interaction.global_position
|
||||
await create_timer(5.5).timeout
|
||||
var shop_catch := _make_catch(catalog.candidates.front())
|
||||
player.inventory.add_catch(shop_catch)
|
||||
var shop_sale_balance_before: int = player.wallet.get_balance()
|
||||
_sale_result.clear()
|
||||
assert(not sale_service.request_local_sale(
|
||||
[shop_catch.catch_id],
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
).is_empty())
|
||||
var shop_sale_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while Time.get_ticks_msec() < shop_sale_deadline:
|
||||
await process_frame
|
||||
if not _sale_result.is_empty():
|
||||
break
|
||||
print("Client shop sale result: ", _sale_result)
|
||||
assert(not _sale_result.is_empty() and bool(_sale_result[1]))
|
||||
assert(not player.inventory.contains_catch_id(shop_catch.catch_id))
|
||||
assert(
|
||||
player.wallet.get_balance()
|
||||
== shop_sale_balance_before + shop_catch.sale_value
|
||||
)
|
||||
assert(not sale_service.is_local_sale_pending())
|
||||
|
||||
assert(player.wallet.credit(100))
|
||||
_shop_result.clear()
|
||||
assert(not shop_service.request_supply(&"coffee").is_empty())
|
||||
|
|
@ -277,34 +289,90 @@ func _test_reservations(
|
|||
)
|
||||
|
||||
|
||||
func _test_rejection_cleanup(
|
||||
func _test_anywhere_sale(
|
||||
player: Player,
|
||||
catalog: FishPool,
|
||||
sale_service: NetworkSaleService,
|
||||
pelican: Node3D,
|
||||
) -> void:
|
||||
var fish_catch := _make_catch(catalog.candidates[5])
|
||||
player.inventory.add_catch(fish_catch)
|
||||
player.global_position = pelican.global_position + Vector3(20.0, 0.0, 0.0)
|
||||
_assert_sale(player, sale_service, [fish_catch.catch_id], false)
|
||||
assert(player.inventory.contains_catch_id(fish_catch.catch_id))
|
||||
assert(not sale_service.is_local_sale_pending())
|
||||
player.global_position = pelican.global_position
|
||||
# The spawn point is intentionally far from the Pelican landmark. Discounted
|
||||
# Pelican sales are a convenience action and do not require proximity.
|
||||
player.global_position = Vector3(0.0, 3.95, 13.0)
|
||||
_assert_sale(player, sale_service, [fish_catch.catch_id], true)
|
||||
|
||||
|
||||
func _test_host_shop_sale(
|
||||
main: Node,
|
||||
player: Player,
|
||||
catalog: FishPool,
|
||||
sale_service: NetworkSaleService,
|
||||
) -> void:
|
||||
var interaction := main.get("_shop_interaction") as FishingShopInteraction
|
||||
assert(interaction != null)
|
||||
var out_of_range_catch := _make_catch(catalog.candidates[6])
|
||||
player.inventory.add_catch(out_of_range_catch)
|
||||
player.global_position = Vector3(0.0, 3.95, 13.0)
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
_assert_sale(
|
||||
player,
|
||||
sale_service,
|
||||
[out_of_range_catch.catch_id],
|
||||
false,
|
||||
true,
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
)
|
||||
player.global_position = interaction.global_position
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
assert(interaction.is_avatar_in_range(player))
|
||||
var balance_before: int = player.wallet.get_balance()
|
||||
_assert_sale(
|
||||
player,
|
||||
sale_service,
|
||||
[out_of_range_catch.catch_id],
|
||||
true,
|
||||
true,
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
)
|
||||
assert(
|
||||
player.wallet.get_balance()
|
||||
== balance_before + out_of_range_catch.sale_value
|
||||
)
|
||||
var species_ids: Array[StringName] = []
|
||||
var expected_payout: int = 0
|
||||
for fish: FishData in catalog.candidates:
|
||||
var fish_catch := _make_catch(fish)
|
||||
player.inventory.add_catch(fish_catch)
|
||||
species_ids.append(fish_catch.catch_id)
|
||||
expected_payout += fish_catch.sale_value
|
||||
var species_balance_before: int = player.wallet.get_balance()
|
||||
_assert_sale(
|
||||
player,
|
||||
sale_service,
|
||||
species_ids,
|
||||
true,
|
||||
true,
|
||||
NetworkSaleService.MAIN_SHOP_BUYER_ID,
|
||||
)
|
||||
assert(
|
||||
player.wallet.get_balance()
|
||||
== species_balance_before + expected_payout
|
||||
)
|
||||
|
||||
|
||||
func _test_player_menu_sale(
|
||||
main: Node,
|
||||
player: Player,
|
||||
catalog: FishPool,
|
||||
sale_service: NetworkSaleService,
|
||||
pelican: Node3D,
|
||||
) -> void:
|
||||
player.global_position = pelican.global_position
|
||||
var game_ui := main.get_node("%GameUI") as GameUI
|
||||
var player_menu := game_ui.get_node("%PlayerMenu") as PlayerMenu
|
||||
var fish_catch := _make_catch(catalog.candidates[6])
|
||||
player.inventory.add_catch(fish_catch)
|
||||
player.global_position = Vector3(0.0, 3.95, 13.0)
|
||||
player_menu.open_menu()
|
||||
await create_timer(2.2).timeout
|
||||
player_menu.call("_on_catch_card_pressed", fish_catch.catch_id)
|
||||
|
|
@ -312,15 +380,23 @@ func _test_player_menu_sale(
|
|||
var sell_action := player_menu.get_node("%SellBubble") as Button
|
||||
var confirmation := player_menu.get_node("%SaleConfirmation") as Control
|
||||
var confirm_button := player_menu.get_node("%ConfirmSaleButton") as Button
|
||||
var ui_viewport := main.get_node(
|
||||
"UIPresentation/UIViewport"
|
||||
) as SubViewport
|
||||
assert(sell_action.visible and not sell_action.disabled)
|
||||
assert(sell_action.mouse_filter == Control.MOUSE_FILTER_STOP)
|
||||
assert(sell_action.focus_mode == Control.FOCUS_ALL)
|
||||
sell_action.pressed.emit()
|
||||
assert(ui_viewport != null)
|
||||
await _activate_focused_button(sell_action, ui_viewport)
|
||||
await process_frame
|
||||
assert(confirmation.visible)
|
||||
assert(confirm_button.visible and not confirm_button.disabled)
|
||||
assert(
|
||||
confirmation.z_index
|
||||
> (player_menu.get_node("%CoolerOuterWall") as Control).z_index
|
||||
)
|
||||
_sale_result.clear()
|
||||
confirm_button.pressed.emit()
|
||||
await _activate_focused_button(confirm_button, ui_viewport)
|
||||
await process_frame
|
||||
assert(not _sale_result.is_empty() and bool(_sale_result[1]))
|
||||
assert(not player.inventory.contains_catch_id(fish_catch.catch_id))
|
||||
|
|
@ -339,6 +415,21 @@ func _test_player_menu_sale(
|
|||
await create_timer(2.2).timeout
|
||||
|
||||
|
||||
func _activate_focused_button(
|
||||
button: Button,
|
||||
ui_viewport: SubViewport,
|
||||
) -> void:
|
||||
button.grab_focus()
|
||||
await process_frame
|
||||
assert(button.has_focus())
|
||||
for is_pressed: bool in [true, false]:
|
||||
var accept := InputEventAction.new()
|
||||
accept.action = &"ui_accept"
|
||||
accept.pressed = is_pressed
|
||||
ui_viewport.push_input(accept, false)
|
||||
await process_frame
|
||||
|
||||
|
||||
func _test_host_shop_purchase(
|
||||
main: Node,
|
||||
player: Player,
|
||||
|
|
@ -359,16 +450,123 @@ func _test_host_shop_purchase(
|
|||
assert(not shop_service.is_local_purchase_pending())
|
||||
|
||||
|
||||
func _test_fishing_shop_sale_ui(
|
||||
main: Node,
|
||||
player: Player,
|
||||
catalog: FishPool,
|
||||
sale_service: NetworkSaleService,
|
||||
reservations: PlayerAssetReservationService,
|
||||
) -> void:
|
||||
var interaction := main.get("_shop_interaction") as FishingShopInteraction
|
||||
var game_ui := main.get_node("%GameUI") as GameUI
|
||||
var shop := game_ui.get_node("%FishingShop") as FishingShop
|
||||
var player_menu := game_ui.get_node("%PlayerMenu") as PlayerMenu
|
||||
var shop_backdrop := main.get_node("%ShopBackdrop") as ColorRect
|
||||
var ui_viewport := main.get_node(
|
||||
"UIPresentation/UIViewport"
|
||||
) as SubViewport
|
||||
assert(
|
||||
interaction != null
|
||||
and shop != null
|
||||
and player_menu != null
|
||||
and shop_backdrop != null
|
||||
and ui_viewport != null
|
||||
)
|
||||
player.global_position = interaction.global_position
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
var fish_catch := _make_catch(catalog.candidates[7])
|
||||
var reserved_catch := _make_catch(catalog.candidates[6])
|
||||
player.inventory.add_catch(fish_catch)
|
||||
player.inventory.add_catch(reserved_catch)
|
||||
var reservation_id := "shop-ui-reservation"
|
||||
assert(reservations.reserve(reservation_id, {
|
||||
"type": PlayerAssetReservationService.AttachmentType.FISH,
|
||||
"catch_id": str(reserved_catch.catch_id),
|
||||
"catch": reserved_catch.to_network_dict(),
|
||||
}))
|
||||
var balance_before: int = player.wallet.get_balance()
|
||||
assert(shop.open_shop())
|
||||
await process_frame
|
||||
assert(shop_backdrop.visible)
|
||||
assert(shop_backdrop.material is ShaderMaterial)
|
||||
assert(shop.has_node("InputBlocker") and not shop.has_node("Dimmer"))
|
||||
var shop_panel := shop.get_node("%ShopPanel") as PanelContainer
|
||||
assert(shop_panel != null and shop_panel.size == Vector2(980.0, 600.0))
|
||||
var shop_panel_style := shop_panel.get_theme_stylebox("panel") as StyleBoxFlat
|
||||
assert(shop_panel_style != null)
|
||||
assert(shop_panel_style.corner_radius_top_left == 24)
|
||||
var buy_mode := shop.get_node("%BuyModeButton") as Button
|
||||
var sell_mode := shop.get_node("%SellModeButton") as Button
|
||||
assert(buy_mode.visible and sell_mode.visible)
|
||||
await _activate_focused_button(sell_mode, ui_viewport)
|
||||
await process_frame
|
||||
assert(shop.visible and not player_menu.visible)
|
||||
assert(not shop.has_node("ShopPanel/Margin/Layout/Body/FishSales"))
|
||||
assert((shop.get_node("%ShopCoolerPage") as Control).visible)
|
||||
assert(not (shop.get_node("%ShopPanel") as Control).visible)
|
||||
var mounted_cooler := player_menu.get("_cooler_page") as Control
|
||||
assert(mounted_cooler != null and mounted_cooler.visible)
|
||||
assert(
|
||||
mounted_cooler.get_parent() == shop.get_node("%ShopCoolerMount")
|
||||
)
|
||||
var cooler_outer_wall := player_menu.get("_cooler_outer_wall") as Control
|
||||
var water_surface := player_menu.get("_cooler_water_surface") as ColorRect
|
||||
assert(cooler_outer_wall != null and cooler_outer_wall.visible)
|
||||
assert(water_surface.visible and water_surface.material is ShaderMaterial)
|
||||
assert(
|
||||
StringName(
|
||||
(player_menu.get("_sale_buyer_override") as FishBuyerProfile).id
|
||||
) == NetworkSaleService.MAIN_SHOP_BUYER_ID
|
||||
)
|
||||
var fish_nodes: Dictionary = player_menu.get("_fish_nodes")
|
||||
var fish_button := fish_nodes.get(fish_catch.catch_id) as Button
|
||||
var reserved_button := fish_nodes.get(reserved_catch.catch_id) as Button
|
||||
assert(fish_button != null and fish_button.visible)
|
||||
assert(reserved_button != null and reserved_button.visible)
|
||||
await _activate_focused_button(fish_button, ui_viewport)
|
||||
var sell_button := player_menu.get("_sell_bubble") as Button
|
||||
assert(sell_button.visible and not sell_button.disabled)
|
||||
await _activate_focused_button(sell_button, ui_viewport)
|
||||
await process_frame
|
||||
var confirmation := player_menu.get("_sale_confirmation") as Control
|
||||
var confirm_button := player_menu.get("_confirm_sale_button") as Button
|
||||
assert(confirmation.visible)
|
||||
assert(
|
||||
confirmation.z_index
|
||||
> cooler_outer_wall.z_index
|
||||
)
|
||||
_sale_result.clear()
|
||||
await _activate_focused_button(confirm_button, ui_viewport)
|
||||
await process_frame
|
||||
assert(not _sale_result.is_empty() and bool(_sale_result[1]))
|
||||
assert(not player.inventory.contains_catch_id(fish_catch.catch_id))
|
||||
assert(player.wallet.get_balance() == balance_before + fish_catch.sale_value)
|
||||
assert(not sale_service.is_local_sale_pending())
|
||||
assert(reservations.release(reservation_id))
|
||||
var back_button := shop.get_node("%BackToShopButton") as Button
|
||||
await _activate_focused_button(back_button, ui_viewport)
|
||||
await process_frame
|
||||
assert(shop.visible and (shop.get_node("%ShopPanel") as Control).visible)
|
||||
assert(not (shop.get_node("%ShopCoolerPage") as Control).visible)
|
||||
assert(not player_menu.is_shop_cooler_mounted())
|
||||
shop.close_shop()
|
||||
await process_frame
|
||||
|
||||
|
||||
func _assert_sale(
|
||||
player: Player,
|
||||
sale_service: NetworkSaleService,
|
||||
catch_ids: Array[StringName],
|
||||
expected_success: bool,
|
||||
expect_request_id: bool = true,
|
||||
buyer_id: StringName = NetworkSaleService.PELICAN_BUYER_ID,
|
||||
) -> void:
|
||||
var balance_before: int = player.wallet.get_balance()
|
||||
_sale_result.clear()
|
||||
var request_id: String = sale_service.request_local_sale(catch_ids)
|
||||
var request_id: String = sale_service.request_local_sale(
|
||||
catch_ids, buyer_id
|
||||
)
|
||||
assert((not request_id.is_empty()) == expect_request_id)
|
||||
assert(not _sale_result.is_empty())
|
||||
assert(bool(_sale_result[1]) == expected_success)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const CollectionLogType = preload("res://collection/collection_log.gd")
|
|||
const FishDataType = preload("res://fish/fish_data.gd")
|
||||
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd")
|
||||
const FishSelectorType = preload("res://fish/fish_selector.gd")
|
||||
const FishingContextType = preload("res://fishing/fishing_context.gd")
|
||||
const NetworkSaleServiceType = preload(
|
||||
|
|
@ -227,7 +228,10 @@ func _validate_catches_and_authoritative_sale() -> void:
|
|||
root.add_child(sale_service)
|
||||
sale_service.set("_session", session)
|
||||
sale_service.set("_fish_catalog", Catalog)
|
||||
sale_service.set("_buyer", PelicanBuyer)
|
||||
var sale_buyers: Dictionary[StringName, FishBuyerProfileType] = {
|
||||
PelicanBuyer.id: PelicanBuyer,
|
||||
}
|
||||
sale_service.set("_buyers", sale_buyers)
|
||||
|
||||
for index: int in Catalog.candidates.size():
|
||||
var fish: FishDataType = Catalog.candidates[index]
|
||||
|
|
@ -257,6 +261,7 @@ func _validate_catches_and_authoritative_sale() -> void:
|
|||
1,
|
||||
"catalog_sale_%d" % index,
|
||||
[loaded.to_network_dict()],
|
||||
PelicanBuyer,
|
||||
)
|
||||
assert(bool(sale_result.get("accepted", false)))
|
||||
assert(int(sale_result.get("base_value", -1)) == loaded.sale_value)
|
||||
|
|
|
|||
273
tests/inventory_notepad_art_validation.gd
Normal file
273
tests/inventory_notepad_art_validation.gd
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
extends SceneTree
|
||||
|
||||
const PlayerMenuScene = preload("res://ui/player_menu.tscn")
|
||||
const UIReferencePresentationType = preload(
|
||||
"res://ui/ui_reference_presentation.gd"
|
||||
)
|
||||
const EXPECTED_HOST_SIZE := Vector2(278.0, 484.0)
|
||||
const EXPECTED_ART_SIZE := Vector2(306.28125, 580.8)
|
||||
const EXPECTED_ART_POSITION := Vector2(-4.140625, -28.4)
|
||||
const INVENTORY_PANEL_RECT := Rect2(54.0, 166.0, 882.0, 484.0)
|
||||
const NOTEPAD_HOST_RECT := Rect2(952.0, 166.0, 278.0, 484.0)
|
||||
const NOTEPAD_ART_RECT := Rect2(
|
||||
NOTEPAD_HOST_RECT.position + EXPECTED_ART_POSITION,
|
||||
EXPECTED_ART_SIZE,
|
||||
)
|
||||
const HOTBAR_BUBBLE_FIELD_RECT := Rect2(245.0, 608.0, 790.0, 104.0)
|
||||
const TARGET_SIZES: Array[Vector2] = [
|
||||
Vector2(1280.0, 720.0),
|
||||
Vector2(1920.0, 1080.0),
|
||||
Vector2(2560.0, 1440.0),
|
||||
Vector2(3840.0, 2160.0),
|
||||
Vector2(640.0, 480.0),
|
||||
Vector2(1024.0, 768.0),
|
||||
Vector2(1729.0, 973.0),
|
||||
Vector2(2560.0, 1080.0),
|
||||
Vector2(3440.0, 1440.0),
|
||||
]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_print_runtime_parameters()
|
||||
var presentation_stage := Control.new()
|
||||
presentation_stage.name = "CanonicalUIStage"
|
||||
_apply_stage_layout(presentation_stage, Vector2(root.size))
|
||||
root.add_child(presentation_stage)
|
||||
var player_menu := PlayerMenuScene.instantiate() as PlayerMenu
|
||||
presentation_stage.add_child(player_menu)
|
||||
await process_frame
|
||||
var notepads: Array[Node] = player_menu.find_children(
|
||||
"*", "InventoryNotepad", true, false
|
||||
)
|
||||
assert(notepads.size() == 3)
|
||||
for node: Node in notepads:
|
||||
var notepad := node as InventoryNotepad
|
||||
assert(notepad != null)
|
||||
assert(notepad.size.is_equal_approx(EXPECTED_HOST_SIZE))
|
||||
assert(notepad.mouse_filter == Control.MOUSE_FILTER_PASS)
|
||||
var art_rect: Rect2 = notepad.get_art_rect()
|
||||
assert(art_rect.size.is_equal_approx(EXPECTED_ART_SIZE))
|
||||
assert(art_rect.position.is_equal_approx(EXPECTED_ART_POSITION))
|
||||
assert(art_rect.get_center().is_equal_approx(
|
||||
EXPECTED_HOST_SIZE * 0.5
|
||||
+ InventoryNotepad.NOTEPAD_ART_OFFSET
|
||||
))
|
||||
_validate_shared_inventory_geometry(player_menu)
|
||||
_validate_inventory_layering(player_menu)
|
||||
_validate_resolution_matrix()
|
||||
await _capture_inventory_pages(player_menu)
|
||||
print("Inventory notepad artwork validation: PASS")
|
||||
presentation_stage.queue_free()
|
||||
await process_frame
|
||||
quit()
|
||||
|
||||
|
||||
func _print_runtime_parameters() -> void:
|
||||
var window: Window = root
|
||||
var screen_index: int = window.current_screen
|
||||
print(
|
||||
(
|
||||
"Runtime UI: window=%s screen=%s screen_scale=%.3f dpi=%d "
|
||||
+ "content_size=%s content_mode=%d content_aspect=%d "
|
||||
+ "content_factor=%.3f"
|
||||
)
|
||||
% [
|
||||
window.size,
|
||||
DisplayServer.screen_get_size(screen_index),
|
||||
DisplayServer.screen_get_scale(screen_index),
|
||||
DisplayServer.screen_get_dpi(screen_index),
|
||||
window.content_scale_size,
|
||||
window.content_scale_mode,
|
||||
window.content_scale_aspect,
|
||||
window.content_scale_factor,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
func _apply_stage_layout(stage: Control, display_size: Vector2) -> void:
|
||||
stage.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
stage.position = UIReferencePresentationType.get_offset(display_size)
|
||||
stage.size = UIReferencePresentationType.REFERENCE_SIZE
|
||||
stage.scale = Vector2.ONE * UIReferencePresentationType.get_scale(
|
||||
display_size
|
||||
)
|
||||
|
||||
|
||||
func _validate_shared_inventory_geometry(player_menu: PlayerMenu) -> void:
|
||||
for node_name: StringName in [
|
||||
&"DetailConstellation",
|
||||
&"BagDetailConstellation",
|
||||
&"TackleDetailPanel",
|
||||
]:
|
||||
var host := player_menu.get_node("%%%s" % node_name) as Control
|
||||
assert(host != null)
|
||||
assert(host.position.is_equal_approx(Vector2(952.0, 166.0)))
|
||||
assert(host.size.is_equal_approx(EXPECTED_HOST_SIZE))
|
||||
|
||||
|
||||
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)
|
||||
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(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.
|
||||
assert(sale_confirmation.z_index > 90)
|
||||
assert(sale_confirmation.position.is_equal_approx(Vector2(380.0, 265.0)))
|
||||
assert(sale_confirmation.size.is_equal_approx(Vector2(520.0, 190.0)))
|
||||
var confirmation_style := sale_confirmation.get_theme_stylebox(
|
||||
"panel"
|
||||
) as StyleBoxFlat
|
||||
assert(confirmation_style != null)
|
||||
assert(
|
||||
confirmation_style.bg_color.is_equal_approx(
|
||||
UtilityPageStyle.OCEAN_PANEL_DEEP
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _validate_resolution_matrix() -> void:
|
||||
for target_size: Vector2 in TARGET_SIZES:
|
||||
var stage_rect: Rect2 = UIReferencePresentationType.get_rect(
|
||||
target_size
|
||||
)
|
||||
var stage_scale: float = UIReferencePresentationType.get_scale(
|
||||
target_size
|
||||
)
|
||||
assert(is_equal_approx(
|
||||
stage_rect.size.x / stage_rect.size.y,
|
||||
1280.0 / 720.0,
|
||||
))
|
||||
assert(stage_rect.get_center().is_equal_approx(target_size * 0.5))
|
||||
_validate_transformed_rect(
|
||||
"Inventory panel", INVENTORY_PANEL_RECT, stage_rect, stage_scale
|
||||
)
|
||||
_validate_transformed_rect(
|
||||
"Notepad host", NOTEPAD_HOST_RECT, stage_rect, stage_scale
|
||||
)
|
||||
_validate_transformed_rect(
|
||||
"Notepad art", NOTEPAD_ART_RECT, stage_rect, stage_scale
|
||||
)
|
||||
_validate_transformed_rect(
|
||||
"Hotbar", HOTBAR_BUBBLE_FIELD_RECT, stage_rect, stage_scale
|
||||
)
|
||||
var screen_host: Rect2 = _to_screen_rect(
|
||||
NOTEPAD_HOST_RECT, stage_rect.position, stage_scale
|
||||
)
|
||||
var screen_art: Rect2 = _to_screen_rect(
|
||||
NOTEPAD_ART_RECT, stage_rect.position, stage_scale
|
||||
)
|
||||
assert(is_equal_approx(
|
||||
screen_art.size.y,
|
||||
screen_host.size.y * InventoryNotepad.NOTEPAD_CANONICAL_OVERSCAN,
|
||||
))
|
||||
assert(screen_art.get_center().is_equal_approx(
|
||||
screen_host.get_center()
|
||||
+ InventoryNotepad.NOTEPAD_ART_OFFSET * stage_scale
|
||||
))
|
||||
print(
|
||||
(
|
||||
"UI geometry: window=%s stage=%s scale=%.6f "
|
||||
+ "inventory=%s notepad=%s art=%s hotbar=%s"
|
||||
)
|
||||
% [
|
||||
target_size,
|
||||
stage_rect,
|
||||
stage_scale,
|
||||
_to_screen_rect(
|
||||
INVENTORY_PANEL_RECT,
|
||||
stage_rect.position,
|
||||
stage_scale,
|
||||
),
|
||||
screen_host,
|
||||
screen_art,
|
||||
_to_screen_rect(
|
||||
HOTBAR_BUBBLE_FIELD_RECT,
|
||||
stage_rect.position,
|
||||
stage_scale,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
func _validate_transformed_rect(
|
||||
label: String,
|
||||
canonical_rect: Rect2,
|
||||
stage_rect: Rect2,
|
||||
stage_scale: float,
|
||||
) -> void:
|
||||
var screen_rect: Rect2 = _to_screen_rect(
|
||||
canonical_rect, stage_rect.position, stage_scale
|
||||
)
|
||||
assert(
|
||||
screen_rect.position.is_equal_approx(
|
||||
stage_rect.position + canonical_rect.position * stage_scale
|
||||
),
|
||||
"%s position does not use the single stage transform" % label,
|
||||
)
|
||||
assert(
|
||||
screen_rect.size.is_equal_approx(canonical_rect.size * stage_scale),
|
||||
"%s size does not use the single stage transform" % label,
|
||||
)
|
||||
|
||||
|
||||
func _to_screen_rect(
|
||||
canonical_rect: Rect2,
|
||||
stage_offset: Vector2,
|
||||
stage_scale: float,
|
||||
) -> Rect2:
|
||||
return Rect2(
|
||||
stage_offset + canonical_rect.position * stage_scale,
|
||||
canonical_rect.size * stage_scale,
|
||||
)
|
||||
|
||||
|
||||
func _capture_inventory_pages(player_menu: PlayerMenu) -> void:
|
||||
if not OS.has_environment("NETFISHING_NOTEPAD_CAPTURE"):
|
||||
return
|
||||
player_menu.visible = true
|
||||
var sections: Array[PlayerMenu.Section] = [
|
||||
PlayerMenu.Section.COOLER,
|
||||
PlayerMenu.Section.TACKLE_BOX,
|
||||
PlayerMenu.Section.BAG,
|
||||
]
|
||||
var suffixes: Array[String] = ["cooler", "tackle", "equipment"]
|
||||
for index: int in sections.size():
|
||||
player_menu.call("_show_section_immediate", sections[index])
|
||||
await process_frame
|
||||
await process_frame
|
||||
await _save_capture(suffixes[index])
|
||||
player_menu.call("_show_section_immediate", PlayerMenu.Section.COOLER)
|
||||
var sale_confirmation := player_menu.get_node("%SaleConfirmation") as Control
|
||||
var confirmation_message := player_menu.get_node(
|
||||
"%ConfirmationMessage"
|
||||
) as Label
|
||||
assert(sale_confirmation != null)
|
||||
assert(confirmation_message != null)
|
||||
confirmation_message.text = "sell selected fish?"
|
||||
sale_confirmation.visible = true
|
||||
await process_frame
|
||||
await process_frame
|
||||
await _save_capture("sale-confirmation")
|
||||
sale_confirmation.visible = false
|
||||
|
||||
|
||||
func _save_capture(suffix: String) -> void:
|
||||
var image: Image = root.get_texture().get_image()
|
||||
var path: String = "%s-%s.png" % [
|
||||
OS.get_environment("NETFISHING_NOTEPAD_CAPTURE"),
|
||||
suffix,
|
||||
]
|
||||
assert(image.save_png(path) == OK)
|
||||
1
tests/inventory_notepad_art_validation.gd.uid
Normal file
1
tests/inventory_notepad_art_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://np6jfuf8tej8
|
||||
195
tests/ui_scaling_runtime_validation.gd
Normal file
195
tests/ui_scaling_runtime_validation.gd
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
extends SceneTree
|
||||
|
||||
const UIPixelationPresenterType = preload(
|
||||
"res://ui/ui_pixelation_presenter.gd"
|
||||
)
|
||||
const UIReferencePresentationType = preload(
|
||||
"res://ui/ui_reference_presentation.gd"
|
||||
)
|
||||
const GameUIScene := preload("res://ui/game_ui.tscn")
|
||||
const TARGET_SIZES: Array[Vector2i] = [
|
||||
Vector2i(1280, 720),
|
||||
Vector2i(1920, 1080),
|
||||
Vector2i(2560, 1440),
|
||||
Vector2i(3840, 2160),
|
||||
Vector2i(640, 480),
|
||||
Vector2i(1024, 768),
|
||||
Vector2i(1729, 973),
|
||||
Vector2i(2560, 1080),
|
||||
Vector2i(3440, 1440),
|
||||
]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var original_size: Vector2i = root.size
|
||||
var presenter := UIPixelationPresenterType.new()
|
||||
presenter.name = "UIPresentation"
|
||||
presenter.stretch = true
|
||||
var ui_viewport := SubViewport.new()
|
||||
ui_viewport.name = "UIViewport"
|
||||
var game_ui := GameUIScene.instantiate() as GameUI
|
||||
var ui_root := game_ui.get_node("UIRoot") as Control
|
||||
var canonical_stage := game_ui.get_node(
|
||||
"UIRoot/CanonicalStage"
|
||||
) as Control
|
||||
var chat_ui := game_ui.get_node("UIRoot/ChatUI") as ChatUI
|
||||
var player_menu := game_ui.get_node(
|
||||
"UIRoot/CanonicalStage/PlayerMenu"
|
||||
) as PlayerMenu
|
||||
var hotbar := game_ui.get_node(
|
||||
"UIRoot/CanonicalStage/Hotbar"
|
||||
) as HotbarUI
|
||||
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(
|
||||
"ResponsiveTitleStage"
|
||||
) as Control
|
||||
var decorative_fish_layer := title_screen.get_node(
|
||||
"DecorativeFishLayer"
|
||||
) as Control
|
||||
var decorative_bubble_layer := title_screen.get_node(
|
||||
"DecorativeBubbleLayer"
|
||||
) as Control
|
||||
ui_viewport.add_child(game_ui)
|
||||
presenter.add_child(ui_viewport)
|
||||
root.add_child(presenter)
|
||||
await process_frame
|
||||
chat_ui.set_available(true)
|
||||
print(
|
||||
"UI viewport embedding: root=%s subviewport=%s"
|
||||
% [root.gui_embed_subwindows, ui_viewport.gui_embed_subwindows]
|
||||
)
|
||||
|
||||
for target_size: Vector2i in TARGET_SIZES:
|
||||
root.size = target_size
|
||||
await process_frame
|
||||
await process_frame
|
||||
var actual_size: Vector2i = root.size
|
||||
var display_size := Vector2(actual_size)
|
||||
var expected_scale: float = UIReferencePresentationType.get_scale(
|
||||
display_size
|
||||
)
|
||||
var expected_stage_offset: Vector2 = UIReferencePresentationType.get_offset(
|
||||
display_size
|
||||
)
|
||||
assert(presenter.position.is_equal_approx(Vector2.ZERO))
|
||||
assert(ui_root.position.is_equal_approx(Vector2.ZERO))
|
||||
var expected_visible_size: Vector2 = (
|
||||
UIReferencePresentationType.get_visible_reference_size(display_size)
|
||||
)
|
||||
assert(ui_root.size.is_equal_approx(expected_visible_size))
|
||||
assert(canonical_stage.size.is_equal_approx(
|
||||
UIReferencePresentationType.REFERENCE_SIZE
|
||||
))
|
||||
assert(canonical_stage.position.is_equal_approx(
|
||||
UIReferencePresentationType.get_stage_position(display_size)
|
||||
))
|
||||
assert(chat_ui.size.is_equal_approx(expected_visible_size))
|
||||
assert(screen_fade.size.is_equal_approx(expected_visible_size))
|
||||
assert(player_menu.position.is_equal_approx(Vector2.ZERO))
|
||||
assert(player_menu.size.is_equal_approx(
|
||||
UIReferencePresentationType.REFERENCE_SIZE
|
||||
))
|
||||
assert(hotbar.position.is_equal_approx(Vector2.ZERO))
|
||||
assert(hotbar.size.is_equal_approx(
|
||||
UIReferencePresentationType.REFERENCE_SIZE
|
||||
))
|
||||
assert(title_screen.size.is_equal_approx(expected_visible_size))
|
||||
assert(title_content_stage.size.is_equal_approx(
|
||||
UIReferencePresentationType.REFERENCE_SIZE
|
||||
))
|
||||
assert(title_content_stage.position.is_equal_approx(
|
||||
canonical_stage.position
|
||||
))
|
||||
assert(decorative_fish_layer.size.is_equal_approx(
|
||||
expected_visible_size
|
||||
))
|
||||
assert(decorative_bubble_layer.size.is_equal_approx(
|
||||
expected_visible_size
|
||||
))
|
||||
var left_to_right_bounds: Vector2 = (
|
||||
TitleScreen.get_decorative_fish_crossing_bounds(
|
||||
expected_visible_size.x, 160.0, 1.0
|
||||
)
|
||||
)
|
||||
var right_to_left_bounds: Vector2 = (
|
||||
TitleScreen.get_decorative_fish_crossing_bounds(
|
||||
expected_visible_size.x, 160.0, -1.0
|
||||
)
|
||||
)
|
||||
assert(left_to_right_bounds.x < -160.0)
|
||||
assert(left_to_right_bounds.y > expected_visible_size.x)
|
||||
assert(right_to_left_bounds.x > expected_visible_size.x)
|
||||
assert(right_to_left_bounds.y < -160.0)
|
||||
assert(is_equal_approx(presenter.scale.x, presenter.scale.y))
|
||||
assert(is_equal_approx(ui_root.scale.x, ui_root.scale.y))
|
||||
var effective_scale: float = presenter.scale.x * ui_root.scale.x
|
||||
assert(is_equal_approx(effective_scale, expected_scale))
|
||||
assert((
|
||||
canonical_stage.position * effective_scale
|
||||
).distance_to(expected_stage_offset) <= 0.01)
|
||||
var expected_render_height: int = mini(
|
||||
roundi(UIReferencePresentationType.REFERENCE_SIZE.y * expected_scale),
|
||||
PlayerSettings.get_ui_render_height(
|
||||
PlayerSettings.DEFAULT_UI_PIXEL_SIZE,
|
||||
roundi(UIReferencePresentationType.REFERENCE_SIZE.y),
|
||||
),
|
||||
)
|
||||
var expected_render_scale: float = (
|
||||
float(expected_render_height)
|
||||
/ UIReferencePresentationType.REFERENCE_SIZE.y
|
||||
)
|
||||
var expected_viewport_size := Vector2i(
|
||||
roundi(expected_visible_size.x * expected_render_scale),
|
||||
roundi(expected_visible_size.y * expected_render_scale),
|
||||
)
|
||||
assert(presenter.get_ui_viewport_size() == expected_viewport_size)
|
||||
assert(presenter.size.is_equal_approx(Vector2(expected_viewport_size)))
|
||||
var chat_panel := chat_ui.get_node("ChatPanel") as Control
|
||||
assert(chat_panel != null)
|
||||
assert(chat_panel.visible)
|
||||
assert(is_equal_approx(chat_panel.position.x, 0.0))
|
||||
assert(is_equal_approx(
|
||||
(chat_panel.position.y + chat_panel.size.y) * effective_scale,
|
||||
display_size.y - ChatUI.BOTTOM_MARGIN * effective_scale,
|
||||
))
|
||||
print(
|
||||
(
|
||||
"UI resize: requested=%s actual=%s viewport=%s container_scale=%.6f "
|
||||
+ "root_scale=%.6f effective_scale=%.6f stage_offset=%s "
|
||||
+ "chat_x=%.3f"
|
||||
)
|
||||
% [
|
||||
target_size,
|
||||
actual_size,
|
||||
presenter.get_ui_viewport_size(),
|
||||
presenter.scale.x,
|
||||
ui_root.scale.x,
|
||||
effective_scale,
|
||||
expected_stage_offset,
|
||||
chat_panel.position.x * effective_scale,
|
||||
]
|
||||
)
|
||||
if OS.has_environment("NETFISHING_UI_SCALE_CAPTURE"):
|
||||
await process_frame
|
||||
var capture: Image = root.get_texture().get_image()
|
||||
var capture_path: String = (
|
||||
"%s-requested-%dx%d-actual-%dx%d.png"
|
||||
) % [
|
||||
OS.get_environment("NETFISHING_UI_SCALE_CAPTURE"),
|
||||
target_size.x,
|
||||
target_size.y,
|
||||
actual_size.x,
|
||||
actual_size.y,
|
||||
]
|
||||
assert(capture.save_png(capture_path) == OK)
|
||||
|
||||
presenter.queue_free()
|
||||
root.size = original_size
|
||||
await process_frame
|
||||
print("UI scaling runtime validation: PASS")
|
||||
quit()
|
||||
1
tests/ui_scaling_runtime_validation.gd.uid
Normal file
1
tests/ui_scaling_runtime_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d1x8t4mf2kqv6
|
||||
|
|
@ -60,6 +60,7 @@ var _send_pending: bool = false
|
|||
var _pending_send_body: String = ""
|
||||
var _prior_movement: bool = true
|
||||
var _prior_camera: bool = true
|
||||
var _output_scale: float = 1.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -563,6 +564,10 @@ func _set_presentation_state(
|
|||
_refresh_visibility()
|
||||
|
||||
|
||||
func set_output_scale(output_scale: float) -> void:
|
||||
_output_scale = maxf(output_scale, 0.001)
|
||||
|
||||
|
||||
func _refresh_handle_labels(state: PresentationState) -> void:
|
||||
var collapsed := state == PresentationState.COLLAPSED
|
||||
var height_state := _visible_state_before_collapse if collapsed else state
|
||||
|
|
@ -632,7 +637,9 @@ func _update_speech() -> void:
|
|||
if camera.is_position_behind(world_position):
|
||||
bubble.hide()
|
||||
continue
|
||||
var screen_position := camera.unproject_position(world_position)
|
||||
var screen_position := (
|
||||
camera.unproject_position(world_position) / _output_scale
|
||||
)
|
||||
var viewport_size := get_viewport_rect().size
|
||||
if (
|
||||
screen_position.x < -80.0
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ const CONTENT_MARGIN_RIGHT := 18.0
|
|||
const CONTENT_MARGIN_BOTTOM := 16.0
|
||||
const HANDWRITTEN_FONT: Font = preload("res://ui/fonts/seattle_avenue.otf")
|
||||
const NOTEPAD_TEXTURE: Texture2D = preload("res://art/ui/ui_notepad.png")
|
||||
const NOTEPAD_ART_SCALE := 1.1
|
||||
const NOTEPAD_CANONICAL_OVERSCAN: float = 1.2
|
||||
const NOTEPAD_ART_OFFSET := Vector2(10.0, 20.0)
|
||||
|
||||
@export var title_text := "notes"
|
||||
@export var inset_content := true
|
||||
|
|
@ -34,17 +35,7 @@ static func apply_handwritten_to(root: Control) -> void:
|
|||
|
||||
|
||||
func _draw() -> void:
|
||||
var texture_size := NOTEPAD_TEXTURE.get_size()
|
||||
var uniform_scale := minf(
|
||||
size.x / texture_size.x,
|
||||
size.y / texture_size.y,
|
||||
) * NOTEPAD_ART_SCALE
|
||||
var rendered_size := texture_size * uniform_scale
|
||||
var rendered_rect := Rect2(
|
||||
(size - rendered_size) * 0.5,
|
||||
rendered_size,
|
||||
)
|
||||
draw_texture_rect(NOTEPAD_TEXTURE, rendered_rect, false)
|
||||
draw_texture_rect(NOTEPAD_TEXTURE, get_art_rect(), false)
|
||||
|
||||
var font: Font = HANDWRITTEN_FONT
|
||||
draw_string(
|
||||
|
|
@ -58,6 +49,34 @@ func _draw() -> void:
|
|||
)
|
||||
|
||||
|
||||
func get_art_rect() -> Rect2:
|
||||
return get_art_rect_for_host(size)
|
||||
|
||||
|
||||
static func get_art_rect_for_host(host_size: Vector2) -> Rect2:
|
||||
# Source pixels establish the artwork aspect ratio and sampling quality. The
|
||||
# canonical host rect, not the source dimensions, owns its displayed size.
|
||||
var source_size: Vector2 = NOTEPAD_TEXTURE.get_size()
|
||||
if (
|
||||
host_size.x <= 0.0
|
||||
or host_size.y <= 0.0
|
||||
or source_size.x <= 0.0
|
||||
or source_size.y <= 0.0
|
||||
):
|
||||
return Rect2()
|
||||
var fit_scale: float = minf(
|
||||
host_size.x / source_size.x,
|
||||
host_size.y / source_size.y,
|
||||
)
|
||||
var rendered_size: Vector2 = (
|
||||
source_size * fit_scale * NOTEPAD_CANONICAL_OVERSCAN
|
||||
)
|
||||
return Rect2(
|
||||
(host_size - rendered_size) * 0.5 + NOTEPAD_ART_OFFSET,
|
||||
rendered_size,
|
||||
)
|
||||
|
||||
|
||||
static func make_layout_style(with_content_margins: bool = true) -> StyleBoxFlat:
|
||||
var paper := StyleBoxFlat.new()
|
||||
paper.bg_color = Color.TRANSPARENT
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ extends Control
|
|||
const INPUT_OWNER: StringName = &"fishing_shop"
|
||||
const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
|
||||
const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd")
|
||||
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||
const FishSaleResultType = preload("res://economy/fish_sale_result.gd")
|
||||
const FishSaleServiceType = preload("res://economy/fish_sale_service.gd")
|
||||
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
||||
const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd")
|
||||
const ItemCatalogType = preload("res://items/item_catalog.gd")
|
||||
|
|
@ -24,11 +20,12 @@ const PlayerCoolerCapacityType = preload(
|
|||
const ShopInteractionType = preload(
|
||||
"res://world/fishing_shop_interaction.gd"
|
||||
)
|
||||
const FishBatchSelectionType = preload(
|
||||
"res://ui/fish_batch_selection.gd"
|
||||
)
|
||||
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
signal sell_fish_requested
|
||||
signal shop_cooler_return_requested
|
||||
signal shop_cooler_confirmation_cancel_requested
|
||||
|
||||
enum CloseReason {
|
||||
USER,
|
||||
|
|
@ -39,17 +36,12 @@ enum CloseReason {
|
|||
}
|
||||
|
||||
@onready var _wallet_label: Label = %WalletLabel
|
||||
@onready var _fish_sales: VBoxContainer = (
|
||||
$ShopPanel/Margin/Layout/Body/FishSales
|
||||
)
|
||||
@onready var _fish_list: ItemList = %FishList
|
||||
@onready var _sales_title: Label = %SalesTitle
|
||||
@onready var _fish_empty: Label = %FishEmpty
|
||||
@onready var _fish_texture: TextureRect = %FishTexture
|
||||
@onready var _fish_name: Label = %FishName
|
||||
@onready var _fish_details: Label = %FishDetails
|
||||
@onready var _selection_summary: Label = %SelectionSummary
|
||||
@onready var _sell_button: Button = %SellButton
|
||||
@onready var _shop_panel: PanelContainer = %ShopPanel
|
||||
@onready var _shop_cooler_page: Control = %ShopCoolerPage
|
||||
@onready var _shop_cooler_mount: Control = %ShopCoolerMount
|
||||
@onready var _back_to_shop_button: Button = %BackToShopButton
|
||||
@onready var _buy_mode_button: Button = %BuyModeButton
|
||||
@onready var _sell_mode_button: Button = %SellModeButton
|
||||
@onready var _feedback: Label = %Feedback
|
||||
@onready var _reel_level: Label = %ReelLevel
|
||||
@onready var _reel_effect: Label = %ReelEffect
|
||||
|
|
@ -64,15 +56,9 @@ enum CloseReason {
|
|||
@onready var _cooler_effect: Label = %CoolerEffect
|
||||
@onready var _cooler_cost: Label = %CoolerCost
|
||||
@onready var _cooler_purchase: Button = %CoolerPurchase
|
||||
@onready var _confirmation: PanelContainer = %SaleConfirmation
|
||||
@onready var _confirmation_text: Label = %ConfirmationText
|
||||
@onready var _confirm_sale: Button = %ConfirmSale
|
||||
@onready var _cancel_sale: Button = %CancelSale
|
||||
|
||||
var _player: PlayerType
|
||||
var _inventory: FishInventoryType
|
||||
var _wallet: PlayerWalletType
|
||||
var _sale_service: FishSaleServiceType
|
||||
var _buyer: FishBuyerProfileType
|
||||
var _upgrades: PlayerFishingUpgradesType
|
||||
var _fishing_spot: FishingSpotType
|
||||
|
|
@ -80,42 +66,93 @@ var _interaction: ShopInteractionType
|
|||
var _bag: PlayerBagType
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _cooler_capacity: PlayerCoolerCapacityType
|
||||
var _network_session: NetworkSession
|
||||
var _network_shop: NetworkShopService
|
||||
var _fish_selection := FishBatchSelectionType.new()
|
||||
var _confirmation_catch_ids: Array[StringName] = []
|
||||
var _confirmation_generation: int = -1
|
||||
var _prior_movement_enabled: bool = true
|
||||
var _prior_camera_enabled: bool = true
|
||||
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
var _snapshot_stored: bool = false
|
||||
var _mouse_snapshot_stored: bool = false
|
||||
var _generation: int = 0
|
||||
var _selection_input_generation: int = 0
|
||||
var _transaction_in_progress: bool = false
|
||||
var _closing: bool = false
|
||||
var _cooler_page_active: bool = false
|
||||
var _cooler_modal_open: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# Catch selling belongs to the Pelican action in the Player Menu. Keep the
|
||||
# legacy shop sales controls out of the active interface.
|
||||
_fish_sales.hide()
|
||||
UtilityPageStyleType.apply_page(self)
|
||||
_apply_shop_styles()
|
||||
%CloseButton.pressed.connect(close_shop)
|
||||
_fish_list.item_selected.connect(_on_fish_selected)
|
||||
_fish_list.item_clicked.connect(_on_fish_clicked)
|
||||
_sell_button.pressed.connect(_open_sale_confirmation)
|
||||
_confirm_sale.pressed.connect(_on_confirm_sale)
|
||||
_cancel_sale.pressed.connect(_close_sale_confirmation)
|
||||
_buy_mode_button.pressed.connect(_focus_buy_page)
|
||||
_sell_mode_button.pressed.connect(_request_shop_cooler)
|
||||
_back_to_shop_button.pressed.connect(shop_cooler_return_requested.emit)
|
||||
_reel_purchase.pressed.connect(_purchase_reel_speed)
|
||||
_barrier_purchase.pressed.connect(_purchase_barrier_power)
|
||||
_cooler_purchase.pressed.connect(_purchase_cooler_capacity)
|
||||
|
||||
|
||||
func _request_shop_cooler() -> void:
|
||||
if _transaction_in_progress:
|
||||
_feedback.text = "Finish the current transaction first."
|
||||
return
|
||||
if not _is_transaction_context_valid():
|
||||
_feedback.text = "The fishing shop is no longer available."
|
||||
return
|
||||
sell_fish_requested.emit()
|
||||
|
||||
|
||||
func _focus_buy_page() -> void:
|
||||
_feedback.text = ""
|
||||
if _supplies_list.get_child_count() > 0:
|
||||
var first_supply := _supplies_list.get_child(0) as Button
|
||||
if first_supply != null and not first_supply.disabled:
|
||||
first_supply.grab_focus()
|
||||
return
|
||||
_reel_purchase.grab_focus()
|
||||
|
||||
|
||||
func _apply_shop_styles() -> void:
|
||||
var shop_panel := $ShopPanel as PanelContainer
|
||||
var shop_panel_style := UtilityPageStyleType.panel_style()
|
||||
shop_panel_style.set_corner_radius_all(24)
|
||||
shop_panel_style.content_margin_left = 0.0
|
||||
shop_panel_style.content_margin_top = 0.0
|
||||
shop_panel_style.content_margin_right = 0.0
|
||||
shop_panel_style.content_margin_bottom = 0.0
|
||||
shop_panel.add_theme_stylebox_override(
|
||||
"panel", shop_panel_style
|
||||
)
|
||||
for panel: PanelContainer in [
|
||||
$ShopPanel/Margin/Layout/Body/Upgrades/ReelCard,
|
||||
$ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard,
|
||||
$ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard,
|
||||
]:
|
||||
panel.add_theme_stylebox_override(
|
||||
"panel", UtilityPageStyleType.row_style()
|
||||
)
|
||||
for button: BaseButton in [
|
||||
%CloseButton,
|
||||
_buy_mode_button,
|
||||
_sell_mode_button,
|
||||
_reel_purchase,
|
||||
_barrier_purchase,
|
||||
_cooler_purchase,
|
||||
_back_to_shop_button,
|
||||
]:
|
||||
UtilityPageStyleType.apply_ocean_button(button)
|
||||
_buy_mode_button.add_theme_stylebox_override(
|
||||
"normal",
|
||||
UtilityPageStyleType.ocean_button_style(
|
||||
UtilityPageStyleType.OCEAN_SELECTED
|
||||
),
|
||||
)
|
||||
_feedback.add_theme_color_override(
|
||||
"font_color", UtilityPageStyleType.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
|
||||
|
||||
func setup(
|
||||
player: PlayerType,
|
||||
inventory: FishInventoryType,
|
||||
wallet: PlayerWalletType,
|
||||
sale_service: FishSaleServiceType,
|
||||
buyer: FishBuyerProfileType,
|
||||
upgrades: PlayerFishingUpgradesType,
|
||||
fishing_spot: FishingSpotType,
|
||||
|
|
@ -123,13 +160,10 @@ func setup(
|
|||
bag: PlayerBagType,
|
||||
item_catalog: ItemCatalogType,
|
||||
cooler_capacity: PlayerCoolerCapacityType,
|
||||
network_session: NetworkSession,
|
||||
network_shop: NetworkShopService,
|
||||
) -> void:
|
||||
_player = player
|
||||
_inventory = inventory
|
||||
_wallet = wallet
|
||||
_sale_service = sale_service
|
||||
_buyer = buyer
|
||||
_upgrades = upgrades
|
||||
_fishing_spot = fishing_spot
|
||||
|
|
@ -137,7 +171,6 @@ func setup(
|
|||
_bag = bag
|
||||
_item_catalog = item_catalog
|
||||
_cooler_capacity = cooler_capacity
|
||||
_network_session = network_session
|
||||
_network_shop = network_shop
|
||||
if (
|
||||
_network_shop != null
|
||||
|
|
@ -151,9 +184,6 @@ func setup(
|
|||
_network_shop.local_purchase_finished.connect(
|
||||
_on_network_purchase_finished
|
||||
)
|
||||
_fish_selection.clear()
|
||||
if not _inventory.catches_changed.is_connected(_on_inventory_changed):
|
||||
_inventory.catches_changed.connect(_on_inventory_changed)
|
||||
if not _wallet.balance_changed.is_connected(_on_wallet_changed):
|
||||
_wallet.balance_changed.connect(_on_wallet_changed)
|
||||
if not _upgrades.upgrades_changed.is_connected(_on_upgrades_changed):
|
||||
|
|
@ -180,7 +210,6 @@ func open_shop() -> bool:
|
|||
or _buyer.id != MAIN_SHOP_BUYER_ID
|
||||
):
|
||||
return false
|
||||
_generation += 1
|
||||
_closing = false
|
||||
_transaction_in_progress = (
|
||||
_network_shop != null
|
||||
|
|
@ -196,10 +225,10 @@ func open_shop() -> bool:
|
|||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_feedback.text = ""
|
||||
_close_sale_confirmation()
|
||||
deactivate_shop_cooler_page()
|
||||
show()
|
||||
_refresh_all()
|
||||
%CloseButton.grab_focus()
|
||||
_buy_mode_button.grab_focus()
|
||||
menu_visibility_changed.emit(true)
|
||||
return true
|
||||
|
||||
|
|
@ -207,13 +236,42 @@ func open_shop() -> bool:
|
|||
func consume_escape() -> bool:
|
||||
if not visible:
|
||||
return false
|
||||
if _confirmation.visible:
|
||||
_close_sale_confirmation()
|
||||
if _cooler_page_active:
|
||||
if _cooler_modal_open:
|
||||
shop_cooler_confirmation_cancel_requested.emit()
|
||||
else:
|
||||
shop_cooler_return_requested.emit()
|
||||
return true
|
||||
close_shop()
|
||||
return true
|
||||
|
||||
|
||||
func get_shop_cooler_mount() -> Control:
|
||||
return _shop_cooler_mount
|
||||
|
||||
|
||||
func activate_shop_cooler_page() -> void:
|
||||
_cooler_page_active = true
|
||||
_shop_panel.hide()
|
||||
_shop_cooler_page.show()
|
||||
_back_to_shop_button.disabled = false
|
||||
|
||||
|
||||
func deactivate_shop_cooler_page() -> void:
|
||||
_cooler_page_active = false
|
||||
_cooler_modal_open = false
|
||||
_back_to_shop_button.disabled = false
|
||||
_shop_cooler_page.hide()
|
||||
_shop_panel.show()
|
||||
if visible:
|
||||
_buy_mode_button.grab_focus()
|
||||
|
||||
|
||||
func set_shop_cooler_modal_open(is_open: bool) -> void:
|
||||
_cooler_modal_open = is_open
|
||||
_back_to_shop_button.disabled = is_open
|
||||
|
||||
|
||||
func close_shop(
|
||||
reason: CloseReason = CloseReason.USER,
|
||||
restore_controls: bool = true,
|
||||
|
|
@ -221,10 +279,7 @@ func close_shop(
|
|||
if not visible:
|
||||
return
|
||||
_closing = true
|
||||
_generation += 1
|
||||
_transaction_in_progress = false
|
||||
_close_sale_confirmation()
|
||||
_fish_selection.clear()
|
||||
hide()
|
||||
get_viewport().gui_release_focus()
|
||||
if _fishing_spot != null and is_instance_valid(_fishing_spot):
|
||||
|
|
@ -241,6 +296,7 @@ func close_shop(
|
|||
_apply_mouse_close_policy(reason)
|
||||
_closing = false
|
||||
menu_visibility_changed.emit(false)
|
||||
deactivate_shop_cooler_page()
|
||||
|
||||
|
||||
func close_for_range_exit() -> void:
|
||||
|
|
@ -268,8 +324,6 @@ func _refresh_all() -> void:
|
|||
if not is_node_ready():
|
||||
return
|
||||
_refresh_wallet()
|
||||
_refresh_fish_list()
|
||||
_refresh_selected_fish()
|
||||
_refresh_upgrades()
|
||||
_refresh_supplies()
|
||||
_refresh_cooler_capacity()
|
||||
|
|
@ -283,144 +337,6 @@ func _refresh_wallet() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _refresh_fish_list() -> void:
|
||||
_fish_list.clear()
|
||||
var catches: Array[FishCatchType] = (
|
||||
_inventory.get_all_catches() if _inventory != null else []
|
||||
)
|
||||
_sales_title.text = "cooler %d / %d • full base value" % [
|
||||
catches.size(),
|
||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||
]
|
||||
catches.sort_custom(
|
||||
func(a: FishCatchType, b: FishCatchType) -> bool:
|
||||
return a.catch_sequence > b.catch_sequence
|
||||
)
|
||||
var visible_ids: Array[StringName] = []
|
||||
for fish_catch: FishCatchType in catches:
|
||||
if fish_catch != null and fish_catch.is_valid():
|
||||
visible_ids.append(fish_catch.catch_id)
|
||||
_fish_selection.set_visible_order(visible_ids)
|
||||
for fish_catch: FishCatchType in catches:
|
||||
if fish_catch == null or not fish_catch.is_valid():
|
||||
continue
|
||||
var marker: String = ""
|
||||
if _fish_selection.is_selected(fish_catch.catch_id):
|
||||
marker += "✓ "
|
||||
if fish_catch.catch_id == _fish_selection.get_focused_id():
|
||||
marker += "◆ "
|
||||
if fish_catch.is_favorited:
|
||||
marker += "★ "
|
||||
var index: int = _fish_list.add_item(
|
||||
"%s%s — %.2f lb"
|
||||
% [marker, fish_catch.fish.display_name, fish_catch.weight_lb],
|
||||
fish_catch.fish.display_texture
|
||||
)
|
||||
_fish_list.set_item_metadata(index, String(fish_catch.catch_id))
|
||||
_fish_list.set_item_tooltip(
|
||||
index,
|
||||
"%s • %s"
|
||||
% [
|
||||
fish_catch.fish.get_rarity_name(),
|
||||
"favorited" if fish_catch.is_favorited else "available",
|
||||
]
|
||||
)
|
||||
_fish_empty.visible = _fish_list.item_count == 0
|
||||
_fish_list.deselect_all()
|
||||
for index: int in range(_fish_list.item_count):
|
||||
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
|
||||
if _fish_selection.is_selected(catch_id):
|
||||
_fish_list.select(index, false)
|
||||
|
||||
|
||||
func _refresh_selected_fish() -> void:
|
||||
var fish_catch: FishCatchType = _get_selected_catch()
|
||||
_fish_texture.texture = (
|
||||
fish_catch.fish.display_texture if fish_catch != null else null
|
||||
)
|
||||
_fish_name.text = (
|
||||
fish_catch.fish.display_name if fish_catch != null else "select a fish"
|
||||
)
|
||||
if fish_catch == null:
|
||||
_fish_details.text = "choose one individual fish from your cooler."
|
||||
_update_sale_summary()
|
||||
return
|
||||
var individual_preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch([fish_catch.catch_id], _buyer)
|
||||
if _sale_service != null
|
||||
else null
|
||||
)
|
||||
var offer: int = (
|
||||
individual_preview.payout if individual_preview != null else -1
|
||||
)
|
||||
_fish_details.text = (
|
||||
"%.2f lb • %s\nbase value: $%d\nmain-shop offer: $%d%s"
|
||||
% [
|
||||
fish_catch.weight_lb,
|
||||
fish_catch.fish.get_rarity_name(),
|
||||
fish_catch.sale_value,
|
||||
offer,
|
||||
"\nfavorited fish cannot be sold."
|
||||
if fish_catch.is_favorited
|
||||
else "",
|
||||
]
|
||||
)
|
||||
_update_sale_summary()
|
||||
|
||||
|
||||
func _update_sale_summary() -> void:
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
var selected_count: int = selected_ids.size()
|
||||
_sell_button.text = (
|
||||
"sell fish"
|
||||
if selected_count <= 1
|
||||
else "sell %d fish" % selected_count
|
||||
)
|
||||
if selected_count == 0:
|
||||
_selection_summary.text = "no fish selected"
|
||||
_sell_button.disabled = true
|
||||
return
|
||||
if _network_session != null and _network_session.is_joined_client():
|
||||
_selection_summary.text = (
|
||||
"1 fish selected"
|
||||
if selected_count == 1
|
||||
else "%d fish selected" % selected_count
|
||||
)
|
||||
_selection_summary.text += (
|
||||
"\nSell catches to the nearby pelicans."
|
||||
)
|
||||
_sell_button.disabled = true
|
||||
return
|
||||
var preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch(selected_ids, _buyer)
|
||||
if _sale_service != null
|
||||
else null
|
||||
)
|
||||
_selection_summary.text = (
|
||||
"1 fish selected"
|
||||
if selected_count == 1
|
||||
else "%d fish selected" % selected_count
|
||||
)
|
||||
if (
|
||||
preview != null
|
||||
and (
|
||||
preview.is_success()
|
||||
or preview.status == FishSaleResultType.Status.FAVORITED
|
||||
)
|
||||
):
|
||||
_selection_summary.text += "\ntotal offer: $%d" % preview.payout
|
||||
if preview != null and preview.status == FishSaleResultType.Status.FAVORITED:
|
||||
_selection_summary.text += (
|
||||
"\nfavorited fish must be removed from the selection."
|
||||
)
|
||||
_sell_button.disabled = (
|
||||
preview == null
|
||||
or not preview.is_success()
|
||||
or _transaction_in_progress
|
||||
or _closing
|
||||
)
|
||||
|
||||
|
||||
func _refresh_upgrades() -> void:
|
||||
if _upgrades == null:
|
||||
return
|
||||
|
|
@ -516,6 +432,7 @@ func _refresh_supplies() -> void:
|
|||
if _network_shop == null or not _network_shop.can_request_purchase()
|
||||
else item.description
|
||||
)
|
||||
UtilityPageStyleType.apply_ocean_button(button)
|
||||
button.pressed.connect(_purchase_supply.bind(item_id))
|
||||
_supplies_list.add_child(button)
|
||||
|
||||
|
|
@ -549,142 +466,6 @@ func _refresh_cooler_capacity() -> void:
|
|||
_cooler_purchase.text = "purchase"
|
||||
|
||||
|
||||
func _on_fish_selected(index: int) -> void:
|
||||
if index < 0 or index >= _fish_list.item_count:
|
||||
return
|
||||
var input_generation: int = _selection_input_generation
|
||||
call_deferred(
|
||||
"_apply_keyboard_fish_selection",
|
||||
index,
|
||||
input_generation
|
||||
)
|
||||
|
||||
|
||||
func _apply_keyboard_fish_selection(
|
||||
index: int,
|
||||
input_generation: int,
|
||||
) -> void:
|
||||
if (
|
||||
input_generation != _selection_input_generation
|
||||
or index < 0
|
||||
or index >= _fish_list.item_count
|
||||
):
|
||||
return
|
||||
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
|
||||
_fish_selection.select_only(catch_id)
|
||||
_feedback.text = ""
|
||||
_refresh_fish_list()
|
||||
_refresh_selected_fish()
|
||||
|
||||
|
||||
func _on_fish_clicked(
|
||||
index: int,
|
||||
_position: Vector2,
|
||||
mouse_button_index: int,
|
||||
) -> void:
|
||||
if (
|
||||
mouse_button_index != MOUSE_BUTTON_LEFT
|
||||
or index < 0
|
||||
or index >= _fish_list.item_count
|
||||
):
|
||||
return
|
||||
_selection_input_generation += 1
|
||||
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
|
||||
_fish_selection.apply_click(
|
||||
catch_id,
|
||||
Input.is_key_pressed(KEY_CTRL),
|
||||
Input.is_key_pressed(KEY_SHIFT)
|
||||
)
|
||||
_feedback.text = ""
|
||||
_refresh_fish_list()
|
||||
_refresh_selected_fish()
|
||||
|
||||
|
||||
func _open_sale_confirmation() -> void:
|
||||
if _network_session != null and _network_session.is_joined_client():
|
||||
_feedback.text = "Sell catches to the nearby pelicans."
|
||||
return
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
if not _is_transaction_context_valid() or selected_ids.is_empty():
|
||||
_feedback.text = "the fish selection is no longer available."
|
||||
_refresh_all()
|
||||
return
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
selected_ids,
|
||||
_buyer
|
||||
)
|
||||
if not preview.is_success():
|
||||
_feedback.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||
else preview.get_message()
|
||||
)
|
||||
return
|
||||
_confirmation_catch_ids = selected_ids.duplicate()
|
||||
_confirmation_generation = _generation
|
||||
_confirmation_text.text = (
|
||||
"sell %d fish to the fishing shop for $%d?"
|
||||
% [preview.fish_count, preview.payout]
|
||||
)
|
||||
_confirmation.show()
|
||||
_cancel_sale.grab_focus()
|
||||
|
||||
|
||||
func _close_sale_confirmation() -> void:
|
||||
_confirmation_catch_ids.clear()
|
||||
_confirmation_generation = -1
|
||||
_confirmation.hide()
|
||||
|
||||
|
||||
func _on_confirm_sale() -> void:
|
||||
if (
|
||||
_transaction_in_progress
|
||||
or (
|
||||
_network_session != null
|
||||
and _network_session.is_joined_client()
|
||||
)
|
||||
):
|
||||
return
|
||||
var transaction_generation: int = _generation
|
||||
var catch_ids: Array[StringName] = _confirmation_catch_ids.duplicate()
|
||||
var confirmation_generation: int = _confirmation_generation
|
||||
_close_sale_confirmation()
|
||||
if (
|
||||
catch_ids.is_empty()
|
||||
or not _is_transaction_context_valid()
|
||||
or transaction_generation != _generation
|
||||
or confirmation_generation != transaction_generation
|
||||
or _buyer.id != MAIN_SHOP_BUYER_ID
|
||||
):
|
||||
_feedback.text = "unable to complete sale."
|
||||
return
|
||||
_transaction_in_progress = true
|
||||
var result: FishSaleResultType = _sale_service.sell_batch(
|
||||
catch_ids,
|
||||
_buyer
|
||||
)
|
||||
_transaction_in_progress = false
|
||||
if (
|
||||
transaction_generation != _generation
|
||||
or not visible
|
||||
):
|
||||
return
|
||||
if result.is_success():
|
||||
_feedback.text = (
|
||||
"fish sold for $%d."
|
||||
if result.fish_count == 1
|
||||
else "%d fish sold for $%d." % [
|
||||
result.fish_count,
|
||||
result.payout,
|
||||
]
|
||||
)
|
||||
_fish_selection.remove_ids(catch_ids)
|
||||
else:
|
||||
_feedback.text = result.get_message()
|
||||
_refresh_all()
|
||||
|
||||
|
||||
func _purchase_reel_speed() -> void:
|
||||
_purchase_upgrade(true)
|
||||
|
||||
|
|
@ -777,13 +558,6 @@ func _on_network_purchase_finished(
|
|||
_refresh_all()
|
||||
|
||||
|
||||
func _get_selected_catch() -> FishCatchType:
|
||||
var focused_id: StringName = _fish_selection.get_focused_id()
|
||||
if _inventory == null or focused_id.is_empty():
|
||||
return null
|
||||
return _inventory.get_catch_by_id(focused_id)
|
||||
|
||||
|
||||
func _is_transaction_context_valid() -> bool:
|
||||
return (
|
||||
visible
|
||||
|
|
@ -798,27 +572,6 @@ func _is_transaction_context_valid() -> bool:
|
|||
)
|
||||
|
||||
|
||||
func _on_inventory_changed() -> void:
|
||||
_refresh_fish_list()
|
||||
if not _confirmation_catch_ids.is_empty():
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
_confirmation_catch_ids,
|
||||
_buyer
|
||||
)
|
||||
if (
|
||||
_confirmation_generation != _generation
|
||||
or not preview.is_success()
|
||||
):
|
||||
_close_sale_confirmation()
|
||||
_feedback.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||
else preview.get_message()
|
||||
)
|
||||
_refresh_selected_fish()
|
||||
|
||||
|
||||
func _on_wallet_changed(_balance: int, _delta: int) -> void:
|
||||
_refresh_wallet()
|
||||
_refresh_upgrades()
|
||||
|
|
@ -839,7 +592,6 @@ func _on_bag_changed() -> void:
|
|||
|
||||
func _on_cooler_capacity_changed(_level: int, _capacity: int) -> void:
|
||||
_refresh_cooler_capacity()
|
||||
_refresh_fish_list()
|
||||
|
||||
|
||||
func _apply_mouse_close_policy(reason: CloseReason) -> void:
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme = ExtResource("2_theme")
|
||||
theme_override_font_sizes/font_size = 20
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Dimmer" type="ColorRect" parent="."]
|
||||
[node name="InputBlocker" type="Control" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
|
@ -29,28 +30,60 @@ anchor_bottom = 1.0
|
|||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
color = Color(0.015, 0.02, 0.03, 0.64)
|
||||
|
||||
[node name="ShopCoolerPage" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 90
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="ShopCoolerMount" type="Control" parent="ShopCoolerPage"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="BackToShopButton" type="Button" parent="ShopCoolerPage"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 110
|
||||
layout_mode = 0
|
||||
offset_left = 1052.0
|
||||
offset_top = 54.0
|
||||
offset_right = 1218.0
|
||||
offset_bottom = 104.0
|
||||
text = "back to shop"
|
||||
|
||||
[node name="ShopPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -560.0
|
||||
offset_top = -320.0
|
||||
offset_right = 560.0
|
||||
offset_bottom = 320.0
|
||||
offset_left = -490.0
|
||||
offset_top = -300.0
|
||||
offset_right = 490.0
|
||||
offset_bottom = 300.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="ShopPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 14
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 14
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="Layout" type="VBoxContainer" parent="ShopPanel/Margin"]
|
||||
layout_mode = 2
|
||||
|
|
@ -64,7 +97,7 @@ theme_override_constants/separation = 8
|
|||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "fishing shop"
|
||||
theme_override_font_sizes/font_size = 36
|
||||
theme_override_font_sizes/font_size = 32
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
|
||||
[node name="CoinIcon" type="TextureRect" parent="ShopPanel/Margin/Layout/Header"]
|
||||
|
|
@ -84,7 +117,24 @@ text = "wallet: $0"
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "close"
|
||||
custom_minimum_size = Vector2(110, 58)
|
||||
custom_minimum_size = Vector2(104, 48)
|
||||
|
||||
[node name="ModeTabs" type="HBoxContainer" parent="ShopPanel/Margin/Layout"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="BuyModeButton" type="Button" parent="ShopPanel/Margin/Layout/ModeTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(180, 42)
|
||||
layout_mode = 2
|
||||
text = "buy gear & supplies"
|
||||
|
||||
[node name="SellModeButton" type="Button" parent="ShopPanel/Margin/Layout/ModeTabs"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(180, 42)
|
||||
layout_mode = 2
|
||||
text = "sell fish"
|
||||
|
||||
[node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -98,123 +148,27 @@ layout_mode = 2
|
|||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="FishSales" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
custom_minimum_size = Vector2(420, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="SalesTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "sell individual cooler fish • full base value"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
|
||||
[node name="SalesBody" type="HSplitContainer" parent="ShopPanel/Margin/Layout/Body/FishSales"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
split_offset = 220
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="ListPanel" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody"]
|
||||
custom_minimum_size = Vector2(205, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ListMargin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 7
|
||||
theme_override_constants/margin_top = 7
|
||||
theme_override_constants/margin_right = 7
|
||||
theme_override_constants/margin_bottom = 7
|
||||
|
||||
[node name="ListStack" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FishEmpty" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin/ListStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "your cooler is empty."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="FishList" type="ItemList" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin/ListStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
fixed_icon_size = Vector2i(48, 36)
|
||||
icon_mode = 1
|
||||
select_mode = 1
|
||||
same_column_width = true
|
||||
|
||||
[node name="FishDetail" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody"]
|
||||
custom_minimum_size = Vector2(185, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DetailMargin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 9
|
||||
theme_override_constants/margin_top = 9
|
||||
theme_override_constants/margin_right = 9
|
||||
theme_override_constants/margin_bottom = 9
|
||||
|
||||
[node name="DetailStack" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="FishTexture" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(160, 90)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
texture_filter = 1
|
||||
|
||||
[node name="FishName" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "select a fish"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 29
|
||||
|
||||
[node name="FishDetails" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "choose one individual fish from your cooler."
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="SelectionSummary" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "no fish selected"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
|
||||
[node name="SellButton" type="Button" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "sell selected"
|
||||
|
||||
[node name="Upgrades" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
custom_minimum_size = Vector2(270, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="UpgradeTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
||||
layout_mode = 2
|
||||
text = "fishing upgrades"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_font_sizes/font_size = 23
|
||||
|
||||
[node name="Supplies" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
custom_minimum_size = Vector2(210, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"]
|
||||
layout_mode = 2
|
||||
text = "supplies"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_font_sizes/font_size = 23
|
||||
|
||||
[node name="Scroll" type="ScrollContainer" parent="ShopPanel/Margin/Layout/Body/Supplies"]
|
||||
layout_mode = 2
|
||||
|
|
@ -233,16 +187,16 @@ layout_mode = 2
|
|||
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 9
|
||||
theme_override_constants/margin_top = 9
|
||||
theme_override_constants/margin_top = 7
|
||||
theme_override_constants/margin_right = 9
|
||||
theme_override_constants/margin_bottom = 9
|
||||
theme_override_constants/margin_bottom = 7
|
||||
|
||||
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 9
|
||||
|
||||
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
|
||||
custom_minimum_size = Vector2(48, 48)
|
||||
custom_minimum_size = Vector2(44, 44)
|
||||
layout_mode = 2
|
||||
texture = ExtResource("3_reel")
|
||||
expand_mode = 1
|
||||
|
|
@ -256,22 +210,25 @@ size_flags_horizontal = 3
|
|||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
layout_mode = 2
|
||||
text = "reel speed"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_font_sizes/font_size = 22
|
||||
|
||||
[node name="ReelLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "level 0"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="ReelEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "1.00× → 1.10×"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="ReelCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "$50"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="ReelPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -284,16 +241,16 @@ layout_mode = 2
|
|||
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 9
|
||||
theme_override_constants/margin_top = 9
|
||||
theme_override_constants/margin_top = 7
|
||||
theme_override_constants/margin_right = 9
|
||||
theme_override_constants/margin_bottom = 9
|
||||
theme_override_constants/margin_bottom = 7
|
||||
|
||||
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 9
|
||||
|
||||
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
|
||||
custom_minimum_size = Vector2(48, 48)
|
||||
custom_minimum_size = Vector2(44, 44)
|
||||
layout_mode = 2
|
||||
texture = ExtResource("4_barrier")
|
||||
expand_mode = 1
|
||||
|
|
@ -307,22 +264,25 @@ size_flags_horizontal = 3
|
|||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
||||
layout_mode = 2
|
||||
text = "barrier power"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_font_sizes/font_size = 22
|
||||
|
||||
[node name="BarrierLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "level 0"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="BarrierEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "1 damage → 2 damage"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="BarrierCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "$100"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="BarrierPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -335,16 +295,16 @@ layout_mode = 2
|
|||
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 9
|
||||
theme_override_constants/margin_top = 9
|
||||
theme_override_constants/margin_top = 7
|
||||
theme_override_constants/margin_right = 9
|
||||
theme_override_constants/margin_bottom = 9
|
||||
theme_override_constants/margin_bottom = 7
|
||||
|
||||
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 9
|
||||
|
||||
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
|
||||
custom_minimum_size = Vector2(48, 48)
|
||||
custom_minimum_size = Vector2(44, 44)
|
||||
layout_mode = 2
|
||||
texture = ExtResource("6_cooler")
|
||||
expand_mode = 1
|
||||
|
|
@ -358,74 +318,27 @@ size_flags_horizontal = 3
|
|||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
||||
layout_mode = 2
|
||||
text = "cooler capacity"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_font_sizes/font_size = 22
|
||||
|
||||
[node name="CoolerLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "level 0"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="CoolerEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "12 → 18 fish"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="CoolerCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "$75"
|
||||
theme_override_font_sizes/font_size = 18
|
||||
|
||||
[node name="CoolerPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "purchase"
|
||||
|
||||
[node name="SaleConfirmation" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 10
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -320.0
|
||||
offset_top = -150.0
|
||||
offset_right = 320.0
|
||||
offset_bottom = 150.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="SaleConfirmation"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 18
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_right = 18
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="Stack" type="VBoxContainer" parent="SaleConfirmation/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="ConfirmationText" type="Label" parent="SaleConfirmation/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
autowrap_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Buttons" type="HBoxContainer" parent="SaleConfirmation/Margin/Stack"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="ConfirmSale" type="Button" parent="SaleConfirmation/Margin/Stack/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "sell fish"
|
||||
theme_type_variation = &"DangerButton"
|
||||
|
||||
[node name="CancelSale" type="Button" parent="SaleConfirmation/Margin/Stack/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "cancel"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ signal crisp_reset_focus_requested
|
|||
signal interactive_pointer_ui_changed(is_open: bool)
|
||||
signal passive_pointer_ui_changed(is_enabled: bool)
|
||||
signal player_menu_backdrop_visibility_changed(is_visible: bool)
|
||||
signal shop_backdrop_visibility_changed(is_visible: bool)
|
||||
|
||||
@onready var _status_label: Label = %StatusLabel
|
||||
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
|
||||
|
|
@ -65,7 +66,7 @@ signal player_menu_backdrop_visibility_changed(is_visible: bool)
|
|||
$UIRoot/TitleScreen/ResponsiveTitleStage/TitlePresentationScaleRoot/SettingsPanel
|
||||
)
|
||||
@onready var _pause_settings_panel: SettingsPanelType = (
|
||||
$UIRoot/PauseMenu/ResponsivePauseStage/PausePresentationScaleRoot/SettingsCenter/SettingsPanel
|
||||
$UIRoot/CanonicalStage/PauseMenu/ResponsivePauseStage/PausePresentationScaleRoot/SettingsCenter/SettingsPanel
|
||||
)
|
||||
|
||||
var _showcase_active: bool = false
|
||||
|
|
@ -77,6 +78,8 @@ var _shop_open: bool = false
|
|||
var _chat_input_open: bool = false
|
||||
var _player_menu_hotbar_visible: bool = false
|
||||
var _item_effects: PlayerItemEffectsType
|
||||
var _main_shop_buyer: FishBuyerProfileType
|
||||
var _shop_interaction: ShopInteractionType
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -151,6 +154,9 @@ func setup(
|
|||
_player_menu.inventory_hotbar_context_changed.connect(
|
||||
_on_inventory_hotbar_context_changed
|
||||
)
|
||||
_player_menu.shop_cooler_modal_changed.connect(
|
||||
_on_shop_cooler_modal_changed
|
||||
)
|
||||
_player_menu.setup(
|
||||
player,
|
||||
inventory,
|
||||
|
|
@ -174,9 +180,7 @@ func setup(
|
|||
_hotbar_ui.setup(hotbar, bag, item_catalog, fishing_spot)
|
||||
_fishing_shop.setup(
|
||||
player,
|
||||
inventory,
|
||||
wallet,
|
||||
sale_service,
|
||||
main_shop_buyer,
|
||||
fishing_upgrades,
|
||||
fishing_spot,
|
||||
|
|
@ -184,10 +188,18 @@ func setup(
|
|||
bag,
|
||||
item_catalog,
|
||||
cooler_capacity,
|
||||
network_session,
|
||||
network_shop_service
|
||||
network_shop_service,
|
||||
)
|
||||
_fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed)
|
||||
_fishing_shop.sell_fish_requested.connect(_on_shop_sell_fish_requested)
|
||||
_fishing_shop.shop_cooler_return_requested.connect(
|
||||
_on_shop_cooler_return_requested
|
||||
)
|
||||
_fishing_shop.shop_cooler_confirmation_cancel_requested.connect(
|
||||
_player_menu.cancel_shop_cooler_confirmation
|
||||
)
|
||||
_main_shop_buyer = main_shop_buyer
|
||||
_shop_interaction = shop_interaction
|
||||
|
||||
|
||||
func setup_data_and_identity(
|
||||
|
|
@ -554,6 +566,9 @@ func _on_player_menu_exit_started() -> void:
|
|||
|
||||
func _on_shop_visibility_changed(is_open: bool) -> void:
|
||||
_shop_open = is_open
|
||||
shop_backdrop_visibility_changed.emit(is_open)
|
||||
if not is_open and _player_menu.is_shop_cooler_mounted():
|
||||
_player_menu.unmount_shop_cooler()
|
||||
_refresh_chat_availability()
|
||||
_refresh_hotbar_visibility()
|
||||
_hotbar_ui.set_gameplay_input_enabled(
|
||||
|
|
@ -567,6 +582,33 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
|
|||
_emit_interactive_pointer_ui_changed()
|
||||
|
||||
|
||||
func _on_shop_sell_fish_requested() -> void:
|
||||
if (
|
||||
not _fishing_shop.visible
|
||||
or _main_shop_buyer == null
|
||||
or not _main_shop_buyer.is_valid()
|
||||
or _shop_interaction == null
|
||||
or not _shop_interaction.is_local_player_in_range()
|
||||
or _fishing_spot == null
|
||||
or not _fishing_spot.is_ready_for_shop_transaction()
|
||||
):
|
||||
return
|
||||
if _player_menu.mount_shop_cooler(
|
||||
_fishing_shop.get_shop_cooler_mount(),
|
||||
_main_shop_buyer,
|
||||
):
|
||||
_fishing_shop.activate_shop_cooler_page()
|
||||
|
||||
|
||||
func _on_shop_cooler_return_requested() -> void:
|
||||
_player_menu.unmount_shop_cooler()
|
||||
_fishing_shop.deactivate_shop_cooler_page()
|
||||
|
||||
|
||||
func _on_shop_cooler_modal_changed(is_open: bool) -> void:
|
||||
_fishing_shop.set_shop_cooler_modal_open(is_open)
|
||||
|
||||
|
||||
func _on_inventory_hotbar_context_changed(show_hotbar: bool) -> void:
|
||||
var context_changed: bool = _player_menu_hotbar_visible != show_hotbar
|
||||
_player_menu_hotbar_visible = show_hotbar
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ script = ExtResource("1_ui")
|
|||
unique_name_in_owner = true
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="GameplayTransientHUD" type="Control" parent="UIRoot"]
|
||||
[node name="CanonicalStage" type="Control" parent="UIRoot"]
|
||||
unique_name_in_owner = true
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="GameplayTransientHUD" type="Control" parent="UIRoot/CanonicalStage"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
|
|
@ -51,7 +55,7 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="FishingPanel" type="PanelContainer" parent="UIRoot/GameplayTransientHUD"]
|
||||
[node name="FishingPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 60
|
||||
|
|
@ -69,33 +73,33 @@ grow_vertical = 0
|
|||
mouse_filter = 2
|
||||
theme = ExtResource("3_theme")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="UIRoot/GameplayTransientHUD/FishingPanel"]
|
||||
[node name="MarginContainer" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel"]
|
||||
theme_override_constants/margin_left = 14
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 14
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer"]
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="StatusLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
text = ""
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 16
|
||||
|
||||
[node name="ShowcaseDetails" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="ShowcaseDetails" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CatchTrack" type="Control" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="CatchTrack" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
|
||||
[node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
[node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
|
|
@ -109,7 +113,7 @@ theme_override_styles/fill = SubResource("StyleBox_catch_fill")
|
|||
value = 0.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
[node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
|
|
@ -123,7 +127,7 @@ theme_override_styles/fill = SubResource("StyleBox_chase_fill")
|
|||
value = 0.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="BarrierMarkers" type="Control" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
[node name="BarrierMarkers" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
|
|
@ -133,23 +137,23 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="BarrierSummary" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="BarrierSummary" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BarrierHealth" type="Label" parent="UIRoot/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
[node name="BarrierHealth" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="PlayerMenu" parent="UIRoot" instance=ExtResource("2_menu")]
|
||||
[node name="PlayerMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("2_menu")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="Hotbar" parent="UIRoot" instance=ExtResource("7_hotbar")]
|
||||
[node name="Hotbar" parent="UIRoot/CanonicalStage" instance=ExtResource("7_hotbar")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/GameplayTransientHUD"]
|
||||
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 55
|
||||
|
|
@ -167,22 +171,22 @@ grow_vertical = 0
|
|||
mouse_filter = 2
|
||||
theme = ExtResource("3_theme")
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="UIRoot/GameplayTransientHUD/ShopPrompt"]
|
||||
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 7
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 7
|
||||
|
||||
[node name="Label" type="Label" parent="UIRoot/GameplayTransientHUD/ShopPrompt/Margin"]
|
||||
[node name="Label" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/Margin"]
|
||||
layout_mode = 2
|
||||
text = "press e to open fishing shop"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="FishingShop" parent="UIRoot" instance=ExtResource("8_shop")]
|
||||
[node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="EffectStatus" type="Label" parent="UIRoot/GameplayTransientHUD"]
|
||||
[node name="EffectStatus" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 54
|
||||
|
|
@ -223,5 +227,5 @@ script = ExtResource("4_fade")
|
|||
[node name="TitleScreen" parent="UIRoot" instance=ExtResource("5_title")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="PauseMenu" parent="UIRoot" instance=ExtResource("6_pause")]
|
||||
[node name="PauseMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("6_pause")]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
|
|||
const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
|
||||
const COMPACT_HEIGHT_THRESHOLD: float = 560.0
|
||||
const VISIBILITY_FADE_DURATION: float = 0.55
|
||||
const BACKDROP_TARGET_ALPHA: float = 0.68
|
||||
const PlayerType = preload("res://player/player.gd")
|
||||
const SaveManagerType = preload("res://save/player_save_manager.gd")
|
||||
const SettingsManagerType = preload(
|
||||
|
|
@ -48,7 +47,6 @@ enum CloseReason {
|
|||
@onready var _presentation_scale_root: Control = %PausePresentationScaleRoot
|
||||
@onready var _root_page: SettingsBubblePage = %RootPage
|
||||
@onready var _settings_panel: SettingsPanelType = %SettingsPanel
|
||||
@onready var _dim_background: ColorRect = %DimBackground
|
||||
@onready var _transition_flurry: BubbleTransitionFlurry = (
|
||||
%TransitionBubbleLayer
|
||||
)
|
||||
|
|
@ -75,8 +73,6 @@ var _action_in_progress: bool = false
|
|||
var _root_transition_active: bool = false
|
||||
var _root_transition_generation: int = 0
|
||||
var _closing_menu: bool = false
|
||||
var _backdrop_fade: Tween
|
||||
var _backdrop_fade_generation: int = 0
|
||||
var _pending_join_endpoint: String = ""
|
||||
|
||||
|
||||
|
|
@ -98,7 +94,6 @@ func _ready() -> void:
|
|||
)
|
||||
_join_game_page.join_requested.connect(_on_join_game_requested)
|
||||
_join_game_page.back_requested.connect(_close_join_game)
|
||||
_dim_background.color.a = 0.0
|
||||
_confirmation_page.hide_page()
|
||||
resized.connect(_update_responsive_pause_stage)
|
||||
call_deferred("_update_responsive_pause_stage")
|
||||
|
|
@ -144,7 +139,6 @@ func open_menu() -> void:
|
|||
_action_in_progress = false
|
||||
_root_page.hide_page()
|
||||
show()
|
||||
_fade_backdrop(true)
|
||||
_update_responsive_pause_stage()
|
||||
_begin_root_entry(false)
|
||||
menu_visibility_changed.emit(true)
|
||||
|
|
@ -159,7 +153,6 @@ func resume() -> void:
|
|||
or _confirmation_action != ConfirmationAction.NONE
|
||||
):
|
||||
return
|
||||
_fade_backdrop(false)
|
||||
_begin_root_exit(_finish_user_close)
|
||||
|
||||
|
||||
|
|
@ -268,7 +261,6 @@ func _finish_open_settings() -> void:
|
|||
_settings_panel.open_panel(
|
||||
_settings_manager,
|
||||
SettingsPanelType.PresentationMode.GAMEPLAY_MODAL,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
|
|
@ -493,9 +485,6 @@ func _finish_close(reason: CloseReason, restore_controls: bool) -> void:
|
|||
_closing_menu = false
|
||||
_root_transition_generation += 1
|
||||
_root_transition_active = false
|
||||
_cancel_backdrop_fade()
|
||||
_dim_background.color.a = 0.0
|
||||
_dim_background.hide()
|
||||
_settings_panel.hide()
|
||||
_join_game_page.close_page()
|
||||
_root_page.hide_page()
|
||||
|
|
@ -519,45 +508,6 @@ func _emit_transition_flurry() -> void:
|
|||
_transition_flurry.emit_flurry()
|
||||
|
||||
|
||||
func _fade_backdrop(fade_in: bool) -> void:
|
||||
_backdrop_fade_generation += 1
|
||||
_cancel_backdrop_fade()
|
||||
_dim_background.show()
|
||||
var target_alpha: float = BACKDROP_TARGET_ALPHA if fade_in else 0.0
|
||||
if is_equal_approx(_dim_background.color.a, target_alpha):
|
||||
_finish_backdrop_fade(_backdrop_fade_generation, fade_in)
|
||||
return
|
||||
var generation: int = _backdrop_fade_generation
|
||||
_backdrop_fade = create_tween()
|
||||
_backdrop_fade.tween_property(
|
||||
_dim_background,
|
||||
"color:a",
|
||||
target_alpha,
|
||||
VISIBILITY_FADE_DURATION
|
||||
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
|
||||
_backdrop_fade.finished.connect(
|
||||
_finish_backdrop_fade.bind(generation, fade_in),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
|
||||
|
||||
func _finish_backdrop_fade(generation: int, faded_in: bool) -> void:
|
||||
if generation != _backdrop_fade_generation:
|
||||
return
|
||||
_backdrop_fade = null
|
||||
_dim_background.color.a = (
|
||||
BACKDROP_TARGET_ALPHA if faded_in else 0.0
|
||||
)
|
||||
if not faded_in:
|
||||
_dim_background.hide()
|
||||
|
||||
|
||||
func _cancel_backdrop_fade() -> void:
|
||||
if _backdrop_fade != null:
|
||||
_backdrop_fade.kill()
|
||||
_backdrop_fade = null
|
||||
|
||||
|
||||
func _update_responsive_pause_stage() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -25,17 +25,6 @@ mouse_filter = 0
|
|||
theme = ExtResource("3_theme")
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="DimBackground" type="ColorRect" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
color = Color(0.025, 0.12, 0.19, 0.68)
|
||||
|
||||
[node name="TransitionBubbleLayer" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
z_index = 1
|
||||
|
|
|
|||
|
|
@ -79,10 +79,12 @@ const COOLER_RARITY_UNCOMMON := Color("64c87c")
|
|||
const COOLER_RARITY_RARE := Color("6098dd")
|
||||
const COOLER_RARITY_EPIC := Color("a979cf")
|
||||
const COOLER_RARITY_LEGENDARY := Color("db78a7")
|
||||
const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
signal inventory_hotbar_context_changed(show_hotbar: bool)
|
||||
signal menu_exit_started
|
||||
signal shop_cooler_modal_changed(is_open: bool)
|
||||
|
||||
enum Section {
|
||||
COOLER,
|
||||
|
|
@ -131,6 +133,7 @@ const INVENTORY_HEADER_INSET := Vector2(24.0, 20.0)
|
|||
const INVENTORY_HEADER_HEIGHT := 40.0
|
||||
const INVENTORY_MAIN_CORNER_RADIUS: int = 58
|
||||
const INVENTORY_INNER_CORNER_RADIUS: int = 45
|
||||
const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||
|
||||
@onready var _navigation_cluster: BubbleClusterType = %NavigationCluster
|
||||
@onready var _presentation_scale_root: Control = %PlayerMenuPresentationScaleRoot
|
||||
|
|
@ -276,6 +279,12 @@ var _reservations: PlayerAssetReservationService
|
|||
var _network_profile_service: NetworkProfileService
|
||||
var _network_player_list: NetworkPlayerListService
|
||||
var _default_buyer: FishBuyerProfileType
|
||||
var _sale_buyer_override: FishBuyerProfileType
|
||||
var _shop_cooler_context_active: bool = false
|
||||
var _cooler_original_parent: Node
|
||||
var _cooler_original_index: int = -1
|
||||
var _confirmation_original_parent: Node
|
||||
var _confirmation_original_index: int = -1
|
||||
var _catalog: FishPoolType
|
||||
var _fishing_spot: FishingSpotType
|
||||
var _bag: PlayerBagType
|
||||
|
|
@ -336,6 +345,10 @@ var _logbook_page_tween: Tween
|
|||
|
||||
func _ready() -> void:
|
||||
_configure_inventory_transition_group()
|
||||
_cooler_original_parent = _cooler_page.get_parent()
|
||||
_cooler_original_index = _cooler_page.get_index()
|
||||
_confirmation_original_parent = _sale_confirmation.get_parent()
|
||||
_confirmation_original_index = _sale_confirmation.get_index()
|
||||
_inventory_sub_tabs.move_child(_tackle_sub_tab, 1)
|
||||
_inventory_sub_tabs.move_child(_bag_sub_tab, 2)
|
||||
_inventory_tab.pressed.connect(_show_last_inventory_section)
|
||||
|
|
@ -652,6 +665,7 @@ func consume_escape() -> bool:
|
|||
func open_menu() -> void:
|
||||
if (
|
||||
visible
|
||||
or _shop_cooler_context_active
|
||||
or _player == null
|
||||
or _fishing_spot == null
|
||||
or not _fishing_spot.can_open_player_menu()
|
||||
|
|
@ -678,6 +692,99 @@ func open_menu() -> void:
|
|||
menu_visibility_changed.emit(true)
|
||||
|
||||
|
||||
func mount_shop_cooler(
|
||||
host: Control,
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> bool:
|
||||
if (
|
||||
visible
|
||||
or _shop_cooler_context_active
|
||||
or host == null
|
||||
or not is_instance_valid(host)
|
||||
or buyer == null
|
||||
or not buyer.is_valid()
|
||||
or buyer.id != MAIN_SHOP_BUYER_ID
|
||||
):
|
||||
return false
|
||||
_menu_generation += 1
|
||||
_cancel_page_tween()
|
||||
_transitioning = false
|
||||
_page_transitioning = false
|
||||
_sale_buyer_override = buyer
|
||||
_shop_cooler_context_active = true
|
||||
_current_section = Section.COOLER
|
||||
_last_inventory_section = Section.COOLER
|
||||
_cooler_page.reparent(host, false)
|
||||
_sale_confirmation.reparent(host, false)
|
||||
_cooler_page.position = Vector2.ZERO
|
||||
_cooler_page.size = DESKTOP_REFERENCE_SIZE
|
||||
_cooler_page.scale = Vector2.ONE
|
||||
_cooler_page.modulate = Color.WHITE
|
||||
_cooler_page.visible = true
|
||||
_sale_confirmation.visible = false
|
||||
_update_shell_layout()
|
||||
_refresh_inventory()
|
||||
_set_content_interactive(true)
|
||||
_update_cooler_water_mask()
|
||||
set_process(true)
|
||||
call_deferred("_focus_shop_cooler")
|
||||
return true
|
||||
|
||||
|
||||
func unmount_shop_cooler() -> void:
|
||||
if not _shop_cooler_context_active:
|
||||
return
|
||||
_close_sale_confirmation()
|
||||
_set_content_interactive(false)
|
||||
_cooler_page.visible = false
|
||||
_cooler_page.reparent(_cooler_original_parent, false)
|
||||
_cooler_original_parent.move_child(
|
||||
_cooler_page,
|
||||
mini(_cooler_original_index, _cooler_original_parent.get_child_count() - 1),
|
||||
)
|
||||
_sale_confirmation.reparent(_confirmation_original_parent, false)
|
||||
_confirmation_original_parent.move_child(
|
||||
_sale_confirmation,
|
||||
mini(
|
||||
_confirmation_original_index,
|
||||
_confirmation_original_parent.get_child_count() - 1,
|
||||
),
|
||||
)
|
||||
_sale_buyer_override = null
|
||||
_shop_cooler_context_active = false
|
||||
_menu_generation += 1
|
||||
if not visible:
|
||||
set_process(false)
|
||||
|
||||
|
||||
func is_shop_cooler_mounted() -> bool:
|
||||
return _shop_cooler_context_active
|
||||
|
||||
|
||||
func cancel_shop_cooler_confirmation() -> void:
|
||||
if _shop_cooler_context_active and _sale_confirmation.visible:
|
||||
_close_sale_confirmation()
|
||||
|
||||
|
||||
func _focus_shop_cooler() -> void:
|
||||
if not _shop_cooler_context_active or not _cooler_page.is_visible_in_tree():
|
||||
return
|
||||
var focused_node := _fish_nodes.get(
|
||||
_fish_selection.get_focused_id()
|
||||
) as CoolerFishSpriteType
|
||||
if focused_node != null and focused_node.focus_mode != Control.FOCUS_NONE:
|
||||
focused_node.grab_focus()
|
||||
return
|
||||
for fish_catch: FishCatchType in _sorted_catches:
|
||||
var fish_node := _fish_nodes.get(
|
||||
fish_catch.catch_id
|
||||
) as CoolerFishSpriteType
|
||||
if fish_node != null and fish_node.focus_mode != Control.FOCUS_NONE:
|
||||
fish_node.grab_focus()
|
||||
return
|
||||
_cooler_sort_option.grab_focus()
|
||||
|
||||
|
||||
func close_menu(
|
||||
reason: CloseReason = CloseReason.USER,
|
||||
restore_controls: bool = true,
|
||||
|
|
@ -886,6 +993,9 @@ func _show_last_inventory_section() -> void:
|
|||
|
||||
|
||||
func _focus_current_section() -> void:
|
||||
if _shop_cooler_context_active:
|
||||
_focus_shop_cooler()
|
||||
return
|
||||
if _current_section == Section.COOLER:
|
||||
_cooler_sub_tab.grab_focus()
|
||||
elif _current_section == Section.BAG:
|
||||
|
|
@ -903,12 +1013,15 @@ func _focus_current_section() -> void:
|
|||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if visible:
|
||||
if visible or _shop_cooler_context_active:
|
||||
_motion_elapsed += delta
|
||||
if visible:
|
||||
_navigation_cluster.advance_motion(delta)
|
||||
_apply_navigation_selection_presentation()
|
||||
for fish_node: CoolerFishSpriteType in _fish_nodes.values():
|
||||
fish_node.advance_presentation(delta, _motion_elapsed)
|
||||
if not visible:
|
||||
return
|
||||
var bag_motion_enabled: bool = (
|
||||
not _bag_drag_active and not get_viewport().gui_is_dragging()
|
||||
)
|
||||
|
|
@ -1040,6 +1153,20 @@ func _apply_inventory_styles() -> void:
|
|||
"font_color",
|
||||
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
|
||||
)
|
||||
_sale_confirmation.add_theme_stylebox_override(
|
||||
"panel",
|
||||
UtilityPageStyle.panel_style(),
|
||||
)
|
||||
_confirmation_message.add_theme_font_override(
|
||||
"font",
|
||||
UtilityPageStyle.TuffyFont,
|
||||
)
|
||||
_confirmation_message.add_theme_color_override(
|
||||
"font_color",
|
||||
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
|
||||
)
|
||||
UtilityPageStyle.apply_ocean_button(_confirm_sale_button)
|
||||
UtilityPageStyle.apply_ocean_button(_cancel_sale_button)
|
||||
|
||||
|
||||
func _set_bag_view(view: BagView) -> void:
|
||||
|
|
@ -1531,11 +1658,9 @@ func _update_shell_layout() -> void:
|
|||
_players_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
_players_page.position = Vector2.ZERO
|
||||
_players_page.size = reference_size
|
||||
_sale_confirmation.size = SALE_CONFIRMATION_SIZE
|
||||
_sale_confirmation.position = (
|
||||
Vector2(70.0, 128.0) if compact else Vector2(320.0, 200.0)
|
||||
)
|
||||
_sale_confirmation.size = (
|
||||
Vector2(500.0, 250.0) if compact else Vector2(640.0, 320.0)
|
||||
(reference_size - SALE_CONFIRMATION_SIZE) * 0.5
|
||||
)
|
||||
_cooler_host.custom_minimum_size = Vector2(
|
||||
520.0 if compact else 800.0,
|
||||
|
|
@ -2592,8 +2717,15 @@ func _configure_cooler_fish_focus() -> void:
|
|||
controls.append(fish_node)
|
||||
var columns: int = 3 if _compact_layout else 9
|
||||
if controls.is_empty():
|
||||
if not _shop_cooler_context_active:
|
||||
_cooler_sub_tab.focus_neighbor_bottom = NodePath()
|
||||
return
|
||||
var top_control: Control = (
|
||||
_cooler_sort_option
|
||||
if _shop_cooler_context_active
|
||||
else _cooler_sub_tab
|
||||
)
|
||||
if not _shop_cooler_context_active:
|
||||
_cooler_sub_tab.focus_neighbor_bottom = _cooler_sub_tab.get_path_to(
|
||||
controls.front()
|
||||
)
|
||||
|
|
@ -2606,7 +2738,7 @@ func _configure_cooler_fish_focus() -> void:
|
|||
controls[mini(index + 1, controls.size() - 1)]
|
||||
)
|
||||
control.focus_neighbor_top = (
|
||||
control.get_path_to(_cooler_sub_tab)
|
||||
control.get_path_to(top_control)
|
||||
if index < columns
|
||||
else control.get_path_to(controls[index - columns])
|
||||
)
|
||||
|
|
@ -2776,9 +2908,10 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
_cooler_detail_texture.texture = fish_catch.fish.display_texture
|
||||
_cooler_detail_texture.visible = fish_catch.fish.display_texture != null
|
||||
_cooler_detail_name.text = fish_catch.fish.display_name
|
||||
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
|
||||
var individual_preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch([fish_catch.catch_id], _default_buyer)
|
||||
if _sale_service != null
|
||||
_sale_service.preview_batch([fish_catch.catch_id], active_buyer)
|
||||
if _sale_service != null and active_buyer != null
|
||||
else null
|
||||
)
|
||||
var buyer_offer: int = (
|
||||
|
|
@ -2793,8 +2926,8 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
):
|
||||
_cooler_detail_name.text += " • reserved in mail"
|
||||
_cooler_weight_unit.text = "lb • %s" % fish_catch.fish.get_rarity_name()
|
||||
if buyer_offer >= 0 and _default_buyer != null:
|
||||
_cooler_offer_label.text = "%s offer" % _default_buyer.display_name
|
||||
if buyer_offer >= 0 and active_buyer != null:
|
||||
_cooler_offer_label.text = _get_offer_label(active_buyer)
|
||||
_cooler_offer_value.text = "$%d" % buyer_offer
|
||||
else:
|
||||
_cooler_offer_label.text = "buyer unavailable"
|
||||
|
|
@ -2831,6 +2964,11 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
|
||||
|
||||
func _update_sale_summary() -> void:
|
||||
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
|
||||
var buyer_id: StringName = (
|
||||
active_buyer.id if active_buyer != null else StringName()
|
||||
)
|
||||
var offer_label: String = _get_offer_label(active_buyer)
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
var selected_count: int = selected_ids.size()
|
||||
_sell_button.text = (
|
||||
|
|
@ -2842,7 +2980,7 @@ func _update_sale_summary() -> void:
|
|||
if selected_count == 0:
|
||||
_selection_summary.text = "no fish selected"
|
||||
_selection_status.set_content("selected", "none")
|
||||
_offer_status.set_content("pelican offer", "—")
|
||||
_offer_status.set_content(offer_label, "—")
|
||||
_sell_button.text = "sell fish"
|
||||
_sell_button.disabled = true
|
||||
_sell_bubble.disabled = true
|
||||
|
|
@ -2866,7 +3004,7 @@ func _update_sale_summary() -> void:
|
|||
else "%d fish selected" % selected_count
|
||||
)
|
||||
_selection_status.set_content("selected", str(selected_count))
|
||||
_offer_status.set_content("pelican offer", "pending")
|
||||
_offer_status.set_content(offer_label, "pending")
|
||||
_sell_button.disabled = true
|
||||
_sell_bubble.disabled = true
|
||||
_sale_unavailable.text = "Selling…"
|
||||
|
|
@ -2874,7 +3012,8 @@ func _update_sale_summary() -> void:
|
|||
return
|
||||
if (
|
||||
_network_sale_service == null
|
||||
or not _network_sale_service.can_request_sale()
|
||||
or buyer_id.is_empty()
|
||||
or not _network_sale_service.can_request_sale(buyer_id)
|
||||
):
|
||||
_selection_summary.text = (
|
||||
"1 fish selected"
|
||||
|
|
@ -2882,7 +3021,7 @@ func _update_sale_summary() -> void:
|
|||
else "%d fish selected" % selected_count
|
||||
)
|
||||
_selection_status.set_content("selected", str(selected_count))
|
||||
_offer_status.set_content("pelican offer", "unavailable")
|
||||
_offer_status.set_content(offer_label, "unavailable")
|
||||
_sell_button.disabled = true
|
||||
_sell_bubble.disabled = true
|
||||
_sell_bubble.persistent_mark = false
|
||||
|
|
@ -2893,8 +3032,8 @@ func _update_sale_summary() -> void:
|
|||
_sale_unavailable.visible = true
|
||||
return
|
||||
var preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch(selected_ids, _default_buyer)
|
||||
if _sale_service != null
|
||||
_sale_service.preview_batch(selected_ids, active_buyer)
|
||||
if _sale_service != null and active_buyer != null
|
||||
else null
|
||||
)
|
||||
var count_text: String = (
|
||||
|
|
@ -2907,21 +3046,21 @@ func _update_sale_summary() -> void:
|
|||
if (
|
||||
preview != null
|
||||
and preview.payout >= 0
|
||||
and _default_buyer != null
|
||||
and active_buyer != null
|
||||
and (
|
||||
preview.is_success()
|
||||
or preview.status == FishSaleResultType.Status.FAVORITED
|
||||
)
|
||||
):
|
||||
_selection_summary.text += "\n%s total offer: $%d" % [
|
||||
_default_buyer.display_name,
|
||||
active_buyer.display_name,
|
||||
preview.payout,
|
||||
]
|
||||
_set_cooler_selection_summary(selected_count, preview.payout)
|
||||
_offer_status.set_content("pelican offer", "$%d" % preview.payout)
|
||||
_offer_status.set_content(offer_label, "$%d" % preview.payout)
|
||||
else:
|
||||
_set_cooler_selection_summary(selected_count, -1)
|
||||
_offer_status.set_content("pelican offer", "unavailable")
|
||||
_offer_status.set_content(offer_label, "unavailable")
|
||||
_sell_button.disabled = preview == null or not preview.is_success()
|
||||
_sell_bubble.disabled = _sell_button.disabled
|
||||
_sell_bubble.persistent_mark = false
|
||||
|
|
@ -2962,6 +3101,10 @@ func _on_favorite_pressed() -> void:
|
|||
|
||||
|
||||
func _on_sell_pressed() -> void:
|
||||
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
|
||||
var buyer_id: StringName = (
|
||||
active_buyer.id if active_buyer != null else StringName()
|
||||
)
|
||||
if (
|
||||
_sale_in_progress
|
||||
or (
|
||||
|
|
@ -2973,7 +3116,8 @@ func _on_sell_pressed() -> void:
|
|||
return
|
||||
if (
|
||||
_network_sale_service == null
|
||||
or not _network_sale_service.can_request_sale()
|
||||
or buyer_id.is_empty()
|
||||
or not _network_sale_service.can_request_sale(buyer_id)
|
||||
):
|
||||
_transaction_feedback.text = (
|
||||
"Selling is not supported by this server."
|
||||
|
|
@ -2983,13 +3127,13 @@ func _on_sell_pressed() -> void:
|
|||
if (
|
||||
_inventory == null
|
||||
or _sale_service == null
|
||||
or _default_buyer == null
|
||||
or active_buyer == null
|
||||
or selected_ids.is_empty()
|
||||
):
|
||||
return
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
selected_ids,
|
||||
_default_buyer
|
||||
active_buyer
|
||||
)
|
||||
if not preview.is_success():
|
||||
_transaction_feedback.text = (
|
||||
|
|
@ -3001,8 +3145,8 @@ func _on_sell_pressed() -> void:
|
|||
_refresh_inventory()
|
||||
return
|
||||
_confirmation_catch_ids = selected_ids.duplicate()
|
||||
_confirmation_buyer = _default_buyer
|
||||
_confirmation_buyer_id = _default_buyer.id
|
||||
_confirmation_buyer = active_buyer
|
||||
_confirmation_buyer_id = active_buyer.id
|
||||
_confirmation_generation = _menu_generation
|
||||
var fish_label: String = "fish"
|
||||
_confirmation_message.text = (
|
||||
|
|
@ -3010,12 +3154,14 @@ func _on_sell_pressed() -> void:
|
|||
% [
|
||||
preview.fish_count,
|
||||
fish_label,
|
||||
_get_buyer_display_group(_default_buyer),
|
||||
_get_buyer_display_group(active_buyer),
|
||||
preview.payout,
|
||||
preview.base_value,
|
||||
]
|
||||
)
|
||||
_sale_confirmation.visible = true
|
||||
if _shop_cooler_context_active:
|
||||
shop_cooler_modal_changed.emit(true)
|
||||
_set_shell_interactive(false)
|
||||
for button: Button in [_confirm_sale_button, _cancel_sale_button]:
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
|
|
@ -3028,7 +3174,9 @@ func _on_confirm_sale_pressed() -> void:
|
|||
if (
|
||||
_sale_in_progress
|
||||
or _network_sale_service == null
|
||||
or not _network_sale_service.can_request_sale()
|
||||
or not _network_sale_service.can_request_sale(
|
||||
_confirmation_buyer_id
|
||||
)
|
||||
or _sale_service == null
|
||||
or _confirmation_catch_ids.is_empty()
|
||||
or _confirmation_buyer == null
|
||||
|
|
@ -3040,11 +3188,13 @@ func _on_confirm_sale_pressed() -> void:
|
|||
var requested_catch_ids: Array[StringName] = (
|
||||
_confirmation_catch_ids.duplicate()
|
||||
)
|
||||
var requested_buyer_id: StringName = _confirmation_buyer_id
|
||||
var transaction_generation: int = _menu_generation
|
||||
_confirm_sale_button.disabled = true
|
||||
_close_sale_confirmation()
|
||||
var request_id: String = _network_sale_service.request_local_sale(
|
||||
requested_catch_ids
|
||||
requested_catch_ids,
|
||||
requested_buyer_id,
|
||||
)
|
||||
if request_id.is_empty():
|
||||
_sale_in_progress = false
|
||||
|
|
@ -3052,7 +3202,7 @@ func _on_confirm_sale_pressed() -> void:
|
|||
if (
|
||||
_network_sale_service.is_local_sale_pending()
|
||||
and transaction_generation == _menu_generation
|
||||
and visible
|
||||
and (visible or _shop_cooler_context_active)
|
||||
):
|
||||
_transaction_feedback.text = "Selling…"
|
||||
|
||||
|
|
@ -3066,7 +3216,7 @@ func _can_use_shared_world_actions() -> bool:
|
|||
|
||||
func _on_network_sale_pending(_request_id: String) -> void:
|
||||
_sale_in_progress = true
|
||||
if visible:
|
||||
if visible or _shop_cooler_context_active:
|
||||
_transaction_feedback.text = "Selling…"
|
||||
_update_sale_summary()
|
||||
|
||||
|
|
@ -3081,19 +3231,26 @@ func _on_network_sale_finished(
|
|||
_sale_in_progress = false
|
||||
if accepted:
|
||||
_fish_selection.remove_ids(catch_ids)
|
||||
if not visible:
|
||||
if not visible and not _shop_cooler_context_active:
|
||||
return
|
||||
_transaction_feedback.text = (
|
||||
_refresh_all()
|
||||
var feedback_message: String = (
|
||||
"Sale complete. $%d received." % payout
|
||||
if accepted
|
||||
else message
|
||||
)
|
||||
_refresh_all()
|
||||
_transaction_feedback.text = feedback_message
|
||||
if not accepted:
|
||||
_sale_unavailable.text = feedback_message
|
||||
_sale_unavailable.visible = true
|
||||
if _current_section == Section.COOLER:
|
||||
call_deferred("_restore_inventory_tab_focus")
|
||||
|
||||
|
||||
func _restore_inventory_tab_focus() -> void:
|
||||
if _shop_cooler_context_active:
|
||||
_focus_shop_cooler()
|
||||
return
|
||||
if (
|
||||
visible
|
||||
and _current_section == Section.COOLER
|
||||
|
|
@ -3110,6 +3267,8 @@ func _close_sale_confirmation() -> void:
|
|||
_confirmation_buyer_id = StringName()
|
||||
_confirmation_generation = -1
|
||||
_sale_confirmation.visible = false
|
||||
if was_visible and _shop_cooler_context_active:
|
||||
shop_cooler_modal_changed.emit(false)
|
||||
_confirmation_message.text = ""
|
||||
_confirm_sale_button.disabled = false
|
||||
for button: Button in [_confirm_sale_button, _cancel_sale_button]:
|
||||
|
|
@ -3117,7 +3276,7 @@ func _close_sale_confirmation() -> void:
|
|||
button.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
if (
|
||||
was_visible
|
||||
and visible
|
||||
and (visible or _shop_cooler_context_active)
|
||||
and not _transitioning
|
||||
and not _page_transitioning
|
||||
):
|
||||
|
|
@ -3171,6 +3330,22 @@ func _get_buyer_display_group(
|
|||
return "buyer"
|
||||
|
||||
|
||||
func _get_active_sale_buyer() -> FishBuyerProfileType:
|
||||
if _sale_buyer_override != null and _sale_buyer_override.is_valid():
|
||||
return _sale_buyer_override
|
||||
return _default_buyer
|
||||
|
||||
|
||||
func _get_offer_label(buyer: FishBuyerProfileType) -> String:
|
||||
if buyer == null:
|
||||
return "buyer offer"
|
||||
if buyer.id == MAIN_SHOP_BUYER_ID:
|
||||
return "shop offer"
|
||||
if buyer.id == NetworkSaleService.PELICAN_BUYER_ID:
|
||||
return "pelican offer"
|
||||
return "buyer offer"
|
||||
|
||||
|
||||
func _refresh_logbook() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ mouse_filter = 1
|
|||
|
||||
[node name="BagFilterTabs" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 5
|
||||
z_index = 55
|
||||
layout_mode = 0
|
||||
offset_left = 122.0
|
||||
offset_top = 166.0
|
||||
|
|
@ -1330,12 +1330,12 @@ columns = 4
|
|||
[node name="SaleConfirmation" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
z_index = 40
|
||||
z_index = 120
|
||||
layout_mode = 0
|
||||
offset_left = 320.0
|
||||
offset_top = 200.0
|
||||
offset_right = 960.0
|
||||
offset_bottom = 520.0
|
||||
offset_left = 380.0
|
||||
offset_top = 265.0
|
||||
offset_right = 840.0
|
||||
offset_bottom = 455.0
|
||||
|
||||
[node name="ConfirmationMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation"]
|
||||
layout_mode = 2
|
||||
|
|
@ -1347,29 +1347,31 @@ theme_override_constants/margin_bottom = 20
|
|||
[node name="ConfirmationLayout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
alignment = 1
|
||||
|
||||
[node name="ConfirmationMessage" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 22
|
||||
autowrap_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ConfirmationButtons" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
alignment = 1
|
||||
|
||||
[node name="ConfirmSaleButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "confirm"
|
||||
theme_type_variation = &"DangerButton"
|
||||
custom_minimum_size = Vector2(170, 64)
|
||||
custom_minimum_size = Vector2(140, 44)
|
||||
|
||||
[node name="CancelSaleButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "cancel"
|
||||
custom_minimum_size = Vector2(170, 64)
|
||||
custom_minimum_size = Vector2(140, 44)
|
||||
|
||||
[node name="NavigationCluster" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ enum PresentationMode {
|
|||
GAMEPLAY_MODAL,
|
||||
}
|
||||
|
||||
@onready var _backdrop: ColorRect = $Backdrop
|
||||
@onready var _root_page: SettingsBubblePage = %RootPage
|
||||
@onready var _display_page: SettingsBubblePage = %DisplayPage
|
||||
@onready var _controls_page: SettingsBubblePage = %ControlsPage
|
||||
|
|
@ -210,15 +209,10 @@ func open_panel(
|
|||
settings_manager: SettingsManagerType,
|
||||
presentation_mode: PresentationMode = PresentationMode.GAMEPLAY_MODAL,
|
||||
animate_gameplay_host_transitions: bool = false,
|
||||
uses_external_backdrop: bool = false,
|
||||
) -> void:
|
||||
_cancel_page_transition()
|
||||
_presentation_mode = presentation_mode
|
||||
_animate_gameplay_host_transitions = animate_gameplay_host_transitions
|
||||
_backdrop.visible = (
|
||||
presentation_mode == PresentationMode.GAMEPLAY_MODAL
|
||||
and not uses_external_backdrop
|
||||
)
|
||||
_settings_manager = settings_manager
|
||||
_load_controls()
|
||||
_feedback.text = ""
|
||||
|
|
|
|||
|
|
@ -15,16 +15,6 @@ anchors_preset = 0
|
|||
theme = ExtResource("2_theme")
|
||||
script = ExtResource("1_panel")
|
||||
|
||||
[node name="Backdrop" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
color = Color(0.025, 0.085, 0.115, 0.72)
|
||||
|
||||
[node name="SettingsFeedback" type="Label" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
|
|
|
|||
|
|
@ -1541,12 +1541,12 @@ func _spawn_decorative_fish(generation: int) -> void:
|
|||
CROSSING_DURATION_MIN,
|
||||
CROSSING_DURATION_MAX
|
||||
)
|
||||
fish_control.position = Vector2(
|
||||
-presentation_size.x - FISH_EDGE_MARGIN
|
||||
if direction > 0.0
|
||||
else layer_size.x + FISH_EDGE_MARGIN,
|
||||
base_y
|
||||
var horizontal_bounds: Vector2 = get_decorative_fish_crossing_bounds(
|
||||
layer_size.x,
|
||||
presentation_size.x,
|
||||
direction,
|
||||
)
|
||||
fish_control.position = Vector2(horizontal_bounds.x, base_y)
|
||||
_decorative_fish_layer.add_child(fish_control)
|
||||
_decorative_fish = fish_control
|
||||
_decorative_fish_tween = create_tween()
|
||||
|
|
@ -1586,17 +1586,14 @@ func _update_decorative_fish(
|
|||
or not is_instance_valid(fish_control)
|
||||
):
|
||||
return
|
||||
var start_x: float = (
|
||||
-fish_control.size.x - FISH_EDGE_MARGIN
|
||||
if direction > 0.0
|
||||
else _decorative_fish_layer.size.x + FISH_EDGE_MARGIN
|
||||
var horizontal_bounds: Vector2 = get_decorative_fish_crossing_bounds(
|
||||
_decorative_fish_layer.size.x,
|
||||
fish_control.size.x,
|
||||
direction,
|
||||
)
|
||||
var end_x: float = (
|
||||
_decorative_fish_layer.size.x + FISH_EDGE_MARGIN
|
||||
if direction > 0.0
|
||||
else -fish_control.size.x - FISH_EDGE_MARGIN
|
||||
fish_control.position.x = lerpf(
|
||||
horizontal_bounds.x, horizontal_bounds.y, progress
|
||||
)
|
||||
fish_control.position.x = lerpf(start_x, end_x, progress)
|
||||
var bobbed_y: float = (
|
||||
base_y
|
||||
+ sin(progress * crossing_duration * bob_speed) * bob_amplitude
|
||||
|
|
@ -1609,6 +1606,20 @@ func _update_decorative_fish(
|
|||
fish_control.position = _snap_world_preview(fish_control.position)
|
||||
|
||||
|
||||
static func get_decorative_fish_crossing_bounds(
|
||||
layer_width: float,
|
||||
fish_width: float,
|
||||
direction: float,
|
||||
) -> Vector2:
|
||||
var outside_left: float = -fish_width - FISH_EDGE_MARGIN
|
||||
var outside_right: float = layer_width + FISH_EDGE_MARGIN
|
||||
return (
|
||||
Vector2(outside_left, outside_right)
|
||||
if direction > 0.0
|
||||
else Vector2(outside_right, outside_left)
|
||||
)
|
||||
|
||||
|
||||
func _on_decorative_fish_finished(
|
||||
fish_control: TextureRect,
|
||||
generation: int,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ signal effective_pixel_size_changed(
|
|||
|
||||
@onready var _ui_viewport: SubViewport = $UIViewport
|
||||
@onready var _ui_root: Control = $UIViewport/GameUI/UIRoot
|
||||
@onready var _canonical_stage: Control = (
|
||||
$UIViewport/GameUI/UIRoot/CanonicalStage
|
||||
)
|
||||
@onready var _chat_ui: ChatUI = $UIViewport/GameUI/UIRoot/ChatUI
|
||||
@onready var _title_content_stage: Control = (
|
||||
$UIViewport/GameUI/UIRoot/TitleScreen/ResponsiveTitleStage
|
||||
)
|
||||
|
||||
var _requested_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
|
||||
var _effective_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
|
||||
|
|
@ -94,21 +101,29 @@ func _resize_presentation() -> void:
|
|||
float(render_height)
|
||||
/ UIReferencePresentationType.REFERENCE_SIZE.y
|
||||
)
|
||||
var visible_reference_size: Vector2 = (
|
||||
UIReferencePresentationType.get_visible_reference_size(display_size)
|
||||
)
|
||||
var viewport_size := Vector2i(
|
||||
roundi(
|
||||
UIReferencePresentationType.REFERENCE_SIZE.x
|
||||
* render_scale
|
||||
),
|
||||
render_height,
|
||||
roundi(visible_reference_size.x * render_scale),
|
||||
roundi(visible_reference_size.y * render_scale),
|
||||
)
|
||||
set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
position = UIReferencePresentationType.get_offset(display_size)
|
||||
position = Vector2.ZERO
|
||||
size = Vector2(viewport_size)
|
||||
scale = Vector2.ONE * (reference_scale / render_scale)
|
||||
stretch_shrink = 1
|
||||
_ui_root.position = Vector2.ZERO
|
||||
_ui_root.scale = Vector2.ONE * render_scale
|
||||
_ui_root.size = UIReferencePresentationType.REFERENCE_SIZE
|
||||
_ui_root.size = visible_reference_size
|
||||
_canonical_stage.position = (
|
||||
UIReferencePresentationType.get_stage_position(display_size)
|
||||
)
|
||||
_canonical_stage.size = UIReferencePresentationType.REFERENCE_SIZE
|
||||
_title_content_stage.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
_title_content_stage.position = _canonical_stage.position
|
||||
_title_content_stage.size = UIReferencePresentationType.REFERENCE_SIZE
|
||||
_chat_ui.set_output_scale(reference_scale)
|
||||
if (
|
||||
previous_effective_size != _effective_pixel_size
|
||||
or _requested_pixel_size != _effective_pixel_size
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
class_name UIReferencePresentation
|
||||
extends RefCounted
|
||||
|
||||
# All functional UI is authored in this coordinate system and receives one
|
||||
# centered, uniform transform at presentation time. Texture source dimensions
|
||||
# describe sampling quality and aspect ratio; a canonical Control rect owns
|
||||
# every texture's on-screen layout size.
|
||||
#
|
||||
# Edge-docked overlays use the same canonical units and uniform scale, but may
|
||||
# use the extra logical canvas outside the centered stage on non-16:9 windows.
|
||||
# This keeps deliberate window-edge attachment without letting individual
|
||||
# controls calculate monitor- or DPI-specific geometry.
|
||||
const REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
|
||||
|
||||
|
||||
|
|
@ -18,6 +27,14 @@ static func get_offset(display_size: Vector2) -> Vector2:
|
|||
) * 0.5
|
||||
|
||||
|
||||
static func get_visible_reference_size(display_size: Vector2) -> Vector2:
|
||||
return display_size / get_scale(display_size)
|
||||
|
||||
|
||||
static func get_stage_position(display_size: Vector2) -> Vector2:
|
||||
return (get_visible_reference_size(display_size) - REFERENCE_SIZE) * 0.5
|
||||
|
||||
|
||||
static func get_rect(display_size: Vector2) -> Rect2:
|
||||
var presentation_scale: float = get_scale(display_size)
|
||||
return Rect2(
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ no_depth_test = true
|
|||
|
||||
[node name="ConvenienceLabel" type="Label3D" parent="Visual"]
|
||||
position = Vector3(0, 1.65, -1.15)
|
||||
text = "sell nearby • 0.25x"
|
||||
text = "sell anytime • 0.25x"
|
||||
font_size = 20
|
||||
outline_size = 6
|
||||
billboard = 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue