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 nom::combinator::opt;
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)?;
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),
})
.map(|(rest, t)| t)
.map(|(_rest, t)| t)
}
}
@ -51,6 +51,12 @@ pub enum GameEvent<'a> {
Say(&'a str),
SayTeam(&'a str),
Healed(HealedEvent<'a>),
ChargeDeployed(ChargeDeployedEvent<'a>),
ChargeEnded(ChargeEndedEvent),
AdvantageLost(AdvantageLostEvent),
FirstHeal(FirstHealEvent),
ChargeReady,
MedicDeath(MedicDeathEvent),
}
impl<'a> GameEvent<'a> {
@ -64,6 +70,22 @@ impl<'a> GameEvent<'a> {
RawEventType::Healed => {
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);
}

View file

@ -1,6 +1,8 @@
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
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 chrono::{DateTime, Utc};
pub use raw_event::{RawEvent, RawEventType};
@ -116,12 +118,14 @@ pub fn parse_with_handler<Handler: EventHandler>(
pub struct LogHandler {
chat: ChatHandler,
heal_spread: HealSpreadHandler,
medic_stats: MedicStatsHandler,
}
#[derive(Default, Serialize)]
pub struct LogOutput {
chat: Vec<ChatMessage>,
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
medic_stats: HashMap<SteamId3, MedicStats>,
}
#[derive(Error, Debug)]
@ -135,7 +139,9 @@ impl EventHandler for LogHandler {
type Error = LogError;
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(
@ -146,6 +152,7 @@ impl EventHandler for LogHandler {
) -> Result<(), Self::Error> {
self.chat.handle(time, subject, event).unwrap();
self.heal_spread.handle(time, subject, event).unwrap();
self.medic_stats.handle(time, subject, event).unwrap();
Ok(())
}
@ -153,6 +160,7 @@ impl EventHandler for LogHandler {
LogOutput {
chat: self.chat.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::raw_event::RawEventType;
use crate::SubjectMap;
use serde::Serialize;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Default)]
pub struct MedicStatsBuilder {
struct MedicStatsBuilder {
advantages_lost: u32,
biggest_advantage_lost: u32,
biggest_advantage_lost: f32,
near_full_charge_death: u32,
deaths_after_uber: u32,
total_time_before_healing: u32,
total_time_before_healing: f32,
start_healing_count: u32,
total_time_to_build: u32,
uber_build_count: u32,
total_time_to_use: u32,
total_uber_length: u32,
total_time_to_use: f32,
total_uber_length: f32,
charge_count: u32,
last_build_start: u32,
last_uber_end: u32,
drops: u32,
}
#[derive(Debug, Serialize)]
pub struct MedicStats {
advantages_lost: u32,
biggest_advantage_lost: u32,
biggest_advantage_lost: f32,
near_full_charge_death: u32,
deaths_after_uber: u32,
avg_time_before_healing: u32,
avg_time_to_build: u32,
avg_time_to_use: u32,
avg_uber_length: u32,
avg_time_before_healing: f32,
avg_time_to_build: f32,
avg_time_to_use: f32,
avg_uber_length: f32,
charge_count: u32,
drops: u32,
}
impl From<MedicStatsBuilder> for MedicStats {
@ -41,11 +47,12 @@ impl From<MedicStatsBuilder> for MedicStats {
near_full_charge_death: builder.near_full_charge_death,
deaths_after_uber: builder.deaths_after_uber,
avg_time_before_healing: builder.total_time_before_healing
/ builder.start_healing_count,
avg_time_to_build: builder.total_time_to_build / builder.uber_build_count,
avg_time_to_use: builder.total_time_to_use / builder.charge_count,
avg_uber_length: builder.total_uber_length / builder.charge_count,
/ builder.start_healing_count as f32,
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 as f32,
avg_uber_length: builder.total_uber_length / builder.charge_count as f32,
charge_count: builder.charge_count,
drops: builder.drops,
}
}
}
@ -64,13 +71,18 @@ impl EventHandler for MedicStatsHandler {
fn does_handle(&self, ty: RawEventType) -> bool {
matches!(
ty,
RawEventType::ChargeDeployed | RawEventType::ChargeEnd | RawEventType::ChargeReady
RawEventType::ChargeDeployed
| RawEventType::ChargeEnd
| RawEventType::ChargeReady
| RawEventType::UberAdvantageLost
| RawEventType::MedicDeath
| RawEventType::FirstHealAfterSpawn
)
}
fn handle(
&mut self,
_time: u32,
time: u32,
subject: SubjectId,
event: &GameEvent,
) -> Result<(), Self::Error> {
@ -79,12 +91,60 @@ impl EventHandler for MedicStatsHandler {
} else {
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(())
}
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.0
.into_iter()
.filter(|(_, builder)| builder.start_healing_count > 0)
.map(|(steam_id, builder)| (steam_id, builder.into()))
.collect()
}

View file

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