/
noire-dev
/
SourceTech
Обзор
Документация
Войти
/
noire-dev
/
SourceTech
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
sourcetech
code/server/sv_ccmds.c
682 строки
16 KB
noire-dev
Update: 2025-12-01 11:49 Clean
01 дек 2025, 02:49
01 дек 2025, 02:49
2aa57d7
Код
Авторство
О чём код?
/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III 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. Quake III 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "server.h" /* =============================================================================== OPERATOR CONSOLE ONLY COMMANDS These commands can only be entered from stdin or by a remote operator datagram =============================================================================== */ /* ================== SV_GetPlayerByHandle Returns the player with player id or name from Cmd_Argv(1) ================== */ client_t *SV_GetPlayerByHandle( void ) { client_t *cl; int i; const char *s; char cleanName[ MAX_NAME_LENGTH ]; // make sure server is running if ( !com_sv_running->integer ) { return NULL; } if ( Cmd_Argc() < 2 ) { Com_Printf( "No player specified.\n" ); return NULL; } s = Cmd_Argv(1); // Check whether this is a numeric player handle for(i = 0; s[i] >= '0' && s[i] <= '9'; i++); if(!s[i]) { int plid = atoi(s); // Check for numeric playerid match if(plid >= 0 && plid < sv.maxclients) { cl = &svs.clients[plid]; if (cl->state >= CS_CONNECTED) return cl; } } // check for a name match for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } if ( !Q_stricmp( cl->name, s ) ) { return cl; } Q_strncpyz( cleanName, cl->name, sizeof(cleanName) ); Q_CleanStr( cleanName ); if ( !Q_stricmp( cleanName, s ) ) { return cl; } } Com_Printf( "Player %s is not on the server\n", s ); return NULL; } /* ================== SV_GetPlayerByNum Returns the player with idnum from Cmd_Argv(1) ================== */ static client_t *SV_GetPlayerByNum( void ) { client_t *cl; int i; int idnum; const char *s; // make sure server is running if ( !com_sv_running->integer ) { return NULL; } if ( Cmd_Argc() < 2 ) { Com_Printf( "No player specified.\n" ); return NULL; } s = Cmd_Argv(1); for (i = 0; s[i]; i++) { if (s[i] < '0' || s[i] > '9') { Com_Printf( "Bad slot number: %s\n", s); return NULL; } } idnum = atoi( s ); if ( idnum < 0 || idnum >= sv.maxclients ) { Com_Printf( "Bad client slot: %i\n", idnum ); return NULL; } cl = &svs.clients[idnum]; if ( cl->state < CS_CONNECTED ) { Com_Printf( "Client %i is not active\n", idnum ); return NULL; } return cl; } /* ================== SV_Map_f Restart the server on a different map ================== */ static void SV_Map_f( void ) { const char *map; char expanded[MAX_QPATH]; char mapname[MAX_QPATH]; int len; map = Cmd_Argv(1); if ( !map || !*map ) { return; } // make sure the level exists before trying to change, so that // a typo at the server console won't end the game Com_sprintf( expanded, sizeof( expanded ), "maps/%s.bsp", map ); len = FS_FOpenFileRead( expanded, NULL, qfalse ); if ( len == -1 ) { Com_Printf( "Can't find map %s\n", expanded ); return; } Cbuf_AddText( "exec maps/default.cfg \n" ); //load default map script on server Cbuf_AddText( va("exec maps/%s.cfg \n", map) ); //load map script on server Cvar_Set("cl_changeqvm", map); //load map fs on server // force latched values to get set Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH ); // save the map name here cause on a map restart we reload the q3config.cfg // and thus nuke the arguments of the map command Q_strncpyz(mapname, map, sizeof(mapname)); // start up the map SV_SpawnServer( mapname ); } /* ================ SV_MapRestart_f Completely restarts a level, but doesn't send a new gamestate to the clients. This allows fair starts with variable load times. ================ */ static void SV_MapRestart_f( void ) { int i; client_t *client; const char *denied; qboolean isBot; // make sure we aren't restarting twice in the same frame if ( com_frameTime == sv.restartedServerId ) { return; } // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } if ( sv.restartTime != 0 ) { return; } // check for changes in variables that can't just be restarted // check for maxclients change if ( sv_gametype->modified ) { char mapname[MAX_QPATH]; // restart the map the slow way Q_strncpyz( mapname, sv_mapname->string, sizeof( mapname ) ); SV_SpawnServer( mapname ); return; } // toggle the server bit so clients can detect that a // map_restart has happened svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; // generate a new restartedServerid sv.restartedServerId = com_frameTime; // if a map_restart occurs while a client is changing maps, we need // to give them the correct time so that when they finish loading // they don't violate the backwards time check in cl_cgame.c for ( i = 0; i < sv.maxclients; i++ ) { if ( svs.clients[i].state == CS_PRIMED ) { svs.clients[i].oldServerTime = sv.restartTime; } } // reset all the vm data in place without changing memory allocation // note that we do NOT set sv.state = SS_LOADING, so configstrings that // had been changed from their default values will generate broadcast updates sv.state = SS_LOADING; sv.restarting = qtrue; SV_RestartGameProgs(); // run a few frames to allow everything to settle for ( i = 0; i < 3; i++ ) { sv.time += 100; VM_Call( gvm, 1, GAME_RUN_FRAME, sv.time ); } sv.state = SS_GAME; sv.restarting = qfalse; // connect and begin all the clients for ( i = 0; i < sv.maxclients; i++ ) { client = &svs.clients[i]; // send the new gamestate to all connected clients if ( client->state < CS_CONNECTED ) { continue; } if ( client->netchan.remoteAddress.type == NA_BOT ) { isBot = qtrue; } else { isBot = qfalse; } // add the map_restart command SV_AddServerCommand( client, "map_restart\n" ); // connect the client again, without the firstTime flag denied = GVM_ArgPtr( VM_Call( gvm, 3, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); if ( denied ) { // this generally shouldn't happen, because the client // was connected before the level change SV_DropClient( client, denied ); Com_Printf( "SV_MapRestart_f: dropped client %i - denied!\n", i ); continue; } if ( client->state == CS_ACTIVE ) { SV_ClientEnterWorld( client ); } } // run another frame to allow things to look at all the players sv.time += 100; VM_Call( gvm, 1, GAME_RUN_FRAME, sv.time ); svs.time += 100; for ( i = 0; i < sv.maxclients; i++ ) { client = &svs.clients[i]; if ( client->state >= CS_PRIMED ) { // accept usercmds starting from current server time only // to emulate original behavior which dropped pre-restart commands via serverid check Com_Memset( &client->lastUsercmd, 0x0, sizeof( client->lastUsercmd ) ); client->lastUsercmd.serverTime = sv.time - 1; } } } /* ================== SV_Kick_f Kick a user off of the server FIXME: move to game ================== */ static void SV_Kick_f( void ) { client_t *cl; int i; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } if ( Cmd_Argc() != 2 ) { Com_Printf ("Usage: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n"); return; } cl = SV_GetPlayerByHandle(); if ( !cl ) { if ( !Q_stricmp( Cmd_Argv( 1 ), "all" ) ) { for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { continue; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } } else if ( !Q_stricmp( Cmd_Argv( 1 ), "allbots" ) ) { for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } if ( cl->netchan.remoteAddress.type != NA_BOT ) { continue; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } } return; } if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { Com_Printf( "Cannot kick host player\n" ); return; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } /* ================== SV_KickBots_f Kick all bots off of the server ================== */ static void SV_KickBots_f( void ) { client_t *cl; int i; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf("Server is not running.\n"); return; } for( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } if ( cl->netchan.remoteAddress.type != NA_BOT ) { continue; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } } /* ================== SV_KickAll_f Kick all users off of the server ================== */ static void SV_KickAll_f( void ) { client_t *cl; int i; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } for( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state < CS_CONNECTED ) { continue; } if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { continue; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } } /* ================== SV_KickNum_f Kick a user off of the server ================== */ static void SV_KickNum_f( void ) { client_t *cl; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } if ( Cmd_Argc() != 2 ) { Com_Printf ("Usage: %s <client number>\n", Cmd_Argv(0)); return; } cl = SV_GetPlayerByNum(); if ( !cl ) { return; } if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { Com_Printf("Cannot kick host player\n"); return; } SV_DropClient( cl, "was kicked" ); cl->lastPacketTime = svs.time; // in case there is a funny zombie } /* ** SV_Strlen -- skips color escape codes */ int SV_Strlen( const char *str ) { const char *s = str; int count = 0; while ( *s ) { if ( Q_IsColorString( s ) ) { s += 2; } else { count++; s++; } } return count; } /* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i, j, l; const client_t *cl; const playerState_t *ps; const char *s; int max_namelength; int max_addrlength; char names[ MAX_CLIENTS * MAX_NAME_LENGTH ], *np[ MAX_CLIENTS ], nl[ MAX_CLIENTS ], *nc; char addrs[ MAX_CLIENTS * 48 ], *ap[ MAX_CLIENTS ], al[ MAX_CLIENTS ], *ac; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } max_namelength = 4; // strlen( "name" ) max_addrlength = 7; // strlen( "address" ) nc = names; *nc = '\0'; ac = addrs; *ac = '\0'; Com_Memset( np, 0, sizeof( np ) ); Com_Memset( nl, 0, sizeof( nl ) ); Com_Memset( ap, 0, sizeof( ap ) ); Com_Memset( al, 0, sizeof( al ) ); // first pass: save and determine max.lengths of name/address fields for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) continue; l = strlen( cl->name ) + 1; strcpy( nc, cl->name ); np[ i ] = nc; nc += l; // name pointer in name buffer nl[ i ] = SV_Strlen( cl->name );// name length without color sequences if ( nl[ i ] > max_namelength ) max_namelength = nl[ i ]; s = NET_AdrToString( &cl->netchan.remoteAddress ); l = strlen( s ) + 1; strcpy( ac, s ); ap[ i ] = ac; ac += l; // address pointer in address buffer al[ i ] = l - 1; // address length if ( al[ i ] > max_addrlength ) max_addrlength = al[ i ]; } Com_Printf( "map: %s\n", sv_mapname->string ); Com_Printf( "cl score ping name" ); for ( i = 0; i < max_namelength - 4; i++ ) Com_Printf( " " ); Com_Printf( " address" ); for ( i = 0; i < max_addrlength - 7; i++ ) Com_Printf( " " ); Com_Printf( " rate\n" ); Com_Printf( "-- ----- ---- " ); for ( i = 0; i < max_namelength; i++ ) Com_Printf( "-" ); Com_Printf( " " ); for ( i = 0; i < max_addrlength; i++ ) Com_Printf( "-" ); Com_Printf( " -----\n" ); for ( i = 0, cl = svs.clients; i < sv.maxclients; i++, cl++ ) { if ( cl->state == CS_FREE ) continue; Com_Printf( "%2i ", i ); // id ps = SV_GameClientNum( i ); Com_Printf( "%5i ", ps->persistant[PERS_SCORE] ); // ping/status if ( cl->state == CS_PRIMED ) Com_Printf( " PRM " ); else if ( cl->state == CS_CONNECTED ) Com_Printf( " CON " ); else if ( cl->state == CS_ZOMBIE ) Com_Printf( " ZMB " ); else Com_Printf( "%4i ", cl->ping < 999 ? cl->ping : 999 ); // variable-length name field s = np[ i ]; Com_Printf( "%s", s ); l = max_namelength - nl[ i ]; for ( j = 0; j < l; j++ ) Com_Printf( " " ); // variable-length address field s = ap[ i ]; Com_Printf( S_COLOR_WHITE " %s", s ); l = max_addrlength - al[ i ]; for ( j = 0; j < l; j++ ) Com_Printf( " " ); // rate Com_Printf( " %5i\n", cl->rate ); } Com_Printf( "\n" ); } /* ================== SV_Heartbeat_f Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer ================== */ void SV_Heartbeat_f( void ) { svs.nextHeartbeatTime = svs.time; } /* =========== SV_DumpUser_f Examine all a users info strings =========== */ static void SV_DumpUser_f( void ) { client_t *cl; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } if ( Cmd_Argc() != 2 ) { Com_Printf ("Usage: dumpuser <userid>\n"); return; } cl = SV_GetPlayerByHandle(); if ( !cl ) { return; } Com_Printf( "userinfo\n" ); Com_Printf( "--------\n" ); Info_Print( cl->userinfo ); } /* ================= SV_KillServer ================= */ static void SV_KillServer_f( void ) { SV_Shutdown( "killserver" ); } /* ================== SV_CompleteMapName ================== */ static void SV_CompleteMapName( const char *args, int argNum ) { if ( argNum == 2 ) { Field_CompleteFilename( "maps", "bsp", qtrue, FS_MATCH_ANY | FS_MATCH_STICK ); } } /* ================== SV_AddOperatorCommands ================== */ void SV_AddOperatorCommands( void ) { static qboolean initialized; if ( initialized ) { return; } initialized = qtrue; Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); Cmd_AddCommand ("kick", SV_Kick_f); Cmd_AddCommand ("kickbots", SV_KickBots_f); Cmd_AddCommand ("kickall", SV_KickAll_f); Cmd_AddCommand ("kicknum", SV_KickNum_f); Cmd_AddCommand ("clientkick", SV_KickNum_f); // Legacy command Cmd_AddCommand ("status", SV_Status_f); Cmd_AddCommand ("dumpuser", SV_DumpUser_f); Cmd_AddCommand ("map_restart", SV_MapRestart_f); Cmd_AddCommand ("map", SV_Map_f); Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName ); Cmd_AddCommand ("killserver", SV_KillServer_f); Cmd_AddCommand( "filter", SV_AddFilter_f ); Cmd_AddCommand( "filtercmd", SV_AddFilterCmd_f ); }