Fix fishing and chat interaction regressions

This commit is contained in:
Alexander Sellite 2026-08-21 12:29:51 -04:00
parent 66ae8c8be7
commit e4ddf4e5a6
11 changed files with 256 additions and 43 deletions

View file

@ -27,6 +27,7 @@ func _run() -> void:
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)
@ -310,6 +311,55 @@ func _run() -> void:
right_stick_motion.axis_value = 0.0
Input.parse_input_event(right_stick_motion)
await process_frame
# Chat owns all movement while text entry is active, including developer
# freecam movement which reads the same WASD actions as the player.
var free_camera_body := player.get("_free_camera_body") as CharacterBody3D
assert(free_camera_body != null)
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))
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")
@ -754,7 +804,6 @@ func _run() -> void:
assert(not service.is_active() and not toolbar.visible)
print("Art tools validation: PASS")
var session := main.get_node("%NetworkSession") as NetworkSession
session.disconnect_session("")
main.queue_free()
for _frame: int in 4:

View file

@ -35,10 +35,17 @@ func _run() -> void:
var fishing_status := game_ui.get_node("%StatusLabel") as Label
var fishing_panel := game_ui.get_node("%FishingPanel") as PanelContainer
var player := main.get("_player") as Player
var item_catalog := main.get("item_catalog") as ItemCatalog
assert(session.is_host())
assert(not session.is_open_host())
assert(service != null and fishing_spot != null and player != null)
assert(player.hotbar.get_selected_item_id() == &"basic_fishing_rod")
var worms: ItemData = item_catalog.get_item_by_id(&"worms")
assert(worms != null)
if player.bag.get_quantity(&"worms") == 0:
assert(player.bag.add_item(&"worms", 2))
assert(player.equip_bait(worms))
var bait_quantity_before: int = player.bag.get_quantity(&"worms")
var fresh_root := main.get_node(
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/FreshWaterBodies"
) as Node3D
@ -90,6 +97,7 @@ func _run() -> void:
await process_frame
assert(fishing_spot.state == FishingSpotType.FishingState.WAITING_FOR_BITE)
assert(service.has_local_attempt())
assert(player.bag.get_quantity(&"worms") == bait_quantity_before)
assert(fishing_status.text.is_empty())
assert(not fishing_status.visible)
assert(not fishing_panel.visible)
@ -124,6 +132,7 @@ func _run() -> void:
assert(fishing_spot.state == FishingSpotType.FishingState.READY)
assert(player.is_movement_enabled())
assert(not bobber.visible)
assert(player.bag.get_quantity(&"worms") == bait_quantity_before)
# A private host rolls and retains the authoritative catch before the fight
# so its quality selects the barrier band clients receive in snapshots.
@ -141,9 +150,11 @@ func _run() -> void:
attempts = service.get("_attempts")
attempt = attempts.get(session.get_local_peer_id())
assert(attempt != null)
assert(player.bag.get_quantity(&"worms") == bait_quantity_before)
service.call("_start_bite", attempt)
await process_frame
assert(attempt.phase == NetworkFishingAttempt.Phase.FIGHTING)
assert(player.bag.get_quantity(&"worms") == bait_quantity_before - 1)
var catalog: FishPool = main.get("fish_catalog") as FishPool
assert(catalog != null)
var fish: FishData = catalog.get_fish_by_id(attempt.fish_id)
@ -218,6 +229,29 @@ func _run() -> void:
assert(fishing_spot.can_change_hotbar_selection())
assert(fishing_spot.can_open_fishing_shop())
# Escape can put the rod into RETURNING immediately before deep-water
# recovery begins. Recovery must finish that return before snapshotting its
# movement state, otherwise the respawn restores a permanent cast lock.
var water_recovery := main.get_node("%WaterRecovery") as WaterRecoveryController
assert(water_recovery != null)
var pause_menu: PauseMenu = game_ui.get_pause_menu()
pause_menu.open_menu()
assert(pause_menu.visible)
player.set_movement_enabled(false)
fishing_spot.set("_active_player", player)
fishing_spot.state = FishingSpotType.FishingState.RETURNING
water_recovery.call(
"_on_recovery_requested",
player,
player.global_position.y,
)
assert(fishing_spot.state == FishingSpotType.FishingState.READY)
assert(bool(water_recovery.get("_prior_movement_enabled")))
assert(bool(water_recovery.get("_prior_camera_input_enabled")))
assert(not pause_menu.visible)
water_recovery.call("_finish_recovery")
assert(player.is_movement_enabled())
print("Fishing authority validation: PASS")
session.disconnect_session("")
main.queue_free()

View file

@ -302,7 +302,9 @@ func _run_client() -> void:
]
)
assert(service.has_local_attempt())
assert(player.bag.get_quantity(&"worms") == worms_before_cast - 1)
# Bait remains available while the bobber is waiting. It is consumed only
# once the authoritative bite begins.
assert(player.bag.get_quantity(&"worms") == worms_before_cast)
var fishing_deadline: int = Time.get_ticks_msec() + 5000
while (
Time.get_ticks_msec() < fishing_deadline

View file

@ -59,6 +59,11 @@ func _run() -> void:
assert(fishing_spot.present_external_catch(crab_catch))
assert(fishing_spot.state == FishingSpot.FishingState.SHOWING_CATCH)
assert(not player.is_movement_enabled())
# Network attempt cleanup may restore the base movement flag before the
# presentation finishes. The showcase owns a separate local-input lock.
player.set_movement_enabled(true)
assert(not bool(player.call("_is_movement_input_enabled")))
assert(not bool(player.call("_is_camera_input_enabled")))
Input.action_release("fish_primary")
await process_frame
assert(not bool(fishing_spot.get("_put_away_press_armed")))
@ -85,6 +90,8 @@ func _run() -> void:
await process_frame
assert(fishing_spot.state == FishingSpot.FishingState.READY)
assert(player.is_movement_enabled())
assert(bool(player.call("_is_movement_input_enabled")))
assert(bool(player.call("_is_camera_input_enabled")))
assert(player.inventory.contains_catch_id(crab_catch.catch_id))
assert(bool(player.get("_active_item_is_net")))
print("Gathering showcase validation: PASS")

View file

@ -181,6 +181,20 @@ func _run() -> void:
catch_profile.barrier_count_min = 0
catch_profile.barrier_count_max = 0
catch_controller.start_encounter(catch_profile, 1.0, 1)
var alternate_echo := InputEventKey.new()
alternate_echo.physical_keycode = KEY_QUOTELEFT
alternate_echo.pressed = true
alternate_echo.echo = true
assert(alternate_echo.is_action(&"reel_alternate"))
fishing_spot._unhandled_input(alternate_echo)
assert(not bool(catch_controller.get("_reel_input_held")))
var alternate_key_press := InputEventKey.new()
alternate_key_press.physical_keycode = KEY_QUOTELEFT
alternate_key_press.pressed = true
assert(alternate_key_press.is_action(&"reel_alternate"))
fishing_spot._unhandled_input(alternate_key_press)
assert(bool(catch_controller.get("_reel_input_held")))
catch_controller.set_reel_input(false)
var alternate_press := InputEventAction.new()
alternate_press.action = &"reel_alternate"
alternate_press.pressed = true