strafe-project/src/gameplay_style.rs

515 lines
15 KiB
Rust
Raw Normal View History

2024-02-01 08:04:07 +00:00
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
2024-01-30 06:38:43 +00:00
use crate::integer::{Time,Ratio64,Planar64,Planar64Vec3};
2024-03-02 12:58:00 +00:00
use crate::controls_bitflag::Controls;
2024-01-30 06:38:43 +00:00
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
2024-01-30 06:38:43 +00:00
pub struct StyleModifiers{
2024-03-02 12:58:00 +00:00
//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
2024-02-01 08:04:07 +00:00
pub strafe:Option<StrafeSettings>,
2024-03-02 12:58:00 +00:00
//player gets a controllable rocket force
pub rocket:Option<PropulsionSettings>,
//flying
//pub move_type:MoveType::Fly(FlySettings)
2024-07-24 02:04:53 +00:00
//MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity)
2024-03-02 12:58:00 +00:00
//jumping is allowed
pub jump:Option<JumpSettings>,
//standing & walking is allowed
pub walk:Option<WalkSettings>,
//laddering is allowed
pub ladder:Option<LadderSettings>,
//water propulsion
pub swim:Option<PropulsionSettings>,
//maximum slope before sloped surfaces become frictionless
2024-02-01 08:04:07 +00:00
pub gravity:Planar64Vec3,
2024-03-02 12:58:00 +00:00
//hitbox
2024-02-01 08:04:07 +00:00
pub hitbox:Hitbox,
2024-03-02 12:58:00 +00:00
//camera location relative to the center (0,0,0) of the hitbox
2024-02-01 08:04:07 +00:00
pub camera_offset:Planar64Vec3,
2024-03-02 12:58:00 +00:00
//unused
pub mass:Planar64,
2024-01-30 06:38:43 +00:00
}
impl std::default::Default for StyleModifiers{
fn default()->Self{
Self::roblox_bhop()
}
}
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
pub enum JumpCalculation{
Capped,//roblox
Energy,//new
Linear,//source
}
2024-01-30 06:38:43 +00:00
2024-03-02 12:58:00 +00:00
#[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
2024-08-07 21:40:59 +00:00
FromLinear(Planar64),//jump velocity is invariant across mass and gravity changes
2024-03-02 12:58:00 +00:00
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(),
2024-08-07 21:40:59 +00:00
&JumpImpulse::FromLinear(deltav)=>deltav,
2024-03-02 12:58:00 +00:00
&JumpImpulse::FromEnergy(energy)=>(energy*2/mass).sqrt(),
2024-01-30 06:38:43 +00:00
}
}
2024-03-02 12:58:00 +00:00
}
2024-01-30 06:38:43 +00:00
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
pub struct ControlsActivation{
//allowed keys
2024-08-07 21:43:34 +00:00
pub controls_mask:Controls,
2024-03-02 12:58:00 +00:00
//allow strafing only if any of the masked controls are held, eg W|S for shsw
2024-08-07 21:43:34 +00:00
pub controls_intersects:Controls,
2024-03-02 12:58:00 +00:00
//allow strafing only if all of the masked controls are held, eg W for hsw, w-only
2024-08-07 21:43:34 +00:00
pub controls_contains:Controls,
2024-03-02 12:58:00 +00:00
//Function(Box<dyn Fn(u32)->bool>),
}
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{
2024-01-30 06:38:43 +00:00
Self{
2024-03-02 12:58:00 +00:00
controls_mask:Controls::wasdqe(),
controls_intersects:Controls::wasdqe(),
controls_contains:Controls::empty(),
2024-01-30 06:38:43 +00:00
}
}
2024-03-02 12:58:00 +00:00
//classical styles
//Normal
pub const fn full_2d()->Self{
2024-01-30 06:38:43 +00:00
Self{
2024-03-02 12:58:00 +00:00
controls_mask:Controls::wasd(),
controls_intersects:Controls::wasd(),
controls_contains:Controls::empty(),
2024-01-30 06:38:43 +00:00
}
}
2024-03-02 12:58:00 +00:00
//Sideways
pub const fn sideways()->Self{
2024-01-30 06:38:43 +00:00
Self{
2024-03-02 12:58:00 +00:00
controls_mask:Controls::MoveForward.union(Controls::MoveBackward),
controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
controls_contains:Controls::empty(),
2024-01-30 06:38:43 +00:00
}
}
2024-03-02 12:58:00 +00:00
//Half-Sideways
pub const fn half_sideways()->Self{
2024-01-30 06:38:43 +00:00
Self{
2024-03-02 12:58:00 +00:00
controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight),
controls_intersects:Controls::MoveLeft.union(Controls::MoveRight),
controls_contains:Controls::MoveForward,
2024-01-30 06:38:43 +00:00
}
}
2024-03-02 12:58:00 +00:00
//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
2024-01-30 06:38:43 +00:00
}
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
2024-02-01 08:04:07 +00:00
pub struct StrafeSettings{
2024-08-07 21:43:34 +00:00
pub enable:ControlsActivation,
pub mv:Planar64,
pub air_accel_limit:Option<Planar64>,
pub tick_rate:Ratio64,
2024-01-30 06:38:43 +00:00
}
2024-02-01 08:04:07 +00:00
impl StrafeSettings{
2024-03-02 12:58:00 +00:00
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
let d=velocity.dot(control_dir);
match d<self.mv{
true=>Some(velocity+control_dir*self.air_accel_limit.map_or(self.mv-d,|limit|limit.min(self.mv-d))),
false=>None,
}
}
2024-02-01 08:04:07 +00:00
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))
}
2024-03-02 12:58:00 +00:00
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{
2024-08-07 21:43:34 +00:00
pub magnitude:Planar64,
2024-03-02 12:58:00 +00:00
}
impl PropulsionSettings{
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
control_dir*self.magnitude
}
}
#[derive(Clone,Debug)]
pub struct JumpSettings{
//information used to calculate jump power
2024-08-07 21:43:34 +00:00
pub impulse:JumpImpulse,
2024-03-02 12:58:00 +00:00
//information used to calculate jump behaviour
2024-08-07 21:43:34 +00:00
pub calculation:JumpCalculation,
2024-03-02 12:58:00 +00:00
}
impl JumpSettings{
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{
2024-08-07 21:43:34 +00:00
pub accel:Planar64,
pub topspeed:Planar64,
2024-07-24 02:04:53 +00:00
}
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
pub struct WalkSettings{
2024-08-07 21:43:34 +00:00
pub accelerate:AccelerateSettings,
pub static_friction:Planar64,
pub kinetic_friction:Planar64,
2024-03-02 12:58:00 +00:00
//if a surf slope angle does not exist, then everything is slippery and walking is impossible
2024-08-07 21:43:34 +00:00
pub surf_dot:Planar64,//surf_dot<n.dot(up)/n.length()
2024-03-02 12:58:00 +00:00
}
impl WalkSettings{
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
//TODO: fallible walk accel
let diff_len=target_diff.length();
let friction=if diff_len<self.accelerate.topspeed{
self.static_friction
}else{
self.kinetic_friction
};
self.accelerate.accel.min(-Planar64Vec3::Y.dot(gravity)*friction)
}
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
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 d<n{
let cr=normal.cross(control_dir);
if cr==Planar64Vec3::ZERO{
Planar64Vec3::ZERO
}else{
cr.cross(normal)*(self.accelerate.topspeed/(n*(n*n-d*d).sqrt()*m))
}
}else{
Planar64Vec3::ZERO
}
}
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
//normal is not guaranteed to be unit length
let ny=normal.dot(up);
let h=normal.length();
//remember this is a normal vector
Planar64::ZERO<ny&&h*self.surf_dot<ny
}
}
#[derive(Clone,Debug)]
pub struct LadderSettings{
2024-08-07 21:43:34 +00:00
pub accelerate:AccelerateSettings,
2024-03-02 12:58:00 +00:00
//how close to pushing directly into/out of the ladder normal
//does your input need to be to redirect straight up/down the ladder
2024-08-07 21:43:34 +00:00
pub dot:Planar64,
2024-03-02 12:58:00 +00:00
}
impl LadderSettings{
2024-07-25 19:13:32 +00:00
pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
2024-03-02 12:58:00 +00:00
//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*n<d{
control_dir=Planar64Vec3::NEG_Y*m;
d=-normal.y();
}
//n=d if you are standing on top of a ladder and press E.
//two fixes:
//- ladder movement is not allowed on walkable surfaces
//- fix the underlying issue
if d.get().unsigned_abs()<n.get().unsigned_abs(){
let cr=normal.cross(control_dir);
if cr==Planar64Vec3::ZERO{
Planar64Vec3::ZERO
}else{
cr.cross(normal)*(self.accelerate.topspeed/(n*(n*n-d*d).sqrt()))
}
}else{
Planar64Vec3::ZERO
2024-02-01 08:04:07 +00:00
}
}
}
2024-01-30 06:38:43 +00:00
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
2024-02-01 08:04:07 +00:00
pub enum HitboxMesh{
2024-01-31 03:30:11 +00:00
Box,//source
Cylinder,//roblox
//Sphere,//roblox old physics
//Point,
//Line,
//DualCone,
}
2024-03-02 12:58:00 +00:00
#[derive(Clone,Debug)]
2024-02-01 08:04:07 +00:00
pub struct Hitbox{
pub halfsize:Planar64Vec3,
pub mesh:HitboxMesh,
2024-01-30 06:38:43 +00:00
}
impl Hitbox{
2024-03-02 12:58:00 +00:00
pub fn roblox()->Self{
2024-01-30 06:38:43 +00:00
Self{
2024-01-31 03:30:11 +00:00
halfsize:Planar64Vec3::int(2,5,2)/2,
mesh:HitboxMesh::Cylinder,
2024-01-30 06:38:43 +00:00
}
}
2024-03-02 12:58:00 +00:00
pub fn source()->Self{
2024-01-30 06:38:43 +00:00
Self{
halfsize:Planar64Vec3::int(33,73,33)/2*VALVE_SCALE,
2024-01-31 03:30:11 +00:00
mesh:HitboxMesh::Box,
2024-01-30 06:38:43 +00:00
}
}
}
2024-03-02 12:58:00 +00:00
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,
}
}
}