//included directly from plugin.c
//this is the client-only things.






plugin_t *menuplug;	//plugin that has the current menu
static plugin_t *protocolclientplugin;



static qintptr_t VARGS Plug_Menu_Control(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	if (qrenderer == QR_NONE)
		return 0;

	switch(VM_LONG(arg[0]))
	{
	case 0:	//take away all menus
	case 1:
		if (menuplug)
		{
			plugin_t *oldplug = currentplug;
			currentplug = menuplug;
			Plug_Menu_Event(3, 0);
			menuplug = NULL;
			currentplug = oldplug;
		}
		if (VM_LONG(arg[0]) != 1)
			return 1;
		//give us menu control
		menuplug = currentplug;
		Key_Dest_Add(kdm_emenu);
		return 1;
	case 2: //weather it's us or not.
		return currentplug == menuplug && Key_Dest_Has(kdm_emenu);
	case 3:	//weather a menu is active
		return !!Key_Dest_Has(kdm_emenu|kdm_gmenu);
	default:
		return 0;
	}
}

static qintptr_t VARGS Plug_Key_GetKeyCode(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int modifier;
	return Key_StringToKeynum(VM_POINTER(arg[0]), &modifier);
}




static qintptr_t VARGS Plug_SCR_CenterPrint(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	if (qrenderer == QR_NONE)
		return 0;

	SCR_CenterPrint(0, VM_POINTER(arg[0]), true);
	return 0;
}




typedef struct {
	//Make SURE that the engine has resolved all cvar pointers into globals before this happens.
	plugin_t *plugin;
	char name[64];
	int type;
	char *script;
	mpic_t *pic;
} pluginimagearray_t;
int pluginimagearraylen;
pluginimagearray_t *pluginimagearray;

#include "shader.h"

static qintptr_t VARGS Plug_Draw_LoadImage(char *name, int type, char *script)
{
	int i;

	mpic_t *pic;

	if (!*name)
		return 0;

	for (i = 0; i < pluginimagearraylen; i++)
	{
		if (!pluginimagearray[i].plugin)
			break;
		if (pluginimagearray[i].plugin == currentplug)
		{
			if (!strcmp(name, pluginimagearray[i].name))
				break;
		}
	}
	if (i == pluginimagearraylen)
	{
		pluginimagearraylen++;
		pluginimagearray = BZ_Realloc(pluginimagearray, pluginimagearraylen*sizeof(pluginimagearray_t));
		pluginimagearray[i].pic = NULL;
	}

	if (pluginimagearray[i].pic)
		return i+1;	//already loaded.

	if (qrenderer != QR_NONE)
	{
		if (type == 3)
			pic = NULL;
		else if (type == 2)
			pic = R_RegisterShader(name, SUF_NONE, script);
		else if (type)
			pic = R2D_SafePicFromWad(name);
		else
			pic = R2D_SafeCachePic(name);
	}
	else
		pic = NULL;

	Q_strncpyz(pluginimagearray[i].name, name, sizeof(pluginimagearray[i].name));
	pluginimagearray[i].type = type;
	pluginimagearray[i].pic = pic;
	pluginimagearray[i].plugin = currentplug;
	pluginimagearray[i].script = script?Z_StrDup(script):NULL;
	return i + 1;
}

qbyte *Read32BitImageFile(qbyte *buf, int len, int *width, int *height, qboolean *hasalpha, const char *fname);
static qintptr_t VARGS Plug_Draw_LoadImageData(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	qintptr_t ret = 0;
	char *name = VM_POINTER(arg[0]);
//	char *mimetype = VM_POINTER(arg[1]);
	void *codeddata = VM_POINTER(arg[2]);
	unsigned int datalength = VM_LONG(arg[3]);
	image_t *t;
	qbyte *rgbdata;
	unsigned int width, height;
	
	if (VM_OOB(arg[2], arg[3]))
		return 0;
		
	if ((rgbdata = Read32BitImageFile(codeddata, datalength, &width, &height, NULL, name)))
	{
//		name = va("%s", name);
		
		t = Image_FindTexture(name, NULL, IF_PREMULTIPLYALPHA|IF_NOMIPMAP|IF_UIPIC|IF_CLAMP);
		if (!TEXVALID(t))
			t = Image_CreateTexture(name, NULL, IF_PREMULTIPLYALPHA|IF_NOMIPMAP|IF_UIPIC|IF_CLAMP);
		if (TEXVALID(t))
		{
			Image_Upload(t, TF_RGBA32, rgbdata, NULL, width, height, IF_PREMULTIPLYALPHA|IF_NOMIPMAP|IF_UIPIC|IF_CLAMP);
			ret = Plug_Draw_LoadImage(name, 3, NULL);
		}
		
		BZ_Free(rgbdata);
	}
	return ret;
}
static qintptr_t VARGS Plug_Draw_LoadImageShader(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	char *script = VM_POINTER(arg[1]);
	return Plug_Draw_LoadImage(name, 2, script);
}
static qintptr_t VARGS Plug_Draw_LoadImagePic(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	int type = arg[1];
	if (type != 0 && type != 1)
		return 0;
	return Plug_Draw_LoadImage(name, type, NULL);
}

void Plug_DrawReloadImages(void)
{
	int i;
	for (i = 0; i < pluginimagearraylen; i++)
	{
		if (!pluginimagearray[i].plugin)
		{
			pluginimagearray[i].pic = NULL;
			continue;
		}

		pluginimagearray[i].pic = R2D_SafePicFromWad(pluginimagearray[i].name);
		//pluginimagearray[i].pic = R2D_SafeCachePic(pluginimagearray[i].name);
		//pluginimagearray[i].pic = NULL;
	}
}

