summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIronClawTrem <louie.nutman@gmail.com>2019-11-01 23:02:33 +0000
committerIronClawTrem <louie.nutman@gmail.com>2019-11-01 23:02:33 +0000
commite136b8c5d070d14721585263b41a325794030f7d (patch)
tree010b119af44bc31ad15feafd9897d244c6804509
parent3235a157fc8511c91008c9f5119775685443f338 (diff)
schachtmeister and autobahn implementation
-rw-r--r--src/game/g_admin.c243
-rw-r--r--src/game/g_admin.h15
-rw-r--r--src/game/g_client.c80
-rw-r--r--src/game/g_cmds.c10
-rw-r--r--src/game/g_local.h5
-rw-r--r--src/game/g_main.c13
-rw-r--r--src/game/g_svcmds.c30
7 files changed, 389 insertions, 7 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c
index 45c37e4..dc19530 100644
--- a/src/game/g_admin.c
+++ b/src/game/g_admin.c
@@ -421,6 +421,11 @@ g_admin_cmd_t g_admin_cmds[ ] =
{"tklog", G_admin_tklog, "tklog",
"list recent teamkill activity",
"(^5start id#|name|-skip#^7) (^5search skip#^7)"
+ },
+
+ {"sm", G_admin_sm, "schachtmeister",
+ "Schachtmeister",
+ "..."
}
};
@@ -1471,6 +1476,9 @@ qboolean G_admin_cmd_check( gentity_t *ent, qboolean say )
return qtrue;
}
+ if( G_admin_is_restricted( ent, qtrue ) )
+ return qtrue;
+
for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
{
if( Q_stricmp( cmd, g_admin_commands[ i ]->command ) )
@@ -1519,11 +1527,49 @@ void G_admin_namelog_cleanup( )
for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
{
+ if( g_admin_namelog[ i ]->smj.comment )
+ G_Free( g_admin_namelog[ i ]->smj.comment );
G_Free( g_admin_namelog[ i ] );
g_admin_namelog[ i ] = NULL;
}
}
+static void dispatchSchachtmeisterIPAQuery( const char *ipa )
+{
+ trap_SendConsoleCommand( EXEC_APPEND, va( "smq ipa \"%s\"\n", ipa ) );
+}
+
+static void schachtmeisterProcess( g_admin_namelog_t *namelog )
+{
+ schachtmeisterJudgement_t *j = &namelog->smj;
+
+ if( !j->ratingTime || level.time - j->ratingTime >= 600000 )
+ {
+ if( j->queryTime )
+ return;
+
+ j->queryTime = level.time;
+ goto dispatch;
+ }
+
+ if( !j->queryTime || level.time - j->queryTime >= 60000 )
+ return;
+
+ if( j->dispatchTime && level.time - j->dispatchTime < 5000 )
+ return;
+
+ dispatch:
+ j->dispatchTime = level.time;
+ dispatchSchachtmeisterIPAQuery( namelog->ip );
+}
+
+void G_admin_schachtmeisterFrame( void )
+{
+ int i;
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ schachtmeisterProcess( g_admin_namelog[ i ] );
+}
+
void G_admin_namelog_update( gclient_t *client, qboolean disconnect )
{
int i, j;
@@ -1659,6 +1705,7 @@ void G_admin_namelog_update( gclient_t *client, qboolean disconnect )
Q_strncpyz( namelog->name[ 0 ], client->pers.netname,
sizeof( namelog->name[ 0 ] ) );
namelog->slot = ( disconnect ) ? -1 : clientNum;
+ schachtmeisterProcess( namelog );
g_admin_namelog[ i ] = namelog;
}
@@ -2370,7 +2417,8 @@ static AdminFlagListEntry_t adminFlagList[] =
{ ADMF_NOSCRIMRESTRICTION, "team joining, vote and chat restrictions during scrims do not apply" },
{ ADMF_NO_BUILD, "can not build" },
{ ADMF_NO_CHAT, "can not talk" },
- { ADMF_NO_VOTE, "can not call votes" }
+ { ADMF_NO_VOTE, "can not call votes" },
+ { ADMF_NOAUTOBAHN, "ignored by the Autobahn system" }
};
static int adminNumFlags= sizeof( adminFlagList ) / sizeof( adminFlagList[ 0 ] );
@@ -2930,6 +2978,102 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
return qtrue;
}
+// If true then don't let the player join a team, use the chat or run commands.
+qboolean G_admin_is_restricted(gentity_t *ent, qboolean sendMessage)
+{
+ schachtmeisterJudgement_t *j = NULL;
+ int i;
+
+ // Never restrict admins or whitelisted players.
+ if (G_admin_permission(ent, ADMF_NOAUTOBAHN) ||
+ G_admin_permission(ent, ADMF_IMMUNITY))
+ return qfalse;
+
+ // Find the relevant namelog.
+ // FIXME: this shouldn't require looping over *all* namelogs.
+ for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; i++) {
+ if (g_admin_namelog[i]->slot == ent - g_entities) {
+ j = &g_admin_namelog[i]->smj;
+ break;
+ }
+ }
+
+ // A missing namelog shouldn't happen.
+ if (!j)
+ return qfalse;
+
+ // Restrictions concern only unrated players.
+ if (j->ratingTime)
+ return qfalse;
+
+ // Don't wait forever, allow up to 15 seconds.
+ if (level.time - j->queryTime >= 15000)
+ return qfalse;
+
+ if (sendMessage)
+ trap_SendServerCommand(ent - g_entities, "print \"Please wait a moment before doing anything.\n\"");
+
+ return qtrue;
+}
+
+static void admin_autobahn(gentity_t *ent, int rating)
+{
+ // Allow per-GUID exceptions and never autoban admins.
+ if (G_admin_permission(ent, ADMF_NOAUTOBAHN) ||
+ G_admin_permission(ent, ADMF_IMMUNITY))
+ return;
+
+ // Don't do anything if the rating is clear.
+ if (rating >= g_schachtmeisterClearThreshold.integer)
+ return;
+
+ // Ban only if the rating is low enough.
+ if (rating > g_schachtmeisterAutobahnThreshold.integer) {
+ G_AdminsPrintf("%s^7 (#%d) has rating %d\n",
+ ent->client->pers.netname, ent - g_entities,
+ rating);
+ return;
+ }
+
+ G_LogAutobahn(ent, NULL, rating, qfalse);
+
+ trap_SendServerCommand(ent - g_entities, va("disconnect \"%s\"\n",
+ g_schachtmeisterAutobahnMessage.string));
+ trap_DropClient(ent - g_entities, "dropped by the Autobahn");
+}
+
+void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment )
+{
+ int i;
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ if( !strcmp( g_admin_namelog[ i ]->ip, ipa ) )
+ {
+ schachtmeisterJudgement_t *j = &g_admin_namelog[ i ]->smj;
+
+ j->ratingTime = level.time;
+ j->queryTime = 0;
+ j->dispatchTime = 0;
+
+ j->rating = rating;
+
+ if( j->comment )
+ G_Free( j->comment );
+
+ if( comment )
+ {
+ j->comment = G_Alloc( strlen( comment ) + 1 );
+ strcpy( j->comment, comment );
+ }
+ else
+ j->comment = NULL;
+
+ if( g_admin_namelog[ i ]->slot != -1 )
+ admin_autobahn( g_entities + g_admin_namelog[ i ]->slot, j->rating );
+ }
+ }
+}
+
qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
{
int bnum;
@@ -6121,6 +6265,35 @@ qboolean G_admin_nextmap( gentity_t *ent, int skiparg )
return qtrue;
}
+static const char *displaySchachtmeisterJudgement(const schachtmeisterJudgement_t *j,
+ g_admin_namelog_t *namelog)
+{
+ static char buffer[20];
+
+ if (G_admin_permission_guid(namelog->guid, ADMF_INCOGNITO)) {
+ Com_sprintf(buffer, sizeof(buffer), "^2 +0");
+ } else if (strcmp(namelog->guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") &&
+ (G_admin_permission_guid(namelog->guid, ADMF_NOAUTOBAHN) ||
+ G_admin_permission_guid(namelog->guid, ADMF_IMMUNITY))) {
+ Com_sprintf(buffer, sizeof(buffer), "^7 N/A ");
+ } else if (!j->ratingTime) {
+ Com_sprintf(buffer, sizeof(buffer), "^5 wait");
+ } else {
+ int color;
+
+ if (j->rating >= g_schachtmeisterClearThreshold.integer)
+ color = 2;
+ else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer)
+ color = 1;
+ else
+ color = 3;
+
+ Com_sprintf(buffer, sizeof(buffer), "^%i%+5i", color, j->rating);
+ }
+
+ return buffer;
+}
+
qboolean G_admin_namelog( gentity_t *ent, int skiparg )
{
int i, j;
@@ -6161,10 +6334,11 @@ qboolean G_admin_namelog( gentity_t *ent, int skiparg )
guid_stub[ j ] = '\0';
if( g_admin_namelog[ i ]->slot > -1 )
ADMBP( "^3" );
- ADMBP( va( "%-2s (*%s) %15s^7",
+ ADMBP( va( "%-2s (*%s) %15s %s^7",
(g_admin_namelog[ i ]->slot > -1 ) ?
va( "%d", g_admin_namelog[ i ]->slot ) : "-",
- guid_stub, g_admin_namelog[ i ]->ip ) );
+ guid_stub, g_admin_namelog[ i ]->ip,
+ displaySchachtmeisterJudgement( &g_admin_namelog[ i ]->smj, g_admin_namelog[ i ] ) ) );
for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
{
@@ -8185,3 +8359,66 @@ qboolean G_admin_tklog( gentity_t *ent, int skiparg )
return qtrue;
}
+
+qboolean G_admin_sm( gentity_t *ent, int skiparg )
+{
+ const char *s;
+ char feature[ 16 ];
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ usage:
+ ADMP( "^3!sm: ^7usage: !sm ipa <IPA>\n" );
+ return qfalse;
+ }
+
+ s = G_SayConcatArgs( 1 + skiparg );
+ if( strchr( s, '\n' ) || strchr( s, '\r' ) )
+ {
+ ADMP( "^3!sm: ^7invalid character\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 1 + skiparg, feature, sizeof( feature ) );
+
+ if( !Q_stricmp( feature, "ipa" ) )
+ {
+ char ipa[ 32 ];
+ int parts[ 4 ];
+
+ if( G_SayArgc() > 3 + skiparg )
+ {
+ ADMP( "^3!sm: ^7excessive arguments\n" );
+ goto usage;
+ }
+ G_SayArgv( 2 + skiparg, ipa, sizeof( ipa ) );
+
+ // have a well-formed IPv4 address, because Schachtmeister silently drops all invalid requests
+
+ if( sscanf( ipa, "%i.%i.%i.%i", &parts[0], &parts[1], &parts[2], &parts[3] ) != 4
+ || parts[0] < 0 || parts[0] > 255
+ || parts[1] < 0 || parts[1] > 255
+ || parts[2] < 0 || parts[2] > 255
+ || parts[3] < 0 || parts[3] > 255 )
+ {
+ ADMP( "^3!sm: ^7invalid IP address\n" );
+ return qfalse;
+ }
+
+ Com_sprintf( ipa, sizeof( ipa ), "%i.%i.%i.%i", parts[0], parts[1], parts[2], parts[3] );
+
+ if( rand() % 2 /* FIXME cache hit */ )
+ {
+ const char *answer = "interesting";
+ ADMP( va( "^3!sm: ^7IP address %s is: %s\n", ipa, answer ) );
+ return qtrue;
+ }
+
+ ADMP( "^3!sm: ^7hmm...\n" );
+ trap_SendConsoleCommand( EXEC_APPEND, va( "smq ipa \"%s\"\n", ipa ) );
+ }
+ else
+ goto usage;
+
+ return qtrue;
+}
diff --git a/src/game/g_admin.h b/src/game/g_admin.h
index 3b94644..25cf2a7 100644
--- a/src/game/g_admin.h
+++ b/src/game/g_admin.h
@@ -100,6 +100,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define ADMF_SPECIALNAME "SPECIALNAME"
#define ADMF_NOSCRIMRESTRICTION "NOSCRIMRESTRICTION"
+#define ADMF_NOAUTOBAHN "NOAUTOBAHN"
#define ADMF_NO_BUILD ".NOBUILD"
#define ADMF_NO_CHAT ".NOCHAT"
@@ -163,6 +164,15 @@ typedef struct g_admin_command
}
g_admin_command_t;
+typedef struct
+{
+ int ratingTime;
+ int queryTime;
+ int dispatchTime;
+ int rating;
+ char *comment;
+} schachtmeisterJudgement_t;
+
typedef struct g_admin_namelog
{
char name[ MAX_ADMIN_NAMELOG_NAMES ][MAX_NAME_LENGTH ];
@@ -177,6 +187,7 @@ typedef struct g_admin_namelog
int denyAlienClasses;
int specExpires;
int voteCount;
+ schachtmeisterJudgement_t smj;
}
g_admin_namelog_t;
@@ -290,6 +301,10 @@ qboolean G_admin_showff(gentity_t *ent, int skiparg);
qboolean G_admin_tklog( gentity_t *ent, int skiparg );
void G_admin_tklog_cleanup( void );
void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath );
+void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment );
+qboolean G_admin_sm( gentity_t *ent, int skiparg );
+void G_admin_schachtmeisterFrame( void );
+qboolean G_admin_is_restricted(gentity_t *ent, qboolean sendMessage);
void G_admin_print( gentity_t *ent, char *m );
void G_admin_buffer_print( gentity_t *ent, char *m );
diff --git a/src/game/g_client.c b/src/game/g_client.c
index e488126..c77d4f7 100644
--- a/src/game/g_client.c
+++ b/src/game/g_client.c
@@ -1377,6 +1377,36 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName )
/*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/
}
+/*
+===========
+LogAutobahn
+===========
+*/
+void G_LogAutobahn(gentity_t *ent, const char *userinfo, int rating,
+ qboolean onConnect)
+{
+ char ip_buffer[20];
+ const char *ip, *name, *verb;
+
+ verb = (onConnect ? "refused" : "dropped");
+
+ if (userinfo) {
+ Q_strncpyz(ip_buffer, Info_ValueForKey(userinfo, "ip"),
+ sizeof(ip_buffer));
+ ip = ip_buffer;
+ name = Info_ValueForKey(userinfo, "name");
+ } else {
+ ip = ent->client->pers.ip;
+ name = ent->client->pers.netname;
+ }
+
+ G_LogPrintf("Autobahn: %s %i %s %+i \"%s^7\"\n", verb, ent - g_entities,
+ ip, rating, name);
+
+ if (g_adminAutobahnNotify.integer)
+ G_AdminsPrintf("Autobahn %s '%s^7' with rating %+i, connecting from %s.\n",
+ verb, name, rating, ip);
+}
/*
===========
@@ -1453,6 +1483,34 @@ char *ClientConnect( int clientNum, qboolean firstTime )
strcmp( g_password.string, value ) != 0 )
return "Invalid password";
+ schachtmeisterJudgement_t *smj = NULL;
+
+ if (!(G_admin_permission_guid(guid, ADMF_NOAUTOBAHN)
+ || G_admin_permission_guid(guid, ADMF_IMMUNITY)))
+ {
+ extern g_admin_namelog_t *g_admin_namelog[128];
+ for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; ++i)
+ {
+ if (!Q_stricmp(g_admin_namelog[i]->ip, ip)
+ || !Q_stricmp(g_admin_namelog[i]->guid, guid))
+ {
+ schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj;
+ if (j->ratingTime)
+ {
+ if (j->rating >= g_schachtmeisterClearThreshold.integer)
+ break;
+ else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer)
+ {
+ G_LogAutobahn( ent, userinfo, j->rating, qtrue );
+ return g_schachtmeisterAutobahnMessage.string;
+ }
+ smj = j;
+ }
+ break;
+ }
+ }
+ }
+
// they can connect
ent->client = level.clients + clientNum;
client = ent->client;
@@ -1541,7 +1599,6 @@ char *ClientConnect( int clientNum, qboolean firstTime )
CalculateRanks( );
G_admin_namelog_update( client, qfalse );
}
-
// if this is after !restart keepteams or !restart switchteams, apply said selection
if ( client->sess.restartTeam != PTE_NONE ) {
@@ -1549,6 +1606,27 @@ char *ClientConnect( int clientNum, qboolean firstTime )
client->sess.restartTeam = PTE_NONE;
}
+ if( !( G_admin_permission( ent, ADMF_NOAUTOBAHN ) ||
+ G_admin_permission( ent, ADMF_IMMUNITY ) ) )
+ {
+ extern g_admin_namelog_t *g_admin_namelog[ 128 ];
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ if( !Q_stricmp( ip, g_admin_namelog[ i ]->ip ) || !Q_stricmp( guid, g_admin_namelog[ i ]->guid ) )
+ {
+ schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj;
+ if( j->ratingTime )
+ {
+ if( j->rating >= g_schachtmeisterClearThreshold.integer )
+ break;
+ else if( j->rating <= g_schachtmeisterAutobahnThreshold.integer )
+ return g_schachtmeisterAutobahnMessage.string;
+ G_AdminsPrintf( "%s^7 (#%d) has rating %d\n", ent->client->pers.netname, ent - g_entities, j->rating );
+ }
+ break;
+ }
+ }
+ }
return NULL;
}
diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c
index e4f6ad7..c380b45 100644
--- a/src/game/g_cmds.c
+++ b/src/game/g_cmds.c
@@ -5081,7 +5081,7 @@ static void Cmd_Ignore_f( gentity_t *ent )
commands_t cmds[ ] = {
// normal commands
{ "team", 0, Cmd_Team_f },
- { "vote", CMD_INTERMISSION, Cmd_Vote_f },
+ { "vote", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Vote_f },
{ "ignore", 0, Cmd_Ignore_f },
{ "unignore", 0, Cmd_Ignore_f },
@@ -5239,6 +5239,14 @@ void ClientCommand( int clientNum )
return;
}
+ // Disallow a large class of commands if a player is restricted.
+ if( G_admin_is_restricted( ent, qtrue ) &&
+ ( !Q_stricmp( cmd, "team" ) ||
+ ( cmds[ i ].cmdFlags & ( CMD_MESSAGE | CMD_TEAM | CMD_NOTEAM ) ) ) )
+ {
+ return;
+ }
+
cmds[ i ].cmdHandler( ent );
}
diff --git a/src/game/g_local.h b/src/game/g_local.h
index 6aea23d..19b05c9 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -1100,6 +1100,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t a
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod );
qboolean SpotWouldTelefrag( gentity_t *spot );
char *G_NextNewbieName( gentity_t *ent );
+void G_LogAutobahn( gentity_t *ent, const char *userinfo, int rating, qboolean onConnect );
//
// g_svcmds.c
@@ -1488,6 +1489,10 @@ extern vmCvar_t g_Bubbles;
extern vmCvar_t g_scrimMode;
extern vmCvar_t g_gradualFreeFunds;
extern vmCvar_t g_bleedingSpree;
+extern vmCvar_t g_schachtmeisterClearThreshold;
+extern vmCvar_t g_schachtmeisterAutobahnThreshold;
+extern vmCvar_t g_schachtmeisterAutobahnMessage;
+extern vmCvar_t g_adminAutobahnNotify;
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 b30f8c0..6623959 100644
--- a/src/game/g_main.c
+++ b/src/game/g_main.c
@@ -239,6 +239,10 @@ vmCvar_t g_Bubbles;
vmCvar_t g_scrimMode;
vmCvar_t g_gradualFreeFunds;
vmCvar_t g_bleedingSpree;
+vmCvar_t g_schachtmeisterClearThreshold;
+vmCvar_t g_schachtmeisterAutobahnThreshold;
+vmCvar_t g_schachtmeisterAutobahnMessage;
+vmCvar_t g_adminAutobahnNotify;
static cvarTable_t gameCvarTable[ ] =
{
@@ -455,7 +459,12 @@ static cvarTable_t gameCvarTable[ ] =
{ &g_Bubbles, "g_Bubbles", "1", CVAR_ARCHIVE, 0, qfalse },
{ &g_scrimMode, "g_scrimMode", "0", CVAR_ARCHIVE, 0, qfalse },
{ &g_gradualFreeFunds, "g_gradualFreeFunds", "2", CVAR_ARCHIVE, 0, qtrue },
- { &g_bleedingSpree, "g_bleedingSpree", "0", CVAR_ARCHIVE, 0, qfalse }
+ { &g_bleedingSpree, "g_bleedingSpree", "0", CVAR_ARCHIVE, 0, qfalse },
+ { &g_gradualFreeFunds, "g_gradualFreeFunds", "2", CVAR_ARCHIVE, 0, qtrue },
+ { &g_schachtmeisterClearThreshold, "g_schachtmeisterClearThreshold", "-10", CVAR_ARCHIVE, 0, qfalse },
+ { &g_schachtmeisterAutobahnThreshold, "g_schachtmeisterAutobahnThreshold", "-30", CVAR_ARCHIVE, 0, qfalse },
+ { &g_schachtmeisterAutobahnMessage, "g_schachtmeisterAutobahnMessage", "Your host is blacklisted.", CVAR_ARCHIVE, 0, qfalse },
+ { &g_adminAutobahnNotify, "g_adminAutobahnNotify", "1", CVAR_ARCHIVE, 0, qfalse }
};
static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] );
@@ -3005,6 +3014,8 @@ void G_RunFrame( int levelTime )
CheckTeamVote( PTE_HUMANS );
CheckTeamVote( PTE_ALIENS );
+ G_admin_schachtmeisterFrame();
+
// for tracking changes
CheckCvars( );
diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c
index 62148c8..f0d45b1 100644
--- a/src/game/g_svcmds.c
+++ b/src/game/g_svcmds.c
@@ -703,7 +703,35 @@ qboolean ConsoleCommand( void )
G_admin_maplog_result( "d" );
return qtrue;
}
-
+
+ if( !Q_stricmp( cmd, "smr" ) )
+ {
+ if( trap_Argc() >= 2 )
+ {
+ char arg[ 32 ];
+ trap_Argv( 1, arg, sizeof( arg ) );
+
+ if( !Q_stricmp( arg, "ipa" ) && trap_Argc() >= 4 )
+ {
+ int rating;
+ const char *comment = NULL;
+
+ trap_Argv( 3, arg, sizeof( arg ) );
+ rating = atoi( arg );
+ if( trap_Argc() >= 5 )
+ comment = ConcatArgs( 4 );
+ trap_Argv( 2, arg, sizeof( arg ) );
+
+ G_admin_IPA_judgement( arg, rating, comment );
+
+ return qtrue;
+ }
+ }
+
+ G_Printf( "unrecognized Schachtmeister response: %s\n", ConcatArgs( 1 ) );
+ return qtrue;
+ }
+
// see if this is a a admin command
if( G_admin_cmd_check( NULL, qfalse ) )
return qtrue;