1use super::*;
2use crate::stats::calculators::*;
3use crate::*;
4
5#[derive(Debug, Clone, Default)]
7pub struct StatsTimelineEventsState {
8 pub events: ReplayStatsTimelineEvents,
9}
10
11const MECHANIC_AIR_DRIBBLE: &str = "air_dribble";
12const MECHANIC_BALL_CARRY: &str = "ball_carry";
13const MECHANIC_CEILING_SHOT: &str = "ceiling_shot";
14const MECHANIC_CENTER: &str = "center";
15const MECHANIC_DOUBLE_TAP: &str = "double_tap";
16const MECHANIC_FLICK: &str = "flick";
17const MECHANIC_FLIP_RESET: &str = "flip_reset";
18const MECHANIC_HALF_FLIP: &str = "half_flip";
19const MECHANIC_HALF_VOLLEY: &str = "half_volley";
20const MECHANIC_ONE_TIMER: &str = "one_timer";
21const MECHANIC_PASS: &str = "pass";
22const MECHANIC_SPEED_FLIP: &str = "speed_flip";
23const MECHANIC_WALL_AERIAL: &str = "wall_aerial";
24const MECHANIC_WALL_AERIAL_SHOT: &str = "wall_aerial_shot";
25const MECHANIC_WAVEDASH: &str = "wavedash";
26
27pub const STATS_TIMELINE_MECHANIC_KINDS: &[&str] = &[
29 MECHANIC_AIR_DRIBBLE,
30 MECHANIC_BALL_CARRY,
31 MECHANIC_CEILING_SHOT,
32 MECHANIC_CENTER,
33 MECHANIC_DOUBLE_TAP,
34 MECHANIC_FLICK,
35 MECHANIC_FLIP_RESET,
36 MECHANIC_HALF_FLIP,
37 MECHANIC_HALF_VOLLEY,
38 MECHANIC_ONE_TIMER,
39 MECHANIC_PASS,
40 MECHANIC_SPEED_FLIP,
41 MECHANIC_WALL_AERIAL,
42 MECHANIC_WALL_AERIAL_SHOT,
43 MECHANIC_WAVEDASH,
44];
45
46pub struct StatsTimelineEventsNode {
48 state: StatsTimelineEventsState,
49}
50
51impl StatsTimelineEventsNode {
52 pub fn new() -> Self {
53 Self {
54 state: StatsTimelineEventsState::default(),
55 }
56 }
57
58 fn dependencies() -> NodeDependencies {
59 vec![
60 frame_info_dependency(),
61 gameplay_state_dependency(),
62 live_play_dependency(),
63 match_stats_dependency(),
64 backboard_dependency(),
66 ceiling_shot_dependency(),
67 wall_aerial_dependency(),
68 wall_aerial_shot_dependency(),
69 double_tap_dependency(),
70 one_timer_dependency(),
71 pass_dependency(),
72 controlled_play_dependency(),
73 fifty_fifty_dependency(),
74 kickoff_dependency(),
75 possession_dependency(),
76 player_possession_dependency(),
77 loose_possession_dependency(),
78 ball_half_dependency(),
79 ball_third_dependency(),
80 territorial_pressure_dependency(),
81 rotation_dependency(),
82 rush_dependency(),
83 touch_dependency(),
84 whiff_dependency(),
85 wavedash_dependency(),
86 flip_impulse_dependency(),
87 speed_flip_dependency(),
88 half_flip_dependency(),
89 flick_dependency(),
90 dodge_reset_dependency(),
91 ball_carry_dependency(),
92 air_dribble_dependency(),
93 boost_dependency(),
94 bump_dependency(),
95 half_volley_dependency(),
96 movement_dependency(),
97 positioning_dependency(),
98 powerslide_dependency(),
99 demo_dependency(),
100 center_dependency(),
101 aerial_goal_dependency(),
102 high_aerial_goal_dependency(),
103 long_distance_goal_dependency(),
104 own_half_goal_dependency(),
105 empty_net_goal_dependency(),
106 counter_attack_goal_dependency(),
107 sustained_pressure_goal_dependency(),
108 flick_goal_dependency(),
109 ceiling_shot_goal_dependency(),
110 double_tap_goal_dependency(),
111 one_timer_goal_dependency(),
112 passing_goal_dependency(),
113 air_dribble_goal_dependency(),
114 flip_reset_goal_dependency(),
115 flip_into_ball_goal_dependency(),
116 bump_goal_dependency(),
117 demo_goal_dependency(),
118 half_volley_goal_dependency(),
119 kickoff_goal_dependency(),
120 ]
121 }
122
123 fn capture_events(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
124 let match_stats = ctx.get::<MatchStatsCalculator>()?;
125 let possession = ctx.get::<PossessionCalculator>()?;
126 let player_possession = ctx.get::<PlayerPossessionCalculator>()?;
127 let loose_possession = ctx.get::<LoosePossessionCalculator>()?;
128 let ball_half = ctx.get::<BallHalfCalculator>()?;
129 let ball_third = ctx.get::<BallThirdCalculator>()?;
130 let territorial_pressure = ctx.get::<TerritorialPressureCalculator>()?;
131 let movement = ctx.get::<MovementCalculator>()?;
132 let positioning = ctx.get::<PositioningCalculator>()?;
133 let rotation = ctx.get::<RotationCalculator>()?;
134 let demo = ctx.get::<DemoCalculator>()?;
135 let backboard = ctx.get::<BackboardCalculator>()?;
136 let ball_carry = ctx.get::<BallCarryCalculator>()?;
137 let air_dribble = ctx.get::<AirDribbleCalculator>()?;
138 let ceiling_shot = ctx.get::<CeilingShotCalculator>()?;
139 let wall_aerial = ctx.get::<WallAerialCalculator>()?;
140 let wall_aerial_shot = ctx.get::<WallAerialShotCalculator>()?;
141 let center = ctx.get::<CenterCalculator>()?;
142 let dodge_reset = ctx.get::<DodgeResetCalculator>()?;
143 let double_tap = ctx.get::<DoubleTapCalculator>()?;
144 let one_timer = ctx.get::<OneTimerCalculator>()?;
145 let pass = ctx.get::<PassCalculator>()?;
146 let controlled_play = ctx.get::<ControlledPlayCalculator>()?;
147 let fifty_fifty = ctx.get::<FiftyFiftyCalculator>()?;
148 let kickoff = ctx.get::<KickoffCalculator>()?;
149 let flick = ctx.get::<FlickCalculator>()?;
150 let aerial_goal = ctx.get::<AerialGoalCalculator>()?;
151 let high_aerial_goal = ctx.get::<HighAerialGoalCalculator>()?;
152 let long_distance_goal = ctx.get::<LongDistanceGoalCalculator>()?;
153 let own_half_goal = ctx.get::<OwnHalfGoalCalculator>()?;
154 let empty_net_goal = ctx.get::<EmptyNetGoalCalculator>()?;
155 let counter_attack_goal = ctx.get::<CounterAttackGoalCalculator>()?;
156 let sustained_pressure_goal = ctx.get::<SustainedPressureGoalCalculator>()?;
157 let flick_goal = ctx.get::<FlickGoalCalculator>()?;
158 let ceiling_shot_goal = ctx.get::<CeilingShotGoalCalculator>()?;
159 let double_tap_goal = ctx.get::<DoubleTapGoalCalculator>()?;
160 let one_timer_goal = ctx.get::<OneTimerGoalCalculator>()?;
161 let passing_goal = ctx.get::<PassingGoalCalculator>()?;
162 let air_dribble_goal = ctx.get::<AirDribbleGoalCalculator>()?;
163 let flip_reset_goal = ctx.get::<FlipResetGoalCalculator>()?;
164 let flip_into_ball_goal = ctx.get::<FlipIntoBallGoalCalculator>()?;
165 let bump_goal = ctx.get::<BumpGoalCalculator>()?;
166 let demo_goal = ctx.get::<DemoGoalCalculator>()?;
167 let half_volley_goal = ctx.get::<HalfVolleyGoalCalculator>()?;
168 let kickoff_goal = ctx.get::<KickoffGoalCalculator>()?;
169 let rush = ctx.get::<RushCalculator>()?;
170 let flip_impulse = ctx.get::<FlipImpulseCalculator>()?;
171 let speed_flip = ctx.get::<SpeedFlipCalculator>()?;
172 let half_flip = ctx.get::<HalfFlipCalculator>()?;
173 let half_volley = ctx.get::<HalfVolleyCalculator>()?;
174 let wavedash = ctx.get::<WavedashCalculator>()?;
175 let whiff = ctx.get::<WhiffCalculator>()?;
176 let powerslide = ctx.get::<PowerslideCalculator>()?;
177 let touch = ctx.get::<TouchCalculator>()?;
178 let boost = ctx.get::<BoostCalculator>()?;
179 let bump = ctx.get::<BumpCalculator>()?;
180
181 let mut timeline = match_stats.timeline().to_vec();
182 timeline.sort_by(|left, right| left.time.total_cmp(&right.time));
183 let goal_tag_assignments = combined_goal_tag_assignments(&[
184 aerial_goal.events(),
185 high_aerial_goal.events(),
186 long_distance_goal.events(),
187 own_half_goal.events(),
188 empty_net_goal.events(),
189 counter_attack_goal.events(),
190 sustained_pressure_goal.events(),
191 flick_goal.events(),
192 ceiling_shot_goal.events(),
193 double_tap_goal.events(),
194 one_timer_goal.events(),
195 passing_goal.events(),
196 air_dribble_goal.events(),
197 flip_reset_goal.events(),
198 flip_into_ball_goal.events(),
199 bump_goal.events(),
200 demo_goal.events(),
201 half_volley_goal.events(),
202 kickoff_goal.events(),
203 ]);
204 let goal_context =
205 goal_context_events_with_tags(match_stats.goal_context_events(), &goal_tag_assignments);
206
207 self.state.events = ReplayStatsTimelineEvents {
208 events: build_replay_events(
209 &timeline,
210 match_stats,
211 possession,
212 player_possession,
213 loose_possession,
214 ball_half,
215 ball_third,
216 territorial_pressure,
217 movement,
218 positioning,
219 rotation,
220 &goal_context,
221 backboard,
222 ball_carry,
223 air_dribble,
224 ceiling_shot,
225 wall_aerial,
226 wall_aerial_shot,
227 center,
228 dodge_reset,
229 double_tap,
230 one_timer,
231 pass,
232 controlled_play,
233 fifty_fifty,
234 kickoff,
235 rush,
236 flip_impulse,
237 speed_flip,
238 half_flip,
239 half_volley,
240 wavedash,
241 whiff,
242 powerslide,
243 touch,
244 boost,
245 bump,
246 demo,
247 flick,
248 ),
249 };
250 Ok(())
251 }
252}
253
254impl Default for StatsTimelineEventsNode {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl AnalysisNode for StatsTimelineEventsNode {
261 type State = StatsTimelineEventsState;
262
263 fn name(&self) -> &'static str {
264 "stats_timeline_events"
265 }
266
267 fn emitted_events(&self) -> &'static [crate::stats::calculators::EmittedEvent] {
268 crate::stats::calculators::STATS_TIMELINE_EVENTS_EMITTED_EVENTS
269 }
270
271 fn dependencies(&self) -> Vec<AnalysisDependency> {
272 Self::dependencies()
273 }
274
275 fn evaluate(&mut self, _ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
276 Ok(())
277 }
278
279 fn finish(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
280 self.capture_events(ctx)
281 }
282
283 fn state(&self) -> &Self::State {
284 &self.state
285 }
286}
287
288fn moment(frame: usize, time: f32) -> EventTiming {
289 EventTiming::Moment { frame, time }
290}
291
292fn span(start_frame: usize, end_frame: usize, start_time: f32, end_time: f32) -> EventTiming {
293 EventTiming::Span {
294 start_frame,
295 end_frame,
296 start_time,
297 end_time,
298 }
299}
300
301#[allow(clippy::too_many_arguments)]
302fn make_event(
303 stream: &str,
304 index: usize,
305 timing: EventTiming,
306 payload: EventPayload,
307 primary_player: Option<PlayerId>,
308 secondary_player: Option<PlayerId>,
309 team_is_team_0: Option<bool>,
310 player_position: Option<[f32; 3]>,
311 ball_position: Option<[f32; 3]>,
312 confidence: Option<f32>,
313) -> Event {
314 let frame_id = match timing {
315 EventTiming::Moment { frame, .. } => frame.to_string(),
316 EventTiming::Span {
317 start_frame,
318 end_frame,
319 ..
320 } => format!("{start_frame}:{end_frame}"),
321 };
322 let scope = payload.scope();
323 Event {
324 meta: EventMeta {
325 id: format!("{stream}:{frame_id}:{index}"),
326 stream: stream.to_owned(),
327 label: stats_timeline_event_label(stream),
328 scope,
329 timing,
330 primary_player,
331 secondary_player,
332 player_position,
333 ball_position,
334 team_is_team_0,
335 confidence,
336 properties: Vec::new(),
337 },
338 payload,
339 }
340}
341
342fn event_start_time(event: &Event) -> f32 {
343 match event.meta.timing {
344 EventTiming::Moment { time, .. } => time,
345 EventTiming::Span { start_time, .. } => start_time,
346 }
347}
348
349#[allow(clippy::too_many_arguments, clippy::cognitive_complexity)]
350fn build_replay_events(
351 timeline: &[TimelineEvent],
352 match_stats: &MatchStatsCalculator,
353 possession: &PossessionCalculator,
354 player_possession: &PlayerPossessionCalculator,
355 loose_possession: &LoosePossessionCalculator,
356 ball_half: &BallHalfCalculator,
357 ball_third: &BallThirdCalculator,
358 territorial_pressure: &TerritorialPressureCalculator,
359 movement: &MovementCalculator,
360 positioning: &PositioningCalculator,
361 rotation: &RotationCalculator,
362 goal_context: &[GoalContextEvent],
363 backboard: &BackboardCalculator,
364 ball_carry: &BallCarryCalculator,
365 air_dribble: &AirDribbleCalculator,
366 ceiling_shot: &CeilingShotCalculator,
367 wall_aerial: &WallAerialCalculator,
368 wall_aerial_shot: &WallAerialShotCalculator,
369 center: &CenterCalculator,
370 dodge_reset: &DodgeResetCalculator,
371 double_tap: &DoubleTapCalculator,
372 one_timer: &OneTimerCalculator,
373 pass: &PassCalculator,
374 controlled_play: &ControlledPlayCalculator,
375 fifty_fifty: &FiftyFiftyCalculator,
376 kickoff: &KickoffCalculator,
377 rush: &RushCalculator,
378 flip_impulse: &FlipImpulseCalculator,
379 speed_flip: &SpeedFlipCalculator,
380 half_flip: &HalfFlipCalculator,
381 half_volley: &HalfVolleyCalculator,
382 wavedash: &WavedashCalculator,
383 whiff: &WhiffCalculator,
384 powerslide: &PowerslideCalculator,
385 touch: &TouchCalculator,
386 boost: &BoostCalculator,
387 bump: &BumpCalculator,
388 demo: &DemoCalculator,
389 flick: &FlickCalculator,
390) -> Vec<Event> {
391 let mut events = Vec::new();
392
393 for (index, event) in timeline.iter().enumerate() {
394 events.push(make_event(
395 "timeline",
396 index,
397 moment(event.frame.unwrap_or_default(), event.time),
398 EventPayload::Timeline(event.clone()),
399 event.player_id.clone(),
400 None,
401 event.is_team_0,
402 event.player_position,
403 None,
404 None,
405 ));
406 }
407
408 for (index, event) in match_stats.core_player_events().iter().enumerate() {
409 events.push(make_event(
410 "core_player",
411 index,
412 moment(event.frame, event.time),
413 EventPayload::CorePlayer(event.clone()),
414 Some(event.player.clone()),
415 None,
416 Some(event.is_team_0),
417 event.player_position,
418 None,
419 None,
420 ));
421 }
422
423 for (index, event) in possession.events().iter().enumerate() {
424 events.push(make_event(
425 "possession",
426 index,
427 span(event.frame, event.end_frame, event.time, event.end_time),
428 EventPayload::Possession(event.clone()),
429 event.player_id.clone(),
430 None,
431 None,
432 None,
433 None,
434 None,
435 ));
436 }
437
438 for (index, event) in loose_possession.events().iter().enumerate() {
439 events.push(make_event(
440 "loose_possession",
441 index,
442 span(event.frame, event.end_frame, event.time, event.end_time),
443 EventPayload::LoosePossession(event.clone()),
444 event.player_id.clone(),
445 None,
446 None,
447 None,
448 None,
449 None,
450 ));
451 }
452
453 for (index, event) in player_possession.events().iter().enumerate() {
454 events.push(make_event(
455 "player_possession",
456 index,
457 span(
458 event.start_frame,
459 event.end_frame,
460 event.start_time,
461 event.end_time,
462 ),
463 EventPayload::PlayerPossession(event.clone()),
464 Some(event.player_id.clone()),
465 None,
466 Some(event.is_team_0),
467 None,
468 None,
469 None,
470 ));
471 }
472
473 for (index, event) in ball_half.events().iter().enumerate() {
474 events.push(make_event(
475 "ball_half",
476 index,
477 span(event.frame, event.end_frame, event.time, event.end_time),
478 EventPayload::BallHalf(event.clone()),
479 None,
480 None,
481 None,
482 None,
483 None,
484 None,
485 ));
486 }
487
488 for (index, event) in ball_third.events().iter().enumerate() {
489 events.push(make_event(
490 "ball_third",
491 index,
492 span(event.frame, event.end_frame, event.time, event.end_time),
493 EventPayload::BallThird(event.clone()),
494 None,
495 None,
496 None,
497 None,
498 None,
499 None,
500 ));
501 }
502
503 for (index, event) in territorial_pressure.events().iter().enumerate() {
504 events.push(make_event(
505 "territorial_pressure",
506 index,
507 span(
508 event.start_frame,
509 event.end_frame,
510 event.start_time,
511 event.end_time,
512 ),
513 EventPayload::TerritorialPressure(event.clone()),
514 None,
515 None,
516 Some(event.team_is_team_0),
517 None,
518 None,
519 None,
520 ));
521 }
522
523 for (index, event) in movement.events().iter().enumerate() {
524 events.push(make_event(
525 "movement",
526 index,
527 span(event.frame, event.end_frame, event.time, event.end_time),
528 EventPayload::Movement(event.clone()),
529 Some(event.player.clone()),
530 None,
531 Some(event.is_team_0),
532 event.player_position,
533 None,
534 None,
535 ));
536 }
537
538 for (index, event) in positioning.activity_events().iter().enumerate() {
539 events.push(make_event(
540 "player_activity",
541 index,
542 span(event.frame, event.end_frame, event.time, event.end_time),
543 EventPayload::PlayerActivity(event.clone()),
544 Some(event.player.clone()),
545 None,
546 Some(event.is_team_0),
547 event.player_position,
548 None,
549 None,
550 ));
551 }
552
553 for (index, event) in positioning.field_third_events().iter().enumerate() {
554 events.push(make_event(
555 "field_third",
556 index,
557 span(event.frame, event.end_frame, event.time, event.end_time),
558 EventPayload::FieldThird(event.clone()),
559 Some(event.player.clone()),
560 None,
561 Some(event.is_team_0),
562 event.player_position,
563 None,
564 None,
565 ));
566 }
567
568 for (index, event) in positioning.field_half_events().iter().enumerate() {
569 events.push(make_event(
570 "field_half",
571 index,
572 span(event.frame, event.end_frame, event.time, event.end_time),
573 EventPayload::FieldHalf(event.clone()),
574 Some(event.player.clone()),
575 None,
576 Some(event.is_team_0),
577 event.player_position,
578 None,
579 None,
580 ));
581 }
582
583 for (index, event) in positioning.ball_depth_events().iter().enumerate() {
584 events.push(make_event(
585 "ball_depth",
586 index,
587 span(event.frame, event.end_frame, event.time, event.end_time),
588 EventPayload::BallDepth(event.clone()),
589 Some(event.player.clone()),
590 None,
591 Some(event.is_team_0),
592 event.player_position,
593 None,
594 None,
595 ));
596 }
597
598 for (index, event) in positioning.depth_role_events().iter().enumerate() {
599 events.push(make_event(
600 "depth_role",
601 index,
602 span(event.frame, event.end_frame, event.time, event.end_time),
603 EventPayload::DepthRole(event.clone()),
604 Some(event.player.clone()),
605 None,
606 Some(event.is_team_0),
607 event.player_position,
608 None,
609 None,
610 ));
611 }
612
613 for (index, event) in positioning.ball_proximity_events().iter().enumerate() {
614 events.push(make_event(
615 "ball_proximity",
616 index,
617 span(event.frame, event.end_frame, event.time, event.end_time),
618 EventPayload::BallProximity(event.clone()),
619 Some(event.player.clone()),
620 None,
621 Some(event.is_team_0),
622 event.player_position,
623 None,
624 None,
625 ));
626 }
627
628 for (index, event) in positioning.shadow_defense_events().iter().enumerate() {
629 events.push(make_event(
630 "shadow_defense",
631 index,
632 span(event.frame, event.end_frame, event.time, event.end_time),
633 EventPayload::ShadowDefense(event.clone()),
634 Some(event.player.clone()),
635 None,
636 Some(event.is_team_0),
637 event.player_position,
638 None,
639 None,
640 ));
641 }
642
643 for (index, event) in rotation.role_events().iter().enumerate() {
644 events.push(make_event(
645 "rotation_role",
646 index,
647 span(event.frame, event.end_frame, event.time, event.end_time),
648 EventPayload::RotationRole(event.clone()),
649 Some(event.player.clone()),
650 None,
651 Some(event.is_team_0),
652 event.player_position,
653 None,
654 None,
655 ));
656 }
657
658 for (index, event) in rotation.first_man_change_events().iter().enumerate() {
659 events.push(make_event(
660 "first_man_change",
661 index,
662 moment(event.frame, event.time),
663 EventPayload::FirstManChange(event.clone()),
664 Some(event.next_first_man.clone()),
665 Some(event.previous_first_man.clone()),
666 Some(event.is_team_0),
667 None,
668 None,
669 None,
670 ));
671 }
672
673 for (index, event) in goal_context.iter().enumerate() {
674 events.push(make_event(
675 "goal_context",
676 index,
677 moment(event.frame, event.time),
678 EventPayload::GoalContext(event.clone()),
679 event.scorer.clone(),
680 None,
681 Some(event.scoring_team_is_team_0),
682 None,
683 event
684 .ball_position
685 .map(|position| [position.x, position.y, position.z]),
686 None,
687 ));
688 }
689
690 for (index, event) in backboard.events().iter().enumerate() {
691 events.push(make_event(
692 "backboard",
693 index,
694 moment(event.frame, event.time),
695 EventPayload::Backboard(event.clone()),
696 Some(event.player.clone()),
697 None,
698 Some(event.is_team_0),
699 event.player_position,
700 None,
701 None,
702 ));
703 }
704
705 for (index, event) in ball_carry.carry_events().iter().enumerate() {
706 events.push(make_event(
707 MECHANIC_BALL_CARRY,
708 index,
709 span(
710 event.start_frame,
711 event.end_frame,
712 event.start_time,
713 event.end_time,
714 ),
715 EventPayload::BallCarry(event.clone()),
716 Some(event.player_id.clone()),
717 None,
718 Some(event.is_team_0),
719 Some(event.end_position),
720 Some(event.end_position),
721 None,
722 ));
723 }
724
725 for (index, event) in air_dribble.events().iter().enumerate() {
726 events.push(make_event(
727 MECHANIC_AIR_DRIBBLE,
728 index,
729 span(
730 event.start_frame,
731 event.end_frame,
732 event.start_time,
733 event.end_time,
734 ),
735 EventPayload::BallCarry(event.clone()),
736 Some(event.player_id.clone()),
737 None,
738 Some(event.is_team_0),
739 Some(event.end_position),
740 Some(event.end_position),
741 None,
742 ));
743 }
744
745 for (index, event) in ceiling_shot.events().iter().enumerate() {
746 events.push(make_event(
747 MECHANIC_CEILING_SHOT,
748 index,
749 span(
750 event.ceiling_contact_frame,
751 event.frame,
752 event.ceiling_contact_time,
753 event.time,
754 ),
755 EventPayload::CeilingShot(event.clone()),
756 Some(event.player.clone()),
757 None,
758 Some(event.is_team_0),
759 event.player_position,
760 Some(event.touch_position),
761 Some(event.confidence),
762 ));
763 }
764
765 for (index, event) in wall_aerial.events().iter().enumerate() {
766 events.push(make_event(
767 MECHANIC_WALL_AERIAL,
768 index,
769 span(
770 event.wall_contact_frame,
771 event.frame,
772 event.wall_contact_time,
773 event.time,
774 ),
775 EventPayload::WallAerial(event.clone()),
776 Some(event.player.clone()),
777 None,
778 Some(event.is_team_0),
779 Some(event.player_position),
780 Some(event.ball_position),
781 Some(event.confidence),
782 ));
783 }
784
785 for (index, event) in wall_aerial_shot.events().iter().enumerate() {
786 events.push(make_event(
787 MECHANIC_WALL_AERIAL_SHOT,
788 index,
789 span(
790 event.takeoff_frame,
791 event.frame,
792 event.takeoff_time,
793 event.time,
794 ),
795 EventPayload::WallAerialShot(event.clone()),
796 Some(event.player.clone()),
797 None,
798 Some(event.is_team_0),
799 Some(event.player_position),
800 Some(event.ball_position),
801 Some(event.confidence),
802 ));
803 }
804
805 for (index, event) in center.events().iter().enumerate() {
806 events.push(make_event(
807 MECHANIC_CENTER,
808 index,
809 span(event.start_frame, event.frame, event.start_time, event.time),
810 EventPayload::Center(event.clone()),
811 Some(event.player.clone()),
812 None,
813 Some(event.is_team_0),
814 event.player_position,
815 Some(event.end_ball_position),
816 None,
817 ));
818 }
819
820 for (index, event) in dodge_reset.events().iter().enumerate() {
821 events.push(make_event(
822 "dodge_reset",
823 index,
824 moment(event.frame, event.time),
825 EventPayload::DodgeReset(event.clone()),
826 Some(event.player.clone()),
827 None,
828 Some(event.is_team_0),
829 event.player_position,
830 None,
831 None,
832 ));
833 }
834
835 for (index, event) in dodge_reset.confirmed_flip_reset_events().iter().enumerate() {
836 events.push(make_event(
837 MECHANIC_FLIP_RESET,
838 index,
839 span(event.reset_frame, event.frame, event.reset_time, event.time),
840 EventPayload::FlipReset(event.clone()),
841 Some(event.player.clone()),
842 None,
843 Some(event.is_team_0),
844 event.player_position,
845 None,
846 None,
847 ));
848 }
849
850 for (index, event) in double_tap.events().iter().enumerate() {
851 events.push(make_event(
852 MECHANIC_DOUBLE_TAP,
853 index,
854 span(
855 event.backboard_frame,
856 event.frame,
857 event.backboard_time,
858 event.time,
859 ),
860 EventPayload::DoubleTap(event.clone()),
861 Some(event.player.clone()),
862 None,
863 Some(event.is_team_0),
864 event.player_position,
865 None,
866 None,
867 ));
868 }
869
870 for (index, event) in one_timer.events().iter().enumerate() {
871 events.push(make_event(
872 MECHANIC_ONE_TIMER,
873 index,
874 span(
875 event.pass_start_frame,
876 event.frame,
877 event.pass_start_time,
878 event.time,
879 ),
880 EventPayload::OneTimer(event.clone()),
881 Some(event.player.clone()),
882 Some(event.passer.clone()),
883 Some(event.is_team_0),
884 event.player_position,
885 None,
886 None,
887 ));
888 }
889
890 for (index, event) in pass.events().iter().enumerate() {
891 events.push(make_event(
892 MECHANIC_PASS,
893 index,
894 span(event.start_frame, event.frame, event.start_time, event.time),
895 EventPayload::Pass(event.clone()),
896 Some(event.passer.clone()),
897 Some(event.receiver.clone()),
898 Some(event.is_team_0),
899 event.passer_position,
900 None,
901 None,
902 ));
903 }
904
905 for (index, event) in controlled_play.events().iter().enumerate() {
906 events.push(make_event(
907 "controlled_play",
908 index,
909 span(
910 event.start_frame,
911 event.end_frame,
912 event.start_time,
913 event.end_time,
914 ),
915 EventPayload::ControlledPlay(event.clone()),
916 Some(event.player_id.clone()),
917 None,
918 Some(event.is_team_0),
919 None,
920 None,
921 None,
922 ));
923 }
924
925 for (index, event) in fifty_fifty.events().iter().enumerate() {
926 events.push(make_event(
927 "fifty_fifty",
928 index,
929 span(
930 event.start_frame,
931 event.resolve_frame,
932 event.start_time,
933 event.resolve_time,
934 ),
935 EventPayload::FiftyFifty(event.clone()),
936 event
937 .team_zero_player
938 .clone()
939 .or_else(|| event.team_one_player.clone()),
940 None,
941 event.winning_team_is_team_0,
942 None,
943 Some(event.midpoint),
944 None,
945 ));
946 }
947
948 for (index, event) in kickoff.events().iter().enumerate() {
949 events.push(make_event(
950 "kickoff",
951 index,
952 span(
953 event.start_frame,
954 event.end_frame,
955 event.start_time,
956 event.end_time,
957 ),
958 EventPayload::Kickoff(Box::new(event.clone())),
959 event.first_touch_player.clone(),
960 None,
961 event.first_touch_team_is_team_0,
962 None,
963 None,
964 None,
965 ));
966 }
967
968 for (index, event) in rush.events().iter().enumerate() {
969 events.push(make_event(
970 "rush",
971 index,
972 span(
973 event.start_frame,
974 event.end_frame,
975 event.start_time,
976 event.end_time,
977 ),
978 EventPayload::Rush(event.clone()),
979 None,
980 None,
981 Some(event.is_team_0),
982 None,
983 None,
984 None,
985 ));
986 }
987
988 for (index, event) in flip_impulse.events().iter().enumerate() {
989 events.push(make_event(
990 "dodge",
991 index,
992 span(
993 event.frame,
994 event.resolved_frame,
995 event.time,
996 event.resolved_time,
997 ),
998 EventPayload::Dodge(event.clone()),
999 Some(event.player.clone()),
1000 None,
1001 Some(event.is_team_0),
1002 event
1003 .dodge_impulse
1004 .as_ref()
1005 .map(|dodge_impulse| dodge_impulse.end_position),
1006 None,
1007 event
1008 .dodge_impulse
1009 .as_ref()
1010 .map(|dodge_impulse| dodge_impulse.confidence),
1011 ));
1012 }
1013
1014 for (index, event) in speed_flip.events().iter().enumerate() {
1015 events.push(make_event(
1016 MECHANIC_SPEED_FLIP,
1017 index,
1018 span(
1019 event.frame,
1020 event.resolved_frame,
1021 event.time,
1022 event.resolved_time,
1023 ),
1024 EventPayload::SpeedFlip(event.clone()),
1025 Some(event.player.clone()),
1026 None,
1027 Some(event.is_team_0),
1028 Some(event.end_position),
1029 None,
1030 Some(event.confidence),
1031 ));
1032 }
1033
1034 for (index, event) in half_flip.events().iter().enumerate() {
1035 events.push(make_event(
1036 MECHANIC_HALF_FLIP,
1037 index,
1038 moment(event.frame, event.time),
1039 EventPayload::HalfFlip(event.clone()),
1040 Some(event.player.clone()),
1041 None,
1042 Some(event.is_team_0),
1043 Some(event.end_position),
1044 None,
1045 Some(event.confidence),
1046 ));
1047 }
1048
1049 for (index, event) in half_volley.events().iter().enumerate() {
1050 events.push(make_event(
1051 MECHANIC_HALF_VOLLEY,
1052 index,
1053 moment(event.frame, event.time),
1054 EventPayload::HalfVolley(event.clone()),
1055 Some(event.player.clone()),
1056 None,
1057 Some(event.is_team_0),
1058 event.player_position,
1059 None,
1060 None,
1061 ));
1062 }
1063
1064 for (index, event) in wavedash.events().iter().enumerate() {
1065 events.push(make_event(
1066 MECHANIC_WAVEDASH,
1067 index,
1068 span(event.dodge_frame, event.frame, event.dodge_time, event.time),
1069 EventPayload::Wavedash(event.clone()),
1070 Some(event.player.clone()),
1071 None,
1072 Some(event.is_team_0),
1073 Some(event.landing_position),
1074 None,
1075 Some(event.confidence),
1076 ));
1077 }
1078
1079 for (index, event) in whiff.events().iter().enumerate() {
1080 events.push(make_event(
1081 "whiff",
1082 index,
1083 span(
1084 event.frame,
1085 event.resolved_frame,
1086 event.time,
1087 event.resolved_time,
1088 ),
1089 EventPayload::Whiff(event.clone()),
1090 Some(event.player.clone()),
1091 None,
1092 Some(event.is_team_0),
1093 event.player_position,
1094 None,
1095 None,
1096 ));
1097 }
1098
1099 for (index, event) in powerslide.events().iter().enumerate() {
1100 events.push(make_event(
1101 "powerslide",
1102 index,
1103 moment(event.frame, event.time),
1104 EventPayload::Powerslide(event.clone()),
1105 Some(event.player.clone()),
1106 None,
1107 Some(event.is_team_0),
1108 event.player_position,
1109 None,
1110 None,
1111 ));
1112 }
1113
1114 for (index, event) in touch.events().iter().enumerate() {
1115 let timing =
1116 event
1117 .ball_movement
1118 .as_ref()
1119 .map_or(moment(event.frame, event.time), |movement| {
1120 span(
1121 movement.start_frame,
1122 movement.end_frame,
1123 movement.start_time,
1124 movement.end_time,
1125 )
1126 });
1127 events.push(make_event(
1128 "touch",
1129 index,
1130 timing,
1131 EventPayload::Touch(event.clone()),
1132 Some(event.player.clone()),
1133 None,
1134 Some(event.is_team_0),
1135 event.player_position,
1136 None,
1137 None,
1138 ));
1139 }
1140
1141 for (index, event) in boost.pickup_events().iter().enumerate() {
1142 events.push(make_event(
1143 "boost_pickups",
1144 index,
1145 moment(event.frame, event.time),
1146 EventPayload::BoostPickup(event.clone()),
1147 Some(event.player_id.clone()),
1148 None,
1149 Some(event.is_team_0),
1150 event.player_position,
1151 None,
1152 None,
1153 ));
1154 }
1155
1156 for (index, event) in boost.respawn_events().iter().enumerate() {
1157 events.push(make_event(
1158 "boost_respawn",
1159 index,
1160 moment(event.frame, event.time),
1161 EventPayload::Respawn(event.clone()),
1162 Some(event.player_id.clone()),
1163 None,
1164 Some(event.is_team_0),
1165 event.player_position,
1166 None,
1167 None,
1168 ));
1169 }
1170
1171 for (index, event) in bump.events().iter().enumerate() {
1172 events.push(make_event(
1173 "bump",
1174 index,
1175 moment(event.frame, event.time),
1176 EventPayload::Bump(event.clone()),
1177 Some(event.initiator.clone()),
1178 Some(event.victim.clone()),
1179 Some(event.initiator_is_team_0),
1180 Some(event.initiator_position),
1181 None,
1182 Some(event.confidence),
1183 ));
1184 }
1185
1186 for (index, event) in demo.events().iter().enumerate() {
1187 events.push(make_event(
1188 "demolition",
1189 index,
1190 moment(event.frame, event.time),
1191 EventPayload::Demolition(event.clone()),
1192 Some(event.attacker.clone()),
1193 Some(event.victim.clone()),
1194 event.attacker_is_team_0,
1195 event.attacker_position,
1196 None,
1197 None,
1198 ));
1199 }
1200
1201 for (index, event) in flick.events().iter().enumerate() {
1202 events.push(make_event(
1203 MECHANIC_FLICK,
1204 index,
1205 span(
1206 event.setup_start_frame,
1207 event.frame,
1208 event.setup_start_time,
1209 event.time,
1210 ),
1211 EventPayload::Flick(event.clone()),
1212 Some(event.player.clone()),
1213 None,
1214 Some(event.is_team_0),
1215 event.player_position,
1216 None,
1217 Some(event.confidence),
1218 ));
1219 }
1220
1221 events.sort_by(|left, right| {
1222 event_start_time(left)
1223 .total_cmp(&event_start_time(right))
1224 .then_with(|| left.meta.stream.cmp(&right.meta.stream))
1225 .then_with(|| left.meta.id.cmp(&right.meta.id))
1226 });
1227 events
1228}