integrate replay save/load
This commit is contained in:
parent
6beb6c5f9a
commit
e90f53a111
@ -10,6 +10,8 @@ pub enum ReadError{
|
|||||||
StrafesNET(strafesnet_snf::Error),
|
StrafesNET(strafesnet_snf::Error),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
StrafesNETMap(strafesnet_snf::map::Error),
|
StrafesNETMap(strafesnet_snf::map::Error),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
StrafesNETBot(strafesnet_snf::bot::Error),
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
UnknownFileFormat,
|
UnknownFileFormat,
|
||||||
}
|
}
|
||||||
@ -20,28 +22,35 @@ impl std::fmt::Display for ReadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for ReadError{}
|
impl std::error::Error for ReadError{}
|
||||||
|
|
||||||
pub enum DataStructure{
|
enum Format{
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
Roblox(strafesnet_rbx_loader::Model),
|
Roblox(strafesnet_rbx_loader::Model),
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
Source(strafesnet_bsp_loader::Bsp),
|
Source(strafesnet_bsp_loader::Bsp),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
StrafesNET(strafesnet_common::map::CompleteMap),
|
SNFM(strafesnet_common::map::CompleteMap),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
SNFB(strafesnet_snf::bot::Segment),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R:Read+std::io::Seek>(input:R)->Result<DataStructure,ReadError>{
|
pub fn read<R:Read+std::io::Seek>(input:R)->Result<Format,ReadError>{
|
||||||
let mut buf=std::io::BufReader::new(input);
|
let mut buf=std::io::BufReader::new(input);
|
||||||
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
||||||
match &peek[0..4]{
|
match &peek[0..4]{
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
b"<rob"=>Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)),
|
b"<rob"=>Ok(Format::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)),
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)),
|
b"VBSP"=>Ok(Format::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
b"SNFM"=>Ok(DataStructure::StrafesNET(
|
b"SNFM"=>Ok(Format::SNFM(
|
||||||
strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)?
|
strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)?
|
||||||
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
||||||
)),
|
)),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
b"SNFB"=>Ok(Format::SNFB(
|
||||||
|
strafesnet_snf::read_bot(buf).map_err(ReadError::StrafesNET)?
|
||||||
|
.read_all().map_err(ReadError::StrafesNETBot)?
|
||||||
|
)),
|
||||||
_=>Err(ReadError::UnknownFileFormat),
|
_=>Err(ReadError::UnknownFileFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,14 +68,23 @@ impl std::fmt::Display for LoadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for LoadError{}
|
impl std::error::Error for LoadError{}
|
||||||
|
|
||||||
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
pub enum Format2{
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
Map(strafesnet_common::map::CompleteMap),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
Bot(strafesnet_snf::bot::Segment),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<Format2,LoadError>{
|
||||||
//blocking because it's simpler...
|
//blocking because it's simpler...
|
||||||
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
||||||
match read(file).map_err(LoadError::ReadError)?{
|
match read(file).map_err(LoadError::ReadError)?{
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
DataStructure::StrafesNET(map)=>Ok(map),
|
Format::SNFB(bot)=>Ok(Format2::Bot(bot)),
|
||||||
|
#[cfg(feature="snf")]
|
||||||
|
Format::SNFM(map)=>Ok(Format2::Map(map)),
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
DataStructure::Roblox(model)=>{
|
Format::Roblox(model)=>{
|
||||||
let mut place=model.into_place();
|
let mut place=model.into_place();
|
||||||
place.run_scripts();
|
place.run_scripts();
|
||||||
|
|
||||||
@ -99,10 +117,10 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::Co
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(map)
|
Ok(Format2::Map(map))
|
||||||
},
|
},
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
DataStructure::Source(bsp)=>{
|
Format::Source(bsp)=>{
|
||||||
let mut loader=strafesnet_deferred_loader::source_legacy();
|
let mut loader=strafesnet_deferred_loader::source_legacy();
|
||||||
|
|
||||||
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
||||||
@ -138,7 +156,7 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::Co
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(map)
|
Ok(Format2::Map(map))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ pub enum Instruction{
|
|||||||
Render,
|
Render,
|
||||||
Resize(winit::dpi::PhysicalSize<u32>),
|
Resize(winit::dpi::PhysicalSize<u32>),
|
||||||
ChangeMap(strafesnet_common::map::CompleteMap),
|
ChangeMap(strafesnet_common::map::CompleteMap),
|
||||||
|
LoadReplay(strafesnet_snf::bot::Segment),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<'a>(
|
pub fn new<'a>(
|
||||||
@ -69,6 +70,9 @@ pub fn new<'a>(
|
|||||||
run_session_instruction!(ins.time,SessionInstruction::Input(SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId::MAIN,strafesnet_common::gameplay_modes::StageId::FIRST))));
|
run_session_instruction!(ins.time,SessionInstruction::Input(SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId::MAIN,strafesnet_common::gameplay_modes::StageId::FIRST))));
|
||||||
run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map));
|
run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map));
|
||||||
},
|
},
|
||||||
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ pub enum Instruction<'a>{
|
|||||||
Control(SessionControlInstruction),
|
Control(SessionControlInstruction),
|
||||||
Playback(SessionPlaybackInstruction),
|
Playback(SessionPlaybackInstruction),
|
||||||
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
||||||
|
LoadReplay(strafesnet_snf::bot::Segment),
|
||||||
Idle,
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ pub enum SessionControlInstruction{
|
|||||||
// copy the current session simulation recording into a replay and view it
|
// copy the current session simulation recording into a replay and view it
|
||||||
CopyRecordingIntoReplayAndSpectate,
|
CopyRecordingIntoReplayAndSpectate,
|
||||||
StopSpectate,
|
StopSpectate,
|
||||||
|
SaveReplay,
|
||||||
}
|
}
|
||||||
pub enum SessionPlaybackInstruction{
|
pub enum SessionPlaybackInstruction{
|
||||||
SkipForward,
|
SkipForward,
|
||||||
@ -87,6 +89,11 @@ pub struct Recording{
|
|||||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
}
|
}
|
||||||
impl Recording{
|
impl Recording{
|
||||||
|
fn new(
|
||||||
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
|
)->Self{
|
||||||
|
Self{instructions}
|
||||||
|
}
|
||||||
fn clear(&mut self){
|
fn clear(&mut self){
|
||||||
self.instructions.clear();
|
self.instructions.clear();
|
||||||
}
|
}
|
||||||
@ -281,6 +288,16 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
}
|
}
|
||||||
_=self.simulation.timer.set_paused(ins.time,false);
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
},
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
let file=std::fs::File::create(format!("{}.snfb",ins.time)).unwrap();
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),replay.recording.instructions).unwrap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
||||||
match &self.view_state{
|
match &self.view_state{
|
||||||
ViewState::Play=>{
|
ViewState::Play=>{
|
||||||
@ -339,6 +356,30 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
self.clear_recording();
|
self.clear_recording();
|
||||||
self.change_map(complete_map);
|
self.change_map(complete_map);
|
||||||
},
|
},
|
||||||
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
// pause simulation
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,true);
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let recording=Recording::new(bot.instructions);
|
||||||
|
|
||||||
|
// create timer starting at first instruction (or zero if the list is empty)
|
||||||
|
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
let timer=Timer::unpaused(ins.time,new_time);
|
||||||
|
|
||||||
|
// create default physics state
|
||||||
|
let simulation=Simulation::new(timer,Default::default());
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
let bot_id=BotId(self.replays.len() as u32);
|
||||||
|
self.replays.insert(bot_id,Replay::new(
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
));
|
||||||
|
|
||||||
|
// begin spectate
|
||||||
|
self.view_state=ViewState::Replay(bot_id);
|
||||||
|
},
|
||||||
Instruction::Idle=>{
|
Instruction::Idle=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
||||||
// this just refreshes the replays
|
// this just refreshes the replays
|
||||||
|
@ -215,7 +215,7 @@ pub fn setup_and_start(title:&str){
|
|||||||
setup_context,
|
setup_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(arg)=std::env::args().nth(1){
|
for arg in std::env::args().skip(1){
|
||||||
let path=std::path::PathBuf::from(arg);
|
let path=std::path::PathBuf::from(arg);
|
||||||
window_thread.send(TimedInstruction{
|
window_thread.send(TimedInstruction{
|
||||||
time:integer::Time::ZERO,
|
time:integer::Time::ZERO,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use strafesnet_common::instruction::TimedInstruction;
|
use strafesnet_common::instruction::TimedInstruction;
|
||||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
|
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
|
||||||
|
use crate::file::Format2;
|
||||||
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
|
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
|
||||||
use crate::session::{SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
|
use crate::session::{SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
|
||||||
|
|
||||||
@ -29,8 +30,9 @@ impl WindowContext<'_>{
|
|||||||
match event{
|
match event{
|
||||||
winit::event::WindowEvent::DroppedFile(path)=>{
|
winit::event::WindowEvent::DroppedFile(path)=>{
|
||||||
match crate::file::load(path.as_path()){
|
match crate::file::load(path.as_path()){
|
||||||
Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
|
Ok(Format2::Map(map))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
|
||||||
Err(e)=>println!("Failed to load map: {e}"),
|
Ok(Format2::Bot(bot))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::LoadReplay(bot)}).unwrap(),
|
||||||
|
Err(e)=>println!("Failed to load file: {e}"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
winit::event::WindowEvent::Focused(state)=>{
|
winit::event::WindowEvent::Focused(state)=>{
|
||||||
@ -153,6 +155,7 @@ impl WindowContext<'_>{
|
|||||||
"F"|"f"=>input_misc!(PracticeFly,s),
|
"F"|"f"=>input_misc!(PracticeFly,s),
|
||||||
"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
|
"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
|
||||||
"X"|"x"=>session_ctrl!(StopSpectate,s),
|
"X"|"x"=>session_ctrl!(StopSpectate,s),
|
||||||
|
"N"|"n"=>session_ctrl!(SaveReplay,s),
|
||||||
_=>None,
|
_=>None,
|
||||||
},
|
},
|
||||||
_=>None,
|
_=>None,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user