/*
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.

*/
#include "qwsvdef.h"

#ifndef CLIENTONLY

#ifndef INVALID_SOCKET 
#define INVALID_SOCKET -1
#endif



qboolean	sv_allow_cheats;

extern cvar_t cl_warncmd;
cvar_t sv_cheats = SCVARF("sv_cheats", "0", CVAR_LATCH);
	extern		redirect_t	sv_redirected;

extern cvar_t sv_public;


//generic helper function for naming players.
client_t *SV_GetClientForString(char *name, int *id)
{
	int i;
	char *s, *s2;
	char nicename[80];
	client_t *cl;

	int first=0;
	if (id && *id != -1)
		first = *id;

	if (!strcmp(name, "*"))	//match with all
	{
		for (i = first, cl = svs.clients+first; i < sv.allocated_client_slots; i++, cl++)
		{
			if (cl->state<=cs_zombie)
				continue;

			*id=i+1;
			return cl;
		}
		*id=sv.allocated_client_slots;
		return NULL;
	}

	//check to make sure it's all an int

	for (s = name; *s; s++)
	{
		if (*s < '0' || *s > '9')
			break;
	}

	//we got to the end of the string and found only numbers. - it's a uid.
	if (!*s)
	{
		int uid = Q_atoi(name);
		for (i = first, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++)
		{
			if (cl->state<=cs_zombie)
				continue;
			if (cl->userid == uid)
			{
				if (id)
					*id=sv.allocated_client_slots;
				return cl;
			}
		}

		return NULL;
	}

	for (i = first, cl = svs.clients+first; i < sv.allocated_client_slots; i++, cl++)
	{
		if (cl->state<=cs_zombie)
			continue;


		s = nicename;
		s2 = cl->name;
		while(*s2)
		{
			*s = *s2 & ~128;
			s2++;
			if (*s == '3')
				*s = 'e';
			else if (*s == '4')
				*s = 'a';
			else if (*s == '1' || *s == '7')
				*s = 'l';
			else if (*s >= 18 && *s < 27)
				*s = *s - 18 + '0';
			else if (*s >= 'A' && *s <= 'Z')
				*s = *s - 'A' + 'a';
			else if (*s<' ' || *s == '~')
				continue;

			s++;
		}
		*s = '\0';

		if (strstr(nicename, name))
		{
			if (id)
				*id=i+1;
			return cl;
		}
	}

	return NULL;
}

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

OPERATOR CONSOLE ONLY COMMANDS

These commands can only be entered from stdin or by a remote operator datagram
===============================================================================
*/

/*
====================
SV_SetMaster_f

Make a master server current
====================
*/
void Master_ClearAll(void);
void Master_Add(char *stringadr);

void SV_SetMaster_f (void)
{
	int		i;

	Cvar_Set(&sv_public, "1");	//go public.

	Master_ClearAll();

	if (!strcmp(Cmd_Argv(1), "none"))
	{
		Con_Printf ("Entering no-master mode\n");
		return;
	}

	for (i=1 ; i<Cmd_Argc() ; i++)
	{
		Master_Add(Cmd_Argv(i));
	}

	svs.last_heartbeat = -99999;
}

/*
==================
SV_Quit_f
==================
*/
void SV_Quit_f (void)
{
	SV_FinalMessage ("server shutdown\n");
	Con_TPrintf (STL_SHUTTINGDOWN);
	SV_Shutdown ();
	Sys_Quit ();
}

/*
============
SV_Logfile_f
============
*/
void SV_Logfile_f (void)
{
	extern cvar_t log_enable, log_dir, log_name;
	extern char gamedirfile[];

	if (log_enable.value)
	{
		Cvar_SetValue(&log_enable, 0);
		Con_Printf("Logging disabled.\n");
	}
	else
	{
		char *d, *f;

		d = gamedirfile;
		if (log_dir.string[0])
			d = log_dir.string;

		f = "qconsole";
		if (log_name.string[0])
			f = log_name.string;

		Con_Printf(va("Logging to %s/%s.log.\n", d, f));
		Cvar_SetValue(&log_enable, 1);
	}

}

/*
============
SV_Fraglogfile_f
============
*/
void SV_Fraglogfile_f (void)
{
	char	name[MAX_OSPATH];
	int		i;

	if (sv_fraglogfile)
	{
		Con_TPrintf (STL_FLOGGINGOFF);
		VFS_CLOSE (sv_fraglogfile);
		sv_fraglogfile = NULL;
		return;
	}

	// find an unused name
	for (i=0 ; i<1000 ; i++)
	{
		sprintf (name, "frag_%i.log", i);
		sv_fraglogfile = FS_OpenVFS(name, "rb", FS_GAME);
		if (!sv_fraglogfile)
		{	// can't read it, so create this one
			sv_fraglogfile = FS_OpenVFS (name, "wb", FS_GAME);
			if (!sv_fraglogfile)
				i=1000;	// give error
			break;
		}
		VFS_CLOSE (sv_fraglogfile);
	}
	if (i==1000)
	{
		Con_TPrintf (STL_FLOGGINGFAILED);
		sv_fraglogfile = NULL;
		return;
	}

	Con_TPrintf (STL_FLOGGINGTO, name);
}


/*
==================
SV_SetPlayer

Sets host_client and sv_player to the player with idnum Cmd_Argv(1)
==================
*/
qboolean SV_SetPlayer (void)
{
	client_t	*cl;
	int			i;
	int			idnum;

	idnum = atoi(Cmd_Argv(1));

	for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
	{
		if (!cl->state)
			continue;
		if (cl->userid == idnum)
		{
			host_client = cl;
			sv_player = host_client->edict;
			return true;
		}
	}
	Con_TPrintf (STL_USERIDNOTONSERVER, idnum);
	return false;
}


/*
==================
SV_God_f

Sets client to godmode
==================
*/
void SV_God_f (void)
{
	if (!sv_allow_cheats)
	{
		Con_TPrintf (STL_NEEDCHEATPARM);
		return;
	}

	if (!SV_SetPlayer ())
		return;

	sv_player->v->flags = (int)sv_player->v->flags ^ FL_GODMODE;
	if ((int)sv_player->v->flags & FL_GODMODE)
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_GODON);
	else
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_GODOFF);
}


void SV_Noclip_f (void)
{
	if (!sv_allow_cheats)
	{
		Con_TPrintf (STL_NEEDCHEATPARM);
		return;
	}

	if (!SV_SetPlayer ())
		return;

	if (sv_player->v->movetype != MOVETYPE_NOCLIP)
	{
		sv_player->v->movetype = MOVETYPE_NOCLIP;
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_NOCLIPON);
	}
	else
	{
		sv_player->v->movetype = MOVETYPE_WALK;
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_NOCLIPOFF);
	}
}


