#include "quakedef.h"

#include "pr_common.h"
#include "shader.h"

#ifdef GLQUAKE
#include "glquake.h"
#endif

#if defined(MENU_DAT) || defined(CSQC_DAT)
#include "cl_master.h"

//MP_MouseMove(mx, my, mouse->qdeviceid)

static qbyte mpkeysdown[K_MAX/8];
extern qboolean csqc_dp_lastwas3d;

void M_Init_Internal (void);
void M_DeInit_Internal (void);

extern unsigned int r2d_be_flags;
static unsigned int PF_SelectDPDrawFlag(pubprogfuncs_t *prinst, int flag)
{
	if (r_refdef.warndraw)
	{
		if (!*r_refdef.rt_destcolour[0].texname)
		{
			r_refdef.warndraw = false; //don't spam too much
			PR_RunWarning(prinst, "Detected attempt to draw to framebuffer where framebuffer is not valid\n");
		}
	}
#ifdef CSQC_DAT
	csqc_dp_lastwas3d = false;	//for compat with dp's stupid beginpolygon
#endif

	//flags:
	//0 = blend
	//1 = add
	//2 = modulate
	//3 = modulate*2
	flag &= 3;
	if (flag == DRAWFLAG_ADD)
		return BEF_FORCEADDITIVE;
	else
		return 0;
}

//float	drawfill(vector position, vector size, vector rgb, float alpha, float flag) = #457;
void QCBUILTIN PF_CL_drawfill (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float *size = G_VECTOR(OFS_PARM1);
	float *rgb = G_VECTOR(OFS_PARM2);
	float alpha = G_FLOAT(OFS_PARM3);
	int flag = prinst->callargc >= 5?G_FLOAT(OFS_PARM4):0;

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	R2D_FillBlock(pos[0], pos[1], size[0], size[1]);
	r2d_be_flags = 0;

	G_FLOAT(OFS_RETURN) = 1;
}
//void	drawsetcliparea(float x, float y, float width, float height) = #458;
void QCBUILTIN PF_CL_drawsetcliparea (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	srect_t srect;
	if (R2D_Flush)
		R2D_Flush();

#ifdef CSQC_DAT
	csqc_dp_lastwas3d = false;
#endif

	srect.x = G_FLOAT(OFS_PARM0) / (float)vid.fbvwidth;
	srect.y = G_FLOAT(OFS_PARM1) / (float)vid.fbvheight;
	srect.width = G_FLOAT(OFS_PARM2) / (float)vid.fbvwidth;
	srect.height = G_FLOAT(OFS_PARM3) / (float)vid.fbvheight;
	srect.dmin = -99999;
	srect.dmax = 99999;
	srect.y = (1-srect.y) - srect.height;
	BE_Scissor(&srect);

	G_FLOAT(OFS_RETURN) = 1;
}
//void	drawresetcliparea(void) = #459;
void QCBUILTIN PF_CL_drawresetcliparea (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (R2D_Flush)
		R2D_Flush();

#ifdef CSQC_DAT
	csqc_dp_lastwas3d = false;
#endif

	BE_Scissor(NULL);
	G_FLOAT(OFS_RETURN) = 1;
}

#define FONT_SLOTS 32
#define FONT_SIZES 16
struct {
	unsigned int owner;	//kdm_foo. whoever has an interest in this font. font is purged when this becomes 0.
	char slotname[16];
	char facename[MAX_OSPATH];
	float scale; //poop
	int outline; //argh
	int sizes;
	int size[FONT_SIZES];
	struct font_s *font[FONT_SIZES];
} fontslot[FONT_SLOTS];

static struct font_s *PR_CL_ChooseFont(float *fontsel, int szx, int szy)
{
	int fontidx = 0;	//default by default...
	struct font_s *font = font_default;

	if (fontsel)
	{
		fontidx = *fontsel;
	}

	if (fontidx >= 0 && fontidx < FONT_SLOTS)
	{
		int i, j;
		int fontdiff = 10000;
		for (i = 0; i < fontslot[fontidx].sizes; i++)
		{
			j = abs(szy - fontslot[fontidx].size[i]);
			if (j < fontdiff && fontslot[fontidx].font[i])
			{
				fontdiff = j;
				font = fontslot[fontidx].font[i];
			}
		}
	}
	return font;
}
void PR_CL_BeginString(pubprogfuncs_t *prinst, float vx, float vy, float szx, float szy, float *px, float *py)
{
	world_t *world = prinst->parms->user;
	struct font_s *font;
	if (world->g.drawfontscale && (world->g.drawfontscale[0] || world->g.drawfontscale[1]))
	{
		szx *= world->g.drawfontscale[0];
		szy *= world->g.drawfontscale[1];
	}
	font = PR_CL_ChooseFont(world->g.drawfont, szx, szy);
	Font_BeginScaledString(font, vx, vy, szx, szy, px, py);
}
int PR_findnamedfont(const char *name, qboolean isslotname)
{
	int i;
	if (isslotname)
	{
		for (i = 0; i < FONT_SLOTS; i++)
		{
			if (!stricmp(fontslot[i].slotname, name))
				return i;
		}
	}
	else
	{
		for (i = 0; i < FONT_SLOTS; i++)
		{
			if (!stricmp(fontslot[i].facename, name))
				return i;
		}
	}
	return -1;
}
int PR_findunusedfont(void)
{
	int i;
	//don't find slot 0.
	for (i = FONT_SLOTS; i-- > 1; )
	{
		if (!*fontslot[i].slotname && !*fontslot[i].facename)
			return i;
	}
	return -1;
}
//purgeowner is the bitmask of owners that are getting freed.
//if purgeowner is 0, fonts will get purged
void PR_ReleaseFonts(unsigned int purgeowner)
{
	int i, j;

	for (i = 0; i < FONT_SLOTS; i++)
	{
		if (fontslot[i].owner)
			continue;	//already free
		fontslot[i].owner &= ~purgeowner;
		if (fontslot[i].owner)
			continue;	//still owned by someone

		for (j = 0; j < fontslot[i].sizes; j++)
		{
			if (fontslot[i].font[j])
				Font_Free(fontslot[i].font[j]);
			fontslot[i].font[j] = NULL;
		}

		fontslot[i].sizes = 0;
		fontslot[i].slotname[0] = '\0';
		fontslot[i].facename[0] = '\0';
		fontslot[i].scale = 1;
		fontslot[i].outline = 0;
	}
}
void PR_ReloadFonts(qboolean reload)
{
	int i, j;

	if (qrenderer == QR_NONE)
		reload = false;

	for (i = 0; i < FONT_SLOTS; i++)
	{
		//already not loaded
		if (!fontslot[i].owner)
			continue;

		//flush it (if loaded)
		for (j = 0; j < fontslot[i].sizes; j++)
		{
			if (fontslot[i].font[j])
				Font_Free(fontslot[i].font[j]);
			fontslot[i].font[j] = NULL;
		}
		//and reload if needed
		if (reload)
		{	//otherwise load it.
			for (j = 0; j < fontslot[i].sizes; j++)
			{
				fontslot[i].font[j] = Font_LoadFont(fontslot[i].facename, fontslot[i].size[j], fontslot[i].scale, fontslot[i].outline);
			}
		}
	}
}
void QCBUILTIN PF_CL_findfont (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *slotname = PR_GetStringOfs(prinst, OFS_PARM0);
	G_FLOAT(OFS_RETURN) = PR_findnamedfont(slotname, true) + 1;	//return default on failure.
}
void QCBUILTIN PF_CL_loadfont (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	extern cvar_t r_font_postprocess_outline;
	const char *slotname = PR_GetStringOfs(prinst, OFS_PARM0);
	const char *facename = PR_GetStringOfs(prinst, OFS_PARM1);
	const char *sizestr = PR_GetStringOfs(prinst, OFS_PARM2);
	int slotnum = (prinst->callargc>3)?G_FLOAT(OFS_PARM3):-1;
	//float fix_scale = (prinst->callargc>4)?G_FLOAT(OFS_PARM4):0;
	//float fix_voffset = (prinst->callargc>5)G_FLOAT(OFS_PARM5):0;
	int i, sz;
	world_t *world = prinst->parms->user;

	G_FLOAT(OFS_RETURN) = 0;	//return default on failure.

	if (slotnum < 0 && *slotname)
		slotnum = PR_findnamedfont(slotname, true);
	else if (slotnum < 0)
		slotnum = PR_findnamedfont(facename, false);
	if (slotnum < 0)
		slotnum = PR_findunusedfont();
	if (slotnum < 0)
		return;	//eep.

	if ((unsigned)slotnum >= FONT_SLOTS)
		return;

	//if its changed, purge it.
	if (stricmp(fontslot[slotnum].slotname, slotname) || stricmp(fontslot[slotnum].facename, facename) || !fontslot[slotnum].sizes)
	{
		Q_strncpyz(fontslot[slotnum].slotname, slotname, sizeof(fontslot[slotnum].slotname));
		Q_strncpyz(fontslot[slotnum].facename, facename, sizeof(fontslot[slotnum].facename));
		for (i = 0; i < fontslot[slotnum].sizes; i++)
		{
			if (fontslot[slotnum].font[i])
				Font_Free(fontslot[slotnum].font[i]);
			fontslot[slotnum].font[i] = NULL;
		}
		fontslot[slotnum].sizes = 0;
		fontslot[slotnum].owner = 0;
		fontslot[slotnum].scale = 1;
		fontslot[slotnum].outline = r_font_postprocess_outline.ival;
	}
	fontslot[slotnum].owner |= world->keydestmask;

	while(*sizestr)
	{
		sizestr = COM_Parse(sizestr);
		if (!strncmp(com_token, "scale=", 6))
		{
			fontslot[slotnum].scale = atof(com_token+6);
			continue;
		}
		if (!strncmp(com_token, "outline=", 8))
		{
			fontslot[slotnum].outline = atoi(com_token+8);
			continue;
		}
		if (!strncmp(com_token, "blur=", 5))
		{
			//fontslot[slotnum].blur = atoi(com_token+5);
			continue;
		}
		if (!strncmp(com_token, "voffset=", 8))
		{
			//com_token+8 unused.
			continue;
		}

		sz = atoi(com_token);
		if (!sz)
			continue;	//o.O

		for (i = 0; i < fontslot[slotnum].sizes; i++)
		{
			if (fontslot[slotnum].size[i] == sz)
				break;
		}
		if (i == fontslot[slotnum].sizes)
		{
			if (i >= FONT_SIZES)
				break;
			fontslot[slotnum].size[i] = sz;
			fontslot[slotnum].font[i] = NULL;
			fontslot[slotnum].sizes++;
		}
	}

	if (qrenderer > QR_NONE)
	{
		for (i = 0; i < fontslot[slotnum].sizes; i++)
			fontslot[slotnum].font[i] = Font_LoadFont(facename, fontslot[slotnum].size[i], fontslot[slotnum].scale, fontslot[slotnum].outline);
	}

	G_FLOAT(OFS_RETURN) = slotnum;
}

#ifdef HAVE_LEGACY
void CL_LoadFont_f(void)
{
	extern cvar_t r_font_postprocess_outline;
	//console command for compat with dp/debug.
	if (Cmd_Argc() == 1)
	{
		int i, j;
		int th;
		for (i = 0; i < FONT_SLOTS; i++)
		{
			if (fontslot[i].sizes)
			{
				Con_Printf("%s[%i]: %s (", fontslot[i].slotname, i, fontslot[i].facename);
				for (j = 0; j < fontslot[i].sizes; j++)
				{
					if (j)
						Con_Printf(", ");
					Con_Printf("%i", fontslot[i].size[j]);
					if (fontslot[i].font[j])
					{
						th = Font_GetTrueHeight(fontslot[i].font[j]);
						if (th != Font_CharPHeight(fontslot[i].font[j]))
							Con_Printf("[%g]", ((float)th*vid.height)/vid.pixelheight);
					}
				}
				Con_Printf(")\n");
			}
		}
	}
	else
	{
		int i;
		int slotnum = 0;
		char *slotname = Cmd_Argv(1);
		char *facename = Cmd_Argv(2);
		int sizenum = 3;
		extern cvar_t dpcompat_console, gl_font, con_textfont;

		//loadfont slot face size1 size2...

		slotnum = PR_findnamedfont(slotname, true);
		if (slotnum < 0)
		{
			char *dpnames[] = {"default", "console", "sbar", "notify", "chat", "centerprint", "infobar", "menu", "user0", "user1", "user2", "user3", "user4", "user5", "user6", "user7", NULL};
			for (i = 0; dpnames[i]; i++)
			{
				if (!strcmp(dpnames[i], slotname))
				{
					//assign it to this slot only if this slot does not already have a face. avoids corrupting already-loaded fonts.
					if (!*fontslot[i].facename)
						slotnum = i;
					break;
				}
			}
			if (slotnum < 0)
				slotnum = PR_findnamedfont("", true);	//whatever is still free
		}
		if (slotnum < 0)
		{
			Con_Printf("out of font slots\n");
			return;
		}

		//if there's a new font in this slot, purge the old and change the name+face strings
		if (stricmp(fontslot[slotnum].slotname, slotname) || stricmp(fontslot[slotnum].facename, facename))
		{
			Q_strncpyz(fontslot[slotnum].slotname, slotname, sizeof(fontslot[slotnum].slotname));
			Q_strncpyz(fontslot[slotnum].facename, facename, sizeof(fontslot[slotnum].facename));
			for (i = 0; i < fontslot[slotnum].sizes; i++)
			{
				if (fontslot[slotnum].font[i])
					Font_Free(fontslot[slotnum].font[i]);
				fontslot[slotnum].font[i] = NULL;
			}
			fontslot[slotnum].owner = 0;
			fontslot[slotnum].scale = 1;
			fontslot[slotnum].sizes = 0;
			fontslot[slotnum].outline = r_font_postprocess_outline.ival;	//locked in at definition, so different fonts can have different settings even with vid_reload going on.
		}
		if (!*facename)
			return;
		fontslot[slotnum].owner |= kdm_console;	//fonts owned by the console are never forgotten.
		
		while(sizenum < Cmd_Argc())
		{
			const char *a = Cmd_Argv(sizenum++);
			int sz;
			if (!strcmp(a, "scale"))
			{
				fontslot[slotnum].scale = atof(Cmd_Argv(sizenum++));
				continue;
			}
			if (!strcmp(a, "outline"))
			{
				fontslot[slotnum].outline = atoi(Cmd_Argv(sizenum++));
				continue;
			}
			if (!strcmp(a, "blur"))
			{
				//fontslot[slotnum].blur = atoi(Cmd_Argv(sizenum++));
				sizenum++;
				continue;
			}
			if (!strcmp(a, "voffset"))
			{
//				fontslot[slotnum].voffset = atof(Cmd_Argv(sizenum++));
				sizenum++;
				continue;
			}
			sz = atoi(a);
			if (sz <= 0)
				sz = 8;

			for (i = 0; i < fontslot[slotnum].sizes; i++)
			{
				if (fontslot[slotnum].size[i] == sz)
					break;
			}
			if (i == fontslot[slotnum].sizes)
			{
				if (i >= FONT_SIZES)
					break;
				fontslot[slotnum].size[i] = sz;
				fontslot[slotnum].font[i] = NULL;
				fontslot[slotnum].sizes++;
			}
		}

		if (qrenderer > QR_NONE)
		{
			for (i = 0; i < fontslot[slotnum].sizes; i++)
				fontslot[slotnum].font[i] = Font_LoadFont(facename, fontslot[slotnum].size[i], fontslot[slotnum].scale, fontslot[slotnum].outline);
		}

		//FIXME: slotnum0==default is problematic.
		if (dpcompat_console.ival && slotnum == 1)
			Cvar_Set(&con_textfont, facename);
		if (dpcompat_console.ival && slotnum == 0)
			Cvar_Set(&gl_font, facename);
	}
}
#endif

//scrolling could be done with scissoring.
//selection could be done with some substrings
void QCBUILTIN PF_CL_DrawTextField (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float *size = G_VECTOR(OFS_PARM1);
	unsigned int flags = G_FLOAT(OFS_PARM2);
	const char *text = PR_GetStringOfs(prinst, OFS_PARM3);

	world_t *world = prinst->parms->user;
	vec2_t scale = {8, 8};
	struct font_s *font;
	if (world->g.drawfontscale && (world->g.drawfontscale[0] || world->g.drawfontscale[1]))
	{
		scale[0] *= world->g.drawfontscale[0];
		scale[1] *= world->g.drawfontscale[1];
	}
	font = PR_CL_ChooseFont(world->g.drawfont, scale[0], scale[1]);

	// Oversight ~eukara
	R2D_ImageColours(1.0f, 1.0f, 1.0f, 1.0f);

	G_FLOAT(OFS_RETURN) = R_DrawTextField(pos[0], pos[1], size[0], size[1], text, CON_WHITEMASK, flags, font, scale);
}

//float	drawstring(vector position, string text, vector scale, float alpha, float flag) = #455;
void QCBUILTIN PF_CL_drawcolouredstring (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	const char *text = PR_GetStringOfs(prinst, OFS_PARM1);
	float *size = G_VECTOR(OFS_PARM2);
	float alpha = 0;
	float flag = 0;
	float r, g, b;
	float px, py, ipx;
	unsigned int codeflags, codepoint;

	conchar_t buffer[2048], *str;

	if (prinst->callargc >= 6)
	{
		r = G_FLOAT(OFS_PARM3 + 0);
		g = G_FLOAT(OFS_PARM3 + 1);
		b = G_FLOAT(OFS_PARM3 + 2);
		alpha = G_FLOAT(OFS_PARM4);
		flag = G_FLOAT(OFS_PARM5);	//flag is mandatory to distinguish it.
	}
	else
	{
		r = 1;
		g = 1;
		b = 1;
		alpha = G_FLOAT(OFS_PARM3);
		flag = prinst->callargc >= 5?G_FLOAT(OFS_PARM4):0;
	}

	if (!text)
	{
		G_FLOAT(OFS_RETURN) = -1;	//was null..
		return;
	}

	COM_ParseFunString(CON_WHITEMASK, text, buffer, sizeof(buffer), false);
	str = buffer;

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	PR_CL_BeginString(prinst, pos[0], pos[1], size[0], size[1], &px, &py);
	ipx = px;
	R2D_ImageColours(r, g, b, alpha);
	while(*str)
	{
		str = Font_Decode(str, &codeflags, &codepoint);
		if (codeflags & CON_HIDDEN)
			continue;
		if (codepoint == '\n')
			py += Font_CharHeight();
		else if (codepoint == '\r')
			px = ipx;
		else
			px = Font_DrawScaleChar(px, py, codeflags, codepoint);
	}
	R2D_ImageColours(1,1,1,1);
	Font_EndString(NULL);
	r2d_be_flags = 0;
}

void QCBUILTIN PF_CL_stringwidth(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	conchar_t buffer[2048], *end;
	float px, py;
	const char *text = PR_GetStringOfs(prinst, OFS_PARM0);
	int usecolours = G_FLOAT(OFS_PARM1);
	float *size = (prinst->callargc > 2)?G_VECTOR(OFS_PARM2):NULL;

	if (!qrenderer)
	{
		G_FLOAT(OFS_RETURN) = 0;
		return;
	}

	end = COM_ParseFunString(CON_WHITEMASK, text, buffer, sizeof(buffer), !usecolours);

	PR_CL_BeginString(prinst, 0, 0, size?size[0]:8, size?size[1]:8, &px, &py);
	px = Font_LineScaleWidth(buffer, end);
	Font_EndString(NULL);

	if (!size)	//for compat with dp, divide by 8 after... because weird.
		px /= 8;

	G_FLOAT(OFS_RETURN) = (px * vid.width) / vid.rotpixelwidth;
}

//float	drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) = #456;
void QCBUILTIN PF_CL_drawpic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	const char *picname = PR_GetStringOfs(prinst, OFS_PARM1);
	float *size = G_VECTOR(OFS_PARM2);
	float *rgb = G_VECTOR(OFS_PARM3);
	float alpha = G_FLOAT(OFS_PARM4);
	int flag = prinst->callargc >= 6?(int)G_FLOAT(OFS_PARM5):0;

	mpic_t *p;

	p = R2D_SafeCachePic(picname);
	if (!p || !R_GetShaderSizes(p, NULL, NULL, false))
		p = R2D_SafePicFromWad(picname);

	if (!p)
	{
		if (!CL_IsDownloading(picname))
			p = R2D_SafeCachePic("no_texture");
		G_FLOAT(OFS_RETURN) = 0;
	}
	else
		G_FLOAT(OFS_RETURN) = 1;

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	if ((size[0] < 0) ^ (size[1] < 0))
		R2D_Image(pos[0]+size[0], pos[1]+size[1], -size[0], -size[1], 1, 1, 0, 0, p);
	else
		R2D_Image(pos[0], pos[1], size[0], size[1], 0, 0, 1, 1, p);
	r2d_be_flags = 0;
}

void QCBUILTIN PF_CL_drawrotpic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pivot = G_VECTOR(OFS_PARM0);
	float *mins = G_VECTOR(OFS_PARM1);
	float *maxs = G_VECTOR(OFS_PARM2);
	const char *picname = PR_GetStringOfs(prinst, OFS_PARM3);
	float *rgb = G_VECTOR(OFS_PARM4);
	float alpha = G_FLOAT(OFS_PARM5);
	float angle = (G_FLOAT(OFS_PARM6) * M_PI)/180;
	int flag = prinst->callargc >= 8?(int) G_FLOAT(OFS_PARM7):0;

	vec2_t points[4];
	vec2_t tcoords[4];
	vec2_t saxis;
	vec2_t taxis;

	mpic_t *p;

	p = R2D_SafeCachePic(picname);
	if (!p)
		p = R2D_SafePicFromWad(picname);

	saxis[0] = cos(angle);
	saxis[1] = sin(angle);
	taxis[0] = -sin(angle);
	taxis[1] = cos(angle);

	Vector2MA(pivot, mins[0], saxis, points[0]); Vector2MA(points[0], mins[1], taxis, points[0]);
	Vector2MA(pivot, maxs[0], saxis, points[1]); Vector2MA(points[1], mins[1], taxis, points[1]);
	Vector2MA(pivot, maxs[0], saxis, points[2]); Vector2MA(points[2], maxs[1], taxis, points[2]);
	Vector2MA(pivot, mins[0], saxis, points[3]); Vector2MA(points[3], maxs[1], taxis, points[3]);

	Vector2Set(tcoords[0], 0, 0);
	Vector2Set(tcoords[1], 1, 0);
	Vector2Set(tcoords[2], 1, 1);
	Vector2Set(tcoords[3], 0, 1);

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	R2D_Image2dQuad((const vec2_t*)points, (const vec2_t*)tcoords, NULL, p);
	r2d_be_flags = 0;

	G_FLOAT(OFS_RETURN) = 1;
}

void QCBUILTIN PF_CL_drawsubpic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float *size = G_VECTOR(OFS_PARM1);
	const char *picname = PR_GetStringOfs(prinst, OFS_PARM2);
	float *srcPos = G_VECTOR(OFS_PARM3);
	float *srcSize = G_VECTOR(OFS_PARM4);
	float *rgb = G_VECTOR(OFS_PARM5);
	float alpha = G_FLOAT(OFS_PARM6);
	int flag = prinst->callargc >= 8?(int) G_FLOAT(OFS_PARM7):0;

	mpic_t *p;

	p = R2D_SafeCachePic(picname);
	if (!p || !R_GetShaderSizes(p, NULL, NULL, false))
		p = R2D_SafePicFromWad(picname);

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	if ((size[0] < 0) ^ (size[1] < 0))
		R2D_Image(pos[0]+size[0], pos[1]+size[1], -size[0], -size[1], srcPos[0]+srcSize[0], srcPos[1]+srcSize[1], srcPos[0], srcPos[1], p);
	else
		R2D_Image(pos[0], pos[1], size[0], size[1], srcPos[0], srcPos[1], srcPos[0]+srcSize[0], srcPos[1]+srcSize[1], p);
	r2d_be_flags = 0;

	G_FLOAT(OFS_RETURN) = 1;
}
void QCBUILTIN PF_CL_drawrotsubpic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pivot = G_VECTOR(OFS_PARM0);
	float *mins = G_VECTOR(OFS_PARM1);
	float *maxs = G_VECTOR(OFS_PARM2);
	const char *picname = PR_GetStringOfs(prinst, OFS_PARM3);
	float *srcPos = G_VECTOR(OFS_PARM4);
	float *srcSize = G_VECTOR(OFS_PARM5);
	float *rgb = G_VECTOR(OFS_PARM6);
	float alpha = G_FLOAT(OFS_PARM7+0);
	float angle = (G_FLOAT(OFS_PARM7+1) * M_PI) / 180;
	int flag = prinst->callargc >= 8?(int) G_FLOAT(OFS_PARM7+2):0;
	vec2_t points[4], tcoords[4];
	vec2_t saxis;
	vec2_t taxis;

	mpic_t *p;

	saxis[0] = cos(angle);
	saxis[1] = sin(angle);
	taxis[0] = -sin(angle);
	taxis[1] = cos(angle);

	p = R2D_SafeCachePic(picname);
	if (!p)
		p = R2D_SafePicFromWad(picname);

	Vector2MA(pivot, mins[0], saxis, points[0]); Vector2MA(points[0], mins[1], taxis, points[0]);
	Vector2MA(pivot, maxs[0], saxis, points[1]); Vector2MA(points[1], mins[1], taxis, points[1]);
	Vector2MA(pivot, maxs[0], saxis, points[2]); Vector2MA(points[2], maxs[1], taxis, points[2]);
	Vector2MA(pivot, mins[0], saxis, points[3]); Vector2MA(points[3], maxs[1], taxis, points[3]);

	Vector2Set(tcoords[0], srcPos[0]			, srcPos[1]				);
	Vector2Set(tcoords[1], srcPos[0]+srcSize[0]	, srcPos[1]				);
	Vector2Set(tcoords[2], srcPos[0]+srcSize[0]	, srcPos[1]+srcSize[1]	);
	Vector2Set(tcoords[3], srcPos[0]			, srcPos[1]+srcSize[1]	);

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	R2D_Image2dQuad((const vec2_t*)points, (const vec2_t*)tcoords, NULL, p);
	r2d_be_flags = 0;

	G_FLOAT(OFS_RETURN) = 1;
}

#ifdef HAVE_LEGACY
/*fuck sake, why does no one give a shit about existing extension?!? seriously this stuff is pissing me off*/
void QCBUILTIN PF_CL_drawrotpic_dp (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pivot = G_VECTOR(OFS_PARM0);
	const char *picname = PR_GetStringOfs(prinst, OFS_PARM1);
	float *size = G_VECTOR(OFS_PARM2);
	float *mins = G_VECTOR(OFS_PARM3);
	float angle = (G_FLOAT(OFS_PARM4) * M_PI)/180;
	float *rgb = G_VECTOR(OFS_PARM5);
	float alpha = G_FLOAT(OFS_PARM6);
	int flag = prinst->callargc >= 8?(int) G_FLOAT(OFS_PARM7):0;

	vec3_t maxs;

	vec2_t points[4];
	vec2_t tcoords[4];
	vec2_t saxis;
	vec2_t taxis;

	mpic_t *p;

	VectorSubtract(size, mins, maxs);

	p = R2D_SafeCachePic(picname);
	if (!p)
		p = R2D_SafePicFromWad(picname);

	saxis[0] = cos(angle);
	saxis[1] = sin(angle);
	taxis[0] = -sin(angle);
	taxis[1] = cos(angle);

	Vector2MA(pivot, mins[0], saxis, points[0]); Vector2MA(points[0], mins[1], taxis, points[0]);
	Vector2MA(pivot, maxs[0], saxis, points[1]); Vector2MA(points[1], mins[1], taxis, points[1]);
	Vector2MA(pivot, maxs[0], saxis, points[2]); Vector2MA(points[2], maxs[1], taxis, points[2]);
	Vector2MA(pivot, mins[0], saxis, points[3]); Vector2MA(points[3], maxs[1], taxis, points[3]);

	Vector2Set(tcoords[0], 0, 0);
	Vector2Set(tcoords[1], 1, 0);
	Vector2Set(tcoords[2], 1, 1);
	Vector2Set(tcoords[3], 0, 1);

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	R2D_Image2dQuad((const vec2_t*)points, (const vec2_t*)tcoords, NULL, p);
	r2d_be_flags = 0;

	G_FLOAT(OFS_RETURN) = 1;
}
#endif


void QCBUILTIN PF_CL_is_cached_pic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*str;
	str = PR_GetStringOfs(prinst, OFS_PARM0);
	G_FLOAT(OFS_RETURN) = !!R_RegisterCustom(NULL, str, SUF_2D, NULL, NULL);
}

void QCBUILTIN PF_CL_precache_pic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	extern cvar_t pr_precachepic_slow;
	const char	*str;
	mpic_t	*pic;
	unsigned int flags;
#define PIC_FROMWAD 1				/*obsolete, probably better to just use gfx/foo.lmp instead*/
//#define PIC_TEMPORARY 2			/*DP, not meaningful here*/
//#define PIC_NOCLAMP 4				/*DP, not useful here - use a shader instead*/
//#define PIC_MIPMAP 8				/*DP, not useful here - use a shader instead*/
#define PRECACHE_PIC_DOWNLOAD 256	/*block until loaded, downloading if missing*/
#define PRECACHE_PIC_TEST 512		/*block until loaded*/

	G_INT(OFS_RETURN) = G_INT(OFS_PARM0);
	str = PR_GetStringOfs(prinst, OFS_PARM0);
	if (prinst->callargc > 1)
		flags = G_FLOAT(OFS_PARM1);
	else
		flags = 0;

	if (pr_precachepic_slow.ival)
		flags |= PRECACHE_PIC_DOWNLOAD|PRECACHE_PIC_TEST;

	if (flags & PIC_FROMWAD)
		pic = R2D_SafePicFromWad(str);
	else
	{
		pic = R2D_SafeCachePic(str);

		if ((flags & PRECACHE_PIC_DOWNLOAD) && cls.state	//if we're allowed to download it...
			 && strchr(str, '.')	//only try to download it if it looks as though it contains a path.
#ifndef CLIENTONLY
			&& !sv.active			//not if we're already the server...
#endif
			&& (!pic || !R_GetShaderSizes(pic, NULL, NULL, true)))	//and it wasn't loaded...
			CL_CheckOrEnqueDownloadFile(str, str, 0);
	}

	if (flags & PRECACHE_PIC_TEST)
		if (!pic || !R_GetShaderSizes(pic, NULL, NULL, true))
			G_INT(OFS_RETURN) = 0;
}

