tf-demo-parser 0.7.0

parser for tf2 demo files
Documentation
use crate::demo::data::game_state::{GameState, Handle, PlayerClassData, PlayerState};
use crate::demo::message::{EntityId, PacketEntity};
use crate::demo::parser::analyser::{Class, Team};
use crate::demo::sendprop::SendPropIdentifier;
use crate::demo::vector::{Vector, VectorXY};
use crate::ParserState;
use std::str::FromStr;

pub fn handle_player_entity(
    state: &mut GameState,
    entity: &PacketEntity,
    parser_state: &ParserState,
) {
    let player = state.get_or_create_player(entity.entity_index);

    const OUTER: SendPropIdentifier = SendPropIdentifier::new("DT_AttributeContainer", "m_hOuter");
    const OUTER2: SendPropIdentifier = SendPropIdentifier::new("DT_AttributeManager", "m_hOuter");

    const HEALTH_PROP: SendPropIdentifier = SendPropIdentifier::new("DT_BasePlayer", "m_iHealth");
    const MAX_HEALTH_PROP: SendPropIdentifier =
        SendPropIdentifier::new("DT_BasePlayer", "m_iMaxHealth");
    const LIFE_STATE_PROP: SendPropIdentifier =
        SendPropIdentifier::new("DT_BasePlayer", "m_lifeState");

    const LOCAL_ORIGIN: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_vecOrigin");
    const NON_LOCAL_ORIGIN: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_vecOrigin");
    const LOCAL_ORIGIN_Z: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_vecOrigin[2]");
    const NON_LOCAL_ORIGIN_Z: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_vecOrigin[2]");
    const LOCAL_EYE_ANGLES: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_angEyeAngles[1]");
    const NON_LOCAL_EYE_ANGLES: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[1]");
    const LOCAL_PITCH_ANGLES: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_angEyeAngles[0]");
    const NON_LOCAL_PITCH_ANGLES: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[0]");

    const SIMTIME_PROP: SendPropIdentifier =
        SendPropIdentifier::new("DT_BaseEntity", "m_flSimulationTime");
    const PROP_BB_MAX: SendPropIdentifier =
        SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled");
    const PLAYER_COND: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCond");
    const PLAYER_COND_EX1: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx");
    const PLAYER_COND_EX2: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx2");
    const PLAYER_COND_EX3: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx3");
    const PLAYER_COND_EX4: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx4");
    const PLAYER_COND_BITS: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerConditionListExclusive", "_condition_bits");

    const WEAPON_0: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "000");
    const WEAPON_1: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "001");
    const WEAPON_2: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "002");

    const DISGUISE_TEAM: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nDisguiseTeam");
    const DISGUISE_CLASS: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_nDisguiseClass");
    const CLOAK_LEVEL: SendPropIdentifier =
        SendPropIdentifier::new("DT_TFPlayerShared", "m_flCloakMeter");

    player.in_pvs = entity.in_pvs;

    for prop in entity.props(parser_state) {
        match prop.identifier {
            OUTER | OUTER2 => {
                player.handle = Handle::try_from(&prop.value).unwrap_or_default();
            }
            HEALTH_PROP => player.health = i64::try_from(&prop.value).unwrap_or_default() as u16,
            MAX_HEALTH_PROP => {
                player.max_health = i64::try_from(&prop.value).unwrap_or_default() as u16
            }
            LIFE_STATE_PROP => {
                player.state = PlayerState::new(i64::try_from(&prop.value).unwrap_or_default())
            }
            LOCAL_ORIGIN | NON_LOCAL_ORIGIN => {
                let pos_xy = VectorXY::try_from(&prop.value).unwrap_or_default();
                player.position.x = pos_xy.x;
                player.position.y = pos_xy.y;
            }
            LOCAL_ORIGIN_Z | NON_LOCAL_ORIGIN_Z => {
                player.position.z = f32::try_from(&prop.value).unwrap_or_default()
            }
            LOCAL_EYE_ANGLES | NON_LOCAL_EYE_ANGLES => {
                player.view_angle = f32::try_from(&prop.value).unwrap_or_default()
            }
            LOCAL_PITCH_ANGLES | NON_LOCAL_PITCH_ANGLES => {
                player.pitch_angle = f32::try_from(&prop.value).unwrap_or_default()
            }
            SIMTIME_PROP => {
                player.simulation_time = i64::try_from(&prop.value).unwrap_or_default() as u16
            }
            PROP_BB_MAX => {
                let max = Vector::try_from(&prop.value).unwrap_or_default();
                player.bounds.max = max;
            }
            WEAPON_0 => {
                let handle = Handle::try_from(&prop.value).unwrap_or_default();
                player.weapons[0] = handle;
            }
            WEAPON_1 => {
                let handle = Handle::try_from(&prop.value).unwrap_or_default();
                player.weapons[1] = handle;
            }
            WEAPON_2 => {
                let handle = Handle::try_from(&prop.value).unwrap_or_default();
                player.weapons[2] = handle;
            }
            PLAYER_COND | PLAYER_COND_BITS => {
                player.conditions[0..4].copy_from_slice(
                    &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
                );
            }
            PLAYER_COND_EX1 => {
                player.conditions[4..8].copy_from_slice(
                    &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
                );
            }
            PLAYER_COND_EX2 => {
                player.conditions[8..12].copy_from_slice(
                    &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
                );
            }
            PLAYER_COND_EX3 => {
                player.conditions[12..16].copy_from_slice(
                    &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
                );
            }
            PLAYER_COND_EX4 => {
                player.conditions[16..20].copy_from_slice(
                    &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
                );
            }
            DISGUISE_TEAM => {
                if let PlayerClassData::Spy { disguise_team, .. } = &mut player.class_data {
                    *disguise_team = Team::new(i64::try_from(&prop.value).unwrap_or_default())
                }
            }
            DISGUISE_CLASS => {
                if let PlayerClassData::Spy { disguise_class, .. } = &mut player.class_data {
                    *disguise_class = Class::new(i64::try_from(&prop.value).unwrap_or_default());
                }
            }
            CLOAK_LEVEL => {
                if let PlayerClassData::Spy { cloak, .. } = &mut player.class_data {
                    *cloak = f32::try_from(&prop.value).unwrap_or_default();
                }
            }
            _ => {}
        }
    }
}

