/*
Copyright (C) 1996-1997 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/*
QuakeTM
Transactional implementation of the original Quake game
Copyright (C) 2008 Barcelona Supercomputing Center
Author: Vladimir Gajinov
*/

// sv_user.c -- server code for moving users

#include "qwsvdef.h"

// THREADPRIVATE ---------------------------------------------------------

extern int				onground;
extern int				waterlevel;
extern int				watertype;
extern playermove_t		pmove;
extern int				net_drop;
extern client_t*		host_client;
extern int				msg_readcount;
extern qboolean			msg_badread;
extern movevars_t		movevars;
THREADPRIVATE (onground, waterlevel, watertype, pmove, net_drop, host_client, msg_readcount, msg_badread, movevars)


#define PLAYTOUCH_EDICTS (MAX_EDICTS+7)/8
edict_t					*sv_player;
usercmd_t				cmnd;	// GAJA changed to cmnd to avoid ambiguos variable names (cmd)
byte 					playertouch[PLAYTOUCH_EDICTS];
vec3_t					pmove_mins, pmove_maxs;
THREADPRIVATE(sv_player, cmnd, playertouch, pmove_mins, pmove_maxs)


// -----------------------------------------------------------------------




cvar_t	cl_rollspeed = {"cl_rollspeed", "200"};
cvar_t	cl_rollangle = {"cl_rollangle", "2.0"};
cvar_t	sv_spectalk = {"sv_spectalk", "1"};

cvar_t	sv_mapcheck	= {"sv_mapcheck", "1"};

extern	vec3_t	player_mins;

extern int fp_messages, fp_persecond, fp_secondsdead;
extern char fp_msg[];
extern cvar_t pausable;



// GAJA declarations of intermodule functions with attribute added
TM_CALLABLE
void SV_New_f (void);
TM_CALLABLE
void SV_Soundlist_f (void);
TM_CALLABLE
void SV_Modellist_f (void);
TM_CALLABLE
void SV_PreSpawn_f (void);
TM_CALLABLE
void SV_Spawn_f (void);
TM_CALLABLE
void SV_SpawnSpectator (void);
TM_CALLABLE
void SV_Begin_f (void);
TM_PURE
void SV_NextDownload_f (void);
TM_PURE
void SV_BeginDownload_f(void);
TM_CALLABLE
void SV_Say (qboolean team);
TM_CALLABLE
void SV_Say_f(void);
TM_CALLABLE
void SV_Say_Team_f(void);
TM_CALLABLE
void SV_Pings_f (void);
TM_CALLABLE
void SV_Kill_f (void);
TM_CALLABLE
void SV_Pause_f (void);
TM_CALLABLE
void SV_Drop_f (void);
TM_CALLABLE
void SV_PTrack_f (void);
TM_CALLABLE
void SV_Rate_f (void);
TM_CALLABLE
void SV_Msg_f (void);
TM_CALLABLE
void SV_SetInfo_f (void);
TM_CALLABLE
void SV_ShowServerinfo_f (void);
TM_CALLABLE
void SV_NoSnap_f(void);

TM_CALLABLE
float V_CalcRoll (vec3_t angles, vec3_t velocity);
TM_CALLABLE
void AddLinksToPmove ( areanode_t *node );

TM_CALLABLE
void SV_PreRunCmd(void);
TM_CALLABLE
void SV_RunCmd (usercmd_t *ucmd);
TM_CALLABLE
void SV_PostRunCmd(void);



/*
============================================================

USER STRINGCMD EXECUTION

host_client and sv_player will be valid.
============================================================
*/

/*
================
SV_New_f

Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/
TM_CALLABLE
void SV_New_f (void)
{
	char		*gamedir;
	int			playernum;

	if (host_client->state == cs_spawned)
		return;

	host_client->state = cs_connected;
	host_client->connection_started = realtime;

	// send the info about the new client to all connected clients
	//	SV_FullClientUpdate (host_client, &sv.reliable_datagram);
	//	host_client->sendinfo = true;

	gamedir = Info_ValueForKey (svs.info, "*gamedir");
	if (!gamedir[0])
		gamedir = "qw";

	CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
		//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
		//spawns.  These functions are written to not overflow
		if (host_client->num_backbuf) {
			Con_Printf("WARNING %s: [SV_New] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
			host_client->num_backbuf = 0;
			SZ_Clear(&host_client->netchan.message);
		}

		// send the serverdata
		MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
		MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION);
		MSG_WriteLong (&host_client->netchan.message, svs.spawncount);
		MSG_WriteString (&host_client->netchan.message, gamedir);

		playernum = NUM_FOR_EDICT(host_client->edict)-1;
		if (host_client->spectator)
			playernum |= 128;
		MSG_WriteByte (&host_client->netchan.message, playernum);

		// send full levelname
		MSG_WriteString (&host_client->netchan.message, PR_GetString(sv.edicts->v.message));

		// send the movevars
		MSG_WriteFloat(&host_client->netchan.message, movevars.gravity);
		MSG_WriteFloat(&host_client->netchan.message, movevars.stopspeed);
		MSG_WriteFloat(&host_client->netchan.message, movevars.maxspeed);
		MSG_WriteFloat(&host_client->netchan.message, movevars.spectatormaxspeed);
		MSG_WriteFloat(&host_client->netchan.message, movevars.accelerate);
		MSG_WriteFloat(&host_client->netchan.message, movevars.airaccelerate);
		MSG_WriteFloat(&host_client->netchan.message, movevars.wateraccelerate);
		MSG_WriteFloat(&host_client->netchan.message, movevars.friction);
		MSG_WriteFloat(&host_client->netchan.message, movevars.waterfriction);
		MSG_WriteFloat(&host_client->netchan.message, movevars.entgravity);

		// send music
		MSG_WriteByte (&host_client->netchan.message, svc_cdtrack);
		MSG_WriteByte (&host_client->netchan.message, sv.edicts->v.sounds);

		// send server info string
		MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
		MSG_WriteString (&host_client->netchan.message, va("fullserverinfo \"%s\"\n", svs.info) );
	});
}

/*
==================
SV_Soundlist_f
==================
*/
TM_CALLABLE
void SV_Soundlist_f (void)
{
	char		**s;
	int			n;

	if (host_client->state != cs_connected)
	{
		Con_Printf ("soundlist not valid -- allready spawned\n");
		return;
	}

	// handle the case of a level changing while a client was connecting
	if ( Q_atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Con_Printf ("SV_Soundlist_f from different level\n");
		SV_New_f ();
		return;
	}

	n = Q_atoi(Cmd_Argv(2));
	
	CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
		//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
		//spawns.  These functions are written to not overflow
		if (host_client->num_backbuf) {
			Con_Printf("WARNING %s: [SV_Soundlist] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
			host_client->num_backbuf = 0;
			SZ_Clear(&host_client->netchan.message);
		}

		MSG_WriteByte (&host_client->netchan.message, svc_soundlist);
		MSG_WriteByte (&host_client->netchan.message, n);
		for (s = sv.sound_precache+1 + n ; 
			*s && host_client->netchan.message.cursize < (MAX_MSGLEN/2); 
			s++, n++)
			MSG_WriteString (&host_client->netchan.message, *s);

		MSG_WriteByte (&host_client->netchan.message, 0);

		// next msg
		if (*s)
			MSG_WriteByte (&host_client->netchan.message, n);
		else
			MSG_WriteByte (&host_client->netchan.message, 0);
	});
}

