basic parsing

This commit is contained in:
Robin Appelman 2020-04-19 19:42:45 +02:00
commit 0151dfce7d
10 changed files with 6974 additions and 2 deletions

272
src/data.rs Normal file
View file

@ -0,0 +1,272 @@
use serde::Deserialize;
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum Team {
Blue,
Red,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum Class {
Scout,
Soldier,
Pyro,
Demoman,
HeavyWeapons,
Engineer,
Medic,
Sniper,
Spy,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum GameMode {
UltiDuo,
Fours,
Sixes,
Sevens,
Highlander,
Other,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize)]
pub enum EventType {
Charge,
PointCap,
MedicDeath,
RoundWin,
}
#[derive(Debug, Clone, sqlx::Type, Deserialize, Hash, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Medigun {
Medigun,
KritzKrieg,
QuickFix,
Vacinator
}
impl Default for Medigun {
fn default() -> Self {
Medigun::Medigun
}
}
#[derive(Debug, Clone, sqlx::Type, Deserialize, Hash, Eq, PartialEq)]
// #[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum Weapon {
Sniperrifle,
TauntSniper,
SydneySleeper,
TheWinger,
HotHand,
DeflectRocket,
ScoutSword,
VoodooPin,
Degreaser,
Shortstop,
RobotArm,
TfPumpkinBomb,
Kunai,
Wrench,
NecroSmasher,
Headtaker,
ProtoSyringe,
EternalReward,
EurekaEffect,
DragonsFuryBonus,
GrapplingHook,
PepPistol,
Guillotine,
LavaAxe,
WranglerKill,
PanicAttack,
LooseCannon,
Thirddegree,
TauntPyro,
IronBomber,
PersianPersuader,
Amputator,
TfProjectilePipe,
AwperHand,
Demokatana,
LooseCannonReflect,
ObjMinisentry,
BackScratcher,
Pomson,
TheClassic,
Sandman,
Axtinguisher,
ObjSentrygun2,
Jar,
Samrevolver,
DeflectFlare,
LochNLoad,
TfProjectileRocket,
Annihilator,
RocketpackStomp,
UllapoolCaberExplosion,
Minigun,
DeflectPromode,
LibertyLauncher,
Nessieclub,
QuickiebombLauncher,
CowMangler,
WrapAssassin,
Blackbox,
CandyCane,
RocketlauncherDirecthit,
AiFlamethrower,
SodaPopper,
FrontierJustice,
RescueRangerReflect,
WarriorSpirit,
Widowmaker,
QuakeRl,
TfProjectileArrow,
Sledgehammer,
DeflectArrow,
MarketGardener,
UniquePickaxeEscape,
PistolScout,
BostonBasher,
SpellbookBats,
Holymackerel,
Club,
Ball,
Fireaxe,
Backburner,
EvictionNotice,
HolidayPunch,
TauntSoldier,
ShotgunPrimary,
NonnonviolentProtest,
ForceANature,
Paintrain,
FreedomStaff,
ShotgunHwg,
LongHeatmaker,
ShotgunSoldier,
Knife,
Batsaber,
Mailbox,
CompoundBow,
UllapoolCaber,
Ambassador,
PlayerPenetration,
Powerjack,
CrusadersCrossbow,
ScotlandShard,
WrenchJag,
Scattergun,
Unknown,
Shahanshah,
Pistol,
SpellbookBoss,
Smg,
DragonsFury,
Revolver,
Player,
TheMaul,
Skullbat,
HamShank,
SolemnVow,
IronCurtain,
Bonesaw,
DumpsterDevice,
Bushwacka,
Builder,
BreadBite,
SouthernHospitality,
Tribalkukri,
TheCapper,
Fists,
DisciplinaryAction,
TfProjectileFlare,
BleedKill,
BlackRose,
Letranger,
Tomislav,
Atomizer,
BackScatter,
PepBrawlerblaster,
TfProjectilePipeRemote,
Battleaxe,
DeflectFlareDetonator,
TauntMedic,
Telefrag,
StickybombDefender,
SplendidScreen,
Claidheamohmor,
Airstrike,
RighteousBison,
GlovesRunningUrgently,
Sword,
Mantreads,
DeflectSticky,
Enforcer,
ScorchShot,
ProRifle,
SpyCicle,
Bat,
SharpDresser,
SpellbookLightning,
TideTurner,
ShotgunPyro,
LavaBat,
TauntHeavy,
Bottle,
UniquePickaxe,
Phlogistinator,
CrossingGuard,
GigerCounter,
ChargedSmg,
SyringegunMedic,
Gloves,
BazaarBargain,
SpellbookMirv,
BigEarner,
Battleneedle,
Warfan,
ObjSentrygun,
Manmelter,
FamilyBusiness,
ReserveShooter,
ShortCircuit,
Flaregun,
SpellbookFireball,
World,
JarMilk,
Flamethrower,
ShootingStar,
TriggerHurt,
Blutsauger,
TauntSpy,
TfProjectileEnergyBall,
JarGas,
TfProjectileMechanicalarmorb,
ObjSentrygun3,
Diamondback,
Shovel,
BrassBeast,
LooseCannonImpact,
Demoshield,
PrinnyMachete,
Machina,
RocketlauncherFireball,
StickyResistance,
Detonator,
TfProjectileSentryrocket,
UnarmedCombat,
SpellbookSkeleton,
Ubersaw,
Maxgun,
RobotArmComboKill,
RescueRanger,
Apocofists,
Natascha,
ProSmg,
SteelFists,
Fryingpan,
}

