Files
roblox-bot-player/lib/src/state.rs
2026-03-06 08:56:40 -08:00

259 lines
8.1 KiB
Rust

use std::collections::HashMap;
use strafesnet_common::run;
use strafesnet_common::physics::Time as PhysicsTime;
use strafesnet_roblox_bot_file::v0;
pub struct Run{
run:run::RunState,
flag_reason:Option<v0::FlagReason>,
}
impl Run{
fn new()->Self{
Self{
run:run::RunState::Created,
flag_reason:None,
}
}
fn flag(&mut self,flag_reason:v0::FlagReason){
if self.flag_reason.is_none(){
self.flag_reason=Some(flag_reason);
}
}
pub fn time(&self,time:PhysicsTime)->run::Time{
self.run.time(time)
}
pub fn is_invalid(&self)->bool{
self.flag_reason.is_some()
}
pub fn is_in_progress(&self)->bool{
matches!(&self.run,run::RunState::Started{..})
}
pub fn is_finished(&self)->bool{
matches!(&self.run,run::RunState::Finished{..})
}
pub fn get_flag_reason_text(&self)->Option<&'static str>{
Some(match self.flag_reason{
Some(v0::FlagReason::Anticheat)=>"Passed through anticheat zone.",
Some(v0::FlagReason::StyleChange)=>"Changed style.",
Some(v0::FlagReason::Clock)=>"Incorrect clock. (This can be caused by internet hiccups)",
Some(v0::FlagReason::Pause)=>"Pausing is not allowed in this style.",
Some(v0::FlagReason::Flying)=>"Flying is not allowed in this style.",
Some(v0::FlagReason::Gravity)=>"Gravity modification is not allowed in this style.",
Some(v0::FlagReason::Timescale)=>"Timescale is not allowed in this style.",
Some(v0::FlagReason::Timetravel)=>"Time travel is not allowed in this style.",
Some(v0::FlagReason::Teleport)=>"Illegal teleport.",
Some(v0::FlagReason::Practice)=>"Practice mode triggers invalidation.",
None=>return None,
})
}
}
pub struct PlaybackState{
// EventType::Input
game_controls:v0::GameControls,
mouse_pos:v0::Vector2,
// EventType::Output
jump_count:u32,
// EventType::Sound
// EventType::World
// EventType::Gravity
gravity:v0::Vector3,
// EventType::Run
runs:HashMap<v0::ModeID,Run>,
style:v0::Style,
// EventType::Camera
// TODO: camera punch
// EventType::Setting
absolute_sensitivity_enabled:bool,
fov_y:f64,
sens_x:f64,
vertical_sensitivity_multipler:f64,
turn_speed:f64,
}
impl PlaybackState{
pub fn new()->Self{
Self{
game_controls:v0::GameControls::empty(),
mouse_pos:v0::Vector2{x:0.0,y:0.0},
jump_count:0,
gravity:v0::Vector3{x:0.0,y:0.0,z:0.0},
runs:HashMap::new(),
style:v0::Style::Autohop,
absolute_sensitivity_enabled:false,
fov_y:1.0,
sens_x:0.3,
vertical_sensitivity_multipler:1.0,
turn_speed:core::f64::consts::TAU/0.715588,
}
}
pub fn get_run(&self,mode:v0::ModeID)->Option<&Run>{
self.runs.get(&mode)
}
fn push_output(&mut self,event:&v0::OutputEvent){
if event.tick_info.contains(v0::TickInfo::Jump){
self.jump_count+=1;
}
}
fn push_input(&mut self,event:&v0::InputEvent){
self.game_controls=event.game_controls;
self.mouse_pos=event.mouse_pos;
}
fn push_gravity(&mut self,event:&v0::GravityEvent){
self.gravity=event.gravity;
}
fn push_run(&mut self,event:&v0::Timed<v0::RunEvent>){
match &event.event{
v0::RunEvent::Prepare(run_event_prepare)=>{
self.runs.insert(run_event_prepare.mode,Run::new());
self.style=run_event_prepare.style;
},
v0::RunEvent::Start(run_event_zone)=>{
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=crate::time::from_float(event.time).unwrap();
if let Some(run)=self.runs.get_mut(&run_event_zone.mode){
_=run.run.finish(time);
}
},
v0::RunEvent::Clear(run_event_clear)=>{
match run_event_clear.mode{
v0::ModeSpec::Exactly(mode_id)=>{
self.runs.remove(&mode_id);
},
v0::ModeSpec::All=>{
self.runs.clear();
},
v0::ModeSpec::Invalid=>{
self.runs.retain(|_,run|!run.is_invalid());
},
v0::ModeSpec::InProgress=>{
self.runs.retain(|_,run|!run.is_in_progress());
},
}
},
v0::RunEvent::Flag(run_event_flag)=>{
match run_event_flag.mode{
v0::ModeSpec::Exactly(mode_id)=>{
if let Some(run)=self.runs.get_mut(&mode_id){
run.flag(run_event_flag.flag_reason);
}
},
v0::ModeSpec::All=>{
for run in self.runs.values_mut(){
run.flag(run_event_flag.flag_reason);
}
},
v0::ModeSpec::Invalid=>{
for run in self.runs.values_mut(){
if run.is_invalid(){
run.flag(run_event_flag.flag_reason);
}
}
},
v0::ModeSpec::InProgress=>{
for run in self.runs.values_mut(){
if run.is_in_progress(){
run.flag(run_event_flag.flag_reason);
}
}
},
}
},
// these should never appear in a uploaded bot file,
// they are just part of the network protocol for spectating
// someone in practice mode.
//
// Yes, this is a design mistake.
// I didn't understand Session vs Simulation when I rewrote bhop in 2022
v0::RunEvent::LoadState(_run_event_practice)=>{},
v0::RunEvent::SaveState(_run_event_practice)=>{},
}
}
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*0.5).to_radians().tan();
},
v0::SettingEvent::Sensitivity(setting_event_sensitivity)=>{
self.sens_x=setting_event_sensitivity.sensitivity;
},
v0::SettingEvent::VerticalSensitivityMultiplier(setting_event_vertical_sensitivity_multiplier)=>{
self.vertical_sensitivity_multipler=setting_event_vertical_sensitivity_multiplier.multiplier;
},
v0::SettingEvent::AbsoluteSensitivity(setting_event_absolute_sensitivity)=>{
self.absolute_sensitivity_enabled=setting_event_absolute_sensitivity.enabled;
},
v0::SettingEvent::TurnSpeed(setting_event_turn_speed)=>{
self.turn_speed=setting_event_turn_speed.turn_speed;
},
}
}
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(&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(&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(&block.setting_events[event_index].event),
}
}
pub const 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 const 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)
}
}
pub const fn get_controls(&self)->v0::GameControls{
self.game_controls
}
pub const fn get_jump_count(&self)->u32{
self.jump_count
}
pub const fn get_gravity(&self)->v0::Vector3{
self.gravity
}
}