s2protocol 3.5.3

A parser for Starcraft II - Replay format, exports to different target formats
Documentation
//! Converts Tracker Events from protocol-version specific to protocol-agnostic versions
use super::byte_aligned::*;
use crate::tracker_events::{
    PlayerSetupEvent, PlayerStats, PlayerStatsEvent, ReplayTrackerEvent, TrackerEvent,
    UnitBornEvent, UnitDiedEvent, UnitDoneEvent, UnitInitEvent, UnitOwnerChangeEvent,
    UnitPositionsEvent, UnitTypeChangeEvent, UpgradeEvent,
};
use crate::*;
use nom::*;
use nom_mpq::MPQ;
use nom_mpq::parser::peek_hex;

impl ReplayTrackerEEventId {
    /// Reads a delta, TrackerEvent pair
    #[tracing::instrument(name="TrackerEvent::parse_event_pair", level = "debug", skip(input), fields(peek = peek_hex(input)))]
    pub fn parse_event_pair(input: &[u8]) -> S2ProtoResult<&[u8], (u32, ReplayTrackerEEventId)> {
        let (tail, delta) = SVarUint32::parse(input)?;
        let (tail, event) = ReplayTrackerEEventId::parse(tail)?;
        let delta = match delta {
            SVarUint32::MUint6(val) => val as u32,
            SVarUint32::MUint14(val) | SVarUint32::MUint22(val) | SVarUint32::MUint32(val) => val,
        };
        Ok((tail, (delta, event)))
    }

    /// Read the Tracker Events
    pub fn read_tracker_events(
        mpq: &MPQ,
        file_contents: &[u8],
    ) -> Result<Vec<TrackerEvent>, S2ProtocolError> {
        // TODO: Make it return an Iterator.
        let (_event_tail, tracker_events) =
            mpq.read_mpq_file_sector("replay.tracker.events", false, file_contents)?;
        let mut res = vec![];
        let mut event_tail: &[u8] = &tracker_events;
        loop {
            let (new_event_tail, (delta, event)) = Self::parse_event_pair(event_tail)?;
            event_tail = new_event_tail;
            match event.try_into() {
                Ok(val) => res.push(TrackerEvent { delta, event: val }),
                Err(err) => {
                    tracing::debug!("Skipping event: {:?}", err);
                }
            };
            if event_tail.input_len() == 0 {
                break;
            }
        }
        Ok(res)
    }
}

impl TryFrom<ReplayTrackerEEventId> for ReplayTrackerEvent {
    type Error = S2ProtocolError;

    fn try_from(value: ReplayTrackerEEventId) -> Result<Self, Self::Error> {
        match value {
            ReplayTrackerEEventId::EPlayerStats(e) => Ok(e.into()),
            ReplayTrackerEEventId::EUnitBorn(e) => Ok(e.try_into()?),
            ReplayTrackerEEventId::EUnitDied(e) => Ok(e.into()),
            ReplayTrackerEEventId::EUnitOwnerChange(e) => Ok(e.into()),
            ReplayTrackerEEventId::EUnitTypeChange(e) => Ok(e.try_into()?),
            ReplayTrackerEEventId::EUpgrade(e) => Ok(e.try_into()?),
            ReplayTrackerEEventId::EUnitInit(e) => Ok(e.try_into()?),
            ReplayTrackerEEventId::EUnitDone(e) => Ok(e.into()),
            ReplayTrackerEEventId::EUnitPosition(e) => Ok(e.into()),
            ReplayTrackerEEventId::EPlayerSetup(e) => Ok(e.into()),
        }
    }
}

