twsnap 0.7.4

Common snapshot format between TwGpu and TwGame
Documentation
use super::{DemoKind, DemoMapHash};
use crate::time::Instant;
use crate::Snap;
use libtw2_common::digest::Sha256;
use libtw2_demo::{ddnet, DemoKind as LibDemoKind};
use libtw2_gamenet_ddnet::msg::game::SvChat;
use libtw2_gamenet_ddnet::msg::{game, Game};
use libtw2_gamenet_ddnet::Protocol as DDNet;
use libtw2_gamenet_ddnet::SnapObj;
use std::cmp::min;
use std::error;
use std::fmt;
use std::io;

pub struct DemoWriter {
    inner: ddnet::DemoWriter<DDNet>,
    item_storage: Vec<(SnapObj, u16)>,
    teams: [u8; 64],
}

#[derive(Debug)]
pub struct WriteError(ddnet::WriteError);

impl fmt::Display for WriteError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl error::Error for WriteError {}

impl From<ddnet::WriteError> for WriteError {
    fn from(value: ddnet::WriteError) -> Self {
        Self(value)
    }
}

impl DemoWriter {
    pub fn new<T: io::Write + io::Seek + 'static>(
        file: T,
        kind: DemoKind,
        timestamp: &str,
        net_version: &str,
        map_name: &str,
        map_data: Option<&[u8]>,
        hash: DemoMapHash,
        length: i32,
    ) -> Result<Self, WriteError> {
        let kind = match kind {
            DemoKind::Client => LibDemoKind::Client,
            DemoKind::Server => LibDemoKind::Server,
        };
        let (sha256, crc) = match hash {
            DemoMapHash::Crc(crc) => (None, crc),
            DemoMapHash::Sha256(sha256) => (Some(Sha256(sha256)), 0),
        };
        let inner = ddnet::DemoWriter::new(
            file,
            net_version[..min(net_version.len(), 63)].as_bytes(),
            map_name[..min(map_name.len(), 63)].as_bytes(),
            sha256,
            crc,
            kind,
            length,
            timestamp[..min(timestamp.len(), 19)].as_bytes(),
            map_data.unwrap_or(&[]),
        )?;
        Ok(Self {
            inner,
            item_storage: Vec::new(),
            teams: [0; 64],
        })
    }
}

impl DemoWriter {
    pub fn write_snapshot(&mut self, tick: Instant, snap: &Snap) -> Result<(), WriteError> {
        let new_teams = snap.to_ddnet(&mut self.item_storage);
        if self.teams != new_teams {
            let mut teams = [0i32; 64];
            for (i, t) in new_teams.iter().enumerate() {
                teams[i] = *t as i32;
            }
            self.inner
                .write_msg(&Game::SvTeamsState(game::SvTeamsState { teams }))?;
            self.teams = new_teams;
        }
        self.inner.write_snap(
            tick.snap_tick(),
            self.item_storage.iter().map(|(obj, id)| (obj, *id)),
        )?;
        Ok(())
    }

    pub fn write_chat(&mut self, msg: &str) -> Result<(), WriteError> {
        self.inner
            .write_msg(&libtw2_gamenet_ddnet::msg::Game::SvChat(SvChat {
                team: 0,
                client_id: -1,
                message: msg.as_bytes(),
            }))?;
        Ok(())
    }

    pub fn write_player_chat(
        &mut self,
        player_id: i32, // TODO: PlayerUid
        msg: &str,
    ) -> Result<(), WriteError> {
        self.inner
            .write_msg(&libtw2_gamenet_ddnet::msg::Game::SvChat(SvChat {
                team: 0,
                client_id: player_id,
                message: msg.as_bytes(),
            }))?;
        Ok(())
    }
}