Move analysis into own crate and some more improvements all around

This commit is contained in:
Lol3rrr
2024-09-23 17:12:52 +02:00
parent 118a25aa9a
commit 7b50da640c
19 changed files with 676 additions and 206 deletions

10
analysis/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "analysis"
version = "0.1.0"
edition = "2021"
[dependencies]
csdemo = { package = "csdemo", git = "https://github.com/Lol3rrr/csdemo.git", ref = "main" }
[dev-dependencies]
pretty_assertions = { version = "1.4" }

179
analysis/src/endofgame.rs Normal file
View File

@@ -0,0 +1,179 @@
#[derive(Debug, PartialEq)]
pub struct EndOfGame {
pub map: String,
pub players: Vec<(PlayerInfo, PlayerStats)>,
}
#[derive(Debug, PartialEq)]
pub struct PlayerInfo {
pub name: String,
pub steam_id: String,
pub team: i32,
pub color: i32,
pub ingame_id: i32,
}
#[derive(Debug, Default, PartialEq)]
pub struct PlayerStats {
pub kills: usize,
pub deaths: usize,
pub damage: usize,
pub team_damage: usize,
pub self_damage: usize,
pub assists: usize,
}
pub fn parse(buf: &[u8]) -> Result<EndOfGame, ()> {
let tmp = csdemo::Container::parse(buf).map_err(|e| ())?;
let output = csdemo::parser::parse(
csdemo::FrameIterator::parse(tmp.inner),
csdemo::parser::EntityFilter::all(),
)
.map_err(|e| ())?;
let header = &output.header;
let mut player_stats = std::collections::HashMap::<_, PlayerStats>::new();
let mut track = false;
let mut player_life = std::collections::HashMap::<_, u8>::new();
for event in output.events.iter() {
match event {
csdemo::DemoEvent::GameEvent(gevent) => {
match gevent.as_ref() {
csdemo::game_event::GameEvent::RoundAnnounceMatchStart(_) => {
player_stats.clear();
track = true;
}
csdemo::game_event::GameEvent::RoundPreStart(_) => {
track = true;
}
csdemo::game_event::GameEvent::PlayerSpawn(pspawn) => {
player_life.insert(pspawn.userid.unwrap(), 100);
}
csdemo::game_event::GameEvent::WinPanelMatch(_) => {
track = false;
}
csdemo::game_event::GameEvent::RoundOfficiallyEnded(_) => {
track = false;
}
csdemo::game_event::GameEvent::PlayerDeath(pdeath) if track => {
player_death(pdeath, &output.player_info, &mut player_stats);
}
csdemo::game_event::GameEvent::PlayerHurt(phurt) if track => {
player_hurt(
phurt,
&output.player_info,
&mut player_stats,
&mut player_life,
);
}
other => {}
};
}
_ => {}
};
}
let mut players: Vec<_> = player_stats
.into_iter()
.filter_map(|(id, stats)| {
let player = output.player_info.get(&id)?;
Some((
PlayerInfo {
name: player.name.clone(),
steam_id: player.xuid.to_string(),
team: player.team,
color: player.color,
ingame_id: id.0,
},
stats,
))
})
.collect();
players.sort_unstable_by_key(|(p, _)| p.ingame_id);
let map = header.map_name().to_owned();
Ok(EndOfGame { map, players })
}
fn player_death(
death: &csdemo::game_event::PlayerDeath,
player_info: &std::collections::HashMap<csdemo::UserId, csdemo::parser::Player>,
player_stats: &mut std::collections::HashMap<csdemo::UserId, PlayerStats>,
) {
let player_died_id = death.userid.unwrap();
let player_died_player = player_info.get(&player_died_id).unwrap();
let player_died = player_stats.entry(player_died_id).or_default();
player_died.deaths += 1;
if let Some(attacker_id) = death.attacker.filter(|p| p.0 < 10) {
let attacker_player = player_info
.get(&attacker_id)
.expect(&format!("Attacker-ID: {:?}", attacker_id));
if attacker_player.team == player_died_player.team {
return;
} else {
let attacker = player_stats.entry(attacker_id).or_default();
attacker.kills += 1;
}
}
if let Some(assist_id) = death.assister.filter(|p| p.0 < 10) {
let assister_player = player_info
.get(&assist_id)
.expect(&format!("Assister-ID: {:?}", assist_id));
if assister_player.team == player_died_player.team {
} else {
let assister = player_stats.entry(assist_id).or_default();
assister.assists += 1;
}
}
}
fn player_hurt(
hurt: &csdemo::game_event::PlayerHurt,
player_info: &std::collections::HashMap<csdemo::UserId, csdemo::parser::Player>,
player_stats: &mut std::collections::HashMap<csdemo::UserId, PlayerStats>,
player_life: &mut std::collections::HashMap<csdemo::UserId, u8>,
) {
let attacked_player = match player_info.get(hurt.userid.as_ref().unwrap()) {
Some(a) => a,
None => return,
};
let attacker_id = match hurt.attacker {
Some(aid) => aid,
None => return,
};
let attacking_player = match player_info.get(&attacker_id) {
Some(a) => a,
None => return,
};
let attacker = player_stats.entry(attacker_id).or_default();
let n_health = match hurt.health {
Some(csdemo::RawValue::F32(v)) => v as u8,
Some(csdemo::RawValue::I32(v)) => v as u8,
Some(csdemo::RawValue::U64(v)) => v as u8,
_ => 0,
};
let dmg_dealt = player_life
.get(hurt.userid.as_ref().unwrap())
.copied()
.unwrap_or(100)
- n_health;
player_life.insert(hurt.userid.unwrap(), n_health);
if attacking_player.team == attacked_player.team {
return;
}
attacker.damage += dmg_dealt as usize;
}