/*
==================
SV_Give_f
==================
*/
void SV_Give_f (void)
{
	char	*t;
	int		v;

	if (!sv_allow_cheats)
	{
		Con_TPrintf (STL_NEEDCHEATPARM);
		return;
	}

	if (!SV_SetPlayer ())
	{
		int oldself;
		oldself = pr_global_struct->self;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
		Con_Printf("Result: %s\n", svprogfuncs->EvaluateDebugString(svprogfuncs, Cmd_Args()));
		pr_global_struct->self = oldself;
		return;
	}

	if (!svprogfuncs)
		return;

	t = Cmd_Argv(2);
	v = atoi (Cmd_Argv(3));

	switch (t[0])
	{
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
		sv_player->v->items = (int)sv_player->v->items | IT_SHOTGUN<< (t[0] - '2');
		break;

	case 's':
		sv_player->v->ammo_shells = v;
		break;
	case 'n':
		sv_player->v->ammo_nails = v;
		break;
	case 'r':
		sv_player->v->ammo_rockets = v;
		break;
	case 'h':
		sv_player->v->health = v;
		break;
	case 'c':
		sv_player->v->ammo_cells = v;
		break;
	default:
		{
			int oldself;
			oldself = pr_global_struct->self;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
			Con_Printf("Result: %s\n", svprogfuncs->EvaluateDebugString(svprogfuncs, Cmd_Args()));
			pr_global_struct->self = oldself;
		}
	}
}

int ShowMapList (char *name, int flags, void *parm)
{
	if (name[5] == 'b' && name[6] == '_')	//skip box models
		return true;
	Con_Printf("%s\n", name+5);
	return true;
}
void SV_MapList_f(void)
{
	COM_EnumerateFiles("maps/*.bsp", ShowMapList, NULL);
}

/*
======================
SV_Map_f

handle a
map <mapname>
command from the console or progs.
======================
*/
void SV_Map_f (void)
{
	char	level[MAX_QPATH];
	char	spot[MAX_QPATH];
	char	expanded[MAX_QPATH];
	char	*nextserver;
	qboolean issamelevel = false;
	qboolean newunit = false;
	qboolean cinematic = false;
	qboolean waschangelevel = false;
	qboolean wasspmap = false;
	int i;
	char *startspot;

	if (Cmd_Argc() != 2 && Cmd_Argc() != 3)
	{
		Con_TPrintf (STL_MAPCOMMANDUSAGE);
		return;
	}

	Q_strncpyz (level, Cmd_Argv(1), sizeof(level));
	startspot = ((Cmd_Argc() == 2)?NULL:Cmd_Argv(2));

	waschangelevel = !strcmp(Cmd_Argv(0), "changelevel");
	wasspmap = !strcmp(Cmd_Argv(0), "spmap");

	nextserver = strchr(level, '+');
	if (nextserver)
	{
		*nextserver = '\0';
		nextserver++;
	}

	if (startspot)
	{
		strcpy(spot, startspot);
		startspot = spot;
	}
	else if ((startspot = strchr(level, '$')))
	{
		strcpy(spot, startspot+1);
		*startspot = '\0';
		startspot = spot;
	}
	else
		startspot = NULL;

	if (!strcmp(level, "."))	//restart current
	{
		COM_StripExtension(COM_SkipPath(sv.modelname), level, sizeof(level));
		issamelevel = true;

		Q_strncpyz(spot, Info_ValueForKey(svs.info, "*startspot"), sizeof(spot));
		startspot = spot;
	}

	COM_FlushFSCache();

	// check to make sure the level exists
	if (*level == '*')
	{
		memmove(level, level+1, strlen(level));
		newunit=true;
	}

	if (strlen(level) > 4 && !strcmp(level + strlen(level)-4, ".cin"))
	{
		cinematic = true;
	}
	else
	{
		snprintf (expanded, sizeof(expanded), "maps/%s.bsp", level);
		if (!COM_FCheckExists (expanded))
		{
			//doesn't exist, so try lowercase. Q3 does this.
			for (i = 0; i < sizeof(level); i++)
			{
				if (level[i] >= 'A' && level[i] <= 'Z')
					level[i] = level[i] - 'A' + 'a';
			}
			snprintf (expanded, sizeof(expanded), "maps/%s.bsp", level);
			if (!COM_FCheckExists (expanded))
			{
				Con_TPrintf (STL_CANTFINDMAP, expanded);
				return;
			}
		}
	}

	if (sv.mvdrecording)
		SV_MVDStop_f();

#ifndef SERVERONLY
	if (!isDedicated)	//otherwise, info used on map loading isn't present
		Cmd_ExecuteString(va("fullserverinfo \"%s\"\n", svs.info), RESTRICT_SERVER);

	if (!sv.state && cls.state)
		CL_Disconnect();
#endif

	SV_SaveSpawnparms (issamelevel);

	if (startspot && !issamelevel && !newunit)
	{
#ifdef Q2SERVER
		if (ge)
		{
			qboolean savedinuse[MAX_CLIENTS];
			for (i=0 ; i<sv.allocated_client_slots; i++)
			{
				savedinuse[i] = svs.clients[i].q2edict->inuse;
				svs.clients[i].q2edict->inuse = false;
			}
			SV_SaveLevelCache(false);
			for (i=0 ; i<sv.allocated_client_slots; i++)
			{
				svs.clients[i].q2edict->inuse = savedinuse[i];
			}
		}
		else
#endif
			SV_SaveLevelCache(false);
	}

#ifdef Q3SERVER
	{
		cvar_t *gametype;
		gametype = Cvar_Get("g_gametype", "0", CVAR_LATCH, "Q3 compatability");
		if (wasspmap)
			Cvar_ForceSet(gametype, "2");
		else if (gametype->value == 2)	//singleplayer
			Cvar_ForceSet(gametype, "0");
	}
#endif

	for (i=0 ; i<MAX_CLIENTS ; i++)	//we need to drop all q2 clients. We don't mix q1w with q2.
	{
		if (svs.clients[i].state>cs_connected)	//so that we don't send a datagram
			svs.clients[i].state=cs_connected;
	}

	SV_BroadcastCommand ("changing\n");
	SV_SendMessagesToAll ();

#ifndef SERVERONLY
	S_StopAllSounds (true);
	SCR_BeginLoadingPlaque();
#endif

	if (newunit || !startspot || !SV_LoadLevelCache(level, startspot, false))
	{
		if (waschangelevel && !startspot)
			startspot = "";
		SV_SpawnServer (level, startspot, false, cinematic);
	}

	SV_BroadcastCommand ("cmd new\n");

	if (!issamelevel)
	{
		cvar_t *nsv;
		nsv = Cvar_Get("nextserver", "", 0, "");
		if (nextserver)
			Cvar_Set(nsv, va("gamemap \"%s\"", nextserver));
		else
			Cvar_Set(nsv, "");
	}
}

void SV_KillServer_f(void)
{
	SV_UnspawnServer();
}


/*
==================
SV_Kick_f

Kick a user off of the server
==================
*/
void SV_Kick_f (void)
{
	client_t	*cl;
	int clnum=-1;

	while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTWASKICKED, cl->name);
		// print directly, because the dropped client won't get the
		// SV_BroadcastPrintf message
		SV_ClientTPrintf (cl, PRINT_HIGH, STL_YOUWEREKICKED);
		SV_DropClient (cl);
	}

	if (clnum == -1)
		Con_TPrintf (STL_USERDOESNTEXIST, Cmd_Argv(1));
}

void SV_BanName_f (void)
{
	client_t	*cl;
	int clnum=-1;
	char *reason = NULL;
	int reasonsize = 0;

	if (Cmd_Argc() < 2)
	{
		Con_Printf("%s userid|nick [reason]\n", Cmd_Argv(0));
		return;
	}

	if (Cmd_Argc() > 2)
	{
		reason = Cmd_Argv(2);
		reasonsize = strlen(reason);
	}

	while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	if (cl)
	{
		bannedips_t *nb;

		if (NET_IsLoopBackAddress(cl->netchan.remote_address))
		{
			Con_Printf("You're not allowed to ban loopback!\n");
			continue;
		}

		nb = Z_Malloc(sizeof(bannedips_t)+reasonsize);
		nb->next = svs.bannedips;
		nb->adr = cl->netchan.remote_address;
		NET_IntegerToMask(&nb->adr, &nb->adrmask, -1); // fill mask
		if (*Cmd_Argv(2))	//explicit blocking of all ports of a client ip
			nb->adr.port = 0;
		svs.bannedips = nb;
		if (reasonsize)
			Q_strcpy(nb->reason, reason);

		SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTWASBANNED, cl->name);
		// print directly, because the dropped client won't get the
		// SV_BroadcastPrintf message
		SV_ClientTPrintf (cl, PRINT_HIGH, STL_YOUWEREBANNED);
		SV_DropClient (cl);
	}

	if (clnum == -1)
		Con_TPrintf (STL_USERDOESNTEXIST, Cmd_Argv(1));
}

void SV_BanIP_f (void)
{
	netadr_t banadr;
	netadr_t banmask;
	int i;
	client_t	*cl;
	bannedips_t *nb;
	char *reason = NULL;
	int reasonsize = 0;

	if (Cmd_Argc() < 2)
	{
		Con_Printf("%s address/mask|adress/maskbits [reason]\n", Cmd_Argv(0));
		return;
	}

	if (!NET_StringToAdrMasked(Cmd_Argv(1), &banadr, &banmask))
	{
		Con_Printf("invalid address or mask\n");
		return;
	}

	if (NET_IsLoopBackAddress(banadr))
	{
		Con_Printf("You're not allowed to ban loopback!\n");
		return;
	}

	if (Cmd_Argc() > 2)
	{
		reason = Cmd_Argv(2);
		reasonsize = strlen(reason);
	}

	// loop through clients and kick the ones that match
	for (i = 0, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++)
	{
		if (cl->state<=cs_zombie)
			continue;

		if (NET_CompareAdrMasked(cl->netchan.remote_address, banadr, banmask))
		{
			// match, so kick
			SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTWASBANNED, cl->name);
			// print directly, because the dropped client won't get the
			// SV_BroadcastPrintf message
			SV_ClientTPrintf (cl, PRINT_HIGH, STL_YOUWEREBANNED);
			SV_DropClient (cl);
		}
	}

	// add IP and mask to ban list
	nb = Z_Malloc(sizeof(bannedips_t)+reasonsize);
	nb->next = svs.bannedips;
	nb->adr = banadr;
	nb->adrmask = banmask;
	svs.bannedips = nb;
	if (reasonsize)
		Q_strcpy(nb->reason, reason);
}

void SV_FilterIP_f (void)
{
	netadr_t banadr;
	netadr_t banmask;
	int i;
	client_t	*cl;
	filteredips_t *nb;
	extern cvar_t filterban;

	if (Cmd_Argc() < 2)
	{
		Con_Printf("%s address/mask|adress/maskbits\n", Cmd_Argv(0));
		return;
	}

	if (!NET_StringToAdrMasked(Cmd_Argv(1), &banadr, &banmask))
	{
		Con_Printf("invalid address or mask\n");
		return;
	}

	if (NET_IsLoopBackAddress(banadr))
	{
		Con_Printf("You're not allowed to filter loopback!\n");
		return;
	}

	// loop through clients and kick the ones that match
	for (i = 0, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++)
	{
		if (cl->state<=cs_zombie)
			continue;

		if (filterban.value && NET_CompareAdrMasked(cl->netchan.remote_address, banadr, banmask))
			SV_DropClient (cl);
	}

	// add IP and mask to filter list
	nb = Z_Malloc(sizeof(filteredips_t));
	nb->next = svs.filteredips;
	nb->adr = banadr;
	nb->adrmask = banmask;
	svs.filteredips = nb;
}

