subtr_actor/stats/calculators/
player_vertical_state.rs1use super::*;
2
3pub const PLAYER_GROUND_Z_THRESHOLD: f32 = 20.0;
4pub const PLAYER_HIGH_AIR_Z_THRESHOLD: f32 = 642.775 + BALL_RADIUS_Z;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PlayerVerticalBand {
9 Ground,
10 LowAir,
11 HighAir,
12}
13
14pub const ALL_PLAYER_VERTICAL_BANDS: [PlayerVerticalBand; 3] = [
15 PlayerVerticalBand::Ground,
16 PlayerVerticalBand::LowAir,
17 PlayerVerticalBand::HighAir,
18];
19
20impl PlayerVerticalBand {
21 pub fn from_height(height: f32) -> Self {
22 if height <= PLAYER_GROUND_Z_THRESHOLD {
23 Self::Ground
24 } else if height >= PLAYER_HIGH_AIR_Z_THRESHOLD {
25 Self::HighAir
26 } else {
27 Self::LowAir
28 }
29 }
30
31 pub fn as_label(self) -> StatLabel {
32 let value = match self {
33 Self::Ground => "ground",
34 Self::LowAir => "low_air",
35 Self::HighAir => "high_air",
36 };
37 StatLabel::new("height_band", value)
38 }
39
40 pub fn is_grounded(self) -> bool {
41 matches!(self, Self::Ground)
42 }
43
44 pub fn is_airborne(self) -> bool {
45 !self.is_grounded()
46 }
47
48 pub fn is_high_air(self) -> bool {
49 matches!(self, Self::HighAir)
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq)]
55pub struct PlayerVerticalSample {
56 pub height: f32,
57 pub band: PlayerVerticalBand,
58}
59
60impl PlayerVerticalSample {
61 pub fn from_height(height: f32) -> Self {
62 Self {
63 height,
64 band: PlayerVerticalBand::from_height(height),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default)]
71pub struct PlayerVerticalState {
72 pub players: HashMap<PlayerId, PlayerVerticalSample>,
73}
74
75impl PlayerVerticalState {
76 pub fn sample(&self, player_id: &PlayerId) -> Option<&PlayerVerticalSample> {
77 self.players.get(player_id)
78 }
79
80 pub fn band_for_player(&self, player_id: &PlayerId) -> Option<PlayerVerticalBand> {
81 self.sample(player_id).map(|sample| sample.band)
82 }
83
84 pub fn is_grounded(&self, player_id: &PlayerId) -> bool {
85 self.band_for_player(player_id)
86 .is_some_and(PlayerVerticalBand::is_grounded)
87 }
88}
89
90#[derive(Default)]
92pub struct PlayerVerticalStateCalculator;
93
94impl PlayerVerticalStateCalculator {
95 pub fn new() -> Self {
96 Self
97 }
98
99 pub fn update(&mut self, players: &PlayerFrameState) -> PlayerVerticalState {
100 let players = players
101 .players
102 .iter()
103 .filter_map(|player| {
104 let height = player.position()?.z;
105 Some((
106 player.player_id.clone(),
107 PlayerVerticalSample::from_height(height),
108 ))
109 })
110 .collect();
111
112 PlayerVerticalState { players }
113 }
114}