/*
==================
SV_Modellist_f
==================
*/
TM_CALLABLE
void SV_Modellist_f (void)
{
	char		**s;
	int			n;

	if (host_client->state != cs_connected)
	{
		Con_Printf ("modellist not valid -- allready spawned\n");
		return;
	}
	
	// handle the case of a level changing while a client was connecting
	if ( Q_atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Con_Printf ("SV_Modellist_f from different level\n");
		SV_New_f ();
		return;
	}

	n = Q_atoi(Cmd_Argv(2));

	CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
		//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
		//spawns.  These functions are written to not overflow
		if (host_client->num_backbuf) {
			Con_Printf("WARNING %s: [SV_Modellist] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
			host_client->num_backbuf = 0;
			SZ_Clear(&host_client->netchan.message);
		}

		MSG_WriteByte (&host_client->netchan.message, svc_modellist);
		MSG_WriteByte (&host_client->netchan.message, n);
		for (s = sv.model_precache+1+n ; 
			*s && host_client->netchan.message.cursize < (MAX_MSGLEN/2); 
			s++, n++)
			MSG_WriteString (&host_client->netchan.message, *s);
		MSG_WriteByte (&host_client->netchan.message, 0);

		// next msg
		if (*s)
			MSG_WriteByte (&host_client->netchan.message, n);
		else
			MSG_WriteByte (&host_client->netchan.message, 0);
	});
}

/*
==================
SV_PreSpawn_f
==================
*/
TM_CALLABLE
void SV_PreSpawn_f (void)
{
	unsigned	buf;

	if (host_client->state != cs_connected)
	{
		Con_Printf ("prespawn not valid -- allready spawned\n");
		return;
	}
	
	// handle the case of a level changing while a client was connecting
	if ( Q_atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Con_Printf ("SV_PreSpawn_f from different level\n");
		SV_New_f ();
		return;
	}
	
	buf = Q_atoi(Cmd_Argv(2));
	if (buf >= sv.num_signon_buffers)
		buf = 0;

	if (!buf) {
		// GAJA: lines commented to avoid checksums. Instead act as everything is ok
		//host_client->checksum = sv.worldmodel->checksum;

 	//	// should be three numbers following containing checksums
		//unsigned  check;
		//check = atoi(Cmd_Argv(3));

		//Con_DPrintf("Client check = %d\n", check);

		//if (sv_mapcheck.value && check != sv.worldmodel->checksum &&
		//check != sv.worldmodel->checksum2) 
		//{
		//	SV_ClientPrintf (host_client, PRINT_HIGH, 
		//		"Map model file does not match (%s), %i != %i/%i.\n"
		//		"You may need a new version of the map, or the proper install files.\n",
		//		sv.modelname, check, sv.worldmodel->checksum, sv.worldmodel->checksum2);
		//	SV_DropClient (host_client); 
		//	return;
		//}
		//host_client->checksum = check;
	}

	CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
		//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
		//spawns.  These functions are written to not overflow
		if (host_client->num_backbuf) {
			Con_Printf("WARNING %s: [SV_PreSpawn] Back buffered (%d0, clearing", host_client->name, host_client->netchan.message.cursize); 
			host_client->num_backbuf = 0;
			SZ_Clear(&host_client->netchan.message);
		}

		SZ_Clear(&host_client->netchan.message);  // GAJA OVERFLOW: added this to avoid overflow
		// Debug the overflow case
		//if ((host_client->netchan.message.cursize + sv.signon_buffer_size[buf]) > host_client->netchan.message.maxsize)
		//{
		//	printf("\n\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
		//	printf("PRESPAWN OVERFLOW\n\n");
		//	fwrite( host_client->netchan.message.data, sizeof(byte), host_client->netchan.message.cursize, stdout);
		//	fflush(stdout);
		//	printf("\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
		//	fwrite( sv.signon_buffers[buf], sizeof(byte), sv.signon_buffer_size[buf], stdout);
		//	fflush(stdout);    
		//	printf("\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n\n");
		//}

		SZ_Write (&host_client->netchan.message, 
			sv.signon_buffers[buf],
			sv.signon_buffer_size[buf]);

		buf++;
		if (buf == sv.num_signon_buffers)
		{	// all done prespawning
			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, va("cmd spawn %i 0\n",svs.spawncount) );
		}
		else
		{	// need to prespawn more
			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, 
				va("cmd prespawn %i %i\n", svs.spawncount, buf) );
		}
	});
}

/*
==================
SV_Spawn_f
==================
*/
TM_CALLABLE
void SV_Spawn_f (void)
{
	int		i;
	client_t	*client;
	edict_t	*ent;
	eval_t *val;
	int n;

	if (host_client->state != cs_connected)
	{
		Con_Printf ("Spawn not valid -- allready spawned\n");
		return;
	}

// handle the case of a level changing while a client was connecting
	if ( Q_atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Con_Printf ("SV_Spawn_f from different level\n");
		SV_New_f ();
		return;
	}

	n = Q_atoi(Cmd_Argv(2));

	// make sure n is valid
	if ( n < 0 || n > MAX_CLIENTS )
	{
		Con_Printf ("SV_Spawn_f invalid client start\n");
		SV_New_f ();
		return;
	}


	CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
		// send all current names, colors, and frag counts
		// FIXME: is this a good thing?
		SZ_Clear (&host_client->netchan.message);

		// send current status of all other players

		// normally this could overflow, but no need to check due to backbuf
		for (i=n, client = svs.clients + n ; i<MAX_CLIENTS ; i++, client++)
		{
			if (client != host_client) 			
            {
#ifdef DO_LOCK
                if (client->msg_lock != NULL) 
				{
					CRITICAL_MESSAGE(client->msg_lock, CLIENT_MSG_LOCK, client->clientId, {
						SV_FullClientUpdateToClient (client, host_client);
					});
				}
				else
					SV_FullClientUpdateToClient (client, host_client);
#else                        
                CRITICAL_MESSAGE(client->msg_lock, CLIENT_MSG_LOCK, client->clientId, {
                    SV_FullClientUpdateToClient (client, host_client);
			    });                  
#endif
            }               
		}
		
		// send all current light styles
		for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
		{
			ClientReliableWrite_Begin (host_client, svc_lightstyle, 
				3 + (sv.lightstyles[i] ? Q_strlen(sv.lightstyles[i]) : 1));
			ClientReliableWrite_Byte (host_client, (char)i);
			ClientReliableWrite_String (host_client, sv.lightstyles[i]);
		}

		// set up the edict
		ent = host_client->edict;

		Q_memset (&ent->v, 0, progs->entityfields * 4);
		ent->v.colormap = NUM_FOR_EDICT(ent);
		ent->v.team = 0;	// FIXME
		ent->v.netname = PR_SetString(host_client->name);

		host_client->entgravity = 1.0;
		val = GetEdictFieldValue(ent, "gravity");
		if (val)
			val->_float = 1.0;
		host_client->maxspeed = sv_maxspeed.value;
		val = GetEdictFieldValue(ent, "maxspeed");
		if (val)
			val->_float = sv_maxspeed.value;

		//
		// force stats to be updated
		//
		Q_memset (host_client->stats, 0, sizeof(host_client->stats));

		ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
		ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
		ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);

		ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
		ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
		ClientReliableWrite_Long (host_client, pr_global_struct->total_monsters);

		ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
		ClientReliableWrite_Byte (host_client, STAT_SECRETS);
		ClientReliableWrite_Long (host_client, pr_global_struct->found_secrets);

		ClientReliableWrite_Begin (host_client, svc_updatestatlong, 6);
		ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
		ClientReliableWrite_Long (host_client, pr_global_struct->killed_monsters);

		// get the client to check and download skins
		// when that is completed, a begin command will be issued
		ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
		ClientReliableWrite_String (host_client, "skins\n" );
	});

}

