1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 10:14:06 +02:00

player condition handling

This commit is contained in:
Robin Appelman 2025-06-26 00:17:05 +02:00
commit 809d2504f5
7 changed files with 499 additions and 4 deletions

141
src/demo/data/cond.rs Normal file
View file

@ -0,0 +1,141 @@
use num_enum::TryFromPrimitive;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TryFromPrimitive)]
#[repr(u8)]
pub enum PlayerCondition {
Aiming = 0,
Zoomed = 1,
Disguising = 2,
Disguised = 3,
Stealthed = 4,
Invulnerable = 5,
Teleported = 6,
Taunting = 7,
InvulnerableWearingOff = 8,
StealthedBlink = 9,
SelectedToTeleport = 10,
CritBoosted = 11,
TmpDamageBonus = 12,
FeignDeath = 13,
Phase = 14,
Stunned = 15,
OffensiveBuff = 16,
ShieldCharge = 17,
DemoBuff = 18,
EnergyBuff = 19,
RadiusHeal = 20,
HealthBuff = 21,
Burning = 22,
HealthOverHealed = 23,
Urine = 24,
Bleeding = 25,
DefensiveBuff = 26,
MadMilk = 27,
MegaHeal = 28,
RegenOnDamageBuff = 29,
MarkedForDeath = 30,
NoHealingDamageBuff = 31,
SpeedBoost = 32,
CritBoostedPumpkin = 33,
CritBoostedUserBuff = 34,
CritBoostedDemoCharge = 35,
SodaPopperHype = 36,
CritBoostedFirstBlood = 37,
CritBoostedBonusTime = 38,
CritBoostedCtfCapture = 39,
CritBoostedOnKill = 40,
CannotSwitchFromMelee = 41,
DefenseBuffNoCritBlock = 42,
Reprogrammed = 43,
CritBoostedRageBuff = 44,
DefenseBuffHigh = 45,
SniperChargeRageBuff = 46,
DisguiseWearingOff = 47,
MarkedForDeathSilent = 48,
DisguisedAsDispenser = 49,
Sapped = 50,
InvulnerableHideUnlessDamaged = 51,
InvulnerableUserBuff = 52,
HalloweenBombHead = 53,
HalloweenThriller = 54,
RadiusHealOnDamage = 55,
CritBoostedCardEffect = 56,
InvulnerableCardEffect = 57,
MedigunUberBulletResist = 58,
MedigunUberBlastResist = 59,
MedigunUberFireResist = 60,
MedigunSmallBulletResist = 61,
MedigunSmallBlastResist = 62,
MedigunSmallFireResist = 63,
StealthedUserBuff = 64,
MedigunDebuff = 65,
StealthedUserBuffFading = 66,
BulletImmune = 67,
BlastImmune = 68,
FireImmune = 69,
PreventDeath = 70,
MvmBotStunRadiowave = 71,
HalloweenSpeedBoost = 72,
HalloweenQuickHeal = 73,
HalloweenGiant = 74,
HalloweenTiny = 75,
HalloweenInHell = 76,
HalloweenGhostMode = 77,
MiniCritBoostedOnKill = 78,
ObscuredSmoke = 79,
ParachuteActive = 80,
BlastJumping = 81,
HalloweenKart = 82,
HalloweenKartDash = 83,
BalloonHead = 84,
MeleeOnly = 85,
SwimmingCurse = 86,
FreezeInput = 87,
HalloweenKartCage = 88,
RuneStrength = 90,
RuneHaste = 91,
RuneRegen = 92,
RuneResist = 93,
RuneVampire = 94,
RuneReflect = 95,
RunePrecision = 96,
RuneAgility = 97,
GrapplingHook = 98,
GrapplingHookSafeFall = 99,
GrapplingHookLatched = 100,
GrapplingHookBleeding = 101,
AfterburnImmune = 102,
RuneKnockout = 103,
RuneImbalance = 104,
CritBoostedRuneTemp = 105,
PasstimeInterception = 106,
SwimmingNoEffects = 107,
PURGATORY = 108,
RuneKing = 109,
RunePlague = 110,
RuneSupernova = 111,
PLAGUE = 112,
KingBuffed = 113,
TeamGlows = 114,
KnockedIntoAir = 115,
CompetitiveWinner = 116,
CompetitiveLoser = 117,
HealingDebuff = 118,
PasstimePenaltyDebuff = 119,
GrappledToPlayer = 120,
GrappledByPlayer = 121,
ParachuteDeployed = 122,
GAS = 123,
BurningPyro = 124,
RocketPack = 125,
LostFooting = 126,
AirCurrent = 127,
HalloweenHellHeal = 128,
PowerUpModeDominant = 129,
ImmuneToPushback = 130,
}
impl PlayerCondition {
pub const MAX: PlayerCondition = PlayerCondition::ImmuneToPushback;
}

