use crate::database::Finishes;
use crate::Game;
use crate::Snapper;
use crate::ThHeader;
use teehistorian::Chunk;
use twsnap::{
compat::ddnet::{DemoWriter, WriteError},
time::Instant,
Snap,
};
use vek::Vec2;
pub type DemoChatPtr<'a> = Option<&'a mut dyn DemoChatWrite>;
pub type DemoPtr<'a, T> = Option<&'a mut dyn DemoWrite<T>>;
#[derive(Debug, Clone, PartialEq)]
pub struct ReplayerTeeInfo {
pub team: i32,
pub practice: bool,
pub pos: Vec2<i32>,
}
pub trait ReplayerConfigure {
fn on_teehistorian_header(&mut self, raw: &[u8], header: &ThHeader);
}
pub trait ReplayerChecker: ReplayerConfigure {
fn on_teehistorian_chunk(&mut self, now: Instant, chunk: &Chunk);
fn on_finish(&mut self, now: Instant, finish: &Finishes);
fn check_tees(
&mut self,
cur_time: Instant,
tees: &[Option<ReplayerTeeInfo>],
demo: DemoChatPtr,
);
fn finalize(&mut self);
}
pub trait ReplayerCheckerHelper {
fn check_tees(
&mut self,
cur_time: Instant,
tees: &[Option<ReplayerTeeInfo>],
demo: DemoChatPtr,
) -> bool;
}
pub trait GameValidator {
fn tee_pos(&self, id: u32) -> Option<Vec2<i32>>;
fn max_tee_id(&self) -> u32;
fn set_tee_pos(&mut self, id: u32, pos: Option<Vec2<i32>>);
fn player_team(&self, id: u32) -> i32;
fn set_player_team(&mut self, id: u32, team: i32);
}
#[macro_export]
macro_rules! info_demo {
($write_chat:expr, $cur_time:expr, $($format_arg:tt)*) => {
if ::log::log_enabled!(log::Level::Info) {
let write_chat = $write_chat;
let cur_time: Instant = $cur_time;
let msg = format!($($format_arg)*);
::log::info!("{cur_time}: {msg}");
if let Some(demo) = write_chat {
demo.write_chat(&msg).unwrap()
}
}
};
}
impl<T> ReplayerCheckerHelper for T
where
T: GameValidator,
{
fn check_tees(
&mut self,
cur_time: Instant,
tees: &[Option<ReplayerTeeInfo>],
demo: DemoChatPtr,
) -> bool {
let mut demo = demo;
let mut correct = true;
for (player_id, player) in tees.iter().enumerate() {
let player_id = player_id as u32;
let world_pos = self.tee_pos(player_id);
let in_practice = player.as_ref().is_some_and(|p| p.practice);
let teehistorian_pos = player.as_ref().map(|p| p.pos);
if teehistorian_pos != world_pos {
if !in_practice {
correct = false;
info_demo!(
demo.as_deref_mut(),
cur_time,
"incorrect tee_pos player_id={}, world={:?}, teehistorian={:?} ({:?}), diff={:?}",
player_id,
world_pos,
teehistorian_pos,
teehistorian_pos.map(|p| p/32),
teehistorian_pos.zip(world_pos).map(|(a,b)| a - b)
);
}
self.set_tee_pos(player_id, teehistorian_pos);
}
if teehistorian_pos.is_some() {
let world_team = self.player_team(player_id);
let teehistorian_team = player.as_ref().map(|p| p.team).unwrap();
if teehistorian_team != world_team {
correct = false;
info_demo!(
demo.as_deref_mut(),
cur_time,
"incorrect team player_id={player_id}, world={world_team}, teehistorian={teehistorian_team}"
);
self.set_player_team(player_id, teehistorian_team);
}
}
}
for player_id in tees.len() as u32..self.max_tee_id() {
if let Some(world_pos) = self.tee_pos(player_id) {
correct = false;
info_demo!(
demo.as_deref_mut(),
cur_time,
"incorrect tee_pos player_id={}, world={:?}, teehistorian=None",
player_id,
world_pos,
);
self.set_tee_pos(player_id, None);
}
}
correct
}
}
pub trait GameReplayerAll: Game + ReplayerChecker {}
impl<T> GameReplayerAll for T where T: Game + ReplayerChecker {}
pub trait DemoWrite<World>: DemoChatWrite {
fn snap_and_write(
&mut self,
tick: Instant,
world: &World,
snap_buf: &mut Snap,
) -> Result<(), WriteError>;
fn chat(&mut self) -> &mut dyn DemoChatWrite;
}
pub trait DemoChatWrite {
fn write_chat(&mut self, msg: &str) -> Result<(), WriteError>;
fn write_player_chat(&mut self, player_id: i32, msg: &str) -> Result<(), WriteError>;
}
impl<T: Snapper> DemoWrite<T> for DemoWriter<'_> {
fn snap_and_write(
&mut self,
tick: Instant,
world: &T,
snap_buf: &mut Snap,
) -> Result<(), WriteError> {
world.snap(snap_buf);
let res = self.write_snapshot(tick, snap_buf);
snap_buf.clear();
res
}
fn chat(&mut self) -> &mut dyn DemoChatWrite {
self
}
}
impl DemoChatWrite for DemoWriter<'_> {
fn write_chat(&mut self, msg: &str) -> Result<(), WriteError> {
self.write_chat(msg)
}
fn write_player_chat(&mut self, player_id: i32, msg: &str) -> Result<(), WriteError> {
self.write_player_chat(player_id, msg)
}
}