/*
==================
SV_SpawnSpectator
==================
*/
TM_CALLABLE
void SV_SpawnSpectator (void)
{
	int		i;
	edict_t	*e;

	VectorCopy (vec3_origin, sv_player->v.origin);
	VectorCopy (vec3_origin, sv_player->v.view_ofs);
	sv_player->v.view_ofs[2] = 22;

	// search for an info_playerstart to spawn the spectator at
	for (i=MAX_CLIENTS-1 ; i<sv.num_edicts ; i++)
	{
		e = EDICT_NUM(i);
		if (!Q_strcmp(PR_GetString(e->v.classname), "info_player_start"))
		{
			VectorCopy (e->v.origin, sv_player->v.origin);
			return;
		}
	}

}

/*
==================
SV_Begin_f
==================
*/
TM_CALLABLE
void SV_Begin_f (void)
{
	unsigned pmodel = 0, emodel = 0;
	int		i;

	if (host_client->state == cs_spawned)
		return; // don't begin again

	host_client->state = cs_spawned;
	
	// handle the case of a level changing while a client was connecting
	if ( Q_atoi(Cmd_Argv(1)) != svs.spawncount )
	{
		Con_Printf ("SV_Begin_f from different level\n");
		SV_New_f ();
		return;
	}

	if (host_client->spectator)
	{
		SV_SpawnSpectator ();

		if (SpectatorConnect) {
			// copy spawn parms out of the client_t
			for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
				(&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
	
			// call the spawn function
			pr_global_struct->time = sv.time;
			pr_global_struct->self = EDICT_TO_PROG(sv_player);
			PR_ExecuteProgram (SpectatorConnect);
		}
	}
	else
	{
		// copy spawn parms out of the client_t
		for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
      (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];

		// call the spawn function
		pr_global_struct->time = sv.time;
		pr_global_struct->self = EDICT_TO_PROG(sv_player);
		PR_ExecuteProgram (pr_global_struct->ClientConnect);

		// actually spawn the player
		pr_global_struct->time = sv.time;
		pr_global_struct->self = EDICT_TO_PROG(sv_player);
		PR_ExecuteProgram (pr_global_struct->PutClientInServer);	

		// GAJA: send spawn spot number to the client
		if (host_client->edict->v.send_spawn_msg) {
			CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
				ClientReliableWrite_Begin(host_client, svc_spawnspotinfo, 5);
				ClientReliableWrite_Long(host_client, (long) host_client->edict->v.spawnspot);
			});
			host_client->edict->v.send_spawn_msg = 0.0;
		}
	}

	// clear the net statistics, because connecting gives a bogus picture
	host_client->netchan.frame_latency = 0;
	host_client->netchan.frame_rate = 0;
	host_client->netchan.drop_count = 0;
	host_client->netchan.good_count = 0;

	//check he's not cheating

	pmodel = Q_atoi(Info_ValueForKey (host_client->userinfo, "pmodel"));
	emodel = Q_atoi(Info_ValueForKey (host_client->userinfo, "emodel"));

	if (pmodel != sv.model_player_checksum ||
		emodel != sv.eyes_player_checksum)
		SV_BroadcastPrintf (PRINT_HIGH, "%s WARNING: non standard player/eyes model detected\n", host_client->name);

	// if we are paused, tell the client
	if (sv.paused) {
		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin (host_client, svc_setpause, 2);
			ClientReliableWrite_Byte (host_client, sv.paused);
		});
		SV_ClientPrintf(host_client, PRINT_HIGH, "Server is paused.\n");
	}

}

//=============================================================================

/*
==================
SV_NextDownload_f
==================
*/
// GAJA: since we disabled this function in TM we can mark it as TM_PURE
TM_PURE
void SV_NextDownload_f (void)
{
	byte	buffer[1024];
	int		r;
	int		percent;
	int		size;

	if (!host_client->download)
		return;

	// GAJA
	// if we are in transaction don't allow this request
	if (inTransaction==true)
	{
		Con_Printf("Disabled in TM version of server\n");
		Sys_Printf("Client request for NextDownload rejected because TM\n");
		return;
	}


	r = host_client->downloadsize - host_client->downloadcount;
	if (r > 768)
		r = 768;
	r = fread (buffer, 1, r, host_client->download);

	CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
		ClientReliableWrite_Begin (host_client, svc_download, 6+r);
		ClientReliableWrite_Short (host_client, r);


		host_client->downloadcount += r;
		size = host_client->downloadsize;
		if (!size)
			size = 1;
		percent = host_client->downloadcount*100/size;
		ClientReliableWrite_Byte (host_client, percent);
		ClientReliableWrite_SZ (host_client, buffer, r);
	});

	if (host_client->downloadcount != host_client->downloadsize)
		return;

	fclose (host_client->download);
	host_client->download = NULL;

}

void OutofBandPrintf(netadr_t where, char *fmt, ...)
{
	va_list		argptr;
	char	send[1024];
	
	send[0] = 0xff;
	send[1] = 0xff;
	send[2] = 0xff;
	send[3] = 0xff;
	send[4] = A2C_PRINT;
	va_start (argptr, fmt);
	vsprintf (send+5, fmt, argptr);
	va_end (argptr);

	NET_SendPacket (strlen(send)+1, send, where);
}

