Fix airborne sitting and remote animation playback
This commit is contained in:
parent
a1ea341e8c
commit
c65176b99f
3 changed files with 105 additions and 9 deletions
|
|
@ -2069,13 +2069,13 @@ func _maybe_send_local_animation_action() -> void:
|
|||
str(action["id"]),
|
||||
int(action["sequence"]),
|
||||
bool(action.get("paused", false)),
|
||||
avatar.get_network_sitting_state(),
|
||||
avatar.get_network_sitting_intent(),
|
||||
]
|
||||
if signature == _last_local_animation_action_signature:
|
||||
return
|
||||
var encoded: Array = _encode_movement_animation_action(
|
||||
action,
|
||||
avatar.get_network_sitting_state(),
|
||||
avatar.get_network_sitting_intent(),
|
||||
)
|
||||
if encoded.is_empty():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1036,7 +1036,13 @@ func _simulate_movement_physics(delta: float) -> void:
|
|||
or (_network_authoritative_simulation and _network_jump_pending)
|
||||
)
|
||||
)
|
||||
if _sit_after_landing and is_on_floor():
|
||||
# Sitting deliberately stops movement processing, so never allow a stale
|
||||
# grounded flag or a reconciled network state to leave an airborne avatar in
|
||||
# that early-return path. Preserve the intent and sit once landing is real.
|
||||
if _sitting and not _can_begin_sitting():
|
||||
_sit_after_landing = true
|
||||
_set_sitting(false)
|
||||
if _sit_after_landing and not jump_requested and _can_begin_sitting():
|
||||
_sit_after_landing = false
|
||||
_set_sitting(true, local_control_enabled)
|
||||
if _sitting:
|
||||
|
|
@ -1489,8 +1495,20 @@ func _update_character_animation() -> void:
|
|||
_presented_animation_action_id = &""
|
||||
_presented_animation_action_sequence = -1
|
||||
_presented_animation_action_paused = false
|
||||
if _character_animation_name == next_animation and not action_changed:
|
||||
if (
|
||||
_character_animation_playback_matches(
|
||||
next_animation,
|
||||
action_selected and animation_action_paused,
|
||||
)
|
||||
and not action_changed
|
||||
):
|
||||
return
|
||||
# The animation name is only a selection cache. A remote player's
|
||||
# AnimationPlayer can occasionally stop or lose its assigned animation while
|
||||
# its replicated locomotion state remains valid. Reassert the selected
|
||||
# presentation here instead of waiting for another network state transition;
|
||||
# movement simulation and replication remain untouched.
|
||||
_character_animation_player.active = true
|
||||
_character_animation_player.play(next_animation)
|
||||
_character_animation_name = next_animation
|
||||
if action_selected:
|
||||
|
|
@ -1513,6 +1531,26 @@ func _update_character_animation() -> void:
|
|||
_character_animation_player.pause()
|
||||
|
||||
|
||||
func _character_animation_playback_matches(
|
||||
animation_name: StringName,
|
||||
paused_action_selected: bool,
|
||||
) -> bool:
|
||||
if (
|
||||
_character_animation_name != animation_name
|
||||
or _character_animation_player.assigned_animation != animation_name
|
||||
or not _character_animation_player.active
|
||||
):
|
||||
return false
|
||||
if paused_action_selected:
|
||||
return true
|
||||
var animation := _character_animation_player.get_animation(animation_name)
|
||||
if animation == null or animation.loop_mode == Animation.LOOP_NONE:
|
||||
# Completed one-shot actions deliberately keep their final pose until the
|
||||
# authoritative action state advances; they must not be restarted here.
|
||||
return true
|
||||
return _character_animation_player.is_playing()
|
||||
|
||||
|
||||
func _on_character_animation_finished(animation_name: StringName) -> void:
|
||||
if animation_name == CHARACTER_NET_STRIKE_ANIMATION:
|
||||
if local_control_enabled:
|
||||
|
|
@ -1610,7 +1648,7 @@ func toggle_sitting() -> void:
|
|||
])
|
||||
):
|
||||
return
|
||||
if not is_on_floor():
|
||||
if not _can_begin_sitting():
|
||||
_sit_after_landing = true
|
||||
return
|
||||
_set_sitting(should_sit, local_control_enabled)
|
||||
|
|
@ -1645,7 +1683,15 @@ func is_sitting() -> bool:
|
|||
return _sitting
|
||||
|
||||
|
||||
func _can_begin_sitting() -> bool:
|
||||
return is_on_floor() and velocity.y <= 0.0
|
||||
|
||||
|
||||
func get_network_sitting_state() -> bool:
|
||||
return _sitting
|
||||
|
||||
|
||||
func get_network_sitting_intent() -> bool:
|
||||
return _sitting or _sit_after_landing
|
||||
|
||||
|
||||
|
|
@ -1654,7 +1700,9 @@ func apply_network_sitting_state(should_sit: bool) -> void:
|
|||
|
||||
|
||||
func apply_authoritative_network_sitting_state(should_sit: bool) -> void:
|
||||
if should_sit and not is_on_floor():
|
||||
if should_sit and (
|
||||
not _can_begin_sitting() or _network_jump_pending
|
||||
):
|
||||
_sit_after_landing = true
|
||||
_set_sitting(false)
|
||||
return
|
||||
|
|
@ -2038,7 +2086,7 @@ func capture_network_input(sequence: int) -> Dictionary:
|
|||
"sprint": false,
|
||||
"sneak": false,
|
||||
"slow_walk": false,
|
||||
"sitting": get_network_sitting_state(),
|
||||
"sitting": get_network_sitting_intent(),
|
||||
"casting": (
|
||||
_fishing_visual_phase == FishingVisualPhase.CASTING
|
||||
),
|
||||
|
|
@ -2063,7 +2111,7 @@ func capture_network_input(sequence: int) -> Dictionary:
|
|||
"sprint": Input.is_action_pressed("sprint"),
|
||||
"sneak": Input.is_action_pressed("sneak"),
|
||||
"slow_walk": Input.is_action_pressed("slow_walk"),
|
||||
"sitting": get_network_sitting_state(),
|
||||
"sitting": get_network_sitting_intent(),
|
||||
"casting": _fishing_visual_phase == FishingVisualPhase.CASTING,
|
||||
"animation_action": _make_animation_action_state(),
|
||||
}
|
||||
|
|
@ -2111,7 +2159,7 @@ func get_network_input_state_hash() -> int:
|
|||
Input.is_action_pressed("sprint"),
|
||||
Input.is_action_pressed("sneak"),
|
||||
Input.is_action_pressed("slow_walk"),
|
||||
get_network_sitting_state(),
|
||||
get_network_sitting_intent(),
|
||||
_fishing_visual_phase == FishingVisualPhase.CASTING,
|
||||
_animation_action_id,
|
||||
_animation_action_sequence,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ func _validate_latency_smoothing() -> void:
|
|||
_validate_animation_action_ordering(avatar)
|
||||
_validate_transit_estimation()
|
||||
_validate_remote_snapshot_smoothing(avatar)
|
||||
_validate_remote_locomotion_playback_recovery(avatar)
|
||||
_validate_reliable_jump_intent(avatar)
|
||||
_validate_airborne_sitting(avatar)
|
||||
_validate_stale_input_expiry(avatar)
|
||||
avatar.queue_free()
|
||||
await process_frame
|
||||
|
|
@ -141,6 +143,26 @@ func _validate_compact_animation_encoding() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _validate_airborne_sitting(avatar: Player) -> void:
|
||||
avatar.reset_network_movement_state()
|
||||
avatar.set("_sit_after_landing", true)
|
||||
assert(avatar.get_network_sitting_intent())
|
||||
assert(not avatar.get_network_sitting_state())
|
||||
assert(not bool(avatar.make_network_snapshot(2)["sitting"]))
|
||||
assert(bool(avatar.capture_network_input(1)["sitting"]))
|
||||
|
||||
# Even a malformed or stale reconciliation that marks an airborne avatar as
|
||||
# seated must not bypass gravity and freeze it in place.
|
||||
avatar.set("_sit_after_landing", false)
|
||||
avatar.call("_set_sitting", true)
|
||||
avatar.velocity = Vector3(0.0, 4.0, 0.0)
|
||||
avatar.call("_simulate_movement_physics", 0.1)
|
||||
assert(not avatar.is_sitting())
|
||||
assert(bool(avatar.get("_sit_after_landing")))
|
||||
assert(avatar.velocity.y < 4.0)
|
||||
avatar.reset_network_movement_state()
|
||||
|
||||
|
||||
func _validate_animation_action_ordering(avatar: Player) -> void:
|
||||
var draw := NetworkPlayerAnimationProtocol.make_action_state(
|
||||
&"draw", 5, 0.2
|
||||
|
|
@ -352,6 +374,32 @@ func _validate_remote_snapshot_smoothing(avatar: Player) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _validate_remote_locomotion_playback_recovery(avatar: Player) -> void:
|
||||
avatar.configure_network_remote(false)
|
||||
avatar.push_network_snapshot(
|
||||
_network_snapshot(Vector3.ZERO, Vector3(4.5, 0.0, 0.0), 21)
|
||||
)
|
||||
avatar.call("_update_character_animation")
|
||||
var animation_player := avatar.get_node(
|
||||
"Visuals/CharacterRig/AnimationPlayer"
|
||||
) as AnimationPlayer
|
||||
assert(animation_player.assigned_animation == &"running")
|
||||
assert(animation_player.is_playing())
|
||||
|
||||
# Reproduce issue #111: locomotion replication still says RUNNING (and the
|
||||
# dust therefore still emits), but the rig playback has fallen idle. The
|
||||
# local presentation pass must repair that state without another packet.
|
||||
animation_player.stop()
|
||||
assert(not animation_player.is_playing())
|
||||
assert(
|
||||
int(avatar.get("_network_target_locomotion_state"))
|
||||
== Player.LocomotionState.RUNNING
|
||||
)
|
||||
avatar.call("_update_character_animation")
|
||||
assert(animation_player.assigned_animation == &"running")
|
||||
assert(animation_player.is_playing())
|
||||
|
||||
|
||||
func _validate_reliable_jump_intent(avatar: Player) -> void:
|
||||
avatar.set_local_control(true)
|
||||
avatar.call("_queue_local_network_jump_intent")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue