From 6bf20c78f5b69d40bcc4931df93d29198435ab67 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 17:39:27 +0000 Subject: newlines fixed git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/server/server.h | 808 ++++++------ code/server/sv_bot.c | 1270 +++++++++---------- code/server/sv_ccmds.c | 1524 +++++++++++----------- code/server/sv_client.c | 3062 ++++++++++++++++++++++---------------------- code/server/sv_game.c | 1962 ++++++++++++++--------------- code/server/sv_init.c | 1390 ++++++++++---------- code/server/sv_main.c | 1710 ++++++++++++------------- code/server/sv_net_chan.c | 414 +++--- code/server/sv_rankings.c | 3074 ++++++++++++++++++++++----------------------- code/server/sv_snapshot.c | 1364 ++++++++++---------- code/server/sv_world.c | 1382 ++++++++++---------- 11 files changed, 8980 insertions(+), 8980 deletions(-) (limited to 'code/server') diff --git a/code/server/server.h b/code/server/server.h index c32b2dc..589e1c4 100755 --- a/code/server/server.h +++ b/code/server/server.h @@ -1,404 +1,404 @@ -/* -=========================================================================== -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 -=========================================================================== -*/ -// server.h - -#include "../game/q_shared.h" -#include "../qcommon/qcommon.h" -#include "../game/g_public.h" -#include "../game/bg_public.h" - -//============================================================================= - -#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND - // GAME BOTH REFERENCE !!! - -#define MAX_ENT_CLUSTERS 16 - -typedef struct svEntity_s { - struct worldSector_s *worldSector; - struct svEntity_s *nextEntityInWorldSector; - - entityState_t baseline; // for delta compression of initial sighting - int numClusters; // if -1, use headnode instead - int clusternums[MAX_ENT_CLUSTERS]; - int lastCluster; // if all the clusters don't fit in clusternums - int areanum, areanum2; - int snapshotCounter; // used to prevent double adding from portal views -} svEntity_t; - -typedef enum { - SS_DEAD, // no map loaded - SS_LOADING, // spawning level entities - SS_GAME // actively running -} serverState_t; - -typedef struct { - serverState_t state; - qboolean restarting; // if true, send configstring changes during SS_LOADING - int serverId; // changes each server start - int restartedServerId; // serverId before a map_restart - int checksumFeed; // the feed key that we use to compute the pure checksum strings - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 - // the serverId associated with the current checksumFeed (always <= serverId) - int checksumFeedServerId; - int snapshotCounter; // incremented for each snapshot built - int timeResidual; // <= 1000 / sv_frame->value - int nextFrameTime; // when time > nextFrameTime, process world - struct cmodel_s *models[MAX_MODELS]; - char *configstrings[MAX_CONFIGSTRINGS]; - svEntity_t svEntities[MAX_GENTITIES]; - - char *entityParsePoint; // used during game VM init - - // the game virtual machine will update these on init and changes - sharedEntity_t *gentities; - int gentitySize; - int num_entities; // current number, <= MAX_GENTITIES - - playerState_t *gameClients; - int gameClientSize; // will be > sizeof(playerState_t) due to game private data - - int restartTime; -} server_t; - - - - - -typedef struct { - int areabytes; - byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits - playerState_t ps; - int num_entities; - int first_entity; // into the circular sv_packet_entities[] - // the entities MUST be in increasing state number - // order, otherwise the delta compression will fail - int messageSent; // time the message was transmitted - int messageAcked; // time the message was acked - int messageSize; // used to rate drop packets -} clientSnapshot_t; - -typedef enum { - CS_FREE, // can be reused for a new connection - CS_ZOMBIE, // client has been disconnected, but don't reuse - // connection for a couple seconds - CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet - CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd - CS_ACTIVE // client is fully in game -} clientState_t; - -typedef struct netchan_buffer_s { - msg_t msg; - byte msgBuffer[MAX_MSGLEN]; - struct netchan_buffer_s *next; -} netchan_buffer_t; - -typedef struct client_s { - clientState_t state; - char userinfo[MAX_INFO_STRING]; // name, etc - - char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; - int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet - int reliableAcknowledge; // last acknowledged reliable message - int reliableSent; // last sent reliable message, not necesarily acknowledged yet - int messageAcknowledge; - - int gamestateMessageNum; // netchan->outgoingSequence of gamestate - int challenge; - - usercmd_t lastUsercmd; - int lastMessageNum; // for delta compression - int lastClientCommand; // reliable client message sequence - char lastClientCommandString[MAX_STRING_CHARS]; - sharedEntity_t *gentity; // SV_GentityNum(clientnum) - char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked - - // downloading - char downloadName[MAX_QPATH]; // if not empty string, we are downloading - fileHandle_t download; // file being downloaded - int downloadSize; // total bytes (can't use EOF because of paks) - int downloadCount; // bytes sent - int downloadClientBlock; // last block we sent to the client, awaiting ack - int downloadCurrentBlock; // current block number - int downloadXmitBlock; // last block we xmited - unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks - int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; - qboolean downloadEOF; // We have sent the EOF block - int downloadSendTime; // time we last got an ack from the client - - int deltaMessage; // frame last client usercmd message - int nextReliableTime; // svs.time when another reliable command will be allowed - int lastPacketTime; // svs.time when packet was last received - int lastConnectTime; // svs.time when connection started - int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime - qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec - int timeoutCount; // must timeout a few frames in a row so debugging doesn't break - clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here - int ping; - int rate; // bytes / second - int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked - int pureAuthentic; - qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all - netchan_t netchan; - // TTimo - // queuing outgoing fragmented messages to send them properly, without udp packet bursts - // in case large fragmented messages are stacking up - // buffer them into this queue, and hand them out to netchan as needed - netchan_buffer_t *netchan_start_queue; - netchan_buffer_t **netchan_end_queue; -} client_t; - -//============================================================================= - - -// MAX_CHALLENGES is made large to prevent a denial -// of service attack that could cycle all of them -// out before legitimate users connected -#define MAX_CHALLENGES 1024 - -#define AUTHORIZE_TIMEOUT 5000 - -typedef struct { - netadr_t adr; - int challenge; - int time; // time the last packet was sent to the autherize server - int pingTime; // time the challenge response was sent to client - int firstTime; // time the adr was first used, for authorize timeout checks - qboolean connected; -} challenge_t; - - -#define MAX_MASTERS 8 // max recipients for heartbeat packets - - -// this structure will be cleared only when the game dll changes -typedef struct { - qboolean initialized; // sv_init has completed - - int time; // will be strictly increasing across level changes - - int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() - - client_t *clients; // [sv_maxclients->integer]; - int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES - int nextSnapshotEntities; // next snapshotEntities to use - entityState_t *snapshotEntities; // [numSnapshotEntities] - int nextHeartbeatTime; - challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting - netadr_t redirectAddress; // for rcon return messages - - netadr_t authorizeAddress; // for rcon return messages -} serverStatic_t; - -//============================================================================= - -extern serverStatic_t svs; // persistant server info across maps -extern server_t sv; // cleared each map -extern vm_t *gvm; // game virtual machine - -#define MAX_MASTER_SERVERS 5 - -extern cvar_t *sv_fps; -extern cvar_t *sv_timeout; -extern cvar_t *sv_zombietime; -extern cvar_t *sv_rconPassword; -extern cvar_t *sv_privatePassword; -extern cvar_t *sv_allowDownload; -extern cvar_t *sv_maxclients; - -extern cvar_t *sv_privateClients; -extern cvar_t *sv_hostname; -extern cvar_t *sv_master[MAX_MASTER_SERVERS]; -extern cvar_t *sv_reconnectlimit; -extern cvar_t *sv_showloss; -extern cvar_t *sv_padPackets; -extern cvar_t *sv_killserver; -extern cvar_t *sv_mapname; -extern cvar_t *sv_mapChecksum; -extern cvar_t *sv_serverid; -extern cvar_t *sv_maxRate; -extern cvar_t *sv_minPing; -extern cvar_t *sv_maxPing; -extern cvar_t *sv_gametype; -extern cvar_t *sv_pure; -extern cvar_t *sv_floodProtect; -extern cvar_t *sv_lanForceRate; -extern cvar_t *sv_strictAuth; - -//=========================================================== - -// -// sv_main.c -// -void SV_FinalMessage (char *message); -void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...); - - -void SV_AddOperatorCommands (void); -void SV_RemoveOperatorCommands (void); - - -void SV_MasterHeartbeat (void); -void SV_MasterShutdown (void); - - - - -// -// sv_init.c -// -void SV_SetConfigstring( int index, const char *val ); -void SV_GetConfigstring( int index, char *buffer, int bufferSize ); - -void SV_SetUserinfo( int index, const char *val ); -void SV_GetUserinfo( int index, char *buffer, int bufferSize ); - -void SV_ChangeMaxClients( void ); -void SV_SpawnServer( char *server, qboolean killBots ); - - - -// -// sv_client.c -// -void SV_GetChallenge( netadr_t from ); - -void SV_DirectConnect( netadr_t from ); - -void SV_AuthorizeIpPacket( netadr_t from ); - -void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); -void SV_UserinfoChanged( client_t *cl ); - -void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); -void SV_DropClient( client_t *drop, const char *reason ); - -void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); -void SV_ClientThink (client_t *cl, usercmd_t *cmd); - -void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ); - -// -// sv_ccmds.c -// -void SV_Heartbeat_f( void ); - -// -// sv_snapshot.c -// -void SV_AddServerCommand( client_t *client, const char *cmd ); -void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ); -void SV_WriteFrameToClient (client_t *client, msg_t *msg); -void SV_SendMessageToClient( msg_t *msg, client_t *client ); -void SV_SendClientMessages( void ); -void SV_SendClientSnapshot( client_t *client ); - -// -// sv_game.c -// -int SV_NumForGentity( sharedEntity_t *ent ); -sharedEntity_t *SV_GentityNum( int num ); -playerState_t *SV_GameClientNum( int num ); -svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ); -sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); -void SV_InitGameProgs ( void ); -void SV_ShutdownGameProgs ( void ); -void SV_RestartGameProgs( void ); -qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); - -// -// sv_bot.c -// -void SV_BotFrame( int time ); -int SV_BotAllocateClient(void); -void SV_BotFreeClient( int clientNum ); - -void SV_BotInitCvars(void); -int SV_BotLibSetup( void ); -int SV_BotLibShutdown( void ); -int SV_BotGetSnapshotEntity( int client, int ent ); -int SV_BotGetConsoleMessage( int client, char *buf, int size ); - -int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points); -void BotImport_DebugPolygonDelete(int id); - -//============================================================ -// -// high level object sorting to reduce interaction tests -// - -void SV_ClearWorld (void); -// called after the world model has been loaded, before linking any entities - -void SV_UnlinkEntity( sharedEntity_t *ent ); -// call before removing an entity, and before trying to move one, -// so it doesn't clip against itself - -void SV_LinkEntity( sharedEntity_t *ent ); -// Needs to be called any time an entity changes origin, mins, maxs, -// or solid. Automatically unlinks if needed. -// sets ent->v.absmin and ent->v.absmax -// sets ent->leafnums[] for pvs determination even if the entity -// is not solid - - -clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ); - - -void SV_SectorList_f( void ); - - -int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); -// fills in a table of entity numbers with entities that have bounding boxes -// that intersect the given area. It is possible for a non-axial bmodel -// to be returned that doesn't actually intersect the area on an exact -// test. -// returns the number of pointers filled in -// The world entity is never returned in this list. - - -int SV_PointContents( const vec3_t p, int passEntityNum ); -// returns the CONTENTS_* value from the world and all entities at the given point. - - -void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ); -// mins and maxs are relative - -// if the entire move stays in a solid volume, trace.allsolid will be set, -// trace.startsolid will be set, and trace.fraction will be 0 - -// if the starting point is in a solid, it will be allowed to move out -// to an open area - -// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE) - - -void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ); -// clip to a specific entity - -// -// sv_net_chan.c -// -void SV_Netchan_Transmit( client_t *client, msg_t *msg); -void SV_Netchan_TransmitNextFragment( client_t *client ); -qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); - +/* +=========================================================================== +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 +=========================================================================== +*/ +// server.h + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../game/g_public.h" +#include "../game/bg_public.h" + +//============================================================================= + +#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND + // GAME BOTH REFERENCE !!! + +#define MAX_ENT_CLUSTERS 16 + +typedef struct svEntity_s { + struct worldSector_s *worldSector; + struct svEntity_s *nextEntityInWorldSector; + + entityState_t baseline; // for delta compression of initial sighting + int numClusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int lastCluster; // if all the clusters don't fit in clusternums + int areanum, areanum2; + int snapshotCounter; // used to prevent double adding from portal views +} svEntity_t; + +typedef enum { + SS_DEAD, // no map loaded + SS_LOADING, // spawning level entities + SS_GAME // actively running +} serverState_t; + +typedef struct { + serverState_t state; + qboolean restarting; // if true, send configstring changes during SS_LOADING + int serverId; // changes each server start + int restartedServerId; // serverId before a map_restart + int checksumFeed; // the feed key that we use to compute the pure checksum strings + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // the serverId associated with the current checksumFeed (always <= serverId) + int checksumFeedServerId; + int snapshotCounter; // incremented for each snapshot built + int timeResidual; // <= 1000 / sv_frame->value + int nextFrameTime; // when time > nextFrameTime, process world + struct cmodel_s *models[MAX_MODELS]; + char *configstrings[MAX_CONFIGSTRINGS]; + svEntity_t svEntities[MAX_GENTITIES]; + + char *entityParsePoint; // used during game VM init + + // the game virtual machine will update these on init and changes + sharedEntity_t *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + playerState_t *gameClients; + int gameClientSize; // will be > sizeof(playerState_t) due to game private data + + int restartTime; +} server_t; + + + + + +typedef struct { + int areabytes; + byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + playerState_t ps; + int num_entities; + int first_entity; // into the circular sv_packet_entities[] + // the entities MUST be in increasing state number + // order, otherwise the delta compression will fail + int messageSent; // time the message was transmitted + int messageAcked; // time the message was acked + int messageSize; // used to rate drop packets +} clientSnapshot_t; + +typedef enum { + CS_FREE, // can be reused for a new connection + CS_ZOMBIE, // client has been disconnected, but don't reuse + // connection for a couple seconds + CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet + CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd + CS_ACTIVE // client is fully in game +} clientState_t; + +typedef struct netchan_buffer_s { + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + struct netchan_buffer_s *next; +} netchan_buffer_t; + +typedef struct client_s { + clientState_t state; + char userinfo[MAX_INFO_STRING]; // name, etc + + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet + int reliableAcknowledge; // last acknowledged reliable message + int reliableSent; // last sent reliable message, not necesarily acknowledged yet + int messageAcknowledge; + + int gamestateMessageNum; // netchan->outgoingSequence of gamestate + int challenge; + + usercmd_t lastUsercmd; + int lastMessageNum; // for delta compression + int lastClientCommand; // reliable client message sequence + char lastClientCommandString[MAX_STRING_CHARS]; + sharedEntity_t *gentity; // SV_GentityNum(clientnum) + char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + + // downloading + char downloadName[MAX_QPATH]; // if not empty string, we are downloading + fileHandle_t download; // file being downloaded + int downloadSize; // total bytes (can't use EOF because of paks) + int downloadCount; // bytes sent + int downloadClientBlock; // last block we sent to the client, awaiting ack + int downloadCurrentBlock; // current block number + int downloadXmitBlock; // last block we xmited + unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks + int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; + qboolean downloadEOF; // We have sent the EOF block + int downloadSendTime; // time we last got an ack from the client + + int deltaMessage; // frame last client usercmd message + int nextReliableTime; // svs.time when another reliable command will be allowed + int lastPacketTime; // svs.time when packet was last received + int lastConnectTime; // svs.time when connection started + int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime + qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec + int timeoutCount; // must timeout a few frames in a row so debugging doesn't break + clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here + int ping; + int rate; // bytes / second + int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked + int pureAuthentic; + qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all + netchan_t netchan; + // TTimo + // queuing outgoing fragmented messages to send them properly, without udp packet bursts + // in case large fragmented messages are stacking up + // buffer them into this queue, and hand them out to netchan as needed + netchan_buffer_t *netchan_start_queue; + netchan_buffer_t **netchan_end_queue; +} client_t; + +//============================================================================= + + +// MAX_CHALLENGES is made large to prevent a denial +// of service attack that could cycle all of them +// out before legitimate users connected +#define MAX_CHALLENGES 1024 + +#define AUTHORIZE_TIMEOUT 5000 + +typedef struct { + netadr_t adr; + int challenge; + int time; // time the last packet was sent to the autherize server + int pingTime; // time the challenge response was sent to client + int firstTime; // time the adr was first used, for authorize timeout checks + qboolean connected; +} challenge_t; + + +#define MAX_MASTERS 8 // max recipients for heartbeat packets + + +// this structure will be cleared only when the game dll changes +typedef struct { + qboolean initialized; // sv_init has completed + + int time; // will be strictly increasing across level changes + + int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() + + client_t *clients; // [sv_maxclients->integer]; + int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES + int nextSnapshotEntities; // next snapshotEntities to use + entityState_t *snapshotEntities; // [numSnapshotEntities] + int nextHeartbeatTime; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + netadr_t redirectAddress; // for rcon return messages + + netadr_t authorizeAddress; // for rcon return messages +} serverStatic_t; + +//============================================================================= + +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map +extern vm_t *gvm; // game virtual machine + +#define MAX_MASTER_SERVERS 5 + +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_rconPassword; +extern cvar_t *sv_privatePassword; +extern cvar_t *sv_allowDownload; +extern cvar_t *sv_maxclients; + +extern cvar_t *sv_privateClients; +extern cvar_t *sv_hostname; +extern cvar_t *sv_master[MAX_MASTER_SERVERS]; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showloss; +extern cvar_t *sv_padPackets; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_maxRate; +extern cvar_t *sv_minPing; +extern cvar_t *sv_maxPing; +extern cvar_t *sv_gametype; +extern cvar_t *sv_pure; +extern cvar_t *sv_floodProtect; +extern cvar_t *sv_lanForceRate; +extern cvar_t *sv_strictAuth; + +//=========================================================== + +// +// sv_main.c +// +void SV_FinalMessage (char *message); +void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...); + + +void SV_AddOperatorCommands (void); +void SV_RemoveOperatorCommands (void); + + +void SV_MasterHeartbeat (void); +void SV_MasterShutdown (void); + + + + +// +// sv_init.c +// +void SV_SetConfigstring( int index, const char *val ); +void SV_GetConfigstring( int index, char *buffer, int bufferSize ); + +void SV_SetUserinfo( int index, const char *val ); +void SV_GetUserinfo( int index, char *buffer, int bufferSize ); + +void SV_ChangeMaxClients( void ); +void SV_SpawnServer( char *server, qboolean killBots ); + + + +// +// sv_client.c +// +void SV_GetChallenge( netadr_t from ); + +void SV_DirectConnect( netadr_t from ); + +void SV_AuthorizeIpPacket( netadr_t from ); + +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); +void SV_UserinfoChanged( client_t *cl ); + +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); +void SV_DropClient( client_t *drop, const char *reason ); + +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); +void SV_ClientThink (client_t *cl, usercmd_t *cmd); + +void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ); + +// +// sv_ccmds.c +// +void SV_Heartbeat_f( void ); + +// +// sv_snapshot.c +// +void SV_AddServerCommand( client_t *client, const char *cmd ); +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ); +void SV_WriteFrameToClient (client_t *client, msg_t *msg); +void SV_SendMessageToClient( msg_t *msg, client_t *client ); +void SV_SendClientMessages( void ); +void SV_SendClientSnapshot( client_t *client ); + +// +// sv_game.c +// +int SV_NumForGentity( sharedEntity_t *ent ); +sharedEntity_t *SV_GentityNum( int num ); +playerState_t *SV_GameClientNum( int num ); +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ); +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); +void SV_InitGameProgs ( void ); +void SV_ShutdownGameProgs ( void ); +void SV_RestartGameProgs( void ); +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); + +// +// sv_bot.c +// +void SV_BotFrame( int time ); +int SV_BotAllocateClient(void); +void SV_BotFreeClient( int clientNum ); + +void SV_BotInitCvars(void); +int SV_BotLibSetup( void ); +int SV_BotLibShutdown( void ); +int SV_BotGetSnapshotEntity( int client, int ent ); +int SV_BotGetConsoleMessage( int client, char *buf, int size ); + +int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points); +void BotImport_DebugPolygonDelete(int id); + +//============================================================ +// +// high level object sorting to reduce interaction tests +// + +void SV_ClearWorld (void); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEntity( sharedEntity_t *ent ); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself + +void SV_LinkEntity( sharedEntity_t *ent ); +// Needs to be called any time an entity changes origin, mins, maxs, +// or solid. Automatically unlinks if needed. +// sets ent->v.absmin and ent->v.absmax +// sets ent->leafnums[] for pvs determination even if the entity +// is not solid + + +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ); + + +void SV_SectorList_f( void ); + + +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); +// fills in a table of entity numbers with entities that have bounding boxes +// that intersect the given area. It is possible for a non-axial bmodel +// to be returned that doesn't actually intersect the area on an exact +// test. +// returns the number of pointers filled in +// The world entity is never returned in this list. + + +int SV_PointContents( const vec3_t p, int passEntityNum ); +// returns the CONTENTS_* value from the world and all entities at the given point. + + +void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ); +// mins and maxs are relative + +// if the entire move stays in a solid volume, trace.allsolid will be set, +// trace.startsolid will be set, and trace.fraction will be 0 + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE) + + +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ); +// clip to a specific entity + +// +// sv_net_chan.c +// +void SV_Netchan_Transmit( client_t *client, msg_t *msg); +void SV_Netchan_TransmitNextFragment( client_t *client ); +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); + diff --git a/code/server/sv_bot.c b/code/server/sv_bot.c index ddb9673..0442d46 100755 --- a/code/server/sv_bot.c +++ b/code/server/sv_bot.c @@ -1,635 +1,635 @@ -/* -=========================================================================== -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 -=========================================================================== -*/ -// sv_bot.c - -#include "server.h" -#include "../game/botlib.h" - -typedef struct bot_debugpoly_s -{ - int inuse; - int color; - int numPoints; - vec3_t points[128]; -} bot_debugpoly_t; - -static bot_debugpoly_t *debugpolygons; -int bot_maxdebugpolys; - -extern botlib_export_t *botlib_export; -int bot_enable; - - -/* -================== -SV_BotAllocateClient -================== -*/ -int SV_BotAllocateClient(void) { - int i; - client_t *cl; - - // find a client slot - for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { - if ( cl->state == CS_FREE ) { - break; - } - } - - if ( i == sv_maxclients->integer ) { - return -1; - } - - cl->gentity = SV_GentityNum( i ); - cl->gentity->s.number = i; - cl->state = CS_ACTIVE; - cl->lastPacketTime = svs.time; - cl->netchan.remoteAddress.type = NA_BOT; - cl->rate = 16384; - - return i; -} - -/* -================== -SV_BotFreeClient -================== -*/ -void SV_BotFreeClient( int clientNum ) { - client_t *cl; - - if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { - Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum ); - } - cl = &svs.clients[clientNum]; - cl->state = CS_FREE; - cl->name[0] = 0; - if ( cl->gentity ) { - cl->gentity->r.svFlags &= ~SVF_BOT; - } -} - -/* -================== -BotDrawDebugPolygons -================== -*/ -void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value) { - static cvar_t *bot_debug, *bot_groundonly, *bot_reachability, *bot_highlightarea; - bot_debugpoly_t *poly; - int i, parm0; - - if (!debugpolygons) - return; - //bot debugging - if (!bot_debug) bot_debug = Cvar_Get("bot_debug", "0", 0); - // - if (bot_enable && bot_debug->integer) { - //show reachabilities - if (!bot_reachability) bot_reachability = Cvar_Get("bot_reachability", "0", 0); - //show ground faces only - if (!bot_groundonly) bot_groundonly = Cvar_Get("bot_groundonly", "1", 0); - //get the hightlight area - if (!bot_highlightarea) bot_highlightarea = Cvar_Get("bot_highlightarea", "0", 0); - // - parm0 = 0; - if (svs.clients[0].lastUsercmd.buttons & BUTTON_ATTACK) parm0 |= 1; - if (bot_reachability->integer) parm0 |= 2; - if (bot_groundonly->integer) parm0 |= 4; - botlib_export->BotLibVarSet("bot_highlightarea", bot_highlightarea->string); - botlib_export->Test(parm0, NULL, svs.clients[0].gentity->r.currentOrigin, - svs.clients[0].gentity->r.currentAngles); - } //end if - //draw all debug polys - for (i = 0; i < bot_maxdebugpolys; i++) { - poly = &debugpolygons[i]; - if (!poly->inuse) continue; - drawPoly(poly->color, poly->numPoints, (float *) poly->points); - //Com_Printf("poly %i, numpoints = %d\n", i, poly->numPoints); - } -} - -/* -================== -BotImport_Print -================== -*/ -void QDECL BotImport_Print(int type, char *fmt, ...) -{ - char str[2048]; - va_list ap; - - va_start(ap, fmt); - vsprintf(str, fmt, ap); - va_end(ap); - - switch(type) { - case PRT_MESSAGE: { - Com_Printf("%s", str); - break; - } - case PRT_WARNING: { - Com_Printf(S_COLOR_YELLOW "Warning: %s", str); - break; - } - case PRT_ERROR: { - Com_Printf(S_COLOR_RED "Error: %s", str); - break; - } - case PRT_FATAL: { - Com_Printf(S_COLOR_RED "Fatal: %s", str); - break; - } - case PRT_EXIT: { - Com_Error(ERR_DROP, S_COLOR_RED "Exit: %s", str); - break; - } - default: { - Com_Printf("unknown print type\n"); - break; - } - } -} - -/* -================== -BotImport_Trace -================== -*/ -void BotImport_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { - trace_t trace; - - SV_Trace(&trace, start, mins, maxs, end, passent, contentmask, qfalse); - //copy the trace information - bsptrace->allsolid = trace.allsolid; - bsptrace->startsolid = trace.startsolid; - bsptrace->fraction = trace.fraction; - VectorCopy(trace.endpos, bsptrace->endpos); - bsptrace->plane.dist = trace.plane.dist; - VectorCopy(trace.plane.normal, bsptrace->plane.normal); - bsptrace->plane.signbits = trace.plane.signbits; - bsptrace->plane.type = trace.plane.type; - bsptrace->surface.value = trace.surfaceFlags; - bsptrace->ent = trace.entityNum; - bsptrace->exp_dist = 0; - bsptrace->sidenum = 0; - bsptrace->contents = 0; -} - -/* -================== -BotImport_EntityTrace -================== -*/ -void BotImport_EntityTrace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask) { - trace_t trace; - - SV_ClipToEntity(&trace, start, mins, maxs, end, entnum, contentmask, qfalse); - //copy the trace information - bsptrace->allsolid = trace.allsolid; - bsptrace->startsolid = trace.startsolid; - bsptrace->fraction = trace.fraction; - VectorCopy(trace.endpos, bsptrace->endpos); - bsptrace->plane.dist = trace.plane.dist; - VectorCopy(trace.plane.normal, bsptrace->plane.normal); - bsptrace->plane.signbits = trace.plane.signbits; - bsptrace->plane.type = trace.plane.type; - bsptrace->surface.value = trace.surfaceFlags; - bsptrace->ent = trace.entityNum; - bsptrace->exp_dist = 0; - bsptrace->sidenum = 0; - bsptrace->contents = 0; -} - - -/* -================== -BotImport_PointContents -================== -*/ -int BotImport_PointContents(vec3_t point) { - return SV_PointContents(point, -1); -} - -/* -================== -BotImport_inPVS -================== -*/ -int BotImport_inPVS(vec3_t p1, vec3_t p2) { - return SV_inPVS (p1, p2); -} - -/* -================== -BotImport_BSPEntityData -================== -*/ -char *BotImport_BSPEntityData(void) { - return CM_EntityString(); -} - -/* -================== -BotImport_BSPModelMinsMaxsOrigin -================== -*/ -void BotImport_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin) { - clipHandle_t h; - vec3_t mins, maxs; - float max; - int i; - - h = CM_InlineModel(modelnum); - CM_ModelBounds(h, mins, maxs); - //if the model is rotated - if ((angles[0] || angles[1] || angles[2])) { - // expand for rotation - - max = RadiusFromBounds(mins, maxs); - for (i = 0; i < 3; i++) { - mins[i] = -max; - maxs[i] = max; - } - } - if (outmins) VectorCopy(mins, outmins); - if (outmaxs) VectorCopy(maxs, outmaxs); - if (origin) VectorClear(origin); -} - -/* -================== -BotImport_GetMemory -================== -*/ -void *BotImport_GetMemory(int size) { - void *ptr; - - ptr = Z_TagMalloc( size, TAG_BOTLIB ); - return ptr; -} - -/* -================== -BotImport_FreeMemory -================== -*/ -void BotImport_FreeMemory(void *ptr) { - Z_Free(ptr); -} - -/* -================= -BotImport_HunkAlloc -================= -*/ -void *BotImport_HunkAlloc( int size ) { - if( Hunk_CheckMark() ) { - Com_Error( ERR_DROP, "SV_Bot_HunkAlloc: Alloc with marks already set\n" ); - } - return Hunk_Alloc( size, h_high ); -} - -/* -================== -BotImport_DebugPolygonCreate -================== -*/ -int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points) { - bot_debugpoly_t *poly; - int i; - - if (!debugpolygons) - return 0; - - for (i = 1; i < bot_maxdebugpolys; i++) { - if (!debugpolygons[i].inuse) - break; - } - if (i >= bot_maxdebugpolys) - return 0; - poly = &debugpolygons[i]; - poly->inuse = qtrue; - poly->color = color; - poly->numPoints = numPoints; - Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t)); - // - return i; -} - -/* -================== -BotImport_DebugPolygonShow -================== -*/ -void BotImport_DebugPolygonShow(int id, int color, int numPoints, vec3_t *points) { - bot_debugpoly_t *poly; - - if (!debugpolygons) return; - poly = &debugpolygons[id]; - poly->inuse = qtrue; - poly->color = color; - poly->numPoints = numPoints; - Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t)); -} - -/* -================== -BotImport_DebugPolygonDelete -================== -*/ -void BotImport_DebugPolygonDelete(int id) -{ - if (!debugpolygons) return; - debugpolygons[id].inuse = qfalse; -} - -/* -================== -BotImport_DebugLineCreate -================== -*/ -int BotImport_DebugLineCreate(void) { - vec3_t points[1]; - return BotImport_DebugPolygonCreate(0, 0, points); -} - -/* -================== -BotImport_DebugLineDelete -================== -*/ -void BotImport_DebugLineDelete(int line) { - BotImport_DebugPolygonDelete(line); -} - -/* -================== -BotImport_DebugLineShow -================== -*/ -void BotImport_DebugLineShow(int line, vec3_t start, vec3_t end, int color) { - vec3_t points[4], dir, cross, up = {0, 0, 1}; - float dot; - - VectorCopy(start, points[0]); - VectorCopy(start, points[1]); - //points[1][2] -= 2; - VectorCopy(end, points[2]); - //points[2][2] -= 2; - VectorCopy(end, points[3]); - - - VectorSubtract(end, start, dir); - VectorNormalize(dir); - dot = DotProduct(dir, up); - if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); - else CrossProduct(dir, up, cross); - - VectorNormalize(cross); - - VectorMA(points[0], 2, cross, points[0]); - VectorMA(points[1], -2, cross, points[1]); - VectorMA(points[2], -2, cross, points[2]); - VectorMA(points[3], 2, cross, points[3]); - - BotImport_DebugPolygonShow(line, color, 4, points); -} - -/* -================== -SV_BotClientCommand -================== -*/ -void BotClientCommand( int client, char *command ) { - SV_ExecuteClientCommand( &svs.clients[client], command, qtrue ); -} - -/* -================== -SV_BotFrame -================== -*/ -void SV_BotFrame( int time ) { - if (!bot_enable) return; - //NOTE: maybe the game is already shutdown - if (!gvm) return; - VM_Call( gvm, BOTAI_START_FRAME, time ); -} - -/* -=============== -SV_BotLibSetup -=============== -*/ -int SV_BotLibSetup( void ) { - if (!bot_enable) { - return 0; - } - - if ( !botlib_export ) { - Com_Printf( S_COLOR_RED "Error: SV_BotLibSetup without SV_BotInitBotLib\n" ); - return -1; - } - - return botlib_export->BotLibSetup(); -} - -/* -=============== -SV_ShutdownBotLib - -Called when either the entire server is being killed, or -it is changing to a different game directory. -=============== -*/ -int SV_BotLibShutdown( void ) { - - if ( !botlib_export ) { - return -1; - } - - return botlib_export->BotLibShutdown(); -} - -/* -================== -SV_BotInitCvars -================== -*/ -void SV_BotInitCvars(void) { - - Cvar_Get("bot_enable", "1", 0); //enable the bot - Cvar_Get("bot_developer", "0", CVAR_CHEAT); //bot developer mode - Cvar_Get("bot_debug", "0", CVAR_CHEAT); //enable bot debugging - Cvar_Get("bot_maxdebugpolys", "2", 0); //maximum number of debug polys - Cvar_Get("bot_groundonly", "1", 0); //only show ground faces of areas - Cvar_Get("bot_reachability", "0", 0); //show all reachabilities to other areas - Cvar_Get("bot_visualizejumppads", "0", CVAR_CHEAT); //show jumppads - Cvar_Get("bot_forceclustering", "0", 0); //force cluster calculations - Cvar_Get("bot_forcereachability", "0", 0); //force reachability calculations - Cvar_Get("bot_forcewrite", "0", 0); //force writing aas file - Cvar_Get("bot_aasoptimize", "0", 0); //no aas file optimisation - Cvar_Get("bot_saveroutingcache", "0", 0); //save routing cache - Cvar_Get("bot_thinktime", "100", CVAR_CHEAT); //msec the bots thinks - Cvar_Get("bot_reloadcharacters", "0", 0); //reload the bot characters each time - Cvar_Get("bot_testichat", "0", 0); //test ichats - Cvar_Get("bot_testrchat", "0", 0); //test rchats - Cvar_Get("bot_testsolid", "0", CVAR_CHEAT); //test for solid areas - Cvar_Get("bot_testclusters", "0", CVAR_CHEAT); //test the AAS clusters - Cvar_Get("bot_fastchat", "0", 0); //fast chatting bots - Cvar_Get("bot_nochat", "0", 0); //disable chats - Cvar_Get("bot_pause", "0", CVAR_CHEAT); //pause the bots thinking - Cvar_Get("bot_report", "0", CVAR_CHEAT); //get a full report in ctf - Cvar_Get("bot_grapple", "0", 0); //enable grapple - Cvar_Get("bot_rocketjump", "1", 0); //enable rocket jumping - Cvar_Get("bot_challenge", "0", 0); //challenging bot - Cvar_Get("bot_minplayers", "0", 0); //minimum players in a team or the game - Cvar_Get("bot_interbreedchar", "", CVAR_CHEAT); //bot character used for interbreeding - Cvar_Get("bot_interbreedbots", "10", CVAR_CHEAT); //number of bots used for interbreeding - Cvar_Get("bot_interbreedcycle", "20", CVAR_CHEAT); //bot interbreeding cycle - Cvar_Get("bot_interbreedwrite", "", CVAR_CHEAT); //write interbreeded bots to this file -} - -/* -================== -SV_BotInitBotLib -================== -*/ -void SV_BotInitBotLib(void) { - botlib_import_t botlib_import; - - if ( !Cvar_VariableValue("fs_restrict") && !Sys_CheckCD() ) { - Com_Error( ERR_NEED_CD, "Game CD not in drive" ); - } - - if (debugpolygons) Z_Free(debugpolygons); - bot_maxdebugpolys = Cvar_VariableIntegerValue("bot_maxdebugpolys"); - debugpolygons = Z_Malloc(sizeof(bot_debugpoly_t) * bot_maxdebugpolys); - - botlib_import.Print = BotImport_Print; - botlib_import.Trace = BotImport_Trace; - botlib_import.EntityTrace = BotImport_EntityTrace; - botlib_import.PointContents = BotImport_PointContents; - botlib_import.inPVS = BotImport_inPVS; - botlib_import.BSPEntityData = BotImport_BSPEntityData; - botlib_import.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; - botlib_import.BotClientCommand = BotClientCommand; - - //memory management - botlib_import.GetMemory = BotImport_GetMemory; - botlib_import.FreeMemory = BotImport_FreeMemory; - botlib_import.AvailableMemory = Z_AvailableMemory; - botlib_import.HunkAlloc = BotImport_HunkAlloc; - - // file system access - botlib_import.FS_FOpenFile = FS_FOpenFileByMode; - botlib_import.FS_Read = FS_Read2; - botlib_import.FS_Write = FS_Write; - botlib_import.FS_FCloseFile = FS_FCloseFile; - botlib_import.FS_Seek = FS_Seek; - - //debug lines - botlib_import.DebugLineCreate = BotImport_DebugLineCreate; - botlib_import.DebugLineDelete = BotImport_DebugLineDelete; - botlib_import.DebugLineShow = BotImport_DebugLineShow; - - //debug polygons - botlib_import.DebugPolygonCreate = BotImport_DebugPolygonCreate; - botlib_import.DebugPolygonDelete = BotImport_DebugPolygonDelete; - - botlib_export = (botlib_export_t *)GetBotLibAPI( BOTLIB_API_VERSION, &botlib_import ); - assert(botlib_export); // bk001129 - somehow we end up with a zero import. -} - - -// -// * * * BOT AI CODE IS BELOW THIS POINT * * * -// - -/* -================== -SV_BotGetConsoleMessage -================== -*/ -int SV_BotGetConsoleMessage( int client, char *buf, int size ) -{ - client_t *cl; - int index; - - cl = &svs.clients[client]; - cl->lastPacketTime = svs.time; - - if ( cl->reliableAcknowledge == cl->reliableSequence ) { - return qfalse; - } - - cl->reliableAcknowledge++; - index = cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ); - - if ( !cl->reliableCommands[index][0] ) { - return qfalse; - } - - Q_strncpyz( buf, cl->reliableCommands[index], size ); - return qtrue; -} - -#if 0 -/* -================== -EntityInPVS -================== -*/ -int EntityInPVS( int client, int entityNum ) { - client_t *cl; - clientSnapshot_t *frame; - int i; - - cl = &svs.clients[client]; - frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; - for ( i = 0; i < frame->num_entities; i++ ) { - if ( svs.snapshotEntities[(frame->first_entity + i) % svs.numSnapshotEntities].number == entityNum ) { - return qtrue; - } - } - return qfalse; -} -#endif - -/* -================== -SV_BotGetSnapshotEntity -================== -*/ -int SV_BotGetSnapshotEntity( int client, int sequence ) { - client_t *cl; - clientSnapshot_t *frame; - - cl = &svs.clients[client]; - frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; - if (sequence < 0 || sequence >= frame->num_entities) { - return -1; - } - return svs.snapshotEntities[(frame->first_entity + sequence) % svs.numSnapshotEntities].number; -} - +/* +=========================================================================== +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 +=========================================================================== +*/ +// sv_bot.c + +#include "server.h" +#include "../game/botlib.h" + +typedef struct bot_debugpoly_s +{ + int inuse; + int color; + int numPoints; + vec3_t points[128]; +} bot_debugpoly_t; + +static bot_debugpoly_t *debugpolygons; +int bot_maxdebugpolys; + +extern botlib_export_t *botlib_export; +int bot_enable; + + +/* +================== +SV_BotAllocateClient +================== +*/ +int SV_BotAllocateClient(void) { + int i; + client_t *cl; + + // find a client slot + for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + if ( cl->state == CS_FREE ) { + break; + } + } + + if ( i == sv_maxclients->integer ) { + return -1; + } + + cl->gentity = SV_GentityNum( i ); + cl->gentity->s.number = i; + cl->state = CS_ACTIVE; + cl->lastPacketTime = svs.time; + cl->netchan.remoteAddress.type = NA_BOT; + cl->rate = 16384; + + return i; +} + +/* +================== +SV_BotFreeClient +================== +*/ +void SV_BotFreeClient( int clientNum ) { + client_t *cl; + + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum ); + } + cl = &svs.clients[clientNum]; + cl->state = CS_FREE; + cl->name[0] = 0; + if ( cl->gentity ) { + cl->gentity->r.svFlags &= ~SVF_BOT; + } +} + +/* +================== +BotDrawDebugPolygons +================== +*/ +void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value) { + static cvar_t *bot_debug, *bot_groundonly, *bot_reachability, *bot_highlightarea; + bot_debugpoly_t *poly; + int i, parm0; + + if (!debugpolygons) + return; + //bot debugging + if (!bot_debug) bot_debug = Cvar_Get("bot_debug", "0", 0); + // + if (bot_enable && bot_debug->integer) { + //show reachabilities + if (!bot_reachability) bot_reachability = Cvar_Get("bot_reachability", "0", 0); + //show ground faces only + if (!bot_groundonly) bot_groundonly = Cvar_Get("bot_groundonly", "1", 0); + //get the hightlight area + if (!bot_highlightarea) bot_highlightarea = Cvar_Get("bot_highlightarea", "0", 0); + // + parm0 = 0; + if (svs.clients[0].lastUsercmd.buttons & BUTTON_ATTACK) parm0 |= 1; + if (bot_reachability->integer) parm0 |= 2; + if (bot_groundonly->integer) parm0 |= 4; + botlib_export->BotLibVarSet("bot_highlightarea", bot_highlightarea->string); + botlib_export->Test(parm0, NULL, svs.clients[0].gentity->r.currentOrigin, + svs.clients[0].gentity->r.currentAngles); + } //end if + //draw all debug polys + for (i = 0; i < bot_maxdebugpolys; i++) { + poly = &debugpolygons[i]; + if (!poly->inuse) continue; + drawPoly(poly->color, poly->numPoints, (float *) poly->points); + //Com_Printf("poly %i, numpoints = %d\n", i, poly->numPoints); + } +} + +/* +================== +BotImport_Print +================== +*/ +void QDECL BotImport_Print(int type, char *fmt, ...) +{ + char str[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(str, fmt, ap); + va_end(ap); + + switch(type) { + case PRT_MESSAGE: { + Com_Printf("%s", str); + break; + } + case PRT_WARNING: { + Com_Printf(S_COLOR_YELLOW "Warning: %s", str); + break; + } + case PRT_ERROR: { + Com_Printf(S_COLOR_RED "Error: %s", str); + break; + } + case PRT_FATAL: { + Com_Printf(S_COLOR_RED "Fatal: %s", str); + break; + } + case PRT_EXIT: { + Com_Error(ERR_DROP, S_COLOR_RED "Exit: %s", str); + break; + } + default: { + Com_Printf("unknown print type\n"); + break; + } + } +} + +/* +================== +BotImport_Trace +================== +*/ +void BotImport_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { + trace_t trace; + + SV_Trace(&trace, start, mins, maxs, end, passent, contentmask, qfalse); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotImport_EntityTrace +================== +*/ +void BotImport_EntityTrace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask) { + trace_t trace; + + SV_ClipToEntity(&trace, start, mins, maxs, end, entnum, contentmask, qfalse); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + + +/* +================== +BotImport_PointContents +================== +*/ +int BotImport_PointContents(vec3_t point) { + return SV_PointContents(point, -1); +} + +/* +================== +BotImport_inPVS +================== +*/ +int BotImport_inPVS(vec3_t p1, vec3_t p2) { + return SV_inPVS (p1, p2); +} + +/* +================== +BotImport_BSPEntityData +================== +*/ +char *BotImport_BSPEntityData(void) { + return CM_EntityString(); +} + +/* +================== +BotImport_BSPModelMinsMaxsOrigin +================== +*/ +void BotImport_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin) { + clipHandle_t h; + vec3_t mins, maxs; + float max; + int i; + + h = CM_InlineModel(modelnum); + CM_ModelBounds(h, mins, maxs); + //if the model is rotated + if ((angles[0] || angles[1] || angles[2])) { + // expand for rotation + + max = RadiusFromBounds(mins, maxs); + for (i = 0; i < 3; i++) { + mins[i] = -max; + maxs[i] = max; + } + } + if (outmins) VectorCopy(mins, outmins); + if (outmaxs) VectorCopy(maxs, outmaxs); + if (origin) VectorClear(origin); +} + +/* +================== +BotImport_GetMemory +================== +*/ +void *BotImport_GetMemory(int size) { + void *ptr; + + ptr = Z_TagMalloc( size, TAG_BOTLIB ); + return ptr; +} + +/* +================== +BotImport_FreeMemory +================== +*/ +void BotImport_FreeMemory(void *ptr) { + Z_Free(ptr); +} + +/* +================= +BotImport_HunkAlloc +================= +*/ +void *BotImport_HunkAlloc( int size ) { + if( Hunk_CheckMark() ) { + Com_Error( ERR_DROP, "SV_Bot_HunkAlloc: Alloc with marks already set\n" ); + } + return Hunk_Alloc( size, h_high ); +} + +/* +================== +BotImport_DebugPolygonCreate +================== +*/ +int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points) { + bot_debugpoly_t *poly; + int i; + + if (!debugpolygons) + return 0; + + for (i = 1; i < bot_maxdebugpolys; i++) { + if (!debugpolygons[i].inuse) + break; + } + if (i >= bot_maxdebugpolys) + return 0; + poly = &debugpolygons[i]; + poly->inuse = qtrue; + poly->color = color; + poly->numPoints = numPoints; + Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t)); + // + return i; +} + +/* +================== +BotImport_DebugPolygonShow +================== +*/ +void BotImport_DebugPolygonShow(int id, int color, int numPoints, vec3_t *points) { + bot_debugpoly_t *poly; + + if (!debugpolygons) return; + poly = &debugpolygons[id]; + poly->inuse = qtrue; + poly->color = color; + poly->numPoints = numPoints; + Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t)); +} + +/* +================== +BotImport_DebugPolygonDelete +================== +*/ +void BotImport_DebugPolygonDelete(int id) +{ + if (!debugpolygons) return; + debugpolygons[id].inuse = qfalse; +} + +/* +================== +BotImport_DebugLineCreate +================== +*/ +int BotImport_DebugLineCreate(void) { + vec3_t points[1]; + return BotImport_DebugPolygonCreate(0, 0, points); +} + +/* +================== +BotImport_DebugLineDelete +================== +*/ +void BotImport_DebugLineDelete(int line) { + BotImport_DebugPolygonDelete(line); +} + +/* +================== +BotImport_DebugLineShow +================== +*/ +void BotImport_DebugLineShow(int line, vec3_t start, vec3_t end, int color) { + vec3_t points[4], dir, cross, up = {0, 0, 1}; + float dot; + + VectorCopy(start, points[0]); + VectorCopy(start, points[1]); + //points[1][2] -= 2; + VectorCopy(end, points[2]); + //points[2][2] -= 2; + VectorCopy(end, points[3]); + + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + dot = DotProduct(dir, up); + if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); + else CrossProduct(dir, up, cross); + + VectorNormalize(cross); + + VectorMA(points[0], 2, cross, points[0]); + VectorMA(points[1], -2, cross, points[1]); + VectorMA(points[2], -2, cross, points[2]); + VectorMA(points[3], 2, cross, points[3]); + + BotImport_DebugPolygonShow(line, color, 4, points); +} + +/* +================== +SV_BotClientCommand +================== +*/ +void BotClientCommand( int client, char *command ) { + SV_ExecuteClientCommand( &svs.clients[client], command, qtrue ); +} + +/* +================== +SV_BotFrame +================== +*/ +void SV_BotFrame( int time ) { + if (!bot_enable) return; + //NOTE: maybe the game is already shutdown + if (!gvm) return; + VM_Call( gvm, BOTAI_START_FRAME, time ); +} + +/* +=============== +SV_BotLibSetup +=============== +*/ +int SV_BotLibSetup( void ) { + if (!bot_enable) { + return 0; + } + + if ( !botlib_export ) { + Com_Printf( S_COLOR_RED "Error: SV_BotLibSetup without SV_BotInitBotLib\n" ); + return -1; + } + + return botlib_export->BotLibSetup(); +} + +/* +=============== +SV_ShutdownBotLib + +Called when either the entire server is being killed, or +it is changing to a different game directory. +=============== +*/ +int SV_BotLibShutdown( void ) { + + if ( !botlib_export ) { + return -1; + } + + return botlib_export->BotLibShutdown(); +} + +/* +================== +SV_BotInitCvars +================== +*/ +void SV_BotInitCvars(void) { + + Cvar_Get("bot_enable", "1", 0); //enable the bot + Cvar_Get("bot_developer", "0", CVAR_CHEAT); //bot developer mode + Cvar_Get("bot_debug", "0", CVAR_CHEAT); //enable bot debugging + Cvar_Get("bot_maxdebugpolys", "2", 0); //maximum number of debug polys + Cvar_Get("bot_groundonly", "1", 0); //only show ground faces of areas + Cvar_Get("bot_reachability", "0", 0); //show all reachabilities to other areas + Cvar_Get("bot_visualizejumppads", "0", CVAR_CHEAT); //show jumppads + Cvar_Get("bot_forceclustering", "0", 0); //force cluster calculations + Cvar_Get("bot_forcereachability", "0", 0); //force reachability calculations + Cvar_Get("bot_forcewrite", "0", 0); //force writing aas file + Cvar_Get("bot_aasoptimize", "0", 0); //no aas file optimisation + Cvar_Get("bot_saveroutingcache", "0", 0); //save routing cache + Cvar_Get("bot_thinktime", "100", CVAR_CHEAT); //msec the bots thinks + Cvar_Get("bot_reloadcharacters", "0", 0); //reload the bot characters each time + Cvar_Get("bot_testichat", "0", 0); //test ichats + Cvar_Get("bot_testrchat", "0", 0); //test rchats + Cvar_Get("bot_testsolid", "0", CVAR_CHEAT); //test for solid areas + Cvar_Get("bot_testclusters", "0", CVAR_CHEAT); //test the AAS clusters + Cvar_Get("bot_fastchat", "0", 0); //fast chatting bots + Cvar_Get("bot_nochat", "0", 0); //disable chats + Cvar_Get("bot_pause", "0", CVAR_CHEAT); //pause the bots thinking + Cvar_Get("bot_report", "0", CVAR_CHEAT); //get a full report in ctf + Cvar_Get("bot_grapple", "0", 0); //enable grapple + Cvar_Get("bot_rocketjump", "1", 0); //enable rocket jumping + Cvar_Get("bot_challenge", "0", 0); //challenging bot + Cvar_Get("bot_minplayers", "0", 0); //minimum players in a team or the game + Cvar_Get("bot_interbreedchar", "", CVAR_CHEAT); //bot character used for interbreeding + Cvar_Get("bot_interbreedbots", "10", CVAR_CHEAT); //number of bots used for interbreeding + Cvar_Get("bot_interbreedcycle", "20", CVAR_CHEAT); //bot interbreeding cycle + Cvar_Get("bot_interbreedwrite", "", CVAR_CHEAT); //write interbreeded bots to this file +} + +/* +================== +SV_BotInitBotLib +================== +*/ +void SV_BotInitBotLib(void) { + botlib_import_t botlib_import; + + if ( !Cvar_VariableValue("fs_restrict") && !Sys_CheckCD() ) { + Com_Error( ERR_NEED_CD, "Game CD not in drive" ); + } + + if (debugpolygons) Z_Free(debugpolygons); + bot_maxdebugpolys = Cvar_VariableIntegerValue("bot_maxdebugpolys"); + debugpolygons = Z_Malloc(sizeof(bot_debugpoly_t) * bot_maxdebugpolys); + + botlib_import.Print = BotImport_Print; + botlib_import.Trace = BotImport_Trace; + botlib_import.EntityTrace = BotImport_EntityTrace; + botlib_import.PointContents = BotImport_PointContents; + botlib_import.inPVS = BotImport_inPVS; + botlib_import.BSPEntityData = BotImport_BSPEntityData; + botlib_import.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; + botlib_import.BotClientCommand = BotClientCommand; + + //memory management + botlib_import.GetMemory = BotImport_GetMemory; + botlib_import.FreeMemory = BotImport_FreeMemory; + botlib_import.AvailableMemory = Z_AvailableMemory; + botlib_import.HunkAlloc = BotImport_HunkAlloc; + + // file system access + botlib_import.FS_FOpenFile = FS_FOpenFileByMode; + botlib_import.FS_Read = FS_Read2; + botlib_import.FS_Write = FS_Write; + botlib_import.FS_FCloseFile = FS_FCloseFile; + botlib_import.FS_Seek = FS_Seek; + + //debug lines + botlib_import.DebugLineCreate = BotImport_DebugLineCreate; + botlib_import.DebugLineDelete = BotImport_DebugLineDelete; + botlib_import.DebugLineShow = BotImport_DebugLineShow; + + //debug polygons + botlib_import.DebugPolygonCreate = BotImport_DebugPolygonCreate; + botlib_import.DebugPolygonDelete = BotImport_DebugPolygonDelete; + + botlib_export = (botlib_export_t *)GetBotLibAPI( BOTLIB_API_VERSION, &botlib_import ); + assert(botlib_export); // bk001129 - somehow we end up with a zero import. +} + + +// +// * * * BOT AI CODE IS BELOW THIS POINT * * * +// + +/* +================== +SV_BotGetConsoleMessage +================== +*/ +int SV_BotGetConsoleMessage( int client, char *buf, int size ) +{ + client_t *cl; + int index; + + cl = &svs.clients[client]; + cl->lastPacketTime = svs.time; + + if ( cl->reliableAcknowledge == cl->reliableSequence ) { + return qfalse; + } + + cl->reliableAcknowledge++; + index = cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ); + + if ( !cl->reliableCommands[index][0] ) { + return qfalse; + } + + Q_strncpyz( buf, cl->reliableCommands[index], size ); + return qtrue; +} + +#if 0 +/* +================== +EntityInPVS +================== +*/ +int EntityInPVS( int client, int entityNum ) { + client_t *cl; + clientSnapshot_t *frame; + int i; + + cl = &svs.clients[client]; + frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + for ( i = 0; i < frame->num_entities; i++ ) { + if ( svs.snapshotEntities[(frame->first_entity + i) % svs.numSnapshotEntities].number == entityNum ) { + return qtrue; + } + } + return qfalse; +} +#endif + +/* +================== +SV_BotGetSnapshotEntity +================== +*/ +int SV_BotGetSnapshotEntity( int client, int sequence ) { + client_t *cl; + clientSnapshot_t *frame; + + cl = &svs.clients[client]; + frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + if (sequence < 0 || sequence >= frame->num_entities) { + return -1; + } + return svs.snapshotEntities[(frame->first_entity + sequence) % svs.numSnapshotEntities].number; +} + diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c index 62ae179..03bd2c9 100755 --- a/code/server/sv_ccmds.c +++ b/code/server/sv_ccmds.c @@ -1,762 +1,762 @@ -/* -=========================================================================== -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" - -/* -=============================================================================== - -OPERATOR CONSOLE ONLY COMMANDS - -These commands can only be entered from stdin or by a remote operator datagram -=============================================================================== -*/ - - -/* -================== -SV_GetPlayerByName - -Returns the player with name from Cmd_Argv(1) -================== -*/ -static client_t *SV_GetPlayerByName( void ) { - client_t *cl; - int i; - char *s; - char cleanName[64]; - - // 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 for a name match - for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { - if ( !cl->state ) { - 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; - 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->integer ) { - Com_Printf( "Bad client slot: %i\n", idnum ); - return NULL; - } - - cl = &svs.clients[idnum]; - if ( !cl->state ) { - Com_Printf( "Client %i is not active\n", idnum ); - return NULL; - } - return cl; - - return NULL; -} - -//========================================================= - - -/* -================== -SV_Map_f - -Restart the server on a different map -================== -*/ -static void SV_Map_f( void ) { - char *cmd; - char *map; - qboolean killBots, cheat; - char expanded[MAX_QPATH]; - char mapname[MAX_QPATH]; - - map = Cmd_Argv(1); - if ( !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); - if ( FS_ReadFile (expanded, NULL) == -1 ) { - Com_Printf ("Can't find map %s\n", expanded); - return; - } - - // force latched values to get set - Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH ); - - cmd = Cmd_Argv(0); - if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) { - Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER ); - Cvar_SetValue( "g_doWarmup", 0 ); - // may not set sv_maxclients directly, always set latched - Cvar_SetLatched( "sv_maxclients", "8" ); - cmd += 2; - cheat = qfalse; - killBots = qtrue; - } - else { - if ( !Q_stricmp( cmd, "devmap" ) || !Q_stricmp( cmd, "spdevmap" ) ) { - cheat = qtrue; - killBots = qtrue; - } else { - cheat = qfalse; - killBots = qfalse; - } - if( sv_gametype->integer == GT_SINGLE_PLAYER ) { - Cvar_SetValue( "g_gametype", GT_FFA ); - } - } - - // 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, killBots ); - - // set the cheat value - // if the level was started with "map ", then - // cheats will not be allowed. If started with "devmap " - // then cheats will be allowed - if ( cheat ) { - Cvar_Set( "sv_cheats", "1" ); - } else { - Cvar_Set( "sv_cheats", "0" ); - } -} - -/* -================ -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; - char *denied; - qboolean isBot; - int delay; - - // make sure we aren't restarting twice in the same frame - if ( com_frameTime == sv.serverId ) { - return; - } - - // make sure server is running - if ( !com_sv_running->integer ) { - Com_Printf( "Server is not running.\n" ); - return; - } - - if ( sv.restartTime ) { - return; - } - - if (Cmd_Argc() > 1 ) { - delay = atoi( Cmd_Argv(1) ); - } - else { - delay = 5; - } - if( delay && !Cvar_VariableValue("g_doWarmup") ) { - sv.restartTime = svs.time + delay * 1000; - SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) ); - return; - } - - // check for changes in variables that can't just be restarted - // check for maxclients change - if ( sv_maxclients->modified || sv_gametype->modified ) { - char mapname[MAX_QPATH]; - - Com_Printf( "variable change -- restarting.\n" ); - // restart the map the slow way - Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); - - SV_SpawnServer( mapname, qfalse ); - return; - } - - // toggle the server bit so clients can detect that a - // map_restart has happened - svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; - - // generate a new serverid - // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart - sv.serverId = com_frameTime; - Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); - - // 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++ ) { - VM_Call( gvm, GAME_RUN_FRAME, svs.time ); - svs.time += 100; - } - - sv.state = SS_GAME; - sv.restarting = qfalse; - - // connect and begin all the clients - for (i=0 ; iinteger ; 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 = VM_ExplicitArgPtr( gvm, VM_Call( gvm, 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(%d): dropped client %i - denied!\n", delay, i ); // bk010125 - continue; - } - - client->state = CS_ACTIVE; - - SV_ClientEnterWorld( client, &client->lastUsercmd ); - } - - // run another frame to allow things to look at all the players - VM_Call( gvm, GAME_RUN_FRAME, svs.time ); - svs.time += 100; -} - -//=============================================================== - -/* -================== -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 \nkick all = kick everyone\nkick allbots = kick all bots\n"); - return; - } - - cl = SV_GetPlayerByName(); - if ( !cl ) { - if ( !Q_stricmp(Cmd_Argv(1), "all") ) { - for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { - if ( !cl->state ) { - 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->integer ; i++,cl++ ) { - if ( !cl->state ) { - 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 ) { - SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); - return; - } - - SV_DropClient( cl, "was kicked" ); - cl->lastPacketTime = svs.time; // in case there is a funny zombie -} - -/* -================== -SV_Ban_f - -Ban a user from being able to play on this server through the auth -server -================== -*/ -static void SV_Ban_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: banUser \n"); - return; - } - - cl = SV_GetPlayerByName(); - - if (!cl) { - return; - } - - if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { - SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); - return; - } - - // look up the authorize server's IP - if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { - Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); - if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, - svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], - svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], - BigShort( svs.authorizeAddress.port ) ); - } - - // otherwise send their ip to the authorize server - if ( svs.authorizeAddress.type != NA_BAD ) { - NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, - "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], - cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); - Com_Printf("%s was banned from coming back\n", cl->name); - } -} - -/* -================== -SV_BanNum_f - -Ban a user from being able to play on this server through the auth -server -================== -*/ -static void SV_BanNum_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: banClient \n"); - return; - } - - cl = SV_GetPlayerByNum(); - if ( !cl ) { - return; - } - if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { - SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); - return; - } - - // look up the authorize server's IP - if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { - Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); - if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, - svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], - svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], - BigShort( svs.authorizeAddress.port ) ); - } - - // otherwise send their ip to the authorize server - if ( svs.authorizeAddress.type != NA_BAD ) { - NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, - "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], - cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); - Com_Printf("%s was banned from coming back\n", cl->name); - } -} - -/* -================== -SV_KickNum_f - -Kick a user off of the server FIXME: move to game -================== -*/ -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: kicknum \n"); - return; - } - - cl = SV_GetPlayerByNum(); - if ( !cl ) { - return; - } - if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { - SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); - return; - } - - SV_DropClient( cl, "was kicked" ); - cl->lastPacketTime = svs.time; // in case there is a funny zombie -} - -/* -================ -SV_Status_f -================ -*/ -static void SV_Status_f( void ) { - int i, j, l; - client_t *cl; - playerState_t *ps; - const char *s; - int ping; - - // make sure server is running - if ( !com_sv_running->integer ) { - Com_Printf( "Server is not running.\n" ); - return; - } - - Com_Printf ("map: %s\n", sv_mapname->string ); - - Com_Printf ("num score ping name lastmsg address qport rate\n"); - Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) - { - if (!cl->state) - continue; - Com_Printf ("%3i ", i); - ps = SV_GameClientNum( i ); - Com_Printf ("%5i ", ps->persistant[PERS_SCORE]); - - if (cl->state == CS_CONNECTED) - Com_Printf ("CNCT "); - else if (cl->state == CS_ZOMBIE) - Com_Printf ("ZMBI "); - else - { - ping = cl->ping < 9999 ? cl->ping : 9999; - Com_Printf ("%4i ", ping); - } - - Com_Printf ("%s", cl->name); - // TTimo adding a ^7 to reset the color - // NOTE: colored names in status breaks the padding (WONTFIX) - Com_Printf ("^7"); - l = 16 - strlen(cl->name); - for (j=0 ; jlastPacketTime ); - - s = NET_AdrToString( cl->netchan.remoteAddress ); - Com_Printf ("%s", s); - l = 22 - strlen(s); - for (j=0 ; jnetchan.qport); - - Com_Printf (" %5i", cl->rate); - - Com_Printf ("\n"); - } - Com_Printf ("\n"); -} - -/* -================== -SV_ConSay_f -================== -*/ -static void SV_ConSay_f(void) { - char *p; - char text[1024]; - - // make sure server is running - if ( !com_sv_running->integer ) { - Com_Printf( "Server is not running.\n" ); - return; - } - - if ( Cmd_Argc () < 2 ) { - return; - } - - strcpy (text, "console: "); - p = Cmd_Args(); - - if ( *p == '"' ) { - p++; - p[strlen(p)-1] = 0; - } - - strcat(text, p); - - SV_SendServerCommand(NULL, "chat \"%s\n\"", text); -} - - -/* -================== -SV_Heartbeat_f - -Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer -================== -*/ -void SV_Heartbeat_f( void ) { - svs.nextHeartbeatTime = -9999999; -} - - -/* -=========== -SV_Serverinfo_f - -Examine the serverinfo string -=========== -*/ -static void SV_Serverinfo_f( void ) { - Com_Printf ("Server info settings:\n"); - Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) ); -} - - -/* -=========== -SV_Systeminfo_f - -Examine or change the serverinfo string -=========== -*/ -static void SV_Systeminfo_f( void ) { - Com_Printf ("System info settings:\n"); - Info_Print ( Cvar_InfoString( CVAR_SYSTEMINFO ) ); -} - - -/* -=========== -SV_DumpUser_f - -Examine all a users info strings FIXME: move to game -=========== -*/ -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: info \n"); - return; - } - - cl = SV_GetPlayerByName(); - 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_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 ("banUser", SV_Ban_f); - Cmd_AddCommand ("banClient", SV_BanNum_f); - Cmd_AddCommand ("clientkick", SV_KickNum_f); - Cmd_AddCommand ("status", SV_Status_f); - Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); - Cmd_AddCommand ("systeminfo", SV_Systeminfo_f); - Cmd_AddCommand ("dumpuser", SV_DumpUser_f); - Cmd_AddCommand ("map_restart", SV_MapRestart_f); - Cmd_AddCommand ("sectorlist", SV_SectorList_f); - Cmd_AddCommand ("map", SV_Map_f); -#ifndef PRE_RELEASE_DEMO - Cmd_AddCommand ("devmap", SV_Map_f); - Cmd_AddCommand ("spmap", SV_Map_f); - Cmd_AddCommand ("spdevmap", SV_Map_f); -#endif - Cmd_AddCommand ("killserver", SV_KillServer_f); - if( com_dedicated->integer ) { - Cmd_AddCommand ("say", SV_ConSay_f); - } -} - -/* -================== -SV_RemoveOperatorCommands -================== -*/ -void SV_RemoveOperatorCommands( void ) { -#if 0 - // removing these won't let the server start again - Cmd_RemoveCommand ("heartbeat"); - Cmd_RemoveCommand ("kick"); - Cmd_RemoveCommand ("banUser"); - Cmd_RemoveCommand ("banClient"); - Cmd_RemoveCommand ("status"); - Cmd_RemoveCommand ("serverinfo"); - Cmd_RemoveCommand ("systeminfo"); - Cmd_RemoveCommand ("dumpuser"); - Cmd_RemoveCommand ("map_restart"); - Cmd_RemoveCommand ("sectorlist"); - Cmd_RemoveCommand ("say"); -#endif -} - +/* +=========================================================================== +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" + +/* +=============================================================================== + +OPERATOR CONSOLE ONLY COMMANDS + +These commands can only be entered from stdin or by a remote operator datagram +=============================================================================== +*/ + + +/* +================== +SV_GetPlayerByName + +Returns the player with name from Cmd_Argv(1) +================== +*/ +static client_t *SV_GetPlayerByName( void ) { + client_t *cl; + int i; + char *s; + char cleanName[64]; + + // 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 for a name match + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + 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; + 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->integer ) { + Com_Printf( "Bad client slot: %i\n", idnum ); + return NULL; + } + + cl = &svs.clients[idnum]; + if ( !cl->state ) { + Com_Printf( "Client %i is not active\n", idnum ); + return NULL; + } + return cl; + + return NULL; +} + +//========================================================= + + +/* +================== +SV_Map_f + +Restart the server on a different map +================== +*/ +static void SV_Map_f( void ) { + char *cmd; + char *map; + qboolean killBots, cheat; + char expanded[MAX_QPATH]; + char mapname[MAX_QPATH]; + + map = Cmd_Argv(1); + if ( !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); + if ( FS_ReadFile (expanded, NULL) == -1 ) { + Com_Printf ("Can't find map %s\n", expanded); + return; + } + + // force latched values to get set + Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH ); + + cmd = Cmd_Argv(0); + if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) { + Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER ); + Cvar_SetValue( "g_doWarmup", 0 ); + // may not set sv_maxclients directly, always set latched + Cvar_SetLatched( "sv_maxclients", "8" ); + cmd += 2; + cheat = qfalse; + killBots = qtrue; + } + else { + if ( !Q_stricmp( cmd, "devmap" ) || !Q_stricmp( cmd, "spdevmap" ) ) { + cheat = qtrue; + killBots = qtrue; + } else { + cheat = qfalse; + killBots = qfalse; + } + if( sv_gametype->integer == GT_SINGLE_PLAYER ) { + Cvar_SetValue( "g_gametype", GT_FFA ); + } + } + + // 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, killBots ); + + // set the cheat value + // if the level was started with "map ", then + // cheats will not be allowed. If started with "devmap " + // then cheats will be allowed + if ( cheat ) { + Cvar_Set( "sv_cheats", "1" ); + } else { + Cvar_Set( "sv_cheats", "0" ); + } +} + +/* +================ +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; + char *denied; + qboolean isBot; + int delay; + + // make sure we aren't restarting twice in the same frame + if ( com_frameTime == sv.serverId ) { + return; + } + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( sv.restartTime ) { + return; + } + + if (Cmd_Argc() > 1 ) { + delay = atoi( Cmd_Argv(1) ); + } + else { + delay = 5; + } + if( delay && !Cvar_VariableValue("g_doWarmup") ) { + sv.restartTime = svs.time + delay * 1000; + SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) ); + return; + } + + // check for changes in variables that can't just be restarted + // check for maxclients change + if ( sv_maxclients->modified || sv_gametype->modified ) { + char mapname[MAX_QPATH]; + + Com_Printf( "variable change -- restarting.\n" ); + // restart the map the slow way + Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); + + SV_SpawnServer( mapname, qfalse ); + return; + } + + // toggle the server bit so clients can detect that a + // map_restart has happened + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // generate a new serverid + // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart + sv.serverId = com_frameTime; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // 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++ ) { + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + svs.time += 100; + } + + sv.state = SS_GAME; + sv.restarting = qfalse; + + // connect and begin all the clients + for (i=0 ; iinteger ; 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 = VM_ExplicitArgPtr( gvm, VM_Call( gvm, 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(%d): dropped client %i - denied!\n", delay, i ); // bk010125 + continue; + } + + client->state = CS_ACTIVE; + + SV_ClientEnterWorld( client, &client->lastUsercmd ); + } + + // run another frame to allow things to look at all the players + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + svs.time += 100; +} + +//=============================================================== + +/* +================== +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 \nkick all = kick everyone\nkick allbots = kick all bots\n"); + return; + } + + cl = SV_GetPlayerByName(); + if ( !cl ) { + if ( !Q_stricmp(Cmd_Argv(1), "all") ) { + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + 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->integer ; i++,cl++ ) { + if ( !cl->state ) { + 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 ) { + SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + return; + } + + SV_DropClient( cl, "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================== +SV_Ban_f + +Ban a user from being able to play on this server through the auth +server +================== +*/ +static void SV_Ban_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: banUser \n"); + return; + } + + cl = SV_GetPlayerByName(); + + if (!cl) { + return; + } + + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); + Com_Printf("%s was banned from coming back\n", cl->name); + } +} + +/* +================== +SV_BanNum_f + +Ban a user from being able to play on this server through the auth +server +================== +*/ +static void SV_BanNum_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: banClient \n"); + return; + } + + cl = SV_GetPlayerByNum(); + if ( !cl ) { + return; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); + Com_Printf("%s was banned from coming back\n", cl->name); + } +} + +/* +================== +SV_KickNum_f + +Kick a user off of the server FIXME: move to game +================== +*/ +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: kicknum \n"); + return; + } + + cl = SV_GetPlayerByNum(); + if ( !cl ) { + return; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + return; + } + + SV_DropClient( cl, "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================ +SV_Status_f +================ +*/ +static void SV_Status_f( void ) { + int i, j, l; + client_t *cl; + playerState_t *ps; + const char *s; + int ping; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + Com_Printf ("map: %s\n", sv_mapname->string ); + + Com_Printf ("num score ping name lastmsg address qport rate\n"); + Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) + { + if (!cl->state) + continue; + Com_Printf ("%3i ", i); + ps = SV_GameClientNum( i ); + Com_Printf ("%5i ", ps->persistant[PERS_SCORE]); + + if (cl->state == CS_CONNECTED) + Com_Printf ("CNCT "); + else if (cl->state == CS_ZOMBIE) + Com_Printf ("ZMBI "); + else + { + ping = cl->ping < 9999 ? cl->ping : 9999; + Com_Printf ("%4i ", ping); + } + + Com_Printf ("%s", cl->name); + // TTimo adding a ^7 to reset the color + // NOTE: colored names in status breaks the padding (WONTFIX) + Com_Printf ("^7"); + l = 16 - strlen(cl->name); + for (j=0 ; jlastPacketTime ); + + s = NET_AdrToString( cl->netchan.remoteAddress ); + Com_Printf ("%s", s); + l = 22 - strlen(s); + for (j=0 ; jnetchan.qport); + + Com_Printf (" %5i", cl->rate); + + Com_Printf ("\n"); + } + Com_Printf ("\n"); +} + +/* +================== +SV_ConSay_f +================== +*/ +static void SV_ConSay_f(void) { + char *p; + char text[1024]; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc () < 2 ) { + return; + } + + strcpy (text, "console: "); + p = Cmd_Args(); + + if ( *p == '"' ) { + p++; + p[strlen(p)-1] = 0; + } + + strcat(text, p); + + SV_SendServerCommand(NULL, "chat \"%s\n\"", text); +} + + +/* +================== +SV_Heartbeat_f + +Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer +================== +*/ +void SV_Heartbeat_f( void ) { + svs.nextHeartbeatTime = -9999999; +} + + +/* +=========== +SV_Serverinfo_f + +Examine the serverinfo string +=========== +*/ +static void SV_Serverinfo_f( void ) { + Com_Printf ("Server info settings:\n"); + Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) ); +} + + +/* +=========== +SV_Systeminfo_f + +Examine or change the serverinfo string +=========== +*/ +static void SV_Systeminfo_f( void ) { + Com_Printf ("System info settings:\n"); + Info_Print ( Cvar_InfoString( CVAR_SYSTEMINFO ) ); +} + + +/* +=========== +SV_DumpUser_f + +Examine all a users info strings FIXME: move to game +=========== +*/ +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: info \n"); + return; + } + + cl = SV_GetPlayerByName(); + 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_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 ("banUser", SV_Ban_f); + Cmd_AddCommand ("banClient", SV_BanNum_f); + Cmd_AddCommand ("clientkick", SV_KickNum_f); + Cmd_AddCommand ("status", SV_Status_f); + Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); + Cmd_AddCommand ("systeminfo", SV_Systeminfo_f); + Cmd_AddCommand ("dumpuser", SV_DumpUser_f); + Cmd_AddCommand ("map_restart", SV_MapRestart_f); + Cmd_AddCommand ("sectorlist", SV_SectorList_f); + Cmd_AddCommand ("map", SV_Map_f); +#ifndef PRE_RELEASE_DEMO + Cmd_AddCommand ("devmap", SV_Map_f); + Cmd_AddCommand ("spmap", SV_Map_f); + Cmd_AddCommand ("spdevmap", SV_Map_f); +#endif + Cmd_AddCommand ("killserver", SV_KillServer_f); + if( com_dedicated->integer ) { + Cmd_AddCommand ("say", SV_ConSay_f); + } +} + +/* +================== +SV_RemoveOperatorCommands +================== +*/ +void SV_RemoveOperatorCommands( void ) { +#if 0 + // removing these won't let the server start again + Cmd_RemoveCommand ("heartbeat"); + Cmd_RemoveCommand ("kick"); + Cmd_RemoveCommand ("banUser"); + Cmd_RemoveCommand ("banClient"); + Cmd_RemoveCommand ("status"); + Cmd_RemoveCommand ("serverinfo"); + Cmd_RemoveCommand ("systeminfo"); + Cmd_RemoveCommand ("dumpuser"); + Cmd_RemoveCommand ("map_restart"); + Cmd_RemoveCommand ("sectorlist"); + Cmd_RemoveCommand ("say"); +#endif +} + diff --git a/code/server/sv_client.c b/code/server/sv_client.c index c3fdc92..1756bab 100755 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -1,1531 +1,1531 @@ -/* -=========================================================================== -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 -=========================================================================== -*/ -// sv_client.c -- server code for dealing with clients - -#include "server.h" - -static void SV_CloseDownload( client_t *cl ); - -/* -================= -SV_GetChallenge - -A "getchallenge" OOB command has been received -Returns a challenge number that can be used -in a subsequent connectResponse command. -We do this to prevent denial of service attacks that -flood the server with invalid connection IPs. With a -challenge, they must give a valid IP address. - -If we are authorizing, a challenge request will cause a packet -to be sent to the authorize server. - -When an authorizeip is returned, a challenge response will be -sent to that ip. -================= -*/ -void SV_GetChallenge( netadr_t from ) { - int i; - int oldest; - int oldestTime; - challenge_t *challenge; - - // ignore if we are in single player - if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) { - return; - } - - oldest = 0; - oldestTime = 0x7fffffff; - - // see if we already have a challenge for this ip - challenge = &svs.challenges[0]; - for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { - if ( !challenge->connected && NET_CompareAdr( from, challenge->adr ) ) { - break; - } - if ( challenge->time < oldestTime ) { - oldestTime = challenge->time; - oldest = i; - } - } - - if (i == MAX_CHALLENGES) { - // this is the first time this client has asked for a challenge - challenge = &svs.challenges[oldest]; - - challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time; - challenge->adr = from; - challenge->firstTime = svs.time; - challenge->time = svs.time; - challenge->connected = qfalse; - i = oldest; - } - - // if they are on a lan address, send the challengeResponse immediately - if ( Sys_IsLANAddress( from ) ) { - challenge->pingTime = svs.time; - NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge ); - return; - } - - // look up the authorize server's IP - if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { - Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); - if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, - svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], - svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], - BigShort( svs.authorizeAddress.port ) ); - } - - // if they have been challenging for a long time and we - // haven't heard anything from the authorize server, go ahead and - // let them in, assuming the id server is down - if ( svs.time - challenge->firstTime > AUTHORIZE_TIMEOUT ) { - Com_DPrintf( "authorize server timed out\n" ); - - challenge->pingTime = svs.time; - NET_OutOfBandPrint( NS_SERVER, challenge->adr, - "challengeResponse %i", challenge->challenge ); - return; - } - - // otherwise send their ip to the authorize server - if ( svs.authorizeAddress.type != NA_BAD ) { - cvar_t *fs; - char game[1024]; - - Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from )); - - strcpy(game, BASEGAME); - fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); - if (fs && fs->string[0] != 0) { - strcpy(game, fs->string); - } - - // the 0 is for backwards compatibility with obsolete sv_allowanonymous flags - // getIpAuthorize 0 - NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, - "getIpAuthorize %i %i.%i.%i.%i %s 0 %s", svs.challenges[i].challenge, - from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, sv_strictAuth->string ); - } -} - -/* -==================== -SV_AuthorizeIpPacket - -A packet has been returned from the authorize server. -If we have a challenge adr for that ip, send the -challengeResponse to it -==================== -*/ -void SV_AuthorizeIpPacket( netadr_t from ) { - int challenge; - int i; - char *s; - char *r; - char ret[1024]; - - if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { - Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); - return; - } - - challenge = atoi( Cmd_Argv( 1 ) ); - - for (i = 0 ; i < MAX_CHALLENGES ; i++) { - if ( svs.challenges[i].challenge == challenge ) { - break; - } - } - if ( i == MAX_CHALLENGES ) { - Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); - return; - } - - // send a packet back to the original client - svs.challenges[i].pingTime = svs.time; - s = Cmd_Argv( 2 ); - r = Cmd_Argv( 3 ); // reason - - if ( !Q_stricmp( s, "demo" ) ) { - if ( Cvar_VariableValue( "fs_restrict" ) ) { - // a demo client connecting to a demo server - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, - "challengeResponse %i", svs.challenges[i].challenge ); - return; - } - // they are a demo client trying to connect to a real server - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" ); - // clear the challenge record so it won't timeout and let them through - Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); - return; - } - if ( !Q_stricmp( s, "accept" ) ) { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, - "challengeResponse %i", svs.challenges[i].challenge ); - return; - } - if ( !Q_stricmp( s, "unknown" ) ) { - if (!r) { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting CD key authorization\n" ); - } else { - sprintf(ret, "print\n%s\n", r); - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); - } - // clear the challenge record so it won't timeout and let them through - Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); - return; - } - - // authorization failed - if (!r) { - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nSomeone is using this CD Key\n" ); - } else { - sprintf(ret, "print\n%s\n", r); - NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); - } - - // clear the challenge record so it won't timeout and let them through - Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); -} - -/* -================== -SV_DirectConnect - -A "connect" OOB command has been received -================== -*/ - -#define PB_MESSAGE "PunkBuster Anti-Cheat software must be installed " \ - "and Enabled in order to join this server. An updated game patch can be downloaded from " \ - "www.idsoftware.com" - -void SV_DirectConnect( netadr_t from ) { - char userinfo[MAX_INFO_STRING]; - int i; - client_t *cl, *newcl; - MAC_STATIC client_t temp; - sharedEntity_t *ent; - int clientNum; - int version; - int qport; - int challenge; - char *password; - int startIndex; - char *denied; - int count; - - Com_DPrintf ("SVC_DirectConnect ()\n"); - - Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); - - version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); - if ( version != PROTOCOL_VERSION ) { - NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION ); - Com_DPrintf (" rejected connect from version %i\n", version); - return; - } - - challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); - qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); - - // quick reject - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - if ( cl->state == CS_FREE ) { - continue; - } - if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) - && ( cl->netchan.qport == qport - || from.port == cl->netchan.remoteAddress.port ) ) { - if (( svs.time - cl->lastConnectTime) - < (sv_reconnectlimit->integer * 1000)) { - Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); - return; - } - break; - } - } - - // see if the challenge is valid (LAN clients don't need to challenge) - if ( !NET_IsLocalAddress (from) ) { - int ping; - - for (i=0 ; ivalue && ping < sv_minPing->value ) { - // don't let them keep trying until they get a big delay - NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); - Com_DPrintf ("Client %i rejected on a too low ping\n", i); - // reset the address otherwise their ping will keep increasing - // with each connect message and they'd eventually be able to connect - svs.challenges[i].adr.port = 0; - return; - } - if ( sv_maxPing->value && ping > sv_maxPing->value ) { - NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); - Com_DPrintf ("Client %i rejected on a too high ping\n", i); - return; - } - } - } else { - // force the "ip" info key to "localhost" - Info_SetValueForKey( userinfo, "ip", "localhost" ); - } - - newcl = &temp; - Com_Memset (newcl, 0, sizeof(client_t)); - - // if there is already a slot for this ip, reuse it - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - if ( cl->state == CS_FREE ) { - continue; - } - if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) - && ( cl->netchan.qport == qport - || from.port == cl->netchan.remoteAddress.port ) ) { - Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); - newcl = cl; - - // this doesn't work because it nukes the players userinfo - -// // disconnect the client from the game first so any flags the -// // player might have are dropped -// VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); - // - goto gotnewcl; - } - } - - // find a client slot - // if "sv_privateClients" is set > 0, then that number - // of client slots will be reserved for connections that - // have "password" set to the value of "sv_privatePassword" - // Info requests will report the maxclients as if the private - // slots didn't exist, to prevent people from trying to connect - // to a full server. - // This is to allow us to reserve a couple slots here on our - // servers so we can play without having to kick people. - - // check for privateClient password - password = Info_ValueForKey( userinfo, "password" ); - if ( !strcmp( password, sv_privatePassword->string ) ) { - startIndex = 0; - } else { - // skip past the reserved slots - startIndex = sv_privateClients->integer; - } - - newcl = NULL; - for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { - cl = &svs.clients[i]; - if (cl->state == CS_FREE) { - newcl = cl; - break; - } - } - - if ( !newcl ) { - if ( NET_IsLocalAddress( from ) ) { - count = 0; - for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { - cl = &svs.clients[i]; - if (cl->netchan.remoteAddress.type == NA_BOT) { - count++; - } - } - // if they're all bots - if (count >= sv_maxclients->integer - startIndex) { - SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); - newcl = &svs.clients[sv_maxclients->integer - 1]; - } - else { - Com_Error( ERR_FATAL, "server is full on local connect\n" ); - return; - } - } - else { - NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); - Com_DPrintf ("Rejected a connection.\n"); - return; - } - } - - // we got a newcl, so reset the reliableSequence and reliableAcknowledge - cl->reliableAcknowledge = 0; - cl->reliableSequence = 0; - -gotnewcl: - // build a new connection - // accept the new client - // this is the only place a client_t is ever initialized - *newcl = temp; - clientNum = newcl - svs.clients; - ent = SV_GentityNum( clientNum ); - newcl->gentity = ent; - - // save the challenge - newcl->challenge = challenge; - - // save the address - Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); - // init the netchan queue - newcl->netchan_end_queue = &newcl->netchan_start_queue; - - // save the userinfo - Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); - - // get the game a chance to reject this connection or modify the userinfo - denied = (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue - if ( denied ) { - // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call - denied = VM_ExplicitArgPtr( gvm, (int)denied ); - - NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); - Com_DPrintf ("Game rejected a connection: %s.\n", denied); - return; - } - - SV_UserinfoChanged( newcl ); - - // send the connect packet to the client - NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); - - Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); - - newcl->state = CS_CONNECTED; - newcl->nextSnapshotTime = svs.time; - newcl->lastPacketTime = svs.time; - newcl->lastConnectTime = svs.time; - - // when we receive the first packet from the client, we will - // notice that it is from a different serverid and that the - // gamestate message was not just sent, forcing a retransmit - newcl->gamestateMessageNum = -1; - - // if this was the first client on the server, or the last client - // the server can hold, send a heartbeat to the master. - count = 0; - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - if ( svs.clients[i].state >= CS_CONNECTED ) { - count++; - } - } - if ( count == 1 || count == sv_maxclients->integer ) { - SV_Heartbeat_f(); - } -} - - -/* -===================== -SV_DropClient - -Called when the player is totally leaving the server, either willingly -or unwillingly. This is NOT called if the entire server is quiting -or crashing -- SV_FinalMessage() will handle that -===================== -*/ -void SV_DropClient( client_t *drop, const char *reason ) { - int i; - challenge_t *challenge; - - if ( drop->state == CS_ZOMBIE ) { - return; // already dropped - } - - if ( !drop->gentity || !(drop->gentity->r.svFlags & SVF_BOT) ) { - // see if we already have a challenge for this ip - challenge = &svs.challenges[0]; - - for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { - if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { - challenge->connected = qfalse; - break; - } - } - } - - // Kill any download - SV_CloseDownload( drop ); - - // tell everyone why they got dropped - SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason ); - - Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); - drop->state = CS_ZOMBIE; // become free in a few seconds - - if (drop->download) { - FS_FCloseFile( drop->download ); - drop->download = 0; - } - - // call the prog function for removing a client - // this will remove the body, among other things - VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients ); - - // add the disconnect command - SV_SendServerCommand( drop, "disconnect \"%s\"", reason); - - if ( drop->netchan.remoteAddress.type == NA_BOT ) { - SV_BotFreeClient( drop - svs.clients ); - } - - // nuke user info - SV_SetUserinfo( drop - svs.clients, "" ); - - // if this was the last client on the server, send a heartbeat - // to the master so it is known the server is empty - // send a heartbeat now so the master will get up to date info - // if there is already a slot for this ip, reuse it - for (i=0 ; i < sv_maxclients->integer ; i++ ) { - if ( svs.clients[i].state >= CS_CONNECTED ) { - break; - } - } - if ( i == sv_maxclients->integer ) { - SV_Heartbeat_f(); - } -} - -/* -================ -SV_SendClientGameState - -Sends the first message from the server to a connected client. -This will be sent on the initial connection and upon each new map load. - -It will be resent if the client acknowledges a later message but has -the wrong gamestate. -================ -*/ -void SV_SendClientGameState( client_t *client ) { - int start; - entityState_t *base, nullstate; - msg_t msg; - byte msgBuffer[MAX_MSGLEN]; - - Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); - Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); - client->state = CS_PRIMED; - client->pureAuthentic = 0; - client->gotCP = qfalse; - - // when we receive the first packet from the client, we will - // notice that it is from a different serverid and that the - // gamestate message was not just sent, forcing a retransmit - client->gamestateMessageNum = client->netchan.outgoingSequence; - - MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); - - // NOTE, MRE: all server->client messages now acknowledge - // let the client know which reliable clientCommands we have received - MSG_WriteLong( &msg, client->lastClientCommand ); - - // send any server commands waiting to be sent first. - // we have to do this cause we send the client->reliableSequence - // with a gamestate and it sets the clc.serverCommandSequence at - // the client side - SV_UpdateServerCommandsToClient( client, &msg ); - - // send the gamestate - MSG_WriteByte( &msg, svc_gamestate ); - MSG_WriteLong( &msg, client->reliableSequence ); - - // write the configstrings - for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { - if (sv.configstrings[start][0]) { - MSG_WriteByte( &msg, svc_configstring ); - MSG_WriteShort( &msg, start ); - MSG_WriteBigString( &msg, sv.configstrings[start] ); - } - } - - // write the baselines - Com_Memset( &nullstate, 0, sizeof( nullstate ) ); - for ( start = 0 ; start < MAX_GENTITIES; start++ ) { - base = &sv.svEntities[start].baseline; - if ( !base->number ) { - continue; - } - MSG_WriteByte( &msg, svc_baseline ); - MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); - } - - MSG_WriteByte( &msg, svc_EOF ); - - MSG_WriteLong( &msg, client - svs.clients); - - // write the checksum feed - MSG_WriteLong( &msg, sv.checksumFeed); - - // deliver this to the client - SV_SendMessageToClient( &msg, client ); -} - - -/* -================== -SV_ClientEnterWorld -================== -*/ -void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { - int clientNum; - sharedEntity_t *ent; - - Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); - client->state = CS_ACTIVE; - - // set up the entity for the client - clientNum = client - svs.clients; - ent = SV_GentityNum( clientNum ); - ent->s.number = clientNum; - client->gentity = ent; - - client->deltaMessage = -1; - client->nextSnapshotTime = svs.time; // generate a snapshot immediately - client->lastUsercmd = *cmd; - - // call the game begin function - VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients ); -} - -/* -============================================================ - -CLIENT COMMAND EXECUTION - -============================================================ -*/ - -/* -================== -SV_CloseDownload - -clear/free any download vars -================== -*/ -static void SV_CloseDownload( client_t *cl ) { - int i; - - // EOF - if (cl->download) { - FS_FCloseFile( cl->download ); - } - cl->download = 0; - *cl->downloadName = 0; - - // Free the temporary buffer space - for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { - if (cl->downloadBlocks[i]) { - Z_Free( cl->downloadBlocks[i] ); - cl->downloadBlocks[i] = NULL; - } - } - -} - -/* -================== -SV_StopDownload_f - -Abort a download if in progress -================== -*/ -void SV_StopDownload_f( client_t *cl ) { - if (*cl->downloadName) - Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", cl - svs.clients, cl->downloadName ); - - SV_CloseDownload( cl ); -} - -/* -================== -SV_DoneDownload_f - -Downloads are finished -================== -*/ -void SV_DoneDownload_f( client_t *cl ) { - Com_DPrintf( "clientDownload: %s Done\n", cl->name); - // resend the game state to update any clients that entered during the download - SV_SendClientGameState(cl); -} - -/* -================== -SV_NextDownload_f - -The argument will be the last acknowledged block from the client, it should be -the same as cl->downloadClientBlock -================== -*/ -void SV_NextDownload_f( client_t *cl ) -{ - int block = atoi( Cmd_Argv(1) ); - - if (block == cl->downloadClientBlock) { - Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", cl - svs.clients, block ); - - // Find out if we are done. A zero-length block indicates EOF - if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) { - Com_Printf( "clientDownload: %d : file \"%s\" completed\n", cl - svs.clients, cl->downloadName ); - SV_CloseDownload( cl ); - return; - } - - cl->downloadSendTime = svs.time; - cl->downloadClientBlock++; - return; - } - // We aren't getting an acknowledge for the correct block, drop the client - // FIXME: this is bad... the client will never parse the disconnect message - // because the cgame isn't loaded yet - SV_DropClient( cl, "broken download" ); -} - -/* -================== -SV_BeginDownload_f -================== -*/ -void SV_BeginDownload_f( client_t *cl ) { - - // Kill any existing download - SV_CloseDownload( cl ); - - // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open - // the file itself - Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) ); -} - -/* -================== -SV_WriteDownloadToClient - -Check to see if the client wants a file, open it if needed and start pumping the client -Fill up msg with data -================== -*/ -void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) -{ - int curindex; - int rate; - int blockspersnap; - int idPack, missionPack; - char errorMessage[1024]; - - if (!*cl->downloadName) - return; // Nothing being downloaded - - if (!cl->download) { - // We open the file here - - Com_Printf( "clientDownload: %d : begining \"%s\"\n", cl - svs.clients, cl->downloadName ); - - missionPack = FS_idPak(cl->downloadName, "missionpack"); - idPack = missionPack || FS_idPak(cl->downloadName, "baseq3"); - - if ( !sv_allowDownload->integer || idPack || - ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { - // cannot auto-download file - if (idPack) { - Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName); - if (missionPack) { - Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" - "The Team Arena mission pack can be found in your local game store.", cl->downloadName); - } - else { - Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); - } - } else if ( !sv_allowDownload->integer ) { - Com_Printf("clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName); - if (sv_pure->integer) { - Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" - "You will need to get this file elsewhere before you " - "can connect to this pure server.\n", cl->downloadName); - } else { - Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" - "The server you are connecting to is not a pure server, " - "set autodownload to No in your settings and you might be " - "able to join the game anyway.\n", cl->downloadName); - } - } else { - // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? - // if the pk3 is referenced, it must have been found somewhere in the filesystem - Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName); - Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); - } - MSG_WriteByte( msg, svc_download ); - MSG_WriteShort( msg, 0 ); // client is expecting block zero - MSG_WriteLong( msg, -1 ); // illegal file size - MSG_WriteString( msg, errorMessage ); - - *cl->downloadName = 0; - return; - } - - // Init - cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; - cl->downloadCount = 0; - cl->downloadEOF = qfalse; - } - - // Perform any reads that we need to - while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && - cl->downloadSize != cl->downloadCount) { - - curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); - - if (!cl->downloadBlocks[curindex]) - cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); - - cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); - - if (cl->downloadBlockSize[curindex] < 0) { - // EOF right now - cl->downloadCount = cl->downloadSize; - break; - } - - cl->downloadCount += cl->downloadBlockSize[curindex]; - - // Load in next block - cl->downloadCurrentBlock++; - } - - // Check to see if we have eof condition and add the EOF block - if (cl->downloadCount == cl->downloadSize && - !cl->downloadEOF && - cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { - - cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; - cl->downloadCurrentBlock++; - - cl->downloadEOF = qtrue; // We have added the EOF block - } - - // Loop up to window size times based on how many blocks we can fit in the - // client snapMsec and rate - - // based on the rate, how many bytes can we fit in the snapMsec time of the client - // normal rate / snapshotMsec calculation - rate = cl->rate; - if ( sv_maxRate->integer ) { - if ( sv_maxRate->integer < 1000 ) { - Cvar_Set( "sv_MaxRate", "1000" ); - } - if ( sv_maxRate->integer < rate ) { - rate = sv_maxRate->integer; - } - } - - if (!rate) { - blockspersnap = 1; - } else { - blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / - MAX_DOWNLOAD_BLKSIZE; - } - - if (blockspersnap < 0) - blockspersnap = 1; - - while (blockspersnap--) { - - // Write out the next section of the file, if we have already reached our window, - // automatically start retransmitting - - if (cl->downloadClientBlock == cl->downloadCurrentBlock) - return; // Nothing to transmit - - if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { - // We have transmitted the complete window, should we start resending? - - //FIXME: This uses a hardcoded one second timeout for lost blocks - //the timeout should be based on client rate somehow - if (svs.time - cl->downloadSendTime > 1000) - cl->downloadXmitBlock = cl->downloadClientBlock; - else - return; - } - - // Send current block - curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); - - MSG_WriteByte( msg, svc_download ); - MSG_WriteShort( msg, cl->downloadXmitBlock ); - - // block zero is special, contains file size - if ( cl->downloadXmitBlock == 0 ) - MSG_WriteLong( msg, cl->downloadSize ); - - MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); - - // Write the block - if ( cl->downloadBlockSize[curindex] ) { - MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); - } - - Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock ); - - // Move on to the next block - // It will get sent with next snap shot. The rate will keep us in line. - cl->downloadXmitBlock++; - - cl->downloadSendTime = svs.time; - } -} - -/* -================= -SV_Disconnect_f - -The client is going to disconnect, so remove the connection immediately FIXME: move to game? -================= -*/ -static void SV_Disconnect_f( client_t *cl ) { - SV_DropClient( cl, "disconnected" ); -} - -/* -================= -SV_VerifyPaks_f - -If we are pure, disconnect the client if they do no meet the following conditions: - -1. the first two checksums match our view of cgame and ui -2. there are no any additional checksums that we do not have - -This routine would be a bit simpler with a goto but i abstained - -================= -*/ -static void SV_VerifyPaks_f( client_t *cl ) { - int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg; - int nClientChkSum[1024]; - int nServerChkSum[1024]; - const char *pPaks, *pArg; - qboolean bGood = qtrue; - - // if we are pure, we "expect" the client to load certain things from - // certain pk3 files, namely we want the client to have loaded the - // ui and cgame that we think should be loaded based on the pure setting - // - if ( sv_pure->integer != 0 ) { - - bGood = qtrue; - nChkSum1 = nChkSum2 = 0; - // we run the game, so determine which cgame and ui the client "should" be running - bGood = (FS_FileIsInPAK("vm/cgame.qvm", &nChkSum1) == 1); - if (bGood) - bGood = (FS_FileIsInPAK("vm/ui.qvm", &nChkSum2) == 1); - - nClientPaks = Cmd_Argc(); - - // start at arg 2 ( skip serverId cl_paks ) - nCurArg = 1; - - pArg = Cmd_Argv(nCurArg++); - if(!pArg) { - bGood = qfalse; - } - else - { - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 - // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore - // since serverId is a frame count, it always goes up - if (atoi(pArg) < sv.checksumFeedServerId) - { - Com_DPrintf("ignoring outdated cp command from client %s\n", cl->name); - return; - } - } - - // we basically use this while loop to avoid using 'goto' :) - while (bGood) { - - // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums" - // numChecksums is encoded - if (nClientPaks < 6) { - bGood = qfalse; - break; - } - // verify first to be the cgame checksum - pArg = Cmd_Argv(nCurArg++); - if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) { - bGood = qfalse; - break; - } - // verify the second to be the ui checksum - pArg = Cmd_Argv(nCurArg++); - if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) { - bGood = qfalse; - break; - } - // should be sitting at the delimeter now - pArg = Cmd_Argv(nCurArg++); - if (*pArg != '@') { - bGood = qfalse; - break; - } - // store checksums since tokenization is not re-entrant - for (i = 0; nCurArg < nClientPaks; i++) { - nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++)); - } - - // store number to compare against (minus one cause the last is the number of checksums) - nClientPaks = i - 1; - - // make sure none of the client check sums are the same - // so the client can't send 5 the same checksums - for (i = 0; i < nClientPaks; i++) { - for (j = 0; j < nClientPaks; j++) { - if (i == j) - continue; - if (nClientChkSum[i] == nClientChkSum[j]) { - bGood = qfalse; - break; - } - } - if (bGood == qfalse) - break; - } - if (bGood == qfalse) - break; - - // get the pure checksums of the pk3 files loaded by the server - pPaks = FS_LoadedPakPureChecksums(); - Cmd_TokenizeString( pPaks ); - nServerPaks = Cmd_Argc(); - if (nServerPaks > 1024) - nServerPaks = 1024; - - for (i = 0; i < nServerPaks; i++) { - nServerChkSum[i] = atoi(Cmd_Argv(i)); - } - - // check if the client has provided any pure checksums of pk3 files not loaded by the server - for (i = 0; i < nClientPaks; i++) { - for (j = 0; j < nServerPaks; j++) { - if (nClientChkSum[i] == nServerChkSum[j]) { - break; - } - } - if (j >= nServerPaks) { - bGood = qfalse; - break; - } - } - if ( bGood == qfalse ) { - break; - } - - // check if the number of checksums was correct - nChkSum1 = sv.checksumFeed; - for (i = 0; i < nClientPaks; i++) { - nChkSum1 ^= nClientChkSum[i]; - } - nChkSum1 ^= nClientPaks; - if (nChkSum1 != nClientChkSum[nClientPaks]) { - bGood = qfalse; - break; - } - - // break out - break; - } - - cl->gotCP = qtrue; - - if (bGood) { - cl->pureAuthentic = 1; - } - else { - cl->pureAuthentic = 0; - cl->nextSnapshotTime = -1; - cl->state = CS_ACTIVE; - SV_SendClientSnapshot( cl ); - SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); - } - } -} - -/* -================= -SV_ResetPureClient_f -================= -*/ -static void SV_ResetPureClient_f( client_t *cl ) { - cl->pureAuthentic = 0; - cl->gotCP = qfalse; -} - -/* -================= -SV_UserinfoChanged - -Pull specific info from a newly changed userinfo string -into a more C friendly form. -================= -*/ -void SV_UserinfoChanged( client_t *cl ) { - char *val; - int i; - - // name for C code - Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); - - // rate command - - // if the client is on the same subnet as the server and we aren't running an - // internet public server, assume they don't need a rate choke - if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) { - cl->rate = 99999; // lans should not rate limit - } else { - val = Info_ValueForKey (cl->userinfo, "rate"); - if (strlen(val)) { - i = atoi(val); - cl->rate = i; - if (cl->rate < 1000) { - cl->rate = 1000; - } else if (cl->rate > 90000) { - cl->rate = 90000; - } - } else { - cl->rate = 3000; - } - } - val = Info_ValueForKey (cl->userinfo, "handicap"); - if (strlen(val)) { - i = atoi(val); - if (i<=0 || i>100 || strlen(val) > 4) { - Info_SetValueForKey( cl->userinfo, "handicap", "100" ); - } - } - - // snaps command - val = Info_ValueForKey (cl->userinfo, "snaps"); - if (strlen(val)) { - i = atoi(val); - if ( i < 1 ) { - i = 1; - } else if ( i > 30 ) { - i = 30; - } - cl->snapshotMsec = 1000/i; - } else { - cl->snapshotMsec = 50; - } - - // TTimo - // maintain the IP information - // this is set in SV_DirectConnect (directly on the server, not transmitted), may be lost when client updates it's userinfo - // the banning code relies on this being consistently present - val = Info_ValueForKey (cl->userinfo, "ip"); - if (!val[0]) - { - //Com_DPrintf("Maintain IP in userinfo for '%s'\n", cl->name); - if ( !NET_IsLocalAddress(cl->netchan.remoteAddress) ) - Info_SetValueForKey( cl->userinfo, "ip", NET_AdrToString( cl->netchan.remoteAddress ) ); - else - // force the "ip" info key to "localhost" for local clients - Info_SetValueForKey( cl->userinfo, "ip", "localhost" ); - } -} - - -/* -================== -SV_UpdateUserinfo_f -================== -*/ -static void SV_UpdateUserinfo_f( client_t *cl ) { - Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); - - SV_UserinfoChanged( cl ); - // call prog code to allow overrides - VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); -} - -typedef struct { - char *name; - void (*func)( client_t *cl ); -} ucmd_t; - -static ucmd_t ucmds[] = { - {"userinfo", SV_UpdateUserinfo_f}, - {"disconnect", SV_Disconnect_f}, - {"cp", SV_VerifyPaks_f}, - {"vdr", SV_ResetPureClient_f}, - {"download", SV_BeginDownload_f}, - {"nextdl", SV_NextDownload_f}, - {"stopdl", SV_StopDownload_f}, - {"donedl", SV_DoneDownload_f}, - - {NULL, NULL} -}; - -/* -================== -SV_ExecuteClientCommand - -Also called by bot code -================== -*/ -void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { - ucmd_t *u; - qboolean bProcessed = qfalse; - - Cmd_TokenizeString( s ); - - // see if it is a server level command - for (u=ucmds ; u->name ; u++) { - if (!strcmp (Cmd_Argv(0), u->name) ) { - u->func( cl ); - bProcessed = qtrue; - break; - } - } - - if (clientOK) { - // pass unknown strings to the game - if (!u->name && sv.state == SS_GAME) { - VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients ); - } - } - else if (!bProcessed) - Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv(0) ); -} - -/* -=============== -SV_ClientCommand -=============== -*/ -static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { - int seq; - const char *s; - qboolean clientOk = qtrue; - - seq = MSG_ReadLong( msg ); - s = MSG_ReadString( msg ); - - // see if we have already executed it - if ( cl->lastClientCommand >= seq ) { - return qtrue; - } - - Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); - - // drop the connection if we have somehow lost commands - if ( seq > cl->lastClientCommand + 1 ) { - Com_Printf( "Client %s lost %i clientCommands\n", cl->name, - seq - cl->lastClientCommand + 1 ); - SV_DropClient( cl, "Lost reliable commands" ); - return qfalse; - } - - // malicious users may try using too many string commands - // to lag other players. If we decide that we want to stall - // the command, we will stop processing the rest of the packet, - // including the usercmd. This causes flooders to lag themselves - // but not other people - // We don't do this when the client hasn't been active yet since its - // normal to spam a lot of commands when downloading - if ( !com_cl_running->integer && - cl->state >= CS_ACTIVE && - sv_floodProtect->integer && - svs.time < cl->nextReliableTime ) { - // ignore any other text messages from this client but let them keep playing - // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept - clientOk = qfalse; - } - - // don't allow another command for one second - cl->nextReliableTime = svs.time + 1000; - - SV_ExecuteClientCommand( cl, s, clientOk ); - - cl->lastClientCommand = seq; - Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s); - - return qtrue; // continue procesing -} - - -//================================================================================== - - -/* -================== -SV_ClientThink - -Also called by bot code -================== -*/ -void SV_ClientThink (client_t *cl, usercmd_t *cmd) { - cl->lastUsercmd = *cmd; - - if ( cl->state != CS_ACTIVE ) { - return; // may have been kicked during the last usercmd - } - - VM_Call( gvm, GAME_CLIENT_THINK, cl - svs.clients ); -} - -/* -================== -SV_UserMove - -The message usually contains all the movement commands -that were in the last three packets, so that the information -in dropped packets can be recovered. - -On very fast clients, there may be multiple usercmd packed into -each of the backup packets. -================== -*/ -static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { - int i, key; - int cmdCount; - usercmd_t nullcmd; - usercmd_t cmds[MAX_PACKET_USERCMDS]; - usercmd_t *cmd, *oldcmd; - - if ( delta ) { - cl->deltaMessage = cl->messageAcknowledge; - } else { - cl->deltaMessage = -1; - } - - cmdCount = MSG_ReadByte( msg ); - - if ( cmdCount < 1 ) { - Com_Printf( "cmdCount < 1\n" ); - return; - } - - if ( cmdCount > MAX_PACKET_USERCMDS ) { - Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); - return; - } - - // use the checksum feed in the key - key = sv.checksumFeed; - // also use the message acknowledge - key ^= cl->messageAcknowledge; - // also use the last acknowledged server command in the key - key ^= Com_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); - - Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); - oldcmd = &nullcmd; - for ( i = 0 ; i < cmdCount ; i++ ) { - cmd = &cmds[i]; - MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd ); - oldcmd = cmd; - } - - // save time for ping calculation - cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; - - // TTimo - // catch the no-cp-yet situation before SV_ClientEnterWorld - // if CS_ACTIVE, then it's time to trigger a new gamestate emission - // if not, then we are getting remaining parasite usermove commands, which we should ignore - if (sv_pure->integer != 0 && cl->pureAuthentic == 0 && !cl->gotCP) { - if (cl->state == CS_ACTIVE) - { - // we didn't get a cp yet, don't assume anything and just send the gamestate all over again - Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name, cl->state ); - SV_SendClientGameState( cl ); - } - return; - } - - // if this is the first usercmd we have received - // this gamestate, put the client into the world - if ( cl->state == CS_PRIMED ) { - SV_ClientEnterWorld( cl, &cmds[0] ); - // the moves can be processed normaly - } - - // a bad cp command was sent, drop the client - if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { - SV_DropClient( cl, "Cannot validate pure client!"); - return; - } - - if ( cl->state != CS_ACTIVE ) { - cl->deltaMessage = -1; - return; - } - - // usually, the first couple commands will be duplicates - // of ones we have previously received, but the servertimes - // in the commands will cause them to be immediately discarded - for ( i = 0 ; i < cmdCount ; i++ ) { - // if this is a cmd from before a map_restart ignore it - if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) { - continue; - } - // extremely lagged or cmd from before a map_restart - //if ( cmds[i].serverTime > svs.time + 3000 ) { - // continue; - //} - // don't execute if this is an old cmd which is already executed - // these old cmds are included when cl_packetdup > 0 - if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { - continue; - } - SV_ClientThink (cl, &cmds[ i ]); - } -} - - -/* -=========================================================================== - -USER CMD EXECUTION - -=========================================================================== -*/ - -/* -=================== -SV_ExecuteClientMessage - -Parse a client packet -=================== -*/ -void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { - int c; - int serverId; - - MSG_Bitstream(msg); - - serverId = MSG_ReadLong( msg ); - cl->messageAcknowledge = MSG_ReadLong( msg ); - - if (cl->messageAcknowledge < 0) { - // usually only hackers create messages like this - // it is more annoying for them to let them hanging -#ifndef NDEBUG - SV_DropClient( cl, "DEBUG: illegible client message" ); -#endif - return; - } - - cl->reliableAcknowledge = MSG_ReadLong( msg ); - - // NOTE: when the client message is fux0red the acknowledgement numbers - // can be out of range, this could cause the server to send thousands of server - // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient - if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { - // usually only hackers create messages like this - // it is more annoying for them to let them hanging -#ifndef NDEBUG - SV_DropClient( cl, "DEBUG: illegible client message" ); -#endif - cl->reliableAcknowledge = cl->reliableSequence; - return; - } - // if this is a usercmd from a previous gamestate, - // ignore it or retransmit the current gamestate - // - // if the client was downloading, let it stay at whatever serverId and - // gamestate it was at. This allows it to keep downloading even when - // the gamestate changes. After the download is finished, we'll - // notice and send it a new game state - // - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 - // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" - // but we still need to read the next message to move to next download or send gamestate - // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else - if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { - if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart - // they just haven't caught the map_restart yet - Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); - return; - } - // if we can tell that the client has dropped the last - // gamestate we sent them, resend it - if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { - Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); - SV_SendClientGameState( cl ); - } - return; - } - - // read optional clientCommand strings - do { - c = MSG_ReadByte( msg ); - if ( c == clc_EOF ) { - break; - } - if ( c != clc_clientCommand ) { - break; - } - if ( !SV_ClientCommand( cl, msg ) ) { - return; // we couldn't execute it because of the flood protection - } - if (cl->state == CS_ZOMBIE) { - return; // disconnect command - } - } while ( 1 ); - - // read the usercmd_t - if ( c == clc_move ) { - SV_UserMove( cl, msg, qtrue ); - } else if ( c == clc_moveNoDelta ) { - SV_UserMove( cl, msg, qfalse ); - } else if ( c != clc_EOF ) { - Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients ); - } -// if ( msg->readcount != msg->cursize ) { -// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); -// } -} +/* +=========================================================================== +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 +=========================================================================== +*/ +// sv_client.c -- server code for dealing with clients + +#include "server.h" + +static void SV_CloseDownload( client_t *cl ); + +/* +================= +SV_GetChallenge + +A "getchallenge" OOB command has been received +Returns a challenge number that can be used +in a subsequent connectResponse command. +We do this to prevent denial of service attacks that +flood the server with invalid connection IPs. With a +challenge, they must give a valid IP address. + +If we are authorizing, a challenge request will cause a packet +to be sent to the authorize server. + +When an authorizeip is returned, a challenge response will be +sent to that ip. +================= +*/ +void SV_GetChallenge( netadr_t from ) { + int i; + int oldest; + int oldestTime; + challenge_t *challenge; + + // ignore if we are in single player + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) { + return; + } + + oldest = 0; + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if ( !challenge->connected && NET_CompareAdr( from, challenge->adr ) ) { + break; + } + if ( challenge->time < oldestTime ) { + oldestTime = challenge->time; + oldest = i; + } + } + + if (i == MAX_CHALLENGES) { + // this is the first time this client has asked for a challenge + challenge = &svs.challenges[oldest]; + + challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time; + challenge->adr = from; + challenge->firstTime = svs.time; + challenge->time = svs.time; + challenge->connected = qfalse; + i = oldest; + } + + // if they are on a lan address, send the challengeResponse immediately + if ( Sys_IsLANAddress( from ) ) { + challenge->pingTime = svs.time; + NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge ); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // if they have been challenging for a long time and we + // haven't heard anything from the authorize server, go ahead and + // let them in, assuming the id server is down + if ( svs.time - challenge->firstTime > AUTHORIZE_TIMEOUT ) { + Com_DPrintf( "authorize server timed out\n" ); + + challenge->pingTime = svs.time; + NET_OutOfBandPrint( NS_SERVER, challenge->adr, + "challengeResponse %i", challenge->challenge ); + return; + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + cvar_t *fs; + char game[1024]; + + Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from )); + + strcpy(game, BASEGAME); + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (fs && fs->string[0] != 0) { + strcpy(game, fs->string); + } + + // the 0 is for backwards compatibility with obsolete sv_allowanonymous flags + // getIpAuthorize 0 + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "getIpAuthorize %i %i.%i.%i.%i %s 0 %s", svs.challenges[i].challenge, + from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, sv_strictAuth->string ); + } +} + +/* +==================== +SV_AuthorizeIpPacket + +A packet has been returned from the authorize server. +If we have a challenge adr for that ip, send the +challengeResponse to it +==================== +*/ +void SV_AuthorizeIpPacket( netadr_t from ) { + int challenge; + int i; + char *s; + char *r; + char ret[1024]; + + if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { + Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); + return; + } + + challenge = atoi( Cmd_Argv( 1 ) ); + + for (i = 0 ; i < MAX_CHALLENGES ; i++) { + if ( svs.challenges[i].challenge == challenge ) { + break; + } + } + if ( i == MAX_CHALLENGES ) { + Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); + return; + } + + // send a packet back to the original client + svs.challenges[i].pingTime = svs.time; + s = Cmd_Argv( 2 ); + r = Cmd_Argv( 3 ); // reason + + if ( !Q_stricmp( s, "demo" ) ) { + if ( Cvar_VariableValue( "fs_restrict" ) ) { + // a demo client connecting to a demo server + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i", svs.challenges[i].challenge ); + return; + } + // they are a demo client trying to connect to a real server + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" ); + // clear the challenge record so it won't timeout and let them through + Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + return; + } + if ( !Q_stricmp( s, "accept" ) ) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i", svs.challenges[i].challenge ); + return; + } + if ( !Q_stricmp( s, "unknown" ) ) { + if (!r) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting CD key authorization\n" ); + } else { + sprintf(ret, "print\n%s\n", r); + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); + } + // clear the challenge record so it won't timeout and let them through + Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + return; + } + + // authorization failed + if (!r) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nSomeone is using this CD Key\n" ); + } else { + sprintf(ret, "print\n%s\n", r); + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); + } + + // clear the challenge record so it won't timeout and let them through + Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); +} + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ + +#define PB_MESSAGE "PunkBuster Anti-Cheat software must be installed " \ + "and Enabled in order to join this server. An updated game patch can be downloaded from " \ + "www.idsoftware.com" + +void SV_DirectConnect( netadr_t from ) { + char userinfo[MAX_INFO_STRING]; + int i; + client_t *cl, *newcl; + MAC_STATIC client_t temp; + sharedEntity_t *ent; + int clientNum; + int version; + int qport; + int challenge; + char *password; + int startIndex; + char *denied; + int count; + + Com_DPrintf ("SVC_DirectConnect ()\n"); + + Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); + + version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); + if ( version != PROTOCOL_VERSION ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION ); + Com_DPrintf (" rejected connect from version %i\n", version); + return; + } + + challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); + qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); + + // quick reject + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + if (( svs.time - cl->lastConnectTime) + < (sv_reconnectlimit->integer * 1000)) { + Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); + return; + } + break; + } + } + + // see if the challenge is valid (LAN clients don't need to challenge) + if ( !NET_IsLocalAddress (from) ) { + int ping; + + for (i=0 ; ivalue && ping < sv_minPing->value ) { + // don't let them keep trying until they get a big delay + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); + Com_DPrintf ("Client %i rejected on a too low ping\n", i); + // reset the address otherwise their ping will keep increasing + // with each connect message and they'd eventually be able to connect + svs.challenges[i].adr.port = 0; + return; + } + if ( sv_maxPing->value && ping > sv_maxPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); + Com_DPrintf ("Client %i rejected on a too high ping\n", i); + return; + } + } + } else { + // force the "ip" info key to "localhost" + Info_SetValueForKey( userinfo, "ip", "localhost" ); + } + + newcl = &temp; + Com_Memset (newcl, 0, sizeof(client_t)); + + // if there is already a slot for this ip, reuse it + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); + newcl = cl; + + // this doesn't work because it nukes the players userinfo + +// // disconnect the client from the game first so any flags the +// // player might have are dropped +// VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); + // + goto gotnewcl; + } + } + + // find a client slot + // if "sv_privateClients" is set > 0, then that number + // of client slots will be reserved for connections that + // have "password" set to the value of "sv_privatePassword" + // Info requests will report the maxclients as if the private + // slots didn't exist, to prevent people from trying to connect + // to a full server. + // This is to allow us to reserve a couple slots here on our + // servers so we can play without having to kick people. + + // check for privateClient password + password = Info_ValueForKey( userinfo, "password" ); + if ( !strcmp( password, sv_privatePassword->string ) ) { + startIndex = 0; + } else { + // skip past the reserved slots + startIndex = sv_privateClients->integer; + } + + newcl = NULL; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if (cl->state == CS_FREE) { + newcl = cl; + break; + } + } + + if ( !newcl ) { + if ( NET_IsLocalAddress( from ) ) { + count = 0; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if (cl->netchan.remoteAddress.type == NA_BOT) { + count++; + } + } + // if they're all bots + if (count >= sv_maxclients->integer - startIndex) { + SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); + newcl = &svs.clients[sv_maxclients->integer - 1]; + } + else { + Com_Error( ERR_FATAL, "server is full on local connect\n" ); + return; + } + } + else { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); + Com_DPrintf ("Rejected a connection.\n"); + return; + } + } + + // we got a newcl, so reset the reliableSequence and reliableAcknowledge + cl->reliableAcknowledge = 0; + cl->reliableSequence = 0; + +gotnewcl: + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + *newcl = temp; + clientNum = newcl - svs.clients; + ent = SV_GentityNum( clientNum ); + newcl->gentity = ent; + + // save the challenge + newcl->challenge = challenge; + + // save the address + Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); + // init the netchan queue + newcl->netchan_end_queue = &newcl->netchan_start_queue; + + // save the userinfo + Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); + + // get the game a chance to reject this connection or modify the userinfo + denied = (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue + if ( denied ) { + // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call + denied = VM_ExplicitArgPtr( gvm, (int)denied ); + + NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); + Com_DPrintf ("Game rejected a connection: %s.\n", denied); + return; + } + + SV_UserinfoChanged( newcl ); + + // send the connect packet to the client + NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + + Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); + + newcl->state = CS_CONNECTED; + newcl->nextSnapshotTime = svs.time; + newcl->lastPacketTime = svs.time; + newcl->lastConnectTime = svs.time; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + newcl->gamestateMessageNum = -1; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + if ( count == 1 || count == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + + +/* +===================== +SV_DropClient + +Called when the player is totally leaving the server, either willingly +or unwillingly. This is NOT called if the entire server is quiting +or crashing -- SV_FinalMessage() will handle that +===================== +*/ +void SV_DropClient( client_t *drop, const char *reason ) { + int i; + challenge_t *challenge; + + if ( drop->state == CS_ZOMBIE ) { + return; // already dropped + } + + if ( !drop->gentity || !(drop->gentity->r.svFlags & SVF_BOT) ) { + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { + challenge->connected = qfalse; + break; + } + } + } + + // Kill any download + SV_CloseDownload( drop ); + + // tell everyone why they got dropped + SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason ); + + Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); + drop->state = CS_ZOMBIE; // become free in a few seconds + + if (drop->download) { + FS_FCloseFile( drop->download ); + drop->download = 0; + } + + // call the prog function for removing a client + // this will remove the body, among other things + VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients ); + + // add the disconnect command + SV_SendServerCommand( drop, "disconnect \"%s\"", reason); + + if ( drop->netchan.remoteAddress.type == NA_BOT ) { + SV_BotFreeClient( drop - svs.clients ); + } + + // nuke user info + SV_SetUserinfo( drop - svs.clients, "" ); + + // if this was the last client on the server, send a heartbeat + // to the master so it is known the server is empty + // send a heartbeat now so the master will get up to date info + // if there is already a slot for this ip, reuse it + for (i=0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + break; + } + } + if ( i == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + +/* +================ +SV_SendClientGameState + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each new map load. + +It will be resent if the client acknowledges a later message but has +the wrong gamestate. +================ +*/ +void SV_SendClientGameState( client_t *client ) { + int start; + entityState_t *base, nullstate; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); + Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); + client->state = CS_PRIMED; + client->pureAuthentic = 0; + client->gotCP = qfalse; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + client->gamestateMessageNum = client->netchan.outgoingSequence; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the gamestate + MSG_WriteByte( &msg, svc_gamestate ); + MSG_WriteLong( &msg, client->reliableSequence ); + + // write the configstrings + for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { + if (sv.configstrings[start][0]) { + MSG_WriteByte( &msg, svc_configstring ); + MSG_WriteShort( &msg, start ); + MSG_WriteBigString( &msg, sv.configstrings[start] ); + } + } + + // write the baselines + Com_Memset( &nullstate, 0, sizeof( nullstate ) ); + for ( start = 0 ; start < MAX_GENTITIES; start++ ) { + base = &sv.svEntities[start].baseline; + if ( !base->number ) { + continue; + } + MSG_WriteByte( &msg, svc_baseline ); + MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); + } + + MSG_WriteByte( &msg, svc_EOF ); + + MSG_WriteLong( &msg, client - svs.clients); + + // write the checksum feed + MSG_WriteLong( &msg, sv.checksumFeed); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + + +/* +================== +SV_ClientEnterWorld +================== +*/ +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { + int clientNum; + sharedEntity_t *ent; + + Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); + client->state = CS_ACTIVE; + + // set up the entity for the client + clientNum = client - svs.clients; + ent = SV_GentityNum( clientNum ); + ent->s.number = clientNum; + client->gentity = ent; + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + client->lastUsercmd = *cmd; + + // call the game begin function + VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients ); +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ + +/* +================== +SV_CloseDownload + +clear/free any download vars +================== +*/ +static void SV_CloseDownload( client_t *cl ) { + int i; + + // EOF + if (cl->download) { + FS_FCloseFile( cl->download ); + } + cl->download = 0; + *cl->downloadName = 0; + + // Free the temporary buffer space + for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { + if (cl->downloadBlocks[i]) { + Z_Free( cl->downloadBlocks[i] ); + cl->downloadBlocks[i] = NULL; + } + } + +} + +/* +================== +SV_StopDownload_f + +Abort a download if in progress +================== +*/ +void SV_StopDownload_f( client_t *cl ) { + if (*cl->downloadName) + Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", cl - svs.clients, cl->downloadName ); + + SV_CloseDownload( cl ); +} + +/* +================== +SV_DoneDownload_f + +Downloads are finished +================== +*/ +void SV_DoneDownload_f( client_t *cl ) { + Com_DPrintf( "clientDownload: %s Done\n", cl->name); + // resend the game state to update any clients that entered during the download + SV_SendClientGameState(cl); +} + +/* +================== +SV_NextDownload_f + +The argument will be the last acknowledged block from the client, it should be +the same as cl->downloadClientBlock +================== +*/ +void SV_NextDownload_f( client_t *cl ) +{ + int block = atoi( Cmd_Argv(1) ); + + if (block == cl->downloadClientBlock) { + Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", cl - svs.clients, block ); + + // Find out if we are done. A zero-length block indicates EOF + if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) { + Com_Printf( "clientDownload: %d : file \"%s\" completed\n", cl - svs.clients, cl->downloadName ); + SV_CloseDownload( cl ); + return; + } + + cl->downloadSendTime = svs.time; + cl->downloadClientBlock++; + return; + } + // We aren't getting an acknowledge for the correct block, drop the client + // FIXME: this is bad... the client will never parse the disconnect message + // because the cgame isn't loaded yet + SV_DropClient( cl, "broken download" ); +} + +/* +================== +SV_BeginDownload_f +================== +*/ +void SV_BeginDownload_f( client_t *cl ) { + + // Kill any existing download + SV_CloseDownload( cl ); + + // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open + // the file itself + Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) ); +} + +/* +================== +SV_WriteDownloadToClient + +Check to see if the client wants a file, open it if needed and start pumping the client +Fill up msg with data +================== +*/ +void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) +{ + int curindex; + int rate; + int blockspersnap; + int idPack, missionPack; + char errorMessage[1024]; + + if (!*cl->downloadName) + return; // Nothing being downloaded + + if (!cl->download) { + // We open the file here + + Com_Printf( "clientDownload: %d : begining \"%s\"\n", cl - svs.clients, cl->downloadName ); + + missionPack = FS_idPak(cl->downloadName, "missionpack"); + idPack = missionPack || FS_idPak(cl->downloadName, "baseq3"); + + if ( !sv_allowDownload->integer || idPack || + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { + // cannot auto-download file + if (idPack) { + Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName); + if (missionPack) { + Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" + "The Team Arena mission pack can be found in your local game store.", cl->downloadName); + } + else { + Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); + } + } else if ( !sv_allowDownload->integer ) { + Com_Printf("clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName); + if (sv_pure->integer) { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "You will need to get this file elsewhere before you " + "can connect to this pure server.\n", cl->downloadName); + } else { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "The server you are connecting to is not a pure server, " + "set autodownload to No in your settings and you might be " + "able to join the game anyway.\n", cl->downloadName); + } + } else { + // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? + // if the pk3 is referenced, it must have been found somewhere in the filesystem + Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName); + Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); + } + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, 0 ); // client is expecting block zero + MSG_WriteLong( msg, -1 ); // illegal file size + MSG_WriteString( msg, errorMessage ); + + *cl->downloadName = 0; + return; + } + + // Init + cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; + cl->downloadCount = 0; + cl->downloadEOF = qfalse; + } + + // Perform any reads that we need to + while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && + cl->downloadSize != cl->downloadCount) { + + curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); + + if (!cl->downloadBlocks[curindex]) + cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); + + cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); + + if (cl->downloadBlockSize[curindex] < 0) { + // EOF right now + cl->downloadCount = cl->downloadSize; + break; + } + + cl->downloadCount += cl->downloadBlockSize[curindex]; + + // Load in next block + cl->downloadCurrentBlock++; + } + + // Check to see if we have eof condition and add the EOF block + if (cl->downloadCount == cl->downloadSize && + !cl->downloadEOF && + cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { + + cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; + cl->downloadCurrentBlock++; + + cl->downloadEOF = qtrue; // We have added the EOF block + } + + // Loop up to window size times based on how many blocks we can fit in the + // client snapMsec and rate + + // based on the rate, how many bytes can we fit in the snapMsec time of the client + // normal rate / snapshotMsec calculation + rate = cl->rate; + if ( sv_maxRate->integer ) { + if ( sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + if ( sv_maxRate->integer < rate ) { + rate = sv_maxRate->integer; + } + } + + if (!rate) { + blockspersnap = 1; + } else { + blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / + MAX_DOWNLOAD_BLKSIZE; + } + + if (blockspersnap < 0) + blockspersnap = 1; + + while (blockspersnap--) { + + // Write out the next section of the file, if we have already reached our window, + // automatically start retransmitting + + if (cl->downloadClientBlock == cl->downloadCurrentBlock) + return; // Nothing to transmit + + if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { + // We have transmitted the complete window, should we start resending? + + //FIXME: This uses a hardcoded one second timeout for lost blocks + //the timeout should be based on client rate somehow + if (svs.time - cl->downloadSendTime > 1000) + cl->downloadXmitBlock = cl->downloadClientBlock; + else + return; + } + + // Send current block + curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); + + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, cl->downloadXmitBlock ); + + // block zero is special, contains file size + if ( cl->downloadXmitBlock == 0 ) + MSG_WriteLong( msg, cl->downloadSize ); + + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); + + // Write the block + if ( cl->downloadBlockSize[curindex] ) { + MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); + } + + Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock ); + + // Move on to the next block + // It will get sent with next snap shot. The rate will keep us in line. + cl->downloadXmitBlock++; + + cl->downloadSendTime = svs.time; + } +} + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately FIXME: move to game? +================= +*/ +static void SV_Disconnect_f( client_t *cl ) { + SV_DropClient( cl, "disconnected" ); +} + +/* +================= +SV_VerifyPaks_f + +If we are pure, disconnect the client if they do no meet the following conditions: + +1. the first two checksums match our view of cgame and ui +2. there are no any additional checksums that we do not have + +This routine would be a bit simpler with a goto but i abstained + +================= +*/ +static void SV_VerifyPaks_f( client_t *cl ) { + int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg; + int nClientChkSum[1024]; + int nServerChkSum[1024]; + const char *pPaks, *pArg; + qboolean bGood = qtrue; + + // if we are pure, we "expect" the client to load certain things from + // certain pk3 files, namely we want the client to have loaded the + // ui and cgame that we think should be loaded based on the pure setting + // + if ( sv_pure->integer != 0 ) { + + bGood = qtrue; + nChkSum1 = nChkSum2 = 0; + // we run the game, so determine which cgame and ui the client "should" be running + bGood = (FS_FileIsInPAK("vm/cgame.qvm", &nChkSum1) == 1); + if (bGood) + bGood = (FS_FileIsInPAK("vm/ui.qvm", &nChkSum2) == 1); + + nClientPaks = Cmd_Argc(); + + // start at arg 2 ( skip serverId cl_paks ) + nCurArg = 1; + + pArg = Cmd_Argv(nCurArg++); + if(!pArg) { + bGood = qfalse; + } + else + { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore + // since serverId is a frame count, it always goes up + if (atoi(pArg) < sv.checksumFeedServerId) + { + Com_DPrintf("ignoring outdated cp command from client %s\n", cl->name); + return; + } + } + + // we basically use this while loop to avoid using 'goto' :) + while (bGood) { + + // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums" + // numChecksums is encoded + if (nClientPaks < 6) { + bGood = qfalse; + break; + } + // verify first to be the cgame checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) { + bGood = qfalse; + break; + } + // verify the second to be the ui checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) { + bGood = qfalse; + break; + } + // should be sitting at the delimeter now + pArg = Cmd_Argv(nCurArg++); + if (*pArg != '@') { + bGood = qfalse; + break; + } + // store checksums since tokenization is not re-entrant + for (i = 0; nCurArg < nClientPaks; i++) { + nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++)); + } + + // store number to compare against (minus one cause the last is the number of checksums) + nClientPaks = i - 1; + + // make sure none of the client check sums are the same + // so the client can't send 5 the same checksums + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nClientPaks; j++) { + if (i == j) + continue; + if (nClientChkSum[i] == nClientChkSum[j]) { + bGood = qfalse; + break; + } + } + if (bGood == qfalse) + break; + } + if (bGood == qfalse) + break; + + // get the pure checksums of the pk3 files loaded by the server + pPaks = FS_LoadedPakPureChecksums(); + Cmd_TokenizeString( pPaks ); + nServerPaks = Cmd_Argc(); + if (nServerPaks > 1024) + nServerPaks = 1024; + + for (i = 0; i < nServerPaks; i++) { + nServerChkSum[i] = atoi(Cmd_Argv(i)); + } + + // check if the client has provided any pure checksums of pk3 files not loaded by the server + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nServerPaks; j++) { + if (nClientChkSum[i] == nServerChkSum[j]) { + break; + } + } + if (j >= nServerPaks) { + bGood = qfalse; + break; + } + } + if ( bGood == qfalse ) { + break; + } + + // check if the number of checksums was correct + nChkSum1 = sv.checksumFeed; + for (i = 0; i < nClientPaks; i++) { + nChkSum1 ^= nClientChkSum[i]; + } + nChkSum1 ^= nClientPaks; + if (nChkSum1 != nClientChkSum[nClientPaks]) { + bGood = qfalse; + break; + } + + // break out + break; + } + + cl->gotCP = qtrue; + + if (bGood) { + cl->pureAuthentic = 1; + } + else { + cl->pureAuthentic = 0; + cl->nextSnapshotTime = -1; + cl->state = CS_ACTIVE; + SV_SendClientSnapshot( cl ); + SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); + } + } +} + +/* +================= +SV_ResetPureClient_f +================= +*/ +static void SV_ResetPureClient_f( client_t *cl ) { + cl->pureAuthentic = 0; + cl->gotCP = qfalse; +} + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C friendly form. +================= +*/ +void SV_UserinfoChanged( client_t *cl ) { + char *val; + int i; + + // name for C code + Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); + + // rate command + + // if the client is on the same subnet as the server and we aren't running an + // internet public server, assume they don't need a rate choke + if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) { + cl->rate = 99999; // lans should not rate limit + } else { + val = Info_ValueForKey (cl->userinfo, "rate"); + if (strlen(val)) { + i = atoi(val); + cl->rate = i; + if (cl->rate < 1000) { + cl->rate = 1000; + } else if (cl->rate > 90000) { + cl->rate = 90000; + } + } else { + cl->rate = 3000; + } + } + val = Info_ValueForKey (cl->userinfo, "handicap"); + if (strlen(val)) { + i = atoi(val); + if (i<=0 || i>100 || strlen(val) > 4) { + Info_SetValueForKey( cl->userinfo, "handicap", "100" ); + } + } + + // snaps command + val = Info_ValueForKey (cl->userinfo, "snaps"); + if (strlen(val)) { + i = atoi(val); + if ( i < 1 ) { + i = 1; + } else if ( i > 30 ) { + i = 30; + } + cl->snapshotMsec = 1000/i; + } else { + cl->snapshotMsec = 50; + } + + // TTimo + // maintain the IP information + // this is set in SV_DirectConnect (directly on the server, not transmitted), may be lost when client updates it's userinfo + // the banning code relies on this being consistently present + val = Info_ValueForKey (cl->userinfo, "ip"); + if (!val[0]) + { + //Com_DPrintf("Maintain IP in userinfo for '%s'\n", cl->name); + if ( !NET_IsLocalAddress(cl->netchan.remoteAddress) ) + Info_SetValueForKey( cl->userinfo, "ip", NET_AdrToString( cl->netchan.remoteAddress ) ); + else + // force the "ip" info key to "localhost" for local clients + Info_SetValueForKey( cl->userinfo, "ip", "localhost" ); + } +} + + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static void SV_UpdateUserinfo_f( client_t *cl ) { + Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); + + SV_UserinfoChanged( cl ); + // call prog code to allow overrides + VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); +} + +typedef struct { + char *name; + void (*func)( client_t *cl ); +} ucmd_t; + +static ucmd_t ucmds[] = { + {"userinfo", SV_UpdateUserinfo_f}, + {"disconnect", SV_Disconnect_f}, + {"cp", SV_VerifyPaks_f}, + {"vdr", SV_ResetPureClient_f}, + {"download", SV_BeginDownload_f}, + {"nextdl", SV_NextDownload_f}, + {"stopdl", SV_StopDownload_f}, + {"donedl", SV_DoneDownload_f}, + + {NULL, NULL} +}; + +/* +================== +SV_ExecuteClientCommand + +Also called by bot code +================== +*/ +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { + ucmd_t *u; + qboolean bProcessed = qfalse; + + Cmd_TokenizeString( s ); + + // see if it is a server level command + for (u=ucmds ; u->name ; u++) { + if (!strcmp (Cmd_Argv(0), u->name) ) { + u->func( cl ); + bProcessed = qtrue; + break; + } + } + + if (clientOK) { + // pass unknown strings to the game + if (!u->name && sv.state == SS_GAME) { + VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients ); + } + } + else if (!bProcessed) + Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv(0) ); +} + +/* +=============== +SV_ClientCommand +=============== +*/ +static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { + int seq; + const char *s; + qboolean clientOk = qtrue; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed it + if ( cl->lastClientCommand >= seq ) { + return qtrue; + } + + Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); + + // drop the connection if we have somehow lost commands + if ( seq > cl->lastClientCommand + 1 ) { + Com_Printf( "Client %s lost %i clientCommands\n", cl->name, + seq - cl->lastClientCommand + 1 ); + SV_DropClient( cl, "Lost reliable commands" ); + return qfalse; + } + + // malicious users may try using too many string commands + // to lag other players. If we decide that we want to stall + // the command, we will stop processing the rest of the packet, + // including the usercmd. This causes flooders to lag themselves + // but not other people + // We don't do this when the client hasn't been active yet since its + // normal to spam a lot of commands when downloading + if ( !com_cl_running->integer && + cl->state >= CS_ACTIVE && + sv_floodProtect->integer && + svs.time < cl->nextReliableTime ) { + // ignore any other text messages from this client but let them keep playing + // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept + clientOk = qfalse; + } + + // don't allow another command for one second + cl->nextReliableTime = svs.time + 1000; + + SV_ExecuteClientCommand( cl, s, clientOk ); + + cl->lastClientCommand = seq; + Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s); + + return qtrue; // continue procesing +} + + +//================================================================================== + + +/* +================== +SV_ClientThink + +Also called by bot code +================== +*/ +void SV_ClientThink (client_t *cl, usercmd_t *cmd) { + cl->lastUsercmd = *cmd; + + if ( cl->state != CS_ACTIVE ) { + return; // may have been kicked during the last usercmd + } + + VM_Call( gvm, GAME_CLIENT_THINK, cl - svs.clients ); +} + +/* +================== +SV_UserMove + +The message usually contains all the movement commands +that were in the last three packets, so that the information +in dropped packets can be recovered. + +On very fast clients, there may be multiple usercmd packed into +each of the backup packets. +================== +*/ +static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { + int i, key; + int cmdCount; + usercmd_t nullcmd; + usercmd_t cmds[MAX_PACKET_USERCMDS]; + usercmd_t *cmd, *oldcmd; + + if ( delta ) { + cl->deltaMessage = cl->messageAcknowledge; + } else { + cl->deltaMessage = -1; + } + + cmdCount = MSG_ReadByte( msg ); + + if ( cmdCount < 1 ) { + Com_Printf( "cmdCount < 1\n" ); + return; + } + + if ( cmdCount > MAX_PACKET_USERCMDS ) { + Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); + return; + } + + // use the checksum feed in the key + key = sv.checksumFeed; + // also use the message acknowledge + key ^= cl->messageAcknowledge; + // also use the last acknowledged server command in the key + key ^= Com_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + for ( i = 0 ; i < cmdCount ; i++ ) { + cmd = &cmds[i]; + MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd ); + oldcmd = cmd; + } + + // save time for ping calculation + cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; + + // TTimo + // catch the no-cp-yet situation before SV_ClientEnterWorld + // if CS_ACTIVE, then it's time to trigger a new gamestate emission + // if not, then we are getting remaining parasite usermove commands, which we should ignore + if (sv_pure->integer != 0 && cl->pureAuthentic == 0 && !cl->gotCP) { + if (cl->state == CS_ACTIVE) + { + // we didn't get a cp yet, don't assume anything and just send the gamestate all over again + Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name, cl->state ); + SV_SendClientGameState( cl ); + } + return; + } + + // if this is the first usercmd we have received + // this gamestate, put the client into the world + if ( cl->state == CS_PRIMED ) { + SV_ClientEnterWorld( cl, &cmds[0] ); + // the moves can be processed normaly + } + + // a bad cp command was sent, drop the client + if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { + SV_DropClient( cl, "Cannot validate pure client!"); + return; + } + + if ( cl->state != CS_ACTIVE ) { + cl->deltaMessage = -1; + return; + } + + // usually, the first couple commands will be duplicates + // of ones we have previously received, but the servertimes + // in the commands will cause them to be immediately discarded + for ( i = 0 ; i < cmdCount ; i++ ) { + // if this is a cmd from before a map_restart ignore it + if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) { + continue; + } + // extremely lagged or cmd from before a map_restart + //if ( cmds[i].serverTime > svs.time + 3000 ) { + // continue; + //} + // don't execute if this is an old cmd which is already executed + // these old cmds are included when cl_packetdup > 0 + if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { + continue; + } + SV_ClientThink (cl, &cmds[ i ]); + } +} + + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { + int c; + int serverId; + + MSG_Bitstream(msg); + + serverId = MSG_ReadLong( msg ); + cl->messageAcknowledge = MSG_ReadLong( msg ); + + if (cl->messageAcknowledge < 0) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + return; + } + + cl->reliableAcknowledge = MSG_ReadLong( msg ); + + // NOTE: when the client message is fux0red the acknowledgement numbers + // can be out of range, this could cause the server to send thousands of server + // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient + if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + cl->reliableAcknowledge = cl->reliableSequence; + return; + } + // if this is a usercmd from a previous gamestate, + // ignore it or retransmit the current gamestate + // + // if the client was downloading, let it stay at whatever serverId and + // gamestate it was at. This allows it to keep downloading even when + // the gamestate changes. After the download is finished, we'll + // notice and send it a new game state + // + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 + // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" + // but we still need to read the next message to move to next download or send gamestate + // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else + if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { + if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart + // they just haven't caught the map_restart yet + Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); + return; + } + // if we can tell that the client has dropped the last + // gamestate we sent them, resend it + if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { + Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); + SV_SendClientGameState( cl ); + } + return; + } + + // read optional clientCommand strings + do { + c = MSG_ReadByte( msg ); + if ( c == clc_EOF ) { + break; + } + if ( c != clc_clientCommand ) { + break; + } + if ( !SV_ClientCommand( cl, msg ) ) { + return; // we couldn't execute it because of the flood protection + } + if (cl->state == CS_ZOMBIE) { + return; // disconnect command + } + } while ( 1 ); + + // read the usercmd_t + if ( c == clc_move ) { + SV_UserMove( cl, msg, qtrue ); + } else if ( c == clc_moveNoDelta ) { + SV_UserMove( cl, msg, qfalse ); + } else if ( c != clc_EOF ) { + Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients ); + } +// if ( msg->readcount != msg->cursize ) { +// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); +// } +} diff --git a/code/server/sv_game.c b/code/server/sv_game.c index 202994e..f99466d 100755 --- a/code/server/sv_game.c +++ b/code/server/sv_game.c @@ -1,981 +1,981 @@ -/* -=========================================================================== -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 -=========================================================================== -*/ -// sv_game.c -- interface to the game dll - -#include "server.h" - -#include "../game/botlib.h" - -botlib_export_t *botlib_export; - -void SV_GameError( const char *string ) { - Com_Error( ERR_DROP, "%s", string ); -} - -void SV_GamePrint( const char *string ) { - Com_Printf( "%s", string ); -} - -// these functions must be used instead of pointer arithmetic, because -// the game allocates gentities with private information after the server shared part -int SV_NumForGentity( sharedEntity_t *ent ) { - int num; - - num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; - - return num; -} - -sharedEntity_t *SV_GentityNum( int num ) { - sharedEntity_t *ent; - - ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(num)); - - return ent; -} - -playerState_t *SV_GameClientNum( int num ) { - playerState_t *ps; - - ps = (playerState_t *)((byte *)sv.gameClients + sv.gameClientSize*(num)); - - return ps; -} - -svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ) { - if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) { - Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); - } - return &sv.svEntities[ gEnt->s.number ]; -} - -sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { - int num; - - num = svEnt - sv.svEntities; - return SV_GentityNum( num ); -} - -/* -=============== -SV_GameSendServerCommand - -Sends a command string to a client -=============== -*/ -void SV_GameSendServerCommand( int clientNum, const char *text ) { - if ( clientNum == -1 ) { - SV_SendServerCommand( NULL, "%s", text ); - } else { - if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { - return; - } - SV_SendServerCommand( svs.clients + clientNum, "%s", text ); - } -} - - -/* -=============== -SV_GameDropClient - -Disconnects the client with a message -=============== -*/ -void SV_GameDropClient( int clientNum, const char *reason ) { - if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { - return; - } - SV_DropClient( svs.clients + clientNum, reason ); -} - - -/* -================= -SV_SetBrushModel - -sets mins and maxs for inline bmodels -================= -*/ -void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { - clipHandle_t h; - vec3_t mins, maxs; - - if (!name) { - Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); - } - - if (name[0] != '*') { - Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name ); - } - - - ent->s.modelindex = atoi( name + 1 ); - - h = CM_InlineModel( ent->s.modelindex ); - CM_ModelBounds( h, mins, maxs ); - VectorCopy (mins, ent->r.mins); - VectorCopy (maxs, ent->r.maxs); - ent->r.bmodel = qtrue; - - ent->r.contents = -1; // we don't know exactly what is in the brushes - - SV_LinkEntity( ent ); // FIXME: remove -} - - - -/* -================= -SV_inPVS - -Also checks portalareas so that doors block sight -================= -*/ -qboolean SV_inPVS (const vec3_t p1, const vec3_t p2) -{ - int leafnum; - int cluster; - int area1, area2; - byte *mask; - - leafnum = CM_PointLeafnum (p1); - cluster = CM_LeafCluster (leafnum); - area1 = CM_LeafArea (leafnum); - mask = CM_ClusterPVS (cluster); - - leafnum = CM_PointLeafnum (p2); - cluster = CM_LeafCluster (leafnum); - area2 = CM_LeafArea (leafnum); - if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) - return qfalse; - if (!CM_AreasConnected (area1, area2)) - return qfalse; // a door blocks sight - return qtrue; -} - - -/* -================= -SV_inPVSIgnorePortals - -Does NOT check portalareas -================= -*/ -qboolean SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2) -{ - int leafnum; - int cluster; - int area1, area2; - byte *mask; - - leafnum = CM_PointLeafnum (p1); - cluster = CM_LeafCluster (leafnum); - area1 = CM_LeafArea (leafnum); - mask = CM_ClusterPVS (cluster); - - leafnum = CM_PointLeafnum (p2); - cluster = CM_LeafCluster (leafnum); - area2 = CM_LeafArea (leafnum); - - if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) - return qfalse; - - return qtrue; -} - - -/* -======================== -SV_AdjustAreaPortalState -======================== -*/ -void SV_AdjustAreaPortalState( sharedEntity_t *ent, qboolean open ) { - svEntity_t *svEnt; - - svEnt = SV_SvEntityForGentity( ent ); - if ( svEnt->areanum2 == -1 ) { - return; - } - CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open ); -} - - -/* -================== -SV_GameAreaEntities -================== -*/ -qboolean SV_EntityContact( vec3_t mins, vec3_t maxs, const sharedEntity_t *gEnt, int capsule ) { - const float *origin, *angles; - clipHandle_t ch; - trace_t trace; - - // check for exact collision - origin = gEnt->r.currentOrigin; - angles = gEnt->r.currentAngles; - - ch = SV_ClipHandleForEntity( gEnt ); - CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, - ch, -1, origin, angles, capsule ); - - return trace.startsolid; -} - - -/* -=============== -SV_GetServerinfo - -=============== -*/ -void SV_GetServerinfo( char *buffer, int bufferSize ) { - if ( bufferSize < 1 ) { - Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize ); - } - Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize ); -} - -/* -=============== -SV_LocateGameData - -=============== -*/ -void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, - playerState_t *clients, int sizeofGameClient ) { - sv.gentities = gEnts; - sv.gentitySize = sizeofGEntity_t; - sv.num_entities = numGEntities; - - sv.gameClients = clients; - sv.gameClientSize = sizeofGameClient; -} - - -/* -=============== -SV_GetUsercmd - -=============== -*/ -void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { - if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { - Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum ); - } - *cmd = svs.clients[clientNum].lastUsercmd; -} - -//============================================== - -static int FloatAsInt( float f ) { - union - { - int i; - float f; - } temp; - - temp.f = f; - return temp.i; -} - -/* -==================== -SV_GameSystemCalls - -The module is making a system call -==================== -*/ -//rcg010207 - see my comments in VM_DllSyscall(), in qcommon/vm.c ... -#if ((defined __linux__) && (defined __powerpc__)) -#define VMA(x) ((void *) args[x]) -#else -#define VMA(x) VM_ArgPtr(args[x]) -#endif - -#define VMF(x) ((float *)args)[x] - -int SV_GameSystemCalls( int *args ) { - switch( args[0] ) { - case G_PRINT: - Com_Printf( "%s", VMA(1) ); - return 0; - case G_ERROR: - Com_Error( ERR_DROP, "%s", VMA(1) ); - return 0; - case G_MILLISECONDS: - return Sys_Milliseconds(); - case G_CVAR_REGISTER: - Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); - return 0; - case G_CVAR_UPDATE: - Cvar_Update( VMA(1) ); - return 0; - case G_CVAR_SET: - Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) ); - return 0; - case G_CVAR_VARIABLE_INTEGER_VALUE: - return Cvar_VariableIntegerValue( (const char *)VMA(1) ); - case G_CVAR_VARIABLE_STRING_BUFFER: - Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); - return 0; - case G_ARGC: - return Cmd_Argc(); - case G_ARGV: - Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); - return 0; - case G_SEND_CONSOLE_COMMAND: - Cbuf_ExecuteText( args[1], VMA(2) ); - return 0; - - case G_FS_FOPEN_FILE: - return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); - case G_FS_READ: - FS_Read2( VMA(1), args[2], args[3] ); - return 0; - case G_FS_WRITE: - FS_Write( VMA(1), args[2], args[3] ); - return 0; - case G_FS_FCLOSE_FILE: - FS_FCloseFile( args[1] ); - return 0; - case G_FS_GETFILELIST: - return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); - case G_FS_SEEK: - return FS_Seek( args[1], args[2], args[3] ); - - case G_LOCATE_GAME_DATA: - SV_LocateGameData( VMA(1), args[2], args[3], VMA(4), args[5] ); - return 0; - case G_DROP_CLIENT: - SV_GameDropClient( args[1], VMA(2) ); - return 0; - case G_SEND_SERVER_COMMAND: - SV_GameSendServerCommand( args[1], VMA(2) ); - return 0; - case G_LINKENTITY: - SV_LinkEntity( VMA(1) ); - return 0; - case G_UNLINKENTITY: - SV_UnlinkEntity( VMA(1) ); - return 0; - case G_ENTITIES_IN_BOX: - return SV_AreaEntities( VMA(1), VMA(2), VMA(3), args[4] ); - case G_ENTITY_CONTACT: - return SV_EntityContact( VMA(1), VMA(2), VMA(3), /*int capsule*/ qfalse ); - case G_ENTITY_CONTACTCAPSULE: - return SV_EntityContact( VMA(1), VMA(2), VMA(3), /*int capsule*/ qtrue ); - case G_TRACE: - SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse ); - return 0; - case G_TRACECAPSULE: - SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue ); - return 0; - case G_POINT_CONTENTS: - return SV_PointContents( VMA(1), args[2] ); - case G_SET_BRUSH_MODEL: - SV_SetBrushModel( VMA(1), VMA(2) ); - return 0; - case G_IN_PVS: - return SV_inPVS( VMA(1), VMA(2) ); - case G_IN_PVS_IGNORE_PORTALS: - return SV_inPVSIgnorePortals( VMA(1), VMA(2) ); - - case G_SET_CONFIGSTRING: - SV_SetConfigstring( args[1], VMA(2) ); - return 0; - case G_GET_CONFIGSTRING: - SV_GetConfigstring( args[1], VMA(2), args[3] ); - return 0; - case G_SET_USERINFO: - SV_SetUserinfo( args[1], VMA(2) ); - return 0; - case G_GET_USERINFO: - SV_GetUserinfo( args[1], VMA(2), args[3] ); - return 0; - case G_GET_SERVERINFO: - SV_GetServerinfo( VMA(1), args[2] ); - return 0; - case G_ADJUST_AREA_PORTAL_STATE: - SV_AdjustAreaPortalState( VMA(1), args[2] ); - return 0; - case G_AREAS_CONNECTED: - return CM_AreasConnected( args[1], args[2] ); - - case G_BOT_ALLOCATE_CLIENT: - return SV_BotAllocateClient(); - case G_BOT_FREE_CLIENT: - SV_BotFreeClient( args[1] ); - return 0; - - case G_GET_USERCMD: - SV_GetUsercmd( args[1], VMA(2) ); - return 0; - case G_GET_ENTITY_TOKEN: - { - const char *s; - - s = COM_Parse( &sv.entityParsePoint ); - Q_strncpyz( VMA(1), s, args[2] ); - if ( !sv.entityParsePoint && !s[0] ) { - return qfalse; - } else { - return qtrue; - } - } - - case G_DEBUG_POLYGON_CREATE: - return BotImport_DebugPolygonCreate( args[1], args[2], VMA(3) ); - case G_DEBUG_POLYGON_DELETE: - BotImport_DebugPolygonDelete( args[1] ); - return 0; - case G_REAL_TIME: - return Com_RealTime( VMA(1) ); - case G_SNAPVECTOR: - Sys_SnapVector( VMA(1) ); - return 0; - - //==================================== - - case BOTLIB_SETUP: - return SV_BotLibSetup(); - case BOTLIB_SHUTDOWN: - return SV_BotLibShutdown(); - case BOTLIB_LIBVAR_SET: - return botlib_export->BotLibVarSet( VMA(1), VMA(2) ); - case BOTLIB_LIBVAR_GET: - return botlib_export->BotLibVarGet( VMA(1), VMA(2), args[3] ); - - case BOTLIB_PC_ADD_GLOBAL_DEFINE: - return botlib_export->PC_AddGlobalDefine( VMA(1) ); - case BOTLIB_PC_LOAD_SOURCE: - return botlib_export->PC_LoadSourceHandle( VMA(1) ); - case BOTLIB_PC_FREE_SOURCE: - return botlib_export->PC_FreeSourceHandle( args[1] ); - case BOTLIB_PC_READ_TOKEN: - return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); - case BOTLIB_PC_SOURCE_FILE_AND_LINE: - return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); - - case BOTLIB_START_FRAME: - return botlib_export->BotLibStartFrame( VMF(1) ); - case BOTLIB_LOAD_MAP: - return botlib_export->BotLibLoadMap( VMA(1) ); - case BOTLIB_UPDATENTITY: - return botlib_export->BotLibUpdateEntity( args[1], VMA(2) ); - case BOTLIB_TEST: - return botlib_export->Test( args[1], VMA(2), VMA(3), VMA(4) ); - - case BOTLIB_GET_SNAPSHOT_ENTITY: - return SV_BotGetSnapshotEntity( args[1], args[2] ); - case BOTLIB_GET_CONSOLE_MESSAGE: - return SV_BotGetConsoleMessage( args[1], VMA(2), args[3] ); - case BOTLIB_USER_COMMAND: - SV_ClientThink( &svs.clients[args[1]], VMA(2) ); - return 0; - - case BOTLIB_AAS_BBOX_AREAS: - return botlib_export->aas.AAS_BBoxAreas( VMA(1), VMA(2), VMA(3), args[4] ); - case BOTLIB_AAS_AREA_INFO: - return botlib_export->aas.AAS_AreaInfo( args[1], VMA(2) ); - case BOTLIB_AAS_ALTERNATIVE_ROUTE_GOAL: - return botlib_export->aas.AAS_AlternativeRouteGoals( VMA(1), args[2], VMA(3), args[4], args[5], VMA(6), args[7], args[8] ); - case BOTLIB_AAS_ENTITY_INFO: - botlib_export->aas.AAS_EntityInfo( args[1], VMA(2) ); - return 0; - - case BOTLIB_AAS_INITIALIZED: - return botlib_export->aas.AAS_Initialized(); - case BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX: - botlib_export->aas.AAS_PresenceTypeBoundingBox( args[1], VMA(2), VMA(3) ); - return 0; - case BOTLIB_AAS_TIME: - return FloatAsInt( botlib_export->aas.AAS_Time() ); - - case BOTLIB_AAS_POINT_AREA_NUM: - return botlib_export->aas.AAS_PointAreaNum( VMA(1) ); - case BOTLIB_AAS_POINT_REACHABILITY_AREA_INDEX: - return botlib_export->aas.AAS_PointReachabilityAreaIndex( VMA(1) ); - case BOTLIB_AAS_TRACE_AREAS: - return botlib_export->aas.AAS_TraceAreas( VMA(1), VMA(2), VMA(3), VMA(4), args[5] ); - - case BOTLIB_AAS_POINT_CONTENTS: - return botlib_export->aas.AAS_PointContents( VMA(1) ); - case BOTLIB_AAS_NEXT_BSP_ENTITY: - return botlib_export->aas.AAS_NextBSPEntity( args[1] ); - case BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY: - return botlib_export->aas.AAS_ValueForBSPEpairKey( args[1], VMA(2), VMA(3), args[4] ); - case BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY: - return botlib_export->aas.AAS_VectorForBSPEpairKey( args[1], VMA(2), VMA(3) ); - case BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY: - return botlib_export->aas.AAS_FloatForBSPEpairKey( args[1], VMA(2), VMA(3) ); - case BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY: - return botlib_export->aas.AAS_IntForBSPEpairKey( args[1], VMA(2), VMA(3) ); - - case BOTLIB_AAS_AREA_REACHABILITY: - return botlib_export->aas.AAS_AreaReachability( args[1] ); - - case BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA: - return botlib_export->aas.AAS_AreaTravelTimeToGoalArea( args[1], VMA(2), args[3], args[4] ); - case BOTLIB_AAS_ENABLE_ROUTING_AREA: - return botlib_export->aas.AAS_EnableRoutingArea( args[1], args[2] ); - case BOTLIB_AAS_PREDICT_ROUTE: - return botlib_export->aas.AAS_PredictRoute( VMA(1), args[2], VMA(3), args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11] ); - - case BOTLIB_AAS_SWIMMING: - return botlib_export->aas.AAS_Swimming( VMA(1) ); - case BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT: - return botlib_export->aas.AAS_PredictClientMovement( VMA(1), args[2], VMA(3), args[4], args[5], - VMA(6), VMA(7), args[8], args[9], VMF(10), args[11], args[12], args[13] ); - - case BOTLIB_EA_SAY: - botlib_export->ea.EA_Say( args[1], VMA(2) ); - return 0; - case BOTLIB_EA_SAY_TEAM: - botlib_export->ea.EA_SayTeam( args[1], VMA(2) ); - return 0; - case BOTLIB_EA_COMMAND: - botlib_export->ea.EA_Command( args[1], VMA(2) ); - return 0; - - case BOTLIB_EA_ACTION: - botlib_export->ea.EA_Action( args[1], args[2] ); - break; - case BOTLIB_EA_GESTURE: - botlib_export->ea.EA_Gesture( args[1] ); - return 0; - case BOTLIB_EA_TALK: - botlib_export->ea.EA_Talk( args[1] ); - return 0; - case BOTLIB_EA_ATTACK: - botlib_export->ea.EA_Attack( args[1] ); - return 0; - case BOTLIB_EA_USE: - botlib_export->ea.EA_Use( args[1] ); - return 0; - case BOTLIB_EA_RESPAWN: - botlib_export->ea.EA_Respawn( args[1] ); - return 0; - case BOTLIB_EA_CROUCH: - botlib_export->ea.EA_Crouch( args[1] ); - return 0; - case BOTLIB_EA_MOVE_UP: - botlib_export->ea.EA_MoveUp( args[1] ); - return 0; - case BOTLIB_EA_MOVE_DOWN: - botlib_export->ea.EA_MoveDown( args[1] ); - return 0; - case BOTLIB_EA_MOVE_FORWARD: - botlib_export->ea.EA_MoveForward( args[1] ); - return 0; - case BOTLIB_EA_MOVE_BACK: - botlib_export->ea.EA_MoveBack( args[1] ); - return 0; - case BOTLIB_EA_MOVE_LEFT: - botlib_export->ea.EA_MoveLeft( args[1] ); - return 0; - case BOTLIB_EA_MOVE_RIGHT: - botlib_export->ea.EA_MoveRight( args[1] ); - return 0; - - case BOTLIB_EA_SELECT_WEAPON: - botlib_export->ea.EA_SelectWeapon( args[1], args[2] ); - return 0; - case BOTLIB_EA_JUMP: - botlib_export->ea.EA_Jump( args[1] ); - return 0; - case BOTLIB_EA_DELAYED_JUMP: - botlib_export->ea.EA_DelayedJump( args[1] ); - return 0; - case BOTLIB_EA_MOVE: - botlib_export->ea.EA_Move( args[1], VMA(2), VMF(3) ); - return 0; - case BOTLIB_EA_VIEW: - botlib_export->ea.EA_View( args[1], VMA(2) ); - return 0; - - case BOTLIB_EA_END_REGULAR: - botlib_export->ea.EA_EndRegular( args[1], VMF(2) ); - return 0; - case BOTLIB_EA_GET_INPUT: - botlib_export->ea.EA_GetInput( args[1], VMF(2), VMA(3) ); - return 0; - case BOTLIB_EA_RESET_INPUT: - botlib_export->ea.EA_ResetInput( args[1] ); - return 0; - - case BOTLIB_AI_LOAD_CHARACTER: - return botlib_export->ai.BotLoadCharacter( VMA(1), VMF(2) ); - case BOTLIB_AI_FREE_CHARACTER: - botlib_export->ai.BotFreeCharacter( args[1] ); - return 0; - case BOTLIB_AI_CHARACTERISTIC_FLOAT: - return FloatAsInt( botlib_export->ai.Characteristic_Float( args[1], args[2] ) ); - case BOTLIB_AI_CHARACTERISTIC_BFLOAT: - return FloatAsInt( botlib_export->ai.Characteristic_BFloat( args[1], args[2], VMF(3), VMF(4) ) ); - case BOTLIB_AI_CHARACTERISTIC_INTEGER: - return botlib_export->ai.Characteristic_Integer( args[1], args[2] ); - case BOTLIB_AI_CHARACTERISTIC_BINTEGER: - return botlib_export->ai.Characteristic_BInteger( args[1], args[2], args[3], args[4] ); - case BOTLIB_AI_CHARACTERISTIC_STRING: - botlib_export->ai.Characteristic_String( args[1], args[2], VMA(3), args[4] ); - return 0; - - case BOTLIB_AI_ALLOC_CHAT_STATE: - return botlib_export->ai.BotAllocChatState(); - case BOTLIB_AI_FREE_CHAT_STATE: - botlib_export->ai.BotFreeChatState( args[1] ); - return 0; - case BOTLIB_AI_QUEUE_CONSOLE_MESSAGE: - botlib_export->ai.BotQueueConsoleMessage( args[1], args[2], VMA(3) ); - return 0; - case BOTLIB_AI_REMOVE_CONSOLE_MESSAGE: - botlib_export->ai.BotRemoveConsoleMessage( args[1], args[2] ); - return 0; - case BOTLIB_AI_NEXT_CONSOLE_MESSAGE: - return botlib_export->ai.BotNextConsoleMessage( args[1], VMA(2) ); - case BOTLIB_AI_NUM_CONSOLE_MESSAGE: - return botlib_export->ai.BotNumConsoleMessages( args[1] ); - case BOTLIB_AI_INITIAL_CHAT: - botlib_export->ai.BotInitialChat( args[1], VMA(2), args[3], VMA(4), VMA(5), VMA(6), VMA(7), VMA(8), VMA(9), VMA(10), VMA(11) ); - return 0; - case BOTLIB_AI_NUM_INITIAL_CHATS: - return botlib_export->ai.BotNumInitialChats( args[1], VMA(2) ); - case BOTLIB_AI_REPLY_CHAT: - return botlib_export->ai.BotReplyChat( args[1], VMA(2), args[3], args[4], VMA(5), VMA(6), VMA(7), VMA(8), VMA(9), VMA(10), VMA(11), VMA(12) ); - case BOTLIB_AI_CHAT_LENGTH: - return botlib_export->ai.BotChatLength( args[1] ); - case BOTLIB_AI_ENTER_CHAT: - botlib_export->ai.BotEnterChat( args[1], args[2], args[3] ); - return 0; - case BOTLIB_AI_GET_CHAT_MESSAGE: - botlib_export->ai.BotGetChatMessage( args[1], VMA(2), args[3] ); - return 0; - case BOTLIB_AI_STRING_CONTAINS: - return botlib_export->ai.StringContains( VMA(1), VMA(2), args[3] ); - case BOTLIB_AI_FIND_MATCH: - return botlib_export->ai.BotFindMatch( VMA(1), VMA(2), args[3] ); - case BOTLIB_AI_MATCH_VARIABLE: - botlib_export->ai.BotMatchVariable( VMA(1), args[2], VMA(3), args[4] ); - return 0; - case BOTLIB_AI_UNIFY_WHITE_SPACES: - botlib_export->ai.UnifyWhiteSpaces( VMA(1) ); - return 0; - case BOTLIB_AI_REPLACE_SYNONYMS: - botlib_export->ai.BotReplaceSynonyms( VMA(1), args[2] ); - return 0; - case BOTLIB_AI_LOAD_CHAT_FILE: - return botlib_export->ai.BotLoadChatFile( args[1], VMA(2), VMA(3) ); - case BOTLIB_AI_SET_CHAT_GENDER: - botlib_export->ai.BotSetChatGender( args[1], args[2] ); - return 0; - case BOTLIB_AI_SET_CHAT_NAME: - botlib_export->ai.BotSetChatName( args[1], VMA(2), args[3] ); - return 0; - - case BOTLIB_AI_RESET_GOAL_STATE: - botlib_export->ai.BotResetGoalState( args[1] ); - return 0; - case BOTLIB_AI_RESET_AVOID_GOALS: - botlib_export->ai.BotResetAvoidGoals( args[1] ); - return 0; - case BOTLIB_AI_REMOVE_FROM_AVOID_GOALS: - botlib_export->ai.BotRemoveFromAvoidGoals( args[1], args[2] ); - return 0; - case BOTLIB_AI_PUSH_GOAL: - botlib_export->ai.BotPushGoal( args[1], VMA(2) ); - return 0; - case BOTLIB_AI_POP_GOAL: - botlib_export->ai.BotPopGoal( args[1] ); - return 0; - case BOTLIB_AI_EMPTY_GOAL_STACK: - botlib_export->ai.BotEmptyGoalStack( args[1] ); - return 0; - case BOTLIB_AI_DUMP_AVOID_GOALS: - botlib_export->ai.BotDumpAvoidGoals( args[1] ); - return 0; - case BOTLIB_AI_DUMP_GOAL_STACK: - botlib_export->ai.BotDumpGoalStack( args[1] ); - return 0; - case BOTLIB_AI_GOAL_NAME: - botlib_export->ai.BotGoalName( args[1], VMA(2), args[3] ); - return 0; - case BOTLIB_AI_GET_TOP_GOAL: - return botlib_export->ai.BotGetTopGoal( args[1], VMA(2) ); - case BOTLIB_AI_GET_SECOND_GOAL: - return botlib_export->ai.BotGetSecondGoal( args[1], VMA(2) ); - case BOTLIB_AI_CHOOSE_LTG_ITEM: - return botlib_export->ai.BotChooseLTGItem( args[1], VMA(2), VMA(3), args[4] ); - case BOTLIB_AI_CHOOSE_NBG_ITEM: - return botlib_export->ai.BotChooseNBGItem( args[1], VMA(2), VMA(3), args[4], VMA(5), VMF(6) ); - case BOTLIB_AI_TOUCHING_GOAL: - return botlib_export->ai.BotTouchingGoal( VMA(1), VMA(2) ); - case BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE: - return botlib_export->ai.BotItemGoalInVisButNotVisible( args[1], VMA(2), VMA(3), VMA(4) ); - case BOTLIB_AI_GET_LEVEL_ITEM_GOAL: - return botlib_export->ai.BotGetLevelItemGoal( args[1], VMA(2), VMA(3) ); - case BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL: - return botlib_export->ai.BotGetNextCampSpotGoal( args[1], VMA(2) ); - case BOTLIB_AI_GET_MAP_LOCATION_GOAL: - return botlib_export->ai.BotGetMapLocationGoal( VMA(1), VMA(2) ); - case BOTLIB_AI_AVOID_GOAL_TIME: - return FloatAsInt( botlib_export->ai.BotAvoidGoalTime( args[1], args[2] ) ); - case BOTLIB_AI_SET_AVOID_GOAL_TIME: - botlib_export->ai.BotSetAvoidGoalTime( args[1], args[2], VMF(3)); - return 0; - case BOTLIB_AI_INIT_LEVEL_ITEMS: - botlib_export->ai.BotInitLevelItems(); - return 0; - case BOTLIB_AI_UPDATE_ENTITY_ITEMS: - botlib_export->ai.BotUpdateEntityItems(); - return 0; - case BOTLIB_AI_LOAD_ITEM_WEIGHTS: - return botlib_export->ai.BotLoadItemWeights( args[1], VMA(2) ); - case BOTLIB_AI_FREE_ITEM_WEIGHTS: - botlib_export->ai.BotFreeItemWeights( args[1] ); - return 0; - case BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC: - botlib_export->ai.BotInterbreedGoalFuzzyLogic( args[1], args[2], args[3] ); - return 0; - case BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC: - botlib_export->ai.BotSaveGoalFuzzyLogic( args[1], VMA(2) ); - return 0; - case BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC: - botlib_export->ai.BotMutateGoalFuzzyLogic( args[1], VMF(2) ); - return 0; - case BOTLIB_AI_ALLOC_GOAL_STATE: - return botlib_export->ai.BotAllocGoalState( args[1] ); - case BOTLIB_AI_FREE_GOAL_STATE: - botlib_export->ai.BotFreeGoalState( args[1] ); - return 0; - - case BOTLIB_AI_RESET_MOVE_STATE: - botlib_export->ai.BotResetMoveState( args[1] ); - return 0; - case BOTLIB_AI_ADD_AVOID_SPOT: - botlib_export->ai.BotAddAvoidSpot( args[1], VMA(2), VMF(3), args[4] ); - return 0; - case BOTLIB_AI_MOVE_TO_GOAL: - botlib_export->ai.BotMoveToGoal( VMA(1), args[2], VMA(3), args[4] ); - return 0; - case BOTLIB_AI_MOVE_IN_DIRECTION: - return botlib_export->ai.BotMoveInDirection( args[1], VMA(2), VMF(3), args[4] ); - case BOTLIB_AI_RESET_AVOID_REACH: - botlib_export->ai.BotResetAvoidReach( args[1] ); - return 0; - case BOTLIB_AI_RESET_LAST_AVOID_REACH: - botlib_export->ai.BotResetLastAvoidReach( args[1] ); - return 0; - case BOTLIB_AI_REACHABILITY_AREA: - return botlib_export->ai.BotReachabilityArea( VMA(1), args[2] ); - case BOTLIB_AI_MOVEMENT_VIEW_TARGET: - return botlib_export->ai.BotMovementViewTarget( args[1], VMA(2), args[3], VMF(4), VMA(5) ); - case BOTLIB_AI_PREDICT_VISIBLE_POSITION: - return botlib_export->ai.BotPredictVisiblePosition( VMA(1), args[2], VMA(3), args[4], VMA(5) ); - case BOTLIB_AI_ALLOC_MOVE_STATE: - return botlib_export->ai.BotAllocMoveState(); - case BOTLIB_AI_FREE_MOVE_STATE: - botlib_export->ai.BotFreeMoveState( args[1] ); - return 0; - case BOTLIB_AI_INIT_MOVE_STATE: - botlib_export->ai.BotInitMoveState( args[1], VMA(2) ); - return 0; - - case BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON: - return botlib_export->ai.BotChooseBestFightWeapon( args[1], VMA(2) ); - case BOTLIB_AI_GET_WEAPON_INFO: - botlib_export->ai.BotGetWeaponInfo( args[1], args[2], VMA(3) ); - return 0; - case BOTLIB_AI_LOAD_WEAPON_WEIGHTS: - return botlib_export->ai.BotLoadWeaponWeights( args[1], VMA(2) ); - case BOTLIB_AI_ALLOC_WEAPON_STATE: - return botlib_export->ai.BotAllocWeaponState(); - case BOTLIB_AI_FREE_WEAPON_STATE: - botlib_export->ai.BotFreeWeaponState( args[1] ); - return 0; - case BOTLIB_AI_RESET_WEAPON_STATE: - botlib_export->ai.BotResetWeaponState( args[1] ); - return 0; - - case BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION: - return botlib_export->ai.GeneticParentsAndChildSelection(args[1], VMA(2), VMA(3), VMA(4), VMA(5)); - - case TRAP_MEMSET: - Com_Memset( VMA(1), args[2], args[3] ); - return 0; - - case TRAP_MEMCPY: - Com_Memcpy( VMA(1), VMA(2), args[3] ); - return 0; - - case TRAP_STRNCPY: - return (int)strncpy( VMA(1), VMA(2), args[3] ); - - case TRAP_SIN: - return FloatAsInt( sin( VMF(1) ) ); - - case TRAP_COS: - return FloatAsInt( cos( VMF(1) ) ); - - case TRAP_ATAN2: - return FloatAsInt( atan2( VMF(1), VMF(2) ) ); - - case TRAP_SQRT: - return FloatAsInt( sqrt( VMF(1) ) ); - - case TRAP_MATRIXMULTIPLY: - MatrixMultiply( VMA(1), VMA(2), VMA(3) ); - return 0; - - case TRAP_ANGLEVECTORS: - AngleVectors( VMA(1), VMA(2), VMA(3), VMA(4) ); - return 0; - - case TRAP_PERPENDICULARVECTOR: - PerpendicularVector( VMA(1), VMA(2) ); - return 0; - - case TRAP_FLOOR: - return FloatAsInt( floor( VMF(1) ) ); - - case TRAP_CEIL: - return FloatAsInt( ceil( VMF(1) ) ); - - - default: - Com_Error( ERR_DROP, "Bad game system trap: %i", args[0] ); - } - return -1; -} - -/* -=============== -SV_ShutdownGameProgs - -Called every time a map changes -=============== -*/ -void SV_ShutdownGameProgs( void ) { - if ( !gvm ) { - return; - } - VM_Call( gvm, GAME_SHUTDOWN, qfalse ); - VM_Free( gvm ); - gvm = NULL; -} - -/* -================== -SV_InitGameVM - -Called for both a full init and a restart -================== -*/ -static void SV_InitGameVM( qboolean restart ) { - int i; - - // start the entity parsing at the beginning - sv.entityParsePoint = CM_EntityString(); - - // clear all gentity pointers that might still be set from - // a previous level - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522 - // now done before GAME_INIT call - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { - svs.clients[i].gentity = NULL; - } - - // use the current msec count for a random seed - // init for this gamestate - VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart ); -} - - - -/* -=================== -SV_RestartGameProgs - -Called on a map_restart, but not on a normal map change -=================== -*/ -void SV_RestartGameProgs( void ) { - if ( !gvm ) { - return; - } - VM_Call( gvm, GAME_SHUTDOWN, qtrue ); - - // do a restart instead of a free - gvm = VM_Restart( gvm ); - if ( !gvm ) { // bk001212 - as done below - Com_Error( ERR_FATAL, "VM_Restart on game failed" ); - } - - SV_InitGameVM( qtrue ); -} - - -/* -=============== -SV_InitGameProgs - -Called on a normal map change, not on a map_restart -=============== -*/ -void SV_InitGameProgs( void ) { - cvar_t *var; - //FIXME these are temp while I make bots run in vm - extern int bot_enable; - - var = Cvar_Get( "bot_enable", "1", CVAR_LATCH ); - if ( var ) { - bot_enable = var->integer; - } - else { - bot_enable = 0; - } - - // load the dll or bytecode - gvm = VM_Create( "qagame", SV_GameSystemCalls, Cvar_VariableValue( "vm_game" ) ); - if ( !gvm ) { - Com_Error( ERR_FATAL, "VM_Create on game failed" ); - } - - SV_InitGameVM( qfalse ); -} - - -/* -==================== -SV_GameCommand - -See if the current console command is claimed by the game -==================== -*/ -qboolean SV_GameCommand( void ) { - if ( sv.state != SS_GAME ) { - return qfalse; - } - - return VM_Call( gvm, GAME_CONSOLE_COMMAND ); -} - +/* +=========================================================================== +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 +=========================================================================== +*/ +// sv_game.c -- interface to the game dll + +#include "server.h" + +#include "../game/botlib.h" + +botlib_export_t *botlib_export; + +void SV_GameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + +void SV_GamePrint( const char *string ) { + Com_Printf( "%s", string ); +} + +// these functions must be used instead of pointer arithmetic, because +// the game allocates gentities with private information after the server shared part +int SV_NumForGentity( sharedEntity_t *ent ) { + int num; + + num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; + + return num; +} + +sharedEntity_t *SV_GentityNum( int num ) { + sharedEntity_t *ent; + + ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(num)); + + return ent; +} + +playerState_t *SV_GameClientNum( int num ) { + playerState_t *ps; + + ps = (playerState_t *)((byte *)sv.gameClients + sv.gameClientSize*(num)); + + return ps; +} + +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ) { + if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + return &sv.svEntities[ gEnt->s.number ]; +} + +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { + int num; + + num = svEnt - sv.svEntities; + return SV_GentityNum( num ); +} + +/* +=============== +SV_GameSendServerCommand + +Sends a command string to a client +=============== +*/ +void SV_GameSendServerCommand( int clientNum, const char *text ) { + if ( clientNum == -1 ) { + SV_SendServerCommand( NULL, "%s", text ); + } else { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + } +} + + +/* +=============== +SV_GameDropClient + +Disconnects the client with a message +=============== +*/ +void SV_GameDropClient( int clientNum, const char *reason ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_DropClient( svs.clients + clientNum, reason ); +} + + +/* +================= +SV_SetBrushModel + +sets mins and maxs for inline bmodels +================= +*/ +void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { + clipHandle_t h; + vec3_t mins, maxs; + + if (!name) { + Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); + } + + if (name[0] != '*') { + Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name ); + } + + + ent->s.modelindex = atoi( name + 1 ); + + h = CM_InlineModel( ent->s.modelindex ); + CM_ModelBounds( h, mins, maxs ); + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + ent->r.bmodel = qtrue; + + ent->r.contents = -1; // we don't know exactly what is in the brushes + + SV_LinkEntity( ent ); // FIXME: remove +} + + + +/* +================= +SV_inPVS + +Also checks portalareas so that doors block sight +================= +*/ +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + int area1, area2; + byte *mask; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + return qfalse; + if (!CM_AreasConnected (area1, area2)) + return qfalse; // a door blocks sight + return qtrue; +} + + +/* +================= +SV_inPVSIgnorePortals + +Does NOT check portalareas +================= +*/ +qboolean SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + int area1, area2; + byte *mask; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + return qfalse; + + return qtrue; +} + + +/* +======================== +SV_AdjustAreaPortalState +======================== +*/ +void SV_AdjustAreaPortalState( sharedEntity_t *ent, qboolean open ) { + svEntity_t *svEnt; + + svEnt = SV_SvEntityForGentity( ent ); + if ( svEnt->areanum2 == -1 ) { + return; + } + CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open ); +} + + +/* +================== +SV_GameAreaEntities +================== +*/ +qboolean SV_EntityContact( vec3_t mins, vec3_t maxs, const sharedEntity_t *gEnt, int capsule ) { + const float *origin, *angles; + clipHandle_t ch; + trace_t trace; + + // check for exact collision + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + ch = SV_ClipHandleForEntity( gEnt ); + CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, + ch, -1, origin, angles, capsule ); + + return trace.startsolid; +} + + +/* +=============== +SV_GetServerinfo + +=============== +*/ +void SV_GetServerinfo( char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize ); + } + Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize ); +} + +/* +=============== +SV_LocateGameData + +=============== +*/ +void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGameClient ) { + sv.gentities = gEnts; + sv.gentitySize = sizeofGEntity_t; + sv.num_entities = numGEntities; + + sv.gameClients = clients; + sv.gameClientSize = sizeofGameClient; +} + + +/* +=============== +SV_GetUsercmd + +=============== +*/ +void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum ); + } + *cmd = svs.clients[clientNum].lastUsercmd; +} + +//============================================== + +static int FloatAsInt( float f ) { + union + { + int i; + float f; + } temp; + + temp.f = f; + return temp.i; +} + +/* +==================== +SV_GameSystemCalls + +The module is making a system call +==================== +*/ +//rcg010207 - see my comments in VM_DllSyscall(), in qcommon/vm.c ... +#if ((defined __linux__) && (defined __powerpc__)) +#define VMA(x) ((void *) args[x]) +#else +#define VMA(x) VM_ArgPtr(args[x]) +#endif + +#define VMF(x) ((float *)args)[x] + +int SV_GameSystemCalls( int *args ) { + switch( args[0] ) { + case G_PRINT: + Com_Printf( "%s", VMA(1) ); + return 0; + case G_ERROR: + Com_Error( ERR_DROP, "%s", VMA(1) ); + return 0; + case G_MILLISECONDS: + return Sys_Milliseconds(); + case G_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + case G_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + case G_CVAR_SET: + Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) ); + return 0; + case G_CVAR_VARIABLE_INTEGER_VALUE: + return Cvar_VariableIntegerValue( (const char *)VMA(1) ); + case G_CVAR_VARIABLE_STRING_BUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + case G_ARGC: + return Cmd_Argc(); + case G_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + case G_SEND_CONSOLE_COMMAND: + Cbuf_ExecuteText( args[1], VMA(2) ); + return 0; + + case G_FS_FOPEN_FILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + case G_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case G_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case G_FS_FCLOSE_FILE: + FS_FCloseFile( args[1] ); + return 0; + case G_FS_GETFILELIST: + return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); + case G_FS_SEEK: + return FS_Seek( args[1], args[2], args[3] ); + + case G_LOCATE_GAME_DATA: + SV_LocateGameData( VMA(1), args[2], args[3], VMA(4), args[5] ); + return 0; + case G_DROP_CLIENT: + SV_GameDropClient( args[1], VMA(2) ); + return 0; + case G_SEND_SERVER_COMMAND: + SV_GameSendServerCommand( args[1], VMA(2) ); + return 0; + case G_LINKENTITY: + SV_LinkEntity( VMA(1) ); + return 0; + case G_UNLINKENTITY: + SV_UnlinkEntity( VMA(1) ); + return 0; + case G_ENTITIES_IN_BOX: + return SV_AreaEntities( VMA(1), VMA(2), VMA(3), args[4] ); + case G_ENTITY_CONTACT: + return SV_EntityContact( VMA(1), VMA(2), VMA(3), /*int capsule*/ qfalse ); + case G_ENTITY_CONTACTCAPSULE: + return SV_EntityContact( VMA(1), VMA(2), VMA(3), /*int capsule*/ qtrue ); + case G_TRACE: + SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse ); + return 0; + case G_TRACECAPSULE: + SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue ); + return 0; + case G_POINT_CONTENTS: + return SV_PointContents( VMA(1), args[2] ); + case G_SET_BRUSH_MODEL: + SV_SetBrushModel( VMA(1), VMA(2) ); + return 0; + case G_IN_PVS: + return SV_inPVS( VMA(1), VMA(2) ); + case G_IN_PVS_IGNORE_PORTALS: + return SV_inPVSIgnorePortals( VMA(1), VMA(2) ); + + case G_SET_CONFIGSTRING: + SV_SetConfigstring( args[1], VMA(2) ); + return 0; + case G_GET_CONFIGSTRING: + SV_GetConfigstring( args[1], VMA(2), args[3] ); + return 0; + case G_SET_USERINFO: + SV_SetUserinfo( args[1], VMA(2) ); + return 0; + case G_GET_USERINFO: + SV_GetUserinfo( args[1], VMA(2), args[3] ); + return 0; + case G_GET_SERVERINFO: + SV_GetServerinfo( VMA(1), args[2] ); + return 0; + case G_ADJUST_AREA_PORTAL_STATE: + SV_AdjustAreaPortalState( VMA(1), args[2] ); + return 0; + case G_AREAS_CONNECTED: + return CM_AreasConnected( args[1], args[2] ); + + case G_BOT_ALLOCATE_CLIENT: + return SV_BotAllocateClient(); + case G_BOT_FREE_CLIENT: + SV_BotFreeClient( args[1] ); + return 0; + + case G_GET_USERCMD: + SV_GetUsercmd( args[1], VMA(2) ); + return 0; + case G_GET_ENTITY_TOKEN: + { + const char *s; + + s = COM_Parse( &sv.entityParsePoint ); + Q_strncpyz( VMA(1), s, args[2] ); + if ( !sv.entityParsePoint && !s[0] ) { + return qfalse; + } else { + return qtrue; + } + } + + case G_DEBUG_POLYGON_CREATE: + return BotImport_DebugPolygonCreate( args[1], args[2], VMA(3) ); + case G_DEBUG_POLYGON_DELETE: + BotImport_DebugPolygonDelete( args[1] ); + return 0; + case G_REAL_TIME: + return Com_RealTime( VMA(1) ); + case G_SNAPVECTOR: + Sys_SnapVector( VMA(1) ); + return 0; + + //==================================== + + case BOTLIB_SETUP: + return SV_BotLibSetup(); + case BOTLIB_SHUTDOWN: + return SV_BotLibShutdown(); + case BOTLIB_LIBVAR_SET: + return botlib_export->BotLibVarSet( VMA(1), VMA(2) ); + case BOTLIB_LIBVAR_GET: + return botlib_export->BotLibVarGet( VMA(1), VMA(2), args[3] ); + + case BOTLIB_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA(1) ); + case BOTLIB_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA(1) ); + case BOTLIB_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case BOTLIB_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); + case BOTLIB_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case BOTLIB_START_FRAME: + return botlib_export->BotLibStartFrame( VMF(1) ); + case BOTLIB_LOAD_MAP: + return botlib_export->BotLibLoadMap( VMA(1) ); + case BOTLIB_UPDATENTITY: + return botlib_export->BotLibUpdateEntity( args[1], VMA(2) ); + case BOTLIB_TEST: + return botlib_export->Test( args[1], VMA(2), VMA(3), VMA(4) ); + + case BOTLIB_GET_SNAPSHOT_ENTITY: + return SV_BotGetSnapshotEntity( args[1], args[2] ); + case BOTLIB_GET_CONSOLE_MESSAGE: + return SV_BotGetConsoleMessage( args[1], VMA(2), args[3] ); + case BOTLIB_USER_COMMAND: + SV_ClientThink( &svs.clients[args[1]], VMA(2) ); + return 0; + + case BOTLIB_AAS_BBOX_AREAS: + return botlib_export->aas.AAS_BBoxAreas( VMA(1), VMA(2), VMA(3), args[4] ); + case BOTLIB_AAS_AREA_INFO: + return botlib_export->aas.AAS_AreaInfo( args[1], VMA(2) ); + case BOTLIB_AAS_ALTERNATIVE_ROUTE_GOAL: + return botlib_export->aas.AAS_AlternativeRouteGoals( VMA(1), args[2], VMA(3), args[4], args[5], VMA(6), args[7], args[8] ); + case BOTLIB_AAS_ENTITY_INFO: + botlib_export->aas.AAS_EntityInfo( args[1], VMA(2) ); + return 0; + + case BOTLIB_AAS_INITIALIZED: + return botlib_export->aas.AAS_Initialized(); + case BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX: + botlib_export->aas.AAS_PresenceTypeBoundingBox( args[1], VMA(2), VMA(3) ); + return 0; + case BOTLIB_AAS_TIME: + return FloatAsInt( botlib_export->aas.AAS_Time() ); + + case BOTLIB_AAS_POINT_AREA_NUM: + return botlib_export->aas.AAS_PointAreaNum( VMA(1) ); + case BOTLIB_AAS_POINT_REACHABILITY_AREA_INDEX: + return botlib_export->aas.AAS_PointReachabilityAreaIndex( VMA(1) ); + case BOTLIB_AAS_TRACE_AREAS: + return botlib_export->aas.AAS_TraceAreas( VMA(1), VMA(2), VMA(3), VMA(4), args[5] ); + + case BOTLIB_AAS_POINT_CONTENTS: + return botlib_export->aas.AAS_PointContents( VMA(1) ); + case BOTLIB_AAS_NEXT_BSP_ENTITY: + return botlib_export->aas.AAS_NextBSPEntity( args[1] ); + case BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_ValueForBSPEpairKey( args[1], VMA(2), VMA(3), args[4] ); + case BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_VectorForBSPEpairKey( args[1], VMA(2), VMA(3) ); + case BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_FloatForBSPEpairKey( args[1], VMA(2), VMA(3) ); + case BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_IntForBSPEpairKey( args[1], VMA(2), VMA(3) ); + + case BOTLIB_AAS_AREA_REACHABILITY: + return botlib_export->aas.AAS_AreaReachability( args[1] ); + + case BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA: + return botlib_export->aas.AAS_AreaTravelTimeToGoalArea( args[1], VMA(2), args[3], args[4] ); + case BOTLIB_AAS_ENABLE_ROUTING_AREA: + return botlib_export->aas.AAS_EnableRoutingArea( args[1], args[2] ); + case BOTLIB_AAS_PREDICT_ROUTE: + return botlib_export->aas.AAS_PredictRoute( VMA(1), args[2], VMA(3), args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11] ); + + case BOTLIB_AAS_SWIMMING: + return botlib_export->aas.AAS_Swimming( VMA(1) ); + case BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT: + return botlib_export->aas.AAS_PredictClientMovement( VMA(1), args[2], VMA(3), args[4], args[5], + VMA(6), VMA(7), args[8], args[9], VMF(10), args[11], args[12], args[13] ); + + case BOTLIB_EA_SAY: + botlib_export->ea.EA_Say( args[1], VMA(2) ); + return 0; + case BOTLIB_EA_SAY_TEAM: + botlib_export->ea.EA_SayTeam( args[1], VMA(2) ); + return 0; + case BOTLIB_EA_COMMAND: + botlib_export->ea.EA_Command( args[1], VMA(2) ); + return 0; + + case BOTLIB_EA_ACTION: + botlib_export->ea.EA_Action( args[1], args[2] ); + break; + case BOTLIB_EA_GESTURE: + botlib_export->ea.EA_Gesture( args[1] ); + return 0; + case BOTLIB_EA_TALK: + botlib_export->ea.EA_Talk( args[1] ); + return 0; + case BOTLIB_EA_ATTACK: + botlib_export->ea.EA_Attack( args[1] ); + return 0; + case BOTLIB_EA_USE: + botlib_export->ea.EA_Use( args[1] ); + return 0; + case BOTLIB_EA_RESPAWN: + botlib_export->ea.EA_Respawn( args[1] ); + return 0; + case BOTLIB_EA_CROUCH: + botlib_export->ea.EA_Crouch( args[1] ); + return 0; + case BOTLIB_EA_MOVE_UP: + botlib_export->ea.EA_MoveUp( args[1] ); + return 0; + case BOTLIB_EA_MOVE_DOWN: + botlib_export->ea.EA_MoveDown( args[1] ); + return 0; + case BOTLIB_EA_MOVE_FORWARD: + botlib_export->ea.EA_MoveForward( args[1] ); + return 0; + case BOTLIB_EA_MOVE_BACK: + botlib_export->ea.EA_MoveBack( args[1] ); + return 0; + case BOTLIB_EA_MOVE_LEFT: + botlib_export->ea.EA_MoveLeft( args[1] ); + return 0; + case BOTLIB_EA_MOVE_RIGHT: + botlib_export->ea.EA_MoveRight( args[1] ); + return 0; + + case BOTLIB_EA_SELECT_WEAPON: + botlib_export->ea.EA_SelectWeapon( args[1], args[2] ); + return 0; + case BOTLIB_EA_JUMP: + botlib_export->ea.EA_Jump( args[1] ); + return 0; + case BOTLIB_EA_DELAYED_JUMP: + botlib_export->ea.EA_DelayedJump( args[1] ); + return 0; + case BOTLIB_EA_MOVE: + botlib_export->ea.EA_Move( args[1], VMA(2), VMF(3) ); + return 0; + case BOTLIB_EA_VIEW: + botlib_export->ea.EA_View( args[1], VMA(2) ); + return 0; + + case BOTLIB_EA_END_REGULAR: + botlib_export->ea.EA_EndRegular( args[1], VMF(2) ); + return 0; + case BOTLIB_EA_GET_INPUT: + botlib_export->ea.EA_GetInput( args[1], VMF(2), VMA(3) ); + return 0; + case BOTLIB_EA_RESET_INPUT: + botlib_export->ea.EA_ResetInput( args[1] ); + return 0; + + case BOTLIB_AI_LOAD_CHARACTER: + return botlib_export->ai.BotLoadCharacter( VMA(1), VMF(2) ); + case BOTLIB_AI_FREE_CHARACTER: + botlib_export->ai.BotFreeCharacter( args[1] ); + return 0; + case BOTLIB_AI_CHARACTERISTIC_FLOAT: + return FloatAsInt( botlib_export->ai.Characteristic_Float( args[1], args[2] ) ); + case BOTLIB_AI_CHARACTERISTIC_BFLOAT: + return FloatAsInt( botlib_export->ai.Characteristic_BFloat( args[1], args[2], VMF(3), VMF(4) ) ); + case BOTLIB_AI_CHARACTERISTIC_INTEGER: + return botlib_export->ai.Characteristic_Integer( args[1], args[2] ); + case BOTLIB_AI_CHARACTERISTIC_BINTEGER: + return botlib_export->ai.Characteristic_BInteger( args[1], args[2], args[3], args[4] ); + case BOTLIB_AI_CHARACTERISTIC_STRING: + botlib_export->ai.Characteristic_String( args[1], args[2], VMA(3), args[4] ); + return 0; + + case BOTLIB_AI_ALLOC_CHAT_STATE: + return botlib_export->ai.BotAllocChatState(); + case BOTLIB_AI_FREE_CHAT_STATE: + botlib_export->ai.BotFreeChatState( args[1] ); + return 0; + case BOTLIB_AI_QUEUE_CONSOLE_MESSAGE: + botlib_export->ai.BotQueueConsoleMessage( args[1], args[2], VMA(3) ); + return 0; + case BOTLIB_AI_REMOVE_CONSOLE_MESSAGE: + botlib_export->ai.BotRemoveConsoleMessage( args[1], args[2] ); + return 0; + case BOTLIB_AI_NEXT_CONSOLE_MESSAGE: + return botlib_export->ai.BotNextConsoleMessage( args[1], VMA(2) ); + case BOTLIB_AI_NUM_CONSOLE_MESSAGE: + return botlib_export->ai.BotNumConsoleMessages( args[1] ); + case BOTLIB_AI_INITIAL_CHAT: + botlib_export->ai.BotInitialChat( args[1], VMA(2), args[3], VMA(4), VMA(5), VMA(6), VMA(7), VMA(8), VMA(9), VMA(10), VMA(11) ); + return 0; + case BOTLIB_AI_NUM_INITIAL_CHATS: + return botlib_export->ai.BotNumInitialChats( args[1], VMA(2) ); + case BOTLIB_AI_REPLY_CHAT: + return botlib_export->ai.BotReplyChat( args[1], VMA(2), args[3], args[4], VMA(5), VMA(6), VMA(7), VMA(8), VMA(9), VMA(10), VMA(11), VMA(12) ); + case BOTLIB_AI_CHAT_LENGTH: + return botlib_export->ai.BotChatLength( args[1] ); + case BOTLIB_AI_ENTER_CHAT: + botlib_export->ai.BotEnterChat( args[1], args[2], args[3] ); + return 0; + case BOTLIB_AI_GET_CHAT_MESSAGE: + botlib_export->ai.BotGetChatMessage( args[1], VMA(2), args[3] ); + return 0; + case BOTLIB_AI_STRING_CONTAINS: + return botlib_export->ai.StringContains( VMA(1), VMA(2), args[3] ); + case BOTLIB_AI_FIND_MATCH: + return botlib_export->ai.BotFindMatch( VMA(1), VMA(2), args[3] ); + case BOTLIB_AI_MATCH_VARIABLE: + botlib_export->ai.BotMatchVariable( VMA(1), args[2], VMA(3), args[4] ); + return 0; + case BOTLIB_AI_UNIFY_WHITE_SPACES: + botlib_export->ai.UnifyWhiteSpaces( VMA(1) ); + return 0; + case BOTLIB_AI_REPLACE_SYNONYMS: + botlib_export->ai.BotReplaceSynonyms( VMA(1), args[2] ); + return 0; + case BOTLIB_AI_LOAD_CHAT_FILE: + return botlib_export->ai.BotLoadChatFile( args[1], VMA(2), VMA(3) ); + case BOTLIB_AI_SET_CHAT_GENDER: + botlib_export->ai.BotSetChatGender( args[1], args[2] ); + return 0; + case BOTLIB_AI_SET_CHAT_NAME: + botlib_export->ai.BotSetChatName( args[1], VMA(2), args[3] ); + return 0; + + case BOTLIB_AI_RESET_GOAL_STATE: + botlib_export->ai.BotResetGoalState( args[1] ); + return 0; + case BOTLIB_AI_RESET_AVOID_GOALS: + botlib_export->ai.BotResetAvoidGoals( args[1] ); + return 0; + case BOTLIB_AI_REMOVE_FROM_AVOID_GOALS: + botlib_export->ai.BotRemoveFromAvoidGoals( args[1], args[2] ); + return 0; + case BOTLIB_AI_PUSH_GOAL: + botlib_export->ai.BotPushGoal( args[1], VMA(2) ); + return 0; + case BOTLIB_AI_POP_GOAL: + botlib_export->ai.BotPopGoal( args[1] ); + return 0; + case BOTLIB_AI_EMPTY_GOAL_STACK: + botlib_export->ai.BotEmptyGoalStack( args[1] ); + return 0; + case BOTLIB_AI_DUMP_AVOID_GOALS: + botlib_export->ai.BotDumpAvoidGoals( args[1] ); + return 0; + case BOTLIB_AI_DUMP_GOAL_STACK: + botlib_export->ai.BotDumpGoalStack( args[1] ); + return 0; + case BOTLIB_AI_GOAL_NAME: + botlib_export->ai.BotGoalName( args[1], VMA(2), args[3] ); + return 0; + case BOTLIB_AI_GET_TOP_GOAL: + return botlib_export->ai.BotGetTopGoal( args[1], VMA(2) ); + case BOTLIB_AI_GET_SECOND_GOAL: + return botlib_export->ai.BotGetSecondGoal( args[1], VMA(2) ); + case BOTLIB_AI_CHOOSE_LTG_ITEM: + return botlib_export->ai.BotChooseLTGItem( args[1], VMA(2), VMA(3), args[4] ); + case BOTLIB_AI_CHOOSE_NBG_ITEM: + return botlib_export->ai.BotChooseNBGItem( args[1], VMA(2), VMA(3), args[4], VMA(5), VMF(6) ); + case BOTLIB_AI_TOUCHING_GOAL: + return botlib_export->ai.BotTouchingGoal( VMA(1), VMA(2) ); + case BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE: + return botlib_export->ai.BotItemGoalInVisButNotVisible( args[1], VMA(2), VMA(3), VMA(4) ); + case BOTLIB_AI_GET_LEVEL_ITEM_GOAL: + return botlib_export->ai.BotGetLevelItemGoal( args[1], VMA(2), VMA(3) ); + case BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL: + return botlib_export->ai.BotGetNextCampSpotGoal( args[1], VMA(2) ); + case BOTLIB_AI_GET_MAP_LOCATION_GOAL: + return botlib_export->ai.BotGetMapLocationGoal( VMA(1), VMA(2) ); + case BOTLIB_AI_AVOID_GOAL_TIME: + return FloatAsInt( botlib_export->ai.BotAvoidGoalTime( args[1], args[2] ) ); + case BOTLIB_AI_SET_AVOID_GOAL_TIME: + botlib_export->ai.BotSetAvoidGoalTime( args[1], args[2], VMF(3)); + return 0; + case BOTLIB_AI_INIT_LEVEL_ITEMS: + botlib_export->ai.BotInitLevelItems(); + return 0; + case BOTLIB_AI_UPDATE_ENTITY_ITEMS: + botlib_export->ai.BotUpdateEntityItems(); + return 0; + case BOTLIB_AI_LOAD_ITEM_WEIGHTS: + return botlib_export->ai.BotLoadItemWeights( args[1], VMA(2) ); + case BOTLIB_AI_FREE_ITEM_WEIGHTS: + botlib_export->ai.BotFreeItemWeights( args[1] ); + return 0; + case BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotInterbreedGoalFuzzyLogic( args[1], args[2], args[3] ); + return 0; + case BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotSaveGoalFuzzyLogic( args[1], VMA(2) ); + return 0; + case BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotMutateGoalFuzzyLogic( args[1], VMF(2) ); + return 0; + case BOTLIB_AI_ALLOC_GOAL_STATE: + return botlib_export->ai.BotAllocGoalState( args[1] ); + case BOTLIB_AI_FREE_GOAL_STATE: + botlib_export->ai.BotFreeGoalState( args[1] ); + return 0; + + case BOTLIB_AI_RESET_MOVE_STATE: + botlib_export->ai.BotResetMoveState( args[1] ); + return 0; + case BOTLIB_AI_ADD_AVOID_SPOT: + botlib_export->ai.BotAddAvoidSpot( args[1], VMA(2), VMF(3), args[4] ); + return 0; + case BOTLIB_AI_MOVE_TO_GOAL: + botlib_export->ai.BotMoveToGoal( VMA(1), args[2], VMA(3), args[4] ); + return 0; + case BOTLIB_AI_MOVE_IN_DIRECTION: + return botlib_export->ai.BotMoveInDirection( args[1], VMA(2), VMF(3), args[4] ); + case BOTLIB_AI_RESET_AVOID_REACH: + botlib_export->ai.BotResetAvoidReach( args[1] ); + return 0; + case BOTLIB_AI_RESET_LAST_AVOID_REACH: + botlib_export->ai.BotResetLastAvoidReach( args[1] ); + return 0; + case BOTLIB_AI_REACHABILITY_AREA: + return botlib_export->ai.BotReachabilityArea( VMA(1), args[2] ); + case BOTLIB_AI_MOVEMENT_VIEW_TARGET: + return botlib_export->ai.BotMovementViewTarget( args[1], VMA(2), args[3], VMF(4), VMA(5) ); + case BOTLIB_AI_PREDICT_VISIBLE_POSITION: + return botlib_export->ai.BotPredictVisiblePosition( VMA(1), args[2], VMA(3), args[4], VMA(5) ); + case BOTLIB_AI_ALLOC_MOVE_STATE: + return botlib_export->ai.BotAllocMoveState(); + case BOTLIB_AI_FREE_MOVE_STATE: + botlib_export->ai.BotFreeMoveState( args[1] ); + return 0; + case BOTLIB_AI_INIT_MOVE_STATE: + botlib_export->ai.BotInitMoveState( args[1], VMA(2) ); + return 0; + + case BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON: + return botlib_export->ai.BotChooseBestFightWeapon( args[1], VMA(2) ); + case BOTLIB_AI_GET_WEAPON_INFO: + botlib_export->ai.BotGetWeaponInfo( args[1], args[2], VMA(3) ); + return 0; + case BOTLIB_AI_LOAD_WEAPON_WEIGHTS: + return botlib_export->ai.BotLoadWeaponWeights( args[1], VMA(2) ); + case BOTLIB_AI_ALLOC_WEAPON_STATE: + return botlib_export->ai.BotAllocWeaponState(); + case BOTLIB_AI_FREE_WEAPON_STATE: + botlib_export->ai.BotFreeWeaponState( args[1] ); + return 0; + case BOTLIB_AI_RESET_WEAPON_STATE: + botlib_export->ai.BotResetWeaponState( args[1] ); + return 0; + + case BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION: + return botlib_export->ai.GeneticParentsAndChildSelection(args[1], VMA(2), VMA(3), VMA(4), VMA(5)); + + case TRAP_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + + case TRAP_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + + case TRAP_STRNCPY: + return (int)strncpy( VMA(1), VMA(2), args[3] ); + + case TRAP_SIN: + return FloatAsInt( sin( VMF(1) ) ); + + case TRAP_COS: + return FloatAsInt( cos( VMF(1) ) ); + + case TRAP_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + + case TRAP_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + + case TRAP_MATRIXMULTIPLY: + MatrixMultiply( VMA(1), VMA(2), VMA(3) ); + return 0; + + case TRAP_ANGLEVECTORS: + AngleVectors( VMA(1), VMA(2), VMA(3), VMA(4) ); + return 0; + + case TRAP_PERPENDICULARVECTOR: + PerpendicularVector( VMA(1), VMA(2) ); + return 0; + + case TRAP_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + + case TRAP_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + + + default: + Com_Error( ERR_DROP, "Bad game system trap: %i", args[0] ); + } + return -1; +} + +/* +=============== +SV_ShutdownGameProgs + +Called every time a map changes +=============== +*/ +void SV_ShutdownGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qfalse ); + VM_Free( gvm ); + gvm = NULL; +} + +/* +================== +SV_InitGameVM + +Called for both a full init and a restart +================== +*/ +static void SV_InitGameVM( qboolean restart ) { + int i; + + // start the entity parsing at the beginning + sv.entityParsePoint = CM_EntityString(); + + // clear all gentity pointers that might still be set from + // a previous level + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522 + // now done before GAME_INIT call + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + svs.clients[i].gentity = NULL; + } + + // use the current msec count for a random seed + // init for this gamestate + VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart ); +} + + + +/* +=================== +SV_RestartGameProgs + +Called on a map_restart, but not on a normal map change +=================== +*/ +void SV_RestartGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qtrue ); + + // do a restart instead of a free + gvm = VM_Restart( gvm ); + if ( !gvm ) { // bk001212 - as done below + Com_Error( ERR_FATAL, "VM_Restart on game failed" ); + } + + SV_InitGameVM( qtrue ); +} + + +/* +=============== +SV_InitGameProgs + +Called on a normal map change, not on a map_restart +=============== +*/ +void SV_InitGameProgs( void ) { + cvar_t *var; + //FIXME these are temp while I make bots run in vm + extern int bot_enable; + + var = Cvar_Get( "bot_enable", "1", CVAR_LATCH ); + if ( var ) { + bot_enable = var->integer; + } + else { + bot_enable = 0; + } + + // load the dll or bytecode + gvm = VM_Create( "qagame", SV_GameSystemCalls, Cvar_VariableValue( "vm_game" ) ); + if ( !gvm ) { + Com_Error( ERR_FATAL, "VM_Create on game failed" ); + } + + SV_InitGameVM( qfalse ); +} + + +/* +==================== +SV_GameCommand + +See if the current console command is claimed by the game +==================== +*/ +qboolean SV_GameCommand( void ) { + if ( sv.state != SS_GAME ) { + return qfalse; + } + + return VM_Call( gvm, GAME_CONSOLE_COMMAND ); +} + diff --git a/code/server/sv_init.c b/code/server/sv_init.c index b3d0c4d..e3a89a6 100755 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -1,695 +1,695 @@ -/* -=========================================================================== -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" - -/* -=============== -SV_SetConfigstring - -=============== -*/ -void SV_SetConfigstring (int index, const char *val) { - int len, i; - int maxChunkSize = MAX_STRING_CHARS - 24; - client_t *client; - - if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { - Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); - } - - if ( !val ) { - val = ""; - } - - // don't bother broadcasting an update if no change - if ( !strcmp( val, sv.configstrings[ index ] ) ) { - return; - } - - // change the string in sv - Z_Free( sv.configstrings[index] ); - sv.configstrings[index] = CopyString( val ); - - // send it to all the clients if we aren't - // spawning a new server - if ( sv.state == SS_GAME || sv.restarting ) { - - // send the data to all relevent clients - for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { - if ( client->state < CS_PRIMED ) { - continue; - } - // do not always send server info to all clients - if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { - continue; - } - - len = strlen( val ); - if( len >= maxChunkSize ) { - int sent = 0; - int remaining = len; - char *cmd; - char buf[MAX_STRING_CHARS]; - - while (remaining > 0 ) { - if ( sent == 0 ) { - cmd = "bcs0"; - } - else if( remaining < maxChunkSize ) { - cmd = "bcs2"; - } - else { - cmd = "bcs1"; - } - Q_strncpyz( buf, &val[sent], maxChunkSize ); - - SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf ); - - sent += (maxChunkSize - 1); - remaining -= (maxChunkSize - 1); - } - } else { - // standard cs, just send it - SV_SendServerCommand( client, "cs %i \"%s\"\n", index, val ); - } - } - } -} - - - -/* -=============== -SV_GetConfigstring - -=============== -*/ -void SV_GetConfigstring( int index, char *buffer, int bufferSize ) { - if ( bufferSize < 1 ) { - Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize ); - } - if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { - Com_Error (ERR_DROP, "SV_GetConfigstring: bad index %i\n", index); - } - if ( !sv.configstrings[index] ) { - buffer[0] = 0; - return; - } - - Q_strncpyz( buffer, sv.configstrings[index], bufferSize ); -} - - -/* -=============== -SV_SetUserinfo - -=============== -*/ -void SV_SetUserinfo( int index, const char *val ) { - if ( index < 0 || index >= sv_maxclients->integer ) { - Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i\n", index); - } - - if ( !val ) { - val = ""; - } - - Q_strncpyz( svs.clients[index].userinfo, val, sizeof( svs.clients[ index ].userinfo ) ); - Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof(svs.clients[index].name) ); -} - - - -/* -=============== -SV_GetUserinfo - -=============== -*/ -void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { - if ( bufferSize < 1 ) { - Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); - } - if ( index < 0 || index >= sv_maxclients->integer ) { - Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i\n", index); - } - Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); -} - - -/* -================ -SV_CreateBaseline - -Entity baselines are used to compress non-delta messages -to the clients -- only the fields that differ from the -baseline will be transmitted -================ -*/ -void SV_CreateBaseline( void ) { - sharedEntity_t *svent; - int entnum; - - for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { - svent = SV_GentityNum(entnum); - if (!svent->r.linked) { - continue; - } - svent->s.number = entnum; - - // - // take current state as baseline - // - sv.svEntities[entnum].baseline = svent->s; - } -} - - -/* -=============== -SV_BoundMaxClients - -=============== -*/ -void SV_BoundMaxClients( int minimum ) { - // get the current maxclients value - Cvar_Get( "sv_maxclients", "8", 0 ); - - sv_maxclients->modified = qfalse; - - if ( sv_maxclients->integer < minimum ) { - Cvar_Set( "sv_maxclients", va("%i", minimum) ); - } else if ( sv_maxclients->integer > MAX_CLIENTS ) { - Cvar_Set( "sv_maxclients", va("%i", MAX_CLIENTS) ); - } -} - - -/* -=============== -SV_Startup - -Called when a host starts a map when it wasn't running -one before. Successive map or map_restart commands will -NOT cause this to be called, unless the game is exited to -the menu system first. -=============== -*/ -void SV_Startup( void ) { - if ( svs.initialized ) { - Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); - } - SV_BoundMaxClients( 1 ); - - svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer ); - if ( com_dedicated->integer ) { - svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; - } else { - // we don't need nearly as many when playing locally - svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; - } - svs.initialized = qtrue; - - Cvar_Set( "sv_running", "1" ); -} - - -/* -================== -SV_ChangeMaxClients -================== -*/ -void SV_ChangeMaxClients( void ) { - int oldMaxClients; - int i; - client_t *oldClients; - int count; - - // get the highest client number in use - count = 0; - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { - if ( svs.clients[i].state >= CS_CONNECTED ) { - if (i > count) - count = i; - } - } - count++; - - oldMaxClients = sv_maxclients->integer; - // never go below the highest client number in use - SV_BoundMaxClients( count ); - // if still the same - if ( sv_maxclients->integer == oldMaxClients ) { - return; - } - - oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); - // copy the clients to hunk memory - for ( i = 0 ; i < count ; i++ ) { - if ( svs.clients[i].state >= CS_CONNECTED ) { - oldClients[i] = svs.clients[i]; - } - else { - Com_Memset(&oldClients[i], 0, sizeof(client_t)); - } - } - - // free old clients arrays - Z_Free( svs.clients ); - - // allocate new clients - svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) ); - Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); - - // copy the clients over - for ( i = 0 ; i < count ; i++ ) { - if ( oldClients[i].state >= CS_CONNECTED ) { - svs.clients[i] = oldClients[i]; - } - } - - // free the old clients on the hunk - Hunk_FreeTempMemory( oldClients ); - - // allocate new snapshot entities - if ( com_dedicated->integer ) { - svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; - } else { - // we don't need nearly as many when playing locally - svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; - } -} - -/* -================ -SV_ClearServer -================ -*/ -void SV_ClearServer(void) { - int i; - - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - if ( sv.configstrings[i] ) { - Z_Free( sv.configstrings[i] ); - } - } - Com_Memset (&sv, 0, sizeof(sv)); -} - -/* -================ -SV_TouchCGame - - touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 -================ -*/ -void SV_TouchCGame(void) { - fileHandle_t f; - char filename[MAX_QPATH]; - - Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", "cgame" ); - FS_FOpenFileRead( filename, &f, qfalse ); - if ( f ) { - FS_FCloseFile( f ); - } -} - -/* -================ -SV_SpawnServer - -Change the server to a new map, taking all connected -clients along with it. -This is NOT called for map_restart -================ -*/ -void SV_SpawnServer( char *server, qboolean killBots ) { - int i; - int checksum; - qboolean isBot; - char systemInfo[16384]; - const char *p; - - // shut down the existing game if it is running - SV_ShutdownGameProgs(); - - Com_Printf ("------ Server Initialization ------\n"); - Com_Printf ("Server: %s\n",server); - - // if not running a dedicated server CL_MapLoading will connect the client to the server - // also print some status stuff - CL_MapLoading(); - - // make sure all the client stuff is unloaded - CL_ShutdownAll(); - - // clear the whole hunk because we're (re)loading the server - Hunk_Clear(); - - // clear collision map data - CM_ClearMap(); - - // init client structures and svs.numSnapshotEntities - if ( !Cvar_VariableValue("sv_running") ) { - SV_Startup(); - } else { - // check for maxclients change - if ( sv_maxclients->modified ) { - SV_ChangeMaxClients(); - } - } - - // clear pak references - FS_ClearPakReferences(0); - - // allocate the snapshot entities on the hunk - svs.snapshotEntities = Hunk_Alloc( sizeof(entityState_t)*svs.numSnapshotEntities, h_high ); - svs.nextSnapshotEntities = 0; - - // toggle the server bit so clients can detect that a - // server has changed - svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; - - // set nextmap to the same map, but it may be overriden - // by the game startup or another console command - Cvar_Set( "nextmap", "map_restart 0"); -// Cvar_Set( "nextmap", va("map %s", server) ); - - // wipe the entire per-level structure - SV_ClearServer(); - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - sv.configstrings[i] = CopyString(""); - } - - // make sure we are not paused - Cvar_Set("cl_paused", "0"); - - // get a new checksum feed and restart the file system - srand(Com_Milliseconds()); - sv.checksumFeed = ( ((int) rand() << 16) ^ rand() ) ^ Com_Milliseconds(); - FS_Restart( sv.checksumFeed ); - - CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); - - // set serverinfo visible name - Cvar_Set( "mapname", server ); - - Cvar_Set( "sv_mapChecksum", va("%i",checksum) ); - - // serverid should be different each time - sv.serverId = com_frameTime; - sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe - sv.checksumFeedServerId = sv.serverId; - Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); - - // clear physics interaction links - SV_ClearWorld (); - - // media configstring setting should be done during - // the loading stage, so connected clients don't have - // to load during actual gameplay - sv.state = SS_LOADING; - - // load and spawn all other entities - SV_InitGameProgs(); - - // don't allow a map_restart if game is modified - sv_gametype->modified = qfalse; - - // run a few frames to allow everything to settle - for ( i = 0 ;i < 3 ; i++ ) { - VM_Call( gvm, GAME_RUN_FRAME, svs.time ); - SV_BotFrame( svs.time ); - svs.time += 100; - } - - // create a baseline for more efficient communications - SV_CreateBaseline (); - - for (i=0 ; iinteger ; i++) { - // send the new gamestate to all connected clients - if (svs.clients[i].state >= CS_CONNECTED) { - char *denied; - - if ( svs.clients[i].netchan.remoteAddress.type == NA_BOT ) { - if ( killBots ) { - SV_DropClient( &svs.clients[i], "" ); - continue; - } - isBot = qtrue; - } - else { - isBot = qfalse; - } - - // connect the client again - denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); // firstTime = qfalse - if ( denied ) { - // this generally shouldn't happen, because the client - // was connected before the level change - SV_DropClient( &svs.clients[i], denied ); - } else { - if( !isBot ) { - // when we get the next packet from a connected client, - // the new gamestate will be sent - svs.clients[i].state = CS_CONNECTED; - } - else { - client_t *client; - sharedEntity_t *ent; - - client = &svs.clients[i]; - client->state = CS_ACTIVE; - ent = SV_GentityNum( i ); - ent->s.number = i; - client->gentity = ent; - - client->deltaMessage = -1; - client->nextSnapshotTime = svs.time; // generate a snapshot immediately - - VM_Call( gvm, GAME_CLIENT_BEGIN, i ); - } - } - } - } - - // run another frame to allow things to look at all the players - VM_Call( gvm, GAME_RUN_FRAME, svs.time ); - SV_BotFrame( svs.time ); - svs.time += 100; - - if ( sv_pure->integer ) { - // the server sends these to the clients so they will only - // load pk3s also loaded at the server - p = FS_LoadedPakChecksums(); - Cvar_Set( "sv_paks", p ); - if (strlen(p) == 0) { - Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" ); - } - p = FS_LoadedPakNames(); - Cvar_Set( "sv_pakNames", p ); - - // if a dedicated pure server we need to touch the cgame because it could be in a - // seperate pk3 file and the client will need to load the latest cgame.qvm - if ( com_dedicated->integer ) { - SV_TouchCGame(); - } - } - else { - Cvar_Set( "sv_paks", "" ); - Cvar_Set( "sv_pakNames", "" ); - } - // the server sends these to the clients so they can figure - // out which pk3s should be auto-downloaded - p = FS_ReferencedPakChecksums(); - Cvar_Set( "sv_referencedPaks", p ); - p = FS_ReferencedPakNames(); - Cvar_Set( "sv_referencedPakNames", p ); - - // save systeminfo and serverinfo strings - Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) ); - cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; - SV_SetConfigstring( CS_SYSTEMINFO, systemInfo ); - - SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); - cvar_modifiedFlags &= ~CVAR_SERVERINFO; - - // any media configstring setting now should issue a warning - // and any configstring changes should be reliably transmitted - // to all clients - sv.state = SS_GAME; - - // send a heartbeat now so the master will get up to date info - SV_Heartbeat_f(); - - Hunk_SetMark(); - - Com_Printf ("-----------------------------------\n"); -} - -/* -=============== -SV_Init - -Only called at main exe startup, not for each game -=============== -*/ -void SV_BotInitBotLib(void); - -void SV_Init (void) { - SV_AddOperatorCommands (); - - // serverinfo vars - Cvar_Get ("dmflags", "0", CVAR_SERVERINFO); - Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO); - Cvar_Get ("timelimit", "0", CVAR_SERVERINFO); - sv_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH ); - Cvar_Get ("sv_keywords", "", CVAR_SERVERINFO); - Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); - sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); - sv_privateClients = Cvar_Get ("sv_privateClients", "0", CVAR_SERVERINFO); - sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE ); - sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); - - sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); - sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); - sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); - sv_floodProtect = Cvar_Get ("sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO ); - - // systeminfo - Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM ); - sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); -#ifndef DLL_ONLY // bk010216 - for DLL-only servers - sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); -#else - sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO | CVAR_INIT | CVAR_ROM ); -#endif - Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); - Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); - Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); - Cvar_Get ("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); - - // server vars - sv_rconPassword = Cvar_Get ("rconPassword", "", CVAR_TEMP ); - sv_privatePassword = Cvar_Get ("sv_privatePassword", "", CVAR_TEMP ); - sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP ); - sv_timeout = Cvar_Get ("sv_timeout", "200", CVAR_TEMP ); - sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); - Cvar_Get ("nextmap", "", CVAR_TEMP ); - - sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); - sv_master[0] = Cvar_Get ("sv_master1", MASTER_SERVER_NAME, 0 ); - sv_master[1] = Cvar_Get ("sv_master2", "", CVAR_ARCHIVE ); - sv_master[2] = Cvar_Get ("sv_master3", "", CVAR_ARCHIVE ); - sv_master[3] = Cvar_Get ("sv_master4", "", CVAR_ARCHIVE ); - sv_master[4] = Cvar_Get ("sv_master5", "", CVAR_ARCHIVE ); - sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0); - sv_showloss = Cvar_Get ("sv_showloss", "0", 0); - sv_padPackets = Cvar_Get ("sv_padPackets", "0", 0); - sv_killserver = Cvar_Get ("sv_killserver", "0", 0); - sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM); - sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE ); - sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE ); - - // initialize bot cvars so they are listed and can be set before loading the botlib - SV_BotInitCvars(); - - // init the botlib here because we need the pre-compiler in the UI - SV_BotInitBotLib(); -} - - -/* -================== -SV_FinalMessage - -Used by SV_Shutdown to send a final message to all -connected clients before the server goes down. The messages are sent immediately, -not just stuck on the outgoing message list, because the server is going -to totally exit after returning from this function. -================== -*/ -void SV_FinalMessage( char *message ) { - int i, j; - client_t *cl; - - // send it twice, ignoring rate - for ( j = 0 ; j < 2 ; j++ ) { - for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { - if (cl->state >= CS_CONNECTED) { - // don't send a disconnect to a local client - if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { - SV_SendServerCommand( cl, "print \"%s\"", message ); - SV_SendServerCommand( cl, "disconnect" ); - } - // force a snapshot to be sent - cl->nextSnapshotTime = -1; - SV_SendClientSnapshot( cl ); - } - } - } -} - - -/* -================ -SV_Shutdown - -Called when each game quits, -before Sys_Quit or Sys_Error -================ -*/ -void SV_Shutdown( char *finalmsg ) { - if ( !com_sv_running || !com_sv_running->integer ) { - return; - } - - Com_Printf( "----- Server Shutdown -----\n" ); - - if ( svs.clients && !com_errorEntered ) { - SV_FinalMessage( finalmsg ); - } - - SV_RemoveOperatorCommands(); - SV_MasterShutdown(); - SV_ShutdownGameProgs(); - - // free current level - SV_ClearServer(); - - // free server static data - if ( svs.clients ) { - Z_Free( svs.clients ); - } - Com_Memset( &svs, 0, sizeof( svs ) ); - - Cvar_Set( "sv_running", "0" ); - Cvar_Set("ui_singlePlayerActive", "0"); - - Com_Printf( "---------------------------\n" ); - - // disconnect any local clients - CL_Disconnect( qfalse ); -} - +/* +=========================================================================== +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" + +/* +=============== +SV_SetConfigstring + +=============== +*/ +void SV_SetConfigstring (int index, const char *val) { + int len, i; + int maxChunkSize = MAX_STRING_CHARS - 24; + client_t *client; + + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); + } + + if ( !val ) { + val = ""; + } + + // don't bother broadcasting an update if no change + if ( !strcmp( val, sv.configstrings[ index ] ) ) { + return; + } + + // change the string in sv + Z_Free( sv.configstrings[index] ); + sv.configstrings[index] = CopyString( val ); + + // send it to all the clients if we aren't + // spawning a new server + if ( sv.state == SS_GAME || sv.restarting ) { + + // send the data to all relevent clients + for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + if ( client->state < CS_PRIMED ) { + continue; + } + // do not always send server info to all clients + if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + continue; + } + + len = strlen( val ); + if( len >= maxChunkSize ) { + int sent = 0; + int remaining = len; + char *cmd; + char buf[MAX_STRING_CHARS]; + + while (remaining > 0 ) { + if ( sent == 0 ) { + cmd = "bcs0"; + } + else if( remaining < maxChunkSize ) { + cmd = "bcs2"; + } + else { + cmd = "bcs1"; + } + Q_strncpyz( buf, &val[sent], maxChunkSize ); + + SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf ); + + sent += (maxChunkSize - 1); + remaining -= (maxChunkSize - 1); + } + } else { + // standard cs, just send it + SV_SendServerCommand( client, "cs %i \"%s\"\n", index, val ); + } + } + } +} + + + +/* +=============== +SV_GetConfigstring + +=============== +*/ +void SV_GetConfigstring( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error (ERR_DROP, "SV_GetConfigstring: bad index %i\n", index); + } + if ( !sv.configstrings[index] ) { + buffer[0] = 0; + return; + } + + Q_strncpyz( buffer, sv.configstrings[index], bufferSize ); +} + + +/* +=============== +SV_SetUserinfo + +=============== +*/ +void SV_SetUserinfo( int index, const char *val ) { + if ( index < 0 || index >= sv_maxclients->integer ) { + Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i\n", index); + } + + if ( !val ) { + val = ""; + } + + Q_strncpyz( svs.clients[index].userinfo, val, sizeof( svs.clients[ index ].userinfo ) ); + Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof(svs.clients[index].name) ); +} + + + +/* +=============== +SV_GetUserinfo + +=============== +*/ +void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= sv_maxclients->integer ) { + Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i\n", index); + } + Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); +} + + +/* +================ +SV_CreateBaseline + +Entity baselines are used to compress non-delta messages +to the clients -- only the fields that differ from the +baseline will be transmitted +================ +*/ +void SV_CreateBaseline( void ) { + sharedEntity_t *svent; + int entnum; + + for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { + svent = SV_GentityNum(entnum); + if (!svent->r.linked) { + continue; + } + svent->s.number = entnum; + + // + // take current state as baseline + // + sv.svEntities[entnum].baseline = svent->s; + } +} + + +/* +=============== +SV_BoundMaxClients + +=============== +*/ +void SV_BoundMaxClients( int minimum ) { + // get the current maxclients value + Cvar_Get( "sv_maxclients", "8", 0 ); + + sv_maxclients->modified = qfalse; + + if ( sv_maxclients->integer < minimum ) { + Cvar_Set( "sv_maxclients", va("%i", minimum) ); + } else if ( sv_maxclients->integer > MAX_CLIENTS ) { + Cvar_Set( "sv_maxclients", va("%i", MAX_CLIENTS) ); + } +} + + +/* +=============== +SV_Startup + +Called when a host starts a map when it wasn't running +one before. Successive map or map_restart commands will +NOT cause this to be called, unless the game is exited to +the menu system first. +=============== +*/ +void SV_Startup( void ) { + if ( svs.initialized ) { + Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); + } + SV_BoundMaxClients( 1 ); + + svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer ); + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } + svs.initialized = qtrue; + + Cvar_Set( "sv_running", "1" ); +} + + +/* +================== +SV_ChangeMaxClients +================== +*/ +void SV_ChangeMaxClients( void ) { + int oldMaxClients; + int i; + client_t *oldClients; + int count; + + // get the highest client number in use + count = 0; + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + if (i > count) + count = i; + } + } + count++; + + oldMaxClients = sv_maxclients->integer; + // never go below the highest client number in use + SV_BoundMaxClients( count ); + // if still the same + if ( sv_maxclients->integer == oldMaxClients ) { + return; + } + + oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); + // copy the clients to hunk memory + for ( i = 0 ; i < count ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + oldClients[i] = svs.clients[i]; + } + else { + Com_Memset(&oldClients[i], 0, sizeof(client_t)); + } + } + + // free old clients arrays + Z_Free( svs.clients ); + + // allocate new clients + svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) ); + Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); + + // copy the clients over + for ( i = 0 ; i < count ; i++ ) { + if ( oldClients[i].state >= CS_CONNECTED ) { + svs.clients[i] = oldClients[i]; + } + } + + // free the old clients on the hunk + Hunk_FreeTempMemory( oldClients ); + + // allocate new snapshot entities + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } +} + +/* +================ +SV_ClearServer +================ +*/ +void SV_ClearServer(void) { + int i; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i] ) { + Z_Free( sv.configstrings[i] ); + } + } + Com_Memset (&sv, 0, sizeof(sv)); +} + +/* +================ +SV_TouchCGame + + touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 +================ +*/ +void SV_TouchCGame(void) { + fileHandle_t f; + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", "cgame" ); + FS_FOpenFileRead( filename, &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +This is NOT called for map_restart +================ +*/ +void SV_SpawnServer( char *server, qboolean killBots ) { + int i; + int checksum; + qboolean isBot; + char systemInfo[16384]; + const char *p; + + // shut down the existing game if it is running + SV_ShutdownGameProgs(); + + Com_Printf ("------ Server Initialization ------\n"); + Com_Printf ("Server: %s\n",server); + + // if not running a dedicated server CL_MapLoading will connect the client to the server + // also print some status stuff + CL_MapLoading(); + + // make sure all the client stuff is unloaded + CL_ShutdownAll(); + + // clear the whole hunk because we're (re)loading the server + Hunk_Clear(); + + // clear collision map data + CM_ClearMap(); + + // init client structures and svs.numSnapshotEntities + if ( !Cvar_VariableValue("sv_running") ) { + SV_Startup(); + } else { + // check for maxclients change + if ( sv_maxclients->modified ) { + SV_ChangeMaxClients(); + } + } + + // clear pak references + FS_ClearPakReferences(0); + + // allocate the snapshot entities on the hunk + svs.snapshotEntities = Hunk_Alloc( sizeof(entityState_t)*svs.numSnapshotEntities, h_high ); + svs.nextSnapshotEntities = 0; + + // toggle the server bit so clients can detect that a + // server has changed + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // set nextmap to the same map, but it may be overriden + // by the game startup or another console command + Cvar_Set( "nextmap", "map_restart 0"); +// Cvar_Set( "nextmap", va("map %s", server) ); + + // wipe the entire per-level structure + SV_ClearServer(); + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.configstrings[i] = CopyString(""); + } + + // make sure we are not paused + Cvar_Set("cl_paused", "0"); + + // get a new checksum feed and restart the file system + srand(Com_Milliseconds()); + sv.checksumFeed = ( ((int) rand() << 16) ^ rand() ) ^ Com_Milliseconds(); + FS_Restart( sv.checksumFeed ); + + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); + + // set serverinfo visible name + Cvar_Set( "mapname", server ); + + Cvar_Set( "sv_mapChecksum", va("%i",checksum) ); + + // serverid should be different each time + sv.serverId = com_frameTime; + sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe + sv.checksumFeedServerId = sv.serverId; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // clear physics interaction links + SV_ClearWorld (); + + // media configstring setting should be done during + // the loading stage, so connected clients don't have + // to load during actual gameplay + sv.state = SS_LOADING; + + // load and spawn all other entities + SV_InitGameProgs(); + + // don't allow a map_restart if game is modified + sv_gametype->modified = qfalse; + + // run a few frames to allow everything to settle + for ( i = 0 ;i < 3 ; i++ ) { + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + SV_BotFrame( svs.time ); + svs.time += 100; + } + + // create a baseline for more efficient communications + SV_CreateBaseline (); + + for (i=0 ; iinteger ; i++) { + // send the new gamestate to all connected clients + if (svs.clients[i].state >= CS_CONNECTED) { + char *denied; + + if ( svs.clients[i].netchan.remoteAddress.type == NA_BOT ) { + if ( killBots ) { + SV_DropClient( &svs.clients[i], "" ); + continue; + } + isBot = qtrue; + } + else { + isBot = qfalse; + } + + // connect the client again + denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); // firstTime = qfalse + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( &svs.clients[i], denied ); + } else { + if( !isBot ) { + // when we get the next packet from a connected client, + // the new gamestate will be sent + svs.clients[i].state = CS_CONNECTED; + } + else { + client_t *client; + sharedEntity_t *ent; + + client = &svs.clients[i]; + client->state = CS_ACTIVE; + ent = SV_GentityNum( i ); + ent->s.number = i; + client->gentity = ent; + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + + VM_Call( gvm, GAME_CLIENT_BEGIN, i ); + } + } + } + } + + // run another frame to allow things to look at all the players + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + SV_BotFrame( svs.time ); + svs.time += 100; + + if ( sv_pure->integer ) { + // the server sends these to the clients so they will only + // load pk3s also loaded at the server + p = FS_LoadedPakChecksums(); + Cvar_Set( "sv_paks", p ); + if (strlen(p) == 0) { + Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" ); + } + p = FS_LoadedPakNames(); + Cvar_Set( "sv_pakNames", p ); + + // if a dedicated pure server we need to touch the cgame because it could be in a + // seperate pk3 file and the client will need to load the latest cgame.qvm + if ( com_dedicated->integer ) { + SV_TouchCGame(); + } + } + else { + Cvar_Set( "sv_paks", "" ); + Cvar_Set( "sv_pakNames", "" ); + } + // the server sends these to the clients so they can figure + // out which pk3s should be auto-downloaded + p = FS_ReferencedPakChecksums(); + Cvar_Set( "sv_referencedPaks", p ); + p = FS_ReferencedPakNames(); + Cvar_Set( "sv_referencedPakNames", p ); + + // save systeminfo and serverinfo strings + Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + SV_SetConfigstring( CS_SYSTEMINFO, systemInfo ); + + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + + // any media configstring setting now should issue a warning + // and any configstring changes should be reliably transmitted + // to all clients + sv.state = SS_GAME; + + // send a heartbeat now so the master will get up to date info + SV_Heartbeat_f(); + + Hunk_SetMark(); + + Com_Printf ("-----------------------------------\n"); +} + +/* +=============== +SV_Init + +Only called at main exe startup, not for each game +=============== +*/ +void SV_BotInitBotLib(void); + +void SV_Init (void) { + SV_AddOperatorCommands (); + + // serverinfo vars + Cvar_Get ("dmflags", "0", CVAR_SERVERINFO); + Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO); + Cvar_Get ("timelimit", "0", CVAR_SERVERINFO); + sv_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH ); + Cvar_Get ("sv_keywords", "", CVAR_SERVERINFO); + Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); + sv_privateClients = Cvar_Get ("sv_privateClients", "0", CVAR_SERVERINFO); + sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE ); + sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); + + sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_floodProtect = Cvar_Get ("sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO ); + + // systeminfo + Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM ); + sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); +#ifndef DLL_ONLY // bk010216 - for DLL-only servers + sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); +#else + sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO | CVAR_INIT | CVAR_ROM ); +#endif + Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + + // server vars + sv_rconPassword = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + sv_privatePassword = Cvar_Get ("sv_privatePassword", "", CVAR_TEMP ); + sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP ); + sv_timeout = Cvar_Get ("sv_timeout", "200", CVAR_TEMP ); + sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); + Cvar_Get ("nextmap", "", CVAR_TEMP ); + + sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); + sv_master[0] = Cvar_Get ("sv_master1", MASTER_SERVER_NAME, 0 ); + sv_master[1] = Cvar_Get ("sv_master2", "", CVAR_ARCHIVE ); + sv_master[2] = Cvar_Get ("sv_master3", "", CVAR_ARCHIVE ); + sv_master[3] = Cvar_Get ("sv_master4", "", CVAR_ARCHIVE ); + sv_master[4] = Cvar_Get ("sv_master5", "", CVAR_ARCHIVE ); + sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0); + sv_showloss = Cvar_Get ("sv_showloss", "0", 0); + sv_padPackets = Cvar_Get ("sv_padPackets", "0", 0); + sv_killserver = Cvar_Get ("sv_killserver", "0", 0); + sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM); + sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE ); + sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE ); + + // initialize bot cvars so they are listed and can be set before loading the botlib + SV_BotInitCvars(); + + // init the botlib here because we need the pre-compiler in the UI + SV_BotInitBotLib(); +} + + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown to send a final message to all +connected clients before the server goes down. The messages are sent immediately, +not just stuck on the outgoing message list, because the server is going +to totally exit after returning from this function. +================== +*/ +void SV_FinalMessage( char *message ) { + int i, j; + client_t *cl; + + // send it twice, ignoring rate + for ( j = 0 ; j < 2 ; j++ ) { + for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { + if (cl->state >= CS_CONNECTED) { + // don't send a disconnect to a local client + if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { + SV_SendServerCommand( cl, "print \"%s\"", message ); + SV_SendServerCommand( cl, "disconnect" ); + } + // force a snapshot to be sent + cl->nextSnapshotTime = -1; + SV_SendClientSnapshot( cl ); + } + } + } +} + + +/* +================ +SV_Shutdown + +Called when each game quits, +before Sys_Quit or Sys_Error +================ +*/ +void SV_Shutdown( char *finalmsg ) { + if ( !com_sv_running || !com_sv_running->integer ) { + return; + } + + Com_Printf( "----- Server Shutdown -----\n" ); + + if ( svs.clients && !com_errorEntered ) { + SV_FinalMessage( finalmsg ); + } + + SV_RemoveOperatorCommands(); + SV_MasterShutdown(); + SV_ShutdownGameProgs(); + + // free current level + SV_ClearServer(); + + // free server static data + if ( svs.clients ) { + Z_Free( svs.clients ); + } + Com_Memset( &svs, 0, sizeof( svs ) ); + + Cvar_Set( "sv_running", "0" ); + Cvar_Set("ui_singlePlayerActive", "0"); + + Com_Printf( "---------------------------\n" ); + + // disconnect any local clients + CL_Disconnect( qfalse ); +} + diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 98c74ce..d5b7353 100755 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -1,855 +1,855 @@ -/* -=========================================================================== -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 -Redirect all printfs -=============== -*/ -void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { - qboolean valid; - unsigned int time; - char remaining[1024]; - // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. - // (OOB messages are the bottleneck here) -#define SV_OUTPUTBUF_LENGTH (1024 - 16) - char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; - static unsigned int lasttime = 0; - char *cmd_aux; - - // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534 - time = Com_Milliseconds(); - if (time<(lasttime+500)) { - return; - } - lasttime = time; - - if ( !strlen( sv_rconPassword->string ) || - strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { - valid = qfalse; - Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); - } else { - valid = qtrue; - Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); - } - - // start redirecting all print outputs to the packet - svs.redirectAddress = from; - Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect); - - if ( !strlen( sv_rconPassword->string ) ) { - Com_Printf ("No rconpassword set on the server.\n"); - } else if ( !valid ) { - Com_Printf ("Bad rconpassword.\n"); - } else { - remaining[0] = 0; - - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 - // get the command directly, "rcon " to avoid quoting issues - // extract the command by walking - // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing - cmd_aux = Cmd_Cmd(); - cmd_aux+=4; - while(cmd_aux[0]==' ') - cmd_aux++; - while(cmd_aux[0] && cmd_aux[0]!=' ') // password - cmd_aux++; - while(cmd_aux[0]==' ') - cmd_aux++; - - Q_strcat( remaining, sizeof(remaining), cmd_aux); - - Cmd_ExecuteString (remaining); - - } - - Com_EndRedirect (); -} - -/* -================= -SV_ConnectionlessPacket - -A connectionless packet has four leading 0xff -characters to distinguish it from a game channel. -Clients that are in the game can still send -connectionless packets. -================= -*/ -void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { - char *s; - char *c; - - MSG_BeginReadingOOB( msg ); - MSG_ReadLong( msg ); // skip the -1 marker - - if (!Q_strncmp("connect", &msg->data[4], 7)) { - Huff_Decompress(msg, 12); - } - - s = MSG_ReadStringLine( msg ); - Cmd_TokenizeString( s ); - - c = Cmd_Argv(0); - Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); - - if (!Q_stricmp(c, "getstatus")) { - SVC_Status( from ); - } else if (!Q_stricmp(c, "getinfo")) { - SVC_Info( from ); - } else if (!Q_stricmp(c, "getchallenge")) { - SV_GetChallenge( from ); - } else if (!Q_stricmp(c, "connect")) { - SV_DirectConnect( from ); - } else if (!Q_stricmp(c, "ipAuthorize")) { - SV_AuthorizeIpPacket( from ); - } else if (!Q_stricmp(c, "rcon")) { - SVC_RemoteCommand( from, msg ); - } else if (!Q_stricmp(c, "disconnect")) { - // if a client starts up a local server, we may see some spurious - // server disconnect messages when their new server sees our final - // sequenced messages to the old client - } else { - Com_DPrintf ("bad connectionless packet from %s:\n%s\n" - , NET_AdrToString (from), s); - } -} - -//============================================================================ - -/* -================= -SV_ReadPackets -================= -*/ -void SV_PacketEvent( netadr_t from, msg_t *msg ) { - int i; - client_t *cl; - int qport; - - // check for connectionless packet (0xffffffff) first - if ( msg->cursize >= 4 && *(int *)msg->data == -1) { - SV_ConnectionlessPacket( from, msg ); - return; - } - - // read the qport out of the message so we can fix up - // stupid address translating routers - MSG_BeginReadingOOB( msg ); - MSG_ReadLong( msg ); // sequence number - qport = MSG_ReadShort( msg ) & 0xffff; - - // find which client the message is from - for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - if (cl->state == CS_FREE) { - continue; - } - if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { - continue; - } - // it is possible to have multiple clients from a single IP - // address, so they are differentiated by the qport variable - if (cl->netchan.qport != qport) { - continue; - } - - // the IP port can't be used to differentiate them, because - // some address translating routers periodically change UDP - // port assignments - if (cl->netchan.remoteAddress.port != from.port) { - Com_Printf( "SV_PacketEvent: fixing up a translated port\n" ); - cl->netchan.remoteAddress.port = from.port; - } - - // make sure it is a valid, in sequence packet - if (SV_Netchan_Process(cl, msg)) { - // zombie clients still need to do the Netchan_Process - // to make sure they don't need to retransmit the final - // reliable message, but they don't do any other processing - if (cl->state != CS_ZOMBIE) { - cl->lastPacketTime = svs.time; // don't timeout - SV_ExecuteClientMessage( cl, msg ); - } - } - return; - } - - // if we received a sequenced packet from an address we don't recognize, - // send an out of band disconnect packet to it - NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); -} - - -/* -=================== -SV_CalcPings - -Updates the cl->ping variables -=================== -*/ -void SV_CalcPings( void ) { - int i, j; - client_t *cl; - int total, count; - int delta; - playerState_t *ps; - - for (i=0 ; i < sv_maxclients->integer ; i++) { - cl = &svs.clients[i]; - if ( cl->state != CS_ACTIVE ) { - cl->ping = 999; - continue; - } - if ( !cl->gentity ) { - cl->ping = 999; - continue; - } - if ( cl->gentity->r.svFlags & SVF_BOT ) { - cl->ping = 0; - continue; - } - - total = 0; - count = 0; - for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { - if ( cl->frames[j].messageAcked <= 0 ) { - continue; - } - delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; - count++; - total += delta; - } - if (!count) { - cl->ping = 999; - } else { - cl->ping = total/count; - if ( cl->ping > 999 ) { - cl->ping = 999; - } - } - - // let the game dll know about the ping - ps = SV_GameClientNum( i ); - ps->ping = cl->ping; - } -} - -/* -================== -SV_CheckTimeouts - -If a packet has not been received from a client for timeout->integer -seconds, drop the conneciton. Server time is used instead of -realtime to avoid dropping the local client while debugging. - -When a client is normally dropped, the client_t goes into a zombie state -for a few seconds to make sure any final reliable message gets resent -if necessary -================== -*/ -void SV_CheckTimeouts( void ) { - int i; - client_t *cl; - int droppoint; - int zombiepoint; - - droppoint = svs.time - 1000 * sv_timeout->integer; - zombiepoint = svs.time - 1000 * sv_zombietime->integer; - - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - // message times may be wrong across a changelevel - if (cl->lastPacketTime > svs.time) { - cl->lastPacketTime = svs.time; - } - - if (cl->state == CS_ZOMBIE - && cl->lastPacketTime < zombiepoint) { - // using the client id cause the cl->name is empty at this point - Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i ); - cl->state = CS_FREE; // can now be reused - continue; - } - if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) { - // wait several frames so a debugger session doesn't - // cause a timeout - if ( ++cl->timeoutCount > 5 ) { - SV_DropClient (cl, "timed out"); - cl->state = CS_FREE; // don't bother with zombie state - } - } else { - cl->timeoutCount = 0; - } - } -} - - -/* -================== -SV_CheckPaused -================== -*/ -qboolean SV_CheckPaused( void ) { - int count; - client_t *cl; - int i; - - if ( !cl_paused->integer ) { - return qfalse; - } - - // only pause if there is just a single client connected - count = 0; - for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { - if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) { - count++; - } - } - - if ( count > 1 ) { - // don't pause - if (sv_paused->integer) - Cvar_Set("sv_paused", "0"); - return qfalse; - } - - if (!sv_paused->integer) - Cvar_Set("sv_paused", "1"); - return qtrue; -} - -/* -================== -SV_Frame - -Player movement occurs as a result of packet events, which -happen before SV_Frame is called -================== -*/ -void SV_Frame( int msec ) { - int frameMsec; - int startTime; - - // the menu kills the server with this cvar - if ( sv_killserver->integer ) { - SV_Shutdown ("Server was killed.\n"); - Cvar_Set( "sv_killserver", "0" ); - return; - } - - if ( !com_sv_running->integer ) { - return; - } - - // allow pause if only the local client is connected - if ( SV_CheckPaused() ) { - return; - } - - // if it isn't time for the next frame, do nothing - if ( sv_fps->integer < 1 ) { - Cvar_Set( "sv_fps", "10" ); - } - frameMsec = 1000 / sv_fps->integer ; - - sv.timeResidual += msec; - - if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual ); - - if ( com_dedicated->integer && sv.timeResidual < frameMsec ) { - // NET_Sleep will give the OS time slices until either get a packet - // or time enough for a server frame has gone by - NET_Sleep(frameMsec - sv.timeResidual); - return; - } - - // if time is about to hit the 32nd bit, kick all clients - // and clear sv.time, rather - // than checking for negative time wraparound everywhere. - // 2giga-milliseconds = 23 days, so it won't be too often - if ( svs.time > 0x70000000 ) { - SV_Shutdown( "Restarting server due to time wrapping" ); - Cbuf_AddText( "vstr nextmap\n" ); - return; - } - // this can happen considerably earlier when lots of clients play and the map doesn't change - if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) { - SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" ); - Cbuf_AddText( "vstr nextmap\n" ); - return; - } - - if( sv.restartTime && svs.time >= sv.restartTime ) { - sv.restartTime = 0; - Cbuf_AddText( "map_restart 0\n" ); - return; - } - - // update infostrings if anything has been changed - if ( cvar_modifiedFlags & CVAR_SERVERINFO ) { - SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); - cvar_modifiedFlags &= ~CVAR_SERVERINFO; - } - if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) { - SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); - cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; - } - - if ( com_speeds->integer ) { - startTime = Sys_Milliseconds (); - } else { - startTime = 0; // quite a compiler warning - } - - // update ping based on the all received frames - SV_CalcPings(); - - if (com_dedicated->integer) SV_BotFrame( svs.time ); - - // run the game simulation in chunks - while ( sv.timeResidual >= frameMsec ) { - sv.timeResidual -= frameMsec; - svs.time += frameMsec; - - // let everything in the world think and move - VM_Call( gvm, GAME_RUN_FRAME, svs.time ); - } - - if ( com_speeds->integer ) { - time_game = Sys_Milliseconds () - startTime; - } - - // check timeouts - SV_CheckTimeouts(); - - // send messages back to the clients - SV_SendClientMessages(); - - // send a heartbeat to the master if needed - SV_MasterHeartbeat(); -} - -//============================================================================ - +/* +=========================================================================== +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 +Redirect all printfs +=============== +*/ +void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { + qboolean valid; + unsigned int time; + char remaining[1024]; + // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. + // (OOB messages are the bottleneck here) +#define SV_OUTPUTBUF_LENGTH (1024 - 16) + char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; + static unsigned int lasttime = 0; + char *cmd_aux; + + // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534 + time = Com_Milliseconds(); + if (time<(lasttime+500)) { + return; + } + lasttime = time; + + if ( !strlen( sv_rconPassword->string ) || + strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { + valid = qfalse; + Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); + } else { + valid = qtrue; + Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); + } + + // start redirecting all print outputs to the packet + svs.redirectAddress = from; + Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect); + + if ( !strlen( sv_rconPassword->string ) ) { + Com_Printf ("No rconpassword set on the server.\n"); + } else if ( !valid ) { + Com_Printf ("Bad rconpassword.\n"); + } else { + remaining[0] = 0; + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + // get the command directly, "rcon " to avoid quoting issues + // extract the command by walking + // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing + cmd_aux = Cmd_Cmd(); + cmd_aux+=4; + while(cmd_aux[0]==' ') + cmd_aux++; + while(cmd_aux[0] && cmd_aux[0]!=' ') // password + cmd_aux++; + while(cmd_aux[0]==' ') + cmd_aux++; + + Q_strcat( remaining, sizeof(remaining), cmd_aux); + + Cmd_ExecuteString (remaining); + + } + + Com_EndRedirect (); +} + +/* +================= +SV_ConnectionlessPacket + +A connectionless packet has four leading 0xff +characters to distinguish it from a game channel. +Clients that are in the game can still send +connectionless packets. +================= +*/ +void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 marker + + if (!Q_strncmp("connect", &msg->data[4], 7)) { + Huff_Decompress(msg, 12); + } + + s = MSG_ReadStringLine( msg ); + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); + + if (!Q_stricmp(c, "getstatus")) { + SVC_Status( from ); + } else if (!Q_stricmp(c, "getinfo")) { + SVC_Info( from ); + } else if (!Q_stricmp(c, "getchallenge")) { + SV_GetChallenge( from ); + } else if (!Q_stricmp(c, "connect")) { + SV_DirectConnect( from ); + } else if (!Q_stricmp(c, "ipAuthorize")) { + SV_AuthorizeIpPacket( from ); + } else if (!Q_stricmp(c, "rcon")) { + SVC_RemoteCommand( from, msg ); + } else if (!Q_stricmp(c, "disconnect")) { + // if a client starts up a local server, we may see some spurious + // server disconnect messages when their new server sees our final + // sequenced messages to the old client + } else { + Com_DPrintf ("bad connectionless packet from %s:\n%s\n" + , NET_AdrToString (from), s); + } +} + +//============================================================================ + +/* +================= +SV_ReadPackets +================= +*/ +void SV_PacketEvent( netadr_t from, msg_t *msg ) { + int i; + client_t *cl; + int qport; + + // check for connectionless packet (0xffffffff) first + if ( msg->cursize >= 4 && *(int *)msg->data == -1) { + SV_ConnectionlessPacket( from, msg ); + return; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // sequence number + qport = MSG_ReadShort( msg ) & 0xffff; + + // find which client the message is from + for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if (cl->state == CS_FREE) { + continue; + } + if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { + continue; + } + // it is possible to have multiple clients from a single IP + // address, so they are differentiated by the qport variable + if (cl->netchan.qport != qport) { + continue; + } + + // the IP port can't be used to differentiate them, because + // some address translating routers periodically change UDP + // port assignments + if (cl->netchan.remoteAddress.port != from.port) { + Com_Printf( "SV_PacketEvent: fixing up a translated port\n" ); + cl->netchan.remoteAddress.port = from.port; + } + + // make sure it is a valid, in sequence packet + if (SV_Netchan_Process(cl, msg)) { + // zombie clients still need to do the Netchan_Process + // to make sure they don't need to retransmit the final + // reliable message, but they don't do any other processing + if (cl->state != CS_ZOMBIE) { + cl->lastPacketTime = svs.time; // don't timeout + SV_ExecuteClientMessage( cl, msg ); + } + } + return; + } + + // if we received a sequenced packet from an address we don't recognize, + // send an out of band disconnect packet to it + NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); +} + + +/* +=================== +SV_CalcPings + +Updates the cl->ping variables +=================== +*/ +void SV_CalcPings( void ) { + int i, j; + client_t *cl; + int total, count; + int delta; + playerState_t *ps; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state != CS_ACTIVE ) { + cl->ping = 999; + continue; + } + if ( !cl->gentity ) { + cl->ping = 999; + continue; + } + if ( cl->gentity->r.svFlags & SVF_BOT ) { + cl->ping = 0; + continue; + } + + total = 0; + count = 0; + for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { + if ( cl->frames[j].messageAcked <= 0 ) { + continue; + } + delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; + count++; + total += delta; + } + if (!count) { + cl->ping = 999; + } else { + cl->ping = total/count; + if ( cl->ping > 999 ) { + cl->ping = 999; + } + } + + // let the game dll know about the ping + ps = SV_GameClientNum( i ); + ps->ping = cl->ping; + } +} + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for timeout->integer +seconds, drop the conneciton. Server time is used instead of +realtime to avoid dropping the local client while debugging. + +When a client is normally dropped, the client_t goes into a zombie state +for a few seconds to make sure any final reliable message gets resent +if necessary +================== +*/ +void SV_CheckTimeouts( void ) { + int i; + client_t *cl; + int droppoint; + int zombiepoint; + + droppoint = svs.time - 1000 * sv_timeout->integer; + zombiepoint = svs.time - 1000 * sv_zombietime->integer; + + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + // message times may be wrong across a changelevel + if (cl->lastPacketTime > svs.time) { + cl->lastPacketTime = svs.time; + } + + if (cl->state == CS_ZOMBIE + && cl->lastPacketTime < zombiepoint) { + // using the client id cause the cl->name is empty at this point + Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i ); + cl->state = CS_FREE; // can now be reused + continue; + } + if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) { + // wait several frames so a debugger session doesn't + // cause a timeout + if ( ++cl->timeoutCount > 5 ) { + SV_DropClient (cl, "timed out"); + cl->state = CS_FREE; // don't bother with zombie state + } + } else { + cl->timeoutCount = 0; + } + } +} + + +/* +================== +SV_CheckPaused +================== +*/ +qboolean SV_CheckPaused( void ) { + int count; + client_t *cl; + int i; + + if ( !cl_paused->integer ) { + return qfalse; + } + + // only pause if there is just a single client connected + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) { + count++; + } + } + + if ( count > 1 ) { + // don't pause + if (sv_paused->integer) + Cvar_Set("sv_paused", "0"); + return qfalse; + } + + if (!sv_paused->integer) + Cvar_Set("sv_paused", "1"); + return qtrue; +} + +/* +================== +SV_Frame + +Player movement occurs as a result of packet events, which +happen before SV_Frame is called +================== +*/ +void SV_Frame( int msec ) { + int frameMsec; + int startTime; + + // the menu kills the server with this cvar + if ( sv_killserver->integer ) { + SV_Shutdown ("Server was killed.\n"); + Cvar_Set( "sv_killserver", "0" ); + return; + } + + if ( !com_sv_running->integer ) { + return; + } + + // allow pause if only the local client is connected + if ( SV_CheckPaused() ) { + return; + } + + // if it isn't time for the next frame, do nothing + if ( sv_fps->integer < 1 ) { + Cvar_Set( "sv_fps", "10" ); + } + frameMsec = 1000 / sv_fps->integer ; + + sv.timeResidual += msec; + + if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual ); + + if ( com_dedicated->integer && sv.timeResidual < frameMsec ) { + // NET_Sleep will give the OS time slices until either get a packet + // or time enough for a server frame has gone by + NET_Sleep(frameMsec - sv.timeResidual); + return; + } + + // if time is about to hit the 32nd bit, kick all clients + // and clear sv.time, rather + // than checking for negative time wraparound everywhere. + // 2giga-milliseconds = 23 days, so it won't be too often + if ( svs.time > 0x70000000 ) { + SV_Shutdown( "Restarting server due to time wrapping" ); + Cbuf_AddText( "vstr nextmap\n" ); + return; + } + // this can happen considerably earlier when lots of clients play and the map doesn't change + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) { + SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" ); + Cbuf_AddText( "vstr nextmap\n" ); + return; + } + + if( sv.restartTime && svs.time >= sv.restartTime ) { + sv.restartTime = 0; + Cbuf_AddText( "map_restart 0\n" ); + return; + } + + // update infostrings if anything has been changed + if ( cvar_modifiedFlags & CVAR_SERVERINFO ) { + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + } + if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) { + SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + } + + if ( com_speeds->integer ) { + startTime = Sys_Milliseconds (); + } else { + startTime = 0; // quite a compiler warning + } + + // update ping based on the all received frames + SV_CalcPings(); + + if (com_dedicated->integer) SV_BotFrame( svs.time ); + + // run the game simulation in chunks + while ( sv.timeResidual >= frameMsec ) { + sv.timeResidual -= frameMsec; + svs.time += frameMsec; + + // let everything in the world think and move + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + } + + if ( com_speeds->integer ) { + time_game = Sys_Milliseconds () - startTime; + } + + // check timeouts + SV_CheckTimeouts(); + + // send messages back to the clients + SV_SendClientMessages(); + + // send a heartbeat to the master if needed + SV_MasterHeartbeat(); +} + +//============================================================================ + diff --git a/code/server/sv_net_chan.c b/code/server/sv_net_chan.c index 180d793..feab271 100755 --- a/code/server/sv_net_chan.c +++ b/code/server/sv_net_chan.c @@ -1,207 +1,207 @@ -/* -=========================================================================== -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 "../game/q_shared.h" -#include "../qcommon/qcommon.h" -#include "server.h" - -/* -============== -SV_Netchan_Encode - - // first four bytes of the data are always: - long reliableAcknowledge; - -============== -*/ -static void SV_Netchan_Encode( client_t *client, msg_t *msg ) { - long reliableAcknowledge, i, index; - byte key, *string; - int srdc, sbit, soob; - - if ( msg->cursize < SV_ENCODE_START ) { - return; - } - - srdc = msg->readcount; - sbit = msg->bit; - soob = msg->oob; - - msg->bit = 0; - msg->readcount = 0; - msg->oob = 0; - - reliableAcknowledge = MSG_ReadLong(msg); - - msg->oob = soob; - msg->bit = sbit; - msg->readcount = srdc; - - string = (byte *)client->lastClientCommandString; - index = 0; - // xor the client challenge with the netchan sequence number - key = client->challenge ^ client->netchan.outgoingSequence; - for (i = SV_ENCODE_START; i < msg->cursize; i++) { - // modify the key with the last received and with this message acknowledged client command - if (!string[index]) - index = 0; - if (string[index] > 127 || string[index] == '%') { - key ^= '.' << (i & 1); - } - else { - key ^= string[index] << (i & 1); - } - index++; - // encode the data with this key - *(msg->data + i) = *(msg->data + i) ^ key; - } -} - -/* -============== -SV_Netchan_Decode - - // first 12 bytes of the data are always: - long serverId; - long messageAcknowledge; - long reliableAcknowledge; - -============== -*/ -static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { - int serverId, messageAcknowledge, reliableAcknowledge; - int i, index, srdc, sbit, soob; - byte key, *string; - - srdc = msg->readcount; - sbit = msg->bit; - soob = msg->oob; - - msg->oob = 0; - - serverId = MSG_ReadLong(msg); - messageAcknowledge = MSG_ReadLong(msg); - reliableAcknowledge = MSG_ReadLong(msg); - - msg->oob = soob; - msg->bit = sbit; - msg->readcount = srdc; - - string = (byte *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; - index = 0; - // - key = client->challenge ^ serverId ^ messageAcknowledge; - for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) { - // modify the key with the last sent and acknowledged server command - if (!string[index]) - index = 0; - if (string[index] > 127 || string[index] == '%') { - key ^= '.' << (i & 1); - } - else { - key ^= string[index] << (i & 1); - } - index++; - // decode the data with this key - *(msg->data + i) = *(msg->data + i) ^ key; - } -} - -/* -================= -SV_Netchan_TransmitNextFragment -================= -*/ -void SV_Netchan_TransmitNextFragment( client_t *client ) { - Netchan_TransmitNextFragment( &client->netchan ); - if (!client->netchan.unsentFragments) - { - // make sure the netchan queue has been properly initialized (you never know) - if (!client->netchan_end_queue) { - Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n"); - } - // the last fragment was transmitted, check wether we have queued messages - if (client->netchan_start_queue) { - netchan_buffer_t *netbuf; - Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); - netbuf = client->netchan_start_queue; - SV_Netchan_Encode( client, &netbuf->msg ); - Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data ); - // pop from queue - client->netchan_start_queue = netbuf->next; - if (!client->netchan_start_queue) { - Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); - client->netchan_end_queue = &client->netchan_start_queue; - } - else - Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); - Z_Free(netbuf); - } - } -} - - -/* -=============== -SV_Netchan_Transmit -TTimo -https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 -if there are some unsent fragments (which may happen if the snapshots -and the gamestate are fragmenting, and collide on send for instance) -then buffer them and make sure they get sent in correct order -================ -*/ - -void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) { - MSG_WriteByte( msg, svc_EOF ); - if (client->netchan.unsentFragments) { - netchan_buffer_t *netbuf; - Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n"); - netbuf = (netchan_buffer_t *)Z_Malloc(sizeof(netchan_buffer_t)); - // store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending - MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg); - netbuf->next = NULL; - // insert it in the queue, the message will be encoded and sent later - *client->netchan_end_queue = netbuf; - client->netchan_end_queue = &(*client->netchan_end_queue)->next; - // emit the next fragment of the current message for now - Netchan_TransmitNextFragment(&client->netchan); - } else { - SV_Netchan_Encode( client, msg ); - Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); - } -} - -/* -================= -Netchan_SV_Process -================= -*/ -qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { - int ret; - ret = Netchan_Process( &client->netchan, msg ); - if (!ret) - return qfalse; - SV_Netchan_Decode( client, msg ); - return qtrue; -} - +/* +=========================================================================== +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 "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "server.h" + +/* +============== +SV_Netchan_Encode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Encode( client_t *client, msg_t *msg ) { + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + if ( msg->cursize < SV_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = 0; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->lastClientCommandString; + index = 0; + // xor the client challenge with the netchan sequence number + key = client->challenge ^ client->netchan.outgoingSequence; + for (i = SV_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received and with this message acknowledged client command + if (!string[index]) + index = 0; + if (string[index] > 127 || string[index] == '%') { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +============== +SV_Netchan_Decode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = 0; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = client->challenge ^ serverId ^ messageAcknowledge; + for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and acknowledged server command + if (!string[index]) + index = 0; + if (string[index] > 127 || string[index] == '%') { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +================= +SV_Netchan_TransmitNextFragment +================= +*/ +void SV_Netchan_TransmitNextFragment( client_t *client ) { + Netchan_TransmitNextFragment( &client->netchan ); + if (!client->netchan.unsentFragments) + { + // make sure the netchan queue has been properly initialized (you never know) + if (!client->netchan_end_queue) { + Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n"); + } + // the last fragment was transmitted, check wether we have queued messages + if (client->netchan_start_queue) { + netchan_buffer_t *netbuf; + Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); + netbuf = client->netchan_start_queue; + SV_Netchan_Encode( client, &netbuf->msg ); + Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data ); + // pop from queue + client->netchan_start_queue = netbuf->next; + if (!client->netchan_start_queue) { + Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); + client->netchan_end_queue = &client->netchan_start_queue; + } + else + Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); + Z_Free(netbuf); + } + } +} + + +/* +=============== +SV_Netchan_Transmit +TTimo +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 +if there are some unsent fragments (which may happen if the snapshots +and the gamestate are fragmenting, and collide on send for instance) +then buffer them and make sure they get sent in correct order +================ +*/ + +void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) { + MSG_WriteByte( msg, svc_EOF ); + if (client->netchan.unsentFragments) { + netchan_buffer_t *netbuf; + Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n"); + netbuf = (netchan_buffer_t *)Z_Malloc(sizeof(netchan_buffer_t)); + // store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending + MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg); + netbuf->next = NULL; + // insert it in the queue, the message will be encoded and sent later + *client->netchan_end_queue = netbuf; + client->netchan_end_queue = &(*client->netchan_end_queue)->next; + // emit the next fragment of the current message for now + Netchan_TransmitNextFragment(&client->netchan); + } else { + SV_Netchan_Encode( client, msg ); + Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); + } +} + +/* +================= +Netchan_SV_Process +================= +*/ +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { + int ret; + ret = Netchan_Process( &client->netchan, msg ); + if (!ret) + return qfalse; + SV_Netchan_Decode( client, msg ); + return qtrue; +} + diff --git a/code/server/sv_rankings.c b/code/server/sv_rankings.c index 5d8a01f..d21a799 100755 --- a/code/server/sv_rankings.c +++ b/code/server/sv_rankings.c @@ -1,1537 +1,1537 @@ -/* -=========================================================================== -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 -=========================================================================== -*/ -// sv_rankings.c -- global rankings interface - -#include "server.h" -#include "..\rankings\1.0\gr\grapi.h" -#include "..\rankings\1.0\gr\grlog.h" - -typedef struct -{ - GR_CONTEXT context; - uint64_t game_id; - uint64_t match; - uint64_t player_id; - GR_PLAYER_TOKEN token; - grank_status_t grank_status; - grank_status_t final_status; // status to set after cleanup - uint32_t grank; // global rank - char name[32]; -} ranked_player_t; - -static int s_rankings_contexts = 0; -static qboolean s_rankings_active = qfalse; -static GR_CONTEXT s_server_context = 0; -static uint64_t s_server_match = 0; -static char* s_rankings_game_key = NULL; -static uint64_t s_rankings_game_id = 0; -static ranked_player_t* s_ranked_players = NULL; -static qboolean s_server_quitting = qfalse; -static const char s_ascii_encoding[] = - "0123456789abcdef" - "ghijklmnopqrstuv" - "wxyzABCDEFGHIJKL" - "MNOPQRSTUVWXYZ[]"; - -// private functions -static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg ); -static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg ); -static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg ); -static void SV_RankSendReportsCBF( GR_STATUS* gr_status, void* cbf_arg ); -static void SV_RankCleanupCBF( GR_STATUS* gr_status, void* cbf_arg ); -static void SV_RankCloseContext( ranked_player_t* ranked_player ); -static int SV_RankAsciiEncode( char* dest, const unsigned char* src, - int src_len ); -static int SV_RankAsciiDecode( unsigned char* dest, const char* src, - int src_len ); -static void SV_RankEncodeGameID( uint64_t game_id, char* result, - int len ); -static uint64_t SV_RankDecodePlayerID( const char* string ); -static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key ); -static char* SV_RankStatusString( GR_STATUS status ); -static void SV_RankError( const char* fmt, ... ); -static char SV_RankGameKey[64]; - -/* -================ -SV_RankBegin -================ -*/ -void SV_RankBegin( char *gamekey ) -{ - GR_INIT init; - GR_STATUS status; - - assert( s_rankings_contexts == 0 ); - assert( !s_rankings_active ); - assert( s_ranked_players == NULL ); - - if( sv_enableRankings->integer == 0 || Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) - { - s_rankings_active = qfalse; - if( sv_rankingsActive->integer == 1 ) - { - Cvar_Set( "sv_rankingsActive", "0" ); - } - return; - } - - // only allow official game key on pure servers - if( strcmp(gamekey, GR_GAMEKEY) == 0 ) - { -/* - if( Cvar_VariableValue("sv_pure") != 1 ) - { - Cvar_Set( "sv_enableRankings", "0" ); - return; - } -*/ - - // substitute game-specific game key - switch( (int)Cvar_VariableValue("g_gametype") ) - { - case GT_FFA: - gamekey = "Q3 Free For All"; - break; - case GT_TOURNAMENT: - gamekey = "Q3 Tournament"; - break; - case GT_TEAM: - gamekey = "Q3 Team Deathmatch"; - break; - case GT_CTF: - gamekey = "Q3 Capture the Flag"; - break; - case GT_1FCTF: - gamekey = "Q3 One Flag CTF"; - break; - case GT_OBELISK: - gamekey = "Q3 Overload"; - break; - case GT_HARVESTER: - gamekey = "Q3 Harvester"; - break; - default: - break; - } - } - s_rankings_game_key = gamekey; - - // initialize rankings - GRankLogLevel( GRLOG_OFF ); - memset(SV_RankGameKey,0,sizeof(SV_RankGameKey)); - strncpy(SV_RankGameKey,gamekey,sizeof(SV_RankGameKey)-1); - init = GRankInit( 1, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); - s_server_context = init.context; - s_rankings_contexts++; - Com_DPrintf( "SV_RankBegin(); GR_GAMEKEY is %s\n", gamekey ); - Com_DPrintf( "SV_RankBegin(); s_rankings_contexts=%d\n",s_rankings_contexts ); - Com_DPrintf( "SV_RankBegin(); s_server_context=%d\n",init.context ); - - // new game - if(!strlen(Cvar_VariableString( "sv_leagueName" ))) - { - status = GRankNewGameAsync - ( - s_server_context, - SV_RankNewGameCBF, - NULL, - GR_OPT_LEAGUENAME, - (void*)(Cvar_VariableString( "sv_leagueName" )), - GR_OPT_END - ); - } - else - { - status = GRankNewGameAsync - ( - s_server_context, - SV_RankNewGameCBF, - NULL, - GR_OPT_END - ); - } - - if( status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankBegin: Expected GR_STATUS_PENDING, got %s", - SV_RankStatusString( status ) ); - return; - } - - // logging - if( com_developer->value ) - { - GRankLogLevel( GRLOG_TRACE ); - } - - // allocate rankings info for each player - s_ranked_players = Z_Malloc( sv_maxclients->value * - sizeof(ranked_player_t) ); - memset( (void*)s_ranked_players, 0 ,sv_maxclients->value - * sizeof(ranked_player_t)); -} - -/* -================ -SV_RankEnd -================ -*/ -void SV_RankEnd( void ) -{ - GR_STATUS status; - int i; - - Com_DPrintf( "SV_RankEnd();\n" ); - - if( !s_rankings_active ) - { - // cleanup after error during game - if( s_ranked_players != NULL ) - { - for( i = 0; i < sv_maxclients->value; i++ ) - { - if( s_ranked_players[i].context != 0 ) - { - SV_RankCloseContext( &(s_ranked_players[i]) ); - } - } - } - if( s_server_context != 0 ) - { - SV_RankCloseContext( NULL ); - } - - return; - } - - for( i = 0; i < sv_maxclients->value; i++ ) - { - if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE ) - { - SV_RankUserLogout( i ); - Com_DPrintf( "SV_RankEnd: SV_RankUserLogout %d\n",i ); - } - } - - assert( s_server_context != 0 ); - - // send match reports, proceed to SV_RankSendReportsCBF - status = GRankSendReportsAsync - ( - s_server_context, - 0, - SV_RankSendReportsCBF, - NULL, - GR_OPT_END - ); - - if( status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankEnd: Expected GR_STATUS_PENDING, got %s", - SV_RankStatusString( status ) ); - } - - s_rankings_active = qfalse; - Cvar_Set( "sv_rankingsActive", "0" ); -} - -/* -================ -SV_RankPoll -================ -*/ -void SV_RankPoll( void ) -{ - GRankPoll(); -} - -/* -================ -SV_RankCheckInit -================ -*/ -qboolean SV_RankCheckInit( void ) -{ - return (s_rankings_contexts > 0); -} - -/* -================ -SV_RankActive -================ -*/ -qboolean SV_RankActive( void ) -{ - return s_rankings_active; -} - -/* -================= -SV_RankUserStatus -================= -*/ -grank_status_t SV_RankUserStatus( int index ) -{ - if( !s_rankings_active ) - { - return GR_STATUS_ERROR; - } - - assert( s_ranked_players != NULL ); - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - - return s_ranked_players[index].grank_status; -} - -/* -================ -SV_RankUserGRank -================ -*/ -int SV_RankUserGrank( int index ) -{ - if( !s_rankings_active ) - { - return 0; - } - - assert( s_ranked_players != NULL ); - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - - return s_ranked_players[index].grank; -} - -/* -================ -SV_RankUserReset -================ -*/ -void SV_RankUserReset( int index ) -{ - if( !s_rankings_active ) - { - return; - } - - assert( s_ranked_players != NULL ); - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - - switch( s_ranked_players[index].grank_status ) - { - case QGR_STATUS_SPECTATOR: - case QGR_STATUS_NO_USER: - case QGR_STATUS_BAD_PASSWORD: - case QGR_STATUS_USER_EXISTS: - case QGR_STATUS_NO_MEMBERSHIP: - case QGR_STATUS_TIMEOUT: - case QGR_STATUS_ERROR: - s_ranked_players[index].grank_status = QGR_STATUS_NEW; - break; - default: - break; - } -} - -/* -================ -SV_RankUserSpectate -================ -*/ -void SV_RankUserSpectate( int index ) -{ - if( !s_rankings_active ) - { - return; - } - - assert( s_ranked_players != NULL ); - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - - // GRANK_FIXME - check current status? - s_ranked_players[index].grank_status = QGR_STATUS_SPECTATOR; -} - -/* -================ -SV_RankUserCreate -================ -*/ -void SV_RankUserCreate( int index, char* username, char* password, - char* email ) -{ - GR_INIT init; - GR_STATUS status; - - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - assert( username != NULL ); - assert( password != NULL ); - assert( email != NULL ); - assert( s_ranked_players ); - assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); - - Com_DPrintf( "SV_RankUserCreate( %d, %s, \"****\", %s );\n", index, - username, email ); - - if( !s_rankings_active ) - { - Com_DPrintf( "SV_RankUserCreate: Not ready to create\n" ); - s_ranked_players[index].grank_status = QGR_STATUS_ERROR; - return; - } - - if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE ) - { - Com_DPrintf( "SV_RankUserCreate: Got Create from active player\n" ); - return; - } - - // get a separate context for the new user - init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); - s_ranked_players[index].context = init.context; - s_rankings_contexts++; - Com_DPrintf( "SV_RankUserCreate(); s_rankings_contexts=%d\n",s_rankings_contexts ); - Com_DPrintf( "SV_RankUserCreate(); s_ranked_players[%d].context=%d\n",index,init.context ); - - // attempt to create a new account, proceed to SV_RankUserCBF - status = GRankUserCreateAsync - ( - s_ranked_players[index].context, - username, - password, - email, - SV_RankUserCBF, - (void*)&s_ranked_players[index], - GR_OPT_END - ); - - if( status == GR_STATUS_PENDING ) - { - s_ranked_players[index].grank_status = QGR_STATUS_PENDING; - s_ranked_players[index].final_status = QGR_STATUS_NEW; - } - else - { - SV_RankError( "SV_RankUserCreate: Expected GR_STATUS_PENDING, got %s", - SV_RankStatusString( status ) ); - } -} - -/* -================ -SV_RankUserLogin -================ -*/ -void SV_RankUserLogin( int index, char* username, char* password ) -{ - GR_INIT init; - GR_STATUS status; - - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - assert( username != NULL ); - assert( password != NULL ); - assert( s_ranked_players ); - assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); - - Com_DPrintf( "SV_RankUserLogin( %d, %s, \"****\" );\n", index, username ); - - if( !s_rankings_active ) - { - Com_DPrintf( "SV_RankUserLogin: Not ready for login\n" ); - s_ranked_players[index].grank_status = QGR_STATUS_ERROR; - return; - } - - if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE ) - { - Com_DPrintf( "SV_RankUserLogin: Got Login from active player\n" ); - return; - } - - // get a separate context for the new user - init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); - s_ranked_players[index].context = init.context; - s_rankings_contexts++; - Com_DPrintf( "SV_RankUserLogin(); s_rankings_contexts=%d\n",s_rankings_contexts ); - Com_DPrintf( "SV_RankUserLogin(); s_ranked_players[%d].context=%d\n",index,init.context ); - - // login user, proceed to SV_RankUserCBF - status = GRankUserLoginAsync - ( - s_ranked_players[index].context, - username, - password, - SV_RankUserCBF, - (void*)&s_ranked_players[index], - GR_OPT_END - ); - - if( status == GR_STATUS_PENDING ) - { - s_ranked_players[index].grank_status = QGR_STATUS_PENDING; - s_ranked_players[index].final_status = QGR_STATUS_NEW; - } - else - { - SV_RankError( "SV_RankUserLogin: Expected GR_STATUS_PENDING, got %s", - SV_RankStatusString( status ) ); - } -} - -/* -=================== -SV_RankUserValidate -=================== -*/ -qboolean SV_RankUserValidate( int index, const char* player_id, const char* key, int token_len, int rank, char* name ) -{ - GR_INIT init; - GR_STATUS status; - qboolean rVal; - ranked_player_t* ranked_player; - int i; - - assert( s_ranked_players ); - assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); - - rVal = qfalse; - - if( !s_rankings_active ) - { - Com_DPrintf( "SV_RankUserValidate: Not ready to validate\n" ); - s_ranked_players[index].grank_status = QGR_STATUS_ERROR; - return rVal; - } - - ranked_player = &(s_ranked_players[index]); - - if ( (player_id != NULL) && (key != NULL)) - { - // the real player_id and key is set when SV_RankJoinGameCBF - // is called we do this so that SV_RankUserValidate - // can be shared by both server side login and client side login - - // for client side logined in players - // server is creating GR_OPT_PLAYERCONTEXT - init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); - ranked_player->context = init.context; - s_rankings_contexts++; - Com_DPrintf( "SV_RankUserValidate(); s_rankings_contexts=%d\n",s_rankings_contexts ); - Com_DPrintf( "SV_RankUserValidate(); s_ranked_players[%d].context=%d\n",index,init.context ); - - // uudecode player id and player token - ranked_player->player_id = SV_RankDecodePlayerID(player_id); - Com_DPrintf( "SV_RankUserValidate(); ranked_player->player_id =%u\n", (uint32_t)ranked_player->player_id ); - SV_RankDecodePlayerKey(key, ranked_player->token); - - // save name and check for duplicates - Q_strncpyz( ranked_player->name, name, sizeof(ranked_player->name) ); - for( i = 0; i < sv_maxclients->value; i++ ) - { - if( (i != index) && (s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE) && - (strcmp( s_ranked_players[i].name, name ) == 0) ) - { - Com_DPrintf( "SV_RankUserValidate: Duplicate login\n" ); - ranked_player->grank_status = QGR_STATUS_NO_USER; - ranked_player->final_status = QGR_STATUS_NEW; - ranked_player->grank = 0; - return qfalse; - } - } - - // then validate - status = GRankPlayerValidate( - s_server_context, - ranked_player->player_id, - ranked_player->token, - token_len, - GR_OPT_PLAYERCONTEXT, - ranked_player->context, - GR_OPT_END); - } - else - { - // make server side login (bots) happy - status = GR_STATUS_OK; - } - - if (status == GR_STATUS_OK) - { - ranked_player->grank_status = QGR_STATUS_ACTIVE; - ranked_player->final_status = QGR_STATUS_NEW; - ranked_player->grank = rank; - rVal = qtrue; - } - else if (status == GR_STATUS_INVALIDUSER) - { - ranked_player->grank_status = QGR_STATUS_INVALIDUSER; - ranked_player->final_status = QGR_STATUS_NEW; - ranked_player->grank = 0; - rVal = qfalse; - } - else - { - SV_RankError( "SV_RankUserValidate: Unexpected status %s", - SV_RankStatusString( status ) ); - s_ranked_players[index].grank_status = QGR_STATUS_ERROR; - ranked_player->grank = 0; - } - - return rVal; -} - -/* -================ -SV_RankUserLogout -================ -*/ -void SV_RankUserLogout( int index ) -{ - GR_STATUS status; - GR_STATUS cleanup_status; - - if( !s_rankings_active ) - { - return; - } - - assert( index >= 0 ); - assert( index < sv_maxclients->value ); - assert( s_ranked_players ); - - if( s_ranked_players[index].context == 0 ) { - return; - } - - Com_DPrintf( "SV_RankUserLogout( %d );\n", index ); - - // masqueraded player may not be active yet, if they fail validation, - // but still they have a context needs to be cleaned - // what matters is the s_ranked_players[index].context - - // send reports, proceed to SV_RankSendReportsCBF - status = GRankSendReportsAsync - ( - s_ranked_players[index].context, - 0, - SV_RankSendReportsCBF, - (void*)&s_ranked_players[index], - GR_OPT_END - ); - - if( status == GR_STATUS_PENDING ) - { - s_ranked_players[index].grank_status = QGR_STATUS_PENDING; - s_ranked_players[index].final_status = QGR_STATUS_NEW; - } - else - { - SV_RankError( "SV_RankUserLogout: Expected GR_STATUS_PENDING, got %s", - SV_RankStatusString( status ) ); - - cleanup_status = GRankCleanupAsync - ( - s_ranked_players[index].context, - 0, - SV_RankCleanupCBF, - (void*)&s_ranked_players[index], - GR_OPT_END - ); - - if( cleanup_status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankUserLogout: Expected " - "GR_STATUS_PENDING from GRankCleanupAsync, got %s", - SV_RankStatusString( cleanup_status ) ); - SV_RankCloseContext( &(s_ranked_players[index]) ); - } - } -} - -/* -================ -SV_RankReportInt -================ -*/ -void SV_RankReportInt( int index1, int index2, int key, int value, - qboolean accum ) -{ - GR_STATUS status; - GR_CONTEXT context; - uint64_t match; - uint64_t user1; - uint64_t user2; - int opt_accum; - - if( !s_rankings_active ) - { - return; - } - - assert( index1 >= -1 ); - assert( index1 < sv_maxclients->value ); - assert( index2 >= -1 ); - assert( index2 < sv_maxclients->value ); - assert( s_ranked_players ); - -// Com_DPrintf( "SV_RankReportInt( %d, %d, %d, %d, %d );\n", index1, index2, -// key, value, accum ); - - // get context, match, and player_id for player index1 - if( index1 == -1 ) - { - context = s_server_context; - match = s_server_match; - user1 = 0; - } - else - { - if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE ) - { - Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE" - " Got Unexpected status %d for player %d\n", - s_ranked_players[index1].grank_status, index1 ); - return; - } - - context = s_ranked_players[index1].context; - match = s_ranked_players[index1].match; - user1 = s_ranked_players[index1].player_id; - } - - // get player_id for player index2 - if( index2 == -1 ) - { - user2 = 0; - } - else - { - if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE ) - { - Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE" - " Got Unexpected status %d for player %d\n", - s_ranked_players[index2].grank_status, index2 ); - return; - } - - user2 = s_ranked_players[index2].player_id; - } - - opt_accum = accum ? GR_OPT_ACCUM : GR_OPT_END; - - status = GRankReportInt - ( - context, - match, - user1, - user2, - key, - value, - opt_accum, - GR_OPT_END - ); - - if( status != GR_STATUS_OK ) - { - SV_RankError( "SV_RankReportInt: Unexpected status %s", - SV_RankStatusString( status ) ); - } - - if( user2 != 0 ) - { - context = s_ranked_players[index2].context; - match = s_ranked_players[index2].match; - - status = GRankReportInt - ( - context, - match, - user1, - user2, - key, - value, - opt_accum, - GR_OPT_END - ); - - if( status != GR_STATUS_OK ) - { - SV_RankError( "SV_RankReportInt: Unexpected status %s", - SV_RankStatusString( status ) ); - } - } -} - -/* -================ -SV_RankReportStr -================ -*/ -void SV_RankReportStr( int index1, int index2, int key, char* value ) -{ - GR_STATUS status; - GR_CONTEXT context; - uint64_t match; - uint64_t user1; - uint64_t user2; - - if( !s_rankings_active ) - { - return; - } - - assert( index1 >= -1 ); - assert( index1 < sv_maxclients->value ); - assert( index2 >= -1 ); - assert( index2 < sv_maxclients->value ); - assert( s_ranked_players ); - -// Com_DPrintf( "SV_RankReportStr( %d, %d, %d, \"%s\" );\n", index1, index2, -// key, value ); - - // get context, match, and player_id for player index1 - if( index1 == -1 ) - { - context = s_server_context; - match = s_server_match; - user1 = 0; - } - else - { - if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE ) - { - Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", - s_ranked_players[index1].grank_status ); - return; - } - - context = s_ranked_players[index1].context; - match = s_ranked_players[index1].match; - user1 = s_ranked_players[index1].player_id; - } - - // get player_id for player index2 - if( index2 == -1 ) - { - user2 = 0; - } - else - { - if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE ) - { - Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", - s_ranked_players[index2].grank_status ); - return; - } - - user2 = s_ranked_players[index2].player_id; - } - - status = GRankReportStr - ( - context, - match, - user1, - user2, - key, - value, - GR_OPT_END - ); - - if( status != GR_STATUS_OK ) - { - SV_RankError( "SV_RankReportStr: Unexpected status %s", - SV_RankStatusString( status ) ); - } - - if( user2 != 0 ) - { - context = s_ranked_players[index2].context; - match = s_ranked_players[index2].match; - - status = GRankReportStr - ( - context, - match, - user1, - user2, - key, - value, - GR_OPT_END - ); - - if( status != GR_STATUS_OK ) - { - SV_RankError( "SV_RankReportInt: Unexpected status %s", - SV_RankStatusString( status ) ); - } - } -} - -/* -================ -SV_RankQuit -================ -*/ -void SV_RankQuit( void ) -{ - int i; - int j = 0; - // yuck - - while( s_rankings_contexts > 1 ) - { - assert(s_ranked_players); - if( s_ranked_players != NULL ) - { - for( i = 0; i < sv_maxclients->value; i++ ) - { - // check for players that weren't yet active in SV_RankEnd - if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE ) - { - SV_RankUserLogout( i ); - Com_DPrintf( "SV_RankQuit: SV_RankUserLogout %d\n",i ); - } - else - { - if( s_ranked_players[i].context ) - { - GR_STATUS cleanup_status; - cleanup_status = GRankCleanupAsync - ( - s_ranked_players[i].context, - 0, - SV_RankCleanupCBF, - (void*)&(s_ranked_players[i]), - GR_OPT_END - ); - - if( cleanup_status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankQuit: Expected " - "GR_STATUS_PENDING from GRankCleanupAsync, got %s", - SV_RankStatusString( cleanup_status ) ); - } - } - } - } - } - SV_RankPoll(); - - // should've finished by now - assert( (j++) < 68 ); - } -} - -/* -============================================================================== - -Private Functions - -============================================================================== -*/ - -/* -================= -SV_RankNewGameCBF -================= -*/ -static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg ) -{ - GR_MATCH match; - int i; - - assert( gr_newgame != NULL ); - assert( cbf_arg == NULL ); - - Com_DPrintf( "SV_RankNewGameCBF( %08X, %08X );\n", gr_newgame, cbf_arg ); - - if( gr_newgame->status == GR_STATUS_OK ) - { - char info[MAX_INFO_STRING]; - char gameid[sizeof(s_ranked_players[i].game_id) * 4 / 3 + 2]; - - // save game id - s_rankings_game_id = gr_newgame->game_id; - - // encode gameid - memset(gameid,0,sizeof(gameid)); - SV_RankEncodeGameID(s_rankings_game_id,gameid,sizeof(gameid)); - - // set CS_GRANK rankingsGameID to pass to client - memset(info,0,sizeof(info)); - Info_SetValueForKey( info, "rankingsGameKey", s_rankings_game_key ); - Info_SetValueForKey( info, "rankingsGameID", gameid ); - SV_SetConfigstring( CS_GRANK, info ); - - // initialize client status - for( i = 0; i < sv_maxclients->value; i++ ) - s_ranked_players[i].grank_status = QGR_STATUS_NEW; - - // start new match - match = GRankStartMatch( s_server_context ); - s_server_match = match.match; - - // ready to go - s_rankings_active = qtrue; - Cvar_Set( "sv_rankingsActive", "1" ); - - } - else if( gr_newgame->status == GR_STATUS_BADLEAGUE ) - { - SV_RankError( "SV_RankNewGameCBF: Invalid League name\n" ); - } - else - { - //GRank handle new game failure - // force SV_RankEnd() to run - //SV_RankEnd(); - SV_RankError( "SV_RankNewGameCBF: Unexpected status %s", - SV_RankStatusString( gr_newgame->status ) ); - } -} - -/* -================ -SV_RankUserCBF -================ -*/ -static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg ) -{ - ranked_player_t* ranked_player; - GR_STATUS join_status; - GR_STATUS cleanup_status; - - assert( gr_login != NULL ); - assert( cbf_arg != NULL ); - - Com_DPrintf( "SV_RankUserCBF( %08X, %08X );\n", gr_login, cbf_arg ); - - ranked_player = (ranked_player_t*)cbf_arg; - assert(ranked_player); - assert( ranked_player->context ); - - switch( gr_login->status ) - { - case GR_STATUS_OK: - // attempt to join the game, proceed to SV_RankJoinGameCBF - join_status = GRankJoinGameAsync - ( - ranked_player->context, - s_rankings_game_id, - SV_RankJoinGameCBF, - cbf_arg, - GR_OPT_END - ); - - if( join_status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING " - "from GRankJoinGameAsync, got %s", - SV_RankStatusString( join_status ) ); - } - break; - case GR_STATUS_NOUSER: - Com_DPrintf( "SV_RankUserCBF: Got status %s\n", - SV_RankStatusString( gr_login->status ) ); - ranked_player->final_status = QGR_STATUS_NO_USER; - break; - case GR_STATUS_BADPASSWORD: - Com_DPrintf( "SV_RankUserCBF: Got status %s\n", - SV_RankStatusString( gr_login->status ) ); - ranked_player->final_status = QGR_STATUS_BAD_PASSWORD; - break; - case GR_STATUS_TIMEOUT: - Com_DPrintf( "SV_RankUserCBF: Got status %s\n", - SV_RankStatusString( gr_login->status ) ); - ranked_player->final_status = QGR_STATUS_TIMEOUT; - break; - default: - Com_DPrintf( "SV_RankUserCBF: Unexpected status %s\n", - SV_RankStatusString( gr_login->status ) ); - ranked_player->final_status = QGR_STATUS_ERROR; - break; - } - - if( ranked_player->final_status != QGR_STATUS_NEW ) - { - // login or create failed, so clean up before the next attempt - cleanup_status = GRankCleanupAsync - ( - ranked_player->context, - 0, - SV_RankCleanupCBF, - (void*)ranked_player, - GR_OPT_END - ); - - if( cleanup_status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING " - "from GRankCleanupAsync, got %s", - SV_RankStatusString( cleanup_status ) ); - SV_RankCloseContext( ranked_player ); - } - } -} - -/* -================ -SV_RankJoinGameCBF -================ -*/ -static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg ) -{ - ranked_player_t* ranked_player; - GR_MATCH match; - GR_STATUS cleanup_status; - - assert( gr_joingame != NULL ); - assert( cbf_arg != NULL ); - - Com_DPrintf( "SV_RankJoinGameCBF( %08X, %08X );\n", gr_joingame, cbf_arg ); - - ranked_player = (ranked_player_t*)cbf_arg; - - assert( ranked_player ); - assert( ranked_player->context != 0 ); - - if( gr_joingame->status == GR_STATUS_OK ) - { - int i; - // save user id - ranked_player->player_id = gr_joingame->player_id; - memcpy(ranked_player->token,gr_joingame->token, - sizeof(GR_PLAYER_TOKEN)) ; - match = GRankStartMatch( ranked_player->context ); - ranked_player->match = match.match; - ranked_player->grank = gr_joingame->rank; - - // find the index and call SV_RankUserValidate - for (i=0;ivalue;i++) - if ( ranked_player == &s_ranked_players[i] ) - SV_RankUserValidate(i,NULL,NULL,0, gr_joingame->rank,ranked_player->name); - } - else - { - //GRand handle join game failure - SV_RankError( "SV_RankJoinGameCBF: Unexpected status %s", - SV_RankStatusString( gr_joingame->status ) ); - - cleanup_status = GRankCleanupAsync - ( - ranked_player->context, - 0, - SV_RankCleanupCBF, - cbf_arg, - GR_OPT_END - ); - - if( cleanup_status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankJoinGameCBF: Expected " - "GR_STATUS_PENDING from GRankCleanupAsync, got %s", - SV_RankStatusString( cleanup_status ) ); - SV_RankCloseContext( ranked_player ); - } - } -} - -/* -================ -SV_RankSendReportsCBF -================ -*/ -static void SV_RankSendReportsCBF( GR_STATUS* status, void* cbf_arg ) -{ - ranked_player_t* ranked_player; - GR_CONTEXT context; - GR_STATUS cleanup_status; - - assert( status != NULL ); - // NULL cbf_arg means server is sending match reports - - Com_DPrintf( "SV_RankSendReportsCBF( %08X, %08X );\n", status, cbf_arg ); - - ranked_player = (ranked_player_t*)cbf_arg; - if( ranked_player == NULL ) - { - Com_DPrintf( "SV_RankSendReportsCBF: server\n" ); - context = s_server_context; - } - else - { - Com_DPrintf( "SV_RankSendReportsCBF: player\n" ); - context = ranked_player->context; - } - - //assert( context != 0 ); - if( *status != GR_STATUS_OK ) - { - SV_RankError( "SV_RankSendReportsCBF: Unexpected status %s", - SV_RankStatusString( *status ) ); - } - - if( context == 0 ) - { - Com_DPrintf( "SV_RankSendReportsCBF: WARNING: context == 0" ); - SV_RankCloseContext( ranked_player ); - } - else - { - cleanup_status = GRankCleanupAsync - ( - context, - 0, - SV_RankCleanupCBF, - cbf_arg, - GR_OPT_END - ); - - if( cleanup_status != GR_STATUS_PENDING ) - { - SV_RankError( "SV_RankSendReportsCBF: Expected " - "GR_STATUS_PENDING from GRankCleanupAsync, got %s", - SV_RankStatusString( cleanup_status ) ); - SV_RankCloseContext( ranked_player ); - } - } -} - -/* -================ -SV_RankCleanupCBF -================ -*/ -static void SV_RankCleanupCBF( GR_STATUS* status, void* cbf_arg ) -{ - ranked_player_t* ranked_player; - ranked_player = (ranked_player_t*)cbf_arg; - - assert( status != NULL ); - // NULL cbf_arg means server is cleaning up - - Com_DPrintf( "SV_RankCleanupCBF( %08X, %08X );\n", status, cbf_arg ); - - if( *status != GR_STATUS_OK ) - { - SV_RankError( "SV_RankCleanupCBF: Unexpected status %s", - SV_RankStatusString( *status ) ); - } - - SV_RankCloseContext( ranked_player ); -} - -/* -================ -SV_RankCloseContext -================ -*/ -static void SV_RankCloseContext( ranked_player_t* ranked_player ) -{ - if( ranked_player == NULL ) - { - // server cleanup - if( s_server_context == 0 ) - { - return; - } - s_server_context = 0; - s_server_match = 0; - } - else - { - // player cleanup - if( s_ranked_players == NULL ) - { - return; - } - if( ranked_player->context == 0 ) - { - return; - } - ranked_player->context = 0; - ranked_player->match = 0; - ranked_player->player_id = 0; - memset( ranked_player->token, 0, sizeof(GR_PLAYER_TOKEN) ); - ranked_player->grank_status = ranked_player->final_status; - ranked_player->final_status = QGR_STATUS_NEW; - ranked_player->name[0] = '\0'; - } - - assert( s_rankings_contexts > 0 ); - s_rankings_contexts--; - Com_DPrintf( "SV_RankCloseContext: s_rankings_contexts = %d\n", - s_rankings_contexts ); - - if( s_rankings_contexts == 0 ) - { - GRankLogLevel( GRLOG_OFF ); - - if( s_ranked_players != NULL ) - { - Z_Free( s_ranked_players ); - s_ranked_players = NULL; - } - - s_rankings_active = qfalse; - Cvar_Set( "sv_rankingsActive", "0" ); - } -} - -/* -================ -SV_RankAsciiEncode - -Encodes src_len bytes of binary data from the src buffer as ASCII text, -using 6 bits per character. The result string is null-terminated and -stored in the dest buffer. - -The dest buffer must be at least (src_len * 4) / 3 + 2 bytes in length. - -Returns the length of the result string, not including the null. -================ -*/ -static int SV_RankAsciiEncode( char* dest, const unsigned char* src, - int src_len ) -{ - unsigned char bin[3]; - unsigned char txt[4]; - int dest_len = 0; - int i; - int j; - int num_chars; - - assert( dest != NULL ); - assert( src != NULL ); - - for( i = 0; i < src_len; i += 3 ) - { - // read three bytes of input - for( j = 0; j < 3; j++ ) - { - bin[j] = (i + j < src_len) ? src[i + j] : 0; - } - - // get four 6-bit values from three bytes - txt[0] = bin[0] >> 2; - txt[1] = ((bin[0] << 4) | (bin[1] >> 4)) & 63; - txt[2] = ((bin[1] << 2) | (bin[2] >> 6)) & 63; - txt[3] = bin[2] & 63; - - // store ASCII encoding of 6-bit values - num_chars = (i + 2 < src_len) ? 4 : ((src_len - i) * 4) / 3 + 1; - for( j = 0; j < num_chars; j++ ) - { - dest[dest_len++] = s_ascii_encoding[txt[j]]; - } - } - - dest[dest_len] = '\0'; - - return dest_len; -} - -/* -================ -SV_RankAsciiDecode - -Decodes src_len characters of ASCII text from the src buffer, stores -the binary result in the dest buffer. - -The dest buffer must be at least (src_len * 3) / 4 bytes in length. - -Returns the length of the binary result, or zero for invalid input. -================ -*/ -static int SV_RankAsciiDecode( unsigned char* dest, const char* src, - int src_len ) -{ - static unsigned char s_inverse_encoding[256]; - static char s_init = 0; - - unsigned char bin[3]; - unsigned char txt[4]; - int dest_len = 0; - int i; - int j; - int num_bytes; - - assert( dest != NULL ); - assert( src != NULL ); - - if( !s_init ) - { - // initialize lookup table for decoding - memset( s_inverse_encoding, 255, sizeof(s_inverse_encoding) ); - for( i = 0; i < 64; i++ ) - { - s_inverse_encoding[s_ascii_encoding[i]] = i; - } - s_init = 1; - } - - for( i = 0; i < src_len; i += 4 ) - { - // read four characters of input, decode them to 6-bit values - for( j = 0; j < 4; j++ ) - { - txt[j] = (i + j < src_len) ? s_inverse_encoding[src[i + j]] : 0; - if (txt[j] == 255) - { - return 0; // invalid input character - } - } - - // get three bytes from four 6-bit values - bin[0] = (txt[0] << 2) | (txt[1] >> 4); - bin[1] = (txt[1] << 4) | (txt[2] >> 2); - bin[2] = (txt[2] << 6) | txt[3]; - - // store binary data - num_bytes = (i + 3 < src_len) ? 3 : ((src_len - i) * 3) / 4; - for( j = 0; j < num_bytes; j++ ) - { - dest[dest_len++] = bin[j]; - } - } - - return dest_len; -} - -/* -================ -SV_RankEncodeGameID -================ -*/ -static void SV_RankEncodeGameID( uint64_t game_id, char* result, - int len ) -{ - assert( result != NULL ); - - if( len < ( ( sizeof(game_id) * 4) / 3 + 2) ) - { - Com_DPrintf( "SV_RankEncodeGameID: result buffer too small\n" ); - result[0] = '\0'; - } - else - { - qint64 gameid = LittleLong64(*(qint64*)&game_id); - SV_RankAsciiEncode( result, (unsigned char*)&gameid, - sizeof(qint64) ); - } -} - -/* -================ -SV_RankDecodePlayerID -================ -*/ -static uint64_t SV_RankDecodePlayerID( const char* string ) -{ - unsigned char buffer[9]; - int len; - qint64 player_id; - - assert( string != NULL ); - - len = strlen (string) ; - Com_DPrintf( "SV_RankDecodePlayerID: string length %d\n",len ); - SV_RankAsciiDecode( buffer, string, len ); - player_id = LittleLong64(*(qint64*)buffer); - return *(uint64_t*)&player_id; -} - -/* -================ -SV_RankDecodePlayerKey -================ -*/ -static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key ) -{ - unsigned char buffer[1400]; - int len; - assert( string != NULL ); - - len = strlen (string) ; - Com_DPrintf( "SV_RankDecodePlayerKey: string length %d\n",len ); - - memset(key,0,sizeof(GR_PLAYER_TOKEN)); - memset(buffer,0,sizeof(buffer)); - memcpy( key, buffer, SV_RankAsciiDecode( buffer, string, len ) ); -} - -/* -================ -SV_RankStatusString -================ -*/ -static char* SV_RankStatusString( GR_STATUS status ) -{ - switch( status ) - { - case GR_STATUS_OK: return "GR_STATUS_OK"; - case GR_STATUS_ERROR: return "GR_STATUS_ERROR"; - case GR_STATUS_BADPARAMS: return "GR_STATUS_BADPARAMS"; - case GR_STATUS_NETWORK: return "GR_STATUS_NETWORK"; - case GR_STATUS_NOUSER: return "GR_STATUS_NOUSER"; - case GR_STATUS_BADPASSWORD: return "GR_STATUS_BADPASSWORD"; - case GR_STATUS_BADGAME: return "GR_STATUS_BADGAME"; - case GR_STATUS_PENDING: return "GR_STATUS_PENDING"; - case GR_STATUS_BADDOMAIN: return "GR_STATUS_BADDOMAIN"; - case GR_STATUS_DOMAINLOCK: return "GR_STATUS_DOMAINLOCK"; - case GR_STATUS_TIMEOUT: return "GR_STATUS_TIMEOUT"; - case GR_STATUS_INVALIDUSER: return "GR_STATUS_INVALIDUSER"; - case GR_STATUS_INVALIDCONTEXT: return "GR_STATUS_INVALIDCONTEXT"; - default: return "(UNKNOWN)"; - } -} - -/* -================ -SV_RankError -================ -*/ -static void SV_RankError( const char* fmt, ... ) -{ - va_list arg_ptr; - char text[1024]; - - va_start( arg_ptr, fmt ); - vsprintf( text, fmt, arg_ptr ); - va_end( arg_ptr ); - - Com_DPrintf( "****************************************\n" ); - Com_DPrintf( "SV_RankError: %s\n", text ); - Com_DPrintf( "****************************************\n" ); - - s_rankings_active = qfalse; - Cvar_Set( "sv_rankingsActive", "0" ); - // FIXME - attempt clean shutdown? -} - +/* +=========================================================================== +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 +=========================================================================== +*/ +// sv_rankings.c -- global rankings interface + +#include "server.h" +#include "..\rankings\1.0\gr\grapi.h" +#include "..\rankings\1.0\gr\grlog.h" + +typedef struct +{ + GR_CONTEXT context; + uint64_t game_id; + uint64_t match; + uint64_t player_id; + GR_PLAYER_TOKEN token; + grank_status_t grank_status; + grank_status_t final_status; // status to set after cleanup + uint32_t grank; // global rank + char name[32]; +} ranked_player_t; + +static int s_rankings_contexts = 0; +static qboolean s_rankings_active = qfalse; +static GR_CONTEXT s_server_context = 0; +static uint64_t s_server_match = 0; +static char* s_rankings_game_key = NULL; +static uint64_t s_rankings_game_id = 0; +static ranked_player_t* s_ranked_players = NULL; +static qboolean s_server_quitting = qfalse; +static const char s_ascii_encoding[] = + "0123456789abcdef" + "ghijklmnopqrstuv" + "wxyzABCDEFGHIJKL" + "MNOPQRSTUVWXYZ[]"; + +// private functions +static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg ); +static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg ); +static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg ); +static void SV_RankSendReportsCBF( GR_STATUS* gr_status, void* cbf_arg ); +static void SV_RankCleanupCBF( GR_STATUS* gr_status, void* cbf_arg ); +static void SV_RankCloseContext( ranked_player_t* ranked_player ); +static int SV_RankAsciiEncode( char* dest, const unsigned char* src, + int src_len ); +static int SV_RankAsciiDecode( unsigned char* dest, const char* src, + int src_len ); +static void SV_RankEncodeGameID( uint64_t game_id, char* result, + int len ); +static uint64_t SV_RankDecodePlayerID( const char* string ); +static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key ); +static char* SV_RankStatusString( GR_STATUS status ); +static void SV_RankError( const char* fmt, ... ); +static char SV_RankGameKey[64]; + +/* +================ +SV_RankBegin +================ +*/ +void SV_RankBegin( char *gamekey ) +{ + GR_INIT init; + GR_STATUS status; + + assert( s_rankings_contexts == 0 ); + assert( !s_rankings_active ); + assert( s_ranked_players == NULL ); + + if( sv_enableRankings->integer == 0 || Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) + { + s_rankings_active = qfalse; + if( sv_rankingsActive->integer == 1 ) + { + Cvar_Set( "sv_rankingsActive", "0" ); + } + return; + } + + // only allow official game key on pure servers + if( strcmp(gamekey, GR_GAMEKEY) == 0 ) + { +/* + if( Cvar_VariableValue("sv_pure") != 1 ) + { + Cvar_Set( "sv_enableRankings", "0" ); + return; + } +*/ + + // substitute game-specific game key + switch( (int)Cvar_VariableValue("g_gametype") ) + { + case GT_FFA: + gamekey = "Q3 Free For All"; + break; + case GT_TOURNAMENT: + gamekey = "Q3 Tournament"; + break; + case GT_TEAM: + gamekey = "Q3 Team Deathmatch"; + break; + case GT_CTF: + gamekey = "Q3 Capture the Flag"; + break; + case GT_1FCTF: + gamekey = "Q3 One Flag CTF"; + break; + case GT_OBELISK: + gamekey = "Q3 Overload"; + break; + case GT_HARVESTER: + gamekey = "Q3 Harvester"; + break; + default: + break; + } + } + s_rankings_game_key = gamekey; + + // initialize rankings + GRankLogLevel( GRLOG_OFF ); + memset(SV_RankGameKey,0,sizeof(SV_RankGameKey)); + strncpy(SV_RankGameKey,gamekey,sizeof(SV_RankGameKey)-1); + init = GRankInit( 1, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); + s_server_context = init.context; + s_rankings_contexts++; + Com_DPrintf( "SV_RankBegin(); GR_GAMEKEY is %s\n", gamekey ); + Com_DPrintf( "SV_RankBegin(); s_rankings_contexts=%d\n",s_rankings_contexts ); + Com_DPrintf( "SV_RankBegin(); s_server_context=%d\n",init.context ); + + // new game + if(!strlen(Cvar_VariableString( "sv_leagueName" ))) + { + status = GRankNewGameAsync + ( + s_server_context, + SV_RankNewGameCBF, + NULL, + GR_OPT_LEAGUENAME, + (void*)(Cvar_VariableString( "sv_leagueName" )), + GR_OPT_END + ); + } + else + { + status = GRankNewGameAsync + ( + s_server_context, + SV_RankNewGameCBF, + NULL, + GR_OPT_END + ); + } + + if( status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankBegin: Expected GR_STATUS_PENDING, got %s", + SV_RankStatusString( status ) ); + return; + } + + // logging + if( com_developer->value ) + { + GRankLogLevel( GRLOG_TRACE ); + } + + // allocate rankings info for each player + s_ranked_players = Z_Malloc( sv_maxclients->value * + sizeof(ranked_player_t) ); + memset( (void*)s_ranked_players, 0 ,sv_maxclients->value + * sizeof(ranked_player_t)); +} + +/* +================ +SV_RankEnd +================ +*/ +void SV_RankEnd( void ) +{ + GR_STATUS status; + int i; + + Com_DPrintf( "SV_RankEnd();\n" ); + + if( !s_rankings_active ) + { + // cleanup after error during game + if( s_ranked_players != NULL ) + { + for( i = 0; i < sv_maxclients->value; i++ ) + { + if( s_ranked_players[i].context != 0 ) + { + SV_RankCloseContext( &(s_ranked_players[i]) ); + } + } + } + if( s_server_context != 0 ) + { + SV_RankCloseContext( NULL ); + } + + return; + } + + for( i = 0; i < sv_maxclients->value; i++ ) + { + if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE ) + { + SV_RankUserLogout( i ); + Com_DPrintf( "SV_RankEnd: SV_RankUserLogout %d\n",i ); + } + } + + assert( s_server_context != 0 ); + + // send match reports, proceed to SV_RankSendReportsCBF + status = GRankSendReportsAsync + ( + s_server_context, + 0, + SV_RankSendReportsCBF, + NULL, + GR_OPT_END + ); + + if( status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankEnd: Expected GR_STATUS_PENDING, got %s", + SV_RankStatusString( status ) ); + } + + s_rankings_active = qfalse; + Cvar_Set( "sv_rankingsActive", "0" ); +} + +/* +================ +SV_RankPoll +================ +*/ +void SV_RankPoll( void ) +{ + GRankPoll(); +} + +/* +================ +SV_RankCheckInit +================ +*/ +qboolean SV_RankCheckInit( void ) +{ + return (s_rankings_contexts > 0); +} + +/* +================ +SV_RankActive +================ +*/ +qboolean SV_RankActive( void ) +{ + return s_rankings_active; +} + +/* +================= +SV_RankUserStatus +================= +*/ +grank_status_t SV_RankUserStatus( int index ) +{ + if( !s_rankings_active ) + { + return GR_STATUS_ERROR; + } + + assert( s_ranked_players != NULL ); + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + + return s_ranked_players[index].grank_status; +} + +/* +================ +SV_RankUserGRank +================ +*/ +int SV_RankUserGrank( int index ) +{ + if( !s_rankings_active ) + { + return 0; + } + + assert( s_ranked_players != NULL ); + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + + return s_ranked_players[index].grank; +} + +/* +================ +SV_RankUserReset +================ +*/ +void SV_RankUserReset( int index ) +{ + if( !s_rankings_active ) + { + return; + } + + assert( s_ranked_players != NULL ); + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + + switch( s_ranked_players[index].grank_status ) + { + case QGR_STATUS_SPECTATOR: + case QGR_STATUS_NO_USER: + case QGR_STATUS_BAD_PASSWORD: + case QGR_STATUS_USER_EXISTS: + case QGR_STATUS_NO_MEMBERSHIP: + case QGR_STATUS_TIMEOUT: + case QGR_STATUS_ERROR: + s_ranked_players[index].grank_status = QGR_STATUS_NEW; + break; + default: + break; + } +} + +/* +================ +SV_RankUserSpectate +================ +*/ +void SV_RankUserSpectate( int index ) +{ + if( !s_rankings_active ) + { + return; + } + + assert( s_ranked_players != NULL ); + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + + // GRANK_FIXME - check current status? + s_ranked_players[index].grank_status = QGR_STATUS_SPECTATOR; +} + +/* +================ +SV_RankUserCreate +================ +*/ +void SV_RankUserCreate( int index, char* username, char* password, + char* email ) +{ + GR_INIT init; + GR_STATUS status; + + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + assert( username != NULL ); + assert( password != NULL ); + assert( email != NULL ); + assert( s_ranked_players ); + assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); + + Com_DPrintf( "SV_RankUserCreate( %d, %s, \"****\", %s );\n", index, + username, email ); + + if( !s_rankings_active ) + { + Com_DPrintf( "SV_RankUserCreate: Not ready to create\n" ); + s_ranked_players[index].grank_status = QGR_STATUS_ERROR; + return; + } + + if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE ) + { + Com_DPrintf( "SV_RankUserCreate: Got Create from active player\n" ); + return; + } + + // get a separate context for the new user + init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); + s_ranked_players[index].context = init.context; + s_rankings_contexts++; + Com_DPrintf( "SV_RankUserCreate(); s_rankings_contexts=%d\n",s_rankings_contexts ); + Com_DPrintf( "SV_RankUserCreate(); s_ranked_players[%d].context=%d\n",index,init.context ); + + // attempt to create a new account, proceed to SV_RankUserCBF + status = GRankUserCreateAsync + ( + s_ranked_players[index].context, + username, + password, + email, + SV_RankUserCBF, + (void*)&s_ranked_players[index], + GR_OPT_END + ); + + if( status == GR_STATUS_PENDING ) + { + s_ranked_players[index].grank_status = QGR_STATUS_PENDING; + s_ranked_players[index].final_status = QGR_STATUS_NEW; + } + else + { + SV_RankError( "SV_RankUserCreate: Expected GR_STATUS_PENDING, got %s", + SV_RankStatusString( status ) ); + } +} + +/* +================ +SV_RankUserLogin +================ +*/ +void SV_RankUserLogin( int index, char* username, char* password ) +{ + GR_INIT init; + GR_STATUS status; + + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + assert( username != NULL ); + assert( password != NULL ); + assert( s_ranked_players ); + assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); + + Com_DPrintf( "SV_RankUserLogin( %d, %s, \"****\" );\n", index, username ); + + if( !s_rankings_active ) + { + Com_DPrintf( "SV_RankUserLogin: Not ready for login\n" ); + s_ranked_players[index].grank_status = QGR_STATUS_ERROR; + return; + } + + if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE ) + { + Com_DPrintf( "SV_RankUserLogin: Got Login from active player\n" ); + return; + } + + // get a separate context for the new user + init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); + s_ranked_players[index].context = init.context; + s_rankings_contexts++; + Com_DPrintf( "SV_RankUserLogin(); s_rankings_contexts=%d\n",s_rankings_contexts ); + Com_DPrintf( "SV_RankUserLogin(); s_ranked_players[%d].context=%d\n",index,init.context ); + + // login user, proceed to SV_RankUserCBF + status = GRankUserLoginAsync + ( + s_ranked_players[index].context, + username, + password, + SV_RankUserCBF, + (void*)&s_ranked_players[index], + GR_OPT_END + ); + + if( status == GR_STATUS_PENDING ) + { + s_ranked_players[index].grank_status = QGR_STATUS_PENDING; + s_ranked_players[index].final_status = QGR_STATUS_NEW; + } + else + { + SV_RankError( "SV_RankUserLogin: Expected GR_STATUS_PENDING, got %s", + SV_RankStatusString( status ) ); + } +} + +/* +=================== +SV_RankUserValidate +=================== +*/ +qboolean SV_RankUserValidate( int index, const char* player_id, const char* key, int token_len, int rank, char* name ) +{ + GR_INIT init; + GR_STATUS status; + qboolean rVal; + ranked_player_t* ranked_player; + int i; + + assert( s_ranked_players ); + assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); + + rVal = qfalse; + + if( !s_rankings_active ) + { + Com_DPrintf( "SV_RankUserValidate: Not ready to validate\n" ); + s_ranked_players[index].grank_status = QGR_STATUS_ERROR; + return rVal; + } + + ranked_player = &(s_ranked_players[index]); + + if ( (player_id != NULL) && (key != NULL)) + { + // the real player_id and key is set when SV_RankJoinGameCBF + // is called we do this so that SV_RankUserValidate + // can be shared by both server side login and client side login + + // for client side logined in players + // server is creating GR_OPT_PLAYERCONTEXT + init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); + ranked_player->context = init.context; + s_rankings_contexts++; + Com_DPrintf( "SV_RankUserValidate(); s_rankings_contexts=%d\n",s_rankings_contexts ); + Com_DPrintf( "SV_RankUserValidate(); s_ranked_players[%d].context=%d\n",index,init.context ); + + // uudecode player id and player token + ranked_player->player_id = SV_RankDecodePlayerID(player_id); + Com_DPrintf( "SV_RankUserValidate(); ranked_player->player_id =%u\n", (uint32_t)ranked_player->player_id ); + SV_RankDecodePlayerKey(key, ranked_player->token); + + // save name and check for duplicates + Q_strncpyz( ranked_player->name, name, sizeof(ranked_player->name) ); + for( i = 0; i < sv_maxclients->value; i++ ) + { + if( (i != index) && (s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE) && + (strcmp( s_ranked_players[i].name, name ) == 0) ) + { + Com_DPrintf( "SV_RankUserValidate: Duplicate login\n" ); + ranked_player->grank_status = QGR_STATUS_NO_USER; + ranked_player->final_status = QGR_STATUS_NEW; + ranked_player->grank = 0; + return qfalse; + } + } + + // then validate + status = GRankPlayerValidate( + s_server_context, + ranked_player->player_id, + ranked_player->token, + token_len, + GR_OPT_PLAYERCONTEXT, + ranked_player->context, + GR_OPT_END); + } + else + { + // make server side login (bots) happy + status = GR_STATUS_OK; + } + + if (status == GR_STATUS_OK) + { + ranked_player->grank_status = QGR_STATUS_ACTIVE; + ranked_player->final_status = QGR_STATUS_NEW; + ranked_player->grank = rank; + rVal = qtrue; + } + else if (status == GR_STATUS_INVALIDUSER) + { + ranked_player->grank_status = QGR_STATUS_INVALIDUSER; + ranked_player->final_status = QGR_STATUS_NEW; + ranked_player->grank = 0; + rVal = qfalse; + } + else + { + SV_RankError( "SV_RankUserValidate: Unexpected status %s", + SV_RankStatusString( status ) ); + s_ranked_players[index].grank_status = QGR_STATUS_ERROR; + ranked_player->grank = 0; + } + + return rVal; +} + +/* +================ +SV_RankUserLogout +================ +*/ +void SV_RankUserLogout( int index ) +{ + GR_STATUS status; + GR_STATUS cleanup_status; + + if( !s_rankings_active ) + { + return; + } + + assert( index >= 0 ); + assert( index < sv_maxclients->value ); + assert( s_ranked_players ); + + if( s_ranked_players[index].context == 0 ) { + return; + } + + Com_DPrintf( "SV_RankUserLogout( %d );\n", index ); + + // masqueraded player may not be active yet, if they fail validation, + // but still they have a context needs to be cleaned + // what matters is the s_ranked_players[index].context + + // send reports, proceed to SV_RankSendReportsCBF + status = GRankSendReportsAsync + ( + s_ranked_players[index].context, + 0, + SV_RankSendReportsCBF, + (void*)&s_ranked_players[index], + GR_OPT_END + ); + + if( status == GR_STATUS_PENDING ) + { + s_ranked_players[index].grank_status = QGR_STATUS_PENDING; + s_ranked_players[index].final_status = QGR_STATUS_NEW; + } + else + { + SV_RankError( "SV_RankUserLogout: Expected GR_STATUS_PENDING, got %s", + SV_RankStatusString( status ) ); + + cleanup_status = GRankCleanupAsync + ( + s_ranked_players[index].context, + 0, + SV_RankCleanupCBF, + (void*)&s_ranked_players[index], + GR_OPT_END + ); + + if( cleanup_status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankUserLogout: Expected " + "GR_STATUS_PENDING from GRankCleanupAsync, got %s", + SV_RankStatusString( cleanup_status ) ); + SV_RankCloseContext( &(s_ranked_players[index]) ); + } + } +} + +/* +================ +SV_RankReportInt +================ +*/ +void SV_RankReportInt( int index1, int index2, int key, int value, + qboolean accum ) +{ + GR_STATUS status; + GR_CONTEXT context; + uint64_t match; + uint64_t user1; + uint64_t user2; + int opt_accum; + + if( !s_rankings_active ) + { + return; + } + + assert( index1 >= -1 ); + assert( index1 < sv_maxclients->value ); + assert( index2 >= -1 ); + assert( index2 < sv_maxclients->value ); + assert( s_ranked_players ); + +// Com_DPrintf( "SV_RankReportInt( %d, %d, %d, %d, %d );\n", index1, index2, +// key, value, accum ); + + // get context, match, and player_id for player index1 + if( index1 == -1 ) + { + context = s_server_context; + match = s_server_match; + user1 = 0; + } + else + { + if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE ) + { + Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE" + " Got Unexpected status %d for player %d\n", + s_ranked_players[index1].grank_status, index1 ); + return; + } + + context = s_ranked_players[index1].context; + match = s_ranked_players[index1].match; + user1 = s_ranked_players[index1].player_id; + } + + // get player_id for player index2 + if( index2 == -1 ) + { + user2 = 0; + } + else + { + if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE ) + { + Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE" + " Got Unexpected status %d for player %d\n", + s_ranked_players[index2].grank_status, index2 ); + return; + } + + user2 = s_ranked_players[index2].player_id; + } + + opt_accum = accum ? GR_OPT_ACCUM : GR_OPT_END; + + status = GRankReportInt + ( + context, + match, + user1, + user2, + key, + value, + opt_accum, + GR_OPT_END + ); + + if( status != GR_STATUS_OK ) + { + SV_RankError( "SV_RankReportInt: Unexpected status %s", + SV_RankStatusString( status ) ); + } + + if( user2 != 0 ) + { + context = s_ranked_players[index2].context; + match = s_ranked_players[index2].match; + + status = GRankReportInt + ( + context, + match, + user1, + user2, + key, + value, + opt_accum, + GR_OPT_END + ); + + if( status != GR_STATUS_OK ) + { + SV_RankError( "SV_RankReportInt: Unexpected status %s", + SV_RankStatusString( status ) ); + } + } +} + +/* +================ +SV_RankReportStr +================ +*/ +void SV_RankReportStr( int index1, int index2, int key, char* value ) +{ + GR_STATUS status; + GR_CONTEXT context; + uint64_t match; + uint64_t user1; + uint64_t user2; + + if( !s_rankings_active ) + { + return; + } + + assert( index1 >= -1 ); + assert( index1 < sv_maxclients->value ); + assert( index2 >= -1 ); + assert( index2 < sv_maxclients->value ); + assert( s_ranked_players ); + +// Com_DPrintf( "SV_RankReportStr( %d, %d, %d, \"%s\" );\n", index1, index2, +// key, value ); + + // get context, match, and player_id for player index1 + if( index1 == -1 ) + { + context = s_server_context; + match = s_server_match; + user1 = 0; + } + else + { + if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE ) + { + Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", + s_ranked_players[index1].grank_status ); + return; + } + + context = s_ranked_players[index1].context; + match = s_ranked_players[index1].match; + user1 = s_ranked_players[index1].player_id; + } + + // get player_id for player index2 + if( index2 == -1 ) + { + user2 = 0; + } + else + { + if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE ) + { + Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", + s_ranked_players[index2].grank_status ); + return; + } + + user2 = s_ranked_players[index2].player_id; + } + + status = GRankReportStr + ( + context, + match, + user1, + user2, + key, + value, + GR_OPT_END + ); + + if( status != GR_STATUS_OK ) + { + SV_RankError( "SV_RankReportStr: Unexpected status %s", + SV_RankStatusString( status ) ); + } + + if( user2 != 0 ) + { + context = s_ranked_players[index2].context; + match = s_ranked_players[index2].match; + + status = GRankReportStr + ( + context, + match, + user1, + user2, + key, + value, + GR_OPT_END + ); + + if( status != GR_STATUS_OK ) + { + SV_RankError( "SV_RankReportInt: Unexpected status %s", + SV_RankStatusString( status ) ); + } + } +} + +/* +================ +SV_RankQuit +================ +*/ +void SV_RankQuit( void ) +{ + int i; + int j = 0; + // yuck + + while( s_rankings_contexts > 1 ) + { + assert(s_ranked_players); + if( s_ranked_players != NULL ) + { + for( i = 0; i < sv_maxclients->value; i++ ) + { + // check for players that weren't yet active in SV_RankEnd + if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE ) + { + SV_RankUserLogout( i ); + Com_DPrintf( "SV_RankQuit: SV_RankUserLogout %d\n",i ); + } + else + { + if( s_ranked_players[i].context ) + { + GR_STATUS cleanup_status; + cleanup_status = GRankCleanupAsync + ( + s_ranked_players[i].context, + 0, + SV_RankCleanupCBF, + (void*)&(s_ranked_players[i]), + GR_OPT_END + ); + + if( cleanup_status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankQuit: Expected " + "GR_STATUS_PENDING from GRankCleanupAsync, got %s", + SV_RankStatusString( cleanup_status ) ); + } + } + } + } + } + SV_RankPoll(); + + // should've finished by now + assert( (j++) < 68 ); + } +} + +/* +============================================================================== + +Private Functions + +============================================================================== +*/ + +/* +================= +SV_RankNewGameCBF +================= +*/ +static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg ) +{ + GR_MATCH match; + int i; + + assert( gr_newgame != NULL ); + assert( cbf_arg == NULL ); + + Com_DPrintf( "SV_RankNewGameCBF( %08X, %08X );\n", gr_newgame, cbf_arg ); + + if( gr_newgame->status == GR_STATUS_OK ) + { + char info[MAX_INFO_STRING]; + char gameid[sizeof(s_ranked_players[i].game_id) * 4 / 3 + 2]; + + // save game id + s_rankings_game_id = gr_newgame->game_id; + + // encode gameid + memset(gameid,0,sizeof(gameid)); + SV_RankEncodeGameID(s_rankings_game_id,gameid,sizeof(gameid)); + + // set CS_GRANK rankingsGameID to pass to client + memset(info,0,sizeof(info)); + Info_SetValueForKey( info, "rankingsGameKey", s_rankings_game_key ); + Info_SetValueForKey( info, "rankingsGameID", gameid ); + SV_SetConfigstring( CS_GRANK, info ); + + // initialize client status + for( i = 0; i < sv_maxclients->value; i++ ) + s_ranked_players[i].grank_status = QGR_STATUS_NEW; + + // start new match + match = GRankStartMatch( s_server_context ); + s_server_match = match.match; + + // ready to go + s_rankings_active = qtrue; + Cvar_Set( "sv_rankingsActive", "1" ); + + } + else if( gr_newgame->status == GR_STATUS_BADLEAGUE ) + { + SV_RankError( "SV_RankNewGameCBF: Invalid League name\n" ); + } + else + { + //GRank handle new game failure + // force SV_RankEnd() to run + //SV_RankEnd(); + SV_RankError( "SV_RankNewGameCBF: Unexpected status %s", + SV_RankStatusString( gr_newgame->status ) ); + } +} + +/* +================ +SV_RankUserCBF +================ +*/ +static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg ) +{ + ranked_player_t* ranked_player; + GR_STATUS join_status; + GR_STATUS cleanup_status; + + assert( gr_login != NULL ); + assert( cbf_arg != NULL ); + + Com_DPrintf( "SV_RankUserCBF( %08X, %08X );\n", gr_login, cbf_arg ); + + ranked_player = (ranked_player_t*)cbf_arg; + assert(ranked_player); + assert( ranked_player->context ); + + switch( gr_login->status ) + { + case GR_STATUS_OK: + // attempt to join the game, proceed to SV_RankJoinGameCBF + join_status = GRankJoinGameAsync + ( + ranked_player->context, + s_rankings_game_id, + SV_RankJoinGameCBF, + cbf_arg, + GR_OPT_END + ); + + if( join_status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING " + "from GRankJoinGameAsync, got %s", + SV_RankStatusString( join_status ) ); + } + break; + case GR_STATUS_NOUSER: + Com_DPrintf( "SV_RankUserCBF: Got status %s\n", + SV_RankStatusString( gr_login->status ) ); + ranked_player->final_status = QGR_STATUS_NO_USER; + break; + case GR_STATUS_BADPASSWORD: + Com_DPrintf( "SV_RankUserCBF: Got status %s\n", + SV_RankStatusString( gr_login->status ) ); + ranked_player->final_status = QGR_STATUS_BAD_PASSWORD; + break; + case GR_STATUS_TIMEOUT: + Com_DPrintf( "SV_RankUserCBF: Got status %s\n", + SV_RankStatusString( gr_login->status ) ); + ranked_player->final_status = QGR_STATUS_TIMEOUT; + break; + default: + Com_DPrintf( "SV_RankUserCBF: Unexpected status %s\n", + SV_RankStatusString( gr_login->status ) ); + ranked_player->final_status = QGR_STATUS_ERROR; + break; + } + + if( ranked_player->final_status != QGR_STATUS_NEW ) + { + // login or create failed, so clean up before the next attempt + cleanup_status = GRankCleanupAsync + ( + ranked_player->context, + 0, + SV_RankCleanupCBF, + (void*)ranked_player, + GR_OPT_END + ); + + if( cleanup_status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING " + "from GRankCleanupAsync, got %s", + SV_RankStatusString( cleanup_status ) ); + SV_RankCloseContext( ranked_player ); + } + } +} + +/* +================ +SV_RankJoinGameCBF +================ +*/ +static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg ) +{ + ranked_player_t* ranked_player; + GR_MATCH match; + GR_STATUS cleanup_status; + + assert( gr_joingame != NULL ); + assert( cbf_arg != NULL ); + + Com_DPrintf( "SV_RankJoinGameCBF( %08X, %08X );\n", gr_joingame, cbf_arg ); + + ranked_player = (ranked_player_t*)cbf_arg; + + assert( ranked_player ); + assert( ranked_player->context != 0 ); + + if( gr_joingame->status == GR_STATUS_OK ) + { + int i; + // save user id + ranked_player->player_id = gr_joingame->player_id; + memcpy(ranked_player->token,gr_joingame->token, + sizeof(GR_PLAYER_TOKEN)) ; + match = GRankStartMatch( ranked_player->context ); + ranked_player->match = match.match; + ranked_player->grank = gr_joingame->rank; + + // find the index and call SV_RankUserValidate + for (i=0;ivalue;i++) + if ( ranked_player == &s_ranked_players[i] ) + SV_RankUserValidate(i,NULL,NULL,0, gr_joingame->rank,ranked_player->name); + } + else + { + //GRand handle join game failure + SV_RankError( "SV_RankJoinGameCBF: Unexpected status %s", + SV_RankStatusString( gr_joingame->status ) ); + + cleanup_status = GRankCleanupAsync + ( + ranked_player->context, + 0, + SV_RankCleanupCBF, + cbf_arg, + GR_OPT_END + ); + + if( cleanup_status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankJoinGameCBF: Expected " + "GR_STATUS_PENDING from GRankCleanupAsync, got %s", + SV_RankStatusString( cleanup_status ) ); + SV_RankCloseContext( ranked_player ); + } + } +} + +/* +================ +SV_RankSendReportsCBF +================ +*/ +static void SV_RankSendReportsCBF( GR_STATUS* status, void* cbf_arg ) +{ + ranked_player_t* ranked_player; + GR_CONTEXT context; + GR_STATUS cleanup_status; + + assert( status != NULL ); + // NULL cbf_arg means server is sending match reports + + Com_DPrintf( "SV_RankSendReportsCBF( %08X, %08X );\n", status, cbf_arg ); + + ranked_player = (ranked_player_t*)cbf_arg; + if( ranked_player == NULL ) + { + Com_DPrintf( "SV_RankSendReportsCBF: server\n" ); + context = s_server_context; + } + else + { + Com_DPrintf( "SV_RankSendReportsCBF: player\n" ); + context = ranked_player->context; + } + + //assert( context != 0 ); + if( *status != GR_STATUS_OK ) + { + SV_RankError( "SV_RankSendReportsCBF: Unexpected status %s", + SV_RankStatusString( *status ) ); + } + + if( context == 0 ) + { + Com_DPrintf( "SV_RankSendReportsCBF: WARNING: context == 0" ); + SV_RankCloseContext( ranked_player ); + } + else + { + cleanup_status = GRankCleanupAsync + ( + context, + 0, + SV_RankCleanupCBF, + cbf_arg, + GR_OPT_END + ); + + if( cleanup_status != GR_STATUS_PENDING ) + { + SV_RankError( "SV_RankSendReportsCBF: Expected " + "GR_STATUS_PENDING from GRankCleanupAsync, got %s", + SV_RankStatusString( cleanup_status ) ); + SV_RankCloseContext( ranked_player ); + } + } +} + +/* +================ +SV_RankCleanupCBF +================ +*/ +static void SV_RankCleanupCBF( GR_STATUS* status, void* cbf_arg ) +{ + ranked_player_t* ranked_player; + ranked_player = (ranked_player_t*)cbf_arg; + + assert( status != NULL ); + // NULL cbf_arg means server is cleaning up + + Com_DPrintf( "SV_RankCleanupCBF( %08X, %08X );\n", status, cbf_arg ); + + if( *status != GR_STATUS_OK ) + { + SV_RankError( "SV_RankCleanupCBF: Unexpected status %s", + SV_RankStatusString( *status ) ); + } + + SV_RankCloseContext( ranked_player ); +} + +/* +================ +SV_RankCloseContext +================ +*/ +static void SV_RankCloseContext( ranked_player_t* ranked_player ) +{ + if( ranked_player == NULL ) + { + // server cleanup + if( s_server_context == 0 ) + { + return; + } + s_server_context = 0; + s_server_match = 0; + } + else + { + // player cleanup + if( s_ranked_players == NULL ) + { + return; + } + if( ranked_player->context == 0 ) + { + return; + } + ranked_player->context = 0; + ranked_player->match = 0; + ranked_player->player_id = 0; + memset( ranked_player->token, 0, sizeof(GR_PLAYER_TOKEN) ); + ranked_player->grank_status = ranked_player->final_status; + ranked_player->final_status = QGR_STATUS_NEW; + ranked_player->name[0] = '\0'; + } + + assert( s_rankings_contexts > 0 ); + s_rankings_contexts--; + Com_DPrintf( "SV_RankCloseContext: s_rankings_contexts = %d\n", + s_rankings_contexts ); + + if( s_rankings_contexts == 0 ) + { + GRankLogLevel( GRLOG_OFF ); + + if( s_ranked_players != NULL ) + { + Z_Free( s_ranked_players ); + s_ranked_players = NULL; + } + + s_rankings_active = qfalse; + Cvar_Set( "sv_rankingsActive", "0" ); + } +} + +/* +================ +SV_RankAsciiEncode + +Encodes src_len bytes of binary data from the src buffer as ASCII text, +using 6 bits per character. The result string is null-terminated and +stored in the dest buffer. + +The dest buffer must be at least (src_len * 4) / 3 + 2 bytes in length. + +Returns the length of the result string, not including the null. +================ +*/ +static int SV_RankAsciiEncode( char* dest, const unsigned char* src, + int src_len ) +{ + unsigned char bin[3]; + unsigned char txt[4]; + int dest_len = 0; + int i; + int j; + int num_chars; + + assert( dest != NULL ); + assert( src != NULL ); + + for( i = 0; i < src_len; i += 3 ) + { + // read three bytes of input + for( j = 0; j < 3; j++ ) + { + bin[j] = (i + j < src_len) ? src[i + j] : 0; + } + + // get four 6-bit values from three bytes + txt[0] = bin[0] >> 2; + txt[1] = ((bin[0] << 4) | (bin[1] >> 4)) & 63; + txt[2] = ((bin[1] << 2) | (bin[2] >> 6)) & 63; + txt[3] = bin[2] & 63; + + // store ASCII encoding of 6-bit values + num_chars = (i + 2 < src_len) ? 4 : ((src_len - i) * 4) / 3 + 1; + for( j = 0; j < num_chars; j++ ) + { + dest[dest_len++] = s_ascii_encoding[txt[j]]; + } + } + + dest[dest_len] = '\0'; + + return dest_len; +} + +/* +================ +SV_RankAsciiDecode + +Decodes src_len characters of ASCII text from the src buffer, stores +the binary result in the dest buffer. + +The dest buffer must be at least (src_len * 3) / 4 bytes in length. + +Returns the length of the binary result, or zero for invalid input. +================ +*/ +static int SV_RankAsciiDecode( unsigned char* dest, const char* src, + int src_len ) +{ + static unsigned char s_inverse_encoding[256]; + static char s_init = 0; + + unsigned char bin[3]; + unsigned char txt[4]; + int dest_len = 0; + int i; + int j; + int num_bytes; + + assert( dest != NULL ); + assert( src != NULL ); + + if( !s_init ) + { + // initialize lookup table for decoding + memset( s_inverse_encoding, 255, sizeof(s_inverse_encoding) ); + for( i = 0; i < 64; i++ ) + { + s_inverse_encoding[s_ascii_encoding[i]] = i; + } + s_init = 1; + } + + for( i = 0; i < src_len; i += 4 ) + { + // read four characters of input, decode them to 6-bit values + for( j = 0; j < 4; j++ ) + { + txt[j] = (i + j < src_len) ? s_inverse_encoding[src[i + j]] : 0; + if (txt[j] == 255) + { + return 0; // invalid input character + } + } + + // get three bytes from four 6-bit values + bin[0] = (txt[0] << 2) | (txt[1] >> 4); + bin[1] = (txt[1] << 4) | (txt[2] >> 2); + bin[2] = (txt[2] << 6) | txt[3]; + + // store binary data + num_bytes = (i + 3 < src_len) ? 3 : ((src_len - i) * 3) / 4; + for( j = 0; j < num_bytes; j++ ) + { + dest[dest_len++] = bin[j]; + } + } + + return dest_len; +} + +/* +================ +SV_RankEncodeGameID +================ +*/ +static void SV_RankEncodeGameID( uint64_t game_id, char* result, + int len ) +{ + assert( result != NULL ); + + if( len < ( ( sizeof(game_id) * 4) / 3 + 2) ) + { + Com_DPrintf( "SV_RankEncodeGameID: result buffer too small\n" ); + result[0] = '\0'; + } + else + { + qint64 gameid = LittleLong64(*(qint64*)&game_id); + SV_RankAsciiEncode( result, (unsigned char*)&gameid, + sizeof(qint64) ); + } +} + +/* +================ +SV_RankDecodePlayerID +================ +*/ +static uint64_t SV_RankDecodePlayerID( const char* string ) +{ + unsigned char buffer[9]; + int len; + qint64 player_id; + + assert( string != NULL ); + + len = strlen (string) ; + Com_DPrintf( "SV_RankDecodePlayerID: string length %d\n",len ); + SV_RankAsciiDecode( buffer, string, len ); + player_id = LittleLong64(*(qint64*)buffer); + return *(uint64_t*)&player_id; +} + +/* +================ +SV_RankDecodePlayerKey +================ +*/ +static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key ) +{ + unsigned char buffer[1400]; + int len; + assert( string != NULL ); + + len = strlen (string) ; + Com_DPrintf( "SV_RankDecodePlayerKey: string length %d\n",len ); + + memset(key,0,sizeof(GR_PLAYER_TOKEN)); + memset(buffer,0,sizeof(buffer)); + memcpy( key, buffer, SV_RankAsciiDecode( buffer, string, len ) ); +} + +/* +================ +SV_RankStatusString +================ +*/ +static char* SV_RankStatusString( GR_STATUS status ) +{ + switch( status ) + { + case GR_STATUS_OK: return "GR_STATUS_OK"; + case GR_STATUS_ERROR: return "GR_STATUS_ERROR"; + case GR_STATUS_BADPARAMS: return "GR_STATUS_BADPARAMS"; + case GR_STATUS_NETWORK: return "GR_STATUS_NETWORK"; + case GR_STATUS_NOUSER: return "GR_STATUS_NOUSER"; + case GR_STATUS_BADPASSWORD: return "GR_STATUS_BADPASSWORD"; + case GR_STATUS_BADGAME: return "GR_STATUS_BADGAME"; + case GR_STATUS_PENDING: return "GR_STATUS_PENDING"; + case GR_STATUS_BADDOMAIN: return "GR_STATUS_BADDOMAIN"; + case GR_STATUS_DOMAINLOCK: return "GR_STATUS_DOMAINLOCK"; + case GR_STATUS_TIMEOUT: return "GR_STATUS_TIMEOUT"; + case GR_STATUS_INVALIDUSER: return "GR_STATUS_INVALIDUSER"; + case GR_STATUS_INVALIDCONTEXT: return "GR_STATUS_INVALIDCONTEXT"; + default: return "(UNKNOWN)"; + } +} + +/* +================ +SV_RankError +================ +*/ +static void SV_RankError( const char* fmt, ... ) +{ + va_list arg_ptr; + char text[1024]; + + va_start( arg_ptr, fmt ); + vsprintf( text, fmt, arg_ptr ); + va_end( arg_ptr ); + + Com_DPrintf( "****************************************\n" ); + Com_DPrintf( "SV_RankError: %s\n", text ); + Com_DPrintf( "****************************************\n" ); + + s_rankings_active = qfalse; + Cvar_Set( "sv_rankingsActive", "0" ); + // FIXME - attempt clean shutdown? +} + diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index 59910c7..e275cf4 100755 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -1,682 +1,682 @@ -/* -=========================================================================== -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" - - -/* -============================================================================= - -Delta encode a client frame onto the network channel - -A normal server packet will look like: - -4 sequence number (high bit set if an oversize fragment) - -1 svc_snapshot -4 last client reliable command -4 serverTime -1 lastframe for delta compression -1 snapFlags -1 areaBytes - - - - -============================================================================= -*/ - -/* -============= -SV_EmitPacketEntities - -Writes a delta update of an entityState_t list to the message. -============= -*/ -static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) { - entityState_t *oldent, *newent; - int oldindex, newindex; - int oldnum, newnum; - int from_num_entities; - - // generate the delta update - if ( !from ) { - from_num_entities = 0; - } else { - from_num_entities = from->num_entities; - } - - newent = NULL; - oldent = NULL; - newindex = 0; - oldindex = 0; - while ( newindex < to->num_entities || oldindex < from_num_entities ) { - if ( newindex >= to->num_entities ) { - newnum = 9999; - } else { - newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities]; - newnum = newent->number; - } - - if ( oldindex >= from_num_entities ) { - oldnum = 9999; - } else { - oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities]; - oldnum = oldent->number; - } - - if ( newnum == oldnum ) { - // delta update from old position - // because the force parm is qfalse, this will not result - // in any bytes being emited if the entity has not changed at all - MSG_WriteDeltaEntity (msg, oldent, newent, qfalse ); - oldindex++; - newindex++; - continue; - } - - if ( newnum < oldnum ) { - // this is a new entity, send it from the baseline - MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue ); - newindex++; - continue; - } - - if ( newnum > oldnum ) { - // the old entity isn't present in the new message - MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue ); - oldindex++; - continue; - } - } - - MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities -} - - - -/* -================== -SV_WriteSnapshotToClient -================== -*/ -static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { - clientSnapshot_t *frame, *oldframe; - int lastframe; - int i; - int snapFlags; - - // this is the snapshot we are creating - frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; - - // try to use a previous frame as the source for delta compressing the snapshot - if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { - // client is asking for a retransmit - oldframe = NULL; - lastframe = 0; - } else if ( client->netchan.outgoingSequence - client->deltaMessage - >= (PACKET_BACKUP - 3) ) { - // client hasn't gotten a good message through in a long time - Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); - oldframe = NULL; - lastframe = 0; - } else { - // we have a valid snapshot to delta from - oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; - lastframe = client->netchan.outgoingSequence - client->deltaMessage; - - // the snapshot's entities may still have rolled off the buffer, though - if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { - Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); - oldframe = NULL; - lastframe = 0; - } - } - - MSG_WriteByte (msg, svc_snapshot); - - // NOTE, MRE: now sent at the start of every message from server to client - // let the client know which reliable clientCommands we have received - //MSG_WriteLong( msg, client->lastClientCommand ); - - // send over the current server time so the client can drift - // its view of time to try to match - MSG_WriteLong (msg, svs.time); - - // what we are delta'ing from - MSG_WriteByte (msg, lastframe); - - snapFlags = svs.snapFlagServerBit; - if ( client->rateDelayed ) { - snapFlags |= SNAPFLAG_RATE_DELAYED; - } - if ( client->state != CS_ACTIVE ) { - snapFlags |= SNAPFLAG_NOT_ACTIVE; - } - - MSG_WriteByte (msg, snapFlags); - - // send over the areabits - MSG_WriteByte (msg, frame->areabytes); - MSG_WriteData (msg, frame->areabits, frame->areabytes); - - // delta encode the playerstate - if ( oldframe ) { - MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); - } else { - MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); - } - - // delta encode the entities - SV_EmitPacketEntities (oldframe, frame, msg); - - // padding for rate debugging - if ( sv_padPackets->integer ) { - for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { - MSG_WriteByte (msg, svc_nop); - } - } -} - - -/* -================== -SV_UpdateServerCommandsToClient - -(re)send all server commands the client hasn't acknowledged yet -================== -*/ -void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) { - int i; - - // write any unacknowledged serverCommands - for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { - MSG_WriteByte( msg, svc_serverCommand ); - MSG_WriteLong( msg, i ); - MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); - } - client->reliableSent = client->reliableSequence; -} - -/* -============================================================================= - -Build a client snapshot structure - -============================================================================= -*/ - -#define MAX_SNAPSHOT_ENTITIES 1024 -typedef struct { - int numSnapshotEntities; - int snapshotEntities[MAX_SNAPSHOT_ENTITIES]; -} snapshotEntityNumbers_t; - -/* -======================= -SV_QsortEntityNumbers -======================= -*/ -static int QDECL SV_QsortEntityNumbers( const void *a, const void *b ) { - int *ea, *eb; - - ea = (int *)a; - eb = (int *)b; - - if ( *ea == *eb ) { - Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" ); - } - - if ( *ea < *eb ) { - return -1; - } - - return 1; -} - - -/* -=============== -SV_AddEntToSnapshot -=============== -*/ -static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums ) { - // if we have already added this entity to this snapshot, don't add again - if ( svEnt->snapshotCounter == sv.snapshotCounter ) { - return; - } - svEnt->snapshotCounter = sv.snapshotCounter; - - // if we are full, silently discard entities - if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) { - return; - } - - eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; - eNums->numSnapshotEntities++; -} - -/* -=============== -SV_AddEntitiesVisibleFromPoint -=============== -*/ -static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, - snapshotEntityNumbers_t *eNums, qboolean portal ) { - int e, i; - sharedEntity_t *ent; - svEntity_t *svEnt; - int l; - int clientarea, clientcluster; - int leafnum; - int c_fullsend; - byte *clientpvs; - byte *bitvector; - - // during an error shutdown message we may need to transmit - // the shutdown message after the server has shutdown, so - // specfically check for it - if ( !sv.state ) { - return; - } - - leafnum = CM_PointLeafnum (origin); - clientarea = CM_LeafArea (leafnum); - clientcluster = CM_LeafCluster (leafnum); - - // calculate the visible areas - frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea ); - - clientpvs = CM_ClusterPVS (clientcluster); - - c_fullsend = 0; - - for ( e = 0 ; e < sv.num_entities ; e++ ) { - ent = SV_GentityNum(e); - - // never send entities that aren't linked in - if ( !ent->r.linked ) { - continue; - } - - if (ent->s.number != e) { - Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n"); - ent->s.number = e; - } - - // entities can be flagged to explicitly not be sent to the client - if ( ent->r.svFlags & SVF_NOCLIENT ) { - continue; - } - - // entities can be flagged to be sent to only one client - if ( ent->r.svFlags & SVF_SINGLECLIENT ) { - if ( ent->r.singleClient != frame->ps.clientNum ) { - continue; - } - } - // entities can be flagged to be sent to everyone but one client - if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) { - if ( ent->r.singleClient == frame->ps.clientNum ) { - continue; - } - } - // entities can be flagged to be sent to a given mask of clients - if ( ent->r.svFlags & SVF_CLIENTMASK ) { - if (frame->ps.clientNum >= 32) - Com_Error( ERR_DROP, "SVF_CLIENTMASK: cientNum > 32\n" ); - if (~ent->r.singleClient & (1 << frame->ps.clientNum)) - continue; - } - - svEnt = SV_SvEntityForGentity( ent ); - - // don't double add an entity through portals - if ( svEnt->snapshotCounter == sv.snapshotCounter ) { - continue; - } - - // broadcast entities are always sent - if ( ent->r.svFlags & SVF_BROADCAST ) { - SV_AddEntToSnapshot( svEnt, ent, eNums ); - continue; - } - - // ignore if not touching a PV leaf - // check area - if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) { - // doors can legally straddle two areas, so - // we may need to check another one - if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) { - continue; // blocked by a door - } - } - - bitvector = clientpvs; - - // check individual leafs - if ( !svEnt->numClusters ) { - continue; - } - l = 0; - for ( i=0 ; i < svEnt->numClusters ; i++ ) { - l = svEnt->clusternums[i]; - if ( bitvector[l >> 3] & (1 << (l&7) ) ) { - break; - } - } - - // if we haven't found it to be visible, - // check overflow clusters that coudln't be stored - if ( i == svEnt->numClusters ) { - if ( svEnt->lastCluster ) { - for ( ; l <= svEnt->lastCluster ; l++ ) { - if ( bitvector[l >> 3] & (1 << (l&7) ) ) { - break; - } - } - if ( l == svEnt->lastCluster ) { - continue; // not visible - } - } else { - continue; - } - } - - // add it - SV_AddEntToSnapshot( svEnt, ent, eNums ); - - // if its a portal entity, add everything visible from its camera position - if ( ent->r.svFlags & SVF_PORTAL ) { - if ( ent->s.generic1 ) { - vec3_t dir; - VectorSubtract(ent->s.origin, origin, dir); - if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) { - continue; - } - } - SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue ); - } - - } -} - -/* -============= -SV_BuildClientSnapshot - -Decides which entities are going to be visible to the client, and -copies off the playerstate and areabits. - -This properly handles multiple recursive portals, but the render -currently doesn't. - -For viewing through other player's eyes, clent can be something other than client->gentity -============= -*/ -static void SV_BuildClientSnapshot( client_t *client ) { - vec3_t org; - clientSnapshot_t *frame; - snapshotEntityNumbers_t entityNumbers; - int i; - sharedEntity_t *ent; - entityState_t *state; - svEntity_t *svEnt; - sharedEntity_t *clent; - int clientNum; - playerState_t *ps; - - // bump the counter used to prevent double adding - sv.snapshotCounter++; - - // this is the frame we are creating - frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; - - // clear everything in this snapshot - entityNumbers.numSnapshotEntities = 0; - Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) ); - - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62 - frame->num_entities = 0; - - clent = client->gentity; - if ( !clent || client->state == CS_ZOMBIE ) { - return; - } - - // grab the current playerState_t - ps = SV_GameClientNum( client - svs.clients ); - frame->ps = *ps; - - // never send client's own entity, because it can - // be regenerated from the playerstate - clientNum = frame->ps.clientNum; - if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { - Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); - } - svEnt = &sv.svEntities[ clientNum ]; - - svEnt->snapshotCounter = sv.snapshotCounter; - - // find the client's viewpoint - VectorCopy( ps->origin, org ); - org[2] += ps->viewheight; - - // add all the entities directly visible to the eye, which - // may include portal entities that merge other viewpoints - SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse ); - - // if there were portals visible, there may be out of order entities - // in the list which will need to be resorted for the delta compression - // to work correctly. This also catches the error condition - // of an entity being included twice. - qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, - sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); - - // now that all viewpoint's areabits have been OR'd together, invert - // all of them to make it a mask vector, which is what the renderer wants - for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) { - ((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1; - } - - // copy the entity states out - frame->num_entities = 0; - frame->first_entity = svs.nextSnapshotEntities; - for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { - ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); - state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; - *state = ent->s; - svs.nextSnapshotEntities++; - // this should never hit, map should always be restarted first in SV_Frame - if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { - Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped"); - } - frame->num_entities++; - } -} - - -/* -==================== -SV_RateMsec - -Return the number of msec a given size message is supposed -to take to clear, based on the current rate -==================== -*/ -#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead -static int SV_RateMsec( client_t *client, int messageSize ) { - int rate; - int rateMsec; - - // individual messages will never be larger than fragment size - if ( messageSize > 1500 ) { - messageSize = 1500; - } - rate = client->rate; - if ( sv_maxRate->integer ) { - if ( sv_maxRate->integer < 1000 ) { - Cvar_Set( "sv_MaxRate", "1000" ); - } - if ( sv_maxRate->integer < rate ) { - rate = sv_maxRate->integer; - } - } - rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate; - - return rateMsec; -} - -/* -======================= -SV_SendMessageToClient - -Called by SV_SendClientSnapshot and SV_SendClientGameState -======================= -*/ -void SV_SendMessageToClient( msg_t *msg, client_t *client ) { - int rateMsec; - - // record information about the message - client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; - client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; - client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; - - // send the datagram - SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data ); - - // set nextSnapshotTime based on rate and requested number of updates - - // local clients get snapshots every frame - // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491 - // added sv_lanForceRate check - if ( client->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress (client->netchan.remoteAddress)) ) { - client->nextSnapshotTime = svs.time - 1; - return; - } - - // normal rate / snapshotMsec calculation - rateMsec = SV_RateMsec( client, msg->cursize ); - - if ( rateMsec < client->snapshotMsec ) { - // never send more packets than this, no matter what the rate is at - rateMsec = client->snapshotMsec; - client->rateDelayed = qfalse; - } else { - client->rateDelayed = qtrue; - } - - client->nextSnapshotTime = svs.time + rateMsec; - - // don't pile up empty snapshots while connecting - if ( client->state != CS_ACTIVE ) { - // a gigantic connection message may have already put the nextSnapshotTime - // more than a second away, so don't shorten it - // do shorten if client is downloading - if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) { - client->nextSnapshotTime = svs.time + 1000; - } - } -} - - -/* -======================= -SV_SendClientSnapshot - -Also called by SV_FinalMessage - -======================= -*/ -void SV_SendClientSnapshot( client_t *client ) { - byte msg_buf[MAX_MSGLEN]; - msg_t msg; - - // build the snapshot - SV_BuildClientSnapshot( client ); - - // bots need to have their snapshots build, but - // the query them directly without needing to be sent - if ( client->gentity && client->gentity->r.svFlags & SVF_BOT ) { - return; - } - - MSG_Init (&msg, msg_buf, sizeof(msg_buf)); - msg.allowoverflow = qtrue; - - // NOTE, MRE: all server->client messages now acknowledge - // let the client know which reliable clientCommands we have received - MSG_WriteLong( &msg, client->lastClientCommand ); - - // (re)send any reliable server commands - SV_UpdateServerCommandsToClient( client, &msg ); - - // send over all the relevant entityState_t - // and the playerState_t - SV_WriteSnapshotToClient( client, &msg ); - - // Add any download data if the client is downloading - SV_WriteDownloadToClient( client, &msg ); - - // check for overflow - if ( msg.overflowed ) { - Com_Printf ("WARNING: msg overflowed for %s\n", client->name); - MSG_Clear (&msg); - } - - SV_SendMessageToClient( &msg, client ); -} - - -/* -======================= -SV_SendClientMessages -======================= -*/ -void SV_SendClientMessages( void ) { - int i; - client_t *c; - - // send a message to each connected client - for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) { - if (!c->state) { - continue; // not connected - } - - if ( svs.time < c->nextSnapshotTime ) { - continue; // not time yet - } - - // send additional message fragments if the last message - // was too large to send at once - if ( c->netchan.unsentFragments ) { - c->nextSnapshotTime = svs.time + - SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); - SV_Netchan_TransmitNextFragment( c ); - continue; - } - - // generate and send a new message - SV_SendClientSnapshot( 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" + + +/* +============================================================================= + +Delta encode a client frame onto the network channel + +A normal server packet will look like: + +4 sequence number (high bit set if an oversize fragment) + +1 svc_snapshot +4 last client reliable command +4 serverTime +1 lastframe for delta compression +1 snapFlags +1 areaBytes + + + + +============================================================================= +*/ + +/* +============= +SV_EmitPacketEntities + +Writes a delta update of an entityState_t list to the message. +============= +*/ +static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) { + entityState_t *oldent, *newent; + int oldindex, newindex; + int oldnum, newnum; + int from_num_entities; + + // generate the delta update + if ( !from ) { + from_num_entities = 0; + } else { + from_num_entities = from->num_entities; + } + + newent = NULL; + oldent = NULL; + newindex = 0; + oldindex = 0; + while ( newindex < to->num_entities || oldindex < from_num_entities ) { + if ( newindex >= to->num_entities ) { + newnum = 9999; + } else { + newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities]; + newnum = newent->number; + } + + if ( oldindex >= from_num_entities ) { + oldnum = 9999; + } else { + oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities]; + oldnum = oldent->number; + } + + if ( newnum == oldnum ) { + // delta update from old position + // because the force parm is qfalse, this will not result + // in any bytes being emited if the entity has not changed at all + MSG_WriteDeltaEntity (msg, oldent, newent, qfalse ); + oldindex++; + newindex++; + continue; + } + + if ( newnum < oldnum ) { + // this is a new entity, send it from the baseline + MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue ); + newindex++; + continue; + } + + if ( newnum > oldnum ) { + // the old entity isn't present in the new message + MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue ); + oldindex++; + continue; + } + } + + MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities +} + + + +/* +================== +SV_WriteSnapshotToClient +================== +*/ +static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { + clientSnapshot_t *frame, *oldframe; + int lastframe; + int i; + int snapFlags; + + // this is the snapshot we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // try to use a previous frame as the source for delta compressing the snapshot + if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { + // client is asking for a retransmit + oldframe = NULL; + lastframe = 0; + } else if ( client->netchan.outgoingSequence - client->deltaMessage + >= (PACKET_BACKUP - 3) ) { + // client hasn't gotten a good message through in a long time + Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); + oldframe = NULL; + lastframe = 0; + } else { + // we have a valid snapshot to delta from + oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; + lastframe = client->netchan.outgoingSequence - client->deltaMessage; + + // the snapshot's entities may still have rolled off the buffer, though + if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { + Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); + oldframe = NULL; + lastframe = 0; + } + } + + MSG_WriteByte (msg, svc_snapshot); + + // NOTE, MRE: now sent at the start of every message from server to client + // let the client know which reliable clientCommands we have received + //MSG_WriteLong( msg, client->lastClientCommand ); + + // send over the current server time so the client can drift + // its view of time to try to match + MSG_WriteLong (msg, svs.time); + + // what we are delta'ing from + MSG_WriteByte (msg, lastframe); + + snapFlags = svs.snapFlagServerBit; + if ( client->rateDelayed ) { + snapFlags |= SNAPFLAG_RATE_DELAYED; + } + if ( client->state != CS_ACTIVE ) { + snapFlags |= SNAPFLAG_NOT_ACTIVE; + } + + MSG_WriteByte (msg, snapFlags); + + // send over the areabits + MSG_WriteByte (msg, frame->areabytes); + MSG_WriteData (msg, frame->areabits, frame->areabytes); + + // delta encode the playerstate + if ( oldframe ) { + MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); + } else { + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); + } + + // delta encode the entities + SV_EmitPacketEntities (oldframe, frame, msg); + + // padding for rate debugging + if ( sv_padPackets->integer ) { + for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { + MSG_WriteByte (msg, svc_nop); + } + } +} + + +/* +================== +SV_UpdateServerCommandsToClient + +(re)send all server commands the client hasn't acknowledged yet +================== +*/ +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) { + int i; + + // write any unacknowledged serverCommands + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + MSG_WriteByte( msg, svc_serverCommand ); + MSG_WriteLong( msg, i ); + MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + client->reliableSent = client->reliableSequence; +} + +/* +============================================================================= + +Build a client snapshot structure + +============================================================================= +*/ + +#define MAX_SNAPSHOT_ENTITIES 1024 +typedef struct { + int numSnapshotEntities; + int snapshotEntities[MAX_SNAPSHOT_ENTITIES]; +} snapshotEntityNumbers_t; + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int QDECL SV_QsortEntityNumbers( const void *a, const void *b ) { + int *ea, *eb; + + ea = (int *)a; + eb = (int *)b; + + if ( *ea == *eb ) { + Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" ); + } + + if ( *ea < *eb ) { + return -1; + } + + return 1; +} + + +/* +=============== +SV_AddEntToSnapshot +=============== +*/ +static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums ) { + // if we have already added this entity to this snapshot, don't add again + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + return; + } + svEnt->snapshotCounter = sv.snapshotCounter; + + // if we are full, silently discard entities + if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) { + return; + } + + eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; + eNums->numSnapshotEntities++; +} + +/* +=============== +SV_AddEntitiesVisibleFromPoint +=============== +*/ +static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, + snapshotEntityNumbers_t *eNums, qboolean portal ) { + int e, i; + sharedEntity_t *ent; + svEntity_t *svEnt; + int l; + int clientarea, clientcluster; + int leafnum; + int c_fullsend; + byte *clientpvs; + byte *bitvector; + + // during an error shutdown message we may need to transmit + // the shutdown message after the server has shutdown, so + // specfically check for it + if ( !sv.state ) { + return; + } + + leafnum = CM_PointLeafnum (origin); + clientarea = CM_LeafArea (leafnum); + clientcluster = CM_LeafCluster (leafnum); + + // calculate the visible areas + frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea ); + + clientpvs = CM_ClusterPVS (clientcluster); + + c_fullsend = 0; + + for ( e = 0 ; e < sv.num_entities ; e++ ) { + ent = SV_GentityNum(e); + + // never send entities that aren't linked in + if ( !ent->r.linked ) { + continue; + } + + if (ent->s.number != e) { + Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n"); + ent->s.number = e; + } + + // entities can be flagged to explicitly not be sent to the client + if ( ent->r.svFlags & SVF_NOCLIENT ) { + continue; + } + + // entities can be flagged to be sent to only one client + if ( ent->r.svFlags & SVF_SINGLECLIENT ) { + if ( ent->r.singleClient != frame->ps.clientNum ) { + continue; + } + } + // entities can be flagged to be sent to everyone but one client + if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) { + if ( ent->r.singleClient == frame->ps.clientNum ) { + continue; + } + } + // entities can be flagged to be sent to a given mask of clients + if ( ent->r.svFlags & SVF_CLIENTMASK ) { + if (frame->ps.clientNum >= 32) + Com_Error( ERR_DROP, "SVF_CLIENTMASK: cientNum > 32\n" ); + if (~ent->r.singleClient & (1 << frame->ps.clientNum)) + continue; + } + + svEnt = SV_SvEntityForGentity( ent ); + + // don't double add an entity through portals + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + continue; + } + + // broadcast entities are always sent + if ( ent->r.svFlags & SVF_BROADCAST ) { + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + + // ignore if not touching a PV leaf + // check area + if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) { + // doors can legally straddle two areas, so + // we may need to check another one + if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) { + continue; // blocked by a door + } + } + + bitvector = clientpvs; + + // check individual leafs + if ( !svEnt->numClusters ) { + continue; + } + l = 0; + for ( i=0 ; i < svEnt->numClusters ; i++ ) { + l = svEnt->clusternums[i]; + if ( bitvector[l >> 3] & (1 << (l&7) ) ) { + break; + } + } + + // if we haven't found it to be visible, + // check overflow clusters that coudln't be stored + if ( i == svEnt->numClusters ) { + if ( svEnt->lastCluster ) { + for ( ; l <= svEnt->lastCluster ; l++ ) { + if ( bitvector[l >> 3] & (1 << (l&7) ) ) { + break; + } + } + if ( l == svEnt->lastCluster ) { + continue; // not visible + } + } else { + continue; + } + } + + // add it + SV_AddEntToSnapshot( svEnt, ent, eNums ); + + // if its a portal entity, add everything visible from its camera position + if ( ent->r.svFlags & SVF_PORTAL ) { + if ( ent->s.generic1 ) { + vec3_t dir; + VectorSubtract(ent->s.origin, origin, dir); + if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) { + continue; + } + } + SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue ); + } + + } +} + +/* +============= +SV_BuildClientSnapshot + +Decides which entities are going to be visible to the client, and +copies off the playerstate and areabits. + +This properly handles multiple recursive portals, but the render +currently doesn't. + +For viewing through other player's eyes, clent can be something other than client->gentity +============= +*/ +static void SV_BuildClientSnapshot( client_t *client ) { + vec3_t org; + clientSnapshot_t *frame; + snapshotEntityNumbers_t entityNumbers; + int i; + sharedEntity_t *ent; + entityState_t *state; + svEntity_t *svEnt; + sharedEntity_t *clent; + int clientNum; + playerState_t *ps; + + // bump the counter used to prevent double adding + sv.snapshotCounter++; + + // this is the frame we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // clear everything in this snapshot + entityNumbers.numSnapshotEntities = 0; + Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) ); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62 + frame->num_entities = 0; + + clent = client->gentity; + if ( !clent || client->state == CS_ZOMBIE ) { + return; + } + + // grab the current playerState_t + ps = SV_GameClientNum( client - svs.clients ); + frame->ps = *ps; + + // never send client's own entity, because it can + // be regenerated from the playerstate + clientNum = frame->ps.clientNum; + if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + svEnt = &sv.svEntities[ clientNum ]; + + svEnt->snapshotCounter = sv.snapshotCounter; + + // find the client's viewpoint + VectorCopy( ps->origin, org ); + org[2] += ps->viewheight; + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse ); + + // if there were portals visible, there may be out of order entities + // in the list which will need to be resorted for the delta compression + // to work correctly. This also catches the error condition + // of an entity being included twice. + qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, + sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); + + // now that all viewpoint's areabits have been OR'd together, invert + // all of them to make it a mask vector, which is what the renderer wants + for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) { + ((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1; + } + + // copy the entity states out + frame->num_entities = 0; + frame->first_entity = svs.nextSnapshotEntities; + for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { + ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); + state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; + *state = ent->s; + svs.nextSnapshotEntities++; + // this should never hit, map should always be restarted first in SV_Frame + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { + Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped"); + } + frame->num_entities++; + } +} + + +/* +==================== +SV_RateMsec + +Return the number of msec a given size message is supposed +to take to clear, based on the current rate +==================== +*/ +#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead +static int SV_RateMsec( client_t *client, int messageSize ) { + int rate; + int rateMsec; + + // individual messages will never be larger than fragment size + if ( messageSize > 1500 ) { + messageSize = 1500; + } + rate = client->rate; + if ( sv_maxRate->integer ) { + if ( sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + if ( sv_maxRate->integer < rate ) { + rate = sv_maxRate->integer; + } + } + rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate; + + return rateMsec; +} + +/* +======================= +SV_SendMessageToClient + +Called by SV_SendClientSnapshot and SV_SendClientGameState +======================= +*/ +void SV_SendMessageToClient( msg_t *msg, client_t *client ) { + int rateMsec; + + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; + + // send the datagram + SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data ); + + // set nextSnapshotTime based on rate and requested number of updates + + // local clients get snapshots every frame + // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491 + // added sv_lanForceRate check + if ( client->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress (client->netchan.remoteAddress)) ) { + client->nextSnapshotTime = svs.time - 1; + return; + } + + // normal rate / snapshotMsec calculation + rateMsec = SV_RateMsec( client, msg->cursize ); + + if ( rateMsec < client->snapshotMsec ) { + // never send more packets than this, no matter what the rate is at + rateMsec = client->snapshotMsec; + client->rateDelayed = qfalse; + } else { + client->rateDelayed = qtrue; + } + + client->nextSnapshotTime = svs.time + rateMsec; + + // don't pile up empty snapshots while connecting + if ( client->state != CS_ACTIVE ) { + // a gigantic connection message may have already put the nextSnapshotTime + // more than a second away, so don't shorten it + // do shorten if client is downloading + if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) { + client->nextSnapshotTime = svs.time + 1000; + } + } +} + + +/* +======================= +SV_SendClientSnapshot + +Also called by SV_FinalMessage + +======================= +*/ +void SV_SendClientSnapshot( client_t *client ) { + byte msg_buf[MAX_MSGLEN]; + msg_t msg; + + // build the snapshot + SV_BuildClientSnapshot( client ); + + // bots need to have their snapshots build, but + // the query them directly without needing to be sent + if ( client->gentity && client->gentity->r.svFlags & SVF_BOT ) { + return; + } + + MSG_Init (&msg, msg_buf, sizeof(msg_buf)); + msg.allowoverflow = qtrue; + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // (re)send any reliable server commands + SV_UpdateServerCommandsToClient( client, &msg ); + + // send over all the relevant entityState_t + // and the playerState_t + SV_WriteSnapshotToClient( client, &msg ); + + // Add any download data if the client is downloading + SV_WriteDownloadToClient( client, &msg ); + + // check for overflow + if ( msg.overflowed ) { + Com_Printf ("WARNING: msg overflowed for %s\n", client->name); + MSG_Clear (&msg); + } + + SV_SendMessageToClient( &msg, client ); +} + + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages( void ) { + int i; + client_t *c; + + // send a message to each connected client + for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) { + if (!c->state) { + continue; // not connected + } + + if ( svs.time < c->nextSnapshotTime ) { + continue; // not time yet + } + + // send additional message fragments if the last message + // was too large to send at once + if ( c->netchan.unsentFragments ) { + c->nextSnapshotTime = svs.time + + SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); + SV_Netchan_TransmitNextFragment( c ); + continue; + } + + // generate and send a new message + SV_SendClientSnapshot( c ); + } +} + diff --git a/code/server/sv_world.c b/code/server/sv_world.c index acb5761..205aa22 100755 --- a/code/server/sv_world.c +++ b/code/server/sv_world.c @@ -1,691 +1,691 @@ -/* -=========================================================================== -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 -=========================================================================== -*/ -// world.c -- world query functions - -#include "server.h" - -/* -================ -SV_ClipHandleForEntity - -Returns a headnode that can be used for testing or clipping to a -given entity. If the entity is a bsp model, the headnode will -be returned, otherwise a custom box tree will be constructed. -================ -*/ -clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) { - if ( ent->r.bmodel ) { - // explicit hulls in the BSP model - return CM_InlineModel( ent->s.modelindex ); - } - if ( ent->r.svFlags & SVF_CAPSULE ) { - // create a temp capsule from bounding box sizes - return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); - } - - // create a temp tree from bounding box sizes - return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); -} - - - -/* -=============================================================================== - -ENTITY CHECKING - -To avoid linearly searching through lists of entities during environment testing, -the world is carved up with an evenly spaced, axially aligned bsp tree. Entities -are kept in chains either at the final leafs, or at the first node that splits -them, which prevents having to deal with multiple fragments of a single entity. - -=============================================================================== -*/ - -typedef struct worldSector_s { - int axis; // -1 = leaf node - float dist; - struct worldSector_s *children[2]; - svEntity_t *entities; -} worldSector_t; - -#define AREA_DEPTH 4 -#define AREA_NODES 64 - -worldSector_t sv_worldSectors[AREA_NODES]; -int sv_numworldSectors; - - -/* -=============== -SV_SectorList_f -=============== -*/ -void SV_SectorList_f( void ) { - int i, c; - worldSector_t *sec; - svEntity_t *ent; - - for ( i = 0 ; i < AREA_NODES ; i++ ) { - sec = &sv_worldSectors[i]; - - c = 0; - for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) { - c++; - } - Com_Printf( "sector %i: %i entities\n", i, c ); - } -} - -/* -=============== -SV_CreateworldSector - -Builds a uniformly subdivided tree for the given world size -=============== -*/ -worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { - worldSector_t *anode; - vec3_t size; - vec3_t mins1, maxs1, mins2, maxs2; - - anode = &sv_worldSectors[sv_numworldSectors]; - sv_numworldSectors++; - - if (depth == AREA_DEPTH) { - anode->axis = -1; - anode->children[0] = anode->children[1] = NULL; - return anode; - } - - VectorSubtract (maxs, mins, size); - if (size[0] > size[1]) { - anode->axis = 0; - } else { - anode->axis = 1; - } - - anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); - VectorCopy (mins, mins1); - VectorCopy (mins, mins2); - VectorCopy (maxs, maxs1); - VectorCopy (maxs, maxs2); - - maxs1[anode->axis] = mins2[anode->axis] = anode->dist; - - anode->children[0] = SV_CreateworldSector (depth+1, mins2, maxs2); - anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1); - - return anode; -} - -/* -=============== -SV_ClearWorld - -=============== -*/ -void SV_ClearWorld( void ) { - clipHandle_t h; - vec3_t mins, maxs; - - Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) ); - sv_numworldSectors = 0; - - // get world map bounds - h = CM_InlineModel( 0 ); - CM_ModelBounds( h, mins, maxs ); - SV_CreateworldSector( 0, mins, maxs ); -} - - -/* -=============== -SV_UnlinkEntity - -=============== -*/ -void SV_UnlinkEntity( sharedEntity_t *gEnt ) { - svEntity_t *ent; - svEntity_t *scan; - worldSector_t *ws; - - ent = SV_SvEntityForGentity( gEnt ); - - gEnt->r.linked = qfalse; - - ws = ent->worldSector; - if ( !ws ) { - return; // not linked in anywhere - } - ent->worldSector = NULL; - - if ( ws->entities == ent ) { - ws->entities = ent->nextEntityInWorldSector; - return; - } - - for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) { - if ( scan->nextEntityInWorldSector == ent ) { - scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; - return; - } - } - - Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" ); -} - - -/* -=============== -SV_LinkEntity - -=============== -*/ -#define MAX_TOTAL_ENT_LEAFS 128 -void SV_LinkEntity( sharedEntity_t *gEnt ) { - worldSector_t *node; - int leafs[MAX_TOTAL_ENT_LEAFS]; - int cluster; - int num_leafs; - int i, j, k; - int area; - int lastLeaf; - float *origin, *angles; - svEntity_t *ent; - - ent = SV_SvEntityForGentity( gEnt ); - - if ( ent->worldSector ) { - SV_UnlinkEntity( gEnt ); // unlink from old position - } - - // encode the size into the entityState_t for client prediction - if ( gEnt->r.bmodel ) { - gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value - } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { - // assume that x/y are equal and symetric - i = gEnt->r.maxs[0]; - if (i<1) - i = 1; - if (i>255) - i = 255; - - // z is not symetric - j = (-gEnt->r.mins[2]); - if (j<1) - j = 1; - if (j>255) - j = 255; - - // and z maxs can be negative... - k = (gEnt->r.maxs[2]+32); - if (k<1) - k = 1; - if (k>255) - k = 255; - - gEnt->s.solid = (k<<16) | (j<<8) | i; - } else { - gEnt->s.solid = 0; - } - - // get the position - origin = gEnt->r.currentOrigin; - angles = gEnt->r.currentAngles; - - // set the abs box - if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) { - // expand for rotation - float max; - int i; - - max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); - for (i=0 ; i<3 ; i++) { - gEnt->r.absmin[i] = origin[i] - max; - gEnt->r.absmax[i] = origin[i] + max; - } - } else { - // normal - VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin); - VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax); - } - - // because movement is clipped an epsilon away from an actual edge, - // we must fully check even when bounding boxes don't quite touch - gEnt->r.absmin[0] -= 1; - gEnt->r.absmin[1] -= 1; - gEnt->r.absmin[2] -= 1; - gEnt->r.absmax[0] += 1; - gEnt->r.absmax[1] += 1; - gEnt->r.absmax[2] += 1; - - // link to PVS leafs - ent->numClusters = 0; - ent->lastCluster = 0; - ent->areanum = -1; - ent->areanum2 = -1; - - //get all leafs, including solids - num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, - leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); - - // if none of the leafs were inside the map, the - // entity is outside the world and can be considered unlinked - if ( !num_leafs ) { - return; - } - - // set areas, even from clusters that don't fit in the entity array - for (i=0 ; iareanum != -1 && ent->areanum != area) { - if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { - Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", - gEnt->s.number, - gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]); - } - ent->areanum2 = area; - } else { - ent->areanum = area; - } - } - } - - // store as many explicit clusters as we can - ent->numClusters = 0; - for (i=0 ; i < num_leafs ; i++) { - cluster = CM_LeafCluster( leafs[i] ); - if ( cluster != -1 ) { - ent->clusternums[ent->numClusters++] = cluster; - if ( ent->numClusters == MAX_ENT_CLUSTERS ) { - break; - } - } - } - - // store off a last cluster if we need to - if ( i != num_leafs ) { - ent->lastCluster = CM_LeafCluster( lastLeaf ); - } - - gEnt->r.linkcount++; - - // find the first world sector node that the ent's box crosses - node = sv_worldSectors; - while (1) - { - if (node->axis == -1) - break; - if ( gEnt->r.absmin[node->axis] > node->dist) - node = node->children[0]; - else if ( gEnt->r.absmax[node->axis] < node->dist) - node = node->children[1]; - else - break; // crosses the node - } - - // link it in - ent->worldSector = node; - ent->nextEntityInWorldSector = node->entities; - node->entities = ent; - - gEnt->r.linked = qtrue; -} - -/* -============================================================================ - -AREA QUERY - -Fills in a list of all entities who's absmin / absmax intersects the given -bounds. This does NOT mean that they actually touch in the case of bmodels. -============================================================================ -*/ - -typedef struct { - const float *mins; - const float *maxs; - int *list; - int count, maxcount; -} areaParms_t; - - -/* -==================== -SV_AreaEntities_r - -==================== -*/ -void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { - svEntity_t *check, *next; - sharedEntity_t *gcheck; - int count; - - count = 0; - - for ( check = node->entities ; check ; check = next ) { - next = check->nextEntityInWorldSector; - - gcheck = SV_GEntityForSvEntity( check ); - - if ( gcheck->r.absmin[0] > ap->maxs[0] - || gcheck->r.absmin[1] > ap->maxs[1] - || gcheck->r.absmin[2] > ap->maxs[2] - || gcheck->r.absmax[0] < ap->mins[0] - || gcheck->r.absmax[1] < ap->mins[1] - || gcheck->r.absmax[2] < ap->mins[2]) { - continue; - } - - if ( ap->count == ap->maxcount ) { - Com_Printf ("SV_AreaEntities: MAXCOUNT\n"); - return; - } - - ap->list[ap->count] = check - sv.svEntities; - ap->count++; - } - - if (node->axis == -1) { - return; // terminal node - } - - // recurse down both sides - if ( ap->maxs[node->axis] > node->dist ) { - SV_AreaEntities_r ( node->children[0], ap ); - } - if ( ap->mins[node->axis] < node->dist ) { - SV_AreaEntities_r ( node->children[1], ap ); - } -} - -/* -================ -SV_AreaEntities -================ -*/ -int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) { - areaParms_t ap; - - ap.mins = mins; - ap.maxs = maxs; - ap.list = entityList; - ap.count = 0; - ap.maxcount = maxcount; - - SV_AreaEntities_r( sv_worldSectors, &ap ); - - return ap.count; -} - - - -//=========================================================================== - - -typedef struct { - vec3_t boxmins, boxmaxs;// enclose the test object along entire move - const float *mins; - const float *maxs; // size of the moving object - const float *start; - vec3_t end; - trace_t trace; - int passEntityNum; - int contentmask; - int capsule; -} moveclip_t; - - -/* -==================== -SV_ClipToEntity - -==================== -*/ -void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) { - sharedEntity_t *touch; - clipHandle_t clipHandle; - float *origin, *angles; - - touch = SV_GentityNum( entityNum ); - - Com_Memset(trace, 0, sizeof(trace_t)); - - // if it doesn't have any brushes of a type we - // are looking for, ignore it - if ( ! ( contentmask & touch->r.contents ) ) { - trace->fraction = 1.0; - return; - } - - // might intersect, so do an exact clip - clipHandle = SV_ClipHandleForEntity (touch); - - origin = touch->r.currentOrigin; - angles = touch->r.currentAngles; - - if ( !touch->r.bmodel ) { - angles = vec3_origin; // boxes don't rotate - } - - CM_TransformedBoxTrace ( trace, (float *)start, (float *)end, - (float *)mins, (float *)maxs, clipHandle, contentmask, - origin, angles, capsule); - - if ( trace->fraction < 1 ) { - trace->entityNum = touch->s.number; - } -} - - -/* -==================== -SV_ClipMoveToEntities - -==================== -*/ -void SV_ClipMoveToEntities( moveclip_t *clip ) { - int i, num; - int touchlist[MAX_GENTITIES]; - sharedEntity_t *touch; - int passOwnerNum; - trace_t trace; - clipHandle_t clipHandle; - float *origin, *angles; - - num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); - - if ( clip->passEntityNum != ENTITYNUM_NONE ) { - passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; - if ( passOwnerNum == ENTITYNUM_NONE ) { - passOwnerNum = -1; - } - } else { - passOwnerNum = -1; - } - - for ( i=0 ; itrace.allsolid ) { - return; - } - touch = SV_GentityNum( touchlist[i] ); - - // see if we should ignore this entity - if ( clip->passEntityNum != ENTITYNUM_NONE ) { - if ( touchlist[i] == clip->passEntityNum ) { - continue; // don't clip against the pass entity - } - if ( touch->r.ownerNum == clip->passEntityNum ) { - continue; // don't clip against own missiles - } - if ( touch->r.ownerNum == passOwnerNum ) { - continue; // don't clip against other missiles from our owner - } - } - - // if it doesn't have any brushes of a type we - // are looking for, ignore it - if ( ! ( clip->contentmask & touch->r.contents ) ) { - continue; - } - - // might intersect, so do an exact clip - clipHandle = SV_ClipHandleForEntity (touch); - - origin = touch->r.currentOrigin; - angles = touch->r.currentAngles; - - - if ( !touch->r.bmodel ) { - angles = vec3_origin; // boxes don't rotate - } - - CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end, - (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, - origin, angles, clip->capsule); - - if ( trace.allsolid ) { - clip->trace.allsolid = qtrue; - trace.entityNum = touch->s.number; - } else if ( trace.startsolid ) { - clip->trace.startsolid = qtrue; - trace.entityNum = touch->s.number; - } - - if ( trace.fraction < clip->trace.fraction ) { - qboolean oldStart; - - // make sure we keep a startsolid from a previous trace - oldStart = clip->trace.startsolid; - - trace.entityNum = touch->s.number; - clip->trace = trace; - clip->trace.startsolid |= oldStart; - } - } -} - - -/* -================== -SV_Trace - -Moves the given mins/maxs volume through the world from start to end. -passEntityNum and entities owned by passEntityNum are explicitly not checked. -================== -*/ -void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ) { - moveclip_t clip; - int i; - - if ( !mins ) { - mins = vec3_origin; - } - if ( !maxs ) { - maxs = vec3_origin; - } - - Com_Memset ( &clip, 0, sizeof ( moveclip_t ) ); - - // clip to world - CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, capsule ); - clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; - if ( clip.trace.fraction == 0 ) { - *results = clip.trace; - return; // blocked immediately by the world - } - - clip.contentmask = contentmask; - clip.start = start; -// VectorCopy( clip.trace.endpos, clip.end ); - VectorCopy( end, clip.end ); - clip.mins = mins; - clip.maxs = maxs; - clip.passEntityNum = passEntityNum; - clip.capsule = capsule; - - // create the bounding box of the entire move - // we can limit it to the part of the move not - // already clipped off by the world, which can be - // a significant savings for line of sight and shot traces - for ( i=0 ; i<3 ; i++ ) { - if ( end[i] > start[i] ) { - clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1; - clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1; - } else { - clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1; - clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1; - } - } - - // clip to other solid entities - SV_ClipMoveToEntities ( &clip ); - - *results = clip.trace; -} - - - -/* -============= -SV_PointContents -============= -*/ -int SV_PointContents( const vec3_t p, int passEntityNum ) { - int touch[MAX_GENTITIES]; - sharedEntity_t *hit; - int i, num; - int contents, c2; - clipHandle_t clipHandle; - float *angles; - - // get base contents from world - contents = CM_PointContents( p, 0 ); - - // or in contents from all the other entities - num = SV_AreaEntities( p, p, touch, MAX_GENTITIES ); - - for ( i=0 ; is.angles; - if ( !hit->r.bmodel ) { - angles = vec3_origin; // boxes don't rotate - } - - c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles); - - contents |= c2; - } - - return contents; -} - - +/* +=========================================================================== +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 +=========================================================================== +*/ +// world.c -- world query functions + +#include "server.h" + +/* +================ +SV_ClipHandleForEntity + +Returns a headnode that can be used for testing or clipping to a +given entity. If the entity is a bsp model, the headnode will +be returned, otherwise a custom box tree will be constructed. +================ +*/ +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) { + if ( ent->r.bmodel ) { + // explicit hulls in the BSP model + return CM_InlineModel( ent->s.modelindex ); + } + if ( ent->r.svFlags & SVF_CAPSULE ) { + // create a temp capsule from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); +} + + + +/* +=============================================================================== + +ENTITY CHECKING + +To avoid linearly searching through lists of entities during environment testing, +the world is carved up with an evenly spaced, axially aligned bsp tree. Entities +are kept in chains either at the final leafs, or at the first node that splits +them, which prevents having to deal with multiple fragments of a single entity. + +=============================================================================== +*/ + +typedef struct worldSector_s { + int axis; // -1 = leaf node + float dist; + struct worldSector_s *children[2]; + svEntity_t *entities; +} worldSector_t; + +#define AREA_DEPTH 4 +#define AREA_NODES 64 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + + +/* +=============== +SV_SectorList_f +=============== +*/ +void SV_SectorList_f( void ) { + int i, c; + worldSector_t *sec; + svEntity_t *ent; + + for ( i = 0 ; i < AREA_NODES ; i++ ) { + sec = &sv_worldSectors[i]; + + c = 0; + for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) { + c++; + } + Com_Printf( "sector %i: %i entities\n", i, c ); + } +} + +/* +=============== +SV_CreateworldSector + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { + worldSector_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_worldSectors[sv_numworldSectors]; + sv_numworldSectors++; + + if (depth == AREA_DEPTH) { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract (maxs, mins, size); + if (size[0] > size[1]) { + anode->axis = 0; + } else { + anode->axis = 1; + } + + anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); + VectorCopy (mins, mins1); + VectorCopy (mins, mins2); + VectorCopy (maxs, maxs1); + VectorCopy (maxs, maxs2); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + + anode->children[0] = SV_CreateworldSector (depth+1, mins2, maxs2); + anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld( void ) { + clipHandle_t h; + vec3_t mins, maxs; + + Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) ); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel( 0 ); + CM_ModelBounds( h, mins, maxs ); + SV_CreateworldSector( 0, mins, maxs ); +} + + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity( sharedEntity_t *gEnt ) { + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + ent = SV_SvEntityForGentity( gEnt ); + + gEnt->r.linked = qfalse; + + ws = ent->worldSector; + if ( !ws ) { + return; // not linked in anywhere + } + ent->worldSector = NULL; + + if ( ws->entities == ent ) { + ws->entities = ent->nextEntityInWorldSector; + return; + } + + for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) { + if ( scan->nextEntityInWorldSector == ent ) { + scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; + return; + } + } + + Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" ); +} + + +/* +=============== +SV_LinkEntity + +=============== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +void SV_LinkEntity( sharedEntity_t *gEnt ) { + worldSector_t *node; + int leafs[MAX_TOTAL_ENT_LEAFS]; + int cluster; + int num_leafs; + int i, j, k; + int area; + int lastLeaf; + float *origin, *angles; + svEntity_t *ent; + + ent = SV_SvEntityForGentity( gEnt ); + + if ( ent->worldSector ) { + SV_UnlinkEntity( gEnt ); // unlink from old position + } + + // encode the size into the entityState_t for client prediction + if ( gEnt->r.bmodel ) { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + // assume that x/y are equal and symetric + i = gEnt->r.maxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-gEnt->r.mins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z maxs can be negative... + k = (gEnt->r.maxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + gEnt->s.solid = (k<<16) | (j<<8) | i; + } else { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + // set the abs box + if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) { + // expand for rotation + float max; + int i; + + max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); + for (i=0 ; i<3 ; i++) { + gEnt->r.absmin[i] = origin[i] - max; + gEnt->r.absmax[i] = origin[i] + max; + } + } else { + // normal + VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin); + VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax); + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + gEnt->r.absmin[0] -= 1; + gEnt->r.absmin[1] -= 1; + gEnt->r.absmin[2] -= 1; + gEnt->r.absmax[0] += 1; + gEnt->r.absmax[1] += 1; + gEnt->r.absmax[2] += 1; + + // link to PVS leafs + ent->numClusters = 0; + ent->lastCluster = 0; + ent->areanum = -1; + ent->areanum2 = -1; + + //get all leafs, including solids + num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, + leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); + + // if none of the leafs were inside the map, the + // entity is outside the world and can be considered unlinked + if ( !num_leafs ) { + return; + } + + // set areas, even from clusters that don't fit in the entity array + for (i=0 ; iareanum != -1 && ent->areanum != area) { + if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { + Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", + gEnt->s.number, + gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]); + } + ent->areanum2 = area; + } else { + ent->areanum = area; + } + } + } + + // store as many explicit clusters as we can + ent->numClusters = 0; + for (i=0 ; i < num_leafs ; i++) { + cluster = CM_LeafCluster( leafs[i] ); + if ( cluster != -1 ) { + ent->clusternums[ent->numClusters++] = cluster; + if ( ent->numClusters == MAX_ENT_CLUSTERS ) { + break; + } + } + } + + // store off a last cluster if we need to + if ( i != num_leafs ) { + ent->lastCluster = CM_LeafCluster( lastLeaf ); + } + + gEnt->r.linkcount++; + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + while (1) + { + if (node->axis == -1) + break; + if ( gEnt->r.absmin[node->axis] > node->dist) + node = node->children[0]; + else if ( gEnt->r.absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + + // link it in + ent->worldSector = node; + ent->nextEntityInWorldSector = node->entities; + node->entities = ent; + + gEnt->r.linked = qtrue; +} + +/* +============================================================================ + +AREA QUERY + +Fills in a list of all entities who's absmin / absmax intersects the given +bounds. This does NOT mean that they actually touch in the case of bmodels. +============================================================================ +*/ + +typedef struct { + const float *mins; + const float *maxs; + int *list; + int count, maxcount; +} areaParms_t; + + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; + sharedEntity_t *gcheck; + int count; + + count = 0; + + for ( check = node->entities ; check ; check = next ) { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + if ( gcheck->r.absmin[0] > ap->maxs[0] + || gcheck->r.absmin[1] > ap->maxs[1] + || gcheck->r.absmin[2] > ap->maxs[2] + || gcheck->r.absmax[0] < ap->mins[0] + || gcheck->r.absmax[1] < ap->mins[1] + || gcheck->r.absmax[2] < ap->mins[2]) { + continue; + } + + if ( ap->count == ap->maxcount ) { + Com_Printf ("SV_AreaEntities: MAXCOUNT\n"); + return; + } + + ap->list[ap->count] = check - sv.svEntities; + ap->count++; + } + + if (node->axis == -1) { + return; // terminal node + } + + // recurse down both sides + if ( ap->maxs[node->axis] > node->dist ) { + SV_AreaEntities_r ( node->children[0], ap ); + } + if ( ap->mins[node->axis] < node->dist ) { + SV_AreaEntities_r ( node->children[1], ap ); + } +} + +/* +================ +SV_AreaEntities +================ +*/ +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) { + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = entityList; + ap.count = 0; + ap.maxcount = maxcount; + + SV_AreaEntities_r( sv_worldSectors, &ap ); + + return ap.count; +} + + + +//=========================================================================== + + +typedef struct { + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object + const float *start; + vec3_t end; + trace_t trace; + int passEntityNum; + int contentmask; + int capsule; +} moveclip_t; + + +/* +==================== +SV_ClipToEntity + +==================== +*/ +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) { + sharedEntity_t *touch; + clipHandle_t clipHandle; + float *origin, *angles; + + touch = SV_GentityNum( entityNum ); + + Com_Memset(trace, 0, sizeof(trace_t)); + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( contentmask & touch->r.contents ) ) { + trace->fraction = 1.0; + return; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( trace, (float *)start, (float *)end, + (float *)mins, (float *)maxs, clipHandle, contentmask, + origin, angles, capsule); + + if ( trace->fraction < 1 ) { + trace->entityNum = touch->s.number; + } +} + + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +void SV_ClipMoveToEntities( moveclip_t *clip ) { + int i, num; + int touchlist[MAX_GENTITIES]; + sharedEntity_t *touch; + int passOwnerNum; + trace_t trace; + clipHandle_t clipHandle; + float *origin, *angles; + + num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); + + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; + if ( passOwnerNum == ENTITYNUM_NONE ) { + passOwnerNum = -1; + } + } else { + passOwnerNum = -1; + } + + for ( i=0 ; itrace.allsolid ) { + return; + } + touch = SV_GentityNum( touchlist[i] ); + + // see if we should ignore this entity + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + if ( touchlist[i] == clip->passEntityNum ) { + continue; // don't clip against the pass entity + } + if ( touch->r.ownerNum == clip->passEntityNum ) { + continue; // don't clip against own missiles + } + if ( touch->r.ownerNum == passOwnerNum ) { + continue; // don't clip against other missiles from our owner + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( clip->contentmask & touch->r.contents ) ) { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end, + (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, + origin, angles, clip->capsule); + + if ( trace.allsolid ) { + clip->trace.allsolid = qtrue; + trace.entityNum = touch->s.number; + } else if ( trace.startsolid ) { + clip->trace.startsolid = qtrue; + trace.entityNum = touch->s.number; + } + + if ( trace.fraction < clip->trace.fraction ) { + qboolean oldStart; + + // make sure we keep a startsolid from a previous trace + oldStart = clip->trace.startsolid; + + trace.entityNum = touch->s.number; + clip->trace = trace; + clip->trace.startsolid |= oldStart; + } + } +} + + +/* +================== +SV_Trace + +Moves the given mins/maxs volume through the world from start to end. +passEntityNum and entities owned by passEntityNum are explicitly not checked. +================== +*/ +void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ) { + moveclip_t clip; + int i; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + Com_Memset ( &clip, 0, sizeof ( moveclip_t ) ); + + // clip to world + CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, capsule ); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( clip.trace.fraction == 0 ) { + *results = clip.trace; + return; // blocked immediately by the world + } + + clip.contentmask = contentmask; + clip.start = start; +// VectorCopy( clip.trace.endpos, clip.end ); + VectorCopy( end, clip.end ); + clip.mins = mins; + clip.maxs = maxs; + clip.passEntityNum = passEntityNum; + clip.capsule = capsule; + + // create the bounding box of the entire move + // we can limit it to the part of the move not + // already clipped off by the world, which can be + // a significant savings for line of sight and shot traces + for ( i=0 ; i<3 ; i++ ) { + if ( end[i] > start[i] ) { + clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1; + } else { + clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1; + } + } + + // clip to other solid entities + SV_ClipMoveToEntities ( &clip ); + + *results = clip.trace; +} + + + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents( const vec3_t p, int passEntityNum ) { + int touch[MAX_GENTITIES]; + sharedEntity_t *hit; + int i, num; + int contents, c2; + clipHandle_t clipHandle; + float *angles; + + // get base contents from world + contents = CM_PointContents( p, 0 ); + + // or in contents from all the other entities + num = SV_AreaEntities( p, p, touch, MAX_GENTITIES ); + + for ( i=0 ; is.angles; + if ( !hit->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles); + + contents |= c2; + } + + return contents; +} + + -- cgit v1.2.3