#ifdef CSQC_DAT
//warning: not threaded.
void QCBUILTIN PF_CL_uploadimage (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *imagename = PR_GetStringOfs(prinst, OFS_PARM0);
	int width = G_INT(OFS_PARM1);
	int height = G_INT(OFS_PARM2);
	int src = G_INT(OFS_PARM3);	//ptr
	int size = (prinst->callargc > 4)?G_INT(OFS_PARM4):(width * height * 4);
	uploadfmt_t format = (prinst->callargc > 5)?PR_TranslateTextureFormat(G_INT(OFS_PARM5)):TF_RGBA32;
	void *imgptr;
	texid_t tid;

	G_INT(OFS_RETURN) = 0;	//assume the worst

	if (width < 0 || height < 0 || width > 16384 || height > 16384)
	{	//this is actually kinda likely when everyone assumes everything is a float.
		PR_BIError(prinst, "PF_CL_uploadimage: dimensions are out of range\n");
		return;
	}
	//FIXME: this should use a proper qclib function to validate more reliably / reusably
	if (src <= 0 || src+size >= prinst->stringtablesize)
	{
		PR_BIError(prinst, "PF_CL_uploadimage: invalid source\n");
		return;
	}
	imgptr = prinst->stringtable + src;


	tid = Image_FindTexture(imagename, NULL, RT_IMAGEFLAGS);
	if (!TEXVALID(tid))
		tid = Image_CreateTexture(imagename, NULL, RT_IMAGEFLAGS);

	if (!format)
	{
		void *data = BZ_Malloc(size);
		memcpy(data, imgptr, size);
		G_INT(OFS_RETURN) = Image_LoadTextureFromMemory(tid, tid->flags, tid->ident, imagename, data, size);
	}
	else
	{
		unsigned int blockbytes, blockwidth, blockheight, blockdepth;
		//get format info
		Image_BlockSizeForEncoding(format, &blockbytes, &blockwidth, &blockheight, &blockdepth);
		//round up as appropriate
		blockwidth = ((width+blockwidth-1)/blockwidth)*blockwidth;
		blockheight = ((height+blockheight-1)/blockheight)*blockheight;
		if (size != blockwidth*blockheight*blockbytes)
			G_INT(OFS_RETURN) = 0;	//size isn't right. which means the pointer might be invalid too.
		else
		{
			Image_Upload(tid, format, imgptr, NULL, width, height, 1, RT_IMAGEFLAGS);
			tid->width = width;
			tid->height = height;
			G_INT(OFS_RETURN) = 1;
		}
	}
}
#endif

//warning: not threadable. hopefully noone abuses it.
void QCBUILTIN PF_CL_readimage (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	size_t filesize;
	const char *filename = PR_GetStringOfs(prinst, OFS_PARM0);

	int imagewidth, imageheight;
	uploadfmt_t format;
	void *filedata;

	G_INT(OFS_RETURN) = 0;	//assume the worst
	G_INT(OFS_PARM1) = 0;	//out width
	G_INT(OFS_PARM2) = 0;	//out height

	filedata = FS_LoadMallocFile(filename, &filesize);

	if (filedata)
	{
		qbyte *imagedata = ReadRawImageFile(filedata, filesize, &imagewidth, &imageheight, &format, true, filename);
		Z_Free(filedata);

		if (imagedata)
		{
			void *ptr = prinst->AddressableAlloc(prinst, imagewidth*imageheight*4);
			if (ptr)
			{
				memcpy(ptr, imagedata, imagewidth*imageheight*4);
				G_INT(OFS_RETURN) = (char*)ptr - prinst->stringtable;
				G_INT(OFS_PARM1) = imagewidth;	//out width
				G_INT(OFS_PARM2) = imageheight;	//out height
				G_INT(OFS_PARM3) = PR_UnTranslateTextureFormat(format);	//out format...
			}
			BZ_Free(imagedata);
		}
	}
}

void QCBUILTIN PF_CL_free_pic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//we don't support this, as the shader could be used elsewhere also, and we have pointers to things.
/*
	char	*str;
	str = PR_GetStringOfs(prinst, OFS_PARM0);
	R_UnloadShader(R_RegisterCustom(str, NULL, NULL));
*/
}

//float	drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) = #454;
void QCBUILTIN PF_CL_drawcharacter (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	int chara = G_FLOAT(OFS_PARM1);
	float *size = G_VECTOR(OFS_PARM2);
	float *rgb = G_VECTOR(OFS_PARM3);
	float alpha = G_FLOAT(OFS_PARM4);
	int flag = prinst->callargc >= 6?G_FLOAT(OFS_PARM5):0;

	float x, y;

	if (!chara)
	{
		G_FLOAT(OFS_RETURN) = -1;	//was null..
		return;
	}

	//no control chars. use quake ones if so
	if (!(flag & 4) && !com_parseutf8.ival)
	{
		//ugly quake chars...
		if (chara >= 32 && chara < 128)
			;	//ascii-comptaible range
		else
			chara |= 0xe000;	//use quake glyphs (including for red text, unfortunately)
	}

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	PR_CL_BeginString(prinst, pos[0], pos[1], size[0], size[1], &x, &y);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	Font_DrawScaleChar(x, y, CON_WHITEMASK, chara);
	R2D_ImageColours(1,1,1,1);
	Font_EndString(NULL);
	r2d_be_flags = 0;

	G_FLOAT(OFS_RETURN) = 1;
}

//float	drawrawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #455;
void QCBUILTIN PF_CL_drawrawstring (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{	
	float *pos = G_VECTOR(OFS_PARM0);
	const char *text = PR_GetStringOfs(prinst, OFS_PARM1);
	float *size = G_VECTOR(OFS_PARM2);
	float *rgb = G_VECTOR(OFS_PARM3);
	float alpha = G_FLOAT(OFS_PARM4);
	int flag = prinst->callargc >= 6?G_FLOAT(OFS_PARM5):0;
	float x, y;
	unsigned int c;
	int error;

	if (!text)
	{
		G_FLOAT(OFS_RETURN) = -1;	//was null..
		return;
	}

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flag);
	PR_CL_BeginString(prinst, pos[0], pos[1], size[0], size[1], &x, &y);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);

	while(*text)
	{
		if (1)//VMUTF8)
			c = unicode_decode(&error, text, &text, false);
		else
		{
			//FIXME: which charset is this meant to be using?
			//quakes? 8859-1? utf8? some weird hacky mixture?
			c = *text++&0xff;
			if ((c&0x7f) < 32)
				c |= 0xe000;	//if its a control char, just use the quake range instead.
			else if (c & 0x80)
				c |= 0xe000;	//if its a high char, just use the quake range instead. we could colour it, but why bother
		}
		x = Font_DrawScaleChar(x, y, CON_WHITEMASK, c);
	}
	R2D_ImageColours(1,1,1,1);
	Font_EndString(NULL);
	r2d_be_flags = 0;
}

//void (float width, vector pos1, vector pos2, vector rgb, float alpha, optional float flags) drawline;
void QCBUILTIN PF_CL_drawline (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//float width = G_FLOAT(OFS_PARM0);
	float *point1	= G_VECTOR(OFS_PARM1);
	float *point2	= G_VECTOR(OFS_PARM2);
	float *rgb	= G_VECTOR(OFS_PARM3);
	float alpha = G_FLOAT(OFS_PARM4);
	int flags	= prinst->callargc >= 6?G_FLOAT(OFS_PARM5):0;
	shader_t *shader_draw_line;

	//this shader lookup might get pricy.
	shader_draw_line = R_RegisterShader("shader_draw_line", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"map $whiteimage\n"
				"rgbgen exactvertex\n"
				"alphagen vertex\n"
				"blendfunc blend\n"
			"}\n"
		"}\n");

	r2d_be_flags = PF_SelectDPDrawFlag(prinst, flags);
	R2D_ImageColours(rgb[0], rgb[1], rgb[2], alpha);
	R2D_Line(point1[0], point1[1], point2[0], point2[1], shader_draw_line);
	R2D_ImageColours(1,1,1,1);
	r2d_be_flags = 0;
}

//vector  drawgetimagesize(string pic) = #460;
void QCBUILTIN PF_CL_drawgetimagesize (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *picname = PR_GetStringOfs(prinst, OFS_PARM0);
	mpic_t *p = R2D_SafeCachePic(picname);

	float *ret = G_VECTOR(OFS_RETURN);
	int iw, ih;
	
	if (R_GetShaderSizes(p, &iw, &ih, true) > 0)
	{
		ret[0] = iw;
		ret[1] = ih;
		ret[2] = 0;
	}
	else
	{
		ret[0] = 0;
		ret[1] = 0;
		ret[2] = 0;
	}
}

//vector	getmousepos(void)  	= #66;
void QCBUILTIN PF_cl_getmousepos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *ret = G_VECTOR(OFS_RETURN);
	world_t *world = prinst->parms->user;
	unsigned int target = world->keydestmask;

	if (key_dest_absolutemouse & target)
	{
		ret[0] = mousecursor_x;
		ret[1] = mousecursor_y;
	}
	else
	{
		ret[0] = mousemove_x;
		ret[1] = mousemove_y;
	}

	mousemove_x=0;
	mousemove_y=0;

//	extern int mousecursor_x, mousecursor_y;
//	ret[0] = mousecursor_x;
//	ret[1] = mousecursor_y;
	ret[2] = 0;
}


void QCBUILTIN PF_SubConGetSet (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *conname = PR_GetStringOfs(prinst, OFS_PARM0);
	const char *field = PR_GetStringOfs(prinst, OFS_PARM1);
	const char *value = (prinst->callargc>2)?PR_GetStringOfs(prinst, OFS_PARM2):NULL;
	console_t *con = Con_FindConsole(conname);
	G_INT(OFS_RETURN) = 0;
	if (!con)
	{
		//null if it doesn't exist
		return;
	}
	if (!strcmp(field, "title"))
	{
		RETURN_TSTRING(con->title);
		if (value)
			Q_strncpyz(con->title, value, sizeof(con->title));
	}
	else if (!strcmp(field, "name"))
	{
		RETURN_TSTRING(con->name);
		if (value && *value && *con->name)
			Q_strncpyz(con->name, value, sizeof(con->name));
	}
	else if (!strcmp(field, "next"))
	{
		con = con->next;
		if (con)
			RETURN_TSTRING(con->name);
	}
	else if (!strcmp(field, "unseen"))
	{
		RETURN_TSTRING(va("%i", con->unseentext));
		if (value)
			con->unseentext = atoi(value);
	}
	else if (!strcmp(field, "markup"))
	{
		int cur;
		if (con->parseflags & PFS_NOMARKUP)
			cur = 0;
		else if (con->parseflags & PFS_KEEPMARKUP)
			cur = 2;
		else
			cur = 1;
		RETURN_TSTRING(va("%i", cur));
		if (value)
		{
			cur = atoi(value);
			con->parseflags &= ~(PFS_NOMARKUP|PFS_KEEPMARKUP);
			if (cur == 0)
				con->parseflags |= PFS_NOMARKUP;
			else if (cur == 2)
				con->parseflags |= PFS_KEEPMARKUP;
		}
	}
	else if (!strcmp(field, "forceutf8"))
	{
		RETURN_TSTRING((con->parseflags&PFS_FORCEUTF8)?"1":"0");
		if (value)
		{
			con->parseflags &= ~PFS_FORCEUTF8;
			if (atoi(value))
				con->parseflags |= PFS_FORCEUTF8;
		}
	}
	else if (!strcmp(field, "close"))
	{
		RETURN_TSTRING("0");	//meant to return the old state...
		if (value && atoi(value))
		{
			if (con->close && atoi(value) != 2 && !con->close(con, true))
				return;
			Con_Destroy(con);
		}
	}
	else if (!strcmp(field, "clear"))
	{
		RETURN_TSTRING(con->linecount?"0":"1");
		if (value && atoi(value))
			Con_ClearCon(con);
	}
	else if (!strcmp(field, "hidden"))
	{
		RETURN_TSTRING((con->flags & CONF_HIDDEN)?"1":"0");
		if (value)
			con->flags = (con->flags & ~CONF_HIDDEN) | (atoi(value)?CONF_HIDDEN:0);
	}
	else if (!strcmp(field, "linecount"))
	{
		RETURN_TSTRING(va("%i", con->linecount));
		if (value)
			con->unseentext = atoi(value);
	}
	else if (!strcmp(field, "backimage"))
	{
		RETURN_TSTRING(con->backshader?con->backshader->name:con->backimage);
		if (value)
		{
			Q_strncpyz(con->backimage, value, sizeof(con->backimage));
			if (con->backshader)
				R_UnloadShader(con->backshader);
		}
	}
	else if (!strcmp(field, "backvideomap"))
	{
		RETURN_TSTRING(con->backshader?con->backshader->name:con->backimage);
		if (value)
		{
			Q_strncpyz(con->backimage, "", sizeof(con->backimage));
			if (con->backshader)
				R_UnloadShader(con->backshader);
			con->backshader = R_RegisterCustom(NULL, va("consolevid_%s", con->name), SUF_NONE, Shader_DefaultCinematic, value);
		}
	}
}
void QCBUILTIN PF_SubConPrintf (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	char outbuf[4096];
	const char *conname = PR_GetStringOfs(prinst, OFS_PARM0);
	const char *fmt = PR_GetStringOfs(prinst, OFS_PARM1);
	console_t *con = Con_FindConsole(conname);
	if (!con)
	{
		con = Con_Create(conname, 0);
		if (!con)
			return;
	}
	PF_sprintf_internal(prinst, pr_globals, fmt, 2, outbuf, sizeof(outbuf));
	Con_PrintCon(con, outbuf, con->parseflags);
}
void QCBUILTIN PF_SubConDraw (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *conname = PR_GetStringOfs(prinst, OFS_PARM0);
	float *pos = G_VECTOR(OFS_PARM1);
	float *size = G_VECTOR(OFS_PARM2);
	float fontsize = G_FLOAT(OFS_PARM3);
	console_t *con = Con_FindConsole(conname);
	world_t *world = prinst->parms->user;
	if (!con)
		return;

	if (world->g.drawfontscale)
	{
//		szx *= world->g.drawfontscale[0];
		fontsize *= world->g.drawfontscale[1];
	}

	Con_DrawOneConsole(con, con->flags & CONF_KEYFOCUSED, PR_CL_ChooseFont(world->g.drawfont, fontsize, fontsize), pos[0], pos[1], size[0], size[1], 0);
}
void QCBUILTIN PF_SubConInput (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *conname = PR_GetStringOfs(prinst, OFS_PARM0);
	int ie = G_FLOAT(OFS_PARM1);
	float pa = G_FLOAT(OFS_PARM2);
	float pb = G_FLOAT(OFS_PARM3);
//	float pc = G_FLOAT(OFS_PARM4);
	console_t *con = Con_FindConsole(conname);
	G_FLOAT(OFS_RETURN) = 0;
	if (!con)
		return;
	switch(ie)
	{
	case CSIE_KEYDOWN:
		//scan, char
		if ((pa && qcinput_scan != pa) || (pb && pb != qcinput_unicode))
			G_FLOAT(OFS_RETURN) = 0;
		else
			G_FLOAT(OFS_RETURN) = Key_Console(con, MP_TranslateQCtoFTECodes(pa), pb);
		break;
	case CSIE_KEYUP:
		//scan, char
		Key_ConsoleRelease(con, MP_TranslateQCtoFTECodes(pa), pb);
		G_FLOAT(OFS_RETURN) = 0;	//does not inhibit
		break;
	case CSIE_MOUSEABS:
		//x, y
		if (con == con_current && (key_dest_mask & kdm_console))
			break;	//no interfering with the main console!
		con->mousecursor[0] = pa;
		con->mousecursor[1] = pb;
		G_FLOAT(OFS_RETURN) = true;
		break;
	case CSIE_FOCUS:
		//mouse, key
		if (pb >= 0)
		{
			con->flags = (con->flags & ~CONF_KEYFOCUSED) | (pb?CONF_KEYFOCUSED:0);
			G_FLOAT(OFS_RETURN) = true;
		}
		break;
	}
}
#endif







#ifdef MENU_DAT

typedef struct menuedict_s
{
	enum ereftype_e	ereftype;
	float			freetime; // sv.time when the object was freed
	int				entnum;
	unsigned int	fieldsize;
	pbool			readonly;	//world

	void			*fields;
} menuedict_t;



static struct
{
	evalc_t chain;
	evalc_t model;
	evalc_t mins;
	evalc_t maxs;
	evalc_t origin;
	evalc_t angles;
	evalc_t skin;
	evalc_t colormap;
	evalc_t frame1;
	evalc_t frame2;
	evalc_t lerpfrac;
	evalc_t frame1time;
	evalc_t frame2time;
	evalc_t renderflags;
	evalc_t skinobject;
	evalc_t colourmod;
	evalc_t alpha;
} menuc_eval;
static playerview_t menuview;


