const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16 use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3}; use crate::controls_bitflag::Controls; #[derive(Clone,Debug)] pub struct StyleModifiers{ //controls which are allowed to pass into gameplay (usually all) pub controls_mask:Controls, //controls which are masked from control state (e.g. !jump in scroll style) pub controls_mask_state:Controls, //strafing pub strafe:Option, //player gets a controllable rocket force pub rocket:Option, //flying //pub move_type:MoveType::Fly(FlySettings) //MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity) //jumping is allowed pub jump:Option, //standing & walking is allowed pub walk:Option, //laddering is allowed pub ladder:Option, //water propulsion pub swim:Option, //maximum slope before sloped surfaces become frictionless pub gravity:Planar64Vec3, //hitbox pub hitbox:Hitbox, //camera location relative to the center (0,0,0) of the hitbox pub camera_offset:Planar64Vec3, //unused pub mass:Planar64, } impl std::default::Default for StyleModifiers{ fn default()->Self{ Self::roblox_bhop() } } #[derive(Clone,Debug)] pub enum JumpCalculation{ Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump()) BoostThenJump,//jumped_speed=velocity.boost().jump() JumpThenBoost,//jumped_speed=velocity.jump().boost() } #[derive(Clone,Debug)] pub enum JumpImpulse{ Time(Time),//jump time is invariant across mass and gravity changes Height(Planar64),//jump height is invariant across mass and gravity changes Linear(Planar64),//jump velocity is invariant across mass and gravity changes Energy(Planar64),// :) } //Jumping acts on dot(walks_state.normal,body.velocity) //Energy means it adds energy //Linear means it linearly adds on impl JumpImpulse{ pub fn jump( &self, velocity:Planar64Vec3, jump_dir:Planar64Vec3, gravity:&Planar64Vec3, mass:Planar64, )->Planar64Vec3{ match self{ &JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()), &JumpImpulse::Height(height)=>{ //height==-v.y*v.y/(2*g.y); //use energy to determine max height let gg=gravity.length_squared(); let g=gg.sqrt().fix_1(); let v_g=gravity.dot(velocity); //do it backwards let radicand=v_g*v_g+(g*height*2).fix_4(); velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1() }, &JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(), &JumpImpulse::Energy(energy)=>{ //calculate energy //let e=gravity.dot(velocity); //add //you get the idea todo!() }, } } //TODO: remove this and implement JumpCalculation properly pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{ //gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction match self{ &JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(), &JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(), &JumpImpulse::Linear(deltav)=>deltav, &JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(), } } } #[derive(Clone,Debug)] pub struct JumpSettings{ //information used to calculate jump power pub impulse:JumpImpulse, //information used to calculate jump behaviour pub calculation:JumpCalculation, //limit the minimum jump power when combined with downwards momentum //This is true in both roblox and source pub limit_minimum:bool, } impl JumpSettings{ pub fn jumped_velocity( &self, style:&StyleModifiers, jump_dir:Planar64Vec3, rel_velocity:Planar64Vec3, booster:Option<&crate::gameplay_attributes::Booster>, )->Planar64Vec3{ let jump_speed=self.impulse.get_jump_deltav(&style.gravity,style.mass); match (self.limit_minimum,&self.calculation){ (true,JumpCalculation::Max)=>{ //the roblox calculation let boost_vel=match booster{ Some(booster)=>booster.boost(rel_velocity), None=>rel_velocity, }; let j=boost_vel.dot(jump_dir); let js=jump_speed.fix_2(); if j{ //the source calculation (?) let boost_vel=match booster{ Some(booster)=>booster.boost(rel_velocity), None=>rel_velocity, }; let j=boost_vel.dot(jump_dir); let js=jump_speed.fix_2(); if j{ //??? calculation //max(boost_vel,jump_vel) let boost_vel=match booster{ Some(booster)=>booster.boost(rel_velocity), None=>rel_velocity, }; let boost_dot=boost_vel.dot(jump_dir); let js=jump_speed.fix_2(); if boost_dot{ let boost_vel=match booster{ Some(booster)=>booster.boost(rel_velocity), None=>rel_velocity, }; boost_vel+jump_dir.with_length(jump_speed).divide().fix_1() }, } } } #[derive(Clone,Debug)] pub struct ControlsActivation{ //allowed keys pub controls_mask:Controls, //allow strafing only if any of the masked controls are held, eg W|S for shsw pub controls_intersects:Controls, //allow strafing only if all of the masked controls are held, eg W for hsw, w-only pub controls_contains:Controls, //Function(Boxbool>), } impl ControlsActivation{ pub const fn mask(&self,controls:Controls)->Controls{ controls.intersection(self.controls_mask) } pub const fn activates(&self,controls:Controls)->bool{ (self.controls_intersects.is_empty()||controls.intersects(self.controls_intersects)) &&controls.contains(self.controls_contains) } pub const fn full_3d()->Self{ Self{ controls_mask:Controls::WASDQE, controls_intersects:Controls::WASDQE, controls_contains:Controls::empty(), } } //classical styles //Normal pub const fn full_2d()->Self{ Self{ controls_mask:Controls::WASD, controls_intersects:Controls::WASD, controls_contains:Controls::empty(), } } //Sideways pub const fn sideways()->Self{ Self{ controls_mask:Controls::MoveForward.union(Controls::MoveBackward), controls_intersects:Controls::MoveForward.union(Controls::MoveBackward), controls_contains:Controls::empty(), } } //Half-Sideways pub const fn half_sideways()->Self{ Self{ controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight), controls_intersects:Controls::MoveLeft.union(Controls::MoveRight), controls_contains:Controls::MoveForward, } } //Surf Half-Sideways pub const fn surf_half_sideways()->Self{ Self{ controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight), controls_intersects:Controls::MoveForward.union(Controls::MoveBackward), controls_contains:Controls::empty(), } } //W-Only pub const fn w_only()->Self{ Self{ controls_mask:Controls::MoveForward, controls_intersects:Controls::empty(), controls_contains:Controls::MoveForward, } } //A-Only pub const fn a_only()->Self{ Self{ controls_mask:Controls::MoveLeft, controls_intersects:Controls::empty(), controls_contains:Controls::MoveLeft, } } //Backwards } #[derive(Clone,Debug)] pub struct StrafeSettings{ pub enable:ControlsActivation, pub mv:Planar64, pub air_accel_limit:Option, pub tick_rate:Ratio64, } impl StrafeSettings{ pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option{ let d=velocity.dot(control_dir); let mv=self.mv.fix_2(); match dSome(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()), false=>None, } } pub fn next_tick(&self,time:Time)->Time{ Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1)) } pub const fn activates(&self,controls:Controls)->bool{ self.enable.activates(controls) } pub const fn mask(&self,controls:Controls)->Controls{ self.enable.mask(controls) } } #[derive(Clone,Debug)] pub struct PropulsionSettings{ pub magnitude:Planar64, } impl PropulsionSettings{ pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{ (control_dir*self.magnitude).fix_1() } } #[derive(Clone,Debug)] pub struct AccelerateSettings{ pub accel:Planar64, pub topspeed:Planar64, } #[derive(Clone,Debug)] pub struct WalkSettings{ pub accelerate:AccelerateSettings, pub static_friction:Planar64, pub kinetic_friction:Planar64, //if a surf slope angle does not exist, then everything is slippery and walking is impossible pub surf_dot:Planar64,//surf_dotPlanar64{ //TODO: fallible walk accel let diff_len=target_diff.length().fix_1(); let friction=if diff_lenPlanar64Vec3{ if control_dir==crate::integer::vec3::ZERO{ return control_dir; } let nn=normal.length_squared(); let mm=control_dir.length_squared(); let nnmm=nn*mm; let d=normal.dot(control_dir); let dd=d*d; if ddbool{ //normal is not guaranteed to be unit length let ny=normal.dot(up); let h=normal.length().fix_1(); //remember this is a normal vector ny.is_positive()&&h*self.surf_dotPlanar64{ //TODO: fallible ladder accel self.accelerate.accel } pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ if control_dir==crate::integer::vec3::ZERO{ return control_dir; } let nn=normal.length_squared(); let mm=control_dir.length_squared(); let nnmm=nn*mm; let d=normal.dot(control_dir); let mut dd=d*d; if (self.dot*self.dot*nnmm).fix_4()Self{ Self{ halfsize:int3(2,5,2)>>1, mesh:HitboxMesh::Cylinder, } } pub fn source()->Self{ Self{ halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(), mesh:HitboxMesh::Box, } } } impl StyleModifiers{ pub const RIGHT_DIR:Planar64Vec3=crate::integer::vec3::X; pub const UP_DIR:Planar64Vec3=crate::integer::vec3::Y; pub const FORWARD_DIR:Planar64Vec3=crate::integer::vec3::NEG_Z; pub fn neo()->Self{ Self{ controls_mask:Controls::all(), controls_mask_state:Controls::all(), strafe:Some(StrafeSettings{ enable:ControlsActivation::full_2d(), air_accel_limit:None, mv:int(3), tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::Energy(int(512)), calculation:JumpCalculation::JumpThenBoost, limit_minimum:false, }), gravity:int3(0,-80,0), mass:int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:int(16), accel:int(80), }, static_friction:int(2), kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static surf_dot:int(3)/4, }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:int(16), accel:int(160), }, dot:(int(1)/2).sqrt(), }), swim:Some(PropulsionSettings{ magnitude:int(12), }), hitbox:Hitbox::roblox(), camera_offset:int3(0,2,0),//4.5-2.5=2 } } pub fn roblox_bhop()->Self{ Self{ controls_mask:Controls::all(), controls_mask_state:Controls::all(), strafe:Some(StrafeSettings{ enable:ControlsActivation::full_2d(), air_accel_limit:None, mv:int(27)/10, tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::Time(Time::from_micros(715_588)), calculation:JumpCalculation::Max, limit_minimum:true, }), gravity:int3(0,-100,0), mass:int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:int(18), accel:int(90), }, static_friction:int(2), kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static surf_dot:int(3)/4,// normal.y=0.75 }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:int(18), accel:int(180), }, dot:(int(1)/2).sqrt(), }), swim:Some(PropulsionSettings{ magnitude:int(12), }), hitbox:Hitbox::roblox(), camera_offset:int3(0,2,0),//4.5-2.5=2 } } pub fn roblox_surf()->Self{ Self{ gravity:int3(0,-50,0), ..Self::roblox_bhop() } } pub fn roblox_rocket()->Self{ Self{ strafe:None, rocket:Some(PropulsionSettings{ magnitude:int(200), }), ..Self::roblox_bhop() } } pub fn source_bhop()->Self{ Self{ controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, controls_mask_state:Controls::all(), strafe:Some(StrafeSettings{ enable:ControlsActivation::full_2d(), air_accel_limit:Some(Planar64::raw(150<<28)*100), mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(), tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), calculation:JumpCalculation::JumpThenBoost, limit_minimum:true, }), gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), mass:int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:int(18),//? accel:int(90),//? }, static_friction:int(2),//? kinetic_friction:int(3),//? surf_dot:int(3)/4,// normal.y=0.75 }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:int(18),//? accel:int(180),//? }, dot:(int(1)/2).sqrt(),//? }), swim:Some(PropulsionSettings{ magnitude:int(12),//? }), hitbox:Hitbox::source(), camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), } } pub fn source_surf()->Self{ Self{ controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, controls_mask_state:Controls::all(), strafe:Some(StrafeSettings{ enable:ControlsActivation::full_2d(), air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()), mv:(int(30)*VALVE_SCALE).fix_1(), tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), calculation:JumpCalculation::JumpThenBoost, limit_minimum:true, }), gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), mass:int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:int(18),//? accel:int(90),//? }, static_friction:int(2),//? kinetic_friction:int(3),//? surf_dot:int(3)/4,// normal.y=0.75 }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:int(18),//? accel:int(180),//? }, dot:(int(1)/2).sqrt(),//? }), swim:Some(PropulsionSettings{ magnitude:int(12),//? }), hitbox:Hitbox::source(), camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), } } }