static void Plug_FreePlugImages(plugin_t *plug)
{
	int i;
	for (i = 0; i < pluginimagearraylen; i++)
	{
		if (pluginimagearray[i].plugin == plug)
		{
			pluginimagearray[i].plugin = 0;
			pluginimagearray[i].pic = NULL;
			pluginimagearray[i].name[0] = '\0';
		}
	}
}

//int R2D_ImageSize (qhandle_t image, float *w, float *h)
static qintptr_t VARGS Plug_Draw_ImageSize(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	float *w = VM_POINTER(arg[1]), *h = VM_POINTER(arg[2]);
	int iw, ih, ret;
	mpic_t *pic;
	int i;
	
	if (VM_OOB(arg[1], sizeof(*w)) || VM_OOB(arg[2], sizeof(*w)))
		return -1;
	*w = 0;
	*h = 0;
	if (qrenderer == QR_NONE)
		return 0;

	i = VM_LONG(arg[0]);
	if (i <= 0 || i > pluginimagearraylen)
		return -1;	// you fool
	i = i - 1;
	if (pluginimagearray[i].plugin != currentplug)
		return -1;

	if (pluginimagearray[i].pic)
		pic = pluginimagearray[i].pic;
	else if (pluginimagearray[i].type == 1)
		return 0;	//wasn't loaded.
	else
	{
		pic = R2D_SafeCachePic(pluginimagearray[i].name);
		if (!pic)
			return -1;
	}

	ret = R_GetShaderSizes(pic, &iw, &ih, true);
	*w = iw;
	*h = ih;
	return ret;
}

//int R2D_Image (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t image)
static qintptr_t VARGS Plug_Draw_Image(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	mpic_t *pic;
	int i;
	if (qrenderer == QR_NONE)
		return 0;

	i = VM_LONG(arg[8]);
	if (i <= 0 || i > pluginimagearraylen)
		return -1;	// you fool
	i = i - 1;
	if (pluginimagearray[i].plugin != currentplug)
		return -1;

	if (pluginimagearray[i].pic)
		pic = pluginimagearray[i].pic;
	else if (pluginimagearray[i].type == 1)
		return 0;	//wasn't loaded.
	else
	{
		pic = R2D_SafeCachePic(pluginimagearray[i].name);
		if (!pic)
			return -1;
	}

	R2D_Image(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4]), VM_FLOAT(arg[5]), VM_FLOAT(arg[6]), VM_FLOAT(arg[7]), pic);
	return 1;
}
//x1,y1,x2,y2
static qintptr_t VARGS Plug_Draw_Line(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	R2D_Line(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), NULL);
	return 1;
}
static qintptr_t VARGS Plug_Draw_Character(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int x, y;
	if (qrenderer == QR_NONE)
		return 0;
	Font_BeginString(font_default, arg[0], arg[1], &x, &y);
	Font_DrawChar(x, y, CON_WHITEMASK, 0xe000 | (unsigned int)arg[2]);
	Font_EndString(font_default);
	return 0;
}
static qintptr_t VARGS Plug_Draw_CharacterH(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	float x = VM_FLOAT(arg[0]);
	float y = VM_FLOAT(arg[1]);
	float h = VM_FLOAT(arg[2]);
	unsigned int flags = VM_LONG(arg[3]);
	unsigned int charc = VM_LONG(arg[4]);
	conchar_t cmask = CON_WHITEMASK;
	if (qrenderer == QR_NONE)
		return 0;
	if (flags & 1)
		cmask |= CON_2NDCHARSETTEXT;
	if (!(flags & 2))
		cmask |= 0xe000;
	Font_BeginScaledString(font_default, x, y, h, h, &x, &y);
	Font_DrawScaleChar(x, y, cmask, charc);
	Font_EndString(font_default);
	return 0;
}
static qintptr_t VARGS Plug_Draw_String(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int ipx, px, py;
	conchar_t buffer[2048], *str;
	unsigned int codeflags, codepoint;
	if (qrenderer == QR_NONE)
		return 0;
	COM_ParseFunString(CON_WHITEMASK, VM_POINTER(arg[2]), buffer, sizeof(buffer), false);
	str = buffer;
	Font_BeginString(font_default, VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), &px, &py);
	ipx = px;
	while(*str)
	{
		str = Font_Decode(str, &codeflags, &codepoint);
		if (codepoint == '\n')
			py += Font_CharHeight();
		else if (codepoint == '\r')
			px = ipx;
		else
			px = Font_DrawChar(px, py, codeflags, codepoint);
	}
	Font_EndString(font_default);
	return 0;
}
static qintptr_t VARGS Plug_Draw_StringH(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	float x = VM_FLOAT(arg[0]);
	float y = VM_FLOAT(arg[1]);
	float h = VM_FLOAT(arg[2]);
	unsigned int flags = VM_LONG(arg[3]);
	char *instr = VM_POINTER(arg[4]);
	float ipx;
	conchar_t buffer[2048], *str, cmask = CON_WHITEMASK;
	unsigned int codeflags, codepoint;
	unsigned int parseflags = 0;
	if (qrenderer == QR_NONE)
		return 0;
	if (flags & 1)
		cmask |= CON_2NDCHARSETTEXT;
	if (flags & 2)
		parseflags |= PFS_FORCEUTF8;
	COM_ParseFunString(CON_WHITEMASK, instr, buffer, sizeof(buffer), parseflags);
	str = buffer;
	Font_BeginScaledString(font_default, x, y, h, h, &x, &y);
	ipx = x;
	while(*str)
	{
		str = Font_Decode(str, &codeflags, &codepoint);
		if (codepoint == '\n')
			y += Font_CharScaleHeight();
		else if (codepoint == '\r')
			x = ipx;
		else
			x = Font_DrawScaleChar(x, y, codeflags, codepoint);
	}
	Font_EndString(font_default);
	return 0;
}

