mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-04 02:34:10 +02:00
healspread
This commit is contained in:
parent
747ee7d1a9
commit
d1b3525b2a
10 changed files with 209 additions and 16 deletions
|
|
@ -9,6 +9,6 @@ nom = "6"
|
||||||
enum-iterator = "0.7"
|
enum-iterator = "0.7"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
[dev-dependencies]
|
serde_json = "1"
|
||||||
main_error = "0.1"
|
main_error = "0.1"
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::raw_event::RawSubject;
|
use crate::raw_event::RawSubject;
|
||||||
|
use serde::Serialize;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use steamid_ng::SteamID;
|
use steamid_ng::{AccountType, Instance, SteamID, Universe};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
|
#[derive(Serialize, Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
|
||||||
pub enum Team {
|
pub enum Team {
|
||||||
Red,
|
Red,
|
||||||
Blue,
|
Blue,
|
||||||
|
|
@ -41,22 +42,44 @@ impl FromStr for Team {
|
||||||
/// Optimized subject id
|
/// Optimized subject id
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd)]
|
||||||
pub enum SubjectId {
|
pub enum SubjectId {
|
||||||
Player(u8),
|
Player(u32),
|
||||||
Team(Team),
|
Team(Team),
|
||||||
System,
|
System,
|
||||||
World,
|
World,
|
||||||
Console,
|
Console,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&RawSubject<'_>> for SubjectId {
|
impl SubjectId {
|
||||||
fn from(raw: &RawSubject) -> Self {
|
pub fn steam_id(&self) -> Option<SteamID> {
|
||||||
match raw {
|
match self {
|
||||||
RawSubject::Player { user_id, .. } => SubjectId::Player(user_id.parse().unwrap()),
|
SubjectId::Player(account_id) => Some(SteamID::new(
|
||||||
RawSubject::Team(team) => SubjectId::Team(team.parse().unwrap()),
|
*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::System(_) => SubjectId::System,
|
||||||
RawSubject::Console => SubjectId::Console,
|
RawSubject::Console => SubjectId::Console,
|
||||||
RawSubject::World => SubjectId::World,
|
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
22
src/event/medic.rs
Normal 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
3
src/event/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod medic;
|
||||||
|
|
||||||
|
pub use medic::{healed_event_parser, HealedEvent};
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::common::{SubjectData, SubjectError, SubjectId};
|
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
|
||||||
use crate::module::EventHandler;
|
use crate::module::EventHandler;
|
||||||
use crate::raw_event::{RawEvent, RawSubject};
|
use crate::raw_event::RawSubject;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
pub use raw_event::{RawEvent, RawEventType};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
@ -9,6 +10,7 @@ use std::ops::Index;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
mod event;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
mod raw_event;
|
mod raw_event;
|
||||||
|
|
||||||
|
|
@ -56,7 +58,7 @@ impl Index<SubjectId> for SubjectMap {
|
||||||
|
|
||||||
impl SubjectMap {
|
impl SubjectMap {
|
||||||
pub fn insert(&mut self, raw: &RawSubject) -> Result<SubjectId, SubjectError> {
|
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) {
|
if !self.0.contains_key(&id) {
|
||||||
self.0.insert(id, raw.try_into()?);
|
self.0.insert(id, raw.try_into()?);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
src/main.rs
Normal file
67
src/main.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ 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 crate::SubjectMap;
|
||||||
|
use serde::Serialize;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use steamid_ng::SteamID;
|
use steamid_ng::SteamID;
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ struct BareChatMessage {
|
||||||
pub chat_type: ChatType,
|
pub chat_type: ChatType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub struct ChatMessage {
|
pub struct ChatMessage {
|
||||||
pub time: u32,
|
pub time: u32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -38,6 +40,7 @@ impl ChatMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub enum ChatType {
|
pub enum ChatType {
|
||||||
All,
|
All,
|
||||||
Team,
|
Team,
|
||||||
|
|
|
||||||
67
src/module/healspread.rs
Normal file
67
src/module/healspread.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::common::SubjectId;
|
||||||
use crate::raw_event::{RawEvent, RawEventType};
|
use crate::raw_event::{RawEvent, RawEventType};
|
||||||
use crate::SubjectMap;
|
use crate::SubjectMap;
|
||||||
pub use chat::{ChatHandler, ChatMessage, ChatType};
|
pub use chat::{ChatHandler, ChatMessage, ChatType};
|
||||||
|
pub use healspread::{HealSpreadHandler, InvalidHealEvent};
|
||||||
pub use lobbysettings::{
|
pub use lobbysettings::{
|
||||||
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
|
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
|
||||||
};
|
};
|
||||||
|
|
@ -11,6 +12,7 @@ use std::fmt::Debug;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
|
mod healspread;
|
||||||
mod lobbysettings;
|
mod lobbysettings;
|
||||||
|
|
||||||
pub trait EventHandler: Default {
|
pub trait EventHandler: Default {
|
||||||
|
|
|
||||||
|
|
@ -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((
|
alt((
|
||||||
subject_parser_console,
|
subject_parser_console,
|
||||||
subject_parser_world,
|
subject_parser_world,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue