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