static qintptr_t VARGS Plug_Draw_StringWidth(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	qintptr_t ret;
	float h = VM_FLOAT(arg[0]);
	unsigned int flags = VM_LONG(arg[1]);
	char *instr = VM_POINTER(arg[2]);
	conchar_t buffer[2048], *str, cmask = CON_WHITEMASK;
	unsigned int parseflags = 0;
	float px,py;
	if (qrenderer == QR_NONE)
		return 0;
	if (flags & 1)
		cmask |= CON_2NDCHARSETTEXT;
	if (flags & 2)
		parseflags |= PFS_FORCEUTF8;
	str = COM_ParseFunString(CON_WHITEMASK, instr, buffer, sizeof(buffer), parseflags);
	
	Font_BeginScaledString(font_default, 0, 0, h, h, &px, &py);
	px = Font_LineScaleWidth(buffer, str);
	Font_EndString(NULL);
	
	//put it back in virtual space
	VM_FLOAT(ret) = (px*(float)vid.width) / (float)vid.rotpixelwidth;
	return ret;
}

static qintptr_t VARGS Plug_Draw_Fill(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	float x, y, width, height;
	if (qrenderer == QR_NONE)
		return 0;
	x = VM_FLOAT(arg[0]);
	y = VM_FLOAT(arg[1]);
	width = VM_FLOAT(arg[2]);
	height = VM_FLOAT(arg[3]);

	R2D_FillBlock(x, y, width, height);
	return 0;
}
static qintptr_t VARGS Plug_Draw_ColourP(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int p = VM_LONG(arg[0]);
	if (p<0 || p>255)
		return false;

	R2D_ImagePaletteColour(p, 1);
	return 1;
}
static qintptr_t VARGS Plug_Draw_ColourPA(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int p = VM_LONG(arg[0]);
	float a = VM_FLOAT(arg[1]);
	if (p<0 || p>255)
		return false;

	R2D_ImagePaletteColour(p, a);
	return 1;
}
static qintptr_t VARGS Plug_Draw_Colour3f(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	R2D_ImageColours(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), 1);
	return 1;
}
static qintptr_t VARGS Plug_Draw_Colour4f(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	R2D_ImageColours(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]));
	return 1;
}









static qintptr_t VARGS Plug_LocalSound(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	if (qrenderer == QR_NONE)
		return false;

	S_LocalSound(VM_POINTER(arg[0]));
	return 0;
}



static qintptr_t VARGS Plug_CL_GetStats(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int i = 0;
	int pnum = VM_LONG(arg[0]);
	unsigned int *stats = VM_POINTER(arg[1]);
	int pluginstats = VM_LONG(arg[2]);
	int max;

	if (VM_OOB(arg[1], arg[2]*4))
		return 0;

	if (qrenderer == QR_NONE || !cls.state)
		return 0;

	max = pluginstats;
	if (max > MAX_CL_STATS)
		max = MAX_CL_STATS;
	if (pnum < 0)
	{
		pnum = -pnum-1;
		if (pnum < MAX_CLIENTS)
		{
			for (i = 0; i < max; i++)
				stats[i] = cl.players[pnum].stats[i];
		}
	}
	else if (pnum < cl.splitclients)
	{
		for (i = 0; i < max; i++)
		{	//fill stats with the right player's stats
			stats[i] = cl.playerview[pnum].stats[i];
		}
	}

	max = i;
	for (; i < pluginstats; i++)	//plugin has too many stats (wow)
		stats[i] = 0;					//fill the rest.
	return max;
}

#define PLUGMAX_SCOREBOARDNAME 64
typedef struct {
	int topcolour;
	int bottomcolour;
	int frags;
	char name[PLUGMAX_SCOREBOARDNAME];
	int ping;
	int pl;
	float activetime;
	int userid;
	int spectator;
	char userinfo[1024];
	char team[8];
} vmplugclientinfo_t;

static qintptr_t VARGS Plug_GetPlayerInfo(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int i, pt;
	vmplugclientinfo_t *out;

	if (VM_OOB(arg[1], sizeof(vmplugclientinfo_t)))
		return -1;
	if (VM_LONG(arg[0]) < -1 || VM_LONG(arg[0] ) >= MAX_CLIENTS)
		return -2;

	i = VM_LONG(arg[0]);
	out = VM_POINTER(arg[1]);
	if (out)
	{
		if (i < 0)
		{
			if (i >= -(int)MAX_SPLITS)
				i = cl.playerview[-i-1].playernum;
			if (i < 0)
			{
				memset(out, 0, sizeof(*out));
				return 0;
			}	
		}
		out->bottomcolour = cl.players[i].rbottomcolor;
		out->topcolour = cl.players[i].rtopcolor;
		out->frags = cl.players[i].frags;
		Q_strncpyz(out->name, cl.players[i].name, PLUGMAX_SCOREBOARDNAME);
		out->ping = cl.players[i].ping;
		out->pl = cl.players[i].pl;
		out->activetime = realtime - cl.players[i].realentertime;
		out->userid = cl.players[i].userid;
		out->spectator = cl.players[i].spectator;
		InfoBuf_ToString(&cl.players[i].userinfo, out->userinfo, sizeof(out->userinfo), basicuserinfos, NULL, NULL, NULL, NULL);
		Q_strncpyz(out->team, cl.players[i].team, sizeof(out->team));
	}

	pt = Cam_TrackNum(&cl.playerview[0]);
	if (pt < 0)
		return (cl.playerview[0].playernum == i);
	else
		return pt == i;
}

static qintptr_t VARGS Plug_LocalPlayerNumber(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	return cl.playerview[0].playernum;
}
static qintptr_t VARGS Plug_GetLocalPlayerNumbers(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int i;
	int	first = VM_LONG(arg[0]);
	int	count = VM_LONG(arg[1]);
	int *playernums = VM_POINTER(arg[2]);
	int *spectracks = VM_POINTER(arg[3]);
	if (count < 0 || count > 1000) count = 0;
	if (VM_OOB(arg[2], sizeof(*playernums)*count) || VM_OOB(arg[3], sizeof(*playernums)*count))
		return false;
	if (first < 0) first = 0;
	if (first > cl.splitclients) first = cl.splitclients;
	if (first+count > cl.splitclients) count = cl.splitclients-first;
	for (i = 0; i < count; i++)
	{
		playernums[i] = cl.playerview[i].playernum;
		spectracks[i] = Cam_TrackNum(&cl.playerview[i]);
	}
	return count;
}

static qintptr_t VARGS Plug_GetServerInfo(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *outptr = VM_POINTER(arg[0]);
	unsigned int outlen = VM_LONG(arg[1]);
	extern float demtime;

	if (VM_OOB(arg[0], outlen))
		return false;

	InfoBuf_ToString(&cl.serverinfo, outptr, outlen, NULL, NULL, NULL, NULL, NULL);
	Q_strncatz(outptr, va("\\intermission\\%i", cl.intermissionmode), outlen);
	switch(cls.demoplayback)
	{
	case DPB_NONE:
		break;
	case DPB_MVD:
	case DPB_EZTV:
		Q_strncatz(outptr, "\\demotype\\mvd", outlen);
		break;
	case DPB_QUAKEWORLD:
		Q_strncatz(outptr, "\\demotype\\qw", outlen);
		break;
#ifdef NQPROT
	case DPB_NETQUAKE:
		Q_strncatz(outptr, "\\demotype\\nq", outlen);
		break;
#endif
#ifdef Q2CLIENT
	case DPB_QUAKE2:
		Q_strncatz(outptr, "\\demotype\\q2", outlen);
		break;
#endif
	}
	Q_strncatz(outptr, va("\\demotime\\%f", demtime-cls.demostarttime), outlen);

#ifdef QUAKEHUD
	if (cl.playerview[0].statsf[STAT_MATCHSTARTTIME])
		Q_strncatz(outptr, va("\\matchstart\\%f", cl.playerview[0].statsf[STAT_MATCHSTARTTIME]/1000), outlen);
	else
#endif
		Q_strncatz(outptr, va("\\matchstart\\%f", cl.matchgametimestart), outlen);

	return true;
}

static qintptr_t VARGS Plug_SetUserInfo(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *key = VM_POINTER(arg[0]);
	char *value = VM_POINTER(arg[1]);

	CL_SetInfo(0, key, value);

	return true;
}

typedef struct {
	int seats;
	struct
	{
		float s_avg;
		float s_mn;
		float s_mx;
		float ms_stddev;
		float fr_avg;
		int fr_mn;
		int fr_mx;
	} ping;
	struct
	{	//decimals
		float dropped;
		float choked;
		float invalid;
	} loss;
	float mlatency;
	float mrate;
	float vlatency;
	float vrate;
	vec3_t speed;	//player speed
	
	struct
	{
		float in_pps;
		float in_bps;
		float out_pps;
		float out_bps;
	} clrate;
	struct
	{
		float in_pps;
		float in_bps;
		float out_pps;
		float out_bps;
	} svrate;
	int capturing;
} vmnetinfo_t;
#define has(x) (((quintptr_t)&((vmnetinfo_t*)NULL)->x + sizeof(((vmnetinfo_t*)NULL)->x)) <= outlen)
//aka: misc other hud timing crap
static qintptr_t VARGS Plug_GetNetworkInfo(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	vmnetinfo_t *outptr = VM_POINTER(arg[0]);
	unsigned int outlen = VM_LONG(arg[1]);
	if (VM_OOB(arg[0], outlen))
		return false;

	if (has(capturing))
	{
#ifdef HAVE_MEDIA_ENCODER
		outptr->capturing = Media_Capturing();
#else
		outptr->capturing = 0;
#endif
	}
	
	if (has(seats))
		outptr->seats = cl.splitclients;
	if (has(ping))		
		CL_CalcNet2 (&outptr->ping.s_avg, &outptr->ping.s_mn, &outptr->ping.s_mx, &outptr->ping.ms_stddev, &outptr->ping.fr_avg, &outptr->ping.fr_mn, &outptr->ping.fr_mx, &outptr->loss.dropped, &outptr->loss.choked, &outptr->loss.invalid);
		
	if (has(mlatency))
		outptr->mlatency = 0;
	if (has(mrate))
		outptr->mrate = IN_DetermineMouseRate();
	if (has(vlatency))
		outptr->vlatency = 0;
		
	if (has(speed))
		VectorCopy(outptr->speed, r_refdef.playerview->simvel);

	if (has(clrate))
		NET_GetRates(cls.sockets, &outptr->clrate.in_pps, &outptr->clrate.out_pps, &outptr->clrate.in_bps, &outptr->clrate.out_bps);		
	if (has(svrate))
	{
		memset(&outptr->svrate, 0, sizeof(outptr->svrate));
#ifndef CLIENTONLY
		NET_GetRates(svs.sockets, &outptr->svrate.in_pps, &outptr->svrate.out_pps, &outptr->svrate.in_bps, &outptr->svrate.out_bps);
#endif
	}
	
	return sizeof(vmnetinfo_t);
}
#undef has

