Recover players on generated worlds to the nearest safe grass or sand surface. Use the active gameplay camera when positioning shop and storage prompts.
1026 lines
38 KiB
GDScript
1026 lines
38 KiB
GDScript
extends SceneTree
|
|
|
|
const MainScene: PackedScene = preload("res://main/main.tscn")
|
|
const TEST_PORT: int = 18136
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
var main: Node = MainScene.instantiate()
|
|
root.add_child(main)
|
|
for _frame: int in 4:
|
|
await process_frame
|
|
if not bool(main.get("_application_initialized")):
|
|
main.call("_activate_selected_data_path", "", true)
|
|
for _frame: int in 8:
|
|
await process_frame
|
|
assert(bool(main.get("_application_initialized")))
|
|
assert(bool(main.call("_prepare_private_host")))
|
|
var save_manager := main.get("_save_manager") as PlayerSaveManager
|
|
assert(save_manager.initialize_new_game())
|
|
main.call("_enter_gameplay")
|
|
await physics_frame
|
|
await physics_frame
|
|
|
|
var player := main.get("_player") as Player
|
|
var session := main.get_node("%NetworkSession") as NetworkSession
|
|
var sprint_dust := player.get_node("%SprintDust") as SprintDustTrail
|
|
assert(sprint_dust != null)
|
|
assert(sprint_dust.get_active_puff_count() == 0)
|
|
sprint_dust.update_trail(
|
|
0.016,
|
|
Vector3.ZERO,
|
|
Vector3(4.5, 0.0, 0.0),
|
|
true,
|
|
)
|
|
assert(sprint_dust.get_active_puff_count() == 0)
|
|
sprint_dust.update_trail(
|
|
0.016,
|
|
Vector3(0.5, 0.0, 0.0),
|
|
Vector3(4.5, 0.0, 0.0),
|
|
true,
|
|
)
|
|
assert(sprint_dust.get_active_puff_count() == 1)
|
|
var planted_puff_position: Vector3 = (
|
|
sprint_dust.get_first_active_puff_position()
|
|
)
|
|
sprint_dust.update_trail(
|
|
0.08,
|
|
Vector3(0.75, 0.0, 0.0),
|
|
Vector3(4.5, 0.0, 0.0),
|
|
true,
|
|
)
|
|
assert(
|
|
sprint_dust.get_first_active_puff_position()
|
|
== planted_puff_position
|
|
)
|
|
sprint_dust.update_trail(
|
|
0.08,
|
|
Vector3(0.75, 0.0, 0.0),
|
|
Vector3.ZERO,
|
|
false,
|
|
)
|
|
assert(sprint_dust.get_active_puff_count() == 1)
|
|
sprint_dust.clear_trail()
|
|
assert(sprint_dust.get_active_puff_count() == 0)
|
|
sprint_dust.emit_landing_burst(Vector3.ZERO, Vector3.FORWARD)
|
|
assert(sprint_dust.get_active_puff_count() == 5)
|
|
sprint_dust.update_trail(
|
|
0.08,
|
|
Vector3.ZERO,
|
|
Vector3.ZERO,
|
|
false,
|
|
)
|
|
assert(sprint_dust.get_active_puff_count() == 5)
|
|
sprint_dust.clear_trail()
|
|
var service := main.get_node(
|
|
"%NetworkSurfaceDrawingService"
|
|
) as NetworkSurfaceDrawingService
|
|
var game_ui := main.get_node("%GameUI") as GameUI
|
|
var keyboard_mapping := main.get_node(
|
|
"%KeyboardMouseMappingManager"
|
|
) as KeyboardMouseMappingManager
|
|
var toolbar := game_ui.get_node(
|
|
"%SurfaceDrawingToolbar"
|
|
) as SurfaceDrawingToolbar
|
|
var chat_ui := game_ui.get_node("%ChatUI") as ChatUI
|
|
assert(player != null and service != null and toolbar != null)
|
|
assert(chat_ui != null)
|
|
assert(keyboard_mapping != null)
|
|
assert(keyboard_mapping.reset_mapping())
|
|
await process_frame
|
|
var shop_prompt_key := game_ui.get_node("%ShopPromptKey") as Label
|
|
var storage_prompt_message := game_ui.get_node(
|
|
"%StoragePromptMessage"
|
|
) as Label
|
|
assert(shop_prompt_key.text == "E")
|
|
assert(storage_prompt_message.text == "E open storage")
|
|
var remapped_interact := InputEventKey.new()
|
|
remapped_interact.physical_keycode = KEY_F
|
|
remapped_interact.pressed = true
|
|
assert(keyboard_mapping.set_binding(
|
|
KeyboardMouseMappingManager.ROLE_INTERACT,
|
|
keyboard_mapping.binding_from_event(remapped_interact),
|
|
))
|
|
await process_frame
|
|
assert(shop_prompt_key.text == "F")
|
|
assert(storage_prompt_message.text == "F open storage")
|
|
assert(keyboard_mapping.reset_mapping())
|
|
await process_frame
|
|
assert(toolbar.get_parent() == chat_ui.get_parent())
|
|
assert(
|
|
toolbar.get_index() > chat_ui.get_index(),
|
|
"The art-kit toolbar must receive overlapping pointer input before Chat.",
|
|
)
|
|
assert(bool(game_ui.call("_can_start_virtual_mouse")))
|
|
var virtual_pointer_states: Array[bool] = []
|
|
game_ui.virtual_pointer_mode_changed.connect(
|
|
func(active: bool) -> void: virtual_pointer_states.append(active)
|
|
)
|
|
game_ui.call("_begin_virtual_mouse", 0)
|
|
var virtual_cursor := game_ui.get_node(
|
|
"%ControllerVirtualCursor"
|
|
) as ControllerVirtualCursor
|
|
assert(virtual_cursor.visible)
|
|
assert(virtual_pointer_states == [true])
|
|
# PopupMenu subwindows can consume the physical shoulder release. The
|
|
# polled primary-button state must still release the synthetic mouse click.
|
|
game_ui.set("_virtual_mouse_button_mask", MOUSE_BUTTON_MASK_LEFT)
|
|
game_ui.call("_sync_virtual_mouse_primary_button", false)
|
|
assert(int(game_ui.get("_virtual_mouse_button_mask")) == 0)
|
|
assert(not player.is_camera_input_enabled())
|
|
game_ui.call("_end_virtual_mouse")
|
|
assert(not virtual_cursor.visible)
|
|
assert(virtual_pointer_states == [true, false])
|
|
assert(player.is_camera_input_enabled())
|
|
var settings_panel := game_ui.get(
|
|
"_pause_settings_panel"
|
|
) as SettingsPanel
|
|
var chat_dock_selector := settings_panel.get_node(
|
|
"%ChatDockSelector"
|
|
) as OptionButton
|
|
var chat_mode_selector := settings_panel.get_node(
|
|
"%ChatModeSelector"
|
|
) as OptionButton
|
|
var paint_dock_selector := settings_panel.get_node(
|
|
"%PaintDockSelector"
|
|
) as OptionButton
|
|
assert(chat_dock_selector != null)
|
|
assert(chat_mode_selector != null)
|
|
assert(paint_dock_selector != null)
|
|
assert(chat_dock_selector.custom_minimum_size.x == 300.0)
|
|
assert(chat_mode_selector.custom_minimum_size.x == 300.0)
|
|
assert(paint_dock_selector.custom_minimum_size.x == 300.0)
|
|
assert(
|
|
settings_panel.find_children(
|
|
"*", "BubbleButton", true, false
|
|
).is_empty()
|
|
)
|
|
assert(not chat_ui.is_docked_right())
|
|
assert(toolbar.is_docked_right())
|
|
var settings_manager := main.get_node(
|
|
"%PlayerSettingsManager"
|
|
) as PlayerSettingsManager
|
|
assert(settings_manager != null)
|
|
var changed_docks: PlayerSettings = settings_manager.current_settings.copy()
|
|
changed_docks.chat_dock_right = true
|
|
changed_docks.chat_mobile_mode = true
|
|
changed_docks.paint_dock_right = false
|
|
assert(settings_manager.apply_settings(changed_docks))
|
|
await process_frame
|
|
assert(not chat_ui.is_docked_right())
|
|
assert(not chat_ui.is_mobile_mode())
|
|
assert(toolbar.is_docked_right())
|
|
changed_docks.presentation_layout_customized = true
|
|
assert(settings_manager.apply_settings(changed_docks))
|
|
await process_frame
|
|
assert(chat_ui.is_docked_right())
|
|
assert(chat_ui.is_mobile_mode())
|
|
assert(not toolbar.is_docked_right())
|
|
var chat_panel := chat_ui.get_node("ChatPanel") as PanelContainer
|
|
var chat_height_button := chat_ui.get_node("ChatHeightButton") as Button
|
|
var chat_status := chat_ui.find_child(
|
|
"ChatStatus", true, false
|
|
) as Label
|
|
assert(chat_status != null)
|
|
assert(
|
|
chat_status.autowrap_mode
|
|
== TextServer.AUTOWRAP_WORD_SMART
|
|
)
|
|
assert(is_zero_approx(chat_panel.position.y))
|
|
assert(is_equal_approx(chat_panel.size.x, ChatUI.MOBILE_COMPACT_WIDTH))
|
|
assert(not chat_height_button.visible)
|
|
var reloaded_settings := PlayerSettingsManager.new()
|
|
root.add_child(reloaded_settings)
|
|
assert(reloaded_settings.load_settings())
|
|
assert(reloaded_settings.current_settings.chat_dock_right)
|
|
assert(reloaded_settings.current_settings.chat_mobile_mode)
|
|
assert(not reloaded_settings.current_settings.paint_dock_right)
|
|
assert(reloaded_settings.current_settings.presentation_layout_customized)
|
|
reloaded_settings.queue_free()
|
|
var legacy_data: Dictionary = JSON.parse_string(
|
|
FileAccess.get_file_as_string(PlayerSettingsManager.SETTINGS_PATH)
|
|
) as Dictionary
|
|
var legacy_presentation := legacy_data.get("presentation", {}) as Dictionary
|
|
legacy_presentation.erase("layout_customized")
|
|
legacy_data["presentation"] = legacy_presentation
|
|
var legacy_file := FileAccess.open(
|
|
PlayerSettingsManager.SETTINGS_PATH,
|
|
FileAccess.WRITE,
|
|
)
|
|
assert(legacy_file != null)
|
|
legacy_file.store_string(JSON.stringify(legacy_data, "\t"))
|
|
legacy_file.close()
|
|
var migrated_legacy_settings := PlayerSettingsManager.new()
|
|
root.add_child(migrated_legacy_settings)
|
|
assert(migrated_legacy_settings.load_settings())
|
|
assert(not migrated_legacy_settings.current_settings.chat_dock_right)
|
|
assert(not migrated_legacy_settings.current_settings.chat_mobile_mode)
|
|
assert(migrated_legacy_settings.current_settings.paint_dock_right)
|
|
assert(
|
|
not migrated_legacy_settings.current_settings.presentation_layout_customized
|
|
)
|
|
migrated_legacy_settings.queue_free()
|
|
var default_docks: PlayerSettings = settings_manager.current_settings.copy()
|
|
default_docks.chat_dock_right = false
|
|
default_docks.chat_mobile_mode = false
|
|
default_docks.paint_dock_right = true
|
|
assert(settings_manager.apply_settings(default_docks))
|
|
await process_frame
|
|
chat_ui.call(
|
|
"_set_status",
|
|
(
|
|
"Unknown command: /this-command-name-is-intentionally-long-"
|
|
+ "enough-to-require-smart-character-wrapping"
|
|
),
|
|
)
|
|
await process_frame
|
|
assert(is_equal_approx(chat_panel.size.x, ChatUI.PANEL_WIDTH))
|
|
assert(chat_status.size.x <= ChatUI.PANEL_WIDTH)
|
|
chat_ui.call("_set_status", "")
|
|
assert(not chat_ui.is_docked_right())
|
|
assert(not chat_ui.is_mobile_mode())
|
|
assert(toolbar.is_docked_right())
|
|
assert(chat_height_button.visible)
|
|
var select_button := InputEventJoypadButton.new()
|
|
select_button.button_index = JOY_BUTTON_BACK
|
|
select_button.pressed = true
|
|
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
|
|
await process_frame
|
|
assert(chat_ui.is_open())
|
|
assert(chat_panel.mouse_filter == Control.MOUSE_FILTER_STOP)
|
|
var typed_chat_entry := chat_ui.find_child(
|
|
"ChatEntry", true, false
|
|
) as LineEdit
|
|
assert(typed_chat_entry != null)
|
|
assert(not typed_chat_entry.virtual_keyboard_enabled)
|
|
var accept_button := InputEventJoypadButton.new()
|
|
accept_button.button_index = JOY_BUTTON_A
|
|
accept_button.pressed = true
|
|
assert(bool(game_ui.call("_handle_controller_chat_controls", accept_button)))
|
|
var ui_pixelation := main.get("_ui_pixelation") as UIPixelationPresenter
|
|
assert(ui_pixelation != null)
|
|
var on_screen_keyboard := ui_pixelation.get(
|
|
"_on_screen_keyboard"
|
|
) as OnScreenKeyboard
|
|
assert(on_screen_keyboard != null)
|
|
if DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD):
|
|
assert(typed_chat_entry.virtual_keyboard_enabled)
|
|
assert(not on_screen_keyboard.is_open())
|
|
await process_frame
|
|
assert(typed_chat_entry.has_focus())
|
|
else:
|
|
assert(not typed_chat_entry.virtual_keyboard_enabled)
|
|
assert(on_screen_keyboard.is_open())
|
|
|
|
# A bite temporarily owns gameplay input without discarding an in-progress
|
|
# message. The draft and insertion point return only after fishing releases
|
|
# that ownership, and any controller keyboard follows the restored field.
|
|
var chat_fishing_spot := main.get_node("%FishingSpot") as FishingSpot
|
|
assert(chat_fishing_spot != null)
|
|
var interrupted_draft := "finish this message after fishing"
|
|
var interrupted_caret: int = 12
|
|
typed_chat_entry.text = interrupted_draft
|
|
typed_chat_entry.caret_column = interrupted_caret
|
|
chat_fishing_spot.call("_set_fishing_input_priority", true)
|
|
await process_frame
|
|
assert(not chat_ui.is_open())
|
|
assert(not bool(chat_ui.get("_input_lock_applied")))
|
|
assert(chat_ui.has_fishing_resume_pending())
|
|
assert(not on_screen_keyboard.is_open())
|
|
chat_ui.open_chat()
|
|
assert(not chat_ui.is_open())
|
|
chat_fishing_spot.call("_set_fishing_input_priority", false)
|
|
for _frame: int in 4:
|
|
await process_frame
|
|
assert(chat_ui.is_open())
|
|
assert(typed_chat_entry.text == interrupted_draft)
|
|
assert(typed_chat_entry.caret_column == interrupted_caret)
|
|
assert(bool(chat_ui.get("_input_lock_applied")))
|
|
if not DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD):
|
|
assert(on_screen_keyboard.is_open())
|
|
on_screen_keyboard.call("_close_keyboard", true)
|
|
assert(typed_chat_entry.has_focus())
|
|
|
|
# A deferred Standby bite has not started the fishing minigame yet. It must
|
|
# release fishing's input ownership so chat remains available until the
|
|
# player confirms the bite.
|
|
chat_fishing_spot.call("_set_fishing_input_priority", true)
|
|
chat_fishing_spot.call("_set_bite_confirmation_pending", true)
|
|
for _frame: int in 4:
|
|
await process_frame
|
|
assert(not chat_fishing_spot.is_fishing_input_priority_active())
|
|
assert(chat_ui.is_open())
|
|
chat_fishing_spot.call("_set_bite_confirmation_pending", false)
|
|
var left_bumper := InputEventJoypadButton.new()
|
|
left_bumper.button_index = JOY_BUTTON_LEFT_SHOULDER
|
|
left_bumper.pressed = true
|
|
assert(bool(game_ui.call("_handle_controller_chat_controls", left_bumper)))
|
|
assert(not chat_ui.is_open())
|
|
assert(chat_panel.mouse_filter == Control.MOUSE_FILTER_IGNORE)
|
|
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 typed_chat_entry.virtual_keyboard_enabled)
|
|
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
|
|
assert(not chat_ui.is_open())
|
|
assert(chat_ui.is_collapsed())
|
|
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
|
|
assert(chat_ui.is_open())
|
|
chat_ui.refocus_gameplay()
|
|
chat_ui.open_chat()
|
|
assert(chat_ui.is_open())
|
|
typed_chat_entry.text = "validation message"
|
|
chat_ui.call("_send")
|
|
assert(not chat_ui.is_open())
|
|
assert(not typed_chat_entry.has_focus())
|
|
assert(not bool(chat_ui.get("_input_lock_applied")))
|
|
assert(not chat_status.visible)
|
|
assert(chat_status.text.is_empty())
|
|
var quick_menu := game_ui.get_node(
|
|
"%QuickRadialMenu"
|
|
) as QuickRadialMenu
|
|
var emote_menu := game_ui.get_node(
|
|
"%EmoteRadialMenu"
|
|
) as EmoteRadialMenu
|
|
assert(quick_menu != null and emote_menu != null)
|
|
assert(QuickRadialMenu.ACTIONS.size() == QuickRadialMenu.SECTOR_COUNT)
|
|
assert(QuickRadialMenu.ACTIONS == [&"chat", &"freecam", &"hud"])
|
|
game_ui.call("_on_quick_action_selected", &"freecam")
|
|
assert(player.is_free_camera_active())
|
|
var free_camera := player.get_active_gameplay_camera()
|
|
assert(free_camera.name == &"FreeCamera")
|
|
var free_camera_yaw_before: float = free_camera.global_rotation.y
|
|
var mapping_manager := main.get(
|
|
"_controller_mapping_manager"
|
|
) as ControllerMappingManager
|
|
var right_stick_motion := InputEventJoypadMotion.new()
|
|
right_stick_motion.device = mapping_manager.get_active_device_id()
|
|
right_stick_motion.axis = JOY_AXIS_RIGHT_X
|
|
right_stick_motion.axis_value = 0.9
|
|
Input.parse_input_event(right_stick_motion)
|
|
await create_timer(0.1).timeout
|
|
assert(
|
|
absf(angle_difference(
|
|
free_camera.global_rotation.y,
|
|
free_camera_yaw_before,
|
|
)) > 0.02
|
|
)
|
|
right_stick_motion.axis_value = 0.0
|
|
Input.parse_input_event(right_stick_motion)
|
|
await process_frame
|
|
|
|
var free_camera_body := player.get("_free_camera_body") as CharacterBody3D
|
|
assert(free_camera_body != null)
|
|
# A released fishing cast locks only the avatar. The detached camera must
|
|
# continue to accept translation while that movement lock is active.
|
|
player.set_movement_enabled(false)
|
|
var cast_locked_camera_position: Vector3 = free_camera_body.global_position
|
|
Input.action_press("move_forward")
|
|
for _frame: int in 3:
|
|
await physics_frame
|
|
Input.action_release("move_forward")
|
|
assert(not free_camera_body.global_position.is_equal_approx(
|
|
cast_locked_camera_position
|
|
))
|
|
player.set_movement_enabled(true)
|
|
|
|
# Chat owns all movement while text entry is active, including developer
|
|
# freecam movement which reads the same WASD actions as the player.
|
|
chat_ui.open_chat()
|
|
assert(chat_ui.is_open())
|
|
var free_camera_position_before: Vector3 = free_camera_body.global_position
|
|
Input.action_press("move_forward")
|
|
for _frame: int in 3:
|
|
await physics_frame
|
|
Input.action_release("move_forward")
|
|
assert(free_camera_body.global_position.is_equal_approx(
|
|
free_camera_position_before
|
|
))
|
|
chat_ui.refocus_gameplay()
|
|
|
|
# Typewriter speech cycles the shared open-mouth faceplates, then restores
|
|
# the player's authored mouth. World speech also follows the active freecam,
|
|
# not the inactive normal gameplay camera.
|
|
var normal_camera := player.get_gameplay_camera()
|
|
var normal_camera_transform: Transform3D = normal_camera.global_transform
|
|
var chat_anchor: Vector3 = player.get_chat_anchor_position()
|
|
normal_camera.look_at(
|
|
normal_camera.global_position
|
|
- (chat_anchor - normal_camera.global_position),
|
|
Vector3.UP,
|
|
)
|
|
assert(normal_camera.is_position_behind(chat_anchor))
|
|
assert(not free_camera.is_position_behind(chat_anchor))
|
|
var shop_prompt := game_ui.get_node("%ShopPrompt") as Control
|
|
var storage_prompt := game_ui.get_node("%StoragePrompt") as Control
|
|
game_ui.set_shop_prompt_visible(true, chat_anchor)
|
|
game_ui.set_storage_prompt_visible(true, chat_anchor)
|
|
assert(shop_prompt.visible)
|
|
assert(storage_prompt.visible)
|
|
chat_ui.show_local_speech("hello there friend")
|
|
await process_frame
|
|
assert(bool(player.get("_speech_mouth_active")))
|
|
var first_speech_mouth: String = str(player.get("_speech_mouth_id"))
|
|
await create_timer(0.12).timeout
|
|
var second_speech_mouth: String = str(player.get("_speech_mouth_id"))
|
|
assert(not first_speech_mouth.is_empty())
|
|
assert(not second_speech_mouth.is_empty())
|
|
assert(first_speech_mouth != second_speech_mouth)
|
|
chat_ui.call("_update_speech")
|
|
var speech: Dictionary = chat_ui.get("_speech")
|
|
var local_speech: Dictionary = speech.get(session.get_local_peer_id(), {})
|
|
var speech_bubble := local_speech.get("bubble") as PanelContainer
|
|
assert(speech_bubble != null and speech_bubble.visible)
|
|
normal_camera.global_transform = normal_camera_transform
|
|
await create_timer(0.6).timeout
|
|
assert(not bool(player.get("_speech_mouth_active")))
|
|
chat_ui.call("_on_peer_removed", session.get_local_peer_id())
|
|
|
|
game_ui.call("_on_quick_action_selected", &"freecam")
|
|
assert(not player.is_free_camera_active())
|
|
game_ui.call("_on_quick_action_selected", &"hud")
|
|
await process_frame
|
|
assert(game_ui.is_gameplay_hud_hidden())
|
|
assert(chat_ui.is_hud_hidden())
|
|
assert(not (
|
|
game_ui.get_node("%GameplayTransientHUD") as Control
|
|
).visible)
|
|
assert(not (
|
|
game_ui.get_node("%ExperiencePresentation") as Control
|
|
).visible)
|
|
var hud_fishing_spot := main.get_node("%FishingSpot") as FishingSpot
|
|
assert(hud_fishing_spot != null)
|
|
hud_fishing_spot.state = FishingSpot.FishingState.SHOWING_CATCH
|
|
game_ui.call("_refresh_gameplay_hud_visibility")
|
|
assert((
|
|
game_ui.get_node("%GameplayTransientHUD") as Control
|
|
).visible)
|
|
assert(not (
|
|
game_ui.get_node("%ExperiencePresentation") as Control
|
|
).visible)
|
|
assert(chat_ui.is_hud_hidden())
|
|
hud_fishing_spot.state = FishingSpot.FishingState.READY
|
|
game_ui.call("_refresh_gameplay_hud_visibility")
|
|
assert(not (
|
|
game_ui.get_node("%GameplayTransientHUD") as Control
|
|
).visible)
|
|
quick_menu.open_menu(true)
|
|
assert(quick_menu.visible and quick_menu.is_open())
|
|
quick_menu.close_menu()
|
|
emote_menu.open_menu(true)
|
|
assert(emote_menu.visible and emote_menu.is_open())
|
|
emote_menu.close_menu()
|
|
chat_ui.open_chat()
|
|
await process_frame
|
|
assert(chat_ui.is_open() and chat_panel.visible)
|
|
chat_ui.refocus_gameplay()
|
|
assert(chat_ui.is_hud_hidden())
|
|
var cancel_button := InputEventJoypadButton.new()
|
|
cancel_button.button_index = JOY_BUTTON_B
|
|
cancel_button.pressed = true
|
|
assert(not bool(main.call("_is_pause_open_request", cancel_button)))
|
|
var start_button := InputEventJoypadButton.new()
|
|
start_button.button_index = JOY_BUTTON_START
|
|
start_button.pressed = true
|
|
assert(bool(main.call("_is_pause_open_request", start_button)))
|
|
await _validate_pause_browser_transition(game_ui, player)
|
|
var player_menu := game_ui.get_node("%PlayerMenu") as PlayerMenu
|
|
player_menu.open_section(PlayerMenu.Section.PROFILE)
|
|
for _frame: int in 12:
|
|
await process_frame
|
|
assert(game_ui.is_gameplay_hud_hidden())
|
|
assert(player_menu.visible)
|
|
assert(root.gui_get_focus_owner() is not LineEdit)
|
|
player_menu.close_menu(PlayerMenu.CloseReason.SESSION_END)
|
|
await process_frame
|
|
game_ui.call("_on_quick_action_selected", &"hud")
|
|
await process_frame
|
|
assert(not game_ui.is_gameplay_hud_hidden())
|
|
assert(not chat_ui.is_hud_hidden())
|
|
assert(not service.can_activate())
|
|
assert(not service.is_active() and not toolbar.visible)
|
|
assert(player.bag.add_item(ArtShopStock.ART_KIT_ITEM_ID, 1))
|
|
player.hotbar.select_slot(1)
|
|
assert(player.hotbar.assign_item(0, ArtShopStock.ART_KIT_ITEM_ID))
|
|
assert(not service.can_activate())
|
|
var paint_key := InputEventKey.new()
|
|
paint_key.physical_keycode = KEY_P
|
|
paint_key.pressed = true
|
|
assert(not service.handle_input(paint_key, true))
|
|
assert(not service.is_active())
|
|
player.hotbar.select_slot(0)
|
|
await process_frame
|
|
assert(service.is_active() and not service.is_placement_mode())
|
|
assert(toolbar.visible)
|
|
chat_ui.open_chat()
|
|
assert(chat_ui.is_open())
|
|
assert(not service.is_active() and not toolbar.visible)
|
|
chat_ui.close_chat()
|
|
assert(not chat_ui.is_open())
|
|
assert(service.is_active() and toolbar.visible)
|
|
var ui_root := game_ui.get_node("%UIRoot") as Control
|
|
assert(toolbar.get_parent() == ui_root)
|
|
assert(is_zero_approx(toolbar.position.y))
|
|
assert(
|
|
is_equal_approx(
|
|
toolbar.get_global_rect().end.x,
|
|
ui_root.get_global_rect().end.x,
|
|
)
|
|
)
|
|
assert(toolbar.get_global_rect().end.x <= 1280.0)
|
|
assert(toolbar.get_global_rect().end.y <= 720.0)
|
|
var top_panel := toolbar.get_node("%TopPanel") as PanelContainer
|
|
var color_panel := toolbar.get_node("%ColorPanel") as PanelContainer
|
|
assert(is_equal_approx(toolbar.size.x, SurfaceDrawingToolbar.TOOLBAR_WIDTH))
|
|
assert(is_equal_approx(top_panel.size.x, SurfaceDrawingToolbar.TOOLBAR_WIDTH))
|
|
assert(top_panel.position.is_equal_approx(Vector2.ZERO))
|
|
assert(color_panel.position.x > 0.0)
|
|
assert(is_equal_approx(color_panel.get_rect().end.x, top_panel.get_rect().end.x))
|
|
assert(color_panel.position.y < top_panel.get_rect().end.y)
|
|
assert(color_panel.get_rect().end.y > top_panel.get_rect().end.y)
|
|
var toolbar_origin: Vector2 = toolbar.get_global_rect().position
|
|
var top_pointer := InputEventMouseMotion.new()
|
|
top_pointer.position = toolbar_origin + Vector2(200.0, 24.0)
|
|
assert(toolbar.owns_pointer_event(top_pointer))
|
|
var rail_pointer := InputEventMouseMotion.new()
|
|
rail_pointer.position = color_panel.get_global_rect().get_center()
|
|
assert(toolbar.owns_pointer_event(rail_pointer))
|
|
var empty_pointer := InputEventMouseMotion.new()
|
|
empty_pointer.position = toolbar_origin + Vector2(200.0, 180.0)
|
|
assert(not toolbar.owns_pointer_event(empty_pointer))
|
|
|
|
var brush_option := toolbar.get_node("%BrushOption") as OptionButton
|
|
var grid_option := toolbar.get_node("%GridOption") as OptionButton
|
|
var mode_button := toolbar.get_node("%ModeButton") as Button
|
|
var eraser_button := toolbar.get_node("%EraserButton") as Button
|
|
var undo_button := toolbar.get_node("%UndoButton") as Button
|
|
var hide_button := toolbar.get_node("%HideGuideButton") as Button
|
|
var restore_button := toolbar.get_node("%RestoreGuideButton") as Button
|
|
var finalize_button := toolbar.get_node("%FinalizeGuideButton") as Button
|
|
var export_button := toolbar.get_node("%ExportButton") as Button
|
|
var stamp_button := toolbar.get_node("%StampButton") as Button
|
|
assert(
|
|
mode_button != null
|
|
and eraser_button != null
|
|
and undo_button != null
|
|
and hide_button != null
|
|
and restore_button != null
|
|
and finalize_button != null
|
|
and export_button != null
|
|
and stamp_button != null
|
|
)
|
|
assert(export_button.text == "png")
|
|
assert(export_button.tooltip_text == "aim at artwork, then click to export PNG")
|
|
assert(stamp_button.text == "stamp")
|
|
assert(stamp_button.disabled)
|
|
assert(toolbar.get_node_or_null("%CloseButton") == null)
|
|
var expected_toolbar_icons: Dictionary[Button, String] = {
|
|
mode_button: "/art/art_kit_marker.png",
|
|
eraser_button: "/art/art_kit_eraser.png",
|
|
hide_button: "/art/art_kit_grid_hidden_light.png",
|
|
restore_button: "/art/art_kit_grid_restore_light.png",
|
|
finalize_button: "/art/art_kit_grid_finish_light.png",
|
|
}
|
|
for icon_button: Button in expected_toolbar_icons:
|
|
assert(icon_button.text.is_empty())
|
|
assert(icon_button.icon != null)
|
|
assert(
|
|
icon_button.icon.resource_path.ends_with(
|
|
expected_toolbar_icons[icon_button]
|
|
)
|
|
)
|
|
for icon_button: Button in [
|
|
eraser_button,
|
|
undo_button,
|
|
hide_button,
|
|
restore_button,
|
|
finalize_button,
|
|
export_button,
|
|
stamp_button,
|
|
]:
|
|
assert(icon_button.custom_minimum_size == Vector2(48, 44))
|
|
assert(icon_button.get_theme_constant("icon_max_width") == 40)
|
|
var icon_style := (
|
|
icon_button.get_theme_stylebox("normal") as StyleBoxFlat
|
|
)
|
|
assert(is_equal_approx(icon_style.content_margin_left, 4.0))
|
|
assert(is_equal_approx(icon_style.content_margin_top, 2.0))
|
|
assert(mode_button.custom_minimum_size == Vector2(48, 48))
|
|
assert(mode_button.get_theme_constant("icon_max_width") == 40)
|
|
assert(export_button.get_index() > finalize_button.get_index())
|
|
assert(stamp_button.get_index() > export_button.get_index())
|
|
assert(mode_button.get_index() > stamp_button.get_index())
|
|
assert(hide_button.button_group != null)
|
|
assert(hide_button.button_group == restore_button.button_group)
|
|
assert(hide_button.button_group == finalize_button.button_group)
|
|
assert(hide_button.button_group.allow_unpress)
|
|
assert(brush_option.item_count == 4)
|
|
assert(grid_option.item_count == 4)
|
|
for pointer_only_control: Control in [
|
|
brush_option,
|
|
grid_option,
|
|
mode_button,
|
|
eraser_button,
|
|
undo_button,
|
|
hide_button,
|
|
restore_button,
|
|
finalize_button,
|
|
export_button,
|
|
stamp_button,
|
|
]:
|
|
assert(pointer_only_control.focus_mode == Control.FOCUS_NONE)
|
|
assert(brush_option.get_popup().unfocusable)
|
|
assert(grid_option.get_popup().unfocusable)
|
|
brush_option.get_popup().popup(Rect2i(100, 100, 220, 220))
|
|
await process_frame
|
|
game_ui.call("_begin_virtual_mouse", 0)
|
|
game_ui.call("_update_virtual_cursor_position", Vector2(160.0, 160.0))
|
|
var brush_popup_cursor := toolbar.get(
|
|
"_brush_popup_cursor"
|
|
) as ControllerVirtualCursor
|
|
assert(brush_popup_cursor != null and brush_popup_cursor.visible)
|
|
assert(brush_popup_cursor.get_parent() == brush_option.get_popup())
|
|
assert(brush_popup_cursor.z_index == RenderingServer.CANVAS_ITEM_Z_MAX)
|
|
assert(not virtual_cursor.visible)
|
|
brush_option.get_popup().hide()
|
|
game_ui.call("_update_virtual_cursor_position", Vector2(160.0, 160.0))
|
|
assert(not brush_popup_cursor.visible)
|
|
assert(virtual_cursor.visible)
|
|
game_ui.call("_end_virtual_mouse")
|
|
var expected_brush_icons: Array[String] = [
|
|
"art_kit_marker_tip_fine.png",
|
|
"art_kit_marker_tip_thin.png",
|
|
"art_kit_marker_tip_mid.png",
|
|
"art_kit_marker_tip_thick.png",
|
|
]
|
|
var expected_grid_icons: Array[String] = [
|
|
"art_kit_grid_small_light.png",
|
|
"art_kit_grid_medium_light.png",
|
|
"art_kit_grid_large_light.png",
|
|
"art_kit_grid_xl_light.png",
|
|
]
|
|
for index: int in 4:
|
|
assert(brush_option.get_item_text(index).is_empty())
|
|
assert(grid_option.get_item_text(index).is_empty())
|
|
assert(brush_option.get_item_icon(index) != null)
|
|
assert(grid_option.get_item_icon(index) != null)
|
|
var brush_unlocked: bool = index == 0
|
|
var grid_unlocked: bool = index == 0
|
|
if brush_unlocked:
|
|
assert(
|
|
str(brush_option.get_item_icon(index).get_meta(
|
|
&"channel_mask_source", ""
|
|
)).ends_with(
|
|
expected_brush_icons[index]
|
|
)
|
|
)
|
|
else:
|
|
assert(bool(brush_option.get_item_icon(index).get_meta(
|
|
&"locked_content_icon", false
|
|
)))
|
|
if grid_unlocked:
|
|
assert(
|
|
grid_option.get_item_icon(index).resource_path.ends_with(
|
|
expected_grid_icons[index]
|
|
)
|
|
)
|
|
else:
|
|
assert(bool(grid_option.get_item_icon(index).get_meta(
|
|
&"locked_content_icon", false
|
|
)))
|
|
var expected_lock_alpha: float = 1.0 if index == 0 else 0.72
|
|
assert(is_equal_approx(
|
|
brush_option.get_popup().get_item_icon_modulate(index).a,
|
|
expected_lock_alpha,
|
|
))
|
|
assert(is_equal_approx(
|
|
grid_option.get_popup().get_item_icon_modulate(index).a,
|
|
expected_lock_alpha,
|
|
))
|
|
assert(not brush_option.get_popup().is_item_disabled(0))
|
|
assert(brush_option.get_popup().is_item_disabled(1))
|
|
assert(not grid_option.get_popup().is_item_disabled(0))
|
|
assert(grid_option.get_popup().is_item_disabled(1))
|
|
var color_buttons: Dictionary = toolbar.get("_color_buttons")
|
|
assert(color_buttons.size() == SurfaceDrawingPalette.COLORS.size())
|
|
for color_button: Button in color_buttons.values():
|
|
assert(color_button.focus_mode == Control.FOCUS_NONE)
|
|
assert(not (color_buttons[&"chalk_white"] as Button).disabled)
|
|
assert((color_buttons[&"ocean_teal"] as Button).disabled)
|
|
var base_color_lock := (
|
|
(color_buttons[&"chalk_white"] as Button).get_node(
|
|
"UnlockStateIcon"
|
|
) as TextureRect
|
|
)
|
|
var locked_color_lock := (
|
|
(color_buttons[&"ocean_teal"] as Button).get_node(
|
|
"UnlockStateIcon"
|
|
) as TextureRect
|
|
)
|
|
assert(not base_color_lock.visible)
|
|
assert(locked_color_lock.visible)
|
|
assert(locked_color_lock.size == Vector2(18.0, 18.0))
|
|
assert(is_equal_approx(locked_color_lock.modulate.a, 0.34))
|
|
var popup_lock_icon := toolbar.get("_popup_lock_icon") as Texture2D
|
|
assert(popup_lock_icon.get_width() == 40)
|
|
assert(popup_lock_icon.get_height() == 40)
|
|
assert(int(popup_lock_icon.get_meta(
|
|
&"locked_content_icon_size", 0
|
|
)) == 24)
|
|
|
|
for product_id: StringName in [
|
|
&"marker_ocean_teal",
|
|
&"brush_4x",
|
|
&"grid_128x",
|
|
PlayerArtUnlocks.STAMP_PRODUCT_ID,
|
|
]:
|
|
assert(player.art_unlocks.unlock_product(product_id))
|
|
await process_frame
|
|
assert(not (color_buttons[&"ocean_teal"] as Button).disabled)
|
|
assert(not brush_option.get_popup().is_item_disabled(3))
|
|
assert(not grid_option.get_popup().is_item_disabled(3))
|
|
assert(not stamp_button.disabled)
|
|
var stamp_library_panel := toolbar.get_node(
|
|
"%StampLibraryPanel"
|
|
) as PanelContainer
|
|
var stamp_library_close := toolbar.get_node(
|
|
"%StampLibraryClose"
|
|
) as Button
|
|
assert(stamp_library_panel != null and not stamp_library_panel.visible)
|
|
stamp_button.pressed.emit()
|
|
assert(stamp_library_panel.visible)
|
|
var library_pointer := InputEventMouseMotion.new()
|
|
library_pointer.position = stamp_library_panel.get_global_rect().get_center()
|
|
assert(toolbar.owns_pointer_event(library_pointer))
|
|
stamp_library_close.pressed.emit()
|
|
assert(not stamp_library_panel.visible)
|
|
assert(not locked_color_lock.visible)
|
|
assert(str(brush_option.get_item_icon(3).get_meta(
|
|
&"channel_mask_source", ""
|
|
)).ends_with(expected_brush_icons[3]))
|
|
assert(grid_option.get_item_icon(3).resource_path.ends_with(
|
|
expected_grid_icons[3]
|
|
))
|
|
assert(is_equal_approx(
|
|
brush_option.get_popup().get_item_icon_modulate(3).a, 1.0
|
|
))
|
|
assert(is_equal_approx(
|
|
grid_option.get_popup().get_item_icon_modulate(3).a, 1.0
|
|
))
|
|
assert(service.set_color_id(&"ocean_teal"))
|
|
assert(service.set_brush_size(4))
|
|
assert(service.set_grid_size(128))
|
|
assert(service.get_color_id() == &"ocean_teal")
|
|
assert(service.get_brush_size() == 4)
|
|
assert(service.get_grid_size() == 128)
|
|
var marker_mode_material := mode_button.material as ShaderMaterial
|
|
assert(marker_mode_material != null)
|
|
assert(brush_option.material == null)
|
|
assert(grid_option.material == null)
|
|
assert(
|
|
marker_mode_material.shader.resource_path.ends_with(
|
|
"/ui/art_kit_marker_icon.gdshader"
|
|
)
|
|
)
|
|
assert(
|
|
marker_mode_material.get_shader_parameter("marker_color")
|
|
== SurfaceDrawingPalette.get_color(&"ocean_teal")
|
|
)
|
|
|
|
mode_button.pressed.emit()
|
|
assert(service.is_placement_mode())
|
|
assert(
|
|
mode_button.icon.resource_path.ends_with(
|
|
"/art/art_kit_grid_light.png"
|
|
)
|
|
)
|
|
eraser_button.pressed.emit()
|
|
assert(service.is_eraser_mode())
|
|
assert(eraser_button.button_pressed)
|
|
hide_button.pressed.emit()
|
|
assert(service.is_placement_mode())
|
|
assert(not service.is_eraser_mode())
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.HIDE
|
|
)
|
|
var world_click := InputEventMouseButton.new()
|
|
world_click.button_index = MOUSE_BUTTON_LEFT
|
|
world_click.pressed = true
|
|
assert(service.handle_input(world_click, true))
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.HIDE
|
|
)
|
|
assert(service.is_placement_mode())
|
|
assert(not service.is_eraser_mode())
|
|
assert(hide_button.button_pressed)
|
|
hide_button.pressed.emit()
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.NONE
|
|
)
|
|
assert(not service.is_placement_mode() and service.is_eraser_mode())
|
|
|
|
restore_button.pressed.emit()
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.RESTORE
|
|
)
|
|
finalize_button.pressed.emit()
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.FINALIZE
|
|
)
|
|
assert(not restore_button.button_pressed)
|
|
assert(finalize_button.button_pressed)
|
|
assert(service.handle_input(world_click, true))
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.FINALIZE
|
|
)
|
|
finalize_button.pressed.emit()
|
|
assert(
|
|
service.get_armed_guide_action()
|
|
== NetworkSurfaceDrawingService.GuideAction.NONE
|
|
)
|
|
assert(not service.is_placement_mode() and service.is_eraser_mode())
|
|
(color_buttons[&"ocean_teal"] as Button).pressed.emit()
|
|
assert(not service.is_eraser_mode())
|
|
undo_button.pressed.emit()
|
|
assert(service.is_active())
|
|
assert(not service.handle_input(paint_key, true))
|
|
assert(service.is_active() and toolbar.visible)
|
|
var fishing_spot := main.get_node("%FishingSpot") as FishingSpot
|
|
await process_frame
|
|
var held_art_kit_display := player.get("_held_art_kit_display") as Node3D
|
|
var held_art_kit_sprite := player.get("_held_art_kit_sprite") as Sprite3D
|
|
var pocket_animation_player := player.get(
|
|
"_character_animation_player"
|
|
) as AnimationPlayer
|
|
var pocket_animation_name: StringName = player.get(
|
|
"_pocket_visual_animation"
|
|
)
|
|
if (
|
|
not held_art_kit_display.visible
|
|
and pocket_animation_player != null
|
|
and not pocket_animation_name.is_empty()
|
|
and pocket_animation_player.has_animation(pocket_animation_name)
|
|
):
|
|
var pocket_animation: Animation = (
|
|
pocket_animation_player.get_animation(pocket_animation_name)
|
|
)
|
|
await create_timer(pocket_animation.length * 0.55).timeout
|
|
assert(held_art_kit_display.visible)
|
|
assert(held_art_kit_sprite.visible and held_art_kit_sprite.texture != null)
|
|
assert(held_art_kit_sprite.texture.resource_path.ends_with("/art/art_kit.png"))
|
|
assert(is_zero_approx(angle_difference(
|
|
held_art_kit_sprite.rotation.z,
|
|
-PI * 0.5,
|
|
)))
|
|
assert(held_art_kit_sprite.flip_h)
|
|
assert(not fishing_spot.has_signal(&"art_ui_toggle_requested"))
|
|
assert(service.is_active() and toolbar.visible)
|
|
assert(service.get_grid_size() == 128)
|
|
assert(service.handle_input(world_click, true))
|
|
assert(service.is_active() and toolbar.visible)
|
|
var hotbar_ui := game_ui.get_node("%Hotbar") as HotbarUI
|
|
var wheel_down := InputEventMouseButton.new()
|
|
wheel_down.button_index = MOUSE_BUTTON_WHEEL_DOWN
|
|
wheel_down.pressed = true
|
|
assert(not service.handle_input(wheel_down, true))
|
|
hotbar_ui._unhandled_input(wheel_down)
|
|
await process_frame
|
|
assert(player.hotbar.get_selected_slot() == 1)
|
|
assert(not service.is_active() and not toolbar.visible)
|
|
hotbar_ui.set_swap_hotbar_camera_scroll(true)
|
|
hotbar_ui._unhandled_input(wheel_down)
|
|
await process_frame
|
|
assert(player.hotbar.get_selected_slot() == 1)
|
|
var shifted_wheel_down := InputEventMouseButton.new()
|
|
shifted_wheel_down.button_index = MOUSE_BUTTON_WHEEL_DOWN
|
|
shifted_wheel_down.pressed = true
|
|
shifted_wheel_down.shift_pressed = true
|
|
hotbar_ui._unhandled_input(shifted_wheel_down)
|
|
await process_frame
|
|
assert(player.hotbar.get_selected_slot() == 2)
|
|
hotbar_ui.set_swap_hotbar_camera_scroll(false)
|
|
|
|
var number_one := InputEventKey.new()
|
|
number_one.physical_keycode = KEY_1
|
|
number_one.pressed = true
|
|
assert(not service.handle_input(number_one, true))
|
|
hotbar_ui._unhandled_input(number_one)
|
|
await process_frame
|
|
assert(service.is_active() and toolbar.visible)
|
|
assert(player.hotbar.get_selected_slot() == 0)
|
|
var number_two := InputEventKey.new()
|
|
number_two.physical_keycode = KEY_2
|
|
number_two.pressed = true
|
|
assert(not service.handle_input(number_two, true))
|
|
hotbar_ui._unhandled_input(number_two)
|
|
await process_frame
|
|
assert(player.hotbar.get_selected_slot() == 1)
|
|
assert(not service.is_active() and not toolbar.visible)
|
|
|
|
print("Art tools validation: PASS")
|
|
session.disconnect_session("")
|
|
main.queue_free()
|
|
for _frame: int in 4:
|
|
await process_frame
|
|
await create_timer(0.1).timeout
|
|
quit()
|
|
|
|
|
|
func _validate_pause_browser_transition(
|
|
game_ui: GameUI,
|
|
player: Player,
|
|
) -> void:
|
|
var pause_menu := game_ui.get_pause_menu()
|
|
var root_page := pause_menu.get_node("%RootPage") as Control
|
|
var join_page := pause_menu.get_node("%JoinGamePage") as JoinGamePage
|
|
pause_menu.open_menu()
|
|
while bool(pause_menu.get("_root_transition_active")):
|
|
await process_frame
|
|
assert(pause_menu.visible and root_page.visible)
|
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
|
|
|
pause_menu.call("_open_join_game")
|
|
while not join_page.visible:
|
|
await process_frame
|
|
assert(bool(pause_menu.get("_root_transition_active")))
|
|
pause_menu.call("_close_join_game")
|
|
assert(not join_page.visible)
|
|
# This is the issue #73 input: Escape arrives while the root is returning.
|
|
assert(pause_menu.handle_escape())
|
|
while bool(pause_menu.get("_root_transition_active")):
|
|
await process_frame
|
|
assert(pause_menu.visible and root_page.visible)
|
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
|
|
|
assert(pause_menu.handle_escape())
|
|
while pause_menu.visible:
|
|
await process_frame
|
|
assert(player.is_movement_enabled())
|
|
|
|
# Fishing can complete while pause owns local input. Closing pause must only
|
|
# release its own lock, not restore the stale movement value from open time.
|
|
player.set_movement_enabled(false)
|
|
pause_menu.open_menu()
|
|
while bool(pause_menu.get("_root_transition_active")):
|
|
await process_frame
|
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
|
player.set_movement_enabled(true)
|
|
assert(player.is_movement_enabled())
|
|
assert(not bool(player.call("_is_movement_input_enabled")))
|
|
pause_menu.close_menu(PauseMenu.CloseReason.USER_RETURN)
|
|
assert(player.is_movement_enabled())
|
|
assert(bool(player.call("_is_movement_input_enabled")))
|
|
|
|
# Opening pause during a right-click camera drag must not snapshot and later
|
|
# restore the drag's transient captured mouse mode after the drag is cancelled.
|
|
assert(
|
|
pause_menu.call(
|
|
"_get_restorable_mouse_mode",
|
|
Input.MOUSE_MODE_CAPTURED,
|
|
) == Input.MOUSE_MODE_VISIBLE
|
|
)
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
var drag_press := InputEventMouseButton.new()
|
|
drag_press.button_index = MOUSE_BUTTON_RIGHT
|
|
drag_press.button_mask = MOUSE_BUTTON_MASK_RIGHT
|
|
drag_press.pressed = true
|
|
player.call("_input", drag_press)
|
|
assert(bool(player.get("_camera_dragging")))
|
|
var camera_yaw := player.get_node("%CameraYaw") as Node3D
|
|
var yaw_before_drag: float = camera_yaw.rotation.y
|
|
var drag_motion := InputEventMouseMotion.new()
|
|
drag_motion.button_mask = MOUSE_BUTTON_MASK_RIGHT
|
|
drag_motion.relative = Vector2(24.0, -8.0)
|
|
drag_motion.screen_relative = drag_motion.relative
|
|
player.call("_input", drag_motion)
|
|
assert(not is_equal_approx(camera_yaw.rotation.y, yaw_before_drag))
|
|
await process_frame
|
|
assert(bool(player.get("_camera_dragging")))
|
|
if DisplayServer.get_name() != "headless":
|
|
assert(Input.mouse_mode == Input.MOUSE_MODE_CAPTURED)
|
|
var drag_release := InputEventMouseButton.new()
|
|
drag_release.button_index = MOUSE_BUTTON_RIGHT
|
|
drag_release.button_mask = 0
|
|
drag_release.pressed = false
|
|
player.call("_input", drag_release)
|
|
assert(not bool(player.get("_camera_dragging")))
|
|
player.call("_input", drag_press)
|
|
assert(bool(player.get("_camera_dragging")))
|
|
pause_menu.open_menu()
|
|
while bool(pause_menu.get("_root_transition_active")):
|
|
await process_frame
|
|
assert(not bool(player.get("_camera_dragging")))
|
|
assert(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE)
|
|
pause_menu.close_menu(PauseMenu.CloseReason.USER_RETURN)
|
|
assert(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE)
|