/* =========================================================================== Copyright (C) 2004-2006 Tony J. White This file is part of the Open Arena source code. Copied from Tremulous under GPL version 2 including any later version. Open Arena 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. Open Arena 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 Open Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "g_local.h" /* ================= G_SayArgc G_SayArgv G_SayConcatArgs trap_Argc, trap_Argv, and ConcatArgs consider say text as a single argument These functions assemble the text and re-parse it on word boundaries ================= */ int G_SayArgc( void ) { int c = 0; char *s; s = ConcatArgs( 0 ); while( 1 ) { while( *s == ' ' ) s++; if( !*s ) break; c++; while( *s && *s != ' ' ) s++; } return c; } qboolean G_SayArgv( int n, char *buffer, int bufferLength ) { char *s; if( bufferLength < 1 ) return qfalse; if( n < 0 ) return qfalse; s = ConcatArgs( 0 ); while( 1 ) { while( *s == ' ' ) s++; if( !*s || n == 0 ) break; n--; while( *s && *s != ' ' ) s++; } if( n > 0 ) return qfalse; //memccpy( buffer, s, ' ', bufferLength ); while( *s && *s != ' ' && bufferLength > 1 ) { *buffer++ = *s++; bufferLength--; } *buffer = 0; return qtrue; } char *G_SayConcatArgs( int start ) { char *s; s = ConcatArgs( 0 ); while( 1 ) { while( *s == ' ' ) s++; if( !*s || start == 0 ) break; start--; while( *s && *s != ' ' ) s++; } return s; } void G_DecolorString( char *in, char *out, int len ) { len--; while( *in && len > 0 ) { if( Q_IsColorString( in ) ) { in++; if( *in ) in++; continue; } *out++ = *in++; len--; } *out = '\0'; } /* ================== G_MatchOnePlayer This is a companion function to G_ClientNumbersFromString() err will be populated with an error message. ================== */ void G_MatchOnePlayer( int *plist, int num, char *err, int len ) { gclient_t *cl; int i; char line[ MAX_NAME_LENGTH + 10 ] = {""}; err[ 0 ] = '\0'; if( num == 0 ) { Q_strcat( err, len, "no connected player by that name or slot #" ); } else if( num > 1 ) { Q_strcat( err, len, "more than one player name matches. Be more specific or use the slot #:\n" ); for( i = 0; i < num; i++ ) { cl = &level.clients[ plist[ i ] ]; if( cl->pers.connected == CON_DISCONNECTED ) continue; Com_sprintf( line, sizeof( line ), "%2i - %s^7\n", plist[ i ], cl->pers.netname ); if( strlen( err ) + strlen( line ) > len ) break; Q_strcat( err, len, line ); } } } /* ================== G_SanitiseString Remove case and control characters from a string ================== */ void G_SanitiseString( char *in, char *out, int len ) { qboolean skip = qtrue; int spaces = 0; len--; while( *in && len > 0 ) { // strip leading white space if( *in == ' ' ) { if( skip ) { in++; continue; } spaces++; } else { spaces = 0; skip = qfalse; } if( Q_IsColorString( in ) ) { in += 2; // skip color code continue; } if( *in < 32 ) { in++; continue; } *out++ = tolower( *in++ ); len--; } out -= spaces; *out = 0; } /* ================== G_ClientNumbersFromString Sets plist to an array of integers that represent client numbers that have names that are a partial match for s. Returns number of matching clientids up to max. ================== */ int G_ClientNumbersFromString( char *s, int *plist, int max ) { gclient_t *p; int i, found = 0; char n2[ MAX_NAME_LENGTH ] = {""}; char s2[ MAX_NAME_LENGTH ] = {""}; if( max == 0 ) return 0; // if a number is provided, it is a clientnum for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); if( !s[ i ] ) { i = atoi( s ); if( i >= 0 && i < level.maxclients ) { p = &level.clients[ i ]; if( p->pers.connected != CON_DISCONNECTED ) { *plist = i; return 1; } } // we must assume that if only a number is provided, it is a clientNum return 0; } // now look for name matches G_SanitiseString( s, s2, sizeof( s2 ) ); if( strlen( s2 ) < 1 ) return 0; for( i = 0; i < level.maxclients && found < max; i++ ) { p = &level.clients[ i ]; if( p->pers.connected == CON_DISCONNECTED ) { continue; } G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); if( strstr( n2, s2 ) ) { *plist++ = i; found++; } } return found; } /* ================== G_FloodLimited Determine whether a user is flood limited, and adjust their flood demerits Print them a warning message if they are over the limit Return is time in msec until the user can speak again ================== */ int G_FloodLimited( gentity_t *ent ) { int deltatime, ms; if( g_floodMinTime.integer <= 0 ) return 0; // handles !ent if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) return 0; deltatime = level.time - ent->client->pers.floodTime; ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime; if( ent->client->pers.floodDemerits < 0 ) ent->client->pers.floodDemerits = 0; ent->client->pers.floodTime = level.time; ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer; if( ms <= 0 ) return 0; trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: please wait %d second%s before trying again\n", ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) ); return ms; } //KK-Private Messaging Not implemented. /* void Cmd_PrivateMessage_f( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; int ignoreids[ MAX_CLIENTS ]; char name[ MAX_NAME_LENGTH ]; char cmd[ 12 ]; char str[ MAX_STRING_CHARS ]; char *msg; char color; int pcount, matches, ignored = 0; int i; int skipargs = 0; qboolean teamonly = qfalse; gentity_t *tmpent; if( !g_privateMessages.integer && ent ) { ADMP( "Sorry, but private messages have been disabled\n" ); return; } G_SayArgv( 0, cmd, sizeof( cmd ) ); if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) { skipargs = 1; G_SayArgv( 1, cmd, sizeof( cmd ) ); } if( G_SayArgc( ) < 3+skipargs ) { ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); return; } if( !Q_stricmp( cmd, "mt" ) || !Q_stricmp( cmd, "/mt" ) ) teamonly = qtrue; G_SayArgv( 1+skipargs, name, sizeof( name ) ); msg = G_SayConcatArgs( 2+skipargs ); pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); if( ent ) { int count = 0; for( i = 0; i < pcount; i++ ) { tmpent = &g_entities[ pids[ i ] ]; if( teamonly && !OnSameTeam( ent, tmpent ) ) continue; if( BG_ClientListTest( &tmpent->client->pers.ignoreList, ent-g_entities ) ) { ignoreids[ ignored++ ] = pids[ i ]; continue; } pids[ count ] = pids[ i ]; count++; } matches = count; } else { matches = pcount; } color = teamonly ? COLOR_CYAN : COLOR_YELLOW; Com_sprintf( str, sizeof( str ), "^%csent to %i player%s: ^7", color, matches, ( matches == 1 ) ? "" : "s" ); for( i=0; i < matches; i++ ) { tmpent = &g_entities[ pids[ i ] ]; if( i > 0 ) Q_strcat( str, sizeof( str ), "^7, " ); Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); trap_SendServerCommand( pids[ i ], va( "chat \"%s^%c -> ^7%s^7: (%d recipient%s): ^%c%s^7\" %i", ( ent ) ? ent->client->pers.netname : "console", color, name, matches, ( matches == 1 ) ? "" : "s", color, msg, ent ? ent-g_entities : -1 ) ); if( ent ) { trap_SendServerCommand( pids[ i ], va( "print \">> to reply, say: /m %d [your message] <<\n\"", ( ent - g_entities ) ) ); } trap_SendServerCommand( pids[ i ], va( "cp \"^%cprivate message from ^7%s^7\"", color, ( ent ) ? ent->client->pers.netname : "console" ) ); } if( !matches ) ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", name ) ); else { ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); ADMP( va( "%s\n", str ) ); G_LogPrintf( "%s: %s^7: %s^7: %s\n", ( teamonly ) ? "tprivmsg" : "privmsg", ( ent ) ? ent->client->pers.netname : "console", name, msg ); } if( ignored ) { Com_sprintf( str, sizeof( str ), "^%cignored by %i player%s: ^7", color, ignored, ( ignored == 1 ) ? "" : "s" ); for( i=0; i < ignored; i++ ) { tmpent = &g_entities[ ignoreids[ i ] ]; if( i > 0 ) Q_strcat( str, sizeof( str ), "^7, " ); Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); } ADMP( va( "%s\n", str ) ); } } */ /* ================= G_AdminMessage Print to all active server admins, and to the logfile, and to the server console Prepend *prefix, or '[SERVER]' if no *prefix is given ================= */ void QDECL G_AdminMessage( const char *prefix, const char *fmt, ... ) { va_list argptr; char string[ 1024 ]; char outstring[ 1024 ]; int i; // Format the text va_start( argptr, fmt ); Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); // If there is no prefix, assume that this function was called directly // and we should add one if( !prefix || !prefix[ 0 ] ) { prefix = "[SERVER]:"; } // Create the final string Com_sprintf( outstring, sizeof( outstring ), "%s " S_COLOR_MAGENTA "%s", prefix, string ); // Send to all appropriate clients for( i = 0; i < level.maxclients; i++ ) if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) trap_SendServerCommand( i, va( "chat \"%s\"", outstring ) ); // Send to the logfile and server console G_LogPrintf("adminmsg: %s\n", outstring ); } /* ================= Cmd_AdminMessage_f Send a message to all active admins ================= */ void Cmd_AdminMessage_f( gentity_t *ent ) { char cmd[ sizeof( "say_team" ) ]; char prefix[ 50 ]; char *msg; int skiparg = 0; // Check permissions and add the appropriate user [prefix] if( !ent ) { Com_sprintf( prefix, sizeof( prefix ), "[CONSOLE]:" ); } else if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) { if( !g_publicAdminMessages.integer ) { ADMP( "Sorry, but use of /a by non-admins has been disabled.\n" ); return; } else { Com_sprintf( prefix, sizeof( prefix ), "[PLAYER] %s" S_COLOR_WHITE ":", ent->client->pers.netname ); ADMP( "Your message has been sent to any available admins and to the server logs.\n" ); } } else { Com_sprintf( prefix, sizeof( prefix ), "[ADMIN] %s" S_COLOR_WHITE ":", ent->client->pers.netname ); } // Skip say/say_team if this was used from one of those G_SayArgv( 0, cmd, sizeof( cmd ) ); if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) { skiparg = 1; G_SayArgv( 1, cmd, sizeof( cmd ) ); } if( G_SayArgc( ) < 2 + skiparg ) { ADMP( va( "usage: %s [message]\n", cmd ) ); return; } msg = G_SayConcatArgs( 1 + skiparg ); // Send it G_AdminMessage( prefix, "%s", msg ); } /* ============== Cmd_Ignore_f KK-OAX Removed Static to Keep in Mod Files Currently Unused until I figure out how to implement it with voice chats. ============== */ /*void Cmd_Ignore_f( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; char name[ MAX_NAME_LENGTH ]; char cmd[ 9 ]; int matches = 0; int i; qboolean ignore = qfalse; trap_Argv( 0, cmd, sizeof( cmd ) ); if( Q_stricmp( cmd, "ignore" ) == 0 ) ignore = qtrue; if( trap_Argc() < 2 ) { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "usage: %s [clientNum | partial name match]\n\"", cmd ) ); return; } Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); if( matches < 1 ) { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "%s: no clients match the name '%s'\n\"", cmd, name ) ); return; } for( i = 0; i < matches; i++ ) { if( ignore ) { if( !BG_ClientListTest( &ent->client->pers.ignoreList, pids[ i ] ) ) { BG_ClientListAdd( &ent->client->pers.ignoreList, pids[ i ] ); ClientUserinfoChanged( ent->client->ps.clientNum ); trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "ignore: added %s^7 to your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } else { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "ignore: %s^7 is already on your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } } else { if( BG_ClientListTest( &ent->client->pers.ignoreList, pids[ i ] ) ) { BG_ClientListRemove( &ent->client->pers.ignoreList, pids[ i ] ); ClientUserinfoChanged( ent->client->ps.clientNum ); trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "unignore: removed %s^7 from your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } else { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "unignore: %s^7 is not on your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } } } } */ /* ================== G_ClientNumberFromString Returns a player number for either a number or name string Returns -1 if invalid ================== */ int G_ClientNumberFromString( char *s ) { gclient_t *cl; int i; char s2[ MAX_NAME_LENGTH ]; char n2[ MAX_NAME_LENGTH ]; // numeric values are just slot numbers for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); if( !s[ i ] ) { i = atoi( s ); if( i < 0 || i >= level.maxclients ) return -1; cl = &level.clients[ i ]; if( cl->pers.connected == CON_DISCONNECTED ) return -1; return i; } // check for a name match G_SanitiseString( s, s2, sizeof( s2 ) ); for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) { if( cl->pers.connected == CON_DISCONNECTED ) continue; G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) ); if( !strcmp( n2, s2 ) ) return i; } return -1; } //KK-OAX Used to !spec999'ers /* ============= G_ClientIsLagging ============= */ qboolean G_ClientIsLagging( gclient_t *client ) { if( client ) { if( client->ps.ping >= 999 ) return qtrue; else return qfalse; } return qfalse; //is a non-existant client lagging? woooo zen } /* ================== SanitizeString Remove case and control characters ================== */ void SanitizeString( char *in, char *out ) { while ( *in ) { if ( *in == 27 ) { in += 2; // skip color code continue; } if ( *in < 32 ) { in++; continue; } *out++ = tolower( *in++ ); } *out = 0; }