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                whiff,
115            ),
116            goal_context: match_stats.goal_context_events().to_vec(),
117            backboard: backboard.events().to_vec(),
118            ceiling_shot: ceiling_shot.events().to_vec(),
119            double_tap: double_tap.events().to_vec(),
120            one_timer: one_timer.events().to_vec(),
121            pass: pass.events().to_vec(),
122            fifty_fifty: fifty_fifty.events().to_vec(),
123            goal_tags,
124            rush: rush.events().to_vec(),
125            speed_flip: speed_flip.events().to_vec(),
126            half_flip: half_flip.events().to_vec(),
127            wavedash: wavedash.events().to_vec(),
128            whiff: whiff.events().to_vec(),
129            boost_pickups: boost.pickup_comparison_events().to_vec(),
130        };
131        Ok(())
132    },
133    state_ref = |node| &node.state,
134}
135
136fn moment_mechanic_event(
137    kind: &str,
138    index: usize,
139    frame: usize,
140    time: f32,
141    player_id: PlayerId,
142    is_team_0: bool,
143) -> MechanicEvent {
144    MechanicEvent {
145        id: format!("{kind}:{frame}:{index}"),
146        kind: kind.to_owned(),
147        player_id,
148        is_team_0,
149        timing: MechanicTiming::Moment { frame, time },
150    }
151}
152
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    whiff: &WhiffCalculator,
191) -> Vec<MechanicEvent> {
192    let mut events = Vec::new();
193
194    for (index, event) in ball_carry.carry_events().iter().enumerate() {
195        let kind = match event.kind {
196            BallCarryKind::Carry => "ball_carry",
197            BallCarryKind::AirDribble => "air_dribble",
198        };
199        events.push(span_mechanic_event(
200            kind,
201            index,
202            event.start_frame,
203            event.end_frame,
204            event.start_time,
205            event.end_time,
206            event.player_id.clone(),
207            event.is_team_0,
208        ));
209    }
210
211    for (index, event) in ceiling_shot.events().iter().enumerate() {
212        events.push(span_mechanic_event(
213            "ceiling_shot",
214            index,
215            event.ceiling_contact_frame,
216            event.frame,
217            event.ceiling_contact_time,
218            event.time,
219            event.player.clone(),
220            event.is_team_0,
221        ));
222    }
223
224    for (index, event) in dodge_reset.on_ball_events().iter().enumerate() {
225        events.push(moment_mechanic_event(
226            "flip_reset",
227            index,
228            event.frame,
229            event.time,
230            event.player.clone(),
231            event.is_team_0,
232        ));
233    }
234
235    for (index, event) in double_tap.events().iter().enumerate() {
236        events.push(span_mechanic_event(
237            "double_tap",
238            index,
239            event.backboard_frame,
240            event.frame,
241            event.backboard_time,
242            event.time,
243            event.player.clone(),
244            event.is_team_0,
245        ));
246    }
247
248    for (index, event) in flick.events().iter().enumerate() {
249        events.push(span_mechanic_event(
250            "flick",
251            index,
252            event.setup_start_frame,
253            event.frame,
254            event.setup_start_time,
255            event.time,
256            event.player.clone(),
257            event.is_team_0,
258        ));
259    }
260
261    for (index, event) in musty_flick.events().iter().enumerate() {
262        events.push(span_mechanic_event(
263            "musty_flick",
264            index,
265            event.dodge_frame,
266            event.frame,
267            event.dodge_time,
268            event.time,
269            event.player.clone(),
270            event.is_team_0,
271        ));
272    }
273
274    for (index, event) in one_timer.events().iter().enumerate() {
275        events.push(span_mechanic_event(
276            "one_timer",
277            index,
278            event.pass_start_frame,
279            event.frame,
280            event.pass_start_time,
281            event.time,
282            event.player.clone(),
283            event.is_team_0,
284        ));
285    }
286
287    for (index, event) in pass.events().iter().enumerate() {
288        events.push(span_mechanic_event(
289            "pass",
290            index,
291            event.start_frame,
292            event.frame,
293            event.start_time,
294            event.time,
295            event.passer.clone(),
296            event.is_team_0,
297        ));
298    }
299
300    for (index, event) in speed_flip.events().iter().enumerate() {
301        events.push(moment_mechanic_event(
302            "speed_flip",
303            index,
304            event.frame,
305            event.time,
306            event.player.clone(),
307            event.is_team_0,
308        ));
309    }
310
311    for (index, event) in half_flip.events().iter().enumerate() {
312        events.push(moment_mechanic_event(
313            "half_flip",
314            index,
315            event.frame,
316            event.time,
317            event.player.clone(),
318            event.is_team_0,
319        ));
320    }
321
322    for (index, event) in wavedash.events().iter().enumerate() {
323        events.push(span_mechanic_event(
324            "wavedash",
325            index,
326            event.dodge_frame,
327            event.frame,
328            event.dodge_time,
329            event.time,
330            event.player.clone(),
331            event.is_team_0,
332        ));
333    }
334
335    for (index, event) in whiff.events().iter().enumerate() {
336        events.push(moment_mechanic_event(
337            "whiff",
338            index,
339            event.frame,
340            event.time,
341            event.player.clone(),
342            event.is_team_0,
343        ));
344    }
345
346    events.sort_by(|left, right| {
347        let left_time = mechanic_event_start_time(left);
348        let right_time = mechanic_event_start_time(right);
349        left_time
350            .total_cmp(&right_time)
351            .then_with(|| left.kind.cmp(&right.kind))
352            .then_with(|| left.id.cmp(&right.id))
353    });
354    events
355}
356
357fn mechanic_event_start_time(event: &MechanicEvent) -> f32 {
358    match event.timing {
359        MechanicTiming::Moment { time, .. } => time,
360        MechanicTiming::Span { start_time, .. } => start_time,
361    }
362}