medic stats

This commit is contained in:
Robin Appelman 2021-08-08 00:05:21 +02:00
commit a5040ec355
5 changed files with 147 additions and 20 deletions

View file

@ -1,4 +1,4 @@
use crate::event::{param_parse, param_parse_with, quoted, u_int}; use crate::event::{param_parse, param_parse_with, quoted, u_int, ParamIter};
use crate::raw_event::{subject_parser, RawSubject}; use crate::raw_event::{subject_parser, RawSubject};
use nom::combinator::opt; use nom::combinator::opt;
use nom::number::complete::float; use nom::number::complete::float;
@ -41,3 +41,39 @@ pub fn charge_ended_event_parser(input: &str) -> IResult<&str, ChargeEndedEvent>
let (input, duration) = opt(param_parse_with("duration", quoted(float)))(input)?; let (input, duration) = opt(param_parse_with("duration", quoted(float)))(input)?;
Ok((input, ChargeEndedEvent { duration })) Ok((input, ChargeEndedEvent { duration }))
} }
#[derive(Debug)]
pub struct AdvantageLostEvent {
pub time: Option<f32>,
}
pub fn advantage_lost_event_parser(input: &str) -> IResult<&str, AdvantageLostEvent> {
let (input, time) = opt(param_parse_with("time", quoted(float)))(input)?;
Ok((input, AdvantageLostEvent { time }))
}
#[derive(Debug)]
pub struct FirstHealEvent {
pub time: Option<f32>,
}
pub fn first_heal_event_parser(input: &str) -> IResult<&str, FirstHealEvent> {
let (input, time) = opt(param_parse_with("time", quoted(float)))(input)?;
Ok((input, FirstHealEvent { time }))
}
#[derive(Debug)]
pub struct MedicDeathEvent {
pub charge: Option<u32>,
}
pub fn medic_death_event_parser(input: &str) -> IResult<&str, MedicDeathEvent> {
let mut charge = None;
for (key, value) in ParamIter::new(input) {
if key == "ubercharge" {
charge = Some(quoted(u_int)(value)?.1);
}
}
let (input, time) = opt(param_parse_with("time", quoted(float)))(input)?;
Ok((input, MedicDeathEvent { charge }))
}

View file

