diff options
| author | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 | 
|---|---|---|
| committer | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 | 
| commit | 6a777afc079c2a8d3af3ecd2145fe8dd50567a39 (patch) | |
| tree | 520f4489cebf8564ef6cb27064ceea45cbc005b3 /src/server | |
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/server.h | 412 | ||||
| -rw-r--r-- | src/server/sv_ccmds.c | 765 | ||||
| -rw-r--r-- | src/server/sv_client.c | 1452 | ||||
| -rw-r--r-- | src/server/sv_game.c | 599 | ||||
| -rw-r--r-- | src/server/sv_init.c | 706 | ||||
| -rw-r--r-- | src/server/sv_main.c | 895 | ||||
| -rw-r--r-- | src/server/sv_net_chan.c | 208 | ||||
| -rw-r--r-- | src/server/sv_rankings.c | 1538 | ||||
| -rw-r--r-- | src/server/sv_snapshot.c | 693 | ||||
| -rw-r--r-- | src/server/sv_world.c | 692 | 
10 files changed, 7960 insertions, 0 deletions
diff --git a/src/server/server.h b/src/server/server.h new file mode 100644 index 0000000..536b24d --- /dev/null +++ b/src/server/server.h @@ -0,0 +1,412 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous 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.
 +
 +Tremulous 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 Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +// server.h
 +
 +#include "../qcommon/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;
 +	int				time;
 +} 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;
 +
 +	int				oldServerTime;
 +	qboolean			csUpdated[MAX_CONFIGSTRINGS+1];	
 +} 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_wwwDownload;
 +extern	cvar_t	*sv_wwwBaseURL;
 +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_minRate;
 +extern	cvar_t	*sv_maxRate;
 +extern	cvar_t	*sv_minPing;
 +extern	cvar_t	*sv_maxPing;
 +extern	cvar_t	*sv_pure;
 +extern	cvar_t	*sv_lanForceRate;
 +extern	cvar_t	*sv_dequeuePeriod;
 +
 +//===========================================================
 +
 +//
 +// 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);
 +void SV_MasterGameStat( const char *data );
 +
 +
 +
 +
 +//
 +// sv_init.c
 +//
 +void SV_SetConfigstring( int index, const char *val );
 +void SV_GetConfigstring( int index, char *buffer, int bufferSize );
 +void SV_UpdateConfigstrings( client_t *client );
 +
 +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, traceType_t type );
 +// 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, traceType_t type );
 +// 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/src/server/sv_ccmds.c b/src/server/sv_ccmds.c new file mode 100644 index 0000000..10664f7 --- /dev/null +++ b/src/server/sv_ccmds.c @@ -0,0 +1,765 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous 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.
 +
 +Tremulous 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 Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +
 +#include "server.h"
 +
 +/*
 +===============================================================================
 +
 +OPERATOR CONSOLE ONLY COMMANDS
 +
 +These commands can only be entered from stdin or by a remote operator datagram
 +===============================================================================
 +*/
 +
 +
 +/*
 +==================
 +SV_GetPlayerByHandle
 +
 +Returns the player with player id or name from Cmd_Argv(1)
 +==================
 +*/
 +static client_t *SV_GetPlayerByHandle( 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 whether this is a numeric player handle
 +	for(i = 0; s[i] >= '0' && s[i] <= '9'; i++);
 +	
 +	if(!s[i])
 +	{
 +		int plid = atoi(s);
 +
 +		// Check for numeric playerid match
 +		if(plid >= 0 && plid < sv_maxclients->integer)
 +		{
 +			cl = &svs.clients[plid];
 +			
 +			if(cl->state)
 +				return cl;
 +		}
 +	}
 +
 +	// 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;
 +}
 +
 +//=========================================================
 +
 +
 +/*
 +==================
 +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];
 +	int			i;
 +
 +	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;
 +	}
 +
 +	cmd = Cmd_Argv(0);
 +	if ( !Q_stricmp( cmd, "devmap" ) ) {
 +		cheat = qtrue;
 +		killBots = qtrue;
 +	} else {
 +		cheat = qfalse;
 +		killBots = qfalse;
 +	}
 +
 +	// save the map name here cause on a map restart we reload the autogen.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" );
 +	}
 +
 +	// This forces the local master server IP address cache
 +	// to be updated on sending the next heartbeat
 +	for( i = 0; i < MAX_MASTER_SERVERS; i++ )
 +		sv_master[ i ]->modified  = qtrue;
 +}
 +
 +/*
 +================
 +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;
 +	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;
 +	}
 +
 +	// check for changes in variables that can't just be restarted
 +	// check for maxclients change
 +	if ( sv_maxclients->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, sv.time);
 +		sv.time += 100;
 +		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;
 +		}
 +
 +		// 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 ) );
 +		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, sv.time);
 +	sv.time += 100;
 +	svs.time += 100;
 +}
 +
 +//===============================================================
 +
 +/*
 +==================
 +SV_KickAll_f
 +
 +Kick all users off of the server  FIXME: move to game
 +==================
 +*/
 +static void SV_KickAll_f( void ) {
 +	client_t	*cl;
 +	int			i;
 +
 +	// make sure server is running
 +	if ( !com_sv_running->integer ) {
 +		Com_Printf( "Server is not running.\n" );
 +		return;
 +	}
 +
 +	for ( i=0, cl=svs.clients ; i < sv_maxclients->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
 +	}
 +}
 +
 +/*
 +==================
 +SV_Kick_f
 +
 +Kick a user off of the server  FIXME: move to game
 +==================
 +*/
 +static void SV_Kick_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: kick <player name>\n");
 +		return;
 +	}
 +
 +	cl = SV_GetPlayerByHandle();
 +	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_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_GetPlayerByHandle();
 +
 +	if (!cl) {
 +		return;
 +	}
 +
 +	if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
 +		SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
 +		return;
 +	}
 +
 +	//FIXME: there is no auth server in Tremulous
 +#if 0
 +	// 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);
 +	}
 +#endif
 +}
 +
 +/*
 +==================
 +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;
 +	}
 +
 +	//FIXME: there is no auth server in Tremulous
 +#if 0
 +	// 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);
 +	}
 +#endif
 +}
 +
 +/*
 +==================
 +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_GetPlayerByHandle();
 +	if ( !cl ) {
 +		return;
 +	}
 +
 +	Com_Printf( "userinfo\n" );
 +	Com_Printf( "--------\n" );
 +	Info_Print( cl->userinfo );
 +}
 +
 +
 +/*
 +=================
 +SV_KillServer
 +=================
 +*/
 +static void SV_KillServer_f( void ) {
 +	SV_Shutdown( "killserver" );
 +}
 +
 +//===========================================================
 +
 +/*
 +==================
 +SV_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 ("kickAll", SV_KickAll_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);
 +	Cmd_AddCommand ("devmap", SV_Map_f);
 +	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/src/server/sv_client.c b/src/server/sv_client.c new file mode 100644 index 0000000..c5f7946 --- /dev/null +++ b/src/server/sv_client.c @@ -0,0 +1,1452 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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; + +	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; +	} + +	// send the challengeResponse +	challenge->pingTime = svs.time; +	NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge ); +} + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ +void SV_DirectConnect( netadr_t from ) { +	char		userinfo[MAX_INFO_STRING]; +	int			i; +	client_t	*cl, *newcl; +	client_t	temp; +	sharedEntity_t *ent; +	int			clientNum; +	int			version; +	int			qport; +	int			challenge; +	char		*password; +	int			startIndex; +	intptr_t		denied; +	int			count; +	char		*ip; + +	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", 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; +		} +	} +	 +	// don't let "ip" overflow userinfo string +	if ( NET_IsLocalAddress (from) ) +		ip = "localhost"; +	else +		ip = (char *)NET_AdrToString( from ); +	if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { +		NET_OutOfBandPrint( NS_SERVER, from, +			"print\nUserinfo string length exceeded.  " +			"Try removing setu cvars from your config.\n" ); +		return; +	} +	Info_SetValueForKey( userinfo, "ip", ip ); + +	// 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" ); +			return; +		} + +		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" ); +				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" ); +				Com_DPrintf ("Client %i rejected on a too high ping\n", i); +				return; +			} +		} +	} + +	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 ) ) { +			Com_Error( ERR_FATAL, "server is full on local connect\n" ); +			return; +		} +		else { +			NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full" ); +			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 = VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue ); // firstTime = qtrue +	if ( denied ) { +		// we can't just use VM_ArgPtr, because that is only valid inside a VM_Call +		char *str = VM_ExplicitArgPtr( gvm, denied ); + +		NET_OutOfBandPrint( NS_SERVER, from, "print\n%s", str ); +		Com_DPrintf ("Game rejected a connection: %s.\n", str); +		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 +	} + +	// 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 ); + + +	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); + +	// nuke user info +	SV_SetUserinfo( drop - svs.clients, "" ); +	 +	Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); +	drop->state = CS_ZOMBIE;		// become free in a few seconds + +	// 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; + +	// resend all configstrings using the cs commands since these are +	// no longer sent when the client is CS_PRIMED +	SV_UpdateConfigstrings( client ); + +	// 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", (int) (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", (int) (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", (int) (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 = 0, missionPack = 0, unreferenced = 1; +	char errorMessage[1024]; +	char pakbuf[MAX_QPATH], *pakptr; +	int numRefPaks; + +	if (!*cl->downloadName) +		return;	// Nothing being downloaded + +	if (!cl->download) { + 		// Chop off filename extension. +		Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); +		pakptr = Q_strrchr(pakbuf, '.'); +		 +		if(pakptr) +		{ +			*pakptr = '\0'; + +			// Check for pk3 filename extension +			if(!Q_stricmp(pakptr + 1, "pk3")) +			{ +				const char *referencedPaks = FS_ReferencedPakNames(); + +				// Check whether the file appears in the list of referenced +				// paks to prevent downloading of arbitrary files. +				Cmd_TokenizeStringIgnoreQuotes(referencedPaks); +				numRefPaks = Cmd_Argc(); + +				for(curindex = 0; curindex < numRefPaks; curindex++) +				{ +					if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf)) +					{ +						unreferenced = 0; + +						// now that we know the file is referenced, +						// check whether it's legal to download it. +						missionPack = FS_idPak(pakbuf, "missionpack"); +						idPack = missionPack || FS_idPak(pakbuf, BASEGAME); + +						break; +					} +				} +			} +		} + +		// We open the file here +		if ( !(sv_allowDownload->integer & DLF_ENABLE) || +			(sv_allowDownload->integer & DLF_NO_UDP) || +			idPack || unreferenced || +			( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { +			// cannot auto-download file +			if(unreferenced) +			{ +				Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", (int) (cl - svs.clients), cl->downloadName); +				Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName); +			} +			else if (idPack) { +				Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", (int) (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 & DLF_ENABLE) || +				(sv_allowDownload->integer & DLF_NO_UDP) ) { + +				Com_Printf("clientDownload: %d : \"%s\" download disabled", (int) (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", (int) (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; +		} +  +		Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); +		 +		// 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 ( sv_minRate->integer ) { +		if ( sv_minRate->integer < 1000 ) +			Cvar_Set( "sv_minRate", "1000" ); +		if ( sv_minRate->integer > rate ) +			rate = sv_minRate->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", (int) (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; +	char	*ip; +	int		i; +	int	len; + +	// 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 > sv_fps->integer ) { +			i = sv_fps->integer; +		} +		cl->snapshotMsec = 1000/i; +	} else { +		cl->snapshotMsec = 50; +	} +	 +	// TTimo +	// maintain the IP information +	// the banning code relies on this being consistently present +	if( NET_IsLocalAddress(cl->netchan.remoteAddress) ) +		ip = "localhost"; +	else +		ip = (char*)NET_AdrToString( cl->netchan.remoteAddress ); + +	val = Info_ValueForKey( cl->userinfo, "ip" ); +	if( val[0] ) +		len = strlen( ip ) - strlen( val ) + strlen( cl->userinfo ); +	else +		len = strlen( ip ) + 4 + strlen( cl->userinfo ); + +	if( len >= MAX_INFO_STRING ) +		SV_DropClient( cl, "userinfo string length exceeded" ); +	else +		Info_SetValueForKey( cl->userinfo, "ip", ip ); + +} + + +/* +================== +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 0 // flood protection in game for trem +	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; +	} +#endif + +	// 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); +			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; +	} + +	// this client has acknowledged the new gamestate so it's +	// safe to start sending it the real time again +	if( cl->oldServerTime && serverId == sv.serverId ){ +		Com_DPrintf( "%s acknowledged gamestate\n", cl->name ); +		cl->oldServerTime = 0; +	} + +	// 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", (int) (cl - svs.clients) ); +	} +//	if ( msg->readcount != msg->cursize ) { +//		Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); +//	} +}
\ No newline at end of file diff --git a/src/server/sv_game.c b/src/server/sv_game.c new file mode 100644 index 0000000..1f56ed0 --- /dev/null +++ b/src/server/sv_game.c @@ -0,0 +1,599 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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" + +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, traceType_t type ) { +	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, type ); + +	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 +==================== +*/ +intptr_t SV_GameSystemCalls( intptr_t *args ) { +	switch( args[0] ) { +	case G_PRINT: +		Com_Printf( "%s", (const char*)VMA(1) ); +		return 0; +	case G_ERROR: +		Com_Error( ERR_DROP, "%s", (const char*)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), TT_AABB ); +	case G_ENTITY_CONTACTCAPSULE: +		return SV_EntityContact( VMA(1), VMA(2), VMA(3), TT_CAPSULE ); +	case G_TRACE: +		SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_AABB ); +		return 0; +	case G_TRACECAPSULE: +		SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_CAPSULE ); +		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_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_REAL_TIME: +		return Com_RealTime( VMA(1) ); +	case G_SNAPVECTOR: +		Sys_SnapVector( VMA(1) ); +		return 0; + +	case G_SEND_GAMESTAT: +		SV_MasterGameStat( VMA(1) ); +		return 0; + +	//==================================== + +	case G_PARSE_ADD_GLOBAL_DEFINE: +		return Parse_AddGlobalDefine( VMA(1) ); +	case G_PARSE_LOAD_SOURCE: +		return Parse_LoadSourceHandle( VMA(1) ); +	case G_PARSE_FREE_SOURCE: +		return Parse_FreeSourceHandle( args[1] ); +	case G_PARSE_READ_TOKEN: +		return Parse_ReadTokenHandle( args[1], VMA(2) ); +	case G_PARSE_SOURCE_FILE_AND_LINE: +		return Parse_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + +	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: +		strncpy( VMA(1), VMA(2), args[3] ); +		return args[1]; + +	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: %ld", (long int) 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, sv.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 ) { +	// load the dll or bytecode +	gvm = VM_Create( "game", 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/src/server/sv_init.c b/src/server/sv_init.c new file mode 100644 index 0000000..c24b50b --- /dev/null +++ b/src/server/sv_init.c @@ -0,0 +1,706 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous 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.
 +
 +Tremulous 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 Tremulous; if not, write to the Free Software
 +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 +===========================================================================
 +*/
 +
 +#include "server.h"
 +
 +
 +/*
 +===============
 +SV_SendConfigstring
 +
 +Creates and sends the server command necessary to update the CS index for the
 +given client
 +===============
 +*/
 +static void SV_SendConfigstring(client_t *client, int index)
 +{
 +	int maxChunkSize = MAX_STRING_CHARS - 24;
 +	int len;
 +
 +	len = strlen(sv.configstrings[index]);
 +
 +	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, &sv.configstrings[index][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,
 +			sv.configstrings[index] );
 +	}
 +}
 +
 +/*
 +===============
 +SV_UpdateConfigstrings
 +
 +Called when a client goes from CS_PRIMED to CS_ACTIVE.  Updates all
 +Configstring indexes that have changed while the client was in CS_PRIMED
 +===============
 +*/
 +void SV_UpdateConfigstrings(client_t *client)
 +{
 +	int index;
 +
 +	for( index = 0; index <= MAX_CONFIGSTRINGS; index++ ) {
 +		// if the CS hasn't changed since we went to CS_PRIMED, ignore
 +		if(!client->csUpdated[index])
 +			continue;
 +
 +		// do not always send server info to all clients
 +		if ( index == CS_SERVERINFO && client->gentity &&
 +			(client->gentity->r.svFlags & SVF_NOSERVERINFO) ) {
 +			continue;
 +		}
 +		SV_SendConfigstring(client, index);
 +		client->csUpdated[index] = qfalse;
 +	}
 +}
 +
 +/*
 +===============
 +SV_SetConfigstring
 +
 +===============
 +*/
 +void SV_SetConfigstring (int index, const char *val) {
 +	int		len, i;
 +	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_ACTIVE ) {
 +				if ( client->state == CS_PRIMED )
 +					client->csUpdated[ index ] = qtrue;
 +				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 );
 +			SV_SendConfigstring(client, index);
 +		}
 +	}
 +}
 +
 +/*
 +===============
 +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;
 +	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;
 +
 +	for (i=0 ; i<sv_maxclients->integer ; i++) {
 +		// save when the server started for each client already connected
 +		if (svs.clients[i].state >= CS_CONNECTED) {
 +			svs.clients[i].oldServerTime = sv.time;
 +		}
 +	}
 +
 +	// 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();
 +
 +	// run a few frames to allow everything to settle
 +	for (i = 0;i < 3; i++)
 +	{
 +		VM_Call (gvm, GAME_RUN_FRAME, sv.time);
 +		sv.time += 100;
 +		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;
 +
 +			// connect the client again
 +			denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse ) );	// 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 {
 +				// when we get the next packet from a connected client,
 +				// the new gamestate will be sent
 +				svs.clients[i].state = CS_CONNECTED;
 +			}
 +		}
 +	}	
 +
 +	// run another frame to allow things to look at all the players
 +	VM_Call (gvm, GAME_RUN_FRAME, sv.time);
 +	sv.time += 100;
 +	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_Init (void) {
 +	SV_AddOperatorCommands ();
 +
 +	// serverinfo vars
 +	Cvar_Get ("timelimit", "0", CVAR_SERVERINFO);
 +	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_minRate = Cvar_Get ("sv_minRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
 +	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 );
 +
 +	// systeminfo
 +	Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM );
 +	sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM );
 +	sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO );
 +	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 );
 +
 +	sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO);
 +	Cvar_Get ("sv_dlURL", "", CVAR_SERVERINFO | CVAR_ARCHIVE);
 +	sv_wwwDownload = Cvar_Get ("sv_wwwDownload", "1",
 +		CVAR_SYSTEMINFO|CVAR_ARCHIVE);
 +	sv_wwwBaseURL = Cvar_Get ("sv_wwwBaseURL", "",
 +		CVAR_SYSTEMINFO|CVAR_ARCHIVE);
 +	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_dequeuePeriod = Cvar_Get ("sv_dequeuePeriod", "500", CVAR_ARCHIVE );
 +}
 +
 +
 +/*
 +==================
 +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\n\"\n", 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 (%s) -----\n", finalmsg );
 +
 +	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
 +	if( sv_killserver->integer != 2 )
 +		CL_Disconnect( qfalse );
 +}
 +
 diff --git a/src/server/sv_main.c b/src/server/sv_main.c new file mode 100644 index 0000000..d054653 --- /dev/null +++ b/src/server/sv_main.c @@ -0,0 +1,895 @@ +/*
 +===========================================================================
 +Copyright (C) 1999-2005 Id Software, Inc.
 +Copyright (C) 2000-2006 Tim Angus
 +
 +This file is part of Tremulous.
 +
 +Tremulous 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.
 +
 +Tremulous 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 Tremulous; 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_wwwBaseURL;
 +cvar_t	*sv_wwwDownload;
 +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_minRate;
 +cvar_t	*sv_maxRate;
 +cvar_t	*sv_minPing;
 +cvar_t	*sv_maxPing;
 +cvar_t	*sv_pure;
 +cvar_t	*sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
 +cvar_t	*sv_dequeuePeriod;
 +
 +/*
 +=============================================================================
 +
 +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 ] ) );
 +				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;
 +//	}
 +
 +	// do not send commands until the gamestate has been sent
 +	if( client->state < CS_PRIMED )
 +		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);
 +
 +	// Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
 +	// The actual cause of the bug is probably further downstream
 +	// and should maybe be addressed later, but this certainly
 +	// fixes the problem for now
 +	if ( strlen ((char *)message) > 1022 ) {
 +		return;
 +	}
 +
 +	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++) {
 +		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	"Tremulous"
 +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 ( !strchr( 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
 +}
 +
 +/*
 +=================
 +SV_MasterGameStat
 +=================
 +*/
 +void SV_MasterGameStat( const char *data )
 +{
 +	static netadr_t	adr;
 +
 +	if( !com_dedicated || com_dedicated->integer != 2 )
 +		return;		// only dedicated servers send stats
 +
 +  Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME );
 +  if( !NET_StringToAdr( MASTER_SERVER_NAME, &adr ) )
 +  {
 +    Com_Printf( "Couldn't resolve address: %s\n", MASTER_SERVER_NAME );
 +    return;
 +  }
 +  else
 +  {
 +    if( !strstr( ":", MASTER_SERVER_NAME ) )
 +      adr.port = BigShort( PORT_MASTER );
 +
 +    Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", MASTER_SERVER_NAME,
 +      adr.ip[0], adr.ip[1], adr.ip[2], adr.ip[3],
 +      BigShort( adr.port ) );
 +  }
 +
 +  Com_Printf ("Sending gamestat to %s\n", MASTER_SERVER_NAME );
 +  NET_OutOfBandPrint( NS_SERVER, adr, "gamestat %s", data );
 +}
 +
 +/*
 +==============================================================================
 +
 +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];
 +
 +	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];
 +
 +	/*
 +	 * Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
 +	 * to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
 +	 */
 +
 +	// A maximum challenge length of 128 should be more than plenty.
 +	if(strlen(Cmd_Argv(1)) > 128)
 +		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, "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 ( (unsigned)( time - lasttime ) < 500u ) {
 +		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", (char *) &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, "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;
 +		}
 +
 +		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 ) {
 +			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");
 +		Cvar_Set( "sv_killserver", "0" );
 +		return;
 +	}
 +
 +	if (!com_sv_running->integer)
 +	{
 +		if(com_dedicated->integer)
 +		{
 +			// Block indefinitely until something interesting happens
 +			// on STDIN.
 +			NET_Sleep(-1);
 +		}
 +		
 +		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 * com_timescale->value;
 +	// don't let it scale below 1ms
 +	if(frameMsec < 1)
 +	{
 +		Cvar_Set("timescale", va("%f", sv_fps->integer / 1000.0f));
 +		frameMsec = 1;
 +	}
 +
 +	sv.timeResidual += msec;
 +
 +	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( va( "map %s\n", Cvar_VariableString( "mapname" ) ) );
 +		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( va( "map %s\n", Cvar_VariableString( "mapname" ) ) );
 +		return;
 +	}
 +
 +	if( sv.restartTime && sv.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();
 +
 +	// run the game simulation in chunks
 +	while ( sv.timeResidual >= frameMsec ) {
 +		sv.timeResidual -= frameMsec;
 +		svs.time += frameMsec;
 +		sv.time += frameMsec;
 +
 +		// let everything in the world think and move
 +		VM_Call (gvm, GAME_RUN_FRAME, sv.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/src/server/sv_net_chan.c b/src/server/sv_net_chan.c new file mode 100644 index 0000000..e1d835d --- /dev/null +++ b/src/server/sv_net_chan.c @@ -0,0 +1,208 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#include "../qcommon/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/src/server/sv_rankings.c b/src/server/sv_rankings.c new file mode 100644 index 0000000..20d24fd --- /dev/null +++ b/src/server/sv_rankings.c @@ -0,0 +1,1538 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 ) +	{ +		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/src/server/sv_snapshot.c b/src/server/sv_snapshot.c new file mode 100644 index 0000000..47471ba --- /dev/null +++ b/src/server/sv_snapshot.c @@ -0,0 +1,693 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 +	if( client->oldServerTime ) { +		// The server has not yet got an acknowledgement of the +		// new gamestate from this client, so continue to send it +		// a time as if the server has not restarted. Note from +		// the client's perspective this time is strictly speaking +		// incorrect, but since it'll be busy loading a map at +		// the time it doesn't really matter. +		MSG_WriteLong (msg, sv.time + client->oldServerTime); +	} else { +		MSG_WriteLong (msg, sv.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; +		} +	} +	if ( sv_minRate->integer ) { +		if ( sv_minRate->integer < 1000 ) +			Cvar_Set( "sv_minRate", "1000" ); +		if ( sv_minRate->integer > rate ) +			rate = sv_minRate->integer; +	} + +	rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate * com_timescale->value; + +	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 server 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 + (1000.0 / sv_fps->integer * com_timescale->value); +		return; +	} +	 +	// normal rate / snapshotMsec calculation +	rateMsec = SV_RateMsec(client, msg->cursize); + +	if ( rateMsec < client->snapshotMsec * com_timescale->value) { +		// never send more packets than this, no matter what the rate is at +		rateMsec = client->snapshotMsec * com_timescale->value; +		client->rateDelayed = qfalse; +	} else { +		client->rateDelayed = qtrue; +	} + +	client->nextSnapshotTime = svs.time + rateMsec * com_timescale->value; + +	// 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 * com_timescale->value) +			client->nextSnapshotTime = svs.time + 1000 * com_timescale->value; +	} +} + + +/* +======================= +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 ); + +	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/src/server/sv_world.c b/src/server/sv_world.c new file mode 100644 index 0000000..d40cc21 --- /dev/null +++ b/src/server/sv_world.c @@ -0,0 +1,692 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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; +	traceType_t	collisionType; +} 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, traceType_t type ) { +	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, type); + +	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->collisionType); + +		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, traceType_t type ) { +	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, type ); +	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.collisionType = type; + +	// 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; +} + +  | 