#ifdef QUAKEHUD
static qintptr_t VARGS Plug_GetTrackerOwnFrags(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int ret;
	int seat = VM_LONG(arg[0]);
	char *outptr = VM_POINTER(arg[1]);
	size_t outlen = VM_LONG(arg[2]);
	if (VM_OOB(arg[1], outlen) || !outlen)
		VM_FLOAT(ret) = 0;
	else
		VM_FLOAT(ret) = Stats_GetLastOwnFrag(seat, outptr, outlen);
	return ret;
}
#endif

static qintptr_t VARGS Plug_GetLocationName(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	float *locpoint = VM_POINTER(arg[0]);
	char *locname = VM_POINTER(arg[1]);
	unsigned int locnamelen = VM_LONG(arg[2]);
	char *result;

	if (VM_OOB(arg[1], locnamelen))
		return 0;

	result = TP_LocationName(locpoint);
	Q_strncpyz(locname, result, locnamelen);
	return VM_LONG(arg[1]);
}

#ifdef QUAKEHUD
typedef struct
{
	unsigned int client;
	unsigned int items;
	float armor;
	float health;
	vec3_t org;
	char nick[16];
} teamplayerinfo_t;
static qintptr_t VARGS Plug_GetTeamInfo(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	teamplayerinfo_t *players = VM_POINTER(arg[0]);
	size_t maxplayers = VM_LONG(arg[1]);
	qboolean showenemies = VM_LONG(arg[2]);
	qboolean showself = VM_LONG(arg[3]);
	int count = 0;
	int i;
	int self;
	lerpents_t		*le;
	player_info_t	*pl;

	if (VM_OOB(arg[0], maxplayers*sizeof(*players)))
		return 0;
	maxplayers = min(maxplayers, cl.allocated_client_slots);
	
	Cvar_Get("ti", "1", CVAR_USERINFO, "Hacks because ktx sucks. Must be 1 in order to receive team information in ktx.");
	
	self = cl.playerview[0].playernum;
	if (cl.playerview[0].cam_state != CAM_FREECAM)
		self = cl.playerview[0].cam_spec_track;
	
	for (i = 0; i < cl.allocated_client_slots && maxplayers > 0; i++)
	{
		if (!*cl.players[i].name)	//empty slot
			continue;
		if (cl.players[i].spectator)	//shoo!
			continue;
		if (i == self && !showself)
			continue;
		if (!showenemies && strcmp(cl.players[i].team, cl.players[self].team))
			continue;
		players->client = i;

		pl = &cl.players[i];
		if (pl->tinfo.time > cl.time)
		{	//mod is explicitly telling us this junk
			players->items = pl->tinfo.items;
			players->health = pl->tinfo.health;
			players->armor = pl->tinfo.armour;
			VectorCopy(pl->tinfo.org, players->org);
			Q_strncpyz(players->nick, pl->tinfo.nick, sizeof(players->nick));
		}
		else if (i == self)
		{	//oh hey look, its me.
			players->items = cl.playerview[0].stats[STAT_ITEMS];
			players->armor = cl.playerview[0].statsf[STAT_ARMOR];
			players->health = cl.playerview[0].statsf[STAT_HEALTH];
			Q_strncpyz(players->nick, "", sizeof(players->nick));
		}
		else if (cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV)
		{	//scrape it from the mvd (assuming there is one...
			players->items = cl.players[i].stats[STAT_ITEMS];
			players->armor = cl.players[i].statsf[STAT_ARMOR];
			players->health = cl.players[i].statsf[STAT_HEALTH];
			Q_strncpyz(players->nick, "", sizeof(players->nick));
			
			VectorClear(players->org);
		}
		else
			continue;	//no stats, don't bother telling the plugin.

		//scrape origin from interpolation, if its more valid.
		if (i+1 < cl.maxlerpents && cl.lerpentssequence && cl.lerpents[i+1].sequence == cl.lerpentssequence)
		{
			le = &cl.lerpents[i+1];
			VectorCopy(le->origin, players->org);
		}
		else if (cl.lerpentssequence && cl.lerpplayers[i].sequence == cl.lerpentssequence)
		{
			le = &cl.lerpplayers[i];
			VectorCopy(le->origin, players->org);
		}

		players++;
		maxplayers--;
		count++;
	}
	
	return VM_LONG(count);
}
#endif
#ifdef QUAKEHUD
static qintptr_t VARGS Plug_GetWeaponStats(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int self = VM_LONG(arg[0]);
	struct wstats_s *result = VM_POINTER(arg[1]);
	size_t maxresults = VM_LONG(arg[2]);

	if (VM_OOB(arg[0], maxresults*sizeof(*result)))
		return 0;
	
	//FIXME: we should support some way to clear this to 0 again, other than nosave.
	Cvar_Get("wpsx", "1", CVAR_USERINFO|CVAR_NOSAVE, "Hacks because ktx sucks. Must be 1 in order to receive weapon stats information in ktx.");

	if (self < 0)
	{
		unsigned int seat = (unsigned)(-self-1)%MAX_SPLITS;
		self = cl.playerview[seat].playernum;
		if (cl.playerview[seat].cam_state != CAM_FREECAM)
			self = cl.playerview[seat].cam_spec_track;
	}
	if (self < 0)
		return 0;

	if (maxresults > countof(cl.players[self].weaponstats))
		maxresults = countof(cl.players[self].weaponstats);
	memcpy(result, cl.players[self].weaponstats, sizeof(*result) * maxresults);
	return VM_LONG(maxresults);
}
#endif

