subtr_actor/stats/calculators/
powerslide.rs1use super::*;
2
3#[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#[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}