828 lines
27 KiB
Text
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.pools; 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;
|
|
}
|
|
}
|