In the events per round, it now dispalys the weapon used and headshot and noscope if applicable. Otherwise also cleaned up other code
185 lines
6.0 KiB
Rust
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 })
|
|
}
|