static menu_t menuqc;	//this is how the client forwards events etc.
static int inmenuprogs;
static progparms_t menuprogparms;
static menuedict_t *menu_edicts;
static int num_menu_edicts;
world_t menu_world;
static int menuentsize;
double  menutime;
static struct
{
	func_t init;
	func_t shutdown;
	func_t draw;	qboolean fuckeddrawsizes;
	func_t drawloading;
	func_t keydown;
	func_t keyup;
	func_t inputevent;
	func_t toggle;
	func_t consolecommand;
	func_t gethostcachecategory;
	func_t rendererrestarted;
} mpfuncs;
jmp_buf mp_abort;


// cvars
#define MENUPROGSGROUP "Menu progs control"
cvar_t forceqmenu = CVAR("forceqmenu", "0");
cvar_t pr_menu_coreonerror = CVAR("pr_menu_coreonerror", "1");
cvar_t pr_menu_memsize = CVAR("pr_menu_memsize", "64m");


//new generic functions.

const char *RemapCvarNameFromDPToFTE(const char *name)
{
	if (!stricmp(name, "vid_bitsperpixel"))
		return "vid_bpp";
	if (!stricmp(name, "_cl_playermodel"))
		return "model";
	if (!stricmp(name, "_cl_playerskin"))
		return "skin";
	if (!stricmp(name, "_cl_color"))
		return "topcolor";
	if (!stricmp(name, "_cl_name"))
		return "name";

	if (!stricmp(name, "v_contrast"))
		return "v_contrast";
	if (!stricmp(name, "v_hwgamma"))
		return "vid_hardwaregamma";
	if (!stricmp(name, "showfps"))
		return "show_fps";
	if (!stricmp(name, "sv_progs"))
		return "progs";

	return name;
}

static void QCBUILTIN PF_menu_cvar (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	cvar_t	*var;
	const char	*str;

	str = PR_GetStringOfs(prinst, OFS_PARM0);

	if (!strcmp(str, "vid_conwidth"))
		G_FLOAT(OFS_RETURN) = vid.width;
	else if (!strcmp(str, "vid_conheight"))
		G_FLOAT(OFS_RETURN) = vid.height;
	else if (!strcmp(str, "vid_pixwidth"))
		G_FLOAT(OFS_RETURN) = vid.pixelwidth;
	else if (!strcmp(str, "vid_pixheight"))
		G_FLOAT(OFS_RETURN) = vid.pixelheight;
	else
	{
		str = RemapCvarNameFromDPToFTE(str);
		var = PF_Cvar_FindOrGet(str);
		if (var && !(var->flags & CVAR_NOUNSAFEEXPAND))
		{
			//menuqc sees desired settings, not latched settings.
			if (var->latched_string)
				G_FLOAT(OFS_RETURN) = atof(var->latched_string);
			else
				G_FLOAT(OFS_RETURN) = var->value;
		}
		else
			G_FLOAT(OFS_RETURN) = 0;
	}
}
static void QCBUILTIN PF_menu_cvar_set (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*var_name, *val;
	cvar_t *var;

	var_name = PR_GetStringOfs(prinst, OFS_PARM0);
	var_name = RemapCvarNameFromDPToFTE(var_name);
	val = PR_GetStringOfs(prinst, OFS_PARM1);

	var = PF_Cvar_FindOrGet(var_name);
	if (var && var->flags & CVAR_NOTFROMSERVER)
	{
		//fixme: menuqc needs some way to display a prompt to allow it anyway.
		return;
	}
	Cvar_Set (var, val);
}
static void QCBUILTIN PF_menu_cvar_string (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*str = PR_GetStringOfs(prinst, OFS_PARM0);
	cvar_t *cv = PF_Cvar_FindOrGet(RemapCvarNameFromDPToFTE(str));
	if (!cv)
		G_INT(OFS_RETURN) = 0;
	else if (cv->flags & CVAR_NOUNSAFEEXPAND)
		G_INT(OFS_RETURN) = 0;
	else if (cv->latched_string)
		G_INT(OFS_RETURN) = (int)PR_TempString(prinst, cv->latched_string);
	else
		G_INT(OFS_RETURN) = (int)PR_TempString(prinst, cv->string);
}




void QCBUILTIN PF_nonfatalobjerror (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*s;
	struct edict_s	*ed;
	eval_t *selfp;

	s = PF_VarString(prinst, 0, pr_globals);

	PR_StackTrace(prinst, true);

	selfp = PR_FindGlobal(prinst, "self", PR_CURRENT, NULL);
	if (selfp && selfp->_int)
	{
		ed = PROG_TO_EDICT(prinst, selfp->_int);

		PR_PrintEdict(prinst, ed);


		if (developer.value)
		{	//enable tracing.
			PR_RunWarning(prinst, "======OBJECT ERROR======\n%s\n", s);
			return;
		}
		else
		{
			ED_Free (prinst, ed);
		}
	}

	Con_Printf ("======OBJECT ERROR======\n%s\n", s);
}






//float	isserver(void)  = #60;
void QCBUILTIN PF_isserver (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
#ifdef CLIENTONLY
	G_FLOAT(OFS_RETURN) = false;
#else
	if (sv.state == ss_dead)
		G_FLOAT(OFS_RETURN) = false;
	else if (sv.allocated_client_slots == 1)
		G_FLOAT(OFS_RETURN) = 0.5;
	else
		G_FLOAT(OFS_RETURN) = true;
#endif
}
void QCBUILTIN PF_isdemo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	G_FLOAT(OFS_RETURN) = !!cls.demoplayback;
}

//float	clientstate(void)  = #62;
void QCBUILTIN PF_clientstate (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//menuqc was originally implemented in DP, so these return values follow NQ norms.
	if (isDedicated)	//unreachable
		G_FLOAT(OFS_RETURN) = 0/*nq ca_dedicated*/;
	else if (	cls.state >= ca_connected	//we're on a server
			||	CL_TryingToConnect()		//or we're trying to connect (avoids bugs with certain menuqc mods)
#ifndef CLIENTONLY
			||	sv.state>=ss_loading
#endif
				)	//or we're going to connect to ourselves once we get our act together
		G_FLOAT(OFS_RETURN) = 2/*nq ca_connected*/;
	else
		G_FLOAT(OFS_RETURN) = 1/*nq ca_disconnected*/;
}

//too specific to the prinst's builtins.
static void QCBUILTIN PF_Fixme (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int binum;
	char fname[MAX_QPATH];
	if (!prinst->GetBuiltinCallInfo(prinst, &binum, fname, sizeof(fname)))
	{
		binum = 0;
		strcpy(fname, "?unknown?");
	}

	Con_Printf("\n");
	prinst->RunError(prinst, "\nBuiltin %i:%s not implemented.\nMenu is not compatible.", binum, fname);
	PR_BIError (prinst, "bulitin not implemented");
}
static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	func_t funcref = G_INT(OFS_PARM0);
	char *funcname = NULL;
	int args;
	int builtinno;
	if (prinst->GetFunctionInfo(prinst, funcref, &args, NULL, &builtinno, funcname, sizeof(funcname)))
	{	//qc defines the function at least. nothing weird there...
		if (builtinno > 0 && builtinno < prinst->parms->numglobalbuiltins)
		{
			if (!prinst->parms->globalbuiltins[builtinno] || prinst->parms->globalbuiltins[builtinno] == PF_Fixme)
				G_FLOAT(OFS_RETURN) = false;	//the builtin with that number isn't defined.
			else
			{
				G_FLOAT(OFS_RETURN) = true;		//its defined, within the sane range, mapped, everything. all looks good.
				//we should probably go through the available builtins and validate that the qc's name matches what would be expected
				//this is really intended more for builtins defined as #0 though, in such cases, mismatched assumptions are impossible.
			}
		}
		else
			G_FLOAT(OFS_RETURN) = false;	//not a valid builtin (#0 builtins get remapped according to the function name)
	}
	else
	{	//not valid somehow.
		G_FLOAT(OFS_RETURN) = false;
	}
}



void QCBUILTIN PF_CL_precache_sound (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*str;

	str = PR_GetStringOfs(prinst, OFS_PARM0);

	if (S_PrecacheSound(str))
		G_INT(OFS_RETURN) = G_INT(OFS_PARM0);
	else
		G_INT(OFS_RETURN) = 0;
}

//void	setkeydest(float dest) 	= #601;
void QCBUILTIN PF_cl_setkeydest (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//these arguments are stupid
	switch((int)G_FLOAT(OFS_PARM0))
	{
	case 0:
		// key_game
		if (Key_Dest_Has(kdm_menu))
		{
			Menu_Unlink(&menuqc, false);
			Key_Dest_Remove(kdm_menu);
//			Key_Dest_Remove(kdm_message);
//			if (cls.state == ca_disconnected)
//				Key_Dest_Add(kdm_console);
		}
		break;
	case 2:
		// key_menu
		Key_Dest_Remove(kdm_message);
		if (!Key_Dest_Has(kdm_menu))
			Key_Dest_Remove(kdm_console);
		Menu_Push(&menuqc, false);
		break;
	case 1:
		// key_message
		//Key_Dest_Remove(kdm_menu);
		//Key_Dest_Add(kdm_message);
		// break;
	default:
		PR_BIError (prinst, "PF_setkeydest: wrong destination %i !\n",(int)G_FLOAT(OFS_PARM0));
	}
}
//float	getkeydest(void)	= #602;
void QCBUILTIN PF_cl_getkeydest (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (Key_Dest_Has(kdm_menu))
		G_FLOAT(OFS_RETURN) = 2;
//	else if (Key_Dest_Has(kdm_message))
//		G_FLOAT(OFS_RETURN) = 1;
	else
		G_FLOAT(OFS_RETURN) = 0;
}

static void QCBUILTIN PF_Remove_ (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *ed;

	ed = (void*)G_EDICT(prinst, OFS_PARM0);

	if (ed->ereftype == ER_FREE)
	{
		Con_DPrintf("Tried removing free entity\n");
		PR_StackTrace(prinst, false);
		return;
	}

	ED_Free (prinst, (void*)ed);
}
static void QCBUILTIN PF_RemoveInstant (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *ed;

	ed = (void*)G_EDICT(prinst, OFS_PARM0);

	if (ed->ereftype == ER_FREE)
	{
		Con_DPrintf("Tried removing free entity\n");
		PR_StackTrace(prinst, false);
		return;
	}

	prinst->EntFree(prinst, (void*)ed, true);
}

static void QCBUILTIN PF_CopyEntity (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *in, *out;

	in = (menuedict_t*)G_EDICT(prinst, OFS_PARM0);
	out = (menuedict_t*)G_EDICT(prinst, OFS_PARM1);

	memcpy(out->fields, in->fields, menuentsize);
}

void QCBUILTIN PF_menu_checkextension (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *extname = PR_GetStringOfs(prinst, OFS_PARM0);
	int i;
	G_FLOAT(OFS_RETURN) = 0;

	for (i = 0; i < QSG_Extensions_count; i++)
	{
		if (!QSG_Extensions[i].name)
			continue;
		if (!stricmp(extname, QSG_Extensions[i].name))
		{
			G_FLOAT(OFS_RETURN) = 1;
			break;
		}
	}
}

void QCBUILTIN PF_CL_precache_file (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	G_INT(OFS_RETURN) = G_INT(OFS_PARM0);
}

//entity	findchainstring(.string _field, string match) = #26;
void QCBUILTIN PF_menu_findchain (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i, f;
	const char *s;
	string_t t;
	menuedict_t *ent, *chain;	//note, all edicts share the common header, but don't use it's fields!
	eval_t *val;

	chain = (menuedict_t *) *prinst->parms->edicts;

	f = G_INT(OFS_PARM0)+prinst->fieldadjust;
	s = PR_GetStringOfs(prinst, OFS_PARM1);

	for (i = 1; i < *prinst->parms->num_edicts; i++)
	{
		ent = (menuedict_t *)EDICT_NUM_PB(prinst, i);
		if (ent->ereftype == ER_FREE)
			continue;
		t = *(string_t *)&((float*)ent->fields)[f];
		if (!t)
			continue;
		if (strcmp(PR_GetString(prinst, t), s))
			continue;

		val = prinst->GetEdictFieldValue(prinst, (void*)ent, "chain", ev_entity, &menuc_eval.chain);
		if (val)
			val->edict = EDICT_TO_PROG(prinst, (void*)chain);
		chain = ent;
	}

	RETURN_EDICT(prinst, (void*)chain);
}
//entity	findchainfloat(.float _field, float match) = #27;
void QCBUILTIN PF_menu_findchainfloat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i, f;
	float s;
	menuedict_t	*ent, *chain;	//note, all edicts share the common header, but don't use it's fields!
	eval_t *val;

	chain = (menuedict_t *) *prinst->parms->edicts;

	f = G_INT(OFS_PARM0)+prinst->fieldadjust;
	s = G_FLOAT(OFS_PARM1);

	for (i = 1; i < *prinst->parms->num_edicts; i++)
	{
		ent = (menuedict_t*)EDICT_NUM_PB(prinst, i);
		if (ent->ereftype == ER_FREE)
			continue;
		if (((float *)ent->fields)[f] != s)
			continue;

		val = prinst->GetEdictFieldValue(prinst, (void*)ent, "chain", ev_entity, &menuc_eval.chain);
		if (val)
			val->edict = EDICT_TO_PROG(prinst, (void*)chain);
		chain = ent;
	}

	RETURN_EDICT(prinst, (void*)chain);
}
//entity	findchainflags(.float _field, float match);
void QCBUILTIN PF_menu_findchainflags (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i, f;
	int s;
	menuedict_t	*ent, *chain;	//note, all edicts share the common header, but don't use it's fields!
	eval_t *val;

	chain = (menuedict_t *) *prinst->parms->edicts;

	f = G_INT(OFS_PARM0)+prinst->fieldadjust;
	s = G_FLOAT(OFS_PARM1);

	for (i = 1; i < *prinst->parms->num_edicts; i++)
	{
		ent = (menuedict_t*)EDICT_NUM_PB(prinst, i);
		if (ent->ereftype == ER_FREE)
			continue;
		if ((int)((float *)ent->fields)[f] & s)
			continue;

		val = prinst->GetEdictFieldValue(prinst, (void*)ent, "chain", ev_entity, &menuc_eval.chain);
		if (val)
			val->edict = EDICT_TO_PROG(prinst, (void*)chain);
		chain = ent;
	}

	RETURN_EDICT(prinst, (void*)chain);
}

void QCBUILTIN PF_etof(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	G_FLOAT(OFS_RETURN) = G_EDICTNUM(prinst, OFS_PARM0);
}
void QCBUILTIN PF_ftoe(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int entnum = G_FLOAT(OFS_PARM0);

	RETURN_EDICT(prinst, EDICT_NUM_UB(prinst, entnum));
}

void QCBUILTIN PF_IsNotNull(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int str = G_INT(OFS_PARM0);
	G_FLOAT(OFS_RETURN) = !!str;
}

