Skip to main content

subtr_actor/stats/accumulators/
center.rs

1use super::*;
2
3/// Per-player accumulated centering-pass stats.
4#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
5#[ts(export)]
6pub struct CenterPlayerStats {
7    pub count: u32,
8    pub total_ball_travel_distance: f32,
9    pub total_ball_advance_distance: f32,
10    pub total_lateral_centering_distance: f32,
11    pub longest_center_distance: f32,
12    pub is_last_center: bool,
13    pub last_center_time: Option<f32>,
14    pub last_center_frame: Option<usize>,
15    pub time_since_last_center: Option<f32>,
16    pub frames_since_last_center: Option<usize>,
17}
18
19impl CenterPlayerStats {
20    pub fn average_ball_travel_distance(&self) -> f32 {
21        if self.count == 0 {
22            0.0
23        } else {
24            self.total_ball_travel_distance / self.count as f32
25        }
26    }
27
28    pub fn average_ball_advance_distance(&self) -> f32 {
29        if self.count == 0 {
30            0.0
31        } else {
32            self.total_ball_advance_distance / self.count as f32
33        }
34    }
35
36    pub fn average_lateral_centering_distance(&self) -> f32 {
37        if self.count == 0 {
38            0.0
39        } else {
40            self.total_lateral_centering_distance / self.count as f32
41        }
42    }
43}
44
45/// Per-team accumulated centering-pass stats.
46#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
47#[ts(export)]
48pub struct CenterTeamStats {
49    pub count: u32,
50    pub total_ball_travel_distance: f32,
51    pub total_ball_advance_distance: f32,
52    pub total_lateral_centering_distance: f32,
53    pub longest_center_distance: f32,
54}
55
56impl CenterTeamStats {
57    pub fn average_ball_travel_distance(&self) -> f32 {
58        if self.count == 0 {
59            0.0
60        } else {
61            self.total_ball_travel_distance / self.count as f32
62        }
63    }
64
65    pub fn average_ball_advance_distance(&self) -> f32 {
66        if self.count == 0 {
67            0.0
68        } else {
69            self.total_ball_advance_distance / self.count as f32
70        }
71    }
72
73    pub fn average_lateral_centering_distance(&self) -> f32 {
74        if self.count == 0 {
75            0.0
76        } else {
77            self.total_lateral_centering_distance / self.count as f32
78        }
79    }
80}
81
82/// Accumulates centering-pass stats over the replay.
83#[derive(Debug, Clone, Default, PartialEq)]
84pub struct CenterStatsAccumulator {
85    player_stats: HashMap<PlayerId, CenterPlayerStats>,
86    team_zero_stats: CenterTeamStats,
87    team_one_stats: CenterTeamStats,
88    current_last_center_player: Option<PlayerId>,
89}
90
91impl CenterStatsAccumulator {
92    pub fn new() -> Self {
93        Self::default()
94    }
95
96    pub fn player_stats(&self) -> &HashMap<PlayerId, CenterPlayerStats> {
97        &self.player_stats
98    }
99
100    pub fn team_zero_stats(&self) -> &CenterTeamStats {
101        &self.team_zero_stats
102    }
103
104    pub fn team_one_stats(&self) -> &CenterTeamStats {
105        &self.team_one_stats
106    }
107
108    pub fn begin_sample(&mut self, frame: &FrameInfo) {
109        for stats in self.player_stats.values_mut() {
110            stats.is_last_center = false;
111            stats.time_since_last_center = stats
112                .last_center_time
113                .map(|time| (frame.time - time).max(0.0));
114            stats.frames_since_last_center = stats
115                .last_center_frame
116                .map(|last_frame| frame.frame_number.saturating_sub(last_frame));
117        }
118    }
119
120    pub fn clear_current_last(&mut self) {
121        self.current_last_center_player = None;
122    }
123
124    pub fn apply_event(&mut self, frame: &FrameInfo, event: &CenterEvent) {
125        let player_stats = self.player_stats.entry(event.player.clone()).or_default();
126        player_stats.count += 1;
127        player_stats.total_ball_travel_distance += event.ball_travel_distance;
128        player_stats.total_ball_advance_distance += event.ball_advance_distance;
129        player_stats.total_lateral_centering_distance += event.lateral_centering_distance;
130        player_stats.longest_center_distance = player_stats
131            .longest_center_distance
132            .max(event.ball_travel_distance);
133        player_stats.last_center_time = Some(event.time);
134        player_stats.last_center_frame = Some(event.frame);
135        player_stats.time_since_last_center = Some((frame.time - event.time).max(0.0));
136        player_stats.frames_since_last_center =
137            Some(frame.frame_number.saturating_sub(event.frame));
138
139        let team_stats = if event.is_team_0 {
140            &mut self.team_zero_stats
141        } else {
142            &mut self.team_one_stats
143        };
144        team_stats.count += 1;
145        team_stats.total_ball_travel_distance += event.ball_travel_distance;
146        team_stats.total_ball_advance_distance += event.ball_advance_distance;
147        team_stats.total_lateral_centering_distance += event.lateral_centering_distance;
148        team_stats.longest_center_distance = team_stats
149            .longest_center_distance
150            .max(event.ball_travel_distance);
151
152        self.current_last_center_player = Some(event.player.clone());
153    }
154
155    pub fn finish_sample(&mut self) {
156        if let Some(player_id) = self.current_last_center_player.as_ref() {
157            if let Some(stats) = self.player_stats.get_mut(player_id) {
158                stats.is_last_center = true;
159            }
160        }
161    }
162}