From e5ff1d3d4bc01d98b12d05f9cb85457c7f15c424 Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Tue, 6 Aug 2019 02:17:23 +0100 Subject: first commit --- src/cgame/cg_snapshot.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 src/cgame/cg_snapshot.c (limited to 'src/cgame/cg_snapshot.c') diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c new file mode 100644 index 0000000..84b419f --- /dev/null +++ b/src/cgame/cg_snapshot.c @@ -0,0 +1,464 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + + +#include "cg_local.h" + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) +{ + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) + cent->previousEvent = 0; + + cent->trailTime = cg.snap->serverTime; + + VectorCopy( cent->currentState.origin, cent->lerpOrigin ); + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + + if( cent->currentState.eType == ET_PLAYER ) + CG_ResetPlayerEntity( cent ); +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) +{ + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if( !cent->interpolate ) + CG_ResetEntity( cent ); + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) +{ + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList( ); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn( ); + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy( ¢->currentState, state, sizeof( entityState_t ) ); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) +{ + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if( !cg.snap ) + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + + if( !cg.nextSnap ) + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + if( !cg.snap ) { } //TA: ? + + // clear the currentValid flag for all entities in the existing snapshot + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg.snap->serverTime; + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if( oldFrame ) + { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || + cg_nopredict.integer || cg_synchronousClients.integer ) + CG_TransitionPlayerState( ps, ops ); + } +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) +{ + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for( num = 0 ; num < snap->numEntities ; num++ ) + { + es = &snap->entities[ num ]; + cent = &cg_entities[ es->number ]; + + memcpy( ¢->nextState, es, sizeof( entityState_t ) ); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) + cent->interpolate = qfalse; + else + cent->interpolate = qtrue; + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) + cg.nextFrameTeleport = qtrue; + else + cg.nextFrameTeleport = qfalse; + + // if changing follow mode, don't interpolate + if( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) + cg.nextFrameTeleport = qtrue; + + // if changing server restarts, don't interpolate + if( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) + cg.nextFrameTeleport = qtrue; + + // sort out solid entities + CG_BuildSolidList( ); +} + + +#ifdef MODULE_INTERFACE_11 +static moduleAlternateSnapshot_t moduleAlternateSnapshot; + +static qboolean CG_GetModuleAlternateSnapshot( int snapshotNumber, snapshot_t *snap ) +{ + moduleAlternateSnapshot_t *alt = &moduleAlternateSnapshot; + int r = trap_GetSnapshot( snapshotNumber, alt ); + + if( r ) + { + int i; + + snap->snapFlags = alt->snapFlags; + snap->ping = alt->ping; + snap->serverTime = alt->serverTime; + snap->numEntities = alt->numEntities; + snap->numServerCommands = alt->numServerCommands; + snap->serverCommandSequence = alt->serverCommandSequence; + memcpy( &snap->areamask, &alt->areamask, sizeof( snap->areamask ) ); + +#define PSFO(x) ((size_t)&(((playerState_t*)0)->x)) + memcpy( &snap->ps.commandTime, &alt->ps.commandTime, PSFO(tauntTimer) - PSFO(commandTime) ); + memcpy( &snap->ps.movementDir, &alt->ps.movementDir, PSFO(ammo) - PSFO(movementDir) ); + memcpy( &snap->ps.generic1, &alt->ps.generic1, PSFO(entityEventSequence) - PSFO(generic1) ); + + snap->ps.weaponAnim = alt->ps.ammo[0] & 0xFF; + snap->ps.pm_flags |= ( alt->ps.ammo[0] & 0xFF00 ) << 8; + snap->ps.ammo = alt->ps.ammo[1] & 0xFFF; + snap->ps.clips = ( alt->ps.ammo[1] & 0xF000 ) >> 12; + snap->ps.tauntTimer = alt->ps.ammo[2] & 0xFFF; + snap->ps.generic1 |= ( alt->ps.ammo[2] & 0x3000 ) >> 4; + + for( i = 0; i < alt->numEntities; ++i ) + { + entityState_t *s = &snap->entities[ i ]; + const moduleAlternateEntityState_t *a = &alt->entities[ i ]; + +#define ESFO(x) ((size_t)&(((entityState_t*)0)->x)) + memcpy( &s->number, &a->number, ESFO(weaponAnim) - ESFO(number) ); + + s->weaponAnim = 0; + s->generic1 = a->generic1; + } + } + + return r; +} +#endif + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) +{ + qboolean r; + snapshot_t *dest; + + if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) + { + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while( cgs.processedSnapshotNum < cg.latestSnapshotNum ) + { + // decide which of the two slots to load it into + if( cg.snap == &cg.activeSnapshots[ 0 ] ) + dest = &cg.activeSnapshots[ 1 ]; + else + dest = &cg.activeSnapshots[ 0 ]; + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; +#ifdef MODULE_INTERFACE_11 + r = CG_GetModuleAlternateSnapshot( cgs.processedSnapshotNum, dest ); +#else + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); +#endif + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if( cg.snap && r && dest->serverTime == cg.snap->serverTime ) + { + //continue; + } + + // if it succeeded, return + if( r ) + { + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) +{ + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + + if( n != cg.latestSnapshotNum ) + { + if( n < cg.latestSnapshotNum ) + { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while( !cg.snap ) + { + snap = CG_ReadNextSnapshot( ); + + if( !snap ) + { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) + CG_SetInitialSnapshot( snap ); + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do + { + // if we don't have a nextframe, try and read a new one in + if( !cg.nextSnap ) + { + snap = CG_ReadNextSnapshot( ); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if( !snap ) + break; + + CG_SetNextSnap( snap ); + + // if time went backwards, we have a level restart + if( cg.nextSnap->serverTime < cg.snap->serverTime ) + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + + // if our time is < nextFrame's, we have a nice interpolating state + if( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) + break; + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot( ); + } while( 1 ); + + // assert our valid conditions upon exiting + if( cg.snap == NULL ) + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + + if( cg.time < cg.snap->serverTime ) + { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + + if( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); +} + -- cgit