twgame_core/
replay.rs

1use crate::database::Finishes;
2use crate::Game;
3use crate::Snapper;
4use teehistorian::Chunk;
5use twsnap::{
6    compat::ddnet::{DemoWriter, WriteError},
7    time::Instant,
8    Snap,
9};
10use vek::Vec2;
11
12pub type DemoChatPtr<'a> = Option<&'a mut (dyn DemoChatWrite + 'static)>;
13pub type DemoPtr<'a, T> = Option<&'a mut (dyn DemoWrite<T> + 'static)>;
14
15#[derive(Debug, Clone, PartialEq)]
16pub struct ReplayerTeeInfo {
17    pub team: i32,
18    pub practice: bool,
19    pub pos: Vec2<i32>,
20}
21
22pub trait ReplayerChecker {
23    fn on_teehistorian_header(&mut self, header: &[u8]);
24    /// called on each Teehistorian chunk
25    fn on_teehistorian_chunk(&mut self, now: Instant, chunk: &Chunk);
26
27    fn on_finish(&mut self, now: Instant, finish: &Finishes);
28
29    fn check_tees(
30        &mut self,
31        cur_time: Instant,
32        tees: &[Option<ReplayerTeeInfo>],
33        demo: DemoChatPtr,
34    );
35
36    /// called after last chunk was read from Teehistorian file
37    fn finalize(&mut self);
38}
39
40pub trait ReplayerCheckerHelper {
41    fn check_tees(
42        &mut self,
43        cur_time: Instant,
44        tees: &[Option<ReplayerTeeInfo>],
45        demo: DemoChatPtr,
46    ) -> bool;
47}
48
49/// trait for easier GameValidator implementation
50pub trait GameValidator {
51    fn tee_pos(&self, id: u32) -> Option<Vec2<i32>>;
52    // at least peak number of player, where player_count-1 is still in game
53    fn max_tee_id(&self) -> u32;
54
55    // adjust functions
56    fn set_tee_pos(&mut self, id: u32, pos: Option<Vec2<i32>>);
57
58    // to check from replayer if players are in correct team. Return 0 if player doesn't exist
59    fn player_team(&self, id: u32) -> i32;
60    // sets player to specific team if incorrect team was return in `player_team`
61    fn set_player_team(&mut self, id: u32, team: i32);
62}
63
64#[macro_export]
65macro_rules! info_demo {
66    ($write_chat:expr, $cur_time:expr, $($format_arg:tt)*) => {
67        if ::log::log_enabled!(log::Level::Info) {
68            let write_chat = $write_chat;
69            let cur_time: Instant = $cur_time;
70            let msg = format!($($format_arg)*);
71            ::log::info!("{cur_time}: {msg}");
72            if let Some(demo) = write_chat {
73                demo.write_chat(&msg).unwrap()
74            }
75        }
76    };
77}
78
79impl<T> ReplayerCheckerHelper for T
80where
81    T: GameValidator,
82{
83    fn check_tees(
84        &mut self,
85        cur_time: Instant,
86        tees: &[Option<ReplayerTeeInfo>],
87        demo: DemoChatPtr,
88    ) -> bool {
89        let mut demo = demo;
90        let mut correct = true;
91        for (player_id, player) in tees.iter().enumerate() {
92            let player_id = player_id as u32;
93            let world_pos = self.tee_pos(player_id);
94
95            let in_practice = player.as_ref().is_some_and(|p| p.practice);
96            let teehistorian_pos = player.as_ref().map(|p| p.pos);
97
98            if teehistorian_pos != world_pos {
99                if !in_practice {
100                    correct = false;
101                    info_demo!(
102                        demo.as_deref_mut(),
103                        cur_time,
104                        "incorrect tee_pos player_id={}, world={:?}, teehistorian={:?} ({:?}), diff={:?}",
105                        player_id,
106                        world_pos,
107                        teehistorian_pos,
108                        teehistorian_pos.map(|p| p/32),
109                        teehistorian_pos.zip(world_pos).map(|(a,b)| a - b)
110                    );
111                }
112                self.set_tee_pos(player_id, teehistorian_pos);
113            }
114
115            // only check team if tee exists
116            if teehistorian_pos.is_some() {
117                let world_team = self.player_team(player_id);
118                let teehistorian_team = player.as_ref().map(|p| p.team).unwrap();
119                if teehistorian_team != world_team {
120                    correct = false;
121                    info_demo!(
122                        demo.as_deref_mut(),
123                        cur_time,
124                        "incorrect team player_id={player_id}, world={world_team}, teehistorian={teehistorian_team}"
125                    );
126                    self.set_player_team(player_id, teehistorian_team);
127                }
128            }
129        }
130        for player_id in tees.len() as u32..self.max_tee_id() {
131            if let Some(world_pos) = self.tee_pos(player_id) {
132                correct = false;
133                info_demo!(
134                    demo.as_deref_mut(),
135                    cur_time,
136                    "incorrect tee_pos player_id={}, world={:?}, teehistorian=None",
137                    player_id,
138                    world_pos,
139                );
140                self.set_tee_pos(player_id, None);
141            }
142        }
143        correct
144    }
145}
146
147/// All structs that implement
148/// * Game and
149/// * GameReplayer (either directly or automatically through GameValidator)
150///
151/// implement this marker trait and can be passed to the Teehistorian Replayer
152pub trait GameReplayerAll: Game + ReplayerChecker {}
153impl<T> GameReplayerAll for T where T: Game + ReplayerChecker {}
154
155pub trait DemoWrite<World>: DemoChatWrite {
156    fn snap_and_write(
157        &mut self,
158        tick: Instant,
159        world: &World,
160        snap_buf: &mut Snap,
161    ) -> Result<(), WriteError>;
162
163    // workaround until https://github.com/rust-lang/rust/issues/65991 stablilzed
164    fn chat(&mut self) -> &mut (dyn DemoChatWrite + 'static);
165}
166pub trait DemoChatWrite {
167    fn write_chat(&mut self, msg: &str) -> Result<(), WriteError>;
168    fn write_player_chat(&mut self, player_id: i32, msg: &str) -> Result<(), WriteError>;
169}
170
171impl<T: Snapper> DemoWrite<T> for DemoWriter {
172    fn snap_and_write(
173        &mut self,
174        tick: Instant,
175        world: &T,
176        snap_buf: &mut Snap,
177    ) -> Result<(), WriteError> {
178        world.snap(snap_buf);
179        let res = self.write_snapshot(tick, snap_buf);
180        snap_buf.clear();
181        res
182    }
183
184    fn chat(&mut self) -> &mut (dyn DemoChatWrite + 'static) {
185        self
186    }
187}
188
189impl DemoChatWrite for DemoWriter {
190    fn write_chat(&mut self, msg: &str) -> Result<(), WriteError> {
191        self.write_chat(msg)
192    }
193    fn write_player_chat(&mut self, player_id: i32, msg: &str) -> Result<(), WriteError> {
194        self.write_player_chat(player_id, msg)
195    }
196}