use std::collections::HashMap; use godot::prelude::*; use crate::{ game_manager::{Game, Player}, map::Map, net::{network_manager::BallSync, util::NetVector3}, player::{ DamageSource, IPlayer, NetTransform, ball::Ball, 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>, ball: Option>, pub current_time: f64, pub spectated_player_id: u16, pub fast_forwarding: bool, 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 ReplayBall::World(ball_state) = &frame.ball_data { if let Some(ball) = &mut self.ball { ball.bind_mut().apply_sync(BallSync { transform: ball_state.transform, velocity: ball_state.velocity, }); } else if let Some(map) = &mut self.current_map { if let Some(ball) = map.bind_mut().spawn_replay_ball( ball_state.transform.into(), ball_state.velocity.into(), None, ) { self.ball = Some(ball); } } } else { if let Some(mut ball) = self.ball.take() { ball.queue_free(); } } if !self.fast_forwarding { if let Some(map) = &mut self.current_map { for (id, data) in &frame.player_data { if !self.player_characters.contains_key(id) && !data.is_dead && let Some(player) = self.replay.players.get(id) { if let Some(player) = map.bind_mut() .spawn_replay_player(*id, player, 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); } } ReplayEvent::Respawn(player_id, data) => { if let Some(mut character) = self.player_characters.remove(player_id) { character.queue_free(); } if let Some(map) = &mut self.current_map && let Some(player) = self.replay.players.get(player_id) { if let Some(player) = map.bind_mut().spawn_replay_player( *player_id, player, data.clone(), ) { self.player_characters.insert(*player_id, player); } } } ReplayEvent::Jump(player_id) => { if let Some(character) = self.player_characters.get_mut(player_id) { character.bind_mut().jump(); } } ReplayEvent::Punch(player_id, to) => { if let Some(character) = self.player_characters.get_mut(player_id) { character.bind_mut().try_punch(*to, false); } } ReplayEvent::TakeDamage(player_id, damage_source, damage) => { if let Some(character) = self.player_characters.get_mut(player_id) { character .bind_mut() .take_damage(*damage_source, *damage, 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()); let weapon = character.bind().get_weapon().clone(); if let Some(weapon) = weapon && weapon.dyn_bind().get_type() != state.current_weapon { character .bind_mut() .swap_weapon(state.current_weapon, false); } } else { character.queue_free(); self.player_characters.remove(&id); } } self.current_frame_idx += 1; } self.fast_forwarding = false; 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); } if let Some(mut ball) = self.ball.take() { ball.queue_free(); } } } fn exit_tree(&mut self) { for (_, character) in &mut self.player_characters { character.queue_free(); } self.player_characters.clear(); if let Some(mut ball) = self.ball.take() { ball.queue_free(); } } } #[derive(GodotClass)] #[class(base=Node, init)] pub struct ReplayRecorder { pub time_elapsed: f64, pub replay: Replay, current_frame: ReplayFrame, recording: bool, base: Base, } #[godot_api] impl INode for ReplayRecorder { fn ready(&mut self) { self.recording = true; self.run_deferred(|s| { s.current_frame = s.record_frame(); if let Some(game) = &Game::singleton() { for player in &game.bind().players { if let Some(team) = game.bind().get_team(player.data.team) { s.replay.players.insert( player.id(), ReplayPlayer { id: player.id(), name: player.data.name.clone(), color: team.bind().color, }, ); } } } }); } fn physics_process(&mut self, delta: f64) { if !self.recording { return; } 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); } pub fn stop_recording(&mut self) { self.recording = false; } fn record_frame(&mut self) -> ReplayFrame { let mut player_data = HashMap::new(); let mut ball_data = ReplayBall::Held; 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); } } ball_data = if let Some(ball) = &game.bind().world_ball { ReplayBall::World(ReplayBallData { transform: ball.bind().get_transform(), velocity: ball.bind().get_velocity(), }) } else { ReplayBall::Held }; } ReplayFrame { time: self.time_elapsed, player_data, ball_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 { pub id: u16, pub name: String, pub color: Color, } #[derive(Debug, Default, Clone)] pub struct ReplayFrame { time: f64, player_data: HashMap, ball_data: ReplayBall, events: Vec, } #[derive(Debug, Clone)] pub enum ReplayEvent { Death(DamageSource, u16), Shoot(u16, Vector3), Respawn(u16, ReplayPlayerState), Jump(u16), Punch(u16, Vector3), TakeDamage(u16, DamageSource, i32), } #[derive(Debug, Clone, Default)] pub enum ReplayBall { World(ReplayBallData), #[default] Held, } #[derive(Debug, Clone)] pub struct ReplayPlayerState { pub transform: NetTransform, pub movement_dir: NetVector3, pub look_up: f32, pub current_weapon: WeaponType, pub is_dead: bool, } impl Player { pub 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), is_dead: character.dyn_bind().is_dead(), }) } else { None } } } #[derive(Debug, Clone)] pub struct ReplayBallData { transform: NetTransform, velocity: NetVector3, }