diff --git a/crates/net/src/client/input.rs b/crates/net/src/client/input.rs index 45c2fa1..0e38634 100644 --- a/crates/net/src/client/input.rs +++ b/crates/net/src/client/input.rs @@ -64,6 +64,11 @@ pub struct ClientActionInput { pub pad_lockon: Option, pub pad_autoaim: Option<(u64, f32)>, pub pad_was_ads: bool, + /// Use+reload is down. + pub use_reload_down: bool, + /// When use+reload went down over an item, and the item's entity: a tap + /// reloads once released, a hold is the item's. + pub use_reload_over_item: Option<(i32, i32)>, } impl Default for ClientActionInput { @@ -98,6 +103,8 @@ impl Default for ClientActionInput { pad_lockon: None, pad_autoaim: None, pad_was_ads: false, + use_reload_down: false, + use_reload_over_item: None, } } } diff --git a/crates/net/src/client/runtime.rs b/crates/net/src/client/runtime.rs index 90095d9..6944422 100644 --- a/crates/net/src/client/runtime.rs +++ b/crates/net/src/client/runtime.rs @@ -749,6 +749,36 @@ fn apply_weapon_switch_requests( } } +/// Use+reload reloads, except over an item: there a hold is the item's +/// (`sim::ITEM_USE_HOLD_MS`, the authority takes it) and a tap still reloads, +/// sent once it is released, if the item is still the one it began on. +fn use_reload_as_reload( + actions: &mut ClientActionInput, + cmd: &mut playerstate_iw4::UserCmd, + ps: Option<&playerstate_iw4::PlayerState>, +) { + use playerstate_iw4::buttons::{RELOAD, USE_RELOAD}; + let down = cmd.buttons & USE_RELOAD != 0; + let now = actions.now_msec; + let item = ps + .filter(|ps| ps.cursor_hint != 0) + .map(|ps| ps.cursor_hint_ent_index); + if down { + if !actions.use_reload_down { + actions.use_reload_over_item = item.map(|ent| (now, ent)); + } + if actions.use_reload_over_item.is_none() && item.is_none() { + cmd.buttons |= RELOAD; + } + } else if let Some((since, ent)) = actions.use_reload_over_item.take() + && now.wrapping_sub(since) < sim::ITEM_USE_HOLD_MS + && item == Some(ent) + { + cmd.buttons |= RELOAD; + } + actions.use_reload_down = down; +} + pub fn sample_client_input( time: Res>, mut actions: ResMut, @@ -1063,11 +1093,7 @@ pub fn sample_client_input( actions.client.airborne = ps.is_some_and(|ps| ps.ground_entity_num == playerstate_iw4::ENTITYNUM_NONE); let mut cmd = build_usercmd(&mut actions, &look, 0); - if cmd.buttons & playerstate_iw4::buttons::USE_RELOAD != 0 - && ps.is_some_and(|ps| ps.cursor_hint == 0) - { - cmd.buttons |= playerstate_iw4::buttons::RELOAD; - } + use_reload_as_reload(&mut actions, &mut cmd, ps); look.angles = cmd.angles; if !frozen && aim.live diff --git a/crates/net/src/lib.rs b/crates/net/src/lib.rs index 27c4b6b..86824a9 100644 --- a/crates/net/src/lib.rs +++ b/crates/net/src/lib.rs @@ -174,6 +174,6 @@ pub use transport::wire::{WireError, WireReader, WireWriter}; pub use svc_script_audio::SvcScriptAudio; -pub const PROTOCOL_VERSION: u32 = 109; +pub const PROTOCOL_VERSION: u32 = 110; 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 84788c5..692d4f1 100644 --- a/crates/net/src/transport/meta_wire.rs +++ b/crates/net/src/transport/meta_wire.rs @@ -1343,6 +1343,7 @@ fn encode_client_meta(out: &mut WireWriter, meta: &ClientSnapshotMeta) { out.put_u32(meta.life_sequence.0); out.put_i32(meta.item_use_spawn_ms); encode_optional_entity_ref(out, meta.item_use_entity); + out.put_i32(meta.item_use_press_ms); out.put_i32(meta.ammo_clip); out.put_i32(meta.ammo_stock); out.put_i32(meta.score); @@ -1564,6 +1565,7 @@ fn decode_client_meta(input: &mut WireReader<'_>) -> Result) -> Result= 500; + // A weapon is taken once use is held; a thrown item on the press. + let hold_ms = if selected.is_some_and(|item| item.projectile) { + 0 + } else { + ITEM_USE_HOLD_MS + }; + let ready = now - meta.item_use_spawn_ms >= 500 && now - meta.item_use_press_ms >= hold_ms; if held && ready && pending.is_some() && selected_ref == pending { let item = selected.expect("selected use item"); if item.projectile { diff --git a/crates/sim/src/lib.rs b/crates/sim/src/lib.rs index ddfcf6e..75687ad 100644 --- a/crates/sim/src/lib.rs +++ b/crates/sim/src/lib.rs @@ -18,6 +18,7 @@ pub mod identities; pub mod input; mod item; mod local_profile; +pub use item::ITEM_USE_HOLD_MS; pub use local_profile::LocalPlayerProfile; mod mantle_xanim; pub mod match_state; diff --git a/crates/sim/src/match_state.rs b/crates/sim/src/match_state.rs index 2919493..8a40262 100644 --- a/crates/sim/src/match_state.rs +++ b/crates/sim/src/match_state.rs @@ -94,6 +94,8 @@ pub struct ClientMatchState { pub item_use_spawn_ms: i32, pub item_use_entity: Option, + /// When the use press on `item_use_entity` began. + pub item_use_press_ms: i32, pub(crate) ammo_clip: i32, @@ -222,6 +224,7 @@ impl ClientMatchState { life_sequence: self.life_sequence, item_use_spawn_ms: self.item_use_spawn_ms, item_use_entity: self.item_use_entity, + item_use_press_ms: self.item_use_press_ms, ammo_clip: self.ammo_clip, ammo_stock: self.ammo_stock, score: self.score, @@ -280,6 +283,7 @@ impl ClientMatchState { self.life_sequence = meta.life_sequence; self.item_use_spawn_ms = meta.item_use_spawn_ms; self.item_use_entity = meta.item_use_entity; + self.item_use_press_ms = meta.item_use_press_ms; self.ammo_clip = meta.ammo_clip; self.ammo_stock = meta.ammo_stock; self.score = meta.score; diff --git a/crates/sim/src/match_state/snapshot_meta.rs b/crates/sim/src/match_state/snapshot_meta.rs index 7288f70..de677dc 100644 --- a/crates/sim/src/match_state/snapshot_meta.rs +++ b/crates/sim/src/match_state/snapshot_meta.rs @@ -24,6 +24,7 @@ pub struct ClientSnapshotMeta { pub item_use_spawn_ms: i32, pub item_use_entity: Option, + pub item_use_press_ms: i32, pub ammo_clip: i32,