diff --git a/docker-scripts/dockerfiles/rtcwpro-compile-18 b/docker-scripts/dockerfiles/rtcwpro-compile-18
index fde6b994..ed37e3a5 100644
--- a/docker-scripts/dockerfiles/rtcwpro-compile-18
+++ b/docker-scripts/dockerfiles/rtcwpro-compile-18
@@ -13,13 +13,18 @@ RUN dpkg --add-architecture i386 && \
apt-get install -y wget libc6:i386 zip \
unzip git gdb-multiarch gcc cmake perl curl gcc-multilib g++-multilib \
autoconf libtool nasm mingw-w64 mingw-w64-tools g++ \
- libgl-dev libsdl2-dev:i386 clang-tools-10 lld-10
+ libgl-dev libsdl2-dev:i386 lsb-release software-properties-common gnupg
-RUN ln -s /usr/bin/clang-10 /usr/bin/clang && \
- ln -s /usr/bin/clang-cl-10 /usr/bin/clang-cl && \
- ln -s /usr/bin/llvm-lib-10 /usr/bin/llvm-lib && \
- ln -s /usr/bin/lld-link-10 /usr/bin/lld-link && \
- ln -s /usr/bin/llvm-rc-10 /usr/bin/llvm-rc
+RUN wget -qO- https://apt.llvm.org/llvm.sh | bash -s -- 15
+
+RUN apt-get update && \
+ apt-get install -y clang-tools-15 lld-15
+
+RUN ln -s /usr/bin/clang-15 /usr/bin/clang && \
+ ln -s /usr/bin/clang-cl-15 /usr/bin/clang-cl && \
+ ln -s /usr/bin/llvm-lib-15 /usr/bin/llvm-lib && \
+ ln -s /usr/bin/lld-link-15 /usr/bin/lld-link && \
+ ln -s /usr/bin/llvm-rc-15 /usr/bin/llvm-rc
RUN mkdir /output
RUN chown compile:compile /output
diff --git a/fetch-dependencies.sh b/fetch-dependencies.sh
index 57ca3120..77b002fa 100755
--- a/fetch-dependencies.sh
+++ b/fetch-dependencies.sh
@@ -95,7 +95,8 @@ cd $DEPS_ROOT
LIBUNWIND_DIR=`pwd`/libunwind
if [ ! -d "$LIBUNWIND_DIR" ]; then
-VER=$(curl --silent -qI https://github.com/libunwind/libunwind/releases/latest | awk -F '/' '/^location/ {print substr($NF, 1, length($NF)-1)}');
+#VER=$(curl --silent -qI https://github.com/libunwind/libunwind/releases/latest | awk -F '/' '/^location/ {print substr($NF, 1, length($NF)-1)}');
+VER="v1.8.1"
wget https://api.github.com/repos/libunwind/libunwind/tarball/$VER
tar xvfz $VER
rm $VER
diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c
index 59da9d21..181f2df6 100644
--- a/src/cgame/cg_ents.c
+++ b/src/cgame/cg_ents.c
@@ -1922,6 +1922,11 @@ CG_CalcEntityLerpPositions
===============
*/
static void CG_CalcEntityLerpPositions( centity_t *cent ) {
+//unlagged - projectile nudge
+ // this will be set to how far forward projectiles will be extrapolated
+ int timeshift = 0;
+//unlagged - projectile nudge
+
if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) {
CG_InterpolateEntityPosition( cent );
return;
@@ -1939,6 +1944,7 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) {
}
// -NERVE - SMF
+//unlagged - timenudge extrapolation
// interpolating failed (probably no nextSnap), so extrapolate
// this can also happen if the teleport bit is flipped, but that
// won't be noticeable
@@ -1948,10 +1954,49 @@ static void CG_CalcEntityLerpPositions( centity_t *cent ) {
cent->currentState.pos.trTime = cg.snap->serverTime;
cent->currentState.pos.trDuration = 1000 / trap_Cvar_VariableIntegerValue("sv_fps");
}
+//unlagged - timenudge extrapolation
+
+//unlagged - projectile nudge
+ // if it's a missile but not a grappling hook
+ if ( cent->currentState.eType == ET_MISSILE /*&& cent->currentState.weapon != WP_GRAPPLING_HOOK*/ ) {
+ // if it's one of ours
+ if ( cent->currentState.otherEntityNum == cg.clientNum ) {
+ // extrapolate one server frame's worth - this will correct for tiny
+ // visual inconsistencies introduced by backward-reconciling all players
+ // one server frame before running projectiles
+ timeshift = 1000 / sv_fps.integer;
+ }
+ // if it's not, and it's not a grenade launcher
+ else if ( cent->currentState.weapon != WP_GRENADE_LAUNCHER ) {
+ // extrapolate based on cg_projectileNudge
+ //timeshift = cg_projectileNudge.integer + 1000 / sv_fps.integer; // removed cg_projectileNudge
+ timeshift = 1000 / sv_fps.integer;
+ }
+ }
// just use the current frame and evaluate as best we can
- BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
- BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
+// BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
+// BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
+ BG_EvaluateTrajectory( ¢->currentState.pos, cg.time + timeshift, cent->lerpOrigin );
+ BG_EvaluateTrajectory( ¢->currentState.apos, cg.time + timeshift, cent->lerpAngles );
+
+ // if there's a time shift
+ if ( timeshift != 0 ) {
+ trace_t tr;
+ vec3_t lastOrigin;
+
+ BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, lastOrigin );
+
+ CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, cent->currentState.number, MASK_SHOT );
+
+ // don't let the projectile go through the floor
+ if ( tr.fraction < 1.0f ) {
+ cent->lerpOrigin[0] = lastOrigin[0] + tr.fraction * ( cent->lerpOrigin[0] - lastOrigin[0] );
+ cent->lerpOrigin[1] = lastOrigin[1] + tr.fraction * ( cent->lerpOrigin[1] - lastOrigin[1] );
+ cent->lerpOrigin[2] = lastOrigin[2] + tr.fraction * ( cent->lerpOrigin[2] - lastOrigin[2] );
+ }
+ }
+//unlagged - projectile nudge
// adjust for riding a mover if it wasn't rolled into the predicted
// player state
@@ -2188,6 +2233,7 @@ void CG_AddPacketEntities( void ) {
centity_t *cent;
playerState_t *ps;
//int clcount;
+ //int a, b, c = 0; //DEBUG for early transitioning
// set cg.frameInterpolation
if ( cg.nextSnap ) {
@@ -2230,13 +2276,31 @@ void CG_AddPacketEntities( void ) {
// lerp the non-predicted value for lightning gun origins
CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
- // add each entity sent over by the server
+ //unlagged - early transitioning
+ if ( cg.nextSnap ) {
+ // pre-add some entities sent over by the server
+ // we have data for them and they don't need to interpolate
+ // NON TAG-CONNECTED ENTITIES
+ for ( num = 0 ; num < cg.nextSnap->numEntities ; num++ ) {
+ cent = &cg_entities[ cg.nextSnap->entities[ num ].number ];
+ if ( !( cent->currentState.eFlags & EF_TAGCONNECT ) && ( cent->nextState.eType == ET_MISSILE || cent->nextState.eType == ET_GENERAL ) ) {
+ // transition it immediately and add it
+ CG_TransitionEntity( cent );
+ cent->interpolate = qtrue;
+ CG_AddCEntity( cent );
+ //a++; //DEBUG for early transitioning
+ }
+ }
+ //CG_Printf("\n(nextSnap) NON-TAGGED: %i ADDED\n", a); //DEBUG for early transitioning
+ }
+ // add each entity sent over by the server
// NON TAG-CONNECTED ENTITIES
for ( num = 0 ; num < cg.snap->numEntities ; num++ ) {
cent = &cg_entities[ cg.snap->entities[ num ].number ];
- if ( !( cent->currentState.eFlags & EF_TAGCONNECT ) ) {
+ if ( !( cent->currentState.eFlags & EF_TAGCONNECT ) && ( !cg.nextSnap || cent->nextState.eType != ET_MISSILE && cent->nextState.eType != ET_GENERAL ) ) {
CG_AddCEntity( cent );
+ //b++; //DEBUG for early transitioning
}
}
@@ -2245,8 +2309,11 @@ void CG_AddPacketEntities( void ) {
cent = &cg_entities[ cg.snap->entities[ num ].number ];
if ( cent->currentState.eFlags & EF_TAGCONNECT ) {
CG_AddEntityToTag( cent );
+ //c++; //DEBUG for early transitioning
}
}
+ //CG_Printf("(snap) NON-TAGGED: %i / TAGGED: %i ADDED\n", b, c); //DEBUG for early transitioning
+ //unlagged - early transitioning
// Ridah, add the flamethrower sounds
CG_UpdateFlamethrowerSounds();
diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c
index eedda1fa..3bbbd1a4 100644
--- a/src/cgame/cg_event.c
+++ b/src/cgame/cg_event.c
@@ -2272,14 +2272,38 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) {
case EV_BULLET_HIT_WALL:
DEBUGNAME( "EV_BULLET_HIT_WALL" );
- ByteToDir( es->eventParm, dir );
- CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qfalse, es->otherEntityNum2, 0 );
-// CG_MachineGunEjectBrass(cent); // JPW NERVE
+ //unlagged - attack prediction #2
+ // if the client is us, unlagged is on server-side, and we've got it client-side
+ if ( CG_PredictedWeapon(cg.predictedPlayerState.weapon) && es->clientNum == cg.predictedPlayerState.clientNum &&
+ cgs.delagHitscan && (cg_delag.integer & 1 || cg_delag.integer & 2) ) {
+ // do nothing, because it was already predicted
+ //Com_Printf("Ignoring bullet event\n");
+ }
+ else {
+ // do the bullet, because it wasn't predicted
+ ByteToDir( es->eventParm, dir );
+ CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qfalse, es->otherEntityNum2, 0 );
+// CG_MachineGunEjectBrass(cent); // JPW NERVE
+ //Com_Printf("Non-predicted bullet\n");
+ }
+ //unlagged - attack prediction #2
break;
case EV_BULLET_HIT_FLESH:
DEBUGNAME( "EV_BULLET_HIT_FLESH" );
- CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qfalse, es->otherEntityNum2, 0 );
+ //unlagged - attack prediction #2
+ // if the client is us, unlagged is on server-side, and we've got it client-side
+ if ( CG_PredictedWeapon(cg.predictedPlayerState.weapon) && es->clientNum == cg.predictedPlayerState.clientNum &&
+ cgs.delagHitscan && (cg_delag.integer & 1 || cg_delag.integer & 2) ) {
+ // do nothing, because it was already predicted
+ //Com_Printf("Ignoring bullet event\n");
+ }
+ else {
+ // do the bullet, because it wasn't predicted
+ CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qfalse, es->otherEntityNum2, 0 );
+ //Com_Printf("Non-predicted bullet\n");
+ }
+ //unlagged - attack prediction #2
break;
case EV_WOLFKICK_HIT_WALL:
diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h
index 0a5e1611..329e9c79 100644
--- a/src/cgame/cg_local.h
+++ b/src/cgame/cg_local.h
@@ -882,6 +882,10 @@ typedef struct {
#define MAX_PREDICTED_EVENTS 16
+//unlagged - optimized prediction
+#define NUM_SAVED_STATES (CMD_BACKUP + 2)
+//unlagged - optimized prediction
+
#define MAX_SPAWN_VARS 64
#define MAX_SPAWN_VARS_CHARS 2048
// OSPx - Draw HUDnames
@@ -1260,6 +1264,13 @@ typedef struct {
// RtcwPro shoutcast overlay
int lastKeyCatcher;
qbool ndpDemoEnabled;
+
+//unlagged - optimized prediction
+ int lastPredictedCommand;
+ int lastServerTime;
+ playerState_t savedPmoveStates[NUM_SAVED_STATES];
+ int stateHead, stateTail;
+//unlagged - optimized prediction
} cg_t;
@@ -1960,6 +1971,11 @@ typedef struct {
qboolean fKeyPressed[256]; // Key status to get around console issues
int timescaleUpdate; // Timescale display for demo playback
demoTimeline_t demoTimeline;
+
+//unlagged - client options
+ // this will be set to the server's g_delagHitscan
+ int delagHitscan;
+//unlagged - client options
} cgs_t;
//==============================================================================
@@ -2071,7 +2087,10 @@ extern vmCvar_t cg_noVoiceText; // NERVE - SMF
extern vmCvar_t cg_enableBreath;
extern vmCvar_t cg_autoactivate;
extern vmCvar_t cg_emptyswitch;
+//unlagged - smooth clients #2
+// this is done server-side now
//extern vmCvar_t cg_smoothClients;
+//unlagged - smooth clients #2
extern vmCvar_t pmove_fixed;
extern vmCvar_t pmove_msec;
@@ -2278,6 +2297,18 @@ extern vmCvar_t cg_teamObituaryColorEnemyTK; // enemy team TK color
extern vmCvar_t cg_drawCI;
+//unlagged - client options
+extern vmCvar_t cg_delag;
+extern vmCvar_t sv_fps;
+extern vmCvar_t cg_optimizePrediction;
+//unlagged - client options
+
+
+//unlagged - cg_unlagged.c
+void CG_PredictWeaponEffects( centity_t *cent );
+qboolean CG_PredictedWeapon( int weapon );
+//unlagged - cg_unlagged.c
+
//
// cg_main.c
//
@@ -2715,6 +2746,10 @@ void CG_RumbleEfx( float pitch, float yaw );
void CG_ProcessSnapshots( void );
void CG_NDP_ProcessSnapshots( void );
+//unlagged - early transitioning
+void CG_TransitionEntity( centity_t *cent );
+//unlagged - early transitioning
+
//
// cg_spawn.c
//
diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c
index 77ad85fd..95a82386 100644
--- a/src/cgame/cg_main.c
+++ b/src/cgame/cg_main.c
@@ -225,7 +225,10 @@ vmCvar_t cg_enableBreath;
vmCvar_t cg_autoactivate;
vmCvar_t cg_blinktime; //----(SA) added
-//vmCvar_t cg_smoothClients;
+//unlagged - smooth clients #2
+// this is done server-side now
+//vmCvar_t cg_smoothClients;
+//unlagged - smooth clients #2
vmCvar_t pmove_fixed;
vmCvar_t pmove_msec;
@@ -470,6 +473,12 @@ vmCvar_t cg_customCrosshairYGap;
vmCvar_t cg_drawCI;
+//unlagged - client options
+vmCvar_t cg_delag;
+vmCvar_t sv_fps;
+vmCvar_t cg_optimizePrediction;
+//unlagged - client options
+
typedef struct {
vmCvar_t *vmCvar;
char *cvarName;
@@ -589,7 +598,10 @@ cvarTable_t cvarTable[] = {
{ &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0},
{ &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0},
{ &cg_timescale, "timescale", "1", 0},
+//unlagged - smooth clients #2
+// this is done server-side now
// { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE},
+//unlagged - smooth clients #2
{ &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT},
{ &pmove_fixed, "pmove_fixed", "0", 0},
@@ -837,7 +849,14 @@ cvarTable_t cvarTable[] = {
{ &cg_customCrosshairXGap, "cg_customCrosshairXGap", "0", CVAR_ARCHIVE },
{ &cg_customCrosshairYGap, "cg_customCrosshairYGap", "0", CVAR_ARCHIVE },
- { &cg_drawCI, "cg_drawCI", "1", CVAR_ARCHIVE }
+ { &cg_drawCI, "cg_drawCI", "1", CVAR_ARCHIVE },
+
+//unlagged - client options
+ { &cg_delag, "cg_delag", "1", CVAR_ARCHIVE | CVAR_USERINFO },
+ // this will be automagically copied from the server
+ { &sv_fps, "sv_fps", "20", 0 },
+ { &cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE },
+//unlagged - client options
};
int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] );
@@ -910,6 +929,7 @@ void CG_UpdateCvars( void ) {
qboolean fSetFlags = qfalse; // OSPx - Auto Actions
for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
+
trap_Cvar_Update( cv->vmCvar );
// RTCWPro - update the modification count and perform actions on special cvars
diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c
index 17ec23bc..d9db275c 100644
--- a/src/cgame/cg_predict.c
+++ b/src/cgame/cg_predict.c
@@ -512,6 +512,143 @@ static void CG_TouchTriggerPrediction( void ) {
}
+//unlagged - optimized prediction
+#define ABS(x) ((x) < 0 ? (-(x)) : (x))
+
+static int IsUnacceptableError( playerState_t *ps, playerState_t *pps ) {
+ vec3_t delta;
+ int i;
+
+ if ( pps->pm_type != ps->pm_type ||
+ pps->pm_flags != ps->pm_flags ||
+ pps->pm_time != ps->pm_time ) {
+ return 1;
+ }
+
+ VectorSubtract( pps->origin, ps->origin, delta );
+ if ( VectorLengthSquared( delta ) > 0.1f * 0.1f ) {
+ if ( cg_showmiss.integer ) {
+ CG_Printf("delta: %.2f ", VectorLength(delta) );
+ }
+ return 2;
+ }
+
+ VectorSubtract( pps->velocity, ps->velocity, delta );
+ if ( VectorLengthSquared( delta ) > 0.1f * 0.1f ) {
+ if ( cg_showmiss.integer ) {
+ CG_Printf("delta: %.2f ", VectorLength(delta) );
+ }
+ return 3;
+ }
+
+ if ( pps->weaponTime != ps->weaponTime ||
+ pps->grenadeTimeLeft != ps->grenadeTimeLeft ||
+ pps->gravity != ps->gravity ||
+ pps->speed != ps->speed ||
+ pps->delta_angles[0] != ps->delta_angles[0] ||
+ pps->delta_angles[1] != ps->delta_angles[1] ||
+ pps->delta_angles[2] != ps->delta_angles[2] ||
+ pps->groundEntityNum != ps->groundEntityNum ) {
+ return 4;
+ }
+
+ if ( pps->legsTimer != ps->legsTimer ||
+ pps->legsAnim != ps->legsAnim ||
+ pps->torsoTimer != ps->torsoTimer ||
+ pps->torsoAnim != ps->torsoAnim ||
+ pps->movementDir != ps->movementDir ) {
+ return 5;
+ }
+
+ /*
+ // No grapple in RTCW
+ VectorSubtract( pps->grapplePoint, ps->grapplePoint, delta );
+ if ( VectorLengthSquared( delta ) > 0.1f * 0.1f ) {
+ return 6;
+ }
+ */
+
+ if ( pps->eFlags != ps->eFlags ) {
+ return 7;
+ }
+
+ if ( pps->eventSequence != ps->eventSequence ) {
+ return 8;
+ }
+
+ for ( i = 0; i < MAX_PS_EVENTS; i++ ) {
+ if ( pps->events[i] != ps->events[i] ||
+ pps->eventParms[i] != ps->eventParms[i] ) {
+ return 9;
+ }
+ }
+
+ if ( pps->externalEvent != ps->externalEvent ||
+ pps->externalEventParm != ps->externalEventParm ||
+ pps->externalEventTime != ps->externalEventTime ) {
+ return 10;
+ }
+
+ if ( pps->clientNum != ps->clientNum ||
+ pps->weapon != ps->weapon ||
+ pps->weaponstate != ps->weaponstate ) {
+ return 11;
+ }
+
+ if ( ABS(pps->viewangles[0] - ps->viewangles[0]) > 1.0f ||
+ ABS(pps->viewangles[1] - ps->viewangles[1]) > 1.0f ||
+ ABS(pps->viewangles[2] - ps->viewangles[2]) > 1.0f ) {
+ return 12;
+ }
+
+ if ( pps->viewheight != ps->viewheight ) {
+ return 13;
+ }
+
+ if ( pps->damageEvent != ps->damageEvent ||
+ pps->damageYaw != ps->damageYaw ||
+ pps->damagePitch != ps->damagePitch ||
+ pps->damageCount != ps->damageCount ) {
+ return 14;
+ }
+
+ for ( i = 0; i < MAX_STATS; i++ ) {
+ if ( pps->stats[i] != ps->stats[i] ) {
+ return 15;
+ }
+ }
+
+ for ( i = 0; i < MAX_PERSISTANT; i++ ) {
+ if ( pps->persistant[i] != ps->persistant[i] ) {
+ return 16;
+ }
+ }
+
+ for ( i = 0; i < MAX_POWERUPS; i++ ) {
+ if ( pps->powerups[i] != ps->powerups[i] ) {
+ return 17;
+ }
+ }
+
+ for ( i = 0; i < MAX_WEAPONS; i++ ) {
+ if ( pps->ammo[i] != ps->ammo[i] ) {
+ return 18;
+ }
+ }
+
+ /*
+ // Q3 related
+ if ( pps->generic1 != ps->generic1 ||
+ pps->loopSound != ps->loopSound ||
+ pps->jumppad_ent != ps->jumppad_ent ) {
+ return 19;
+ }
+ */
+
+ return 0;
+}
+//unlagged - optimized prediction
+
/*
=================
@@ -548,6 +685,11 @@ void CG_PredictPlayerState( void ) {
usercmd_t latestCmd;
vec3_t deltaAngles;
+//unlagged - optimized prediction
+ int stateIndex, predictCmd;
+ int numPredicted = 0, numPlayedBack = 0; // debug code
+//unlagged - optimized prediction
+
cg.hyperspace = qfalse; // will be set if touching a trigger_teleport
// if this is the first frame we must guarantee
@@ -654,6 +796,93 @@ void CG_PredictPlayerState( void ) {
cg_pmove.pmove_fixed = pmove_fixed.integer; // | cg_pmove_fixed.integer;
cg_pmove.pmove_msec = pmove_msec.integer;
+//unlagged - optimized prediction
+ // Like the comments described above, a player's state is entirely
+ // re-predicted from the last valid snapshot every client frame, which
+ // can be really, really, really slow. Every old command has to be
+ // run again. For every client frame that is *not* directly after a
+ // snapshot, this is unnecessary, since we have no new information.
+ // For those, we'll play back the predictions from the last frame and
+ // predict only the newest commands. Essentially, we'll be doing
+ // an incremental predict instead of a full predict.
+ //
+ // If we have a new snapshot, we can compare its player state's command
+ // time to the command times in the queue to find a match. If we find
+ // a matching state, and the predicted version has not deviated, we can
+ // use the predicted state as a base - and also do an incremental predict.
+ //
+ // With this method, we get incremental predicts on every client frame
+ // 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.nextFrameTeleport || cg.thisFrameTeleport ) {
+ // do a full predict
+ cg.lastPredictedCommand = 0;
+ cg.stateTail = cg.stateHead;
+ predictCmd = current - CMD_BACKUP + 1;
+ }
+ // cg.physicsTime is the current snapshot's serverTime
+ // if it's the same as the last one
+ else if ( cg.physicsTime == cg.lastServerTime ) {
+ // we have no new information, so do an incremental predict
+ predictCmd = cg.lastPredictedCommand + 1;
+ }
+ else {
+ // we have a new snapshot
+
+ int i;
+ qboolean error = qtrue;
+
+ // loop through the saved states queue
+ for ( i = cg.stateHead; i != cg.stateTail; i = (i + 1) % NUM_SAVED_STATES ) {
+ // if we find a predicted state whose commandTime matches the snapshot player state's commandTime
+ if ( cg.savedPmoveStates[i].commandTime == cg.predictedPlayerState.commandTime ) {
+ // make sure the state differences are acceptable
+ int errorcode = IsUnacceptableError( &cg.predictedPlayerState, &cg.savedPmoveStates[i] );
+
+ // too much change?
+ if ( errorcode ) {
+ if ( cg_showmiss.integer ) {
+ CG_Printf("errorcode %d at %d\n", errorcode, cg.time);
+ }
+ // yeah, so do a full predict
+ break;
+ }
+
+ // this one is almost exact, so we'll copy it in as the starting point
+ *cg_pmove.ps = cg.savedPmoveStates[i];
+ // advance the head
+ cg.stateHead = (i + 1) % NUM_SAVED_STATES;
+
+ // set the next command to predict
+ predictCmd = cg.lastPredictedCommand + 1;
+
+ // a saved state matched, so flag it
+ error = qfalse;
+ break;
+ }
+ }
+
+ // if no saved states matched
+ if ( error ) {
+ // do a full predict
+ cg.lastPredictedCommand = 0;
+ cg.stateTail = cg.stateHead;
+ predictCmd = current - CMD_BACKUP + 1;
+ }
+ }
+
+ // keep track of the server time of the last snapshot so we
+ // know when we're starting from a new one in future calls
+ cg.lastServerTime = cg.physicsTime;
+ stateIndex = cg.stateHead;
+ }
+//unlagged - optimized prediction
+
// RTCWPro - fixed physics
if (cg_fixedphysicsfps.integer)
{
@@ -797,7 +1026,51 @@ void CG_PredictPlayerState( void ) {
cg_pmove.medicChargeTime = cg_medicChargeTime.integer;
// -NERVE - SMF
- Pmove( &cg_pmove );
+//unlagged - optimized prediction
+ // we check for cg_latentCmds because it'll mess up the optimization
+ if ( cg_optimizePrediction.integer /*&& !cg_latentCmds.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
+ Pmove (&cg_pmove);
+
+ numPredicted++; // debug code
+
+ // record the last predicted command
+ cg.lastPredictedCommand = cmdNum;
+
+ // if we haven't run out of space in the saved states queue
+ if ( (stateIndex + 1) % NUM_SAVED_STATES != cg.stateHead ) {
+ // save the state for the false case (of cmdNum >= predictCmd)
+ // in later calls to this function
+ cg.savedPmoveStates[stateIndex] = *cg_pmove.ps;
+ stateIndex = (stateIndex + 1) % NUM_SAVED_STATES;
+ cg.stateTail = stateIndex;
+ }
+ }
+ else {
+ numPlayedBack++; // debug code
+
+ if ( cg_showmiss.integer &&
+ cg.savedPmoveStates[stateIndex].commandTime != cg_pmove.cmd.serverTime) {
+ // this should ONLY happen just after changing the value of pmove_fixed
+ CG_Printf( "saved state miss\n" );
+ }
+
+ // play back the command from the saved states
+ *cg_pmove.ps = cg.savedPmoveStates[stateIndex];
+
+ // go to the next element in the saved states array
+ stateIndex = (stateIndex + 1) % NUM_SAVED_STATES;
+ }
+ }
+ else {
+ // run the Pmove
+ Pmove (&cg_pmove);
+
+ numPredicted++; // debug code
+ }
+//unlagged - optimized prediction
moved = qtrue;
@@ -805,6 +1078,15 @@ void CG_PredictPlayerState( void ) {
CG_TouchTriggerPrediction();
}
+//unlagged - optimized prediction
+ // do a /condump after a few seconds of this
+ //CG_Printf("cg.time: %d, numPredicted: %d, numPlayedBack: %d\n", cg.time, numPredicted, numPlayedBack); // debug code
+ // if everything is working right, numPredicted should be 1 more than 98%
+ // of the time, meaning only ONE predicted move was done in the frame
+ // you should see other values for numPredicted after IsUnacceptableError
+ // returns nonzero, and that's it
+//unlagged - optimized prediction
+
if ( cg_showmiss.integer > 1 ) {
CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time );
}
diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c
index 11384ec2..383ce510 100644
--- a/src/cgame/cg_servercmds.c
+++ b/src/cgame/cg_servercmds.c
@@ -326,6 +326,12 @@ void CG_ParseServerinfo( void ) {
trap_Cvar_Set( "g_allowEnemySpawnTimer", Info_ValueForKey( info, "g_allowEnemySpawnTimer" ) );
trap_Cvar_Set( "stats_matchid", Info_ValueForKey( info, "stats_matchid" ) );
trap_Cvar_Set( "version", Info_ValueForKey( info, "version" ) );
+
+//unlagged - server options
+ // we'll need this for deciding whether or not to predict weapon effects
+ cgs.delagHitscan = atoi( Info_ValueForKey( info, "g_delagHitscan" ) );
+ trap_Cvar_Set("g_delagHitscan", va("%i", cgs.delagHitscan));
+//unlagged - server options
}
/*
diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c
index 10dcbaf5..a3763360 100644
--- a/src/cgame/cg_snapshot.c
+++ b/src/cgame/cg_snapshot.c
@@ -68,7 +68,9 @@ CG_TransitionEntity
cent->nextState is moved to cent->currentState and events are fired
===============
*/
-static void CG_TransitionEntity( centity_t *cent ) {
+//unlagged - early transitioning
+// used to be static, now needed to transition entities from within cg_ents.c
+void CG_TransitionEntity( centity_t *cent ) {
// Ridah, update the fireDir if it's on fire
if ( CG_EntOnFire( cent ) ) {
diff --git a/src/cgame/cg_unlagged.c b/src/cgame/cg_unlagged.c
new file mode 100644
index 00000000..76f11bc3
--- /dev/null
+++ b/src/cgame/cg_unlagged.c
@@ -0,0 +1,178 @@
+/*
+===========================================================================
+Copyright (C) 2006 Neil Toronto.
+
+This file is part of the Unlagged source code.
+
+Unlagged source code is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+Unlagged source code is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Unlagged source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+// we'll need these prototypes
+void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, qboolean wolfkick, int otherEntNum2, int seed );
+// and this as well
+// RF, wrote this so we can dynamically switch between old and new values while testing g_userAim
+// must match in g_weapon.c
+float G_GetWeaponSpread( int weapon ) {
+ switch ( weapon ) {
+ case WP_LUGER: return 600;
+ case WP_SILENCER: return 900;
+ case WP_COLT: return 800;
+ case WP_AKIMBO: return 800; //----(SA)added
+ case WP_VENOM: return 600;
+ case WP_MP40: return 400;
+ case WP_FG42SCOPE:
+ case WP_FG42: return 500;
+ case WP_BAR:
+ case WP_BAR2: return 500;
+ case WP_THOMPSON: return 600;
+ case WP_STEN: return 200;
+ case WP_MAUSER: return 2000;
+ case WP_GARAND: return 600;
+ case WP_SNIPERRIFLE: return 700; // was 300
+ case WP_SNOOPERSCOPE: return 700;
+ }
+
+ // jpw
+ return 0; // shouldn't get here
+}
+
+/*
+=======================
+CG_PredictWeaponEffects
+
+Draws predicted effects for the railgun, shotgun, and machinegun. The
+lightning gun is done in CG_LightningBolt, since it was just a matter
+of setting the right origin and angles.
+=======================
+*/
+void CG_PredictWeaponEffects( centity_t *cent ) {
+ entityState_t *ent = ¢->currentState;
+
+ // if the client isn't us, forget it
+ if ( cent->currentState.number != cg.predictedPlayerState.clientNum ) {
+ return;
+ }
+
+ // if it's not switched on server-side, forget it
+ if ( !cgs.delagHitscan ) {
+ return;
+ }
+
+ // do we have it on for the machinegun?
+ if ( cg_delag.integer & 1 || cg_delag.integer & 2 ) {
+ vec3_t muzzlePoint, forward, right, up;
+ // the server will use this exact time (it'll be serverTime on that end)
+ int seed = cg.oldTime % 256;
+ float r, u;
+ trace_t tr;
+ qboolean flesh;
+ int fleshEntityNum;
+ vec3_t endPoint;
+ qboolean randSpread = qtrue;
+ int dist = 8192;
+ float aimSpreadScale;
+ float spread;
+
+
+ aimSpreadScale = (float)cg.predictedPlayerState.aimSpreadScale / 255.0;
+ /*
+ // This comment seems to match the server side aimSpreadScale more closely than the above line
+ aimSpreadScale = (float)( ( cg.nextSnap ) ? cg.nextSnap->ps.aimSpreadScale / 255.0 : cg.snap->ps.aimSpreadScale / 255.0 );
+ aimSpreadScale += 0.15f; // (SA) just adding a temp /maximum/ accuracy for player (this will be re-visited in greater detail :)
+ */
+ if ( cg.predictedPlayerState.groundEntityNum == ENTITYNUM_NONE ) {
+ aimSpreadScale = 2.0f;
+ }
+ else if ( aimSpreadScale > 1 /*|| cg.predictedPlayerState.weapon == WP_MAUSER*/) {
+ aimSpreadScale = 1.0f; // still cap at 1.0
+ }
+
+ spread = G_GetWeaponSpread( cg.predictedPlayerState.weapon ) * aimSpreadScale;
+
+ r = Q_crandom(&seed) * spread;
+ u = Q_crandom(&seed) * spread;
+
+ /*if ( cg.predictedPlayerState.weapon == WP_SNOOPERSCOPE || cg.predictedPlayerState.weapon == WP_SNIPERRIFLE ) {
+ // aim dir already accounted for sway of scoped weapons in CalcMuzzlePoints()
+ dist *= 2;
+ randSpread = qfalse;
+ }*/
+
+ // get the muzzle point
+ VectorCopy( cg.predictedPlayerState.origin, muzzlePoint );
+ muzzlePoint[2] += cg.predictedPlayerState.viewheight;
+
+ // get forward, right, and up
+ AngleVectors( cg.predictedPlayerState.viewangles, forward, right, up );
+ VectorMA( muzzlePoint, 14, forward, muzzlePoint );
+
+ VectorMA( muzzlePoint, dist, forward, endPoint );
+ if ( randSpread ) {
+ VectorMA( endPoint, r, right, endPoint );
+ VectorMA( endPoint, u, up, endPoint );
+ }
+
+ CG_Trace(&tr, muzzlePoint, NULL, NULL, endPoint, cg.predictedPlayerState.clientNum, MASK_SHOT );
+
+ if ( tr.surfaceFlags & SURF_NOIMPACT ) {
+ return;
+ }
+
+ // snap the endpos to integers, but nudged towards the line
+ SnapVectorTowards( tr.endpos, muzzlePoint );
+
+ // do bullet impact
+ if ( tr.entityNum < MAX_CLIENTS ) {
+ flesh = qtrue;
+ fleshEntityNum = tr.entityNum;
+ } else {
+ flesh = qfalse;
+ }
+
+ // do the bullet impact
+ CG_Bullet( tr.endpos, cg.predictedPlayerState.clientNum, tr.plane.normal, flesh, fleshEntityNum , qfalse, cg.predictedPlayerEntity.currentState.otherEntityNum2, 0 );
+ //CG_Printf( "Predicted bullet\n" );
+ }
+}
+
+/*
+=======================
+CG_PredictedWeapon
+
+Returns true if weapon is predicted
+=======================
+*/
+qboolean CG_PredictedWeapon( int weapon ) {
+
+ if ( weapon == WP_MP40 ||
+ weapon == WP_THOMPSON ||
+ weapon == WP_COLT ||
+ weapon == WP_LUGER /*||
+ weapon == WP_STEN ||
+ weapon == WP_MAUSER ||
+ weapon == WP_GARAND ||
+ weapon == WP_SNIPERRIFLE ||
+ weapon == WP_SNOOPERSCOPE*/ ) {
+
+ return qtrue;
+
+ }
+
+ return qfalse;
+
+}
\ No newline at end of file
diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c
index 4f967655..1da3f5fd 100644
--- a/src/cgame/cg_weapons.c
+++ b/src/cgame/cg_weapons.c
@@ -4770,6 +4770,12 @@ void CG_FireWeapon( centity_t *cent ) {
weap->ejectBrassFunc( cent );
}
} // jpw
+
+ //unlagged - attack prediction #1
+ if ( CG_PredictedWeapon( ent->weapon ) ) {
+ CG_PredictWeaponEffects( cent );
+ }
+ //unlagged - attack prediction #1
}
@@ -6161,7 +6167,7 @@ static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) {
}
-void SnapVectorTowards( vec3_t v, vec3_t to ) {
+/*void SnapVectorTowards( vec3_t v, vec3_t to ) {
int i;
for ( i = 0 ; i < 3 ; i++ ) {
@@ -6173,7 +6179,7 @@ void SnapVectorTowards( vec3_t v, vec3_t to ) {
v[i] = ceil( v[i] );
}
}
-}
+}*/
/**
* @brief Renders bullet tracers if tracer option is valid.
diff --git a/src/cgame/cgame.vcproj b/src/cgame/cgame.vcproj
index ae8192dc..62a6cff4 100644
--- a/src/cgame/cgame.vcproj
+++ b/src/cgame/cgame.vcproj
@@ -782,6 +782,27 @@
/>
+
+
+
+
+
+
+
+
diff --git a/src/cgame/cgame.vcxproj b/src/cgame/cgame.vcxproj
index 7bca850c..a7ce38b1 100644
--- a/src/cgame/cgame.vcxproj
+++ b/src/cgame/cgame.vcxproj
@@ -317,6 +317,11 @@ powershell cp """$(SolutionDir)Builds\$(Configuration)\$(ProjectName)\$(TargetNa
false
MaxSpeed
+
+ Disabled
+ false
+ MaxSpeed
+
Disabled
false
diff --git a/src/cgame/cgame.vcxproj.filters b/src/cgame/cgame.vcxproj.filters
index 80cddb3a..7d82c2d0 100644
--- a/src/cgame/cgame.vcxproj.filters
+++ b/src/cgame/cgame.vcxproj.filters
@@ -82,6 +82,9 @@
Source Files
+
+ Source Files
+
Source Files
diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c
index 8097b4b5..19616995 100644
--- a/src/game/bg_misc.c
+++ b/src/game/bg_misc.c
@@ -4118,6 +4118,11 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean
SnapVector( s->pos.trBase );
}
+ VectorCopy(ps->velocity, s->pos.trDelta);
+ if (snap) {
+ SnapVector(s->pos.trDelta);
+ }
+
s->apos.trType = TR_INTERPOLATE;
VectorCopy( ps->viewangles, s->apos.trBase );
if ( snap ) {
@@ -4130,6 +4135,7 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean
s->angles2[YAW] = ps->movementDir;
}
+ s->angles2[PITCH] = 0;
s->legsAnim = ps->legsAnim;
s->torsoAnim = ps->torsoAnim;
s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number
diff --git a/src/game/g_active.c b/src/game/g_active.c
index 1c417990..a943a23a 100644
--- a/src/game/g_active.c
+++ b/src/game/g_active.c
@@ -1240,66 +1240,16 @@ void ClientThink_real( gentity_t *ent ) {
// does a G_RunFrame()
client->frameOffset = trap_Milliseconds() - level.frameStartTime;
-
- //unlagged - lag simulation #3
- // if the client wants to simulate outgoing packet loss
- /*if ( client->pers.plOut ) {
- // see if a random value is below the threshhold
- float thresh = (float)client->pers.plOut / 100.0f;
- if ( random() < thresh ) {
- // do nothing at all if it is - this is a lost command
- return;
- }
- }*/
- //unlagged - lag simulation #3
-
- //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 - 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;
-
//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 - 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);
- }*/
}
// RTCWPro
@@ -1716,12 +1666,21 @@ void ClientThink_real( gentity_t *ent ) {
// RTCWPro
// Ridah, fixes jittery zombie movement
- if (g_smoothClients.integer) {
- BG_PlayerStateToEntityStateExtraPolate(&ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue);
- }
- else {
+ if (g_antilag.integer < 2) // Nobo antilag or off
+ {
+ // RTCWPro
+ // 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);
+ }
+ }
+ else if (g_antilag.integer == 2) // Unlagged
+ {
BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qtrue);
- }
+ }
/*if (g_thinkStateLevelTime.integer)
{
diff --git a/src/game/g_client.c b/src/game/g_client.c
index a678df17..26a98234 100644
--- a/src/game/g_client.c
+++ b/src/game/g_client.c
@@ -1603,6 +1603,16 @@ void ClientUserinfoChanged(int clientNum) {
client->pers.predictItemPickup = qtrue;
}
+ //unlagged - client options
+ // see if the player has opted out
+ s = Info_ValueForKey( userinfo, "cg_delag" );
+ if ( !atoi( s ) ) {
+ client->pers.delag = 0;
+ } else {
+ client->pers.delag = atoi( s );
+ }
+ //unlagged - client options
+
// check the auto activation
s = Info_ValueForKey( userinfo, "cg_autoactivate" );
if ( !atoi( s ) ) {
diff --git a/src/game/g_local.h b/src/game/g_local.h
index 1b2de4fb..9f7d3d9d 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -661,6 +661,11 @@ typedef struct {
int samplehead;
unsigned int pingsample_counter;
int deathYaw;
+
+ //unlagged - client options
+ // these correspond with variables in the userinfo string
+ int delag;
+ //unlagged - client options
} clientPersistant_t;
// L0 - antilag port
@@ -851,7 +856,7 @@ struct gclient_s {
// an approximation of the actual server time we received this
// command (not in 50ms increments)
int frameOffset;
-//unlagged - backward reconciliation #1
+ //unlagged - backward reconciliation #1
//unlagged - smooth clients #1
// the last frame number we got an update from this client
@@ -1322,7 +1327,10 @@ void mg42_fire( gentity_t *other );
//
qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker );
void CalcMuzzlePoint( gentity_t *ent, int weapon, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint );
-void SnapVectorTowards( vec3_t v, vec3_t to );
+//unlagged - attack prediction #3
+// we're making this available to both games
+//void SnapVectorTowards( vec3_t v, vec3_t to );
+//unlagged - attack prediction #3
trace_t *CheckMeleeAttack( gentity_t *ent, float dist, qboolean isTest );
gentity_t *weapon_grenadelauncher_fire( gentity_t *ent, int grenadeWPID );
// Rafael
@@ -1781,6 +1789,7 @@ extern vmCvar_t g_allowReadyTeam;
extern vmCvar_t g_delagHitscan;
extern vmCvar_t g_maxExtrapolatedFrames;
extern vmCvar_t g_maxLagCompensation;
+extern vmCvar_t g_unlaggedVersion;
void trap_Printf( const char *fmt );
void trap_Error( const char *fmt );
diff --git a/src/game/g_main.c b/src/game/g_main.c
index 3f8ab8a8..6139d2c7 100644
--- a/src/game/g_main.c
+++ b/src/game/g_main.c
@@ -302,12 +302,14 @@ vmCvar_t g_broadcastClients; // fix clients appearing from thin air on some maps
vmCvar_t g_logConfigStringChanges; // log config string changes (debugging)
vmCvar_t g_playPauseMusic; // play music during pause
- // unlagged
+//unlagged - server options
vmCvar_t g_floatPlayerPosition;
vmCvar_t g_delagHitscan;
vmCvar_t g_maxExtrapolatedFrames;
vmCvar_t g_maxLagCompensation;
vmCvar_t g_delagMissiles;
+vmCvar_t g_unlaggedVersion;
+//unlagged - server options
cvarTable_t gameCvarTable[] = {
// don't override the cheat state set by the system
@@ -560,11 +562,13 @@ cvarTable_t gameCvarTable[] = {
{ &g_allowReadyTeam, "g_allowReadyTeam", "0", CVAR_ARCHIVE, 0, qfalse },// Allow readyteam command
- // unlagged
+ //unlagged - server options
{ &g_delagHitscan, "g_delagHitscan", "1", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
{ &g_maxExtrapolatedFrames, "g_maxExtrapolatedFrames", "2", 0 , 0, qfalse },
{ &g_maxLagCompensation, "g_maxLagCompensation", "125", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
- { &g_delagMissiles, "g_delagMissiles", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue }
+ { &g_delagMissiles, "g_delagMissiles", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
+ { &g_unlaggedVersion, "g_unlaggedVersion", "2.0", CVAR_ROM | CVAR_SERVERINFO, 0, qfalse }
+ //unlagged - server options
};
// bk001129 - made static to avoid aliasing
diff --git a/src/game/g_missile.c b/src/game/g_missile.c
index c08e4439..e175b559 100644
--- a/src/game/g_missile.c
+++ b/src/game/g_missile.c
@@ -1378,6 +1378,10 @@ gentity_t *fire_flamechunk( gentity_t *self, vec3_t start, vec3_t dir ) {
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_NOCLIENT;
bolt->s.weapon = self->s.weapon;
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->methodOfDeath = MOD_FLAMETHROWER;
bolt->clipmask = MASK_MISSILESHOT;
@@ -1487,6 +1491,10 @@ gentity_t *fire_grenade( gentity_t *self, vec3_t start, vec3_t dir, int grenadeW
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST;
bolt->s.weapon = grenadeWPID;
bolt->r.ownerNum = self->s.number;
+ //unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+ //unlagged - projectile nudge
bolt->parent = self;
// JPW NERVE -- commented out bolt->damage and bolt->splashdamage, override with G_GetWeaponDamage()
@@ -1610,6 +1618,10 @@ gentity_t *fire_speargun( gentity_t *self, vec3_t start, vec3_t dir ) {
bolt->s.weapon = WP_SPEARGUN;
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->damage = 15; // (SA) spear damage here
bolt->splashDamage = 0;
@@ -1662,6 +1674,10 @@ gentity_t *fire_rocket( gentity_t *self, vec3_t start, vec3_t dir ) {
}
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->damage = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); // JPW NERVE
bolt->splashDamage = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); // JPW NERVE
@@ -1714,6 +1730,10 @@ gentity_t *fire_flamebarrel( gentity_t *self, vec3_t start, vec3_t dir ) {
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
bolt->s.weapon = WP_ROCKET_LAUNCHER;
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->damage = 100;
bolt->splashDamage = 20;
@@ -1876,6 +1896,10 @@ gentity_t *fire_mortar( gentity_t *self, vec3_t start, vec3_t dir ) {
bolt->s.weapon = WP_MORTAR;
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->damage = G_GetWeaponDamage( WP_MORTAR ); // JPW NERVE
bolt->splashDamage = G_GetWeaponDamage( WP_MORTAR ); // JPW NERVE
@@ -1918,6 +1942,10 @@ gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t righ
// bolt->s.weapon = WP_NAILGUN;
bolt->s.weapon = WP_VENOM_FULL;
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->damage = G_GetWeaponDamage( WP_VENOM_FULL );
// bolt->methodOfDeath = MOD_NAIL;
@@ -1973,6 +2001,10 @@ gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t dir ) {
// bolt->s.weapon = WP_PROX_LAUNCHER;
bolt->s.eFlags = 0;
bolt->r.ownerNum = self->s.number;
+//unlagged - projectile nudge
+ // we'll need this for nudging projectiles later
+ bolt->s.otherEntityNum = self->s.number;
+//unlagged - projectile nudge
bolt->parent = self;
bolt->damage = 0;
bolt->splashDamage = 100;
diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c
index 9d8414eb..e993e42b 100644
--- a/src/game/g_weapon.c
+++ b/src/game/g_weapon.c
@@ -1305,6 +1305,9 @@ into a wall.
// too far off the target surface causes the the distance between the transmitted impact
// point and the actual hit surface larger than the mark radius. (so nothing shows) )
+//unlagged - attack prediction #3
+// moved to q_shared.c
+/*
void SnapVectorTowards( vec3_t v, vec3_t to ) {
int i;
@@ -1318,6 +1321,8 @@ void SnapVectorTowards( vec3_t v, vec3_t to ) {
}
}
}
+*/
+//unlagged - attack prediction #3
// JPW
// mechanism allows different weapon damage for single/multiplayer; we want "balanced" weapons
@@ -1843,15 +1848,30 @@ void Bullet_Endpos( gentity_t *ent, float spread, vec3_t *end ) {
qboolean randSpread = qtrue;
int dist = 8192;
- r = crandom() * spread;
- u = crandom() * spread;
+ //unlagged - attack prediction #2
+ // we have to use something now that the client knows in advance
+ int seed = ent->client->attackTime % 256;
+ //unlagged - attack prediction #2
+
+ //unlagged - attack prediction #2
+ // this has to match what's on the client
+ //r = crandom() * spread;
+ //u = crandom() * spread;
+ r = Q_crandom(&seed) * spread;
+ u = Q_crandom(&seed) * spread;
+ //unlagged - attack prediction #2
// Ridah, if this is an AI shooting, apply their accuracy
if ( ent->r.svFlags & SVF_CASTAI ) {
float accuracy;
accuracy = ( 1.0 - AICast_GetAccuracy( ent->s.number ) ) * AICAST_AIM_SPREAD;
- r += crandom() * accuracy;
- u += crandom() * ( accuracy * 1.25 );
+ //unlagged - attack prediction #2
+ // this has to match what's on the client
+ //r += crandom() * accuracy;
+ //u += crandom() * ( accuracy * 1.25 );
+ r += Q_crandom(&seed) * accuracy;
+ u += Q_crandom(&seed) * ( accuracy * 1.25 );
+ //unlagged - attack prediction #2
} else {
if ( ent->s.weapon == WP_SNOOPERSCOPE || ent->s.weapon == WP_SNIPERRIFLE ) {
// aim dir already accounted for sway of scoped weapons in CalcMuzzlePoints()
@@ -2027,6 +2047,11 @@ void Bullet_Fire_Extended(gentity_t* source, gentity_t* attacker, vec3_t start,
if (traceEnt->takedamage && (traceEnt->client) && !(traceEnt->flags & FL_DEFENSE_GUARD)) {
tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH);
tent->s.eventParm = traceEnt->s.number;
+ //unlagged - attack prediction #2
+ // we need the client number to determine whether or not to
+ // suppress this event
+ tent->s.clientNum = attacker->s.clientNum;
+ //unlagged - attack prediction #2
if (LogAccuracyHit(traceEnt, attacker) && g_gamestate.integer == GS_PLAYING) {
attacker->client->ps.persistant[PERS_ACCURACY_HITS]++;
// L0 - Stats
@@ -2053,6 +2078,11 @@ void Bullet_Fire_Extended(gentity_t* source, gentity_t* attacker, vec3_t start,
else if (traceEnt->takedamage && traceEnt->s.eType == ET_BAT) {
tent = G_TempEntity(tr.endpos, EV_BULLET_HIT_FLESH);
tent->s.eventParm = traceEnt->s.number;
+ //unlagged - attack prediction #2
+ // we need the client number to determine whether or not to
+ // suppress this event
+ tent->s.clientNum = attacker->s.clientNum;
+ //unlagged - attack prediction #2
}
else {
// Ridah, bullet impact should reflect off surface
@@ -2088,6 +2118,11 @@ void Bullet_Fire_Extended(gentity_t* source, gentity_t* attacker, vec3_t start,
VectorNormalize(reflect);
tent->s.eventParm = DirToByte(reflect);
+ //unlagged - attack prediction #2
+ // we need the client number to determine whether or not to
+ // suppress this event
+ tent->s.clientNum = attacker->s.clientNum;
+ //unlagged - attack prediction #2
if (traceEnt->flags & FL_DEFENSE_GUARD) {
tent->s.otherEntityNum2 = traceEnt->s.number; // force sparks
@@ -2362,8 +2397,8 @@ void VenomPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) {
// generate the "random" spread pattern
for ( i = 0 ; i < DEFAULT_VENOM_COUNT ; i++ ) {
- r = Q_crandom( &seed ) * DEFAULT_VENOM_SPREAD;
- u = Q_crandom( &seed ) * DEFAULT_VENOM_SPREAD;
+ r = crandom() * DEFAULT_VENOM_SPREAD;
+ u = crandom() * DEFAULT_VENOM_SPREAD;
VectorMA( origin, 8192, forward, end );
VectorMA( end, r, right, end );
VectorMA( end, u, up, end );
@@ -2445,7 +2480,7 @@ void weapon_venom_fire( gentity_t *ent, qboolean fullmode, float aimSpreadScale
VectorScale( forward, 4096, tent->s.origin2 );
SnapVector( tent->s.origin2 );
- tent->s.eventParm = rand() & 255; // seed for spread pattern
+ tent->s.eventParm = rand() & 255; // seed for spread pattern
tent->s.otherEntityNum = ent->s.number;
if ( fullmode ) {
diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c
index 4435bddc..905e45bb 100644
--- a/src/qcommon/q_shared.c
+++ b/src/qcommon/q_shared.c
@@ -1513,4 +1513,27 @@ void Q_ColorizeString(char colorCode, const char* inStr, char* outStr, size_t ou
outStr[outOffset] = 0;
}
-
+//unlagged - attack prediction #3
+// moved from g_weapon.c
+/*
+======================
+SnapVectorTowards
+
+Round a vector to integers for more efficient network
+transmission, but make sure that it rounds towards a given point
+rather than blindly truncating. This prevents it from truncating
+into a wall.
+======================
+*/
+void SnapVectorTowards( vec3_t v, vec3_t to ) {
+ int i;
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( to[i] <= v[i] ) {
+ v[i] = (int)v[i];
+ } else {
+ v[i] = (int)v[i] + 1;
+ }
+ }
+}
+//unlagged - attack prediction #3
diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h
index 14f7349f..b9f3f4eb 100644
--- a/src/qcommon/q_shared.h
+++ b/src/qcommon/q_shared.h
@@ -654,6 +654,11 @@ typedef struct {
#define SnapVector( v ) {v[0] = ( (int)( v[0] ) ); v[1] = ( (int)( v[1] ) ); v[2] = ( (int)( v[2] ) );}
+//unlagged - attack prediction #3
+// moved from g_weapon.c
+void SnapVectorTowards( vec3_t v, vec3_t to );
+//unlagged - attack prediction #3
+
// just in case you do't want to use the macros
vec_t _DotProduct( const vec3_t v1, const vec3_t v2 );
void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out );
diff --git a/src/unix/Conscript-cgame b/src/unix/Conscript-cgame
index 4e25f6c7..75483c73 100644
--- a/src/unix/Conscript-cgame
+++ b/src/unix/Conscript-cgame
@@ -61,6 +61,7 @@ $CG_FILESREF = \@CG_FILES_OUT;
../cgame/cg_spawn.c
../cgame/cg_syscalls.c
../cgame/cg_trails.c
+ ../cgame/cg_unlagged.c
../cgame/cg_view.c
../cgame/cg_weapons.c
);