View file

@ -1,3 +1,4 @@
pub use super::cond::PlayerCondition;
use crate::demo::data::DemoTick; use crate::demo::data::DemoTick;
use crate::demo::gameevent_gen::PlayerDeathEvent; use crate::demo::gameevent_gen::PlayerDeathEvent;
use crate::demo::gamevent::GameEvent; use crate::demo::gamevent::GameEvent;
@ -8,6 +9,7 @@ use crate::demo::vector::Vector;
use parse_display::Display; use parse_display::Display;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::ops::Rem;
#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, Display)] #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, Display)]
pub struct Handle(pub i64); pub struct Handle(pub i64);
@ -73,6 +75,7 @@ pub struct Player {
pub in_pvs: bool, pub in_pvs: bool,
pub bounds: Box, pub bounds: Box,
pub weapons: [Handle; 3], pub weapons: [Handle; 3],
pub(crate) conditions: [u8; 20],
} }
pub const PLAYER_BOX_DEFAULT: Box = Box { pub const PLAYER_BOX_DEFAULT: Box = Box {
@ -108,6 +111,31 @@ impl Player {
} }
} }
} }
pub fn conditions(&self) -> impl Iterator<Item = PlayerCondition> + '_ {
(1..=(PlayerCondition::MAX as u8)).filter_map(|cond_int| {
let byte = cond_int / 8;
let bit = cond_int.rem(8);
let cond_byte = *self.conditions.get(byte as usize)?;
if (cond_byte >> bit as usize) == 1 {
PlayerCondition::try_from(cond_byte).ok()
} else {
None
}
})
}
pub fn has_condition(&self, condition: PlayerCondition) -> bool {
let cond_int = condition as u8;
let byte = cond_int / 8;
let bit = cond_int.rem(8);
let cond_byte = self
.conditions
.get(byte as usize)
.copied()
.unwrap_or_default();
cond_byte >> bit as usize == 1
}
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]

View file

@ -1,3 +1,4 @@
mod cond;
pub mod game_state; pub mod game_state;
pub mod userinfo; pub mod userinfo;

View file

