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