per-player data wip

This commit is contained in:
Robin Appelman 2021-08-10 19:32:17 +02:00
commit 9da2d2230a
10 changed files with 341 additions and 127 deletions

View file

@ -25,7 +25,7 @@ pub struct ChatMessage {
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()),
(SubjectData::Player { name, steam_id, .. }, _) => (name.clone(), steam_id.clone()),
_ => {
unreachable!("only player messages are added");
}
@ -50,13 +50,21 @@ pub enum ChatType {
pub struct ChatHandler(Vec<BareChatMessage>);
impl EventHandler for ChatHandler {
type Output = Vec<ChatMessage>;
type GlobalOutput = Vec<ChatMessage>;
type PerSubjectData = ();
type PerSubjectOutput = ();
fn does_handle(&self, ty: RawEventType) -> bool {
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(_)) {
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
.into_iter()
.map(|bare| ChatMessage::from_bare(bare, subjects))
.collect()
}
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data
}
}

View file

@ -2,7 +2,7 @@ use crate::common::{Class, ClassMap, SteamId3, SubjectId};
use crate::event::{DamageEvent, GameEvent, RoleChangeEvent, SpawnEvent};
use crate::module::EventHandler;
use crate::raw_event::{RawEventType, RawSubject};
use crate::SubjectMap;
use crate::{SubjectData, SubjectMap};
use serde::Serialize;
use std::collections::BTreeMap;
use std::ops::{Add, AddAssign};
@ -65,7 +65,9 @@ impl 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 {
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 {
GameEvent::Spawned(SpawnEvent { class: Some(class) })
| GameEvent::RoleChange(RoleChangeEvent { class: Some(class) }) => {
@ -140,7 +148,15 @@ impl EventHandler for ClassStatsHandler {
}
}
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.stats
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
()
}
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data
}
}

View file

@ -2,42 +2,48 @@ use crate::common::{SteamId3, SubjectId};
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use crate::{SubjectData, SubjectMap};
use std::collections::BTreeMap;
use std::convert::TryFrom;
#[derive(Default)]
pub struct HealSpreadHandler(BTreeMap<SteamId3, BTreeMap<SteamId3, u32>>);
pub struct 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 {
matches!(ty, RawEventType::Healed)
}
fn handle(&mut self, _time: u32, subject: SubjectId, event: &GameEvent) {
let healer_steam_id = if let Some(steam_id) = subject.steam_id() {
steam_id
} else {
return;
};
fn handle(
&mut self,
_time: u32,
_subject: SubjectId,
subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
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
.entry(SteamId3(healer_steam_id))
.or_default()
.entry(SteamId3(target_steam_id))
.or_default();
let healed = subject_data.entry(SteamId3(target_steam_id)).or_default();
*healed += heal_event.amount
}
}
}
}
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.0
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
()
}
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data
}
}

View file

