// bg_pmove.c -- both games player movement code // takes a playerstate and a usercmd as input and returns a modifed playerstate // define GAME_INCLUDE so that g_public.h does not define the // short, server-visible gclient_t and gentity_t structures, // because we define the full size ones in this file #define GAME_INCLUDE #include "q_shared.h" #include "g_shared.h" #include "bg_local.h" #include "g_local.h" #include "anims.h" #include "../cgame/cg_local.h" // yeah I know this is naughty, but we're shipping soon... extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); extern void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel ); extern void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel ); extern cvar_t *g_timescale; static qboolean MatrixMode; qboolean Flying; pmove_t *pm; pml_t pml; // movement parameters const float pm_stopspeed = 100.0f; const float pm_duckScale = 0.50f; const float pm_swimScale = 0.50f; float pm_ladderScale = 0.7f; const float pm_accelerate = 12.0f; const float pm_airaccelerate = 4.0f; const float pm_wateraccelerate = 4.0f; const float pm_flyaccelerate = 8.0f; const float pm_friction = 6.0f; const float pm_waterfriction = 1.0f; const float pm_flightfriction = 3.0f; const float pm_frictionModifier = 3.0f; //Used for "careful" mode (when pressing use) const float pm_airDecelRate = 1.35f; //Used for air decelleration away from current movement velocity int c_pmove = 0; extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags); extern void PM_TorsoAnimation( void ); extern int PM_TorsoAnimForFrame( gentity_t *ent, int torsoFrame ); extern int PM_AnimLength( int index, animNumber_t anim ); extern qboolean PM_InDeathAnim ( void ); extern qboolean PM_InOnGroundAnim (gentity_t *self); extern weaponInfo_t cg_weapons[MAX_WEAPONS]; extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim ); #define PHASER_RECHARGE_TIME 100 /* =============== PM_AddEvent =============== */ void PM_AddEvent( int newEvent ) { AddEventToPlayerstate( newEvent, 0, pm->ps ); } /* =============== PM_AddTouchEnt =============== */ void PM_AddTouchEnt( int entityNum ) { int i; if ( entityNum == ENTITYNUM_WORLD ) { return; } if ( pm->numtouch == MAXTOUCH ) { return; } // see if it is already added for ( i = 0 ; i < pm->numtouch ; i++ ) { if ( pm->touchents[ i ] == entityNum ) { return; } } // add it pm->touchents[pm->numtouch] = entityNum; pm->numtouch++; } /* ================== PM_ClipVelocity Slide off of the impacting surface This will pull you down onto slopes if heading away from them and push you up them as you go up them. Also stops you when you hit walls. ================== */ void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { float backoff; float change; int i; backoff = DotProduct (in, normal); if ( backoff < 0 ) { backoff *= overbounce; } else { backoff /= overbounce; } for ( i=0 ; i<3 ; i++ ) { change = normal[i]*backoff; out[i] = in[i] - change; } } /* ================== PM_Friction Handles both ground friction and water friction ================== */ static void PM_Friction( void ) { vec3_t vec; float *vel; float speed, newspeed, control; float drop, friction = pm->ps->friction; vel = pm->ps->velocity; VectorCopy( vel, vec ); if ( pml.walking ) { vec[2] = 0; // ignore slope movement } speed = VectorLength(vec); if (speed < 1) { vel[0] = 0; vel[1] = 0; // allow sinking underwater // FIXME: still have z friction underwater? return; } drop = 0; // apply ground friction, even if on ladder if ( (pm->watertype & CONTENTS_LADDER) || pm->waterlevel <= 1 ) { if ( (pm->watertype & CONTENTS_LADDER) || (pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK)) ) { // if getting knocked back, no friction if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { //If the use key is pressed. slow the player more quickly if ( pm->cmd.buttons & BUTTON_USE ) friction *= pm_frictionModifier; control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*friction*pml.frametime; } } } // apply water friction even if just wading if ( pm->waterlevel && !(pm->watertype & CONTENTS_LADDER)) { drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; } // apply flying friction if ( pm->ps->pm_type == PM_SPECTATOR ) { drop += speed*pm_flightfriction*pml.frametime; } // scale the velocity newspeed = speed - drop; if (newspeed < 0) newspeed = 0; newspeed /= speed; vel[0] = vel[0] * newspeed; vel[1] = vel[1] * newspeed; vel[2] = vel[2] * newspeed; } /* ============== PM_Accelerate Handles user intended acceleration ============== */ static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { int i; float addspeed, accelspeed, currentspeed; currentspeed = DotProduct (pm->ps->velocity, wishdir); addspeed = wishspeed - currentspeed; if (addspeed <= 0) { return; } accelspeed = ( accel * pml.frametime ) * wishspeed; if (accelspeed > addspeed) { accelspeed = addspeed; } for (i=0 ; i<3 ; i++) { pm->ps->velocity[i] += accelspeed * wishdir[i]; } } /* ============ PM_CmdScale Returns the scale factor to apply to cmd movements This allows the clients to use axial -127 to 127 values for all directions without getting a sqrt(2) distortion in speed. ============ */ static float PM_CmdScale( usercmd_t *cmd ) { int max; float total; float scale; max = abs( cmd->forwardmove ); if ( abs( cmd->rightmove ) > max ) { max = abs( cmd->rightmove ); } if ( abs( cmd->upmove ) > max ) { max = abs( cmd->upmove ); } if ( !max ) { return 0; } total = sqrt( ( cmd->forwardmove * cmd->forwardmove ) + ( cmd->rightmove * cmd->rightmove ) + ( cmd->upmove * cmd->upmove ) ); scale = (float) pm->ps->speed * max / ( 127.0f * total ); return scale; } /* ================ PM_SetMovementDir Determine the rotation of the legs reletive to the facing dir ================ */ static void PM_SetMovementDir( void ) { if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { pm->ps->movementDir = 0; } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { pm->ps->movementDir = 1; } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { pm->ps->movementDir = 2; } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { pm->ps->movementDir = 3; } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { pm->ps->movementDir = 4; } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { pm->ps->movementDir = 5; } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { pm->ps->movementDir = 6; } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { pm->ps->movementDir = 7; } } else { // if they aren't actively going directly sideways, // change the animation to the diagonal so they // don't stop too crooked if ( pm->ps->movementDir == 2 ) { pm->ps->movementDir = 1; } else if ( pm->ps->movementDir == 6 ) { pm->ps->movementDir = 7; } } } /* ============= PM_CheckJump ============= */ static qboolean PM_CheckJump( void ) { //Don't allow jump until all buttons are up if ( pm->ps->pm_flags & PMF_RESPAWNED ) { return qfalse; } //Not jumping if ( pm->cmd.upmove < 10 ) { return qfalse; } // must wait for jump to be released if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { // clear upmove so cmdscale doesn't lower running speed pm->cmd.upmove = 0; return qfalse; } { gentity_t *groundEnt = &g_entities[pm->ps->groundEntityNum]; if ( groundEnt && groundEnt->NPC ) {//Can't jump off of someone's head return qfalse; } } //Jumping pml.groundPlane = qfalse; pml.walking = qfalse; pm->ps->pm_flags |= PMF_JUMP_HELD; pm->ps->groundEntityNum = ENTITYNUM_NONE; pm->ps->velocity[2] = JUMP_VELOCITY; if ( pm->gent ) { if ( !Q3_TaskIDPending( pm->gent, TID_CHAN_VOICE ) ) { PM_AddEvent( EV_JUMP ); } } else { PM_AddEvent( EV_JUMP ); } //Set the animations if ( pm->cmd.forwardmove >= 0 ) { if(!PM_InDeathAnim()) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE); } pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; } else { if(!PM_InDeathAnim()) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE); } pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; } return qtrue; } /* ============= PM_CheckWaterJump ============= */ static qboolean PM_CheckWaterJump( void ) { vec3_t spot; int cont; vec3_t flatforward; if (pm->ps->pm_time) { return qfalse; } // check for water jump if ( pm->waterlevel != 2 ) { return qfalse; } if ( pm->watertype & CONTENTS_LADDER ) { if (pm->ps->velocity[2] <= 0) return qfalse; } flatforward[0] = pml.forward[0]; flatforward[1] = pml.forward[1]; flatforward[2] = 0; VectorNormalize( flatforward ); VectorMA( pm->ps->origin, 30, flatforward, spot ); spot[2] += 4; cont = pm->pointcontents (spot, pm->ps->clientNum ); if ( !(cont & CONTENTS_SOLID) ) { return qfalse; } spot[2] += 16; cont = pm->pointcontents( spot, pm->ps->clientNum ); if ( cont ) { return qfalse; } // jump out of water VectorScale( pml.forward, 200, pm->ps->velocity ); pm->ps->velocity[2] = 350; pm->ps->pm_flags |= PMF_TIME_WATERJUMP; pm->ps->pm_time = 2000; return qtrue; } //============================================================================ /* =================== PM_WaterJumpMove Flying out of the water =================== */ static void PM_WaterJumpMove( void ) { // waterjump has no control, but falls PM_StepSlideMove( qtrue ); pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; if (pm->ps->velocity[2] < 0) { // cancel as soon as we are falling down again pm->ps->pm_flags &= ~PMF_ALL_TIMES; pm->ps->pm_time = 0; } } /* =================== PM_WaterMove =================== */ static void PM_WaterMove( void ) { int i; vec3_t wishvel; float wishspeed; vec3_t wishdir; float scale; float vel; if ( PM_CheckWaterJump() ) { PM_WaterJumpMove(); return; } #if 0 // jump = head for surface if ( pm->cmd.upmove >= 10 ) { if (pm->ps->velocity[2] > -300) { if ( pm->watertype == CONTENTS_WATER ) { pm->ps->velocity[2] = 100; } else if (pm->watertype == CONTENTS_SLIME) { pm->ps->velocity[2] = 80; } else { pm->ps->velocity[2] = 50; } } } #endif PM_Friction (); scale = PM_CmdScale( &pm->cmd ); // // user intentions // if ( !scale ) { wishvel[0] = 0; wishvel[1] = 0; if ( pm->watertype & CONTENTS_LADDER ) { wishvel[2] = 0; } else { wishvel[2] = -60; // sink towards bottom } } else { for (i=0 ; i<3 ; i++) { wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; } wishvel[2] += scale * pm->cmd.upmove; } VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); if ( pm->watertype & CONTENTS_LADDER ) //ladder { if ( wishspeed > pm->ps->speed * pm_ladderScale ) { wishspeed = pm->ps->speed * pm_ladderScale; } PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); } else { if ( wishspeed > pm->ps->speed * pm_swimScale ) { wishspeed = pm->ps->speed * pm_swimScale; } PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate ); } // make sure we can go up slopes easily under water if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { vel = VectorLength(pm->ps->velocity); // slide along the ground plane PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); VectorNormalize(pm->ps->velocity); VectorScale(pm->ps->velocity, vel, pm->ps->velocity); } PM_SlideMove( qfalse ); } /* =================== PM_FlyMove Only with the flight powerup =================== */ static void PM_FlyMove( void ) { int i; vec3_t wishvel; float wishspeed; vec3_t wishdir; float scale; // normal slowdown PM_Friction (); scale = PM_CmdScale( &pm->cmd ); // // user intentions // if ( !scale ) { wishvel[0] = 0; wishvel[1] = 0; wishvel[2] = 0; } else { for (i=0 ; i<3 ; i++) { wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; } wishvel[2] += scale * pm->cmd.upmove; } VectorCopy( wishvel, wishdir ); wishspeed = VectorNormalize( wishdir ); PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); PM_StepSlideMove( qfalse ); } /* =================== PM_AirMove =================== */ static void PM_AirMove( void ) { int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float scale; usercmd_t cmd; PM_Friction(); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; cmd = pm->cmd; scale = PM_CmdScale( &cmd ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir(); // project moves down to flat plane pml.forward[2] = 0; pml.right[2] = 0; VectorNormalize (pml.forward); VectorNormalize (pml.right); for ( i = 0 ; i < 2 ; i++ ) { wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; } wishvel[2] = 0; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); /* if ( pm->gent && pm->gent->client && pm->gent->client->playerTeam == TEAM_STASIS ) {//FIXME: do a check for movetype_float //Can move fairly well in air while falling PM_Accelerate (wishdir, wishspeed, pm_accelerate/2.0f); } else { */ if ( ( DotProduct (pm->ps->velocity, wishdir) ) < 0.0f ) {//Encourage deceleration away from the current velocity wishspeed *= pm_airDecelRate; } // not on ground, so little effect on velocity PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); //} // we may have a ground plane that is very steep, even // though we don't have a groundentity // slide along the steep plane if ( pml.groundPlane ) { PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); } PM_StepSlideMove ( qtrue ); } /* =================== PM_WalkMove =================== */ static void PM_WalkMove( void ) { int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float scale; usercmd_t cmd; float accelerate; float vel; if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { // begin swimming PM_WaterMove(); return; } if ( PM_CheckJump () ) { // jumped away if ( pm->waterlevel > 1 ) { PM_WaterMove(); } else { PM_AirMove(); } return; } PM_Friction (); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; cmd = pm->cmd; scale = PM_CmdScale( &cmd ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir(); // project moves down to flat plane pml.forward[2] = 0; pml.right[2] = 0; // project the forward and right directions onto the ground plane PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); // VectorNormalize (pml.forward); VectorNormalize (pml.right); for ( i = 0 ; i < 3 ; i++ ) { wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; } // when going up or down slopes the wish velocity should Not be zero // wishvel[2] = 0; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); wishspeed *= scale; // clamp the speed lower if ducking if ( pm->ps->pm_flags & PMF_DUCKED ) { if ( wishspeed > pm->ps->speed * pm_duckScale ) { wishspeed = pm->ps->speed * pm_duckScale; } } // clamp the speed lower if wading or walking on the bottom if ( pm->waterlevel ) { float waterScale; waterScale = pm->waterlevel / 3.0; waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; if ( wishspeed > pm->ps->speed * waterScale ) { wishspeed = pm->ps->speed * waterScale; } } // when a player gets hit, they temporarily lose // full control, which allows them to be moved a bit if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { accelerate = pm_airaccelerate; } else { accelerate = pm_accelerate; } PM_Accelerate (wishdir, wishspeed, accelerate); //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; } else { // don't reset the z velocity for slopes // pm->ps->velocity[2] = 0; } vel = VectorLength(pm->ps->velocity); // slide along the ground plane PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); // don't decrease velocity when going up or down a slope VectorNormalize(pm->ps->velocity); VectorScale(pm->ps->velocity, vel, pm->ps->velocity); // don't do anything if standing still if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { return; } PM_StepSlideMove( qfalse ); //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); } /* ============== PM_DeadMove ============== */ static void PM_DeadMove( void ) { float forward; if ( !pml.walking ) { return; } // extra friction forward = VectorLength (pm->ps->velocity); forward -= 20; if ( forward <= 0 ) { VectorClear (pm->ps->velocity); } else { VectorNormalize (pm->ps->velocity); VectorScale (pm->ps->velocity, forward, pm->ps->velocity); } } /* =============== PM_NoclipMove =============== */ static void PM_NoclipMove( void ) { float speed, drop, friction, control, newspeed; int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float scale; if(pm->gent && pm->gent->client) { pm->ps->viewheight = pm->gent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET; if ( !pm->gent->mins[0] || !pm->gent->mins[1] || !pm->gent->mins[2] || !pm->gent->maxs[0] || !pm->gent->maxs[1] || !pm->gent->maxs[2] ) { assert(0); } VectorCopy( pm->gent->mins, pm->mins ); VectorCopy( pm->gent->maxs, pm->maxs ); } else { pm->ps->viewheight = DEFAULT_MAXS_2 + STANDARD_VIEWHEIGHT_OFFSET;//DEFAULT_VIEWHEIGHT; if ( !DEFAULT_MINS_0 || !DEFAULT_MINS_1 || !DEFAULT_MAXS_0 || !DEFAULT_MAXS_1 || !DEFAULT_MINS_2 || !DEFAULT_MAXS_2 ) { assert(0); } pm->mins[0] = DEFAULT_MINS_0; pm->mins[1] = DEFAULT_MINS_1; pm->mins[2] = DEFAULT_MINS_2; pm->maxs[0] = DEFAULT_MAXS_0; pm->maxs[1] = DEFAULT_MAXS_1; pm->maxs[2] = DEFAULT_MAXS_2; } // friction speed = VectorLength (pm->ps->velocity); if (speed < 1) { VectorCopy (vec3_origin, pm->ps->velocity); } else { drop = 0; friction = pm_friction*1.5; // extra friction control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*friction*pml.frametime; // scale the velocity newspeed = speed - drop; if (newspeed < 0) newspeed = 0; newspeed /= speed; VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); } // accelerate scale = PM_CmdScale( &pm->cmd ); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; for (i=0 ; i<3 ; i++) wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; wishvel[2] += pm->cmd.upmove; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); wishspeed *= scale; PM_Accelerate( wishdir, wishspeed, pm_accelerate ); // move VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); } //============================================================================ /* ================ PM_FootstepForSurface Returns an event number apropriate for the groundsurface ================ */ static int PM_FootstepForSurface( void ) { if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { return 0; } if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { return EV_FOOTSTEP_METAL; } return EV_FOOTSTEP; } /* ================= PM_CrashLand Check for hard landings that generate sound events ================= */ static void PM_CrashLand( void ) { float delta; float dist; float vel, acc; float t; float a, b, c, den; // calculate the exact velocity on landing dist = pm->ps->origin[2] - pml.previous_origin[2]; vel = pml.previous_velocity[2]; acc = -pm->ps->gravity; a = acc / 2; b = vel; c = -dist; den = b * b - 4 * a * c; if ( den < 0 ) { return; } t = (-b - sqrt( den ) ) / ( 2 * a ); delta = vel + t * acc; delta = delta*delta * 0.0001; // ducking while falling doubles damage /* if ( pm->ps->pm_flags & PMF_DUCKED ) { delta *= 2; } */ // never take falling damage if completely underwater if ( pm->waterlevel == 3 ) { return; } // reduce falling damage if there is standing water if ( pm->waterlevel == 2 ) { delta *= 0.25; } if ( pm->waterlevel == 1 ) { delta *= 0.5; } if ( delta < 1 ) { return; } if(!PM_InDeathAnim()) { if((pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_FALLDEATH1||(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_FALLDEATH1INAIR) {//FIXME: add a little bounce? //FIXME: cut voice channel? pm->ps->gravity = 1.0; PM_AddEvent( EV_FALL_FAR ); int old_pm_type = pm->ps->pm_type; pm->ps->pm_type = PM_NORMAL; //Hack because for some reason PM_SetAnim just returns if you're dead...??? PM_SetAnim(pm, SETANIM_BOTH, BOTH_FALLDEATH1LAND, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); pm->ps->pm_type = old_pm_type; return; } if ( delta > 20 || pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) //EV_FALL_SHORT or jumping back {// decide which landing animation to use if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_LANDBACK1,SETANIM_FLAG_OVERRIDE); } else { PM_SetAnim(pm,SETANIM_LEGS,BOTH_LAND1,SETANIM_FLAG_OVERRIDE); } } } // create a local entity event to play the sound // SURF_NODAMAGE is used for bounce pads where you don't ever // want to take damage or play a crunch sound if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { if ( delta > 50 ) { PM_AddEvent( EV_FALL_FAR );//damage is dealt in g_active, ClientEvents if ( pm->gent && pm->gent->s.number == 0 ) { vec3_t bottom; VectorCopy( pm->ps->origin, bottom ); bottom[2] += pm->mins[2]; if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, bottom, 256, AEL_SUSPICIOUS ); } } } else if ( delta > 25 ) { // this is a pain grunt, so don't play it if dead if ( pm->ps->stats[STAT_HEALTH] > 0 ) { PM_AddEvent( EV_FALL_MEDIUM );//damage is dealt in g_active, ClientEvents if ( pm->gent && pm->gent->s.number == 0 ) { vec3_t bottom; VectorCopy( pm->ps->origin, bottom ); bottom[2] += pm->mins[2]; if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, bottom, 256, AEL_MINOR ); } } } } else if ( delta > 7 ) { PM_AddEvent( EV_FALL_SHORT ); if ( pm->gent && pm->gent->s.number == 0 ) { vec3_t bottom; VectorCopy( pm->ps->origin, bottom ); bottom[2] += pm->mins[2]; if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, bottom, 128, AEL_MINOR ); } } if ( pm->gent ) { if ( pm->gent->client && pm->gent->client->race == RACE_REAVER ) { PM_SetAnim( pm,SETANIM_BOTH,BOTH_LAND1,SETANIM_FLAG_OVERRIDE ); } } } else { PM_AddEvent( PM_FootstepForSurface() ); } } // start footstep cycle over pm->ps->bobCycle = 0; } /* ============= PM_CorrectAllSolid ============= */ static void PM_CorrectAllSolid( void ) { if ( pm->debugLevel ) { Com_Printf("%i:allsolid\n", c_pmove); //NOTENOTE: If this ever happens, I'd really like to see this print! } // FIXME: jitter around pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; pml.walking = qfalse; } /* ============= PM_GroundTraceMissed The ground trace didn't hit a surface, so we are in freefall ============= */ static void PM_GroundTraceMissed( void ) { trace_t trace; vec3_t point; if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { // we just transitioned into freefall if ( pm->debugLevel ) { Com_Printf("%i:lift\n", c_pmove); } // if they aren't in a jumping animation and the ground is a ways away, force into it // if we didn't do the trace, the player would be backflipping down staircases VectorCopy( pm->ps->origin, point ); point[2] -= 64; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); if ( trace.fraction == 1.0 ) {//FIXME: if velocity[2] < 0 and didn't jump, use some falling anim if ( pm->cmd.forwardmove >= 0 ) { if(!PM_InDeathAnim()) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE); } pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; } else { if(!PM_InDeathAnim()) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE); } pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; } } } pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; pml.walking = qfalse; } /* ============= PM_GroundTrace ============= */ static void PM_GroundTrace( void ) { vec3_t point; trace_t trace; point[0] = pm->ps->origin[0]; point[1] = pm->ps->origin[1]; point[2] = pm->ps->origin[2] - 0.25; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); pml.groundTrace = trace; // do something corrective if the trace starts in a solid... if ( trace.allsolid ) { PM_CorrectAllSolid(); return; } // if the trace didn't hit anything, we are in free fall if ( trace.fraction == 1.0 ) { PM_GroundTraceMissed(); pml.groundPlane = qfalse; pml.walking = qfalse; return; } // check if getting thrown off the ground if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { if ( pm->debugLevel ) { Com_Printf("%i:kickoff\n", c_pmove); } // go into jump animation if ( pm->cmd.forwardmove >= 0 ) { if(!PM_InDeathAnim()) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE); } pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; } else { if(!PM_InDeathAnim()) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE); } pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; } pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; pml.walking = qfalse; return; } // slopes that are too steep will not be considered onground if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { if ( pm->debugLevel ) { Com_Printf("%i:steep\n", c_pmove); } // FIXME: if they can't slide down the slope, let them // walk (sharp crevices) pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qtrue; pml.walking = qfalse; return; } pml.groundPlane = qtrue; pml.walking = qtrue; // hitting solid ground will end a waterjump if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); pm->ps->pm_time = 0; } if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { // just hit the ground if ( pm->debugLevel ) { Com_Printf("%i:Land\n", c_pmove); } PM_CrashLand(); // don't do landing time if we were just going down a slope if ( pml.previous_velocity[2] < -200 ) { // don't allow another jump for a little while pm->ps->pm_flags |= PMF_TIME_LAND; pm->ps->pm_time = 250; } if (!pm->cmd.forwardmove && !pm->cmd.rightmove) { pm->ps->velocity[2] = 0; //wouldn't normally want this because of slopes, but we aren't tyring to move... } } pm->ps->groundEntityNum = trace.entityNum; // don't reset the z velocity for slopes // pm->ps->velocity[2] = 0; PM_AddTouchEnt( trace.entityNum ); } /* ============= PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving ============= */ static void PM_SetWaterLevel( void ) { vec3_t point; int cont; int sample1; int sample2; // // get waterlevel, accounting for ducking // pm->waterlevel = 0; pm->watertype = 0; point[0] = pm->ps->origin[0]; point[1] = pm->ps->origin[1]; point[2] = pm->ps->origin[2] + DEFAULT_MINS_2 + 1; cont = pm->pointcontents( point, pm->ps->clientNum ); if ( cont & (MASK_WATER|CONTENTS_LADDER) ) { sample2 = pm->ps->viewheight - DEFAULT_MINS_2; sample1 = sample2 / 2; pm->watertype = cont; pm->waterlevel = 1; point[2] = pm->ps->origin[2] + DEFAULT_MINS_2 + sample1; cont = pm->pointcontents (point, pm->ps->clientNum ); if ( cont & (MASK_WATER|CONTENTS_LADDER) ) { if ( Q_stricmp( "biohulk", pm->gent->NPC_type ) != 0 ) {//special hack for biohulk- they're never waist-high in water pm->waterlevel = 2; } point[2] = pm->ps->origin[2] + DEFAULT_MINS_2 + sample2; cont = pm->pointcontents (point, pm->ps->clientNum ); if ( cont & (MASK_WATER|CONTENTS_LADDER) ) { pm->waterlevel = 3; } } } } /* ============== PM_SetBounds Sets mins, maxs ============== */ static void PM_SetBounds (void) { if ( pm->gent && pm->gent->client ) { if ( !pm->gent->mins[0] || !pm->gent->mins[1] || !pm->gent->mins[2] || !pm->gent->maxs[0] || !pm->gent->maxs[1] || !pm->gent->maxs[2] ) { //assert(0); } VectorCopy( pm->gent->mins, pm->mins ); VectorCopy( pm->gent->maxs, pm->maxs ); } else { if ( !DEFAULT_MINS_0 || !DEFAULT_MINS_1 || !DEFAULT_MAXS_0 || !DEFAULT_MAXS_1 || !DEFAULT_MINS_2 || !DEFAULT_MAXS_2 ) { assert(0); } pm->mins[0] = DEFAULT_MINS_0; pm->mins[1] = DEFAULT_MINS_1; pm->maxs[0] = DEFAULT_MAXS_0; pm->maxs[1] = DEFAULT_MAXS_1; pm->mins[2] = DEFAULT_MINS_2; pm->maxs[2] = DEFAULT_MAXS_2; } } /* ============== PM_CheckDuck Sets mins, maxs, and pm->ps->viewheight ============== */ static void PM_CheckDuck (void) { trace_t trace; int standheight; int crouchheight; int oldHeight; if ( pm->gent && pm->gent->client ) { if ( !pm->gent->mins[0] || !pm->gent->mins[1] || !pm->gent->mins[2] || !pm->gent->maxs[0] || !pm->gent->maxs[1] || !pm->gent->maxs[2] ) { //assert(0); } standheight = pm->gent->client->standheight; crouchheight = pm->gent->client->crouchheight; } else { if ( !DEFAULT_MINS_0 || !DEFAULT_MINS_1 || !DEFAULT_MAXS_0 || !DEFAULT_MAXS_1 || !DEFAULT_MINS_2 || !DEFAULT_MAXS_2 ) { assert(0); } standheight = DEFAULT_MAXS_2; crouchheight = CROUCH_MAXS_2; } oldHeight = pm->maxs[2]; if ( pm->cmd.upmove < 0 ) { // trying to duck pm->maxs[2] = crouchheight; pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET;//CROUCH_VIEWHEIGHT; if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) {//Not ducked already and trying to duck in mid-air //will raise your feet, unducking whilst in air will drop feet if ( !(pm->ps->pm_flags&PMF_DUCKED) ) { pm->ps->eFlags ^= EF_TELEPORT_BIT; } pm->ps->origin[2] += oldHeight - pm->maxs[2];//diff will be zero if were already ducking //Don't worry, we know we fit in a smaller size } pm->ps->pm_flags |= PMF_DUCKED; } else { // want to stop ducking, stand up if possible if ( pm->ps->pm_flags & PMF_DUCKED ) {//Was ducking if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) {//unducking whilst in air will try to drop feet pm->maxs[2] = standheight; pm->ps->origin[2] += oldHeight - pm->maxs[2]; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); if ( !trace.allsolid ) { pm->ps->eFlags ^= EF_TELEPORT_BIT; pm->ps->pm_flags &= ~PMF_DUCKED; } else {//Put us back pm->ps->origin[2] -= oldHeight - pm->maxs[2]; } //NOTE: this isn't the best way to check this, you may have room to unduck //while in air, but your feet are close to landing. Probably won't be a //noticable shortcoming } else { // try to stand up pm->maxs[2] = standheight; pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); if ( !trace.allsolid ) { pm->ps->pm_flags &= ~PMF_DUCKED; } } } if ( pm->ps->pm_flags & PMF_DUCKED ) {//Still ducking pm->maxs[2] = crouchheight; pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET;//CROUCH_VIEWHEIGHT; } else {//standing now pm->maxs[2] = standheight; //FIXME: have a crouchviewheight and standviewheight on ent? pm->ps->viewheight = standheight + STANDARD_VIEWHEIGHT_OFFSET;//DEFAULT_VIEWHEIGHT; } } } //=================================================================== /* =============== PM_Footsteps =============== */ static void PM_Footsteps( void ) { float bobmove; int old, oldAnim; qboolean footstep = qfalse; qboolean careful = qfalse; qboolean validNPC = qfalse; if( pm->gent == NULL || pm->gent->client == NULL ) return; if( pm->gent->NPC != NULL ) { validNPC = qtrue; if( pm->cmd.buttons & BUTTON_CAREFUL ) { careful = qtrue; } } pm->gent->client->renderInfo.legsFpsMod = 1.0f; // // calculate speed and cycle to be used for // all cyclic walking effects // pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + pm->ps->velocity[1] * pm->ps->velocity[1] ); if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { // airborne leaves position in cycle intact, but doesn't advance if ( pm->waterlevel > 0 ) { if ( pm->watertype & CONTENTS_LADDER ) {//FIXME: check for watertype, save waterlevel for whether to play //the get off ladder transition anim! if ( pm->ps->velocity[2] ) {//going up or down it int anim; if ( pm->ps->velocity[2] > 0 ) { anim = BOTH_LADDER_UP1; } else { anim = BOTH_LADDER_DWN1; } PM_SetAnim( pm, SETANIM_LEGS, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); if ( pm->waterlevel >= 2 ) //arms on ladder { PM_SetAnim( pm, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } if (fabs(pm->ps->velocity[2]) >5) { bobmove = 0.005 * fabs(pm->ps->velocity[2]); // climbing bobs slow if (bobmove > 0.3) bobmove = 0.3F; goto DoFootSteps; } } else { PM_SetAnim( pm, SETANIM_LEGS, BOTH_LADDER_IDLE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); pm->ps->legsAnimTimer += 300; if ( pm->waterlevel >= 2 ) //arms on ladder { PM_SetAnim( pm, SETANIM_TORSO, BOTH_LADDER_IDLE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); pm->ps->torsoAnimTimer += 300; } } return; } else if ( pm->waterlevel > 1 ) //off ground and in deep water { // PM_SetAnim(pm,SETANIM_LEGS,BOTH_SWIM1,SETANIM_FLAG_NORMAL); PM_SetAnim(pm,SETANIM_LEGS,BOTH_FLOAT2,SETANIM_FLAG_NORMAL); return; } } else { if ( pm->ps->pm_flags & PMF_DUCKED ) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1IDLE,SETANIM_FLAG_NORMAL); } return; } } // if not trying to move if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { if ( pm->xyspeed < 5 ) { pm->ps->bobCycle = 0; // start at beginning of cycle again if ( pm->ps->pm_flags & PMF_DUCKED ) { if(!pm->gent || !PM_InOnGroundAnim(pm->gent)) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1IDLE,SETANIM_FLAG_NORMAL); } } else { if ( pm->ps->weapon == WP_IMPERIAL_BLADE || pm->ps->weapon == WP_KLINGON_BLADE ) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND3,SETANIM_FLAG_NORMAL); } else if( careful || (validNPC && pm->ps->weapon > WP_PHASER && pm->ps->weapon < WP_TRICORDER && pm->gent->client->race != RACE_BORG))//Being careful or carrying a 2-handed weapon {//Squadmates use BOTH_STAND2 oldAnim = pm->ps->legsAnim&~ANIM_TOGGLEBIT; if(oldAnim != BOTH_GUARD_LOOKAROUND1 && oldAnim != BOTH_GUARD_IDLE1 && oldAnim != BOTH_STAND2_RANDOM1 && oldAnim != BOTH_STAND2_RANDOM2 && oldAnim != BOTH_STAND2_RANDOM3 && oldAnim != BOTH_STAND2_RANDOM4 && oldAnim != BOTH_STAND2TO4 && oldAnim != BOTH_STAND4TO2 && oldAnim != BOTH_STAND4 ) {//Don't auto-override the guard idles PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); //if(oldAnim != BOTH_STAND2 && (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_STAND2) //{ // pm->ps->legsAnimTimer = 500; //} } } /* else if(pm->gent && pm->gent->client->race == RACE_BORG && pm->gent->enemy) {//Angry borg PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); } */ else { PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); } } } else {//not trying to move, but I am, must be sliding? Default to standing? PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); } return; } if ( pm->ps->pm_flags & PMF_DUCKED ) { bobmove = 0.5; // ducked characters bob much faster if(!pm->gent || !PM_InOnGroundAnim(pm->gent)) { PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1WALK,SETANIM_FLAG_NORMAL); } // ducked characters never play footsteps } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) {//Moving backwards if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {//running backwards bobmove = 0.4F; // faster speeds bob faster if( careful ) {//Squadmates use LEGS_RUNBACK2 oldAnim = pm->ps->legsAnim&~ANIM_TOGGLEBIT; PM_SetAnim(pm,SETANIM_LEGS,LEGS_RUNBACK2,SETANIM_FLAG_NORMAL); //Now figure out an FPS bias if(pm->gent->client->ps.speed) { pm->gent->client->renderInfo.legsFpsMod = (float)pm->gent->NPC->stats.runSpeed/(float)pm->gent->client->ps.speed; } if(oldAnim != LEGS_RUNBACK2 && (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == LEGS_RUNBACK2) { pm->ps->legsAnimTimer = 500; } } else { PM_SetAnim(pm,SETANIM_LEGS,LEGS_RUNBACK1,SETANIM_FLAG_NORMAL); } footstep = qtrue; } else {//walking backwards bobmove = 0.3F; // faster speeds bob faster if( careful ) {//Squadmates use BOTH_WALK2 PM_SetAnim(pm,SETANIM_LEGS,LEGS_WALKBACK2,SETANIM_FLAG_NORMAL); } else { PM_SetAnim(pm,SETANIM_LEGS,LEGS_WALKBACK1,SETANIM_FLAG_NORMAL); } // footstep = qtrue; } } else { if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { bobmove = 0.4F; // faster speeds bob faster if( careful ) {//Squadmates use BOTH_RUN2 oldAnim = pm->ps->legsAnim&~ANIM_TOGGLEBIT; PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN2,SETANIM_FLAG_NORMAL); //FIXME: Until we get a better running animation //Now figure out an FPS bias if(pm->gent->client->ps.speed) { pm->gent->client->renderInfo.legsFpsMod = (float)pm->gent->NPC->stats.runSpeed/(float)pm->gent->client->ps.speed; } if(oldAnim != BOTH_RUN2 && (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_RUN2) { pm->ps->legsAnimTimer = 500; } } else { PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN1,SETANIM_FLAG_NORMAL); } footstep = qtrue; } else { bobmove = 0.3F; // walking bobs slow if( careful || (validNPC && pm->ps->weapon > WP_PHASER && pm->ps->weapon < WP_TRICORDER && pm->gent->client->race != RACE_BORG))//Being careful or carrying a 2-handed weapon {//Squadmates use BOTH_WALK2 oldAnim = pm->ps->legsAnim&~ANIM_TOGGLEBIT; PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK2,SETANIM_FLAG_NORMAL); if(pm->gent->client->ps.speed) { pm->gent->client->renderInfo.legsFpsMod = (float)pm->gent->NPC->stats.walkSpeed/(float)pm->gent->client->ps.speed; } if(oldAnim != BOTH_WALK2 && (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALK2) { pm->ps->legsAnimTimer = 500; } } /* else if(pm->gent && pm->gent->client->race == RACE_BORG && pm->gent->enemy) {//Angry borg PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK2,SETANIM_FLAG_NORMAL); } */ else { PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK1,SETANIM_FLAG_NORMAL); } //Enemy NPCs always make footsteps for the benefit of the player if ( pm->gent && pm->gent->NPC && pm->gent->client && pm->gent->client->playerTeam != TEAM_STARFLEET ) { footstep = qtrue; } } } if(pm->gent != NULL) { if( pm->gent->client->renderInfo.legsFpsMod > 2 ) { pm->gent->client->renderInfo.legsFpsMod = 2; } else if(pm->gent->client->renderInfo.legsFpsMod < 0.5) { pm->gent->client->renderInfo.legsFpsMod = 0.5; } } //Stasis have no feet, hence, no footsteps if ( pm->gent->client->playerTeam == TEAM_STASIS ) return; DoFootSteps: // check for footstep / splash sounds old = pm->ps->bobCycle; pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; // if we just crossed a cycle boundary, play an apropriate footstep event if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { if ( pm->watertype & CONTENTS_LADDER ) { if ( !pm->noFootsteps ) { if (pm->ps->groundEntityNum == ENTITYNUM_NONE) {// on ladder PM_AddEvent( EV_FOOTSTEP_METAL ); } else { PM_AddEvent( PM_FootstepForSurface() ); //still on ground } if ( pm->gent && pm->gent->s.number == 0 ) { if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, pm->ps->origin, 128, AEL_MINOR ); } } } } else if ( pm->waterlevel == 0 ) { // on ground will only play sounds if running if ( footstep && !pm->noFootsteps ) { PM_AddEvent( PM_FootstepForSurface() ); if ( pm->gent && pm->gent->s.number == 0 ) { vec3_t bottom; VectorCopy( pm->ps->origin, bottom ); bottom[2] += pm->mins[2]; if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, bottom, 256, AEL_MINOR ); } } } } else if ( pm->waterlevel == 1 ) { // splashing PM_AddEvent( EV_FOOTSPLASH ); if ( pm->gent && pm->gent->s.number == 0 ) { vec3_t bottom; VectorCopy( pm->ps->origin, bottom ); bottom[2] += pm->mins[2]; if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, bottom, 256, AEL_MINOR ); } } } else if ( pm->waterlevel == 2 ) { // wading / swimming at surface PM_AddEvent( EV_SWIM ); if ( pm->gent && pm->gent->s.number == 0 ) { if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) { AddSoundEvent( pm->gent, pm->ps->origin, 128, AEL_MINOR ); } } } // no sound when completely underwater } } /* ============== PM_WaterEvents Generate sound events for entering and leaving water ============== */ static void PM_WaterEvents( void ) { // FIXME? if ( pm->watertype & CONTENTS_LADDER ) //fake water for ladder { return; } // // if just entered a water volume, play a sound // if (!pml.previous_waterlevel && pm->waterlevel) { PM_AddEvent( EV_WATER_TOUCH ); } // // if just completely exited a water volume, play a sound // if (pml.previous_waterlevel && !pm->waterlevel) { PM_AddEvent( EV_WATER_LEAVE ); } // // check for head just going under water // if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { PM_AddEvent( EV_WATER_UNDER ); } // // check for head just coming out of water // if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { PM_AddEvent( EV_WATER_CLEAR ); } } qboolean G_WeaponChangeEffect ( gentity_t *ent ) { if ( !ent || !ent->client ) { return qfalse; } int torsoAnim = PM_TorsoAnimForFrame( ent, ent->client->renderInfo.torsoFrame ); if ( torsoAnim == TORSO_DROPWEAP1 || torsoAnim == TORSO_DROPWEAP2 || torsoAnim == TORSO_DROPWEAP3 || torsoAnim == TORSO_RAISEWEAP1 || torsoAnim == TORSO_RAISEWEAP2 || torsoAnim == TORSO_RAISEWEAP3 ) {//are getting the weapon from our transporter buffer if ( ent->s.number == 0 || strstr( ent->client->renderInfo.torsoModelName , "hazard" ) != NULL || Q_stricmp( "alexascav", ent->NPC_type ) == 0 || Q_stricmp( "munroscav", ent->NPC_type ) == 0 ) {//UGH... hacky way of knowing if this is a hazard guy //Do weapon select transporter effect ent->fx_time = level.time; ent->s.powerups |= ( 1 << PW_DISINT_6 ); ent->client->ps.powerups[PW_DISINT_6] = level.time + 600; return qtrue; } } return qfalse; } /* =============== PM_BeginWeaponChange =============== */ static void PM_BeginWeaponChange( int weapon ) { if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { return; } if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { return; } if ( pm->ps->weaponstate == WEAPON_DROPPING ) { return; } if ( cg.time > 0 ) {//this way we don't get that annoying change weapon sound every time a map starts PM_AddEvent( EV_CHANGE_WEAPON ); } pm->ps->weaponstate = WEAPON_DROPPING; pm->ps->weaponTime += 200; PM_SetAnim(pm,SETANIM_TORSO,TORSO_DROPWEAP1,SETANIM_FLAG_NORMAL); if ( pm->gent && pm->gent->client ) {//predicting weapon switch effect pm->gent->fx_time = level.time; pm->gent->s.powerups |= ( 1 << PW_DISINT_6 ); pm->gent->client->ps.powerups[PW_DISINT_6] = level.time + PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, TORSO_DROPWEAP1) + PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, TORSO_RAISEWEAP1); } } /* =============== PM_FinishWeaponChange =============== */ static void PM_FinishWeaponChange( void ) { int weapon; weapon = pm->cmd.weapon; if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { weapon = WP_NONE; } if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { weapon = WP_NONE; } pm->ps->weapon = weapon; pm->ps->weaponstate = WEAPON_RAISING; pm->ps->weaponTime += 250; PM_SetAnim(pm,SETANIM_TORSO,TORSO_RAISEWEAP1,SETANIM_FLAG_NORMAL); } /* ============== PM_Use Generates a use event ============== */ #define USE_DELAY 2000 void PM_Use( void ) { if ( pm->ps->useTime > 0 ) pm->ps->useTime -= 100;//pm->cmd.msec; if ( pm->ps->useTime > 0 ) { return; } if ( ! (pm->cmd.buttons & BUTTON_USE ) ) { pm->useEvent = 0; pm->ps->useTime = 0; return; } pm->useEvent = EV_USE; pm->ps->useTime = USE_DELAY; } /* ============== PM_Weapon Generates weapon events and modifes the weapon counter ============== */ static void PM_Weapon( void ) { int addTime,amount; qboolean delayed_fire = qfalse; weaponInfo_t *weapon; if(pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0) {//FIXME: this is going to fire off one frame before you expect, actually pm->gent->client->fireDelay -= pml.msec; if(pm->gent->client->fireDelay <= 0) {//just finished delay timer pm->gent->client->fireDelay = 0; delayed_fire = qtrue; } } // don't allow attack until all buttons are up if ( pm->ps->pm_flags & PMF_RESPAWNED ) { return; } //Check for phaser ammo recharge if ( pm->ps->rechargeTime <= 0 ) { if ( pm->ps->ammo[weaponData[WP_PHASER].ammoIndex] < ammoData[AMMO_PHASER].max) { pm->ps->rechargeTime = PHASER_RECHARGE_TIME; pm->ps->ammo[weaponData[WP_PHASER].ammoIndex]++; } } else { pm->ps->rechargeTime -= pml.msec; } // check for dead player if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { if ( pm->gent && pm->gent->client ) { // Sigh..borg shouldn't drop their weapon attachments when they die. if ( pm->gent->client->playerTeam != TEAM_BORG ) { pm->ps->weapon = WP_NONE; } } if ( pm->gent ) { pm->gent->s.loopSound = 0; } return; } // make weapon function if ( pm->ps->weaponTime > 0 ) { pm->ps->weaponTime -= pml.msec; } // check for weapon change // can't change if weapon is firing, but can change again if lowering or raising if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { if ( pm->ps->weapon != pm->cmd.weapon ) { PM_BeginWeaponChange( pm->cmd.weapon ); } } if ( pm->ps->weaponTime > 0 ) { return; } // change weapon if time if ( pm->ps->weaponstate == WEAPON_DROPPING ) { PM_FinishWeaponChange(); return; } if ( pm->ps->weaponstate == WEAPON_RAISING ) {//Just selected the weapon pm->ps->weaponstate = WEAPON_IDLE; if(pm->gent && pm->gent->s.number == 0) { PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); } else { switch(pm->ps->weapon) { case WP_PHASER: PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE1,SETANIM_FLAG_NORMAL); break; case WP_COMPRESSION_RIFLE: PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE2,SETANIM_FLAG_NORMAL); break; case WP_BORG_WEAPON: case WP_BORG_TASER: PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); break; case WP_TRICORDER: case WP_RED_HYPO: case WP_BLUE_HYPO: case WP_VOYAGER_HYPO: PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); break; default: PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); break; } } return; } weapon = &cg_weapons[pm->ps->weapon]; if(!delayed_fire) { // check for fire if ( !(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) { pm->ps->weaponTime = 0; if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) {//Still firing pm->ps->weaponstate = WEAPON_FIRING; } else if ( pm->ps->weaponstate != WEAPON_READY ) { pm->ps->weaponstate = WEAPON_IDLE; } return; } // start the animation even if out of ammo switch(pm->ps->weapon) { case WP_PHASER://1 - handed PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; case WP_COMPRESSION_RIFLE://2-handed PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; case WP_BORG_WEAPON: PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_BORG_TASER: PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_BORG_ASSIMILATOR: case WP_BORG_DRILL: PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_FORGE_PSYCH: PM_SetAnim(pm,SETANIM_BOTH,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_FORGE_PROJ: if ( rand() & 1 ) PM_SetAnim(pm,SETANIM_BOTH,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); else PM_SetAnim(pm,SETANIM_BOTH,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_MELEE: // The slash trail "formation" hasn't been started yet, so don't try and use the old points. pm->gent->trigger_formation = qfalse; if ( pm->gent->client->race == RACE_REAVER || pm->gent->client->race == RACE_HARVESTER || pm->gent->client->race == RACE_STASIS || pm->gent->client->race == RACE_BOT) { if ( rand() & 1 ) PM_SetAnim(pm,SETANIM_BOTH,BOTH_MELEE1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); else PM_SetAnim(pm,SETANIM_BOTH,BOTH_MELEE2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } else if ( pm->gent->client->race == RACE_AVATAR ) { PM_SetAnim(pm,SETANIM_BOTH,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } else { PM_SetAnim(pm,SETANIM_BOTH,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } break; case WP_STASIS_ATTACK: PM_SetAnim( pm, SETANIM_BOTH,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); break; case WP_TETRION_DISRUPTOR: PM_SetAnim( pm, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_SCAVENGER_RIFLE: case WP_CHAOTICA_GUARD_GUN: if ( (pm->ps->torsoAnim&~ANIM_TOGGLEBIT) != BOTH_HEROSTANCE1 ) {//HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK PM_SetAnim( pm, SETANIM_TORSO, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } break; case WP_PARASITE: PM_SetAnim( pm, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_KLINGON_BLADE: case WP_IMPERIAL_BLADE: { int anim = -1; int parts = SETANIM_TORSO; anim = PM_PickAnim( pm->gent, BOTH_MELEE1, BOTH_MELEE3 ); if ( anim == -1 ) { anim = BOTH_ATTACK1; } if ( !pm->cmd.forwardmove && !pm->cmd.rightmove && pm->cmd.upmove >= 0 ) { parts = SETANIM_BOTH; } PM_SetAnim(pm,parts,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); } break; case WP_DESPERADO: PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; case WP_PALADIN: PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; case WP_BLUE_HYPO: case WP_RED_HYPO: case WP_VOYAGER_HYPO: PM_SetAnim(pm,SETANIM_TORSO,TORSO_HYPOSPRAY1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; case WP_TRICORDER: PM_SetAnim(pm,SETANIM_TORSO,TORSO_MEDICORDER1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); break; case WP_BOT_WELDER: PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; default://2-handed heavy weapon PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); break; } } pm->ps->weaponstate = WEAPON_FIRING; if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) amount = weaponData[pm->ps->weapon].altEnergyPerShot; else amount = weaponData[pm->ps->weapon].energyPerShot; // take an ammo away if not infinite if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) { // enough energy to fire this weapon? if ((pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - amount) >= 0) { pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= amount; } else // Not enough energy { if ( pm->ps->weapon == WP_PHASER ) { // force our ammo to be zero pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] = 0; } else { // Switch weapons PM_AddEvent( EV_NOAMMO ); pm->ps->weaponTime += 500; return; } } } if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) {//FIXME: this is going to fire off one frame before you expect, actually // Clear these out since we're not actually firing yet pm->ps->eFlags &= ~EF_FIRING; pm->ps->eFlags &= ~EF_ALT_FIRING; return; } if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) { PM_AddEvent( EV_ALT_FIRE ); addTime = weaponData[pm->ps->weapon].altFireTime; } else { PM_AddEvent( EV_FIRE_WEAPON ); addTime = weaponData[pm->ps->weapon].fireTime; } //If the phaser has been fired, delay the next recharge time if ( pm->ps->weapon == WP_PHASER ) { pm->ps->rechargeTime = PHASER_RECHARGE_TIME; } if(pm->gent && pm->gent->NPC != NULL ) {//NPCs have their own refire logic return; } if( MatrixMode ) {//Special test for Matrix Mode (tm) //player always fires at normal speed addTime *= g_timescale->value; } pm->ps->weaponTime += addTime; } /* ================ PM_DropTimers ================ */ static void PM_DropTimers( void ) { // drop misc timing counter if ( pm->ps->pm_time ) { if ( pml.msec >= pm->ps->pm_time ) { pm->ps->pm_flags &= ~PMF_ALL_TIMES; pm->ps->pm_time = 0; } else { pm->ps->pm_time -= pml.msec; } } // drop legs animation counter if ( pm->ps->legsAnimTimer > 0 ) { int newTime = pm->ps->legsAnimTimer - pml.msec; if ( newTime < 0 ) { newTime = 0; } PM_SetLegsAnimTimer( pm->gent, &pm->ps->legsAnimTimer, newTime ); } // drop torso animation counter if ( pm->ps->torsoAnimTimer > 0 ) { int newTime = pm->ps->torsoAnimTimer - pml.msec; if ( newTime < 0 ) { newTime = 0; } PM_SetTorsoAnimTimer( pm->gent, &pm->ps->torsoAnimTimer, newTime ); } } void PM_SetSpecialMoveValues (void ) { Flying = qfalse; if ( pm->gent ) { if ( pm->gent->NPC ) { if ( pm->gent->NPC->stats.moveType == MT_FLYSWIM ) { Flying = qtrue; } } } MatrixMode = qfalse; if ( g_timescale != NULL ) { if ( g_timescale->value < 1.0f ) { if ( pm->ps->clientNum == 0 ) { MatrixMode = qtrue; } } } if( MatrixMode ) { float upScale = 1.0f/g_timescale->value; //player always moves at full speed of 20fps pml.msec *= upScale; if ( pm->cmd.forwardmove * upScale > 127 ) { pm->cmd.forwardmove = 127; } else if ( pm->cmd.forwardmove * upScale < -127 ) { pm->cmd.forwardmove = -127; } else { pm->cmd.forwardmove = (int)(pm->cmd.forwardmove * upScale); } if ( pm->cmd.rightmove * upScale > 127 ) { pm->cmd.rightmove = 127; } else if ( pm->cmd.rightmove * upScale < -127 ) { pm->cmd.rightmove = -127; } else { pm->cmd.rightmove = (int)(pm->cmd.rightmove * upScale); } } } /* ================ Pmove Can be called by either the server or the client ================ */ void Pmove( pmove_t *pmove ) { pm = pmove; int amount; // this counter lets us debug movement problems with a journal by setting a conditional breakpoint fot the previous frame c_pmove++; // clear results pm->numtouch = 0; pm->watertype = 0; pm->waterlevel = 0; // Clear the blocked flag pm->ps->pm_flags &= ~PMF_BLOCKED; pm->ps->pm_flags &= ~PMF_BUMPED; // set the talk balloon flag if ( pm->cmd.buttons & BUTTON_TALK ) pm->ps->eFlags |= EF_TALK; else pm->ps->eFlags &= ~EF_TALK; if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].altEnergyPerShot; else amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].energyPerShot; // set the firing flag for continuous beam weapons, phaser will fire even if out of ammo if ( (( pm->cmd.buttons & BUTTON_ATTACK || pm->cmd.buttons & BUTTON_ALT_ATTACK ) && ( amount >= 0 || pm->ps->weapon == WP_PHASER )) ) { if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) { pm->ps->eFlags |= EF_ALT_FIRING; } else { pm->ps->eFlags &= ~EF_ALT_FIRING; } // This flag should always get set, even when alt-firing pm->ps->eFlags |= EF_FIRING; } else { int iFlags = pm->ps->eFlags; // Clear 'em out pm->ps->eFlags &= ~EF_FIRING; pm->ps->eFlags &= ~EF_ALT_FIRING; // if I don't check the flags before stopping FX then it switches them off too often, which tones down // the stronger FFFX so you can hardly feel them. However, if you only do iton these flags then the // repeat-fire weapons like tetrion and dreadnought don't switch off quick enough. So... // if (pm->ps->weapon == WP_TETRION_DISRUPTOR || pm->ps->weapon == WP_DREADNOUGHT || (iFlags & (EF_FIRING|EF_ALT_FIRING)) ) { cgi_FF_StopAllFX(); } } // clear the respawned flag if attack and use are cleared if ( pm->ps->stats[STAT_HEALTH] > 0 && !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { pm->ps->pm_flags &= ~PMF_RESPAWNED; } // if talk button is down, dissallow all other input // this is to prevent any possible intercept proxy from // adding fake talk balloons if ( pmove->cmd.buttons & BUTTON_TALK ) { pmove->cmd.buttons = 0; pmove->cmd.forwardmove = 0; pmove->cmd.rightmove = 0; pmove->cmd.upmove = 0; } // clear all pmove local vars memset (&pml, 0, sizeof(pml)); // determine the time pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; if ( pml.msec < 1 ) { pml.msec = 1; } else if ( pml.msec > 200 ) { pml.msec = 200; } PM_SetSpecialMoveValues(); pm->ps->commandTime = pmove->cmd.serverTime; // save old org in case we get stuck VectorCopy (pm->ps->origin, pml.previous_origin); // save old velocity for crashlanding VectorCopy (pm->ps->velocity, pml.previous_velocity); pml.frametime = pml.msec * 0.001; // update the viewangles PM_UpdateViewAngles( pm->ps, &pm->cmd, pm->gent); AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); if ( pm->cmd.upmove < 10 ) { // not holding jump pm->ps->pm_flags &= ~PMF_JUMP_HELD; } // decide if backpedaling animations should be used if ( pm->cmd.forwardmove < 0 ) { pm->ps->pm_flags |= PMF_BACKWARDS_RUN; } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; } if ( pm->ps->pm_type >= PM_DEAD ) { pm->cmd.forwardmove = 0; pm->cmd.rightmove = 0; pm->cmd.upmove = 0; if ( pm->ps->viewheight > -12 ) {//slowly sink view to ground pm->ps->viewheight -= 1; } } if ( pm->ps->pm_type == PM_SPECTATOR ) { PM_CheckDuck (); PM_FlyMove (); PM_DropTimers (); return; } if ( pm->ps->pm_type == PM_NOCLIP ) { PM_NoclipMove (); PM_DropTimers (); return; } if (pm->ps->pm_type == PM_FREEZE) { return; // no movement at all } if ( pm->ps->pm_type == PM_INTERMISSION ) { return; // no movement at all } // set watertype, and waterlevel PM_SetWaterLevel(); if ( !(pm->watertype & CONTENTS_LADDER) ) {//Don't want to remember this for ladders, is only for waterlevel change events (sounds) pml.previous_waterlevel = pmove->waterlevel; } // set mins, maxs, and viewheight PM_SetBounds(); if ( !Flying && !(pm->watertype & CONTENTS_LADDER) && pm->ps->pm_type != PM_DEAD ) {//NOTE: noclippers shouldn't jump or duck either, no? PM_CheckDuck(); } // set groundentity PM_GroundTrace(); if ( pm->ps->pm_type == PM_DEAD ) { PM_DeadMove (); } PM_DropTimers(); if ( Flying ) { // flight powerup doesn't allow jump and has different friction PM_FlyMove(); } else if ( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) { PM_WaterJumpMove(); } else if ( pm->waterlevel > 1 ) { // swimming or in ladder PM_WaterMove(); } else if ( pml.walking ) {// walking on ground vec3_t oldOrg; VectorCopy( pm->ps->origin, oldOrg ); PM_WalkMove(); if ( VectorCompare( oldOrg, pm->ps->origin ) ) {//didn't move, play no legs anim pm->cmd.forwardmove = pm->cmd.rightmove = 0; } } else { // airborne PM_AirMove(); } //PM_Animate(); // set groundentity, watertype, and waterlevel PM_GroundTrace(); PM_SetWaterLevel(); // weapons PM_Weapon(); if(pm->gent && pm->gent->s.number == 0) {//player only // Use PM_Use(); } // footstep events / legs animations PM_Footsteps(); // torso animation PM_TorsoAnimation(); // entering / leaving water splashes PM_WaterEvents(); // snap some parts of playerstate to save network bandwidth SnapVector( pm->ps->velocity ); }