📄 sv_main.c
字号:
/*
===========================================================================
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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "server.h"
serverStatic_t svs; // persistant server info
server_t sv; // local server
vm_t *gvm = NULL; // game virtual machine // bk001212 init
cvar_t *sv_fps; // time rate for running non-clients
cvar_t *sv_timeout; // seconds without any message
cvar_t *sv_zombietime; // seconds to sink messages after disconnect
cvar_t *sv_rconPassword; // password for remote server commands
cvar_t *sv_privatePassword; // password for the privateClient slots
cvar_t *sv_allowDownload;
cvar_t *sv_maxclients;
cvar_t *sv_privateClients; // number of clients reserved for password
cvar_t *sv_hostname;
cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address
cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
cvar_t *sv_showloss; // report when usercmds are lost
cvar_t *sv_padPackets; // add nop bytes to messages
cvar_t *sv_killserver; // menu system can set to 1 to shut server down
cvar_t *sv_mapname;
cvar_t *sv_mapChecksum;
cvar_t *sv_serverid;
cvar_t *sv_maxRate;
cvar_t *sv_minPing;
cvar_t *sv_maxPing;
cvar_t *sv_gametype;
cvar_t *sv_pure;
cvar_t *sv_floodProtect;
cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
cvar_t *sv_strictAuth;
/*
=============================================================================
EVENT MESSAGES
=============================================================================
*/
/*
===============
SV_ExpandNewlines
Converts newlines to "\n" so a line prints nicer
===============
*/
char *SV_ExpandNewlines( char *in ) {
static char string[1024];
int l;
l = 0;
while ( *in && l < sizeof(string) - 3 ) {
if ( *in == '\n' ) {
string[l++] = '\\';
string[l++] = 'n';
} else {
string[l++] = *in;
}
in++;
}
string[l] = 0;
return string;
}
/*
======================
SV_ReplacePendingServerCommands
This is ugly
======================
*/
int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
int i, index, csnum1, csnum2;
for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
index = i & ( MAX_RELIABLE_COMMANDS - 1 );
//
if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
sscanf(cmd, "cs %i", &csnum1);
sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
if ( csnum1 == csnum2 ) {
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
/*
if ( client->netchan.remoteAddress.type != NA_BOT ) {
Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
}
*/
return qtrue;
}
}
}
return qfalse;
}
/*
======================
SV_AddServerCommand
The given command will be transmitted to the client, and is guaranteed to
not have future snapshot_t executed before it is executed
======================
*/
void SV_AddServerCommand( client_t *client, const char *cmd ) {
int index, i;
// this is very ugly but it's also a waste to for instance send multiple config string updates
// for the same config string index in one snapshot
// if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
// return;
// }
client->reliableSequence++;
// if we would be losing an old command that hasn't been acknowledged,
// we must drop the connection
// we check == instead of >= so a broadcast print added by SV_DropClient()
// doesn't cause a recursive drop client
if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
Com_Printf( "===== pending server commands =====\n" );
for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
}
Com_Printf( "cmd %5d: %s\n", i, cmd );
SV_DropClient( client, "Server command overflow" );
return;
}
index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
}
/*
=================
SV_SendServerCommand
Sends a reliable command string to be interpreted by
the client game module: "cp", "print", "chat", etc
A NULL client will broadcast to all clients
=================
*/
void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) {
va_list argptr;
byte message[MAX_MSGLEN];
client_t *client;
int j;
va_start (argptr,fmt);
Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr);
va_end (argptr);
if ( cl != NULL ) {
SV_AddServerCommand( cl, (char *)message );
return;
}
// hack to echo broadcast prints to console
if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) {
Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) );
}
// send the data to all relevent clients
for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) {
if ( client->state < CS_PRIMED ) {
continue;
}
SV_AddServerCommand( client, (char *)message );
}
}
/*
==============================================================================
MASTER SERVER FUNCTIONS
==============================================================================
*/
/*
================
SV_MasterHeartbeat
Send a message to the masters every few minutes to
let it know we are alive, and log information.
We will also have a heartbeat sent when a server
changes from empty to non-empty, and full to non-full,
but not on every player enter or exit.
================
*/
#define HEARTBEAT_MSEC 300*1000
#define HEARTBEAT_GAME "QuakeArena-1"
void SV_MasterHeartbeat( void ) {
static netadr_t adr[MAX_MASTER_SERVERS];
int i;
// "dedicated 1" is for lan play, "dedicated 2" is for inet public play
if ( !com_dedicated || com_dedicated->integer != 2 ) {
return; // only dedicated servers send heartbeats
}
// if not time yet, don't send anything
if ( svs.time < svs.nextHeartbeatTime ) {
return;
}
svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;
// send to group masters
for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) {
if ( !sv_master[i]->string[0] ) {
continue;
}
// see if we haven't already resolved the name
// resolving usually causes hitches on win95, so only
// do it when needed
if ( sv_master[i]->modified ) {
sv_master[i]->modified = qfalse;
Com_Printf( "Resolving %s\n", sv_master[i]->string );
if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) {
// if the address failed to resolve, clear it
// so we don't take repeated dns hits
Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string );
Cvar_Set( sv_master[i]->name, "" );
sv_master[i]->modified = qfalse;
continue;
}
if ( !strstr( ":", sv_master[i]->string ) ) {
adr[i].port = BigShort( PORT_MASTER );
}
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string,
adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3],
BigShort( adr[i].port ) );
}
Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string );
// this command should be changed if the server info / status format
// ever incompatably changes
NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME );
}
}
/*
=================
SV_MasterShutdown
Informs all masters that this server is going down
=================
*/
void SV_MasterShutdown( void ) {
// send a hearbeat right now
svs.nextHeartbeatTime = -9999;
SV_MasterHeartbeat();
// send it again to minimize chance of drops
svs.nextHeartbeatTime = -9999;
SV_MasterHeartbeat();
// when the master tries to poll the server, it won't respond, so
// it will be removed from the list
}
/*
==============================================================================
CONNECTIONLESS COMMANDS
==============================================================================
*/
/*
================
SVC_Status
Responds with all the info that qplug or qspy can see about the server
and all connected players. Used for getting detailed information after
the simple info query.
================
*/
void SVC_Status( netadr_t from ) {
char player[1024];
char status[MAX_MSGLEN];
int i;
client_t *cl;
playerState_t *ps;
int statusLength;
int playerLength;
char infostring[MAX_INFO_STRING];
// ignore if we are in single player
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) {
return;
}
strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
// echo back the parameter to status. so master servers can use it as a challenge
// to prevent timed spoofed reply packets that add ghost servers
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
// add "demo" to the sv_keywords if restricted
if ( Cvar_VariableValue( "fs_restrict" ) ) {
char keywords[MAX_INFO_STRING];
Com_sprintf( keywords, sizeof( keywords ), "demo %s",
Info_ValueForKey( infostring, "sv_keywords" ) );
Info_SetValueForKey( infostring, "sv_keywords", keywords );
}
status[0] = 0;
statusLength = 0;
for (i=0 ; i < sv_maxclients->integer ; i++) {
cl = &svs.clients[i];
if ( cl->state >= CS_CONNECTED ) {
ps = SV_GameClientNum( i );
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
ps->persistant[PERS_SCORE], cl->ping, cl->name);
playerLength = strlen(player);
if (statusLength + playerLength >= sizeof(status) ) {
break; // can't hold any more
}
strcpy (status + statusLength, player);
statusLength += playerLength;
}
}
NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status );
}
/*
================
SVC_Info
Responds with a short info message that should be enough to determine
if a user is interested in a server to do a full status
================
*/
void SVC_Info( netadr_t from ) {
int i, count;
char *gamedir;
char infostring[MAX_INFO_STRING];
// ignore if we are in single player
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
return;
}
// don't count privateclients
count = 0;
for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) {
if ( svs.clients[i].state >= CS_CONNECTED ) {
count++;
}
}
infostring[0] = 0;
// echo back the parameter to status. so servers can use it as a challenge
// to prevent timed spoofed reply packets that add ghost servers
Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) );
Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) );
Info_SetValueForKey( infostring, "hostname", sv_hostname->string );
Info_SetValueForKey( infostring, "mapname", sv_mapname->string );
Info_SetValueForKey( infostring, "clients", va("%i", count) );
Info_SetValueForKey( infostring, "sv_maxclients",
va("%i", sv_maxclients->integer - sv_privateClients->integer ) );
Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
if( sv_minPing->integer ) {
Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
}
if( sv_maxPing->integer ) {
Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) );
}
gamedir = Cvar_VariableString( "fs_game" );
if( *gamedir ) {
Info_SetValueForKey( infostring, "game", gamedir );
}
NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
}
/*
================
SVC_FlushRedirect
================
*/
void SV_FlushRedirect( char *outputbuf ) {
NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf );
}
/*
===============
SVC_RemoteCommand
An rcon packet arrived from the network.
Shift down the remaining args
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -