Files
illusion-arena/code/game/g_cmds_ext.c
2025-07-15 15:01:00 -03:00

706 lines
16 KiB
C

/*
===========================================================================
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;
}