healspread

This commit is contained in:
Robin Appelman 2021-08-07 20:50:23 +02:00
commit d1b3525b2a
10 changed files with 209 additions and 16 deletions

View file

@ -9,6 +9,6 @@ nom = "6"
enum-iterator = "0.7"
chrono = "0.4"
thiserror = "1"
[dev-dependencies]
main_error = "0.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
main_error = "0.1"

View file

@ -1,11 +1,12 @@
use crate::raw_event::RawSubject;
use serde::Serialize;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use steamid_ng::SteamID;
use steamid_ng::{AccountType, Instance, SteamID, Universe};
use thiserror::Error;
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
#[derive(Serialize, Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub enum Team {
Red,
Blue,
@ -41,22 +42,44 @@ impl FromStr for Team {
/// Optimized subject id
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub enum SubjectId {
Player(u8),
Player(u32),
Team(Team),
System,
World,
Console,
}
impl From<&RawSubject<'_>> for SubjectId {
fn from(raw: &RawSubject) -> Self {
match raw {
RawSubject::Player { user_id, .. } => SubjectId::Player(user_id.parse().unwrap()),
RawSubject::Team(team) => SubjectId::Team(team.parse().unwrap()),
impl SubjectId {
pub fn steam_id(&self) -> Option<SteamID> {
match self {
SubjectId::Player(account_id) => Some(SteamID::new(
*account_id,
Instance::All,
AccountType::Individual,
Universe::Public,
)),
_ => None,
}
}
}
impl TryFrom<&RawSubject<'_>> for SubjectId {
type Error = SubjectError;
fn try_from(raw: &RawSubject) -> Result<Self, Self::Error> {
Ok(match raw {
RawSubject::Player { steam_id, .. } => SubjectId::Player(
SteamID::from_steam3(steam_id)
.map_err(|_| SubjectError::InvalidSteamId)?
.account_id(),
),
RawSubject::Team(team) => {
SubjectId::Team(team.parse().map_err(|_| SubjectError::InvalidTeam)?)
}
RawSubject::System(_) => SubjectId::System,
RawSubject::Console => SubjectId::Console,
RawSubject::World => SubjectId::World,
}
})
}
}
@ -108,3 +131,7 @@ impl TryFrom<&RawSubject<'_>> for SubjectData {
})
}
}
/// Steam id formatted as steamid3 when serialized
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct SteamId3(pub SteamID);

22
src/event/medic.rs Normal file
View file

@ -0,0 +1,22 @@
use crate::raw_event::{subject_parser, RawSubject};
use nom::bytes::complete::tag;
use nom::character::complete::digit1;
use nom::error::ErrorKind;
use nom::IResult;
#[derive(Debug)]
pub struct HealedEvent<'a> {
pub subject: RawSubject<'a>,
pub amount: u32,
}
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
let (input, _) = tag("against ")(input)?;
let (input, subject) = subject_parser(input)?;
let (input, _) = tag(r#" (healing ""#)(input)?;
let (input, raw_amount) = digit1(input)?;
let amount = raw_amount
.parse()
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw_amount, ErrorKind::Digit)))?;
Ok((input, HealedEvent { subject, amount }))
}

3
src/event/mod.rs Normal file
View file

@ -0,0 +1,3 @@
mod medic;
pub use medic::{healed_event_parser, HealedEvent};

View file