/*
==================
SV_NextUpload
==================
*/
void SV_NextUpload (void)
{
// WARNING CLEANUP	
//	byte	buffer[1024];
//	int		r;
	int		percent;
	int		size;
//	client_t *client;

	if (!*host_client->uploadfn) {
		SV_ClientPrintf(host_client, PRINT_HIGH, "Upload denied\n");

		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
			ClientReliableWrite_String (host_client, "stopul");
		});

		// suck out rest of packet
		size = MSG_ReadShort ();	MSG_ReadByte ();
		msg_readcount += size;
		return;
	}

	size = MSG_ReadShort ();
	percent = MSG_ReadByte ();

	if (!host_client->upload)
	{
		host_client->upload = fopen(host_client->uploadfn, "wb");
		if (!host_client->upload) {
			Sys_Printf("Can't create %s\n", host_client->uploadfn);

			CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
				ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
				ClientReliableWrite_String (host_client, "stopul");
			});

			*host_client->uploadfn = 0;
			return;
		}
		Sys_Printf("Receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
		if (host_client->remote_snap)
			OutofBandPrintf(host_client->snap_from, "Server receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
	}

	fwrite (net_message.data + msg_readcount, 1, size, host_client->upload);
	msg_readcount += size;

	Con_DPrintf ("UPLOAD: %d received\n", size);

	if (percent != 100) {
		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
			ClientReliableWrite_String (host_client, "nextul\n");
		});
	} else {
		fclose (host_client->upload);
		host_client->upload = NULL;

		Sys_Printf("%s upload completed.\n", host_client->uploadfn);

		if (host_client->remote_snap) {
			char *p;

			if ((p = strchr(host_client->uploadfn, '/')) != NULL)
				p++;
			else
				p = host_client->uploadfn;
				OutofBandPrintf(host_client->snap_from, "%s upload completed.\nTo download, enter:\ndownload %s\n", 
				host_client->uploadfn, p);
		}
	}

}

/*
==================
SV_BeginDownload_f
==================
*/
// GAJA: since we disabled this function in TM we can mark it as TM_PURE
TM_PURE
void SV_BeginDownload_f(void)
{
	char	*name;
	extern	cvar_t	allow_download;
	extern	cvar_t	allow_download_skins;
	extern	cvar_t	allow_download_models;
	extern	cvar_t	allow_download_sounds;
	extern	cvar_t	allow_download_maps;
	extern	int		file_from_pak; // ZOID did file come from pak?

	// GAJA
	// if we are in transaction don't allow this request
	if (inTransaction==true)
	{
		Con_Printf("Disabled in TM version of server\n");
		Sys_Printf("Client request for BeginDownload rejected because of TM\n");
		return;
	}


	name = Cmd_Argv(1);
// hacked by zoid to allow more conrol over download
		// first off, no .. or global allow check
	if (Q_strstr (name, "..") || !allow_download.value
		// leading dot is no good
		|| *name == '.' 
		// leading slash bad as well, must be in subdir
		|| *name == '/'
		// next up, skin check
		|| (Q_strncmp(name, "skins/", 6) == 0 && !allow_download_skins.value)
		// now models
		|| (Q_strncmp(name, "progs/", 6) == 0 && !allow_download_models.value)
		// now sounds
		|| (Q_strncmp(name, "sound/", 6) == 0 && !allow_download_sounds.value)
		// now maps (note special case for maps, must not be in pak)
		|| (Q_strncmp(name, "maps/", 6) == 0 && !allow_download_maps.value)
		// MUST be in a subdirectory	
		|| !Q_strstr (name, "/") )	
	{	// don't allow anything with .. path
		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin (host_client, svc_download, 4);
			ClientReliableWrite_Short (host_client, -1);
			ClientReliableWrite_Byte (host_client, 0);
		});
		return;
	}

	if (host_client->download) {
		fclose (host_client->download);
		host_client->download = NULL;
	}

	// lowercase name (needed for casesen file systems)
	{
		char *p;

		for (p = name; *p; p++)
			*p = (char)tolower(*p);
	}


	host_client->downloadsize = COM_FOpenFile (name, &host_client->download);
	host_client->downloadcount = 0;

	if (!host_client->download
		// special check for maps, if it came from a pak file, don't allow
		// download  ZOID
		|| (Q_strncmp(name, "maps/", 5) == 0 && file_from_pak))
	{
		if (host_client->download) {
			fclose(host_client->download);
			host_client->download = NULL;
		}

		Sys_Printf ("Couldn't download %s to %s\n", name, host_client->name);
		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin (host_client, svc_download, 4);
			ClientReliableWrite_Short (host_client, -1);
			ClientReliableWrite_Byte (host_client, 0);
		});
		return;
	}

	SV_NextDownload_f ();
	Sys_Printf ("Downloading %s to %s\n", name, host_client->name);
}

//=============================================================================

/*
==================
SV_Say
==================
*/
TM_CALLABLE
void SV_Say (qboolean team)
{
	client_t *client;
	int		j, tmp;
	char	*p;
	char	text[2048];
	char	t1[32], *t2;

	if (Cmd_Argc () < 2)
		return;

	if (team)
	{
		Q_strncpy (t1, Info_ValueForKey (host_client->userinfo, "team"), 31);
		t1[31] = 0;
	}

	if (host_client->spectator && (!sv_spectalk.value || team))
		Q_sprintf (text, "[SPEC] %s: ", host_client->name); 
	else if (team)
		Q_sprintf (text, "(%s): ", host_client->name);  
	else {
		Q_sprintf (text, "%s: ", host_client->name);  
	}

	if (fp_messages) {
		if (!sv.paused && realtime<host_client->lockedtill) {
			SV_ClientPrintf(host_client, PRINT_CHAT,
				"You can't talk for %d more seconds\n", 
					(int) (host_client->lockedtill - realtime));
			return;
		}
		tmp = host_client->whensaidhead - fp_messages + 1;
		if (tmp < 0)
			tmp = 10+tmp;
		if (!sv.paused &&
			host_client->whensaid[tmp] && (realtime-host_client->whensaid[tmp] < fp_persecond)) {
			host_client->lockedtill = realtime + fp_secondsdead;
			if (fp_msg[0])
				SV_ClientPrintf(host_client, PRINT_CHAT,
					"FloodProt: %s\n", fp_msg);
			else
				SV_ClientPrintf(host_client, PRINT_CHAT,
					"FloodProt: You can't talk for %d seconds.\n", fp_secondsdead);
			return;
		}
		host_client->whensaidhead++;
		if (host_client->whensaidhead > 9)
			host_client->whensaidhead = 0;
		host_client->whensaid[host_client->whensaidhead] = realtime;
	}

	p = Cmd_Args();

	if (*p == '"')
	{
		p++;
		p[Q_strlen(p)-1] = 0;
	}

	Q_strcat(text, p);
	Q_strcat(text, "\n");

	Sys_Printf ("%s", text);

	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
	{
		if (client->state != cs_spawned)
			continue;
		if (host_client->spectator && !sv_spectalk.value)
			if (!client->spectator)
				continue;

		if (team)
		{
			// the spectator team
			if (host_client->spectator) {
				if (!client->spectator)
					continue;
			} else {
				t2 = Info_ValueForKey (client->userinfo, "team");
				if (Q_strcmp(t1, t2) || client->spectator)
					continue;	// on different teams
			}
		}
		SV_ClientPrintf(client, PRINT_CHAT, "%s", text);
	}
}