//float 	altstr_count(string str) = #82;
//returns number of single quoted strings in the string.
void QCBUILTIN PF_altstr_count(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *s;
	int count = 0;
	s = PR_GetStringOfs(prinst, OFS_PARM0);
	for (;*s;s++)
	{
		if (*s == '\\')
		{
			if (!*++s)
				break;
		}
		else if (*s == '\'')
			count++;
	}
	G_FLOAT(OFS_RETURN) = count/2;
}
//string  altstr_prepare(string str) = #83;
void QCBUILTIN PF_altstr_prepare(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	char outstr[8192], *out;
	const char *instr, *in;
	int size;

//	VM_SAFEPARMCOUNT( 1, VM_altstr_prepare );

	instr = PR_GetStringOfs(prinst, OFS_PARM0 );
	//VM_CheckEmptyString( instr );

	for( out = outstr, in = instr, size = sizeof(outstr) - 1 ; size && *in ; size--, in++, out++ )
	{
		if( *in == '\'' )
		{
			*out++ = '\\';
			*out = '\'';
			size--;
		}
		else
			*out = *in;
	}
	*out = 0;

	G_INT( OFS_RETURN ) = (int)PR_TempString( prinst, outstr );
}
//string  altstr_get(string str, float num) = #84;
void QCBUILTIN PF_altstr_get(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *altstr, *pos;
	char outstr[8192], *out;
	int count, size;

//	VM_SAFEPARMCOUNT( 2, VM_altstr_get );

	altstr = PR_GetStringOfs(prinst, OFS_PARM0 );
	//VM_CheckEmptyString( altstr );

	count = G_FLOAT( OFS_PARM1 );
	count = count * 2 + 1;

	for( pos = altstr ; *pos && count ; pos++ )
	{
		if( *pos == '\\' && !*++pos )
			break;
		else if( *pos == '\'' )
			count--;
	}

	if( !*pos )
	{
		G_INT( OFS_RETURN ) = (int)PR_SetString( prinst, "" );
		return;
	}

	for( out = outstr, size = sizeof(outstr) - 1 ; size && *pos ; size--, pos++, out++ )
	{
		if( *pos == '\\' )
		{
			if( !*++pos )
				break;
			*out = *pos;
			size--;
		}
		else if( *pos == '\'' )
			break;
		else
			*out = *pos;
	}

	*out = 0;
	G_INT( OFS_RETURN ) = (int)PR_TempString( prinst, outstr );
}
//string  altstr_set(string str, float num, string set) = #85
void QCBUILTIN PF_altstr_set(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int num;
	const char *altstr, *str;
	const char *in;
	char outstr[8192], *out;

//	VM_SAFEPARMCOUNT( 3, VM_altstr_set );

	altstr = PR_GetStringOfs(prinst, OFS_PARM0 );
	//VM_CheckEmptyString( altstr );

	num = G_FLOAT( OFS_PARM1 );

	str = PR_GetStringOfs(prinst, OFS_PARM2 );
	//VM_CheckEmptyString( str );

	out = outstr;
	for( num = num * 2 + 1, in = altstr; *in && num; *out++ = *in++ )
	{
		if( *in == '\\' && !*++in )
			break;
		else if( *in == '\'' )
			num--;
	}

	if( !in )
	{
		G_INT( OFS_RETURN ) = (int)PR_SetString( prinst, "" );
		return;
	}
	// copy set in
	for( ; *str; *out++ = *str++ )
		;
	// now jump over the old contents
	for( ; *in ; in++ )
	{
		if( *in == '\'' || (*in == '\\' && !*++in) )
			break;
	}

	if( !in ) {
		G_INT( OFS_RETURN ) = (int)PR_SetString( prinst, "" );
		return;
	}

	strcpy( out, in );
	G_INT( OFS_RETURN ) = (int)PR_TempString( prinst, outstr );

}

//string(string serveraddress) crypto_getkeyfp
void QCBUILTIN PF_crypto_getkeyfp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}
//string(string serveraddress) crypto_getidfp
void QCBUILTIN PF_crypto_getidfp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}
//float(string serveraddress) crypto_getidstatus
void QCBUILTIN PF_crypto_getidstatus(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}
//string(string serveraddress) crypto_getencryptlevel
void QCBUILTIN PF_crypto_getencryptlevel(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}
//string(float i) crypto_getmykeyfp
void QCBUILTIN PF_crypto_getmykeyfp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}
//string(float i) crypto_getmyidfp
void QCBUILTIN PF_crypto_getmyidfp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}
//float(float i) PF_crypto_getmyidstatus
void QCBUILTIN PF_crypto_getmyidstatus(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//not supported.
	G_INT(OFS_RETURN) = 0;
}

static void QCBUILTIN PF_m_precache_model(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *modelname = PR_GetStringOfs(prinst, OFS_PARM0);
	Mod_ForName(modelname, MLV_WARN);
}
static void QCBUILTIN PF_m_setmodel(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	const char *modelname = PR_GetStringOfs(prinst, OFS_PARM1);
	eval_t *modelval = prinst->GetEdictFieldValue(prinst, (void*)ent, "model", ev_string, &menuc_eval.model);
	eval_t *minsval = prinst->GetEdictFieldValue(prinst, (void*)ent, "mins", ev_vector, &menuc_eval.mins);
	eval_t *maxsval = prinst->GetEdictFieldValue(prinst, (void*)ent, "maxs", ev_vector, &menuc_eval.maxs);
	model_t *mod = Mod_ForName(modelname, MLV_WARN);
	if (modelval)
		modelval->string = G_INT(OFS_PARM1);	//lets hope garbage collection is enough.
	else
		Con_Printf("PF_m_setmodel: no model field!\n");

	if (mod)
		while(mod->loadstate == MLS_LOADING)
			COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);

	if (mod && minsval)
		VectorCopy(mod->mins, minsval->_vector);
	if (mod && maxsval)
		VectorCopy(mod->maxs, maxsval->_vector);
}
static void QCBUILTIN PF_m_setcustomskin(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	const char *fname = PR_GetStringOfs(prinst, OFS_PARM1);
	const char *skindata = PF_VarString(prinst, 2, pr_globals);
	eval_t *val = prinst->GetEdictFieldValue(prinst, (void*)ent, "skinobject", ev_string, &menuc_eval.skinobject);
	if (!val)
	{
		Con_Printf("PF_m_setcustomskin: no skinobject field!\n");
		return;
	}

	if (val->_float > 0)
	{
		Mod_WipeSkin(val->_float, false);
		val->_float = 0;
	}

	if (*fname || *skindata)
	{
		if (*skindata)
			val->_float = Mod_ReadSkinFile(fname, skindata);
		else
			val->_float = -(int)Mod_RegisterSkinFile(fname);
	}
}
//trivially basic
static void QCBUILTIN PF_m_setorigin(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	float *org = G_VECTOR(OFS_PARM1);
	eval_t *val = prinst->GetEdictFieldValue(prinst, (void*)ent, "origin", ev_vector, &menuc_eval.origin);
	if (val)
		VectorCopy(org, val->_vector);
	else
		Con_Printf("PF_m_setorigin: no origin field!\n");
}
static void QCBUILTIN PF_m_clearscene(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
//	CL_DecayLights ();

#if defined(SKELETALOBJECTS) || defined(RAGDOLLS)
	world_t *world = prinst->parms->user;
	if (world)
		skel_dodelete(world);
#endif
	CL_ClearEntityLists();

	V_ClearRefdef(&menuview);
	r_refdef.drawsbar = false;
	r_refdef.drawcrosshair = false;
	V_CalcRefdef(&menuview);	//set up the defaults
	r_refdef.flags |= RDF_NOWORLDMODEL;
}
static qboolean CopyMenuEdictToEntity(pubprogfuncs_t *prinst, menuedict_t *in, entity_t *out)
{
	eval_t *modelval = prinst->GetEdictFieldValue(prinst, (void*)in, "model", ev_string, &menuc_eval.model);
	eval_t *originval = prinst->GetEdictFieldValue(prinst, (void*)in, "origin", ev_vector, &menuc_eval.origin);
	eval_t *anglesval = prinst->GetEdictFieldValue(prinst, (void*)in, "angles", ev_vector, &menuc_eval.angles);
	eval_t *skinval = prinst->GetEdictFieldValue(prinst, (void*)in, "skin", ev_float, &menuc_eval.skin);
	eval_t *frame1val = prinst->GetEdictFieldValue(prinst, (void*)in, "frame", ev_float, &menuc_eval.frame1);
	eval_t *frame2val = prinst->GetEdictFieldValue(prinst, (void*)in, "frame2", ev_float, &menuc_eval.frame2);
	eval_t *lerpfracval = prinst->GetEdictFieldValue(prinst, (void*)in, "lerpfrac", ev_float, &menuc_eval.lerpfrac);
	eval_t *frame1timeval = prinst->GetEdictFieldValue(prinst, (void*)in, "frame1time", ev_float, &menuc_eval.frame1time);
	eval_t *frame2timeval = prinst->GetEdictFieldValue(prinst, (void*)in, "frame2time", ev_float, &menuc_eval.frame2time);
	eval_t *colormapval = prinst->GetEdictFieldValue(prinst, (void*)in, "colormap", ev_float, &menuc_eval.colormap);
	eval_t *renderflagsval = prinst->GetEdictFieldValue(prinst, (void*)in, "renderflags", ev_float, &menuc_eval.renderflags);
	eval_t *skinobjectval = prinst->GetEdictFieldValue(prinst, (void*)in, "skinobject", ev_float, &menuc_eval.skinobject);
	eval_t *colourmodval = prinst->GetEdictFieldValue(prinst, (void*)in, "colormod", ev_vector, &menuc_eval.colourmod);
	eval_t *alphaval = prinst->GetEdictFieldValue(prinst, (void*)in, "alpha", ev_float, &menuc_eval.alpha);
	int ival;
	int rflags;

	rflags = renderflagsval?renderflagsval->_float:0;

	memset(out, 0, sizeof(*out));
	if (modelval)
		out->model = Mod_ForName(prinst->StringToNative(prinst, modelval->_int), MLV_WARN);
	if (originval)
		VectorCopy(originval->_vector, out->origin);
	if (!anglesval)anglesval = (eval_t*)vec3_origin;
	AngleVectors(anglesval->_vector, out->axis[0], out->axis[1], out->axis[2]);
	VectorInverse(out->axis[1]);

	out->scale = 1;
	out->skinnum = skinval?skinval->_float:0;
	out->framestate.g[FS_REG].frame[0] = frame1val?frame1val->_float:0;
	out->framestate.g[FS_REG].frame[1] = frame2val?frame2val->_float:0;
	out->framestate.g[FS_REG].lerpweight[1] = lerpfracval?lerpfracval->_float:0;
	out->framestate.g[FS_REG].lerpweight[0] = 1-out->framestate.g[FS_REG].lerpweight[1];
	out->framestate.g[FS_REG].frametime[0] = frame1timeval?frame1timeval->_float:0;
	out->framestate.g[FS_REG].frametime[1] = frame2timeval?frame2timeval->_float:0;

	out->customskin = skinobjectval?skinobjectval->_float:0;

	//FIXME: colourmap
	ival = colormapval?colormapval->_float:0;
	out->playerindex = -1;
	if (ival >= 1024)
	{
		//DP COLORMAP extension
		out->topcolour = (ival>>4) & 0x0f;
		out->bottomcolour = ival & 0xf;
	}
/*	else if (ival > 0 && ival <= MAX_CLIENTS)
	{	//FIXME: tie to the current skin/topcolor/bottomcolor cvars somehow?
		out->playerindex = ival - 1;
		out->topcolour = cl.players[ival-1].ttopcolor;
		out->bottomcolour = cl.players[ival-1].tbottomcolor;
	}*/
	else
	{
		out->topcolour = TOP_DEFAULT;
		out->bottomcolour = BOTTOM_DEFAULT;
	}

	VectorSet(out->glowmod, 1,1,1);
	if (!colourmodval || (!colourmodval->_vector[0] && !colourmodval->_vector[1] && !colourmodval->_vector[2]))
		VectorSet(out->shaderRGBAf, 1, 1, 1);
	else
	{
		out->flags |= RF_FORCECOLOURMOD;
		VectorCopy(colourmodval->_vector, out->shaderRGBAf);
	}
	if (!alphaval || !alphaval->_float || alphaval->_float == 1)
		out->shaderRGBAf[3] = 1.0f;
	else
	{
		out->flags |= RF_TRANSLUCENT;
		out->shaderRGBAf[3] = alphaval->_float;
	}

	if (rflags & CSQCRF_ADDITIVE)
		out->flags |= RF_ADDITIVE;
	if (rflags & CSQCRF_DEPTHHACK)
		out->flags |= RF_DEPTHHACK;

	if (out->model)
		return true;
	return false;
}
static void QCBUILTIN PF_m_addentity(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	menuedict_t *in = (void*)G_EDICT(prinst, OFS_PARM0);
	entity_t ent;
	if (in->ereftype == ER_FREE || in->entnum == 0)
	{
		Con_Printf("Tried drawing a free/removed/world entity\n");
		return;
	}

	if (CopyMenuEdictToEntity(prinst, in, &ent))
		V_AddAxisEntity(&ent);
}
static void QCBUILTIN PF_m_renderscene(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	V_ApplyRefdef();
	R_RenderView();
}
void QCBUILTIN PF_R_SetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_R_GetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);


static void QCBUILTIN PF_menu_cprint (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *str = PF_VarString(prinst, 0, pr_globals);
	SCR_CenterPrint(0, str, true);
}
static void QCBUILTIN PF_cl_changelevel (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
#ifndef CLIENTONLY
	const char *nextmap = PR_GetStringOfs(prinst, OFS_PARM0);
	if (sv.active || !cls.state)
	{
		char buf[1024];
		Cbuf_AddText(va("changelevel %s\n", COM_QuotedString(nextmap, buf, sizeof(buf), false)), RESTRICT_INSECURE);
	}
#endif
}
static void QCBUILTIN PF_crash (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int binum;
	char fname[MAX_QPATH];
	//allow people to rename it or whatever
	if (!prinst->GetBuiltinCallInfo(prinst, &binum, fname, sizeof(fname)))
	{
		binum = 0;
		strcpy(fname, "?unknown?");
	}

	prinst->RunError(prinst, "\n%s called", fname);
}
static void QCBUILTIN PF_stackdump (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	prinst->StackTrace(prinst, true);
}
#define PF_cl_clientcommand PF_Fixme
#define PF_altstr_ins PF_Fixme	//insert after, apparently

static void MP_ConsoleCommand_f(void)
{
	char cmd[2048];
	Q_snprintfz(cmd, sizeof(cmd), "%s %s", Cmd_Argv(0), Cmd_Args());
	MP_ConsoleCommand(cmd);
}
static void QCBUILTIN PF_menu_registercommand (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *str = PF_VarString(prinst, 0, pr_globals);
	if (!Cmd_Exists(str))
		Cmd_AddCommand(str, MP_ConsoleCommand_f);
}

static void PF_m_clipboard_got(void *ctx, const char *utf8)
{
	void *pr_globals;
	unsigned int unicode;
	int error;

	while (*utf8)
	{
		unicode = utf8_decode(&error, utf8, &utf8);
		if (error)
			unicode = 0xfffdu;

		if (!menu_world.progs || !mpfuncs.inputevent)
			return;
	#ifdef TEXTEDITOR
		if (editormodal)
			return;
	#endif

		pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = CSIE_PASTE;
		G_FLOAT(OFS_PARM1) = 0;
		G_FLOAT(OFS_PARM2) = unicode;
		G_FLOAT(OFS_PARM3) = 0;

		qcinput_scan = G_FLOAT(OFS_PARM1);
		qcinput_unicode = G_FLOAT(OFS_PARM2);
		PR_ExecuteProgram (menu_world.progs, mpfuncs.inputevent);
		qcinput_scan = 0;	//and stop replay attacks
		qcinput_unicode = 0;
	}
}
static void QCBUILTIN PF_m_clipboard_get(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	clipboardtype_t cliptype = G_FLOAT(OFS_PARM0);
	Sys_Clipboard_PasteText(cliptype, PF_m_clipboard_got, prinst);
}

