vault 11.0.1

A parser for Company of Heroes replay files.
Documentation
use crate::command::Command;
use crate::data::chunks::Chunk::{DataAuto, DataData, DataSdsc};
use crate::data::chunks::{Chunk, DataAutoChunk, DataDataChunk, DataSdscChunk};
use crate::data::ticks::{CommandTick, Tick};
use crate::data::{Chunky, Header};
use crate::data::{ParserResult, Span};
use crate::Message;
use nom::combinator::eof;
use nom::combinator::map;
use nom::multi::many_till;
use nom::sequence::tuple;
use nom_tracable::tracable_parser;
use std::collections::HashMap;

#[cfg(feature = "raw")]
use crate::command::RawCommand;

#[derive(Debug)]
pub struct Replay {
    pub header: Header,
    _chunkies: Vec<Chunky>,
    pub chunks: Vec<Chunk>,
    pub ticks: Vec<Tick>,
}

impl Replay {
    #[tracable_parser]
    pub fn from_span(input: Span) -> ParserResult<Replay> {
        let (input, header) = Header::parse_header(input)?;

        let mut parser = map(
            tuple((
                Chunky::parse,
                Chunk::parse(header.version),
                Chunky::parse,
                Chunk::parse(header.version),
                Chunk::parse(header.version),
                many_till(Tick::parse, eof),
            )),
            |(
                first_chunky,
                foldpost_chunk,
                second_chunky,
                foldinfo_chunk,
                datasdsc_chunk,
                (ticks, _),
            )| {
                Replay {
                    header: header.clone(),
                    _chunkies: vec![first_chunky, second_chunky],
                    chunks: vec![foldpost_chunk, foldinfo_chunk, datasdsc_chunk],
                    ticks,
                }
            },
        );

        parser(input)
    }

    pub fn data_chunks(&self) -> Vec<&Chunk> {
        self.chunks
            .iter()
            .flat_map(|chunk| match chunk {
                Chunk::Fold(fold) => fold.chunks.iter().collect(),
                _ => vec![chunk],
            })
            .collect()
    }

    pub fn game_data(&self) -> &DataDataChunk {
        let chunks = self.data_chunks();

        let data_chunk = chunks
            .iter()
            .find(|chunk| matches!(chunk, DataData(_)))
            .unwrap();

        match data_chunk {
            DataData(data) => data,
            _ => panic!(),
        }
    }

    pub fn automatch_data(&self) -> Option<&DataAutoChunk> {
        match self
            .data_chunks()
            .iter()
            .find(|chunk| matches!(chunk, DataAuto(_)))
        {
            Some(DataAuto(chunk)) => Some(chunk),
            None => None,
            _ => panic!(),
        }
    }

    pub fn map_data(&self) -> &DataSdscChunk {
        let chunks = self.data_chunks();

        let map_chunk = chunks
            .iter()
            .find(|chunk| matches!(chunk, DataSdsc(_)))
            .unwrap();

        match map_chunk {
            DataSdsc(map) => map,
            _ => panic!(),
        }
    }

    pub fn command_ticks(&self) -> impl Iterator<Item = &CommandTick> {
        self.ticks.iter().filter_map(|tick| match tick {
            Tick::Command(command) => Some(command),
            _ => None,
        })
    }

    pub fn commands(&self) -> HashMap<u32, Vec<Command>> {
        self.command_ticks()
            .enumerate()
            .fold(HashMap::new(), |mut acc, (idx, tick)| {
                for bundle in &tick.bundles {
                    for command in &bundle.commands {
                        let player_commands = acc.entry(command.player_id as u32).or_default();
                        player_commands.push(Command::from_data_command_at_tick(
                            command.clone(),
                            idx as u32 + 1,
                        ));
                    }
                }
                acc
            })
    }

    #[cfg(feature = "raw")]
    pub fn raw_commands(&self) -> HashMap<u32, Vec<RawCommand>> {
        self.command_ticks()
            .enumerate()
            .fold(HashMap::new(), |mut acc, (idx, tick)| {
                for bundle in &tick.bundles {
                    for command in &bundle.commands {
                        let player_commands = acc.entry(command.player_id as u32).or_default();
                        player_commands.push(RawCommand::from_data_command_at_tick(
                            command.clone(),
                            idx as u32 + 1,
                        ));
                    }
                }
                acc
            })
    }

    pub fn messages(&self) -> HashMap<String, Vec<Message>> {
        self.ticks
            .iter()
            .enumerate()
            .filter_map(|(idx, tick)| match tick {
                Tick::Message(message) => Some((idx + 1, message.messages.clone())),
                _ => None,
            })
            .fold(HashMap::new(), |mut acc, (tick, messages)| {
                for message in messages.iter() {
                    let msgs = acc.entry(message.name.clone()).or_default();
                    msgs.push(Message::new(tick as u32, message.message.clone()));
                }
                acc
            })
    }
}