@ -318,9 +318,10 @@ fn get_entity_for_update(
update_type: UpdateType, update_type: UpdateType,
delta: Option<ServerTick>, delta: Option<ServerTick>,
) -> Result<PacketEntity> { ) -> Result<PacketEntity> {
let class_id = *state let class_id = state
.entity_classes .entity_classes
.get(&entity_index) .get(&entity_index)
.copied()
.ok_or(ParseError::UnknownEntity(entity_index))?; .ok_or(ParseError::UnknownEntity(entity_index))?;
Ok(PacketEntity { Ok(PacketEntity {
@ -354,13 +355,14 @@ impl Parse<'_> for PacketEntitiesMessage {
for _ in 0..updated_entries { for _ in 0..updated_entries {
let diff: u32 = read_bit_var(&mut data)?; let diff: u32 = read_bit_var(&mut data)?;
last_index = last_index.saturating_add(diff as i32).saturating_add(1); let index = last_index.saturating_add(diff as i32).saturating_add(1);
if last_index >= 2048 { if index >= 2048 {
return Err(ParseError::InvalidDemo("invalid entity index")); return Err(ParseError::InvalidDemo("invalid entity index"));
} }
let entity_index = EntityId::from(last_index as u32); let entity_index = EntityId::from(index as u32);
let update_type = data.read()?; let update_type = data.read()?;
if update_type == UpdateType::Enter { if update_type == UpdateType::Enter {
let mut entity = let mut entity =
Self::read_enter(&mut data, entity_index, state, base_line, delta)?; Self::read_enter(&mut data, entity_index, state, base_line, delta)?;
@ -392,6 +394,8 @@ impl Parse<'_> for PacketEntitiesMessage {
baseline_index: BaselineIndex::First, baseline_index: BaselineIndex::First,
}); });
} }
last_index = index;
} }
if delta.is_some() { if delta.is_some() {

View file

@ -235,6 +235,16 @@ impl GameStateAnalyser {
SendPropIdentifier::new("DT_BaseEntity", "m_flSimulationTime"); SendPropIdentifier::new("DT_BaseEntity", "m_flSimulationTime");
const PROP_BB_MAX: SendPropIdentifier = const PROP_BB_MAX: SendPropIdentifier =
SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled"); SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled");
const PLAYER_COND: SendPropIdentifier =
SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCond");
const PLAYER_COND_EX1: SendPropIdentifier =
SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx");
const PLAYER_COND_EX2: SendPropIdentifier =
SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx2");
const PLAYER_COND_EX3: SendPropIdentifier =
SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx3");
const PLAYER_COND_EX4: SendPropIdentifier =
SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx4");
const WEAPON_0: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "000"); const WEAPON_0: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "000");
const WEAPON_1: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "001"); const WEAPON_1: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "001");
@ -286,6 +296,31 @@ impl GameStateAnalyser {
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default()); let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
player.weapons[2] = handle; player.weapons[2] = handle;
} }
PLAYER_COND => {
player.conditions[0..4].copy_from_slice(
&i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
);
}
PLAYER_COND_EX1 => {
player.conditions[4..8].copy_from_slice(
&i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
);
}
PLAYER_COND_EX2 => {
player.conditions[8..12].copy_from_slice(
&i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
);
}
PLAYER_COND_EX3 => {
player.conditions[12..16].copy_from_slice(
&i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
);
}
PLAYER_COND_EX4 => {
player.conditions[16..20].copy_from_slice(
&i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
);
}
_ => {} _ => {}
} }
} }

View file

@ -45,6 +45,28 @@ expression: state
1041070, 1041070,
268979, 268979,
316089 316089
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -88,6 +110,28 @@ expression: state
1237729, 1237729,
1182437, 1182437,
2097151 2097151
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -131,6 +175,28 @@ expression: state
404259, 404259,
1958250, 1958250,
1315401 1315401
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -174,6 +240,28 @@ expression: state
545153, 545153,
1917551, 1917551,
31344 31344
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -217,6 +305,28 @@ expression: state
143747, 143747,
1278549, 1278549,
1200734 1200734
],
"conditions": [
0,
0,
0,
0,
64,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -260,6 +370,28 @@ expression: state
733777, 733777,
14930, 14930,
1096287 1096287
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -303,6 +435,28 @@ expression: state
385251, 385251,
447062, 447062,
1856087 1856087
],
"conditions": [
0,
0,
160,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -346,6 +500,28 @@ expression: state
186746, 186746,
1476987, 1476987,
330275 330275
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -389,6 +565,28 @@ expression: state
197220, 197220,
240229, 240229,
1909366 1909366
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -432,6 +630,28 @@ expression: state
2081024, 2081024,
1640708, 1640708,
1423621 1423621
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -475,6 +695,28 @@ expression: state
702829, 702829,
381314, 381314,
194959 194959
],
"conditions": [
0,
0,
128,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
}, },
{ {
@ -518,6 +760,28 @@ expression: state
1909409, 1909409,
260770, 260770,
1403555 1403555
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
} }
], ],

View file

@ -45,6 +45,28 @@ expression: state
1606276, 1606276,
1587845, 1587845,
1211014 1211014
],
"conditions": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
] ]
} }
], ],