static struct {
	char *name;
	builtin_t bifunc;
	int ebfsnum;
}  BuiltinList[] = {
	{"checkextension",			PF_menu_checkextension,		1},
	{"checkbuiltin",			PF_checkbuiltin,			0},
	{"error",					PF_error,					2},
	{"objerror",				PF_nonfatalobjerror,		3},
	{"print",					PF_print,					4},
	{"bprint",					PF_cl_bprint,				5},
	{"msprint",					PF_cl_sprint,				6},
	{"cprint",					PF_menu_cprint,				7},
	{"normalize",				PF_normalize,				8},
	{"vlen",					PF_vlen,					9},
	{"vectoyaw",				PF_vectoyaw,				10},
	{"vectoangles",				PF_vectoangles,				11},
	{"crossproduct",			PF_crossproduct,			0},
	{"random",					PF_random,					12},
	{"localcmd",				PF_localcmd,				13},
	{"cvar",					PF_menu_cvar,				14},
	{"cvar_set",				PF_menu_cvar_set,			15},
	{"dprint",					PF_dprint,					16},
	{"ftos",					PF_ftos,					17},
	{"fabs",					PF_fabs,					18},
	{"vtos",					PF_vtos,					19},
	{"etos",					PF_etos,					20},
	{"stof",					PF_stof,					21},
	
	{"stoi",					PF_stoi,					0},
	{"itos",					PF_itos,					0},
	{"stoh",					PF_stoh,					0},
	{"htos",					PF_htos,					0},
	{"ftoi",					PF_ftoi,					0},
	{"itof",					PF_itof,					0},

	{"spawn",					PF_Spawn,					22},
	{"remove",					PF_Remove_,					23},
	{"removeinstant",			PF_RemoveInstant,			0},
	{"find",					PF_FindString,				24},
	{"findfloat",				PF_FindFloat,				25},
	{"findentity",				PF_FindFloat,				25},
	{"findchain",				PF_menu_findchain,			26},
	{"findchainfloat",			PF_menu_findchainfloat,		27},
#ifdef QCGC
	{"find_list",				PF_FindList,				0},
#endif
	{"precache_file",			PF_CL_precache_file,		28},
	{"precache_sound",			PF_CL_precache_sound,		29},
	{"coredump",				PF_coredump,				30},
	{"traceon",					PF_traceon,					31},
	{"traceoff",				PF_traceoff,				32},
	{"eprint",					PF_eprint,					33},
	{"rint",					PF_rint,					34},
	{"floor",					PF_floor,					35},
	{"ceil",					PF_ceil,					36},
	{"nextent",					PF_nextent,					37},
	{"sin",						PF_Sin,						38},
	{"cos",						PF_Cos,						39},
	{"sqrt",					PF_Sqrt,					40},
	{"randomvector",			PF_randomvector,			41},
	{"registercvar",			PF_registercvar,			42},
	{"min",						PF_min,						43},
	{"max",						PF_max,						44},
	{"bound",					PF_bound,					45},
	{"pow",						PF_pow,						46},
	{"logarithm",				PF_Logarithm,				0},
	{"entityprotection",		PF_entityprotection,		0},
	{"copyentity",				PF_CopyEntity,				47},
	{"fopen",					PF_fopen,					48},
	{"fclose",					PF_fclose,					49},
	{"fgets",					PF_fgets,					50},
	{"fputs",					PF_fputs,					51},
	{"fread",					PF_fread,					0},
	{"fwrite",					PF_fwrite,					0},
	{"fseek",					PF_fseek,					0},
	{"fsize",					PF_fsize,					0},
	{"strlen",					PF_strlen,					52},
	{"strcat",					PF_strcat,					53},
	{"substring",				PF_substring,				54},
	{"stov",					PF_stov,					55},
	{"strzone",					PF_strzone,					56},
	{"strunzone",				PF_strunzone,				57},
#ifdef QCGC
	{"createbuffer",			PF_createbuffer,			0},
#endif
	{"tokenize",				PF_Tokenize,				58},
	{"argv",					PF_ArgV,					59},
	{"isserver",				PF_isserver,				60},
	{"clientcount",				PF_cl_clientcount,			61},						//float	clientcount(void)  = #61;
	{"clientstate",				PF_clientstate,				62},
	{"clientcommand",			PF_cl_clientcommand,		63},						//void	clientcommand(float client, string s)  = #63;
	{"changelevel",				PF_cl_changelevel,			64},						//void	changelevel(string map)  = #64;
	{"localsound",				PF_cl_localsound,			65},
	{"getmousepos",				PF_cl_getmousepos,			66},
	{"gettime",					PF_gettime,					67},
	{"loadfromdata",			PF_loadfromdata,			68},
	{"loadfromfile",			PF_loadfromfile,			69},
	{"mod",						PF_mod,						70},
	{"cvar_string",				PF_menu_cvar_string,		71},
	{"crash",					PF_crash,					72},				//void	crash(void)	= #72;
	{"stackdump",				PF_stackdump,				73},			//void	stackdump(void) = #73;
	{"search_begin",			PF_search_begin,			74},
	{"search_end",				PF_search_end,				75},
	{"search_getsize",			PF_search_getsize,			76},
	{"search_getfilename",		PF_search_getfilename,		77},
	{"search_getfilesize",		PF_search_getfilesize,		0},
	{"search_getfilemtime",		PF_search_getfilemtime,		0},
	{"search_getpackagename",	PF_search_getpackagename,	0},
	{"search_fopen",			PF_search_fopen,			0},
	{"chr2str",					PF_chr2str,					78},
	{"etof",					PF_etof,					79},
	{"ftoe",					PF_ftoe,					80},
	{"validstring",				PF_IsNotNull,				81},
	{"altstr_count",			PF_altstr_count, 			82},
	{"altstr_prepare",			PF_altstr_prepare, 			83},
	{"altstr_get",				PF_altstr_get,				84},
	{"altstr_set",				PF_altstr_set, 				85},
	{"altstr_ins",				PF_altstr_ins,				86},
	{"findflags",				PF_FindFlags,				87},
	{"findchainflags",			PF_menu_findchainflags,		88},
	{"cvar_defstring",			PF_cvar_defstring,			89},
	{"setmodel",				PF_m_setmodel,				90},
	{"precache_model",			PF_m_precache_model,		91},
	{"setorigin",				PF_m_setorigin,				92},
															//gap
	{"abort",					PF_Abort,					211},
															//gap
	{"strstrofs",				PF_strstrofs,				221},
	{"str2chr",					PF_str2chr,					222},
	{"chr2str",					PF_chr2str,					223},
	{"strconv",					PF_strconv,					224},
	{"strpad",					PF_strpad,					225},
	{"infoadd",					PF_infoadd,					226},
	{"infoget",					PF_infoget,					227},
	{"strcmp",					PF_strncmp,					228},
	{"strncmp",					PF_strncmp,					228},
	{"strcasecmp",				PF_strncasecmp,				229},
	{"strncasecmp",				PF_strncasecmp,				230},
	{"strtrim",					PF_strtrim,					0},
															//gap
	{"shaderforname",			PF_shaderforname,			238},
	{"sendpacket",				PF_cl_SendPacket,			242},
															//gap
	{"hash_createtab",			PF_hash_createtab,			287},
	{"hash_destroytab",			PF_hash_destroytab,			288},
	{"hash_add",				PF_hash_add,				289},
	{"hash_get",				PF_hash_get,				290},
	{"hash_delete",				PF_hash_delete,				291},
	{"hash_getkey",				PF_hash_getkey,				292},
	{"hash_getcb",				PF_hash_getcb,				293},
	{"checkcommand",			PF_checkcommand,			294},
	{"argescape",				PF_argescape,				295},
															//gap
	{"clearscene",				PF_m_clearscene,			300},
//	{"addentities",				PF_Fixme,					301},
	{"addentity",				PF_m_addentity,				302},//FIXME: needs setmodel, origin, angles, colormap(eep), frame etc, skin, 
#ifdef CSQC_DAT
	{"setproperty",				PF_R_SetViewFlag,			303},//should be okay to share
#endif
	{"renderscene",				PF_m_renderscene,			304},//too module-specific
//	{"dynamiclight_add",		PF_R_DynamicLight_Add,		305},//should be okay to share
	{"R_BeginPolygon",			PF_R_PolygonBegin,			306},//useful for 2d stuff
	{"R_PolygonVertex",			PF_R_PolygonVertex,			307},
	{"R_EndPolygon",			PF_R_PolygonEnd,			308},
#ifdef CSQC_DAT
	{"getproperty",				PF_R_GetViewFlag,			309},//should be okay to share
#endif
//unproject													310
//project													311

#ifdef CSQC_DAT
	{"r_uploadimage",			PF_CL_uploadimage,			0},
#endif
	{"r_readimage",				PF_CL_readimage,			0},


	{"print_csqc",				PF_print,					339},
	{"keynumtostring_csqc",		PF_cl_keynumtostring,		340},
	{"stringtokeynum_csqc",		PF_cl_stringtokeynum,		341},
	{"getkeybind",				PF_cl_getkeybind,			342},
	{"setcursormode",			PF_cl_setcursormode,		343},
	{"getcursormode",			PF_cl_getcursormode,		0},	
	{"setmousepos",				PF_cl_setmousepos,			0},


	{"clipboard_get",			PF_m_clipboard_get,			0},
	{"clipboard_set",			PF_cl_clipboard_set,		0},

//	{NULL,						PF_Fixme,					344},
//	{NULL,						PF_Fixme,					345},
//	{NULL,						PF_Fixme,					346},
//	{NULL,						PF_Fixme,					347},
//	{NULL,						PF_Fixme,					348},
	{"isdemo",					PF_isdemo,					349},
//	{NULL,						PF_Fixme,					350},
//	{NULL,						PF_Fixme,					351},
	{"registercommand",			PF_menu_registercommand,	352},
	{"wasfreed",				PF_WasFreed,				353},
//	{NULL,						PF_Fixme,					354},
#ifdef HAVE_MEDIA_DECODER
	{"videoplaying",			PF_cs_media_getstate,		355},
#endif
	{"findfont",				PF_CL_findfont,				356},
	{"loadfont",				PF_CL_loadfont,				357},
															//gap
//	{"dynamiclight_get",		PF_R_DynamicLight_Get,		372},
//	{"dynamiclight_set",		PF_R_DynamicLight_Set,		373},
//	{NULL,						PF_Fixme,					374},
//	{NULL,						PF_Fixme,					375},
	{"setcustomskin",			PF_m_setcustomskin,			376},
															//gap
	{"memalloc",				PF_memalloc,				384},
	{"memfree",					PF_memfree,					385},
	{"memcpy",					PF_memcpy,					386},
	{"memfill8",				PF_memfill8,				387},
	{"memgetval",				PF_memgetval,				388},
	{"memsetval",				PF_memsetval,				389},
	{"memptradd",				PF_memptradd,				390},
	{"memstrsize",				PF_memstrsize,				0},
	{"con_getset",				PF_SubConGetSet,			391},
	{"con_printf",				PF_SubConPrintf,			392},
	{"con_draw",				PF_SubConDraw,				393},
	{"con_input",				PF_SubConInput,				394},
	{"setwindowcaption",		PF_cl_setwindowcaption,		0},
	{"cvars_haveunsaved",		PF_cvars_haveunsaved,		0},
															//gap
//	{"writebyte,				PF_Fixme,					401},
//	{"writechar,				PF_Fixme,					402},
//	{"writeshort,				PF_Fixme,					403},
//	{"writelong,				PF_Fixme,					404},
//	{"writeangle,				PF_Fixme,					405},
//	{"writecoord,				PF_Fixme,					406},
//	{"writestring,				PF_Fixme,					407},
//	{"writeentity,				PF_Fixme,					408},
															//gap
	{"buf_create",				PF_buf_create,				440},
	{"buf_del",					PF_buf_del,					441},
	{"buf_getsize",				PF_buf_getsize,				442},
	{"buf_copy",				PF_buf_copy,				443},
	{"buf_sort",				PF_buf_sort,				444},
	{"buf_implode",				PF_buf_implode,				445},
	{"bufstr_get",				PF_bufstr_get,				446},
	{"bufstr_set",				PF_bufstr_set,				447},
	{"bufstr_add",				PF_bufstr_add,				448},
	{"bufstr_free",				PF_bufstr_free,				449},
//	{NULL,						PF_Fixme,					450},
	{"iscachedpic",				PF_CL_is_cached_pic,		451},
	{"precache_pic",			PF_CL_precache_pic,			452},
	{"free_pic",				PF_CL_free_pic,				453},
	{"drawcharacter",			PF_CL_drawcharacter,		454},
	{"drawrawstring",			PF_CL_drawrawstring,		455},
	{"drawpic",					PF_CL_drawpic,				456},
	{"drawrotpic",				PF_CL_drawrotpic,			0},
	{"drawfill",				PF_CL_drawfill,				457},
	{"drawsetcliparea",			PF_CL_drawsetcliparea,		458},
	{"drawresetcliparea",		PF_CL_drawresetcliparea,	459},
	{"drawgetimagesize",		PF_CL_drawgetimagesize,		460},
#ifdef HAVE_MEDIA_DECODER
	{"cin_open",				PF_cs_media_create,			461},
	{"cin_close",				PF_cs_media_destroy,		462},
	{"cin_setstate",			PF_cs_media_setstate,		463},
	{"cin_getstate",			PF_cs_media_getstate,		464},
	{"cin_restart",				PF_cs_media_restart, 		465},
#endif
	{"drawline",				PF_CL_drawline,				466},
	{"drawstring",				PF_CL_drawcolouredstring,	467},
	{"stringwidth",				PF_CL_stringwidth,			468},
	{"drawsubpic",				PF_CL_drawsubpic,			469},
	{"drawrotsubpic",			PF_CL_drawrotsubpic,		0},
	{"drawtextfield",			PF_CL_DrawTextField,		0},

#ifdef HAVE_LEGACY
	{"drawrotpic_dp",			PF_CL_drawrotpic_dp,		470},
#endif
//MERGES WITH CLIENT+SERVER BUILTIN MAPPINGS BELOW
	{"asin",					PF_asin,					471},
	{"acos",					PF_acos,					472},
	{"atan",					PF_atan,					473},
	{"atan2",					PF_atan2,					474},
	{"tan",						PF_tan,						475},
	{"strlennocol",				PF_strlennocol,				476},
	{"strdecolorize",			PF_strdecolorize,			477},
	{"strftime",				PF_strftime,				478},
	{"tokenizebyseparator",		PF_tokenizebyseparator,		479},
	{"strtolower",				PF_strtolower,				480},
	{"strtoupper",				PF_strtoupper,				481},
	{"csqc_cvar_defstring",		PF_cvar_defstring,			482},
//	{NULL,						PF_Fixme,					483},
	{"strreplace",				PF_strreplace,				484},
	{"strireplace",				PF_strireplace,				485},
//	{NULL,						PF_Fixme,					486},
#ifdef HAVE_MEDIA_DECODER
	{"gecko_create",			PF_cs_media_create,			487},
	{"gecko_destroy",			PF_cs_media_destroy,		488},
	{"gecko_navigate",			PF_cs_media_command,		489},
	{"gecko_keyevent",			PF_cs_media_keyevent,		490},
	{"gecko_mousemove",			PF_cs_media_mousemove,		491},
	{"gecko_resize",			PF_cs_media_resize,			492},
	{"gecko_get_texture_extent",PF_cs_media_get_texture_extent,493},
	{"gecko_getproperty",		PF_cs_media_getproperty,	0},
#endif
	{"crc16",					PF_crc16,					494},
	{"cvar_type",				PF_cvar_type,				495},
	{"numentityfields",			PF_numentityfields,			496},
	{"findentityfield",			PF_findentityfield,			0},
	{"entityfieldref",			PF_entityfieldref,			0},
	{"entityfieldname",			PF_entityfieldname,			497},
	{"entityfieldtype",			PF_entityfieldtype,			498},
	{"getentityfieldstring",	PF_getentityfieldstring,	499},
	{"putentityfieldstring",	PF_putentityfieldstring,	500},
//	{NULL,						PF_Fixme,					501},
//	{NULL,						PF_Fixme,					502},
	{"whichpack",				PF_whichpack,				503},
															//gap
	{"uri_escape",				PF_uri_escape,				510},
	{"uri_unescape",			PF_uri_unescape,			511},
	{"num_for_edict",			PF_etof,					512},
	{"uri_get",					PF_uri_get,					513},
	{"uri_post",				PF_uri_get,					513},
	{"tokenize_console",		PF_tokenize_console,		514},
	{"argv_start_index",		PF_argv_start_index,		515},
	{"argv_end_index",			PF_argv_end_index,			516},
	{"buf_cvarlist",			PF_buf_cvarlist,			517},
	{"cvar_description",		PF_cvar_description,		518},
															//gap
	{"log",						PF_Logarithm,				532},
//	{"getsoundtime",			PF_Fixme,					533},
	{"soundlength",				PF_soundlength,				534},
	{"buf_loadfile",			PF_buf_loadfile,			535},
	{"buf_writefile",			PF_buf_writefile,			536},
	{"bufstr_find",				PF_bufstr_find,				537},
//	{"matchpattern",			PF_Fixme,					538},
															//gap
	{"setkeydest",				PF_cl_setkeydest,			601},
	{"getkeydest",				PF_cl_getkeydest,			602},
	{"setmousetarget",			PF_cl_setmousetarget,		603},
	{"getmousetarget",			PF_cl_getmousetarget,		604},
	{"callfunction",			PF_callfunction,			605},
	{"writetofile",				PF_writetofile,				606},
	{"isfunction",				PF_isfunction,				607},
	{"getresolution",			PF_cl_getresolution,		608},
	{"keynumtostring",			PF_cl_keynumtostring,		609},
	{"findkeysforcommand",		PF_cl_findkeysforcommand,	610},
	{"gethostcachevalue",		PF_cl_gethostcachevalue,	611},
	{"gethostcachestring",		PF_cl_gethostcachestring,	612},
	{"parseentitydata",			PF_parseentitydata,			613},
	{"generateentitydata",		PF_generateentitydata,		0},

	{"stringtokeynum",			PF_cl_stringtokeynum,		614},

	{"resethostcachemasks",		PF_cl_resethostcachemasks,	615},
	{"sethostcachemaskstring",	PF_cl_sethostcachemaskstring,616},
	{"sethostcachemasknumber",	PF_cl_sethostcachemasknumber,617},
	{"resorthostcache",			PF_cl_resorthostcache,		618},
	{"sethostcachesort",		PF_cl_sethostcachesort,		619},
	{"refreshhostcache",		PF_cl_refreshhostcache,		620},
	{"gethostcachenumber",		PF_cl_gethostcachenumber,	621},
	{"gethostcacheindexforkey",	PF_cl_gethostcacheindexforkey,622},
	{"addwantedhostcachekey",	PF_cl_addwantedhostcachekey,623},
#ifdef CL_MASTER
	{"getextresponse",			PF_cl_getextresponse,		624},
#endif
	{"netaddress_resolve",		PF_netaddress_resolve,		625},
	{"getgamedirinfo",			PF_cl_getgamedirinfo,		626},
#ifdef PACKAGEMANAGER
	{"getpackagemanagerinfo",	PF_cl_getpackagemanagerinfo,0},
#endif
	{"sprintf",					PF_sprintf,					627},
//	{NULL,						PF_Fixme,					628},
//	{NULL,						PF_Fixme,					629},
	{"setkeybind",				PF_cl_setkeybind,			630},
	{"getbindmaps",				PF_cl_GetBindMap,			631},
	{"setbindmaps",				PF_cl_SetBindMap,			632},
	{"crypto_getkeyfp",			PF_crypto_getkeyfp,			633},
	{"crypto_getidfp",			PF_crypto_getidfp,			634},
	{"crypto_getencryptlevel",	PF_crypto_getencryptlevel,	635},
	{"crypto_getmykeyfp",		PF_crypto_getmykeyfp,		636},
	{"crypto_getmyidfp",		PF_crypto_getmyidfp,		637},
//	{NULL,						PF_Fixme,					638},
	{"digest_hex",				PF_digest_hex,				639},
	{"digest_ptr",				PF_digest_ptr,				0},
//	{NULL,						PF_Fixme,					640},
	{"crypto_getmyidstatus",	PF_crypto_getmyidstatus,	641},
//	{"coverage",				PF_Fixme,					642},
	{"crypto_getidstatus",		PF_crypto_getidstatus,		643},
//	{NULL,						PF_Fixme,					644},
//	{NULL,						PF_Fixme,					645},
//	{NULL,						PF_Fixme,					646},
//	{NULL,						PF_Fixme,					647},
//	{NULL,						PF_Fixme,					648},
//	{NULL,						PF_Fixme,					649},
	{"fcopy",					PF_fcopy,					650},
	{"frename",					PF_frename,					651},
	{"fremove",					PF_fremove,					652},
	{"fexists",					PF_fexists,					653},
	{"rmtree",					PF_rmtree,					654},


	{"setlocaluserinfo",		PF_cl_setlocaluserinfo,			0},
	{"getlocaluserinfo",		PF_cl_getlocaluserinfostring,	0},
	{"setlocaluserinfoblob",	PF_cl_setlocaluserinfo,			0},
	{"getlocaluserinfoblob",	PF_cl_getlocaluserinfoblob,		0},

	{NULL}
};
static builtin_t menu_builtins[1024];


