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