/*
==================
SV_Say_f
==================
*/
TM_CALLABLE
void SV_Say_f(void)
{
	SV_Say (false);
}


/*
==================
SV_Say_Team_f
==================
*/
TM_CALLABLE
void SV_Say_Team_f(void)
{
	SV_Say (true);
}



//============================================================================

/*
=================
SV_Pings_f

The client is showing the scoreboard, so send new ping times for all
clients
=================
*/
TM_CALLABLE
void SV_Pings_f (void)
{
	client_t *client;
	int		j;

	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
	{
		if (client->state != cs_spawned)
			continue;

		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin (host_client, svc_updateping, 4);
			ClientReliableWrite_Byte (host_client, j);
			ClientReliableWrite_Short (host_client, SV_CalcPing(client));
			ClientReliableWrite_Begin (host_client, svc_updatepl, 4);
			ClientReliableWrite_Byte (host_client, j);
			ClientReliableWrite_Byte (host_client, client->lossage);
		});
	}
}



/*
==================
SV_Kill_f
==================
*/
TM_CALLABLE
void SV_Kill_f (void)
{
	if (sv_player->v.health <= 0)
	{
		SV_ClientPrintf (host_client, PRINT_HIGH, "Can't suicide -- allready dead!\n");
		return;
	}
	
	pr_global_struct->time = sv.time;
	pr_global_struct->self = EDICT_TO_PROG(sv_player);
	PR_ExecuteProgram (pr_global_struct->ClientKill);

	// GAJA: send spawn spot number to the client
	if (host_client->edict->v.send_spawn_msg) 
	{
		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin(host_client, svc_spawnspotinfo, 5);
			ClientReliableWrite_Long(host_client, (long) host_client->edict->v.spawnspot);
		});
		host_client->edict->v.send_spawn_msg = 0.0;
	}
}


/*
==================
SV_TogglePause
==================
*/
TM_CALLABLE
void SV_TogglePause (const char *msg)
{
	int i;
	client_t *cl;

	sv.paused = (sv.paused == true) ? false : true;		// turn ON/OFF

	if (msg)
		SV_BroadcastPrintf (PRINT_HIGH, "%s", msg);

	// send notification to all clients
	for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
	{
		if (!cl->state)
			continue;
		CRITICAL_MESSAGE(cl->msg_lock, CLIENT_MSG_LOCK, cl->clientId, {
			ClientReliableWrite_Begin (cl, svc_setpause, 2);
			ClientReliableWrite_Byte (cl, sv.paused);
		});
	}
}


/*
==================
SV_Pause_f
==================
*/
TM_CALLABLE
void SV_Pause_f (void)
{
// WARNING CLEANUP	
//	int i;
//	client_t *cl;
	char st[sizeof(host_client->name) + 32];

	if (!pausable.value) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Pause not allowed.\n");
		return;
	}

	if (host_client->spectator) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Spectators can not pause.\n");
		return;
	}

	if (sv.paused)
		Q_sprintf (st, "%s paused the game\n", host_client->name);  
	else
		Q_sprintf (st, "%s unpaused the game\n", host_client->name);  

	SV_TogglePause(st);
}


/*
=================
SV_Drop_f

The client is going to disconnect, so remove the connection immediately
=================
*/
TM_CALLABLE
void SV_Drop_f (void)
{
	SV_EndRedirect ();
	if (!host_client->spectator)
		SV_BroadcastPrintf (PRINT_HIGH, "%s dropped\n", host_client->name);
	SV_DropClient (host_client);	
}

/*
=================
SV_PTrack_f

Set the client which spectator will track
=================
*/
TM_CALLABLE
void SV_PTrack_f (void)
{
	int		i;
	edict_t *ent, *tent;
	
	if (!host_client->spectator)
		return;

	if (Cmd_Argc() != 2)
	{
		// turn off tracking
		host_client->spec_track = 0;
		ent = EDICT_NUM(host_client - svs.clients + 1);
		tent = EDICT_NUM(0);
		ent->v.goalentity = EDICT_TO_PROG(tent);
		return;
	}
	
	i = Q_atoi(Cmd_Argv(1));
	if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned ||
		svs.clients[i].spectator) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Invalid client to track\n");
		host_client->spec_track = 0;
		ent = EDICT_NUM(host_client - svs.clients + 1);
		tent = EDICT_NUM(0);
		ent->v.goalentity = EDICT_TO_PROG(tent);
		return;
	}
	host_client->spec_track = i + 1; // now tracking

	ent = EDICT_NUM(host_client - svs.clients + 1);
	tent = EDICT_NUM(i + 1);
	ent->v.goalentity = EDICT_TO_PROG(tent);
}


/*
=================
SV_Rate_f

Change the bandwidth estimate for a client
=================
*/
TM_CALLABLE
void SV_Rate_f (void)
{
	int		rate;
	
	if (Cmd_Argc() != 2)
	{
		SV_ClientPrintf (host_client, PRINT_HIGH, "Current rate is %i\n",
			(int)(1.0/host_client->netchan.rate + 0.5));
		return;
	}
	
	rate = Q_atoi(Cmd_Argv(1));
	if (rate < 500)
		rate = 500;
	if (rate > 10000)
		rate = 10000;

	SV_ClientPrintf (host_client, PRINT_HIGH, "Net rate set to %i\n", rate);
	host_client->netchan.rate = 1.0/rate;
}


/*
=================
SV_Msg_f

Change the message level for a client
=================
*/
TM_CALLABLE
void SV_Msg_f (void)
{	
	if (Cmd_Argc() != 2)
	{
		SV_ClientPrintf (host_client, PRINT_HIGH, "Current msg level is %i\n",
			host_client->messagelevel);
		return;
	}
	
	host_client->messagelevel = Q_atoi(Cmd_Argv(1));

	SV_ClientPrintf (host_client, PRINT_HIGH, "Msg level set to %i\n", host_client->messagelevel);
}

