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