void SV_BanList_f (void)
{
	int bancount = 0;
	bannedips_t *nb = svs.bannedips;

	while (nb)
	{
		if (nb->reason[0])
			Con_Printf("%s, %s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask), nb->reason);
		else
			Con_Printf("%s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask));
		bancount++;
		nb = nb->next;
	}

	Con_Printf("%i total entries in ban list\n", bancount);
}

void SV_FilterList_f (void)
{
	int filtercount = 0;
	filteredips_t *nb = svs.filteredips;

	while (nb)
	{
		Con_Printf("%s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask));
		filtercount++;
		nb = nb->next;
	}

	Con_Printf("%i total entries in filter list\n", filtercount);
}

void SV_Unban_f (void)
{
	qboolean all = false;
	bannedips_t *nb = svs.bannedips;
	bannedips_t *nbnext;
	netadr_t unbanadr = {0};
	netadr_t unbanmask = {0};

	if (Cmd_Argc() < 2)
	{
		Con_Printf("%s address/mask|address/maskbits|all\n", Cmd_Argv(0));
		return;
	}

	if (!Q_strcasecmp(Cmd_Argv(1), "all"))
		all = true;
	else if (!NET_StringToAdrMasked(Cmd_Argv(1), &unbanadr, &unbanmask))
	{
		Con_Printf("invalid address or mask\n");
		return;
	}

	while (nb)
	{
		nbnext = nb->next;
		if (all || (NET_CompareAdr(nb->adr, unbanadr) && NET_CompareAdr(nb->adrmask, unbanmask)))
		{
			if (!all)
				Con_Printf("unbanned %s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask));
			if (svs.bannedips == nb)
				svs.bannedips = nbnext;
			Z_Free(nb);
			break;
		}

		nb = nbnext;
	}
}

void SV_Unfilter_f (void)
{
	qboolean all = false;
	filteredips_t *nb = svs.filteredips;
	filteredips_t *nbnext;
	netadr_t unbanadr = {0};
	netadr_t unbanmask = {0};

	if (Cmd_Argc() < 2)
	{
		Con_Printf("%s address/mask|address/maskbits|all\n", Cmd_Argv(0));
		return;
	}

	if (!Q_strcasecmp(Cmd_Argv(1), "all"))
		all = true;
	else if (!NET_StringToAdrMasked(Cmd_Argv(1), &unbanadr, &unbanmask))
	{
		Con_Printf("invalid address or mask\n");
		return;
	}

	while (nb)
	{
		nbnext = nb->next;
		if (all || (NET_CompareAdr(nb->adr, unbanadr) && NET_CompareAdr(nb->adrmask, unbanmask)))
		{
			if (!all)
				Con_Printf("unfiltered %s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask));
			if (svs.filteredips == nb)
				svs.filteredips = nbnext;
			Z_Free(nb);
			break;
		}

		nb = nbnext;
	}
}

void SV_WriteIP_f (void)
{
	vfsfile_t	*f;
	char	name[MAX_OSPATH];
	bannedips_t *bi;
	filteredips_t *fi;
	char *s;

	strcpy (name, "listip.cfg");

	Con_Printf ("Writing %s.\n", name);

	f = FS_OpenVFS(name, "wb", FS_GAME);
	if (!f)
	{
		Con_Printf ("Couldn't open %s\n", name);
		return;
	}

	s = "// banned ip addresses\n";
	VFS_WRITE(f, s, strlen(s));

	bi = svs.bannedips;
	while (bi)
	{
		if (bi->reason[0])
			s = va("banip %s \"%s\"\n", NET_AdrToStringMasked(bi->adr, bi->adrmask), bi->reason);
		else
			s = va("banip %s\n", NET_AdrToStringMasked(bi->adr, bi->adrmask));
		VFS_WRITE(f, s, strlen(s));
		bi = bi->next;
	}

	s = "\n// filtered ip addresses\n";
	VFS_WRITE(f, s, strlen(s));

	fi = svs.filteredips;
	while (fi)
	{
		s = va("addip %s\n", NET_AdrToStringMasked(fi->adr, fi->adrmask));
		VFS_WRITE(f, s, strlen(s));
		fi = fi->next;
	}

	VFS_CLOSE (f);
}


void SV_ForceName_f (void)
{
	client_t	*cl;
	int clnum=-1;
	int i;

	while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		Info_SetValueForKey(cl->userinfo, "name", Cmd_Argv(2), MAX_INFO_STRING);
		SV_ExtractFromUserinfo(cl);
		Q_strncpyz(cl->name, Cmd_Argv(2), sizeof(cl->namebuf));
		i = cl - svs.clients;
		MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
		MSG_WriteByte (&sv.reliable_datagram, i);
		MSG_WriteString (&sv.reliable_datagram, "name");
		MSG_WriteString (&sv.reliable_datagram, cl->name);

		return;
	}

	if (clnum == -1)
		Con_TPrintf (STL_USERDOESNTEXIST, Cmd_Argv(1));
}

void SV_CripplePlayer_f (void)
{
	client_t	*cl;
	int clnum=-1;

	int persist = *Cmd_Argv(2);

	while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		if (!cl->iscrippled)
		{
			if (persist)
			{
				cl->iscrippled = 2;
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTISCRIPPLEDPERMANENTLY, cl->name);
			}
			else
			{
				cl->iscrippled = true;
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTISCRIPPLED, cl->name);
			}
		}
		else
		{
			cl->iscrippled = false;
			SV_ClientTPrintf (cl, PRINT_HIGH, STL_YOUARNTCRIPPLED);
		}
	}

	if (clnum == -1)
		Con_TPrintf (STL_USERDOESNTEXIST, Cmd_Argv(1));
}

void SV_Mute_f (void)
{
	client_t	*cl;
	int clnum=-1;

	int persist = *Cmd_Argv(2);

	while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		if (!cl->ismuted)
		{
			if (persist)
			{
				cl->ismuted = 2;
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTISMUTEDPERMANENTLY, cl->name);
			}
			else
			{
				cl->ismuted = true;
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTISMUTED, cl->name);
			}
		}
		else
		{
			cl->ismuted = false;
			SV_ClientTPrintf (cl, PRINT_HIGH, STL_YOUARNTMUTED);
		}
	}

	if (clnum == -1)
		Con_TPrintf (STL_USERDOESNTEXIST, Cmd_Argv(1));
}

void SV_Cuff_f (void)
{
	client_t	*cl;
	int clnum=-1;

	int persist = *Cmd_Argv(2);

	while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		if (!cl->iscuffed)
		{
			if (persist)
			{
				cl->iscuffed = 2;
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTISCUFFEDPERMANENTLY, cl->name);
			}
			else
			{
				cl->iscuffed = true;
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTISCUFFED, cl->name);
			}
		}
		else
		{
			cl->iscuffed = false;
			SV_ClientTPrintf (cl, PRINT_HIGH, STL_YOUARNTCUFFED);
		}
		return;
	}

	if (clnum == -1)
		Con_TPrintf (STL_USERDOESNTEXIST, Cmd_Argv(1));
}

void SV_Floodprot_f(void)
{
	extern cvar_t sv_floodprotect;
	extern cvar_t sv_floodprotect_messages;
	extern cvar_t sv_floodprotect_interval;
	extern cvar_t sv_floodprotect_silencetime;

	if (Cmd_Argc() == 1)
	{
		if (sv_floodprotect_messages.value <= 0 || !sv_floodprotect.value)
			Con_Printf("Flood protection is off.\n");
		else
			Con_Printf("Current flood protection settings: \nAfter %g msgs for %g seconds, silence for %g seconds\n",
				sv_floodprotect_messages.value,
				sv_floodprotect_interval.value,
				sv_floodprotect_silencetime.value);
		return;
	}

	if (Cmd_Argc() != 4)
	{
		Con_Printf("Usage: %s <messagerate> <ratepersecond> <silencetime>\n", Cmd_Argv(0));
		return;
	}

	Cvar_SetValue(&sv_floodprotect_messages, atof(Cmd_Argv(1)));
	Cvar_SetValue(&sv_floodprotect_interval, atof(Cmd_Argv(2)));
	Cvar_SetValue(&sv_floodprotect_silencetime, atof(Cmd_Argv(3)));
}

