subtr_actor/stats/analysis_graph/nodes/
stats_timeline_frame.rs1use 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}