static qintptr_t VARGS Plug_Con_SubPrint(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	char *text = VM_POINTER(arg[1]);
	console_t *con;
	if (!name)
		name = "";

	if (qrenderer == QR_NONE)
	{
		if (!*name)
		{
			Con_Printf("%s", text);
			return 1;
		}
		return false;
	}

	con = Con_FindConsole(name);
	if (!con)
	{
		con = Con_Create(name, 0);
		Con_SetActive(con);

		if (currentplug->conexecutecommand)
		{
			con->notif_x = 0;
			con->notif_y = 8*4;
			con->notif_w = vid.width;
			con->notif_t = 8;
			con->notif_l = 4;
			con->flags |= CONF_NOTIFY;
			con->userdata = currentplug;
			con->linebuffered = Plug_SubConsoleCommand;
		}
	}

	Con_PrintCon(con, text, con->parseflags);

	return 1;
}
static qintptr_t VARGS Plug_Con_RenameSub(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	console_t *con;
	if (qrenderer == QR_NONE)
		return false;
	con = Con_FindConsole(name);
	if (!con)
		return 0;

	Q_strncpyz(con->name, name, sizeof(con->name));

	return 1;
}
static qintptr_t VARGS Plug_Con_IsActive(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	console_t *con;
	if (qrenderer == QR_NONE)
		return false;
	con = Con_FindConsole(name);
	if (!con)
		return false;

	return Con_IsActive(con);
}
static qintptr_t VARGS Plug_Con_SetActive(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	console_t *con;
	if (qrenderer == QR_NONE)
		return false;
	con = Con_FindConsole(name);
	if (!con)
		con = Con_Create(name, 0);

	Con_SetActive(con);
	return true;
}
static qintptr_t VARGS Plug_Con_Destroy(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *name = VM_POINTER(arg[0]);
	console_t *con;
	if (qrenderer == QR_NONE)
		return false;
	con = Con_FindConsole(name);
	if (!con)
		return false;

	Con_Destroy(con);
	return true;
}
static qintptr_t VARGS Plug_Con_NameForNum(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char num = VM_LONG(arg[0]);
	char *buffer = VM_POINTER(arg[1]);
	int buffersize = VM_LONG(arg[2]);
	if (VM_OOB(arg[1], buffersize) || buffersize < 1)
		return false;
	if (qrenderer == QR_NONE)
		return false;

	return Con_NameForNum(num, buffer, buffersize);
}

static qintptr_t VARGS Plug_Con_GetConsoleFloat(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *conname = VM_POINTER(arg[0]);
	char *attrib = VM_POINTER(arg[1]);
	int retbuf;
	float ret;
	console_t *con = Con_FindConsole(conname);
	ret = -1;

	if (!con)
		ret = -1;
	else if (!strcmp(attrib, "unseen"))
		ret = con->unseentext;
	else if (!strcmp(attrib, "markup"))	
	{
		if (con->parseflags & PFS_NOMARKUP)
			ret = 0;
		else if (con->parseflags & PFS_KEEPMARKUP)
			ret = 2;
		else
			ret = 1;
	}
	else if (!strcmp(attrib, "forceutf8"))
		ret = (con->parseflags&PFS_FORCEUTF8)?true:false;
	else if (!strcmp(attrib, "hidden"))
		ret = (con->flags & CONF_HIDDEN)?true:false;
	else if (!strcmp(attrib, "iswindow"))
		ret = (con->flags & CONF_ISWINDOW)?true:false;
	else if (!strcmp(attrib, "maxlines"))
		ret = con->maxlines;
	else if (!strcmp(attrib, "wnd_x"))
		ret = con->wnd_x;
	else if (!strcmp(attrib, "wnd_y"))
		ret = con->wnd_y;
	else if (!strcmp(attrib, "wnd_w"))
		ret = con->wnd_w;
	else if (!strcmp(attrib, "wnd_h"))
		ret = con->wnd_h;
	else if (!strcmp(attrib, "linecount"))
		ret = con->linecount;

	VM_FLOAT(retbuf) = ret;
	return retbuf;
}