/*
==================
SV_SetInfo_f

Allow clients to change userinfo
==================
*/
TM_CALLABLE
void SV_SetInfo_f (void)
{
	int i;
	char oldval[MAX_INFO_STRING];


	if (Cmd_Argc() == 1)
	{
		Con_Printf ("User info settings:\n");
		Info_Print (host_client->userinfo);
		return;
	}

	if (Cmd_Argc() != 3)
	{
		Con_Printf ("usage: setinfo [ <key> <value> ]\n");
		return;
	}

	if (Cmd_Argv(1)[0] == '*')
		return;		// don't set priveledged values

	Q_strcpy(oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));

	Info_SetValueForKey (host_client->userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING);

	// name is extracted below in ExtractFromUserInfo
	//	strncpy (host_client->name, Info_ValueForKey (host_client->userinfo, "name")
	//		, sizeof(host_client->name)-1);	
	//	SV_FullClientUpdate (host_client, &sv.reliable_datagram);
	//	host_client->sendinfo = true;

	if (!Q_strcmp(Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)), oldval))
		return; // key hasn't changed

	// process any changed values
	SV_ExtractFromUserinfo (host_client);

	i = host_client - svs.clients;

	CRITICAL_MESSAGE(sv_reldatagram_lock, SV_RELDATAGRAM_LOCK, 0, {
		MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
		MSG_WriteByte (&sv.reliable_datagram, i);
		MSG_WriteString (&sv.reliable_datagram, Cmd_Argv(1));
		MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));
	});
}

/*
==================
SV_ShowServerinfo_f

Dumps the serverinfo info string
==================
*/
TM_CALLABLE
void SV_ShowServerinfo_f (void)
{
	Info_Print (svs.info);
}

TM_CALLABLE
void SV_NoSnap_f(void)
{
	if (*host_client->uploadfn) {
		*host_client->uploadfn = 0;
		SV_BroadcastPrintf (PRINT_HIGH, "%s refused remote screenshot\n", host_client->name);
	}
}

// GAJA: Added for client respawning
TM_CALLABLE
void SV_Respawn_f(void)
{
	SZ_Clear (&host_client->netchan.message);
	SZ_Clear (&host_client->datagram);
	host_client->num_backbuf = 0;

	// GAJA: respawn the client
	pr_global_struct->time = sv.time;
	pr_global_struct->self = EDICT_TO_PROG(sv_player);
	PR_ExecuteProgram (pr_global_struct->TraceRespawn); // PutClientInServer

	// GAJA: send spawn spot number to the client
	if (host_client->edict->v.send_spawn_msg) 
	{
		CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
			ClientReliableWrite_Begin(host_client, svc_spawnspotinfo, 5);
			ClientReliableWrite_Long(host_client, (long) host_client->edict->v.spawnspot);
		});
		host_client->edict->v.send_spawn_msg = 0.0;
	}
}

typedef struct ucmd_s
{
	char	*name;
  	TM_CALLABLE
	void	(*func) (void); 
} ucmd_t;



ucmd_t ucmds[] =
{
	{"new", SV_New_f},
	{"modellist", SV_Modellist_f},
	{"soundlist", SV_Soundlist_f},
	{"prespawn", SV_PreSpawn_f},
	{"spawn", SV_Spawn_f},
	{"begin", SV_Begin_f},

	{"drop", SV_Drop_f},
	{"pings", SV_Pings_f},

// issued by hand at client consoles	
	{"rate", SV_Rate_f},
	{"kill", SV_Kill_f},
	{"pause", SV_Pause_f},
	{"msg", SV_Msg_f},

	{"say", SV_Say_f},
	{"say_team", SV_Say_Team_f},

	{"setinfo", SV_SetInfo_f},

	{"serverinfo", SV_ShowServerinfo_f},

	{"download", SV_BeginDownload_f},	// GAJA Disabled in TM version
	{"nextdl", SV_NextDownload_f},		// GAJA Disabled in TM version

	{"ptrack", SV_PTrack_f},			//ZOID - used with autocam

	{"snap", SV_NoSnap_f},

	{"respawn", SV_Respawn_f},			// GAJA: added this to enable respawn of the client - hack
	
	{NULL, NULL}
};

/*
==================
SV_ExecuteUserCommand
==================
*/
TM_CALLABLE
void SV_ExecuteUserCommand (char *s)
{
	ucmd_t	*u;
	
	Cmd_TokenizeString (s);
	sv_player = host_client->edict;

	SV_BeginRedirect (RD_CLIENT);

	for (u=ucmds ; u->name ; u++)
		if (!Q_strcmp (Cmd_Argv(0), u->name) )
		{
			u->func ();
			break;
		}

	if (!u->name)
		Con_Printf ("Bad user command: %s\n", Cmd_Argv(0));

	SV_EndRedirect ();
}

/*
===========================================================================

USER CMD EXECUTION

===========================================================================
*/

/*
===============
V_CalcRoll

Used by view and sv_user
===============
*/
TM_CALLABLE
float V_CalcRoll (vec3_t angles, vec3_t velocity)
{
	vec3_t	forward, right, up;
	float	sign;
	float	side;
	float	value;
	
	AngleVectors (angles, forward, right, up);
	side = DotProduct (velocity, right);
	sign = side < 0 ? -1 : 1;
	side = Q_fabs(side);
	
	value = cl_rollangle.value;

	if (side < cl_rollspeed.value)
		side = side * value / cl_rollspeed.value;
	else
		side = value;
	
	return side*sign;
	
}




//============================================================================

//vec3_t	pmove_mins, pmove_maxs;	// GAJA: moved to the threadprivate section in the beggining

/*
====================
AddLinksToPmove

Traverse the areanode tree and find touched entities. Add those entities to the pmove
The function only checks entities for a single areanode. In the end we go down the tree 
to search other nodes.
The list is traversed and for each object we check in all 3 coordinates. 
If the object is out of range we skip it, otherwise we include it in the pmove.
For solid objects we set the model from sv_models, for others we set it to NULL
====================
*/
TM_CALLABLE
void AddLinksToPmove ( areanode_t *node )
{
	link_t		*l, *next;
	edict_t		*check;
	int			pl;
	int			i;
	physent_t	*pe;
    // GAJA:
    qboolean    max_reached = false;

	pl = EDICT_TO_PROG(sv_player);

	LOCKPRINT("THREAD %d\tAddLinksToPmove\n", GetThreadId());

	CRITICAL_AREA(node->areanode_lock, {

		//l = &node->solid_edicts;
		//next = l->next;
		
		// touch linked edicts
		// traverse the entity list for the node
		for (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next)        
		{
			//while (1)
			//{
					//l = next;
					//if (l == &node->solid_edicts)
					//{
					//        break;
					//}
			
			next = l->next;
			check = EDICT_FROM_AREA(l);

			if (check->v.owner == pl)
			{
				continue;		// player's own missile
			}

			// only check solid objects
			if (check->v.solid == SOLID_BSP 
				|| check->v.solid == SOLID_BBOX 
				|| check->v.solid == SOLID_SLIDEBOX)
			{
				// skip our own entity
				if (check == sv_player)
				{
					continue;
				}

				// check all 3 coordinates
				for (i=0 ; i<3 ; i++)
					if (check->v.absmin[i] > pmove_maxs[i]
					|| check->v.absmax[i] < pmove_mins[i])
						break;
				if (i != 3)
					continue;
				
				// this entity may be affected so add it to pmove

				if (pmove.numphysent == MAX_PHYSENTS)
				{
                    max_reached = true;
                    break;
				}                                
				
				pe = &pmove.physents[pmove.numphysent];
				pmove.numphysent++;

				// save the origin of this entity to pmove stucture
				VectorCopy (check->v.origin, pe->origin);
				
				pe->info = NUM_FOR_EDICT(check);

				if (check->v.solid == SOLID_BSP)
				{
					pe->model = sv.models[(int)(check->v.modelindex)];
				}
				else
				{
					pe->model = NULL;
					VectorCopy (check->v.mins, pe->mins);
					VectorCopy (check->v.maxs, pe->maxs);
				}
			}
		}
	
	});		// END_CRITICAL

    // GAJA:
    if (max_reached)
        return;        

	// recurse down both sides
	if (node->axis == -1)
		return;

	if ( pmove_maxs[node->axis] > node->dist )
		AddLinksToPmove ( node->children[0] );
	if ( pmove_mins[node->axis] < node->dist )
		AddLinksToPmove ( node->children[1] );
}


