Skip to main content

subtr_actor/stats/analysis_graph/nodes/
stats_timeline_frame.rs

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