static qintptr_t VARGS Plug_Con_SetConsoleFloat(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	char *conname = VM_POINTER(arg[0]);
	char *attrib = VM_POINTER(arg[1]);
	float val = VM_FLOAT(arg[2]);
	console_t *con = Con_FindConsole(conname);

	if (!con)
	{
		con = Con_Create(conname, 0);
		if (!con)
			return -1;
		con->userdata = currentplug;
		con->linebuffered = Plug_SubConsoleCommand;
	}

	if (!strcmp(attrib, "unseen"))
		con->unseentext = !!val;
	else if (!strcmp(attrib, "markup"))	
	{
		int cur = val;
		con->parseflags &= ~(PFS_NOMARKUP|PFS_KEEPMARKUP);
		if (cur == 0)
			con->parseflags |= PFS_NOMARKUP;
		else if (cur == 2)
			con->parseflags |= PFS_KEEPMARKUP;
	}
	else if (!strcmp(attrib, "forceutf8"))
		con->parseflags = (con->parseflags & ~PFS_FORCEUTF8) | (val?PFS_FORCEUTF8:0);
	else if (!strcmp(attrib, "hidden"))
		con->flags = (con->flags & ~CONF_HIDDEN) | (val?CONF_HIDDEN:0);
	else if (!strcmp(attrib, "iswindow"))
	{
		con->flags = (con->flags & ~CONF_ISWINDOW) | (val?CONF_ISWINDOW:0);
		con->flags = (con->flags & ~CONF_NOTIFY) | (val>1?CONF_NOTIFY:0);
		if (con_curwindow == con && !(con->flags & CONF_ISWINDOW))
			con_curwindow = NULL;
		else if (!con_curwindow && (con->flags & CONF_ISWINDOW))
			con_curwindow = con;
	}
	else if (!strcmp(attrib, "maxlines"))
		con->maxlines = val;
	else if (!strcmp(attrib, "wnd_x"))
		con->wnd_x = val;
	else if (!strcmp(attrib, "wnd_y"))
		con->wnd_y = val;
	else if (!strcmp(attrib, "wnd_w"))
		con->wnd_w = val;
	else if (!strcmp(attrib, "wnd_h"))
		con->wnd_h = val;
	else if (!strcmp(attrib, "linebuffered"))
	{
		con->userdata = currentplug;
		if (val == 2)
			con->linebuffered = NULL;//Con_Navigate;
		else if (val == 1)
			con->linebuffered = Plug_SubConsoleCommand;
		else
			con->linebuffered = NULL;
	}
	else if (!strcmp(attrib, "linecount"))
	{
		if (val == 0)
		{
			int pfl = con->parseflags;
			Con_ClearCon(con);
			con->parseflags = pfl;
		}
		else
			return -1;
	}
	else
		return -1;
	return true;
}

static qintptr_t VARGS Plug_Con_GetConsoleString(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	const char *conname = VM_POINTER(arg[0]);
	const char *attrib = VM_POINTER(arg[1]);
	char *value = VM_POINTER(arg[2]);
	size_t size = VM_LONG(arg[3]);
	console_t *con = Con_FindConsole(conname);
	
	if (VM_OOB(arg[2], arg[3]))
		return 0;

	if (!con)
		return 0;
	else if (!strcmp(attrib, "footer"))
		;
	else if (!strcmp(attrib, "title"))
	{
		Q_strncpyz(value, con->title, size);
	}
	else if (!strcmp(attrib, "icon"))
	{
		Q_strncpyz(value, con->icon, size);
	}
	else if (!strcmp(attrib, "prompt"))
	{
		Q_strncpyz(value, con->prompt, size);
	}
	else if (!strcmp(attrib, "backimage"))
	{
		if (con->backshader)
			Q_strncpyz(value, con->backshader->name, size);
		else
			Q_strncpyz(value, con->backimage, size);
	}
	return 0;
}
static qintptr_t VARGS Plug_Con_SetConsoleString(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	const char *conname = VM_POINTER(arg[0]);
	const char *attrib = VM_POINTER(arg[1]);
	const char *value = VM_POINTER(arg[2]);
	console_t *con = Con_FindConsole(conname);

	if (!con)
	{
		con = Con_Create(conname, 0);
		if (!con)
			return -1;
		con->userdata = currentplug;
		con->linebuffered = Plug_SubConsoleCommand;
	}
	if (!con)
		return 0;
	else if (!strcmp(attrib, "footer"))
		Con_Footerf(con, false, "%s", value);
	else if (!strcmp(attrib, "title"))
		Q_strncpyz(con->title, value, sizeof(con->title));
	else if (!strcmp(attrib, "icon"))
		Q_strncpyz(con->icon, value, sizeof(con->icon));
	else if (!strcmp(attrib, "prompt"))
		Q_strncpyz(con->prompt, value, sizeof(con->prompt));
	else if (!strcmp(attrib, "backimage"))
	{
		Q_strncpyz(con->backimage, value, sizeof(con->backimage));
		if (con->backshader)
			R_UnloadShader(con->backshader);
	}
	else if (!strcmp(attrib, "backvideomap"))
	{
		Q_strncpyz(con->backimage, "", sizeof(con->backimage));
		if (con->backshader)
			R_UnloadShader(con->backshader);
		if (qrenderer != QR_NONE)
			con->backshader = R_RegisterCustom(va("consolevid_%s", con->name), SUF_NONE, Shader_DefaultCinematic, value);
		else
			con->backshader = NULL;
	}
	else
		return -1;
	return 0;
}

static qintptr_t VARGS Plug_S_RawAudio(void *offset, quintptr_t mask, const qintptr_t *arg)
{
	int sourceid = VM_LONG(arg[0]);
	qbyte *data = VM_POINTER(arg[1]);
	int speed = VM_LONG(arg[2]);
	int samples = VM_LONG(arg[3]);
	int channels = VM_LONG(arg[4]);
	int width = VM_LONG(arg[5]);
	int volume = VM_FLOAT(arg[6]);
	
	int datasize = samples * channels * width;
	
	if (VM_OOB(arg[1], datasize) || datasize < 1)
		return false;
		
	S_RawAudio(sourceid, data, speed, samples, channels, width, volume);
	return 0;
}

#include "com_mesh.h"