/*
===========
SV_PreRunCmd
===========
Done before running a player command.  Clears the touch array
*/

TM_CALLABLE
void SV_PreRunCmd(void)
{
	Q_memset(playertouch, 0, PLAYTOUCH_EDICTS * sizeof(byte));
}


/*
===========
SV_RunCmd
===========
*/
TM_CALLABLE
void SV_RunCmd (usercmd_t *ucmd)
{
	edict_t		*ent;
	int			i, n;
	int			oldmsec;

	cmnd = *ucmd;

	// chop up very long commands
	if (cmnd.msec > 50)
	{
		oldmsec = ucmd->msec;
		cmnd.msec = oldmsec/2;
		SV_RunCmd (&cmnd);
		cmnd.msec = oldmsec/2;
		cmnd.impulse = 0;
		SV_RunCmd (&cmnd);
		return;
	}

// ------------------------------------------

    LOCK_COARSE
    TRANSACTION_COARSE

		if (!sv_player->v.fixangle)
			VectorCopy (ucmd->angles, sv_player->v.v_angle);

		sv_player->v.button0 = ucmd->buttons & 1;
		sv_player->v.button2 = (ucmd->buttons & 2)>>1;
		if (ucmd->impulse)
			sv_player->v.impulse = ucmd->impulse;

		//
		// angles
		// show 1/3 the pitch angle and all the roll angle	
		if (sv_player->v.health > 0)
		{
			if (!sv_player->v.fixangle)
			{
				sv_player->v.angles[PITCH] = -sv_player->v.v_angle[PITCH]/3;
				sv_player->v.angles[YAW] = sv_player->v.v_angle[YAW];
			}
			sv_player->v.angles[ROLL] = 
				V_CalcRoll (sv_player->v.angles, sv_player->v.velocity)*4;
		}

		host_frametime = ucmd->msec * 0.001;
		if (host_frametime > 0.1)
			host_frametime = 0.1;

		if (!host_client->spectator)
		{
			pr_global_struct->frametime = host_frametime;
			pr_global_struct->time = sv.time;
			pr_global_struct->self = EDICT_TO_PROG(sv_player);
			PR_ExecuteProgram (pr_global_struct->PlayerPreThink);

			SV_RunThink (sv_player);

			// GAJA: send spawn spot number to the client
			if ((host_client->edict->v.send_spawn_msg) != 0.0) 
			{
				CRITICAL_MESSAGE(host_client->msg_lock, CLIENT_MSG_LOCK, host_client->clientId, {
					ClientReliableWrite_Begin(host_client, svc_spawnspotinfo, 5);
					ClientReliableWrite_Long(host_client, (long) host_client->edict->v.spawnspot);
				});
				host_client->edict->v.send_spawn_msg = 0.0;
			}

		}
	
		for (i=0 ; i<3 ; i++)
			pmove.origin[i] = sv_player->v.origin[i] + (sv_player->v.mins[i] - player_mins[i]);
		VectorCopy (sv_player->v.velocity, pmove.velocity);
		VectorCopy (sv_player->v.v_angle, pmove.angles);

		pmove.spectator = host_client->spectator;
		pmove.waterjumptime = sv_player->v.teleport_time;
		pmove.numphysent = 1;
		pmove.physents[0].model = sv.worldmodel;
		pmove.cmd = *ucmd;
		pmove.dead = (sv_player->v.health <= 0) ? true : false;
		pmove.oldbuttons = host_client->oldbuttons;

    TRANSACTION_COARSE_END
    UNLOCK_COARSE

// ------------------------------------------

	movevars.entgravity = host_client->entgravity;
	movevars.maxspeed = host_client->maxspeed;

	for (i=0 ; i<3 ; i++)
	{
		pmove_mins[i] = pmove.origin[i] - 256;
		pmove_maxs[i] = pmove.origin[i] + 256;
	}
        
    LOCK_COARSE
    TRANSACTION_COARSE
	//LOCK_FINE(areanode_lock)
		AddLinksToPmove ( sv_areanodes );
	//UNLOCK_FINE(areanode_lock)
    TRANSACTION_COARSE_END
    UNLOCK_COARSE

// ------------------------------------------

    LOCK_COARSE
    TRANSACTION_COARSE

		PlayerMove ();


		host_client->oldbuttons = pmove.oldbuttons;
		sv_player->v.teleport_time = pmove.waterjumptime;
		sv_player->v.waterlevel = waterlevel;
		sv_player->v.watertype = watertype;

		if (onground != -1)
		{
			sv_player->v.flags = (int)sv_player->v.flags | FL_ONGROUND;
			sv_player->v.groundentity = EDICT_TO_PROG(EDICT_NUM(pmove.physents[onground].info));
		}
		else
			sv_player->v.flags = (int)sv_player->v.flags & ~FL_ONGROUND;
		
			for (i=0 ; i<3 ; i++)
			sv_player->v.origin[i] = pmove.origin[i] - (sv_player->v.mins[i] - player_mins[i]);


		VectorCopy (pmove.velocity, sv_player->v.velocity);
		VectorCopy (pmove.angles, sv_player->v.v_angle);

    TRANSACTION_COARSE_END
    UNLOCK_COARSE

// ------------------------------------------

	if (!host_client->spectator)
	{
        LOCK_COARSE
        TRANSACTION_COARSE

			// link into place and touch triggers
			SV_LinkEdict (sv_player, true);

			// touch other objects
			for (i=0 ; i<pmove.numtouch ; i++)
			{
				n = pmove.physents[pmove.touchindex[i]].info;
				ent = EDICT_NUM(n);
				if (!ent->v.touch || (playertouch[n/8]&(1<<(n%8))))
					continue;
				pr_global_struct->self = EDICT_TO_PROG(ent);
				pr_global_struct->other = EDICT_TO_PROG(sv_player);
				PR_ExecuteProgram (ent->v.touch);
				playertouch[n/8] |= 1 << (n%8);
			}

        TRANSACTION_COARSE_END
        UNLOCK_COARSE
	}
}

