event rework wip

This commit is contained in:
Robin Appelman 2021-08-07 23:25:36 +02:00
commit b2d169601c
10 changed files with 240 additions and 63 deletions

View file

@ -1,5 +1,5 @@
use crate::raw_event::RawSubject;
use serde::Serialize;
use serde::{Serialize, Serializer};
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
@ -135,3 +135,12 @@ impl TryFrom<&RawSubject<'_>> for SubjectData {
/// Steam id formatted as steamid3 when serialized
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct SteamId3(pub SteamID);
impl Serialize for SteamId3 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.steam3().serialize(serializer)
}
}

View file

@ -1,15 +1,43 @@
use crate::event::{param_parse_with, quoted, u_int};
use crate::event::{param_parse, param_parse_with, quoted, u_int};
use crate::raw_event::{subject_parser, RawSubject};
use nom::combinator::opt;
use nom::number::complete::float;
use nom::IResult;
#[derive(Debug)]
pub struct HealedEvent<'a> {
pub subject: RawSubject<'a>,
pub target: RawSubject<'a>,
pub amount: u32,
}
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
let (input, subject) = param_parse_with("against", subject_parser)(input)?;
let (input, amount) = param_parse_with("healing", quoted(u_int))(input)?;
Ok((input, HealedEvent { subject, amount }))
Ok((
input,
HealedEvent {
target: subject,
amount,
},
))
}
#[derive(Debug)]
pub struct ChargeDeployedEvent<'a> {
pub medigun: Option<&'a str>,
}
pub fn charge_deployed_event_parser(input: &str) -> IResult<&str, ChargeDeployedEvent> {
let (input, medigun) = opt(param_parse("healing"))(input)?;
Ok((input, ChargeDeployedEvent { medigun }))
}
#[derive(Debug)]
pub struct ChargeEndedEvent {
pub duration: Option<f32>,
}
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 }))
}

View file

