diff --git a/code/api/shared/q_endian.h b/code/api/shared/q_endian.h index 678de3e..0440aa3 100644 --- a/code/api/shared/q_endian.h +++ b/code/api/shared/q_endian.h @@ -93,6 +93,12 @@ void CopyLongSwap(void *dest, void *src); #define LongLongSwap(x) bswap64(x) #endif +#elif defined(__MINGW32__) + +#define ShortSwap(x) __builtin_bswap16(x) +#define LongSwap(x) __builtin_bswap32(x) +#define LongLongSwap(x) __builtin_bswap64(x) + #else #include diff --git a/code/cgame/cg_cvar.h b/code/cgame/cg_cvar.h index 6503781..ba5db70 100644 --- a/code/cgame/cg_cvar.h +++ b/code/cgame/cg_cvar.h @@ -148,7 +148,8 @@ CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE ) CG_CVAR( cl_maxpackets, "cl_maxpackets", "60", CVAR_ARCHIVE ) CG_CVAR( r_maxpolys, "r_maxpolys", "1800", CVAR_ARCHIVE ) CG_CVAR( r_maxpolyverts, "r_maxpolyverts", "9000", CVAR_ARCHIVE ) -CG_CVAR( com_maxfps, "com_maxfps", "85", CVAR_ARCHIVE ) +CG_CVAR( com_maxfps, "com_maxfps", "125", CVAR_CHEAT) +CG_CVAR( cl_maxfps, "cl_maxfps", "-1", CVAR_ARCHIVE ) CG_CVAR( cg_blood, "com_blood", "1", CVAR_ARCHIVE ) CG_CVAR( cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE ) CG_CVAR( cg_scorePlum, "cg_scorePlums", "1", CVAR_ARCHIVE ) @@ -163,6 +164,9 @@ CG_CVAR( cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0 ) CG_CVAR( cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0 ) CG_CVAR( cg_timescale, "timescale", "1", 0 ) CG_CVAR( r_clear, "r_clear", "0", 0 ) +CG_CVAR( cg_packetdelay, "cg_packetdelay", "0", CVAR_USERINFO ) +CG_CVAR( cl_packetdelay, "cl_packetdelay", "0", CVAR_CHEAT ) +CG_CVAR( cg_pmovesmooth, "cg_pmovesmooth", "1", CVAR_ARCHIVE ) CG_CVAR( r_debugSort, "r_debugSort", "0", CVAR_CHEAT ) @@ -225,8 +229,6 @@ CG_CVAR( cg_cmdTimeNudge, "cg_cmdTimeNudge", "0", CVAR_ARCHIVE | CVAR_USERI CG_CVAR( cg_projectileNudge, "cg_projectileNudge", "0", CVAR_ARCHIVE ) CG_CVAR( cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE ) CG_CVAR( cl_timeNudge, "cl_timeNudge", "0", CVAR_ARCHIVE | CVAR_USERINFO ) -CG_CVAR( cg_latentSnaps, "cg_latentSnaps", "0", CVAR_USERINFO | CVAR_CHEAT ) -CG_CVAR( cg_latentCmds, "cg_latentCmds", "0", CVAR_USERINFO | CVAR_CHEAT ) CG_CVAR( cg_plOut, "cg_plOut", "0", CVAR_USERINFO | CVAR_CHEAT ) CG_CVAR( cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE ) CG_CVAR( cg_predictWeapons, "cg_predictWeapons", "0", CVAR_ARCHIVE ) diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index ee2f34c..3af2787 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -314,7 +314,6 @@ typedef struct centity_s { // exact interpolated position of entity on this frame vec3_t lerpOrigin; vec3_t lerpAngles; - } centity_t; @@ -764,6 +763,7 @@ typedef struct { vec3_t kick_origin; // temp working variables for player view + vec3_t view_org, last_pmove_fixed; float bobfracsin; int bobcycle; float xyspeed; @@ -1451,7 +1451,8 @@ typedef struct { float oldtimescale; // Timescale value prior to pausing qboolean pmove_fixed; - int pmove_msec; + int pmove_msec; + int pmove_float; int sv_fps; @@ -1459,6 +1460,7 @@ typedef struct { qboolean sv_cheats; + int active_maxfps; } cgs_t; //============================================================================== @@ -2302,5 +2304,6 @@ extern int cvar_developer; extern int dll_trap_R_AddRefEntityToScene2; extern int dll_trap_R_AddLinearLightToScene; +void CG_Update_MaxFPS(void); #endif //__CG_LOCAL_H diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 55a266f..adda4e5 100644 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -967,7 +967,7 @@ static void CG_UntrackPredictedEnt(localEntity_t *le) { void CG_MatchPredictedEnt(centity_t* cent) { localEntity_t* match = NULL; - float best = SQR(64); + float best = SQR(32); vec3_t diff; // Can only match entities we own. @@ -1025,6 +1025,7 @@ void CG_UpdateLocalPredictedEnts(void) { } void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) { + usercmd_t cmd; localEntity_t *le; int vel, tmod = 0; @@ -1058,15 +1059,21 @@ void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) { le->leFlags = LEF_PREDICTED; le->pred_weapon = ent->weapon; + // Technically we could chain this from where we predict the event but this + // should always be correct since command generation is tied to frame + // generation. + trap_GetUserCmd( trap_GetCurrentCmdNumber(), &cmd ); + le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time - tmod + cl_timeNudge.integer; + le->pos.trTime = cmd.serverTime - tmod + cl_timeNudge.integer; - VectorCopy(origin, le->lerp.trBase); - VectorCopy(origin, le->pos.trBase); if (ent->weapon == WP_SUPERNAILGUN || ent->weapon == WP_NAILGUN) { - VectorMA(le->pos.trBase, -15, forward, le->pos.trBase); - le->pos.trBase[2] -= 6; + VectorMA(origin, -15, forward, origin); + origin[2] -= 6; + SnapVector(origin); } + VectorCopy(origin, le->lerp.trBase); + VectorCopy(origin, le->pos.trBase); VectorScale(forward, vel, le->pos.trDelta ); SnapVector(le->pos.trDelta); diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index 32167d2..23e4fb8 100644 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -170,7 +170,6 @@ static cvarLimitTable_t cvarLimitTable[] = { { &cl_maxpackets, "cl_maxpackets", 40, 30, -1, 0, 0, qfalse }, { &r_maxpolys, "r_maxpolys", 1800, 1800, -1, 0, 0, qfalse }, { &r_maxpolyverts, "r_maxpolyverts", 9000, 9000, -1, 0, 0, qfalse }, - { &com_maxfps, "com_maxfps", 85, 30, 130, 0, 0, qfalse }, { &cg_fov, "cg_fov", 90, 5, 135, 0, 0, qfalse }, { &cg_thirdPerson, "cg_thirdPerson", 0, 0, 0, 0, 0, qfalse }, { &r_lodCurveError, "r_lodCurveError", 250, 1, -1, 0, 0, qfalse }, @@ -198,8 +197,6 @@ static cvarLimitTable_t cvarLimitTable[] = { //Unlagged cvars { &cg_cmdTimeNudge, "cg_cmdTimeNudge", 0, 0, 999, 0, 0, qtrue }, { &cl_timeNudge, "cl_timeNudge", 0, -50, 50, 0, 0, qtrue }, - { &cg_latentSnaps, "cg_latentSnaps", 0, 0, 10, 0, 0, qtrue }, - { &cg_latentCmds, "cg_latentCmds", 0, 0, MAX_LATENT_CMDS - 1 , 0, 0, qtrue }, { &cg_plOut, "cg_plOut", 0, 0, 100 , 0, 0, qtrue }, // hunkmegs { &com_hunkmegs, "com_hunkmegs", 128, 128, -1, 0, 0, qfalse }, @@ -209,6 +206,8 @@ static cvarLimitTable_t cvarLimitTable[] = { { &cg_cl_yawspeed, "cl_yawspeed", 140, 0, 0, 0, 0, qfalse }, { &cg_cl_pitchspeed, "cl_pitchspeed", 140, 0, 0, 0, 0, qfalse }, { &cg_cl_freelook, "cl_freelook", 1, 1, 1, 0, 0, qfalse }, + + { &cg_packetdelay, "cg_packetdelay", 0, 0, 200, 0, 0, qfalse }, }; static const int cvarLimitTableSize = (int)ARRAY_LEN( cvarLimitTable ); @@ -515,6 +514,8 @@ void CG_UpdateCvars( void ) { } else if (cv->vmCvar == &cg_pipeTrail) { CG_UpdateColorFromCvar(cg_pipeTrail.string, colorPipeTrail, &cg.pipeTrailTeam, cg.pipeTrailColor); + } else if (cv->vmCvar == &cl_maxfps || cv->vmCvar == &com_maxfps) { + CG_Update_MaxFPS(); } } } @@ -538,6 +539,12 @@ void CG_UpdateCvars( void ) { // limit cvars CG_LimitCvars(); + + // Handle packet delay + if (cg_packetdelay.integer != cl_packetdelay.integer) { + trap_Cvar_Set("cl_packetdelay", cg_packetdelay.string); + trap_Cvar_Update(&cl_packetdelay); + } } diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index fa8749f..d65ff03 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -664,11 +664,11 @@ void CG_PredictPlayerState( void ) { cg.predictedPlayerState = cg.snap->ps; } - // demo playback just copies the moves - if ( cg.demoPlayback || - (cg.snap->ps.pm_flags & PMF_FOLLOW) || - (cg.snap->ps.pm_flags & PMF_CHASE) ) { + // demo playback/spectating just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + (cg.snap->ps.pm_flags & PMF_CHASE) ) { CG_InterpolatePlayerState( qfalse ); + VectorCopy( cg.predictedPlayerState.origin, cg.view_org ); return; } @@ -736,6 +736,7 @@ void CG_PredictPlayerState( void ) { cg_pmove.pmove_fixed = (int)cgs.pmove_fixed; cg_pmove.pmove_msec = cgs.pmove_msec; + cg_pmove.pmove_float = cgs.pmove_float; // Like the comments described above, a player's state is entirely // re-predicted from the last valid snapshot every client frame, which @@ -755,10 +756,7 @@ void CG_PredictPlayerState( void ) { // except a frame following a new snapshot in which there was a prediction // error. This yeilds anywhere from a 15% to 40% performance increase, // depending on how much of a bottleneck the CPU is. - - // we check for cg_latentCmds because it'll mess up the optimization - // FIXME: make cg_latentCmds work with cg_optimizePrediction? - if ( cg_optimizePrediction.integer && !cg_latentCmds.integer ) { + if ( cg_optimizePrediction.integer) { if ( cg.nextFrameTeleport || cg.thisFrameTeleport ) { // do a full predict cg.lastPredictedCommand = 0; @@ -829,10 +827,6 @@ void CG_PredictPlayerState( void ) { // get the command trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); - 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.predictedPlayerState.commandTime ) { continue; @@ -917,24 +911,21 @@ void CG_PredictPlayerState( void ) { VectorSet( cg_pmove.groundVelocity, 0, 0, 0 ); } - if ( cg_pmove.pmove_fixed ) { - cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + cgs.pmove_msec-1) / cgs.pmove_msec) * cgs.pmove_msec; - } - // ydnar: if server respawning, freeze the player if ( cg.serverRespawning ) { cg_pmove.ps->pm_type = PM_FREEZE; } cg_pmove.airleft = (cg.waterundertime - cg.time); + cg_pmove.retflags = 0; if( cg.agentDataEntity && cg.agentDataEntity->currentValid ) { cg_pmove.agentclass = cg.agentDataEntity->currentState.torsoAnim; } else { cg_pmove.agentclass = 0; } - // we check for cg_latentCmds because it'll mess up the optimization - if ( cg_optimizePrediction.integer && !cg_latentCmds.integer ) { + + if ( cg_optimizePrediction.integer ) { // if we need to predict this command, or we've run out of space in the saved states queue if ( cmdNum >= predictCmd || (stateIndex + 1) % NUM_SAVED_STATES == cg.stateHead ) { // run the Pmove @@ -985,6 +976,8 @@ void CG_PredictPlayerState( void ) { //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); } + VectorCopy( cg.predictedPlayerState.origin, cg.view_org ); + if ( cg_showmiss.integer > 1 ) { CG_Printf( BOX_PRINT_MODE_CHAT, "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); } @@ -1016,4 +1009,23 @@ void CG_PredictPlayerState( void ) { cg.eventSequence = cg.predictedPlayerState.eventSequence; } } + + // Need to interp with pmove_fixed to avoid effective 1/pmove_msec fps. + if (cgs.pmove_fixed) { + if ((cg_pmove.retflags & PMRF_PMOVE_PARTIAL) == 0) // New full frame + VectorCopy(oldPlayerState.origin, cg.last_pmove_fixed); + + // We can be very conservative about when we choose to stitch. + if (cg_pmovesmooth.integer && + cg.predictedPlayerState.commandTime - oldPlayerState.commandTime <= cgs.pmove_msec && + DistanceSquared(cg.predictedPlayerState.origin, cg.last_pmove_fixed) < SQR(16) && + !cg.thisFrameTeleport) { + vec3_t diff; + float dt = 1 + (cg.predictedPlayerState.commandTime % cgs.pmove_msec); + dt /= 1.0 * cgs.pmove_msec; + + VectorSubtract(cg.predictedPlayerState.origin, cg.last_pmove_fixed, diff); + VectorMA(cg.last_pmove_fixed, dt, diff, cg.view_org); + } + } } diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index e9c953b..3d2d2f5 100644 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -217,6 +217,8 @@ void CG_ParseSysteminfo( void ) { } else if ( cgs.pmove_msec > 33 ) { cgs.pmove_msec = 33; } + cgs.pmove_float = ( atoi( Info_ValueForKey( info, "pmove_float" ) ) ) ? qtrue : qfalse; + CG_Update_MaxFPS(); cgs.sv_fps = Q_atoi( Info_ValueForKey( info, "sv_fps" ) ); @@ -899,3 +901,23 @@ void CG_ExecuteNewServerCommands( int latestSequence ) { } } } + + +/* +==================== +CG_Update_MaxFPS + +Update `com_maxfps` to max allowed value by gamesettings and `cl_maxfps`. +==================== +*/ +void CG_Update_MaxFPS(void) { + // Temp: Higher than 250 wants changes later in stack at higher pings. + int fps = cl_maxfps.integer == -1 ? 250 : cl_maxfps.integer; + fps = Q_max(Q_min(fps, cgs.pmove_fixed ? 500 : 125), 60); + + if (com_maxfps.integer == fps) + return; + + trap_Cvar_Set("com_maxfps", va("%d", fps)); + trap_Cvar_Update(&com_maxfps); +} diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c index 939d7cc..37d40cb 100644 --- a/code/cgame/cg_snapshot.c +++ b/code/cgame/cg_snapshot.c @@ -306,26 +306,13 @@ static snapshot_t *CG_ReadNextSnapshot( void ) { // try to read the snapshot from the client system cgs.processedSnapshotNum++; r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); - // the client wants latent snaps and the just-read snapshot is valid - if ( cg_latentSnaps.integer && r ) { - int i = 0, time = dest->serverTime; - - // keep grabbing one snapshot earlier until we get to the right time - while ( dest->serverTime > time - cg_latentSnaps.integer * (1000 / cgs.sv_fps) ) { - if ( !(r = trap_GetSnapshot( cgs.processedSnapshotNum - i, dest )) ) { - // the snapshot is not valid, so stop here - break; - } - - // go back one more - i++; - } - } +#if 0 // FIXME: why would trap_GetSnapshot return a snapshot with the same server time if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { //continue; } +#endif // if it succeeded, return if ( r ) { @@ -349,7 +336,6 @@ static snapshot_t *CG_ReadNextSnapshot( void ) { return NULL; } - /* ============ CG_ProcessSnapshots @@ -377,13 +363,8 @@ void CG_ProcessSnapshots( void ) { trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); if ( n != cg.latestSnapshotNum ) { if ( n < cg.latestSnapshotNum ) { - // this may actually happen with lag simulation going on - if ( cg_latentSnaps.integer ) { - CG_Printf( BOX_PRINT_MODE_CHAT, "WARNING: CG_ProcessSnapshots: n < cg.latestSnapshotNum\n" ); - } else { // this should never happen - CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); - } + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); } cg.latestSnapshotNum = n; } @@ -423,12 +404,7 @@ void CG_ProcessSnapshots( void ) { // if time went backwards, we have a level restart if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { - // this may actually happen with lag simulation going on - if ( cg_latentSnaps.integer ) { - CG_Printf( BOX_PRINT_MODE_CHAT, "WARNING: CG_ProcessSnapshots: Server time went backwards\n" ); - } else { - CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); - } + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); } } diff --git a/code/cgame/cg_unlagged.c b/code/cgame/cg_unlagged.c index 2dd4db3..0446e9e 100644 --- a/code/cgame/cg_unlagged.c +++ b/code/cgame/cg_unlagged.c @@ -64,6 +64,7 @@ void CG_PredictWeaponEffects( centity_t *cent ) { // get the muzzle point VectorCopy( cg.predictedPlayerState.origin, muzzlePoint ); + SnapVector(muzzlePoint); // match remote s.pos muzzlePoint[2] += cg.predictedPlayerState.viewheight; // get forward, right, and up @@ -75,18 +76,17 @@ void CG_PredictWeaponEffects( centity_t *cent ) { AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL ); } VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + SnapVector(muzzlePoint); CG_AddPredictedMissile(ent, muzzlePoint, forward); if ( ent->weapon == WP_SHOTGUN ) { VectorScale( forward, 4096, endPoint ); SnapVector( endPoint ); - SnapVector( muzzlePoint ); CG_SingleShotgunPattern( muzzlePoint, endPoint , ent->clientNum, cg.oldTime % 256 ); } else if ( ent->weapon == WP_SUPERSHOTGUN ) { VectorScale( forward, 4096, endPoint ); SnapVector( endPoint ); - SnapVector( muzzlePoint ); CG_ShotgunPattern( muzzlePoint, endPoint , ent->clientNum, cg.oldTime % 256 ); } else if ( ent->weapon == WP_MINIGUN ) { int heat = cg.minigunHeat - ((cg.time - cg.minigunLast)*15) / 5000; @@ -94,7 +94,6 @@ void CG_PredictWeaponEffects( centity_t *cent ) { heat = 0; VectorScale( forward, 4096, endPoint ); SnapVector( endPoint ); - SnapVector( muzzlePoint ); CG_MinigunPattern(muzzlePoint, endPoint , ent->clientNum, cg.oldTime % 256, heat ); } else if ( ent->weapon == WP_ASSAULTRIFLE ) { VectorMA (muzzlePoint, 4096, forward, endPoint ); diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c index 94b1693..367d3cd 100644 --- a/code/cgame/cg_view.c +++ b/code/cgame/cg_view.c @@ -310,7 +310,6 @@ static void CG_OffsetThirdPersonView( void ) { // this causes a compiler bug on mac MrC compiler static void CG_StepOffset( void ) { int timeDelta; - // smooth out stair climbing timeDelta = cg.time - cg.stepTime; if ( timeDelta < STEP_TIME ) { @@ -998,7 +997,7 @@ static int CG_CalcViewValues( void ) { cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + ps->velocity[1] * ps->velocity[1] ); - VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( cg.view_org, cg.refdef.vieworg ); VectorCopy( ps->viewangles, cg.refdefViewAngles ); if (cg_cameraOrbit.integer) { @@ -1208,8 +1207,6 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo } cg.time = serverTime; - // adjust the clock to reflect latent snaps - cg.time -= cg_latentSnaps.integer * (1000 / cgs.sv_fps); cg.demoPlayback = demoPlayback; diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index f3bf624..fa8582c 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -2936,9 +2936,36 @@ void PmoveSingle (pmove_t *pmove) { PM_WaterEvents(); // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity ); + // But only if pmove_float is not enabled. We always snap on slick + // surfaces to prevent acceleration. + if (!pm->pmove_float || pml.groundTrace.surfaceFlags & SURF_SLICK) + trap_SnapVector( pm->ps->velocity ); } +/* +================ +PmoveFixedPartial + +For overlapping frames with pmove_fixed we must handle still non-movement state +such as buttons and viewangle updates. +================ +*/ +void PmoveFixedPartial (pmove_t *pmove) { + pm = pmove; + memset(&pml, 0, sizeof(pml)); // Paranoia beyond clearing msec + + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + PM_Animate(); + + PM_Weapon(); + if (pm->ps->weaponstate >= WEAPON_FIRING) + pm->ps->eFlags |= EF_FIRING; + + PM_TorsoAnimation(); + + pm->retflags |= PMRF_PMOVE_PARTIAL; +} /* ================ @@ -2947,38 +2974,52 @@ Pmove Can be called by either the server or the client ================ */ +static inline int RoundUp(int x, int d) { + return ((x/d)*d) + d - 1; +} + void Pmove (pmove_t *pmove) { - int finalTime; + int end, final; - finalTime = pmove->cmd.serverTime; + final = end = pmove->cmd.serverTime; - if ( finalTime < pmove->ps->commandTime ) { + if ( final < pmove->ps->commandTime ) return; // should not happen - } - - if ( finalTime > pmove->ps->commandTime + 1000 ) { - pmove->ps->commandTime = finalTime - 1000; - } + else if ( final > pmove->ps->commandTime + 1000 ) + pmove->ps->commandTime = final - 1000; pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<pmove_fixed) { + pmove->ps->commandTime = RoundUp(pmove->ps->commandTime, pmove->pmove_msec); + final = RoundUp(final, pmove->pmove_msec); + + if (pmove->ps->commandTime == final) // Is this a real movement frame? + PmoveFixedPartial(pmove); + } + // chop the move up if it is too long, to prevent framerate // dependent behavior - while ( pmove->ps->commandTime != finalTime ) { + while ( pmove->ps->commandTime != final ) { int msec; - msec = finalTime - pmove->ps->commandTime; + msec = final - pmove->ps->commandTime; if ( pmove->pmove_fixed ) { - if ( msec > pmove->pmove_msec ) { + if ( msec > pmove->pmove_msec ) msec = pmove->pmove_msec; - } } else { - if ( msec > 66 ) { + if ( msec > 66 ) msec = 66; - } } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; PmoveSingle( pmove ); @@ -2987,5 +3028,7 @@ void Pmove (pmove_t *pmove) { } } + pmove->ps->commandTime = end; // Need "true" time //PM_CheckStuck(); } + diff --git a/code/game/bg_public.h b/code/game/bg_public.h index e45e69b..69dcb2d 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -228,6 +228,7 @@ typedef enum { #define PMRF_SKIP_TAUNT 0x00001 // Done a taunt this pmove #define PMRF_DONE_TAUNT 0x00002 // Done a taunt this pmove +#define PMRF_PMOVE_PARTIAL 0x00004 // PMOVE_FIXED frame that didn't update position #define MAXTOUCH 32 typedef struct { @@ -259,6 +260,7 @@ typedef struct { // for fixed msec Pmove int pmove_fixed; int pmove_msec; + int pmove_float; // callbacks to test the world // these will be different functions during game and cgame @@ -280,7 +282,6 @@ typedef struct { // if a full pmove isn't done on the client, you can just update the angles void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); void Pmove (pmove_t *pmove); - //=================================================================================== diff --git a/code/game/g_active.c b/code/game/g_active.c index ed5e244..0618b1d 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -542,96 +542,340 @@ ClientTimerActions Actions that happen once a second ================== */ -void ClientTimerActions( gentity_t *ent, int msec ) { +void ClientTimerActions( gentity_t *ent ) { gclient_t *client; - bg_q3f_playerclass_t *cls; + bg_q3f_playerclass_t *cls, *agentcls; char userinfo[MAX_INFO_STRING]; const char *dataptr; + gentity_t *other; int data; client = ent->client; - cls = BG_Q3F_GetClass( &client->ps ); - client->timeResidual += msec; - while ( client->timeResidual >= 1000 ) { - client->timeResidual -= 1000; + if ( client->wantsscore ) { + G_SendScore(ent); + client->wantsscore = qfalse; + } - // regenerate - if ( client->ps.powerups[PW_REGEN] ) { - if ( ent->health < cls->maxhealth) { - ent->health += 15; - if ( ent->health > cls->maxhealth * 1.1 ) { - ent->health = cls->maxhealth * 1.1; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } else if ( ent->health < cls->maxhealth * 2) { - ent->health += 5; - if ( ent->health > cls->maxhealth * 2 ) { - ent->health = cls->maxhealth * 2; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } + // clear the rewards if time + if ( level.time > client->rewardTime ) { + client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT ); + } + + // clear drop anim if time + if( !(client->ps.extFlags & EXTF_ANI_THROWING) && + !(client->ps.extFlags & EXTF_ANI_OPERATING)) { + client->torsoanimEndTime = 0; + } else if( client->torsoanimEndTime < level.time ) { + client->ps.extFlags &= ~EXTF_ANI_THROWING; + client->ps.extFlags &= ~EXTF_ANI_OPERATING; + } + + 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.eFlags & EF_Q3F_INVISIBLE ) { + client->ps.pm_type = PM_INVISIBLE; + } else { + client->ps.pm_type = PM_NORMAL; + } + + // JT: Pay attention to STAT_Q3F_FLAGS + + client->ps.gravity = g_gravity.value; + + // set speed + // Golliwog: Altered for class speeds, and max health + if( (cls = BG_Q3F_GetClass( &client->ps )) != NULL ) + client->ps.speed = cls->maxspeed; + else + client->ps.speed = g_speed.value; + // Golliwog. + + // Golliwog: Have agents slow down to apparent class speed + if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_AGENT && client->agentclass && client->sess.adjustAgentSpeed ) + { + agentcls = bg_q3f_classlist[client->agentclass]; + if( client->ps.speed > agentcls->maxspeed ) + client->ps.speed = agentcls->maxspeed; + } + // Golliwog. + + // Tranquilised + if( client->tranqTime ) { + if( level.time >= client->tranqTime ) { + trap_SendServerCommand( ent->s.number, "print \"You feel better now.\n\"" ); + client->tranqTime = 0; + client->tranqEnt = NULL; + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1<ps.speed /= 2; + client->ps.stats[STAT_Q3F_FLAGS] |= (1<ps.stats[STAT_Q3F_FLAGS] &= ~(1<flames > 0 && ent->health > 0) { + client->ps.extFlags |= EXTF_BURNING; + } else { + client->ps.extFlags &= ~EXTF_BURNING; + } + //Canabis, am i tranqed? + if (client->tranqTime >= level.time && ent->health > 0) { + client->ps.extFlags |= EXTF_TRANQED; + } else { + client->ps.extFlags &= ~EXTF_TRANQED; + } + //Ensiform, am i legshot? + if (client->legwounds && ent->health > 0) { + if( client->legwounds > 6 ) + client->legwounds = 6; // Cap the wounds to something sensible. + client->ps.extFlags |= EXTF_LEGWOUNDS; + } else { + client->ps.extFlags &= ~EXTF_LEGWOUNDS; + } + // Agent invisible + if( client->ps.eFlags & EF_Q3F_INVISIBLE ) + client->ps.speed = 0; // Can't move if invisible. + + // Laying a charge. + if( client->chargeTime ) { + if( client->chargeTime <= level.time ) { + client->ps.ammoclip[3] = client->chargeEntity->soundPos1; + trap_SendServerCommand( ent->s.number, "print \"Charge set.\n\"" ); + if( client->ps.ammo[AMMO_CHARGE] > 0 ) + client->ps.ammo[AMMO_CHARGE]--; + client->chargeTime = 0; // Allow movement again + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); + client->chargeEntity->s.legsAnim = 1; + #ifdef BUILD_BOTS + Bot_Event_DetpackBuilt(ent, client->chargeEntity); + #endif + ent->client->pers.stats.data[STATS_GREN + Q3F_GREN_CHARGE].shots++; } else { - // count down health when over max - if ( ent->health > cls->maxhealth ) { - ent->health--; + other = client->chargeEntity; + if( Distance( other->r.currentOrigin, ent->r.currentOrigin ) > 100 ) { + // They've moved too far, cancel the lay. + if( other->inuse && (other->s.eType == ET_Q3F_GRENADE && other->s.weapon == Q3F_GREN_CHARGE)) + G_FreeEntity( other ); + else G_Printf( "Attempted to free '%s' as charge.\n", other->classname ); + client->chargeEntity = NULL; + client->chargeTime = 0; + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); } + else client->ps.speed = 0; // Stop all movement. } + } + // Disarming a charge + if( client->chargeDisarmTime ) + { + if( !client->chargeDisarmEnt || !client->chargeDisarmEnt->inuse ) + { + // Lost our disarm ent? - // count down armor when over max - // FALCON: Modified to suit Q3F's per-class max armour - if ( client->ps.stats[STAT_ARMOR] > cls->maxarmour ) { - client->ps.stats[STAT_ARMOR]--; - // FALCON: END + client->chargeDisarmEnt = NULL; + client->chargeDisarmTime = 0; } + else if( !(client->ps.pm_flags & PMF_DUCKED) || + !trap_EntityContact( ent->r.absmin, ent->r.absmax, client->chargeDisarmEnt ) ) + { + // We've lost contact. + + if( client->chargeDisarmEnt->count >= 5 && !(rand() % 10) ) + { + // They've flumped the disarming process :) - // Golliwog: Agent invisible, consumes cells. This should be handled - // in Pmove, I think. - if( ent->s.eFlags & EF_Q3F_INVISIBLE ) + trap_SendServerCommand( ent->s.number, "print \"HE Charge Triggered! RUN!\n\"" ); + client->chargeDisarmEnt->count = 5; + client->chargeDisarmEnt->nextthink = level.time; + } + client->chargeDisarmEnt = NULL; + client->chargeDisarmTime = 0; + } + else if( client->chargeDisarmTime <= level.time ) { - if( --client->ps.ammo[AMMO_CELLS] <= 0 ) + // We've finished, clean this ent up. + + trap_SendServerCommand( ent->s.number, "print \"HE Charge Disarmed.\n\"" ); + if( client->chargeDisarmEnt->activator && client->chargeDisarmEnt->activator->inuse ) { - client->ps.ammo[AMMO_CELLS] = 0; - G_Q3F_StopAgentInvisible( ent ); - trap_SendServerCommand( ent->s.number, "print \"Invisibility failed for lack of cells!\n\"" ); + client->chargeDisarmEnt->activator->client->chargeEntity = NULL; + client->chargeDisarmEnt->activator->client->chargeTime = 0; + client->chargeDisarmEnt->activator->client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); } - else if( VectorLength( ent->client->ps.velocity ) > 20 ) + G_FreeEntity( client->chargeDisarmEnt ); + client->chargeDisarmEnt = NULL; + client->chargeDisarmTime = 0; + } else client->ps.speed = 0; // Stop all movement. + } + + if( client->buildTime ) { + if( client->buildTime <= level.time ) { + client->buildTime = 0; // Allow movement again + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); + } else { + if( (client->sentry && !client->sentry->s.legsAnim && + Distance( client->sentry->r.currentOrigin, ent->r.currentOrigin ) > 100) || + (client->supplystation && !client->supplystation->s.legsAnim && + Distance( client->supplystation->r.currentOrigin, ent->r.currentOrigin ) > 100) ) + { + // They've moved too far, cancel the build. + if( client->sentry ) + G_Q3F_SentryCancel( client->sentry ); + if( client->supplystation ) + G_Q3F_SupplyStationCancel( client->supplystation ); + client->buildTime = 0; + client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); + } else + client->ps.speed = 0; // Stop all movement. + } + } + if( client->repairEnt && client->repairEnt->inuse ) { + if( client->repairEnt->s.eType == ET_Q3F_SENTRY && !G_Q3F_CheckSentryUpgradeable( ent, client->repairEnt->s.number, qtrue, qtrue ) ) { + trap_SendServerCommand( ent->s.number, "menu cancel upgradeautosentry" ); + client->repairEnt = NULL; + } else if ( client->repairEnt->s.eType == ET_Q3F_SUPPLYSTATION && !G_Q3F_CheckSupplyStation( ent, client->repairEnt->s.number, qtrue ) ) { + trap_SendServerCommand( ent->s.number, "menu cancel upgradesupplystation" ); + client->repairEnt = NULL; + } + } + + if( client->speedscale ) // Goalitem speed scale + client->ps.speed *= client->speedscale; + + if( client->callTime && client->callTime <= level.time ) { + // Golliwog: Reset call flags (should use a PlayerStateToEntityState call) + client->ps.eFlags &= ~EF_Q3F_MASKME; + ent->s.eFlags &= ~EF_Q3F_MASKME; + client->callTime = 0; + } + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } +#ifdef SENTRY_MOVE + if ( client->ps.stats[STAT_Q3F_FLAGS] & (1 << FL_Q3F_MOVING)) { + client->ps.speed *= 0.5; + } +#endif + // Sniper shots to legs. + if(client->legwounds) { + client->ps.speed -= 0.1 * client->ps.speed * client->legwounds; + } + + if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_GRENADIER ) + G_Q3F_CheckPipesForPlayer(ent); + + G_Q3F_Check_Maladies(ent); + + // turn off any expired powerups + for (int i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ent->client->ps.powerups[ i ] && ent->client->ps.powerups[ i ] < level.time ) { + switch( i ) { - G_Q3F_StopAgentInvisible( ent ); - trap_SendServerCommand( ent->s.number, "print \"Invisibility failed because of movement!\n\"" ); + case PW_Q3F_CONCUSS: + trap_SendServerCommand( ent->s.number, "print \"You can see straight again.\n\"" ); + break; + case PW_Q3F_FLASH: + trap_SendServerCommand( ent->s.number, "print \"Your blindness has cleared.\n\"" ); + break; + case PW_Q3F_GAS: + trap_SendServerCommand( ent->s.number, "print \"You feel a little less confused now.\n\"" ); + break; } + ent->client->ps.powerups[ i ] = 0; } - // Golliwog. + } - // Golliwog: Do a minimum rate check. - if( g_minRate.integer && g_banRules.value > 1 ) - { - trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); - dataptr = Info_ValueForKey( userinfo, "rate" ); - data = 0; - if( !dataptr || (data = Q_atoi( dataptr )) < g_minRate.integer ) { - G_Q3F_AdminTempBan( ent, va( "Rate set to %d (sv_MinRate is %d)", data, g_minRate.integer ), Q3F_ADMIN_TEMPBAN_TIME ); + // canabis: Check for automatic reload + if( client->pers.autoReload == 3 && client->ps.weaponstate == WEAPON_READY ) + { + if( Q3F_GetClipValue( client->ps.weapon, &client->ps ) < BG_Q3F_GetWeapon( client->ps.weapon )->numammo ) + BG_Q3F_Request_Reload( &client->ps ); + } + + // PERIODIC (1hz) ACTIONS BELOW + if (client->periodicNext > level.time) + return; + client->periodicNext = level.time + 1000; + + cls = BG_Q3F_GetClass( &client->ps ); + // regenerate + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < cls->maxhealth) { + ent->health += 15; + if ( ent->health > cls->maxhealth * 1.1 ) { + ent->health = cls->maxhealth * 1.1; } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < cls->maxhealth * 2) { + ent->health += 5; + if ( ent->health > cls->maxhealth * 2 ) { + ent->health = cls->maxhealth * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } - // Golliwog. - // RR2DO2: Do a minimum snaps check. - if( g_minSnaps.integer && g_banRules.value > 1 ) + } else { + // count down health when over max + if ( ent->health > cls->maxhealth ) { + ent->health--; + } + } + + // count down armor when over max + // FALCON: Modified to suit Q3F's per-class max armour + if ( client->ps.stats[STAT_ARMOR] > cls->maxarmour ) { + client->ps.stats[STAT_ARMOR]--; + // FALCON: END + } + + // Golliwog: Agent invisible, consumes cells. This should be handled + // in Pmove, I think. + if( ent->s.eFlags & EF_Q3F_INVISIBLE ) + { + if( --client->ps.ammo[AMMO_CELLS] <= 0 ) { - trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); - dataptr = Info_ValueForKey( userinfo, "snaps" ); - data = 0; - if( !dataptr || (data = Q_atoi( dataptr )) < g_minSnaps.integer ) - G_Q3F_AdminTempBan( ent, va( "Snaps set to %d (sv_MinSnaps is %d)", data, g_minSnaps.integer ), Q3F_ADMIN_TEMPBAN_TIME ); + client->ps.ammo[AMMO_CELLS] = 0; + G_Q3F_StopAgentInvisible( ent ); + trap_SendServerCommand( ent->s.number, "print \"Invisibility failed for lack of cells!\n\"" ); } + else if( VectorLength( ent->client->ps.velocity ) > 20 ) + { + G_Q3F_StopAgentInvisible( ent ); + trap_SendServerCommand( ent->s.number, "print \"Invisibility failed because of movement!\n\"" ); + } + } + // Golliwog. - // Check for valid cg_adjustAgentSpeed + // Golliwog: Do a minimum rate check. + if( g_minRate.integer && g_banRules.value > 1 ) + { trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); - dataptr = Info_ValueForKey( userinfo, "cg_adjustAgentSpeed" ); + dataptr = Info_ValueForKey( userinfo, "rate" ); data = 0; - if( dataptr && ent->client ) - ent->client->sess.adjustAgentSpeed = Q_atoi( dataptr ); - // RR2DO2 + if( !dataptr || (data = Q_atoi( dataptr )) < g_minRate.integer ) { + G_Q3F_AdminTempBan( ent, va( "Rate set to %d (sv_MinRate is %d)", data, g_minRate.integer ), Q3F_ADMIN_TEMPBAN_TIME ); + } + } + // Golliwog. + // RR2DO2: Do a minimum snaps check. + if( g_minSnaps.integer && g_banRules.value > 1 ) + { + trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); + dataptr = Info_ValueForKey( userinfo, "snaps" ); + data = 0; + if( !dataptr || (data = Q_atoi( dataptr )) < g_minSnaps.integer ) + G_Q3F_AdminTempBan( ent, va( "Snaps set to %d (sv_MinSnaps is %d)", data, g_minSnaps.integer ), Q3F_ADMIN_TEMPBAN_TIME ); } + + // Check for valid cg_adjustAgentSpeed + trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); + dataptr = Info_ValueForKey( userinfo, "cg_adjustAgentSpeed" ); + data = 0; + if( dataptr && ent->client ) + ent->client->sess.adjustAgentSpeed = atoi( dataptr ); + // RR2DO2 } /* @@ -842,15 +1086,10 @@ once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { - gclient_t *client; + gclient_t *client = ent->client; pmove_t pm; int oldEventSequence; - int msec; - usercmd_t *ucmd; - bg_q3f_playerclass_t *cls, *agentcls; - gentity_t *other; - - client = ent->client; + usercmd_t *ucmd = &ent->client->pers.cmd; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { @@ -875,120 +1114,20 @@ void ClientThink_real( gentity_t *ent ) { // does a G_RunFrame() client->frameOffset = trap_Milliseconds() - level.frameStartTime; - // save the estimated ping in a queue for averaging later - // we use level.previousTime to account for 50ms lag correction - // besides, this will turn out numbers more like what players are used to - client->pers.pingsamples[client->pers.samplehead] = level.previousTime + client->frameOffset - ucmd->serverTime; - client->pers.samplehead++; - if ( client->pers.samplehead >= NUM_PING_SAMPLES ) { - client->pers.samplehead -= NUM_PING_SAMPLES; - } - - // initialize the real ping - if ( g_truePing.integer ) { - int i, sum = 0; - - // get an average of the samples we saved up - for ( i = 0; i < NUM_PING_SAMPLES; i++ ) { - sum += client->pers.pingsamples[i]; - } - - client->pers.realPing = sum / NUM_PING_SAMPLES; - } - else { - // if g_truePing is off, use the normal ping - client->pers.realPing = client->ps.ping; - } - -//unlagged - lag simulation #2 - // keep a queue of past commands - client->pers.cmdqueue[client->pers.cmdhead] = client->pers.cmd; - client->pers.cmdhead++; - if ( client->pers.cmdhead >= MAX_LATENT_CMDS ) { - client->pers.cmdhead -= MAX_LATENT_CMDS; - } - - // if the client wants latency in commands (client-to-server latency) - if ( client->pers.latentCmds ) { - // save the actual command time - int time = ucmd->serverTime; - - // find out which index in the queue we want - int cmdindex = client->pers.cmdhead - client->pers.latentCmds - 1; - while ( cmdindex < 0 ) { - cmdindex += MAX_LATENT_CMDS; - } - - // read in the old command - client->pers.cmd = client->pers.cmdqueue[cmdindex]; - - // adjust the real ping to reflect the new latency - client->pers.realPing += time - ucmd->serverTime; - } -//unlagged - lag simulation #2 - - -//unlagged - backward reconciliation #4 - // save the command time *before* pmove_fixed messes with the serverTime, - // and *after* lag simulation messes with it :) - // attackTime will be used for backward reconciliation later (time shift) - client->attackTime = ucmd->serverTime; - // For projectile attacks we want raw client positions - client->attackTimeProj = ucmd->serverTime + client->pers.timeNudge + 1000/142 + 1; -//unlagged - backward reconciliation #4 - -//unlagged - smooth clients #1 - // keep track of this for later - we'll use this to decide whether or not - // to send extrapolated positions for this client - client->lastUpdateFrame = level.framenum; -//unlagged - smooth clients #1 - - -//unlagged - lag simulation #1 - // if the client is adding latency to received snapshots (server-to-client latency) - if ( client->pers.latentSnaps ) { - // adjust the real ping - client->pers.realPing += client->pers.latentSnaps * (1000 / sv_fps.integer); - // adjust the attack time so backward reconciliation will work - client->attackTime -= client->pers.latentSnaps * (1000 / sv_fps.integer); - } -//unlagged - lag simulation #1 - - -//unlagged - true ping - // make sure the true ping is over 0 - with cl_timenudge it can be less - if ( client->pers.realPing < 0 ) { - client->pers.realPing = 0; - } -//unlagged - true ping - - 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 && client->sess.spectatorState != SPECTATOR_CHASE ) { - 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; - } +//unlagged - backward reconciliation #4 + // save the command time *before* pmove_fixed messes with the serverTime, + // and *after* lag simulation messes with it :) + // attackTime will be used for backward reconciliation later (time shift) + client->attackTime = ucmd->serverTime; + // For projectile attacks we want raw client positions + client->attackTimeProj = ucmd->serverTime + client->pers.timeNudge; +//unlagged - backward reconciliation #4 - if ( client->wantsscore ) { - G_SendScore(ent); - client->wantsscore = qfalse; - } +//unlagged - smooth clients #1 + // keep track of this for later - we'll use this to decide whether or not + // to send extrapolated positions for this client + client->lastUpdateFrame = level.framenum; +//unlagged - smooth clients #1 // // check for exiting intermission @@ -1009,221 +1148,8 @@ void ClientThink_real( gentity_t *ent ) { } // check for inactivity timer, but never drop the local client of a non-dedicated server - if ( !ClientInactivityTimer( ent ) ) { + if ( !ClientInactivityTimer( ent ) ) return; - } - - // clear the rewards if time - if ( level.time > client->rewardTime ) { - client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT ); - } - - // clear drop anim if time - if( !(client->ps.extFlags & EXTF_ANI_THROWING) && - !(client->ps.extFlags & EXTF_ANI_OPERATING)) { - client->torsoanimEndTime = 0; - } else if( client->torsoanimEndTime < level.time ) { - client->ps.extFlags &= ~EXTF_ANI_THROWING; - client->ps.extFlags &= ~EXTF_ANI_OPERATING; - } - - 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.eFlags & EF_Q3F_INVISIBLE ) { - client->ps.pm_type = PM_INVISIBLE; - } else { - client->ps.pm_type = PM_NORMAL; - } - - // JT: Pay attention to STAT_Q3F_FLAGS - - client->ps.gravity = g_gravity.value; - - // set speed - // Golliwog: Altered for class speeds, and max health - if( (cls = BG_Q3F_GetClass( &client->ps )) != NULL ) - client->ps.speed = cls->maxspeed; - else - client->ps.speed = g_speed.value; - // Golliwog. - - // Golliwog: Have agents slow down to apparent class speed - if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_AGENT && client->agentclass && client->sess.adjustAgentSpeed ) - { - agentcls = bg_q3f_classlist[client->agentclass]; - if( client->ps.speed > agentcls->maxspeed ) - client->ps.speed = agentcls->maxspeed; - } - // Golliwog. - - // Tranquilised - if( client->tranqTime ) { - if( level.time >= client->tranqTime ) { - trap_SendServerCommand( ent->s.number, "print \"You feel better now.\n\"" ); - client->tranqTime = 0; - client->tranqEnt = NULL; - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1<ps.speed /= 2; - client->ps.stats[STAT_Q3F_FLAGS] |= (1<ps.stats[STAT_Q3F_FLAGS] &= ~(1<flames > 0 && ent->health > 0) { - client->ps.extFlags |= EXTF_BURNING; - } else { - client->ps.extFlags &= ~EXTF_BURNING; - } - //Canabis, am i tranqed? - if (client->tranqTime >= level.time && ent->health > 0) { - client->ps.extFlags |= EXTF_TRANQED; - } else { - client->ps.extFlags &= ~EXTF_TRANQED; - } - //Ensiform, am i legshot? - if (client->legwounds && ent->health > 0) { - if( client->legwounds > 6 ) - client->legwounds = 6; // Cap the wounds to something sensible. - client->ps.extFlags |= EXTF_LEGWOUNDS; - } else { - client->ps.extFlags &= ~EXTF_LEGWOUNDS; - } - // Agent invisible - if( client->ps.eFlags & EF_Q3F_INVISIBLE ) - client->ps.speed = 0; // Can't move if invisible. - - // Laying a charge. - if( client->chargeTime ) { - if( client->chargeTime <= level.time ) { - client->ps.ammoclip[3] = client->chargeEntity->soundPos1; - trap_SendServerCommand( ent->s.number, "print \"Charge set.\n\"" ); - if( client->ps.ammo[AMMO_CHARGE] > 0 ) - client->ps.ammo[AMMO_CHARGE]--; - client->chargeTime = 0; // Allow movement again - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); - client->chargeEntity->s.legsAnim = 1; - #ifdef BUILD_BOTS - Bot_Event_DetpackBuilt(ent, client->chargeEntity); - #endif - ent->client->pers.stats.data[STATS_GREN + Q3F_GREN_CHARGE].shots++; - } else { - other = client->chargeEntity; - if( Distance( other->r.currentOrigin, ent->r.currentOrigin ) > 100 ) { - // They've moved too far, cancel the lay. - if( other->inuse && (other->s.eType == ET_Q3F_GRENADE && other->s.weapon == Q3F_GREN_CHARGE)) - G_FreeEntity( other ); - else G_Printf( "Attempted to free '%s' as charge.\n", other->classname ); - client->chargeEntity = NULL; - client->chargeTime = 0; - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); - } - else client->ps.speed = 0; // Stop all movement. - } - } - // Disarming a charge - if( client->chargeDisarmTime ) - { - if( !client->chargeDisarmEnt || !client->chargeDisarmEnt->inuse ) - { - // Lost our disarm ent? - - client->chargeDisarmEnt = NULL; - client->chargeDisarmTime = 0; - } - else if( !(client->ps.pm_flags & PMF_DUCKED) || - !trap_EntityContact( ent->r.absmin, ent->r.absmax, client->chargeDisarmEnt ) ) - { - // We've lost contact. - - if( client->chargeDisarmEnt->count >= 5 && !(rand() % 10) ) - { - // They've flumped the disarming process :) - - trap_SendServerCommand( ent->s.number, "print \"HE Charge Triggered! RUN!\n\"" ); - client->chargeDisarmEnt->count = 5; - client->chargeDisarmEnt->nextthink = level.time; - } - client->chargeDisarmEnt = NULL; - client->chargeDisarmTime = 0; - } - else if( client->chargeDisarmTime <= level.time ) - { - // We've finished, clean this ent up. - - trap_SendServerCommand( ent->s.number, "print \"HE Charge Disarmed.\n\"" ); - if( client->chargeDisarmEnt->activator && client->chargeDisarmEnt->activator->inuse ) - { - client->chargeDisarmEnt->activator->client->chargeEntity = NULL; - client->chargeDisarmEnt->activator->client->chargeTime = 0; - client->chargeDisarmEnt->activator->client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_LAYCHARGE); - } - G_FreeEntity( client->chargeDisarmEnt ); - client->chargeDisarmEnt = NULL; - client->chargeDisarmTime = 0; - } else client->ps.speed = 0; // Stop all movement. - } - - if( client->buildTime ) { - if( client->buildTime <= level.time ) { - client->buildTime = 0; // Allow movement again - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); - } else { - if( (client->sentry && !client->sentry->s.legsAnim && - Distance( client->sentry->r.currentOrigin, ent->r.currentOrigin ) > 100) || - (client->supplystation && !client->supplystation->s.legsAnim && - Distance( client->supplystation->r.currentOrigin, ent->r.currentOrigin ) > 100) ) - { - // They've moved too far, cancel the build. - if( client->sentry ) - G_Q3F_SentryCancel( client->sentry ); - if( client->supplystation ) - G_Q3F_SupplyStationCancel( client->supplystation ); - client->buildTime = 0; - client->ps.stats[STAT_Q3F_FLAGS] &= ~(1 << FL_Q3F_BUILDING); - } else - client->ps.speed = 0; // Stop all movement. - } - } - if( client->repairEnt && client->repairEnt->inuse ) { - if( client->repairEnt->s.eType == ET_Q3F_SENTRY && !G_Q3F_CheckSentryUpgradeable( ent, client->repairEnt->s.number, qtrue, qtrue ) ) { - trap_SendServerCommand( ent->s.number, "menu cancel upgradeautosentry" ); - client->repairEnt = NULL; - } else if ( client->repairEnt->s.eType == ET_Q3F_SUPPLYSTATION && !G_Q3F_CheckSupplyStation( ent, client->repairEnt->s.number, qtrue ) ) { - trap_SendServerCommand( ent->s.number, "menu cancel upgradesupplystation" ); - client->repairEnt = NULL; - } - } - - if( client->speedscale ) // Goalitem speed scale - client->ps.speed *= client->speedscale; - - if( client->callTime && client->callTime <= level.time ) { - // Golliwog: Reset call flags (should use a PlayerStateToEntityState call) - client->ps.eFlags &= ~EF_Q3F_MASKME; - ent->s.eFlags &= ~EF_Q3F_MASKME; - client->callTime = 0; - } - if ( client->ps.powerups[PW_HASTE] ) { - client->ps.speed *= 1.3; - } -#ifdef SENTRY_MOVE - if ( client->ps.stats[STAT_Q3F_FLAGS] & (1 << FL_Q3F_MOVING)) { - client->ps.speed *= 0.5; - } -#endif - // Sniper shots to legs. - if(client->legwounds) { - client->ps.speed -= 0.1 * client->ps.speed * client->legwounds; - } - - if( client->ps.persistant[PERS_CURRCLASS] == Q3F_CLASS_GRENADIER ) - G_Q3F_CheckPipesForPlayer(ent); - - G_Q3F_Check_Maladies(ent); // set up for pmove oldEventSequence = client->ps.eventSequence; @@ -1249,8 +1175,9 @@ void ClientThink_real( gentity_t *ent ) { 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_fixed = pmove_fixed.integer; pm.pmove_msec = pmove_msec.integer; + pm.pmove_float = pmove_float.integer; VectorCopy( client->ps.origin, client->oldOrigin ); @@ -1316,11 +1243,13 @@ void ClientThink_real( gentity_t *ent ) { // 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); + if ((pm.retflags & PMRF_PMOVE_PARTIAL) == 0) { + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + } // execute client events ClientEvents( ent, oldEventSequence ); @@ -1339,9 +1268,8 @@ void ClientThink_real( gentity_t *ent ) { ClientImpacts( ent, &pm ); // save results of triggers and client events - if (ent->client->ps.eventSequence != oldEventSequence) { + if (ent->client->ps.eventSequence != oldEventSequence) ent->eventTime = level.time; - } // swap and latch button actions client->oldbuttons = client->buttons; @@ -1377,13 +1305,6 @@ void ClientThink_real( gentity_t *ent ) { if( !(ucmd->buttons & BUTTON_ATTACK) ) client->lastflame = 0; - // canabis: Check for automatic reload - if( client->pers.autoReload == 3 && client->ps.weaponstate == WEAPON_READY ) - { - if( Q3F_GetClipValue( client->ps.weapon, &client->ps ) < BG_Q3F_GetWeapon( client->ps.weapon )->numammo ) - BG_Q3F_Request_Reload( &client->ps ); - } - // RR2DO2: if we carry an item with "revealagent" make sure we stop our disguise as agent, just in case if( client->agentdata && ( (client->agentdata->s.modelindex2 & Q3F_AGENT_DISGUISEMASK) == 1 || (client->agentdata->s.modelindex2 & Q3F_AGENT_INVISMASK) == 4 ) ) { gentity_t *goalitem; @@ -1470,9 +1391,6 @@ void ClientThink_real( gentity_t *ent ) { break; } } - - // perform once-a-second actions - ClientTimerActions( ent, msec ); } /* @@ -1580,9 +1498,7 @@ while a slow client may have multiple ClientEndFrame between ClientThink. ============== */ void ClientEndFrame( gentity_t *ent ) { - int i; - //clientPersistant_t *pers; - int frames; + int frames; if ( !ent->client ) return; @@ -1635,25 +1551,7 @@ void ClientEndFrame( gentity_t *ent ) { } //pers = &ent->client->pers; - - // turn off any expired powerups - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ent->client->ps.powerups[ i ] && ent->client->ps.powerups[ i ] < level.time ) { - switch( i ) - { - case PW_Q3F_CONCUSS: - trap_SendServerCommand( ent->s.number, "print \"You can see straight again.\n\"" ); - break; - case PW_Q3F_FLASH: - trap_SendServerCommand( ent->s.number, "print \"Your blindness has cleared.\n\"" ); - break; - case PW_Q3F_GAS: - trap_SendServerCommand( ent->s.number, "print \"You feel a little less confused now.\n\"" ); - break; - } - ent->client->ps.powerups[ i ] = 0; - } - } + ClientTimerActions(ent); // save network bandwidth #if 0 diff --git a/code/game/g_client.c b/code/game/g_client.c index 557f276..9bf9837 100644 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -1061,14 +1061,6 @@ qboolean ClientUserinfoChanged( int clientNum, const char *reason ) { client->pers.debugDelag = qtrue; } - // see if the player is simulating incoming latency - s = Info_ValueForKey( userinfo, "cg_latentSnaps" ); - client->pers.latentSnaps = Q_atoi( s ); - - // see if the player is simulating outgoing latency - s = Info_ValueForKey( userinfo, "cg_latentCmds" ); - client->pers.latentCmds = Q_atoi( s ); - // see if the player is simulating outgoing packet loss s = Info_ValueForKey( userinfo, "cg_plOut" ); client->pers.plOut = Q_atoi( s ); diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c index 2ae3715..8a88f61 100644 --- a/code/game/g_cmds.c +++ b/code/game/g_cmds.c @@ -104,9 +104,7 @@ void G_SendScore( gentity_t *ent ) { } else if( cl->pers.initializing ) { ping = -2; } else { -//unlagged - true ping - ping = cl->pers.realPing < 999 ? cl->pers.realPing : 999; -// ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; } flags = 0; @@ -3677,7 +3675,7 @@ static void G_Q3F_PlayerStatus( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, va("print \"%3d %4d %6s %5d %s\n\"", player->s.number, - player->client->pers.realPing,//ps.ping + player->client->ps.ping, (player->client->sess.sessionTeam ? g_q3f_teamlist[player->client->sess.sessionTeam].name : "spec"), player->client->ps.persistant[PERS_SCORE], player->client->pers.netname ) ); diff --git a/code/game/g_cvar.h b/code/game/g_cvar.h index b04dab8..e4a9948 100644 --- a/code/game/g_cvar.h +++ b/code/game/g_cvar.h @@ -126,6 +126,7 @@ G_CVAR( g_listEntity, "g_listEntity", "0", 0, qfalse ) G_CVAR( pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, qfalse ) G_CVAR( pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, qfalse ) +G_CVAR( pmove_float, "pmove_float", "1", CVAR_SYSTEMINFO, qfalse ) G_CVAR( g_suicideDelay, "g_suicideDelay", "7", 0, qfalse ) // Golliwog: Delay after suiciding G_CVAR( g_teamChatSounds, "g_teamChatSounds", "4", 0, qfalse ) // Golliwog: Allow sounds/sounddict in team chat diff --git a/code/game/g_local.h b/code/game/g_local.h index 8befad8..22d43ca 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -350,7 +350,6 @@ typedef struct { qboolean localClient; // true if "ip" info key is "localhost" qboolean initialSpawn; // the first spawn should be at a cool location qboolean predictItemPickup; // based on cg_predictItems userinfo - qboolean pmoveFixed; // char netname[MAX_NETNAME]; int enterTime; // level.time the client entered the game int connectTime; @@ -368,15 +367,7 @@ typedef struct { int timeNudge; int cmdTimeNudge; //unlagged - client options - int latentSnaps; - int latentCmds; int plOut; - usercmd_t cmdqueue[MAX_LATENT_CMDS]; - int cmdhead; -//unlagged - true ping - int realPing; - int pingsamples[NUM_PING_SAMPLES]; - int samplehead; //unlagged - true ping // Canabis, stats block @@ -446,9 +437,9 @@ struct gclient_s { int switchTeamTime; // time the player switched teams - // timeResidual is used to handle events that happen every second + // periodicNext is used to handle events that happen every second // like health / armor countdowns and regeneration - int timeResidual; + int periodicNext; char *areabits; diff --git a/code/game/g_main.c b/code/game/g_main.c index 85c9eaa..9a4955d 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -414,6 +414,11 @@ static void G_UpdateCvars( void ) { } } + if (pmove_msec.integer < 8) + trap_Cvar_Set("pmove_msec", "8"); + else if (pmove_msec.integer > 33) + trap_Cvar_Set("pmove_msec", "33"); + // Golliwog: Check the damage type and gravity if( g_friendlyFire.modificationCount != level.friendlyFireCount ) { @@ -865,7 +870,6 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) { G_LuaHook_InitGame( levelTime, randomSeed, restart ); #endif // BUILD_LUA - #ifdef PERFLOG BG_Q3F_PerformanceMonitor_LogFunctionStop(); #endif @@ -1526,7 +1530,7 @@ void LogExit( const char *string ) { continue; } - ping = cl->pers.realPing < 999 ? cl->pers.realPing : 999;//ps.ping + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; G_LogPrintf( "score: %i ping: %i client: %i %s^7\n", cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i], cl->pers.netname ); } @@ -1981,7 +1985,7 @@ void G_Q3F_CalculateTeamPings(void) if( !scan->inuse || !scan->client ) continue; counts[scan->client->sess.sessionTeam]++; - pings[scan->client->sess.sessionTeam] += scan->client->pers.realPing;// ps.ping; + pings[scan->client->sess.sessionTeam] += scan->client->ps.ping; } for( index = 0; index < Q3F_TEAM_NUM_TEAMS - 1; index++ ) @@ -2227,14 +2231,12 @@ void G_RunEntities(void) { continue; switch (ent->s.eType) { - case ET_MISSILE: - { - if (ent->antilag_time) { - int snap_ms = 1000 / sv_fps.integer; - int prestep = ent->s.pos.trTime - projectile_now; - int steps = (projectile_now - ent->antilag_time) / snap_ms, rs = 0; - //int ot = ent->s.pos.trTime - prestep; - ent->s.pos.trTime = ent->antilag_time + prestep; + case ET_MISSILE: { + if (ent->antilag_time) { + int snap_ms = 1000 / sv_fps.integer; + int prestep = ent->s.pos.trTime - projectile_now; + int steps = (projectile_now - ent->antilag_time) / snap_ms, rs = 0; + ent->s.pos.trTime = ent->antilag_time + prestep; while (steps > 0) { int now = projectile_now - steps * snap_ms; diff --git a/code/game/g_missile.c b/code/game/g_missile.c index a70a88f..61190ca 100644 --- a/code/game/g_missile.c +++ b/code/game/g_missile.c @@ -788,12 +788,12 @@ gentity_t *fire_nail (gentity_t *self, vec3_t start, vec3_t dir, int damage, int bolt->r.maxs[2] = 1; bolt->s.pos.trTime = level.time; - //VectorMA( muzzle, -50, dir, bolt->s.pos.trBase ); // Draw a frame previous. - VectorMA( start, -15, dir, bolt->s.pos.trBase ); // Draw a frame previous. - bolt->s.pos.trBase[2] -= 6; -// VectorScale( dir, 1100, bolt->s.pos.trDelta ); + VectorMA( start, -15, dir, start); // Draw a frame previous. + start[2] -= 6; VectorScale( dir, mod == MOD_SUPERNAILGUN ? PROJ_SPEED_SNG : PROJ_SPEED_NG, bolt->s.pos.trDelta ); // djbob: 2.2 value 1.6 1250 for both nailguns SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + SnapVector(start); + VectorCopy (start, bolt->s.pos.trBase); VectorCopy (start, bolt->r.currentOrigin); if (self->client && self->client->pers.unlagged) diff --git a/code/game/g_q3f_admin.c b/code/game/g_q3f_admin.c index 8b2b0c4..cf44269 100644 --- a/code/game/g_q3f_admin.c +++ b/code/game/g_q3f_admin.c @@ -411,7 +411,7 @@ static void G_Q3F_AdminStatus( gentity_t *admin ) { G_Q3F_AdminPrint( admin, "%3d %21s %4d %6s %5d %s\n", player->s.number, player->client->pers.ipStr, - player->client->pers.realPing,//ps.ping + player->client->ps.ping, (player->client->sess.sessionTeam ? g_q3f_teamlist[player->client->sess.sessionTeam].name : "spec"), player->client->ps.persistant[PERS_SCORE], player->client->pers.netname ); diff --git a/code/game/g_q3f_controllable.c b/code/game/g_q3f_controllable.c index a95d301..89fe7eb 100644 --- a/code/game/g_q3f_controllable.c +++ b/code/game/g_q3f_controllable.c @@ -122,7 +122,7 @@ qboolean G_Q3F_Control( gentity_t *ent ) 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_fixed = pmove_fixed.integer; pm.pmove_msec = pmove_msec.integer; pm.cs = &controllable->s;