/*
 * Copyright (C) 1997-2001 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.
 *
 */
/* sv_user.c -- server code for moving users */

#include "server.h"

edict_t        *sv_player;

/*
 * ============================================================
 *
 * USER STRINGCMD EXECUTION
 *
 * sv_client and sv_player will be valid.
 * ============================================================
 */

/*
 * ================== SV_BeginDemoServer ==================
 */
void
SV_BeginDemoserver(void)
{
	char		name[MAX_OSPATH];

	Com_sprintf(name, sizeof(name), "demos/%s", sv.name);
	FS_FOpenFile(name, &sv.demofile, FS_READ);
	if (!sv.demofile)
		Com_Error(ERR_DROP, "Couldn't open %s\n", name);
}

/*
 * ================ 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. ================
 */
void
SV_New_f(void)
{
	char           *gamedir;
	int		playernum;
	edict_t        *ent;

	Com_DPrintf("New() from %s\n", sv_client->name);

	if (sv_client->state != cs_connected) {
		Com_Printf("New not valid -- already spawned\n");
		return;
	}
	/* demo servers just dump the file message */
	if (sv.state == ss_demo) {
		SV_BeginDemoserver();
		return;
	}
	
	/* serverdata needs to go over for all types of servers */
	/* to make sure the protocol is right, and to set the gamedir */
	gamedir = Cvar_VariableString("gamedir");

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

	if (sv.state == ss_cinematic || sv.state == ss_pic)
		playernum = -1;
	else
		playernum = sv_client - svs.clients;
	MSG_WriteShort(&sv_client->netchan.message, playernum);

	/* send full levelname */
	MSG_WriteString(&sv_client->netchan.message, sv.configstrings[CS_NAME]);

	/* game server */
	if (sv.state == ss_game) {
		/* set up the entity for the client */
		ent = EDICT_NUM(playernum + 1);
		ent->s.number = playernum + 1;
		sv_client->edict = ent;
		memset(&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));

		/* begin fetching configstrings */
		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString(&sv_client->netchan.message, va("cmd configstrings %i 0\n", svs.spawncount));
	}
}

/*
 * ================== SV_Configstrings_f ==================
 */
void
SV_Configstrings_f(void)
{
	int		start;

	Com_DPrintf("Configstrings() from %s\n", sv_client->name);

	if (sv_client->state != cs_connected) {
		Com_Printf("configstrings not valid -- already spawned\n");
		return;
	}
	/* handle the case of a level changing while a client was connecting */
	if (atoi(Cmd_Argv(1)) != svs.spawncount) {
		Com_Printf("SV_Configstrings_f from different level\n");
		SV_New_f();
		return;
	}
	start = atoi(Cmd_Argv(2));
	if (start < 0) {
		start = 0;	/* sku - catch negative offsets */
	}
	/* write a packet full of data */

	while (sv_client->netchan.message.cursize < MAX_MSGLEN / 2
	    && start < MAX_CONFIGSTRINGS) {
		if (sv.configstrings[start][0]) {
			int		length;

			/*
			 * sku - write configstrings that exceed MAX_QPATH in
			 * proper-sized chunks
			 */
			length = strlen(sv.configstrings[start]);
			if (length > MAX_QPATH) {
				length = MAX_QPATH;
			}
			MSG_WriteByte(&sv_client->netchan.message, svc_configstring);
			MSG_WriteShort(&sv_client->netchan.message, start);

			/*
			 * MSG_WriteString (&sv_client->netchan.message,
			 * sv.configstrings[start]);
			 */
			SZ_Write(&sv_client->netchan.message, sv.configstrings[start], length);
			MSG_WriteByte(&sv_client->netchan.message, 0);
		}
		start++;
	}

	/* send next command */

	if (start == MAX_CONFIGSTRINGS) {
		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i 0\n", svs.spawncount));
	} else {
		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString(&sv_client->netchan.message, va("cmd configstrings %i %i\n", svs.spawncount, start));
	}
}

/*
 * ================== SV_Baselines_f ==================
 */
