From cd00f3c55eab6d0c21d3fba98a19fea722842d2d Mon Sep 17 00:00:00 2001 From: Voyager Date: Tue, 1 Sep 2026 17:30:04 -0400 Subject: [PATCH] Restore remote locomotion after fishing returns --- fishing/remote_fishing_presentation.gd | 17 +++++++++++++++ tests/fishing_multiplayer_validation.gd | 12 ++++++++++ tests/fishing_surface_validation.gd | 29 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/fishing/remote_fishing_presentation.gd b/fishing/remote_fishing_presentation.gd index 380afdf..d813503 100644 --- a/fishing/remote_fishing_presentation.gd +++ b/fishing/remote_fishing_presentation.gd @@ -57,6 +57,10 @@ func show_cast( ): return _kill_cast_tween() + # A previous attempt can finish its fighting presentation immediately before + # a replacement cast is reconstructed. Reset the owning avatar as well as our + # local bookkeeping so fighting cannot continue to outrank the release pose. + _owner.set_fighting_visual(false) _active = true _attempt_id = attempt_id _observer_fighting_active = false @@ -256,6 +260,7 @@ func _on_return_finished() -> void: _showcase_tween.tween_interval(2.5) _showcase_tween.finished.connect(_on_showcase_finished) return + _restore_owner_after_return() return_completed.emit() @@ -271,6 +276,18 @@ func _on_showcase_put_away_finished() -> void: return_completed.emit() +func _restore_owner_after_return() -> void: + # Return completion owns the transition out of the observer-only fishing + # poses. This must happen before completion listeners queue_free this node; + # otherwise an escape or withdrawal leaves RETRACT selected indefinitely and + # masks every later locomotion animation on this observer. + _observer_fighting_active = false + if _owner == null or not is_instance_valid(_owner): + return + _owner.set_fighting_visual(false) + _owner.set_fishing_visual(false) + + func _kill_cast_tween() -> void: if _cast_tween != null and _cast_tween.is_valid(): _cast_tween.kill() diff --git a/tests/fishing_multiplayer_validation.gd b/tests/fishing_multiplayer_validation.gd index 667d87e..f660fc0 100644 --- a/tests/fishing_multiplayer_validation.gd +++ b/tests/fishing_multiplayer_validation.gd @@ -194,6 +194,18 @@ func _run_host() -> void: ): await process_frame assert(remote_avatar.is_retract_visual_complete()) + var observer_cleanup_deadline: int = Time.get_ticks_msec() + 3000 + while ( + Time.get_ticks_msec() < observer_cleanup_deadline + and int(remote_avatar.get("_fishing_visual_phase")) + != Player.FishingVisualPhase.NONE + ): + await process_frame + assert( + int(remote_avatar.get("_fishing_visual_phase")) + == Player.FishingVisualPhase.NONE + ) + assert(remote_animation_player.assigned_animation != &"retract_sit") # Give the client that owns the same presentation a frame boundary to finish # local cleanup before the host intentionally tears down this test session. for _frame: int in 4: diff --git a/tests/fishing_surface_validation.gd b/tests/fishing_surface_validation.gd index 8248c36..eddc66e 100644 --- a/tests/fishing_surface_validation.gd +++ b/tests/fishing_surface_validation.gd @@ -545,6 +545,35 @@ func _validate_remote_presentation() -> void: presentation.synchronize_active("late-attempt", target, false) assert(not bool(player.get("_fighting_visual_active"))) + # An escape or withdrawal has no showcase to clear the retract pose. Return + # completion itself must restore ordinary locomotion before listeners dispose + # of the observer presentation. + presentation.play_return() + await presentation.return_completed + assert( + int(player.get("_fishing_visual_phase")) + == Player.FishingVisualPhase.NONE + ) + assert(not bool(player.get("_fighting_visual_active"))) + player.set_physics_process(false) + player.velocity = Vector3(8.0, 0.0, 0.0) + player.call("_update_character_animation") + var animation_player := player.get_node( + "Visuals/CharacterRig/AnimationPlayer" + ) as AnimationPlayer + assert(animation_player.assigned_animation == &"running") + var running_position: float = animation_player.current_animation_position + await create_timer(0.1).timeout + assert(animation_player.current_animation == &"running") + assert( + not is_equal_approx( + animation_player.current_animation_position, + running_position, + ) + ) + presentation.cleanup() + presentation.synchronize_active("catch-attempt", target, false) + var fish_catch := FishCatchType.new() var fish: FishData = PondPool.candidates.front() fish_catch.fish = fish