@ -2,7 +2,7 @@ use crate::common::SubjectId;
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use crate::{SubjectData, SubjectMap};
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
use serde::{Serialize, Serializer};
use std::num::ParseIntError;
@ -197,13 +197,21 @@ impl 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 {
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) {
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 {
LobbySettingsHandler::NotAvailable => Ok(None),
LobbySettingsHandler::Active(settings) => Ok(Some(settings)),
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> {

View file

@ -1,14 +1,13 @@
use crate::common::{SteamId3, SubjectId};
use crate::common::SubjectId;
use crate::event::GameEvent;
use crate::module::EventHandler;
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use crate::{SubjectData, SubjectMap};
use serde::Serialize;
use std::collections::BTreeMap;
use thiserror::Error;
#[derive(Default)]
struct MedicStatsBuilder {
pub struct MedicStatsBuilder {
advantages_lost: u32,
biggest_advantage_lost: f32,
near_full_charge_death: u32,
@ -62,10 +61,12 @@ impl From<MedicStatsBuilder> for MedicStats {
pub struct InvalidMedicEvent(String);
#[derive(Default)]
pub struct MedicStatsHandler(BTreeMap<SteamId3, MedicStatsBuilder>);
pub struct 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 {
matches!(
@ -79,66 +80,65 @@ impl EventHandler for MedicStatsHandler {
)
}
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent) {
let healer_steam_id = if let Some(steam_id) = subject.steam_id() {
steam_id
} else {
return;
};
fn handle(
&mut self,
time: u32,
_subject: SubjectId,
subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
match event {
GameEvent::ChargeEnded(end) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.total_uber_length += end.duration.unwrap_or_default();
builder.last_uber_end = time;
subject_data.total_uber_length += end.duration.unwrap_or_default();
subject_data.last_uber_end = time;
}
GameEvent::ChargeDeployed(_deployed) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.charge_count += 1;
subject_data.charge_count += 1;
}
GameEvent::AdvantageLost(lost) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.advantages_lost += 1;
subject_data.advantages_lost += 1;
let time = lost.time.unwrap_or_default();
if time > builder.biggest_advantage_lost {
builder.biggest_advantage_lost = time;
if time > subject_data.biggest_advantage_lost {
subject_data.biggest_advantage_lost = time;
}
}
GameEvent::FirstHeal(first) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
builder.total_time_before_healing += first.time.unwrap_or_default();
builder.start_healing_count += 1;
builder.last_build_start = time;
subject_data.total_time_before_healing += first.time.unwrap_or_default();
subject_data.start_healing_count += 1;
subject_data.last_build_start = time;
}
GameEvent::ChargeReady => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
if builder.last_build_start > 0 {
let build_time = time - builder.last_build_start;
builder.last_build_start = 0;
builder.total_time_to_build += build_time;
builder.uber_build_count += 1;
if subject_data.last_build_start > 0 {
let build_time = time - subject_data.last_build_start;
subject_data.last_build_start = 0;
subject_data.total_time_to_build += build_time;
subject_data.uber_build_count += 1;
}
}
GameEvent::MedicDeath(death) => {
let builder = self.0.entry(SteamId3(healer_steam_id)).or_default();
let charge = death.charge.unwrap_or_default();
if charge >= 95 && charge < 100 {
builder.near_full_charge_death += 1;
subject_data.near_full_charge_death += 1;
} else if charge >= 100 {
builder.drops += 1;
subject_data.drops += 1;
}
if time - builder.last_uber_end <= 10 {
builder.deaths_after_uber += 1;
if time - subject_data.last_uber_end <= 10 {
subject_data.deaths_after_uber += 1;
}
}
_ => {}
}
}
fn finish(self, _subjects: &SubjectMap) -> Self::Output {
self.0
.into_iter()
.filter(|(_, builder)| builder.start_healing_count > 0)
.map(|(steam_id, builder)| (steam_id, builder.into()))
.collect()
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
()
}
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data.into()
}
}

View file

@ -1,7 +1,7 @@
use crate::common::SubjectId;
use crate::event::GameEvent;
use crate::raw_event::RawEventType;
use crate::SubjectMap;
use crate::{SubjectData, SubjectMap};
pub use chat::{ChatHandler, ChatMessage, ChatType};
pub use classstats::{ClassStat, ClassStatsHandler};
pub use healspread::HealSpreadHandler;
@ -17,13 +17,27 @@ mod lobbysettings;
mod medicstats;
pub trait EventHandler: Default {
type Output;
type GlobalOutput;
type PerSubjectData: Default;
type PerSubjectOutput;
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)]
@ -33,20 +47,52 @@ pub struct 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 {
self.head.does_handle(ty) || self.tail.does_handle(ty)
}
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent) {
self.head.handle(time, subject, event);
self.tail.handle(time, subject, event);
fn handle(
&mut self,
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 {
(self.head.finish(subjects), self.tail.finish(subjects))
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput {
(
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]
@ -61,25 +107,55 @@ macro_rules! handler {
$($child: $ty),*
}
pub struct [<$name Output>] {
$($child: <$ty as $crate::EventHandler>::Output),*
pub struct [<$name GlobalOutput>] {
$($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>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct(concat!("$name", "output"), 3)?;
$(state.serialize_field("$child", &self.$child)?;)*
let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), count_tts!($($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()
}
}
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 {
#[allow(unused_imports)]
@ -87,17 +163,23 @@ macro_rules! handler {
$(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)]
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)]
use $crate::EventHandler;
Self::Output {
$($child: self.$child.finish(subjects),)*
Self::GlobalOutput {
$($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),)*
}
}
}