@ -1,7 +1,8 @@
use crate::common::{SubjectData, SubjectError, SubjectId};
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawSubject};
use crate::raw_event::RawSubject;
use chrono::{DateTime, Utc};
pub use raw_event::{RawEvent, RawEventType};
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::fmt::{Debug, Formatter};
@ -9,6 +10,7 @@ use std::ops::Index;
use thiserror::Error;
mod common;
mod event;
pub mod module;
mod raw_event;
@ -56,7 +58,7 @@ impl Index<SubjectId> for SubjectMap {
impl SubjectMap {
pub fn insert(&mut self, raw: &RawSubject) -> Result<SubjectId, SubjectError> {
let id = raw.into();
let id = raw.try_into()?;
if !self.0.contains_key(&id) {
self.0.insert(id, raw.try_into()?);
}

67
src/main.rs Normal file
View file

@ -0,0 +1,67 @@
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_with_handler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap};
use thiserror::Error;
#[derive(Default)]
struct LogHandler {
chat: ChatHandler,
heal_spread: HealSpreadHandler,
}
#[derive(Default, Serialize)]
struct LogOutput {
chat: Vec<ChatMessage>,
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
}
#[derive(Error, Debug)]
enum LogError {
#[error("{0}")]
HealSpread(#[from] InvalidHealEvent),
}
impl EventHandler for LogHandler {
type Output = LogOutput;
type Error = LogError;
fn does_handle(&self, ty: RawEventType) -> bool {
self.chat.does_handle(ty) || self.heal_spread.does_handle(ty)
}
fn handle(
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
) -> Result<(), Self::Error> {
self.chat.handle(time, subject, event).unwrap();
self.heal_spread.handle(time, subject, event)?;
Ok(())
}
fn finish(self, subjects: &SubjectMap) -> Self::Output {
LogOutput {
chat: self.chat.finish(subjects),
heal_spread: self.heal_spread.finish(subjects),
}
}
}
fn main() -> Result<(), MainError> {
let path = args().skip(1).next().expect("No path provided");
let content = fs::read_to_string(path)?;
let log = parse_with_handler::<LogHandler>(&content)?;
serde_json::to_writer_pretty(stdout().lock(), &log).unwrap();
Ok(())
}

View file

@ -2,6 +2,7 @@ use crate::common::{SubjectData, SubjectId};
use crate::module::EventHandler;
use crate::raw_event::{RawEvent, RawEventType};
use crate::SubjectMap;
use serde::Serialize;
use std::convert::Infallible;
use steamid_ng::SteamID;
@ -12,6 +13,7 @@ struct BareChatMessage {
pub chat_type: ChatType,
}
#[derive(Serialize)]
pub struct ChatMessage {
pub time: u32,
pub name: String,
@ -38,6 +40,7 @@ impl ChatMessage {
}
}
#[derive(Serialize)]
pub enum ChatType {
All,
Team,

67
src/module/healspread.rs Normal file
View file

@ -0,0 +1,67 @@
use crate::common::{SteamId3, SubjectId};
use crate::event::healed_event_parser;
use crate::module::EventHandler;
use crate::raw_event::{RawEvent, 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)
}
}
#[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;
fn does_handle(&self, ty: RawEventType) -> bool {
matches!(ty, RawEventType::Healed)
}
fn handle(
&mut self,
_time: u32,
subject: SubjectId,
event: &RawEvent,
) -> 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 Some(target_steam_id) = target_subject.steam_id() {
let healed = self
.0
.entry(SteamId3(healer_steam_id))
.or_default()
.entry(SteamId3(target_steam_id))
.or_default();
*healed += heal_event.amount
}
}
}
Ok(())
}
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.0
}
}

View file

@ -2,6 +2,7 @@ use crate::common::SubjectId;
use crate::raw_event::{RawEvent, RawEventType};
use crate::SubjectMap;
pub use chat::{ChatHandler, ChatMessage, ChatType};
pub use healspread::{HealSpreadHandler, InvalidHealEvent};
pub use lobbysettings::{
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
};
@ -11,6 +12,7 @@ use std::fmt::Debug;
use thiserror::Error;
mod chat;
mod healspread;
mod lobbysettings;
pub trait EventHandler: Default {

View file

@ -190,7 +190,7 @@ fn subject_parser_player(input: &str) -> IResult<&str, RawSubject> {
))
}
fn subject_parser(input: &str) -> IResult<&str, RawSubject> {
pub fn subject_parser(input: &str) -> IResult<&str, RawSubject> {
alt((
subject_parser_console,
subject_parser_world,