int MP_BuiltinValid(const char *name, int num)
{
	int i;
	for (i = 0; BuiltinList[i].name; i++)
	{
		if (BuiltinList[i].ebfsnum == num)
		{
			if (!strcmp(BuiltinList[i].name, name))
			{
				if (/*BuiltinList[i].bifunc == PF_NoMenu ||*/ BuiltinList[i].bifunc == PF_Fixme)
					return false;
				else
					return true;
			}
		}
	}
	return false;
}

static void MP_SetupBuiltins(void)
{
	int i;
	for (i = 0; i < sizeof(menu_builtins)/sizeof(menu_builtins[0]); i++)
		menu_builtins[i] = PF_Fixme;
	for (i = 0; BuiltinList[i].bifunc; i++)
	{
		if (BuiltinList[i].ebfsnum)
			menu_builtins[BuiltinList[i].ebfsnum] = BuiltinList[i].bifunc;
	}
}

static int PDECL PR_Menu_MapNamedBuiltin(pubprogfuncs_t *progfuncs, int headercrc, const char *builtinname)
{
	int i, binum;
	for (i = 0;BuiltinList[i].name;i++)
	{
		if (!strcmp(BuiltinList[i].name, builtinname) && BuiltinList[i].bifunc != PF_Fixme)
		{
			for (binum = sizeof(menu_builtins)/sizeof(menu_builtins[0]); --binum; )
			{
				if (menu_builtins[binum] && menu_builtins[binum] != PF_Fixme && BuiltinList[i].bifunc)
					continue;
				menu_builtins[binum] = BuiltinList[i].bifunc;
				return binum;
			}
			Con_Printf("No more builtin slots to allocate for %s\n", builtinname);
			break;
		}
	}
	Con_DPrintf("Unknown menu builtin: %s\n", builtinname);
	return 0;
}

static qboolean MP_MouseMove(menu_t *menu, qboolean isabs, unsigned int devid, float xdelta, float ydelta)
{
	void *pr_globals;

	if (!menu_world.progs || !mpfuncs.inputevent)
		return false;

	if (setjmp(mp_abort))
		return false;
	inmenuprogs++;
	pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
	G_FLOAT(OFS_PARM0) = isabs?CSIE_MOUSEABS:CSIE_MOUSEDELTA;
	G_FLOAT(OFS_PARM1) = (xdelta * vid.width) / vid.pixelwidth;
	G_FLOAT(OFS_PARM2) = (ydelta * vid.height) / vid.pixelheight;
	G_FLOAT(OFS_PARM3) = devid;
	PR_ExecuteProgram (menu_world.progs, mpfuncs.inputevent);
	if (R2D_Flush)
		R2D_Flush();
	inmenuprogs--;
	return G_FLOAT(OFS_RETURN);
}

static qboolean MP_JoystickAxis(menu_t *menu, unsigned int devid, int axis, float value)
{
	void *pr_globals;
	if (!menu_world.progs || !mpfuncs.inputevent)
		return false;
	if (setjmp(mp_abort))
		return false;
	inmenuprogs++;
	pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
	G_FLOAT(OFS_PARM0) = CSIE_JOYAXIS;
	G_FLOAT(OFS_PARM1) = axis;
	G_FLOAT(OFS_PARM2) = value;
	G_FLOAT(OFS_PARM3) = devid;
	PR_ExecuteProgram (menu_world.progs, mpfuncs.inputevent);
	if (R2D_Flush)
		R2D_Flush();
	inmenuprogs--;
	return G_FLOAT(OFS_RETURN);
}
static qboolean MP_KeyEvent(menu_t *menu, qboolean isdown, unsigned int devid, int key, int unicode)
{
	qboolean result;

#ifdef TEXTEDITOR
	if (editormodal)
		return false;
#endif

	if (setjmp(mp_abort))
		return true;

	if (isdown)
	{
#ifndef NOBUILTINMENUS
#ifdef _DEBUG
		if (key == 'c')
		{
			if ((keydown[K_LCTRL] || keydown[K_RCTRL]) && (keydown[K_LSHIFT] || keydown[K_RSHIFT]))
			{
				MP_Shutdown();
				M_Init_Internal();
				return true;
			}
		}
#endif
#endif

		mpkeysdown[key>>3] |= (1<<(key&7));
	}
	else
	{	//don't fire up events if it was not actually pressed.
		if (key && !(mpkeysdown[key>>3] & (1<<(key&7))))
			return false;
		mpkeysdown[key>>3] &= ~(1<<(key&7));
	}

	menutime = Sys_DoubleTime();
	if (menu_world.g.time)
		*menu_world.g.time = menutime;

	inmenuprogs++;
	if (mpfuncs.inputevent)
	{
		void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = isdown?CSIE_KEYDOWN:CSIE_KEYUP;
		G_FLOAT(OFS_PARM1) = MP_TranslateFTEtoQCCodes(key);
		G_FLOAT(OFS_PARM2) = unicode;
		G_FLOAT(OFS_PARM3) = devid;
		if (isdown)
		{
			qcinput_scan = G_FLOAT(OFS_PARM1);
			qcinput_unicode = G_FLOAT(OFS_PARM2);
		}
		PR_ExecuteProgram(menu_world.progs, mpfuncs.inputevent);
		result = G_FLOAT(OFS_RETURN);
		qcinput_scan = 0;
		qcinput_unicode = 0;
	}
	else if (isdown && mpfuncs.keydown)
	{
		void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = MP_TranslateFTEtoQCCodes(key);
		G_FLOAT(OFS_PARM1) = unicode;
		PR_ExecuteProgram(menu_world.progs, mpfuncs.keydown);
		result = true;	//doesn't have a return value, so if the menu is set up for key events, all events are considered eaten.
	}
	else if (!isdown && mpfuncs.keyup)
	{
		void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = MP_TranslateFTEtoQCCodes(key);
		G_FLOAT(OFS_PARM1) = unicode;
		PR_ExecuteProgram(menu_world.progs, mpfuncs.keyup);
		result = false; // doesn't have a return value, so don't block it
	}
	else
		result = false;
	inmenuprogs--;

	if (R2D_Flush)	//shouldn't be needed, but in case the mod is buggy.
		R2D_Flush();
	return result;
}

void MP_Shutdown (void)
{
	func_t temp;
	if (!menu_world.progs)
		return;

	menuqc.release = NULL; //don't notify
	Menu_Unlink(&menuqc, false);
/*
	{
		char *buffer;
		int size = 1024*1024*8;
		buffer = Z_Malloc(size);
		menuprogs->save_ents(menuprogs, buffer, &size, 1);
		COM_WriteFile("menucore.txt", buffer, size);
		Z_Free(buffer);
	}
*/
	temp = mpfuncs.shutdown;
	mpfuncs.shutdown = 0;
	if (temp && !inmenuprogs)
		PR_ExecuteProgram(menu_world.progs, temp);

	PR_Common_Shutdown(menu_world.progs, false);
	menu_world.progs->Shutdown(menu_world.progs);
	memset(&menu_world, 0, sizeof(menu_world));
	PR_ReleaseFonts(kdm_menu);

#ifdef CL_MASTER
	Master_ClearMasks();
#endif

	Cmd_RemoveCommands(MP_ConsoleCommand_f);

	Key_Dest_Remove(kdm_menu);
	key_dest_absolutemouse &= ~kdm_menu;
}

static void MP_TryRelease(menu_t *m, qboolean force)
{
	if (inmenuprogs)
		return;	//if the qc asked for it, the qc probably already knows about it. don't recurse.
	MP_Toggle(0);

	if (force && Menu_IsLinked(m))
	{
		Con_Printf(CON_ERROR"Menuqc retook key grabs when ordered to yield. Killing.\n");
		MP_Shutdown();
		M_Init_Internal();
	}
}

void *VARGS PR_CB_Malloc(int size);	//these functions should be tracked by the library reliably, so there should be no need to track them ourselves.
void VARGS PR_CB_Free(void *mem);

//Any menu builtin error or anything like that will come here.
void VARGS Menu_Abort (char *format, ...)
{
	va_list		argptr;
	char		string[1024];

	va_start (argptr, format);
	vsnprintf (string,sizeof(string)-1, format,argptr);
	va_end (argptr);

	Con_Printf("Menu_Abort: %s\nShutting down menu.dat\n", string);

	if (pr_menu_coreonerror.value)
	{
		char *buffer;
		size_t size = 1024*1024*8;
		buffer = Z_Malloc(size);
		menu_world.progs->save_ents(menu_world.progs, buffer, &size, size, 3);
		COM_WriteFile("menucore.txt", FS_GAMEONLY, buffer, size);
		Z_Free(buffer);
	}

	MP_Shutdown();
	M_Init_Internal();

	if (inmenuprogs)	//something in the menu caused the problem, so...
	{
		inmenuprogs = 0;
		longjmp(mp_abort, 1);
	}
}

void MP_CvarChanged(cvar_t *var)
{
	if (menu_world.progs)
	{
		PR_AutoCvar(menu_world.progs, var);
	}
}

pbool PDECL Menu_CheckHeaderCrc(pubprogfuncs_t *inst, progsnum_t idx, int crc, const char *filename)
{
	if (crc == 10020)
		return true;	//its okay
	Con_Printf("progs crc is invalid for %s\n", filename);
	if (crc == 12776)
	{	//whoever wrote wrath fucked up.
		Con_Printf("(please correct .src include orders)\n");
		return true;
	}
	return false;
}

static void *PDECL MP_PRReadFile (const char *path, qbyte *(PDECL *buf_get)(void *buf_ctx, size_t size), void *buf_ctx, size_t *size, pbool issource)
{
	flocation_t loc;
	if (FS_FLocateFile(path, FSLF_IFFOUND|FSLF_SECUREONLY, &loc))
	{
		qbyte *buffer = NULL;
		vfsfile_t *file = FS_OpenReadLocation(path, &loc);
		if (file)
		{
			*size = loc.len;
			buffer = buf_get(buf_ctx, *size);
			if (buffer)
				VFS_READ(file, buffer, *size);
			VFS_CLOSE(file);
		}
		return buffer;
	}
	else
	{
		if (FS_FLocateFile(path, FSLF_IFFOUND, &loc))
			Con_Printf("Not loading %s because it comes from an untrusted source\n", path);
		return NULL;
	}
}
static int PDECL MP_PRFileSize (const char *path)
{
	flocation_t loc;
	if (FS_FLocateFile(path, FSLF_IFFOUND|FSLF_SECUREONLY, &loc))
		return loc.len;
	else
		return -1;
}