impl TryFrom<ReplayTrackerSUpgradeEvent> for ReplayTrackerEvent {
    type Error = S2ProtocolError;
    fn try_from(source: ReplayTrackerSUpgradeEvent) -> Result<Self, Self::Error> {
        let upgrade_type_name = match str::from_utf8(&source.m_upgrade_type_name) {
            Ok(val) => val.to_string(),
            Err(_) => format!("{:?}", source.m_upgrade_type_name),
        };
        Ok(ReplayTrackerEvent::Upgrade(UpgradeEvent {
            player_id: source.m_player_id,
            upgrade_type_name,
            count: source.m_count,
            player_name: None,
        }))
    }
}
impl TryFrom<ReplayTrackerSUnitBornEvent> for ReplayTrackerEvent {
    type Error = S2ProtocolError;
    fn try_from(source: ReplayTrackerSUnitBornEvent) -> Result<Self, Self::Error> {
        let creator_ability_name = if let Some(val) = source.m_creator_ability_name {
            Some(str::from_utf8(&val)?.to_string())
        } else {
            None
        };
        Ok(ReplayTrackerEvent::UnitBorn(UnitBornEvent {
            unit_tag_index: source.m_unit_tag_index,
            unit_tag_recycle: source.m_unit_tag_recycle,
            unit_type_name: str::from_utf8(&source.m_unit_type_name)?.to_string(),
            control_player_id: source.m_control_player_id,
            upkeep_player_id: source.m_upkeep_player_id,
            x: source.m_x,
            y: source.m_y,
            creator_unit_tag_index: source.m_creator_unit_tag_index,
            creator_unit_tag_recycle: source.m_creator_unit_tag_recycle,
            creator_ability_name,
        }))
    }
}

impl From<ReplayTrackerSUnitOwnerChangeEvent> for ReplayTrackerEvent {
    fn from(source: ReplayTrackerSUnitOwnerChangeEvent) -> ReplayTrackerEvent {
        ReplayTrackerEvent::UnitOwnerChange(UnitOwnerChangeEvent {
            unit_tag_index: source.m_unit_tag_index,
            unit_tag_recycle: source.m_unit_tag_recycle,
            control_player_id: source.m_control_player_id,
            upkeep_player_id: source.m_upkeep_player_id,
        })
    }
}

impl From<ReplayTrackerSUnitDiedEvent> for ReplayTrackerEvent {
    fn from(source: ReplayTrackerSUnitDiedEvent) -> ReplayTrackerEvent {
        ReplayTrackerEvent::UnitDied(UnitDiedEvent {
            unit_tag_index: source.m_unit_tag_index,
            unit_tag_recycle: source.m_unit_tag_recycle,
            killer_player_id: source.m_killer_player_id,
            x: source.m_x,
            y: source.m_y,
            killer_unit_tag_index: source.m_killer_unit_tag_index,
            killer_unit_tag_recycle: source.m_killer_unit_tag_recycle,
        })
    }
}

impl TryFrom<ReplayTrackerSUnitTypeChangeEvent> for ReplayTrackerEvent {
    type Error = S2ProtocolError;
    fn try_from(source: ReplayTrackerSUnitTypeChangeEvent) -> Result<Self, Self::Error> {
        Ok(ReplayTrackerEvent::UnitTypeChange(UnitTypeChangeEvent {
            unit_tag_index: source.m_unit_tag_index,
            unit_tag_recycle: source.m_unit_tag_recycle,
            unit_type_name: str::from_utf8(&source.m_unit_type_name)?.to_string(),
        }))
    }
}

impl TryFrom<ReplayTrackerSUnitInitEvent> for ReplayTrackerEvent {
    type Error = S2ProtocolError;
    fn try_from(source: ReplayTrackerSUnitInitEvent) -> Result<Self, Self::Error> {
        Ok(ReplayTrackerEvent::UnitInit(UnitInitEvent {
            unit_tag_index: source.m_unit_tag_index,
            unit_tag_recycle: source.m_unit_tag_recycle,
            unit_type_name: str::from_utf8(&source.m_unit_type_name)?.to_string(),
            control_player_id: source.m_control_player_id,
            upkeep_player_id: source.m_upkeep_player_id,
            x: source.m_x,
            y: source.m_y,
        }))
    }
}

impl From<ReplayTrackerSUnitDoneEvent> for ReplayTrackerEvent {
    fn from(source: ReplayTrackerSUnitDoneEvent) -> ReplayTrackerEvent {
        ReplayTrackerEvent::UnitDone(UnitDoneEvent {
            unit_tag_index: source.m_unit_tag_index,
            unit_tag_recycle: source.m_unit_tag_recycle,
        })
    }
}

impl From<ReplayTrackerSUnitPositionsEvent> for ReplayTrackerEvent {
    fn from(source: ReplayTrackerSUnitPositionsEvent) -> ReplayTrackerEvent {
        ReplayTrackerEvent::UnitPosition(UnitPositionsEvent {
            first_unit_index: source.m_first_unit_index,
            items: source.m_items,
        })
    }
}

impl From<ReplayTrackerSPlayerStatsEvent> for ReplayTrackerEvent {
    fn from(source: ReplayTrackerSPlayerStatsEvent) -> ReplayTrackerEvent {
        ReplayTrackerEvent::PlayerStats(PlayerStatsEvent {
            player_id: source.m_player_id,
            stats: source.m_stats.into(),
        })
    }
}

impl From<ReplayTrackerSPlayerStats> for PlayerStats {
    fn from(source: ReplayTrackerSPlayerStats) -> PlayerStats {
        PlayerStats {
            minerals_current: source.m_score_value_minerals_current,
            vespene_current: source.m_score_value_vespene_current,
            minerals_collection_rate: source.m_score_value_minerals_collection_rate,
            vespene_collection_rate: source.m_score_value_vespene_collection_rate,
            workers_active_count: source.m_score_value_workers_active_count,
            minerals_used_in_progress_army: source.m_score_value_minerals_used_in_progress_army,
            minerals_used_in_progress_economy: source
                .m_score_value_minerals_used_in_progress_economy,
            minerals_used_in_progress_technology: source
                .m_score_value_minerals_used_in_progress_technology,
            vespene_used_in_progress_army: source.m_score_value_vespene_used_in_progress_army,
            vespene_used_in_progress_economy: source.m_score_value_vespene_used_in_progress_economy,
            vespene_used_in_progress_technology: source
                .m_score_value_vespene_used_in_progress_technology,
            minerals_used_current_army: source.m_score_value_minerals_used_current_army,
            minerals_used_current_economy: source.m_score_value_minerals_used_current_economy,
            minerals_used_current_technology: source.m_score_value_minerals_used_current_technology,
            vespene_used_current_army: source.m_score_value_vespene_used_current_army,
            vespene_used_current_economy: source.m_score_value_vespene_used_current_economy,
            vespene_used_current_technology: source.m_score_value_vespene_used_current_technology,
            minerals_lost_army: source.m_score_value_minerals_lost_army,
            minerals_lost_economy: source.m_score_value_minerals_lost_economy,
            minerals_lost_technology: source.m_score_value_minerals_lost_technology,
            vespene_lost_army: source.m_score_value_vespene_lost_army,
            vespene_lost_economy: source.m_score_value_vespene_lost_economy,
            vespene_lost_technology: source.m_score_value_vespene_lost_technology,
            minerals_killed_army: source.m_score_value_minerals_killed_army,
            minerals_killed_economy: source.m_score_value_minerals_killed_economy,
            minerals_killed_technology: source.m_score_value_minerals_killed_technology,
            vespene_killed_army: source.m_score_value_vespene_killed_army,
            vespene_killed_economy: source.m_score_value_vespene_killed_economy,
            vespene_killed_technology: source.m_score_value_vespene_killed_technology,
            // According to s2blizzard repo these values are fixed points values and must be
            // divided by 4096.
            food_used: source.m_score_value_food_used / 4096,
            food_made: source.m_score_value_food_made / 4096,
            minerals_used_active_forces: source.m_score_value_minerals_used_active_forces,
            vespene_used_active_forces: source.m_score_value_vespene_used_active_forces,
            minerals_friendly_fire_army: source.m_score_value_minerals_friendly_fire_army,
            minerals_friendly_fire_economy: source.m_score_value_minerals_friendly_fire_economy,
            minerals_friendly_fire_technology: source
                .m_score_value_minerals_friendly_fire_technology,
            vespene_friendly_fire_army: source.m_score_value_vespene_friendly_fire_army,
            vespene_friendly_fire_economy: source.m_score_value_vespene_friendly_fire_economy,
            vespene_friendly_fire_technology: source.m_score_value_vespene_friendly_fire_technology,
        }
    }
}

impl From<ReplayTrackerSPlayerSetupEvent> for ReplayTrackerEvent {
    fn from(source: ReplayTrackerSPlayerSetupEvent) -> ReplayTrackerEvent {
        ReplayTrackerEvent::PlayerSetup(PlayerSetupEvent {
            player_id: source.m_player_id,
            m_type: source.m_type,
            user_id: source.m_user_id,
            slot_id: source.m_slot_id,
        })
    }
}