pub fn handle_player_resource(
    state: &mut GameState,
    entity: &PacketEntity,
    parser_state: &ParserState,
) {
    for prop in entity.props(parser_state) {
        if let Some((table_name, prop_name)) = prop.identifier.names()
            && let Ok(player_id) = u32::from_str(prop_name.as_str()) {
                let entity_id = EntityId::from(player_id);
                if let Some(player) = state
                    .players
                    .iter_mut()
                    .find(|player| player.entity == entity_id)
                {
                    match table_name.as_str() {
                        "m_iTeam" => {
                            player.team = Team::new(i64::try_from(&prop.value).unwrap_or_default())
                        }
                        "m_iMaxHealth" => {
                            player.max_health =
                                i64::try_from(&prop.value).unwrap_or_default() as u16
                        }
                        "m_iPlayerClass" => {
                            let class = Class::new(i64::try_from(&prop.value).unwrap_or_default());
                            if player.class != class {
                                player.class = class;
                                player.class_data = PlayerClassData::default_for_class(class);
                            }
                        }
                        "m_iChargeLevel" => {
                            if let PlayerClassData::Medic { charge, .. } = &mut player.class_data {
                                *charge = i64::try_from(&prop.value).unwrap_or_default() as u8
                            }
                        }
                        "m_iPing" => {
                            player.ping = i64::try_from(&prop.value).unwrap_or_default() as u16
                        }
                        _ => {}
                    }
                }
            }
    }
}