Skip to main content

supreme_replay_analyzer/
checkpoint_times.rs

1//! Checkpoint crossing time extraction
2
3use crate::Level;
4use crate::models::{AnalyzeResult, CheckpointTimesComputed};
5use crate::plane_intersection::{LevelPlaneIntersection, get_plane_intersections_with_level_data};
6
7#[must_use]
8pub fn extract_checkpoint_times(
9    analyze_result: &AnalyzeResult,
10    level_name: &str,
11    level_data: &[Level],
12) -> CheckpointTimesComputed {
13    let coords = match &analyze_result.coords {
14        Some(c) => c,
15        None => {
16            return CheckpointTimesComputed {
17                start_computed: None,
18                cp1_computed: None,
19                cp2_computed: None,
20                finish_computed: None,
21            };
22        }
23    };
24
25    // Get plane intersections
26    let level_plane_intersections =
27        get_plane_intersections_with_level_data(&coords.rows, level_data);
28
29    // Handle special case for Forest Medium/Hard ambiguity
30    if level_name == "ForestMediumOrHard" {
31        let forest_medium_intersection = level_plane_intersections
32            .iter()
33            .find(|lpc| lpc.name == "ForestMedium");
34
35        let (start_computed, cp1_computed, cp2_computed, finish_computed) = if let Some(lpc) =
36            forest_medium_intersection
37        {
38            (
39                get_first_valid_intersection_time(lpc, "Start_Point", analyze_result.lag_start_ms),
40                get_first_valid_intersection_time(
41                    lpc,
42                    "Check_Point_1",
43                    analyze_result.lag_start_ms,
44                ),
45                get_first_valid_intersection_time(
46                    lpc,
47                    "Check_Point_2",
48                    analyze_result.lag_start_ms,
49                ),
50                get_first_valid_intersection_time(lpc, "Finish_Point", analyze_result.lag_start_ms),
51            )
52        } else {
53            (None, None, None, None)
54        };
55
56        return CheckpointTimesComputed {
57            start_computed,
58            cp1_computed,
59            cp2_computed,
60            finish_computed,
61        };
62    }
63
64    // Find intersections for the specific level
65    let level_intersection = level_plane_intersections
66        .iter()
67        .find(|lpc| lpc.name == level_name);
68
69    let (start_computed, cp1_computed, cp2_computed, finish_computed) = if let Some(lpc) =
70        level_intersection
71    {
72        (
73            get_first_valid_intersection_time(lpc, "Start_Point", analyze_result.lag_start_ms),
74            get_first_valid_intersection_time(lpc, "Check_Point_1", analyze_result.lag_start_ms),
75            get_first_valid_intersection_time(lpc, "Check_Point_2", analyze_result.lag_start_ms),
76            get_first_valid_intersection_time(lpc, "Finish_Point", analyze_result.lag_start_ms),
77        )
78    } else {
79        (None, None, None, None)
80    };
81
82    CheckpointTimesComputed {
83        start_computed,
84        cp1_computed,
85        cp2_computed,
86        finish_computed,
87    }
88}
89
90fn get_first_valid_intersection_time(
91    lpc: &LevelPlaneIntersection,
92    object_name: &str,
93    start_lag_ms: f32, // Start lag in milliseconds to compensate for the countdown timer
94) -> Option<f32> {
95    const PLANE_RADIUS: f32 = 90.5;
96    lpc.intersections
97        .iter()
98        .filter(|c| c.object_name == object_name && c.distance <= PLANE_RADIUS)
99        .map(|c| c.exact_time_with_start_lag_ms - start_lag_ms) // Use the exact interpolated time
100        .next()
101}