Skip to main content

subtr_actor/stats/calculators/
powerslide.rs

1use super::*;
2
3/// A state-change event for effective grounded powerslide use.
4#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
5#[ts(export)]
6pub struct PowerslideEvent {
7    pub time: f32,
8    pub frame: usize,
9    #[ts(as = "crate::interop::ts_bindings::RemoteIdTs")]
10    pub player: PlayerId,
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub player_position: Option<[f32; 3]>,
13    pub is_team_0: bool,
14    pub active: bool,
15}
16
17/// Detects powerslide usage from player frame state.
18#[derive(Debug, Clone, Default)]
19pub struct PowerslideCalculator {
20    last_active: HashMap<PlayerId, bool>,
21    events: EventStream<PowerslideEvent>,
22}
23
24impl PowerslideCalculator {
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    pub fn events(&self) -> &[PowerslideEvent] {
30        self.events.all()
31    }
32
33    pub fn new_events(&self) -> &[PowerslideEvent] {
34        self.events.new_events()
35    }
36
37    fn is_effective_powerslide(player: &PlayerSample) -> bool {
38        player.powerslide_active
39            && player
40                .position()
41                .map(|position| position.z <= POWERSLIDE_MAX_Z_THRESHOLD)
42                .unwrap_or(false)
43    }
44
45    pub fn update(
46        &mut self,
47        frame: &FrameInfo,
48        players: &PlayerFrameState,
49        _live_play_state: &LivePlayState,
50    ) -> SubtrActorResult<()> {
51        self.events.begin_update();
52        for player in &players.players {
53            let effective_powerslide = Self::is_effective_powerslide(player);
54            let previous_active = self
55                .last_active
56                .get(&player.player_id)
57                .copied()
58                .unwrap_or(false);
59
60            if effective_powerslide != previous_active {
61                self.events.push(PowerslideEvent {
62                    time: frame.time,
63                    frame: frame.frame_number,
64                    player: player.player_id.clone(),
65                    player_position: player.position().map(|position| position.to_array()),
66                    is_team_0: player.is_team_0,
67                    active: effective_powerslide,
68                });
69            }
70
71            self.last_active
72                .insert(player.player_id.clone(), effective_powerslide);
73        }
74        Ok(())
75    }
76}