319 lines
9.7 KiB
Rust
319 lines
9.7 KiB
Rust
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<Gd<Map>>,
|
|
current_frame_idx: usize,
|
|
player_characters: HashMap<u16, Gd<RemotePlayer>>,
|
|
ball: Option<Gd<Ball>>,
|
|
|
|
pub current_time: f64,
|
|
|
|
pub spectated_player_id: u16,
|
|
|
|
pub fast_forwarding: bool,
|
|
|
|
base: Base<Node>,
|
|
}
|
|
|
|
#[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_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 {
|
|
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);
|
|
}
|
|
}
|
|
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 {
|
|
if let Some(player) =
|
|
map.bind_mut().spawn_replay_player(*player_id, 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
|
|
base: Base<Node>,
|
|
}
|
|
|
|
#[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();
|
|
|
|
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<u16, ReplayPlayer>,
|
|
frames: Vec<ReplayFrame>,
|
|
}
|
|
|
|
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<u16, ReplayPlayerState>,
|
|
ball_data: ReplayBall,
|
|
events: Vec<ReplayEvent>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum ReplayEvent {
|
|
Death(DamageSource, u16),
|
|
Shoot(u16, Vector3),
|
|
Respawn(u16, ReplayPlayerState),
|
|
Jump(u16),
|
|
}
|
|
|
|
#[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<ReplayPlayerState> {
|
|
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,
|
|
}
|