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            musty_flick: ctx
157                .get::<MustyFlickCalculator>()?
158                .player_stats()
159                .get(player_id)
160                .cloned()
161                .unwrap_or_default(),
162            dodge_reset: ctx
163                .get::<DodgeResetCalculator>()?
164                .player_stats()
165                .get(player_id)
166                .cloned()
167                .unwrap_or_default(),
168            ball_carry: ctx
169                .get::<BallCarryCalculator>()?
170                .player_stats()
171                .get(player_id)
172                .cloned()
173                .unwrap_or_default(),
174            boost: ctx
175                .get::<BoostCalculator>()?
176                .player_stats()
177                .get(player_id)
178                .cloned()
179                .unwrap_or_default(),
180            movement: ctx
181                .get::<MovementCalculator>()?
182                .player_stats()
183                .get(player_id)
184                .cloned()
185                .unwrap_or_default(),
186            positioning: ctx
187                .get::<PositioningCalculator>()?
188                .player_stats()
189                .get(player_id)
190                .cloned()
191                .unwrap_or_default(),
192            powerslide: ctx
193                .get::<PowerslideCalculator>()?
194                .player_stats()
195                .get(player_id)
196                .cloned()
197                .unwrap_or_default(),
198            demo: ctx
199                .get::<DemoCalculator>()?
200                .player_stats()
201                .get(player_id)
202                .cloned()
203                .unwrap_or_default(),
204        })
205    }
206}
207
208impl Default for StatsTimelineFrameNode {
209    fn default() -> Self {
210        Self::new()
211    }
212}
213
214impl AnalysisNode for StatsTimelineFrameNode {
215    type State = StatsTimelineFrameState;
216
217    fn name(&self) -> &'static str {
218        "stats_timeline_frame"
219    }
220
221    fn on_replay_meta(&mut self, meta: &ReplayMeta) -> SubtrActorResult<()> {
222        self.replay_meta = Some(meta.clone());
223        Ok(())
224    }
225
226    fn dependencies(&self) -> NodeDependencies {
227        vec![
228            frame_info_dependency(),
229            gameplay_state_dependency(),
230            live_play_dependency(),
231            match_stats_dependency(),
232            backboard_dependency(),
233            ceiling_shot_dependency(),
234            double_tap_dependency(),
235            fifty_fifty_dependency(),
236            possession_dependency(),
237            pressure_dependency(),
238            rush_dependency(),
239            touch_dependency(),
240            speed_flip_dependency(),
241            musty_flick_dependency(),
242            dodge_reset_dependency(),
243            ball_carry_dependency(),
244            boost_dependency(),
245            movement_dependency(),
246            positioning_dependency(),
247            powerslide_dependency(),
248            demo_dependency(),
249        ]
250    }
251
252    fn evaluate(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
253        let replay_meta = self.replay_meta()?;
254        let frame = ctx.get::<FrameInfo>()?;
255        let gameplay = ctx.get::<GameplayState>()?;
256        let live_play_state = ctx.get::<LivePlayState>()?;
257        self.state.frame = Some(ReplayStatsFrame {
258            frame_number: frame.frame_number,
259            time: frame.time,
260            dt: frame.dt,
261            seconds_remaining: frame.seconds_remaining,
262            game_state: gameplay.game_state,
263            gameplay_phase: live_play_state.gameplay_phase,
264            is_live_play: live_play_state.is_live_play,
265            team_zero: self.team_snapshot(ctx, true)?,
266            team_one: self.team_snapshot(ctx, false)?,
267            players: replay_meta
268                .player_order()
269                .map(|player| self.player_snapshot(ctx, replay_meta, player))
270                .collect::<SubtrActorResult<Vec<_>>>()?,
271        });
272        Ok(())
273    }
274
275    fn state(&self) -> &Self::State {
276        &self.state
277    }
278}
279
280pub(crate) fn boxed_default() -> Box<dyn AnalysisNodeDyn> {
281    Box::new(StatsTimelineFrameNode::new())
282}