summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/cl_main.c98
-rw-r--r--src/qcommon/common.c174
-rw-r--r--src/qcommon/qcommon.h5
-rw-r--r--src/server/sv_ccmds.c65
4 files changed, 342 insertions, 0 deletions
diff --git a/src/client/cl_main.c b/src/client/cl_main.c
index 03bd542c..5b3f6028 100644
--- a/src/client/cl_main.c
+++ b/src/client/cl_main.c
@@ -1732,6 +1732,50 @@ static void CL_CompleteRcon( char *args, int argNum )
}
/*
+==================
+CL_CompletePlayerName
+==================
+*/
+static void CL_CompletePlayerName( char *args, int argNum )
+{
+ if( argNum == 2 )
+ {
+ char names[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char *namesPtr[MAX_CLIENTS];
+ int i;
+ int clientCount;
+ int nameCount;
+ const char *info;
+ const char *name;
+
+ //configstring
+ info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_SERVERINFO];
+ clientCount = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
+
+ nameCount = 0;
+
+ for( i = 0; i < clientCount; i++ ) {
+ if( i == clc.clientNum )
+ continue;
+
+ info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_PLAYERS+i];
+
+ name = Info_ValueForKey( info, "n" );
+ if( name[0] == '\0' )
+ continue;
+ Q_strncpyz( names[nameCount], name, sizeof(names[nameCount]) );
+ Q_CleanStr( names[nameCount] );
+
+ namesPtr[nameCount] = names[nameCount];
+ nameCount++;
+ }
+ qsort( (void*)namesPtr, nameCount, sizeof( namesPtr[0] ), Com_strCompare );
+
+ Field_CompletePlayerName( namesPtr, nameCount );
+ }
+}
+
+/*
=====================
CL_Rcon_f
@@ -3474,6 +3518,56 @@ static void CL_GenerateQKey(void)
}
}
+void CL_Sayto_f( void ) {
+ char *rawname;
+ char name[MAX_NAME_LENGTH];
+ char cleanName[MAX_NAME_LENGTH];
+ const char *info;
+ int count;
+ int i;
+ int clientNum;
+ char *p;
+
+ if ( Cmd_Argc() < 3 ) {
+ Com_Printf ("sayto <player name> <text>\n");
+ return;
+ }
+
+ rawname = Cmd_Argv(1);
+
+ Com_FieldStringToPlayerName( name, MAX_NAME_LENGTH, rawname );
+
+ info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_SERVERINFO];
+ count = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
+
+ clientNum = -1;
+ for( i = 0; i < count; i++ ) {
+
+ info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_PLAYERS+i];
+ Q_strncpyz( cleanName, Info_ValueForKey( info, "n" ), sizeof(cleanName) );
+ Q_CleanStr( cleanName );
+
+ if ( !Q_stricmp( cleanName, name ) ) {
+ clientNum = i;
+ break;
+ }
+ }
+ if( clientNum <= -1 )
+ {
+ Com_Printf ("No such player name: %s.\n", name);
+ return;
+ }
+
+ p = Cmd_ArgsFrom(2);
+
+ if ( *p == '"' ) {
+ p++;
+ p[strlen(p)-1] = 0;
+ }
+
+ CL_AddReliableCommand(va("tell %i \"%s\"", clientNum, p ), qfalse);
+}
+
/*
====================
CL_Init
@@ -3663,6 +3757,10 @@ void CL_Init( void ) {
Cmd_AddCommand ("model", CL_SetModel_f );
Cmd_AddCommand ("video", CL_Video_f );
Cmd_AddCommand ("stopvideo", CL_StopVideo_f );
+ if( !com_dedicated->integer ) {
+ Cmd_AddCommand ("sayto", CL_Sayto_f );
+ Cmd_SetCommandCompletionFunc( "sayto", CL_CompletePlayerName );
+ }
CL_InitRef();
SCR_Init ();
diff --git a/src/qcommon/common.c b/src/qcommon/common.c
index fef14331..11e723c1 100644
--- a/src/qcommon/common.c
+++ b/src/qcommon/common.c
@@ -3421,3 +3421,177 @@ qboolean Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientN
return qfalse;
}
+
+/*
+===============
+Field_CompletePlayerName
+===============
+*/
+static qboolean Field_CompletePlayerNameFinal( qboolean whitespace )
+{
+ int completionOffset;
+
+ if( matchCount == 0 )
+ return qtrue;
+
+ completionOffset = strlen( completionField->buffer ) - strlen( completionString );
+
+ Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch,
+ sizeof( completionField->buffer ) - completionOffset );
+
+ completionField->cursor = strlen( completionField->buffer );
+
+ if( matchCount == 1 && whitespace )
+ {
+ Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
+ completionField->cursor++;
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+static void Name_PlayerNameCompletion( const char **names, int nameCount, void(*callback)(const char *s) )
+{
+ int i;
+
+ for( i = 0; i < nameCount; i++ ) {
+ callback( names[ i ] );
+ }
+}
+
+qboolean Com_FieldStringToPlayerName( char *name, int length, const char *rawname )
+{
+ char hex[5];
+ int i;
+ int ch;
+
+ if( name == NULL || rawname == NULL )
+ return qfalse;
+
+ if( length <= 0 )
+ return qtrue;
+
+ for( i = 0; *rawname && i + 1 <= length; rawname++, i++ ) {
+ if( *rawname == '\\' ) {
+ Q_strncpyz( hex, rawname + 1, sizeof(hex) );
+ ch = Com_HexStrToInt( hex );
+ if( ch > -1 ) {
+ name[i] = ch;
+ rawname += 4; //hex string length, 0xXX
+ } else {
+ name[i] = *rawname;
+ }
+ } else {
+ name[i] = *rawname;
+ }
+ }
+ name[i] = '\0';
+
+ return qtrue;
+}
+
+qboolean Com_PlayerNameToFieldString( char *str, int length, const char *name )
+{
+ const char *p;
+ int i;
+ int x1, x2;
+
+ if( str == NULL || name == NULL )
+ return qfalse;
+
+ if( length <= 0 )
+ return qtrue;
+
+ *str = '\0';
+ p = name;
+
+ for( i = 0; *p != '\0'; i++, p++ )
+ {
+ if( i + 1 >= length )
+ break;
+
+ if( *p <= ' ' )
+ {
+ if( i + 5 + 1 >= length )
+ break;
+
+ x1 = *p >> 4;
+ x2 = *p & 15;
+
+ str[i+0] = '\\';
+ str[i+1] = '0';
+ str[i+2] = 'x';
+ str[i+3] = x1 > 9 ? x1 - 10 + 'a' : x1 + '0';
+ str[i+4] = x2 > 9 ? x2 - 10 + 'a' : x2 + '0';
+
+ i += 4;
+ } else {
+ str[i] = *p;
+ }
+ }
+ str[i] = '\0';
+
+ return qtrue;
+}
+
+void Field_CompletePlayerName( char **names, int nameCount )
+{
+ qboolean whitespace;
+
+ matchCount = 0;
+ shortestMatch[ 0 ] = 0;
+
+ if( nameCount <= 0 )
+ return;
+
+ Name_PlayerNameCompletion( names, nameCount, FindMatches );
+
+ if( completionString[0] == '\0' )
+ {
+ Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ 0 ] );
+ }
+
+ //allow to tab player names
+ //if full player name switch to next player name
+ if( completionString[0] != '\0'
+ && Q_stricmp( shortestMatch, completionString ) == 0
+ && nameCount > 1 )
+ {
+ int i;
+
+ for( i = 0; i < nameCount; i++ ) {
+ if( Q_stricmp( names[ i ], completionString ) == 0 )
+ {
+ i++;
+ if( i >= nameCount )
+ {
+ i = 0;
+ }
+
+ Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ i ] );
+ break;
+ }
+ }
+ }
+
+ if( matchCount > 1 )
+ {
+ Com_Printf( "]%s\n", completionField->buffer );
+
+ Name_PlayerNameCompletion( names, nameCount, PrintMatches );
+ }
+
+ whitespace = nameCount == 1? qtrue: qfalse;
+ if( !Field_CompletePlayerNameFinal( whitespace ) )
+ {
+
+ }
+}
+
+int QDECL Com_strCompare( const void *a, const void *b )
+{
+ const char **pa = (const char **)a;
+ const char **pb = (const char **)b;
+ return strcmp( *pa, *pb );
+}
diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h
index 98d1f1ec..d71531b6 100644
--- a/src/qcommon/qcommon.h
+++ b/src/qcommon/qcommon.h
@@ -740,6 +740,7 @@ void Field_CompleteFilename( const char *dir,
const char *ext, qboolean stripExt, qboolean allowNonPureFilesOnDisk );
void Field_CompleteCommand( char *cmd,
qboolean doCommands, qboolean doCvars );
+void Field_CompletePlayerName( char **names, int count );
/*
==============================================================
@@ -815,6 +816,10 @@ void Com_StartupVariable( const char *match );
// if match is NULL, all set commands will be executed, otherwise
// only a set with the exact name. Only used during startup.
+qboolean Com_PlayerNameToFieldString( char *str, int length, const char *name );
+qboolean Com_FieldStringToPlayerName( char *name, int length, const char *rawname );
+int QDECL Com_strCompare( const void *a, const void *b );
+
extern cvar_t *com_developer;
extern cvar_t *com_dedicated;
diff --git a/src/server/sv_ccmds.c b/src/server/sv_ccmds.c
index 3ec3b8c7..1cc6cf3e 100644
--- a/src/server/sv_ccmds.c
+++ b/src/server/sv_ccmds.c
@@ -225,6 +225,71 @@ static void SV_MapRestart_f( void ) {
/*
==================
+SV_ConSayto_f
+==================
+*/
+static void SV_ConSayto_f(void) {
+ char *p;
+ char text[1024];
+ client_t *cl;
+ char *rawname;
+ char name[MAX_NAME_LENGTH];
+ char cleanName[MAX_NAME_LENGTH];
+ client_t *saytocl;
+ int i;
+
+ // make sure server is running
+ if ( !com_sv_running->integer ) {
+ Com_Printf( "Server is not running.\n" );
+ return;
+ }
+
+ if ( Cmd_Argc() < 3 ) {
+ Com_Printf ("Usage: sayto <player name> <text>\n");
+ return;
+ }
+
+ rawname = Cmd_Argv(1);
+
+ //allowing special characters in the console
+ //with hex strings for player names
+ Com_FieldStringToPlayerName( name, MAX_NAME_LENGTH, rawname );
+
+ saytocl = NULL;
+ for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
+ if ( !cl->state ) {
+ continue;
+ }
+ Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
+ Q_CleanStr( cleanName );
+
+ if ( !Q_stricmp( cleanName, name ) ) {
+ saytocl = cl;
+ break;
+ }
+ }
+ if( !saytocl )
+ {
+ Com_Printf ("No such player name: %s.\n", name);
+ return;
+ }
+
+ strcpy (text, "console_sayto: ");
+ p = Cmd_ArgsFrom(2);
+
+ if ( *p == '"' ) {
+ p++;
+ p[strlen(p)-1] = 0;
+ }
+
+ strcat(text, p);
+
+ SV_SendServerCommand(saytocl, "chat \"%s\"", text);
+}
+
+
+/*
+==================
SV_Heartbeat_f
Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer