From 81d8e39c6756b30a4245fe2075ee41a95be60d66 Mon Sep 17 00:00:00 2001 From: Noah Metzger Date: Fri, 1 Apr 2022 00:07:54 -0500 Subject: [PATCH] player move refactoring and improvements --- build/qvm_build/build_game.bat | 1 + code/cgame/cg_local.h | 1 + code/cgame/cg_predict.c | 46 ++- code/cgame/cg_servercmds.c | 2 + code/common/bg_pmove.c | 8 +- code/common/bg_public.h | 3 +- code/game/g_active.c | 322 +++++++++--------- code/game/g_client.c | 1 - code/game/g_cvar_defs.h | 5 - code/game/g_local.h | 5 +- code/game/g_main.c | 44 --- code/game/mods/features/feature_player_move.c | 144 ++++++++ code/game/mods/g_mod_defs.h | 17 + code/game/mods/g_mod_local.h | 3 + code/game/mods/g_mod_main.c | 5 + code/game/mods/g_mod_public.h | 6 + code/game/mods/g_mod_stubs.c | 5 + 17 files changed, 393 insertions(+), 225 deletions(-) create mode 100644 code/game/mods/features/feature_player_move.c diff --git a/build/qvm_build/build_game.bat b/build/qvm_build/build_game.bat index 0809003..aca0e76 100644 --- a/build/qvm_build/build_game.bat +++ b/build/qvm_build/build_game.bat @@ -63,6 +63,7 @@ set src=game&set name=g_weapon&call :compile set src=game\mods&set name=g_mod_main&call :compile set src=game\mods&set name=g_mod_stubs&call :compile set src=game\mods&set name=g_mod_utils&call :compile +set src=game\mods\features&set name=feature_player_move&call :compile set src=common&set name=logging&call :compile set src=common&set name=vm_extensions&call :compile diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index c49a7be..a33b12e 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1069,6 +1069,7 @@ typedef struct { typedef struct { // player movement int pMoveFixed; + int pMoveTriggerMode; qboolean noJumpKeySlowdown; qboolean bounceFix; int snapVectorGravLimit; diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index 8e7d1ad..ad6591a 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -264,6 +264,12 @@ void CG_FilterPredictableEvent( entity_event_t event, int eventParm, playerState debounceTable[i].lastCommandTime = ps->commandTime; debounceTable[i].lastClientTime = cg.time; + + // Make sure predicted playerstate is up to date in case any event handling code accesses it + if ( !serverEvent ) { + cg.predictedPlayerState = *ps; + } + CG_ExecutePredictableEvent( event, eventParm ); return; } @@ -943,7 +949,7 @@ static void CG_FinalPredict( playerState_t *ps, usercmd_t *cmd ) { temp.eventSequence = 0; CG_InitPmove( &temp, cmd ); - Pmove( &cg_pmove, 0 ); + Pmove( &cg_pmove, 0, NULL, NULL ); ps->origin[0] = temp.origin[0]; ps->origin[1] = temp.origin[1]; @@ -955,6 +961,31 @@ static void CG_FinalPredict( playerState_t *ps, usercmd_t *cmd ) { ps->bobCycle = temp.bobCycle; } +/* +================= +CG_PostPmove +================= +*/ +static void CG_PostPmove( pmove_t *pmove, qboolean finalFragment, void *context) { + predictionFrame_t *frame = (predictionFrame_t *)context; + if ( finalFragment || cgs.modConfig.pMoveTriggerMode ) { + int i; + + CG_TouchTriggerPrediction( &frame->ps, &frame->predictedTeleport ); + + // Note that running events here means that CG_FilterPredictableEvent won't be called for cached + // frames. This should be fine since cached frames should only contain duplicate events that would + // filtered anyway, but it might have implications for testing/debug purposes. + for ( i = frame->ps.eventSequence - MAX_PS_EVENTS; i < frame->ps.eventSequence; ++i ) { + if ( i >= 0 ) { + CG_FilterPredictableEvent( frame->ps.events[ i & (MAX_PS_EVENTS-1) ], + frame->ps.eventParms[ i & (MAX_PS_EVENTS-1) ], &frame->ps, qfalse ); + } + } + frame->ps.eventSequence = 0; + } +} + /* ================= CG_PredictPlayerState @@ -978,7 +1009,6 @@ to ease the jerk. ================= */ void CG_PredictPlayerState( void ) { - int i; snapshot_t *snap = PREDICTION_SNAPSHOT; playerState_t oldPlayerState; usercmd_t *latestCmd; @@ -1125,24 +1155,16 @@ void CG_PredictPlayerState( void ) { // perform the move CG_InitPmove( &frame->ps, cmd ); - Pmove( &cg_pmove, cgs.modConfig.pMoveFixed ); - CG_TouchTriggerPrediction( &frame->ps, &frame->predictedTeleport ); + Pmove( &cg_pmove, cgs.modConfig.pMoveFixed, CG_PostPmove, frame ); CG_PatchWeaponAutoswitch( &frame->ps ); predictCache.latest = frameNum; } - // check for predicted events... + // check for predicted teleport... if ( frame->predictedTeleport ) { cg.hyperspace = qtrue; } - - for ( i = frame->ps.eventSequence - MAX_PS_EVENTS; i < frame->ps.eventSequence; ++i ) { - if ( i >= 0 ) { - CG_FilterPredictableEvent( frame->ps.events[ i & (MAX_PS_EVENTS-1) ], - frame->ps.eventParms[ i & (MAX_PS_EVENTS-1) ], &frame->ps, qfalse ); - } - } } } diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index 95eeac9..63faaf6 100644 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -161,6 +161,8 @@ void CG_ParseModConfig( void ) { break; if ( !Q_stricmp( key, "pMoveFixed" ) ) cgs.modConfig.pMoveFixed = atoi( value ); + if ( !Q_stricmp( key, "pMoveTriggerMode" ) ) + cgs.modConfig.pMoveTriggerMode = atoi( value ); if ( !Q_stricmp( key, "noJumpKeySlowdown" ) ) cgs.modConfig.noJumpKeySlowdown = atoi( value ) ? qtrue : qfalse; if ( !Q_stricmp( key, "bounceFix" ) ) diff --git a/code/common/bg_pmove.c b/code/common/bg_pmove.c index 8112ae0..b1a7e45 100644 --- a/code/common/bg_pmove.c +++ b/code/common/bg_pmove.c @@ -2332,7 +2332,8 @@ Pmove Can be called by either the server or the client ================ */ -void Pmove( pmove_t *pmove, int pMoveFixed ) { +void Pmove( pmove_t *pmove, int pMoveFixed, + void ( *postMove )( pmove_t *pmove, qboolean finalFragment, void *context ), void *postMoveContext ) { usercmd_t inputCmd = pmove->cmd; if ( inputCmd.serverTime > pmove->ps->commandTime + 1000 ) { @@ -2350,5 +2351,10 @@ void Pmove( pmove_t *pmove, int pMoveFixed ) { // reset any changes to command during move pmove->cmd = inputCmd; + + if ( postMove ) { + qboolean finalFragment = !PM_IsMoveNeeded( pmove->ps->commandTime,inputCmd.serverTime, pMoveFixed ); + postMove( pmove, finalFragment, postMoveContext ); + } } } diff --git a/code/common/bg_public.h b/code/common/bg_public.h index b39fa8b..2cff9d3 100644 --- a/code/common/bg_public.h +++ b/code/common/bg_public.h @@ -190,7 +190,8 @@ typedef struct { void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); int PM_NextMoveTime( int currentTime, int targetTime, int pMoveFixed ); qboolean PM_IsMoveNeeded( int currentTime, int targetTime, int pMoveFixed ); -void Pmove( pmove_t *pmove, int pMoveFixed ); +void Pmove( pmove_t *pmove, int pMoveFixed, + void ( *postMove )( pmove_t *pmove, qboolean finalFragment, void *context ), void *postMoveContext ); //=================================================================================== diff --git a/code/game/g_active.c b/code/game/g_active.c index e3a9eb5..17997df 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -459,55 +459,6 @@ void G_TouchTriggers( gentity_t *ent ) { } } -/* -================= -SpectatorThink -================= -*/ -void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { - pmove_t pm; - gclient_t *client; - - client = ent->client; - - if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { - client->ps.pm_type = PM_SPECTATOR; - client->ps.speed = 400; // faster than normal - - // set up for pmove - memset (&pm, 0, sizeof(pm)); - pm.ps = &client->ps; - pm.cmd = *ucmd; - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies - pm.trace = trap_Trace; - pm.pointcontents = trap_PointContents; - pm.noSpectatorDrift = qtrue; - - // perform a pmove - Pmove (&pm, G_PmoveFixedValue()); - - // save results of pmove - VectorCopy( client->ps.origin, ent->s.origin ); - - G_TouchTriggers( ent ); - trap_UnlinkEntity( ent ); - } - - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - - // attack button cycles through spectators - if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { - Cmd_FollowCycle_f( ent, 1 ); - } - else if ( ( client->buttons & BUTTON_ALT_ATTACK ) && ! ( client->oldbuttons & BUTTON_ALT_ATTACK ) ) - { - if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { - StopFollowing( ent ); - } - } -} - /* @@ -640,12 +591,9 @@ void ClientIntermissionThink( gclient_t *client ) { // the level will exit when everyone wants to or after timeouts - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = client->pers.cmd.buttons; if (g_gametype.integer != GT_SINGLE_PLAYER) { - if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { + if ( client->pers.cmd.buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->pers.oldbuttons ^ client->pers.cmd.buttons ) ) { client->readyToExit ^= 1; } } @@ -1624,6 +1572,161 @@ void SendPendingPredictableEvents( playerState_t *ps ) { } } +/* +============== +(ModFN) PmoveInit +============== +*/ +LOGFUNCTION_VOID( ModFNDefault_PmoveInit, ( int clientNum, pmove_t *pmove ), ( clientNum, pmove ), "G_MODFN_PMOVEINIT" ) { + gclient_t *client = &level.clients[clientNum]; + + memset( pmove, 0, sizeof( *pmove ) ); + + pmove->ps = &client->ps; + pmove->cmd = client->pers.cmd; + if ( pmove->ps->pm_type == PM_DEAD || pmove->ps->pm_type == PM_SPECTATOR ) { + pmove->tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } else { + pmove->tracemask = MASK_PLAYERSOLID; + } + pmove->trace = trap_Trace; + pmove->pointcontents = trap_PointContents; + pmove->debugLevel = g_debugMove.integer; + pmove->noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + if ( client->ps.weapon >= 1 && client->ps.weapon < WP_NUM_WEAPONS ) { + if ( client->sess.altSwapFlags & ( 1 << ( client->ps.weapon - 1 ) ) ) { + pmove->altFireMode = ALTMODE_SWAPPED; + } + } +} + +/* +============== +(ModFN) PostPmoveActions + +Process triggers and other operations after player move(s) have completed. +This may be called 0, 1, or multiple times per input usercmd depending on move partitioning. +============== +*/ +LOGFUNCTION_VOID( ModFNDefault_PostPmoveActions, ( pmove_t *pmove, int clientNum, int oldEventSequence ), + ( pmove, clientNum, oldEventSequence ), "G_MODFN_POSTPMOVEACTIONS" ) { + gentity_t *ent = &g_entities[clientNum]; + gclient_t *client = &level.clients[clientNum]; + + if ( client->ps.pm_type == PM_SPECTATOR ) { + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + + return; + } + + // save results of pmove + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy( pmove->mins, ent->r.mins ); + VectorCopy( pmove->maxs, ent->r.maxs ); + + ent->waterlevel = pmove->waterlevel; + ent->watertype = pmove->watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + if ( pmove->useEvent ) { + TryUse( ent ); + } + + // link entity now, after any personal teleporters have been used + trap_LinkEntity( ent ); + if ( !ent->client->noclip ) { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + //test for solid areas in the AAS file + BotTestSolid( ent->r.currentOrigin ); + + // touch other objects + ClientImpacts( ent, pmove ); +} + +typedef struct { + int clientNum; + int oldEventSequence; +} postMoveContext_t; + +/* +============== +G_PostPmoveCallback +============== +*/ +LOGFUNCTION_SVOID( G_PostPmoveCallback, ( pmove_t *pmove, qboolean finalFragment, void *context ), + ( pmove, finalFragment, context ), "" ) { + if ( finalFragment || modfn.AdjustPmoveConstant( PMC_PARTIAL_MOVE_TRIGGERS, 0 ) ) { + postMoveContext_t *pmc = (postMoveContext_t *)context; + modfn.PostPmoveActions( pmove, pmc->clientNum, pmc->oldEventSequence ); + pmc->oldEventSequence = level.clients[pmc->clientNum].ps.eventSequence; + } +} + +/* +============== +(ModFN) RunPlayerMove + +Performs player movement corresponding to a single input usercmd from the client. +============== +*/ +LOGFUNCTION_VOID( ModFNDefault_RunPlayerMove, ( int clientNum ), ( clientNum ), "G_MODFN_RUNPLAYERMOVE" ) { + gclient_t *client = &level.clients[clientNum]; + playerState_t *ps = &client->ps; + pmove_t pmove; + postMoveContext_t pmc = { 0 }; + + pmc.clientNum = clientNum; + pmc.oldEventSequence = ps->eventSequence; + + modfn.PmoveInit( clientNum, &pmove ); + Pmove( &pmove, modfn.AdjustPmoveConstant( PMC_FIXED_LENGTH, 0 ), G_PostPmoveCallback, &pmc ); +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + + modfn.RunPlayerMove( ent - g_entities ); + } + + // attack button cycles through spectators + if ( ( ucmd->buttons & BUTTON_ATTACK ) && ! ( client->pers.oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } + else if ( ( ucmd->buttons & BUTTON_ALT_ATTACK ) && ! ( client->pers.oldbuttons & BUTTON_ALT_ATTACK ) ) + { + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + StopFollowing( ent ); + } + } +} + /* ============== ClientThink @@ -1636,34 +1739,28 @@ once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { - int oldCommandTime = ent->client->ps.commandTime; - gclient_t *client; - pmove_t pm; - vec3_t oldOrigin; - int oldEventSequence; + int clientNum = ent - g_entities; + gclient_t *client = &level.clients[clientNum]; + int oldCommandTime = client->ps.commandTime; int msec; usercmd_t *ucmd; - client = ent->client; - // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } - // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; -// G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; -// G_Printf("serverTime >>>>>\n" ); } - if ( !PM_IsMoveNeeded( client->ps.commandTime, ucmd->serverTime, G_PmoveFixedValue() ) && + if ( !PM_IsMoveNeeded( client->ps.commandTime, ucmd->serverTime, modfn.AdjustPmoveConstant( PMC_FIXED_LENGTH, 0 ) ) && // following others may result in bad times, but we still want // to check for follow toggles client->sess.spectatorState != SPECTATOR_FOLLOW ) { @@ -1678,16 +1775,9 @@ void ClientThink_real( gentity_t *ent ) { return; } - // Don't move while under intro sequence. - if (client->ps.introTime > level.time) - { // Don't be visible either. - ent->s.eFlags |= EF_NODRAW; + // cancel out view angle changes during holodeck intro sequence + if ( client->ps.introTime > level.time ) { SetClientViewAngle( ent, ent->s.angles ); - ucmd->buttons = 0; - ucmd->weapon = 0; -// ucmd->angles[0] = ucmd->angles[1] = ucmd->angles[2] = 0; - ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; -// return; } // spectators don't do much @@ -1737,90 +1827,7 @@ void ClientThink_real( gentity_t *ent ) { client->ps.speed *= 0.75; } - // set up for pmove - oldEventSequence = client->ps.eventSequence; - - memset (&pm, 0, sizeof(pm)); - - pm.ps = &client->ps; - pm.cmd = *ucmd; - if ( pm.ps->pm_type == PM_DEAD ) { - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; - } - else { - pm.tracemask = MASK_PLAYERSOLID; - } - pm.trace = trap_Trace; - pm.pointcontents = trap_PointContents; - pm.debugLevel = g_debugMove.integer; - pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; - pm.pModDisintegration = g_pModDisintegration.integer > 0; - pm.noJumpKeySlowdown = g_noJumpKeySlowdown.integer ? qtrue : qfalse; - pm.bounceFix = qtrue; - pm.snapVectorGravLimit = SNAPVECTOR_GRAV_LIMIT; - pm.noFlyingDrift = qtrue; - pm.infilJumpFactor = g_infilJumpFactor.value; - pm.infilAirAccelFactor = g_infilAirAccelFactor.value; - if ( client->ps.weapon >= 1 && client->ps.weapon < WP_NUM_WEAPONS ) { - if ( client->sess.altSwapFlags & ( 1 << ( client->ps.weapon - 1 ) ) ) { - pm.altFireMode = ALTMODE_SWAPPED; - } - } - - VectorCopy( client->ps.origin, oldOrigin ); - - // perform a pmove - Pmove (&pm, G_PmoveFixedValue()); - - // save results of pmove - if ( ent->client->ps.eventSequence != oldEventSequence ) { - ent->eventTime = level.time; - } - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - - SendPendingPredictableEvents( &ent->client->ps ); - - // use the snapped origin for linking so it matches client predicted versions - VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - - VectorCopy (pm.mins, ent->r.mins); - VectorCopy (pm.maxs, ent->r.maxs); - - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; - - // execute client events - ClientEvents( ent, oldEventSequence ); - - if ( pm.useEvent ) - { //TODO: Use - TryUse( ent ); - } - - // link entity now, after any personal teleporters have been used - trap_LinkEntity (ent); - if ( !ent->client->noclip ) { - G_TouchTriggers( ent ); - } - - // NOTE: now copy the exact origin over otherwise clients can be snapped into solid - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - - //test for solid areas in the AAS file - BotTestSolid(ent->r.currentOrigin); - - // touch other objects - ClientImpacts( ent, &pm ); - - // save results of triggers and client events - if (ent->client->ps.eventSequence != oldEventSequence) { - ent->eventTime = level.time; - } - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - client->latched_buttons |= client->buttons & ~client->oldbuttons; + modfn.RunPlayerMove( clientNum ); // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { @@ -1891,6 +1898,7 @@ void ClientThink( int clientNum ) { gentity_t *ent; ent = g_entities + clientNum; + ent->client->pers.oldbuttons = ent->client->pers.cmd.buttons; trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); // mark the time we got info, so we can display the diff --git a/code/game/g_client.c b/code/game/g_client.c index 423210d..30d97e6 100644 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -2239,7 +2239,6 @@ void ClientSpawn(gentity_t *ent) { client->respawnTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; diff --git a/code/game/g_cvar_defs.h b/code/game/g_cvar_defs.h index 1d211c1..f0314cc 100644 --- a/code/game/g_cvar_defs.h +++ b/code/game/g_cvar_defs.h @@ -85,9 +85,4 @@ CVAR_DEF( g_random_skin_limit, "g_random_skin_limit", "4", CVAR_ARCHIVE, qfalse CVAR_DEF( g_noJoinTimeout, "g_noJoinTimeout", "120", CVAR_ARCHIVE, qfalse ) CVAR_DEF( g_classChangeDebounceTime, "g_classChangeDebounceTime", "180", CVAR_ARCHIVE, qfalse ) -CVAR_DEF( g_pMoveFixed, "g_pMoveFixed", "1", CVAR_ARCHIVE, qfalse ) -CVAR_DEF( g_pMoveMsec, "g_pMoveMsec", "8", CVAR_ARCHIVE, qfalse ) -CVAR_DEF( g_noJumpKeySlowdown, "g_noJumpKeySlowdown", "0", CVAR_ARCHIVE, qfalse ) -CVAR_DEF( g_infilJumpFactor, "g_infilJumpFactor", "0", CVAR_ARCHIVE, qfalse ) -CVAR_DEF( g_infilAirAccelFactor, "g_infilAirAccelFactor", "0", CVAR_ARCHIVE, qfalse ) CVAR_DEF( g_altSwapSupport, "g_altSwapSupport", "1", CVAR_ARCHIVE, qfalse ) diff --git a/code/game/g_local.h b/code/game/g_local.h index 842a3aa..db42705 100644 --- a/code/game/g_local.h +++ b/code/game/g_local.h @@ -255,6 +255,7 @@ typedef struct { typedef struct { clientConnected_t connected; usercmd_t cmd; // we would lose angles if not persistant + int oldbuttons; // buttons from previous command qboolean localClient; // true if "ip" info key is "localhost" qboolean initialSpawn; // the first spawn should be at a cool location qboolean predictItemPickup; // based on cg_predictItems userinfo @@ -283,9 +284,6 @@ struct gclient_s { int lastCmdTime; // level.time of last usercmd_t, for EF_CONNECTION // we can't just use pers.lastCommand.time, because // of the g_sycronousclients case - int buttons; - int oldbuttons; - int latched_buttons; // sum up damage over an entire frame, so // shotgun blasts give a single big kick @@ -594,7 +592,6 @@ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, cons // // g_main.c // -int G_PmoveFixedValue( void ); void G_UpdateModConfigInfo( void ); void G_RegisterTrackedCvar( trackedCvar_t *tc, const char *cvarName, const char *defaultValue, int flags, qboolean announceChanges ); void G_RegisterCvarCallback( trackedCvar_t *tc, void ( *callback )( trackedCvar_t *tc ), qboolean callNow ); diff --git a/code/game/g_main.c b/code/game/g_main.c index 7b3b0d9..03a03fd 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -259,21 +259,6 @@ void G_FindTeams( void ) { G_Printf ("%i teams with %i entities\n", c, c2); } -/* -================= -G_PmoveFixedValue - -Returns fixed frame length control value used by pmove and shared with clients via configstring. -================= -*/ -int G_PmoveFixedValue( void ) { - if ( g_pMoveFixed.integer && g_pMoveMsec.integer > 0 && g_pMoveMsec.integer < 35 ) { - return g_pMoveMsec.integer; - } - - return 0; -} - // Only set mod config after initialization is complete to avoid unnecessary configstring updates. static qboolean modConfigReady = qfalse; @@ -292,30 +277,6 @@ void G_UpdateModConfigInfo( void ) { modfn.AddModConfigInfo( info ); - { - int pMoveFixed = G_PmoveFixedValue(); - if ( pMoveFixed ) { - Info_SetValueForKey( info, "pMoveFixed", va( "%i", pMoveFixed ) ); - } - } - - if ( g_noJumpKeySlowdown.integer ) { - Info_SetValueForKey( info, "noJumpKeySlowdown", "1" ); - } - - if ( g_infilJumpFactor.value > 0.0f ) { - Info_SetValueForKey( info, "infilJumpFactor", va( "%f", g_infilJumpFactor.value ) ); - } - - if ( g_infilAirAccelFactor.value > 0.0f ) { - Info_SetValueForKey( info, "infilAirAccelFactor", va( "%f", g_infilAirAccelFactor.value ) ); - } - - // general stuff that is just automatically enabled - Info_SetValueForKey( info, "bounceFix", "1" ); - Info_SetValueForKey( info, "snapVectorGravLimit", SNAPVECTOR_GRAV_LIMIT_STR ); - Info_SetValueForKey( info, "noFlyingDrift", "1" ); - if ( g_altSwapSupport.integer ) { Info_SetValueForKey( info, "altSwapSupport", "1" ); } @@ -440,11 +401,6 @@ static void G_RegisterCvars( void ) { G_RegisterCvarCallback( &g_password, G_UpdateNeedPass, qtrue ); // handle mod config cvar changes - G_RegisterCvarCallback( &g_pMoveFixed, G_UpdateModConfigCvar, qfalse ); - G_RegisterCvarCallback( &g_pMoveMsec, G_UpdateModConfigCvar, qfalse ); - G_RegisterCvarCallback( &g_noJumpKeySlowdown, G_UpdateModConfigCvar, qfalse ); - G_RegisterCvarCallback( &g_infilJumpFactor, G_UpdateModConfigCvar, qfalse ); - G_RegisterCvarCallback( &g_infilAirAccelFactor, G_UpdateModConfigCvar, qfalse ); G_RegisterCvarCallback( &g_altSwapSupport, G_UpdateModConfigCvar, qfalse ); } diff --git a/code/game/mods/features/feature_player_move.c b/code/game/mods/features/feature_player_move.c new file mode 100644 index 0000000..906bc93 --- /dev/null +++ b/code/game/mods/features/feature_player_move.c @@ -0,0 +1,144 @@ +/* +* Player Move Features +* +* This module provides some additional cvar options to customize player movement. +* +* g_pMoveFixed - enables fps-neutral physics (0 = disabled, 1 = enabled) +* g_pMoveMsec - frame length for fps-neutral physics (e.g. 8 = 125fps, 3 = 333fps) +* g_pMoveTriggerMode - process touch triggers after every move fragment (0 = disabled, 1 = enabled) +* This prevents lag tricks to drop through kill areas in certain maps. +* g_noJumpKeySlowdown - don't reduce acceleration when jump key is held midair (0 = disabled, 1 = enabled) +* In the original game, holding the jump key for longer when jumping reduces stafe jumping +* effectiveness. Enabling this options cancels this effect so strafe jumping has full +* acceleration regardless of how long the jump key is held. +*/ + +#include "mods/g_mod_local.h" + +#define PREFIX( x ) ModPlayerMove_##x +#define MOD_STATE PREFIX( state ) + +#define SNAPVECTOR_GRAV_LIMIT 100 +#define SNAPVECTOR_GRAV_LIMIT_STR "100" + +static struct { + trackedCvar_t g_pMoveFixed; + trackedCvar_t g_pMoveMsec; + trackedCvar_t g_pMoveTriggerMode; + trackedCvar_t g_noJumpKeySlowdown; + + // For mod function stacking + ModFNType_AdjustPmoveConstant Prev_AdjustPmoveConstant; + ModFNType_PmoveInit Prev_PmoveInit; + ModFNType_AddModConfigInfo Prev_AddModConfigInfo; +} *MOD_STATE; + +/* +================== +(ModFN) AdjustPmoveConstant +================== +*/ +LOGFUNCTION_SRET( int, PREFIX(AdjustPmoveConstant), ( pmoveConstant_t pmcType, int defaultValue ), + ( pmcType, defaultValue ), "G_MODFN_ADJUSTPMOVECONSTANT" ) { + if ( pmcType == PMC_FIXED_LENGTH && MOD_STATE->g_pMoveFixed.integer && + MOD_STATE->g_pMoveMsec.integer > 0 && MOD_STATE->g_pMoveMsec.integer < 35 ) { + return MOD_STATE->g_pMoveMsec.integer; + } + if ( pmcType == PMC_PARTIAL_MOVE_TRIGGERS ) { + return MOD_STATE->g_pMoveTriggerMode.integer ? 1 : 0; + } + + return MOD_STATE->Prev_AdjustPmoveConstant( pmcType, defaultValue ); +} + +/* +================ +(ModFN) PmoveInit +================ +*/ +LOGFUNCTION_SVOID( PREFIX(PmoveInit), ( int clientNum, pmove_t *pmove ), + ( clientNum, pmove ), "G_MODFN_PMOVEINIT" ) { + MOD_STATE->Prev_PmoveInit( clientNum, pmove ); + + if ( MOD_STATE->g_noJumpKeySlowdown.integer ) { + pmove->noJumpKeySlowdown = qtrue; + } + + // general stuff that is just automatically enabled + pmove->bounceFix = qtrue; + pmove->snapVectorGravLimit = SNAPVECTOR_GRAV_LIMIT; + pmove->noSpectatorDrift = qtrue; // currently no client prediction, as it isn't really needed + pmove->noFlyingDrift = qtrue; +} + +/* +============== +(ModFN) AddModConfigInfo +============== +*/ +LOGFUNCTION_SVOID( PREFIX(AddModConfigInfo), ( char *info ), ( info ), "G_MODFN_ADDMODCONFIGINFO" ) { + int pMoveFixed = modfn.AdjustPmoveConstant( PMC_FIXED_LENGTH, 0 ); + + if ( pMoveFixed ) { + Info_SetValueForKey( info, "pMoveFixed", va( "%i", pMoveFixed ) ); + } + + if ( MOD_STATE->g_pMoveTriggerMode.integer ) { + Info_SetValueForKey( info, "pMoveTriggerMode", "1" ); + } + + if ( MOD_STATE->g_noJumpKeySlowdown.integer ) { + Info_SetValueForKey( info, "noJumpKeySlowdown", "1" ); + } + + // general stuff that is just automatically enabled + Info_SetValueForKey( info, "bounceFix", "1" ); + Info_SetValueForKey( info, "snapVectorGravLimit", SNAPVECTOR_GRAV_LIMIT_STR ); + Info_SetValueForKey( info, "noFlyingDrift", "1" ); + + MOD_STATE->Prev_AddModConfigInfo( info ); +} + +/* +============== +ModPlayerMove_ModcfgCvarChanged + +Called when any of the cvars potentially affecting the content of the mod configstring have changed. +============== +*/ +static void ModPlayerMove_ModcfgCvarChanged( trackedCvar_t *cvar ) { + G_UpdateModConfigInfo(); +} + +/* +================ +ModPlayerMove_Init +================ +*/ + +#define INIT_FN_STACKABLE( name ) \ + MOD_STATE->Prev_##name = modfn.name; \ + modfn.name = PREFIX(name); + +#define INIT_FN_OVERRIDE( name ) \ + modfn.name = PREFIX(name); + +LOGFUNCTION_VOID( ModPlayerMove_Init, ( void ), (), "G_MOD_INIT" ) { + if ( !MOD_STATE ) { + MOD_STATE = G_Alloc( sizeof( *MOD_STATE ) ); + + INIT_FN_STACKABLE( AdjustPmoveConstant ); + INIT_FN_STACKABLE( PmoveInit ); + INIT_FN_STACKABLE( AddModConfigInfo ); + + G_RegisterTrackedCvar( &MOD_STATE->g_pMoveFixed, "g_pMoveFixed", "1", CVAR_ARCHIVE, qfalse ); + G_RegisterTrackedCvar( &MOD_STATE->g_pMoveMsec, "g_pMoveMsec", "8", CVAR_ARCHIVE, qfalse ); + G_RegisterTrackedCvar( &MOD_STATE->g_pMoveTriggerMode, "g_pMoveTriggerMode", "1", CVAR_ARCHIVE, qfalse ); + G_RegisterTrackedCvar( &MOD_STATE->g_noJumpKeySlowdown, "g_noJumpKeySlowdown", "1", CVAR_ARCHIVE, qfalse ); + + G_RegisterCvarCallback( &MOD_STATE->g_pMoveFixed, ModPlayerMove_ModcfgCvarChanged, qfalse ); + G_RegisterCvarCallback( &MOD_STATE->g_pMoveMsec, ModPlayerMove_ModcfgCvarChanged, qfalse ); + G_RegisterCvarCallback( &MOD_STATE->g_pMoveTriggerMode, ModPlayerMove_ModcfgCvarChanged, qfalse ); + G_RegisterCvarCallback( &MOD_STATE->g_noJumpKeySlowdown, ModPlayerMove_ModcfgCvarChanged, qfalse ); + } +} diff --git a/code/game/mods/g_mod_defs.h b/code/game/mods/g_mod_defs.h index 81a8fb2..09a90aa 100644 --- a/code/game/mods/g_mod_defs.h +++ b/code/game/mods/g_mod_defs.h @@ -23,6 +23,23 @@ MOD_FUNCTION_DEF( GeneralInit, void, ( void ) ) // Called after G_RunFrame completes. MOD_FUNCTION_DEF( PostRunFrame, void, ( void ) ) +////////////////////////// +// player movement +////////////////////////// + +// Support modifying pmove-related integer constants. +MOD_FUNCTION_DEF( AdjustPmoveConstant, int, ( pmoveConstant_t pmcType, int defaultValue ) ) + +// Initialize pmove_t structure ahead of player move. +MOD_FUNCTION_DEF( PmoveInit, void, ( int clientNum, pmove_t *pmove ) ) + +// Performs player movement corresponding to a single input usercmd from the client. +MOD_FUNCTION_DEF( RunPlayerMove, void, ( int clientNum ) ) + +// Process triggers and other operations after player move(s) have completed. +// This may be called 0, 1, or multiple times per input usercmd depending on move partitioning. +MOD_FUNCTION_DEF( PostPmoveActions, void, ( pmove_t *pmove, int clientNum, int oldEventSequence ) ) + ////////////////////////// // misc ////////////////////////// diff --git a/code/game/mods/g_mod_local.h b/code/game/mods/g_mod_local.h index 33385e8..bf04de5 100644 --- a/code/game/mods/g_mod_local.h +++ b/code/game/mods/g_mod_local.h @@ -8,3 +8,6 @@ // Utils int G_ModUtils_GetLatchedValue( const char *cvar_name, const char *default_value, int flags ); + +// Feature Initialization +void ModPlayerMove_Init( void ); diff --git a/code/game/mods/g_mod_main.c b/code/game/mods/g_mod_main.c index bfad0ce..4233add 100644 --- a/code/game/mods/g_mod_main.c +++ b/code/game/mods/g_mod_main.c @@ -26,4 +26,9 @@ LOGFUNCTION_VOID( G_ModsInit, ( void ), (), "G_MOD_INIT" ) { if ( modsEnabled <= 0 ) { return; } + + if ( modsEnabled >= 2 ) { + // Default mods + ModPlayerMove_Init(); + } } diff --git a/code/game/mods/g_mod_public.h b/code/game/mods/g_mod_public.h index 68af21a..3a4b560 100644 --- a/code/game/mods/g_mod_public.h +++ b/code/game/mods/g_mod_public.h @@ -7,6 +7,12 @@ void G_ModCoreInit( void ); void G_ModsInit( void ); +typedef enum { + PMC_NONE, + PMC_FIXED_LENGTH, // subdivide moves into this frame length (0 = disabled) + PMC_PARTIAL_MOVE_TRIGGERS, // if 1, process triggers after each subdivided move fragment +} pmoveConstant_t; + // // mod functions (modfn.*) // diff --git a/code/game/mods/g_mod_stubs.c b/code/game/mods/g_mod_stubs.c index 0efdc0c..74bcdf3 100644 --- a/code/game/mods/g_mod_stubs.c +++ b/code/game/mods/g_mod_stubs.c @@ -15,6 +15,11 @@ LOGFUNCTION_VOID( ModFNDefault_PostRunFrame, ( void ), (), "G_MODFN_POSTRUNFRAME" ) { } +LOGFUNCTION_RET( int, ModFNDefault_AdjustPmoveConstant, ( pmoveConstant_t pmcType, int defaultValue ), + ( pmcType, defaultValue ), "G_MODFN_ADJUSTPMOVECONSTANT" ) { + return defaultValue; +} + LOGFUNCTION_VOID( ModFNDefault_AddModConfigInfo, ( char *info ), ( info ), "G_MODFN_ADDMODCONFIGINFO" ) { }