/*
===========
SV_PostRunCmd
===========
Done after running a player command.
*/
TM_CALLABLE
void SV_PostRunCmd(void)
{
	// run post-think
    LOCK_COARSE
    TRANSACTION_COARSE
    	if (!host_client->spectator) {
	        pr_global_struct->time = sv.time;
        	pr_global_struct->self = EDICT_TO_PROG(sv_player);
    		PR_ExecuteProgram (pr_global_struct->PlayerPostThink);
	        SV_RunNewmis ();
        } else if (SpectatorThink) {
    		pr_global_struct->time = sv.time;
    		pr_global_struct->self = EDICT_TO_PROG(sv_player);
	        PR_ExecuteProgram (SpectatorThink);
        }
    TRANSACTION_COARSE_END
    UNLOCK_COARSE
}


/*
===================
SV_ExecuteClientMessage

The current net_message is parsed for the given client
===================
*/
TM_CALLABLE
void SV_ExecuteClientMessage (client_t *cl)
{
	int		c;
	char	s[2048];
	usercmd_t	oldest, oldcmd, newcmd;
	client_frame_t	*frame;
	vec3_t o;
	qboolean	move_issued = false; //only allow one move command
	int		checksumIndex;
	byte	checksum, calculatedChecksum;
	int		seq_hash;


	// calc ping time
	frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
	frame->ping_time = realtime - frame->senttime;

	// make sure the reply sequence number matches the incoming
	// sequence number 
	if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence)
		cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;
	else
		cl->send_message = false;	// don't reply, sequences have slipped		

	// save time for ping calculations
	cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
	cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;

	host_client = cl;
	sv_player = host_client->edict;

//	seq_hash = (cl->netchan.incoming_sequence & 0xffff) ; // ^ QW_CHECK_HASH;
	seq_hash = cl->netchan.incoming_sequence;
	
	// mark time so clients will know how much to predict
	// other players
 	cl->localtime = sv.time;
	cl->delta_sequence = -1;	// no delta unless requested
	
        while (1)
	{
		if (msg_badread)
		{
            LOCK_COARSE
            TRANSACTION_COARSE
		        Con_Printf ("SV_ReadClientMessage: badread\n");
		        SV_DropClient (cl);
            TRANSACTION_COARSE_END
            UNLOCK_COARSE
			return;
		}	

		c = MSG_ReadByte ();
		if (c == -1)
			break;

		//DEBUGPRINT(READ_PACKETS,HIGH) printf("GAJA: Character read in ExecuteMessage: \t%d\n", c);
				
		switch (c)
		{
		default:
            LOCK_COARSE
            TRANSACTION_COARSE
		        DEBUGPRINT(READ_PACKETS,HIGH) 
		        {
			        PRINT("WRONG CHARACTER: %d = %c\n", c, c);    // GAJA:
			        PrintBuffer(net_message.data, net_message.cursize);
		        }
		        Con_Printf ("SV_ReadClientMessage: unknown command char\n", c);
		        SV_DropClient (cl);
            TRANSACTION_COARSE_END
            UNLOCK_COARSE
			return;
						
		case clc_nop:
			//DEBUGPRINT(READ_PACKETS,HIGH) printf("MSGREAD: clc_nop\n");
			break;

		case clc_delta:
			//DEBUGPRINT(READ_PACKETS,HIGH) printf("MSGREAD: clc_delta\n");
			cl->delta_sequence = MSG_ReadByte ();
			break;

		case clc_move:
			//DEBUGPRINT(READ_PACKETS,HIGH) printf("MSGREAD: clc_move\n");
			if (move_issued)
				return;		// someone is trying to cheat...

			move_issued = true;

			checksumIndex = MSG_GetReadCount();
			checksum = (byte)MSG_ReadByte ();

			// read loss percentage
			cl->lossage = MSG_ReadByte();

			MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
			MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
			MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);

			if ( cl->state != cs_spawned )
				break;

			// if the checksum fails, ignore the rest of the packet
			calculatedChecksum = COM_BlockSequenceCRCByte(
				net_message.data + checksumIndex + 1,
				MSG_GetReadCount() - checksumIndex - 1,
				seq_hash);

			// GAJA: commented this to be able to run traces
// 			if (calculatedChecksum != checksum)
// 			{
// 				Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n", 
// 					cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum);
// 				return;
// 			}

			if (!sv.paused) {
				SV_PreRunCmd();

				if (net_drop < 20)
				{
					while (net_drop > 2)
					{
						SV_RunCmd (&cl->lastcmd);
						net_drop--;
					}
					if (net_drop > 1)
						SV_RunCmd (&oldest);
					if (net_drop > 0)
						SV_RunCmd (&oldcmd);
				}
				SV_RunCmd (&newcmd);

				SV_PostRunCmd();
			}
			
			cl->lastcmd = newcmd;
			cl->lastcmd.buttons = 0; // avoid multiple fires on lag

			break;


		case clc_stringcmd:	
			//DEBUGPRINT(READ_PACKETS,HIGH) printf("MSGREAD: clc_stringcmd\n");
	        MSG_ReadString (s, sizeof(s));
            LOCK_COARSE
            TRANSACTION_COARSE
				SV_ExecuteUserCommand (s);	// GAJA transactified
            TRANSACTION_COARSE_END
            UNLOCK_COARSE
			break;

		case clc_tmove:
			//DEBUGPRINT(READ_PACKETS,HIGH) printf("MSGREAD: clc_tmove\n");
			// teleport - only allowed to spectators                        
			o[0] = MSG_ReadCoord();
			o[1] = MSG_ReadCoord();
			o[2] = MSG_ReadCoord();
			// only allowed by spectators
			if (host_client->spectator) {
				VectorCopy(o, sv_player->v.origin);
				SV_LinkEdict(sv_player, false);
			}
			break;

		case clc_upload:
			//DEBUGPRINT(READ_PACKETS,HIGH) printf("MSGREAD: clc_upload\n");
			// GAJA 
			// Disabled in TM version of server
			// SV_NextUpload();
			Con_Printf("Disabled in TM version of server\n");
			Sys_Printf("Client request for NextUpload rejected because TM\n");
			break;

		}
	}
}

/*
==============
SV_UserInit
==============
*/
void SV_UserInit (void)
{
	Cvar_RegisterVariable (&cl_rollspeed);
	Cvar_RegisterVariable (&cl_rollangle);
	Cvar_RegisterVariable (&sv_spectalk);
	Cvar_RegisterVariable (&sv_mapcheck);
}


