skullball/rust/src/replay.rs

246 lines
6.7 KiB
Rust

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<Gd<Map>>,
current_frame_idx: usize,
player_characters: HashMap<u16, Gd<RemotePlayer>>,
pub current_time: f64,
pub spectated_player_id: u16,
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 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<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();
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<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>,
events: Vec<ReplayEvent>,
}
#[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<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),
})
} else {
None
}
}
}
#[derive(Debug)]
pub struct ReplayBallData {
transform: NetTransform,
velocity: NetVector3,
}