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