Compare commits
17 Commits
ratio
...
disable-no
| Author | SHA1 | Date | |
|---|---|---|---|
|
6b29e5b335
|
|||
|
c3676349b0
|
|||
|
299a2b8051
|
|||
|
a4c4f20bad
|
|||
|
3644dd7f15
|
|||
|
197f840246
|
|||
|
c3cca22839
|
|||
|
81a158d08f
|
|||
|
7a421d1eab
|
|||
|
e821fb6982
|
|||
|
cb71fa7257
|
|||
|
51fdc72e0e
|
|||
|
29e49587ff
|
|||
|
d03f84c893
|
|||
|
48a7b06b71
|
|||
|
43cc9b6416
|
|||
|
e4433cf06c
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -1632,9 +1632,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_common"
|
||||
version = "0.8.1"
|
||||
version = "0.8.5"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "cba84ffce9c31b70b5dcaab0b9e946fe34ca4d6613a4c83980745b47dfc86edf"
|
||||
checksum = "28b5e3829a22fa11cbf7bde7ff248cc3562f80d571907c2957f9cba213e29fe5"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 2.10.0",
|
||||
@@ -1661,9 +1661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strafesnet_roblox_bot_file"
|
||||
version = "0.9.0"
|
||||
version = "0.9.3"
|
||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
checksum = "3ee6a8c592794145fdbbb2fbdaedbbc5813373e2e2bbbb9bac6ab7944775cc0a"
|
||||
checksum = "423d931e4f4f97406a86519a22172d1fc0d5b9d8c3b2d4553ae89b641bbd555c"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags 2.10.0",
|
||||
|
||||
@@ -12,7 +12,7 @@ strip = true
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.dependencies]
|
||||
strafesnet_common = { version = "0.8.1", registry = "strafesnet" }
|
||||
strafesnet_common = { version = "0.8.5", registry = "strafesnet" }
|
||||
strafesnet_graphics = { version = "0.0.2", registry = "strafesnet" }
|
||||
strafesnet_roblox_bot_file = { version = "0.9.0", registry = "strafesnet" }
|
||||
strafesnet_roblox_bot_file = { version = "0.9.3", registry = "strafesnet" }
|
||||
strafesnet_snf = { version = "0.3.2", registry = "strafesnet" }
|
||||
|
||||
14
README.md
14
README.md
@@ -1,23 +1,29 @@
|
||||
How to clone this repository:
|
||||
### How to clone this repository:
|
||||
- Install git
|
||||
- Install git lfs (for test files)
|
||||
```
|
||||
git clone https://git.itzana.me/StrafesNET/roblox-bot-player
|
||||
cd roblox-bot-player
|
||||
git lfs pull
|
||||
```
|
||||
|
||||
How to build the wasm module:
|
||||
### How to build the wasm module:
|
||||
- Install rust
|
||||
- Install wasm-pack
|
||||
```
|
||||
cd wasm-module
|
||||
wasm-pack build --target web --out-dir ../web-demo/pkg
|
||||
```
|
||||
|
||||
How to serve the web demo (requires wasm module):
|
||||
### How to serve the web demo (requires wasm module):
|
||||
- Install python3 or use your favourite http server
|
||||
```
|
||||
cd web-demo
|
||||
python3 -m http.server
|
||||
```
|
||||
|
||||
How to run the native player:
|
||||
### How to run the native player:
|
||||
- Install rust
|
||||
```
|
||||
cd native-player
|
||||
cargo run --release -- ../web-demo/bhop_marble_5692093612.snfm ../web-demo/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d.qbot
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use strafesnet_common::timer::{TimerFixed,Realtime,Unpaused};
|
||||
use strafesnet_common::run::{Time as RunTime};
|
||||
use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner};
|
||||
use strafesnet_roblox_bot_file::v0;
|
||||
use strafesnet_common::physics::{Time as PhysicsTime};
|
||||
|
||||
use crate::head::{Time as PlaybackTime,TimeInner as PlaybackTimeInner};
|
||||
|
||||
/// A loaded bot file.
|
||||
pub struct CompleteBot{
|
||||
//Instructions
|
||||
timelines:v0::Block,
|
||||
time_base:PhysicsTime,
|
||||
timer:TimerFixed<Realtime<PlaybackTimeInner,PhysicsTimeInner>,Unpaused>,
|
||||
duration:PhysicsTime,
|
||||
}
|
||||
impl CompleteBot{
|
||||
@@ -13,16 +17,16 @@ impl CompleteBot{
|
||||
pub fn new(
|
||||
timelines:v0::Block,
|
||||
)->Self{
|
||||
let first=timelines.output_events.first().unwrap();
|
||||
let last=timelines.output_events.last().unwrap();
|
||||
let start=crate::time::from_float(timelines.output_events.first().unwrap().time).unwrap();
|
||||
let end=crate::time::from_float(timelines.output_events.last().unwrap().time).unwrap();
|
||||
Self{
|
||||
time_base:crate::time::from_float(first.time).unwrap(),
|
||||
duration:crate::time::from_float(last.time-first.time).unwrap(),
|
||||
timer:TimerFixed::new(PlaybackTime::ZERO,start),
|
||||
duration:end-start,
|
||||
timelines,
|
||||
}
|
||||
}
|
||||
pub const fn time_base(&self)->PhysicsTime{
|
||||
self.time_base
|
||||
pub fn time(&self,time:PlaybackTime)->PhysicsTime{
|
||||
self.timer.time(time)
|
||||
}
|
||||
pub const fn duration(&self)->PhysicsTime{
|
||||
self.duration
|
||||
@@ -30,7 +34,7 @@ impl CompleteBot{
|
||||
pub const fn timelines(&self)->&v0::Block{
|
||||
&self.timelines
|
||||
}
|
||||
pub fn run_duration(&self,mode_id:v0::ModeID)->Option<PhysicsTime>{
|
||||
pub fn run_duration(&self,mode_id:v0::ModeID)->Option<RunTime>{
|
||||
let mut it=self.timelines.run_events.iter().rev();
|
||||
let end=it.find_map(|event|match &event.event{
|
||||
v0::RunEvent::Finish(run_start_event) if run_start_event.mode==mode_id=>Some(event.time),
|
||||
|
||||
@@ -9,8 +9,7 @@ pub struct Graphics{
|
||||
}
|
||||
impl Graphics{
|
||||
pub fn new(device:wgpu::Device,queue:wgpu::Queue,config:wgpu::SurfaceConfiguration)->Self{
|
||||
let mut graphics=strafesnet_graphics::graphics::GraphicsState::new(&device,&queue,&config);
|
||||
graphics.resize(&device,&config,glam::Vec2::ONE);
|
||||
let graphics=strafesnet_graphics::graphics::GraphicsState::new(&device,&queue,&config);
|
||||
Self{
|
||||
graphics,
|
||||
device,
|
||||
@@ -22,11 +21,11 @@ impl Graphics{
|
||||
self.graphics.clear();
|
||||
self.graphics.generate_models(&self.device,&self.queue,map);
|
||||
}
|
||||
pub fn resize(&mut self,surface:&wgpu::Surface<'_>,size:glam::UVec2){
|
||||
pub fn resize(&mut self,surface:&wgpu::Surface<'_>,size:glam::UVec2,fov:glam::Vec2){
|
||||
self.config.width=size.x.max(1);
|
||||
self.config.height=size.y.max(1);
|
||||
surface.configure(&self.device,&self.config);
|
||||
self.graphics.resize(&self.device,&self.config,glam::Vec2::ONE);
|
||||
self.graphics.resize(&self.device,&self.config,fov);
|
||||
}
|
||||
pub fn render(&mut self,surface:&wgpu::Surface<'_>,pos:glam::Vec3,angles:glam::Vec2){
|
||||
//this has to go deeper somehow
|
||||
|
||||
102
lib/src/head.rs
102
lib/src/head.rs
@@ -1,7 +1,6 @@
|
||||
use glam::Vec3Swizzles;
|
||||
use strafesnet_common::timer::{Scaled,Timer,TimerState};
|
||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||
use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner};
|
||||
use strafesnet_roblox_bot_file::v0::{EventType,Head,Timed};
|
||||
|
||||
use crate::bot::CompleteBot;
|
||||
@@ -11,71 +10,65 @@ fn vector3_to_glam(v:&strafesnet_roblox_bot_file::v0::Vector3)->glam::Vec3{
|
||||
glam::vec3(v.x,v.y,v.z)
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||
pub enum TimeInner{}
|
||||
pub type Time=strafesnet_common::integer::Time<TimeInner>;
|
||||
|
||||
/// A playback context. Advance time and then generate a camera position to pass to the renderer.
|
||||
pub struct PlaybackHead{
|
||||
head:Head,
|
||||
loop_offset:PhysicsTime,
|
||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||
timer:Timer<Scaled<SessionTimeInner,TimeInner>>,
|
||||
state:PlaybackState,
|
||||
}
|
||||
const HEAD_NO_CRASH:Head={
|
||||
let mut head=Head::new();
|
||||
// push one output event so that output-1 doesn't underflow
|
||||
head.push(EventType::Output);
|
||||
head
|
||||
};
|
||||
impl PlaybackHead{
|
||||
pub fn new(time:SessionTime)->Self{
|
||||
let timer=Timer::unpaused(time,PhysicsTime::ZERO);
|
||||
pub fn new(bot:&CompleteBot,time:SessionTime)->Self{
|
||||
let timer=Timer::unpaused(time,Time::ZERO);
|
||||
let head=Head::after_time(bot.timelines(),bot.time(Time::ZERO).into());
|
||||
let mut state=PlaybackState::new();
|
||||
state.process_head(bot.timelines(),&head);
|
||||
Self{
|
||||
head:HEAD_NO_CRASH,
|
||||
loop_offset:PhysicsTime::ZERO,
|
||||
head,
|
||||
timer,
|
||||
state:PlaybackState::new(),
|
||||
state,
|
||||
}
|
||||
}
|
||||
pub const fn state(&self)->&PlaybackState{
|
||||
&self.state
|
||||
}
|
||||
pub fn time(&self,bot:&CompleteBot,time:SessionTime)->PhysicsTime{
|
||||
bot.time_base()+self.timer.time(time)+self.loop_offset
|
||||
pub fn time(&self,time:SessionTime)->Time{
|
||||
self.timer.time(time)
|
||||
}
|
||||
pub fn timer(&self)->&Timer<Scaled<SessionTimeInner,TimeInner>>{
|
||||
&self.timer
|
||||
}
|
||||
pub fn set_paused(&mut self,time:SessionTime,paused:bool){
|
||||
_=self.timer.set_paused(time,paused);
|
||||
}
|
||||
pub fn set_time(&mut self,bot:&CompleteBot,time:SessionTime,new_time:Time){
|
||||
let new_time=new_time.rem_euclid(bot.duration().coerce());
|
||||
self.timer.set_time(time,new_time);
|
||||
// reset head
|
||||
self.head=Head::after_time(bot.timelines(),bot.time(new_time).into());
|
||||
|
||||
self.state=PlaybackState::new();
|
||||
self.state.process_head(bot.timelines(),&self.head);
|
||||
}
|
||||
pub fn get_scale(&self)->strafesnet_common::integer::Ratio64{
|
||||
self.timer.get_scale()
|
||||
}
|
||||
pub fn set_scale(&mut self,time:SessionTime,new_scale:strafesnet_common::integer::Ratio64){
|
||||
self.timer.set_scale(time,new_scale);
|
||||
}
|
||||
pub fn next_event(&self,bot:&CompleteBot)->Option<Timed<EventType>>{
|
||||
self.head.next_event(bot.timelines())
|
||||
}
|
||||
pub fn process_event(&mut self,bot:&CompleteBot,event_type:EventType){
|
||||
self.state.process_event(bot,event_type,self.head.get_event_index(event_type));
|
||||
self.state.process_event(bot.timelines(),event_type,self.head.get_event_index(event_type));
|
||||
self.head.push(event_type);
|
||||
}
|
||||
pub fn set_paused(&mut self,time:SessionTime,paused:bool){
|
||||
_=self.timer.set_paused(time,paused);
|
||||
}
|
||||
pub fn seek_to(&mut self,time:SessionTime,new_time:PhysicsTime){
|
||||
self.timer.set_time(time,new_time);
|
||||
self.loop_offset=PhysicsTime::ZERO;
|
||||
// reset head
|
||||
self.head=HEAD_NO_CRASH;
|
||||
}
|
||||
pub fn seek_backward(&mut self,time_offset:SessionTime){
|
||||
let (mut state,paused)=self.timer.clone().into_state();
|
||||
let offset=state.get_time(-time_offset).coerce();
|
||||
state.set_offset(offset);
|
||||
self.timer=Timer::from_state(state,paused);
|
||||
// reset head
|
||||
self.head=HEAD_NO_CRASH;
|
||||
}
|
||||
pub fn seek_forward(&mut self,time_offset:SessionTime){
|
||||
let (mut state,paused)=self.timer.clone().into_state();
|
||||
let offset=state.get_time(time_offset).coerce();
|
||||
state.set_offset(offset);
|
||||
self.timer=Timer::from_state(state,paused);
|
||||
}
|
||||
pub fn set_scale(&mut self,time:SessionTime,new_scale:strafesnet_common::integer::Ratio64){
|
||||
self.timer.set_scale(time,new_scale);
|
||||
}
|
||||
pub fn advance_time(&mut self,bot:&CompleteBot,time:SessionTime){
|
||||
let mut simulation_time=self.time(bot,time);
|
||||
let mut time_float=simulation_time.get() as f64/PhysicsTime::ONE_SECOND.get() as f64;
|
||||
let mut simulation_time=bot.time(self.time(time));
|
||||
let mut time_float=simulation_time.into();
|
||||
loop{
|
||||
match self.next_event(bot){
|
||||
Some(next_event)=>{
|
||||
@@ -87,16 +80,25 @@ impl PlaybackHead{
|
||||
},
|
||||
None=>{
|
||||
//reset playback
|
||||
self.head=HEAD_NO_CRASH;
|
||||
self.loop_offset-=bot.duration();
|
||||
self.head=Head::after_time(bot.timelines(),bot.time(Time::ZERO).into());
|
||||
self.state=PlaybackState::new();
|
||||
self.state.process_head(bot.timelines(),&self.head);
|
||||
|
||||
// hack to wind back timer offset without precise session timestamp
|
||||
let (mut state,paused)=self.timer.clone().into_state();
|
||||
let offset=state.get_offset()-bot.duration().coerce();
|
||||
state.set_offset(offset);
|
||||
self.timer=Timer::from_state(state,paused);
|
||||
|
||||
// update loop variables
|
||||
simulation_time-=bot.duration();
|
||||
time_float=simulation_time.get() as f64/PhysicsTime::ONE_SECOND.get() as f64;
|
||||
time_float=simulation_time.into();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_position_angles(&self,bot:&CompleteBot,time:SessionTime)->(glam::Vec3,glam::Vec2){
|
||||
let time=self.time(bot,time);
|
||||
let time=bot.time(self.time(time));
|
||||
let event0=&bot.timelines().output_events[self.head.get_event_index(EventType::Output)-1];
|
||||
let event1=&bot.timelines().output_events[self.head.get_event_index(EventType::Output)];
|
||||
let p0=vector3_to_glam(&event0.event.position);
|
||||
@@ -107,7 +109,7 @@ impl PlaybackHead{
|
||||
// let a1=vector3_to_glam(&event1.event.acceleration);
|
||||
let t0=event0.time;
|
||||
let t1=event1.time;
|
||||
let time_float=time.get() as f64/PhysicsTime::ONE_SECOND.get() as f64;
|
||||
let time_float:f64=time.into();
|
||||
let t=((time_float-t0)/(t1-t0)) as f32;
|
||||
let p=p0.lerp(p1,t);
|
||||
// let v=v0.lerp(v1,t);
|
||||
|
||||
@@ -4,8 +4,6 @@ use strafesnet_common::run;
|
||||
use strafesnet_common::physics::Time as PhysicsTime;
|
||||
use strafesnet_roblox_bot_file::v0;
|
||||
|
||||
use crate::bot::CompleteBot;
|
||||
|
||||
pub struct Run{
|
||||
run:run::RunState,
|
||||
flag_reason:Option<v0::FlagReason>,
|
||||
@@ -84,9 +82,9 @@ impl PlaybackState{
|
||||
style:v0::Style::Autohop,
|
||||
absolute_sensitivity_enabled:false,
|
||||
fov_y:1.0,
|
||||
sens_x:1.0,
|
||||
sens_x:0.3,
|
||||
vertical_sensitivity_multipler:1.0,
|
||||
turn_speed:1.0,
|
||||
turn_speed:core::f64::consts::TAU/0.715588,
|
||||
}
|
||||
}
|
||||
pub fn get_run(&self,mode:v0::ModeID)->Option<&Run>{
|
||||
@@ -111,13 +109,13 @@ impl PlaybackState{
|
||||
self.style=run_event_prepare.style;
|
||||
},
|
||||
v0::RunEvent::Start(run_event_zone)=>{
|
||||
let time=PhysicsTime::raw((event.time*PhysicsTime::ONE_SECOND.get() as f64) as i64);
|
||||
let time=crate::time::from_float(event.time).unwrap();
|
||||
if let Some(run)=self.runs.get_mut(&run_event_zone.mode){
|
||||
_=run.run.start(time);
|
||||
}
|
||||
},
|
||||
v0::RunEvent::Finish(run_event_zone)=>{
|
||||
let time=PhysicsTime::raw((event.time*PhysicsTime::ONE_SECOND.get() as f64) as i64);
|
||||
let time=crate::time::from_float(event.time).unwrap();
|
||||
if let Some(run)=self.runs.get_mut(&run_event_zone.mode){
|
||||
_=run.run.finish(time);
|
||||
}
|
||||
@@ -179,7 +177,7 @@ impl PlaybackState{
|
||||
fn push_setting(&mut self,event:&v0::SettingEvent){
|
||||
match event{
|
||||
v0::SettingEvent::FieldOfView(setting_event_field_of_view)=>{
|
||||
self.fov_y=setting_event_field_of_view.fov;
|
||||
self.fov_y=(setting_event_field_of_view.fov*0.5).to_radians().tan();
|
||||
},
|
||||
v0::SettingEvent::Sensitivity(setting_event_sensitivity)=>{
|
||||
self.sens_x=setting_event_sensitivity.sensitivity;
|
||||
@@ -195,16 +193,57 @@ impl PlaybackState{
|
||||
},
|
||||
}
|
||||
}
|
||||
pub(crate) fn process_event(&mut self,bot:&CompleteBot,event_type:v0::EventType,event_index:usize){
|
||||
pub(crate) fn process_head(&mut self,block:&v0::Block,head:&v0::Head){
|
||||
// The whole point of this is to avoid running the realtime events!
|
||||
/*
|
||||
for event in &block.input_events[0..head.get_event_index(v0::EventType::Input)]{
|
||||
self.push_input(&event.event);
|
||||
}
|
||||
for event in &block.output_events[0..head.get_event_index(v0::EventType::Output)]{
|
||||
self.push_output(&event.event);
|
||||
}
|
||||
for event in &bot.sound_events[0..head.get_event_index(v0::EventType::Sound)]{
|
||||
self.push_sound(&event.event);
|
||||
}
|
||||
*/
|
||||
// for event in &bot.world_events[0..head.get_event_index(v0::EventType::World)]{
|
||||
// self.push_world(&event.event);
|
||||
// }
|
||||
for event in &block.gravity_events[0..head.get_event_index(v0::EventType::Gravity)]{
|
||||
self.push_gravity(&event.event);
|
||||
}
|
||||
for event in &block.run_events[0..head.get_event_index(v0::EventType::Run)]{
|
||||
self.push_run(event);
|
||||
}
|
||||
// for event in &bot.camera_events[0..head.get_event_index(v0::EventType::Camera)]{
|
||||
// self.push_camera(&event.event);
|
||||
// }
|
||||
for event in &block.setting_events[0..head.get_event_index(v0::EventType::Setting)]{
|
||||
self.push_setting(&event.event);
|
||||
}
|
||||
}
|
||||
pub(crate) fn process_event(&mut self,block:&v0::Block,event_type:v0::EventType,event_index:usize){
|
||||
match event_type{
|
||||
v0::EventType::Input=>self.push_input(&bot.timelines().input_events[event_index].event),
|
||||
v0::EventType::Output=>self.push_output(&bot.timelines().output_events[event_index].event),
|
||||
v0::EventType::Input=>self.push_input(&block.input_events[event_index].event),
|
||||
v0::EventType::Output=>self.push_output(&block.output_events[event_index].event),
|
||||
v0::EventType::Sound=>{},
|
||||
v0::EventType::World=>{},
|
||||
v0::EventType::Gravity=>self.push_gravity(&bot.timelines().gravity_events[event_index].event),
|
||||
v0::EventType::Run=>self.push_run(&bot.timelines().run_events[event_index]),
|
||||
v0::EventType::Gravity=>self.push_gravity(&block.gravity_events[event_index].event),
|
||||
v0::EventType::Run=>self.push_run(&block.run_events[event_index]),
|
||||
v0::EventType::Camera=>{},
|
||||
v0::EventType::Setting=>self.push_setting(&bot.timelines().setting_events[event_index].event),
|
||||
v0::EventType::Setting=>self.push_setting(&block.setting_events[event_index].event),
|
||||
}
|
||||
}
|
||||
pub fn get_fov_y(&self)->f64{
|
||||
let zoom_enabled=self.game_controls.contains(v0::GameControls::Zoom);
|
||||
if zoom_enabled{self.fov_y*0.2}else{self.fov_y}
|
||||
}
|
||||
pub fn get_sensitivity(&self)->(f64,f64){
|
||||
if self.absolute_sensitivity_enabled{
|
||||
(self.sens_x,self.sens_x*self.vertical_sensitivity_multipler)
|
||||
}else{
|
||||
let sens_x=self.sens_x*self.get_fov_y()/128.0;
|
||||
(sens_x,sens_x*self.vertical_sensitivity_multipler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use strafesnet_common::instruction::TimedInstruction;
|
||||
use strafesnet_common::session::Time as SessionTime;
|
||||
use strafesnet_common::physics::Time as PhysicsTime;
|
||||
use strafesnet_roblox_bot_player::{bot::CompleteBot,graphics::Graphics,head::PlaybackHead};
|
||||
use strafesnet_common::timer::TimerState;
|
||||
use strafesnet_roblox_bot_player::{bot::CompleteBot,graphics::Graphics,head::{PlaybackHead,Time as PlaybackTime}};
|
||||
|
||||
pub enum SessionControlInstruction{
|
||||
SetPaused(bool),
|
||||
@@ -28,62 +28,73 @@ fn speed_ratio(speed:i8)->strafesnet_common::integer::Ratio64{
|
||||
}
|
||||
}
|
||||
|
||||
struct Playback{
|
||||
bot:CompleteBot,
|
||||
playback_head:PlaybackHead,
|
||||
playback_speed:i8,
|
||||
}
|
||||
|
||||
pub struct PlayerWorker<'a>{
|
||||
surface:wgpu::Surface<'a>,
|
||||
graphics_thread:Graphics,
|
||||
bot:Option<CompleteBot>,
|
||||
playback_head:PlaybackHead,
|
||||
playback_speed:i8,
|
||||
playback:Option<Playback>,
|
||||
}
|
||||
impl<'a> PlayerWorker<'a>{
|
||||
pub fn new(
|
||||
surface:wgpu::Surface<'a>,
|
||||
graphics_thread:Graphics,
|
||||
)->Self{
|
||||
let playback_head=PlaybackHead::new(SessionTime::ZERO);
|
||||
Self{
|
||||
surface,
|
||||
graphics_thread,
|
||||
bot:None,
|
||||
playback_head,
|
||||
playback_speed:0,
|
||||
playback:None,
|
||||
}
|
||||
}
|
||||
pub fn send(&mut self,ins:TimedInstruction<Instruction,SessionTime>){
|
||||
match ins.instruction{
|
||||
Instruction::SessionControl(SessionControlInstruction::SetPaused(paused))=>{
|
||||
self.playback_head.set_paused(ins.time,paused);
|
||||
Instruction::SessionControl(SessionControlInstruction::SetPaused(paused))=>if let Some(playback)=&mut self.playback{
|
||||
playback.playback_head.set_paused(ins.time,paused);
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::Restart)=>{
|
||||
self.playback_head.seek_to(ins.time,PhysicsTime::ZERO);
|
||||
Instruction::SessionControl(SessionControlInstruction::Restart)=>if let Some(playback)=&mut self.playback{
|
||||
playback.playback_head.set_time(&playback.bot,ins.time,PlaybackTime::ZERO);
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::SkipForward)=>{
|
||||
self.playback_head.seek_forward(SessionTime::from_secs(2));
|
||||
Instruction::SessionControl(SessionControlInstruction::SkipForward)=>if let Some(playback)=&mut self.playback{
|
||||
let head_time=playback.playback_head.timer().clone().into_state().0.get_time(ins.time+SessionTime::from_secs(2));
|
||||
playback.playback_head.set_time(&playback.bot,ins.time,head_time);
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::SkipBack)=>{
|
||||
self.playback_head.seek_backward(SessionTime::from_secs(2));
|
||||
Instruction::SessionControl(SessionControlInstruction::SkipBack)=>if let Some(playback)=&mut self.playback{
|
||||
let head_time=playback.playback_head.timer().clone().into_state().0.get_time(ins.time-SessionTime::from_secs(2));
|
||||
playback.playback_head.set_time(&playback.bot,ins.time,head_time);
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::DecreaseTimescale)=>{
|
||||
self.playback_speed=self.playback_speed.saturating_sub(1).max(-27);
|
||||
self.playback_head.set_scale(ins.time,speed_ratio(self.playback_speed));
|
||||
Instruction::SessionControl(SessionControlInstruction::DecreaseTimescale)=>if let Some(playback)=&mut self.playback{
|
||||
playback.playback_speed=playback.playback_speed.saturating_sub(1).max(-27);
|
||||
playback.playback_head.set_scale(ins.time,speed_ratio(playback.playback_speed));
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::IncreaseTimescale)=>{
|
||||
self.playback_speed=self.playback_speed.saturating_add(1).min(27);
|
||||
self.playback_head.set_scale(ins.time,speed_ratio(self.playback_speed));
|
||||
Instruction::SessionControl(SessionControlInstruction::IncreaseTimescale)=>if let Some(playback)=&mut self.playback{
|
||||
playback.playback_speed=playback.playback_speed.saturating_add(1).min(27);
|
||||
playback.playback_head.set_scale(ins.time,speed_ratio(playback.playback_speed));
|
||||
},
|
||||
Instruction::Render=>if let Some(bot)=&self.bot{
|
||||
self.playback_head.advance_time(bot,ins.time);
|
||||
let (pos,angles)=self.playback_head.get_position_angles(bot,ins.time);
|
||||
Instruction::Render=>if let Some(playback)=&mut self.playback{
|
||||
playback.playback_head.advance_time(&playback.bot,ins.time);
|
||||
let (pos,angles)=playback.playback_head.get_position_angles(&playback.bot,ins.time);
|
||||
self.graphics_thread.render(&self.surface,pos,angles);
|
||||
},
|
||||
Instruction::Resize(physical_size)=>{
|
||||
self.graphics_thread.resize(&self.surface,glam::uvec2(physical_size.width,physical_size.height));
|
||||
Instruction::Resize(physical_size)=>if let Some(playback)=&self.playback{
|
||||
let fov_y=playback.playback_head.state().get_fov_y();
|
||||
let fov_x=fov_y*physical_size.width as f64/physical_size.height as f64;
|
||||
self.graphics_thread.resize(&self.surface,glam::uvec2(physical_size.width,physical_size.height),glam::vec2(fov_x as f32,fov_y as f32));
|
||||
},
|
||||
Instruction::ChangeMap(complete_map)=>{
|
||||
self.graphics_thread.change_map(&complete_map);
|
||||
},
|
||||
Instruction::LoadReplay(bot)=>{
|
||||
self.bot=Some(CompleteBot::new(bot));
|
||||
let bot=CompleteBot::new(bot);
|
||||
let playback_head=PlaybackHead::new(&bot,SessionTime::ZERO);
|
||||
self.playback=Some(Playback{
|
||||
bot,
|
||||
playback_head,
|
||||
playback_speed:0,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ strafesnet_snf.workspace = true
|
||||
wasm-bindgen = "0.2.108"
|
||||
wasm-bindgen-futures = "0.4.58"
|
||||
web-sys = { version = "0.3.85", features = ["HtmlCanvasElement"] }
|
||||
wgpu = "28.0.0"
|
||||
wgpu = { version = "28.0.0", features = ["web"], default-features = false }
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ["-Oz", "--enable-bulk-memory","--enable-nontrapping-float-to-int"]
|
||||
|
||||
@@ -3,7 +3,6 @@ use wasm_bindgen::JsValue;
|
||||
use strafesnet_roblox_bot_file::v0;
|
||||
use strafesnet_roblox_bot_player::{bot,head,time,graphics};
|
||||
use strafesnet_graphics::setup;
|
||||
use strafesnet_common::physics::Time as PhysicsTime;
|
||||
|
||||
// Hack to keep the code compiling,
|
||||
// SurfaceTarget::Canvas is not available in IDE for whatever reason.
|
||||
@@ -48,8 +47,8 @@ impl Graphics{
|
||||
self.graphics.render(&self.surface,pos,angles);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn resize(&mut self,width:u32,height:u32){
|
||||
self.graphics.resize(&self.surface,[width,height].into());
|
||||
pub fn resize(&mut self,width:u32,height:u32,fov_slope_x:f32,fov_slope_y:f32){
|
||||
self.graphics.resize(&self.surface,[width,height].into(),[fov_slope_x as f32,fov_slope_y as f32].into());
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn change_map(&mut self,map:&CompleteMap){
|
||||
@@ -72,12 +71,12 @@ impl CompleteBot{
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn duration(&self)->f64{
|
||||
self.bot.duration().get() as f64/PhysicsTime::ONE_SECOND.get() as f64
|
||||
self.bot.duration().into()
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn run_duration(&self,mode_id:u32)->Option<f64>{
|
||||
let mode=v0::ModeID(mode_id);
|
||||
Some(self.bot.run_duration(mode)?.get() as f64/PhysicsTime::ONE_SECOND.get() as f64)
|
||||
Some(self.bot.run_duration(mode)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,10 +105,10 @@ pub struct PlaybackHead{
|
||||
#[wasm_bindgen]
|
||||
impl PlaybackHead{
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(time:f64)->Result<Self,JsValue>{
|
||||
pub fn new(bot:&CompleteBot,time:f64)->Result<Self,JsValue>{
|
||||
let time=time::from_float(time).unwrap();
|
||||
Ok(Self{
|
||||
head:head::PlaybackHead::new(time),
|
||||
head:head::PlaybackHead::new(&bot.bot,time),
|
||||
})
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
@@ -118,12 +117,40 @@ impl PlaybackHead{
|
||||
self.head.advance_time(&bot.bot,time);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn set_paused(&mut self,time:f64,paused:bool){
|
||||
let time=time::from_float(time).unwrap();
|
||||
self.head.set_paused(time,paused);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn get_scale(&mut self)->f64{
|
||||
let scale=self.head.get_scale();
|
||||
scale.num() as f64/scale.den() as f64
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn set_scale(&mut self,time:f64,scale:f64){
|
||||
let time=time::from_float(time).unwrap();
|
||||
self.head.set_scale(time,scale.try_into().unwrap());
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn get_head_time(&self,time:f64)->f64{
|
||||
let time=time::from_float(time).unwrap();
|
||||
let time=self.head.time(time);
|
||||
time.into()
|
||||
}
|
||||
/// Set the playback head position to new_time.
|
||||
#[wasm_bindgen]
|
||||
pub fn set_head_time(&mut self,bot:&CompleteBot,time:f64,new_time:f64){
|
||||
let time=time::from_float(time).unwrap();
|
||||
let new_time=time::from_float(new_time).unwrap();
|
||||
self.head.set_time(&bot.bot,time,new_time);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn get_run_time(&self,bot:&CompleteBot,time:f64,mode_id:u32)->Option<f64>{
|
||||
let time=time::from_float(time).unwrap();
|
||||
let time=self.head.time(&bot.bot,time);
|
||||
let time=bot.bot.time(self.head.time(time));
|
||||
let mode=v0::ModeID(mode_id);
|
||||
let run_time=self.head.state().get_run(mode)?.time(time);
|
||||
Some(run_time.get() as f64/strafesnet_common::run::Time::ONE_SECOND.get() as f64)
|
||||
Some(run_time.into())
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn is_run_in_progress(&self,mode_id:u32)->Option<bool>{
|
||||
@@ -136,29 +163,7 @@ impl PlaybackHead{
|
||||
Some(self.head.state().get_run(mode)?.is_finished())
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn set_paused(&mut self,time:f64,paused:bool){
|
||||
let time=time::from_float(time).unwrap();
|
||||
self.head.set_paused(time,paused);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn set_scale(&mut self,time:f64,scale_num:i64,scale_den:u64){
|
||||
let time=time::from_float(time).unwrap();
|
||||
self.head.set_scale(time,strafesnet_common::integer::Ratio64::new(scale_num,scale_den).unwrap());
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn seek_to(&mut self,time:f64,new_time:f64){
|
||||
let time=time::from_float(time).unwrap();
|
||||
let new_time=time::from_float(new_time).unwrap();
|
||||
self.head.seek_to(time,new_time);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn seek_forward(&mut self,time_offset:f64){
|
||||
let time_offset=time::from_float(time_offset).unwrap();
|
||||
self.head.seek_forward(time_offset);
|
||||
}
|
||||
#[wasm_bindgen]
|
||||
pub fn seek_backward(&mut self,time_offset:f64){
|
||||
let time_offset=time::from_float(time_offset).unwrap();
|
||||
self.head.seek_backward(time_offset);
|
||||
pub fn get_fov_slope_y(&self)->f64{
|
||||
self.head.state().get_fov_y()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const canvas = document.getElementById("viewport");
|
||||
const graphics = await setup_graphics(canvas);
|
||||
const bot = new CompleteBot(new Uint8Array(await b.arrayBuffer()));
|
||||
const map = new CompleteMap(new Uint8Array(await m.arrayBuffer()));
|
||||
const playback = new PlaybackHead(0);
|
||||
const playback = new PlaybackHead(bot, 0);
|
||||
|
||||
graphics.change_map(map);
|
||||
|
||||
@@ -44,56 +44,54 @@ function elapsed() {
|
||||
const control_speed = document.getElementById("control_speed");
|
||||
|
||||
var paused = false;
|
||||
var speed = 0;
|
||||
function set_speed(new_speed) {
|
||||
speed = new_speed;
|
||||
var speed_num = null;
|
||||
var speed_den = null;
|
||||
if (new_speed < 0) {
|
||||
speed_num = BigInt(4) ** BigInt(-new_speed);
|
||||
speed_den = BigInt(5) ** BigInt(-new_speed);
|
||||
} else {
|
||||
speed_num = BigInt(5) ** BigInt(new_speed);
|
||||
speed_den = BigInt(4) ** BigInt(new_speed);
|
||||
}
|
||||
playback.set_scale(elapsed(), speed_num, speed_den);
|
||||
control_speed.value = `${((5 / 4) ** new_speed).toPrecision(3)}x`;
|
||||
var scale = 1;
|
||||
function set_scale(new_scale) {
|
||||
scale = new_scale;
|
||||
playback.set_scale(elapsed(), scale);
|
||||
control_speed.value = `${scale.toPrecision(3)}x`;
|
||||
}
|
||||
|
||||
const SEEK_DURATION = 1.0;
|
||||
|
||||
// Controls
|
||||
document.getElementById("control_reset").addEventListener("click", (e) => {
|
||||
playback.seek_to(elapsed(), 0.0);
|
||||
playback.set_head_time(bot, elapsed(), 0.0);
|
||||
});
|
||||
document.getElementById("control_pause").addEventListener("click", (e) => {
|
||||
paused = !paused;
|
||||
playback.set_paused(elapsed(), paused);
|
||||
});
|
||||
document.getElementById("control_forward").addEventListener("click", (e) => {
|
||||
playback.seek_forward(2.0);
|
||||
const time_now = elapsed();
|
||||
const playback_time = playback.get_head_time(time_now);
|
||||
const time_offset = playback.get_scale() * SEEK_DURATION;
|
||||
playback.set_head_time(bot, time_now, playback_time + time_offset);
|
||||
});
|
||||
document.getElementById("control_backward").addEventListener("click", (e) => {
|
||||
playback.seek_backward(2.0);
|
||||
const time_now = elapsed();
|
||||
const playback_time = playback.get_head_time(time_now);
|
||||
const time_offset = playback.get_scale() * SEEK_DURATION;
|
||||
playback.set_head_time(bot, time_now, playback_time - time_offset);
|
||||
});
|
||||
document.getElementById("control_slower").addEventListener("click", (e) => {
|
||||
set_speed(Math.max(speed - 1, -27));
|
||||
set_scale((scale * 4) / 5);
|
||||
});
|
||||
const regex = new RegExp("^([^x]*)x?$");
|
||||
control_speed.addEventListener("change", (e) => {
|
||||
const parsed = regex.exec(e.target.value);
|
||||
if (!parsed) {
|
||||
set_speed(0);
|
||||
set_scale(1);
|
||||
return;
|
||||
}
|
||||
const input = Number(parsed.at(1));
|
||||
if (Number.isNaN(input)) {
|
||||
set_speed(0);
|
||||
set_scale(1);
|
||||
return;
|
||||
}
|
||||
const rounded = Math.round(Math.log(input) / Math.log(5 / 4));
|
||||
set_speed(Math.max(-27, Math.min(27, rounded)));
|
||||
set_scale(input);
|
||||
});
|
||||
document.getElementById("control_faster").addEventListener("click", (e) => {
|
||||
set_speed(Math.min(speed + 1, 27));
|
||||
set_scale((scale * 5) / 4);
|
||||
});
|
||||
|
||||
// Rendering
|
||||
@@ -121,7 +119,9 @@ requestAnimationFrame(animate);
|
||||
function resize() {
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
graphics.resize(canvas.width, canvas.height);
|
||||
const fov_y = playback.get_fov_slope_y();
|
||||
const fov_x = (fov_y * canvas.width) / canvas.height;
|
||||
graphics.resize(canvas.width, canvas.height, fov_x, fov_y);
|
||||
}
|
||||
window.addEventListener("resize", resize);
|
||||
resize();
|
||||
|
||||
Reference in New Issue
Block a user