mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 18:24:05 +02:00
Fix hardcoded server class IDs
In hindsight, these are obviously different between games and shouldn't be hardcoded Also clean up main function a bit, since the analyzer already uses packets there's no need to switch between `all`
This commit is contained in:
parent
df793083aa
commit
138343c251
3 changed files with 156 additions and 170 deletions
|
|
@ -38,11 +38,7 @@ fn main() -> Result<(), MainError> {
|
|||
let (_, state) = parser.parse()?;
|
||||
println!("{}", serde_json::to_string(&state)?);
|
||||
} else {
|
||||
let parser = if all {
|
||||
DemoParser::new_all_with_analyser(demo.get_stream(), PlayerSummaryAnalyzer::new())
|
||||
} else {
|
||||
DemoParser::new_with_analyser(demo.get_stream(), PlayerSummaryAnalyzer::new())
|
||||
};
|
||||
let parser = DemoParser::new_with_analyser(demo.get_stream(), PlayerSummaryAnalyzer::new());
|
||||
let (header, state) = parser.parse()?;
|
||||
|
||||
println!("{:?}", header);
|
||||
|
|
|
|||
|
|
@ -58,16 +58,6 @@ impl PartialEq<u16> for ClassId {
|
|||
}
|
||||
}
|
||||
|
||||
impl ClassId {
|
||||
pub const fn new(val: u16) -> ClassId {
|
||||
ClassId(val)
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[derive(BitRead, BitWrite, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Clone, Display)]
|
||||
pub struct ServerClassName(String);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ impl MessageHandler for PlayerSummaryAnalyzer {
|
|||
|
||||
fn handle_message(&mut self, message: &Message, tick: DemoTick, parser_state: &ParserState) {
|
||||
match message {
|
||||
// Message::GameEvent(message) => self.handle_event(&message.event, tick),
|
||||
Message::PacketEntities(message) => {
|
||||
for entity in message.entities.iter() {
|
||||
self.handle_packet_entity(&entity, parser_state);
|
||||
|
|
@ -127,166 +126,167 @@ impl PlayerSummaryAnalyzer {
|
|||
fn handle_packet_entity(&mut self, packet: &PacketEntity, parser_state: &ParserState) {
|
||||
use crate::demo::sendprop::SendPropValue;
|
||||
|
||||
/// Data for a particular player
|
||||
const PLAYER_DATA_CLASS: ClassId = ClassId::new(247);
|
||||
// println!("Known server classes: {:?}", parser_state.server_classes);
|
||||
|
||||
/// Metadata for all players (eg pings and user IDs)
|
||||
const PLAYER_META_CLASS: ClassId = ClassId::new(249);
|
||||
if let Some(class) = parser_state.server_classes.get(<ClassId as Into<usize>>::into(packet.server_class)) {
|
||||
// println!("Got a {} data packet: {:?}", class.name, packet);
|
||||
match class.name.as_str() {
|
||||
"CTFPlayer" => {
|
||||
match self.user_id_map.get(&packet.entity_index) {
|
||||
Some(user_id) => {
|
||||
let summaries = &mut self.state.player_summaries;
|
||||
let player_summary = summaries.entry(*user_id).or_default();
|
||||
|
||||
match packet.server_class {
|
||||
PLAYER_DATA_CLASS => {
|
||||
match self.user_id_map.get(&packet.entity_index) {
|
||||
Some(user_id) => {
|
||||
let summaries = &mut self.state.player_summaries;
|
||||
let player_summary = summaries.entry(*user_id).or_default();
|
||||
// Extract scoreboard information, if present, and update the player's summary accordingly
|
||||
// NOTE: Multiple DT_TFPlayerScoringDataExclusive structures may be present - one for the entire match,
|
||||
// and one for just the current round. Since we're only interested in the overall match scores,
|
||||
// we need to ignore the round-specific values. Fortunately, this is easy - just ignore the
|
||||
// lesser value (if multiple values are present), since none of these scores are able to decrement.
|
||||
|
||||
// Extract scoreboard information, if present, and update the player's summary accordingly
|
||||
// NOTE: Multiple DT_TFPlayerScoringDataExclusive structures may be present - one for the entire match,
|
||||
// and one for just the current round. Since we're only interested in the overall match scores,
|
||||
// we need to ignore the round-specific values. Fortunately, this is easy - just ignore the
|
||||
// lesser value (if multiple values are present), since none of these scores are able to decrement.
|
||||
|
||||
/*
|
||||
* Member: m_iCaptures (offset 4) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDefenses (offset 8) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iKills (offset 12) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDeaths (offset 16) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iSuicides (offset 20) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDominations (offset 24) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iRevenge (offset 28) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iBuildingsBuilt (offset 32) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iBuildingsDestroyed (offset 36) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iHeadshots (offset 40) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iBackstabs (offset 44) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iHealPoints (offset 48) (type integer) (bits 20) (Unsigned)
|
||||
* Member: m_iInvulns (offset 52) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iTeleports (offset 56) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDamageDone (offset 60) (type integer) (bits 20) (Unsigned)
|
||||
* Member: m_iCrits (offset 64) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iResupplyPoints (offset 68) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iKillAssists (offset 72) (type integer) (bits 12) (Unsigned)
|
||||
* Member: m_iBonusPoints (offset 76) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iPoints (offset 80) (type integer) (bits 10) (Unsigned)
|
||||
*
|
||||
* NOTE: support points aren't included here, but is equal to the sum of m_iHealingAssist and m_iDamageAssist
|
||||
* TODO: pull data for support points
|
||||
*/
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iCaptures", parser_state, |captures| {
|
||||
if captures > player_summary.captures {
|
||||
player_summary.captures = captures;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDefenses", parser_state, |defenses| {
|
||||
if defenses > player_summary.defenses {
|
||||
player_summary.defenses = defenses;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iKills", parser_state, |kills| {
|
||||
if kills > player_summary.kills {
|
||||
// TODO: This might not be accruate. Tested with a demo file with 89 kills (88 on the scoreboard),
|
||||
// but only a 83 were reported in the scoring data.
|
||||
player_summary.kills = kills;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDeaths", parser_state, |deaths| {
|
||||
if deaths > player_summary.deaths {
|
||||
player_summary.deaths = deaths;
|
||||
}
|
||||
});
|
||||
// ignore m_iSuicides
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDominations", parser_state, |dominations| {
|
||||
if dominations > player_summary.dominations {
|
||||
player_summary.dominations = dominations;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iRevenge", parser_state, |revenges| {
|
||||
if revenges > player_summary.revenges {
|
||||
player_summary.revenges = revenges;
|
||||
}
|
||||
});
|
||||
// ignore m_iBuildingsBuilt
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iBuildingsDestroyed", parser_state, |buildings_destroyed| {
|
||||
if buildings_destroyed > player_summary.buildings_destroyed {
|
||||
player_summary.buildings_destroyed = buildings_destroyed;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iHeadshots", parser_state, |headshots| {
|
||||
if headshots > player_summary.headshots {
|
||||
player_summary.headshots = headshots;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iBackstabs", parser_state, |backstabs| {
|
||||
if backstabs > player_summary.backstabs {
|
||||
player_summary.backstabs = backstabs;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iHealPoints", parser_state, |healing| {
|
||||
if healing > player_summary.healing {
|
||||
player_summary.healing = healing;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iInvulns", parser_state, |ubercharges| {
|
||||
if ubercharges > player_summary.ubercharges {
|
||||
player_summary.ubercharges = ubercharges;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iTeleports", parser_state, |teleports| {
|
||||
if teleports > player_summary.teleports {
|
||||
player_summary.teleports = teleports;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDamageDone", parser_state, |damage_dealt| {
|
||||
if damage_dealt > player_summary.damage_dealt {
|
||||
player_summary.damage_dealt = damage_dealt;
|
||||
}
|
||||
});
|
||||
// ignore m_iCrits
|
||||
// ignore m_iResupplyPoints
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iKillAssists", parser_state, |assists| {
|
||||
if assists > player_summary.assists {
|
||||
player_summary.assists = assists;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iBonusPoints", parser_state, |bonus_points| {
|
||||
if bonus_points > player_summary.bonus_points {
|
||||
player_summary.bonus_points = bonus_points;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iPoints", parser_state, |points| {
|
||||
if points > player_summary.points {
|
||||
player_summary.points = points;
|
||||
}
|
||||
});
|
||||
},
|
||||
None => {
|
||||
// Player entity likely spawned before the player was assigned to it?
|
||||
// This can rarely happen, but doesn't seem to affect the end result
|
||||
},
|
||||
}
|
||||
},
|
||||
PLAYER_META_CLASS => {
|
||||
// Player summaries - including entity IDs!
|
||||
// look for props like m_iUserID.<entity_id> = <user_id>
|
||||
// for example, `m_iUserID.024 = 2523` means entity 24 is user 2523
|
||||
for i in 0..33 { // 0 to 32, inclusive (1..33 might also work, not sure if there's a user 0 or not). Not exhaustive and doesn't work for servers with > 32 players
|
||||
match packet.get_prop_by_name("m_iUserID", format!("{:0>3}", i).as_str(), parser_state) {
|
||||
Some(prop) => {
|
||||
match prop.value {
|
||||
SendPropValue::Integer(x) => {
|
||||
let entity_id = EntityId::from(i as u32);
|
||||
let user_id = UserId::from(x as u32);
|
||||
self.user_id_map.insert(entity_id, user_id);
|
||||
},
|
||||
_ => {
|
||||
// These properties should all be integers...
|
||||
/*
|
||||
* Member: m_iCaptures (offset 4) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDefenses (offset 8) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iKills (offset 12) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDeaths (offset 16) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iSuicides (offset 20) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDominations (offset 24) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iRevenge (offset 28) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iBuildingsBuilt (offset 32) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iBuildingsDestroyed (offset 36) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iHeadshots (offset 40) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iBackstabs (offset 44) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iHealPoints (offset 48) (type integer) (bits 20) (Unsigned)
|
||||
* Member: m_iInvulns (offset 52) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iTeleports (offset 56) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iDamageDone (offset 60) (type integer) (bits 20) (Unsigned)
|
||||
* Member: m_iCrits (offset 64) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iResupplyPoints (offset 68) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iKillAssists (offset 72) (type integer) (bits 12) (Unsigned)
|
||||
* Member: m_iBonusPoints (offset 76) (type integer) (bits 10) (Unsigned)
|
||||
* Member: m_iPoints (offset 80) (type integer) (bits 10) (Unsigned)
|
||||
*
|
||||
* NOTE: support points aren't included here, but is equal to the sum of m_iHealingAssist and m_iDamageAssist
|
||||
* TODO: pull data for support points
|
||||
*/
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iCaptures", parser_state, |captures| {
|
||||
if captures > player_summary.captures {
|
||||
player_summary.captures = captures;
|
||||
}
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDefenses", parser_state, |defenses| {
|
||||
if defenses > player_summary.defenses {
|
||||
player_summary.defenses = defenses;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iKills", parser_state, |kills| {
|
||||
if kills > player_summary.kills {
|
||||
// TODO: This might not be accruate. Tested with a demo file with 89 kills (88 on the scoreboard),
|
||||
// but only a 83 were reported in the scoring data.
|
||||
player_summary.kills = kills;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDeaths", parser_state, |deaths| {
|
||||
if deaths > player_summary.deaths {
|
||||
player_summary.deaths = deaths;
|
||||
}
|
||||
});
|
||||
// ignore m_iSuicides
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDominations", parser_state, |dominations| {
|
||||
if dominations > player_summary.dominations {
|
||||
player_summary.dominations = dominations;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iRevenge", parser_state, |revenges| {
|
||||
if revenges > player_summary.revenges {
|
||||
player_summary.revenges = revenges;
|
||||
}
|
||||
});
|
||||
// ignore m_iBuildingsBuilt
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iBuildingsDestroyed", parser_state, |buildings_destroyed| {
|
||||
if buildings_destroyed > player_summary.buildings_destroyed {
|
||||
player_summary.buildings_destroyed = buildings_destroyed;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iHeadshots", parser_state, |headshots| {
|
||||
if headshots > player_summary.headshots {
|
||||
player_summary.headshots = headshots;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iBackstabs", parser_state, |backstabs| {
|
||||
if backstabs > player_summary.backstabs {
|
||||
player_summary.backstabs = backstabs;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iHealPoints", parser_state, |healing| {
|
||||
if healing > player_summary.healing {
|
||||
player_summary.healing = healing;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iInvulns", parser_state, |ubercharges| {
|
||||
if ubercharges > player_summary.ubercharges {
|
||||
player_summary.ubercharges = ubercharges;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iTeleports", parser_state, |teleports| {
|
||||
if teleports > player_summary.teleports {
|
||||
player_summary.teleports = teleports;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iDamageDone", parser_state, |damage_dealt| {
|
||||
if damage_dealt > player_summary.damage_dealt {
|
||||
player_summary.damage_dealt = damage_dealt;
|
||||
}
|
||||
});
|
||||
// ignore m_iCrits
|
||||
// ignore m_iResupplyPoints
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iKillAssists", parser_state, |assists| {
|
||||
if assists > player_summary.assists {
|
||||
player_summary.assists = assists;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iBonusPoints", parser_state, |bonus_points| {
|
||||
if bonus_points > player_summary.bonus_points {
|
||||
player_summary.bonus_points = bonus_points;
|
||||
}
|
||||
});
|
||||
parse_integer_prop(packet, "DT_TFPlayerScoringDataExclusive", "m_iPoints", parser_state, |points| {
|
||||
if points > player_summary.points {
|
||||
player_summary.points = points;
|
||||
}
|
||||
});
|
||||
},
|
||||
None => {
|
||||
// Player entity likely spawned before the player was assigned to it?
|
||||
// This can rarely happen, but doesn't seem to affect the end result
|
||||
},
|
||||
None => {} // ignore
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {} // ignore
|
||||
},
|
||||
"CTFPlayerResource" => {
|
||||
// Player summaries - including entity IDs!
|
||||
// look for props like m_iUserID.<entity_id> = <user_id>
|
||||
// for example, `m_iUserID.024 = 2523` means entity 24 is user 2523
|
||||
for i in 0..33 { // 0 to 32, inclusive (1..33 might also work, not sure if there's a user 0 or not). Not exhaustive and doesn't work for servers with > 32 players
|
||||
match packet.get_prop_by_name("m_iUserID", format!("{:0>3}", i).as_str(), parser_state) {
|
||||
Some(prop) => {
|
||||
match prop.value {
|
||||
SendPropValue::Integer(x) => {
|
||||
let entity_id = EntityId::from(i as u32);
|
||||
let user_id = UserId::from(x as u32);
|
||||
self.user_id_map.insert(entity_id, user_id);
|
||||
},
|
||||
_ => {
|
||||
// These properties should all be integers...
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {} // ignore, no property for this entity was included
|
||||
}
|
||||
}
|
||||
},
|
||||
_other => {
|
||||
// Don't care
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue