swwmgz_m/zscript/player/swwm_player_move.zsc

828 lines
27 KiB
Text

// general movement code
extend Class Demolitionist
{
bool IsRunning()
{
// for analog move, check for full sprint speed
if ( isAnalogMoving )
return (NormalizedMove().length() >= (gameinfo.normforwardmove[1]*256.));
return !!(player.cmd.buttons&BT_RUN);
}
// directional movement without straferunning
Vector2 NormalizedMove()
{
if ( !(player.cmd.forwardmove|player.cmd.sidemove) )
return (0,0);
int idx = !!(player.cmd.buttons&BT_RUN);
// ratio between forwardmove and sidemove (depending on BT_RUN state)
double fs = gameinfo.normforwardmove[idx]/gameinfo.normsidemove[idx];
// raw axes scaled to 1:1 ratio
Vector2 mvec = (player.cmd.forwardmove,-player.cmd.sidemove*fs);
// should we use raw analog movement?
if ( isAnalogMoving )
{
// straferun cap (lol sorry)
if ( mvec.length() > gameinfo.normforwardmove[idx]*256. )
mvec = mvec.unit()*gameinfo.normforwardmove[idx]*256.;
return mvec; // passed as-is otherwise
}
// multiply unit vector back to "raw" running speed (as TweakSpeed handles the "true" scaling for us later)
return mvec.unit()*gameinfo.normforwardmove[1]*256.;
}
double TweakSpeed()
{
double fact;
// when analog moving, lerp between factors (should feel ok?)
if ( isAnalogMoving )
{
double mscale = NormalizedMove().length();
double rspeed = gameinfo.normforwardmove[0]*256.;
double sspeed = gameinfo.normforwardmove[1]*256.;
if ( mscale < rspeed ) // between walking/running
fact = SWWMUtility.MapRange(0,rspeed,.08,.4,mscale);
else if ( mscale < sspeed ) // between running/sprinting
fact = SWWMUtility.MapRange(rspeed,sspeed,.4,1.1,mscale);
else fact = 1.1; // sprinting
}
else fact = bWalking?.08:IsRunning()?1.1:.4;
for ( Inventory i=Inv; i; i=i.Inv ) fact *= i.GetSpeedFactor();
return fact;
}
void LeaveFootprint( double yofs, bool foot )
{
Vector3 checkpos = level.Vec3Offset((pos.xy,floorz),(RotateVector((0,yofs*.25*radius),angle),0));
let s = level.PointInSector(checkpos.xy);
// part 1: leave a footprint
if ( footblood[foot] > 0. )
{
double zatstep = s.floorplane.ZAtPoint(checkpos.xy);
for ( int i=0; i<s.Get3DFloorCount(); i++ )
{
if ( !(s.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
double ffz = s.Get3DFloor(i).top.ZAtPoint(checkpos.xy);
if ( ffz > checkpos.z ) continue;
else if ( ffz < zatstep ) continue;
zatstep = ffz;
break;
}
if ( abs(floorz-zatstep) <= 8. )
{
let bs = Spawn("mkBloodStep",(checkpos.xy,zatstep));
bs.angle = angle;
bs.frame = foot;
bs.A_ChangeModel("",0,"","",0,"models/extra",String.Format("BloodFoot%d.png",Random[Blood](1,4)));
bs.alpha = min(1.,footblood[foot]);
bs.SetShade(footbloodcol[foot]);
}
footblood[foot] = max(0.,footblood[foot]*.95-.05);
}
// part 2: check for stepping on a blood pool, make feet bloody
if ( !hnd ) hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
for ( mkBloodPool p=hnd.bloodpools; p; p=p.nextpool )
{
// vertical distance fast check
if ( abs(checkpos.z-p.pos.z) > 8. )
continue;
// horizontal distance slower check
double distsq = level.Vec2Diff(checkpos.xy,p.pos.xy).lengthsquared();
if ( distsq > (p.radius*p.radius+.25*radius*radius) )
continue;
// muck it up, with some color mixing too
double oldblood = footblood[foot];
footblood[foot] = 2.;
footbloodcol[foot] = SWWMUtility.LerpColor(p.stepcol,footbloodcol[foot],min(1.,oldblood)*.75);
}
}
void PlayFootstep( double yofs, int run = 0, double vol = .3, bool nosplash = false )
{
if ( run == 2 )
{
A_StartSound("demolitionist/run",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
if ( !nosplash )
{
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
}
else if ( run == 1 )
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
if ( !nosplash )
{
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
}
else
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.2);
if ( !nosplash )
{
let b = Spawn("SmolInvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
}
LeaveFootprint(yofs,yofs>0);
}
override void CalcHeight()
{
double defviewh = viewheight+player.crouchviewdelta;
oldbob = player.bob;
if ( bFlyCheat || (player.cheats&CF_NOCLIP2) )
player.bob = 0.;
else
player.bob = min((player.vel dot player.vel)*player.GetMoveBob(),MAXBOB);
if ( player.cheats&CF_NOVELOCITY )
{
player.viewz = pos.z+defviewh;
if ( player.viewz > ceilingz-4 )
player.viewz = ceilingz-4;
SetViewPos((0.,0.,0.));
return;
}
// adjust viewheight
if ( player.playerstate == PST_LIVE )
{
player.viewheight += player.deltaviewheight;
if ( player.viewheight > defviewh )
{
player.viewheight = defviewh;
player.deltaviewheight = 0.;
}
else if ( player.viewheight < (defviewh/2.) )
{
player.viewheight = defviewh/2.;
if ( player.deltaviewheight <= 0. )
player.deltaviewheight = 1./65536.;
}
if ( player.deltaviewheight )
{
player.deltaviewheight += .25;
if ( !player.deltaviewheight )
player.deltaviewheight = 1./65536.;
}
}
// apply bobbing (formula adapted from Unreal)
oldbobtime = bobtime;
double vel2d = vel.xy dot vel.xy;
bobtime += (1.8+.4*min(vel2d/100.,3.))/GameTicRate;
Vector2 bob = (sin(bobtime*180.),.5*sin(bobtime*360.))*player.bob*.35;
if ( player.morphtics )
bob = (0.,0.);
else if ( player.bob && !multiplayer && !(player.cheats&CF_CHASECAM)
&& player.onground && !bNoGravity && (player.crouchdir != -1)
&& (player.cmd.forwardmove|player.cmd.sidemove) )
{
// bob-based footsteps in first person
int m = int(2.*M_PI+oldbobtime);
int n = int(2.*M_PI+bobtime);
if ( m != n )
{
int side = (sin(bobtime*180.)>0.)?1:-1;
int spd = bWalking?0:IsRunning()?2:1;
PlayFootstep(side,spd);
}
}
// set up viewz
player.viewz = pos.z+player.viewheight+(bob.y*clamp(viewbob,0.,1.5));
// handle smooth step down (hacky but looks ok)
player.viewz += ssup;
ssup = max(0,(ssup*.7)-.25);
if ( floorclip && (player.playerstate != PST_DEAD) && (pos.z <= floorz) )
player.viewz -= floorclip;
if ( player.viewz > ceilingz-4 )
player.viewz = ceilingz-4;
if ( player.viewz < floorz+4 )
player.viewz = floorz+4;
// add viewpos Y for side bob (currently breaks SCALEDNOLERP)
SetViewPos((0.,bob.x*clamp(viewbob,0.,1.5),0.));
}
override void CheckPitch()
{
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
if ( player.cmd.pitch == -32768 ) player.centering = true;
return; // the rest is handled in moveplayer
}
// fun fact: we can't override freelook disabling
// so... just call super, I guess
Super.CheckPitch();
}
override void CheckMoveUpDown()
{
if ( InStateSequence(CurState,FindState("Dash")) )
player.cmd.upmove = 0;
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
double fs = TweakSpeed();
let [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
Vector3 accel;
if ( (player.cmd.upmove == -32768) || sendtoground )
{
sendtoground = true;
player.centering = true;
accel = (0,0,-4096);
}
else accel = z*player.cmd.upmove*8.;
accel *= fs/128.;
vel = vel+accel/GameTicRate;
if ( sendtoground ) vel.xy *= .6;
if ( (pos.z <= floorz) || bOnMobj ) sendtoground = false;
if ( vel.length() > 50. )
vel = vel.unit()*50.;
return;
}
else sendtoground = false;
Super.CheckMoveUpDown();
}
private bool ShouldDecelerate( Sector s )
{
// check if we can apply fast decel while standing on this sector
// (important to not break certain intended vanilla effects)
if ( bNOFRICTION )
return false; // we don't have friction at all (e.g.: while dashing)
if ( bWINDTHRUST && (s.special >= 40) && (s.special <= 51) )
return false; // wind
if ( (s.special == 84) || (s.special == 118) || ((s.special >= 201) && (s.special <= 244)) )
return false; // current
// TODO check for scroller thinkers if that ever gets exposed to ZScript
return true;
}
private Vector3 GetGroundNormal()
{
// find closest 3d floor for its normal
F3DFloor ff;
for ( int i=0; i<floorsector.Get3DFloorCount(); i++ )
{
ff = floorsector.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_SOLID) ) continue;
if ( !(ff.top.ZAtPoint(pos.xy) ~== floorz) ) continue;
return -ff.top.Normal;
}
// fall back to floorsector's normal
return floorsector.floorplane.Normal;
}
override void MovePlayer()
{
if ( !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
{
dashboost = 0.;
player.vel *= 0.;
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
Super.MovePlayer();
return;
}
bool isdashing = InStateSequence(CurState,FindState("Dash"));
if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0;
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
player.cheats &= ~CF_SCALEDNOLERP; // we cannot permit this flag here
player.onground = false;
if ( player.turnticks )
{
player.turnticks--;
A_SetAngle(angle+(180./TURN180_TICKS),SPF_INTERPOLATE);
}
else guideangle += .2*player.cmd.yaw*(360./65536.);
guidepitch -= .2*player.cmd.pitch*(360./65536.);
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
guideroll = clamp(deltaangle(roll,0),-3.,3.);
A_SetAngle(angle+guideangle,SPF_INTERPOLATE);
A_SetPitch(clamp(pitch+guidepitch,player.MinPitch,player.MaxPitch),SPF_INTERPOLATE);
A_SetRoll(roll+guideroll,SPF_INTERPOLATE);
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
{
guideroll = guidepitch = roll = pitch = 0.;
player.centering = false;
}
double fs = TweakSpeed();
double jcmove = 0.;
if ( player.cmd.buttons&BT_JUMP ) jcmove += 4096.;
if ( player.cmd.buttons&BT_CROUCH ) jcmove -= 4096.;
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
let [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
Vector2 nmove = NormalizedMove();
Vector3 accel = x*nmove.x-y*nmove.y+z*jcmove;
accel *= fs/320.;
double spd = vel.length();
if ( spd > 40. ) vel = (vel+accel/GameTicRate).unit()*spd;
else vel = vel+accel/GameTicRate;
vel *= .95;
player.vel += RotateVector(nmove,angle)*cos(pitch)*16.;
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) && (player.cmd.forwardmove|player.cmd.sidemove) )
PlayRunning();
if ( player.cheats&CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
else
{
if ( player.turnticks )
{
player.turnticks--;
A_SetAngle(angle+(180./TURN180_TICKS),SPF_INTERPOLATE);
}
else angle += player.cmd.yaw*(360./65536.);
player.onground = (pos.z<=floorz)||bOnMobj||bMBFBouncer||(player.cheats&CF_NOCLIP2);
let [friction, movefactor] = GetFriction();
if ( player.cmd.forwardmove|player.cmd.sidemove )
{
double bobfactor;
bobfactor = (friction<ORIG_FRICTION)?movefactor:ORIG_FRICTION_FACTOR;
if ( !player.onground && !bNoGravity )
{
// no air control here, done afterwards
movefactor *= 0.;
bobfactor *= 0.;
}
// use normalized movement vector, no SR40 (not that we need it with how fast we can run)
Vector2 nmove = NormalizedMove();
nmove *= TweakSpeed();
nmove *= Speed/256.;
// When crouching, speed and bobbing have to be reduced
if ( CanCrouch() && (player.crouchfactor != 1) )
{
nmove *= player.crouchfactor;
// don't reduce bob as much, there needs to be SOME visible bobbing
bobfactor *= (1.+player.crouchfactor)/2.;
}
nmove *= movefactor;
if ( bNOGRAVITY && !player.GetClassicFlight() )
{
Vector3 accel;
accel.xy = RotateVector(nmove,angle)*cos(pitch);
accel.z += nmove.x*sin(-pitch);
double maxspd = 16.*TweakSpeed();
// account for special cases where max speed is zero
double spdup = 1.;
if ( maxspd <= 0. ) vel += accel;
else
{
if ( CanCrouch() && (player.crouchfactor != 1) ) maxspd *= player.crouchfactor;
double spd = vel.length();
// quicker speedup
spdup = clamp(maxspd/max(.01,spd),1.,2.);
vel += accel*spdup;
// additional steering + quicker slow down
spd = vel.length();
if ( spd > maxspd ) spd -= (spd-maxspd)*.1;
vel = (vel+accel).unit()*spd;
}
player.vel += RotateVector(nmove,angle)*bobfactor*cos(pitch)*16.*spdup;
}
else if ( player.onground )
{
Vector2 accel = RotateVector(nmove,angle);
double maxspd = 16.*TweakSpeed();
// account for special cases where max speed is zero
// also, slippery if waist-high or deeper
double spdup = 1.;
if ( (waterlevel > 1) || (maxspd <= 0.) ) vel.xy += accel;
else
{
// factors for slippery floors (make steering harder)
double fact1 = SWWMUtility.MapRange(.90625,.97265625,2.,1.,friction);
fact1 = clamp(fact1,1.,2.);
double fact2 = SWWMUtility.MapRange(.90625,.97265625,1.,4.,friction);
fact2 = clamp(fact2,1.,4.);
if ( CanCrouch() && (player.crouchfactor != 1) ) maxspd *= player.crouchfactor;
double spd = vel.xy.length();
// quicker speedup
spdup = clamp(maxspd/max(.01,spd),1.,fact1);
vel.xy += accel*spdup;
// additional steering + quicker slow down
spd = vel.xy.length();
if ( spd > maxspd ) spd -= (spd-maxspd)*.1;
vel.xy = (vel.xy*fact2+accel).unit()*spd;
}
player.vel += RotateVector(nmove,angle)*bobfactor*16.*spdup;
}
else player.vel *= .75;
// override air control because we REALLY need the extra mobility
if ( !player.onground && !bNOGRAVITY )
{
nmove = NormalizedMove();
double fs = TweakSpeed();
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector2 accel = RotateVector(nmove,angle);
accel *= fs/320.;
double spd = vel.xy.length();
double maxspd = fs*12.;
if ( spd > maxspd ) vel.xy = (vel.xy+accel/GameTicRate).unit()*spd;
else vel.xy = vel.xy+accel/GameTicRate;
}
if ( !(player.cheats&CF_PREDICTING) && (nmove.length() > 0.) )
PlayRunning();
if ( player.cheats&CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
else if ( player.onground && ShouldDecelerate(floorsector) )
{
// quickly decelerate if we're not holding movement keys
// the default friction is mapped to a 70% reduction,
// while slippery floors (above 95%) will have no reduction at all
double fact = SWWMUtility.MapRange(.90625,.97265625,.7,1.,friction);
fact = clamp(fact,.6,1.); // ensure this doesn't go too far out of range (especially, prevent endless acceleration)
vel *= fact;
player.vel *= fact;
}
if ( abs(roll) > 0. ) roll += clamp(deltaangle(roll,0),-3.,3.);
}
guideangle *= .9;
guidepitch *= .9;
guideroll *= .9;
// anchor to ground when going down steps
if ( !hnd ) hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( lastground && !player.onground && !bFly && !bFlyCheat && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z <= 0) && !isdashing && !hnd.nogroundanchor )
{
// test for gap crossing (i.e: climbing up platforms with holes between them)
Vector3 storepos = pos;
double storefloorz = floorz;
bool crossgap = false;
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
{
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),true);
if ( floorz < storepos.z ) continue;
crossgap = true;
break;
}
SetOrigin(storepos,true);
floorz = storefloorz;
if ( !crossgap )
{
ssup = max(0,(pos.z-floorz));
SetZ(floorz);
lastground = player.onground = true;
}
}
if ( player.onground ) lastgroundtic = level.maptime;
else lastairtic = level.maptime;
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
PlayIdle();
if ( !(player.cmd.buttons&BT_USER2) || (gamestate != GS_LEVEL) || (dashcooldown > 0) || (dashfuel < 20.) )
return;
Vector3 dodge = (0,0,0);
let [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
int fm = player.cmd.forwardmove;
int sm = player.cmd.sidemove;
if ( !(fm|sm) ) fm = 1;
if ( fm ) dodge += (fm>0)?X:-X;
if ( sm ) dodge += (sm>0)?Y:-Y;
if ( dodge == (0,0,0) ) dodge = X; // do we really need this?
if ( player.onground && !bNOGRAVITY && !(player.cheats&CF_NOCLIP2) )
{
Vector3 fnorm = GetGroundNormal();
if ( dodge dot fnorm <= 0. )
{
// recalc and project onto floor normal
dodge = (0,0,0);
Vector2 xdir = (cos(angle),sin(angle));
Vector2 ydir = (sin(angle),-cos(angle));
if ( fm ) dodge.xy += (fm>0)?xdir:-xdir;
if ( sm ) dodge.xy += (sm>0)?ydir:-ydir;
dodge = dodge-(dodge dot fnorm)*fnorm;
}
}
if ( dodge.length() <= 0. ) return; // not sure if this can happen, really
fullfuel = (dashfuel >= default.dashfuel);
dashdir = dodge.unit();
dashcooldown = 10;
dashboost = 20.;
bOnMobj = false;
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
vel += dashdir*dashboost;
vel.z += clamp(-vel.z*.4,0.,30.);
player.jumptics = -1;
SetStateLabel("Dash");
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
lastbump *= .95;
mystats.dashcount++;
BumpView(5.,vel);
}
override void CheckJump()
{
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
if ( !(player.cmd.buttons&BT_JUMP) || (gamestate != GS_LEVEL) ) return;
Vector2 walldir = AngleToVector(angle);
bool walljump = false, wallclimb = false;
double climbvelz = 0.;
FLineTraceData d;
Actor jumpactor = null;
for ( int i=-4; i<int(height*.8); i++ )
{
if ( !walljump )
{
for ( int k=0; k<60; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = (angle-180)+k*j;
walljump = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( walljump )
{
jumpactor = d.HitActor;
walldir = (walldir*.7+AngleToVector(ang+180)*.3);
break;
}
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( walljump ) break;
if ( !wallclimb )
{
for ( int k=0; k<30; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = angle+k*j;
wallclimb = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( wallclimb )
{
// double-check that it's climbable
if ( d.HitType == TRACE_HitWall )
{
if ( !d.HitLine.sidedef[1] || !!(d.HitLine.flags&(Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING|Line.ML_BLOCK_PLAYERS)) ) // nope
{
wallclimb = false;
break;
}
Sector s = d.HitLine.sidedef[!d.LineSide].sector;
double backfloorz = s.floorplane.ZAtPoint(d.HitLocation.xy);
double backceilz = s.ceilingplane.ZAtPoint(d.HitLocation.xy);
// cling to 3D floor if hit
if ( d.Hit3DFloor ) backfloorz = d.Hit3DFloor.top.ZAtPoint(d.HitLocation.xy);
if ( ((backceilz-backfloorz) < Height) || ((pos.z+Height+32) < backfloorz) ) // nope
{
wallclimb = false;
break;
}
climbvelz = 1.5*sqrt(max(1.,backfloorz-pos.z));
}
else if ( d.HitType == TRACE_HitActor )
{
if ( ((d.HitActor.ceilingz-d.HitActor.pos.z+d.HitActor.Height) < Height) || ((pos.z+Height+32) < (d.HitActor.pos.z+d.HitActor.Height)) ) // nope
{
wallclimb = false;
break;
}
climbvelz = 1.5*sqrt(max(1.,(d.HitActor.pos.z+d.HitActor.Height)-pos.z));
}
jumpactor = d.HitActor;
walldir = (walldir*.7+AngleToVector(ang)*.3);
break;
}
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
if ( level.maptime < (lastgroundtic+4) )
{
walljump = false;
wallclimb = false;
jumpactor = null;
}
if ( player.crouchoffset ) player.crouching = 1;
else if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return;
else if ( bNoGravity ) vel.z = 3;
else if ( (player.onground && (player.jumptics == 0))
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb)) )
{
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
return;
double jumpvelz = JumpZ*35./GameTicRate;
double jumpfac = 0;
for ( let p=Inv; p; p=p.Inv )
{
let pp = PowerHighJump(p);
if ( pp )
{
double f = pp.Strength;
if ( f > jumpfac ) jumpfac = f;
}
}
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
bool raging = FindInventory("RagekitPower");
if ( raging ) jumpvelz *= 2.;
double pvelz = vel.z;
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
{
// check for wall stuff
if ( walljump )
{
if ( vel.z < 10. )
vel.z += 2.*jumpvelz+clamp(-pvelz*.6,0.,30.);
vel.xy += walldir*20*Speed;
lastbump *= .95;
}
else if ( wallclimb )
{
if ( vel.z < 10. )
vel.z += climbvelz+clamp(-pvelz*.95,0.,30.);
vel.xy += walldir*10*Speed;
lastbump *= .97;
}
if ( walljump && jumpactor && jumpactor.bSHOOTABLE )
{
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
if ( raging )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = dmg;
}
}
if ( walljump || wallclimb )
{
last_kick = level.maptime+1;
SWWMUtility.AchievementProgressInc("jump",1,player);
}
}
bOnMobj = false;
player.jumpTics = -1;
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
{
dashboost = 3.;
boostcooldown = 20;
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
lastbump *= .95;
mystats.boostcount++;
last_boost = level.maptime+1;
BumpView(3.,vel);
SetStateLabel("Boost");
}
else
{
A_StartSound(walljump?"demolitionist/kick":"demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
dashboost = 0.;
// bunnyhop time
if ( !walljump && !wallclimb )
{
if ( !bWalking && (level.maptime < (lastairtic+10)) )
{
SWWMUtility.AchievementProgressInc("bune",1,player);
// bhop, z vel relative to vel size
if ( vel.z < 25. ) // don't ramp up too hard
{
vel.z += jumpvelz*((IsRunning()?1.2:.65)+vel.length()*.01);
// add part of last landing z velocity too
vel.z += max(0,-landvelz*(raging?.45:.35));
}
// accelerate
vel.xy += (RotateVector(NormalizedMove(),angle)/2400.)*(1.+vel.length()*.025)*TweakSpeed();
}
else
{
// first jump
if ( vel.z < 10. ) // don't ramp up too hard
{
vel.z += jumpvelz*(bWalking?.75:IsRunning()?1.25:1.);
// add part of last landing z velocity too
vel.z += max(0,-landvelz*(raging?.35:.25));
}
// long jump if running/sprinting
if ( !walljump && !wallclimb && !bWalking )
vel.xy += (RotateVector(NormalizedMove(),angle)/1500.)*(raging?2.:1.)*TweakSpeed();
}
}
BumpView(clamp((vel.length()-10)/12.,1.,20.),vel);
if ( swwm_mutevoice < 4 )
{
String myvoice = CVar.GetCVar('swwm_voicetype',player).GetString();
int loudlv = swwm_voiceamp;
int maxjump = StringTable.Localize("$SWWM_"..myvoice.."_NJUMP").ToInt();
int idx = (maxjump<=1)?1:Random[DemoLines](1,maxjump);
A_StartSound(String.Format("voice/%s/jump%d",myvoice,idx),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/jump%d",myvoice,idx),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/jump%d",myvoice,idx),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/jump%d",myvoice,idx),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
SetStateLabel("Jump");
}
}
last_jump_held = level.maptime+1;
}
bool AllowCrouch()
{
if ( player.cmd.buttons&BT_JUMP ) return false;
if ( InStateSequence(CurState,FindState("Dash")) ) return false; // no crouch during dash
return true;
}
// Imagine having to duplicate two functions only to change a couple values in both
// I sure love constants
override void CrouchMove( int direction )
{
double defaultheight = FullHeight;
double savedheight = Height;
double crouchspeed = direction*CROUCHSPEED*((direction>0)?.65:.9); // match animation speed
double oldheight = player.viewheight;
player.crouchdir = direction;
player.crouchfactor += crouchspeed;
// check whether the move is ok
Height = defaultheight; // actually test the full height, or it'll look weird
if ( !TryMove(pos.xy,false) )
{
Height = savedheight;
if ( direction > 0 )
{
// doesn't fit
player.crouchfactor -= crouchspeed;
player.crouchdir = -1; // force crouch
return;
}
}
Height = savedheight;
player.crouchfactor = clamp(player.crouchfactor,.3,1.);
player.viewheight = ViewHeight*player.crouchfactor;
player.crouchviewdelta = player.viewheight-ViewHeight;
// Check for eyes going above/below fake floor due to crouching motion.
CheckFakeFloorTriggers(pos.z+oldheight,true);
}
override void CheckCrouch( bool totallyfrozen )
{
// crouch to swim/float down
if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) && bNOGRAVITY )
vel.z = -3;
bool wascrouching = !!(player.cmd.buttons&BT_CROUCH);
if ( !AllowCrouch() ) player.cmd.buttons &= ~BT_CROUCH;
if ( CanCrouch() && (player.health > 0) )
{
if ( !totallyfrozen )
{
int crouchdir = player.crouching;
if ( !crouchdir ) crouchdir = (player.cmd.buttons&BT_CROUCH)?-1:1;
else if ( player.cmd.buttons&BT_CROUCH ) player.crouching = 0;
if ( (crouchdir == 1) && (player.crouchfactor < 1) && (pos.z+height < ceilingz) )
CrouchMove(1);
else if ( (crouchdir == -1) && (player.crouchfactor > .3) )
CrouchMove(-1);
}
}
else player.Uncrouch();
player.crouchoffset = -(ViewHeight)*(1-player.crouchfactor);
// we need the crouch button state to be preserved for other functions
if ( wascrouching ) player.cmd.buttons |= BT_CROUCH;
}
// let's customize our gravity
override void FallAndSink( double grav, double oldfloorz )
{
if ( !player || (player.mo != self) || (player.cheats&CF_TOTALLYFROZEN) )
{
Super.FallAndSink(grav,oldfloorz);
return;
}
// do nothing if standing on ground or "floating"
if ( player.onground || bNOGRAVITY ) return;
// ensure we don't pass terminal velocity just from falling
if ( vel.z < -50 ) return;
// we don't care about "the doom way" here, gravity is
// ALWAYS in effect when not standing on solid ground
if ( waterlevel > 1 )
{
// sink faster
grav *= .35;
}
// reduce gravity while we're boosting
else if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Boost")) )
grav *= .25;
vel.z -= grav;
}
}