/* ============== 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; }
/* ============== 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 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 ) { 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; } }