View file

@ -1,3 +1,6 @@
mod data;
mod raw;
fn main() {
println!("Hello, world!");
}

294
src/raw.rs Normal file
View file

@ -0,0 +1,294 @@
use std::collections::HashMap;
use steamid_ng::SteamID;
use crate::data::{Medigun, Class, Weapon, Team as TeamId};
use serde::Deserialize;
use serde::export::TryFrom;
#[derive(Debug, Clone, Deserialize)]
pub struct RawLog {
#[serde(default)]
pub version: u8,
#[serde(default)]
pub length: u32,
pub teams: Option<Teams>,
pub players: HashMap<SteamID, Player>,
pub names: HashMap<SteamID, String>,
pub rounds: Option<Vec<Round>>,
pub healspread: HashMap<SteamID, HashMap<SteamID, u32>>,
pub classkills: HashMap<SteamID, ClassNumbers>,
pub classdeaths: HashMap<SteamID, ClassNumbers>,
pub classkillassists: Option<HashMap<SteamID, ClassNumbers>>,
pub chat: Vec<ChatMessage>,
pub info: Info,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Teams {
pub red: Team,
pub blue: Team,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Team {
pub score: u32,
pub kills: u32,
#[serde(default)]
pub deaths: u32,
pub dmg: u32,
#[serde(default)]
pub charges: u32,
#[serde(default)]
pub drops: u32,
#[serde(default)]
pub firstcaps: u32,
#[serde(default)]
pub caps: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Player {
pub team: TeamId,
pub kills: u16,
pub deaths: u16,
pub assists: u16,
#[serde(default)]
pub suicides: u16,
#[serde(default)]
pub dmg: u32,
#[serde(default)]
pub dmg_real: u32,
#[serde(default)]
pub dt: u32,
#[serde(default)]
pub dt_real: u32,
#[serde(default)]
pub hr: u16,
#[serde(default)]
pub lks: u16,
pub ubers: u8,
#[serde(default)]
pub ubertypes: HashMap<Medigun, u8>,
pub drops: u8,
pub medkits: u8,
#[serde(default)]
pub medkits_hp: u16,
pub backstabs: u8,
pub headshots: u8,
#[serde(default)]
pub headshots_hit: u8,
pub heal: u32,
pub cpc: u8,
pub ic: u8,
pub medicstat: Option<MedicStats>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MedicStats {
pub advantages_lost: u8,
pub biggest_advantage_list: u16,
pub deaths_within_20s_after_uber: u8,
pub deaths_with_95_99_uber: u8,
pub avg_time_before_healing: f32,
pub avg_time_to_build: f32,
pub avg_time_before_using: f32,
pub avg_uber_length: f32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClassStat {
#[serde(rename = "type")]
pub class: Class,
pub kills: u16,
pub dmg: u32,
pub total_time: u32,
pub weapon: HashMap<Weapon, WeaponStat>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WeaponStat {
pub kills: u32,
pub dmg: u32,
pub avg_dmg: u32,
pub shots: u32,
pub hits: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Round {
#[serde(default)]
pub start_time: u64,
pub winner: TeamId,
#[serde(rename = "firstcap")]
pub first_cap: Option<TeamId>,
pub length: u32,
pub team: Option<Teams>,
pub players: HashMap<SteamID, RoundPlayer>,
pub events: Vec<Event>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RoundPlayer {
pub kills: u32,
pub dmg: u32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum Event {
Charge {
#[serde(default)]
medigun: Medigun,
time: u32,
steamid: SteamID,
team: TeamId,
},
#[serde(rename = "pointcap")]
PointCap {
time: u32,
team: TeamId,
point: u8,
},
MedicDeath {
time: u32,
team: TeamId,
steamid: SteamID,
killer: SteamID,
},
RoundWin {
time: u32,
team: TeamId,
},
Drop {
time: u32,
steamid: SteamID,
team: TeamId,
},
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClassNumbers {
#[serde(default)]
pub scout: u8,
#[serde(default)]
pub soldier: u8,
#[serde(default)]
pub pyro: u8,
#[serde(default)]
pub demoman: u8,
#[serde(default)]
pub heavyweapons: u8,
#[serde(default)]
pub engineer: u8,
#[serde(default)]
pub medic: u8,
#[serde(default)]
pub sniper: u8,
#[serde(default)]
pub spy: u8,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ChatMessage {
pub steamid: ChatFrom,
pub name: String,
pub msg: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
#[serde(try_from = "String")]
pub enum ChatFrom {
Player(SteamID),
Console,
}
impl TryFrom<String> for ChatFrom {
type Error = steamid_ng::SteamIDParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value == "Console" {
Ok(ChatFrom::Console)
} else {
value.parse().map(ChatFrom::Player)
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Info {
pub map: String,
pub total_length: u32,
#[serde(default)]
pub supplemental: bool,
#[serde(default)]
pub has_real_damage: bool,
#[serde(default)]
pub has_weapon_damage: bool,
#[serde(default)]
pub has_accuracy: bool,
#[serde(default)]
pub has_hp: bool,
#[serde(default)]
pub has_hp_real: bool,
#[serde(default)]
pub has_hs: bool,
#[serde(default)]
pub has_hs_hit: bool,
#[serde(default)]
pub has_bs: bool,
#[serde(default)]
pub has_cp: bool,
#[serde(default)]
pub has_sb: bool,
#[serde(default)]
pub has_dt: bool,
#[serde(default)]
pub has_as: bool,
#[serde(default)]
pub has_hr: bool,
#[serde(default)]
pub has_intel: bool,
pub title: String,
pub date: u64,
pub uploader: Uploader,
pub rounds: Option<Vec<Round>>,
#[serde(flatten)]
pub teams: Option<InfoTeams>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct InfoTeams {
pub red: InfoTeam,
pub blue: InfoTeam,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InfoTeam {
pub score: u32
}
#[derive(Debug, Clone, Deserialize)]
pub struct Uploader {
pub id: SteamID,
pub name: String,
pub info: Option<String>,
}
#[cfg(test)]
mod tests {
use std::fs;
use test_case::test_case;
use super::*;
#[test_case("1.json")]
#[test_case("550237.json")]
fn test_parse(file: &str) {
let content = fs::read_to_string(format!("tests/data/{}", file)).unwrap();
let parsed: RawLog = serde_json::from_str(&content).unwrap();
assert!(parsed.teams.is_some() || parsed.info.teams.is_some());
assert!(parsed.rounds.is_some() || parsed.info.rounds.is_some());
}
}