diff --git a/crates/net/src/client/presentation/centity_runtime.rs b/crates/net/src/client/presentation/centity_runtime.rs index 09ff883..97ab1b4 100644 --- a/crates/net/src/client/presentation/centity_runtime.rs +++ b/crates/net/src/client/presentation/centity_runtime.rs @@ -335,6 +335,7 @@ pub fn corpse_slot_to_entity_state(slot: &PlayerCorpseSlot) -> EntityState { es.number = slot.entnum; es.e_type = ET_PLAYER_CORPSE; es.client_num = i32::try_from(slot.victim.0).unwrap_or(0); + es.time2 = slot.spawn_time_ms; es.e_flags = slot.e_flags; es.tr_type = slot.tr_type; es.tr_time = slot.tr_time; diff --git a/crates/net/src/lib.rs b/crates/net/src/lib.rs index 0419ea7..6e76cef 100644 --- a/crates/net/src/lib.rs +++ b/crates/net/src/lib.rs @@ -175,6 +175,6 @@ pub use transport::wire::{WireError, WireReader, WireWriter}; pub use svc_script_audio::SvcScriptAudio; -pub const PROTOCOL_VERSION: u32 = 116; +pub const PROTOCOL_VERSION: u32 = 117; pub use client::fire_verdict::{FireVerdictState, PredictedFireStatus}; diff --git a/crates/net/src/transport/meta_wire.rs b/crates/net/src/transport/meta_wire.rs index bb2a565..4930ec1 100644 --- a/crates/net/src/transport/meta_wire.rs +++ b/crates/net/src/transport/meta_wire.rs @@ -2210,6 +2210,7 @@ fn encode_corpse_pool(out: &mut WireWriter, pool: &PlayerCorpsePool) { } out.put_u8(u8::from(slot.falling)); out.put_i32(slot.ground_entity_num); + out.put_i32(slot.spawn_time_ms); } } @@ -2243,6 +2244,7 @@ fn decode_corpse_pool(input: &mut WireReader<'_>) -> Result return Err(WireError::Malformed("bad corpse slot tag")), diff --git a/crates/render_anim/src/anim/remote_animation.rs b/crates/render_anim/src/anim/remote_animation.rs index 66af2ed..956bd6f 100644 --- a/crates/render_anim/src/anim/remote_animation.rs +++ b/crates/render_anim/src/anim/remote_animation.rs @@ -12,7 +12,7 @@ pub struct PersistentRemoteTree { pub(crate) legs_restart: bool, pub(crate) torso_restart: bool, - pub(crate) occupation_tr_time: Option, + pub(crate) occupation_key: Option, pub(crate) legs_rate_sample: ClientAnimSample, @@ -88,16 +88,36 @@ pub fn skip_frozen_corpse_dobj( is_corpse && tree_reused && last_was_cache_hit && has_cache } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CorpseOccupationKey { + pub spawn_time_ms: i32, + pub victim: i32, +} + +pub fn corpse_occupation_key(es: &entity_iw4::EntityState) -> CorpseOccupationKey { + CorpseOccupationKey { + spawn_time_ms: es.time2, + victim: es.client_num, + } +} + +pub fn fresh_corpse_clip_time(age_ms: i32, clip_length_ms: i32) -> f32 { + if clip_length_ms <= 0 || age_ms <= 0 { + return 0.0; + } + (age_ms as f32 / clip_length_ms as f32).min(1.0) +} + pub fn clone_corpse_tree_from_victim( trees: &mut RemoteBodyTrees, corpse_ent: u32, victim: u32, - tr_time: i32, + key: CorpseOccupationKey, ) { if trees .by_ent .get(&corpse_ent) - .is_some_and(|slot| slot.occupation_tr_time == Some(tr_time)) + .is_some_and(|slot| slot.occupation_key == Some(key)) { return; } @@ -108,7 +128,7 @@ pub fn clone_corpse_tree_from_victim( let mut dst = src; dst.dobj = None; dst.reuse_key = None; - dst.occupation_tr_time = Some(tr_time); + dst.occupation_key = Some(key); trees.by_ent.insert(corpse_ent, dst); } @@ -129,6 +149,7 @@ pub fn advance_remote_tree( dt: f32, origin: [f32; 3], pose_time_ms: i32, + corpse_age_ms: Option, trees: &mut RemoteBodyTrees, ) -> Result { let tree = binding.tree(); @@ -139,7 +160,7 @@ pub fn advance_remote_tree( let legs_restart = legs.restart_toggle(); let torso_restart = torso.restart_toggle(); let previous = trees.by_ent.remove(&persist_key); - let occupation_tr_time = previous.as_ref().and_then(|slot| slot.occupation_tr_time); + let occupation_key = previous.as_ref().and_then(|slot| slot.occupation_key); let prev = previous.filter(|slot| Arc::ptr_eq(&slot.binding, binding)); let prev_restart_changed_legs = prev .as_ref() @@ -178,6 +199,7 @@ pub fn advance_remote_tree( let overlayed = overlay_legs_clip(&clip, &torso_clip); (Arc::new(overlayed), Some(torso_clip)) }; + let fresh = prev.is_none(); let old_legs = prev.as_ref().map(|slot| slot.legs).unwrap_or(0); let old_torso = prev.as_ref().map(|slot| slot.torso).unwrap_or(0); let old_legs_moving = prev @@ -225,7 +247,7 @@ pub fn advance_remote_tree( torso: 0, legs_restart: false, torso_restart: false, - occupation_tr_time, + occupation_key, legs_rate_sample: ClientAnimSample::default(), torso_rate_sample: ClientAnimSample::default(), }, @@ -292,6 +314,23 @@ pub fn advance_remote_tree( .set_state(id, state) .map_err(|error| error.to_string())?; } + if fresh && let Some(age_ms) = corpse_age_ms { + for (index, leaf_clip) in [ + (legs_index, Some(&clip)), + (torso_index, torso_clip.as_ref()), + ] { + let Some(leaf_clip) = leaf_clip else { + continue; + }; + let time = fresh_corpse_clip_time(age_ms, leaf_clip.length_msec()); + let mut state = slot.runtime.states()[index as usize]; + state.time = time; + state.old_time = time; + slot.runtime + .set_state(xmodel_runtime::XAnimNodeId(index), state) + .map_err(|error| error.to_string())?; + } + } clips = Some(PoseClips { clip, torso_clip, diff --git a/crates/render_anim/src/occupancy/remote_body.rs b/crates/render_anim/src/occupancy/remote_body.rs index 07fcdb8..435b046 100644 --- a/crates/render_anim/src/occupancy/remote_body.rs +++ b/crates/render_anim/src/occupancy/remote_body.rs @@ -3,10 +3,10 @@ use crate::anim::remote_body::{ RemoteBodySkinnedItem, RemoteBodySkinnedQueue, RemoteBodyTrees, RemoteModelLods, RemoteSkinModels, RemoteSkinPoseHashes, SkinAfterPose, WorldGunGap, advance_remote_tree, bind_remote_skin_models, clone_corpse_tree_from_victim, commit_assembled_body, - ensure_remote_dobj, hash_skin_matrices, occupy_lod_byte, occupy_remote_kit_dobj, packed_anim, - pose_remote_dobj, push_cached_surfaces, radii, remote_player_controller, select_remote_lods, - select_remote_models, skin_after_pose, skin_slot_need, skip_frozen_corpse_dobj, - take_unique_geom, validate_remote_tracks, zero_anim, + corpse_occupation_key, ensure_remote_dobj, hash_skin_matrices, occupy_lod_byte, + occupy_remote_kit_dobj, packed_anim, pose_remote_dobj, push_cached_surfaces, radii, + remote_player_controller, select_remote_lods, select_remote_models, skin_after_pose, + skin_slot_need, skip_frozen_corpse_dobj, take_unique_geom, validate_remote_tracks, zero_anim, }; use crate::anim::scene_submission::{AnimDObjSceneSkels, AnimDObjSceneSubmission, AnimSceneSubmit}; use crate::anim::world_weapon::PreparedItemCompositions; @@ -868,16 +868,13 @@ impl<'a> RemotePoseFrame<'a> { let persist_key = u32::from(identity.number()); let is_corpse = runtime.next_state.e_type == ET_PLAYER_CORPSE || runtime.pose_e_type == ET_PLAYER_CORPSE as u8; - let occupation_tr_time = is_corpse.then_some(runtime.next_state.tr_time); - if is_corpse { + let occupation_key = is_corpse.then(|| corpse_occupation_key(&runtime.next_state)); + if let Some(key) = occupation_key { let victim = u32::try_from(runtime.next_state.client_num).unwrap_or(0); - clone_corpse_tree_from_victim( - self.trees, - persist_key, - victim, - runtime.next_state.tr_time, - ); + clone_corpse_tree_from_victim(self.trees, persist_key, victim, key); } + let corpse_age_ms = + occupation_key.map(|key| runtime.pose_time_ms.wrapping_sub(key.spawn_time_ms)); if runtime.previous_pose.is_none() { if let Some(slot) = self.trees.get_mut(persist_key) { @@ -955,6 +952,7 @@ impl<'a> RemotePoseFrame<'a> { dt, sample.rate_origin, sample.rate_time_ms, + corpse_age_ms, trees, )?; @@ -1051,11 +1049,11 @@ impl<'a> RemotePoseFrame<'a> { } Ok(PoseOneOutcome::Posed) })(); - if let Some(tr_time) = occupation_tr_time + if let Some(key) = occupation_key && let Some(slot) = trees.get_mut(persist_key) - && slot.occupation_tr_time.is_none() + && slot.occupation_key.is_none() { - slot.occupation_tr_time = Some(tr_time); + slot.occupation_key = Some(key); } result } diff --git a/crates/sim/src/corpse.rs b/crates/sim/src/corpse.rs index a6a9209..078041f 100644 --- a/crates/sim/src/corpse.rs +++ b/crates/sim/src/corpse.rs @@ -36,6 +36,8 @@ pub struct PlayerCorpseSlot { pub falling: bool, pub ground_entity_num: i32, + + pub spawn_time_ms: i32, } impl Default for PlayerCorpseSlot { @@ -57,6 +59,7 @@ impl Default for PlayerCorpseSlot { tr_base: [0.0; 3], falling: false, ground_entity_num: ENTITYNUM_NONE, + spawn_time_ms: 0, } } } @@ -178,6 +181,7 @@ pub(crate) fn occupy_player_clone( tr_delta: clamp_clone_tr_delta(ps.velocity), tr_base: ps.origin, falling: true, + spawn_time_ms: time_ms, ..PlayerCorpseSlot::default() }, );