Prepare v0.6.6-alpha release
This commit is contained in:
parent
96df168851
commit
b044d35021
29 changed files with 568 additions and 134 deletions
|
|
@ -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.5-alpha/windows-x86_64/NETfishing.exe"
|
export_path="builds/v0.6.6-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.5.0"
|
application/file_version="0.6.6.0"
|
||||||
application/product_version="0.6.5.0"
|
application/product_version="0.6.6.0"
|
||||||
application/company_name=""
|
application/company_name=""
|
||||||
application/product_name="NETfishing"
|
application/product_name="NETfishing"
|
||||||
application/file_description="NETfishing v0.6.5-alpha"
|
application/file_description="NETfishing v0.6.6-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.5-alpha/linux-arm64/NETfishing.arm64"
|
export_path="builds/v0.6.6-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.5-alpha/macos/NETfishing.zip"
|
export_path="builds/v0.6.6-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.4"
|
application/short_version="0.6.6"
|
||||||
application/version="0.6.4"
|
application/version="0.6.6"
|
||||||
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.5-alpha/server-linux-x86_64/NETfishingServer.x86_64"
|
export_path="builds/v0.6.6-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.5-alpha/android/NETfishing.apk"
|
export_path="builds/v0.6.6-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=60500
|
version/code=60600
|
||||||
version/name="v0.6.5-alpha"
|
version/name="v0.6.6-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.5-alpha/linux-x86_64/NETfishing.x86_64"
|
export_path="builds/v0.6.6-alpha/linux-x86_64/NETfishing.x86_64"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
|
var ribbon_vertices := PackedVector3Array()
|
||||||
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,12 +452,17 @@ 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)
|
||||||
_line_mesh.surface_add_vertex(start_left)
|
ribbon_vertices.append(start_left)
|
||||||
_line_mesh.surface_add_vertex(start_right)
|
ribbon_vertices.append(start_right)
|
||||||
_line_mesh.surface_add_vertex(end_right)
|
ribbon_vertices.append(end_right)
|
||||||
_line_mesh.surface_add_vertex(start_left)
|
ribbon_vertices.append(start_left)
|
||||||
_line_mesh.surface_add_vertex(end_right)
|
ribbon_vertices.append(end_right)
|
||||||
_line_mesh.surface_add_vertex(end_left)
|
ribbon_vertices.append(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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
NETfishing
|
NETfishing
|
||||||
v0.6.5-alpha
|
v0.6.6-alpha
|
||||||
Alpha 0.6.5
|
Alpha 0.6.6
|
||||||
|
|
||||||
Thank you for trying this early private playtest.
|
Thank you for trying this early private playtest.
|
||||||
|
|
||||||
NETfishing is currently a local gameplay prototype for a future
|
NETfishing is an early multiplayer fishing game. This alpha includes local
|
||||||
multiplayer-first game. Networking and multiplayer are NOT implemented in
|
progression, player-hosted and dedicated multiplayer sessions, public room
|
||||||
this build.
|
discovery, and direct UDP connections. Expect rough edges and please keep a
|
||||||
|
backup of any save or identity data you care about.
|
||||||
|
|
||||||
INSTALLATION
|
INSTALLATION
|
||||||
|
|
||||||
|
|
@ -60,6 +61,9 @@ 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
|
||||||
|
|
@ -101,11 +105,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 PRE-ALPHA LIMITATIONS
|
KNOWN ALPHA LIMITATIONS
|
||||||
|
|
||||||
- Multiplayer/networking is not implemented.
|
- Public sessions remain direct connections; restrictive NAT or carrier-grade
|
||||||
- Fish and character art are temporary.
|
networks may prevent some players from joining.
|
||||||
- UI and balance values are placeholders.
|
- Multiplayer, dedicated-server deployment, UI, and balance are still alpha.
|
||||||
- There is no audio.
|
- Some fish and character artwork remains temporary.
|
||||||
- There is no installer or automatic updater.
|
- There is no installer or automatic updater.
|
||||||
- Windows builds are unsigned.
|
- Windows builds are unsigned.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ config_version=5
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="NETFISHING"
|
config/name="NETFISHING"
|
||||||
config/version="0.6.5-alpha"
|
config/version="0.6.6-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"
|
||||||
|
|
|
||||||
|
|
@ -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.5-alpha"
|
readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.6.6-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.5-alpha-windows-x86_64.zip"
|
readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.6.6-alpha-windows-x86_64.zip"
|
||||||
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.6.5-alpha-linux-x86_64.zip"
|
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.6.6-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
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ 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(
|
||||||
|
|
@ -121,6 +122,8 @@ 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
|
||||||
|
|
@ -129,10 +132,8 @@ 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(chat_ui.is_open())
|
|
||||||
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(not chat_ui.is_open())
|
||||||
|
assert(not typed_chat_entry.virtual_keyboard_enabled)
|
||||||
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())
|
||||||
|
|
@ -315,5 +316,7 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,15 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,45 @@ 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
|
||||||
_expect(root.gui_get_focus_owner() == original, "original option receives focus")
|
await process_frame
|
||||||
|
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()
|
||||||
|
|
@ -53,6 +84,63 @@ 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
|
||||||
|
|
@ -64,6 +152,14 @@ 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():
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -229,7 +231,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -408,7 +412,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_focused_button(sell_action, ui_viewport)
|
await _activate_pointer_control(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)
|
||||||
|
|
@ -417,7 +421,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_focused_button(confirm_button, ui_viewport)
|
await _activate_pointer_control(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))
|
||||||
|
|
@ -436,18 +440,29 @@ func _test_player_menu_sale(
|
||||||
await create_timer(2.2).timeout
|
await create_timer(2.2).timeout
|
||||||
|
|
||||||
|
|
||||||
func _activate_focused_button(
|
func _activate_pointer_control(
|
||||||
button: Button,
|
control: Control,
|
||||||
ui_viewport: SubViewport,
|
ui_viewport: SubViewport,
|
||||||
) -> void:
|
) -> void:
|
||||||
button.grab_focus()
|
var presenter := ui_viewport.get_parent() as SubViewportContainer
|
||||||
|
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(button.has_focus())
|
assert(ui_viewport.gui_get_hovered_control() == control)
|
||||||
for is_pressed: bool in [true, false]:
|
for is_pressed: bool in [true, false]:
|
||||||
var accept := InputEventAction.new()
|
var click := InputEventMouseButton.new()
|
||||||
accept.action = &"ui_accept"
|
click.button_index = MOUSE_BUTTON_LEFT
|
||||||
accept.pressed = is_pressed
|
click.position = center
|
||||||
ui_viewport.push_input(accept, false)
|
click.global_position = center
|
||||||
|
click.pressed = is_pressed
|
||||||
|
root.push_input(click, true)
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -560,14 +575,14 @@ func _test_fishing_shop_sale_ui(
|
||||||
assert(art_supplies_tab != null and art_supplies_tab.text == "Art Supplies")
|
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_focused_button(art_supplies_tab, ui_viewport)
|
await _activate_pointer_control(art_supplies_tab, ui_viewport)
|
||||||
await process_frame
|
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 == ["art kit", "markers", "brushes", "grids"])
|
||||||
await _activate_focused_button(sell_mode, ui_viewport)
|
await _activate_pointer_control(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"))
|
||||||
|
|
@ -584,6 +599,11 @@ 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
|
||||||
|
|
@ -594,10 +614,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_focused_button(fish_button, ui_viewport)
|
await _activate_pointer_control(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_focused_button(sell_button, ui_viewport)
|
await _activate_pointer_control(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
|
||||||
|
|
@ -607,14 +627,14 @@ func _test_fishing_shop_sale_ui(
|
||||||
> cooler_outer_wall.z_index
|
> cooler_outer_wall.z_index
|
||||||
)
|
)
|
||||||
_sale_result.clear()
|
_sale_result.clear()
|
||||||
await _activate_focused_button(confirm_button, ui_viewport)
|
await _activate_pointer_control(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_focused_button(shop_tabs[0] as Button, ui_viewport)
|
await _activate_pointer_control(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)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ 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:
|
||||||
|
|
@ -40,7 +39,6 @@ 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)
|
||||||
|
|
@ -83,22 +81,18 @@ 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)
|
||||||
assert((player.get_node("%HeldFishDisplay") as Node3D).visible)
|
await _wait_for_held_fish_visibility(player, true)
|
||||||
fishing_spot.call("_unhandled_input", press)
|
assert(service.toggle_selected_fish())
|
||||||
await process_frame
|
await process_frame
|
||||||
assert(not service.is_local_showcase_visible())
|
assert(not service.is_local_showcase_visible())
|
||||||
assert(not (player.get_node("%HeldFishDisplay") as Node3D).visible)
|
await _wait_for_held_fish_visibility(player, false)
|
||||||
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"))
|
||||||
|
|
@ -116,7 +110,7 @@ func _run() -> void:
|
||||||
== 125
|
== 125
|
||||||
)
|
)
|
||||||
assert(absf(
|
assert(absf(
|
||||||
float((parsed as Dictionary)["world"]["time_hours"]) - 19.75
|
float((parsed as Dictionary)["world"]["time_hours"]) - saved_time_hours
|
||||||
) < 0.01)
|
) < 0.01)
|
||||||
assert(
|
assert(
|
||||||
int((parsed as Dictionary)["world"]["weather"])
|
int((parsed as Dictionary)["world"]["weather"])
|
||||||
|
|
@ -149,7 +143,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() - 19.75) < 0.01)
|
assert(absf(world_time.get_time_hours() - saved_time_hours) < 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
|
||||||
|
|
@ -163,14 +157,13 @@ 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())
|
||||||
assert(not (player.get_node("%HeldFishDisplay") as Node3D).visible)
|
await _wait_for_held_fish_visibility(player, false)
|
||||||
|
|
||||||
var valid_state: Dictionary = {
|
var valid_state: Dictionary = {
|
||||||
"session_id": "session",
|
"session_id": "session",
|
||||||
|
|
@ -202,5 +195,26 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,7 +66,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,8 +120,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.toggle_selected_fish())
|
assert(service.is_local_showcase_visible())
|
||||||
assert((player.get("_held_fish_display") as Node3D).visible)
|
await _wait_for_held_fish_visibility(player, true)
|
||||||
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
|
||||||
|
|
@ -132,7 +134,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -166,3 +170,18 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,14 @@ 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 = Vector3(-0.5, 3.95, 2.1)
|
player.global_position = pond.global_position + Vector3(8.9, 1.44, 0.0)
|
||||||
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:
|
||||||
|
|
@ -50,8 +56,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 < -1.35)
|
assert(aimed_target.x < player.global_position.x - 0.85)
|
||||||
assert(is_equal_approx(aimed_target.y, 2.51))
|
assert(is_equal_approx(aimed_target.y, pond_surface_y))
|
||||||
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)
|
||||||
|
|
@ -68,7 +74,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, 2.51))
|
assert(is_equal_approx(attempt.target.y, pond_surface_y))
|
||||||
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)
|
||||||
|
|
@ -124,29 +130,42 @@ func _run() -> void:
|
||||||
fish,
|
fish,
|
||||||
)
|
)
|
||||||
assert(fish_catch != null and fish_catch.is_valid())
|
assert(fish_catch != null and fish_catch.is_valid())
|
||||||
var baseline_controller := CatchController.new()
|
var reference_controller := CatchController.new()
|
||||||
root.add_child(baseline_controller)
|
root.add_child(reference_controller)
|
||||||
baseline_controller.start_authoritative_encounter(
|
reference_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,
|
||||||
FishQuality.Tier.BORING,
|
fish_catch.quality,
|
||||||
|
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 baseline_barriers: Array = baseline_controller.get("_barriers")
|
var reference_barriers: Array = reference_controller.get("_barriers")
|
||||||
assert(quality_barriers.size() == baseline_barriers.size())
|
assert(quality_barriers.size() == reference_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 baseline_barrier: RefCounted = baseline_barriers[barrier_index]
|
var reference_barrier: RefCounted = reference_barriers[barrier_index]
|
||||||
assert(
|
assert(
|
||||||
int(quality_barrier.get("maximum_health"))
|
is_equal_approx(
|
||||||
== FishQuality.apply_barrier_health(
|
float(quality_barrier.get("position")),
|
||||||
int(baseline_barrier.get("maximum_health")),
|
float(reference_barrier.get("position")),
|
||||||
fish_catch.quality,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
baseline_controller.queue_free()
|
assert(
|
||||||
|
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())
|
||||||
|
|
@ -154,5 +173,7 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -325,7 +327,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -266,11 +266,47 @@ 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
|
||||||
assert(quick_count >= 2300 and quick_count <= 2700)
|
var quick_ratio: float = float(quick_count) / 10000.0
|
||||||
|
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(very_long_count >= 100 and very_long_count <= 300)
|
assert(absf(very_long_ratio - very_long_probability) <= 0.005)
|
||||||
assert(average_wait_seconds >= 45.0)
|
assert(absf(
|
||||||
assert(average_wait_seconds <= 49.0)
|
average_wait_seconds - expected_average_wait_seconds
|
||||||
|
) <= 1.0)
|
||||||
fishing_spot.queue_free()
|
fishing_spot.queue_free()
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
|
|
@ -305,11 +341,16 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 10:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -93,7 +95,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,10 +79,15 @@ func _run() -> void:
|
||||||
"_show_section_immediate", PlayerMenu.Section.PROFILE
|
"_show_section_immediate", PlayerMenu.Section.PROFILE
|
||||||
)
|
)
|
||||||
await process_frame
|
await process_frame
|
||||||
assert(not bool(
|
assert(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(
|
||||||
|
|
@ -90,7 +95,8 @@ func _run() -> void:
|
||||||
% [root.size.x, root.size.y]
|
% [root.size.x, root.size.y]
|
||||||
)
|
)
|
||||||
main.queue_free()
|
main.queue_free()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 == 5)
|
assert((page.get("_catalog_grid") as GridContainer).columns == 4)
|
||||||
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
|
||||||
== LogbookPortraitType.ENTRY_FRAME_SIZE
|
== LogbookPage.CATALOG_PORTRAIT_SIZE
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
portrait.expand_mode
|
portrait.expand_mode
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,14 @@ 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,
|
||||||
|
|
@ -20,13 +28,6 @@ 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)
|
||||||
|
|
@ -43,5 +44,8 @@ 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()
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -207,7 +209,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,6 +120,7 @@ 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:
|
||||||
|
|
@ -265,6 +268,7 @@ 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 = {
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,8 @@ func _run() -> void:
|
||||||
|
|
||||||
presenter.queue_free()
|
presenter.queue_free()
|
||||||
root.size = original_size
|
root.size = original_size
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
print("UI scaling runtime validation: PASS")
|
print("UI scaling runtime validation: PASS")
|
||||||
quit()
|
quit()
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -195,7 +197,9 @@ 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()
|
||||||
await process_frame
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
await create_timer(0.1).timeout
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,28 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,23 +44,51 @@ 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:
|
||||||
if (event as InputEventMouseButton).pressed:
|
var mouse_button := event as InputEventMouseButton
|
||||||
_controller_active = false
|
if mouse_button.button_index in [
|
||||||
|
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:
|
||||||
|
|
@ -58,8 +103,16 @@ 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 _controller_active and control is BaseButton:
|
if _focus_navigation_active:
|
||||||
_remember_focus(control)
|
_pending_focus = null
|
||||||
|
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
|
||||||
|
|
@ -92,13 +145,18 @@ 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()
|
||||||
return
|
else:
|
||||||
|
_scope_chain.clear()
|
||||||
|
return
|
||||||
|
_scope_chain.clear()
|
||||||
|
|
||||||
|
|
||||||
func _best_replacement_in(scope: Control) -> Control:
|
func _best_replacement_in(scope: Control) -> Control:
|
||||||
|
|
@ -124,7 +182,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 == Control.FOCUS_NONE
|
or control.focus_mode not in [Control.FOCUS_CLICK, Control.FOCUS_ALL]
|
||||||
):
|
):
|
||||||
return false
|
return false
|
||||||
var button := control as BaseButton
|
var button := control as BaseButton
|
||||||
|
|
@ -140,3 +198,80 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,8 @@ 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
|
||||||
|
|
@ -367,6 +369,7 @@ 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()
|
||||||
|
|
@ -377,6 +380,7 @@ 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()
|
||||||
|
|
@ -385,6 +389,17 @@ 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:
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,10 @@ func _open_confirmation(
|
||||||
else BubbleConfirmationPageType.InitialFocus.CONFIRM
|
else BubbleConfirmationPageType.InitialFocus.CONFIRM
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_begin_root_exit(_show_confirmation)
|
# Confirmation actions reject input while the root page is still leaving.
|
||||||
|
# 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:
|
||||||
|
|
@ -450,7 +453,10 @@ func _finish_root_entry(generation: int) -> void:
|
||||||
_root_transition_active = false
|
_root_transition_active = false
|
||||||
|
|
||||||
|
|
||||||
func _begin_root_exit(completed: Callable) -> void:
|
func _begin_root_exit(
|
||||||
|
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
|
||||||
|
|
@ -458,9 +464,14 @@ func _begin_root_exit(completed: Callable) -> void:
|
||||||
_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(generation),
|
_finish_root_exit.bind(
|
||||||
|
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
|
||||||
|
|
@ -471,10 +482,13 @@ func _begin_root_exit(completed: Callable) -> void:
|
||||||
|
|
||||||
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:
|
||||||
|
|
|
||||||
|
|
@ -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.5-alpha"
|
text = "v0.6.6-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"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue