mirror of
https://github.com/vladtrc/iw4L
synced 2026-10-28 10:59:19 -04:00
Give the Sleight of Hand, Steady Aim and Cold-Blooded Pro perks effect
MW2's class scripts set specialty_quickdraw, specialty_holdbreath and specialty_spygame for the pro perks, but no perk bit was mapped, so they did nothing. With MW2's bits and values: - quickdraw (perk 26) moves into and out of ADS 1.5 times as fast (perk_quickDrawSpeedScale) and shortens the ADS-only fire delay to match; - holdbreath (perk 4) holds the breath 5 s longer (perk_extraBreath), in the movement code, the breath hint and the gasp sound; - spygame (perk 32, bit 0 of perks[1]) hides the player's name from enemies aiming at them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use asset_core::AssetNamespace;
|
||||
use bevy::prelude::*;
|
||||
use net::{LocalPresentClient, PresentedSnapshot};
|
||||
use playerstate_iw4::{BREATH_HOLD_TIME_MS, weap_flags};
|
||||
use playerstate_iw4::{breath_hold_time_ms, weap_flags};
|
||||
|
||||
use crate::sources::{DesiredSource, SourceCueRequest, SourceKey};
|
||||
use crate::{AliasCommand, PlayAlias};
|
||||
@@ -112,7 +112,7 @@ pub(crate) fn update(
|
||||
});
|
||||
}
|
||||
if let Some(ps) = ps {
|
||||
let alias = if ps.hold_breath_timer > BREATH_HOLD_TIME_MS {
|
||||
let alias = if ps.hold_breath_timer > breath_hold_time_ms(ps) {
|
||||
cues.gasp
|
||||
} else {
|
||||
cues.exhale
|
||||
|
||||
@@ -4,7 +4,7 @@ use asset_game::MenuCatalog;
|
||||
use assets::{PreparedLocalizedStrings, PreparedWeapons};
|
||||
use bevy::prelude::*;
|
||||
use net::{LocalPresentClient, PresentedSnapshot};
|
||||
use playerstate_iw4::{BREATH_HOLD_TIME_MS, weap_flags};
|
||||
use playerstate_iw4::{breath_hold_time_ms, weap_flags};
|
||||
|
||||
use crate::draw2d::{Draw2dCmd, Draw2dList, Draw2dOp, Draw2dProvenance, tessellate_fonts};
|
||||
use crate::gpu_list::{HudTessPass, TessJob};
|
||||
@@ -75,7 +75,7 @@ pub(crate) fn update(
|
||||
let text = if ps.weap_flags & weap_flags::HOLD_BREATH != 0 {
|
||||
format!(
|
||||
"Holding breath {:.1}s",
|
||||
(BREATH_HOLD_TIME_MS - ps.hold_breath_timer).max(0) as f32 * 0.001
|
||||
(breath_hold_time_ms(ps) - ps.hold_breath_timer).max(0) as f32 * 0.001
|
||||
)
|
||||
} else if ps.hold_breath_timer > 0 {
|
||||
format!(
|
||||
|
||||
@@ -296,6 +296,16 @@ fn update_overhead_names(
|
||||
if meta.lifecycle != sim::ClientLifecycle::Alive {
|
||||
continue;
|
||||
}
|
||||
let enemy = local_meta.client_state_team == 0
|
||||
|| local_meta.client_state_team != meta.client_state_team;
|
||||
if enemy
|
||||
&& snapshot
|
||||
.players
|
||||
.iter()
|
||||
.any(|(id, ps)| *id == client && ps.perks[1] & playerstate_iw4::PERK1_SPYGAME != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(name) = entity_iw4::client_state_name(&meta.name) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -61,7 +61,10 @@ pub fn update_ads_frac(ps: &mut PlayerState, msec: i32, context: AdsFracContext)
|
||||
{
|
||||
ads_requested = true;
|
||||
}
|
||||
let dt = msec.max(0) as f32;
|
||||
let mut dt = msec.max(0) as f32;
|
||||
if ps.perks[0] & playerstate_iw4::PERK_QUICKDRAW != 0 {
|
||||
dt *= playerstate_iw4::PERK_QUICKDRAW_SPEED_SCALE;
|
||||
}
|
||||
|
||||
if ads_requested {
|
||||
if ps.f_weapon_pos_frac >= 1.0 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use playerstate_iw4::{
|
||||
BREATH_GASP_TIME_MS, BREATH_HOLD_TIME_MS, PlayerState, buttons, pm_flags, weap_flags,
|
||||
BREATH_GASP_TIME_MS, PlayerState, breath_hold_time_ms, buttons, pm_flags, weap_flags,
|
||||
};
|
||||
|
||||
pub(crate) fn update_hold_breath(ps: &mut PlayerState, buttons: u32, msec: i32, eligible: bool) {
|
||||
@@ -10,12 +10,13 @@ pub(crate) fn update_hold_breath(ps: &mut PlayerState, buttons: u32, msec: i32,
|
||||
&& ps.pm_flags & (pm_flags::SPRINTING | pm_flags::MANTLE | pm_flags::LADDER) == 0
|
||||
&& ps.weap_flags & weap_flags::OFFHAND_VIEW == 0
|
||||
&& ps.health > 0;
|
||||
let hold_time = breath_hold_time_ms(ps);
|
||||
let holding = wants_hold && (was_holding || ps.hold_breath_timer == 0);
|
||||
ps.weap_flags &= !weap_flags::HOLD_BREATH;
|
||||
if holding {
|
||||
ps.hold_breath_timer = ps.hold_breath_timer.saturating_add(msec);
|
||||
if ps.hold_breath_timer >= BREATH_HOLD_TIME_MS {
|
||||
ps.hold_breath_timer = BREATH_HOLD_TIME_MS + BREATH_GASP_TIME_MS;
|
||||
if ps.hold_breath_timer >= hold_time {
|
||||
ps.hold_breath_timer = hold_time + BREATH_GASP_TIME_MS;
|
||||
} else {
|
||||
ps.weap_flags |= weap_flags::HOLD_BREATH;
|
||||
}
|
||||
@@ -26,7 +27,7 @@ pub(crate) fn update_hold_breath(ps: &mut PlayerState, buttons: u32, msec: i32,
|
||||
let target = if holding {
|
||||
0.0
|
||||
} else {
|
||||
1.0 + 3.5 * ps.hold_breath_timer as f32 / (BREATH_HOLD_TIME_MS + BREATH_GASP_TIME_MS) as f32
|
||||
1.0 + 3.5 * ps.hold_breath_timer as f32 / (hold_time + BREATH_GASP_TIME_MS) as f32
|
||||
};
|
||||
let target = 1.0 + (target - 1.0) * ps.f_weapon_pos_frac;
|
||||
let rate = if holding { 1.0 } else { 6.0 };
|
||||
|
||||
@@ -15,9 +15,10 @@ pub use chrome::{
|
||||
};
|
||||
pub use playerstate::{
|
||||
AnimPair, BREATH_GASP_TIME_MS, BREATH_HOLD_TIME_MS, ENTITYNUM_NONE, PERK_COLDBLOODED,
|
||||
PERK_FASTMANTLE, PERK_HEARTBREAKER, PERK_PISTOLDEATH, PERK_QUIETER, PERK_SCAVENGER,
|
||||
PlayerState, eflags, get_viewmodel_weapon_index, mantle_flags, other_flags, pm_flags,
|
||||
weap_flags,
|
||||
PERK_EXTRA_BREATH_MS, PERK_FASTMANTLE, PERK_HEARTBREAKER, PERK_HOLDBREATH, PERK_PISTOLDEATH,
|
||||
PERK_QUICKDRAW, PERK_QUICKDRAW_SPEED_SCALE, PERK_QUIETER, PERK_SCAVENGER, PERK1_SPYGAME,
|
||||
PlayerState, breath_hold_time_ms, eflags, get_viewmodel_weapon_index, mantle_flags,
|
||||
other_flags, pm_flags, weap_flags,
|
||||
};
|
||||
pub use seat::{
|
||||
HITSCAN_KILL_CAM_ENTITY, SeatFocus, apply_killcam_seat, rebase_archived_timers,
|
||||
|
||||
@@ -357,10 +357,35 @@ pub const PERK_FASTMANTLE: u32 = 1 << 19;
|
||||
|
||||
pub const PERK_SCAVENGER: u32 = 1 << 22;
|
||||
|
||||
/// Steady Aim Pro: breath holds `PERK_EXTRA_BREATH_MS` longer.
|
||||
pub const PERK_HOLDBREATH: u32 = 1 << 4;
|
||||
|
||||
/// `perk_extraBreath`, in milliseconds.
|
||||
pub const PERK_EXTRA_BREATH_MS: i32 = 5000;
|
||||
|
||||
/// Sleight of Hand Pro: aiming down sights is `PERK_QUICKDRAW_SPEED_SCALE` faster.
|
||||
pub const PERK_QUICKDRAW: u32 = 1 << 26;
|
||||
|
||||
/// `perk_quickDrawSpeedScale`.
|
||||
pub const PERK_QUICKDRAW_SPEED_SCALE: f32 = 1.5;
|
||||
|
||||
pub const PERK_COLDBLOODED: u32 = 1 << 27;
|
||||
|
||||
pub const PERK_HEARTBREAKER: u32 = 1 << 28;
|
||||
|
||||
/// Cold-Blooded Pro (perk 32, so bit 0 of `perks[1]`): enemies aiming at the
|
||||
/// player see no name.
|
||||
pub const PERK1_SPYGAME: u32 = 1;
|
||||
|
||||
/// How long this player can hold their breath before gasping.
|
||||
pub fn breath_hold_time_ms(ps: &PlayerState) -> i32 {
|
||||
if ps.perks[0] & PERK_HOLDBREATH != 0 {
|
||||
BREATH_HOLD_TIME_MS + PERK_EXTRA_BREATH_MS
|
||||
} else {
|
||||
BREATH_HOLD_TIME_MS
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct AnimPair {
|
||||
pub legs_anim: i32,
|
||||
|
||||
@@ -716,7 +716,7 @@ pub(crate) fn give_start_ammo(world: &mut FrameWorld, id: ClientId, weapon: u32)
|
||||
set_ammo_stock(world, id, weapon, stock);
|
||||
}
|
||||
|
||||
fn perk_bits(name: &str) -> (u32, u32) {
|
||||
fn perk_bits(name: &str) -> ([u32; 2], u32) {
|
||||
let perk = match name {
|
||||
"specialty_fastreload" => weapon_iw4::PERK_FASTRELOAD,
|
||||
"specialty_coldblooded" => playerstate_iw4::PERK_COLDBLOODED,
|
||||
@@ -728,23 +728,34 @@ fn perk_bits(name: &str) -> (u32, u32) {
|
||||
"specialty_bulletaccuracy" => weapon_iw4::PERK_BULLETACCURACY,
|
||||
"specialty_pistoldeath" => playerstate_iw4::PERK_PISTOLDEATH,
|
||||
"specialty_fastmantle" => playerstate_iw4::PERK_FASTMANTLE,
|
||||
"specialty_quickdraw" => playerstate_iw4::PERK_QUICKDRAW,
|
||||
"specialty_holdbreath" => playerstate_iw4::PERK_HOLDBREATH,
|
||||
_ => 0,
|
||||
};
|
||||
let perk1 = match name {
|
||||
"specialty_spygame" => playerstate_iw4::PERK1_SPYGAME,
|
||||
_ => 0,
|
||||
};
|
||||
let e_flags = match name {
|
||||
"specialty_localjammer" => playerstate_iw4::eflags::RADAR_JAM,
|
||||
_ => 0,
|
||||
};
|
||||
(perk, e_flags)
|
||||
([perk, perk1], e_flags)
|
||||
}
|
||||
|
||||
pub(crate) fn set_perk(world: &mut FrameWorld, id: ClientId, name: &str, on: bool) {
|
||||
let (perk, e_flags) = perk_bits(name);
|
||||
let (perks, e_flags) = perk_bits(name);
|
||||
if let Some(ps) = world.player_mut(id) {
|
||||
for (word, perk) in ps.perks.iter_mut().zip(perks) {
|
||||
if on {
|
||||
*word |= perk;
|
||||
} else {
|
||||
*word &= !perk;
|
||||
}
|
||||
}
|
||||
if on {
|
||||
ps.perks[0] |= perk;
|
||||
ps.e_flags |= e_flags;
|
||||
} else {
|
||||
ps.perks[0] &= !perk;
|
||||
ps.e_flags &= !e_flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1207,8 +1207,12 @@ fn finish_weapon_tick(
|
||||
hand.weaponstate = WeaponState::Firing as i32;
|
||||
hand.weapon_time = facts.fire_time_ms.max(1);
|
||||
if facts.ads_fire_only {
|
||||
hand.weapon_delay =
|
||||
ads_fire_only_delay_ms(cmd.f_weapon_pos_frac, facts.ads_in_rate);
|
||||
let ads_in_rate = if cmd.perks0 & playerstate_iw4::PERK_QUICKDRAW != 0 {
|
||||
facts.ads_in_rate * playerstate_iw4::PERK_QUICKDRAW_SPEED_SCALE
|
||||
} else {
|
||||
facts.ads_in_rate
|
||||
};
|
||||
hand.weapon_delay = ads_fire_only_delay_ms(cmd.f_weapon_pos_frac, ads_in_rate);
|
||||
} else if facts.fire_delay_ms > 0 {
|
||||
hand.weapon_delay = facts.fire_delay_ms;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user