// general movement code extend Class Demolitionist { // 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); // 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 = bWalking?.08:(player.cmd.buttons&BT_RUN)?1.1:.4; for ( Inventory i=Inv; i; i=i.Inv ) fact *= i.GetSpeedFactor(); return fact; } 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.length(); if ( vel2d < .25 ) bobtime += .2/GameTicRate; else bobtime += (.5+.8*min(vel2d/10.,3.))/GameTicRate; Vector2 bob = (sin(bobtime*180.),sin(bobtime*360.))*player.bob*.25; if ( player.morphtics ) bob = (0.,0.); // 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 SetViewPos((0.,bob.x*clamp(viewbob,0.,1.5),0.)); } override void CheckPitch() { if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return; // handled in moveplayer Super.CheckPitch(); } override void CheckMoveUpDown() { if ( InStateSequence(CurState,FindState("Dash")) ) player.cmd.upmove = 0; if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) { double fs = TweakSpeed(); Vector3 x, y, z; [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 return true; } 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--; angle += (180./TURN180_TICKS); } 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; Vector3 x, y, z; [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 = (1,1)*vel.length(); 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 { player.cheats |= CF_SCALEDNOLERP; // smoother turning if ( player.turnticks ) { player.turnticks--; angle += (180./TURN180_TICKS); } else angle += player.cmd.yaw*(360./65536.); player.onground = (pos.z<=floorz)||bOnMobj||bMBFBouncer||(player.cheats&CF_NOCLIP2); if ( player.cmd.forwardmove|player.cmd.sidemove ) { double bobfactor; double friction, movefactor; [friction, movefactor] = GetFriction(); bobfactor = (friction 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 // (account for slippery floors, and assume approx .9 friction as "midpoint" for 85% reduction) double fact = clamp(floorsector.GetFriction(0)*.95,.5,1.); vel *= fact; player.vel *= fact; } if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE); } guideangle *= .9; guidepitch *= .9; guideroll *= .9; // anchor to ground when going down steps if ( lastground && !player.onground && !bFly && !bFlyCheat && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z <= 0) && !isdashing ) { // 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(); Vector3 dodge = (0,0,0), x, y, z; [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 ( player.cmd.buttons&BT_CROUCH ) dodge = (0,0,-1); // death from above if ( dodge == (0,0,0) ) { if ( !player.onground || bNOGRAVITY || (player.cheats&CF_NOCLIP2) ) dodge = X; else dodge.xy = RotateVector((1,0),angle); } if ( player.onground && !bNOGRAVITY && !(player.cheats&CF_NOCLIP2) ) { dodge.z = max(0,dodge.z); if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z); } if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && (player.onground || level.IsJumpingAllowed() || (player.cmd.buttons&BT_CROUCH)) && (gamestate == GS_LEVEL) ) { 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 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*(((player.cmd.buttons&BT_RUN)?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:(player.cmd.buttons&BT_RUN)?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 ) { int loudlv = swwm_voiceamp; A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP); if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP); if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP); if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),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*.8; // slow down slightly so it matches the animation 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) && level.IsCrouchingAllowed() ) { 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; } }