subtr-actor 0.6.2

Rocket League replay transformer
Documentation
use super::*;

pub const PLAYER_GROUND_Z_THRESHOLD: f32 = 20.0;
pub const PLAYER_HIGH_AIR_Z_THRESHOLD: f32 = 642.775 + BALL_RADIUS_Z;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlayerVerticalBand {
    Ground,
    LowAir,
    HighAir,
}

pub const ALL_PLAYER_VERTICAL_BANDS: [PlayerVerticalBand; 3] = [
    PlayerVerticalBand::Ground,
    PlayerVerticalBand::LowAir,
    PlayerVerticalBand::HighAir,
];

impl PlayerVerticalBand {
    pub fn from_height(height: f32) -> Self {
        if height <= PLAYER_GROUND_Z_THRESHOLD {
            Self::Ground
        } else if height >= PLAYER_HIGH_AIR_Z_THRESHOLD {
            Self::HighAir
        } else {
            Self::LowAir
        }
    }

    pub fn as_label(self) -> StatLabel {
        let value = match self {
            Self::Ground => "ground",
            Self::LowAir => "low_air",
            Self::HighAir => "high_air",
        };
        StatLabel::new("height_band", value)
    }

    pub fn is_grounded(self) -> bool {
        matches!(self, Self::Ground)
    }

    pub fn is_airborne(self) -> bool {
        !self.is_grounded()
    }

    pub fn is_high_air(self) -> bool {
        matches!(self, Self::HighAir)
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PlayerVerticalSample {
    pub height: f32,
    pub band: PlayerVerticalBand,
}

impl PlayerVerticalSample {
    pub fn from_height(height: f32) -> Self {
        Self {
            height,
            band: PlayerVerticalBand::from_height(height),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct PlayerVerticalState {
    pub players: HashMap<PlayerId, PlayerVerticalSample>,
}

impl PlayerVerticalState {
    pub fn sample(&self, player_id: &PlayerId) -> Option<&PlayerVerticalSample> {
        self.players.get(player_id)
    }

    pub fn band_for_player(&self, player_id: &PlayerId) -> Option<PlayerVerticalBand> {
        self.sample(player_id).map(|sample| sample.band)
    }

    pub fn is_grounded(&self, player_id: &PlayerId) -> bool {
        self.band_for_player(player_id)
            .is_some_and(PlayerVerticalBand::is_grounded)
    }
}

#[derive(Default)]
pub struct PlayerVerticalStateCalculator;

impl PlayerVerticalStateCalculator {
    pub fn new() -> Self {
        Self
    }

    pub fn update(&mut self, players: &PlayerFrameState) -> PlayerVerticalState {
        let players = players
            .players
            .iter()
            .filter_map(|player| {
                let height = player.position()?.z;
                Some((
                    player.player_id.clone(),
                    PlayerVerticalSample::from_height(height),
                ))
            })
            .collect();

        PlayerVerticalState { players }
    }
}