/* ============== ClientEndFrame Called at the end of each server frame for each connected client A fast client will have multiple ClientThink for each ClientEndFrame, while a slow client may have multiple ClientEndFrame between ClientThink. ============== */ void ClientEndFrame(gentity_t *ent) { int i; // Nico, flood protection if (level.time >= (ent->client->sess.nextReliableTime + 1000) && ent->client->sess.numReliableCmds) { ent->client->sess.numReliableCmds--; // Reset the threshold because they were good for a bit if (!ent->client->sess.numReliableCmds) { ent->client->sess.thresholdTime = 0; } } // Nico, update best speeds if (ent->client->sess.timerunActive) { float currentSpeed; currentSpeed = sqrt(ent->client->ps.velocity[0] * ent->client->ps.velocity[0] + ent->client->ps.velocity[1] * ent->client->ps.velocity[1]); // Nico, update overall max speed if (currentSpeed > ent->client->sess.overallMaxSpeed) { ent->client->sess.overallMaxSpeed = currentSpeed; } // Nico, update max speed of the current run if (currentSpeed > ent->client->sess.maxSpeed) { ent->client->sess.maxSpeed = currentSpeed; } } // used for informing of speclocked teams. // Zero out here and set only for certain specs ent->client->ps.powerups[PW_BLACKOUT] = 0; if ((ent->client->sess.sessionTeam == TEAM_SPECTATOR) || (ent->client->ps.pm_flags & PMF_LIMBO)) { // JPW NERVE SpectatorClientEndFrame(ent); return; } // turn off any expired powerups // OSP -- range changed for MV for (i = 0 ; i < PW_NUM_POWERUPS ; ++i) { if (i == PW_FIRE || // these aren't dependant on level.time i == PW_ELECTRIC || i == PW_BREATHER || ent->client->ps.powerups[i] == 0 // OSP || i == PW_OPS_CLASS_1 || i == PW_OPS_CLASS_2 || i == PW_OPS_CLASS_3 ) { continue; } // OSP -- If we're paused, update powerup timers accordingly. // Make sure we dont let stuff like CTF flags expire. if (ent->client->ps.powerups[i] < level.time) { ent->client->ps.powerups[i] = 0; } } // // If the end of unit layout is displayed, don't give // the player any normal movement attributes // // burn from lava, etc P_WorldEffects(ent); // apply all the damage taken this frame P_DamageFeedback(ent); // add the EF_CONNECTION flag if we haven't gotten commands recently if (level.time - ent->client->lastCmdTime > 1000) { ent->s.eFlags |= EF_CONNECTION; } else { ent->s.eFlags &= ~EF_CONNECTION; } ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... // Gordon: WHY? other ents use it. G_SetClientSound(ent); // set the latest infor // 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); } // DHM - Nerve :: If it's been a couple frames since being revived, and props_frame_state // wasn't reset, go ahead and reset it if (ent->props_frame_state >= 0 && ((level.time - ent->s.effect3Time) > 100)) { ent->props_frame_state = -1; } // DHM - Nerve :: Reset 'count2' for flamethrower if (!(ent->client->buttons & BUTTON_ATTACK)) { ent->count2 = 0; } // dhm // zinx - #280 - run touch functions here too, so movers don't have to wait // until the next ClientThink, which will be too late for some map // scripts (railgun) G_TouchTriggers(ent); // run entity scripting G_Script_ScriptRun(ent); // store the client's current position for antilag traces G_StoreClientPosition(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; // 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 ); }