subtr_actor/stats/analysis_graph/nodes/
stats_timeline_frame.rs1use super::*;
2use crate::stats::calculators::*;
3use crate::*;
4
5#[derive(Debug, Clone, Default)]
7pub struct StatsTimelineFrameState {
8 pub frame: Option<ReplayStatsFrame>,
9}
10
11pub struct StatsTimelineFrameNode {
19 replay_meta: Option<ReplayMeta>,
20 state: StatsTimelineFrameState,
21}
22
23impl StatsTimelineFrameNode {
24 pub fn new() -> Self {
25 Self {
26 replay_meta: None,
27 state: StatsTimelineFrameState::default(),
28 }
29 }
30
31 fn replay_meta(&self) -> SubtrActorResult<&ReplayMeta> {
32 self.replay_meta.as_ref().ok_or_else(|| {
33 SubtrActorError::new(SubtrActorErrorVariant::CallbackError(
34 "missing ReplayMeta state while building timeline frame".to_owned(),
35 ))
36 })
37 }
38
39 fn is_team_zero_player(replay_meta: &ReplayMeta, player: &PlayerInfo) -> bool {
40 replay_meta
41 .team_zero
42 .iter()
43 .any(|team_player| team_player.remote_id == player.remote_id)
44 }
45
46 fn team_snapshot(
47 &self,
48 ctx: &AnalysisStateContext<'_>,
49 is_team_zero: bool,
50 ) -> SubtrActorResult<TeamStatsSnapshot> {
51 let projection = ctx.get::<StatsProjectionState>()?;
52 Ok(TeamStatsSnapshot {
53 fifty_fifty: projection.fifty_fifty.stats().for_team(is_team_zero),
54 kickoff: projection.kickoff.stats().for_team(is_team_zero),
55 possession: projection.possession.stats().for_team(is_team_zero),
56 ball_half: projection.ball_half.stats().for_team(is_team_zero),
57 ball_third: projection.ball_third.stats().for_team(is_team_zero),
58 territorial_pressure: projection
59 .territorial_pressure
60 .stats()
61 .for_team(is_team_zero),
62 rotation: if is_team_zero {
63 projection.rotation.team_zero_stats().clone()
64 } else {
65 projection.rotation.team_one_stats().clone()
66 },
67 rush: projection.rush.stats().for_team(is_team_zero),
68 core: if is_team_zero {
69 projection.core.team_zero_stats()
70 } else {
71 projection.core.team_one_stats()
72 },
73 backboard: if is_team_zero {
74 projection.backboard.team_zero_stats().clone()
75 } else {
76 projection.backboard.team_one_stats().clone()
77 },
78 double_tap: if is_team_zero {
79 projection.double_tap.team_zero_stats().clone()
80 } else {
81 projection.double_tap.team_one_stats().clone()
82 },
83 one_timer: if is_team_zero {
84 projection.one_timer.team_zero_stats().clone()
85 } else {
86 projection.one_timer.team_one_stats().clone()
87 },
88 pass: if is_team_zero {
89 projection.pass.team_zero_stats().clone()
90 } else {
91 projection.pass.team_one_stats().clone()
92 },
93 ball_carry: if is_team_zero {
94 projection.ball_carry.team_zero_stats().clone()
95 } else {
96 projection.ball_carry.team_one_stats().clone()
97 },
98 controlled_play: if is_team_zero {
99 projection.controlled_play.team_zero_stats().clone()
100 } else {
101 projection.controlled_play.team_one_stats().clone()
102 },
103 air_dribble: if is_team_zero {
104 projection.ball_carry.team_zero_air_dribble_stats().clone()
105 } else {
106 projection.ball_carry.team_one_air_dribble_stats().clone()
107 },
108 boost: if is_team_zero {
109 projection.boost.team_zero_stats().clone()
110 } else {
111 projection.boost.team_one_stats().clone()
112 },
113 bump: if is_team_zero {
114 projection.bump.team_zero_stats().clone()
115 } else {
116 projection.bump.team_one_stats().clone()
117 },
118 half_volley: if is_team_zero {
119 projection.half_volley.team_zero_stats().clone()
120 } else {
121 projection.half_volley.team_one_stats().clone()
122 },
123 movement: if is_team_zero {
124 projection.movement.team_zero_stats().clone()
125 } else {
126 projection.movement.team_one_stats().clone()
127 },
128 positioning: if is_team_zero {
129 projection.positioning.team_zero_stats().clone()
130 } else {
131 projection.positioning.team_one_stats().clone()
132 },
133 powerslide: if is_team_zero {
134 projection.powerslide.team_zero_stats().clone()
135 } else {
136 projection.powerslide.team_one_stats().clone()
137 },
138 demo: if is_team_zero {
139 projection.demo.team_zero_stats().clone()
140 } else {
141 projection.demo.team_one_stats().clone()
142 },
143 })
144 }
145
146 fn player_snapshot(
147 &self,
148 ctx: &AnalysisStateContext<'_>,
149 replay_meta: &ReplayMeta,
150 player: &PlayerInfo,
151 ) -> SubtrActorResult<PlayerStatsSnapshot> {
152 let player_id = &player.remote_id;
153 let projection = ctx.get::<StatsProjectionState>()?;
154 Ok(PlayerStatsSnapshot {
155 player_id: player.remote_id.clone(),
156 name: player.name.clone(),
157 is_team_0: Self::is_team_zero_player(replay_meta, player),
158 core: projection
159 .core
160 .player_stats()
161 .get(player_id)
162 .cloned()
163 .unwrap_or_default(),
164 backboard: projection
165 .backboard
166 .player_stats()
167 .get(player_id)
168 .cloned()
169 .unwrap_or_default(),
170 ceiling_shot: projection
171 .ceiling_shot
172 .player_stats()
173 .get(player_id)
174 .cloned()
175 .unwrap_or_default(),
176 wall_aerial: projection
177 .wall_aerial
178 .player_stats()
179 .get(player_id)
180 .cloned()
181 .unwrap_or_default(),
182 wall_aerial_shot: projection
183 .wall_aerial_shot
184 .player_stats()
185 .get(player_id)
186 .cloned()
187 .unwrap_or_default(),
188 double_tap: projection
189 .double_tap
190 .player_stats()
191 .get(player_id)
192 .cloned()
193 .unwrap_or_default(),
194 one_timer: projection
195 .one_timer
196 .player_stats()
197 .get(player_id)
198 .cloned()
199 .unwrap_or_default(),
200 pass: projection
201 .pass
202 .player_stats()
203 .get(player_id)
204 .cloned()
205 .unwrap_or_default(),
206 fifty_fifty: projection
207 .fifty_fifty
208 .player_stats()
209 .get(player_id)
210 .cloned()
211 .unwrap_or_default(),
212 kickoff: projection
213 .kickoff
214 .player_stats()
215 .get(player_id)
216 .cloned()
217 .unwrap_or_default(),
218 speed_flip: projection
219 .speed_flip
220 .player_stats()
221 .get(player_id)
222 .cloned()
223 .unwrap_or_default(),
224 half_flip: projection
225 .half_flip
226 .player_stats()
227 .get(player_id)
228 .cloned()
229 .unwrap_or_default(),
230 wavedash: projection
231 .wavedash
232 .player_stats()
233 .get(player_id)
234 .cloned()
235 .unwrap_or_default(),
236 touch: projection
237 .touch
238 .player_stats()
239 .get(player_id)
240 .cloned()
241 .unwrap_or_default(),
242 whiff: projection
243 .whiff
244 .player_stats()
245 .get(player_id)
246 .cloned()
247 .unwrap_or_default(),
248 flick: projection
249 .flick
250 .player_stats()
251 .get(player_id)
252 .cloned()
253 .unwrap_or_default(),
254 dodge_reset: projection
255 .dodge_reset
256 .player_stats()
257 .get(player_id)
258 .cloned()
259 .unwrap_or_default(),
260 flip_reset: projection
261 .flip_reset
262 .player_stats()
263 .get(player_id)
264 .cloned()
265 .unwrap_or_default(),
266 ball_carry: projection
267 .ball_carry
268 .player_stats()
269 .get(player_id)
270 .cloned()
271 .unwrap_or_default(),
272 controlled_play: projection
273 .controlled_play
274 .player_stats()
275 .get(player_id)
276 .cloned()
277 .unwrap_or_default(),
278 air_dribble: projection
279 .ball_carry
280 .player_air_dribble_stats()
281 .get(player_id)
282 .cloned()
283 .unwrap_or_default(),
284 boost: projection
285 .boost
286 .player_stats()
287 .get(player_id)
288 .cloned()
289 .unwrap_or_default(),
290 bump: projection
291 .bump
292 .player_stats()
293 .get(player_id)
294 .cloned()
295 .unwrap_or_default(),
296 half_volley: projection
297 .half_volley
298 .player_stats()
299 .get(player_id)
300 .cloned()
301 .unwrap_or_default(),
302 movement: projection
303 .movement
304 .player_stats()
305 .get(player_id)
306 .cloned()
307 .unwrap_or_default(),
308 positioning: projection
309 .positioning
310 .player_stats()
311 .get(player_id)
312 .cloned()
313 .unwrap_or_default(),
314 rotation: projection
315 .rotation
316 .player_stats()
317 .get(player_id)
318 .cloned()
319 .unwrap_or_default(),
320 powerslide: projection
321 .powerslide
322 .player_stats()
323 .get(player_id)
324 .cloned()
325 .unwrap_or_default(),
326 demo: projection
327 .demo
328 .player_stats()
329 .get(player_id)
330 .cloned()
331 .unwrap_or_default(),
332 })
333 }
334
335 fn update_snapshot(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
336 let replay_meta = self.replay_meta()?;
337 let frame = ctx.get::<FrameInfo>()?;
338 let gameplay = ctx.get::<GameplayState>()?;
339 let live_play_state = ctx.get::<LivePlayState>()?;
340 self.state.frame = Some(ReplayStatsFrame {
341 frame_number: frame.frame_number,
342 time: frame.time,
343 dt: frame.dt,
344 seconds_remaining: frame.seconds_remaining,
345 game_state: gameplay.game_state,
346 ball_has_been_hit: gameplay.ball_has_been_hit,
347 kickoff_countdown_time: gameplay.kickoff_countdown_time,
348 gameplay_phase: live_play_state.gameplay_phase,
349 is_live_play: live_play_state.is_live_play,
350 team_zero: self.team_snapshot(ctx, true)?,
351 team_one: self.team_snapshot(ctx, false)?,
352 players: replay_meta
353 .player_order()
354 .map(|player| self.player_snapshot(ctx, replay_meta, player))
355 .collect::<SubtrActorResult<Vec<_>>>()?,
356 });
357 Ok(())
358 }
359}
360
361impl Default for StatsTimelineFrameNode {
362 fn default() -> Self {
363 Self::new()
364 }
365}
366
367impl AnalysisNode for StatsTimelineFrameNode {
368 type State = StatsTimelineFrameState;
369
370 fn name(&self) -> &'static str {
371 "stats_timeline_frame"
372 }
373
374 fn on_replay_meta(&mut self, meta: &ReplayMeta) -> SubtrActorResult<()> {
375 self.replay_meta = Some(meta.clone());
376 Ok(())
377 }
378
379 fn dependencies(&self) -> NodeDependencies {
380 vec![
381 frame_info_dependency(),
382 gameplay_state_dependency(),
383 live_play_dependency(),
384 stats_projection_dependency(),
385 ]
386 }
387
388 fn evaluate(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
389 self.update_snapshot(ctx)
390 }
391
392 fn finish(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
393 self.update_snapshot(ctx)
394 }
395
396 fn state(&self) -> &Self::State {
397 &self.state
398 }
399}