use crate::Snap;
use libtw2_demo::ddnet;
use libtw2_gamenet_ddnet::Protocol as DDNet;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
use std::str::from_utf8;
use crate::time::SnapTick;
#[derive(Debug)]
pub struct ReadError(ddnet::ReadError);
impl Display for ReadError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Error for ReadError {}
impl From<ddnet::ReadError> for ReadError {
fn from(value: ddnet::ReadError) -> Self {
Self(value)
}
}
pub struct DemoReader {
reader: ddnet::DemoReader<DDNet>,
converter: super::Converter,
last_tick: Option<SnapTick>,
}
pub enum DemoChunk {
Snapshot(SnapTick),
NetMsg,
}
impl DemoReader {
pub fn new<R: io::Read + io::Seek + 'static>(file: R) -> Result<Self, ReadError> {
let reader = ddnet::DemoReader::new(file, &mut warn::Log)?;
let converter = super::Converter::default();
Ok(Self {
reader,
converter,
last_tick: None,
})
}
pub fn next_chunk(&mut self, snap: &mut Snap) -> Result<Option<DemoChunk>, ReadError> {
loop {
match self.reader.next_chunk(&mut warn::Log)? {
None => break Ok(None),
Some(ddnet::Chunk::Tick(tick)) => self.last_tick = Some(tick),
Some(ddnet::Chunk::Snapshot(items)) => match self.last_tick.take() {
None => continue,
Some(tick) => {
self.converter.process_next_snap(snap, items);
break Ok(Some(DemoChunk::Snapshot(tick)));
}
},
Some(ddnet::Chunk::Message(_)) => break Ok(Some(DemoChunk::NetMsg)),
Some(ddnet::Chunk::Invalid) => continue,
}
}
}
pub fn map_data(&self) -> Option<&[u8]> {
match self.reader.inner().map_data() {
&[] => None,
data => Some(data),
}
}
pub fn map_name(&self) -> &str {
lossy_str(self.reader.inner().map_name())
}
pub fn map_hash(&self) -> DemoMapHash {
match self.reader.inner().map_sha256() {
Some(sha) => DemoMapHash::Sha256(sha.0),
None => DemoMapHash::Crc(self.reader.inner().map_crc()),
}
}
pub fn kind(&self) -> DemoKind {
let demo_reader = &self.reader;
match demo_reader.inner().kind() {
libtw2_demo::DemoKind::Client => DemoKind::Client,
libtw2_demo::DemoKind::Server => DemoKind::Server,
}
}
pub fn net_version(&self) -> &str {
lossy_str(self.reader.inner().net_version())
}
pub fn length(&self) -> i32 {
self.reader.inner().length()
}
pub fn timestamp(&self) -> &str {
lossy_str(self.reader.inner().timestamp())
}
pub fn timeline_markers(&self) -> &[i32] {
self.reader.inner().timeline_markers()
}
}
pub enum DemoKind {
Client,
Server,
}
pub enum DemoMapHash {
Crc(u32),
Sha256([u8; 32]),
}
fn lossy_str(bytes: &[u8]) -> &str {
match from_utf8(bytes) {
Ok(s) => s,
Err(err) => from_utf8(&bytes[..err.valid_up_to()]).unwrap(),
}
}