use std::collections::HashMap; use godot::prelude::*; use crate::{ game_manager::{Game, Player}, map::Map, net::util::NetVector3, player::{ DamageSource, IPlayer, NetTransform, remote_player::RemotePlayer, weapon::WeaponType, }, }; #[derive(GodotClass)] #[class(base=Node, init)] pub struct ReplayPlayback { pub replay: Replay, pub current_map: Option>, current_frame_idx: usize, player_characters: HashMap>, pub current_time: f64, pub spectated_player_id: u16, base: Base, } #[godot_api] impl INode for ReplayPlayback { fn ready(&mut self) {} fn process(&mut self, _: f64) { if let Some(character) = self.player_characters.get_mut(&self.spectated_player_id) { if let Some(camera) = &mut character.bind_mut().spectator_camera { if !camera.is_current() { camera.make_current(); } } } } fn physics_process(&mut self, delta: f64) { self.current_time += delta; while let Some(frame) = self.replay.frames.get(self.current_frame_idx) && frame.time <= self.current_time { if let Some(map) = &mut self.current_map { for (id, data) in &frame.player_data { if !self.player_characters.contains_key(id) { if let Some(player) = map.bind_mut().spawn_replay_player(*id, data.clone()) { self.player_characters.insert(*id, player); } } } } for event in &frame.events { match event { ReplayEvent::Death(damage_source, player_id) => { if let Some(character) = self.player_characters.get_mut(player_id) { character.bind_mut().kill(*damage_source); } } ReplayEvent::Shoot(player_id, to) => { if let Some(character) = self.player_characters.get_mut(player_id) { character.bind_mut().try_shoot(*to, false); } } } } for (id, mut character) in self.player_characters.clone() { if let Some(state) = frame.player_data.get(&id) { character.bind_mut().apply_transform(state.transform); character.bind_mut().apply_look_up(state.look_up); character .bind_mut() .set_movement_dir(state.movement_dir.into()); character .bind_mut() .swap_weapon(state.current_weapon, false); } else { character.queue_free(); self.player_characters.remove(&id); } } self.current_frame_idx += 1; } if self.current_frame_idx >= self.replay.frames.len() { for (id, mut character) in self.player_characters.clone() { character.queue_free(); self.player_characters.remove(&id); } } } fn exit_tree(&mut self) { for (_, character) in &mut self.player_characters { character.queue_free(); } self.player_characters.clear(); } } #[derive(GodotClass)] #[class(base=Node, init)] pub struct ReplayRecorder { pub time_elapsed: f64, pub replay: Replay, current_frame: ReplayFrame, base: Base, } #[godot_api] impl INode for ReplayRecorder { fn ready(&mut self) { self.run_deferred(|s| { s.current_frame = s.record_frame(); if let Some(game) = &Game::singleton() { for player in &game.bind().players { s.replay.players.insert( player.id(), ReplayPlayer { id: player.id(), name: player.data.name.clone(), }, ); } } }); } fn physics_process(&mut self, delta: f64) { self.time_elapsed += delta; if self.time_elapsed > (self.current_frame.time + (1. / 60.)) { self.replay.frames.push(self.current_frame.clone()); self.current_frame = self.record_frame(); } } } impl ReplayRecorder { pub fn record_event(&mut self, event: ReplayEvent) { self.current_frame.events.push(event); } fn record_frame(&mut self) -> ReplayFrame { let mut player_data = HashMap::new(); if let Some(game) = Game::singleton() { for player in &game.bind().players { if let Some(state) = player.get_state() { player_data.insert(player.id(), state); } } } ReplayFrame { time: self.time_elapsed, player_data, events: Vec::new(), } } } #[derive(Debug, Clone)] pub struct Replay { players: HashMap, frames: Vec, } impl Default for Replay { fn default() -> Self { Self { players: Default::default(), frames: Default::default(), } } } #[derive(Debug, Clone)] pub struct ReplayPlayer { id: u16, name: String, } #[derive(Debug, Default, Clone)] pub struct ReplayFrame { time: f64, player_data: HashMap, events: Vec, } #[derive(Debug, Clone)] pub enum ReplayEvent { Death(DamageSource, u16), Shoot(u16, Vector3), } #[derive(Debug)] pub enum ReplayBall { World(ReplayBallData), Held, } #[derive(Debug, Clone)] pub struct ReplayPlayerState { pub transform: NetTransform, pub movement_dir: NetVector3, pub look_up: f32, pub current_weapon: WeaponType, } impl Player { fn get_state(&self) -> Option { if let Some(character) = &self.character { Some(ReplayPlayerState { transform: character.dyn_bind().get_transform(), movement_dir: character.dyn_bind().get_movement_dir().into(), look_up: character.dyn_bind().get_look_up(), current_weapon: character .dyn_bind() .get_weapon() .map(|w| w.dyn_bind().get_type()) .unwrap_or(WeaponType::Raygun), }) } else { None } } } #[derive(Debug)] pub struct ReplayBallData { transform: NetTransform, velocity: NetVector3, }