qboolean MP_Init (void)
{
	struct key_cursor_s *m = &key_customcursor[kc_menuqc];

	if (qrenderer == QR_NONE)
	{
		return false;
	}

	if (forceqmenu.value)
	{
		Con_DPrintf("menu.dat disabled\n");
		return false;
	}

	MP_SetupBuiltins();

	memset(&menuc_eval, 0, sizeof(menuc_eval));


	menuprogparms.progsversion = PROGSTRUCT_VERSION;
	menuprogparms.ReadFile = MP_PRReadFile;//char *(*ReadFile) (char *fname, void *buffer, int *len);
	menuprogparms.FileSize = MP_PRFileSize;//int (*FileSize) (char *fname);	//-1 if file does not exist
	menuprogparms.WriteFile = QC_WriteFile;//bool (*WriteFile) (char *name, void *data, int len);
	menuprogparms.Printf = PR_Printf;//Con_Printf;//void (*printf) (char *, ...);
	menuprogparms.DPrintf = PR_DPrintf;//Con_DPrintf;//void (*dprintf) (char *, ...);
	menuprogparms.Sys_Error = Sys_Error;
	menuprogparms.Abort = Menu_Abort;
	menuprogparms.CheckHeaderCrc = Menu_CheckHeaderCrc;
	menuprogparms.edictsize = sizeof(menuedict_t);

	menuprogparms.entspawn = NULL;//void (*entspawn) (struct edict_s *ent);	//ent has been spawned, but may not have all the extra variables (that may need to be set) set
	menuprogparms.entcanfree = NULL;//bool (*entcanfree) (struct edict_s *ent);	//return true to stop ent from being freed
	menuprogparms.stateop = NULL;//StateOp;//void (*stateop) (float var, func_t func);
	menuprogparms.cstateop = NULL;//CStateOp;
	menuprogparms.cwstateop = NULL;//CWStateOp;
	menuprogparms.thinktimeop = NULL;//ThinkTimeOp;
	menuprogparms.MapNamedBuiltin = PR_Menu_MapNamedBuiltin;
	menuprogparms.loadcompleate = NULL;//void (*loadcompleate) (int edictsize);	//notification to reset any pointers.

	menuprogparms.memalloc = PR_CB_Malloc;//void *(*memalloc) (int size);	//small string allocation	malloced and freed randomly
	menuprogparms.memfree = PR_CB_Free;//void (*memfree) (void * mem);


	menuprogparms.globalbuiltins = menu_builtins;//builtin_t *globalbuiltins;	//these are available to all progs
	menuprogparms.numglobalbuiltins = sizeof(menu_builtins) / sizeof(menu_builtins[0]);

	menuprogparms.autocompile = PR_COMPILEIGNORE;//PR_COMPILEEXISTANDCHANGED;//enum {PR_NOCOMPILE, PR_COMPILENEXIST, PR_COMPILECHANGED, PR_COMPILEALWAYS} autocompile;

	menuprogparms.gametime = &menutime;
#ifdef MULTITHREAD
	menuprogparms.usethreadedgc = pr_gc_threaded.ival;
#endif

	menuprogparms.edicts = (struct edict_s **)&menu_edicts;
	menuprogparms.num_edicts = &num_menu_edicts;

	menuprogparms.useeditor = QCEditor;//void (*useeditor) (char *filename, int line, int nump, char **parms);
	menuprogparms.user = &menu_world;
	menu_world.keydestmask = kdm_menu;

	//default to free mouse+hidden cursor, to match dp's default setting, and because its generally the right thing for a menu.
	Q_strncpyz(m->name, "none", sizeof(m->name));
	m->hotspot[0] = 0;
	m->hotspot[1] = 0;
	m->scale = 1;
	m->dirty = true;

	menuqc.cursor = &key_customcursor[kc_menuqc];
	menuqc.drawmenu = NULL;		//menuqc sucks!
	menuqc.mousemove = MP_MouseMove;
	menuqc.keyevent = MP_KeyEvent;
	menuqc.joyaxis = MP_JoystickAxis;
	menuqc.release = MP_TryRelease;

	menutime = Sys_DoubleTime();
	if (!menu_world.progs)
	{
		int mprogs;
		Con_DPrintf("Initializing menu.dat\n");
		menu_world.progs = InitProgs(&menuprogparms);
		PR_Configure(menu_world.progs, PR_ReadBytesString(pr_menu_memsize.string), 1, pr_enable_profiling.ival);
		mprogs = PR_LoadProgs(menu_world.progs, "menu.dat");
		if (mprogs < 0) //no per-progs builtins.
		{
			//failed to load or something
//			CloseProgs(menu_world.progs);
//			menuprogs = NULL;
			return false;
		}
		if (setjmp(mp_abort))
		{
			Con_DPrintf("Failed to initialize menu.dat\n");
			inmenuprogs = false;
			return false;
		}
		inmenuprogs++;

		M_DeInit_Internal();

		PF_InitTempStrings(menu_world.progs);

		menu_world.g.self = (int*)PR_FindGlobal(menu_world.progs, "self", 0, NULL);
		menu_world.g.time = (float*)PR_FindGlobal(menu_world.progs, "time", 0, NULL);
		if (menu_world.g.time)
			*menu_world.g.time = Sys_DoubleTime();
		menu_world.g.frametime = (float*)PR_FindGlobal(menu_world.progs, "frametime", 0, NULL);

		menu_world.g.drawfont = (float*)PR_FindGlobal(menu_world.progs, "drawfont", 0, NULL);
		menu_world.g.drawfontscale = (float*)PR_FindGlobal(menu_world.progs, "drawfontscale", 0, NULL);

		PR_ProgsAdded(menu_world.progs, mprogs, "menu.dat");

		//ensure that there's space for these fields in.
		//other fields will always be referenced/defined by the qc, or 0.
		PR_RegisterFieldVar(menu_world.progs, ev_string, "model", -1, -1);
		PR_RegisterFieldVar(menu_world.progs, ev_vector, "origin", -1, -1);
		PR_RegisterFieldVar(menu_world.progs, ev_float, "skinobject", -1, -1);

		menuentsize = PR_InitEnts(menu_world.progs, 8192);


		//'world' edict
//		EDICT_NUM_PB(menu_world.progs, 0)->readonly = true;
		EDICT_NUM_PB(menu_world.progs, 0)->ereftype = ER_ENTITY;


		mpfuncs.init = PR_FindFunction(menu_world.progs, "m_init", PR_ANY);
		mpfuncs.shutdown = PR_FindFunction(menu_world.progs, "m_shutdown", PR_ANY);
		{
			int args = 0;
			qbyte *argsizes = NULL;
			mpfuncs.draw = PR_FindFunction(menu_world.progs, "m_draw", PR_ANY);
			menu_world.progs->GetFunctionInfo(menu_world.progs, mpfuncs.draw, &args, &argsizes, NULL, NULL, 0);
			mpfuncs.fuckeddrawsizes = (args == 2 && argsizes[0] == 1 && argsizes[1] == 1);
		}
		mpfuncs.drawloading = PR_FindFunction(menu_world.progs, "m_drawloading", PR_ANY);
		mpfuncs.inputevent = PR_FindFunction(menu_world.progs, "Menu_InputEvent", PR_ANY);
		mpfuncs.keydown = PR_FindFunction(menu_world.progs, "m_keydown", PR_ANY);
		mpfuncs.keyup = PR_FindFunction(menu_world.progs, "m_keyup", PR_ANY);
		mpfuncs.toggle = PR_FindFunction(menu_world.progs, "m_toggle", PR_ANY);
		mpfuncs.consolecommand = PR_FindFunction(menu_world.progs, "m_consolecommand", PR_ANY);
		mpfuncs.gethostcachecategory = PR_FindFunction(menu_world.progs, "m_gethostcachecategory", PR_ANY);
		mpfuncs.rendererrestarted = PR_FindFunction(menu_world.progs, "Menu_RendererRestarted", PR_ANY);
		if (mpfuncs.init)
			PR_ExecuteProgram(menu_world.progs, mpfuncs.init);
		inmenuprogs--;

		EDICT_NUM_PB(menu_world.progs, 0)->readonly = true;

		Con_DPrintf("Initialized menu.dat\n");
		return true;
	}
	return false;
}

static void MP_GameCommand_f(void)
{
	void *pr_globals;
	func_t gamecommand;
	if (!menu_world.progs)
		return;
	gamecommand = PR_FindFunction(menu_world.progs, "GameCommand", PR_ANY);
	if (!gamecommand)
		return;

	if (setjmp(mp_abort))
		return;
	inmenuprogs++;
	pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(menu_world.progs, Cmd_Args()));
	PR_ExecuteProgram (menu_world.progs, gamecommand);
	inmenuprogs--;
}

qboolean MP_ConsoleCommand(const char *cmdtext)
{
	void *pr_globals;
	if (!menu_world.progs)
		return false;
	if (!mpfuncs.consolecommand)
		return false;

	if (setjmp(mp_abort))
		return true;
	inmenuprogs++;
	pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(menu_world.progs, cmdtext));
	PR_ExecuteProgram (menu_world.progs, mpfuncs.consolecommand);
	inmenuprogs--;
	return G_FLOAT(OFS_RETURN);
}

void MP_CoreDump_f(void)
{
	if (!menu_world.progs)
	{
		Con_Printf("Can't core dump, you need to be running the CSQC progs first.");
		return;
	}

	{
		size_t size = 1024*1024*8;
		char *buffer = BZ_Malloc(size);
		menu_world.progs->save_ents(menu_world.progs, buffer, &size, size, 3);
		COM_WriteFile("menucore.txt", FS_GAMEONLY, buffer, size);
		BZ_Free(buffer);
	}
}

static void MP_Poke_f(void)
{
	/*if (!SV_MayCheat())
		Con_TPrintf ("Please set sv_cheats 1 and restart the map first.\n");
	else */if (menu_world.progs && menu_world.progs->EvaluateDebugString)
		Con_TPrintf("Result: %s\n", menu_world.progs->EvaluateDebugString(menu_world.progs, Cmd_Args()));
	else
		Con_TPrintf ("not supported.\n");
}

void MP_Breakpoint_f(void)
{
	int wasset;
	int isset;
	char *filename = Cmd_Argv(1);
	int line = atoi(Cmd_Argv(2));

	if (!menu_world.progs)
	{
		Con_Printf("Menu not running\n");
		return;
	}
	wasset = menu_world.progs->ToggleBreak(menu_world.progs, filename, line, 3);
	isset = menu_world.progs->ToggleBreak(menu_world.progs, filename, line, 2);

	if (wasset == isset)
		Con_Printf("Breakpoint was not valid\n");
	else if (isset)
		Con_Printf("Breakpoint has been set\n");
	else
		Con_Printf("Breakpoint has been cleared\n");

	Cvar_Set(Cvar_FindVar("pr_debugger"), "1");
}

void MP_RegisterCvarsAndCmds(void)
{
	Cmd_AddCommand("coredump_menuqc", MP_CoreDump_f);
	Cmd_AddCommand("menu_cmd", MP_GameCommand_f);
	Cmd_AddCommand("breakpoint_menu", MP_Breakpoint_f);
#ifdef HAVE_LEGACY
	Cmd_AddCommand("loadfont", CL_LoadFont_f);
#endif

	Cmd_AddCommand("poke_menuqc", MP_Poke_f);


	Cvar_Register(&forceqmenu, MENUPROGSGROUP);
	Cvar_Register(&pr_menu_coreonerror, MENUPROGSGROUP);
	Cvar_Register(&pr_menu_memsize, MENUPROGSGROUP);
}

qboolean MP_UsingGamecodeLoadingScreen(void)
{
	return menu_world.progs && mpfuncs.drawloading;
}

int MP_GetServerCategory(int index)
{
	int category = 0;
	if (menu_world.progs && mpfuncs.gethostcachecategory)
	{
		void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		if (!setjmp(mp_abort))
		{
			inmenuprogs++;
			G_FLOAT(OFS_PARM0) = index;
			PR_ExecuteProgram(menu_world.progs, mpfuncs.gethostcachecategory);
			category = G_FLOAT(OFS_RETURN);
			inmenuprogs--;
		}
	}
	return category;
}

void MP_RendererRestarted(void)
{
	int i;
	if (!menu_world.progs)
		return;

	menu_world.worldmodel = cl.worldmodel;

	for (i = 0; i < MAX_CSMODELS; i++)
	{
		cl.model_csqcprecache[i] = NULL;
	}

	//FIXME: registered shaders

	//let the csqc know that its rendertargets got purged
	if (mpfuncs.rendererrestarted)
	{
		void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(menu_world.progs, rf->description));
		PR_ExecuteProgram(menu_world.progs, mpfuncs.rendererrestarted);
	}
	//in case it drew to any render targets.
	if (R2D_Flush)
		R2D_Flush();
	if (*r_refdef.rt_destcolour[0].texname)
	{
		Q_strncpyz(r_refdef.rt_destcolour[0].texname, "", sizeof(r_refdef.rt_destcolour[0].texname));
		BE_RenderToTextureUpdate2d(true);
	}
}

void MP_Draw(void)
{
	extern qboolean scr_drawloading;
	globalvars_t *pr_globals;
	if (!menu_world.progs)
		return;
	if (setjmp(mp_abort))
		return;

	menutime = Sys_DoubleTime();
	if (menu_world.g.time)
		*menu_world.g.time = menutime;
	if (menu_world.g.frametime)
		*menu_world.g.frametime = host_frametime;

	inmenuprogs++;
	pr_globals = PR_globals(menu_world.progs, PR_CURRENT);

	if (scr_drawloading||scr_disabled_for_loading)
	{	//don't draw the menu if we're meant to be drawing a loading screen
		//the menu should provide a special function if it wants to draw custom loading screens. this is for compat with old/dp/lazy/crappy menus.
		if (mpfuncs.drawloading)
		{
			((float *)pr_globals)[OFS_PARM0+0] = vid.width;
			((float *)pr_globals)[OFS_PARM0+1] = vid.height;
			((float *)pr_globals)[OFS_PARM0+2] = 0;

			((float *)pr_globals)[OFS_PARM1] = scr_disabled_for_loading;
			PR_ExecuteProgram(menu_world.progs, mpfuncs.drawloading);
		}
	}
	else if (mpfuncs.draw)
	{
		if (mpfuncs.fuckeddrawsizes)
		{	//pass useless sizes in two args if its a dp menu
			((float *)pr_globals)[OFS_PARM0] = vid.pixelwidth;
			((float *)pr_globals)[OFS_PARM0+1] = 0;	//make sure its set, just in case...
			((float *)pr_globals)[OFS_PARM0+2] = 0;
			((float *)pr_globals)[OFS_PARM1] = vid.pixelheight;
			((float *)pr_globals)[OFS_PARM1+1] = 0;
			((float *)pr_globals)[OFS_PARM1+2] = 0;
		}
		else
		{	//pass useful sizes in a 1-arg vector if its an fte menu.
			((float *)pr_globals)[OFS_PARM0+0] = vid.width;
			((float *)pr_globals)[OFS_PARM0+1] = vid.height;
			((float *)pr_globals)[OFS_PARM0+2] = 0;

			//make physical pixel counts available too, because we can.
			((float *)pr_globals)[OFS_PARM1+0] = vid.pixelwidth;
			((float *)pr_globals)[OFS_PARM1+1] = vid.pixelheight;
			((float *)pr_globals)[OFS_PARM1+2] = 0;
		}

		PR_ExecuteProgram(menu_world.progs, mpfuncs.draw);
	}
	inmenuprogs--;
}

qboolean MP_Toggle(int mode)
{
	if (!menu_world.progs)
		return false;
#ifdef TEXTEDITOR
	if (editormodal)
		return false;
#endif

	if (!mode && !Key_Dest_Has(kdm_menu))
		return false;

	if (setjmp(mp_abort))
		return false;

	menutime = Sys_DoubleTime();
	if (menu_world.g.time)
		*menu_world.g.time = menutime;

	inmenuprogs++;
	if (mpfuncs.toggle)
	{
		void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = mode;
		PR_ExecuteProgram(menu_world.progs, mpfuncs.toggle);
	}
	if (R2D_Flush)
		R2D_Flush();
	inmenuprogs--;

	return true;
}
#endif