void SV_StuffToClient_f(void)
{	//with this we emulate the progs 'stuffcmds' builtin

	client_t	*cl;

	int clnum=-1;
	char *clientname = Cmd_Argv(1);
	char *str;
	char *c;
	char *key;

	Cmd_ShiftArgs(1, Cmd_ExecLevel==RESTRICT_LOCAL);
	if (!strcmp(Cmd_Argv(1), "bind"))
	{
		key = Z_Malloc(strlen(Cmd_Argv(2))+1);
		strcpy(key, Cmd_Argv(2));
		Cmd_ShiftArgs(2, Cmd_ExecLevel==RESTRICT_LOCAL);
	}
	else
		key = NULL;
	str = Cmd_Args();

	while(*str <= ' ')	//strim leading spaces
	{
		if (!*str)
			break;
		str++;
	}

	//a list of safe, allowed commands. Allows any extention of this.
	if (strchr(str, '\n') || strchr(str, ';') || (
		!strncmp(str, "setinfo", 7) &&
		!strncmp(str, "quit", 4) &&
		!strncmp(str, "gl_fb", 5) &&
		!strncmp(str, "r_fb", 4) &&
		!strncmp(str, "say", 3) &&	//note that the say parsing could be useful here.
		!strncmp(str, "echo", 4) &&
		!strncmp(str, "name", 4) &&
		!strncmp(str, "skin", 4) &&
		!strncmp(str, "color", 5) &&
		!strncmp(str, "cmd", 3) &&
		!strncmp(str, "fov", 3) &&
		!strncmp(str, "connect", 7) &&
		!strncmp(str, "rate", 4) &&
		!strncmp(str, "cd", 2) &&
		!strncmp(str, "easyrecord", 10) &&
		!strncmp(str, "leftisright", 11) &&
		!strncmp(str, "menu_", 5) &&
		!strncmp(str, "r_fullbright", 12) &&
		!strncmp(str, "toggleconsole", 13) &&
		!strncmp(str, "v_i", 3) &&	//idlescale vars
		!strncmp(str, "bf", 2) &&
		!strncmp(str, "+", 1) &&
		!strncmp(str, "-", 1) &&
		!strncmp(str, "impulse", 7) &&
		1))
	{
		Con_Printf("You're not allowed to stuffcmd that\n");

		if (key)
			Z_Free(key);
		return;
	}

	while((cl = SV_GetClientForString(clientname, &clnum)))
	{
		if (cl->protocol == SCP_QUAKE2)
			ClientReliableWrite_Begin (cl, svcq2_stufftext, 3+strlen(str) + (key?strlen(key)+6:0));
		else
			ClientReliableWrite_Begin (cl, svc_stufftext, 3+strlen(str) + (key?strlen(key)+6:0));

		if (key)
		{
			for (c = "bind "; *c; c++)
				ClientReliableWrite_Byte (cl, *c);

			for (c = key; *c; c++)
				ClientReliableWrite_Byte (cl, *c);

			ClientReliableWrite_Byte (cl, ' ');
		}

		for (c = str; *c; c++)
			ClientReliableWrite_Byte (cl, *c);
		ClientReliableWrite_Byte (cl, '\n');
		ClientReliableWrite_Byte (cl, '\0');
	}

	if (key)
		Z_Free(key);
}

/*
================
SV_Status_f
================
*/
void SV_Status_f (void)
{
	int			i, j, l;
	client_t	*cl;
	float		cpu, avg, pak;
	char		*s;

	int columns = 80;

	if (sv_redirected != RD_OBLIVION && (sv_redirected != RD_NONE
#ifndef SERVERONLY
		|| (vid.width < 68*8 && qrenderer != QR_NONE)
#endif
		))
		columns = 40;

	if (!sv.state)
	{
		if (net_local_sv_ipadr.type != NA_LOOPBACK)
			Con_Printf ("ip address       : %s\n",NET_AdrToString (net_local_sv_ipadr));

		Con_Printf("Server is not running\n");
		return;
	}

	if (Cmd_Argc()>1)
		columns = atoi(Cmd_Argv(1));

	cpu = (svs.stats.latched_active+svs.stats.latched_idle);
	if (cpu)
		cpu = 100*svs.stats.latched_active/cpu;
	avg = 1000*svs.stats.latched_active / STATFRAMES;
	pak = (float)svs.stats.latched_packets/ STATFRAMES;

	if (svs.socketip != INVALID_SOCKET && net_local_sv_ipadr.type != NA_LOOPBACK)
	{
		extern cvar_t pr_imitatemvdsv;
		if (pr_imitatemvdsv.value)
			Con_Printf ("net address      : %s\n",NET_AdrToString (net_local_sv_ipadr));
		else
			Con_Printf ("ip address       : %s\n",NET_AdrToString (net_local_sv_ipadr));
	}
	if (svs.socketip6 != INVALID_SOCKET && net_local_sv_ip6adr.type != NA_LOOPBACK)
		Con_Printf ("ipv6 address     : %s\n",NET_AdrToString (net_local_sv_ip6adr));
	if (svs.socketipx != INVALID_SOCKET && net_local_sv_ipxadr.type != NA_LOOPBACK)
		Con_Printf ("ipx address      : %s\n",NET_AdrToString (net_local_sv_ipxadr));
#ifdef TCPCONNECT
	if (svs.sockettcp != INVALID_SOCKET && net_local_sv_tcpipadr.type != NA_LOOPBACK)
		Con_Printf ("tcp address      : %s\n",NET_AdrToString (net_local_sv_tcpipadr));
#endif

	Con_Printf ("cpu utilization  : %3i%%\n",(int)cpu);
	Con_Printf ("avg response time: %i ms\n",(int)avg);
	Con_Printf ("packets/frame    : %5.2f\n", pak);	//not relevent as a limit.

// min fps lat drp
	if (columns < 80)
	{
		// most remote clients are 40 columns
		//           0123456789012345678901234567890123456789
		Con_Printf ("name               userid frags\n");
        Con_Printf ("  address          rate ping drop\n");
		Con_Printf ("  ---------------- ---- ---- -----\n");
		for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
		{
			if (!cl->state)
				continue;

			Con_Printf ("%-16.16s  ", cl->name);

			Con_Printf ("%6i %5i", cl->userid, (int)cl->old_frags);
			if (cl->spectator)
				Con_Printf(" (s)\n");
			else
				Con_Printf("\n");

			if (cl->istobeloaded && cl->state == cs_zombie)
				s = "LoadZombie";
			else if (cl->protocol == SCP_BAD)
				s = "bot";
			else
				s = NET_BaseAdrToString ( cl->netchan.remote_address);
			Con_Printf ("  %-16.16s", s);
			if (cl->state == cs_connected)
			{
				Con_Printf ("CONNECTING\n");
				continue;
			}
			if (cl->state == cs_zombie)
			{
				Con_Printf ("ZOMBIE\n");
				continue;
			}
			Con_Printf ("%4i %4i %5.2f\n"
				, (int)(1000*cl->netchan.frame_rate)
				, (int)SV_CalcPing (cl)
				, 100.0*cl->netchan.drop_count / cl->netchan.incoming_sequence);
		}
	}
	else
	{
		Con_Printf ("frags userid address         name            rate ping drop  qport dl%% dls\n");
		Con_Printf ("----- ------ --------------- --------------- ---- ---- ----- ----- --- ----\n");
		for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
		{
			if (!cl->state)
				continue;
			Con_Printf ("%5i %6i ", (int)cl->old_frags,  cl->userid);

			if (cl->istobeloaded && cl->state == cs_zombie)
				s = "LoadZombie";
			else if (cl->protocol == SCP_BAD)
				s = "bot";
			else
				s = NET_BaseAdrToString ( cl->netchan.remote_address);
			Con_Printf ("%s", s);
			l = 16 - strlen(s);
			for (j=0 ; j<l ; j++)
				Con_Printf (" ");

			Con_Printf ("%s", cl->name);
			l = 16 - strlen(cl->name);
			for (j=0 ; j<l ; j++)
				Con_Printf (" ");
			if (cl->state == cs_connected)
			{
				Con_Printf ("CONNECTING           ");
			}
			else if (cl->state == cs_zombie)
			{
				Con_Printf ("ZOMBIE               ");
			}
			else
				Con_Printf ("%4i %4i %5.1f %4i"
				, (int)(1000*cl->netchan.frame_rate)
				, (int)SV_CalcPing (cl)
				, 100.0*cl->netchan.drop_count / cl->netchan.incoming_sequence
				, cl->netchan.qport);
			if (cl->download)
			{
				Con_Printf (" %3i %4i", (cl->downloadcount*100)/cl->downloadsize, cl->downloadsize/1024);
			}
			if (cl->spectator)
				Con_Printf(" (s)\n");
			else
				Con_Printf("\n");


		}
	}
	Con_Printf ("\n");
}

