1use super::*;
2use crate::stats::calculators::*;
3use crate::*;
4
5#[derive(Debug, Clone, Default)]
6pub struct StatsTimelineEventsState {
7 pub events: ReplayStatsTimelineEvents,
8}
9
10pub struct StatsTimelineEventsNode {
11 state: StatsTimelineEventsState,
12}
13
14impl StatsTimelineEventsNode {
15 pub fn new() -> Self {
16 Self {
17 state: StatsTimelineEventsState::default(),
18 }
19 }
20}
21
22impl_analysis_node! {
23 node = StatsTimelineEventsNode,
24 state = StatsTimelineEventsState,
25 name = "stats_timeline_events",
26 dependencies = [
27 match_stats_dependency(),
28 demo_dependency(),
29 backboard_dependency(),
30 ball_carry_dependency(),
31 ceiling_shot_dependency(),
32 wall_aerial_dependency(),
33 wall_aerial_shot_dependency(),
34 center_dependency(),
35 dodge_reset_dependency(),
36 double_tap_dependency(),
37 one_timer_dependency(),
38 pass_dependency(),
39 fifty_fifty_dependency(),
40 flick_dependency(),
41 musty_flick_dependency(),
42 aerial_goal_dependency(),
43 high_aerial_goal_dependency(),
44 long_distance_goal_dependency(),
45 own_half_goal_dependency(),
46 empty_net_goal_dependency(),
47 counter_attack_goal_dependency(),
48 flick_goal_dependency(),
49 double_tap_goal_dependency(),
50 one_timer_goal_dependency(),
51 air_dribble_goal_dependency(),
52 flip_reset_goal_dependency(),
53 half_volley_goal_dependency(),
54 rush_dependency(),
55 speed_flip_dependency(),
56 half_flip_dependency(),
57 half_volley_dependency(),
58 wavedash_dependency(),
59 whiff_dependency(),
60 boost_dependency(),
61 bump_dependency(),
62 ],
63 inputs = {
64 match_stats: MatchStatsCalculator,
65 demo: DemoCalculator,
66 backboard: BackboardCalculator,
67 ball_carry: BallCarryCalculator,
68 ceiling_shot: CeilingShotCalculator,
69 wall_aerial: WallAerialCalculator,
70 wall_aerial_shot: WallAerialShotCalculator,
71 center: CenterCalculator,
72 dodge_reset: DodgeResetCalculator,
73 double_tap: DoubleTapCalculator,
74 one_timer: OneTimerCalculator,
75 pass: PassCalculator,
76 fifty_fifty: FiftyFiftyCalculator,
77 flick: FlickCalculator,
78 musty_flick: MustyFlickCalculator,
79 aerial_goal: AerialGoalCalculator,
80 high_aerial_goal: HighAerialGoalCalculator,
81 long_distance_goal: LongDistanceGoalCalculator,
82 own_half_goal: OwnHalfGoalCalculator,
83 empty_net_goal: EmptyNetGoalCalculator,
84 counter_attack_goal: CounterAttackGoalCalculator,
85 flick_goal: FlickGoalCalculator,
86 double_tap_goal: DoubleTapGoalCalculator,
87 one_timer_goal: OneTimerGoalCalculator,
88 air_dribble_goal: AirDribbleGoalCalculator,
89 flip_reset_goal: FlipResetGoalCalculator,
90 half_volley_goal: HalfVolleyGoalCalculator,
91 rush: RushCalculator,
92 speed_flip: SpeedFlipCalculator,
93 half_flip: HalfFlipCalculator,
94 half_volley: HalfVolleyCalculator,
95 wavedash: WavedashCalculator,
96 whiff: WhiffCalculator,
97 boost: BoostCalculator,
98 bump: BumpCalculator,
99 },
100 evaluate = |node| {
101 let mut timeline = match_stats.timeline().to_vec();
102 timeline.extend(demo.timeline().to_vec());
103 timeline.sort_by(|left, right| left.time.total_cmp(&right.time));
104 let goal_tags = combined_goal_tag_events(&[
105 aerial_goal.events(),
106 high_aerial_goal.events(),
107 long_distance_goal.events(),
108 own_half_goal.events(),
109 empty_net_goal.events(),
110 counter_attack_goal.events(),
111 flick_goal.events(),
112 double_tap_goal.events(),
113 one_timer_goal.events(),
114 air_dribble_goal.events(),
115 flip_reset_goal.events(),
116 half_volley_goal.events(),
117 ]);
118
119 node.state.events = ReplayStatsTimelineEvents {
120 timeline,
121 mechanics: build_mechanic_events(
122 ball_carry,
123 ceiling_shot,
124 wall_aerial,
125 wall_aerial_shot,
126 center,
127 dodge_reset,
128 double_tap,
129 flick,
130 musty_flick,
131 one_timer,
132 pass,
133 speed_flip,
134 half_flip,
135 half_volley,
136 wavedash,
137 ),
138 goal_context: match_stats.goal_context_events().to_vec(),
139 backboard: backboard.events().to_vec(),
140 ceiling_shot: ceiling_shot.events().to_vec(),
141 wall_aerial: wall_aerial.events().to_vec(),
142 wall_aerial_shot: wall_aerial_shot.events().to_vec(),
143 center: center.events().to_vec(),
144 double_tap: double_tap.events().to_vec(),
145 one_timer: one_timer.events().to_vec(),
146 pass: pass.events().to_vec(),
147 fifty_fifty: fifty_fifty.events().to_vec(),
148 goal_tags,
149 rush: rush.events().to_vec(),
150 speed_flip: speed_flip.events().to_vec(),
151 half_flip: half_flip.events().to_vec(),
152 half_volley: half_volley.events().to_vec(),
153 wavedash: wavedash.events().to_vec(),
154 whiff: whiff.events().to_vec(),
155 boost_pickups: boost.pickup_comparison_events().to_vec(),
156 bump: bump.events().to_vec(),
157 };
158 Ok(())
159 },
160 state_ref = |node| &node.state,
161}
162
163fn moment_mechanic_event(
164 kind: &str,
165 index: usize,
166 frame: usize,
167 time: f32,
168 player_id: PlayerId,
169 is_team_0: bool,
170) -> MechanicEvent {
171 MechanicEvent {
172 id: format!("{kind}:{frame}:{index}"),
173 kind: kind.to_owned(),
174 player_id,
175 is_team_0,
176 timing: MechanicTiming::Moment { frame, time },
177 properties: Vec::new(),
178 }
179}
180
181#[allow(clippy::too_many_arguments)]
182fn span_mechanic_event(
183 kind: &str,
184 index: usize,
185 start_frame: usize,
186 end_frame: usize,
187 start_time: f32,
188 end_time: f32,
189 player_id: PlayerId,
190 is_team_0: bool,
191) -> MechanicEvent {
192 MechanicEvent {
193 id: format!("{kind}:{start_frame}:{end_frame}:{index}"),
194 kind: kind.to_owned(),
195 player_id,
196 is_team_0,
197 timing: MechanicTiming::Span {
198 start_frame,
199 end_frame,
200 start_time,
201 end_time,
202 },
203 properties: Vec::new(),
204 }
205}
206
207fn mechanic_event_text_property(key: &str, value: &str) -> MechanicEventProperty {
208 MechanicEventProperty {
209 key: key.to_owned(),
210 value: MechanicEventPropertyValue::Text(value.to_owned()),
211 }
212}
213
214fn mechanic_event_unsigned_property(key: &str, value: u32) -> MechanicEventProperty {
215 MechanicEventProperty {
216 key: key.to_owned(),
217 value: MechanicEventPropertyValue::Unsigned(value),
218 }
219}
220
221fn ball_carry_mechanic_event_properties(event: &BallCarryEvent) -> Vec<MechanicEventProperty> {
222 let mut properties = Vec::new();
223 if let Some(origin) = event.air_dribble_origin {
224 properties.push(mechanic_event_text_property(
225 "origin",
226 origin.as_label_value(),
227 ));
228 }
229 if event.kind == BallCarryKind::AirDribble {
230 properties.push(mechanic_event_unsigned_property(
231 "touch_count",
232 event.touch_count,
233 ));
234 }
235 properties
236}
237
238#[allow(clippy::too_many_arguments)]
239fn build_mechanic_events(
240 ball_carry: &BallCarryCalculator,
241 ceiling_shot: &CeilingShotCalculator,
242 wall_aerial: &WallAerialCalculator,
243 wall_aerial_shot: &WallAerialShotCalculator,
244 center: &CenterCalculator,
245 dodge_reset: &DodgeResetCalculator,
246 double_tap: &DoubleTapCalculator,
247 flick: &FlickCalculator,
248 musty_flick: &MustyFlickCalculator,
249 one_timer: &OneTimerCalculator,
250 pass: &PassCalculator,
251 speed_flip: &SpeedFlipCalculator,
252 half_flip: &HalfFlipCalculator,
253 half_volley: &HalfVolleyCalculator,
254 wavedash: &WavedashCalculator,
255) -> Vec<MechanicEvent> {
256 let mut events = Vec::new();
257
258 for (index, event) in ball_carry.carry_events().iter().enumerate() {
259 let kind = match event.kind {
260 BallCarryKind::Carry => "ball_carry",
261 BallCarryKind::AirDribble => "air_dribble",
262 };
263 let mut mechanic_event = span_mechanic_event(
264 kind,
265 index,
266 event.start_frame,
267 event.end_frame,
268 event.start_time,
269 event.end_time,
270 event.player_id.clone(),
271 event.is_team_0,
272 );
273 mechanic_event.properties = ball_carry_mechanic_event_properties(event);
274 events.push(mechanic_event);
275 }
276
277 for (index, event) in ceiling_shot.events().iter().enumerate() {
278 events.push(span_mechanic_event(
279 "ceiling_shot",
280 index,
281 event.ceiling_contact_frame,
282 event.frame,
283 event.ceiling_contact_time,
284 event.time,
285 event.player.clone(),
286 event.is_team_0,
287 ));
288 }
289
290 for (index, event) in wall_aerial.events().iter().enumerate() {
291 let mut mechanic_event = span_mechanic_event(
292 "wall_aerial",
293 index,
294 event.wall_contact_frame,
295 event.frame,
296 event.wall_contact_time,
297 event.time,
298 event.player.clone(),
299 event.is_team_0,
300 );
301 mechanic_event.properties = vec![mechanic_event_text_property(
302 "wall",
303 event.wall.as_label_value(),
304 )];
305 events.push(mechanic_event);
306 }
307
308 for (index, event) in wall_aerial_shot.events().iter().enumerate() {
309 let mut mechanic_event = span_mechanic_event(
310 "wall_aerial_shot",
311 index,
312 event.wall_contact_frame,
313 event.frame,
314 event.wall_contact_time,
315 event.time,
316 event.player.clone(),
317 event.is_team_0,
318 );
319 mechanic_event.properties = vec![mechanic_event_text_property(
320 "wall",
321 event.wall.as_label_value(),
322 )];
323 events.push(mechanic_event);
324 }
325
326 for (index, event) in center.events().iter().enumerate() {
327 events.push(span_mechanic_event(
328 "center",
329 index,
330 event.start_frame,
331 event.frame,
332 event.start_time,
333 event.time,
334 event.player.clone(),
335 event.is_team_0,
336 ));
337 }
338
339 for (index, event) in dodge_reset.on_ball_events().iter().enumerate() {
340 events.push(moment_mechanic_event(
341 "flip_reset",
342 index,
343 event.frame,
344 event.time,
345 event.player.clone(),
346 event.is_team_0,
347 ));
348 }
349
350 for (index, event) in double_tap.events().iter().enumerate() {
351 events.push(span_mechanic_event(
352 "double_tap",
353 index,
354 event.backboard_frame,
355 event.frame,
356 event.backboard_time,
357 event.time,
358 event.player.clone(),
359 event.is_team_0,
360 ));
361 }
362
363 for (index, event) in flick.events().iter().enumerate() {
364 events.push(span_mechanic_event(
365 "flick",
366 index,
367 event.setup_start_frame,
368 event.frame,
369 event.setup_start_time,
370 event.time,
371 event.player.clone(),
372 event.is_team_0,
373 ));
374 }
375
376 for (index, event) in musty_flick.events().iter().enumerate() {
377 events.push(span_mechanic_event(
378 "musty_flick",
379 index,
380 event.dodge_frame,
381 event.frame,
382 event.dodge_time,
383 event.time,
384 event.player.clone(),
385 event.is_team_0,
386 ));
387 }
388
389 for (index, event) in one_timer.events().iter().enumerate() {
390 events.push(span_mechanic_event(
391 "one_timer",
392 index,
393 event.pass_start_frame,
394 event.frame,
395 event.pass_start_time,
396 event.time,
397 event.player.clone(),
398 event.is_team_0,
399 ));
400 }
401
402 for (index, event) in pass.events().iter().enumerate() {
403 events.push(span_mechanic_event(
404 "pass",
405 index,
406 event.start_frame,
407 event.frame,
408 event.start_time,
409 event.time,
410 event.passer.clone(),
411 event.is_team_0,
412 ));
413 }
414
415 for (index, event) in speed_flip.events().iter().enumerate() {
416 events.push(moment_mechanic_event(
417 "speed_flip",
418 index,
419 event.frame,
420 event.time,
421 event.player.clone(),
422 event.is_team_0,
423 ));
424 }
425
426 for (index, event) in half_flip.events().iter().enumerate() {
427 events.push(moment_mechanic_event(
428 "half_flip",
429 index,
430 event.frame,
431 event.time,
432 event.player.clone(),
433 event.is_team_0,
434 ));
435 }
436
437 for (index, event) in half_volley.events().iter().enumerate() {
438 events.push(moment_mechanic_event(
439 "half_volley",
440 index,
441 event.frame,
442 event.time,
443 event.player.clone(),
444 event.is_team_0,
445 ));
446 }
447
448 for (index, event) in wavedash.events().iter().enumerate() {
449 events.push(span_mechanic_event(
450 "wavedash",
451 index,
452 event.dodge_frame,
453 event.frame,
454 event.dodge_time,
455 event.time,
456 event.player.clone(),
457 event.is_team_0,
458 ));
459 }
460
461 events.sort_by(|left, right| {
462 let left_time = mechanic_event_start_time(left);
463 let right_time = mechanic_event_start_time(right);
464 left_time
465 .total_cmp(&right_time)
466 .then_with(|| left.kind.cmp(&right.kind))
467 .then_with(|| left.id.cmp(&right.id))
468 });
469 events
470}
471
472fn mechanic_event_start_time(event: &MechanicEvent) -> f32 {
473 match event.timing {
474 MechanicTiming::Moment { time, .. } => time,
475 MechanicTiming::Span { start_time, .. } => start_time,
476 }
477}