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 );