std::vector< std::pair<std::string, Ogre::Vector3> > PhysicsSystem::doPhysicsFixed ( const std::vector<std::pair<std::string, Ogre::Vector3> >& actors) { Pmove(playerphysics); std::vector< std::pair<std::string, Ogre::Vector3> > response; for(std::map<std::string,OEngine::Physic::PhysicActor*>::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) { btVector3 newPos = it->second->getPosition(); Ogre::Vector3 coord(newPos.x(), newPos.y(), newPos.z()); if(it->first == "player"){ coord = playerphysics->ps.origin; //std::cout << "ZCoord: " << coord.z << "\n"; //std::cout << "Coord" << coord << "\n"; //coord = Ogre::Vector3(coord.x, coord.z, coord.y); //x, z, -y } response.push_back(std::pair<std::string, Ogre::Vector3>(it->first, coord)); } return response; }
/* * Cl_PredictMovement * * Run the latest movement command through the player movement code locally, * using the resulting origin and angles to reduce perceived latency. */ void Cl_PredictMovement(void) { int i, ack, current; pm_move_t pm; float step; if (cls.state != CL_ACTIVE) return; if (!cl_predict->value || (cl.frame.ps.pmove.pm_flags & PMF_NO_PREDICTION)) { for (i = 0; i < 3; i++) { // just set angles cl.predicted_angles[i] = cl.angles[i] + SHORT2ANGLE(cl.frame.ps.pmove.delta_angles[i]); } return; } ack = cls.netchan.incoming_acknowledged; current = cls.netchan.outgoing_sequence; // if we are too far out of date, just freeze if (current - ack >= CMD_BACKUP) { Com_Warn("Cl_PredictMovement: Exceeded CMD_BACKUP.\n"); return; } // copy current state to pmove memset(&pm, 0, sizeof(pm)); pm.Trace = Cl_Trace; pm.PointContents = Cl_Pointcontents; pm.s = cl.frame.ps.pmove; pm.s.gravity = cl_gravity; // run frames while (++ack <= current) { const int frame = ack & CMD_MASK; const user_cmd_t *cmd = &cl.cmds[frame]; if (!cmd->msec) continue; pm.cmd = *cmd; Pmove(&pm); // save for debug checking VectorCopy(pm.s.origin, cl.predicted_origins[frame]); } step = pm.s.origin[2] * 0.125 - cl.predicted_origin[2]; if ((pm.s.pm_flags & PMF_ON_STAIRS) && step > 4.0) { // save for stair lerping cl.predicted_step_time = cls.real_time; cl.predicted_step = step; } // copy results out for rendering VectorScale(pm.s.origin, 0.125, cl.predicted_origin); VectorCopy(pm.angles, cl.predicted_angles); cl.underwater = pm.water_level > 2; }
/* ================= SpectatorThink ================= */ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { gclient_t *client = ent->client; if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { client->ps.pm_type = PM_SPECTATOR; client->ps.speed = 400; // faster than normal pmove_t pm; // set up for pmove memset (&pm, 0, sizeof(pm)); pm.ps = &client->ps; pm.cmd = *ucmd; pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.animations = NULL; // perform a pmove Pmove (&pm); G_UpdatePlayerStateScores ( ent ); // save results of pmove VectorCopy( client->ps.origin, ent->s.origin ); G_TouchTriggers( ent ); trap_UnlinkEntity( ent ); } client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; // attack button cycles through spectators if ( client->sess.spectatorState != SPECTATOR_FOLLOW && g_forceFollow.integer ) { Cmd_FollowCycle_f( ent, 1 ); } if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { Cmd_FollowCycle_f( ent, 1 ); } else if ( ( client->buttons & BUTTON_ALT_ATTACK ) && ! ( client->oldbuttons & BUTTON_ALT_ATTACK ) ) { Cmd_FollowCycle_f( ent, -1 ); } else if ( !g_forceFollow.integer && ucmd->upmove > 0 && (client->ps.pm_flags & PMF_FOLLOW) ) { G_StopFollowing( ent ); } }
std::vector< std::pair<std::string, Ogre::Vector3> > PhysicsSystem::doPhysicsFixed ( const std::vector<std::pair<std::string, Ogre::Vector3> >& actors) { Pmove(playerphysics); std::vector< std::pair<std::string, Ogre::Vector3> > response; for(std::map<std::string,OEngine::Physic::PhysicActor*>::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) { Ogre::Vector3 coord = it->second->getPosition(); if(it->first == "player"){ coord = playerphysics->ps.origin ; } response.push_back(std::pair<std::string, Ogre::Vector3>(it->first, coord)); } return response; }
void PhysicActor::runPmove() { Pmove(pmove); Ogre::Vector3 newpos = pmove->ps.origin; mBody->getWorldTransform().setOrigin(btVector3(newpos.x, newpos.y, newpos.z)); }
/* ============== AICast_PredictMovement Simulates movement over a number of frames, returning the end position ============== */ void AICast_PredictMovement( cast_state_t *cs, int numframes, float frametime, aicast_predictmove_t *move, usercmd_t *ucmd, int checkHitEnt ) { int frame, i; playerState_t ps; pmove_t pm; trace_t tr; vec3_t end, startHitVec, thisHitVec, lastOrg, projPoint; qboolean checkReachMarker; //int pretime = Sys_MilliSeconds(); //G_Printf("PredictMovement: %f duration, %i frames\n", frametime, numframes ); VectorCopy( vec3_origin, startHitVec ); if ( cs->bs ) { ps = cs->bs->cur_ps; } else { ps = g_entities[cs->entityNum].client->ps; } ps.eFlags |= EF_DUMMY_PMOVE; move->stopevent = PREDICTSTOP_NONE; if ( checkHitEnt >= 0 && !Q_stricmp( g_entities[checkHitEnt].classname, "ai_marker" ) ) { checkReachMarker = qtrue; VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, ps.origin, startHitVec ); VectorCopy( ps.origin, lastOrg ); } else { checkReachMarker = qfalse; } // don't let the frametime be too high // while (frametime > 0.2) { // numframes *= 2; // frametime /= 2; // } for ( frame = 0; frame < numframes; frame++ ) { memset( &pm, 0, sizeof( pm ) ); pm.ps = &ps; pm.cmd = *ucmd; pm.oldcmd = *ucmd; pm.ps->commandTime = 0; pm.cmd.serverTime = (int)( 1000.0 * frametime ); pm.tracemask = g_entities[cs->entityNum].clipmask; //MASK_PLAYERSOLID; pm.trace = trap_TraceCapsule; //trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = qfalse; pm.noFootsteps = qtrue; // RF, not needed for prediction //pm.noWeapClips = qtrue; // (SA) AI's ignore weapon clips // perform a pmove Pmove( &pm ); if ( checkHitEnt >= 0 ) { // if we've hit the checkent, abort if ( checkReachMarker ) { VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, pm.ps->origin, thisHitVec ); if ( DotProduct( startHitVec, thisHitVec ) < 0 ) { // project the marker onto the movement vec, and check distance ProjectPointOntoVector( g_entities[checkHitEnt].r.currentOrigin, lastOrg, pm.ps->origin, projPoint ); if ( VectorDistance( g_entities[checkHitEnt].r.currentOrigin, projPoint ) < 8 ) { move->stopevent = PREDICTSTOP_HITENT; goto done; } } // use this position as the base for the next test //VectorCopy( thisHitVec, startHitVec ); VectorCopy( pm.ps->origin, lastOrg ); } // if we didnt reach the marker, then check for something that blocked us for ( i = 0; i < pm.numtouch; i++ ) { if ( pm.touchents[i] == pm.ps->groundEntityNum ) { continue; } if ( pm.touchents[i] == checkHitEnt ) { move->stopevent = PREDICTSTOP_HITENT; goto done; } else if ( pm.touchents[i] < MAX_CLIENTS || ( pm.touchents[i] != ENTITYNUM_WORLD && ( g_entities[pm.touchents[i]].s.eType != ET_MOVER || g_entities[pm.touchents[i]].moverState != MOVER_POS1 ) ) ) { // we have hit another entity, so abort move->stopevent = PREDICTSTOP_HITCLIENT; goto done; } else if ( !Q_stricmp( g_entities[pm.touchents[i]].classname, "script_mover" ) ) { // avoid script_mover's move->stopevent = PREDICTSTOP_HITCLIENT; goto done; } } } } done: // hack, if we are above ground, chances are it's because we only did one frame, and gravity isn't applied until // after the frame, so try and drop us down some if ( move->groundEntityNum == ENTITYNUM_NONE ) { VectorCopy( move->endpos, end ); end[2] -= 32; trap_Trace( &tr, move->endpos, pm.mins, pm.maxs, end, pm.ps->clientNum, pm.tracemask ); if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1 ) { VectorCopy( tr.endpos, pm.ps->origin ); pm.ps->groundEntityNum = tr.entityNum; } } // copy off the results VectorCopy( pm.ps->origin, move->endpos ); move->frames = numframes; //move->presencetype = cs->bs->presencetype; VectorCopy( pm.ps->velocity, move->velocity ); move->numtouch = pm.numtouch; memcpy( move->touchents, pm.touchents, sizeof( pm.touchents ) ); move->groundEntityNum = pm.ps->groundEntityNum; //G_Printf("PredictMovement: %i ms\n", -pretime + Sys_MilliSeconds() ); }
/* ================= SpectatorThink NiceAss: Heavy modifications will be here for AQ2-like spectator mode and zcam!? ================= */ void SpectatorThink(gentity_t * ent, usercmd_t * ucmd) { pmove_t pm; gclient_t *client; int clientNum; client = ent->client; clientNum = client - level.clients; if (client->sess.spectatorState == SPECTATOR_ZCAM) { client->ps.commandTime = ucmd->serverTime; camera_think(ent); } if (client->sess.spectatorState == SPECTATOR_FREE) { if (g_gametype.integer == GT_CTF && level.team_round_going && (client->sess.savedTeam == TEAM_RED || client->sess.savedTeam == TEAM_BLUE)) client->ps.pm_type = PM_FREEZE; else client->ps.pm_type = PM_SPECTATOR; client->ps.speed = 400; // faster than normal // set up for pmove memset(&pm, 0, sizeof(pm)); pm.ps = &client->ps; pm.cmd = *ucmd; pm.tracemask = 0; // spectators can fly through bodies pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.predict = qtrue; // perform a pmove Pmove(&pm); // save results of pmove VectorCopy(client->ps.origin, ent->s.origin); G_TouchTriggers(ent); trap_UnlinkEntity(ent); } // JBravo: Lets not allow bots to use any specmode other than FREE if (ent->r.svFlags & SVF_BOT) return; //Slicer - Changing this for aq2 way // Jump button cycles throught spectators if (client->sess.spectatorState == SPECTATOR_FOLLOW || client->sess.spectatorState == SPECTATOR_ZCAM) { if (ucmd->upmove >= 10) { if (!(client->ps.pm_flags & PMF_JUMP_HELD)) { client->ps.pm_flags |= PMF_JUMP_HELD; if (client->sess.spectatorState == SPECTATOR_ZCAM) { if (client->camera->mode == CAMERA_MODE_SWING) CameraSwingCycle(ent, 1); } else Cmd_FollowCycle_f(ent, 1); } } else client->ps.pm_flags &= ~PMF_JUMP_HELD; } client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; if (g_gametype.integer == GT_CTF && client->sess.spectatorState == SPECTATOR_FREE && (client->sess.savedTeam == TEAM_RED || client->sess.savedTeam == TEAM_BLUE)) return; // Attack Button cycles throught free view, follow or zcam if ((ucmd->buttons & BUTTON_ATTACK) && !(client->oldbuttons & BUTTON_ATTACK)) { if (g_gametype.integer == GT_TEAMPLAY && g_RQ3_limchasecam.integer != 0 && client->sess.referee == 0 ) { if (!OKtoFollow(clientNum)) return; if (client->sess.spectatorState != SPECTATOR_FOLLOW) { client->sess.spectatorState = SPECTATOR_FOLLOW; client->specMode = SPECTATOR_FOLLOW; client->ps.pm_flags |= PMF_FOLLOW; client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM; } return; } else if (client->sess.spectatorState == SPECTATOR_FREE && OKtoFollow(clientNum)) { client->sess.spectatorState = SPECTATOR_ZCAM; client->specMode = SPECTATOR_ZCAM; client->camera->mode = CAMERA_MODE_SWING; client->ps.stats[STAT_RQ3] |= RQ3_ZCAM; client->ps.pm_flags &= ~PMF_FOLLOW; CameraSwingCycle(ent, 1); RQ3_SpectatorMode(ent); } else if (client->sess.spectatorState == SPECTATOR_ZCAM && client->camera->mode == CAMERA_MODE_SWING && OKtoFollow(clientNum)) { client->sess.spectatorState = SPECTATOR_ZCAM; client->specMode = SPECTATOR_ZCAM; client->camera->mode = CAMERA_MODE_FLIC; client->ps.stats[STAT_RQ3] |= RQ3_ZCAM; client->ps.pm_flags &= ~PMF_FOLLOW; CameraFlicBegin(ent); RQ3_SpectatorMode(ent); } else if (client->sess.spectatorState == SPECTATOR_ZCAM && OKtoFollow(clientNum)) { client->sess.spectatorState = SPECTATOR_FOLLOW; client->specMode = SPECTATOR_FOLLOW; client->ps.pm_flags |= PMF_FOLLOW; client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM; Cmd_FollowCycle_f(ent, 1); RQ3_SpectatorMode(ent); } else if (client->sess.spectatorState == SPECTATOR_FOLLOW) { client->sess.spectatorState = SPECTATOR_FREE; client->specMode = SPECTATOR_FREE; client->ps.pm_flags &= ~PMF_FOLLOW; client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM; StopFollowing(ent); RQ3_SpectatorMode(ent); } } }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real(gentity_t * ent) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; usercmd_t *ucmd; int bJumping = 0; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if (ucmd->serverTime > level.time + 200) { ucmd->serverTime = level.time + 200; } if (ucmd->serverTime < level.time - 1000) { ucmd->serverTime = level.time - 1000; } client->lastUpdateFrame = level.framenum; msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if (msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW) { return; } if (msec > 200) { msec = 200; } if (pmove_msec.integer < 8) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } if (pmove_fixed.integer || client->pers.pmoveFixed) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer - 1) / pmove_msec.integer) * pmove_msec.integer; } // // check for exiting intermission // if (level.intermissiontime) { ClientIntermissionThink(client); return; } // spectators don't do much if (client->sess.sessionTeam == TEAM_SPECTATOR) { if (client->sess.spectatorState == SPECTATOR_SCOREBOARD) { return; } SpectatorThink(ent, ucmd); return; } // check for inactivity timer, but never drop the local client of a non-dedicated server if (!ClientInactivityTimer(client)) { return; } // clear the rewards if time if (level.time > client->rewardTime) { client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); } if (client->noclip) { client->ps.pm_type = PM_NOCLIP; } else if (client->ps.stats[STAT_HEALTH] <= 0) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; // set up for pmove oldEventSequence = client->ps.eventSequence; memset(&pm, 0, sizeof(pm)); if (ent->flags & FL_FORCE_GESTURE) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } //Elder: 3rb Code moved to bg_pmove.c (resides in PM_Weapon) pm.ps = &client->ps; pm.cmd = *ucmd; if (pm.ps->pm_type == PM_DEAD) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else if (ent->r.svFlags & SVF_BOT) { pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; } else { pm.tracemask = MASK_PLAYERSOLID; } // JBravo: fixing telefragging and shit during spawnig. (Thanks NiceAss! :) if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_TEAM) && ((ent->client->ps.stats[STAT_RQ3] & RQ3_PLAYERSOLID) != RQ3_PLAYERSOLID) && !level.lights_camera_action) { UnstickPlayer(ent); } if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_TEAM) && ((ent->client->ps.stats[STAT_RQ3] & RQ3_PLAYERSOLID) != RQ3_PLAYERSOLID)) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = (g_dmflags.integer & DF_NO_FOOTSTEPS) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; VectorCopy(client->ps.origin, client->oldOrigin); // JBravo: setting lca in pm if appropriate if (g_RQ3_lca.integer == 1) pm.lca = qtrue; else pm.lca = qfalse; pm.predict = qtrue; Pmove(&pm); if ((pm.cmd.upmove > 10) && (pm.waterlevel == 0) && ent->s.groundEntityNum != ENTITYNUM_NONE && pm.ps->groundEntityNum == ENTITYNUM_NONE) bJumping = 1; // save results of pmove if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qtrue); SendPendingPredictableEvents(&ent->client->ps); if (!(ent->client->ps.eFlags & EF_FIRING)) { client->fireHeld = qfalse; // for grapple } // use the snapped origin for linking so it matches client predicted versions VectorCopy(ent->s.pos.trBase, ent->r.currentOrigin); VectorCopy(pm.mins, ent->r.mins); VectorCopy(pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents(ent, oldEventSequence); // link entity now, after any personal teleporters have been used // JBravo: this call reactivates gibbed players. if (!ent->client->gibbed) trap_LinkEntity(ent); if (!ent->client->noclip) { G_TouchTriggers(ent); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); //test for solid areas in the AAS file BotTestAAS(ent->r.currentOrigin); // touch other objects ClientImpacts(ent, &pm); //Elder: someone added if (bJumping) JumpKick(ent); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning // JBravo: Lets make dead players into spectators. if (client->ps.stats[STAT_HEALTH] <= 0) { // wait for the attack button to be pressed if (level.time > client->respawnTime) { // forcerespawn is to prevent users from waiting out powerups if (g_forcerespawn.integer > 0 && (level.time - client->respawnTime) > g_forcerespawn.integer * 1000 && g_gametype.integer != GT_TEAMPLAY && g_gametype.integer != GT_CTF) { respawn(ent); return; } if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_CTF) && level.time > client->respawnTime) { MakeSpectator(ent); } // pressing attack or use is the normal respawn method // JBravo: make'em spactate if (ucmd->buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE)) { if (g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_CTF) { MakeSpectator(ent); } else { respawn(ent); } } } return; } // JBravo: Idle sounds if (g_RQ3_ppl_idletime.integer) { if (ucmd->forwardmove == 0 && ucmd->rightmove == 0) { if (client->idletime) { if (level.time >= client->idletime + (g_RQ3_ppl_idletime.integer * 1000)) { if (g_gametype.integer >= GT_TEAM && g_RQ3_idleaction.integer == 1 && (ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE)) { trap_SendServerCommand( -1, va("print \"Removing %s^7 from his team for excessive Idling\n\"", ent->client->pers.netname)); trap_SendServerCommand(ent - g_entities, "stuff team none\n"); } else if (g_gametype.integer >= GT_TEAM && g_RQ3_idleaction.integer == 2 && (ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE)) { trap_SendServerCommand( -1, va("print \"Kicking %s^7 for excessive Idling\n\"", ent->client->pers.netname)); trap_DropClient(ent - g_entities, "Dropped due to excessive Idling"); } else G_TempEntity(ent->r.currentOrigin, EV_INSANESOUND); client->idletime = 0; } } else { client->idletime = level.time; } } else { client->idletime = 0; } } // perform once-a-second actions ClientTimerActions(ent, msec); }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; // vec3_t oldOrigin; int oldEventSequence; int msec; usercmd_t *ucmd; int monsterslick = 0; // JPW NERVE int i; vec3_t muzzlebounce; gitem_t *item; gentity_t *ent2; vec3_t velocity, org, offset; vec3_t angles,mins,maxs; int weapon; trace_t tr; // jpw // Rafael wolfkick //int validkick; //static int wolfkicktimer = 0; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if ( client->pers.connected != CON_CONNECTED ) { return; } if ( client->cameraPortal ) { G_SetOrigin( client->cameraPortal, client->ps.origin ); trap_LinkEntity( client->cameraPortal ); VectorCopy( client->cameraOrigin, client->cameraPortal->s.origin2 ); } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; ent->client->ps.identifyClient = ucmd->identClient; // NERVE - SMF // JPW NERVE -- update counter for capture & hold display if ( g_gametype.integer == GT_WOLF_CPH ) { client->ps.stats[STAT_CAPTUREHOLD_RED] = level.capturetimes[TEAM_RED]; client->ps.stats[STAT_CAPTUREHOLD_BLUE] = level.capturetimes[TEAM_BLUE]; } // jpw // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; /* // Ridah, fixes savegame timing issue if (msec < -100) { client->ps.commandTime = ucmd->serverTime - 100; msec = 100; } else { return; } */ // done. } if ( msec > 200 ) { msec = 200; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set( "pmove_msec", "8" ); } else if ( pmove_msec.integer > 33 ) { trap_Cvar_Set( "pmove_msec", "33" ); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much // DHM - Nerve :: In limbo use SpectatorThink if ( client->sess.sessionTeam == TEAM_SPECTATOR || client->ps.pm_flags & PMF_LIMBO ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } // JPW NERVE do some time-based muzzle flip -- this never gets touched in single player (see g_weapon.c) // #define RIFLE_SHAKE_TIME 150 // JPW NERVE this one goes with the commented out old damped "realistic" behavior below #define RIFLE_SHAKE_TIME 300 // per Id request, longer recoil time if ( client->sniperRifleFiredTime ) { if ( level.time - client->sniperRifleFiredTime > RIFLE_SHAKE_TIME ) { client->sniperRifleFiredTime = 0; } else { VectorCopy( client->ps.viewangles,muzzlebounce ); // JPW per Id request, longer recoil time muzzlebounce[PITCH] -= 2 * cos( 2.5 * ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); muzzlebounce[YAW] += 0.5*client->sniperRifleMuzzleYaw*cos( 1.0 - ( level.time - client->sniperRifleFiredTime ) * 3 / RIFLE_SHAKE_TIME ); muzzlebounce[PITCH] -= 0.25 * random() * ( 1.0f - ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); muzzlebounce[YAW] += 0.5 * crandom() * ( 1.0f - ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); SetClientViewAngle( ent,muzzlebounce ); } } if ( client->ps.stats[STAT_PLAYER_CLASS] == PC_MEDIC ) { if ( level.time > client->ps.powerups[PW_REGEN] + 5000 ) { client->ps.powerups[PW_REGEN] = level.time; } } // also update weapon recharge time // JPW drop button drops secondary weapon so new one can be picked up // TTimo explicit braces to avoid ambiguous 'else' if ( g_gametype.integer != GT_SINGLE_PLAYER ) { if ( ucmd->wbuttons & WBUTTON_DROP ) { if ( !client->dropWeaponTime ) { client->dropWeaponTime = 1; // just latch it for now if ( ( client->ps.stats[STAT_PLAYER_CLASS] == PC_SOLDIER ) || ( client->ps.stats[STAT_PLAYER_CLASS] == PC_LT ) ) { for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { weapon = weapBanksMultiPlayer[3][i]; if ( COM_BitCheck( client->ps.weapons,weapon ) ) { item = BG_FindItemForWeapon( weapon ); VectorCopy( client->ps.viewangles, angles ); // clamp pitch if ( angles[PITCH] < -30 ) { angles[PITCH] = -30; } else if ( angles[PITCH] > 30 ) { angles[PITCH] = 30; } AngleVectors( angles, velocity, NULL, NULL ); VectorScale( velocity, 64, offset ); offset[2] += client->ps.viewheight / 2; VectorScale( velocity, 75, velocity ); velocity[2] += 50 + random() * 35; VectorAdd( client->ps.origin,offset,org ); VectorSet( mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); VectorSet( maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); trap_Trace( &tr, client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID ); VectorCopy( tr.endpos, org ); ent2 = LaunchItem( item, org, velocity, client->ps.clientNum ); COM_BitClear( client->ps.weapons,weapon ); if ( weapon == WP_MAUSER ) { COM_BitClear( client->ps.weapons,WP_SNIPERRIFLE ); } // Clear out empty weapon, change to next best weapon G_AddEvent( ent, EV_NOAMMO, 0 ); i = MAX_WEAPS_IN_BANK_MP; // show_bug.cgi?id=568 if ( client->ps.weapon == weapon ) { client->ps.weapon = 0; } ent2->count = client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; ent2->item->quantity = client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; client->ps.ammoclip[BG_FindClipForWeapon( weapon )] = 0; } } } } } else { client->dropWeaponTime = 0; } } // jpw // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } if ( reloading || client->cameraPortal ) { ucmd->buttons = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->wbuttons = 0; ucmd->wolfkick = 0; if ( client->cameraPortal ) { client->ps.pm_type = PM_FREEZE; } } else if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } // set parachute anim condition flag BG_UpdateConditionValue( ent->s.number, ANIM_COND_PARACHUTE, ( ent->flags & FL_PARACHUTE ) != 0, qfalse ); // all playing clients are assumed to be in combat mode if ( !client->ps.aiChar ) { client->ps.aiState = AISTATE_COMBAT; } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; if ( client->ps.powerups[PW_HASTE] ) { client->ps.speed *= 1.3; } // set up for pmove oldEventSequence = client->ps.eventSequence; client->currentAimSpreadScale = (float)client->ps.aimSpreadScale / 255.0; memset( &pm, 0, sizeof( pm ) ); pm.ps = &client->ps; pm.pmext = &client->pmext; pm.cmd = *ucmd; pm.oldcmd = client->pers.oldcmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // DHM-Nerve added:: EF_DEAD is checked for in Pmove functions, but wasn't being set // until after Pmove pm.ps->eFlags |= EF_DEAD; // dhm-Nerve end } else { pm.tracemask = MASK_PLAYERSOLID; } // MrE: always use capsule for AI and player //pm.trace = trap_TraceCapsule;//trap_Trace; //DHM - Nerve :: We've gone back to using normal bbox traces pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; pm.noWeapClips = ( g_dmflags.integer & DF_NO_WEAPRELOAD ) > 0; if ( ent->aiCharacter && AICast_NoReload( ent->s.number ) ) { pm.noWeapClips = qtrue; // ensure AI characters don't use clips if they're not supposed to. } // Ridah // if (ent->r.svFlags & SVF_NOFOOTSTEPS) // pm.noFootsteps = qtrue; VectorCopy( client->ps.origin, client->oldOrigin ); // NERVE - SMF pm.gametype = g_gametype.integer; pm.ltChargeTime = g_LTChargeTime.integer; pm.soldierChargeTime = g_soldierChargeTime.integer; pm.engineerChargeTime = g_engineerChargeTime.integer; pm.medicChargeTime = g_medicChargeTime.integer; // -NERVE - SMF monsterslick = Pmove( &pm ); if ( monsterslick && !( ent->flags & FL_NO_MONSTERSLICK ) ) { //vec3_t dir; //vec3_t kvel; //vec3_t forward; // TTimo gcc: might be used unitialized in this function float angle = 0.0f; qboolean bogus = qfalse; // NE if ( ( monsterslick & SURF_MONSLICK_N ) && ( monsterslick & SURF_MONSLICK_E ) ) { angle = 45; } // NW else if ( ( monsterslick & SURF_MONSLICK_N ) && ( monsterslick & SURF_MONSLICK_W ) ) { angle = 135; } // N else if ( monsterslick & SURF_MONSLICK_N ) { angle = 90; } // SE else if ( ( monsterslick & SURF_MONSLICK_S ) && ( monsterslick & SURF_MONSLICK_E ) ) { angle = 315; } // SW else if ( ( monsterslick & SURF_MONSLICK_S ) && ( monsterslick & SURF_MONSLICK_W ) ) { angle = 225; } // S else if ( monsterslick & SURF_MONSLICK_S ) { angle = 270; } // E else if ( monsterslick & SURF_MONSLICK_E ) { angle = 0; } // W else if ( monsterslick & SURF_MONSLICK_W ) { angle = 180; } else { bogus = qtrue; } } // server cursor hints if ( ent->lastHintCheckTime < level.time ) { G_CheckForCursorHints( ent ); ent->lastHintCheckTime = level.time + FRAMETIME; } // DHM - Nerve :: Set animMovetype to 1 if ducking if ( ent->client->ps.pm_flags & PMF_DUCKED ) { ent->s.animMovetype = 1; } else { ent->s.animMovetype = 0; } // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; ent->r.eventTime = level.time; } // Ridah, fixes jittery zombie movement if ( g_smoothClients.integer ) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = qfalse; // for grapple } // // // use the precise origin for linking // VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // // // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); // link entity now, after any personal teleporters have been used trap_LinkEntity( ent ); if ( !ent->client->noclip ) { G_TouchTriggers( ent ); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // store the client's current position for antilag traces G_StoreClientPosition( ent ); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons = client->buttons & ~client->oldbuttons; // client->latched_buttons |= client->buttons & ~client->oldbuttons; // FIXME:? (SA) MP method (causes problems for us. activate 'sticks') //----(SA) added client->oldwbuttons = client->wbuttons; client->wbuttons = ucmd->wbuttons; client->latched_wbuttons = client->wbuttons & ~client->oldwbuttons; // client->latched_wbuttons |= client->wbuttons & ~client->oldwbuttons; // FIXME:? (SA) MP method // Rafael - Activate // Ridah, made it a latched event (occurs on keydown only) if ( client->latched_buttons & BUTTON_ACTIVATE ) { Cmd_Activate_f( ent ); } if ( ent->flags & FL_NOFATIGUE ) { ent->client->ps.sprintTime = 20000; } // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // DHM - Nerve if ( g_gametype.integer >= GT_WOLF ) { WolfFindMedic( ent ); } // dhm - end // wait for the attack button to be pressed if ( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups if ( ( g_gametype.integer != GT_SINGLE_PLAYER ) && ( g_forcerespawn.integer > 0 ) && ( ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) && ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE // JPW NERVE if ( g_gametype.integer >= GT_WOLF ) { limbo( ent, qtrue ); } else { respawn( ent ); } // jpw return; } // DHM - Nerve :: Single player game respawns immediately as before, // but in multiplayer, require button press before respawn if ( g_gametype.integer == GT_SINGLE_PLAYER ) { respawn( ent ); } // NERVE - SMF - we want to only respawn on jump button now else if ( ( ucmd->upmove > 0 ) && ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE // JPW NERVE if ( g_gametype.integer >= GT_WOLF ) { limbo( ent, qtrue ); } else { respawn( ent ); } // jpw } // dhm - Nerve :: end // NERVE - SMF - we want to immediately go to limbo mode if gibbed else if ( client->ps.stats[STAT_HEALTH] <= GIB_HEALTH && !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { if ( g_gametype.integer >= GT_WOLF ) { limbo( ent, qfalse ); } else { respawn( ent ); } } // -NERVE - SMF } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); }
/* * ClientThink */ void ClientThink( edict_t *ent, usercmd_t *ucmd, int timeDelta ) { gclient_t *client; int i, j; static pmove_t pm; int delta, count; client = ent->r.client; client->ps.POVnum = ENTNUM( ent ); client->ps.playerNum = PLAYERNUM( ent ); // anti-lag if( ent->r.svflags & SVF_FAKECLIENT ) { client->timeDelta = 0; } else { int nudge; int fixedNudge = ( game.snapFrameTime ) * 0.5; // fixme: find where this nudge comes from. // add smoothing to timeDelta between the last few ucmds and a small fine-tuning nudge. nudge = fixedNudge + g_antilag_timenudge->integer; timeDelta += nudge; clamp( timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // smooth using last valid deltas i = client->timeDeltasHead - 6; if( i < 0 ) i = 0; for( count = 0, delta = 0; i < client->timeDeltasHead; i++ ) { if( client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK] < 0 ) { delta += client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK]; count++; } } if( !count ) client->timeDelta = timeDelta; else { delta /= count; client->timeDelta = ( delta + timeDelta ) * 0.5; } client->timeDeltas[client->timeDeltasHead & G_MAX_TIME_DELTAS_MASK] = timeDelta; client->timeDeltasHead++; #ifdef UCMDTIMENUDGE client->timeDelta += client->pers.ucmdTimeNudge; #endif } clamp( client->timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // update activity if he touched any controls if( ucmd->forwardmove != 0 || ucmd->sidemove != 0 || ucmd->upmove != 0 || ( ucmd->buttons & ~BUTTON_BUSYICON ) != 0 || client->ucmd.angles[PITCH] != ucmd->angles[PITCH] || client->ucmd.angles[YAW] != ucmd->angles[YAW] ) G_Client_UpdateActivity( client ); client->ucmd = *ucmd; // can exit intermission after two seconds, not counting postmatch if( GS_MatchState() == MATCH_STATE_WAITEXIT && ( ucmd->buttons & BUTTON_ATTACK ) && game.serverTime > GS_MatchStartTime() + 2000 ) level.exitNow = true; // (is this really needed?:only if not cared enough about ps in the rest of the code) // refresh player state position from the entity VectorCopy( ent->s.origin, client->ps.pmove.origin ); VectorCopy( ent->velocity, client->ps.pmove.velocity ); VectorCopy( ent->s.angles, client->ps.viewangles ); client->ps.pmove.gravity = level.gravity; if( GS_MatchState() >= MATCH_STATE_POSTMATCH || GS_MatchPaused() || ( ent->movetype != MOVETYPE_PLAYER && ent->movetype != MOVETYPE_NOCLIP ) ) client->ps.pmove.pm_type = PM_FREEZE; else if( ent->s.type == ET_GIB ) client->ps.pmove.pm_type = PM_GIB; else if( ent->movetype == MOVETYPE_NOCLIP || client->isTV ) client->ps.pmove.pm_type = PM_SPECTATOR; else client->ps.pmove.pm_type = PM_NORMAL; // set up for pmove memset( &pm, 0, sizeof( pmove_t ) ); pm.playerState = &client->ps; if( !client->isTV ) pm.cmd = *ucmd; if( memcmp( &client->old_pmove, &client->ps.pmove, sizeof( pmove_state_t ) ) ) pm.snapinitial = true; // perform a pmove Pmove( &pm ); // save results of pmove client->old_pmove = client->ps.pmove; // update the entity with the new position VectorCopy( client->ps.pmove.origin, ent->s.origin ); VectorCopy( client->ps.pmove.velocity, ent->velocity ); VectorCopy( client->ps.viewangles, ent->s.angles ); ent->viewheight = client->ps.viewheight; VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; if( pm.groundentity == -1 ) { ent->groundentity = NULL; } else { G_AwardResetPlayerComboStats( ent ); ent->groundentity = &game.edicts[pm.groundentity]; ent->groundentity_linkcount = ent->groundentity->linkcount; } GClip_LinkEntity( ent ); GS_AddLaserbeamPoint( &ent->r.client->resp.trail, &ent->r.client->ps, ucmd->serverTimeStamp ); // Regeneration if( ent->r.client->ps.inventory[POWERUP_REGEN] > 0 && ent->health < 200) { ent->health += ( game.frametime * 0.001f ) * 10.0f; // Regen expires if health reaches 200 if ( ent->health >= 199.0f ) ent->r.client->ps.inventory[POWERUP_REGEN]--; } // fire touch functions if( ent->movetype != MOVETYPE_NOCLIP ) { edict_t *other; // touch other objects for( i = 0; i < pm.numtouch; i++ ) { other = &game.edicts[pm.touchents[i]]; for( j = 0; j < i; j++ ) { if( &game.edicts[pm.touchents[j]] == other ) break; } if( j != i ) continue; // duplicated // player can't touch projectiles, only projectiles can touch the player G_CallTouch( other, ent, NULL, 0 ); } } ent->s.weapon = GS_ThinkPlayerWeapon( &client->ps, ucmd->buttons, ucmd->msec, client->timeDelta ); if( G_IsDead( ent ) ) { if( ent->deathTimeStamp + g_respawn_delay_min->integer <= level.time ) client->resp.snap.buttons |= ucmd->buttons; } else if( client->ps.pmove.stats[PM_STAT_NOUSERCONTROL] <= 0 ) client->resp.snap.buttons |= ucmd->buttons; // trigger the instashield if( GS_Instagib() && g_instashield->integer ) { if( client->ps.pmove.pm_type == PM_NORMAL && pm.cmd.upmove < 0 && client->resp.instashieldCharge == INSTA_SHIELD_MAX && client->ps.inventory[POWERUP_SHELL] == 0 ) { client->ps.inventory[POWERUP_SHELL] = client->resp.instashieldCharge; G_Sound( ent, CHAN_AUTO, trap_SoundIndex( GS_FindItemByTag( POWERUP_SHELL )->pickup_sound ), ATTN_NORM ); } } // if( client->ps.pmove.pm_type == PM_NORMAL ) client->level.stats.had_playtime = true; // generating plrkeys (optimized for net communication) ClientMakePlrkeys( client, ucmd ); }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; int i; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; } if ( msec > 200 ) { msec = 200; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } if (ent && ent->client && (ent->client->ps.eFlags & EF_INVULNERABLE)) { if (ent->client->invulnerableTimer <= level.time) { ent->client->ps.eFlags &= ~EF_INVULNERABLE; } } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } // clear the rewards if time if ( level.time > client->rewardTime ) { client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); } if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.eFlags & EF_DISINTEGRATION ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { if (client->ps.forceGripChangeMovetype) { client->ps.pm_type = client->ps.forceGripChangeMovetype; } else { client->ps.pm_type = PM_NORMAL; } } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; client->ps.basespeed = g_speed.value; if (ent->client->ps.duelInProgress) { gentity_t *duelAgainst = &g_entities[ent->client->ps.duelIndex]; //Keep the time updated, so once this duel ends this player can't engage in a duel for another //10 seconds. This will give other people a chance to engage in duels in case this player wants //to engage again right after he's done fighting and someone else is waiting. ent->client->ps.fd.privateDuelTime = level.time + 10000; if (ent->client->ps.duelTime < level.time) { //Bring out the sabers if (ent->client->ps.weapon == WP_SABER && ent->client->ps.saberHolstered && ent->client->ps.duelTime) { if (!saberOffSound || !saberOnSound) { saberOffSound = G_SoundIndex("sound/weapons/saber/saberoffquick.wav"); saberOnSound = G_SoundIndex("sound/weapons/saber/saberon.wav"); } ent->client->ps.saberHolstered = qfalse; G_Sound(ent, CHAN_AUTO, saberOnSound); G_AddEvent(ent, EV_PRIVATE_DUEL, 2); ent->client->ps.duelTime = 0; } if (duelAgainst && duelAgainst->client && duelAgainst->inuse && duelAgainst->client->ps.weapon == WP_SABER && duelAgainst->client->ps.saberHolstered && duelAgainst->client->ps.duelTime) { if (!saberOffSound || !saberOnSound) { saberOffSound = G_SoundIndex("sound/weapons/saber/saberoffquick.wav"); saberOnSound = G_SoundIndex("sound/weapons/saber/saberon.wav"); } duelAgainst->client->ps.saberHolstered = qfalse; G_Sound(duelAgainst, CHAN_AUTO, saberOnSound); G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 2); duelAgainst->client->ps.duelTime = 0; } } else { client->ps.speed = 0; client->ps.basespeed = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; } if (!duelAgainst || !duelAgainst->client || !duelAgainst->inuse || duelAgainst->client->ps.duelIndex != ent->s.number) { ent->client->ps.duelInProgress = 0; G_AddEvent(ent, EV_PRIVATE_DUEL, 0); } else if (duelAgainst->health < 1 || duelAgainst->client->ps.stats[STAT_HEALTH] < 1) { ent->client->ps.duelInProgress = 0; duelAgainst->client->ps.duelInProgress = 0; G_AddEvent(ent, EV_PRIVATE_DUEL, 0); G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); //Winner gets full health.. providing he's still alive if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) { if (ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]) { ent->client->ps.stats[STAT_HEALTH] = ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; } if (g_spawnInvulnerability.integer) { ent->client->ps.eFlags |= EF_INVULNERABLE; ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; } } /* trap_SendServerCommand( ent-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStripEdString("SVINGAME", "PLDUELWINNER")) ); trap_SendServerCommand( duelAgainst-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStripEdString("SVINGAME", "PLDUELWINNER")) ); */ //Private duel announcements are now made globally because we only want one duel at a time. if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) { trap_SendServerCommand( -1, va("cp \"%s %s %s!\n\"", ent->client->pers.netname, G_GetStripEdString("SVINGAME", "PLDUELWINNER"), duelAgainst->client->pers.netname) ); } else { //it was a draw, because we both managed to die in the same frame trap_SendServerCommand( -1, va("cp \"%s\n\"", G_GetStripEdString("SVINGAME", "PLDUELTIE")) ); } } else { vec3_t vSub; float subLen = 0; VectorSubtract(ent->client->ps.origin, duelAgainst->client->ps.origin, vSub); subLen = VectorLength(vSub); if (subLen >= 1024) { ent->client->ps.duelInProgress = 0; duelAgainst->client->ps.duelInProgress = 0; G_AddEvent(ent, EV_PRIVATE_DUEL, 0); G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStripEdString("SVINGAME", "PLDUELSTOP")) ); } } } /* if ( client->ps.powerups[PW_HASTE] ) { client->ps.speed *= 1.3; } */ if (client->ps.usingATST && ent->health > 0) { //we have special shot clip boxes as an ATST ent->r.contents |= CONTENTS_NOSHOT; ATST_ManageDamageBoxes(ent); } else { ent->r.contents &= ~CONTENTS_NOSHOT; client->damageBoxHandle_Head = 0; client->damageBoxHandle_RLeg = 0; client->damageBoxHandle_LLeg = 0; } //rww - moved this stuff into the pmove code so that it's predicted properly //BG_AdjustClientSpeed(&client->ps, &client->pers.cmd, level.time); // set up for pmove oldEventSequence = client->ps.eventSequence; memset (&pm, 0, sizeof(pm)); if ( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } if (ent->client && ent->client->ps.fallingToDeath && (level.time - FALL_FADE_TIME) > ent->client->ps.fallingToDeath) { //die! player_die(ent, ent, ent, 100000, MOD_FALLING); respawn(ent); ent->client->ps.fallingToDeath = 0; G_MuteSound(ent->s.number, CHAN_VOICE); //stop screaming, because you are dead! } if (ent->client->ps.otherKillerTime > level.time && ent->client->ps.groundEntityNum != ENTITYNUM_NONE && ent->client->ps.otherKillerDebounceTime < level.time) { ent->client->ps.otherKillerTime = 0; ent->client->ps.otherKiller = ENTITYNUM_NONE; } else if (ent->client->ps.otherKillerTime > level.time && ent->client->ps.groundEntityNum == ENTITYNUM_NONE) { if (ent->client->ps.otherKillerDebounceTime < (level.time + 100)) { ent->client->ps.otherKillerDebounceTime = level.time + 100; } } // WP_ForcePowersUpdate( ent, msec, ucmd); //update any active force powers // WP_SaberPositionUpdate(ent, ucmd); //check the server-side saber point, do apprioriate server-side actions (effects are cs-only) if ((ent->client->pers.cmd.buttons & BUTTON_USE) && ent->client->ps.useDelay < level.time) { TryUse(ent); ent->client->ps.useDelay = level.time + 100; } pm.ps = &client->ps; pm.cmd = *ucmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else if ( ent->r.svFlags & SVF_BOT ) { pm.tracemask = MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP; } else { pm.tracemask = MASK_PLAYERSOLID; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; pm.animations = bgGlobalAnimations;//NULL; pm.gametype = g_gametype.integer; VectorCopy( client->ps.origin, client->oldOrigin ); if (level.intermissionQueued != 0 && g_singlePlayer.integer) { if ( level.time - level.intermissionQueued >= 1000 ) { pm.cmd.buttons = 0; pm.cmd.forwardmove = 0; pm.cmd.rightmove = 0; pm.cmd.upmove = 0; if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); } ent->client->ps.pm_type = PM_SPINTERMISSION; } } for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { if (g_entities[i].inuse && g_entities[i].client) { pm.bgClients[i] = &g_entities[i].client->ps; } } if (ent->client->ps.saberLockTime > level.time) { gentity_t *blockOpp = &g_entities[ent->client->ps.saberLockEnemy]; if (blockOpp && blockOpp->inuse && blockOpp->client) { vec3_t lockDir, lockAng; //VectorClear( ent->client->ps.velocity ); VectorSubtract( blockOpp->r.currentOrigin, ent->r.currentOrigin, lockDir ); //lockAng[YAW] = vectoyaw( defDir ); vectoangles(lockDir, lockAng); SetClientViewAngle( ent, lockAng ); } if ( ( ent->client->buttons & BUTTON_ATTACK ) && ! ( ent->client->oldbuttons & BUTTON_ATTACK ) ) { ent->client->ps.saberLockHits++; } if (ent->client->ps.saberLockHits > 2) { if (!ent->client->ps.saberLockAdvance) { ent->client->ps.saberLockHits -= 3; } ent->client->ps.saberLockAdvance = qtrue; } } else { ent->client->ps.saberLockFrame = 0; //check for taunt if ( (pm.cmd.generic_cmd == GENCMD_ENGAGE_DUEL) && (g_gametype.integer == GT_TOURNAMENT) ) {//already in a duel, make it a taunt command pm.cmd.buttons |= BUTTON_GESTURE; } } Pmove (&pm); if (pm.checkDuelLoss) { if (pm.checkDuelLoss > 0 && pm.checkDuelLoss <= MAX_CLIENTS) { gentity_t *clientLost = &g_entities[pm.checkDuelLoss-1]; if (clientLost && clientLost->inuse && clientLost->client && Q_irand(0, 40) > clientLost->health) { vec3_t attDir; VectorSubtract(ent->client->ps.origin, clientLost->client->ps.origin, attDir); VectorNormalize(attDir); VectorClear(clientLost->client->ps.velocity); clientLost->client->ps.forceHandExtend = HANDEXTEND_NONE; clientLost->client->ps.forceHandExtendTime = 0; gGAvoidDismember = 1; G_Damage(clientLost, ent, ent, attDir, clientLost->client->ps.origin, 9999, DAMAGE_NO_PROTECTION, MOD_SABER); if (clientLost->health < 1) { gGAvoidDismember = 2; G_CheckForDismemberment(clientLost, clientLost->client->ps.origin, 999, (clientLost->client->ps.legsAnim&~ANIM_TOGGLEBIT)); } gGAvoidDismember = 0; } } pm.checkDuelLoss = 0; } switch(pm.cmd.generic_cmd) { case 0: break; case GENCMD_SABERSWITCH: Cmd_ToggleSaber_f(ent); break; case GENCMD_ENGAGE_DUEL: if ( g_gametype.integer == GT_TOURNAMENT ) {//already in a duel, made it a taunt command } else { Cmd_EngageDuel_f(ent); } break; case GENCMD_FORCE_HEAL: ForceHeal(ent); break; case GENCMD_FORCE_SPEED: ForceSpeed(ent, 0); break; case GENCMD_FORCE_THROW: ForceThrow(ent, qfalse); break; case GENCMD_FORCE_PULL: ForceThrow(ent, qtrue); break; case GENCMD_FORCE_DISTRACT: ForceTelepathy(ent); break; case GENCMD_FORCE_RAGE: ForceRage(ent); break; case GENCMD_FORCE_PROTECT: ForceProtect(ent); break; case GENCMD_FORCE_ABSORB: ForceAbsorb(ent); break; case GENCMD_FORCE_HEALOTHER: ForceTeamHeal(ent); break; case GENCMD_FORCE_FORCEPOWEROTHER: ForceTeamForceReplenish(ent); break; case GENCMD_FORCE_SEEING: ForceSeeing(ent); break; case GENCMD_USE_SEEKER: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) && G_ItemUsable(&ent->client->ps, HI_SEEKER) ) { ItemUse_Seeker(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_SEEKER, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SEEKER); } break; case GENCMD_USE_FIELD: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) && G_ItemUsable(&ent->client->ps, HI_SHIELD) ) { ItemUse_Shield(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_SHIELD, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SHIELD); } break; case GENCMD_USE_BACTA: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) && G_ItemUsable(&ent->client->ps, HI_MEDPAC) ) { ItemUse_MedPack(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_MEDPAC, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_MEDPAC); } break; case GENCMD_USE_ELECTROBINOCULARS: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) { ItemUse_Binoculars(ent); if (ent->client->ps.zoomMode == 0) { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); } else { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); } } break; case GENCMD_ZOOM: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) { ItemUse_Binoculars(ent); if (ent->client->ps.zoomMode == 0) { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); } else { G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); } } break; case GENCMD_USE_SENTRY: if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) && G_ItemUsable(&ent->client->ps, HI_SENTRY_GUN) ) { ItemUse_Sentry(ent); G_AddEvent(ent, EV_USE_ITEM0+HI_SENTRY_GUN, 0); ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SENTRY_GUN); } break; case GENCMD_SABERATTACKCYCLE: Cmd_SaberAttackCycle_f(ent); break; default: break; } // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } SendPendingPredictableEvents( &ent->client->ps ); if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = qfalse; // for grapple } // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy (pm.mins, ent->r.mins); VectorCopy (pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); if ( pm.useEvent ) { //TODO: Use // TryUse( ent ); } // link entity now, after any personal teleporters have been used trap_LinkEntity (ent); if ( !ent->client->noclip ) { G_TouchTriggers( ent ); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); //test for solid areas in the AAS file // BotTestAAS(ent->r.currentOrigin); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // Did we kick someone in our pmove sequence? if (client->ps.forceKickFlip) { gentity_t *faceKicked = &g_entities[client->ps.forceKickFlip-1]; if (faceKicked && faceKicked->client && (!OnSameTeam(ent, faceKicked) || g_friendlyFire.integer) && (!faceKicked->client->ps.duelInProgress || faceKicked->client->ps.duelIndex == ent->s.number) && (!ent->client->ps.duelInProgress || ent->client->ps.duelIndex == faceKicked->s.number)) { if ( faceKicked && faceKicked->client && faceKicked->health && faceKicked->takedamage ) {//push them away and do pain vec3_t oppDir; int strength = (int)VectorNormalize2( client->ps.velocity, oppDir ); strength *= 0.05; VectorScale( oppDir, -1, oppDir ); G_Damage( faceKicked, ent, ent, oppDir, client->ps.origin, strength, DAMAGE_NO_ARMOR, MOD_MELEE ); if ( faceKicked->client->ps.weapon != WP_SABER || faceKicked->client->ps.fd.saberAnimLevel < FORCE_LEVEL_3 || (!BG_SaberInAttack(faceKicked->client->ps.saberMove) && !PM_SaberInStart(faceKicked->client->ps.saberMove) && !PM_SaberInReturn(faceKicked->client->ps.saberMove) && !PM_SaberInTransition(faceKicked->client->ps.saberMove)) ) { if (faceKicked->health > 0 && faceKicked->client->ps.stats[STAT_HEALTH] > 0 && faceKicked->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) { if (Q_irand(1, 10) <= 3) { //only actually knock over sometimes, but always do velocity hit faceKicked->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; faceKicked->client->ps.forceHandExtendTime = level.time + 1100; faceKicked->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim } faceKicked->client->ps.otherKiller = ent->s.number; faceKicked->client->ps.otherKillerTime = level.time + 5000; faceKicked->client->ps.otherKillerDebounceTime = level.time + 100; faceKicked->client->ps.velocity[0] = oppDir[0]*(strength*40); faceKicked->client->ps.velocity[1] = oppDir[1]*(strength*40); faceKicked->client->ps.velocity[2] = 200; } } G_Sound( faceKicked, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); } } client->ps.forceKickFlip = 0; } // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( level.time > client->respawnTime && !gDoSlowMoDuel ) { // forcerespawn is to prevent users from waiting out powerups if ( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } else if (gDoSlowMoDuel) { client->respawnTime = level.time + 1000; } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); G_UpdateClientBroadcasts ( ent ); }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if( client->pers.connected != CON_CONNECTED ) return; // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) return; if( msec > 200 ) msec = 200; if( pmove_msec.integer < 8 ) trap_Cvar_Set( "pmove_msec", "8" ); else if( pmove_msec.integer > 33 ) trap_Cvar_Set( "pmove_msec", "33" ); if( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if( client->sess.sessionTeam == TEAM_SPECTATOR ) { if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) return; SpectatorThink( ent, ucmd ); return; } G_UpdatePTRConnection( client ); // check for inactivity timer, but never drop the local client of a non-dedicated server if( !ClientInactivityTimer( client ) ) return; if( client->noclip ) client->ps.pm_type = PM_NOCLIP; else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) client->ps.pm_type = PM_DEAD; else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING || client->ps.stats[ STAT_STATE ] & SS_HOVELING ) client->ps.pm_type = PM_FREEZE; else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || client->ps.stats[ STAT_STATE ] & SS_GRABBED ) client->ps.pm_type = PM_GRABBED; else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) client->ps.pm_type = PM_JETPACK; else client->ps.pm_type = PM_NORMAL; if( client->ps.stats[ STAT_STATE ] & SS_GRABBED && client->grabExpiryTime < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED && client->lastLockTime + 5000 < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED && client->lastLockTime + ABUILDER_BLOB_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime; if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED && client->lastBoostedTime + BOOST_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; if( client->ps.stats[ STAT_STATE ] & SS_POISONED && client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->ps.gravity = g_gravity.value; if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) { //if currently using a medkit or have no need for a medkit now if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE || ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) { BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); } else if( client->ps.stats[ STAT_HEALTH ] > 0 ) { //remove anti toxin BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE; client->lastMedKitTime = level.time; client->medKitHealthToRestore = client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; client->medKitIncrementTime = level.time + ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); G_AddEvent( ent, EV_MEDKIT_USED, 0 ); } } if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) { int lastWeapon = ent->s.weapon; //remove grenade BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); //M-M-M-M-MONSTER HACK ent->s.weapon = WP_GRENADE; FireWeapon( ent ); ent->s.weapon = lastWeapon; } // set speed client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; //randomly disable the jet pack if damaged if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) { if( random( ) > JETPACK_DISABLE_CHANCE ) client->ps.pm_type = PM_NORMAL; } //switch jetpack off if no reactor if( !level.reactorPresent ) BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); } // set up for pmove oldEventSequence = client->ps.eventSequence; memset( &pm, 0, sizeof( pm ) ); if( !( ucmd->buttons & BUTTON_TALK ) ) //&& client->ps.weaponTime <= 0 ) //TA: erk more server load { switch( client->ps.weapon ) { case WP_ALEVEL0: if( client->ps.weaponTime <= 0 ) pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent ); break; case WP_ALEVEL1: case WP_ALEVEL1_UPG: CheckGrabAttack( ent ); break; case WP_ALEVEL3: case WP_ALEVEL3_UPG: if( client->ps.weaponTime <= 0 ) pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent ); break; default: break; } } if( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } pm.ps = &client->ps; pm.cmd = *ucmd; if( pm.ps->pm_type == PM_DEAD ) pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY; if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING || pm.ps->stats[ STAT_STATE ] & SS_HOVELING ) pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; else pm.tracemask = MASK_PLAYERSOLID; pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = (qboolean)0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; VectorCopy( client->ps.origin, client->oldOrigin ); // moved from after Pmove -- potentially the cause of // future triggering bugs if( !ent->client->noclip ) G_TouchTriggers( ent ); Pmove( &pm ); // save results of pmove if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; if( g_smoothClients.integer ) BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); else BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); if( !( ent->client->ps.eFlags & EF_FIRING ) ) client->fireHeld = qfalse; // for grapple if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) client->fire2Held = qfalse; // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); // link entity now, after any personal teleporters have been used trap_LinkEntity( ent ); // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); VectorCopy( ent->client->ps.origin, ent->s.origin ); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) && client->ps.stats[ STAT_HEALTH ] > 0 ) { trace_t trace; vec3_t view, point; gentity_t *traceEnt; if( client->ps.stats[ STAT_STATE ] & SS_HOVELING ) { gentity_t *hovel = client->hovel; //only let the player out if there is room if( !AHovel_Blocked( hovel, ent, qtrue ) ) { //prevent lerping client->ps.eFlags ^= EF_TELEPORT_BIT; client->ps.eFlags &= ~EF_NODRAW; //client leaves hovel client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; //hovel is empty G_setBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); hovel->active = qfalse; } else { //exit is blocked G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); } } else { #define USE_OBJECT_RANGE 64 int entityList[ MAX_GENTITIES ]; vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; vec3_t mins, maxs; int i, num; //TA: look for object infront of player AngleVectors( client->ps.viewangles, view, NULL, NULL ); VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); traceEnt = &g_entities[ trace.entityNum ]; if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context else { //no entity in front of player - do a small area search VectorAdd( client->ps.origin, range, maxs ); VectorSubtract( client->ps.origin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { traceEnt = &g_entities[ entityList[ i ] ]; if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) { traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context break; } } if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { if( BG_UpgradeClassAvailable( &client->ps ) ) { //no nearby objects and alien - show class menu G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); } else { //flash frags G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); } } } } } // check for respawning if( client->ps.stats[ STAT_HEALTH ] <= 0 ) { // wait for the attack button to be pressed if( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups if( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > 0 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu ) { G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY ); client->retriggerArmouryMenu = 0; } // Give clients some credit periodically if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time ) { if( g_suddenDeathTime.integer && ( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 ) ) { //gotta love logic like this eh? } else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue ); else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue ); ent->client->lastKillTime = level.time; } // perform once-a-second actions ClientTimerActions( ent, msec ); if( ent->suicideTime > 0 && ent->suicideTime < level.time ) { ent->flags &= ~FL_GODMODE; ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, ent, ent, 100000, MOD_SUICIDE ); ent->suicideTime = 0; } }
/* ================= SpectatorThink ================= */ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { pmove_t pm; gclient_t *client; client = ent->client; client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; if( client->sess.spectatorState != SPECTATOR_FOLLOW ) { if( client->sess.spectatorState == SPECTATOR_LOCKED ) client->ps.pm_type = PM_FREEZE; else client->ps.pm_type = PM_SPECTATOR; client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); client->ps.stats[ STAT_STAMINA ] = 0; client->ps.stats[ STAT_MISC ] = 0; client->ps.stats[ STAT_BUILDABLE ] = 0; client->ps.stats[ STAT_PCLASS ] = PCL_NONE; client->ps.weapon = WP_NONE; // set up for pmove memset( &pm, 0, sizeof( pm ) ); pm.ps = &client->ps; pm.cmd = *ucmd; pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; // perform a pmove Pmove( &pm ); // save results of pmove VectorCopy( client->ps.origin, ent->s.origin ); G_TouchTriggers( ent ); trap_UnlinkEntity( ent ); if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) ) { //if waiting in a queue remove from the queue if( client->ps.pm_flags & PMF_QUEUED ) { if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); client->pers.classSelection = PCL_NONE; client->ps.stats[ STAT_PCLASS ] = PCL_NONE; } else if( client->pers.classSelection == PCL_NONE ) { if( client->pers.teamSelection == PTE_NONE ) G_TriggerMenu( client->ps.clientNum, MN_TEAM ); else if( client->pers.teamSelection == PTE_ALIENS ) G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); else if( client->pers.teamSelection == PTE_HUMANS ) G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); } } //set the queue position for the client side if( client->ps.pm_flags & PMF_QUEUED ) { if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { client->ps.persistant[ PERS_QUEUEPOS ] = G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); } else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { client->ps.persistant[ PERS_QUEUEPOS ] = G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); } } } if( ( client->buttons & BUTTON_USE_HOLDABLE ) && !( client->oldbuttons & BUTTON_USE_HOLDABLE ) ) Cmd_Follow_f( ent, qtrue ); }
/* ================= CG_PredictPlayerState Generates cg.cur_lc->predictedPlayerState for the current cg.time cg.cur_lc->predictedPlayerState is guaranteed to be valid after exiting. For demo playback, this will be an interpolation between two valid playerState_t. For normal gameplay, it will be the result of predicted usercmd_t on top of the most recent playerState_t received from the server. Each new snapshot will usually have one or more new usercmd over the last, but we simulate all unacknowledged commands each time, not just the new ones. This means that on an internet connection, quite a few pmoves may be issued each frame. OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t differs from the predicted one. Would require saving all intermediate playerState_t during prediction. We detect prediction errors and allow them to be decayed off over several frames to ease the jerk. ================= */ void CG_PredictPlayerState( void ) { int cmdNum, current; playerState_t oldPlayerState; qboolean moved; usercmd_t oldestCmd; usercmd_t latestCmd; cg.cur_lc->hyperspace = qfalse; // will be set if touching a trigger_teleport // if this is the first frame we must guarantee // predictedPlayerState is valid even if there is some // other error condition if ( !cg.cur_lc->validPPS ) { cg.cur_lc->validPPS = qtrue; cg.cur_lc->predictedPlayerState = *cg.cur_ps; } // demo playback just copies the moves if ( cg.demoPlayback || (cg.cur_ps->pm_flags & PMF_FOLLOW) ) { CG_InterpolatePlayerState( qfalse ); return; } // non-predicting local movement will grab the latest angles if ( cg_nopredict.integer || cg_synchronousClients.integer ) { CG_InterpolatePlayerState( qtrue ); return; } // prepare for pmove cg_pmove.ps = &cg.cur_lc->predictedPlayerState; if (cg.cur_lc->predictedPlayerState.collisionType == CT_CAPSULE) { cg_pmove.trace = CG_TraceCapsule; } else { cg_pmove.trace = CG_Trace; } cg_pmove.pointcontents = CG_PointContents; if ( cg_pmove.ps->pm_type == PM_DEAD ) { cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else { cg_pmove.tracemask = MASK_PLAYERSOLID; } if ( cg.cur_ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies } cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; // save the state before the pmove so we can detect transitions oldPlayerState = cg.cur_lc->predictedPlayerState; current = trap_GetCurrentCmdNumber(); // if we don't have the commands right after the snapshot, we // can't accurately predict a current position, so just freeze at // the last good position we had cmdNum = current - CMD_BACKUP + 1; trap_GetUserCmd( cmdNum, &oldestCmd, cg.cur_localPlayerNum ); if ( oldestCmd.serverTime > cg.cur_ps->commandTime && oldestCmd.serverTime < cg.time ) { // special check for map_restart if ( cg_showmiss.integer ) { CG_Printf ("exceeded PACKET_BACKUP on commands\n"); } return; } // get the latest command so we can know which commands are from previous map_restarts trap_GetUserCmd( current, &latestCmd, cg.cur_localPlayerNum ); // get the most recent information we have, even if // the server time is beyond our current cg.time, // because predicted player positions are going to // be ahead of everything else anyway if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport && cg.nextSnap->playerNums[cg.cur_localPlayerNum] != -1) { cg.cur_lc->predictedPlayerState = cg.nextSnap->pss[cg.cur_localPlayerNum]; cg.physicsTime = cg.nextSnap->serverTime; } else { cg.cur_lc->predictedPlayerState = *cg.cur_ps; cg.physicsTime = cg.snap->serverTime; } if ( pmove_msec.integer < 8 ) { trap_Cvar_SetValue( "pmove_msec", 8 ); } else if ( pmove_msec.integer > 33 ) { trap_Cvar_SetValue( "pmove_msec", 33 ); } cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; cg_pmove.pmove_msec = pmove_msec.integer; cg_pmove.pmove_overbounce = pmove_overbounce.integer; // run cmds moved = qfalse; for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { // get the command trap_GetUserCmd( cmdNum, &cg_pmove.cmd, cg.cur_localPlayerNum ); if ( cg_pmove.pmove_fixed ) { PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); } // don't do anything if the time is before the snapshot player time if ( cg_pmove.cmd.serverTime <= cg.cur_lc->predictedPlayerState.commandTime ) { continue; } // don't do anything if the command was from a previous map_restart if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { continue; } // check for a prediction error from last frame // on a lan, this will often be the exact value // from the snapshot, but on a wan we will have // to predict several commands to get to the point // we want to compare if ( cg.cur_lc->predictedPlayerState.commandTime == oldPlayerState.commandTime ) { vec3_t delta; float len; if ( cg.thisFrameTeleport ) { // a teleport will not cause an error decay VectorClear( cg.cur_lc->predictedError ); if ( cg_showmiss.integer ) { CG_Printf( "PredictionTeleport\n" ); } cg.thisFrameTeleport = qfalse; } else { vec3_t adjusted, new_angles; CG_AdjustPositionForMover( cg.cur_lc->predictedPlayerState.origin, cg.cur_lc->predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted, cg.cur_lc->predictedPlayerState.viewangles, new_angles); if ( cg_showmiss.integer ) { if (!VectorCompare( oldPlayerState.origin, adjusted )) { CG_Printf("prediction error\n"); } } VectorSubtract( oldPlayerState.origin, adjusted, delta ); len = VectorLength( delta ); if ( len > 0.1 ) { if ( cg_showmiss.integer ) { CG_Printf("Prediction miss: %f\n", len); } if ( cg_errorDecay.integer ) { int t; float f; t = cg.time - cg.cur_lc->predictedErrorTime; f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; if ( f < 0 ) { f = 0; } if ( f > 0 && cg_showmiss.integer ) { CG_Printf("Double prediction decay: %f\n", f); } VectorScale( cg.cur_lc->predictedError, f, cg.cur_lc->predictedError ); } else { VectorClear( cg.cur_lc->predictedError ); } VectorAdd( delta, cg.cur_lc->predictedError, cg.cur_lc->predictedError ); cg.cur_lc->predictedErrorTime = cg.oldTime; } } } // don't predict gauntlet firing, which is only supposed to happen // when it actually inflicts damage cg_pmove.gauntletHit = qfalse; if ( cg_pmove.pmove_fixed ) { cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; } Pmove (&cg_pmove); moved = qtrue; // add push trigger movement effects CG_TouchTriggerPrediction(); // check for predictable events that changed from previous predictions //CG_CheckChangedPredictableEvents(&cg.cur_lc->predictedPlayerState); } if ( cg_showmiss.integer > 1 ) { CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); } if ( !moved ) { if ( cg_showmiss.integer ) { CG_Printf( "not moved\n" ); } return; } // adjust for the movement of the groundentity CG_AdjustPositionForMover( cg.cur_lc->predictedPlayerState.origin, cg.cur_lc->predictedPlayerState.groundEntityNum, cg.physicsTime, cg.time, cg.cur_lc->predictedPlayerState.origin, cg.cur_lc->predictedPlayerState.viewangles,cg.cur_lc->predictedPlayerState.viewangles); if ( cg_showmiss.integer ) { if (cg.cur_lc->predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { CG_Printf("WARNING: dropped event\n"); } } // fire events and other transition triggered things CG_TransitionPlayerState( &cg.cur_lc->predictedPlayerState, &oldPlayerState ); if ( cg_showmiss.integer ) { if (cg.cur_lc->eventSequence > cg.cur_lc->predictedPlayerState.eventSequence) { CG_Printf("WARNING: double event\n"); cg.cur_lc->eventSequence = cg.cur_lc->predictedPlayerState.eventSequence; } } }
/* ================= SpectatorThink ================= */ void SpectatorThink(gentity_t *ent, usercmd_t *ucmd) { pmove_t pm; gclient_t *client; client = ent->client; if (client->sess.spectatorState != SPECTATOR_FOLLOW) { client->ps.pm_type = PM_SPECTATOR; client->ps.speed = SPECTATOR_SPEED; // was: 400 // faster than normal if (client->ps.sprintExertTime) { client->ps.speed *= 3; // (SA) allow sprint in free-cam mode } // OSP - dead players are frozen too, in a timeout if (client->ps.pm_flags & PMF_LIMBO) { client->ps.pm_type = PM_FREEZE; } else if (client->noclip) { client->ps.pm_type = PM_NOCLIP; } // set up for pmove memset(&pm, 0, sizeof (pm)); pm.ps = &client->ps; pm.pmext = &client->pmext; pm.character = client->pers.character; pm.cmd = *ucmd; pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies pm.trace = trap_TraceCapsuleNoEnts; pm.pointcontents = trap_PointContents; Pmove(&pm); // JPW NERVE // Rafael - Activate // Ridah, made it a latched event (occurs on keydown only) if (client->latched_buttons & BUTTON_ACTIVATE) { Cmd_Activate_f(ent); } // save results of pmove VectorCopy(client->ps.origin, ent->s.origin); G_TouchTriggers(ent); trap_UnlinkEntity(ent); } client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; //----(SA) added client->oldwbuttons = client->wbuttons; client->wbuttons = ucmd->wbuttons; // attack button cycles through spectators if ((client->buttons & BUTTON_ATTACK) && !(client->oldbuttons & BUTTON_ATTACK)) { Cmd_FollowCycle_f(ent, 1); } else if ( (client->sess.sessionTeam == TEAM_SPECTATOR) && // don't let dead team players do free fly (client->sess.spectatorState == SPECTATOR_FOLLOW) && (((client->buttons & BUTTON_ACTIVATE) && !(client->oldbuttons & BUTTON_ACTIVATE)) || ucmd->upmove > 0)) { // code moved to StopFollowing StopFollowing(ent); } }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; } if ( msec > 200 ) { msec = 200; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if ( G_IsClientSpectating ( client ) ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; // set up for pmove oldEventSequence = client->ps.eventSequence; memset (&pm, 0, sizeof(pm)); pm.ps = &client->ps; pm.cmd = *ucmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else if ( client->siameseTwin ) { // Make sure we are still stuck, if so, clip through players. if ( G_IsClientSiameseTwin ( ent, client->siameseTwin ) ) { pm.tracemask = MASK_PLAYERSOLID & ~(CONTENTS_BODY); } else { // Ok, we arent stuck anymore so we can clear the stuck flag. client->siameseTwin->client->siameseTwin = NULL; client->siameseTwin = NULL; pm.tracemask = MASK_PLAYERSOLID; } } else if ( ent->r.svFlags & SVF_BOT ) { pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; } else { pm.tracemask = MASK_PLAYERSOLID; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pmove_fixed = (bool)pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; pm.animations = NULL; VectorCopy( client->ps.origin, client->oldOrigin ); Pmove (&pm); G_UpdatePlayerStateScores ( ent ); // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } // See if the invulnerable flag should be removed for this client if ( ent->client->ps.eFlags & EF_INVULNERABLE ) { if ( level.time - ent->client->invulnerableTime >= g_respawnInvulnerability.integer * 1000 ) { ent->client->ps.eFlags &= ~EF_INVULNERABLE; } } if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, true ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, true ); } SendPendingPredictableEvents( &ent->client->ps ); if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = false; // for grapple } // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy (pm.mins, ent->r.mins); VectorCopy (pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // Need to cache off the firemodes to the persitant data segment so they // are maintained across spectating and respawning memcpy ( ent->client->pers.firemode, ent->client->ps.firemode, sizeof(ent->client->ps.firemode ) ); // execute client events ClientEvents( ent, oldEventSequence ); // Update the client animation info G_UpdateClientAnimations ( ent ); // link entity now, after any personal teleporters have been used trap_LinkEntity (ent); if ( !ent->client->noclip ) { G_TouchTriggers( ent ); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // Update the clients anti-lag history G_UpdateClientAntiLag ( ent ); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( level.time > client->respawnTime ) { if ( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if ( ucmd->buttons & BUTTON_ATTACK ) { respawn( ent ); } } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real(gentity_t *ent) { int msec, oldEventSequence, speed, i, counter, Zaccel; pmove_t pm; usercmd_t *ucmd; gclient_t *client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } if (ent->s.eFlags & EF_MOUNTEDTANK) { client->pmext.centerangles[YAW] = ent->tagParent->r.currentAngles[YAW]; client->pmext.centerangles[PITCH] = ent->tagParent->r.currentAngles[PITCH]; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; ent->client->ps.identifyClient = ucmd->identClient; // NERVE - SMF // sanity check the command time to prevent speedup cheating if (ucmd->serverTime > level.time + 200) { ucmd->serverTime = level.time + 200; } if (ucmd->serverTime < level.time - 1000) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if (msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW) { return; } if (msec > 200) { msec = 200; } // Nico, pmove_fixed if (client->pers.pmoveFixed) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer - 1) / pmove_msec.integer) * pmove_msec.integer; } if (client->wantsscore) { G_SendScore(ent); client->wantsscore = qfalse; } // check for inactivity timer, but never drop the local client of a non-dedicated server // OSP - moved here to allow for spec inactivity checks as well if (!ClientInactivityTimer(client)) { return; } if (!(ucmd->flags & 0x01) || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove || ucmd->wbuttons || ucmd->doubleTap) { ent->r.svFlags &= ~(SVF_SELF_PORTAL_EXCLUSIVE | SVF_SELF_PORTAL); } // spectators don't do much // DHM - Nerve :: In limbo use SpectatorThink // suburb, check for noclip not to override client->ps.speed if ((client->sess.sessionTeam == TEAM_SPECTATOR || client->ps.pm_flags & PMF_LIMBO) && !client->noclip) { SpectatorThink(ent, ucmd); return; } if (client->ps.eFlags & EF_VIEWING_CAMERA) { ucmd->buttons = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->wbuttons = 0; ucmd->doubleTap = 0; // freeze player client->ps.pm_type = PM_FREEZE; } else if (client->noclip) { client->ps.pm_type = PM_NOCLIP; } else if (client->ps.stats[STAT_HEALTH] <= 0) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.aiState = AISTATE_COMBAT; client->ps.gravity = DEFAULT_GRAVITY; client->ps.speed = DEFAULT_SPEED; if (client->speedScale) { // Goalitem speed scale client->ps.speed *= (client->speedScale * 0.01); } // set up for pmove oldEventSequence = client->ps.eventSequence; client->currentAimSpreadScale = (float)client->ps.aimSpreadScale / 255.0; memset(&pm, 0, sizeof (pm)); pm.ps = &client->ps; pm.pmext = &client->pmext; pm.character = client->pers.character; pm.cmd = *ucmd; pm.oldcmd = client->pers.oldcmd; // MrE: always use capsule for AI and player pm.trace = trap_TraceCapsule; // Nico, ghost players pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; if (pm.ps->pm_type == PM_DEAD) { pm.ps->eFlags |= EF_DEAD; } else if (pm.ps->pm_type == PM_SPECTATOR) { pm.trace = trap_TraceCapsuleNoEnts; } // Nico, end of ghost players //DHM - Nerve :: We've gone back to using normal bbox traces pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = qfalse; // Nico, pmove_fixed // pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_fixed = client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; // suburb, Noclip speed scale pm.noclipSpeed = client->pers.noclipSpeed; // Nico, game physics pm.physics = physics.integer; pm.isTimerun = isTimerun.integer; pm.timerunActive = client->sess.timerunActive; pm.timerunStartTime = client->sess.timerunStartTime + 500; // Nico, store logins status in pmove if (client->sess.logged) { pm.isLogged = 1; } else { pm.isLogged = 0; } pm.noWeapClips = qfalse; VectorCopy(client->ps.origin, client->oldOrigin); // NERVE - SMF pm.ltChargeTime = level.lieutenantChargeTime[client->sess.sessionTeam - 1]; pm.soldierChargeTime = level.soldierChargeTime[client->sess.sessionTeam - 1]; pm.engineerChargeTime = level.engineerChargeTime[client->sess.sessionTeam - 1]; pm.medicChargeTime = level.medicChargeTime[client->sess.sessionTeam - 1]; // -NERVE - SMF client->pmext.airleft = ent->client->airOutTime - level.time; pm.covertopsChargeTime = level.covertopsChargeTime[client->sess.sessionTeam - 1]; // Gordon: bit hacky, stop the slight lag from client -> server even on locahost, switching back to the weapon you were holding // and then back to what weapon you should have, became VERY noticible for the kar98/carbine + gpg40, esp now i've added the // animation locking if (level.time - client->pers.lastSpawnTime < 1000) { pm.cmd.weapon = client->ps.weapon; } Pmove(&pm); // Gordon: thx to bani for this // ikkyo - fix leaning players bug VectorCopy(client->ps.velocity, ent->s.pos.trDelta); SnapVector(ent->s.pos.trDelta); // end // server cursor hints if (ent->lastHintCheckTime < level.time) { G_CheckForCursorHints(ent); ent->lastHintCheckTime = level.time + FRAMETIME; } // DHM - Nerve :: Set animMovetype to 1 if ducking if (ent->client->ps.pm_flags & PMF_DUCKED) { ent->s.animMovetype = 1; } else { ent->s.animMovetype = 0; } // save results of pmove if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; ent->r.eventTime = level.time; } // Ridah, fixes jittery zombie movement if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate(&ent->client->ps, &ent->s, level.time, qfalse); } else { BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qfalse); } if (!(ent->client->ps.eFlags & EF_FIRING)) { client->fireHeld = qfalse; // for grapple } // // use the snapped origin for linking so it matches client predicted versions VectorCopy(ent->s.pos.trBase, ent->r.currentOrigin); VectorCopy(pm.mins, ent->r.mins); VectorCopy(pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents(ent, oldEventSequence); // link entity now, after any personal teleporters have been used trap_LinkEntity(ent); if (!ent->client->noclip) { G_TouchTriggers(ent); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); // touch other objects ClientImpacts(ent, &pm); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons = client->buttons & ~client->oldbuttons; //----(SA) added client->oldwbuttons = client->wbuttons; client->wbuttons = ucmd->wbuttons; client->latched_wbuttons = client->wbuttons & ~client->oldwbuttons; // suburb, return here for noclippers in spec to avoid following checks if (client->sess.sessionTeam == TEAM_SPECTATOR && client->noclip) { return; } // Rafael - Activate // Ridah, made it a latched event (occurs on keydown only) if (client->latched_buttons & BUTTON_ACTIVATE) { Cmd_Activate_f(ent); } if (g_entities[ent->client->ps.identifyClient].team != ent->team || !g_entities[ent->client->ps.identifyClient].client) { ent->client->ps.identifyClient = -1; } // check for respawning if (client->ps.stats[STAT_HEALTH] <= 0) { // Nico, forcing respawn limbo(ent); return; } // perform once-a-second actions ClientTimerActions(ent, msec); // Nico, check ping if (client->ps.ping > MAX_PLAYER_PING) { if (!client->pers.loadKillNeeded) { CP(va("cpm \"%s^w: ^1Too high ping detected, load or kill required.\n\"", GAME_VERSION_COLORED)); // suburb, prevent trigger bug client->pers.loadKillNeeded = qtrue; } } // Nico, pmove_fixed if (!client->pers.pmoveFixed) { CP(va("cpm \"%s^w: ^1You were removed from teams because you can not use pmove_fixed 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "pmoveon"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check rate if (client->pers.rate < MIN_PLAYER_RATE_VALUE || client->pers.rate > MAX_PLAYER_RATE_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= rate <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_RATE_VALUE, MAX_PLAYER_RATE_VALUE)); trap_SendServerCommand(ent - g_entities, "resetRate"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check snaps (unsigned int) if (client->pers.snaps > MAX_PLAYER_SNAPS_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= snaps <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_SNAPS_VALUE, MAX_PLAYER_SNAPS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetSnaps"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check timenudge if (client->pers.clientTimeNudge != FORCED_PLAYER_TIMENUDGE_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cl_timenudge %d.\n\"", GAME_VERSION_COLORED, FORCED_PLAYER_TIMENUDGE_VALUE)); trap_SendServerCommand(ent - g_entities, "resetTimeNudge"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check maxpackets if (client->pers.clientMaxPackets < MIN_PLAYER_MAX_PACKETS_VALUE || client->pers.clientMaxPackets > MAX_PLAYER_MAX_PACKETS_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= cl_maxpackets <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_MAX_PACKETS_VALUE, MAX_PLAYER_MAX_PACKETS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetMaxPackets"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check max FPS if (client->pers.maxFPS < MIN_PLAYER_FPS_VALUE || client->pers.maxFPS > MAX_PLAYER_FPS_VALUE) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use %d <= com_maxfps <= %d.\n\"", GAME_VERSION_COLORED, MIN_PLAYER_FPS_VALUE, MAX_PLAYER_FPS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetMaxFPS"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force yawspeed 0 if (client->pers.yawspeed != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cl_yawspeed 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "resetYawspeed"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force pitchspeed 0 if (client->pers.pitchspeed != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cl_pitchspeed 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "resetPitchspeed"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force auto demo record in cup mode if (g_cupMode.integer != 0 && client->pers.autoDemo == 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_autoDemo 1.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "autoDemoOn"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force hide me in cup mode if (g_cupMode.integer != 0 && client->pers.hideme == 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_hideMe 1.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "hideMeOn"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force CGaz if disabled on server if (g_disableCGaz.integer != 0 && client->pers.cgaz != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_drawCGaz 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "CGazOff"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, force snapping hud if disabled on server or logged in if ((g_disableSnappingHUD.integer != 0 || (client->sess.logged && (client->sess.sessionTeam == TEAM_AXIS || client->sess.sessionTeam == TEAM_ALLIES))) && client->pers.snapping != 0) { CP(va("cpm \"%s^w: ^1You were removed from teams because you must use cg_drawVelocitySnapping 0.\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "SnappingOff"); SetTeam(ent, "s", -1, -1, qfalse); } // suburb, prevent pronebug & wallbug counter = 0; for (i = 0; i < 3; ++i) { if (client->pers.oldPosition[i] == (int) pm.ps->origin[i]) { counter++; } } Zaccel = (int) pm.ps->velocity[2] - client->pers.oldZvelocity; speed = sqrt(pm.ps->velocity[0] * pm.ps->velocity[0] + pm.ps->velocity[1] * pm.ps->velocity[1]); if (client->sess.logged && ((!client->sess.timerunActive && speed > BUGGING_MAX_SPEED && counter == 3) || // prevent accelerating in brushes (pm.ps->eFlags & EF_PRONE && Zaccel > -6 && Zaccel < 0 && client->ps.groundEntityNum == ENTITYNUM_NONE && !client->pers.isTouchingJumppad))) { // prevent accelerating on steep slopes if (!client->pers.buggedLastFrame) { // only do something the second frame not to break jumppads client->pers.buggedLastFrame = qtrue; return; } if (!client->pers.loadKillNeeded) { CP(va("cpm \"%s^w: ^1Bugging detected, load or kill required.\n\"", GAME_VERSION_COLORED)); client->pers.loadKillNeeded = qtrue; } } // checking acceleration in brushes every frame would break corner skimming if (level.time - client->pers.lastBuggingCheck > BUGGING_CHECK_FREQUENCY) { for (i = 0; i < 3; ++i) { client->pers.oldPosition[i] = (int) pm.ps->origin[i]; client->pers.lastBuggingCheck = level.time; } } client->pers.oldZvelocity = (int) pm.ps->velocity[2]; client->pers.buggedLastFrame = qfalse; }
void ClientThink_real( gentity_t *ent, usercmd_t *ucmd ) { gclient_t *client; pmove_t pm; vec3_t oldOrigin; int oldEventSequence; int msec; //Don't let the player do anything if in a camera if ( ent->s.number == 0 ) { extern cvar_t *g_skippingcin; if ( in_camera ) { // watch the code here, you MUST "return" within this IF(), *unless* you're stopping the cinematic skip. // if ( ClientCinematicThink(ent->client) ) { if (g_skippingcin->integer) // already doing cinematic skip? { // yes... so stop skipping... gi.cvar_set("skippingCinematic", "0"); gi.cvar_set("timescale", "1"); } else { // no... so start skipping... gi.cvar_set("skippingCinematic", "1"); gi.cvar_set("timescale", "100"); return; } } else { return; } } else { if ( g_skippingcin->integer ) {//We're skipping the cinematic and it's over now gi.cvar_set("timescale", "1"); gi.cvar_set("skippingCinematic", "0"); } if ( ent->client->ps.pm_type == PM_DEAD && cg.missionStatusDeadTime < level.time ) {//mission status screen is up because player is dead, stop all scripts if (Q_stricmpn(level.mapname,"_holo",5)) { stop_icarus = qtrue; } } } // Don't allow the player to adjust the pitch when they are in third person overhead cam. extern vmCvar_t cg_thirdPerson; if ( cg_thirdPerson.integer == 2 ) { ucmd->angles[PITCH] = 0; } if ( player_locked && ent->client->ps.pm_type < PM_DEAD ) {//lock out player control unless dead VectorClear(ucmd->angles) ; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->buttons = 0; ucmd->upmove = 0; } } else { G_NPCMunroMatchPlayerWeapon( ent ); } client = ent->client; // mark the time, so the connection sprite can be removed client->lastCmdTime = level.time; client->pers.lastCommand = *ucmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; if ( msec < 1 ) { msec = 1; } if ( msec > 200 ) { msec = 200; } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) return; if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } //FIXME: if global gravity changes this should update everyone's personal gravity... if ( !(ent->svFlags & SVF_CUSTOM_GRAVITY) ) { client->ps.gravity = g_gravity->value; } // set speed if ( ent->NPC != NULL ) {//we don't actually scale the ucmd, we use actual speeds if ( ent->NPC->combatMove == qfalse ) { if ( !(ucmd->buttons & BUTTON_USE) ) {//Not leaning qboolean Flying = (ucmd->upmove && ent->NPC->stats.moveType == MT_FLYSWIM); qboolean Climbing = (ucmd->upmove && ent->watertype&CONTENTS_LADDER ); client->ps.friction = 6; if ( ucmd->forwardmove || ucmd->rightmove || Flying ) { if ( ent->NPC->behaviorState != BS_FORMATION ) {//In - Formation NPCs set thier desiredSpeed themselves if ( ucmd->buttons & BUTTON_WALKING ) { ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed; } else//running { ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed; } if ( ent->NPC->currentSpeed >= 80 ) {//At higher speeds, need to slow down close to stuff //Slow down as you approach your goal if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 { if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED ) { float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST; ent->NPC->desiredSpeed = ceil(slowdownSpeed); if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED ) {//don't slow down too much ent->NPC->desiredSpeed = MIN_NPC_SPEED; } } } } } } else if ( Climbing ) { ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed; } else {//We want to stop ent->NPC->desiredSpeed = 0; } NPC_Accelerate( ent, (ent->NPC->behaviorState==BS_FORMATION), (ent->NPC->behaviorState==BS_FORMATION) ); if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) {//No-one walks this slow client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop ucmd->forwardmove = 0; ucmd->rightmove = 0; } else { if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed ) {//Play the walkanim ucmd->buttons |= BUTTON_WALKING; } else { ucmd->buttons &= ~BUTTON_WALKING; } if ( ent->NPC->currentSpeed > 0 ) {//We should be moving if ( Climbing || Flying ) { if ( !ucmd->upmove ) {//We need to force them to take a couple more steps until stopped ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove; } } else if ( !ucmd->forwardmove && !ucmd->rightmove ) {//We need to force them to take a couple more steps until stopped ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove; ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove; } } client->ps.speed = ent->NPC->currentSpeed; //Slow down on turns - don't orbit!!! float turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->desiredYaw ) ))/180; if ( turndelta < 0.75f ) { client->ps.speed = 0; } else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 ) {//Turn is greater than 45 degrees or closer than 100 to goal client->ps.speed = floor(((float)(client->ps.speed))*turndelta); } } } } else { ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent ); client->ps.speed = ent->NPC->desiredSpeed; } } else {//Client sets ucmds and such for speed alterations client->ps.speed = g_speed->value;//default is 320 } //Apply forced movement if ( client->forced_forwardmove ) { ucmd->forwardmove = client->forced_forwardmove; if ( !client->ps.speed ) { if ( ent->NPC != NULL ) { client->ps.speed = ent->NPC->stats.runSpeed; } else { client->ps.speed = g_speed->value;//default is 320 } } } if ( client->forced_rightmove ) { ucmd->rightmove = client->forced_rightmove; if ( !client->ps.speed ) { if ( ent->NPC != NULL ) { client->ps.speed = ent->NPC->stats.runSpeed; } else { client->ps.speed = g_speed->value;//default is 320 } } } //FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?) BG_AddPushVecToUcmd( ent, ucmd ); BG_CalculateOffsetAngles( ent, ucmd ); // set up for pmove oldEventSequence = client->ps.eventSequence; memset( &pm, 0, sizeof(pm) ); pm.gent = ent; pm.ps = &client->ps; pm.cmd = *ucmd; // pm.tracemask = MASK_PLAYERSOLID; // used differently for navgen pm.tracemask = ent->clipmask; pm.trace = gi.trace; pm.pointcontents = gi.pointcontents; pm.debugLevel = g_debugMove->integer; pm.noFootsteps = 0;//( g_dmflags->integer & DF_NO_FOOTSTEPS ) > 0; VectorCopy( client->ps.origin, oldOrigin ); // perform a pmove Pmove( &pm ); // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; { int seq; seq = (ent->client->ps.eventSequence-1) & (MAX_PS_EVENTS-1); ent->s.event = ent->client->ps.events[ seq ] | ( ( ent->client->ps.eventSequence & 3 ) << 8 ); ent->s.eventParm = ent->client->ps.eventParms[ seq ]; } } PlayerStateToEntityState( &ent->client->ps, &ent->s ); VectorCopy ( ent->currentOrigin, ent->lastOrigin ); #if 1 // use the precise origin for linking VectorCopy( ent->client->ps.origin, ent->currentOrigin ); #else //We don't use prediction anymore, so screw this // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); #endif VectorCopy (pm.mins, ent->mins); VectorCopy (pm.maxs, ent->maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; VectorCopy( ucmd->angles, client->pers.cmd_angles ); // execute client events ClientEvents( ent, oldEventSequence ); if ( pm.useEvent ) { //TODO: Use TryUse( ent ); } // link entity now, after any personal teleporters have been used gi.linkentity( ent ); ent->client->hiddenDist = 0; if ( !ent->client->noclip ) { G_TouchTriggersLerped( ent ); } // touch other objects ClientImpacts( ent, &pm ); // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( ent->NPC == NULL && level.time > client->respawnTime ) { // don't allow respawn if they are still flying through the // air, unless 10 extra seconds have passed, meaning something // strange is going on, like the corpse is caught in a wind tunnel if ( level.time < client->respawnTime + 10000 ) { if ( client->ps.groundEntityNum == ENTITYNUM_NONE ) { return; } } // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } if ((cg.missionStatusShow) && ((cg.missionStatusDeadTime + 1) < level.time)) { if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { cg.missionStatusShow = 0; ScoreBoardReset(); // Q3_TaskIDComplete( ent, TID_MISSIONSTATUS ); } } // perform once-a-second actions //ClientTimerActions( ent, msec ); //DEBUG INFO /* if ( client->ps.clientNum < 1 ) {//Only a player if ( ucmd->buttons & BUTTON_USE ) { NAV_PrintLocalWpDebugInfo( ent ); } } */ }