@ -1,13 +1,75 @@
mod medic;
mod player;
use crate::{RawEvent, RawEventType};
pub use medic::*;
use nom::bytes::complete::{tag, take_while};
use nom::character::complete::{alpha1, digit1};
use nom::combinator::opt;
use nom::error::ErrorKind;
use nom::IResult;
use nom::{Err, IResult};
pub use player::*;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GameEventError {
#[error("malformed game event({ty:?}): {err}")]
Error {
err: nom::error::Error<String>,
ty: RawEventType,
},
#[error("incomplete event body({0:?})")]
Incomplete(RawEventType),
}
trait GameEventErrTrait<T> {
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError>;
}
impl<'a, T> GameEventErrTrait<T> for IResult<&str, T> {
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError> {
self.map_err(|err| match err {
nom::Err::Error(e) | nom::Err::Failure(e) => GameEventError::Error {
err: nom::error::Error {
input: e.input.to_string(),
code: e.code,
},
ty,
},
Err::Incomplete(_) => GameEventError::Incomplete(ty),
})
.map(|(rest, t)| t)
}
}
pub enum GameEvent<'a> {
ShotFired(ShotFiredEvent<'a>),
ShotHit(ShotHitEvent<'a>),
Damage(DamageEvent<'a>),
Kill(KillEvent<'a>),
Say(&'a str),
SayTeam(&'a str),
Healed(HealedEvent<'a>),
}
impl<'a> GameEvent<'a> {
pub fn parse(raw: &RawEvent<'a>) -> Result<GameEvent<'a>, GameEventError> {
Ok(match raw.ty {
RawEventType::ShotFired => {
GameEvent::ShotFired(shot_fired_event_parser(raw.params).with_type(raw.ty)?)
}
RawEventType::SayTeam => GameEvent::SayTeam(raw.params.trim_matches('"')),
RawEventType::Say => GameEvent::Say(raw.params.trim_matches('"')),
RawEventType::Healed => {
GameEvent::Healed(healed_event_parser(raw.params).with_type(raw.ty)?)
}
_ => {
todo!("{:?} not parsed yet", raw.ty);
}
})
}
}
struct ParamIter<'a> {
input: &'a str,

View file

@ -1,5 +1,6 @@
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
use crate::module::{ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent};
use crate::event::{GameEvent, GameEventError};
use crate::module::{ChatHandler, ChatMessage, EventHandler, HealSpreadHandler};
use crate::raw_event::RawSubject;
use chrono::{DateTime, Utc};
pub use raw_event::{RawEvent, RawEventType};
@ -20,6 +21,8 @@ pub enum Error<Handler: EventHandler> {
#[error("Malformed logfile: {0}")]
Malformed(String),
#[error("{0}")]
MalformedEvent(#[from] GameEventError),
#[error("{0}")]
HandlerError(Handler::Error),
}
@ -27,6 +30,7 @@ impl<Handler: EventHandler> Debug for Error<Handler> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Error::Malformed(e) => e.fmt(f),
Error::MalformedEvent(e) => e.fmt(f),
Error::HandlerError(e) => e.fmt(f),
}
}
@ -85,9 +89,10 @@ pub fn parse_with_handler<Handler: EventHandler>(
let mut subjects = SubjectMap::default();
for event_res in events {
let event = event_res?;
if handler.does_handle(event.ty) || start_time.is_none() {
let event_time: DateTime<Utc> = (&event.date).try_into().unwrap();
let raw_event = event_res?;
let should_handle = handler.does_handle(raw_event.ty);
if should_handle || start_time.is_none() {
let event_time: DateTime<Utc> = (&raw_event.date).try_into().unwrap();
let match_time = match start_time {
Some(start_time) => (event_time - start_time).num_seconds() as u32,
None => {
@ -95,11 +100,14 @@ pub fn parse_with_handler<Handler: EventHandler>(
0
}
};
if should_handle {
let event = GameEvent::parse(&raw_event)?;
handler
.handle(match_time, subjects.insert(&event.subject)?, &event)
.handle(match_time, subjects.insert(&raw_event.subject)?, &event)
.map_err(Error::HandlerError)?;
}
}
}
Ok(handler.finish(&subjects))
}
@ -119,7 +127,7 @@ pub struct LogOutput {
#[derive(Error, Debug)]
pub enum LogError {
#[error("{0}")]
HealSpread(#[from] InvalidHealEvent),
MalformedEvent(#[from] GameEventError),
}
impl EventHandler for LogHandler {
@ -134,10 +142,10 @@ impl EventHandler for LogHandler {
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Self::Error> {
self.chat.handle(time, subject, event).unwrap();
self.heal_spread.handle(time, subject, event)?;
self.heal_spread.handle(time, subject, event).unwrap();
Ok(())
}

View file

@ -1,15 +1,8 @@
use main_error::MainError;
use serde::Serialize;
use std::collections::HashMap;
use std::env::args;
use std::fs;
use std::io::stdout;
use tf_log_parser::module::{
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent,
};
use tf_log_parser::{
parse, parse_with_handler, LogHandler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap,
};
use tf_log_parser::parse;
fn main() -> Result<(), MainError> {
let path = args().skip(1).next().expect("No path provided");

View file

@ -1,6 +1,7 @@
use crate::common::{SubjectData, SubjectId};
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawEventType};
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use serde::Serialize;
use std::convert::Infallible;
@ -61,22 +62,22 @@ impl EventHandler for ChatHandler {
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Infallible> {
if !matches!(subject, SubjectId::Player(_)) {
return Ok(());
}
match event.ty {
RawEventType::SayTeam => self.0.push(BareChatMessage {
match event {
GameEvent::SayTeam(message) => self.0.push(BareChatMessage {
time,
subject,
message: event.params.trim_matches('"').into(),
message: message.to_string(),
chat_type: ChatType::Team,
}),
RawEventType::Say => self.0.push(BareChatMessage {
GameEvent::Say(message) => self.0.push(BareChatMessage {
time,
subject,
message: event.params.trim_matches('"').into(),
message: message.to_string(),
chat_type: ChatType::All,
}),
_ => {}

View file

@ -1,32 +1,17 @@
use crate::common::{SteamId3, SubjectId};
use crate::event::healed_event_parser;
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawEventType};
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use serde::{Serialize, Serializer};
use std::collections::HashMap;
use std::convert::TryFrom;
use thiserror::Error;
impl Serialize for SteamId3 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.steam3().serialize(serializer)
}
}
use std::convert::{Infallible, TryFrom};
#[derive(Default)]
pub struct HealSpreadHandler(HashMap<SteamId3, HashMap<SteamId3, u32>>);
#[derive(Error, Debug)]
#[error("Invalid healed event: {0}")]
pub struct InvalidHealEvent(String);
impl EventHandler for HealSpreadHandler {
type Output = HashMap<SteamId3, HashMap<SteamId3, u32>>;
type Error = InvalidHealEvent;
type Error = Infallible;
fn does_handle(&self, ty: RawEventType) -> bool {
matches!(ty, RawEventType::Healed)
@ -36,17 +21,15 @@ impl EventHandler for HealSpreadHandler {
&mut self,
_time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Self::Error> {
let healer_steam_id = if let Some(steam_id) = subject.steam_id() {
steam_id
} else {
return Ok(());
};
if matches!(event.ty, RawEventType::Healed) {
let (_, heal_event) = healed_event_parser(event.params)
.map_err(|_| InvalidHealEvent(event.params.into()))?;
if let Ok(target_subject) = SubjectId::try_from(&heal_event.subject) {
if let GameEvent::Healed(heal_event) = event {
if let Ok(target_subject) = SubjectId::try_from(&heal_event.target) {
if let Some(target_steam_id) = target_subject.steam_id() {
let healed = self
.0

View file

@ -1,6 +1,7 @@
use crate::common::SubjectId;
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawEventType};
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
use std::str::{FromStr, ParseBoolError};
@ -138,13 +139,12 @@ impl EventHandler for LobbySettingsHandler {
&mut self,
_time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Self::Error> {
if !matches!(subject, SubjectId::Console) {
return Ok(());
}
if matches!(event.ty, RawEventType::Say) {
let msg = event.params.trim_matches('"');
if let GameEvent::Say(msg) = event {
if let Some((id, _)) = msg
.strip_prefix("TF2Center Lobby #")
.and_then(|s| str::split_once(s, " |"))

91
src/module/medicstats.rs Normal file
View file

@ -0,0 +1,91 @@
use crate::common::{SteamId3, SubjectId};
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Default)]
pub struct MedicStatsBuilder {
advantages_lost: u32,
biggest_advantage_lost: u32,
near_full_charge_death: u32,
deaths_after_uber: u32,
total_time_before_healing: u32,
start_healing_count: u32,
total_time_to_build: u32,
uber_build_count: u32,
total_time_to_use: u32,
total_uber_length: u32,
charge_count: u32,
}
pub struct MedicStats {
advantages_lost: u32,
biggest_advantage_lost: u32,
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,
charge_count: u32,
}
impl From<MedicStatsBuilder> for MedicStats {
fn from(builder: MedicStatsBuilder) -> Self {
MedicStats {
advantages_lost: builder.advantages_lost,
biggest_advantage_lost: builder.biggest_advantage_lost,
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,
charge_count: builder.charge_count,
}
}
}
#[derive(Error, Debug)]
#[error("Invalid charge event: {0}")]
pub struct InvalidMedicEvent(String);
#[derive(Default)]
pub struct MedicStatsHandler(HashMap<SteamId3, MedicStatsBuilder>);
impl EventHandler for MedicStatsHandler {
type Output = HashMap<SteamId3, MedicStats>;
type Error = InvalidMedicEvent;
fn does_handle(&self, ty: RawEventType) -> bool {
matches!(
ty,
RawEventType::ChargeDeployed | RawEventType::ChargeEnd | RawEventType::ChargeReady
)
}
fn handle(
&mut self,
_time: u32,
subject: SubjectId,
event: &GameEvent,
) -> Result<(), Self::Error> {
let healer_steam_id = if let Some(steam_id) = subject.steam_id() {
steam_id
} else {
return Ok(());
};
Ok(())
}
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.0
.into_iter()
.map(|(steam_id, builder)| (steam_id, builder.into()))
.collect()
}
}

View file

@ -1,8 +1,9 @@
use crate::common::SubjectId;
use crate::raw_event::{RawEvent, RawEventType};
use crate::event::GameEvent;
use crate::raw_event::RawEventType;
use crate::SubjectMap;
pub use chat::{ChatHandler, ChatMessage, ChatType};
pub use healspread::{HealSpreadHandler, InvalidHealEvent};
pub use healspread::HealSpreadHandler;
pub use lobbysettings::{
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
};
@ -14,6 +15,7 @@ use thiserror::Error;
mod chat;
mod healspread;
mod lobbysettings;
mod medicstats;
pub trait EventHandler: Default {
type Output;
@ -25,7 +27,7 @@ pub trait EventHandler: Default {
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Self::Error>;
fn finish(self, subjects: &SubjectMap) -> Self::Output;
@ -49,7 +51,7 @@ impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head,
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Self::Error> {
self.head
.handle(time, subject, event)
@ -100,7 +102,7 @@ impl<Handler: EventHandler> EventHandler for OptionalHandler<Handler> {
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
event: &GameEvent,
) -> Result<(), Self::Error> {
let res = if let OptionalHandler::Active(handler) = self {
handler.handle(time, subject, event)