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