Skip to content

Commit

Permalink
Set player_death jump attribute.
Browse files Browse the repository at this point in the history
  • Loading branch information
abenea committed Oct 14, 2023
1 parent 695af0b commit 46e0b94
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 33 deletions.
33 changes: 19 additions & 14 deletions csdemoparser/src/cs2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use crate::demoinfo::{
};

use crate::game_event::{from_cs2_event, parse_game_event_list_impl, GameEvent};
use crate::{game_event, DemoInfo};
use crate::last_jump::LastJump;
use crate::{game_event, DemoInfo, Slot, UserId};
use cs2_demo::proto::demo::CDemoFileHeader;
use cs2_demo::proto::gameevents::CMsgSource1LegacyGameEventList;
use cs2_demo::{DemoCommand, StringTable};
Expand All @@ -20,7 +21,7 @@ pub fn parse(read: &mut dyn io::Read) -> anyhow::Result<DemoInfo> {
let mut parser = cs2_demo::DemoParser::try_new_after_demo_type(read)?;
let mut state = GameState::new();
while let Some((tick, cmd)) = parser.parse_next_demo_command()? {
trace!("t#{tick:?} {cmd}");
// trace!("t#{tick:?} {cmd}");
match cmd {
DemoCommand::FileHeader(header) => {
state.handle_file_header(header)?;
Expand All @@ -37,16 +38,10 @@ pub fn parse(read: &mut dyn io::Read) -> anyhow::Result<DemoInfo> {
state.get_info()
}

#[derive(Eq, PartialEq, Hash, Clone, Copy)]
struct Slot(u16);
#[derive(Eq, PartialEq, Hash, Clone, Copy)]
struct UserId(u16);

#[derive(Default)]
struct GameState {
game_event_descriptors: HashMap<i32, game_event::Descriptor>,
/// Maps player user_id to last jump tick.
jumped_last: HashMap<UserId, Tick>,
last_jump: LastJump<UserId>,
/// Maps player user_id to slot.
user_id2slot: HashMap<UserId, Slot>,
/// Maps player slot to player info.
Expand All @@ -62,9 +57,7 @@ struct GameState {

impl GameState {
fn new() -> Self {
GameState {
..Default::default()
}
Default::default()
}

fn handle_file_header(&mut self, header: CDemoFileHeader) -> anyhow::Result<()> {
Expand Down Expand Up @@ -109,6 +102,12 @@ impl GameState {
self.events.push(EventTick { tick, event })
}

/// Returns the user XUID if available.
///
/// userid 65535 is used as a marker for events where there is no alive player, for example:
/// - kills with no assister
/// - player disconnected
/// - player died, for example before the smoke_expired event
fn maybe_xuid(&self, userid: i32) -> u64 {
let Some(slot) = self.user_id2slot.get(&UserId(userid as u16)) else {
return userid as u64;
Expand All @@ -120,7 +119,7 @@ impl GameState {
}

fn handle_game_event(&mut self, ge: GameEvent, tick: Tick) -> anyhow::Result<()> {
trace!("GameEvent {:?}", ge);
trace!("#{tick} GameEvent {:?}", ge);
match ge {
GameEvent::BombDefused(e) => {
let userid = self.maybe_xuid(e.userid);
Expand All @@ -135,6 +134,11 @@ impl GameState {
let userid = self.maybe_xuid(e.userid);
let attacker = self.maybe_xuid(e.attacker);
let assister = self.maybe_xuid(e.assister);
let jump = self.last_jump.ticks_since_last_jump(
UserId(e.attacker as u16),
tick,
self.tick_interval,
);
self.add_event(
tick,
Event::PlayerDeath(PlayerDeath {
Expand All @@ -149,6 +153,7 @@ impl GameState {
thrusmoke: e.thrusmoke,
attackerblind: e.attackerblind,
distance: e.distance,
jump,
}),
)
}
Expand All @@ -173,7 +178,7 @@ impl GameState {
)
}
GameEvent::PlayerJump(e) => {
self.jumped_last.insert(UserId(e.userid as u16), tick);
self.last_jump.record_jump(UserId(e.userid as u16), tick);
}
GameEvent::PlayerSpawn(_) => {
// In CS:GO, player_spawn was used to determine the team composition
Expand Down
25 changes: 9 additions & 16 deletions csdemoparser/src/csgo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::entity::{Entities, Entity, EntityId, PropValue, Scalar};
use crate::entity::{ServerClasses, TrackProp};
use crate::game_event::parse_game_event_list_impl;
use crate::geometry::{through_smoke, Point};
use crate::last_jump::LastJump;
use crate::string_table::{self, PlayerInfo, StringTables};
use crate::{
account_id_to_xuid, game_event, guid_to_xuid, maybe_get_i32, maybe_get_u16, DemoInfo, TeamScore,
Expand Down Expand Up @@ -80,7 +81,7 @@ struct HeadshotBoxParser<'a> {
game_event_descriptors: HashMap<i32, game_event::Descriptor>,
string_tables: StringTables,
players: HashMap<i32, PlayerInfo>,
jumped_last: HashMap<i32, Tick>,
last_jump: LastJump<i32>,
tick_interval: f32,
entities: Entities<'a>,
smokes: BTreeMap<u16, Point>,
Expand Down Expand Up @@ -159,7 +160,7 @@ impl<'a> HeadshotBoxParser<'a> {
game_event_descriptors: Default::default(),
string_tables: StringTables::new(),
players: Default::default(),
jumped_last: HashMap::new(),
last_jump: Default::default(),
tick_interval: 0.0,
entities: Entities::new(server_classes),
smokes: Default::default(),
Expand Down Expand Up @@ -276,7 +277,7 @@ impl<'a> HeadshotBoxParser<'a> {
match attrs.get("type").unwrap().as_str().unwrap() {
"player_jump" => {
if let Some(user_id) = maybe_get_i32(attrs.get("userid")) {
self.jumped_last.insert(user_id, tick);
self.last_jump.record_jump(user_id, tick);
}
}
"smokegrenade_detonate" => {
Expand All @@ -297,7 +298,11 @@ impl<'a> HeadshotBoxParser<'a> {
"player_death" => {
if let Some(attacker_user_id) = maybe_get_i32(attrs.get("attacker")) {
if self.players.get(&attacker_user_id).is_some() {
if let Some(jump) = self.jumped_last(attacker_user_id, tick) {
if let Some(jump) = self.last_jump.ticks_since_last_jump(
attacker_user_id,
tick,
self.tick_interval,
) {
attrs.insert("jump".to_string(), json!(jump));
}
}
Expand Down Expand Up @@ -421,18 +426,6 @@ impl<'a> HeadshotBoxParser<'a> {
Ok(attrs)
}

fn jumped_last(&self, user_id: i32, tick: Tick) -> Option<Tick> {
let &jumped_last = self.jumped_last.get(&user_id)?;
const JUMP_DURATION: f64 = 0.75;
if self.tick_interval > 0_f32
&& jumped_last as f64 >= tick as f64 - JUMP_DURATION / self.tick_interval as f64
{
Some(tick - jumped_last)
} else {
None
}
}

fn get_player_info(&self, key: &str, attrs: &GameEvent) -> Option<&PlayerInfo> {
let user_id = maybe_get_i32(attrs.get(key))?;
let player_info = self.players.get(&user_id)?;
Expand Down
5 changes: 5 additions & 0 deletions csdemoparser/src/demoinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ pub struct PlayerDeath {
pub noscope: bool,
pub thrusmoke: bool,
pub attackerblind: bool,
/// Distance in meters. 1 meter = 39.38 coordinate distance.
pub distance: f32,
/// Number of ticks since the attacker jumped. Only set if death occurred
/// less than 0.75 seconds since the jump.
#[serde(skip_serializing_if = "Option::is_none")]
pub jump: Option<Tick>,
}

#[derive(Serialize)]
Expand Down
31 changes: 31 additions & 0 deletions csdemoparser/src/last_jump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use demo_format::Tick;
use std::collections::HashMap;

#[derive(Default)]
pub(crate) struct LastJump<U> {
/// Maps player user_id to last jump tick.
jumped_last: HashMap<U, Tick>,
}

impl<U: Eq + PartialEq + std::hash::Hash> LastJump<U> {
pub(crate) fn record_jump(&mut self, user_id: U, tick: Tick) {
self.jumped_last.insert(user_id, tick);
}

pub(crate) fn ticks_since_last_jump(
&self,
user_id: U,
tick: Tick,
tick_interval: f32,
) -> Option<Tick> {
let &jumped_last = self.jumped_last.get(&user_id)?;
const JUMP_DURATION: f64 = 0.75;
if tick_interval > 0_f32
&& jumped_last as f64 >= tick as f64 - JUMP_DURATION / tick_interval as f64
{
Some(tick - jumped_last)
} else {
None
}
}
}
19 changes: 16 additions & 3 deletions csdemoparser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod demoinfo;
mod entity;
mod game_event;
mod geometry;
mod last_jump;
mod string_table;

use crate::entity::{Entity, EntityId, PropValue, Scalar};
Expand All @@ -29,6 +30,11 @@ pub fn parse(mut read: &mut dyn io::Read) -> anyhow::Result<DemoInfo> {
}
}

#[derive(Eq, PartialEq, Hash, Clone, Copy)]
struct Slot(u16);
#[derive(Eq, PartialEq, Hash, Clone, Copy, Default)]
struct UserId(u16);

#[derive(Default)]
struct TeamScore {
team_entity_id: [Option<EntityId>; 2],
Expand All @@ -40,9 +46,16 @@ struct TeamScore {

impl TeamScore {
fn update(&mut self, entity: &Entity, value: &PropValue) -> bool {
let Some(pos) = self.team_entity_id.iter().position(|i| &Some(entity.id) == i)
else { return false };
let &PropValue::Scalar(Scalar::I32(new_score)) = value else { return false };
let Some(pos) = self
.team_entity_id
.iter()
.position(|i| &Some(entity.id) == i)
else {
return false;
};
let &PropValue::Scalar(Scalar::I32(new_score)) = value else {
return false;
};
if new_score < self.round_start[0] && new_score < self.round_start[1] {
return false;
}
Expand Down

0 comments on commit 46e0b94

Please sign in to comment.