void
SV_Baselines_f(void)
{
	int		start;
	entity_state_t	nullstate;
	entity_state_t *base;

	Com_DPrintf("Baselines() from %s\n", sv_client->name);

	if (sv_client->state != cs_connected) {
		Com_Printf("baselines not valid -- already spawned\n");
		return;
	}
	/* handle the case of a level changing while a client was connecting */
	if (atoi(Cmd_Argv(1)) != svs.spawncount) {
		Com_Printf("SV_Baselines_f from different level\n");
		SV_New_f();
		return;
	}
	start = atoi(Cmd_Argv(2));

	if (start < 0) {
		start = 0;
	}
	memset(&nullstate, 0, sizeof(nullstate));

	/* write a packet full of data */

	while (sv_client->netchan.message.cursize < MAX_MSGLEN / 2
	    && start < MAX_EDICTS) {
		base = &sv.baselines[start];
		if (base->modelindex || base->sound || base->effects) {
			MSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline);
			MSG_WriteDeltaEntity(&nullstate, base, &sv_client->netchan.message, true, true);
		}
		start++;
	}

	/* send next command */

	if (start == MAX_EDICTS) {
		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString(&sv_client->netchan.message, va("precache %i\n", svs.spawncount));
	} else {
		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
		MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i %i\n", svs.spawncount, start));
	}
}

/*
 * ================== SV_Begin_f ==================
 */
void
SV_Begin_f(void)
{
	Com_DPrintf("Begin() from %s\n", sv_client->name);

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

	/* call the game begin function */
	ge->ClientBegin(sv_player);

	Cbuf_InsertFromDefer();
}

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

/*
 * ================== SV_NextDownload_f ==================
 */
void
SV_NextDownload_f(void)
{
	int		r;
	int		percent;
	int		size;

	if (!sv_client->download)
		return;

	r = sv_client->downloadsize - sv_client->downloadcount;
	if (r > 1024)
		r = 1024;

	MSG_WriteByte(&sv_client->netchan.message, svc_download);
	MSG_WriteShort(&sv_client->netchan.message, r);

	sv_client->downloadcount += r;
	size = sv_client->downloadsize;
	if (!size)
		size = 1;
	percent = sv_client->downloadcount * 100 / size;
	MSG_WriteByte(&sv_client->netchan.message, percent);
	SZ_Write(&sv_client->netchan.message,
	    sv_client->download + sv_client->downloadcount - r, r);

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

	FS_FreeFile(sv_client->download);
	sv_client->download = NULL;
}

/*
 * ================== SV_BeginDownload_f ==================
 */
void
SV_BeginDownload_f(void)
{
	char           *name;
	int		offset = 0;

	name = Cmd_Argv(1);

	if (Cmd_Argc() > 2)
		offset = atoi(Cmd_Argv(2));	/* downloaded offset */

	/* hacked by zoid to allow more conrol over download */
	/* first off, no .. or global allow check */
	if (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 */
	    || (strncmp(name, "players/", 8) == 0 && !allow_download_players->value)
	/* now models */
	    || (strncmp(name, "models/", 7) == 0 && !allow_download_models->value)
	/* now sounds */
	    || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
	/* now maps (note special case for maps, must not be in pak) */
	    || (strncmp(name, "maps/", 5) == 0 && !allow_download_maps->value)
	/* MUST be in a subdirectory, unless a pk3	 */
	    || (!strstr(name, "/") && strcmp(name + strlen(name) - 4, ".pk3")
	    && strcmp(name + strlen(name) - 4, ".pk2")
	    && strcmp(name + strlen(name) - 4, ".zip"))) {	/* don't allow anything
								 * with .. path */
		MSG_WriteByte(&sv_client->netchan.message, svc_download);
		MSG_WriteShort(&sv_client->netchan.message, -1);
		MSG_WriteByte(&sv_client->netchan.message, 0);
		return;
	}
	if (sv_client->download)
		FS_FreeFile(sv_client->download);

	sv_client->downloadsize = FS_LoadFile(name, (void **)&sv_client->download);
	sv_client->downloadcount = offset;

	if (offset > sv_client->downloadsize)
		sv_client->downloadcount = sv_client->downloadsize;

	if (!sv_client->download
	/* special check for maps, if it came from a pak file, don't allow */
	/* download  ZOID */
	    || (strncmp(name, "maps/", 5) == 0 && file_from_pak)) {
		Com_DPrintf("Couldn't download %s to %s\n", name, sv_client->name);
		if (sv_client->download) {
			FS_FreeFile(sv_client->download);
			sv_client->download = NULL;
		}
		MSG_WriteByte(&sv_client->netchan.message, svc_download);
		MSG_WriteShort(&sv_client->netchan.message, -1);
		MSG_WriteByte(&sv_client->netchan.message, 0);
		return;
	}
	SV_NextDownload_f();
	Com_DPrintf("Downloading %s to %s\n", name, sv_client->name);
}



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


