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"]),
|
str(action["id"]),
|
||||||
int(action["sequence"]),
|
int(action["sequence"]),
|
||||||
bool(action.get("paused", false)),
|
bool(action.get("paused", false)),
|
||||||
avatar.get_network_sitting_state(),
|
avatar.get_network_sitting_intent(),
|
||||||
]
|
]
|
||||||
if signature == _last_local_animation_action_signature:
|
if signature == _last_local_animation_action_signature:
|
||||||
return
|
return
|
||||||
var encoded: Array = _encode_movement_animation_action(
|
var encoded: Array = _encode_movement_animation_action(
|
||||||
action,
|
action,
|
||||||
avatar.get_network_sitting_state(),
|
avatar.get_network_sitting_intent(),
|
||||||
)
|
)
|
||||||
if encoded.is_empty():
|
if encoded.is_empty():
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1036,7 +1036,13 @@ func _simulate_movement_physics(delta: float) -> void:
|
||||||
or (_network_authoritative_simulation and _network_jump_pending)
|
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
|
_sit_after_landing = false
|
||||||
_set_sitting(true, local_control_enabled)
|
_set_sitting(true, local_control_enabled)
|
||||||
if _sitting:
|
if _sitting:
|
||||||
|
|
@ -1489,8 +1495,20 @@ func _update_character_animation() -> void:
|
||||||
_presented_animation_action_id = &""
|
_presented_animation_action_id = &""
|
||||||
_presented_animation_action_sequence = -1
|
_presented_animation_action_sequence = -1
|
||||||
_presented_animation_action_paused = false
|
_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
|
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_player.play(next_animation)
|
||||||
_character_animation_name = next_animation
|
_character_animation_name = next_animation
|
||||||
if action_selected:
|
if action_selected:
|
||||||
|
|
@ -1513,6 +1531,26 @@ func _update_character_animation() -> void:
|
||||||
_character_animation_player.pause()
|
_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:
|
func _on_character_animation_finished(animation_name: StringName) -> void:
|
||||||
if animation_name == CHARACTER_NET_STRIKE_ANIMATION:
|
if animation_name == CHARACTER_NET_STRIKE_ANIMATION:
|
||||||
if local_control_enabled:
|
if local_control_enabled:
|
||||||
|
|
@ -1610,7 +1648,7 @@ func toggle_sitting() -> void:
|
||||||
])
|
])
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if not is_on_floor():
|
if not _can_begin_sitting():
|
||||||
_sit_after_landing = true
|
_sit_after_landing = true
|
||||||
return
|
return
|
||||||
_set_sitting(should_sit, local_control_enabled)
|
_set_sitting(should_sit, local_control_enabled)
|
||||||
|
|
@ -1645,7 +1683,15 @@ func is_sitting() -> bool:
|
||||||
return _sitting
|
return _sitting
|
||||||
|
|
||||||
|
|
||||||
|
func _can_begin_sitting() -> bool:
|
||||||
|
return is_on_floor() and velocity.y <= 0.0
|
||||||
|
|
||||||
|
|
||||||
func get_network_sitting_state() -> bool:
|
func get_network_sitting_state() -> bool:
|
||||||
|
return _sitting
|
||||||
|
|
||||||
|
|
||||||
|
func get_network_sitting_intent() -> bool:
|
||||||
return _sitting or _sit_after_landing
|
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:
|
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
|
_sit_after_landing = true
|
||||||
_set_sitting(false)
|
_set_sitting(false)
|
||||||
return
|
return
|
||||||
|
|
@ -2038,7 +2086,7 @@ func capture_network_input(sequence: int) -> Dictionary:
|
||||||
"sprint": false,
|
"sprint": false,
|
||||||
"sneak": false,
|
"sneak": false,
|
||||||
"slow_walk": false,
|
"slow_walk": false,
|
||||||
"sitting": get_network_sitting_state(),
|
"sitting": get_network_sitting_intent(),
|
||||||
"casting": (
|
"casting": (
|
||||||
_fishing_visual_phase == FishingVisualPhase.CASTING
|
_fishing_visual_phase == FishingVisualPhase.CASTING
|
||||||
),
|
),
|
||||||
|
|
@ -2063,7 +2111,7 @@ func capture_network_input(sequence: int) -> Dictionary:
|
||||||
"sprint": Input.is_action_pressed("sprint"),
|
"sprint": Input.is_action_pressed("sprint"),
|
||||||
"sneak": Input.is_action_pressed("sneak"),
|
"sneak": Input.is_action_pressed("sneak"),
|
||||||
"slow_walk": Input.is_action_pressed("slow_walk"),
|
"slow_walk": Input.is_action_pressed("slow_walk"),
|
||||||
"sitting": get_network_sitting_state(),
|
"sitting": get_network_sitting_intent(),
|
||||||
"casting": _fishing_visual_phase == FishingVisualPhase.CASTING,
|
"casting": _fishing_visual_phase == FishingVisualPhase.CASTING,
|
||||||
"animation_action": _make_animation_action_state(),
|
"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("sprint"),
|
||||||
Input.is_action_pressed("sneak"),
|
Input.is_action_pressed("sneak"),
|
||||||
Input.is_action_pressed("slow_walk"),
|
Input.is_action_pressed("slow_walk"),
|
||||||
get_network_sitting_state(),
|
get_network_sitting_intent(),
|
||||||
_fishing_visual_phase == FishingVisualPhase.CASTING,
|
_fishing_visual_phase == FishingVisualPhase.CASTING,
|
||||||
_animation_action_id,
|
_animation_action_id,
|
||||||
_animation_action_sequence,
|
_animation_action_sequence,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,9 @@ func _validate_latency_smoothing() -> void:
|
||||||
_validate_animation_action_ordering(avatar)
|
_validate_animation_action_ordering(avatar)
|
||||||
_validate_transit_estimation()
|
_validate_transit_estimation()
|
||||||
_validate_remote_snapshot_smoothing(avatar)
|
_validate_remote_snapshot_smoothing(avatar)
|
||||||
|
_validate_remote_locomotion_playback_recovery(avatar)
|
||||||
_validate_reliable_jump_intent(avatar)
|
_validate_reliable_jump_intent(avatar)
|
||||||
|
_validate_airborne_sitting(avatar)
|
||||||
_validate_stale_input_expiry(avatar)
|
_validate_stale_input_expiry(avatar)
|
||||||
avatar.queue_free()
|
avatar.queue_free()
|
||||||
await process_frame
|
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:
|
func _validate_animation_action_ordering(avatar: Player) -> void:
|
||||||
var draw := NetworkPlayerAnimationProtocol.make_action_state(
|
var draw := NetworkPlayerAnimationProtocol.make_action_state(
|
||||||
&"draw", 5, 0.2
|
&"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:
|
func _validate_reliable_jump_intent(avatar: Player) -> void:
|
||||||
avatar.set_local_control(true)
|
avatar.set_local_control(true)
|
||||||
avatar.call("_queue_local_network_jump_intent")
|
avatar.call("_queue_local_network_jump_intent")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue