const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16 use crate::integer::{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{ Capped,//roblox Energy,//new Linear,//source } #[derive(Clone,Debug)] pub enum JumpImpulse{ FromTime(Time),//jump time is invariant across mass and gravity changes FromHeight(Planar64),//jump height is invariant across mass and gravity changes FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes FromEnergy(Planar64),// :) } //Jumping acts on dot(walks_state.normal,body.velocity) //Capped means it increases the dot to the cap //Energy means it adds energy //Linear means it linearly adds on impl JumpImpulse{ //fn get_jump_time(&self)->Planar64 //fn get_jump_height(&self)->Planar64 //fn get_jump_energy(&self)->Planar64 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::FromTime(time)=>gravity.length()*(time/2), &JumpImpulse::FromHeight(height)=>(gravity.length()*height*2).sqrt(), &JumpImpulse::FromDeltaV(deltav)=>deltav, &JumpImpulse::FromEnergy(energy)=>(energy*2/mass).sqrt(), } } } #[derive(Clone,Debug)] pub struct ControlsActivation{ //allowed keys controls_mask:Controls, //allow strafing only if any of the masked controls are held, eg W|S for shsw controls_intersects:Controls, //allow strafing only if all of the masked controls are held, eg W for hsw, w-only controls_contains:Controls, //Function(Boxbool>), } impl ControlsActivation{ pub const fn new( controls_mask:Controls, controls_intersects:Controls, controls_contains:Controls, )->Self{ Self{ controls_mask, controls_intersects, controls_contains, } } 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{ enable:ControlsActivation, mv:Planar64, air_accel_limit:Option, tick_rate:Ratio64, } impl StrafeSettings{ pub fn new( enable:ControlsActivation, mv:Planar64, air_accel_limit:Option, tick_rate:Ratio64, )->Self{ Self{enable,mv,air_accel_limit,tick_rate} } pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option{ let d=velocity.dot(control_dir); match dSome(velocity+control_dir*self.air_accel_limit.map_or(self.mv-d,|limit|limit.min(self.mv-d))), 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{ magnitude:Planar64, } impl PropulsionSettings{ pub fn new(magnitude:Planar64)->Self{ Self{magnitude} } pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{ control_dir*self.magnitude } } #[derive(Clone,Debug)] pub struct JumpSettings{ //information used to calculate jump power impulse:JumpImpulse, //information used to calculate jump behaviour calculation:JumpCalculation, } impl JumpSettings{ pub fn new( impulse:JumpImpulse, calculation:JumpCalculation, )->Self{ Self{impulse,calculation} } pub fn jumped_velocity(&self,style:&StyleModifiers,jump_dir:Planar64Vec3,velocity:Planar64Vec3)->Planar64Vec3{ match self.calculation{ //roblox style JumpCalculation::Capped=>todo!(), //something different JumpCalculation::Energy=>todo!(), //source style JumpCalculation::Linear=>velocity+jump_dir*(self.impulse.get_jump_deltav(&style.gravity,style.mass)/jump_dir.length()), } } } #[derive(Clone,Debug)] pub struct AccelerateSettings{ accel:Planar64, topspeed:Planar64, } impl AccelerateSettings{ pub fn new( accel:Planar64, topspeed:Planar64, )->Self{ Self{accel,topspeed} } } #[derive(Clone,Debug)] pub struct WalkSettings{ accelerate:AccelerateSettings, static_friction:Planar64, kinetic_friction:Planar64, //if a surf slope angle does not exist, then everything is slippery and walking is impossible surf_dot:Planar64,//surf_dotSelf{ Self{accelerate,static_friction,kinetic_friction,surf_dot} } pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ //TODO: fallible walk accel let diff_len=target_diff.length(); let friction=if diff_lenPlanar64Vec3{ if control_dir==Planar64Vec3::ZERO{ return control_dir; } let n=normal.length(); let m=control_dir.length(); let d=normal.dot(control_dir)/m; if dbool{ //normal is not guaranteed to be unit length let ny=normal.dot(up); let h=normal.length(); //remember this is a normal vector Planar64::ZEROSelf{ Self{accelerate,dot} } pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ //TODO: fallible ladder accel self.accelerate.accel } pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ if control_dir==Planar64Vec3::ZERO{ return control_dir; } let n=normal.length(); let m=control_dir.length(); let mut d=normal.dot(control_dir)/m; if d< -self.dot*n{ control_dir=Planar64Vec3::Y*m; d=normal.y(); }else if self.dot*nSelf{ Self{ halfsize:Planar64Vec3::int(2,5,2)/2, mesh:HitboxMesh::Cylinder, } } pub fn source()->Self{ Self{ halfsize:Planar64Vec3::raw(33,73,33)/2*VALVE_SCALE, mesh:HitboxMesh::Box, } } } impl StyleModifiers{ pub const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X; pub const UP_DIR:Planar64Vec3=Planar64Vec3::Y; pub const FORWARD_DIR:Planar64Vec3=Planar64Vec3::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:Planar64::int(3), tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::FromEnergy(Planar64::int(512)), calculation:JumpCalculation::Energy, }), gravity:Planar64Vec3::int(0,-80,0), mass:Planar64::int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(16), accel:Planar64::int(80), }, static_friction:Planar64::int(2), kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static surf_dot:Planar64::int(3)/4, }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(16), accel:Planar64::int(160), }, dot:(Planar64::int(1)/2).sqrt(), }), swim:Some(PropulsionSettings{ magnitude:Planar64::int(12), }), hitbox:Hitbox::roblox(), camera_offset:Planar64Vec3::int(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:Planar64::int(27)/10, tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), calculation:JumpCalculation::Linear,//Should be capped }), gravity:Planar64Vec3::int(0,-100,0), mass:Planar64::int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(18), accel:Planar64::int(90), }, static_friction:Planar64::int(2), kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static surf_dot:Planar64::int(3)/4,// normal.y=0.75 }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(18), accel:Planar64::int(180), }, dot:(Planar64::int(1)/2).sqrt(), }), swim:Some(PropulsionSettings{ magnitude:Planar64::int(12), }), hitbox:Hitbox::roblox(), camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 } } pub fn roblox_surf()->Self{ Self{ gravity:Planar64Vec3::int(0,-50,0), ..Self::roblox_bhop() } } pub fn roblox_rocket()->Self{ Self{ strafe:None, rocket:Some(PropulsionSettings{ magnitude:Planar64::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, tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE), calculation:JumpCalculation::Linear, }), gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE, mass:Planar64::int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(18),//? accel:Planar64::int(90),//? }, static_friction:Planar64::int(2),//? kinetic_friction:Planar64::int(3),//? surf_dot:Planar64::int(3)/4,// normal.y=0.75 }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(18),//? accel:Planar64::int(180),//? }, dot:(Planar64::int(1)/2).sqrt(),//? }), swim:Some(PropulsionSettings{ magnitude:Planar64::int(12),//? }), hitbox:Hitbox::source(), camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE, } } 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(Planar64::int(150)*66*VALVE_SCALE), mv:Planar64::int(30)*VALVE_SCALE, tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump:Some(JumpSettings{ impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE), calculation:JumpCalculation::Linear, }), gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE, mass:Planar64::int(1), rocket:None, walk:Some(WalkSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(18),//? accel:Planar64::int(90),//? }, static_friction:Planar64::int(2),//? kinetic_friction:Planar64::int(3),//? surf_dot:Planar64::int(3)/4,// normal.y=0.75 }), ladder:Some(LadderSettings{ accelerate:AccelerateSettings{ topspeed:Planar64::int(18),//? accel:Planar64::int(180),//? }, dot:(Planar64::int(1)/2).sqrt(),//? }), swim:Some(PropulsionSettings{ magnitude:Planar64::int(12),//? }), hitbox:Hitbox::source(), camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE, } } }