Compare commits

..

No commits in common. "d944367301c936042c6e59dbdebc864619b9093d" and "e791f15e879f8de09d643ff2625687ad5ed9dada" have entirely different histories.

32 changed files with 140 additions and 660 deletions

View file

@ -9,7 +9,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,playtest/*,scripts/*"
export_path="builds/v0.6.7-alpha/windows-x86_64/NETfishing.exe" export_path="builds/v0.6.5-alpha/windows-x86_64/NETfishing.exe"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -35,11 +35,11 @@ application/modify_resources=true
application/icon="res://art/exported/system_icons/netfishing.ico" application/icon="res://art/exported/system_icons/netfishing.ico"
application/console_wrapper_icon="" application/console_wrapper_icon=""
application/icon_interpolation=4 application/icon_interpolation=4
application/file_version="0.6.7.0" application/file_version="0.6.5.0"
application/product_version="0.6.7.0" application/product_version="0.6.5.0"
application/company_name="" application/company_name=""
application/product_name="NETfishing" application/product_name="NETfishing"
application/file_description="NETfishing v0.6.7-alpha" application/file_description="NETfishing v0.6.5-alpha"
application/copyright="" application/copyright=""
application/trademarks="" application/trademarks=""
application/export_angle=0 application/export_angle=0
@ -62,7 +62,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,playtest/*,scripts/*"
export_path="builds/v0.6.7-alpha/linux-arm64/NETfishing.arm64" export_path="builds/v0.6.5-alpha/linux-arm64/NETfishing.arm64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -92,7 +92,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,playtest/*,scripts/*"
export_path="builds/v0.6.7-alpha/macos/NETfishing.zip" export_path="builds/v0.6.5-alpha/macos/NETfishing.zip"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -108,8 +108,8 @@ custom_template/release=""
application/bundle_identifier="io.woofmeow.netfishing" application/bundle_identifier="io.woofmeow.netfishing"
application/icon="res://art/exported/system_icons/netfishing_1024.png" application/icon="res://art/exported/system_icons/netfishing_1024.png"
application/icon_interpolation=0 application/icon_interpolation=0
application/short_version="0.6.7" application/short_version="0.6.4"
application/version="0.6.7" application/version="0.6.4"
application/architecture="universal" application/architecture="universal"
codesign/enable=false codesign/enable=false
notarization/enable=false notarization/enable=false
@ -125,7 +125,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="builds/*,playtest/*,scripts/*,tests/*" exclude_filter="builds/*,playtest/*,scripts/*,tests/*"
export_path="builds/v0.6.7-alpha/server-linux-x86_64/NETfishingServer.x86_64" export_path="builds/v0.6.5-alpha/server-linux-x86_64/NETfishingServer.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -155,7 +155,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,playtest/*,scripts/*"
export_path="builds/v0.6.7-alpha/android/NETfishing.apk" export_path="builds/v0.6.5-alpha/android/NETfishing.apk"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -176,8 +176,8 @@ architectures/armeabi-v7a=false
architectures/arm64-v8a=true architectures/arm64-v8a=true
architectures/x86=false architectures/x86=false
architectures/x86_64=false architectures/x86_64=false
version/code=60700 version/code=60500
version/name="v0.6.7-alpha" version/name="v0.6.5-alpha"
package/unique_name="io.woofmeow.netfishing" package/unique_name="io.woofmeow.netfishing"
package/name="NETfishing" package/name="NETfishing"
package/signed=true package/signed=true
@ -214,7 +214,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="builds/*,playtest/*,scripts/*" exclude_filter="builds/*,playtest/*,scripts/*"
export_path="builds/v0.6.7-alpha/linux-x86_64/NETfishing.x86_64" export_path="builds/v0.6.5-alpha/linux-x86_64/NETfishing.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""

View file

@ -431,7 +431,7 @@ func _draw_line_ribbon(points: PackedVector3Array) -> void:
return return
var half_thickness: float = line_thickness * 0.5 var half_thickness: float = line_thickness * 0.5
var ribbon_vertices := PackedVector3Array() _line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
for point_index: int in range(1, points.size()): for point_index: int in range(1, points.size()):
var start: Vector3 = points[point_index - 1] var start: Vector3 = points[point_index - 1]
var end: Vector3 = points[point_index] var end: Vector3 = points[point_index]
@ -452,17 +452,12 @@ func _draw_line_ribbon(points: PackedVector3Array) -> void:
var start_right: Vector3 = to_local(start + side) var start_right: Vector3 = to_local(start + side)
var end_right: Vector3 = to_local(end + side) var end_right: Vector3 = to_local(end + side)
var end_left: Vector3 = to_local(end - side) var end_left: Vector3 = to_local(end - side)
ribbon_vertices.append(start_left) _line_mesh.surface_add_vertex(start_left)
ribbon_vertices.append(start_right) _line_mesh.surface_add_vertex(start_right)
ribbon_vertices.append(end_right) _line_mesh.surface_add_vertex(end_right)
ribbon_vertices.append(start_left) _line_mesh.surface_add_vertex(start_left)
ribbon_vertices.append(end_right) _line_mesh.surface_add_vertex(end_right)
ribbon_vertices.append(end_left) _line_mesh.surface_add_vertex(end_left)
if ribbon_vertices.is_empty():
return
_line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
for vertex: Vector3 in ribbon_vertices:
_line_mesh.surface_add_vertex(vertex)
_line_mesh.surface_end() _line_mesh.surface_end()

View file

@ -865,46 +865,12 @@ func _parse_response_dictionary(body: PackedByteArray) -> Dictionary:
func _request_failure(response: Dictionary) -> String: func _request_failure(response: Dictionary) -> String:
var error: Variant = response.get("error", {}) var error: Variant = response.get("error", {})
if typeof(error) == TYPE_DICTIONARY: if typeof(error) == TYPE_DICTIONARY:
var details := error as Dictionary var message: String = str((error as Dictionary).get("message", "")).strip_edges()
if str(details.get("code", "")) == "game_version_mismatch":
var required_version: String = str(
details.get("required_game_version", "")
).strip_edges()
if not required_version.is_empty():
return _discovery_version_mismatch_message(required_version)
var message: String = str(details.get("message", "")).strip_edges()
if not message.is_empty(): if not message.is_empty():
return message return message
return "Room discovery is temporarily unavailable." return "Room discovery is temporarily unavailable."
func _discovery_version_mismatch_message(required_version: String) -> String:
var local_version: String = NetworkProtocol.game_version()
var rejection: NetworkProtocol.RejectionCode = (
NetworkProtocol.game_version_rejection(
local_version,
required_version,
)
)
match rejection:
NetworkProtocol.RejectionCode.CLIENT_OUTDATED:
return (
"Your NETfishing version is out of date. Update to %s to "
+ "enable public discovery. This room will not be listed "
+ "until you update."
) % required_version
NetworkProtocol.RejectionCode.SERVER_OUTDATED:
return (
"Public discovery is still on NETfishing %s. This room "
+ "will not be listed until discovery is updated for %s."
) % [required_version, local_version]
_:
return (
"Public discovery requires NETfishing %s. This room will "
+ "not be listed until both versions match."
) % required_version
func _on_session_state_changed(state: NetworkSession.State) -> void: func _on_session_state_changed(state: NetworkSession.State) -> void:
if state == NetworkSession.State.CONNECTING and not _pending_join_token.is_empty(): if state == NetworkSession.State.CONNECTING and not _pending_join_token.is_empty():
_set_public_join_state(PublicJoinState.CONNECTING) _set_public_join_state(PublicJoinState.CONNECTING)

View file

@ -1,13 +1,12 @@
NETfishing NETfishing
v0.6.7-alpha v0.6.5-alpha
Alpha 0.6.7 Alpha 0.6.5
Thank you for trying this early private playtest. Thank you for trying this early private playtest.
NETfishing is an early multiplayer fishing game. This alpha includes local NETfishing is currently a local gameplay prototype for a future
progression, player-hosted and dedicated multiplayer sessions, public room multiplayer-first game. Networking and multiplayer are NOT implemented in
discovery, and direct UDP connections. Expect rough edges and please keep a this build.
backup of any save or identity data you care about.
INSTALLATION INSTALLATION
@ -61,9 +60,6 @@ FISHING
FEATURES TO TRY FEATURES TO TRY
- Hosting a private or public room and joining compatible online sessions
- Direct-IP, saved-server, recent-server, and public-room connection flows
- Text chat, player calls, player lists, relationships, and moderation tools
- Movement, jumping, camera rotation, and zoom - Movement, jumping, camera rotation, and zoom
- Short, medium, and maximum-distance casts - Short, medium, and maximum-distance casts
- Valid water and invalid land casts - Valid water and invalid land casts
@ -105,11 +101,11 @@ Please include:
- How movement, casting, chase pressure, barrier clicking, menus, and camera - How movement, casting, chase pressure, barrier clicking, menus, and camera
controls felt controls felt
KNOWN ALPHA LIMITATIONS KNOWN PRE-ALPHA LIMITATIONS
- Public sessions remain direct connections; restrictive NAT or carrier-grade - Multiplayer/networking is not implemented.
networks may prevent some players from joining. - Fish and character art are temporary.
- Multiplayer, dedicated-server deployment, UI, and balance are still alpha. - UI and balance values are placeholders.
- Some fish and character artwork remains temporary. - There is no audio.
- There is no installer or automatic updater. - There is no installer or automatic updater.
- Windows builds are unsigned. - Windows builds are unsigned.

View file

@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="NETFISHING" config/name="NETFISHING"
config/version="0.6.7-alpha" config/version="0.6.5-alpha"
run/main_scene="res://main/main.tscn" run/main_scene="res://main/main.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility") config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://art/exported/system_icons/netfishing_256.png" config/icon="res://art/exported/system_icons/netfishing_256.png"

View file

@ -4,12 +4,12 @@ set -euo pipefail
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.6.7-alpha" readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.6.5-alpha"
readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64" readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64"
readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64" readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64"
readonly README_SOURCE="${PROJECT_ROOT}/playtest/README-PLAYTEST.txt" readonly README_SOURCE="${PROJECT_ROOT}/playtest/README-PLAYTEST.txt"
readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.6.7-alpha-windows-x86_64.zip" readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.6.5-alpha-windows-x86_64.zip"
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.6.7-alpha-linux-x86_64.zip" readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.6.5-alpha-linux-x86_64.zip"
readonly GODOT_BIN="${GODOT_BIN:-godot}" readonly GODOT_BIN="${GODOT_BIN:-godot}"
if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then

View file

@ -109,7 +109,6 @@ func _run() -> void:
select_button.button_index = JOY_BUTTON_BACK select_button.button_index = JOY_BUTTON_BACK
select_button.pressed = true select_button.pressed = true
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button))) assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
await process_frame
assert(chat_ui.is_open()) assert(chat_ui.is_open())
assert(chat_panel.mouse_filter == Control.MOUSE_FILTER_STOP) assert(chat_panel.mouse_filter == Control.MOUSE_FILTER_STOP)
var typed_chat_entry := chat_ui.find_child( var typed_chat_entry := chat_ui.find_child(
@ -122,8 +121,6 @@ func _run() -> void:
accept_button.pressed = true accept_button.pressed = true
assert(bool(game_ui.call("_handle_controller_chat_controls", accept_button))) assert(bool(game_ui.call("_handle_controller_chat_controls", accept_button)))
assert(typed_chat_entry.virtual_keyboard_enabled) assert(typed_chat_entry.virtual_keyboard_enabled)
await process_frame
assert(typed_chat_entry.has_focus())
var left_bumper := InputEventJoypadButton.new() var left_bumper := InputEventJoypadButton.new()
left_bumper.button_index = JOY_BUTTON_LEFT_SHOULDER left_bumper.button_index = JOY_BUTTON_LEFT_SHOULDER
left_bumper.pressed = true left_bumper.pressed = true
@ -132,8 +129,10 @@ func _run() -> void:
assert(chat_panel.mouse_filter == Control.MOUSE_FILTER_IGNORE) assert(chat_panel.mouse_filter == Control.MOUSE_FILTER_IGNORE)
assert(not typed_chat_entry.virtual_keyboard_enabled) assert(not typed_chat_entry.virtual_keyboard_enabled)
assert(bool(game_ui.call("_handle_controller_chat_controls", left_bumper))) assert(bool(game_ui.call("_handle_controller_chat_controls", left_bumper)))
assert(not chat_ui.is_open()) assert(chat_ui.is_open())
assert(not typed_chat_entry.virtual_keyboard_enabled) assert(not typed_chat_entry.virtual_keyboard_enabled)
assert(bool(game_ui.call("_handle_controller_chat_controls", left_bumper)))
assert(not chat_ui.is_open())
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button))) assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
assert(not chat_ui.is_open()) assert(not chat_ui.is_open())
assert(chat_ui.is_collapsed()) assert(chat_ui.is_collapsed())
@ -316,7 +315,5 @@ func _run() -> void:
var session := main.get_node("%NetworkSession") as NetworkSession var session := main.get_node("%NetworkSession") as NetworkSession
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -39,15 +39,6 @@ func _run() -> void:
standard_button.material != null, standard_button.material != null,
"ordinary controller focus receives inversion", "ordinary controller focus receives inversion",
) )
var pointer_motion := InputEventMouseMotion.new()
pointer_motion.position = Vector2(4.0, 4.0)
presentation._input(pointer_motion)
await process_frame
_expect(
standard_button.material == null,
"mouse motion clears controller focus presentation",
)
presentation._input(controller_event)
authored_selector.grab_focus() authored_selector.grab_focus()
await process_frame await process_frame

View file

@ -23,45 +23,14 @@ func _run() -> void:
var original := _make_button("cute", "cute (show variants)") var original := _make_button("cute", "cute (show variants)")
option_list.add_child(original) option_list.add_child(original)
await process_frame await process_frame
original.grab_focus()
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"programmatic focus stays pending outside navigation mode",
)
var keyboard_navigation := InputEventKey.new()
keyboard_navigation.keycode = KEY_DOWN
keyboard_navigation.pressed = true
recovery._input(keyboard_navigation)
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == original,
"keyboard navigation restores pending initial focus",
)
var menu_shortcut := InputEventKey.new()
menu_shortcut.physical_keycode = KEY_TAB
menu_shortcut.pressed = true
recovery._input(menu_shortcut)
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"the menu shortcut does not leave a pseudo-focused option",
)
var controller_event := InputEventJoypadButton.new() var controller_event := InputEventJoypadButton.new()
controller_event.button_index = JOY_BUTTON_A controller_event.button_index = JOY_BUTTON_A
controller_event.pressed = true controller_event.pressed = true
recovery._input(controller_event) recovery._input(controller_event)
original.grab_focus()
await process_frame await process_frame
await process_frame _expect(root.gui_get_focus_owner() == original, "original option receives focus")
await process_frame
_expect(
root.gui_get_focus_owner() == original,
"controller navigation restores pending initial focus",
)
option_list.remove_child(original) option_list.remove_child(original)
original.queue_free() original.queue_free()
@ -84,63 +53,6 @@ func _run() -> void:
root.gui_get_focus_owner() == explicit_target, root.gui_get_focus_owner() == explicit_target,
"explicit focus changes take precedence over recovery", "explicit focus changes take precedence over recovery",
) )
var pointer_motion := InputEventMouseMotion.new()
pointer_motion.position = Vector2(4.0, 4.0)
recovery._input(pointer_motion)
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"mouse motion releases controller focus",
)
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"mouse motion prevents automatic focus recovery",
)
recovery._input(controller_event)
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == explicit_target,
"controller navigation can resume from pointer mode",
)
var pointer_press := InputEventMouseButton.new()
pointer_press.button_index = MOUSE_BUTTON_LEFT
pointer_press.pressed = true
recovery._input(pointer_press)
await process_frame
_expect(
root.gui_get_focus_owner() == explicit_target,
"mouse-down preserves focus through button activation",
)
var pointer_release := InputEventMouseButton.new()
pointer_release.button_index = MOUSE_BUTTON_LEFT
pointer_release.pressed = false
recovery._input(pointer_release)
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"mouse-up clears the completed button focus",
)
recovery._input(controller_event)
await process_frame
await process_frame
var outside_button := _make_button("outside", "outside")
stage.add_child(outside_button)
option_list.hide()
root.gui_release_focus()
await process_frame
await process_frame
_expect(
root.gui_get_focus_owner() == null,
"focus recovery never escapes a hidden menu scope",
)
option_list.show()
explicit_target.grab_focus()
await process_frame
var leave_world_ui_event := InputEventJoypadButton.new() var leave_world_ui_event := InputEventJoypadButton.new()
leave_world_ui_event.button_index = JOY_BUTTON_LEFT_SHOULDER leave_world_ui_event.button_index = JOY_BUTTON_LEFT_SHOULDER
leave_world_ui_event.pressed = true leave_world_ui_event.pressed = true
@ -152,14 +64,6 @@ func _run() -> void:
root.gui_get_focus_owner() == null, root.gui_get_focus_owner() == null,
"LB intentionally leaving world UI is never recovered", "LB intentionally leaving world UI is never recovered",
) )
var accessibility_only := Control.new()
accessibility_only.focus_mode = Control.FOCUS_ACCESSIBILITY
stage.add_child(accessibility_only)
await process_frame
_expect(
not bool(recovery.call("_is_focusable", accessibility_only)),
"accessibility-only controls are not recovery targets",
)
stage.queue_free() stage.queue_free()
if _failures.is_empty(): if _failures.is_empty():

View file

@ -107,46 +107,6 @@ func _run() -> void:
"0.6.5-beta", "0.6.5-alpha" "0.6.5-beta", "0.6.5-alpha"
) == NetworkProtocol.RejectionCode.SERVER_OUTDATED ) == NetworkProtocol.RejectionCode.SERVER_OUTDATED
) )
var outdated_discovery_error := {
"error": {
"code": "game_version_mismatch",
"message": "generic mismatch",
"required_game_version": "0.6.8-alpha",
},
}
var outdated_message: String = str(
discovery.call("_request_failure", outdated_discovery_error)
)
assert("out of date" in outdated_message)
assert("0.6.8-alpha" in outdated_message)
assert("will not be listed" in outdated_message)
discovery.set(
"_host_request_kind",
DiscoveryClient.HostRequestKind.CREATE,
)
discovery.set("_host_request_in_flight", true)
discovery.call(
"_on_host_request_completed",
HTTPRequest.RESULT_SUCCESS,
HTTPClient.RESPONSE_CONFLICT,
PackedStringArray(),
JSON.stringify(outdated_discovery_error).to_utf8_buffer(),
)
assert(discovery.get_host_state() == DiscoveryClient.HostState.ERROR)
assert(discovery.host_status_is_error())
assert(discovery.get_host_status_message() == outdated_message)
var newer_discovery_error := {
"error": {
"code": "game_version_mismatch",
"message": "generic mismatch",
"required_game_version": "0.6.6-alpha",
},
}
var newer_message: String = str(
discovery.call("_request_failure", newer_discovery_error)
)
assert("discovery is updated" in newer_message)
assert("will not be listed" in newer_message)
discovery.free() discovery.free()
print("DEDICATED_SERVER_CONFIG_VALIDATION_OK") print("DEDICATED_SERVER_CONFIG_VALIDATION_OK")
quit() quit()

View file

@ -82,8 +82,7 @@ func _run() -> void:
print("Economy regression validation: PASS") print("Economy regression validation: PASS")
session.disconnect_session("Economy validation complete.") session.disconnect_session("Economy validation complete.")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
quit() quit()
@ -127,9 +126,7 @@ func _run_multiplayer_host() -> void:
print("Economy multiplayer host validation: PASS") print("Economy multiplayer host validation: PASS")
session.disconnect_session("Economy host validation complete.") session.disconnect_session("Economy host validation complete.")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -231,9 +228,7 @@ func _run_multiplayer_client() -> void:
print("Economy multiplayer client validation: PASS") print("Economy multiplayer client validation: PASS")
session.disconnect_session("Economy client validation complete.") session.disconnect_session("Economy client validation complete.")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -412,7 +407,7 @@ func _test_player_menu_sale(
assert(sell_action.mouse_filter == Control.MOUSE_FILTER_STOP) assert(sell_action.mouse_filter == Control.MOUSE_FILTER_STOP)
assert(sell_action.focus_mode == Control.FOCUS_ALL) assert(sell_action.focus_mode == Control.FOCUS_ALL)
assert(ui_viewport != null) assert(ui_viewport != null)
await _activate_pointer_control(sell_action, ui_viewport) await _activate_focused_button(sell_action, ui_viewport)
await process_frame await process_frame
assert(confirmation.visible) assert(confirmation.visible)
assert(confirm_button.visible and not confirm_button.disabled) assert(confirm_button.visible and not confirm_button.disabled)
@ -421,7 +416,7 @@ func _test_player_menu_sale(
> (player_menu.get_node("%CoolerOuterWall") as Control).z_index > (player_menu.get_node("%CoolerOuterWall") as Control).z_index
) )
_sale_result.clear() _sale_result.clear()
await _activate_pointer_control(confirm_button, ui_viewport) await _activate_focused_button(confirm_button, ui_viewport)
await process_frame await process_frame
assert(not _sale_result.is_empty() and bool(_sale_result[1])) assert(not _sale_result.is_empty() and bool(_sale_result[1]))
assert(not player.inventory.contains_catch_id(fish_catch.catch_id)) assert(not player.inventory.contains_catch_id(fish_catch.catch_id))
@ -440,29 +435,18 @@ func _test_player_menu_sale(
await create_timer(2.2).timeout await create_timer(2.2).timeout
func _activate_pointer_control( func _activate_focused_button(
control: Control, button: Button,
ui_viewport: SubViewport, ui_viewport: SubViewport,
) -> void: ) -> void:
var presenter := ui_viewport.get_parent() as SubViewportContainer button.grab_focus()
assert(presenter != null)
var local_center: Vector2 = control.get_global_transform_with_canvas() * (
control.size * 0.5
)
var center: Vector2 = presenter.position + local_center * presenter.scale
var motion := InputEventMouseMotion.new()
motion.position = center
motion.global_position = center
root.push_input(motion, true)
await process_frame await process_frame
assert(ui_viewport.gui_get_hovered_control() == control) assert(button.has_focus())
for is_pressed: bool in [true, false]: for is_pressed: bool in [true, false]:
var click := InputEventMouseButton.new() var accept := InputEventAction.new()
click.button_index = MOUSE_BUTTON_LEFT accept.action = &"ui_accept"
click.position = center accept.pressed = is_pressed
click.global_position = center ui_viewport.push_input(accept, false)
click.pressed = is_pressed
root.push_input(click, true)
await process_frame await process_frame
@ -571,25 +555,19 @@ func _test_fishing_shop_sale_ui(
assert(not shop.has_node("ShopPanel/Margin/Layout/ModeTabs")) assert(not shop.has_node("ShopPanel/Margin/Layout/ModeTabs"))
var shop_tabs: Array = shop.get("_shop_tabs") as Array var shop_tabs: Array = shop.get("_shop_tabs") as Array
assert(shop_tabs.size() == 6) assert(shop_tabs.size() == 6)
var art_supplies_tab := shop_tabs[4] as Button
assert(art_supplies_tab != null and art_supplies_tab.text == "Art Supplies")
var sell_mode := shop_tabs[5] as Button var sell_mode := shop_tabs[5] as Button
assert(sell_mode != null and sell_mode.text == "Sell Fish") assert(sell_mode != null and sell_mode.text == "Sell Fish")
await _activate_pointer_control(art_supplies_tab, ui_viewport)
await process_frame
var stock_sections: Array[String] = [] var stock_sections: Array[String] = []
for child: Node in shop.get_node("%SuppliesList").get_children(): for child: Node in shop.get_node("%SuppliesList").get_children():
if child is Label: if child is Label:
stock_sections.append((child as Label).text) stock_sections.append((child as Label).text)
assert(stock_sections == ["art kit", "markers", "brushes", "grids"]) assert(stock_sections == ["supplies", "art kit", "markers", "brushes", "grids"])
await _activate_pointer_control(sell_mode, ui_viewport) await _activate_focused_button(sell_mode, ui_viewport)
await process_frame await process_frame
assert(shop.visible and not player_menu.visible) assert(shop.visible and not player_menu.visible)
assert(not shop.has_node("ShopPanel/Margin/Layout/Body/FishSales")) assert(not shop.has_node("ShopPanel/Margin/Layout/Body/FishSales"))
assert((shop.get_node("%ShopCoolerPage") as Control).visible) assert((shop.get_node("%ShopCoolerPage") as Control).visible)
assert((shop.get_node("%ShopPanel") as Control).visible) assert(not (shop.get_node("%ShopPanel") as Control).visible)
assert(not (shop.get_node("ShopPanel/Margin/Layout/Body") as Control).visible)
assert(not (shop.get_node("%Feedback") as Control).visible)
var mounted_cooler := player_menu.get("_cooler_page") as Control var mounted_cooler := player_menu.get("_cooler_page") as Control
assert(mounted_cooler != null and mounted_cooler.visible) assert(mounted_cooler != null and mounted_cooler.visible)
assert( assert(
@ -599,11 +577,6 @@ func _test_fishing_shop_sale_ui(
var water_surface := player_menu.get("_cooler_water_surface") as ColorRect var water_surface := player_menu.get("_cooler_water_surface") as ColorRect
assert(cooler_outer_wall != null and cooler_outer_wall.visible) assert(cooler_outer_wall != null and cooler_outer_wall.visible)
assert(water_surface.visible and water_surface.material is ShaderMaterial) assert(water_surface.visible and water_surface.material is ShaderMaterial)
var cooler_sort_option := player_menu.get("_cooler_sort_option") as Control
await _activate_pointer_control(cooler_sort_option, ui_viewport)
var cooler_choice_panel := cooler_sort_option.get("_choice_panel") as Control
assert(cooler_choice_panel.visible)
cooler_sort_option.call("close_choices")
assert( assert(
StringName( StringName(
(player_menu.get("_sale_buyer_override") as FishBuyerProfile).id (player_menu.get("_sale_buyer_override") as FishBuyerProfile).id
@ -614,10 +587,10 @@ func _test_fishing_shop_sale_ui(
var reserved_button := fish_nodes.get(reserved_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(fish_button != null and fish_button.visible)
assert(reserved_button != null and reserved_button.visible) assert(reserved_button != null and reserved_button.visible)
await _activate_pointer_control(fish_button, ui_viewport) await _activate_focused_button(fish_button, ui_viewport)
var sell_button := player_menu.get("_sell_bubble") as Button var sell_button := player_menu.get("_sell_bubble") as Button
assert(sell_button.visible and not sell_button.disabled) assert(sell_button.visible and not sell_button.disabled)
await _activate_pointer_control(sell_button, ui_viewport) await _activate_focused_button(sell_button, ui_viewport)
await process_frame await process_frame
var confirmation := player_menu.get("_sale_confirmation") as Control var confirmation := player_menu.get("_sale_confirmation") as Control
var confirm_button := player_menu.get("_confirm_sale_button") as Button var confirm_button := player_menu.get("_confirm_sale_button") as Button
@ -627,21 +600,20 @@ func _test_fishing_shop_sale_ui(
> cooler_outer_wall.z_index > cooler_outer_wall.z_index
) )
_sale_result.clear() _sale_result.clear()
await _activate_pointer_control(confirm_button, ui_viewport) await _activate_focused_button(confirm_button, ui_viewport)
await process_frame await process_frame
assert(not _sale_result.is_empty() and bool(_sale_result[1])) assert(not _sale_result.is_empty() and bool(_sale_result[1]))
assert(not player.inventory.contains_catch_id(fish_catch.catch_id)) assert(not player.inventory.contains_catch_id(fish_catch.catch_id))
assert(player.wallet.get_balance() == balance_before + fish_catch.sale_value) assert(player.wallet.get_balance() == balance_before + fish_catch.sale_value)
assert(not sale_service.is_local_sale_pending()) assert(not sale_service.is_local_sale_pending())
assert(reservations.release(reservation_id)) assert(reservations.release(reservation_id))
await _activate_pointer_control(shop_tabs[0] as Button, ui_viewport) await _activate_focused_button(shop_tabs[0] as Button, ui_viewport)
await process_frame await process_frame
assert(shop.visible and (shop.get_node("%ShopPanel") as Control).visible) assert(shop.visible and (shop.get_node("%ShopPanel") as Control).visible)
assert(not (shop.get_node("%ShopCoolerPage") as Control).visible) assert(not (shop.get_node("%ShopCoolerPage") as Control).visible)
assert(not player_menu.is_shop_cooler_mounted()) assert(not player_menu.is_shop_cooler_mounted())
shop.close_shop() shop.close_shop()
await shop.menu_visibility_changed await process_frame
assert(not shop.visible)
func _assert_sale( func _assert_sale(
@ -662,9 +634,7 @@ func _assert_sale(
assert(bool(_sale_result[1]) == expected_success) assert(bool(_sale_result[1]) == expected_success)
assert(not sale_service.is_local_sale_pending()) assert(not sale_service.is_local_sale_pending())
if expected_success: if expected_success:
var payout: int = int(_sale_result[4]) assert(player.wallet.get_balance() > balance_before)
assert(payout >= 0)
assert(player.wallet.get_balance() == balance_before + payout)
for catch_id: StringName in catch_ids: for catch_id: StringName in catch_ids:
assert(not player.inventory.contains_catch_id(catch_id)) assert(not player.inventory.contains_catch_id(catch_id))
else: else:

View file

@ -3,6 +3,7 @@ extends SceneTree
const MainScene = preload("res://main/main.tscn") const MainScene = preload("res://main/main.tscn")
const FishCatchType = preload("res://fish/fish_catch.gd") const FishCatchType = preload("res://fish/fish_catch.gd")
const FishDataType = preload("res://fish/fish_data.gd") const FishDataType = preload("res://fish/fish_data.gd")
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
func _initialize() -> void: func _initialize() -> void:
@ -39,6 +40,7 @@ func _run() -> void:
var service := main.get_node( var service := main.get_node(
"%NetworkFishShowcaseService" "%NetworkFishShowcaseService"
) as NetworkFishShowcaseService ) as NetworkFishShowcaseService
var fishing_spot := main.get_node("%FishingSpot") as FishingSpotType
assert(player != null and fish_catalog != null and service != null) assert(player != null and fish_catalog != null and service != null)
var fish: FishDataType = fish_catalog.get_fish_by_id(&"bluegill") var fish: FishDataType = fish_catalog.get_fish_by_id(&"bluegill")
assert(fish != null) assert(fish != null)
@ -81,18 +83,22 @@ func _run() -> void:
assert(player.hotbar.get_selected_fish_catch_id() == fish_catch.catch_id) assert(player.hotbar.get_selected_fish_catch_id() == fish_catch.catch_id)
assert(player.hotbar.get_selected_item_id().is_empty()) assert(player.hotbar.get_selected_item_id().is_empty())
var press := InputEventMouseButton.new()
press.button_index = MOUSE_BUTTON_LEFT
press.pressed = true
fishing_spot.call("_unhandled_input", press)
await process_frame
assert(service.is_local_showcase_visible()) assert(service.is_local_showcase_visible())
assert(service.get_local_showcase_catch_id() == fish_catch.catch_id) assert(service.get_local_showcase_catch_id() == fish_catch.catch_id)
await _wait_for_held_fish_visibility(player, true) assert((player.get_node("%HeldFishDisplay") as Node3D).visible)
assert(service.toggle_selected_fish()) fishing_spot.call("_unhandled_input", press)
await process_frame await process_frame
assert(not service.is_local_showcase_visible()) assert(not service.is_local_showcase_visible())
await _wait_for_held_fish_visibility(player, false) assert(not (player.get_node("%HeldFishDisplay") as Node3D).visible)
var saved_weather: WorldWeatherService.Weather = ( var saved_weather: WorldWeatherService.Weather = (
world_weather.get_weather() world_weather.get_weather()
) )
var saved_weather_seconds: float = world_weather.get_seconds_remaining() var saved_weather_seconds: float = world_weather.get_seconds_remaining()
var saved_time_hours: float = world_time.get_time_hours()
assert(save_manager.save_now()) assert(save_manager.save_now())
var save_path: String = str(save_manager.get("_save_path")) var save_path: String = str(save_manager.get("_save_path"))
@ -110,7 +116,7 @@ func _run() -> void:
== 125 == 125
) )
assert(absf( assert(absf(
float((parsed as Dictionary)["world"]["time_hours"]) - saved_time_hours float((parsed as Dictionary)["world"]["time_hours"]) - 19.75
) < 0.01) ) < 0.01)
assert( assert(
int((parsed as Dictionary)["world"]["weather"]) int((parsed as Dictionary)["world"]["weather"])
@ -143,7 +149,7 @@ func _run() -> void:
) )
assert(save_manager.load_player_data()) assert(save_manager.load_player_data())
assert(player.experience.get_total_experience() == 125) assert(player.experience.get_total_experience() == 125)
assert(absf(world_time.get_time_hours() - saved_time_hours) < 0.01) assert(absf(world_time.get_time_hours() - 19.75) < 0.01)
assert(world_weather.get_weather() == saved_weather) assert(world_weather.get_weather() == saved_weather)
assert(absf( assert(absf(
world_weather.get_seconds_remaining() - saved_weather_seconds world_weather.get_seconds_remaining() - saved_weather_seconds
@ -157,13 +163,14 @@ func _run() -> void:
FishQuality.Tier.EXCEPTIONAL, FishQuality.Tier.EXCEPTIONAL,
) )
) )
assert(not service.is_local_showcase_visible())
assert(service.toggle_selected_fish())
assert(service.is_local_showcase_visible()) assert(service.is_local_showcase_visible())
await _wait_for_held_fish_visibility(player, true)
assert(player.inventory.remove_catch_by_id(fish_catch.catch_id) != null) assert(player.inventory.remove_catch_by_id(fish_catch.catch_id) != null)
await process_frame await process_frame
assert(player.hotbar.get_fish_catch_id(1).is_empty()) assert(player.hotbar.get_fish_catch_id(1).is_empty())
assert(not service.is_local_showcase_visible()) assert(not service.is_local_showcase_visible())
await _wait_for_held_fish_visibility(player, false) assert(not (player.get_node("%HeldFishDisplay") as Node3D).visible)
var valid_state: Dictionary = { var valid_state: Dictionary = {
"session_id": "session", "session_id": "session",
@ -195,26 +202,5 @@ func _run() -> void:
print("Fish hotbar showcase validation: PASS") print("Fish hotbar showcase validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
func _wait_for_held_fish_visibility(
player: Player,
expected_visible: bool,
) -> void:
var held_fish_display := player.get("_held_fish_display") as Node3D
assert(held_fish_display != null)
var deadline_msec: int = Time.get_ticks_msec() + 2000
while (
held_fish_display.visible != expected_visible
and Time.get_ticks_msec() < deadline_msec
):
await process_frame
assert(
held_fish_display.visible == expected_visible,
"Held fish display did not become %s within the timeout."
% expected_visible,
)

View file

@ -35,8 +35,8 @@ func _run_host() -> void:
var fish_catch: FishCatch = _add_bluegill(main, player) var fish_catch: FishCatch = _add_bluegill(main, player)
assert(player.hotbar.assign_fish(1, fish_catch.catch_id)) assert(player.hotbar.assign_fish(1, fish_catch.catch_id))
assert(player.hotbar.select_slot(1)) assert(player.hotbar.select_slot(1))
assert(service.toggle_selected_fish())
assert(service.is_local_showcase_visible()) assert(service.is_local_showcase_visible())
await _wait_for_held_fish_visibility(player, true)
assert(session.set_host_open(true)) assert(session.set_host_open(true))
var remote_peer_id: int = 0 var remote_peer_id: int = 0
@ -66,9 +66,7 @@ func _run_host() -> void:
print("Fish showcase multiplayer host validation: PASS") print("Fish showcase multiplayer host validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -120,8 +118,8 @@ func _run_client() -> void:
var fish_catch: FishCatch = _add_bluegill(main, player) var fish_catch: FishCatch = _add_bluegill(main, player)
assert(player.hotbar.assign_fish(1, fish_catch.catch_id)) assert(player.hotbar.assign_fish(1, fish_catch.catch_id))
assert(player.hotbar.select_slot(1)) assert(player.hotbar.select_slot(1))
assert(service.is_local_showcase_visible()) assert(service.toggle_selected_fish())
await _wait_for_held_fish_visibility(player, true) assert((player.get("_held_fish_display") as Node3D).visible)
await create_timer(2.0).timeout await create_timer(2.0).timeout
assert(service.toggle_selected_fish()) assert(service.toggle_selected_fish())
var held_display := player.get("_held_fish_display") as Node3D var held_display := player.get("_held_fish_display") as Node3D
@ -134,9 +132,7 @@ func _run_client() -> void:
print("Fish showcase multiplayer client validation: PASS") print("Fish showcase multiplayer client validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -170,18 +166,3 @@ func _create_initialized_main() -> Node:
await process_frame await process_frame
assert(bool(main.get("_application_initialized"))) assert(bool(main.get("_application_initialized")))
return main return main
func _wait_for_held_fish_visibility(
player: Player,
expected_visible: bool,
) -> void:
var held_fish_display := player.get("_held_fish_display") as Node3D
assert(held_fish_display != null)
var deadline_msec: int = Time.get_ticks_msec() + 2000
while (
held_fish_display.visible != expected_visible
and Time.get_ticks_msec() < deadline_msec
):
await process_frame
assert(held_fish_display.visible == expected_visible)

View file

@ -36,14 +36,8 @@ func _run() -> void:
assert(not session.is_open_host()) assert(not session.is_open_host())
assert(service != null and fishing_spot != null and player != null) assert(service != null and fishing_spot != null and player != null)
assert(player.hotbar.get_selected_item_id() == &"basic_fishing_rod") assert(player.hotbar.get_selected_item_id() == &"basic_fishing_rod")
var pond := main.get_node(
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond"
) as Node3D
var pond_region := pond.get_node("FishingRegion") as FishableWaterRegion
assert(pond != null and pond_region != null)
var pond_surface_y: float = pond_region.get_surface_height()
player.global_position = pond.global_position + Vector3(8.9, 1.44, 0.0) player.global_position = Vector3(-0.5, 3.95, 2.1)
var visuals := player.get_node("Visuals") as Node3D var visuals := player.get_node("Visuals") as Node3D
visuals.rotation.y = PI * 0.5 visuals.rotation.y = PI * 0.5
for _frame: int in 4: for _frame: int in 4:
@ -56,8 +50,8 @@ func _run() -> void:
fishing_spot.set("_cast_charge", 0.32) fishing_spot.set("_cast_charge", 0.32)
fishing_spot.call("_update_cast_charge", 0.0) fishing_spot.call("_update_cast_charge", 0.0)
var aimed_target: Vector3 = fishing_spot.get("_cast_target") var aimed_target: Vector3 = fishing_spot.get("_cast_target")
assert(aimed_target.x < player.global_position.x - 0.85) assert(aimed_target.x < -1.35)
assert(is_equal_approx(aimed_target.y, pond_surface_y)) assert(is_equal_approx(aimed_target.y, 2.51))
assert(fishing_spot.is_target_fishable(aimed_target)) assert(fishing_spot.is_target_fishable(aimed_target))
fishing_spot.call("_confirm_cast") fishing_spot.call("_confirm_cast")
assert(fishing_spot.state == FishingSpotType.FishingState.CASTING) assert(fishing_spot.state == FishingSpotType.FishingState.CASTING)
@ -74,7 +68,7 @@ func _run() -> void:
var attempts: Dictionary = service.get("_attempts") var attempts: Dictionary = service.get("_attempts")
var attempt: NetworkFishingAttempt = attempts.get(session.get_local_peer_id()) var attempt: NetworkFishingAttempt = attempts.get(session.get_local_peer_id())
assert(attempt != null) assert(attempt != null)
assert(is_equal_approx(attempt.target.y, pond_surface_y)) assert(is_equal_approx(attempt.target.y, 2.51))
assert(attempt.bobber_position.is_equal_approx(attempt.target)) assert(attempt.bobber_position.is_equal_approx(attempt.target))
fishing_spot.set("_withdrawal_input_held", true) fishing_spot.set("_withdrawal_input_held", true)
@ -130,42 +124,29 @@ func _run() -> void:
fish, fish,
) )
assert(fish_catch != null and fish_catch.is_valid()) assert(fish_catch != null and fish_catch.is_valid())
var reference_controller := CatchController.new() var baseline_controller := CatchController.new()
root.add_child(reference_controller) root.add_child(baseline_controller)
reference_controller.start_authoritative_encounter( baseline_controller.start_authoritative_encounter(
fish.catch_profile, fish.catch_profile,
attempt.reel_speed, attempt.reel_speed,
attempt.barrier_damage, attempt.barrier_damage,
attempt.encounter_seed, attempt.encounter_seed,
fish_catch.quality, FishQuality.Tier.BORING,
int(fish.rarity),
fish.get_weight_percentile(fish_catch.weight_lb),
) )
var quality_barriers: Array = attempt.controller.get("_barriers") var quality_barriers: Array = attempt.controller.get("_barriers")
var reference_barriers: Array = reference_controller.get("_barriers") var baseline_barriers: Array = baseline_controller.get("_barriers")
assert(quality_barriers.size() == reference_barriers.size()) assert(quality_barriers.size() == baseline_barriers.size())
for barrier_index: int in quality_barriers.size(): for barrier_index: int in quality_barriers.size():
var quality_barrier: RefCounted = quality_barriers[barrier_index] var quality_barrier: RefCounted = quality_barriers[barrier_index]
var reference_barrier: RefCounted = reference_barriers[barrier_index] var baseline_barrier: RefCounted = baseline_barriers[barrier_index]
assert( assert(
is_equal_approx( int(quality_barrier.get("maximum_health"))
float(quality_barrier.get("position")), == FishQuality.apply_barrier_health(
float(reference_barrier.get("position")), int(baseline_barrier.get("maximum_health")),
fish_catch.quality,
) )
) )
assert( baseline_controller.queue_free()
int(quality_barrier.get("maximum_health"))
== int(reference_barrier.get("maximum_health"))
)
assert(
int(quality_barrier.get("maximum_health"))
>= FishQuality.BARRIER_HEALTH_MINIMUMS[fish_catch.quality]
)
assert(
int(quality_barrier.get("maximum_health"))
<= FishQuality.BARRIER_HEALTH_MAXIMUMS[fish_catch.quality]
)
reference_controller.queue_free()
service.call("_cancel_attempt", session.get_local_peer_id(), "") service.call("_cancel_attempt", session.get_local_peer_id(), "")
await process_frame await process_frame
assert(not service.has_local_attempt()) assert(not service.has_local_attempt())
@ -173,7 +154,5 @@ func _run() -> void:
print("Fishing authority validation: PASS") print("Fishing authority validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -150,9 +150,7 @@ func _run_host() -> void:
print("Fishing multiplayer host validation: PASS") print("Fishing multiplayer host validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -327,9 +325,7 @@ func _run_client() -> void:
print("Fishing multiplayer client validation: PASS") print("Fishing multiplayer client validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -266,47 +266,11 @@ func _validate_bite_wait_distribution() -> void:
if wait_seconds >= FishingSpotType.BITE_LONG_MAX_SECONDS: if wait_seconds >= FishingSpotType.BITE_LONG_MAX_SECONDS:
very_long_count += 1 very_long_count += 1
var average_wait_seconds: float = total_wait_seconds / 10000.0 var average_wait_seconds: float = total_wait_seconds / 10000.0
var quick_ratio: float = float(quick_count) / 10000.0 assert(quick_count >= 2300 and quick_count <= 2700)
var very_long_ratio: float = float(very_long_count) / 10000.0
var very_long_probability: float = 1.0 - (
FishingSpotType.BITE_QUICK_PROBABILITY
+ FishingSpotType.BITE_TYPICAL_PROBABILITY
+ FishingSpotType.BITE_LONG_PROBABILITY
)
var expected_average_wait_seconds: float = (
FishingSpotType.BITE_QUICK_PROBABILITY
* (
FishingSpotType.BITE_QUICK_MIN_SECONDS
+ FishingSpotType.BITE_QUICK_MAX_SECONDS
)
* 0.5
+ FishingSpotType.BITE_TYPICAL_PROBABILITY
* (
FishingSpotType.BITE_QUICK_MAX_SECONDS
+ FishingSpotType.BITE_TYPICAL_MAX_SECONDS
)
* 0.5
+ FishingSpotType.BITE_LONG_PROBABILITY
* (
FishingSpotType.BITE_TYPICAL_MAX_SECONDS
+ FishingSpotType.BITE_LONG_MAX_SECONDS
)
* 0.5
+ very_long_probability
* (
FishingSpotType.BITE_LONG_MAX_SECONDS
+ FishingSpotType.BITE_MAX_SECONDS
)
* 0.5
)
assert(absf(
quick_ratio - FishingSpotType.BITE_QUICK_PROBABILITY
) <= 0.02)
assert(typical_or_long_count > quick_count) assert(typical_or_long_count > quick_count)
assert(absf(very_long_ratio - very_long_probability) <= 0.005) assert(very_long_count >= 100 and very_long_count <= 300)
assert(absf( assert(average_wait_seconds >= 45.0)
average_wait_seconds - expected_average_wait_seconds assert(average_wait_seconds <= 49.0)
) <= 1.0)
fishing_spot.queue_free() fishing_spot.queue_free()
await process_frame await process_frame
@ -341,16 +305,11 @@ func _validate_remote_presentation() -> void:
fish_catch.sale_value = 1 fish_catch.sale_value = 1
assert(fish_catch.is_valid()) assert(fish_catch.is_valid())
presentation.play_return(fish_catch) presentation.play_return(fish_catch)
await create_timer(0.6).timeout
var catch_display := player.find_child( var catch_display := player.find_child(
"CatchDisplay", true, false "CatchDisplay", true, false
) as Node3D ) as Node3D
assert(catch_display != null) assert(catch_display != null)
var showcase_deadline_msec: int = Time.get_ticks_msec() + 2000
while (
not catch_display.visible
and Time.get_ticks_msec() < showcase_deadline_msec
):
await process_frame
assert(catch_display.visible) assert(catch_display.visible)
await presentation.return_completed await presentation.return_completed
assert(not catch_display.visible) assert(not catch_display.visible)

View file

@ -62,9 +62,7 @@ func _run() -> void:
await _capture_inventory_pages(player_menu) await _capture_inventory_pages(player_menu)
print("Inventory notepad artwork validation: PASS") print("Inventory notepad artwork validation: PASS")
presentation_stage.queue_free() presentation_stage.queue_free()
for _frame: int in 10: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -56,9 +56,7 @@ func _run_host() -> void:
print("Job multiplayer host validation: PASS") print("Job multiplayer host validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -95,9 +93,7 @@ func _run_client() -> void:
print("Job multiplayer client validation: PASS") print("Job multiplayer client validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -187,10 +187,8 @@ func _run() -> void:
assert(int(save_data.get("save_version", -1)) == 7) assert(int(save_data.get("save_version", -1)) == 7)
assert(PlayerJobService.validate_save_data(save_data.get("jobs", {}))) assert(PlayerJobService.validate_save_data(save_data.get("jobs", {})))
session.disconnect_session("Job system validation complete.")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
print("Job system validation: PASS") print("Job system validation: PASS")
quit() quit()

View file

@ -79,15 +79,10 @@ func _run() -> void:
"_show_section_immediate", PlayerMenu.Section.PROFILE "_show_section_immediate", PlayerMenu.Section.PROFILE
) )
await process_frame await process_frame
assert(bool( assert(not bool(
player_menu.call("_handle_direct_page_shortcut", shortcut) player_menu.call("_handle_direct_page_shortcut", shortcut)
)) ))
await create_timer(0.5).timeout
assert(player_menu.visible) assert(player_menu.visible)
assert(logbook.visible)
assert(
player_menu.get("_current_section") == PlayerMenu.Section.LOGBOOK
)
player_menu.close_menu(PlayerMenu.CloseReason.TEARDOWN, false) player_menu.close_menu(PlayerMenu.CloseReason.TEARDOWN, false)
print( print(
@ -95,8 +90,7 @@ func _run() -> void:
% [root.size.x, root.size.y] % [root.size.x, root.size.y]
) )
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
quit() quit()

View file

@ -171,7 +171,7 @@ func _validate_page() -> void:
page.activate() page.activate()
await process_frame await process_frame
assert(page.get("_category") == WaterType.Type.FRESH_WATER) assert(page.get("_category") == WaterType.Type.FRESH_WATER)
assert((page.get("_catalog_grid") as GridContainer).columns == 4) assert((page.get("_catalog_grid") as GridContainer).columns == 5)
var initial_detail_body := page.get("_detail_body") as VBoxContainer var initial_detail_body := page.get("_detail_body") as VBoxContainer
assert(not initial_detail_body.get_parent() is ScrollContainer) assert(not initial_detail_body.get_parent() is ScrollContainer)
assert( assert(
@ -209,7 +209,7 @@ func _validate_page() -> void:
assert(portrait.source_texture == fish.display_texture) assert(portrait.source_texture == fish.display_texture)
assert( assert(
portrait.custom_minimum_size portrait.custom_minimum_size
== LogbookPage.CATALOG_PORTRAIT_SIZE == LogbookPortraitType.ENTRY_FRAME_SIZE
) )
assert( assert(
portrait.expand_mode portrait.expand_mode

View file

@ -12,14 +12,6 @@ func _run() -> void:
root.add_child(game_ui) root.add_child(game_ui)
await process_frame await process_frame
game_ui.set("_gameplay_ui_enabled", true) game_ui.set("_gameplay_ui_enabled", true)
var panel := game_ui.get_node("%ExperienceProgressPanel") as PanelContainer
var bubble := game_ui.get_node("%ExperienceBubble") as PanelContainer
var bubble_label := game_ui.get_node("%ExperienceBubbleLabel") as Label
var award_label := game_ui.get_node("%ExperienceAwardLabel") as Label
assert(panel != null and bubble != null)
assert(not panel.visible and not bubble.visible)
game_ui.call("_on_showcase_changed", "bluegill", "common", 1.0, 0, true)
await process_frame
game_ui.call( game_ui.call(
"_on_experience_awarded", "_on_experience_awarded",
50, 50,
@ -28,6 +20,13 @@ func _run() -> void:
1, 1,
1, 1,
) )
var panel := game_ui.get_node("%ExperienceProgressPanel") as PanelContainer
var bubble := game_ui.get_node("%ExperienceBubble") as PanelContainer
var bubble_label := game_ui.get_node("%ExperienceBubbleLabel") as Label
var award_label := game_ui.get_node("%ExperienceAwardLabel") as Label
assert(panel != null and bubble != null)
assert(not panel.visible and not bubble.visible)
game_ui.call("_on_showcase_changed", "bluegill", "common", 1.0, 0, true)
await process_frame await process_frame
assert(not panel.visible and not bubble.visible) assert(not panel.visible and not bubble.visible)
game_ui.call("_on_showcase_changed", "", "", 0.0, 0, false) game_ui.call("_on_showcase_changed", "", "", 0.0, 0, false)
@ -44,8 +43,5 @@ func _run() -> void:
await create_timer(1.2).timeout await create_timer(1.2).timeout
assert(not panel.visible and not bubble.visible) assert(not panel.visible and not bubble.visible)
game_ui.queue_free() game_ui.queue_free()
for _frame: int in 4:
await process_frame
await create_timer(0.1).timeout
print("Player experience UI validation: PASS") print("Player experience UI validation: PASS")
quit() quit()

View file

@ -114,9 +114,7 @@ func _run_host() -> void:
print("Surface drawing multiplayer host validation: PASS") print("Surface drawing multiplayer host validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -209,9 +207,7 @@ func _run_client() -> void:
print("Surface drawing multiplayer client validation: PASS") print("Surface drawing multiplayer client validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -110,9 +110,7 @@ func _run() -> void:
print("Surface drawing runtime validation: PASS") print("Surface drawing runtime validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -21,8 +21,6 @@ func _run() -> void:
await _validate_blocked_author_visibility() await _validate_blocked_author_visibility()
_validate_peer_capability_tracking() _validate_peer_capability_tracking()
print("Surface drawing validation: PASS") print("Surface drawing validation: PASS")
for _frame: int in 4:
await process_frame
quit() quit()
@ -120,7 +118,6 @@ func _validate_art_unlocks() -> void:
assert(unlocks.is_brush_size_unlocked(4)) assert(unlocks.is_brush_size_unlocked(4))
assert(unlocks.is_grid_size_unlocked(128)) assert(unlocks.is_grid_size_unlocked(128))
assert(not unlocks.restore_mask(PlayerArtUnlocks.ALL_UNLOCK_MASK + 1)) assert(not unlocks.restore_mask(PlayerArtUnlocks.ALL_UNLOCK_MASK + 1))
unlocks.free()
func _validate_grid_snapping() -> void: func _validate_grid_snapping() -> void:
@ -268,7 +265,6 @@ func _validate_blocked_author_visibility() -> void:
}) })
var world := Node3D.new() var world := Node3D.new()
root.add_child(world) root.add_child(world)
world.add_child(relationships)
var canvas := SurfaceDrawingCanvas.new() var canvas := SurfaceDrawingCanvas.new()
world.add_child(canvas) world.add_child(canvas)
var state: Dictionary = { var state: Dictionary = {

View file

@ -205,8 +205,6 @@ func _run() -> void:
presenter.queue_free() presenter.queue_free()
root.size = original_size root.size = original_size
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
print("UI scaling runtime validation: PASS") print("UI scaling runtime validation: PASS")
quit() quit()

View file

@ -83,9 +83,7 @@ func _run_host() -> void:
print("World time multiplayer host validation: PASS") print("World time multiplayer host validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()
@ -197,9 +195,7 @@ func _run_client() -> void:
print("World time multiplayer client validation: PASS") print("World time multiplayer client validation: PASS")
session.disconnect_session("") session.disconnect_session("")
main.queue_free() main.queue_free()
for _frame: int in 4: await process_frame
await process_frame
await create_timer(0.1).timeout
quit() quit()

View file

@ -41,8 +41,6 @@ func _input(event: InputEvent) -> void:
CONTROLLER_MOTION_THRESHOLD CONTROLLER_MOTION_THRESHOLD
): ):
_set_controller_active(true) _set_controller_active(true)
elif event is InputEventMouseMotion:
_set_controller_active(false)
elif event is InputEventMouseButton: elif event is InputEventMouseButton:
if (event as InputEventMouseButton).pressed: if (event as InputEventMouseButton).pressed:
_set_controller_active(false) _set_controller_active(false)

View file

@ -3,28 +3,11 @@ extends Node
const CONTROLLER_AXIS_THRESHOLD: float = 0.35 const CONTROLLER_AXIS_THRESHOLD: float = 0.35
const SEMANTIC_MATCH_BONUS: float = 1000000.0 const SEMANTIC_MATCH_BONUS: float = 1000000.0
const KEYBOARD_NAVIGATION_ACTIONS: Array[StringName] = [
&"ui_accept",
&"ui_cancel",
&"ui_up",
&"ui_down",
&"ui_left",
&"ui_right",
&"ui_focus_next",
&"ui_focus_prev",
&"ui_page_up",
&"ui_page_down",
&"ui_home",
&"ui_end",
]
var _controller_active: bool = false var _controller_active: bool = false
var _focus_navigation_active: bool = false
var _last_focus_center: Vector2 = Vector2.ZERO var _last_focus_center: Vector2 = Vector2.ZERO
var _last_focus_key: String = "" var _last_focus_key: String = ""
var _scope_chain: Array[WeakRef] = [] var _scope_chain: Array[WeakRef] = []
var _pending_focus: WeakRef
var _pointer_button_down: bool = false
var _recovery_generation: int = 0 var _recovery_generation: int = 0
@ -44,51 +27,23 @@ func _input(event: InputEvent) -> void:
if event is InputEventJoypadButton: if event is InputEventJoypadButton:
var button_event := event as InputEventJoypadButton var button_event := event as InputEventJoypadButton
if button_event.pressed: if button_event.pressed:
_pointer_button_down = false
_controller_active = true _controller_active = true
_focus_navigation_active = true
if button_event.button_index == JOY_BUTTON_LEFT_SHOULDER: if button_event.button_index == JOY_BUTTON_LEFT_SHOULDER:
_recovery_generation += 1 _recovery_generation += 1
_scope_chain.clear() _scope_chain.clear()
_pending_focus = null
return
_request_pending_focus()
return return
if event is InputEventJoypadMotion: if event is InputEventJoypadMotion:
if absf((event as InputEventJoypadMotion).axis_value) >= ( if absf((event as InputEventJoypadMotion).axis_value) >= (
CONTROLLER_AXIS_THRESHOLD CONTROLLER_AXIS_THRESHOLD
): ):
_pointer_button_down = false
_controller_active = true _controller_active = true
_focus_navigation_active = true
_request_pending_focus()
return
if event is InputEventMouseMotion:
_pointer_button_down = (
(event as InputEventMouseMotion).button_mask != 0
)
_leave_focus_navigation(not _pointer_button_down)
return return
if event is InputEventMouseButton: if event is InputEventMouseButton:
var mouse_button := event as InputEventMouseButton if (event as InputEventMouseButton).pressed:
if mouse_button.button_index in [ _controller_active = false
MOUSE_BUTTON_LEFT,
MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_MIDDLE,
]:
_pointer_button_down = mouse_button.pressed
_leave_focus_navigation(false)
if not mouse_button.pressed:
_release_current_pointer_focus.call_deferred()
return return
if event is InputEventKey and (event as InputEventKey).pressed: if event is InputEventKey and (event as InputEventKey).pressed:
_pointer_button_down = false
_controller_active = false _controller_active = false
if _is_keyboard_navigation_event(event):
_focus_navigation_active = true
_request_pending_focus()
else:
_leave_focus_navigation()
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
@ -103,16 +58,8 @@ func _process(_delta: float) -> void:
func _on_gui_focus_changed(control: Control) -> void: func _on_gui_focus_changed(control: Control) -> void:
_recovery_generation += 1 _recovery_generation += 1
if control != null: if control != null:
if _focus_navigation_active: if _controller_active and control is BaseButton:
_pending_focus = null _remember_focus(control)
if _controller_active and control is BaseButton:
_remember_focus(control)
return
if _keeps_pointer_focus(control):
return
if _is_focusable(control):
_pending_focus = weakref(control)
_release_focus_if_inactive.call_deferred(control)
return return
if not _controller_active or _scope_chain.is_empty(): if not _controller_active or _scope_chain.is_empty():
return return
@ -145,18 +92,13 @@ func _recover_focus(generation: int) -> void:
scope == null scope == null
or not is_instance_valid(scope) or not is_instance_valid(scope)
or not scope.is_inside_tree() or not scope.is_inside_tree()
or not scope.is_visible_in_tree()
): ):
continue continue
if not scope.is_visible_in_tree():
_scope_chain.clear()
return
var replacement := _best_replacement_in(scope) var replacement := _best_replacement_in(scope)
if replacement != null: if replacement != null:
replacement.grab_focus() replacement.grab_focus()
else: return
_scope_chain.clear()
return
_scope_chain.clear()
func _best_replacement_in(scope: Control) -> Control: func _best_replacement_in(scope: Control) -> Control:
@ -182,7 +124,7 @@ func _is_focusable(control: Control) -> bool:
control == null control == null
or not control.is_inside_tree() or not control.is_inside_tree()
or not control.is_visible_in_tree() or not control.is_visible_in_tree()
or control.focus_mode not in [Control.FOCUS_CLICK, Control.FOCUS_ALL] or control.focus_mode == Control.FOCUS_NONE
): ):
return false return false
var button := control as BaseButton var button := control as BaseButton
@ -198,80 +140,3 @@ func _semantic_key(control: Control) -> String:
tooltip = tooltip.trim_suffix(" (show variants)") tooltip = tooltip.trim_suffix(" (show variants)")
tooltip = tooltip.trim_suffix(" (hide variants)") tooltip = tooltip.trim_suffix(" (hide variants)")
return "%s|%s|%s" % [control.get_class(), label, tooltip] return "%s|%s|%s" % [control.get_class(), label, tooltip]
func _leave_focus_navigation(release_focus: bool = true) -> void:
_controller_active = false
_focus_navigation_active = false
_recovery_generation += 1
_scope_chain.clear()
var focus_owner: Control = get_viewport().gui_get_focus_owner()
if focus_owner == null or _keeps_pointer_focus(focus_owner):
return
if _is_focusable(focus_owner):
_pending_focus = weakref(focus_owner)
if release_focus:
_release_focus_if_inactive.call_deferred(focus_owner)
func _release_focus_if_inactive(control: Control) -> void:
if (
_focus_navigation_active
or _pointer_button_down
or control == null
or not is_instance_valid(control)
or get_viewport().gui_get_focus_owner() != control
or _keeps_pointer_focus(control)
):
return
get_viewport().gui_release_focus()
func _release_current_pointer_focus() -> void:
var focus_owner: Control = get_viewport().gui_get_focus_owner()
if focus_owner != null:
_release_focus_if_inactive(focus_owner)
func _request_pending_focus() -> void:
if (
not _focus_navigation_active
or _pending_focus == null
or get_viewport().gui_get_focus_owner() != null
):
return
_restore_pending_focus.call_deferred(_recovery_generation)
func _restore_pending_focus(generation: int) -> void:
if (
generation != _recovery_generation
or not _focus_navigation_active
or _pending_focus == null
or get_viewport().gui_get_focus_owner() != null
):
return
var target := _pending_focus.get_ref() as Control
if not _is_focusable(target):
_pending_focus = null
return
_pending_focus = null
target.grab_focus()
func _is_keyboard_navigation_event(event: InputEvent) -> bool:
if event.is_action_pressed(&"open_backpack"):
return false
if (
event.is_action_pressed(&"ui_cancel")
and get_viewport().gui_get_focus_owner() == null
):
return false
for action: StringName in KEYBOARD_NAVIGATION_ACTIONS:
if event.is_action_pressed(action):
return true
return false
func _keeps_pointer_focus(control: Control) -> bool:
return control is LineEdit or control is TextEdit

View file

@ -74,8 +74,6 @@ const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0
@onready var _wallet_label: Label = %WalletLabel @onready var _wallet_label: Label = %WalletLabel
@onready var _shop_panel: PanelContainer = %ShopPanel @onready var _shop_panel: PanelContainer = %ShopPanel
@onready var _shop_panel_margin: MarginContainer = $ShopPanel/Margin
@onready var _shop_panel_layout: VBoxContainer = $ShopPanel/Margin/Layout
@onready var _shop_cooler_page: Control = %ShopCoolerPage @onready var _shop_cooler_page: Control = %ShopCoolerPage
@onready var _shop_cooler_mount: Control = %ShopCoolerMount @onready var _shop_cooler_mount: Control = %ShopCoolerMount
@onready var _shop_body: HBoxContainer = $ShopPanel/Margin/Layout/Body @onready var _shop_body: HBoxContainer = $ShopPanel/Margin/Layout/Body
@ -369,7 +367,6 @@ func get_shop_cooler_mount() -> Control:
func activate_shop_cooler_page() -> void: func activate_shop_cooler_page() -> void:
_cooler_page_active = true _cooler_page_active = true
_shop_panel.show() _shop_panel.show()
_set_shop_panel_background_pointer_blocking(false)
_shop_body.hide() _shop_body.hide()
_feedback.hide() _feedback.hide()
_shop_cooler_page.show() _shop_cooler_page.show()
@ -380,7 +377,6 @@ func deactivate_shop_cooler_page() -> void:
_shop_tab_bar.show() _shop_tab_bar.show()
_cooler_page_active = false _cooler_page_active = false
_cooler_modal_open = false _cooler_modal_open = false
_set_shop_panel_background_pointer_blocking(true)
for tab: OrganizerTab in _shop_tabs: for tab: OrganizerTab in _shop_tabs:
tab.disabled = false tab.disabled = false
_shop_cooler_page.hide() _shop_cooler_page.hide()
@ -389,17 +385,6 @@ func deactivate_shop_cooler_page() -> void:
_feedback.show() _feedback.show()
func _set_shop_panel_background_pointer_blocking(blocking: bool) -> void:
var mouse_filter := (
Control.MOUSE_FILTER_STOP
if blocking
else Control.MOUSE_FILTER_IGNORE
)
_shop_panel.mouse_filter = mouse_filter
_shop_panel_margin.mouse_filter = mouse_filter
_shop_panel_layout.mouse_filter = mouse_filter
func set_shop_cooler_modal_open(is_open: bool) -> void: func set_shop_cooler_modal_open(is_open: bool) -> void:
_cooler_modal_open = is_open _cooler_modal_open = is_open
for tab: OrganizerTab in _shop_tabs: for tab: OrganizerTab in _shop_tabs:

View file

@ -351,10 +351,7 @@ func _open_confirmation(
else BubbleConfirmationPageType.InitialFocus.CONFIRM else BubbleConfirmationPageType.InitialFocus.CONFIRM
) )
) )
# Confirmation actions reject input while the root page is still leaving. _begin_root_exit(_show_confirmation)
# Finish that exit before exposing an interactive confirmation page so a
# pointer click can never land in the overlap window and be discarded.
_begin_root_exit(_show_confirmation, false)
func _show_confirmation() -> void: func _show_confirmation() -> void:
@ -453,10 +450,7 @@ func _finish_root_entry(generation: int) -> void:
_root_transition_active = false _root_transition_active = false
func _begin_root_exit( func _begin_root_exit(completed: Callable) -> void:
completed: Callable,
overlap_next_page: bool = true,
) -> void:
if _root_transition_active or not _root_page.visible: if _root_transition_active or not _root_page.visible:
return return
_root_transition_generation += 1 _root_transition_generation += 1
@ -464,14 +458,9 @@ func _begin_root_exit(
_emit_transition_flurry() _emit_transition_flurry()
var generation: int = _root_transition_generation var generation: int = _root_transition_generation
_root_page.transition_out( _root_page.transition_out(
_finish_root_exit.bind( _finish_root_exit.bind(generation),
generation,
Callable() if overlap_next_page else completed,
),
0.0 0.0
) )
if not overlap_next_page:
return
await get_tree().create_timer( await get_tree().create_timer(
UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY
).timeout ).timeout
@ -482,13 +471,10 @@ func _begin_root_exit(
func _finish_root_exit( func _finish_root_exit(
generation: int, generation: int,
completed: Callable,
) -> void: ) -> void:
if generation != _root_transition_generation or not visible: if generation != _root_transition_generation or not visible:
return return
_root_transition_active = false _root_transition_active = false
if completed.is_valid():
completed.call()
func _finish_user_close() -> void: func _finish_user_close() -> void:

View file

@ -209,7 +209,7 @@ unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1) theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
theme_override_font_sizes/font_size = 22 theme_override_font_sizes/font_size = 22
text = "v0.6.7-alpha" text = "v0.6.5-alpha"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"] [node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]