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