mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 10:14:10 +02:00
per-player data wip
This commit is contained in:
parent
cba5236496
commit
9da2d2230a
10 changed files with 341 additions and 127 deletions
|
|
@ -19,7 +19,7 @@ struct HighestDamageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for HighestDamageHandler {
|
impl EventHandler for HighestDamageHandler {
|
||||||
type Output = Option<HighestDamage>;
|
type GlobalOutput = Option<HighestDamage>;
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
matches!(ty, RawEventType::Damage)
|
matches!(ty, RawEventType::Damage)
|
||||||
|
|
@ -42,7 +42,7 @@ impl EventHandler for HighestDamageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
self.current.map(|(subject, damage)| {
|
self.current.map(|(subject, damage)| {
|
||||||
let user = match &subjects[subject] {
|
let user = match &subjects[subject] {
|
||||||
SubjectData::Player { name, .. } => name.clone(),
|
SubjectData::Player { name, .. } => name.clone(),
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ impl TryFrom<&RawSubject<'_>> for SubjectId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum SubjectData {
|
pub enum SubjectData {
|
||||||
Player {
|
Player {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
||||||
67
src/lib.rs
67
src/lib.rs
|
|
@ -2,14 +2,13 @@ pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
|
||||||
use crate::event::GameEventError;
|
use crate::event::GameEventError;
|
||||||
pub use crate::module::EventHandler;
|
pub use crate::module::EventHandler;
|
||||||
use crate::module::{ChatHandler, ClassStatsHandler, HealSpreadHandler, MedicStatsHandler};
|
use crate::module::{ChatHandler, ClassStatsHandler, HealSpreadHandler, MedicStatsHandler};
|
||||||
use crate::raw_event::RawSubject;
|
use crate::subjectmap::SubjectMap;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
pub use event::GameEvent;
|
pub use event::GameEvent;
|
||||||
pub use raw_event::{RawEvent, RawEventType};
|
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;
|
use std::fmt::Debug;
|
||||||
use std::ops::Index;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
@ -17,6 +16,7 @@ pub mod event;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod module;
|
pub mod module;
|
||||||
mod raw_event;
|
mod raw_event;
|
||||||
|
mod subjectmap;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|
@ -39,34 +39,27 @@ impl From<nom::error::Error<&'_ str>> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
pub fn parse(
|
||||||
pub struct SubjectMap(BTreeMap<SubjectId, SubjectData>);
|
log: &str,
|
||||||
|
) -> Result<
|
||||||
impl Index<SubjectId> for SubjectMap {
|
(
|
||||||
type Output = SubjectData;
|
<LogHandler as EventHandler>::GlobalOutput,
|
||||||
|
BTreeMap<SteamId3, <LogHandler as EventHandler>::PerSubjectOutput>,
|
||||||
fn index(&self, index: SubjectId) -> &Self::Output {
|
),
|
||||||
self.0
|
Error,
|
||||||
.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.try_into()?;
|
|
||||||
if !self.0.contains_key(&id) {
|
|
||||||
self.0.insert(id, raw.try_into()?);
|
|
||||||
}
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(log: &str) -> Result<<LogHandler as EventHandler>::Output, Error> {
|
|
||||||
parse_with_handler::<LogHandler>(log)
|
parse_with_handler::<LogHandler>(log)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_with_handler<Handler: EventHandler>(log: &str) -> Result<Handler::Output, Error> {
|
pub fn parse_with_handler<Handler: EventHandler>(
|
||||||
|
log: &str,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
Handler::GlobalOutput,
|
||||||
|
BTreeMap<SteamId3, Handler::PerSubjectOutput>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
let events = log
|
let events = log
|
||||||
.lines()
|
.lines()
|
||||||
.filter(|line| line.starts_with("L "))
|
.filter(|line| line.starts_with("L "))
|
||||||
|
|
@ -75,7 +68,7 @@ pub fn parse_with_handler<Handler: EventHandler>(log: &str) -> Result<Handler::O
|
||||||
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();
|
let mut subjects = SubjectMap::<Handler::PerSubjectData>::default();
|
||||||
|
|
||||||
for event_res in events {
|
for event_res in events {
|
||||||
let raw_event = event_res?;
|
let raw_event = event_res?;
|
||||||
|
|
@ -91,12 +84,26 @@ pub fn parse_with_handler<Handler: EventHandler>(log: &str) -> Result<Handler::O
|
||||||
};
|
};
|
||||||
if should_handle {
|
if should_handle {
|
||||||
let event = GameEvent::parse(&raw_event)?;
|
let event = GameEvent::parse(&raw_event)?;
|
||||||
handler.handle(match_time, subjects.insert(&raw_event.subject)?, &event);
|
let (subject, data) = subjects.insert(&raw_event.subject)?;
|
||||||
|
handler.handle(match_time, subject, data, &event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(handler.finish(&subjects))
|
let just_subjects = subjects.to_just_subjects();
|
||||||
|
let per_player = subjects
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(id, subject, data)| Some((id.steam_id()?, subject, data)))
|
||||||
|
.map(|(steam_id, subject, data)| {
|
||||||
|
(
|
||||||
|
SteamId3(steam_id),
|
||||||
|
handler.finish_per_subject(&subject, data),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let global = handler.finish_global(&just_subjects);
|
||||||
|
|
||||||
|
Ok((global, per_player))
|
||||||
}
|
}
|
||||||
|
|
||||||
handler!(LogHandler {
|
handler!(LogHandler {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub struct ChatMessage {
|
||||||
impl ChatMessage {
|
impl ChatMessage {
|
||||||
fn from_bare(bare: BareChatMessage, subjects: &SubjectMap) -> Self {
|
fn from_bare(bare: BareChatMessage, subjects: &SubjectMap) -> Self {
|
||||||
let (name, steam_id) = match &subjects[bare.subject] {
|
let (name, steam_id) = match &subjects[bare.subject] {
|
||||||
SubjectData::Player { name, steam_id, .. } => (name.clone(), steam_id.clone()),
|
(SubjectData::Player { name, steam_id, .. }, _) => (name.clone(), steam_id.clone()),
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("only player messages are added");
|
unreachable!("only player messages are added");
|
||||||
}
|
}
|
||||||
|
|
@ -50,13 +50,21 @@ pub enum ChatType {
|
||||||
pub struct ChatHandler(Vec<BareChatMessage>);
|
pub struct ChatHandler(Vec<BareChatMessage>);
|
||||||
|
|
||||||
impl EventHandler for ChatHandler {
|
impl EventHandler for ChatHandler {
|
||||||
type Output = Vec<ChatMessage>;
|
type GlobalOutput = Vec<ChatMessage>;
|
||||||
|
type PerSubjectData = ();
|
||||||
|
type PerSubjectOutput = ();
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
matches!(ty, RawEventType::SayTeam | RawEventType::Say)
|
matches!(ty, RawEventType::SayTeam | RawEventType::Say)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent) {
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
_subject_data: &mut Self::PerSubjectData,
|
||||||
|
event: &GameEvent,
|
||||||
|
) {
|
||||||
if !matches!(subject, SubjectId::Player(_)) {
|
if !matches!(subject, SubjectId::Player(_)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -77,10 +85,18 @@ impl EventHandler for ChatHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
self.0
|
self.0
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|bare| ChatMessage::from_bare(bare, subjects))
|
.map(|bare| ChatMessage::from_bare(bare, subjects))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish_per_subject(
|
||||||
|
&self,
|
||||||
|
_subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput {
|
||||||
|
data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::common::{Class, ClassMap, SteamId3, SubjectId};
|
||||||
use crate::event::{DamageEvent, GameEvent, RoleChangeEvent, SpawnEvent};
|
use crate::event::{DamageEvent, GameEvent, RoleChangeEvent, SpawnEvent};
|
||||||
use crate::module::EventHandler;
|
use crate::module::EventHandler;
|
||||||
use crate::raw_event::{RawEventType, RawSubject};
|
use crate::raw_event::{RawEventType, RawSubject};
|
||||||
use crate::SubjectMap;
|
use crate::{SubjectData, SubjectMap};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
@ -65,7 +65,9 @@ impl ClassStatsHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for ClassStatsHandler {
|
impl EventHandler for ClassStatsHandler {
|
||||||
type Output = BTreeMap<SteamId3, ClassMap<ClassStat>>;
|
type GlobalOutput = ();
|
||||||
|
type PerSubjectData = ClassMap<ClassStat>;
|
||||||
|
type PerSubjectOutput = ClassMap<ClassStat>;
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
@ -80,7 +82,13 @@ impl EventHandler for ClassStatsHandler {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, _time: u32, subject: SubjectId, event: &GameEvent) {
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
_time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
_subject_data: &mut Self::PerSubjectData,
|
||||||
|
event: &GameEvent,
|
||||||
|
) {
|
||||||
match event {
|
match event {
|
||||||
GameEvent::Spawned(SpawnEvent { class: Some(class) })
|
GameEvent::Spawned(SpawnEvent { class: Some(class) })
|
||||||
| GameEvent::RoleChange(RoleChangeEvent { class: Some(class) }) => {
|
| GameEvent::RoleChange(RoleChangeEvent { class: Some(class) }) => {
|
||||||
|
|
@ -140,7 +148,15 @@ impl EventHandler for ClassStatsHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
self.stats
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_per_subject(
|
||||||
|
&self,
|
||||||
|
_subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput {
|
||||||
|
data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,42 +2,48 @@ use crate::common::{SteamId3, SubjectId};
|
||||||
use crate::event::GameEvent;
|
use crate::event::GameEvent;
|
||||||
use crate::module::EventHandler;
|
use crate::module::EventHandler;
|
||||||
use crate::raw_event::RawEventType;
|
use crate::raw_event::RawEventType;
|
||||||
use crate::SubjectMap;
|
use crate::{SubjectData, SubjectMap};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct HealSpreadHandler(BTreeMap<SteamId3, BTreeMap<SteamId3, u32>>);
|
pub struct HealSpreadHandler;
|
||||||
|
|
||||||
impl EventHandler for HealSpreadHandler {
|
impl EventHandler for HealSpreadHandler {
|
||||||
type Output = BTreeMap<SteamId3, BTreeMap<SteamId3, u32>>;
|
type GlobalOutput = ();
|
||||||
|
type PerSubjectData = BTreeMap<SteamId3, u32>;
|
||||||
|
type PerSubjectOutput = BTreeMap<SteamId3, u32>;
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
matches!(ty, RawEventType::Healed)
|
matches!(ty, RawEventType::Healed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, _time: u32, subject: SubjectId, event: &GameEvent) {
|
fn handle(
|
||||||
let healer_steam_id = if let Some(steam_id) = subject.steam_id() {
|
&mut self,
|
||||||
steam_id
|
_time: u32,
|
||||||
} else {
|
_subject: SubjectId,
|
||||||
return;
|
subject_data: &mut Self::PerSubjectData,
|
||||||
};
|
event: &GameEvent,
|
||||||
|
) {
|
||||||
if let GameEvent::Healed(heal_event) = event {
|
if let GameEvent::Healed(heal_event) = event {
|
||||||
if let Ok(target_subject) = SubjectId::try_from(&heal_event.target) {
|
if let Ok(target_subject) = SubjectId::try_from(&heal_event.target) {
|
||||||
if let Some(target_steam_id) = target_subject.steam_id() {
|
if let Some(target_steam_id) = target_subject.steam_id() {
|
||||||
let healed = self
|
let healed = subject_data.entry(SteamId3(target_steam_id)).or_default();
|
||||||
.0
|
|
||||||
.entry(SteamId3(healer_steam_id))
|
|
||||||
.or_default()
|
|
||||||
.entry(SteamId3(target_steam_id))
|
|
||||||
.or_default();
|
|
||||||
*healed += heal_event.amount
|
*healed += heal_event.amount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
self.0
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_per_subject(
|
||||||
|
&self,
|
||||||
|
_subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput {
|
||||||
|
data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::common::SubjectId;
|
||||||
use crate::event::GameEvent;
|
use crate::event::GameEvent;
|
||||||
use crate::module::EventHandler;
|
use crate::module::EventHandler;
|
||||||
use crate::raw_event::RawEventType;
|
use crate::raw_event::RawEventType;
|
||||||
use crate::SubjectMap;
|
use crate::{SubjectData, SubjectMap};
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
|
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
|
|
@ -197,13 +197,21 @@ impl LobbySettingsHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for LobbySettingsHandler {
|
impl EventHandler for LobbySettingsHandler {
|
||||||
type Output = Result<Option<Settings>, LobbySettingsError>;
|
type GlobalOutput = Result<Option<Settings>, LobbySettingsError>;
|
||||||
|
type PerSubjectData = ();
|
||||||
|
type PerSubjectOutput = ();
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
matches!(ty, RawEventType::Say)
|
matches!(ty, RawEventType::Say)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, _time: u32, subject: SubjectId, event: &GameEvent) {
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
_time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
_subject_data: &mut Self::PerSubjectData,
|
||||||
|
event: &GameEvent,
|
||||||
|
) {
|
||||||
if !matches!(subject, SubjectId::Console) {
|
if !matches!(subject, SubjectId::Console) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -214,13 +222,21 @@ impl EventHandler for LobbySettingsHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
match self {
|
match self {
|
||||||
LobbySettingsHandler::NotAvailable => Ok(None),
|
LobbySettingsHandler::NotAvailable => Ok(None),
|
||||||
LobbySettingsHandler::Active(settings) => Ok(Some(settings)),
|
LobbySettingsHandler::Active(settings) => Ok(Some(settings)),
|
||||||
LobbySettingsHandler::Err(e) => Err(e),
|
LobbySettingsHandler::Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish_per_subject(
|
||||||
|
&self,
|
||||||
|
_subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput {
|
||||||
|
data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_timezone(date: &str) -> Result<FixedOffset, LobbySettingsError> {
|
fn get_timezone(date: &str) -> Result<FixedOffset, LobbySettingsError> {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::common::{SteamId3, SubjectId};
|
use crate::common::SubjectId;
|
||||||
use crate::event::GameEvent;
|
use crate::event::GameEvent;
|
||||||
use crate::module::EventHandler;
|
use crate::module::EventHandler;
|
||||||
use crate::raw_event::RawEventType;
|
use crate::raw_event::RawEventType;
|
||||||
use crate::SubjectMap;
|
use crate::{SubjectData, SubjectMap};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MedicStatsBuilder {
|
pub struct MedicStatsBuilder {
|
||||||
advantages_lost: u32,
|
advantages_lost: u32,
|
||||||
biggest_advantage_lost: f32,
|
biggest_advantage_lost: f32,
|
||||||
near_full_charge_death: u32,
|
near_full_charge_death: u32,
|
||||||
|
|
@ -62,10 +61,12 @@ impl From<MedicStatsBuilder> for MedicStats {
|
||||||
pub struct InvalidMedicEvent(String);
|
pub struct InvalidMedicEvent(String);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MedicStatsHandler(BTreeMap<SteamId3, MedicStatsBuilder>);
|
pub struct MedicStatsHandler;
|
||||||
|
|
||||||
impl EventHandler for MedicStatsHandler {
|
impl EventHandler for MedicStatsHandler {
|
||||||
type Output = BTreeMap<SteamId3, MedicStats>;
|
type GlobalOutput = ();
|
||||||
|
type PerSubjectData = MedicStatsBuilder;
|
||||||
|
type PerSubjectOutput = MedicStats;
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
@ -79,66 +80,65 @@ impl EventHandler for MedicStatsHandler {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent) {
|
fn handle(
|
||||||
let healer_steam_id = if let Some(steam_id) = subject.steam_id() {
|
&mut self,
|
||||||
steam_id
|
time: u32,
|
||||||
} else {
|
_subject: SubjectId,
|
||||||
return;
|
subject_data: &mut Self::PerSubjectData,
|
||||||
};
|
event: &GameEvent,
|
||||||
|
) {
|
||||||
match event {
|
match event {
|
||||||
GameEvent::ChargeEnded(end) => {
|
GameEvent::ChargeEnded(end) => {
|
||||||
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
|
subject_data.total_uber_length += end.duration.unwrap_or_default();
|
||||||
builder.total_uber_length += end.duration.unwrap_or_default();
|
subject_data.last_uber_end = time;
|
||||||
builder.last_uber_end = time;
|
|
||||||
}
|
}
|
||||||
GameEvent::ChargeDeployed(_deployed) => {
|
GameEvent::ChargeDeployed(_deployed) => {
|
||||||
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
|
subject_data.charge_count += 1;
|
||||||
builder.charge_count += 1;
|
|
||||||
}
|
}
|
||||||
GameEvent::AdvantageLost(lost) => {
|
GameEvent::AdvantageLost(lost) => {
|
||||||
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
|
subject_data.advantages_lost += 1;
|
||||||
builder.advantages_lost += 1;
|
|
||||||
let time = lost.time.unwrap_or_default();
|
let time = lost.time.unwrap_or_default();
|
||||||
if time > builder.biggest_advantage_lost {
|
if time > subject_data.biggest_advantage_lost {
|
||||||
builder.biggest_advantage_lost = time;
|
subject_data.biggest_advantage_lost = time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameEvent::FirstHeal(first) => {
|
GameEvent::FirstHeal(first) => {
|
||||||
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
|
subject_data.total_time_before_healing += first.time.unwrap_or_default();
|
||||||
builder.total_time_before_healing += first.time.unwrap_or_default();
|
subject_data.start_healing_count += 1;
|
||||||
builder.start_healing_count += 1;
|
subject_data.last_build_start = time;
|
||||||
builder.last_build_start = time;
|
|
||||||
}
|
}
|
||||||
GameEvent::ChargeReady => {
|
GameEvent::ChargeReady => {
|
||||||
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
|
if subject_data.last_build_start > 0 {
|
||||||
if builder.last_build_start > 0 {
|
let build_time = time - subject_data.last_build_start;
|
||||||
let build_time = time - builder.last_build_start;
|
subject_data.last_build_start = 0;
|
||||||
builder.last_build_start = 0;
|
subject_data.total_time_to_build += build_time;
|
||||||
builder.total_time_to_build += build_time;
|
subject_data.uber_build_count += 1;
|
||||||
builder.uber_build_count += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameEvent::MedicDeath(death) => {
|
GameEvent::MedicDeath(death) => {
|
||||||
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
|
|
||||||
let charge = death.charge.unwrap_or_default();
|
let charge = death.charge.unwrap_or_default();
|
||||||
if charge >= 95 && charge < 100 {
|
if charge >= 95 && charge < 100 {
|
||||||
builder.near_full_charge_death += 1;
|
subject_data.near_full_charge_death += 1;
|
||||||
} else if charge >= 100 {
|
} else if charge >= 100 {
|
||||||
builder.drops += 1;
|
subject_data.drops += 1;
|
||||||
}
|
}
|
||||||
if time - builder.last_uber_end <= 10 {
|
if time - subject_data.last_uber_end <= 10 {
|
||||||
builder.deaths_after_uber += 1;
|
subject_data.deaths_after_uber += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
self.0
|
()
|
||||||
.into_iter()
|
}
|
||||||
.filter(|(_, builder)| builder.start_healing_count > 0)
|
|
||||||
.map(|(steam_id, builder)| (steam_id, builder.into()))
|
fn finish_per_subject(
|
||||||
.collect()
|
&self,
|
||||||
|
_subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput {
|
||||||
|
data.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::common::SubjectId;
|
use crate::common::SubjectId;
|
||||||
use crate::event::GameEvent;
|
use crate::event::GameEvent;
|
||||||
use crate::raw_event::RawEventType;
|
use crate::raw_event::RawEventType;
|
||||||
use crate::SubjectMap;
|
use crate::{SubjectData, SubjectMap};
|
||||||
pub use chat::{ChatHandler, ChatMessage, ChatType};
|
pub use chat::{ChatHandler, ChatMessage, ChatType};
|
||||||
pub use classstats::{ClassStat, ClassStatsHandler};
|
pub use classstats::{ClassStat, ClassStatsHandler};
|
||||||
pub use healspread::HealSpreadHandler;
|
pub use healspread::HealSpreadHandler;
|
||||||
|
|
@ -17,13 +17,27 @@ mod lobbysettings;
|
||||||
mod medicstats;
|
mod medicstats;
|
||||||
|
|
||||||
pub trait EventHandler: Default {
|
pub trait EventHandler: Default {
|
||||||
type Output;
|
type GlobalOutput;
|
||||||
|
type PerSubjectData: Default;
|
||||||
|
type PerSubjectOutput;
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool;
|
fn does_handle(&self, ty: RawEventType) -> bool;
|
||||||
|
|
||||||
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent);
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
subject_data: &mut Self::PerSubjectData,
|
||||||
|
event: &GameEvent,
|
||||||
|
);
|
||||||
|
|
||||||
fn finish(self, subjects: &SubjectMap) -> Self::Output;
|
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput;
|
||||||
|
|
||||||
|
fn finish_per_subject(
|
||||||
|
&self,
|
||||||
|
subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -33,20 +47,52 @@ pub struct HandlerStack<Head, Tail> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head, Tail> {
|
impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head, Tail> {
|
||||||
type Output = (Head::Output, Tail::Output);
|
type GlobalOutput = (Head::GlobalOutput, Tail::GlobalOutput);
|
||||||
|
type PerSubjectData = (Head::PerSubjectData, Tail::PerSubjectData);
|
||||||
|
type PerSubjectOutput = (Head::PerSubjectOutput, Tail::PerSubjectOutput);
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
self.head.does_handle(ty) || self.tail.does_handle(ty)
|
self.head.does_handle(ty) || self.tail.does_handle(ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent) {
|
fn handle(
|
||||||
self.head.handle(time, subject, event);
|
&mut self,
|
||||||
self.tail.handle(time, subject, event);
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
subject_data: &mut Self::PerSubjectData,
|
||||||
|
event: &GameEvent,
|
||||||
|
) {
|
||||||
|
self.head.handle(time, subject, &mut subject_data.0, event);
|
||||||
|
self.tail.handle(time, subject, &mut subject_data.1, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, subjects: &SubjectMap) -> Self::Output {
|
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput {
|
||||||
(self.head.finish(subjects), self.tail.finish(subjects))
|
(
|
||||||
|
self.head.finish_global(subjects),
|
||||||
|
self.tail.finish_global(subjects),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish_per_subject(
|
||||||
|
&self,
|
||||||
|
subject: &SubjectData,
|
||||||
|
data: Self::PerSubjectData,
|
||||||
|
) -> Self::PerSubjectOutput {
|
||||||
|
(
|
||||||
|
self.head.finish_per_subject(subject, data.0),
|
||||||
|
self.tail.finish_per_subject(subject, data.1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! replace_expr {
|
||||||
|
($_t:tt $sub:expr) => {
|
||||||
|
$sub
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! count_tts {
|
||||||
|
($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -61,25 +107,55 @@ macro_rules! handler {
|
||||||
$($child: $ty),*
|
$($child: $ty),*
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct [<$name Output>] {
|
pub struct [<$name GlobalOutput>] {
|
||||||
$($child: <$ty as $crate::EventHandler>::Output),*
|
$($child: <$ty as $crate::EventHandler>::GlobalOutput),*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::Serialize for [<$name Output>] {
|
impl serde::Serialize for [<$name GlobalOutput>] {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
let mut state = serializer.serialize_struct(concat!("$name", "output"), 3)?;
|
let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), count_tts!($($child)*))?;
|
||||||
$(state.serialize_field("$child", &self.$child)?;)*
|
$(state.serialize_field(stringify!($child), &self.$child)?;)*
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct [<$name PerSubjectData>] {
|
||||||
|
$($child: <$ty as $crate::EventHandler>::PerSubjectData),*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for [<$name PerSubjectData>] {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
$($child: <$ty as $crate::EventHandler>::PerSubjectData::default()),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct [<$name PerSubjectOutput>] {
|
||||||
|
$($child: <$ty as $crate::EventHandler>::PerSubjectOutput),*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for [<$name PerSubjectOutput>] {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
use serde::ser::SerializeStruct;
|
||||||
|
let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), count_tts!($($child)*))?;
|
||||||
|
$(state.serialize_field(stringify!($child), &self.$child)?;)*
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl $crate::EventHandler for $name {
|
impl $crate::EventHandler for $name {
|
||||||
type Output = [<$name Output>];
|
type GlobalOutput = [<$name GlobalOutput>];
|
||||||
|
type PerSubjectData = [<$name PerSubjectData>];
|
||||||
|
type PerSubjectOutput = [<$name PerSubjectOutput>];
|
||||||
|
|
||||||
fn does_handle(&self, ty: $crate::RawEventType) -> bool {
|
fn does_handle(&self, ty: $crate::RawEventType) -> bool {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|
@ -87,17 +163,23 @@ macro_rules! handler {
|
||||||
$(self.$child.does_handle(ty))||*
|
$(self.$child.does_handle(ty))||*
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle(&mut self, time: u32, subject: $crate::SubjectId, event: &$crate::GameEvent) {
|
fn handle(&mut self, time: u32, subject: $crate::SubjectId,subject_data:&mut Self::PerSubjectData, event: &$crate::GameEvent) {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use $crate::EventHandler;
|
use $crate::EventHandler;
|
||||||
$(self.$child.handle(time, subject, event);)*
|
$(self.$child.handle(time, subject, &mut subject_data.$child, event);)*
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, subjects: &$crate::SubjectMap) -> Self::Output {
|
fn finish_global(self, subjects: &$crate::SubjectMap) -> Self::GlobalOutput {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use $crate::EventHandler;
|
use $crate::EventHandler;
|
||||||
Self::Output {
|
Self::GlobalOutput {
|
||||||
$($child: self.$child.finish(subjects),)*
|
$($child: self.$child.finish_global(subjects),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_per_subject(&self, subject: &SubjectData, data: Self::PerSubjectData) -> Self::PerSubjectOutput {
|
||||||
|
Self::PerSubjectOutput {
|
||||||
|
$($child: self.$child.finish_per_subject(subject, data.$child),)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
src/subjectmap.rs
Normal file
71
src/subjectmap.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use crate::raw_event::RawSubject;
|
||||||
|
use crate::{SubjectData, SubjectError, SubjectId};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SubjectMap<T = ()>(BTreeMap<SubjectId, (SubjectData, T)>);
|
||||||
|
|
||||||
|
impl<T> Index<SubjectId> for SubjectMap<T> {
|
||||||
|
type Output = (SubjectData, T);
|
||||||
|
|
||||||
|
fn index(&self, index: SubjectId) -> &Self::Output {
|
||||||
|
self.0
|
||||||
|
.get(&index)
|
||||||
|
.expect("subject id created without matching subject data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IndexMut<SubjectId> for SubjectMap<T> {
|
||||||
|
fn index_mut(&mut self, index: SubjectId) -> &mut Self::Output {
|
||||||
|
self.0
|
||||||
|
.get_mut(&index)
|
||||||
|
.expect("subject id created without matching subject data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> SubjectMap<T> {
|
||||||
|
pub fn insert(&mut self, raw: &RawSubject) -> Result<(SubjectId, &mut T), SubjectError> {
|
||||||
|
let id = raw.id()?;
|
||||||
|
let (_, data) = self
|
||||||
|
.0
|
||||||
|
.entry(id)
|
||||||
|
.or_insert_with(|| (raw.try_into().unwrap(), T::default()));
|
||||||
|
Ok((id, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SubjectMap<T> {
|
||||||
|
pub fn to_just_subjects(&self) -> SubjectMap<()> {
|
||||||
|
SubjectMap(
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|(k, (a, _))| (*k, (a.clone(), ())))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for SubjectMap<T> {
|
||||||
|
type Item = (SubjectId, SubjectData, T);
|
||||||
|
type IntoIter = SubjectMapIter<T>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
SubjectMapIter {
|
||||||
|
iter: self.0.into_iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SubjectMapIter<T> {
|
||||||
|
iter: std::collections::btree_map::IntoIter<SubjectId, (SubjectData, T)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Iterator for SubjectMapIter<T> {
|
||||||
|
type Item = (SubjectId, SubjectData, T);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.iter.next().map(|(k, (a, b))| (k, a, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue