Files
knifer/analysis/src/perround.rs
Lol3rrr 898a889a53 Add weapon and some other info to perround
In the events per round, it now dispalys the weapon used and headshot and noscope if applicable.
Otherwise also cleaned up other code
2024-11-03 14:47:34 +01:00

185 lines
6.0 KiB
Rust

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum WinReason {
StillInProgress,
BombExploded,
VipEscaped,
VipKilled,
TSaved,
CtStoppedEscape,
RoundEndReasonTerroristsStopped,
BombDefused,
TKilled,
CTKilled,
Draw,
HostageRescued,
TimeRanOut,
RoundEndReasonHostagesNotRescued,
TerroristsNotEscaped,
VipNotEscaped,
GameStart,
TSurrender,
CTSurrender,
TPlanted,
CTReachedHostage,
}
// https://github.com/markus-wa/demoinfocs-golang/blob/205b0bb25e9f3e96e1d306d154199b4a6292940e/pkg/demoinfocs/events/events.go#L53
pub static ROUND_WIN_REASON: phf::Map<i32, WinReason> = phf::phf_map! {
0_i32 => WinReason::StillInProgress,
1_i32 => WinReason::BombExploded,
2_i32 => WinReason::VipEscaped,
3_i32 => WinReason::VipKilled,
4_i32 => WinReason::TSaved,
5_i32 => WinReason::CtStoppedEscape,
6_i32 => WinReason::RoundEndReasonTerroristsStopped,
7_i32 => WinReason::BombDefused,
8_i32 => WinReason::TKilled,
9_i32 => WinReason::CTKilled,
10_i32 => WinReason::Draw,
11_i32 => WinReason::HostageRescued,
12_i32 => WinReason::TimeRanOut,
13_i32 => WinReason::RoundEndReasonHostagesNotRescued,
14_i32 => WinReason::TerroristsNotEscaped,
15_i32 => WinReason::VipNotEscaped,
16_i32 => WinReason::GameStart,
17_i32 => WinReason::TSurrender,
18_i32 => WinReason::CTSurrender,
19_i32 => WinReason::TPlanted,
20_i32 => WinReason::CTReachedHostage,
};
#[derive(Debug)]
pub struct Round {
pub winreason: WinReason,
pub start: u32,
pub end: u32,
pub events: Vec<RoundEvent>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RoundEvent {
BombPlanted,
BombDefused,
Kill {
attacker: u64,
died: u64,
#[serde(default)]
weapon: Option<String>,
#[serde(default)]
headshot: bool,
#[serde(default)]
noscope: bool,
},
}
#[derive(Debug)]
pub struct PerRound {
pub rounds: Vec<Round>,
}
pub fn parse(buf: &[u8]) -> Result<PerRound, ()> {
let tmp = csdemo::Container::parse(buf).map_err(|e| ())?;
let output = csdemo::lazyparser::LazyParser::new(tmp);
let player_info = output.player_info();
let mut rounds: Vec<Round> = Vec::new();
for (tick, state) in output.entities().filter_map(|e| e.ok()) {
let round_start_count = state
.get_prop("CCSGameRulesProxy.CCSGameRules.m_nRoundStartCount")
.map(|v| v.value.as_u32())
.flatten();
if let Some(round_start_count) = round_start_count {
if rounds.len() < (round_start_count - 1) as usize {
rounds.push(Round {
winreason: WinReason::StillInProgress,
start: tick,
end: u32::MAX,
events: Vec::new(),
});
}
}
let round_end_count = state
.get_prop("CCSGameRulesProxy.CCSGameRules.m_nRoundEndCount")
.map(|v| v.value.as_u32())
.flatten();
if let Some(round_end_count) = round_end_count {
if rounds.len() == (round_end_count - 1) as usize {
rounds.last_mut().unwrap().end = tick;
}
}
if state.class.as_ref() == "CCSGameRulesProxy" {
let round_win_reason = state
.get_prop("CCSGameRulesProxy.CCSGameRules.m_eRoundWinReason")
.map(|p| p.value.as_i32())
.flatten()
.map(|v| ROUND_WIN_REASON.get(&v))
.flatten()
.filter(|r| !matches!(r, WinReason::StillInProgress));
if let Some(round_win_reason) = round_win_reason {
rounds.last_mut().unwrap().winreason = round_win_reason.clone();
}
}
}
let mut rounds_iter = rounds.iter_mut();
let mut current_tick = 0;
let mut current_round = rounds_iter.next().unwrap();
'events: for event in output.events().filter_map(|e| e.ok()) {
match event {
csdemo::DemoEvent::Tick(tick) => {
current_tick = tick.tick();
}
csdemo::DemoEvent::GameEvent(ge) => {
if current_tick < current_round.start {
continue;
}
while current_tick > current_round.end {
match rounds_iter.next() {
Some(r) => {
current_round = r;
}
None => break 'events,
};
}
let event = match *ge {
csdemo::game_event::GameEvent::BombPlanted(planted) => RoundEvent::BombPlanted,
csdemo::game_event::GameEvent::BombDefused(defused) => RoundEvent::BombDefused,
csdemo::game_event::GameEvent::PlayerDeath(death) => {
let died = match death.userid {
Some(d) => d,
None => continue,
};
let attacker = match death.attacker.filter(|p| p.0 <= 10) {
Some(a) => a,
None => died.clone(),
};
let died_player = player_info.get(&died).unwrap();
let attacker_player = player_info.get(&attacker).unwrap();
RoundEvent::Kill {
attacker: attacker_player.xuid,
died: died_player.xuid,
weapon: death.weapon,
noscope: death.noscope.unwrap_or(false),
headshot: death.headshot.unwrap_or(false),
}
}
_ => continue,
};
current_round.events.push(event);
}
_ => {}
};
}
Ok(PerRound { rounds })
}