/*
==================
SV_ConSay_f
==================
*/
void SV_ConSay_f(void)
{
	client_t *client;
	int		j;
	char	*p;
	char	text[1024];

	if (Cmd_Argc () < 2)
		return;

	Q_strcpy (text, "console: ");
	p = Cmd_Args();

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

	Q_strcat(text, p);

	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
	{
		if (client->state == cs_free)
			continue;
		SV_ClientPrintf(client, PRINT_CHAT, "%s\n", text);
	}

	if (sv.mvdrecording)
	{
		MVDWrite_Begin (dem_all, 0, strlen(text)+3);
		MSG_WriteByte ((sizebuf_t*)demo.dbuf, svc_print);
		MSG_WriteByte ((sizebuf_t*)demo.dbuf, PRINT_CHAT);
		MSG_WriteString ((sizebuf_t*)demo.dbuf, text);
	}
}

void SV_ConSayOne_f (void)
{
	char	text[2048];
	client_t	*to;
	int i;
	char *s;
	int clnum=-1;

	if (Cmd_Argc () < 3)
		return;

	while((to = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		Q_strcpy (text, "{console}: ");

		for (i = 2; ; i++)
		{
			s = Cmd_Argv(i);
			if (!*s)
				break;

			if (strlen(text) + strlen(s) + 2 >= sizeof(text)-1)
				break;
			strcat(text, " ");
			strcat(text, s);
		}
		strcat(text, "\n");
		SV_ClientPrintf(to, PRINT_CHAT, "%s", text);
	}
	if (!clnum)
		Con_TPrintf(STL_USERDOESNTEXIST, Cmd_Argv(1));
}

/*
==================
SV_Heartbeat_f
==================
*/
void SV_Heartbeat_f (void)
{
	svs.last_heartbeat = -9999;
}

void SV_SendServerInfoChange(char *key, const char *value)
{
	if (!sv.state)
		return;
#ifdef Q2SERVER
	if (ge)
		return;	//FIXME!!!
#endif

	MSG_WriteByte (&sv.reliable_datagram, svc_serverinfo);
	MSG_WriteString (&sv.reliable_datagram, key);
	MSG_WriteString (&sv.reliable_datagram, value);
}

/*
===========
SV_Serverinfo_f

  Examine or change the serverinfo string
===========
*/
char *CopyString(char *s);
extern char *Info_KeyForNumber(char *s, int num);
void SV_Serverinfo_f (void)
{
	cvar_t	*var;
	char value[512];
	int i;

	if (Cmd_Argc() == 1)
	{
		Con_TPrintf (STL_SERVERINFOSETTINGS);
		Info_Print (svs.info);
		return;
	}

	if (Cmd_Argc() < 3)
	{
		Con_TPrintf (STL_SERVERINFOSYNTAX);
		return;
	}

	if (Cmd_Argv(1)[0] == '*')
	{
		if (!strcmp(Cmd_Argv(1), "*"))
			if (!strcmp(Cmd_Argv(2), ""))
			{	//clear it out
				char *k;
				for(i=0;;)
				{
					k = Info_KeyForNumber(svs.info, i);
					if (!*k)
						break;	//no more.
					else if (*k == '*')
						i++;	//can't remove * keys
					else if ((var = Cvar_FindVar(k)) && var->flags&CVAR_SERVERINFO)
						i++;	//this one is a cvar.
					else
						Info_RemoveKey(svs.info, k);	//we can remove this one though, so yay.
				}

				return;
			}
		Con_TPrintf (TL_STARKEYPROTECTED);
		return;
	}
	Q_strncpyz(value, Cmd_Argv(2), sizeof(value));
	value[sizeof(value)-1] = '\0';
	for (i = 3; i < Cmd_Argc(); i++)
	{
		strncat(value, " ", sizeof(value)-1);
		strncat(value, Cmd_Argv(i), sizeof(value)-1);
	}

	Info_SetValueForKey (svs.info, Cmd_Argv(1), value, MAX_SERVERINFO_STRING);

	// if this is a cvar, change it too
	var = Cvar_FindVar (Cmd_Argv(1));
	if (var)
	{
		Cvar_Set(var, value);
/*		Z_Free (var->string);	// free the old value string
		var->string = CopyString (value);
		var->value = Q_atof (var->string);
*/	}

	SV_SendServerInfoChange(Cmd_Argv(1), value);
}


/*
===========
SV_Serverinfo_f

  Examine or change the serverinfo string
===========
*/
char *CopyString(char *s);
void SV_Localinfo_f (void)
{
	char *old;

	if (Cmd_Argc() == 1)
	{
		Con_TPrintf (STL_LOCALINFOSETTINGS);
		Info_Print (localinfo);
		return;
	}

	if (Cmd_Argc() != 3)
	{
		Con_TPrintf (STL_LOCALINFOSYNTAX);
		return;
	}

	if (Cmd_Argv(1)[0] == '*')
	{
		if (!strcmp(Cmd_Argv(1), "*"))
			if (!strcmp(Cmd_Argv(2), ""))
			{	//clear it out
				Info_RemoveNonStarKeys(localinfo);
				return;
			}
		Con_TPrintf (TL_STARKEYPROTECTED);
		return;
	}
	old = Info_ValueForKey(localinfo, Cmd_Argv(1));
	Info_SetValueForKey (localinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_LOCALINFO_STRING);

	PR_LocalInfoChanged(Cmd_Argv(1), old, Cmd_Argv(2));

	Con_DPrintf("Localinfo %s changed (%s -> %s)\n", Cmd_Argv(1), old, Cmd_Argv(2));
}

void SV_SaveInfos(vfsfile_t *f)
{
	VFS_WRITE(f, "\n", 1);
	VFS_WRITE(f, "serverinfo * \"\"\n", 16);
	Info_WriteToFile(f, svs.info, "serverinfo", CVAR_SERVERINFO);
	VFS_WRITE(f, "\n", 1);
	VFS_WRITE(f, "localinfo * \"\"\n", 15);
	Info_WriteToFile(f, localinfo, "localinfo", 0);
}

/*
void SV_ResetInfos(void)
{
	// TODO: add me
}
*/

/*
===========
SV_User_f

Examine a users info strings
===========
*/
void SV_User_f (void)
{
	if (Cmd_Argc() != 2)
	{
		Con_TPrintf (STL_USERINFOSYNTAX);
		return;
	}

	if (!SV_SetPlayer ())
		return;

	Info_Print (host_client->userinfo);
}

/*
================
SV_Floodport_f

Sets the gamedir and path to a different directory.
================
*/

/*
================
SV_Gamedir

Sets the fake *gamedir to a different directory.
================
*/
void SV_Gamedir (void)
{
	char			*dir;

	if (Cmd_Argc() == 1)
	{
		Con_TPrintf (STL_CURRENTGAMEDIR, Info_ValueForKey (svs.info, "*gamedir"));
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Con_TPrintf (STL_SVGAMEDIRUSAGE);
		return;
	}

	dir = Cmd_Argv(1);

	if (strstr(dir, "..") || strstr(dir, "/")
		|| strstr(dir, "\\") || strstr(dir, ":") )
	{
		Con_TPrintf (STL_GAMEDIRCANTBEPATH);
		return;
	}

	Info_SetValueForStarKey (svs.info, "*gamedir", dir, MAX_SERVERINFO_STRING);
}

/*
================
SV_Gamedir_f

Sets the gamedir and path to a different directory.
================
*/
void SV_Gamedir_f (void)
{
	char			*dir;

	if (Cmd_Argc() == 1)
	{
		Con_TPrintf (STL_CURRENTGAMEDIR, com_gamedir);
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Con_TPrintf (STL_GAMEDIRUSAGE);
		return;
	}

	dir = Cmd_Argv(1);

	if (strstr(dir, "..") || strstr(dir, "/")
		|| strstr(dir, "\\") || strstr(dir, ":") )
	{
		Con_TPrintf (STL_GAMEDIRCANTBEPATH);
		return;
	}

	COM_Gamedir (dir);
	Info_SetValueForStarKey (svs.info, "*gamedir", dir, MAX_SERVERINFO_STRING);
}


extern char	gamedirfile[MAX_OSPATH];
/*
================
SV_Snap
================
*/
void SV_Snap (int uid)
{
	client_t *cl;
	char		pcxname[80];
	char		checkname[MAX_OSPATH];
	int			i;

	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)
	{
		if (!cl->state)
			continue;
		if (cl->userid == uid)
			break;
	}
	if (i >= MAX_CLIENTS) {
		Con_TPrintf (STL_USERDOESNTEXIST);
		return;
	}
	if (!ISQWCLIENT(cl))
	{
		Con_Printf("Can only snap QW clients\n");
		return;
	}

	sprintf(pcxname, "%d-00.pcx", uid);

	sprintf(checkname, "%s/snap", gamedirfile);
	Sys_mkdir(gamedirfile);
	Sys_mkdir(checkname);

	for (i=0 ; i<=99 ; i++)
	{
		pcxname[strlen(pcxname) - 6] = i/10 + '0';
		pcxname[strlen(pcxname) - 5] = i%10 + '0';
		sprintf (checkname, "%s/snap/%s", gamedirfile, pcxname);
		if (Sys_FileTime(checkname) == -1)
			break;	// file doesn't exist
	}
	if (i==100)
	{
		Con_TPrintf (STL_SNAPTOOMANYFILES);
		return;
	}
	strcpy(cl->uploadfn, checkname);

	memcpy(&cl->snap_from, &net_from, sizeof(net_from));
	if (sv_redirected != RD_NONE)
		cl->remote_snap = true;
	else
		cl->remote_snap = false;

	ClientReliableWrite_Begin (cl, svc_stufftext, 24);
	ClientReliableWrite_String (cl, "cmd snap\n");
	Con_TPrintf (STL_SNAPREQUEST, uid);
}

