forked from StrafesNET/roblox-bot-player
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
51bbc93116
|
|||
|
8c5e80d860
|
|||
|
413e88d1ec
|
|||
|
8c5ff71af2
|
|||
|
4528e6b0c2
|
|||
|
834aa4263f
|
|||
|
98e2b72418
|
|||
|
385fa57f0a
|
|||
|
54beb3e9df
|
|||
|
aa0a333431
|
|||
|
cb7474522f
|
|||
|
87fa1220c6
|
|||
|
4fb2e6c800
|
|||
|
b1ebd01463
|
|||
|
de8ef7fae2
|
|||
|
dd55987403
|
|||
|
e4f6f8490d
|
|||
|
db634247ac
|
|||
|
c5cd25c27a
|
|||
|
57c545efa6
|
176
LICENSE-APACHE
Normal file
176
LICENSE-APACHE
Normal file
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
23
LICENSE-MIT
Normal file
23
LICENSE-MIT
Normal file
@@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
26
README.md
26
README.md
@@ -1,4 +1,9 @@
|
||||
This respository uses git lfs for large file storage. You will need to run `git lfs pull` after cloning.
|
||||
How to clone this repository:
|
||||
```
|
||||
git clone https://git.itzana.me/StrafesNET/roblox-bot-player
|
||||
cd roblox-bot-player
|
||||
git lfs pull
|
||||
```
|
||||
|
||||
How to build the wasm module:
|
||||
```
|
||||
@@ -18,3 +23,22 @@ cd native-player
|
||||
cargo run --release -- ../web-demo/bhop_marble_5692093612.snfm ../web-demo/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d.qbot
|
||||
```
|
||||
You can drag and drop map files and bot files to load them.
|
||||
|
||||
#### License
|
||||
|
||||
This code depends on a proprietary module, [strafesnet_graphics](https://git.itzana.me/StrafesNET/strafe-project/src/branch/master/engine/graphics). The resulting binary is
|
||||
not redistributable without the express permission of Rhys Lloyd. The code in this repository
|
||||
is MIT & Apache 2.0 dual licensed, and is free to use.
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
||||
</sub>
|
||||
|
||||
@@ -50,20 +50,29 @@ impl PlaybackHead{
|
||||
pub fn set_paused(&mut self,time:SessionTime,paused:bool){
|
||||
_=self.timer.set_paused(time,paused);
|
||||
}
|
||||
pub fn seek_backward(&mut self,time:SessionTime){
|
||||
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_offset()-time.coerce();
|
||||
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:SessionTime){
|
||||
pub fn seek_forward(&mut self,time_offset:SessionTime){
|
||||
let (mut state,paused)=self.timer.clone().into_state();
|
||||
let offset=state.get_offset()+time.coerce();
|
||||
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;
|
||||
|
||||
@@ -4,12 +4,12 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
glam = "0.31.0"
|
||||
pollster = "0.4.0"
|
||||
strafesnet_roblox_bot_player = { version = "0.1.0", path = "../lib" }
|
||||
wgpu = "28.0.0"
|
||||
winit = "0.30.12"
|
||||
strafesnet_roblox_bot_player = { version = "0.1.0", path = "../lib" }
|
||||
strafesnet_common.workspace = true
|
||||
strafesnet_graphics.workspace = true
|
||||
strafesnet_roblox_bot_file.workspace = true
|
||||
strafesnet_snf.workspace = true
|
||||
glam = "0.31.0"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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};
|
||||
|
||||
pub enum SessionControlInstruction{
|
||||
SetPaused(bool),
|
||||
Restart,
|
||||
SkipForward,
|
||||
SkipBack,
|
||||
DecreaseTimescale,
|
||||
@@ -18,11 +20,20 @@ pub enum Instruction{
|
||||
LoadReplay(strafesnet_roblox_bot_file::v0::Block),
|
||||
}
|
||||
|
||||
fn speed_ratio(speed:i8)->strafesnet_common::integer::Ratio64{
|
||||
if speed.is_negative(){
|
||||
strafesnet_common::integer::Ratio64::new(4i64.pow(-speed as u32),5u64.pow(-speed as u32)).unwrap()
|
||||
}else{
|
||||
strafesnet_common::integer::Ratio64::new(5i64.pow(speed as u32),4u64.pow(speed as u32)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlayerWorker<'a>{
|
||||
surface:wgpu::Surface<'a>,
|
||||
graphics_thread:Graphics,
|
||||
bot:Option<CompleteBot>,
|
||||
playback_head:PlaybackHead,
|
||||
playback_speed:i8,
|
||||
}
|
||||
impl<'a> PlayerWorker<'a>{
|
||||
pub fn new(
|
||||
@@ -35,6 +46,7 @@ impl<'a> PlayerWorker<'a>{
|
||||
graphics_thread,
|
||||
bot:None,
|
||||
playback_head,
|
||||
playback_speed:0,
|
||||
}
|
||||
}
|
||||
pub fn send(&mut self,ins:TimedInstruction<Instruction,SessionTime>){
|
||||
@@ -42,13 +54,23 @@ impl<'a> PlayerWorker<'a>{
|
||||
Instruction::SessionControl(SessionControlInstruction::SetPaused(paused))=>{
|
||||
self.playback_head.set_paused(ins.time,paused);
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::Restart)=>{
|
||||
self.playback_head.seek_to(ins.time,PhysicsTime::ZERO);
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::SkipForward)=>{
|
||||
self.playback_head.seek_forward(SessionTime::from_secs(5));
|
||||
self.playback_head.seek_forward(SessionTime::from_secs(2));
|
||||
},
|
||||
Instruction::SessionControl(SessionControlInstruction::SkipBack)=>{
|
||||
self.playback_head.seek_backward(SessionTime::from_secs(5));
|
||||
self.playback_head.seek_backward(SessionTime::from_secs(2));
|
||||
},
|
||||
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::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(session_control_instruction)=>{},
|
||||
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);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use strafesnet_common::instruction::TimedInstruction;
|
||||
use strafesnet_common::session::Time as SessionTime;
|
||||
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
|
||||
use crate::file::LoadFormat;
|
||||
use crate::player::{PlayerWorker,Instruction as PhysicsWorkerInstruction,SessionControlInstruction};
|
||||
|
||||
@@ -12,7 +11,6 @@ pub enum Instruction{
|
||||
//holds thread handles to dispatch to
|
||||
pub struct WindowContext<'a>{
|
||||
simulation_paused:bool,
|
||||
screen_size:glam::UVec2,
|
||||
window:&'a winit::window::Window,
|
||||
physics_thread:PlayerWorker<'a>,
|
||||
}
|
||||
@@ -70,6 +68,10 @@ impl WindowContext<'_>{
|
||||
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowDown)=>session_ctrl!(DecreaseTimescale,s),
|
||||
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowLeft)=>session_ctrl!(SkipBack,s),
|
||||
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowRight)=>session_ctrl!(SkipForward,s),
|
||||
winit::keyboard::Key::Character(key)=>match key.as_str(){
|
||||
"R"|"r"=>session_ctrl!(Restart,s),
|
||||
_=>None,
|
||||
},
|
||||
_=>None,
|
||||
}{
|
||||
self.physics_thread.send(TimedInstruction{
|
||||
@@ -101,7 +103,7 @@ impl WindowContext<'_>{
|
||||
}
|
||||
}
|
||||
|
||||
fn device_event(&mut self,time:SessionTime,event:winit::event::DeviceEvent){
|
||||
fn device_event(&mut self,_time:SessionTime,_event:winit::event::DeviceEvent){
|
||||
}
|
||||
|
||||
pub fn send(&mut self,ins:TimedInstruction<Instruction,SessionTime>){
|
||||
@@ -122,12 +124,9 @@ impl WindowContext<'_>{
|
||||
surface:wgpu::Surface<'a>,
|
||||
config:wgpu::SurfaceConfiguration,
|
||||
)->WindowContext<'a>{
|
||||
let screen_size=glam::uvec2(config.width,config.height);
|
||||
let graphics=strafesnet_roblox_bot_player::graphics::Graphics::new(device,queue,config);
|
||||
WindowContext{
|
||||
simulation_paused:false,
|
||||
//make sure to update this!!!!!
|
||||
screen_size,
|
||||
window,
|
||||
physics_thread:crate::player::PlayerWorker::new(
|
||||
surface,
|
||||
|
||||
@@ -5,6 +5,8 @@ use strafesnet_roblox_bot_player::{bot,head,time,graphics};
|
||||
use strafesnet_graphics::setup;
|
||||
use strafesnet_common::physics::Time as PhysicsTime;
|
||||
|
||||
mod ratio;
|
||||
|
||||
// Hack to keep the code compiling,
|
||||
// SurfaceTarget::Canvas is not available in IDE for whatever reason.
|
||||
struct ToSurfaceTarget(web_sys::HtmlCanvasElement);
|
||||
@@ -135,4 +137,31 @@ impl PlaybackHead{
|
||||
let mode=v0::ModeID(mode_id);
|
||||
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:f64){
|
||||
let time=time::from_float(time).unwrap();
|
||||
let scale=crate::ratio::ratio_from_float(scale).unwrap();
|
||||
self.head.set_scale(time,scale);
|
||||
}
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
130
wasm-module/src/ratio.rs
Normal file
130
wasm-module/src/ratio.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use strafesnet_common::integer::Ratio64;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RatioFromFloatError{
|
||||
Nan,
|
||||
Overflow,
|
||||
Underflow,
|
||||
}
|
||||
|
||||
fn f64_into_parts(f: f64) -> (u64, i16, i8) {
|
||||
let bits: u64 = f.to_bits();
|
||||
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0xfffffffffffff) << 1
|
||||
} else {
|
||||
(bits & 0xfffffffffffff) | 0x10000000000000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 1023 + 52;
|
||||
(mantissa, exponent, sign)
|
||||
}
|
||||
|
||||
pub fn ratio_from_float(value:f64)->Result<Ratio64,RatioFromFloatError>{
|
||||
// Handle special values first
|
||||
match value.classify(){
|
||||
core::num::FpCategory::Nan=>return Err(RatioFromFloatError::Nan),
|
||||
core::num::FpCategory::Zero=>return Ok(Ratio64::ZERO),
|
||||
core::num::FpCategory::Subnormal
|
||||
|core::num::FpCategory::Normal
|
||||
|core::num::FpCategory::Infinite=>{
|
||||
if value<i64::MIN as f64{
|
||||
return Err(RatioFromFloatError::Underflow);
|
||||
}
|
||||
if (i64::MAX as f64)<value{
|
||||
return Err(RatioFromFloatError::Overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// value = sign * mantissa * 2 ^ exponent
|
||||
let (mantissa,exponent,sign)=f64_into_parts(value);
|
||||
|
||||
if 0<=exponent{
|
||||
// we know value < i64::MIN, so it must just be an integer.
|
||||
return Ok(Ratio64::new((sign as i64*mantissa as i64)<<exponent,1).unwrap());
|
||||
}
|
||||
|
||||
if exponent< -62-52{
|
||||
// very small number is not representable
|
||||
return Ok(Ratio64::ZERO);
|
||||
}
|
||||
|
||||
if -63<exponent{
|
||||
// exactly representable
|
||||
return Ok(Ratio64::new(sign as i64*mantissa as i64,1<<-exponent).unwrap());
|
||||
}
|
||||
|
||||
// the denominator is necessarily bigger than the numerator at this point.
|
||||
|
||||
// create exact float num/den ratio
|
||||
let mut in_num=mantissa as u128;
|
||||
let mut in_den=1u128<<-exponent;
|
||||
|
||||
// a+b*c
|
||||
fn ma(a:i64,b:i64,c:i64)->Option<i64>{
|
||||
a.checked_add(b.checked_mul(c)?)
|
||||
}
|
||||
|
||||
let mut p_prev=1i64;
|
||||
let mut q_prev=0i64;
|
||||
let mut p_cur=0i64;
|
||||
let mut q_cur=1i64;
|
||||
|
||||
// compute continued fraction
|
||||
loop{
|
||||
let whole=in_den/in_num;
|
||||
|
||||
if whole==0{
|
||||
// we have depleted the input fraction and created an exact representation
|
||||
break;
|
||||
}
|
||||
|
||||
if (i64::MAX as u128)<whole{
|
||||
// cannot continue fraction
|
||||
break;
|
||||
}
|
||||
|
||||
let Some(p_next)=ma(p_prev,p_cur,whole as i64)else{
|
||||
// overflow
|
||||
break;
|
||||
};
|
||||
let Some(q_next)=ma(q_prev,q_cur,whole as i64)else{
|
||||
// overflow
|
||||
break;
|
||||
};
|
||||
|
||||
p_prev=p_cur;
|
||||
q_prev=q_cur;
|
||||
p_cur=p_next;
|
||||
q_cur=q_next;
|
||||
(in_num,in_den)=(in_den-in_num*whole,in_num);
|
||||
}
|
||||
|
||||
Ok(Ratio64::new(p_cur,q_cur as u64).unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn req(r0:Ratio64,r1:Ratio64){
|
||||
println!("r0={r0:?} r1={r1:?}");
|
||||
assert_eq!(r0.num(),r1.num(),"Nums not eq");
|
||||
assert_eq!(r0.den(),r1.den(),"Dens not eq");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test(){
|
||||
req(ratio_from_float(2.0).unwrap(),Ratio64::new(2,1).unwrap());
|
||||
req(ratio_from_float(1.0).unwrap(),Ratio64::new(1,1).unwrap());
|
||||
req(ratio_from_float(0.5).unwrap(),Ratio64::new(1,2).unwrap());
|
||||
req(ratio_from_float(1.1).unwrap(),Ratio64::new(2476979795053773,2251799813685248).unwrap());
|
||||
req(ratio_from_float(0.8).unwrap(),Ratio64::new(3602879701896397,4503599627370496).unwrap());
|
||||
req(ratio_from_float(0.61).unwrap(),Ratio64::new(5494391545392005,9007199254740992).unwrap());
|
||||
req(ratio_from_float(0.01).unwrap(),Ratio64::new(5764607523034235,576460752303423488).unwrap());
|
||||
req(ratio_from_float(0.001).unwrap(),Ratio64::new(1152921504606847,1152921504606846976).unwrap());
|
||||
req(ratio_from_float(0.00001).unwrap(),Ratio64::new(89605456633725,8960545663372499267).unwrap());
|
||||
req(ratio_from_float(0.00000000001).unwrap(),Ratio64::new(35204848,3520484800000000213).unwrap());
|
||||
req(ratio_from_float(0.000000000000000001).unwrap(),Ratio64::new(2,1999999999999999857).unwrap());
|
||||
req(ratio_from_float(2222222222222.0).unwrap(),Ratio64::new(2222222222222,1).unwrap());
|
||||
req(ratio_from_float(core::f64::consts::PI).unwrap(),Ratio64::new(884279719003555,281474976710656).unwrap());
|
||||
}
|
||||
@@ -40,6 +40,15 @@
|
||||
font-size: 14px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 120px;
|
||||
display: flex;
|
||||
flex-direction: row; /* stack horizontally */
|
||||
gap: 4px; /* space between timers */
|
||||
}
|
||||
</style>
|
||||
<script defer src="player.js" type="module"></script>
|
||||
<script defer type="module" src="iframe-helper.js"></script>
|
||||
@@ -47,8 +56,17 @@
|
||||
<body>
|
||||
<canvas id="viewport"> </canvas>
|
||||
<div class="hud">
|
||||
<div id="duration" class="timer">00:00:00</div>
|
||||
<div id="timer" class="timer">00:00:00</div>
|
||||
<div id="hud_duration" class="timer">00:00:00</div>
|
||||
<div id="hud_timer" class="timer">00:00:00</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button id="control_reset">↪️</button>
|
||||
<button id="control_pause">⏯</button>
|
||||
<button id="control_backward">⬅️</button>
|
||||
<button id="control_forward">➡️</button>
|
||||
<button id="control_slower">⏪</button>
|
||||
<input type="text" id="control_speed" value="1.00x"></>
|
||||
<button id="control_faster">⏩</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,6 +5,7 @@ import init, {
|
||||
PlaybackHead,
|
||||
} from "./pkg/strafesnet_roblox_bot_player_wasm_module.js";
|
||||
|
||||
// Loading
|
||||
await init(); // load the wasm module
|
||||
|
||||
const b = await fetch("bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d.qbot");
|
||||
@@ -19,9 +20,9 @@ const playback = new PlaybackHead(0);
|
||||
|
||||
graphics.change_map(map);
|
||||
|
||||
const startTime = performance.now();
|
||||
const timer = document.getElementById("timer");
|
||||
const duration = document.getElementById("duration");
|
||||
// HUD
|
||||
const hud_timer = document.getElementById("hud_timer");
|
||||
const hud_duration = document.getElementById("hud_duration");
|
||||
const MODE_MAIN = 0;
|
||||
|
||||
function timer_text(t) {
|
||||
@@ -32,8 +33,60 @@ function timer_text(t) {
|
||||
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(ms).padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
duration.textContent = timer_text(bot.run_duration(MODE_MAIN));
|
||||
hud_duration.textContent = timer_text(bot.run_duration(MODE_MAIN));
|
||||
|
||||
// Stuff
|
||||
const startTime = document.timeline.currentTime;
|
||||
function elapsed() {
|
||||
return (document.timeline.currentTime - startTime) / 1000;
|
||||
}
|
||||
|
||||
const control_speed = document.getElementById("control_speed");
|
||||
|
||||
var paused = false;
|
||||
var scale = 1;
|
||||
function set_scale(new_scale) {
|
||||
scale = new_scale;
|
||||
playback.set_scale(elapsed(), scale);
|
||||
control_speed.value = `${scale.toPrecision(3)}x`;
|
||||
}
|
||||
|
||||
// Controls
|
||||
document.getElementById("control_reset").addEventListener("click", (e) => {
|
||||
playback.seek_to(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);
|
||||
});
|
||||
document.getElementById("control_backward").addEventListener("click", (e) => {
|
||||
playback.seek_backward(2.0);
|
||||
});
|
||||
document.getElementById("control_slower").addEventListener("click", (e) => {
|
||||
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_scale(1);
|
||||
return;
|
||||
}
|
||||
const input = Number(parsed.at(1));
|
||||
if (Number.isNaN(input)) {
|
||||
set_scale(1);
|
||||
return;
|
||||
}
|
||||
set_scale(input);
|
||||
});
|
||||
document.getElementById("control_faster").addEventListener("click", (e) => {
|
||||
set_scale((scale * 5) / 4);
|
||||
});
|
||||
|
||||
// Rendering
|
||||
function animate(now) {
|
||||
const elapsedMs = now - startTime;
|
||||
const elapsedSec = elapsedMs / 1000; // wasm expects seconds
|
||||
@@ -43,7 +96,7 @@ function animate(now) {
|
||||
|
||||
// update the timer text
|
||||
const time = playback.get_run_time(bot, elapsedSec, MODE_MAIN);
|
||||
timer.textContent = timer_text(time);
|
||||
hud_timer.textContent = timer_text(time);
|
||||
|
||||
// Render the frame that the bot is at that time
|
||||
graphics.render(bot, playback, elapsedSec);
|
||||
@@ -54,6 +107,7 @@ function animate(now) {
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Resizing
|
||||
function resize() {
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
|
||||
Reference in New Issue
Block a user