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