@ -39,7 +39,7 @@ impl<'a, T> GameEventErrTrait<T> for IResult<&str, T> {
Err::Incomplete(_) => GameEventError::Incomplete(ty), Err::Incomplete(_) => GameEventError::Incomplete(ty),
}) })
.map(|(rest, t)| t) .map(|(_rest, t)| t)
} }
} }
@ -51,6 +51,12 @@ pub enum GameEvent<'a> {
Say(&'a str), Say(&'a str),
SayTeam(&'a str), SayTeam(&'a str),
Healed(HealedEvent<'a>), Healed(HealedEvent<'a>),
ChargeDeployed(ChargeDeployedEvent<'a>),
ChargeEnded(ChargeEndedEvent),
AdvantageLost(AdvantageLostEvent),
FirstHeal(FirstHealEvent),
ChargeReady,
MedicDeath(MedicDeathEvent),
} }
impl<'a> GameEvent<'a> { impl<'a> GameEvent<'a> {
@ -64,6 +70,22 @@ impl<'a> GameEvent<'a> {
RawEventType::Healed => { RawEventType::Healed => {
GameEvent::Healed(healed_event_parser(raw.params).with_type(raw.ty)?) GameEvent::Healed(healed_event_parser(raw.params).with_type(raw.ty)?)
} }
RawEventType::ChargeDeployed => GameEvent::ChargeDeployed(
charge_deployed_event_parser(raw.params).with_type(raw.ty)?,
),
RawEventType::ChargeEnd => {
GameEvent::ChargeEnded(charge_ended_event_parser(raw.params).with_type(raw.ty)?)
}
RawEventType::UberAdvantageLost => {
GameEvent::AdvantageLost(advantage_lost_event_parser(raw.params).with_type(raw.ty)?)
}
RawEventType::FirstHealAfterSpawn => {
GameEvent::FirstHeal(first_heal_event_parser(raw.params).with_type(raw.ty)?)
}
RawEventType::ChargeReady => GameEvent::ChargeReady,
RawEventType::MedicDeath => {
GameEvent::MedicDeath(medic_death_event_parser(raw.params).with_type(raw.ty)?)
}
_ => { _ => {
todo!("{:?} not parsed yet", raw.ty); todo!("{:?} not parsed yet", raw.ty);
} }

View file

@ -1,6 +1,8 @@
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId}; pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
use crate::event::{GameEvent, GameEventError}; use crate::event::{GameEvent, GameEventError};
use crate::module::{ChatHandler, ChatMessage, EventHandler, HealSpreadHandler}; use crate::module::{
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, MedicStats, MedicStatsHandler,
};
use crate::raw_event::RawSubject; use crate::raw_event::RawSubject;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub use raw_event::{RawEvent, RawEventType}; pub use raw_event::{RawEvent, RawEventType};
@ -116,12 +118,14 @@ pub fn parse_with_handler<Handler: EventHandler>(
pub struct LogHandler { pub struct LogHandler {
chat: ChatHandler, chat: ChatHandler,
heal_spread: HealSpreadHandler, heal_spread: HealSpreadHandler,
medic_stats: MedicStatsHandler,
} }
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct LogOutput { pub struct LogOutput {
chat: Vec<ChatMessage>, chat: Vec<ChatMessage>,
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>, heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
medic_stats: HashMap<SteamId3, MedicStats>,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -135,7 +139,9 @@ impl EventHandler for LogHandler {
type Error = LogError; type Error = LogError;
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(&self, ty: RawEventType) -> bool {
self.chat.does_handle(ty) || self.heal_spread.does_handle(ty) self.chat.does_handle(ty)
|| self.heal_spread.does_handle(ty)
|| self.medic_stats.does_handle(ty)
} }
fn handle( fn handle(
@ -146,6 +152,7 @@ impl EventHandler for LogHandler {
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.chat.handle(time, subject, event).unwrap(); self.chat.handle(time, subject, event).unwrap();
self.heal_spread.handle(time, subject, event).unwrap(); self.heal_spread.handle(time, subject, event).unwrap();
self.medic_stats.handle(time, subject, event).unwrap();
Ok(()) Ok(())
} }
@ -153,6 +160,7 @@ impl EventHandler for LogHandler {
LogOutput { LogOutput {
chat: self.chat.finish(subjects), chat: self.chat.finish(subjects),
heal_spread: self.heal_spread.finish(subjects), heal_spread: self.heal_spread.finish(subjects),
medic_stats: self.medic_stats.finish(subjects),
} }
} }
} }

View file

@ -3,34 +3,40 @@ use crate::event::GameEvent;
use crate::module::EventHandler; use crate::module::EventHandler;
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::SubjectMap; use crate::SubjectMap;
use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
#[derive(Default)] #[derive(Default)]
pub struct MedicStatsBuilder { struct MedicStatsBuilder {
advantages_lost: u32, advantages_lost: u32,
biggest_advantage_lost: u32, biggest_advantage_lost: f32,
near_full_charge_death: u32, near_full_charge_death: u32,
deaths_after_uber: u32, deaths_after_uber: u32,
total_time_before_healing: u32, total_time_before_healing: f32,
start_healing_count: u32, start_healing_count: u32,
total_time_to_build: u32, total_time_to_build: u32,
uber_build_count: u32, uber_build_count: u32,
total_time_to_use: u32, total_time_to_use: f32,
total_uber_length: u32, total_uber_length: f32,
charge_count: u32, charge_count: u32,
last_build_start: u32,
last_uber_end: u32,
drops: u32,
} }
#[derive(Debug, Serialize)]
pub struct MedicStats { pub struct MedicStats {
advantages_lost: u32, advantages_lost: u32,
biggest_advantage_lost: u32, biggest_advantage_lost: f32,
near_full_charge_death: u32, near_full_charge_death: u32,
deaths_after_uber: u32, deaths_after_uber: u32,
avg_time_before_healing: u32, avg_time_before_healing: f32,
avg_time_to_build: u32, avg_time_to_build: f32,
avg_time_to_use: u32, avg_time_to_use: f32,
avg_uber_length: u32, avg_uber_length: f32,
charge_count: u32, charge_count: u32,
drops: u32,
} }
impl From<MedicStatsBuilder> for MedicStats { impl From<MedicStatsBuilder> for MedicStats {
@ -41,11 +47,12 @@ impl From<MedicStatsBuilder> for MedicStats {
near_full_charge_death: builder.near_full_charge_death, near_full_charge_death: builder.near_full_charge_death,
deaths_after_uber: builder.deaths_after_uber, deaths_after_uber: builder.deaths_after_uber,
avg_time_before_healing: builder.total_time_before_healing avg_time_before_healing: builder.total_time_before_healing
/ builder.start_healing_count, / builder.start_healing_count as f32,
avg_time_to_build: builder.total_time_to_build / builder.uber_build_count, avg_time_to_build: builder.total_time_to_build as f32 / builder.uber_build_count as f32,
avg_time_to_use: builder.total_time_to_use / builder.charge_count, avg_time_to_use: builder.total_time_to_use / builder.charge_count as f32,
avg_uber_length: builder.total_uber_length / builder.charge_count, avg_uber_length: builder.total_uber_length / builder.charge_count as f32,
charge_count: builder.charge_count, charge_count: builder.charge_count,
drops: builder.drops,
} }
} }
} }
@ -64,13 +71,18 @@ impl EventHandler for MedicStatsHandler {
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(&self, ty: RawEventType) -> bool {
matches!( matches!(
ty, ty,
RawEventType::ChargeDeployed | RawEventType::ChargeEnd | RawEventType::ChargeReady RawEventType::ChargeDeployed
| RawEventType::ChargeEnd
| RawEventType::ChargeReady
| RawEventType::UberAdvantageLost
| RawEventType::MedicDeath
| RawEventType::FirstHealAfterSpawn
) )
} }
fn handle( fn handle(
&mut self, &mut self,
_time: u32, time: u32,
subject: SubjectId, subject: SubjectId,
event: &GameEvent, event: &GameEvent,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
@ -79,12 +91,60 @@ impl EventHandler for MedicStatsHandler {
} else { } else {
return Ok(()); return Ok(());
}; };
match event {
GameEvent::ChargeEnded(end) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.total_uber_length += end.duration.unwrap_or_default();
builder.last_uber_end = time;
}
GameEvent::ChargeDeployed(_deployed) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.charge_count += 1;
}
GameEvent::AdvantageLost(lost) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.advantages_lost += 1;
let time = lost.time.unwrap_or_default();
if time > builder.biggest_advantage_lost {
builder.biggest_advantage_lost = time;
}
}
GameEvent::FirstHeal(first) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.total_time_before_healing += first.time.unwrap_or_default();
builder.start_healing_count += 1;
builder.last_build_start = time;
}
GameEvent::ChargeReady => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
if builder.last_build_start > 0 {
let build_time = time - builder.last_build_start;
builder.last_build_start = 0;
builder.total_time_to_build += build_time;
builder.uber_build_count += 1;
}
}
GameEvent::MedicDeath(death) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
let charge = death.charge.unwrap_or_default();
if charge > 95 && charge < 100 {
builder.near_full_charge_death += 1;
} else if charge >= 100 {
builder.drops += 1;
}
if time - builder.last_uber_end <= 20 {
builder.deaths_after_uber += 1;
}
}
_ => {}
}
Ok(()) Ok(())
} }
fn finish(self, _subjects: &SubjectMap) -> Self::Output { fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.0 self.0
.into_iter() .into_iter()
.filter(|(_, builder)| builder.start_healing_count > 0)
.map(|(steam_id, builder)| (steam_id, builder.into())) .map(|(steam_id, builder)| (steam_id, builder.into()))
.collect() .collect()
} }

View file

@ -7,6 +7,7 @@ pub use healspread::HealSpreadHandler;
pub use lobbysettings::{ pub use lobbysettings::{
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings, LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
}; };
pub use medicstats::{MedicStats, MedicStatsHandler};
use std::convert::Infallible; use std::convert::Infallible;
use std::error::Error; use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;