/*
 * ================= SV_Disconnect_f
 *
 * The client is going to disconnect, so remove the connection immediately
 * =================
 */
void
SV_Disconnect_f(void)
{
	/* SV_EndRedirect (); */
	SV_DropClient(sv_client);
}


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

void
SV_Nextserver(void)
{
	char           *v;
	/* ZOID, ss_pic can be nextserver'd in coop mode */
	if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
		return;		/* can't nextserver while playing a normal game */

	svs.spawncount++;	/* make sure another doesn't sneak in */
	v = Cvar_VariableString("nextserver");

	if (!v[0] && sv_killserver->value)
		Cbuf_AddText("killserver\n");
	else {
		Cbuf_AddText(v);
		Cbuf_AddText("\n");
	}
	Cvar_Set("nextserver", "");
}

/*
 * ================== SV_Nextserver_f
 *
 * A cinematic has completed or been aborted by a client, so move to the next
 * server, ==================
 */
void
SV_Nextserver_f(void)
{
	if (atoi(Cmd_Argv(1)) != svs.spawncount) {
		Com_DPrintf("Nextserver() from wrong level, from %s\n", sv_client->name);
		return;		/* leftover from last server */
	}
	Com_DPrintf("Nextserver() from %s\n", sv_client->name);

	SV_Nextserver();
}

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

ucmd_t		ucmds   [] =
{
	/* auto issued */
	{"new", SV_New_f},
	{"configstrings", SV_Configstrings_f},
	{"baselines", SV_Baselines_f},
	{"begin", SV_Begin_f},

	{"nextserver", SV_Nextserver_f},

	{"disconnect", SV_Disconnect_f},

	/* issued by hand at client consoles	 */
	{"info", SV_ShowServerinfo_f},

	{"download", SV_BeginDownload_f},
	{"nextdl", SV_NextDownload_f},

	{NULL, NULL}
};

/*
 * ================== SV_ExecuteUserCommand ==================
 */
void
SV_ExecuteUserCommand(char *s)
{
	ucmd_t         *u;

	/*******
	 * Security Fix... This is being set to false so that client's can't
	 * macro expand variables on the server.  It seems unlikely that a
	 * client ever ought to need to be able to do this...
	 * old line = Cmd_TokenizeString (s, true);
	 *******/
	Cmd_TokenizeString(s, false);
	sv_player = sv_client->edict;

	/* SV_BeginRedirect (RD_CLIENT); */

	for (u = ucmds; u->name; u++)
		if (!strcmp(Cmd_Argv(0), u->name)) {
			u->func();
			break;
		}
	if (!u->name && sv.state == ss_game)
		ge->ClientCommand(sv_player);

	/* SV_EndRedirect (); */
}

/*
 *
 * ===========================================================================
 *
 * USER CMD EXECUTION
 *
 * ===========================================================================
 */