/*
================
SV_Snap_f
================
*/
void SV_Snap_f (void)
{
	int			uid;

	if (Cmd_Argc() != 2)
	{
		Con_TPrintf (STL_SNAPUSAGE);
		return;
	}

	uid = atoi(Cmd_Argv(1));

	SV_Snap(uid);
}

/*
================
SV_Snap
================
*/
void SV_SnapAll_f (void)
{
	client_t *cl;
	int			i;

	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)
	{
		if (cl->state < cs_connected || cl->spectator)
			continue;
		SV_Snap(cl->userid);
	}
}

float mytimer;
float lasttimer;
int ticsleft;
float timerinterval;
int timerlevel;
cvar_t *timercommand;
void SV_CheckTimer(void)
{
	float ctime = Sys_DoubleTime();
//	if (ctime < lasttimer) //new map? (shouldn't happen)
//		mytimer = ctime+5;	//trigger in a few secs
	lasttimer = ctime;

	if (ticsleft)
	{
		if (mytimer < ctime)
		{
			mytimer += timerinterval;
			if (ticsleft > 0)
				ticsleft--;

			if (timercommand)
			{
				Cbuf_AddText(timercommand->string, timerlevel);
				Cbuf_AddText("\n", timerlevel);
			}
		}
	}
}

void SV_SetTimer_f(void)
{
	int count;
	float interval;
	char *command;

	if (Cmd_Argc() < 2)
	{
		Con_Printf("%s <count> <interval> <command>\n", Cmd_Argv(0));
		return;
	}

	count = atoi(Cmd_Argv(1));
	interval = atof(Cmd_Argv(2));

	if (!count && Cmd_Argc() == 2)
	{
		ticsleft = 0;
		return;
	}

	if (interval <= 0 || (count <= 0 && count != -1))	//makes sure the args are right. :)
	{
		Con_Printf("%s count interval command\n", Cmd_Argv(0));
		return;
	}

	Cmd_ShiftArgs(2, Cmd_ExecLevel==RESTRICT_LOCAL);	//strip the two vars
	command = Cmd_Args();

	timercommand = Cvar_Get("sv_timer", "", CVAR_NOSET, NULL);
	Cvar_ForceSet(timercommand, command);

	mytimer = Sys_DoubleTime() + interval;
	ticsleft = count;
	timerinterval = interval;

	timerlevel = Cmd_ExecLevel;
}

void SV_SendGameCommand_f(void)
{
#ifdef Q3SERVER
	if (SVQ3_ConsoleCommand())
		return;
#endif

#ifdef Q2SERVER
	if (ge)
	{
		ge->ServerCommand();
	}
	else
#endif
		Con_Printf("This command requires a Q2 sever\n");
}




void PIN_LoadMessages(void);
void PIN_SaveMessages(void);
void PIN_DeleteOldestMessage(void);
void PIN_MakeMessage(char *from, char *msg);

void SV_Pin_Save_f(void)
{
	PIN_SaveMessages();
}
void SV_Pin_Reload_f(void)
{
	PIN_LoadMessages();
}
void SV_Pin_Delete_f(void)
{
	PIN_DeleteOldestMessage();
}
void SV_Pin_Add_f(void)
{
	PIN_MakeMessage(Cmd_Argv(1), Cmd_Argv(2));
}


/*
==================
SV_InitOperatorCommands
==================
*/
void SV_InitOperatorCommands (void)
{
#ifndef SERVERONLY
	if (isDedicated)
#endif
	{
		Cmd_AddCommand ("quit", SV_Quit_f);
		Cmd_AddCommand ("say", SV_ConSay_f);
		Cmd_AddCommand ("sayone", SV_ConSayOne_f);
		Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);	//commands that conflict with client commands.
		Cmd_AddCommand ("user", SV_User_f);

		Cmd_AddCommand ("god", SV_God_f);
		Cmd_AddCommand ("give", SV_Give_f);
		Cmd_AddCommand ("noclip", SV_Noclip_f);
	}

	Cvar_Register(&sv_cheats, "Server Permissions");
	if (COM_CheckParm ("-cheats"))
	{
		Cvar_Set(&sv_cheats, "1");
	}

	Cmd_AddCommand ("logfile", SV_Logfile_f);
	Cmd_AddCommand ("fraglogfile", SV_Fraglogfile_f);

	Cmd_AddCommand ("snap", SV_Snap_f);
	Cmd_AddCommand ("snapall", SV_SnapAll_f);
	Cmd_AddCommand ("kick", SV_Kick_f);
	Cmd_AddCommand ("mute", SV_Mute_f);
	Cmd_AddCommand ("cuff", SV_Cuff_f);
	Cmd_AddCommand ("renameclient", SV_ForceName_f);
	Cmd_AddCommand ("cripple", SV_CripplePlayer_f);
	Cmd_AddCommand ("banname", SV_BanName_f);
	Cmd_AddCommand ("banlist", SV_BanList_f);
	Cmd_AddCommand ("banip", SV_BanIP_f);
	Cmd_AddCommand ("unban", SV_Unban_f);
//	Cmd_AddCommand ("ban", SV_BanName_f);
	Cmd_AddCommand ("status", SV_Status_f);

	Cmd_AddCommand ("addip", SV_FilterIP_f);
	Cmd_AddCommand ("removeip", SV_Unfilter_f);
	Cmd_AddCommand ("listip", SV_FilterList_f);
	Cmd_AddCommand ("writeip", SV_WriteIP_f);

	Cmd_AddCommand ("floodprot", SV_Floodprot_f);

//	Cmd_AddCommand ("filterip", SV_FilterIP_f);
//	Cmd_AddCommand ("unfilter", SV_Unfilter_f);
//	Cmd_AddCommand ("filterlist", SV_FilterList_f);

//	Cmd_AddCommand ("writeip", SV_WriteIP_f);

	Cmd_AddCommand ("sv", SV_SendGameCommand_f);

	Cmd_AddCommand ("killserver", SV_KillServer_f);
	Cmd_AddCommand ("map", SV_Map_f);
#ifdef Q3SERVER
	Cmd_AddCommand ("spmap", SV_Map_f);
#endif
	Cmd_AddCommand ("gamemap", SV_Map_f);
	Cmd_AddCommand ("changelevel", SV_Map_f);
	Cmd_AddCommand ("listmaps", SV_MapList_f);
	Cmd_AddCommand ("setmaster", SV_SetMaster_f);

	Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);

	Cmd_AddCommand ("localinfo", SV_Localinfo_f);
	Cmd_AddCommand ("gamedir", SV_Gamedir_f);
	Cmd_AddCommand ("sv_gamedir", SV_Gamedir);
	Cmd_AddCommand ("sv_settimer", SV_SetTimer_f);
	Cmd_AddCommand ("stuffcmd", SV_StuffToClient_f);

	Cmd_AddCommand ("pin_save", SV_Pin_Save_f);
	Cmd_AddCommand ("pin_reload", SV_Pin_Reload_f);
	Cmd_AddCommand ("pin_delete", SV_Pin_Delete_f);
	Cmd_AddCommand ("pin_add", SV_Pin_Add_f);

	cl_warncmd.value = 1;
}

#endif