#ifdef SKELETALMODELS
static int QDECL Plug_RegisterModelFormatText(const char *formatname, char *magictext, qboolean (QDECL *load) (struct model_s *mod, void *buffer, size_t fsize))
{
	void *module = currentplug;
	return Mod_RegisterModelFormatText(module, formatname, magictext, load);
}
static int QDECL Plug_RegisterModelFormatMagic(const char *formatname, unsigned int magic, qboolean (QDECL *load) (struct model_s *mod, void *buffer, size_t fsize))
{
	void *module = currentplug;
	return Mod_RegisterModelFormatMagic(module, formatname, magic, load);
}
static void QDECL Plug_UnRegisterModelFormat(int idx)
{
	void *module = currentplug;
	Mod_UnRegisterModelFormat(module, idx);
}
static void QDECL Plug_UnRegisterAllModelFormats(void)
{
	void *module = currentplug;
	Mod_UnRegisterAllModelFormats(module);
}
#endif
qintptr_t VARGS Plug_Mod_GetPluginModelFuncs(void *offset, quintptr_t mask, const qintptr_t *arg)
{
#ifdef SKELETALMODELS
	static modplugfuncs_t funcs =
	{
		MODPLUGFUNCS_VERSION,
		
		Plug_RegisterModelFormatText,
		Plug_RegisterModelFormatMagic,
		Plug_UnRegisterModelFormat,
		Plug_UnRegisterAllModelFormats,
		ZG_Malloc,

		R_ConcatTransforms,
		Matrix3x4_Invert_Simple,
		VectorAngles,
		AngleVectors,
		GenMatrixPosQuat4Scale,
		COM_StripExtension,
		Alias_ForceConvertBoneData,
#ifdef TERRAIN
		Terr_GetTerrainFuncs,
#else
		NULL,
#endif
		NULL,
		Image_GetTexture,
		FS_OpenVFS
	};
	if (VM_LONG(arg[0]) >= sizeof(funcs))
		return (qintptr_t)&funcs;
	else
#endif
		return 0;
}

void Plug_Client_Init(void)
{
	Plug_RegisterBuiltin("CL_GetStats",				Plug_CL_GetStats, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Menu_Control",			Plug_Menu_Control, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Key_GetKeyCode",			Plug_Key_GetKeyCode, PLUG_BIF_NEEDSRENDERER);

	Plug_RegisterBuiltin("Draw_LoadImageData",		Plug_Draw_LoadImageData, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_LoadImageShader",	Plug_Draw_LoadImageShader, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_LoadImage",			Plug_Draw_LoadImagePic, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Image",				Plug_Draw_Image, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_ImageSize",			Plug_Draw_ImageSize, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Character",			Plug_Draw_Character, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_CharacterH",			Plug_Draw_CharacterH, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_String",				Plug_Draw_String, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_StringH",			Plug_Draw_StringH, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_StringWidth",		Plug_Draw_StringWidth, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Fill",				Plug_Draw_Fill, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Line",				Plug_Draw_Line, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Colourp",			Plug_Draw_ColourP, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Colourpa",			Plug_Draw_ColourPA, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Colour3f",			Plug_Draw_Colour3f, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Draw_Colour4f",			Plug_Draw_Colour4f, PLUG_BIF_NEEDSRENDERER);

	Plug_RegisterBuiltin("Con_SubPrint",			Plug_Con_SubPrint, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_RenameSub",			Plug_Con_RenameSub, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_IsActive",			Plug_Con_IsActive, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_SetActive",			Plug_Con_SetActive, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_Destroy",				Plug_Con_Destroy, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_NameForNum",			Plug_Con_NameForNum, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_GetConsoleFloat",		Plug_Con_GetConsoleFloat, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_SetConsoleFloat",		Plug_Con_SetConsoleFloat, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_GetConsoleString",	Plug_Con_GetConsoleString, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("Con_SetConsoleString",	Plug_Con_SetConsoleString, PLUG_BIF_NEEDSRENDERER);

	Plug_RegisterBuiltin("LocalSound",				Plug_LocalSound, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("SCR_CenterPrint",			Plug_SCR_CenterPrint, PLUG_BIF_NEEDSRENDERER);

#ifdef QUAKEHUD
	Plug_RegisterBuiltin("GetTeamInfo",				Plug_GetTeamInfo, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("GetWeaponStats",			Plug_GetWeaponStats, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("GetTrackerOwnFrags",		Plug_GetTrackerOwnFrags, PLUG_BIF_NEEDSRENDERER);
#endif
	Plug_RegisterBuiltin("GetLocationName",			Plug_GetLocationName, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("GetPlayerInfo",			Plug_GetPlayerInfo, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("LocalPlayerNumber",		Plug_LocalPlayerNumber, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("GetLocalPlayerNumbers",	Plug_GetLocalPlayerNumbers, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("GetServerInfo",			Plug_GetServerInfo, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("SetUserInfo",				Plug_SetUserInfo, PLUG_BIF_NEEDSRENDERER);
	Plug_RegisterBuiltin("GetNetworkInfo",			Plug_GetNetworkInfo, PLUG_BIF_NEEDSRENDERER);
	
	Plug_RegisterBuiltin("S_RawAudio",				Plug_S_RawAudio, PLUG_BIF_NEEDSRENDERER);
	
	Plug_RegisterBuiltin("Mod_GetPluginModelFuncs",	Plug_Mod_GetPluginModelFuncs, PLUG_BIF_NEEDSRENDERER|PLUG_BIF_DLLONLY);
}

void Plug_Client_Close(plugin_t *plug)
{
	Plug_FreePlugImages(plug);


	if (menuplug == plug)
	{
		menuplug = NULL;
		Key_Dest_Remove(kdm_emenu);
	}
	if (protocolclientplugin == plug)
	{
		protocolclientplugin = NULL;
		if (cls.protocol == CP_PLUGIN)
			cls.protocol = CP_UNKNOWN;
	}
}

void Plug_Client_Shutdown(void)
{
	BZ_Free(pluginimagearray);
	pluginimagearray = NULL;
	pluginimagearraylen = 0;
}






