Skip to main content

subtr_actor/stats/analysis_graph/nodes/
stats_timeline_events.rs

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        dodge_reset_dependency(),
33        double_tap_dependency(),
34        one_timer_dependency(),
35        pass_dependency(),
36        fifty_fifty_dependency(),
37        flick_dependency(),
38        musty_flick_dependency(),
39        aerial_goal_dependency(),
40        high_aerial_goal_dependency(),
41        long_distance_goal_dependency(),
42        own_half_goal_dependency(),
43        empty_net_goal_dependency(),
44        flick_goal_dependency(),
45        one_timer_goal_dependency(),
46        air_dribble_goal_dependency(),
47        flip_reset_goal_dependency(),
48        rush_dependency(),
49        speed_flip_dependency(),
50        half_flip_dependency(),
51        wavedash_dependency(),
52        whiff_dependency(),
53        boost_dependency(),
54    ],
55    inputs = {
56        match_stats: MatchStatsCalculator,
57        demo: DemoCalculator,
58        backboard: BackboardCalculator,
59        ball_carry: BallCarryCalculator,
60        ceiling_shot: CeilingShotCalculator,
61        dodge_reset: DodgeResetCalculator,
62        double_tap: DoubleTapCalculator,
63        one_timer: OneTimerCalculator,
64        pass: PassCalculator,
65        fifty_fifty: FiftyFiftyCalculator,
66        flick: FlickCalculator,
67        musty_flick: MustyFlickCalculator,
68        aerial_goal: AerialGoalCalculator,
69        high_aerial_goal: HighAerialGoalCalculator,
70        long_distance_goal: LongDistanceGoalCalculator,
71        own_half_goal: OwnHalfGoalCalculator,
72        empty_net_goal: EmptyNetGoalCalculator,
73        flick_goal: FlickGoalCalculator,
74        one_timer_goal: OneTimerGoalCalculator,
75        air_dribble_goal: AirDribbleGoalCalculator,
76        flip_reset_goal: FlipResetGoalCalculator,
77        rush: RushCalculator,
78        speed_flip: SpeedFlipCalculator,
79        half_flip: HalfFlipCalculator,
80        wavedash: WavedashCalculator,
81        whiff: WhiffCalculator,
82        boost: BoostCalculator,
83    },
84    evaluate = |node| {
85        let mut timeline = match_stats.timeline().to_vec();
86        timeline.extend(demo.timeline().to_vec());
87        timeline.sort_by(|left, right| left.time.total_cmp(&right.time));
88        let goal_tags = combined_goal_tag_events(&[
89            aerial_goal.events(),
90            high_aerial_goal.events(),
91            long_distance_goal.events(),
92            own_half_goal.events(),
93            empty_net_goal.events(),
94            flick_goal.events(),
95            one_timer_goal.events(),
96            air_dribble_goal.events(),
97            flip_reset_goal.events(),
98        ]);
99
100        node.state.events = ReplayStatsTimelineEvents {
101            timeline,
102            mechanics: build_mechanic_events(
103                ball_carry,
104                ceiling_shot,
105                dodge_reset,
106                double_tap,
107                flick,
108                musty_flick,
109                one_timer,
110                pass,
111                speed_flip,
112                half_flip,
113                wavedash,
114            ),
115            goal_context: match_stats.goal_context_events().to_vec(),
116            backboard: backboard.events().to_vec(),
117            ceiling_shot: ceiling_shot.events().to_vec(),
118            double_tap: double_tap.events().to_vec(),
119            one_timer: one_timer.events().to_vec(),
120            pass: pass.events().to_vec(),
121            fifty_fifty: fifty_fifty.events().to_vec(),
122            goal_tags,
123            rush: rush.events().to_vec(),
124            speed_flip: speed_flip.events().to_vec(),
125            half_flip: half_flip.events().to_vec(),
126            wavedash: wavedash.events().to_vec(),
127            whiff: whiff.events().to_vec(),
128            boost_pickups: boost.pickup_comparison_events().to_vec(),
129        };
130        Ok(())
131    },
132    state_ref = |node| &node.state,
133}
134
135fn moment_mechanic_event(
136    kind: &str,
137    index: usize,
138    frame: usize,
139    time: f32,
140    player_id: PlayerId,
141    is_team_0: bool,
142) -> MechanicEvent {
143    MechanicEvent {
144        id: format!("{kind}:{frame}:{index}"),
145        kind: kind.to_owned(),
146        player_id,
147        is_team_0,
148        timing: MechanicTiming::Moment { frame, time },
149    }
150}
151
152#[allow(clippy::too_many_arguments)]
153fn span_mechanic_event(
154    kind: &str,
155    index: usize,
156    start_frame: usize,
157    end_frame: usize,
158    start_time: f32,
159    end_time: f32,
160    player_id: PlayerId,
161    is_team_0: bool,
162) -> MechanicEvent {
163    MechanicEvent {
164        id: format!("{kind}:{start_frame}:{end_frame}:{index}"),
165        kind: kind.to_owned(),
166        player_id,
167        is_team_0,
168        timing: MechanicTiming::Span {
169            start_frame,
170            end_frame,
171            start_time,
172            end_time,
173        },
174    }
175}
176
177#[allow(clippy::too_many_arguments)]
178fn build_mechanic_events(
179    ball_carry: &BallCarryCalculator,
180    ceiling_shot: &CeilingShotCalculator,
181    dodge_reset: &DodgeResetCalculator,
182    double_tap: &DoubleTapCalculator,
183    flick: &FlickCalculator,
184    musty_flick: &MustyFlickCalculator,
185    one_timer: &OneTimerCalculator,
186    pass: &PassCalculator,
187    speed_flip: &SpeedFlipCalculator,
188    half_flip: &HalfFlipCalculator,
189    wavedash: &WavedashCalculator,
190) -> Vec<MechanicEvent> {
191    let mut events = Vec::new();
192
193    for (index, event) in ball_carry.carry_events().iter().enumerate() {
194        let kind = match event.kind {
195            BallCarryKind::Carry => "ball_carry",
196            BallCarryKind::AirDribble => "air_dribble",
197        };
198        events.push(span_mechanic_event(
199            kind,
200            index,
201            event.start_frame,
202            event.end_frame,
203            event.start_time,
204            event.end_time,
205            event.player_id.clone(),
206            event.is_team_0,
207        ));
208    }
209
210    for (index, event) in ceiling_shot.events().iter().enumerate() {
211        events.push(span_mechanic_event(
212            "ceiling_shot",
213            index,
214            event.ceiling_contact_frame,
215            event.frame,
216            event.ceiling_contact_time,
217            event.time,
218            event.player.clone(),
219            event.is_team_0,
220        ));
221    }
222
223    for (index, event) in dodge_reset.on_ball_events().iter().enumerate() {
224        events.push(moment_mechanic_event(
225            "flip_reset",
226            index,
227            event.frame,
228            event.time,
229            event.player.clone(),
230            event.is_team_0,
231        ));
232    }
233
234    for (index, event) in double_tap.events().iter().enumerate() {
235        events.push(span_mechanic_event(
236            "double_tap",
237            index,
238            event.backboard_frame,
239            event.frame,
240            event.backboard_time,
241            event.time,
242            event.player.clone(),
243            event.is_team_0,
244        ));
245    }
246
247    for (index, event) in flick.events().iter().enumerate() {
248        events.push(span_mechanic_event(
249            "flick",
250            index,
251            event.setup_start_frame,
252            event.frame,
253            event.setup_start_time,
254            event.time,
255            event.player.clone(),
256            event.is_team_0,
257        ));
258    }
259
260    for (index, event) in musty_flick.events().iter().enumerate() {
261        events.push(span_mechanic_event(
262            "musty_flick",
263            index,
264            event.dodge_frame,
265            event.frame,
266            event.dodge_time,
267            event.time,
268            event.player.clone(),
269            event.is_team_0,
270        ));
271    }
272
273    for (index, event) in one_timer.events().iter().enumerate() {
274        events.push(span_mechanic_event(
275            "one_timer",
276            index,
277            event.pass_start_frame,
278            event.frame,
279            event.pass_start_time,
280            event.time,
281            event.player.clone(),
282            event.is_team_0,
283        ));
284    }
285
286    for (index, event) in pass.events().iter().enumerate() {
287        events.push(span_mechanic_event(
288            "pass",
289            index,
290            event.start_frame,
291            event.frame,
292            event.start_time,
293            event.time,
294            event.passer.clone(),
295            event.is_team_0,
296        ));
297    }
298
299    for (index, event) in speed_flip.events().iter().enumerate() {
300        events.push(moment_mechanic_event(
301            "speed_flip",
302            index,
303            event.frame,
304            event.time,
305            event.player.clone(),
306            event.is_team_0,
307        ));
308    }
309
310    for (index, event) in half_flip.events().iter().enumerate() {
311        events.push(moment_mechanic_event(
312            "half_flip",
313            index,
314            event.frame,
315            event.time,
316            event.player.clone(),
317            event.is_team_0,
318        ));
319    }
320
321    for (index, event) in wavedash.events().iter().enumerate() {
322        events.push(span_mechanic_event(
323            "wavedash",
324            index,
325            event.dodge_frame,
326            event.frame,
327            event.dodge_time,
328            event.time,
329            event.player.clone(),
330            event.is_team_0,
331        ));
332    }
333
334    events.sort_by(|left, right| {
335        let left_time = mechanic_event_start_time(left);
336        let right_time = mechanic_event_start_time(right);
337        left_time
338            .total_cmp(&right_time)
339            .then_with(|| left.kind.cmp(&right.kind))
340            .then_with(|| left.id.cmp(&right.id))
341    });
342    events
343}
344
345fn mechanic_event_start_time(event: &MechanicEvent) -> f32 {
346    match event.timing {
347        MechanicTiming::Moment { time, .. } => time,
348        MechanicTiming::Span { start_time, .. } => start_time,
349    }
350}