void
SV_ClientThink(client_t * cl, usercmd_t * cmd)
{
	cl->commandMsec -= cmd->msec;

	if (cl->commandMsec < 0 && sv_enforcetime->value) {
		Com_DPrintf("commandMsec underflow from %s\n", cl->name);
		return;
	}
	ge->ClientThink(cl->edict, cmd);
}



#define	MAX_STRINGCMDS	8

/*
 * =================== SV_ExecuteClientMessage
 *
 * The current net_message is parsed for the given client ===================
 */
void
SV_ExecuteClientMessage(client_t * cl)
{
	int		c;
	char           *s;

	usercmd_t	nullcmd;
	usercmd_t	oldest, oldcmd, newcmd;
	int		net_drop;
	int		stringCmdCount;
	int		checksum  , calculatedChecksum;
	int		checksumIndex;
	qboolean	move_issued;
	int		lastframe;

	sv_client = cl;
	sv_player = sv_client->edict;

	/* only allow one move command */
	move_issued = false;
	stringCmdCount = 0;

	while (1) {
		if (net_message.readcount > net_message.cursize) {
			Com_Printf("SV_ReadClientMessage: badread\n");
			SV_DropClient(cl);
			return;
		}
		c = MSG_ReadByte(&net_message);
		if (c == -1)
			break;

		switch (c) {
		default:
			Com_Printf("SV_ReadClientMessage: unknown command char\n");
			SV_DropClient(cl);
			return;

		case clc_nop:
			break;

		case clc_userinfo:
			Q_strncpyz(cl->userinfo, MSG_ReadString(&net_message), sizeof(cl->userinfo));
			SV_UserinfoChanged(cl);
			break;

		case clc_move:
			if (move_issued)
				return;	/* someone is trying to cheat... */

			move_issued = true;
			checksumIndex = net_message.readcount;
			checksum = MSG_ReadByte(&net_message);
			lastframe = MSG_ReadLong(&net_message);
			if (lastframe != cl->lastframe) {
				cl->lastframe = lastframe;
				if (cl->lastframe > 0) {
					cl->frame_latency[cl->lastframe & (LATENCY_COUNTS - 1)] =
					    svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
				}
			}
			memset(&nullcmd, 0, sizeof(nullcmd));
			MSG_ReadDeltaUsercmd(&net_message, &nullcmd, &oldest);
			MSG_ReadDeltaUsercmd(&net_message, &oldest, &oldcmd);
			MSG_ReadDeltaUsercmd(&net_message, &oldcmd, &newcmd);

			if (cl->state != cs_spawned) {
				cl->lastframe = -1;
				break;
			}

			/*
			 * if the checksum fails, ignore the rest of the
			 * packet
			 */
			calculatedChecksum = COM_BlockSequenceCRCByte(
			    net_message.data + checksumIndex + 1,
			    net_message.readcount - checksumIndex - 1,
			    cl->netchan.incoming_sequence);

			if (calculatedChecksum != checksum) {
				Com_DPrintf("Failed command checksum for %s (%d != %d)/%d\n",
				    cl->name, calculatedChecksum, checksum,
				    cl->netchan.incoming_sequence);
				return;
			}
			if (!sv_paused->value) {
				net_drop = cl->netchan.dropped;
				if (net_drop < 20) {

					/* if (net_drop > 2) */

					/*
					 * Com_Printf ("drop %i\n",
					 * net_drop);
					 */
					while (net_drop > 2) {
						SV_ClientThink(cl, &cl->lastcmd);

						net_drop--;
					}
					if (net_drop > 1)
						SV_ClientThink(cl, &oldest);

					if (net_drop > 0)
						SV_ClientThink(cl, &oldcmd);

				}
				SV_ClientThink(cl, &newcmd);
			}
			cl->lastcmd = newcmd;
			break;

		case clc_stringcmd:
			s = MSG_ReadString(&net_message);

			/*
			 * malicious users may try using too many string
			 * commands
			 */
			if (++stringCmdCount < MAX_STRINGCMDS)
				SV_ExecuteUserCommand(s);

			if (cl->state == cs_zombie)
				return;	/* disconnect command */
			break;
		}
	}
}
