diff --git a/build/msvc_2019/game.vcxproj b/build/msvc_2019/game.vcxproj
index b545c9a..8eed06d 100644
--- a/build/msvc_2019/game.vcxproj
+++ b/build/msvc_2019/game.vcxproj
@@ -201,12 +201,16 @@
+
+
+
+
@@ -249,6 +253,11 @@
+
+
+
+
+
diff --git a/build/msvc_2019/game.vcxproj.filters b/build/msvc_2019/game.vcxproj.filters
index 2c327f2..a2506f3 100644
--- a/build/msvc_2019/game.vcxproj.filters
+++ b/build/msvc_2019/game.vcxproj.filters
@@ -7,6 +7,12 @@
{9F35977C-8B6C-980D-3459-7E10206F140F}
+
+ {81BBD40A-ED25-8981-3664-3A27A20D67D6}
+
+
+ {CF5F3109-BB43-F25E-24A4-ECB110A7DCE0}
+
@@ -78,6 +84,9 @@
game
+
+ game
+
game
@@ -96,6 +105,15 @@
game
+
+ game\mods
+
+
+ game\mods
+
+
+ game\mods
+
game
@@ -218,5 +236,20 @@
game
+
+ game\mods\features
+
+
+ game\mods\features
+
+
+ game\mods
+
+
+ game\mods
+
+
+ game\mods
+
\ No newline at end of file
diff --git a/build/qvm_build/build_game.bat b/build/qvm_build/build_game.bat
index aca0e76..ded0c15 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_altswap_handler&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/game/g_active.c b/code/game/g_active.c
index 17997df..23fefe0 100644
--- a/code/game/g_active.c
+++ b/code/game/g_active.c
@@ -1593,12 +1593,6 @@ LOGFUNCTION_VOID( ModFNDefault_PmoveInit, ( int clientNum, pmove_t *pmove ), ( c
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;
- }
- }
}
/*
diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c
index 2a112e6..16452f7 100644
--- a/code/game/g_cmds.c
+++ b/code/game/g_cmds.c
@@ -1643,17 +1643,6 @@ void Cmd_SetViewpos_f( gentity_t *ent ) {
TeleportPlayer( ent, origin, angles, TP_NORMAL );
}
-/*
-=================
-Cmd_SetAltSwap_f
-=================
-*/
-void Cmd_SetAltSwap_f( gentity_t *ent ) {
- char buffer[MAX_TOKEN_CHARS];
- trap_Argv( 1, buffer, sizeof( buffer ) );
- ent->client->sess.altSwapFlags = atoi( buffer );
-}
-
/*
=================
@@ -1672,6 +1661,11 @@ void ClientCommand( int clientNum ) {
trap_Argv( 0, cmd, sizeof( cmd ) );
+ // Check if any mods have special handling of this command
+ if ( modfn.ModClientCommand( clientNum, cmd ) ) {
+ return;
+ }
+
if (Q_stricmp (cmd, "say") == 0) {
Cmd_Say_f (ent, SAY_ALL, qfalse);
return;
@@ -1732,8 +1726,6 @@ void ClientCommand( int clientNum ) {
Cmd_GameCommand_f( ent );
else if (Q_stricmp (cmd, "setviewpos") == 0)
Cmd_SetViewpos_f( ent );
- else if (Q_stricmp (cmd, "setaltswap") == 0)
- Cmd_SetAltSwap_f( ent );
else
trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) );
}
diff --git a/code/game/g_cvar_defs.h b/code/game/g_cvar_defs.h
index f0314cc..533635b 100644
--- a/code/game/g_cvar_defs.h
+++ b/code/game/g_cvar_defs.h
@@ -84,5 +84,3 @@ CVAR_DEF( g_team_group_blue, "g_team_group_blue", "", CVAR_LATCH, qfalse ) // U
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_altSwapSupport, "g_altSwapSupport", "1", CVAR_ARCHIVE, qfalse )
diff --git a/code/game/g_local.h b/code/game/g_local.h
index 40a9bc9..697fc8e 100644
--- a/code/game/g_local.h
+++ b/code/game/g_local.h
@@ -249,7 +249,6 @@ typedef struct {
spectatorState_t spectatorState;
int spectatorClient; // for chasecam and follow mode
int wins, losses; // tournament stats
- int altSwapFlags; // for alt-fire swapping
} clientSession_t;
#define MAX_VOTE_COUNT 3
@@ -362,6 +361,7 @@ typedef struct {
// we changed gametype
qboolean restarted; // waiting for a map_restart to fire
+ qboolean hasRestarted; // whether any map_restart has been done since map was loaded
int numConnectedClients;
int numNonSpectatorClients; // includes connecting clients
diff --git a/code/game/g_main.c b/code/game/g_main.c
index 03a03fd..e595071 100644
--- a/code/game/g_main.c
+++ b/code/game/g_main.c
@@ -277,10 +277,6 @@ void G_UpdateModConfigInfo( void ) {
modfn.AddModConfigInfo( info );
- if ( g_altSwapSupport.integer ) {
- Info_SetValueForKey( info, "altSwapSupport", "1" );
- }
-
if ( *info ) {
trap_SetConfigstring( CS_MOD_CONFIG, buffer );
} else {
@@ -289,17 +285,6 @@ void G_UpdateModConfigInfo( void ) {
}
}
-/*
-=================
-G_UpdateModConfigCvar
-
-Called when a cvar linked to a mod config value has changed.
-=================
-*/
-static void G_UpdateModConfigCvar( trackedCvar_t *cv ) {
- G_UpdateModConfigInfo();
-}
-
/*
=================
G_UpdateNeedPass
@@ -399,9 +384,6 @@ static void G_RegisterCvars( void ) {
// configure g_needpass auto update
G_RegisterCvarCallback( &g_password, G_UpdateNeedPass, qtrue );
-
- // handle mod config cvar changes
- G_RegisterCvarCallback( &g_altSwapSupport, G_UpdateModConfigCvar, qfalse );
}
/*
@@ -476,6 +458,7 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) {
level.time = levelTime;
level.startTime = levelTime;
level.restarted = restart;
+ level.hasRestarted = restart;
level.snd_fry = G_SoundIndex("sound/player/fry.wav"); // FIXME standing in lava / slime
diff --git a/code/game/g_session.c b/code/game/g_session.c
index 9c7c19e..70219b6 100644
--- a/code/game/g_session.c
+++ b/code/game/g_session.c
@@ -71,15 +71,14 @@ void G_WriteClientSessionData( gclient_t *client ) {
const char *var;
info_string_t info;
- s = va("%i %i %i %i %i %i %i %i",
+ s = va("%i %i %i %i %i %i %i",
client->sess.sessionTeam,
client->sess.sessionClass,
client->sess.spectatorTime,
client->sess.spectatorState,
client->sess.spectatorClient,
client->sess.wins,
- client->sess.losses,
- client->sess.altSwapFlags
+ client->sess.losses
);
var = va( "session%i", clientNum );
@@ -128,15 +127,14 @@ void G_ReadSessionData( gclient_t *client ) {
var = va( "session%i", clientNum );
trap_Cvar_VariableStringBuffer( var, s, sizeof(s) );
- sscanf( s, "%i %i %i %i %i %i %i %i",
+ sscanf( s, "%i %i %i %i %i %i %i",
&client->sess.sessionTeam,
&client->sess.sessionClass,
&client->sess.spectatorTime,
&client->sess.spectatorState,
&client->sess.spectatorClient,
&client->sess.wins,
- &client->sess.losses,
- &client->sess.altSwapFlags
+ &client->sess.losses
);
// Call mod initialization
diff --git a/code/game/mods/features/feature_altswap_handler.c b/code/game/mods/features/feature_altswap_handler.c
new file mode 100644
index 0000000..c73bd5e
--- /dev/null
+++ b/code/game/mods/features/feature_altswap_handler.c
@@ -0,0 +1,186 @@
+/*
+* Alt Fire Swap Handler
+*
+* This module implements the "setAltSwap" command which is used by the cgame-based
+* alt fire swapping system.
+*
+* This is mainly intended as a backup option for cases where neither server or client
+* engine support for alt fire swapping is available. It is only enabled in local games
+* or if sv_floodProtect is disabled on the server, because otherwise the flood protection
+* could cause the command to be dropped leading to an inconsistent state.
+*/
+
+#include "mods/g_mod_local.h"
+
+#define PREFIX( x ) ModAltSwapHandler_##x
+#define MOD_STATE PREFIX( state )
+
+typedef struct {
+ int swapFlags;
+} AltFireSwap_client_t;
+
+static struct {
+ qboolean enabled;
+ AltFireSwap_client_t clients[MAX_CLIENTS];
+
+ // For mod function stacking
+ ModFNType_InitClientSession Prev_InitClientSession;
+ ModFNType_GenerateClientSessionInfo Prev_GenerateClientSessionInfo;
+ ModFNType_ModClientCommand Prev_ModClientCommand;
+ ModFNType_PmoveInit Prev_PmoveInit;
+ ModFNType_AddModConfigInfo Prev_AddModConfigInfo;
+ ModFNType_PostRunFrame Prev_PostRunFrame;
+} *MOD_STATE;
+
+#define DEDICATED_SERVER ( trap_Cvar_VariableIntegerValue( "dedicated" ) || !trap_Cvar_VariableIntegerValue( "cl_running" ) )
+
+/*
+================
+ModAltSwapHandler_CheckEnabled
+================
+*/
+static void ModAltSwapHandler_CheckEnabled( void ) {
+ qboolean enabled = qtrue;
+
+ // Disable swap support if flood protection is enabled because it can cause commands to be dropped
+ if ( trap_Cvar_VariableIntegerValue( "sv_floodProtect" ) && DEDICATED_SERVER ) {
+ enabled = qfalse;
+ }
+
+ if ( enabled != MOD_STATE->enabled ) {
+ MOD_STATE->enabled = enabled;
+ G_UpdateModConfigInfo();
+ if ( !enabled ) {
+ memset( MOD_STATE->clients, 0, sizeof( MOD_STATE->clients ) );
+ }
+ }
+}
+
+/*
+================
+(ModFN) InitClientSession
+================
+*/
+LOGFUNCTION_SVOID( PREFIX(InitClientSession), ( int clientNum, qboolean initialConnect, const info_string_t *info ),
+ ( clientNum, initialConnect, info ), "G_MODFN_INITCLIENTSESSION" ) {
+ AltFireSwap_client_t *modclient = &MOD_STATE->clients[clientNum];
+
+ MOD_STATE->Prev_InitClientSession( clientNum, initialConnect, info );
+ memset( modclient, 0, sizeof( *modclient ) );
+
+ if ( !initialConnect && level.hasRestarted ) {
+ // Restore flags when coming back from a map restart
+ modclient->swapFlags = atoi ( Info_ValueForKey( info->s, "altSwapFlags" ) );
+ }
+}
+
+/*
+================
+(ModFN) GenerateClientSessionInfo
+================
+*/
+LOGFUNCTION_SVOID( PREFIX(GenerateClientSessionInfo), ( int clientNum, info_string_t *info ),
+ ( clientNum, info ), "G_MODFN_GENERATECLIENTSESSIONINFO" ) {
+ AltFireSwap_client_t *modclient = &MOD_STATE->clients[clientNum];
+
+ MOD_STATE->Prev_GenerateClientSessionInfo( clientNum, info );
+
+ if ( modclient->swapFlags ) {
+ Info_SetValueForKey_Big( info->s, "altSwapFlags", va( "%i", modclient->swapFlags ) );
+ }
+}
+
+/*
+================
+(ModFN) ModClientCommand
+
+Handle setAltSwap command.
+================
+*/
+LOGFUNCTION_SRET( qboolean, PREFIX(ModClientCommand), ( int clientNum, const char *cmd ),
+ ( clientNum, cmd ), "G_MODFN_MODCLIENTCOMMAND" ) {
+ if ( MOD_STATE->enabled && !Q_stricmp( cmd, "setAltSwap" ) ) {
+ AltFireSwap_client_t *modclient = &MOD_STATE->clients[clientNum];
+ char buffer[32];
+ trap_Argv( 1, buffer, sizeof( buffer ) );
+ modclient->swapFlags = atoi( buffer );
+ return qtrue;
+ }
+
+ return MOD_STATE->Prev_ModClientCommand( clientNum, cmd );
+}
+
+/*
+================
+(ModFN) PmoveInit
+================
+*/
+LOGFUNCTION_SVOID( PREFIX(PmoveInit), ( int clientNum, pmove_t *pmove ),
+ ( clientNum, pmove ), "G_MODFN_PMOVEINIT" ) {
+ playerState_t *ps = &level.clients[clientNum].ps;
+
+ MOD_STATE->Prev_PmoveInit( clientNum, pmove );
+
+ if ( MOD_STATE->enabled && ps->weapon >= 1 && ps->weapon < WP_NUM_WEAPONS ) {
+ AltFireSwap_client_t *modclient = &MOD_STATE->clients[clientNum];
+ if ( modclient->swapFlags & ( 1 << ( ps->weapon - 1 ) ) ) {
+ pmove->altFireMode = ALTMODE_SWAPPED;
+ }
+ }
+}
+
+/*
+==============
+(ModFN) AddModConfigInfo
+==============
+*/
+LOGFUNCTION_SVOID( PREFIX(AddModConfigInfo), ( char *info ), ( info ), "G_MODFN_ADDMODCONFIGINFO" ) {
+ if ( MOD_STATE->enabled ) {
+ Info_SetValueForKey( info, "altSwapSupport", "1" );
+ }
+
+ MOD_STATE->Prev_AddModConfigInfo( info );
+}
+
+/*
+================
+(ModFN) PostRunFrame
+================
+*/
+LOGFUNCTION_SVOID( PREFIX(PostRunFrame), ( void ), (), "G_MODFN_POSTRUNFRAME" ) {
+ MOD_STATE->Prev_PostRunFrame();
+ ModAltSwapHandler_CheckEnabled();
+}
+
+/*
+================
+ModAltSwapHandler_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( ModAltSwapHandler_Init, ( void ), (), "G_MOD_INIT" ) {
+ // Don't enable if server engine has its own alt swap handler
+ if ( VMExt_GVCommandInt( "sv_support_setAltSwap", 0 ) ) {
+ return;
+ }
+
+ if ( !MOD_STATE ) {
+ MOD_STATE = G_Alloc( sizeof( *MOD_STATE ) );
+
+ INIT_FN_STACKABLE( InitClientSession );
+ INIT_FN_STACKABLE( GenerateClientSessionInfo );
+ INIT_FN_STACKABLE( ModClientCommand );
+ INIT_FN_STACKABLE( PmoveInit );
+ INIT_FN_STACKABLE( AddModConfigInfo );
+ INIT_FN_STACKABLE( PostRunFrame );
+
+ ModAltSwapHandler_CheckEnabled();
+ }
+}
diff --git a/code/game/mods/g_mod_defs.h b/code/game/mods/g_mod_defs.h
index 7abde15..fa9763c 100644
--- a/code/game/mods/g_mod_defs.h
+++ b/code/game/mods/g_mod_defs.h
@@ -55,5 +55,8 @@ MOD_FUNCTION_DEF( PostPmoveActions, void, ( pmove_t *pmove, int clientNum, int o
// misc
//////////////////////////
+// Allows mods to handle client commands. Returns qtrue to suspend normal handling of command.
+MOD_FUNCTION_DEF( ModClientCommand, qboolean, ( int clientNum, const char *cmd ) )
+
// Allows mods to add values to the mod config configstring.
MOD_FUNCTION_DEF( AddModConfigInfo, void, ( char *info ) )
diff --git a/code/game/mods/g_mod_local.h b/code/game/mods/g_mod_local.h
index bf04de5..825cfb3 100644
--- a/code/game/mods/g_mod_local.h
+++ b/code/game/mods/g_mod_local.h
@@ -11,3 +11,4 @@ int G_ModUtils_GetLatchedValue( const char *cvar_name, const char *default_value
// Feature Initialization
void ModPlayerMove_Init( void );
+void ModAltSwapHandler_Init( void );
diff --git a/code/game/mods/g_mod_main.c b/code/game/mods/g_mod_main.c
index 4233add..4198967 100644
--- a/code/game/mods/g_mod_main.c
+++ b/code/game/mods/g_mod_main.c
@@ -30,5 +30,6 @@ LOGFUNCTION_VOID( G_ModsInit, ( void ), (), "G_MOD_INIT" ) {
if ( modsEnabled >= 2 ) {
// Default mods
ModPlayerMove_Init();
+ ModAltSwapHandler_Init();
}
}
diff --git a/code/game/mods/g_mod_stubs.c b/code/game/mods/g_mod_stubs.c
index 74bcdf3..7512865 100644
--- a/code/game/mods/g_mod_stubs.c
+++ b/code/game/mods/g_mod_stubs.c
@@ -20,6 +20,11 @@ LOGFUNCTION_RET( int, ModFNDefault_AdjustPmoveConstant, ( pmoveConstant_t pmcTyp
return defaultValue;
}
+LOGFUNCTION_RET( qboolean, ModFNDefault_ModClientCommand, ( int clientNum, const char *cmd ),
+ ( clientNum, cmd ), "G_MODFN_MODCLIENTCOMMAND" ) {
+ return qfalse;
+}
+
LOGFUNCTION_VOID( ModFNDefault_AddModConfigInfo, ( char *info ),
( info ), "G_MODFN_ADDMODCONFIGINFO" ) {
}