subject postprocessing

This commit is contained in:
Robin Appelman 2021-08-07 19:28:58 +02:00
commit 747ee7d1a9
10 changed files with 2970 additions and 22 deletions

View file

@ -18,7 +18,7 @@ fn main() -> Result<(), MainError> {
} }
for message in chat { for message in chat {
println!("{}: {}", message.time, message.message); println!("{}: {}", message.name, message.message);
} }
Ok(()) Ok(())
} }

View file

@ -1,12 +1,31 @@
use crate::raw_event::RawSubject; use crate::raw_event::RawSubject;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use steamid_ng::SteamID;
use thiserror::Error;
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub enum Team { pub enum Team {
Red, Red,
Blue, Blue,
} }
impl Team {
pub fn as_str(self) -> &'static str {
match self {
Team::Red => "Red",
Team::Blue => "Blue",
}
}
}
impl Display for Team {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl FromStr for Team { impl FromStr for Team {
type Err = (); type Err = ();
@ -20,7 +39,7 @@ impl FromStr for Team {
} }
/// Optimized subject id /// Optimized subject id
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub enum SubjectId { pub enum SubjectId {
Player(u8), Player(u8),
Team(Team), Team(Team),
@ -40,3 +59,52 @@ impl From<&RawSubject<'_>> for SubjectId {
} }
} }
} }
#[derive(Debug, PartialEq)]
pub enum SubjectData {
Player {
name: String,
user_id: u8,
steam_id: SteamID,
team: Team,
},
Team(Team),
System(String),
Console,
World,
}
#[derive(Debug, Error)]
pub enum SubjectError {
#[error("Invalid user id")]
InvalidUserId,
#[error("Invalid steam id")]
InvalidSteamId,
#[error("Invalid team name")]
InvalidTeam,
}
impl TryFrom<&RawSubject<'_>> for SubjectData {
type Error = SubjectError;
fn try_from(raw: &RawSubject<'_>) -> Result<Self, Self::Error> {
Ok(match raw {
RawSubject::Player {
name,
user_id,
steam_id,
team,
} => SubjectData::Player {
name: name.to_string(),
user_id: user_id.parse().map_err(|_| SubjectError::InvalidUserId)?,
steam_id: SteamID::from_steam3(steam_id)
.map_err(|_| SubjectError::InvalidSteamId)?,
team: team.parse().map_err(|_| SubjectError::InvalidTeam)?,
},
RawSubject::Team(team) => SubjectData::Team(team.parse().unwrap()),
RawSubject::System(name) => SubjectData::System(name.to_string()),
RawSubject::Console => SubjectData::Console,
RawSubject::World => SubjectData::World,
})
}
}

View file

@ -1,8 +1,11 @@
use crate::common::{SubjectData, SubjectError, SubjectId};
use crate::module::EventHandler; use crate::module::EventHandler;
use crate::raw_event::RawEvent; use crate::raw_event::{RawEvent, RawSubject};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::collections::BTreeMap;
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::ops::Index;
use thiserror::Error; use thiserror::Error;
mod common; mod common;
@ -26,12 +29,41 @@ impl<Handler: EventHandler> Debug for Error<Handler> {
} }
} }
impl<Handler: EventHandler> From<SubjectError> for Error<Handler> {
fn from(e: SubjectError) -> Self {
Error::Malformed(e.to_string())
}
}
impl<Handler: EventHandler> From<nom::error::Error<&'_ str>> for Error<Handler> { impl<Handler: EventHandler> From<nom::error::Error<&'_ str>> for Error<Handler> {
fn from(e: nom::error::Error<&str>) -> Self { fn from(e: nom::error::Error<&str>) -> Self {
Error::Malformed(e.to_string()) Error::Malformed(e.to_string())
} }
} }
#[derive(Default)]
pub struct SubjectMap(BTreeMap<SubjectId, SubjectData>);
impl Index<SubjectId> for SubjectMap {
type Output = SubjectData;
fn index(&self, index: SubjectId) -> &Self::Output {
self.0
.get(&index)
.expect("subject id created without matching subject data")
}
}
impl SubjectMap {
pub fn insert(&mut self, raw: &RawSubject) -> Result<SubjectId, SubjectError> {
let id = raw.into();
if !self.0.contains_key(&id) {
self.0.insert(id, raw.try_into()?);
}
Ok(id)
}
}
pub fn parse_with_handler<Handler: EventHandler>( pub fn parse_with_handler<Handler: EventHandler>(
log: &str, log: &str,
) -> Result<Handler::Output, Error<Handler>> { ) -> Result<Handler::Output, Error<Handler>> {
@ -43,6 +75,7 @@ pub fn parse_with_handler<Handler: EventHandler>(
let mut handler = Handler::default(); let mut handler = Handler::default();
let mut start_time: Option<DateTime<Utc>> = None; let mut start_time: Option<DateTime<Utc>> = None;
let mut subjects = SubjectMap::default();
for event_res in events { for event_res in events {
let event = event_res?; let event = event_res?;
@ -56,10 +89,10 @@ pub fn parse_with_handler<Handler: EventHandler>(
} }
}; };
handler handler
.handle(match_time, (&event.subject).into(), &event) .handle(match_time, subjects.insert(&event.subject)?, &event)
.map_err(Error::HandlerError)?; .map_err(Error::HandlerError)?;
} }
} }
Ok(handler.finish()) Ok(handler.finish(&subjects))
} }