1
analysis/src/lib.rs Normal file
View File

@@ -0,0 +1 @@
pub mod endofgame;

190
analysis/tests/endofgame.rs Normal file
View File

@@ -0,0 +1,190 @@
use analysis::endofgame;
use pretty_assertions::assert_eq;
#[test]
fn ancient() {
let input_bytes = include_bytes!("../../testfiles/nuke.dem");
let result = endofgame::parse(input_bytes).unwrap();
let expected = endofgame::EndOfGame {
map: "de_nuke".to_owned(),
players: vec![
(
endofgame::PlayerInfo {
name: "Excel".to_owned(),
steam_id: "76561198236134832".to_owned(),
team: 2,
color: 0,
ingame_id: 0,
},
endofgame::PlayerStats {
kills: 28,
deaths: 11,
damage: 2504,
team_damage: 0,
self_damage: 0,
assists: 4,
},
),
(
endofgame::PlayerInfo {
name: "Der Porzellan König".to_owned(),
steam_id: "76561198301388087".to_owned(),
team: 2,
color: 2,
ingame_id: 1,
},
endofgame::PlayerStats {
kills: 15,
deaths: 12,
damage: 1827,
team_damage: 0,
self_damage: 0,
assists: 6,
},
),
(
endofgame::PlayerInfo {
name: "Crippled Hentai addict".to_owned(),
steam_id: "76561198386810758".to_owned(),
team: 2,
color: 3,
ingame_id: 2,
},
endofgame::PlayerStats {
kills: 11,
deaths: 16,
damage: 1394,
team_damage: 0,
self_damage: 0,
assists: 5,
},
),
(
endofgame::PlayerInfo {
name: "Skalla_xD".to_owned(),
steam_id: "76561199014043225".to_owned(),
team: 2,
color: 1,
ingame_id: 3,
},
endofgame::PlayerStats {
kills: 11,
deaths: 15,
damage: 1331,
team_damage: 0,
self_damage: 0,
assists: 3,
},
),
(
endofgame::PlayerInfo {
name: "xTee".to_owned(),
steam_id: "76561199132258707".to_owned(),
team: 2,
color: 4,
ingame_id: 4,
},
endofgame::PlayerStats {
kills: 9,
deaths: 17,
damage: 1148,
team_damage: 0,
self_damage: 0,
assists: 2,
},
),
(
endofgame::PlayerInfo {
name: "cute".to_owned(),
steam_id: "76561197966517722".to_owned(),
team: 3,
color: 3,
ingame_id: 5,
},
endofgame::PlayerStats {
kills: 17,
deaths: 16,
damage: 2143,
team_damage: 0,
self_damage: 0,
assists: 7,
},
),
(
endofgame::PlayerInfo {
name: "zodiac".to_owned(),
steam_id: "76561198872143644".to_owned(),
team: 3,
color: 4,
ingame_id: 6,
},
endofgame::PlayerStats {
kills: 7,
deaths: 15,
damage: 844,
team_damage: 0,
self_damage: 0,
assists: 4,
},
),
(
endofgame::PlayerInfo {
name: "IReLaX exe".to_owned(),
steam_id: "76561199077629121".to_owned(),
team: 3,
color: 2,
ingame_id: 7,
},
endofgame::PlayerStats {
kills: 13,
deaths: 17,
damage: 1423,
team_damage: 0,
self_damage: 0,
assists: 6,
},
),
(
endofgame::PlayerInfo {
name: "Haze".to_owned(),
steam_id: "76561198375555469".to_owned(),
team: 3,
color: 0,
ingame_id: 8,
},
endofgame::PlayerStats {
kills: 19,
deaths: 15,
damage: 1512,
team_damage: 0,
self_damage: 0,
assists: 3,
},
),
(
endofgame::PlayerInfo {
name: "Know_Name".to_owned(),
steam_id: "76561198119236104".to_owned(),
team: 3,
color: 1,
ingame_id: 9,
},
endofgame::PlayerStats {
kills: 14,
deaths: 16,
damage: 1431,
team_damage: 0,
self_damage: 0,
assists: 4,
},
),
],
};
// TODO
// Add stats for rest of players
assert_eq!(result, expected);
}