diff options
Diffstat (limited to 'code/server')
-rwxr-xr-x | code/server/server.h | 808 | ||||
-rwxr-xr-x | code/server/sv_bot.c | 1270 | ||||
-rwxr-xr-x | code/server/sv_ccmds.c | 1524 | ||||
-rwxr-xr-x | code/server/sv_client.c | 3062 | ||||
-rwxr-xr-x | code/server/sv_game.c | 1962 | ||||
-rwxr-xr-x | code/server/sv_init.c | 1390 | ||||
-rwxr-xr-x | code/server/sv_main.c | 1710 | ||||
-rwxr-xr-x | code/server/sv_net_chan.c | 414 | ||||
-rwxr-xr-x | code/server/sv_rankings.c | 3074 | ||||
-rwxr-xr-x | code/server/sv_snapshot.c | 1364 | ||||
-rwxr-xr-x | code/server/sv_world.c | 1382 |
11 files changed, 8980 insertions, 8980 deletions
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 <levelname>", then
- // cheats will not be allowed. If started with "devmap <levelname>"
- // 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 ; i<sv_maxclients->integer ; 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 <player name>\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 <player name>\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 <client number>\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 <client number>\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 ; j<l ; j++)
- Com_Printf (" ");
-
- Com_Printf ("%7i ", svs.time - cl->lastPacketTime );
-
- s = NET_AdrToString( cl->netchan.remoteAddress );
- Com_Printf ("%s", s);
- l = 22 - strlen(s);
- for (j=0 ; j<l ; j++)
- Com_Printf (" ");
-
- Com_Printf ("%5i", cl->netchan.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 <userid>\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 <levelname>", then + // cheats will not be allowed. If started with "devmap <levelname>" + // 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 ; i<sv_maxclients->integer ; 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 <player name>\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 <player name>\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 <client number>\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 <client number>\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 ; j<l ; j++) + Com_Printf (" "); + + Com_Printf ("%7i ", svs.time - cl->lastPacketTime ); + + s = NET_AdrToString( cl->netchan.remoteAddress ); + Com_Printf ("%s", s); + l = 22 - strlen(s); + for (j=0 ; j<l ; j++) + Com_Printf (" "); + + Com_Printf ("%5i", cl->netchan.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 <userid>\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 <challenge> <IP> <game> 0 <auth-flag>
- 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 ; i<MAX_CHALLENGES ; i++) {
- if (NET_CompareAdr(from, svs.challenges[i].adr)) {
- if ( challenge == svs.challenges[i].challenge ) {
- break; // good
- }
- }
- }
- if (i == MAX_CHALLENGES) {
- NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for address.\n" );
- return;
- }
- // force the IP key/value pair so the game can filter based on ip
- Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ) );
-
- ping = svs.time - svs.challenges[i].pingTime;
- Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping );
- svs.challenges[i].connected = qtrue;
-
- // never reject a LAN client based on ping
- if ( !Sys_IsLANAddress( from ) ) {
- if ( sv_minPing->value && 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 <challenge> <IP> <game> 0 <auth-flag> + 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 ; i<MAX_CHALLENGES ; i++) { + if (NET_CompareAdr(from, svs.challenges[i].adr)) { + if ( challenge == svs.challenges[i].challenge ) { + break; // good + } + } + } + if (i == MAX_CHALLENGES) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for address.\n" ); + return; + } + // force the IP key/value pair so the game can filter based on ip + Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ) ); + + ping = svs.time - svs.challenges[i].pingTime; + Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping ); + svs.challenges[i].connected = qtrue; + + // never reject a LAN client based on ping + if ( !Sys_IsLANAddress( from ) ) { + if ( sv_minPing->value && 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 ; i<sv_maxclients->integer ; 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 ; i<sv_maxclients->integer ; 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 <pass> <command>" 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 <pass> <command>" 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;i<sv_maxclients->value;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;i<sv_maxclients->value;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)
-<optional reliable commands>
-1 svc_snapshot
-4 last client reliable command
-4 serverTime
-1 lastframe for delta compression
-1 snapFlags
-1 areaBytes
-<areabytes>
-<playerstate>
-<packetentities>
-
-=============================================================================
-*/
-
-/*
-=============
-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) +<optional reliable commands> +1 svc_snapshot +4 last client reliable command +4 serverTime +1 lastframe for delta compression +1 snapFlags +1 areaBytes +<areabytes> +<playerstate> +<packetentities> + +============================================================================= +*/ + +/* +============= +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 ; i<num_leafs ; i++) {
- area = CM_LeafArea (leafs[i]);
- if (area != -1) {
- // doors may legally straggle two areas,
- // but nothing should evern need more than that
- if (ent->areanum != -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 ; i<num ; i++ ) {
- if ( clip->trace.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 ; i<num ; i++ ) {
- if ( touch[i] == passEntityNum ) {
- continue;
- }
- hit = SV_GentityNum( touch[i] );
- // might intersect, so do an exact clip
- clipHandle = SV_ClipHandleForEntity( hit );
- angles = hit->s.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 ; i<num_leafs ; i++) { + area = CM_LeafArea (leafs[i]); + if (area != -1) { + // doors may legally straggle two areas, + // but nothing should evern need more than that + if (ent->areanum != -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 ; i<num ; i++ ) { + if ( clip->trace.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 ; i<num ; i++ ) { + if ( touch[i] == passEntityNum ) { + continue; + } + hit = SV_GentityNum( touch[i] ); + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity( hit ); + angles = hit->s.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; +} + + |