View file

@ -1,22 +1,50 @@
use crate::common::SubjectId; use crate::common::{SubjectData, SubjectId};
use crate::module::EventHandler; use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawEventType}; use crate::raw_event::{RawEvent, RawEventType};
use crate::SubjectMap;
use std::convert::Infallible; use std::convert::Infallible;
use steamid_ng::SteamID;
pub struct ChatMessage { struct BareChatMessage {
pub time: u32, pub time: u32,
pub subject: SubjectId, pub subject: SubjectId,
pub message: String, pub message: String,
pub chat_type: ChatType, pub chat_type: ChatType,
} }
pub struct ChatMessage {
pub time: u32,
pub name: String,
pub steam_id: SteamID,
pub message: String,
pub chat_type: ChatType,
}
impl ChatMessage {
fn from_bare(bare: BareChatMessage, subjects: &SubjectMap) -> Self {
let (name, steam_id) = match &subjects[bare.subject] {
SubjectData::Player { name, steam_id, .. } => (name.clone(), steam_id.clone()),
_ => {
unreachable!("only player messages are added");
}
};
ChatMessage {
time: bare.time,
name,
steam_id,
message: bare.message,
chat_type: bare.chat_type,
}
}
}
pub enum ChatType { pub enum ChatType {
All, All,
Team, Team,
} }
#[derive(Default)] #[derive(Default)]
pub struct ChatHandler(Vec<ChatMessage>); pub struct ChatHandler(Vec<BareChatMessage>);
impl EventHandler for ChatHandler { impl EventHandler for ChatHandler {
type Output = Vec<ChatMessage>; type Output = Vec<ChatMessage>;
@ -36,13 +64,13 @@ impl EventHandler for ChatHandler {
return Ok(()); return Ok(());
} }
match event.ty { match event.ty {
RawEventType::SayTeam => self.0.push(ChatMessage { RawEventType::SayTeam => self.0.push(BareChatMessage {
time, time,
subject, subject,
message: event.params.trim_matches('"').into(), message: event.params.trim_matches('"').into(),
chat_type: ChatType::Team, chat_type: ChatType::Team,
}), }),
RawEventType::Say => self.0.push(ChatMessage { RawEventType::Say => self.0.push(BareChatMessage {
time, time,
subject, subject,
message: event.params.trim_matches('"').into(), message: event.params.trim_matches('"').into(),
@ -53,7 +81,10 @@ impl EventHandler for ChatHandler {
Ok(()) Ok(())
} }
fn finish(self) -> Self::Output { fn finish(self, subjects: &SubjectMap) -> Self::Output {
self.0 self.0
.into_iter()
.map(|bare| ChatMessage::from_bare(bare, subjects))
.collect()
} }
} }

View file

@ -1,6 +1,7 @@
use crate::common::SubjectId; use crate::common::SubjectId;
use crate::module::EventHandler; use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawEventType}; use crate::raw_event::{RawEvent, RawEventType};
use crate::SubjectMap;
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc}; use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
use std::str::{FromStr, ParseBoolError}; use std::str::{FromStr, ParseBoolError};
use steamid_ng::SteamID; use steamid_ng::SteamID;
@ -182,7 +183,7 @@ impl EventHandler for LobbySettingsHandler {
Ok(()) Ok(())
} }
fn finish(self) -> Self::Output { fn finish(self, _subjects: &SubjectMap) -> Self::Output {
if self.0.id > 0 { if self.0.id > 0 {
Some(self.0) Some(self.0)
} else { } else {

View file

@ -1,5 +1,6 @@
use crate::common::SubjectId; use crate::common::SubjectId;
use crate::raw_event::{RawEvent, RawEventType}; use crate::raw_event::{RawEvent, RawEventType};
use crate::SubjectMap;
pub use chat::{ChatHandler, ChatMessage, ChatType}; pub use chat::{ChatHandler, ChatMessage, ChatType};
pub use lobbysettings::{ pub use lobbysettings::{
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings, LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
@ -25,7 +26,7 @@ pub trait EventHandler: Default {
event: &RawEvent, event: &RawEvent,
) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>;
fn finish(self) -> Self::Output; fn finish(self, subjects: &SubjectMap) -> Self::Output;
} }
#[derive(Default)] #[derive(Default)]
@ -57,8 +58,8 @@ impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head,
Ok(()) Ok(())
} }
fn finish(self) -> Self::Output { fn finish(self, subjects: &SubjectMap) -> Self::Output {
(self.head.finish(), self.tail.finish()) (self.head.finish(subjects), self.tail.finish(subjects))
} }
} }
@ -112,9 +113,9 @@ impl<Handler: EventHandler> EventHandler for OptionalHandler<Handler> {
Ok(()) Ok(())
} }
fn finish(self) -> Self::Output { fn finish(self, subjects: &SubjectMap) -> Self::Output {
match self { match self {
OptionalHandler::Active(handler) => Ok(handler.finish()), OptionalHandler::Active(handler) => Ok(handler.finish(subjects)),
OptionalHandler::Failed(e) => Err(e), OptionalHandler::Failed(e) => Err(e),
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1,211 @@
{"game":{"rounds":[],"toatlLength":0},"chat":[{"timeInSeconds":588,"steamid":"Console","name":"Console","team":"Console","message":"hello, bear! just donated to serveme.tf - 1 year! 31 percent of our monthly server bills are now taken care of"}],"healspread":{},"killstreaks":[{"steamid":"[U:1:101449025]","streak":3,"time":29},{"steamid":"[U:1:101608870]","streak":4,"time":44},{"steamid":"[U:1:101449025]","streak":3,"time":187},{"steamid":"[U:1:82123920]","streak":3,"time":207},{"steamid":"[U:1:88677982]","streak":4,"time":234},{"steamid":"[U:1:101608870]","streak":3,"time":310},{"steamid":"[U:1:82123920]","streak":4,"time":371},{"steamid":"[U:1:101608870]","streak":3,"time":463}],"playerClasses":{},"players":{"[U:1:82123920]":{"team":"Red","kills":31,"assists":6,"deaths":32,"damage":6461,"suicides":3,"damageTaken":7399,"charges":0,"chargesByType":{},"drops":0,"airshots":4,"sentriesBuilt":0,"sentriesDestroyed":0,"headshots":0,"headshotKills":0,"healing":0,"healingReceived":0,"medkits":0,"medkitsHp":0,"backstabs":0,"capturesPoint":0,"capturesIntel":1,"longestKillStreak":3,"currentKillStreak":0,"medicstats":null},"[U:1:101608870]":{"team":"Blue","kills":31,"assists":10,"deaths":29,"damage":8262,"suicides":1,"damageTaken":6274,"charges":0,"chargesByType":{},"drops":0,"airshots":4,"sentriesBuilt":0,"sentriesDestroyed":0,"headshots":0,"headshotKills":0,"healing":0,"healingReceived":0,"medkits":0,"medkitsHp":0,"backstabs":0,"capturesPoint":0,"capturesIntel":10,"longestKillStreak":6,"currentKillStreak":0,"medicstats":null},"[U:1:88677982]":{"team":"Blue","kills":30,"assists":8,"deaths":29,"damage":5764,"suicides":1,"damageTaken":5226,"charges":0,"chargesByType":{},"drops":0,"airshots":6,"sentriesBuilt":0,"sentriesDestroyed":0,"headshots":0,"headshotKills":0,"healing":0,"healingReceived":0,"medkits":0,"medkitsHp":0,"backstabs":0,"capturesPoint":0,"capturesIntel":15,"longestKillStreak":5,"currentKillStreak":2,"medicstats":null},"[U:1:101449025]":{"team":"Red","kills":25,"assists":9,"deaths":32,"damage":5039,"suicides":0,"damageTaken":6627,"charges":0,"chargesByType":{},"drops":0,"airshots":5,"sentriesBuilt":0,"sentriesDestroyed":0,"headshots":0,"headshotKills":0,"healing":0,"healingReceived":0,"medkits":0,"medkitsHp":0,"backstabs":0,"capturesPoint":0,"capturesIntel":15,"longestKillStreak":3,"currentKillStreak":0,"medicstats":null}},"PvC":{},"PvP":{},"realDamage":{"[U:1:82123920]":{"DamageTaken":7399,"DamageDealt":6461},"[U:1:101608870]":{"DamageTaken":6274,"DamageDealt":8160},"[U:1:88677982]":{"DamageTaken":5226,"DamageDealt":5764},"[U:1:101449025]":{"DamageTaken":6525,"DamageDealt":5039}},"teams":{"Red":{"score":0,"kills":56,"deaths":61,"damage":11500,"charges":0,"drops":0,"captures":0,"midfights":0},"Blue":{"score":1,"kills":61,"deaths":56,"damage":14026,"charges":0,"drops":0,"captures":0,"midfights":0}}} {
"game": {
"rounds": [],
"toatlLength": 0
},
"chat": [
{
"timeInSeconds": 588,
"steamid": "Console",
"name": "Console",
"team": "Console",
"message": "hello, bear! just donated to serveme.tf - 1 year! 31 percent of our monthly server bills are now taken care of"
}
],
"healspread": {},
"killstreaks": [
{
"steamid": "[U:1:101449025]",
"streak": 3,
"time": 29
},
{
"steamid": "[U:1:101608870]",
"streak": 4,
"time": 44
},
{
"steamid": "[U:1:101449025]",
"streak": 3,
"time": 187
},
{
"steamid": "[U:1:82123920]",
"streak": 3,
"time": 207
},
{
"steamid": "[U:1:88677982]",
"streak": 4,
"time": 234
},
{
"steamid": "[U:1:101608870]",
"streak": 3,
"time": 310
},
{
"steamid": "[U:1:82123920]",
"streak": 4,
"time": 371
},
{
"steamid": "[U:1:101608870]",
"streak": 3,
"time": 463
}
],
"playerClasses": {},
"players": {
"[U:1:82123920]": {
"team": "Red",
"kills": 31,
"assists": 6,
"deaths": 32,
"damage": 6461,
"suicides": 3,
"damageTaken": 7399,
"charges": 0,
"chargesByType": {},
"drops": 0,
"airshots": 4,
"sentriesBuilt": 0,
"sentriesDestroyed": 0,
"headshots": 0,
"headshotKills": 0,
"healing": 0,
"healingReceived": 0,
"medkits": 0,
"medkitsHp": 0,
"backstabs": 0,
"capturesPoint": 0,
"capturesIntel": 1,
"longestKillStreak": 3,
"currentKillStreak": 0,
"medicstats": null
},
"[U:1:101608870]": {
"team": "Blue",
"kills": 31,
"assists": 10,
"deaths": 29,
"damage": 8262,
"suicides": 1,
"damageTaken": 6274,
"charges": 0,
"chargesByType": {},
"drops": 0,
"airshots": 4,
"sentriesBuilt": 0,
"sentriesDestroyed": 0,
"headshots": 0,
"headshotKills": 0,
"healing": 0,
"healingReceived": 0,
"medkits": 0,
"medkitsHp": 0,
"backstabs": 0,
"capturesPoint": 0,
"capturesIntel": 10,
"longestKillStreak": 6,
"currentKillStreak": 0,
"medicstats": null
},
"[U:1:88677982]": {
"team": "Blue",
"kills": 30,
"assists": 8,
"deaths": 29,
"damage": 5764,
"suicides": 1,
"damageTaken": 5226,
"charges": 0,
"chargesByType": {},
"drops": 0,
"airshots": 6,
"sentriesBuilt": 0,
"sentriesDestroyed": 0,
"headshots": 0,
"headshotKills": 0,
"healing": 0,
"healingReceived": 0,
"medkits": 0,
"medkitsHp": 0,
"backstabs": 0,
"capturesPoint": 0,
"capturesIntel": 15,
"longestKillStreak": 5,
"currentKillStreak": 2,
"medicstats": null
},
"[U:1:101449025]": {
"team": "Red",
"kills": 25,
"assists": 9,
"deaths": 32,
"damage": 5039,
"suicides": 0,
"damageTaken": 6627,
"charges": 0,
"chargesByType": {},
"drops": 0,
"airshots": 5,
"sentriesBuilt": 0,
"sentriesDestroyed": 0,
"headshots": 0,
"headshotKills": 0,
"healing": 0,
"healingReceived": 0,
"medkits": 0,
"medkitsHp": 0,
"backstabs": 0,
"capturesPoint": 0,
"capturesIntel": 15,
"longestKillStreak": 3,
"currentKillStreak": 0,
"medicstats": null
}
},
"PvC": {},
"PvP": {},
"realDamage": {
"[U:1:82123920]": {
"DamageTaken": 7399,
"DamageDealt": 6461
},
"[U:1:101608870]": {
"DamageTaken": 6274,
"DamageDealt": 8160
},
"[U:1:88677982]": {
"DamageTaken": 5226,
"DamageDealt": 5764
},
"[U:1:101449025]": {
"DamageTaken": 6525,
"DamageDealt": 5039
}
},
"teams": {
"Red": {
"score": 0,
"kills": 56,
"deaths": 61,
"damage": 11500,
"charges": 0,
"drops": 0,
"captures": 0,
"midfights": 0
},
"Blue": {
"score": 1,
"kills": 61,
"deaths": 56,
"damage": 14026,
"charges": 0,
"drops": 0,
"captures": 0,
"midfights": 0
}
}
}

File diff suppressed because one or more lines are too long