/*
Copyright (C) 2011 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "quakedef.h"

/*

  EXT_CSQC is the 'root' extension
  EXT_CSQC_1 are a collection of additional features to cover omissions in the original spec

  simplecsqc lacks CSQC_UpdateView and has CSQC_DrawHud+CSQC_DrawScores instead.
  if we're running arbitrary csqc, we block things that require too much game interaction...
*/

#ifdef CSQC_DAT

#include "glquake.h"	//evil to include this
#include "shader.h"

#include "pr_common.h"

extern usercmd_t cl_pendingcmd[MAX_SPLITS];
extern cvar_t sv_demo_write_csqc;


static pubprogfuncs_t *csqcprogs;

typedef struct csqctreadstate_s {
	float resumetime;
	struct qcthread_s *thread;
	int self;
	int other;

	struct csqctreadstate_s *next;
} csqctreadstate_t;

static qboolean csprogs_promiscuous;
static unsigned int csprogs_checksum;
static size_t csprogs_checksize;
static char csprogs_checkname[MAX_QPATH];
static csqctreadstate_t *csqcthreads;
qboolean csqc_resortfrags;
world_t csqc_world;

float csqc_starttime;	//reset on each csqc reload to restore lost precision of cltime on each map restart.

int	csqc_playerseat;	//can be negative.
static playerview_t *csqc_playerview;
qboolean csqc_dp_lastwas3d;	//to emulate DP correctly, we need to track whether drawpic/drawfill or clearscene was called last. blame 515.
#ifndef HAVE_LEGACY
#define csqc_isdarkplaces false	//hopefully this will allow a smart enough compiler to optimise it out cleanly
#else
static qboolean csqc_isdarkplaces;
#endif
static qboolean csqc_nogameaccess;	/*the module is not trusted by the server, so isn't allowed to access origins+stuffcmds+etc*/
static qboolean csqc_singlecheats;	/*single player or cheats active, allowing custom addons*/
static qboolean csqc_mayread;		/*csqc is allowed to ReadByte();*/
static qboolean csqc_worldchanged;	/*make sure any caches are rebuilt properly before the next renderscene*/

static char csqc_printbuffer[8192];

#define CSQCPROGSGROUP "CSQC progs control"
cvar_t	pr_csqc_maxedicts = CVAR("pr_csqc_maxedicts", "65536");	//not tied to protocol nor server. can be set arbitrarily high, except for memory allocations.
cvar_t	pr_csqc_memsize = CVAR("pr_csqc_memsize", "-1");
cvar_t	cl_csqcdebug = CVAR("cl_csqcdebug", "0");	//prints entity numbers which arrive (so I can tell people not to apply it to players...)
static cvar_t	cl_csqc_nodeprecate = CVARAD("cl_csqc_nodeprecate", "0", "dpcompat_nocsqcwarnings", "When set, disables deprecation warnings.");
cvar_t  cl_nocsqc = CVAR("cl_nocsqc", "0");
cvar_t  pr_csqc_coreonerror = CVAR("pr_csqc_coreonerror", "1");
#if defined(NOBUILTINMENUS) && !defined(MENU_DAT)
cvar_t  pr_csqc_formenus = CVARF("pr_csqc_formenus", "1", CVAR_NOSET);
#else
cvar_t  pr_csqc_formenus = CVAR("pr_csqc_formenus", "0");
#endif
static cvar_t	dpcompat_csqcinputeventtypes = CVARD("dpcompat_csqcinputeventtypes", "999999", "Specifies the first csqc input event that the mod does not recognise. This should never have been a thing, but some mods are simply too buggy.");
extern cvar_t dpcompat_stats;

// standard effect cvars/sounds
extern cvar_t r_explosionlight;
extern sfx_t			*cl_sfx_wizhit;
extern sfx_t			*cl_sfx_knighthit;
extern sfx_t			*cl_sfx_tink1;
extern sfx_t			*cl_sfx_ric1;
extern sfx_t			*cl_sfx_ric2;
extern sfx_t			*cl_sfx_ric3;
extern sfx_t			*cl_sfx_r_exp3;

#define ENDLIST	//clarifies \ in list macros.
#ifndef HAVE_LEGACY
#define legacycsqcglobals
#else
#define legacycsqcglobals	\
	globalstring(trace_dphittexturename,	"trace_dphittexturename");			/*for dp compat*/	\
	globalfloat(trace_dpstartcontents,	"trace_dpstartcontents");			/*for dp compat*/	\
	globalfloat(trace_dphitcontents,	"trace_dphitcontents");				/*for dp compat*/	\
	globalfloat(trace_dphitq3surfaceflags,	"trace_dphitq3surfaceflags");	/*for dp compat*/	\
	globalfloat(trace_surfaceflagsf,	"trace_surfaceflagsf");				/*float		written by traceline, for mods that lack ints*/	\
	globalfloat(trace_endcontentsf,		"trace_endcontentsf");				/*float		written by traceline EXT_CSQC_1, for mods that lack ints*/	\
	ENDLIST
#endif

//If I do it like this, I'll never forget to register something...
#define csqcglobals	\
	globalfunction(init_function,		"CSQC_Init");	\
	globalfunction(worldloaded,			"CSQC_WorldLoaded");	\
	globalfunction(shutdown_function,	"CSQC_Shutdown");	\
	globalfunction(f_updateview,		"CSQC_UpdateView");	\
	globalfunction(f_updateviewloading,	"CSQC_UpdateViewLoading");	\
	globalfunction(f_drawhud,			"CSQC_DrawHud");/*simple csqc*/	\
	globalfunction(f_drawscores,		"CSQC_DrawScores");/*simple csqc*/	\
	globalfunction(parse_stuffcmd,		"CSQC_Parse_StuffCmd");	\
	globalfunction(parse_centerprint,	"CSQC_Parse_CenterPrint");	\
	globalfunction(parse_print,			"CSQC_Parse_Print");	\
	globalfunction(parse_event,			"CSQC_Parse_Event");	\
	globalfunction(parse_damage,		"CSQC_Parse_Damage");	\
	globalfunction(parse_setangles,		"CSQC_Parse_SetAngles");	\
	globalfunction(playerinfochanged,	"CSQC_PlayerInfoChanged");	\
	globalfunction(serverinfochanged,	"CSQC_ServerInfoChanged");	\
	globalfunction(input_event,			"CSQC_InputEvent");	\
	globalfunction(input_frame,			"CSQC_Input_Frame");/*EXT_CSQC_1*/	\
	globalfunction(rendererrestarted,	"CSQC_RendererRestarted");	\
	globalfunction(console_command,		"CSQC_ConsoleCommand");	\
	globalfunction(console_link,		"CSQC_ConsoleLink");	\
	globalfunction(gamecommand,			"GameCommand");	/*DP extension*/\
	\
	globalfunction(ent_spawn,			"CSQC_Ent_Spawn");	\
	globalfunction(ent_update,			"CSQC_Ent_Update");	\
	globalfunction(ent_remove,			"CSQC_Ent_Remove");	\
	\
	globalfunction(event_sound,			"CSQC_Event_Sound");	\
	globalfunction(serversound,			"CSQC_ServerSound");/*obsolete, use event_sound*/	\
	/*globalfunction(loadresource,		"CSQC_LoadResource");*//*EXT_CSQC_1*/	\
	globalfunction(parse_tempentity,	"CSQC_Parse_TempEntity");/*EXT_CSQC_ABSOLUTLY_VILE*/	\
	\
	globalfunction(mapentityedited,		"CSQC_MapEntityEdited");\
	\
	/*These are pointers to the csqc's globals.*/	\
	globalfloat(simtime,				"time");				/*float		The simulation(aka: smoothed server) time, speed drifts based upon latency*/	\
	globalfloat(frametime,				"frametime");			/*float		Client render frame interval*/	\
	globalfloat(gamespeed,				"gamespeed");			/*float		Multiplier for real time -> simulation time*/	\
	globalfloat(cltime,					"cltime");				/*float		Clientside map uptime indepent of gamespeed, latency, and the server in general*/	\
	globalfloat(clframetime,			"clframetime");			/*float		time since last video frame*/	\
	globalfloat(netnewtime,				"servertime");			/*float		Server time of latest inbound network frame*/	\
	globalfloat(netoldtime,				"serverprevtime");		/*float		Server time of previous inbound network frame */	\
	globalfloat(netdeltatime,			"serverdeltatime");		/*float		new-old */	\
	globalfloat(physics_mode,			"physics_mode");		/*float		Written before entering most qc functions*/	\
	globalentity(self,					"self");				/*entity	Written before entering most qc functions*/	\
	globalentity(other,					"other");				/*entity	Written before entering most qc functions*/	\
	\
	globalentity(deathmatch,			"deathmatch");			/*for simplecsqc*/	\
	globalentity(coop,					"coop");				/*for simplecsqc*/	\
	\
	globalfloat(maxclients,				"maxclients");			/*float		max number of players allowed*/	\
	globalfloat(numclientseats,			"numclientseats");		/*float		number of seats/splitscreen clients running on this client*/	\
	\
	globalvector(forward,				"v_forward");			/*vector	written by anglevectors*/	\
	globalvector(right,					"v_right");				/*vector	written by anglevectors*/	\
	globalvector(up,					"v_up");				/*vector	written by anglevectors*/	\
	\
	globalfloat(trace_allsolid,			"trace_allsolid");		/*bool		written by traceline*/	\
	globalfloat(trace_startsolid,		"trace_startsolid");	/*bool		written by traceline*/	\
	globalfloat(trace_fraction,			"trace_fraction");		/*float		written by traceline*/	\
	globalfloat(trace_inwater,			"trace_inwater");		/*bool		written by traceline*/	\
	globalfloat(trace_inopen,			"trace_inopen");		/*bool		written by traceline*/	\
	globalvector(trace_endpos,			"trace_endpos");		/*vector	written by traceline*/	\
	globalvector(trace_plane_normal,	"trace_plane_normal");	/*vector	written by traceline*/	\
	globalfloat(trace_plane_dist,		"trace_plane_dist");	/*float		written by traceline*/	\
	globalentity(trace_ent,				"trace_ent");			/*entity	written by traceline*/	\
	globalint(trace_surfaceflagsi,		"trace_surfaceflagsi");	/*int		written by traceline*/	\
	globalstring(trace_surfacename,		"trace_surfacename");	/*string	written by traceline*/	\
	globalint(trace_endcontentsi,		"trace_endcontentsi");	/*int		written by traceline EXT_CSQC_1*/	\
	globalint(trace_brush_id,			"trace_brush_id");		/*int		written by traceline*/	\
	globalint(trace_brush_faceid,		"trace_brush_faceid");	/*int		written by traceline*/	\
	globalint(trace_surface_id,			"trace_surface_id");	/*int		written by traceline*/	\
	globalint(trace_bone_id,			"trace_bone_id");		/*int		written by traceline*/	\
	globalint(trace_triangle_id,		"trace_triangle_id");	/*int		written by traceline*/	\
	globalfloat(trace_networkentity,	"trace_networkentity");	/*float		written by traceline*/	\
	legacycsqcglobals \
	\
	globalfloat(clientcommandframe,		"clientcommandframe");	/*float		the next frame that will be sent*/ \
	globalfloat(servercommandframe,		"servercommandframe");	/*float		the most recent frame received from the server*/ \
	\
	globalfloat(player_localentnum,		"player_localentnum");	/*float		the entity number the local player is looking out from*/	\
	globalfloat(player_localnum,		"player_localnum");		/*float		the player number of the local player*/	\
	globalfloat(intermission,			"intermission");		/*float		set when the client receives svc_intermission*/	\
	globalfloat(intermission_time,		"intermission_time");	/*float		set when the client receives svc_intermission*/	\
	globalvector(view_angles,			"view_angles");			/*float		set to the view angles at the start of each new frame (EXT_CSQC_1)*/ \
	\
	globalvector(pmove_org,				"pmove_org");			/*deprecated. read/written by runplayerphysics*/ \
	globalvector(pmove_vel,				"pmove_vel");			/*deprecated. read/written by runplayerphysics*/ \
	globalvector(pmove_mins,			"pmove_mins");			/*deprecated. read/written by runplayerphysics*/ \
	globalvector(pmove_maxs,			"pmove_maxs");			/*deprecated. read/written by runplayerphysics*/ \
	globalfloat(pmove_jump_held,		"pmove_jump_held");		/*deprecated. read/written by runplayerphysics*/ \
	globalfloat(pmove_waterjumptime,	"pmove_waterjumptime");	/*deprecated. read/written by runplayerphysics*/ \
	globalfloat(pmove_onground,			"pmove_onground");		/*deprecated. read/written by runplayerphysics*/ \
	\
	globalfloat(input_timelength,		"input_timelength");	/*float		filled by getinputstate, read by runplayerphysics*/ \
	globalvector(input_angles,			"input_angles");		/*vector	filled by getinputstate, read by runplayerphysics*/ \
	globalvector(input_movevalues,		"input_movevalues");	/*vector	filled by getinputstate, read by runplayerphysics*/ \
	globalfloat(input_buttons,			"input_buttons");		/*float		filled by getinputstate, read by runplayerphysics*/ \
	globalfloat(input_impulse,			"input_impulse");		/*float		filled by getinputstate, read by runplayerphysics*/ \
	globalfloat(input_lightlevel,		"input_lightlevel");	/*unused float		filled by getinputstate, read by runplayerphysics*/ \
	globalfloat(input_weapon,			"input_weapon");		/*unused float		filled by getinputstate, read by runplayerphysics*/ \
	globalfloat(input_servertime,		"input_servertime");	/*float		filled by getinputstate, read by runplayerphysics*/ \
	globalfloat(input_clienttime,		"input_clienttime");	/*float		filled by getinputstate, read by runplayerphysics*/ \
	globalvector(input_cursor_screen,	"input_cursor_screen");	/*float		filled by getinputstate*/ \
	globalvector(input_cursor_start,	"input_cursor_trace_start");	/*float		filled by getinputstate*/ \
	globalvector(input_cursor_impact,	"input_cursor_trace_endpos");	/*float		filled by getinputstate*/ \
	globalfloat(input_cursor_entitynumber,	"input_cursor_entitynumber");	/*float		filled by getinputstate*/ \
	\
	globalvector(global_gravitydir,		"global_gravitydir");	/*vector	used when .gravitydir is 0 0 0 */ \
	globalfloat(dimension_default,		"dimension_default");	/*float		default value for dimension_hit+dimension_solid*/ \
	globalfloat(autocvar_vid_conwidth,	"autocvar_vid_conwidth");	/*float		hackfix for dp mods*/	\
	globalfloat(autocvar_vid_conheight,	"autocvar_vid_conheight");	/*float		hackfix for dp mods*/	\
	globalfloat(cycle_wrapped,			"cycle_wrapped");	\
	ENDLIST

typedef struct {
#define globalfloat(name,qcname) float *name
#define globalint(name,qcname) int *name
#define globalvector(name,qcname) float *name
#define globalentity(name,qcname) int *name
#define globalstring(name,qcname) string_t *name
#define globalfunction(name,qcname) func_t name
//These are the functions the engine will call to, found by name.

	csqcglobals

#undef globalfloat
#undef globalint
#undef globalvector
#undef globalentity
#undef globalstring
#undef globalfunction
} csqcglobals_t;
static csqcglobals_t csqcg;

playerview_t csqc_nullview;

static void VARGS CSQC_Abort (char *format, ...);	//an error occured.
static void cs_set_input_state (usercmd_t *cmd);

//fixme: we should be using entity numbers, not view numbers.
static void CSQC_ChangeLocalPlayer(int seat)
{
	if (seat < 0 || seat >= MAX_SPLITS)
	{
		csqc_playerseat = -1;
		csqc_playerview = &csqc_nullview;
	}
	else
	{
		csqc_playerseat = seat;
		csqc_playerview = &cl.playerview[seat];
	}
	if (csqcg.player_localentnum)
	{
		if (csqc_playerview->viewentity)
			*csqcg.player_localentnum = csqc_playerview->viewentity;
		else if (csqc_playerview->spectator && Cam_TrackNum(csqc_playerview) >= 0)
			*csqcg.player_localentnum = Cam_TrackNum(csqc_playerview) + 1;
		else if (csqc_playerview == &csqc_nullview)
			*csqcg.player_localentnum = 0;
		else
			*csqcg.player_localentnum = csqc_playerview->playernum+1;
	}
	if (csqcg.player_localnum)
		*csqcg.player_localnum = csqc_playerview->playernum;

	if (csqc_nogameaccess)
		return;	//don't give much info otherwise.

	if (csqcg.view_angles)
	{
		csqcg.view_angles[0] = csqc_playerview->viewangles[0];
		csqcg.view_angles[1] = csqc_playerview->viewangles[1];
		csqcg.view_angles[2] = csqc_playerview->viewangles[2];
	}
	if ((unsigned int)seat < MAX_SPLITS)
	{
//		int i;
		usercmd_t *cmd = &cl_pendingcmd[seat];
//		usercmd_t tmp;
//		for (i=0 ; i<3 ; i++)
//			cmd->angles[i] = ((int)(csqc_playerview->viewangles[i]*65536.0/360)&65535);
//		if (!cmd->msec)
//			CL_BaseMove (cmd, seat, cmd->msec, newtime);
//		tmp = *cmd;
//		cmd = &tmp;
//		cmd->msec = (realtime - cl.outframes[(cl.movesequence-1)&UPDATE_MASK].senttime)*1000;
		cs_set_input_state(cmd);

		if (csqcg.pmove_org)
			VectorCopy(csqc_playerview->simorg, csqcg.pmove_org);
		if (csqc_isdarkplaces)
		{	//dp mods tend to require these to be totally unlerped
			if (csqcg.pmove_vel)
				VectorCopy(cl.inframes[cl.validsequence&UPDATE_MASK].playerstate[csqc_playerview->playernum].velocity, csqcg.pmove_vel);
			if (csqcg.pmove_onground)
				*csqcg.pmove_onground = cl.inframes[cl.validsequence&UPDATE_MASK].playerstate[csqc_playerview->playernum].onground;
		}
		else
		{
			if (csqcg.pmove_vel)
				VectorCopy(csqc_playerview->simvel, csqcg.pmove_vel);
			if (csqcg.pmove_onground)
				*csqcg.pmove_onground = csqc_playerview->onground;
		}
	}
}

static void CSQC_FindGlobals(qboolean nofuncs)
{
	static eval_t junk;
	static float csphysicsmode = 0;
	static float dimension_default = 255;
	static vec3_t defaultgravity = {0, 0, -1};
#define globalfloat(name,qcname) csqcg.name = (float*)PR_FindGlobal(csqcprogs, qcname, 0, NULL);
#define globalint(name,qcname) csqcg.name = (int*)PR_FindGlobal(csqcprogs, qcname, 0, NULL);
#define globalvector(name,qcname) csqcg.name = (float*)PR_FindGlobal(csqcprogs, qcname, 0, NULL);
#define globalentity(name,qcname) csqcg.name = (int*)PR_FindGlobal(csqcprogs, qcname, 0, NULL);
#define globalstring(name,qcname) csqcg.name = (string_t*)PR_FindGlobal(csqcprogs, qcname, 0, NULL);
#define globalfunction(name,qcname) csqcg.name = nofuncs?0:PR_FindFunction(csqcprogs,qcname,PR_ANY);

	csqcglobals

#undef globalfloat
#undef globalint
#undef globalvector
#undef globalentity
#undef globalstring
#undef globalfunction

#define ensurefloat(name)	if (!csqcg.name) csqcg.name = &junk._float;
#define ensureint(name)		if (!csqcg.name) csqcg.name = &junk._int;
#define ensurevector(name)	if (!csqcg.name) csqcg.name = junk._vector;
#define ensureentity(name)	if (!csqcg.name) csqcg.name = &junk.edict;

	if (csqc_nogameaccess)
	{
		csqcg.f_updateview = 0;	//would fail
		csqcg.f_updateviewloading = 0;	//would fail
		csqcg.parse_stuffcmd = 0;	//could block cvar changes, thus allow cheats
		csqcg.parse_setangles = 0;	//too evil
		csqcg.input_frame = 0;	//no aimbot writing
		csqcg.event_sound = csqcg.serversound = 0; //no sound snooping
		csqcg.parse_tempentity = 0; //compat nightmare
		csqcg.view_angles = NULL;
		csqcg.physics_mode = NULL;	//no thinks stuff
		csqcg.pmove_org = NULL;	//can't make aimbots if you don't know where you're aiming from.
		csqcg.pmove_vel = NULL;	//no dead reckoning please
		csqcg.pmove_mins = csqcg.pmove_maxs = csqcg.pmove_jump_held = csqcg.pmove_waterjumptime = csqcg.pmove_onground = NULL; //I just want to kill theses
		csqcg.input_angles = csqcg.input_movevalues = csqcg.input_buttons = csqcg.input_impulse = csqcg.input_lightlevel = csqcg.input_weapon = csqcg.input_servertime = NULL;
		csqcg.input_clienttime = csqcg.input_cursor_screen = csqcg.input_cursor_start = csqcg.input_cursor_impact = csqcg.input_cursor_entitynumber = NULL;
	}
	else if (csqcg.f_updateview || csqcg.f_updateviewloading)
	{	//full csqc AND simplecsqc's entry points at the same time are a bad idea that just result in confusion.
		//full csqc mods should just disable engine hud drawing
		csqcg.f_drawhud = 0;
		csqcg.f_drawscores = 0;
	}

#ifndef HAVE_LEGACY
	{
		etype_t etype = ev_void;
		if (!csqcg.trace_surfaceflagsi)
			csqcg.trace_surfaceflagsi = (int*)PR_FindGlobal(csqcprogs, "trace_surfaceflags", 0, &etype);
		if (!csqcg.trace_endcontentsi)
			csqcg.trace_endcontentsi = (int*)PR_FindGlobal(csqcprogs, "trace_endcontents", 0, &etype);
	}
#else
	if (!csqcg.trace_surfaceflagsf && !csqcg.trace_surfaceflagsi)
	{
		etype_t etype = ev_void;
		eval_t *v = PR_FindGlobal(csqcprogs, "trace_surfaceflags", 0, &etype);
		if (etype == ev_float)
			csqcg.trace_surfaceflagsf = &v->_float;
		else if (etype == ev_integer)
			csqcg.trace_surfaceflagsi = &v->_int;
	}
	if (!csqcg.trace_endcontentsf && !csqcg.trace_endcontentsi)
	{
		etype_t etype = ev_void;
		eval_t *v = PR_FindGlobal(csqcprogs, "trace_endcontents", 0, &etype);
		if (etype == ev_float)
			csqcg.trace_endcontentsf = &v->_float;
		else if (etype == ev_integer)
			csqcg.trace_endcontentsi = &v->_int;
	}
	ensurefloat(trace_surfaceflagsf);
	ensurefloat(trace_endcontentsf);
#endif

	ensurefloat(trace_allsolid);
	ensurefloat(trace_startsolid);
	ensurefloat(trace_fraction);
	ensurefloat(trace_inwater);
	ensurefloat(trace_inopen);
	ensurevector(trace_endpos);
	ensurevector(trace_plane_normal);
	ensurefloat(trace_plane_dist);
	ensureint(trace_surfaceflagsi);
	ensureint(trace_endcontentsi);
	ensureint(trace_brush_id);
	ensureint(trace_brush_faceid);
	ensureint(trace_surface_id);
	ensureint(trace_bone_id);
	ensureint(trace_triangle_id);
	ensurefloat(trace_networkentity);
	ensureentity(trace_ent);


	if (csqcg.simtime)
		*csqcg.simtime = cl.servertime;
	if (csqcg.cltime)
		*csqcg.cltime = realtime-csqc_starttime;

	if (!csqcg.global_gravitydir)
		csqcg.global_gravitydir = defaultgravity;

	CSQC_ChangeLocalPlayer(cl_forceseat.ival?(cl_forceseat.ival - 1) % cl.splitclients:0);

	csqc_world.g.self = csqcg.self;
	csqc_world.g.other = csqcg.other;
	csqc_world.g.force_retouch = (float*)PR_FindGlobal(csqcprogs, "force_retouch", 0, NULL);
	csqc_world.g.physics_mode = csqcg.physics_mode;
	csqc_world.g.frametime = csqcg.frametime;
	csqc_world.g.newmis = (int*)PR_FindGlobal(csqcprogs, "newmis", 0, NULL);
	csqc_world.g.time = csqcg.simtime;
	csqc_world.g.v_forward = csqcg.forward;
	csqc_world.g.v_right = csqcg.right;
	csqc_world.g.v_up = csqcg.up;
	csqc_world.g.defaultgravitydir = csqcg.global_gravitydir;
	csqc_world.g.drawfont = (float*)PR_FindGlobal(csqcprogs, "drawfont", 0, NULL);
	csqc_world.g.drawfontscale = (float*)PR_FindGlobal(csqcprogs, "drawfontscale", 0, NULL);

	if (!csqc_world.g.physics_mode)
	{
		csphysicsmode = 0;	/*note: dp handles think functions as part of addentity rather than elsewhere. if we're in a compat mode, we don't want to have to duplicate work*/
		csqc_world.g.physics_mode = &csphysicsmode;
	}

	if (!csqcg.dimension_default)
		csqcg.dimension_default = &dimension_default;

	if (csqcg.maxclients)
		*csqcg.maxclients = cl.allocated_client_slots;
}



//note: doesn't even have to match the clprogs.dat :)
typedef struct {

#define comfieldfloat(csqcname,desc) float csqcname;
#define comfieldvector(csqcname,desc) vec3_t csqcname;
#define comfieldentity(csqcname,desc) int csqcname;
#define comfieldstring(csqcname,desc) string_t csqcname;
#define comfieldfunction(csqcname, typestr,desc) func_t csqcname;
comqcfields
#undef comfieldfloat
#undef comfieldvector
#undef comfieldentity
#undef comfieldstring
#undef comfieldfunction

#ifdef VM_Q1
} csqcentvars_t;
typedef struct {
#endif

#define comfieldfloat(name,desc) float name;
#define comfieldint(name,desc) int name;
#define comfieldvector(name,desc) vec3_t name;
#define comfieldentity(name,desc) int name;
#define comfieldstring(name,desc) string_t name;
#define comfieldfunction(name, typestr,desc) func_t name;
comextqcfields
csqcextfields
#undef comfieldfloat
#undef comfieldint
#undef comfieldvector
#undef comfieldentity
#undef comfieldstring
#undef comfieldfunction

#ifdef VM_Q1
} csqcextentvars_t;
#else
} csqcentvars_t;
#endif

typedef struct csqcedict_s
{
	enum ereftype_e	ereftype;
	float			freetime; // sv.time when the object was freed
	int				entnum;
	unsigned int	fieldsize;
	pbool			readonly;	//world
#ifdef VM_Q1
	csqcentvars_t	*v;
	csqcextentvars_t	*xv;
#else
	union {
		csqcentvars_t	*v;
		csqcentvars_t	*xv;
	};
#endif
	/*the above is shared with qclib*/
#ifdef USEAREAGRID
	areagridlink_t	gridareas[AREAGRIDPERENT];	//on overflow, use the inefficient overflow list.
	size_t			gridareasequence;	//used to avoid iterrating the same ent twice.
#else
	link_t	area;
#endif
	pvscache_t pvsinfo;
	int lastruntime;
	int solidsize;
#ifdef USERBE
	entityrbe_t rbe;
#endif
	/*the above is shared with ssqc*/

	//add whatever you wish here
	trailstate_t *trailstate;
	int		skinobject;
} csqcedict_t;


static void CSQC_InitFields(void)
{	//CHANGING THIS FUNCTION REQUIRES CHANGES TO csqcentvars_t
#define comfieldfloat(name,desc) PR_RegisterFieldVar(csqcprogs, ev_float, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldint(name,desc) PR_RegisterFieldVar(csqcprogs, ev_integer, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldvector(name,desc) PR_RegisterFieldVar(csqcprogs, ev_vector, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldentity(name,desc) PR_RegisterFieldVar(csqcprogs, ev_entity, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldstring(name,desc) PR_RegisterFieldVar(csqcprogs, ev_string, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldfunction(name, typestr,desc) PR_RegisterFieldVar(csqcprogs, ev_function, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
comqcfields
#undef comfieldfloat
#undef comfieldint
#undef comfieldvector
#undef comfieldentity
#undef comfieldstring
#undef comfieldfunction

#ifdef VM_Q1
#define comfieldfloat(name,desc) PR_RegisterFieldVar(csqcprogs, ev_float, #name, sizeof(csqcentvars_t) + (size_t)&((csqcextentvars_t*)0)->name, -1);
#define comfieldint(name,desc) PR_RegisterFieldVar(csqcprogs, ev_integer, #name, sizeof(csqcentvars_t) + (size_t)&((csqcextentvars_t*)0)->name, -1);
#define comfieldvector(name,desc) PR_RegisterFieldVar(csqcprogs, ev_vector, #name, sizeof(csqcentvars_t) + (size_t)&((csqcextentvars_t*)0)->name, -1);
#define comfieldentity(name,desc) PR_RegisterFieldVar(csqcprogs, ev_entity, #name, sizeof(csqcentvars_t) + (size_t)&((csqcextentvars_t*)0)->name, -1);
#define comfieldstring(name,desc) PR_RegisterFieldVar(csqcprogs, ev_string, #name, sizeof(csqcentvars_t) + (size_t)&((csqcextentvars_t*)0)->name, -1);
#define comfieldfunction(name, typestr,desc) PR_RegisterFieldVar(csqcprogs, ev_function, #name, sizeof(csqcentvars_t) + (size_t)&((csqcextentvars_t*)0)->name, -1);
#else
#define comfieldfloat(name,desc) PR_RegisterFieldVar(csqcprogs, ev_float, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldint(name,desc) PR_RegisterFieldVar(csqcprogs, ev_integer, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldvector(name,desc) PR_RegisterFieldVar(csqcprogs, ev_vector, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldentity(name,desc) PR_RegisterFieldVar(csqcprogs, ev_entity, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldstring(name,desc) PR_RegisterFieldVar(csqcprogs, ev_string, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#define comfieldfunction(name, typestr,desc) PR_RegisterFieldVar(csqcprogs, ev_function, #name, (size_t)&((csqcentvars_t*)0)->name, -1);
#endif
comextqcfields
csqcextfields
#undef fieldfloat
#undef fieldint
#undef fieldvector
#undef fieldentity
#undef fieldstring
#undef fieldfunction

	PR_RegisterFieldVar(csqcprogs, ev_void, NULL, pr_fixbrokenqccarrays.ival, -1);
}

static csqcedict_t **csqcent;
static int maxcsqcentities;

static int csqcentsize;

static const char *csqcmapentitydata;
static qboolean csqcmapentitydataloaded;

static unsigned int csqc_deprecated_warned;
#define csqc_deprecated(s) do {if (!csqc_deprecated_warned++){Con_Printf(CON_WARNING"csqc deprecation warning: %s\n", s); PR_StackTrace (prinst, false);}}while(0)

static model_t *CSQC_GetModelForIndex(int index);

static void CS_CheckVelocity(csqcedict_t *ent)
{
}









static void cs_getframestate(csqcedict_t *in, unsigned int rflags, framestate_t *fte_restrict out)
{
	//FTE_CSQC_HALFLIFE_MODELS
#ifdef HALFLIFEMODELS
	out->bonecontrols[0] = in->xv->bonecontrol1;
	out->bonecontrols[1] = in->xv->bonecontrol2;
	out->bonecontrols[2] = in->xv->bonecontrol3;
	out->bonecontrols[3] = in->xv->bonecontrol4;
	out->bonecontrols[4] = in->xv->bonecontrol5;
	out->g[FS_REG].subblendfrac = in->xv->subblendfrac;
	out->g[FS_REG].subblend2frac = in->xv->subblend2frac;
	out->g[FST_BASE].subblendfrac = in->xv->subblendfrac;
	out->g[FST_BASE].subblend2frac = in->xv->subblend2frac;
#endif

	//FTE_CSQC_BASEFRAME
	out->g[FST_BASE].endbone = in->xv->basebone;
	if (out->g[FST_BASE].endbone)
	{	//small optimisation.
		out->g[FST_BASE].endbone -= 1;

		out->g[FST_BASE].frame[0] = in->xv->baseframe;
		out->g[FST_BASE].frame[1] = in->xv->baseframe2;
		out->g[FST_BASE].lerpweight[1] = in->xv->baselerpfrac;
		//out->g[FST_BASE].frame[3] = in->xv->baseframe4;

#if 0//FRAME_BLENDS >= 4
//		out->g[FST_BASE].frame[2] = in->xv->baseframe3;
//		out->g[FST_BASE].lerpweight[2] = in->xv->baselerpfrac3;
//		out->g[FST_BASE].frame[3] = in->xv->baseframe3;
//		out->g[FST_BASE].lerpweight[3] = in->xv->baselerpfrac4;
		out->g[FST_BASE].lerpweight[0] = 1-(out->g[FST_BASE].lerpweight[1]+out->g[FST_BASE].lerpweight[2]+out->g[FST_BASE].lerpweight[3]);
#else
		out->g[FST_BASE].lerpweight[0] = 1-(out->g[FST_BASE].lerpweight[1]);
#endif
		if (rflags & CSQCRF_FRAMETIMESARESTARTTIMES)
		{
			out->g[FST_BASE].frametime[0] = *csqcg.simtime - in->xv->baseframe1time;
			out->g[FST_BASE].frametime[1] = *csqcg.simtime - in->xv->baseframe2time;
			//out->g[FST_BASE].frametime[2] = *csqcg.simtime - in->xv->baseframe3time;
			//out->g[FST_BASE].frametime[3] = *csqcg.simtime - in->xv->baseframe4time;
		}
		else
		{
			out->g[FST_BASE].frametime[0] = in->xv->baseframe1time;
			out->g[FST_BASE].frametime[1] = in->xv->baseframe2time;
			//out->g[FST_BASE].frametime[2] = in->xv->baseframe3time;
			//out->g[FST_BASE].frametime[3] = in->xv->baseframe4time;
		}
	}

	//and the normal frames.
	out->g[FS_REG].endbone = 0x7fffffff;
	out->g[FS_REG].frame[0] = in->v->frame;
	out->g[FS_REG].frame[1] = in->xv->frame2;
	out->g[FS_REG].lerpweight[1] = in->xv->lerpfrac;
#if FRAME_BLENDS >= 4
	out->g[FS_REG].frame[2] = in->xv->frame3;
	out->g[FS_REG].lerpweight[2] = in->xv->lerpfrac3;
	out->g[FS_REG].frame[3] = in->xv->frame4;
	out->g[FS_REG].lerpweight[3] = in->xv->lerpfrac4;
	out->g[FS_REG].lerpweight[0] = 1-(out->g[FS_REG].lerpweight[1]+out->g[FS_REG].lerpweight[2]+out->g[FS_REG].lerpweight[3]);
#else
	out->g[FS_REG].lerpweight[0] = 1-(out->g[FS_REG].lerpweight[1]);
#endif
	if ((rflags & CSQCRF_FRAMETIMESARESTARTTIMES) || csqc_isdarkplaces)
	{
		out->g[FS_REG].frametime[0] = *csqcg.simtime - in->xv->frame1time;
		out->g[FS_REG].frametime[1] = *csqcg.simtime - in->xv->frame2time;
#if FRAME_BLENDS >= 4
		out->g[FS_REG].frametime[2] = *csqcg.simtime - in->xv->frame3time;
		out->g[FS_REG].frametime[3] = *csqcg.simtime - in->xv->frame4time;
#endif
	}
	else
	{
		out->g[FS_REG].frametime[0] = in->xv->frame1time;
		out->g[FS_REG].frametime[1] = in->xv->frame2time;
#if FRAME_BLENDS >= 4
		out->g[FS_REG].frametime[2] = in->xv->frame3time;
		out->g[FS_REG].frametime[3] = in->xv->frame4time;
#endif
	}


#if defined(SKELETALOBJECTS) || defined(RAGDOLL)
	out->bonecount = 0;
	out->bonestate = NULL;
	if (in->xv->skeletonindex)
		skel_lookup(&csqc_world, in->xv->skeletonindex, out);
#endif
}


static void QCBUILTIN PF_cs_remove (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ed;

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

	if (ED_ISFREE(ed))
	{
		csqc_deprecated("Tried removing free entity");
		return;
	}

	if (!ed->entnum)
	{
		Con_Printf("Unable to remove the world. Try godmode.");
		PR_StackTrace (prinst, false);
		return;
	}
	if (ed->readonly)
	{
		Con_Printf("Entity %i is readonly.", ed->entnum);
		return;
	}

	if (pe)
		pe->DelinkTrailstate(&ed->trailstate);
	World_UnlinkEdict((wedict_t*)ed);
	ED_Free (prinst, (void*)ed);
}

static void QCBUILTIN PF_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"))
	{
		if (!csqc_isdarkplaces)	//don't warn when its unfixable...
			csqc_deprecated("vid_conwidth - use (vector)getviewprop(VF_SCREENVSIZE)");
		G_FLOAT(OFS_RETURN) = vid.width;
	}
	else if (!strcmp(str, "vid_conheight"))
	{
		if (!csqc_isdarkplaces)
			csqc_deprecated("vid_conheight - use (vector)getviewprop(VF_SCREENVSIZE)");
		G_FLOAT(OFS_RETURN) = vid.height;
	}
	else
	{
		var = PF_Cvar_FindOrGet (str);
		if (var && !(var->flags & CVAR_NOUNSAFEEXPAND))
			G_FLOAT(OFS_RETURN) = var->value;
		else
			G_FLOAT(OFS_RETURN) = 0;
	}
}

//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.\nCSQC is not compatible.", binum, fname);
	PR_BIError (prinst, "bulitin not implemented");
}
static void QCBUILTIN PF_NoCSQC (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 does not make sense in csqc.\nCSQC 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 || prinst->parms->globalbuiltins[builtinno] == PF_NoCSQC)
				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;
	}
}

static void QCBUILTIN PF_cl_cprint (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *str = PF_VarString(prinst, 0, pr_globals);
	if (csqc_playerseat >= 0)
		SCR_CenterPrint(csqc_playerseat, str, true);
}

static void QCBUILTIN PF_cs_makevectors (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqcg.forward || !csqcg.right || !csqcg.up)
		Host_EndGame("PF_makevectors: one of v_forward, v_right or v_up was not defined\n");
	AngleVectors (G_VECTOR(OFS_PARM0), csqcg.forward, csqcg.right, csqcg.up);
}

static model_t *CSQC_GetModelForIndex(int index)
{
	if (index == 0)
		return NULL;
	else if (index > 0 && index < MAX_PRECACHE_MODELS)
		return cl.model_precache[index];
	else if (index < 0 && index > -MAX_CSMODELS)
	{
		if (!cl.model_csqcprecache[-index])
			cl.model_csqcprecache[-index] = Mod_ForName(cl.model_csqcname[-index], MLV_WARN);
		return cl.model_csqcprecache[-index];
	}
	else
		return NULL;
}

static float CSQC_PitchScaleForModelIndex(int index)
{
	model_t *mod = CSQC_GetModelForIndex(index);
	if (mod && (mod->type == mod_alias || mod->type == mod_halflife))
		return r_meshpitch.value;	//these are buggy.
	return 1;
}

#ifdef SKELETALOBJECTS
wedict_t *skel_gettaginfo_args (pubprogfuncs_t *prinst, vec3_t axis[3], vec3_t origin, int tagent, int tagnum);
#endif

static qboolean CopyCSQCEdictToEntity(csqcedict_t *fte_restrict in, entity_t *fte_restrict out)
{
	int ival;
	model_t *model;
	unsigned int rflags;
	unsigned int effects;

	ival = in->v->modelindex;
	model = CSQC_GetModelForIndex(ival);
	if (!model)
		return false; //there might be other ent types later as an extension that stop this.

	memset(out, 0, sizeof(*out));
	out->model = model;
	out->pvscache = in->pvsinfo;

	rflags = in->xv->renderflags;
	if (csqc_isdarkplaces)
		rflags ^= CSQCRF_FRAMETIMESARESTARTTIMES;
	if (rflags)
	{
		rflags = in->xv->renderflags;
		if (rflags & CSQCRF_VIEWMODEL)
		{
			out->flags |= RF_DEPTHHACK|RF_WEAPONMODEL;
			out->pvscache.num_leafs = -1;
		}
		if (rflags & CSQCRF_EXTERNALMODEL)
			out->flags |= RF_EXTERNALMODEL;
		if (rflags & CSQCRF_DEPTHHACK)
			out->flags |= RF_DEPTHHACK;
		if (rflags & CSQCRF_ADDITIVE)
			out->flags |= RF_ADDITIVE;
		//CSQCRF_USEAXIS is below
		if (rflags & CSQCRF_NOSHADOW)
			out->flags |= RF_NOSHADOW;
		//CSQCRF_FRAMETIMESARESTARTTIMES is handled by cs_getframestate below

		if (rflags & CSQCRF_REMOVED)
			Con_Printf("Warning: CSQCRF_NOAUTOADD is no longer supported\n");
	}

	effects = in->v->effects;
	if (effects & EF_FULLBRIGHT)
		out->flags |= RF_FULLBRIGHT;
	if (effects & NQEF_ADDITIVE)
		out->flags |= RF_ADDITIVE;
	if (effects & EF_NOSHADOW)
		out->flags |= RF_NOSHADOW;
	if (effects & EF_NODEPTHTEST)
		out->flags |= RF_NODEPTHTEST;
	if ((effects & DPEF_NOGUNBOB) && (out->flags & RF_WEAPONMODEL))
		out->flags |= RF_WEAPONMODELNOBOB;

	cs_getframestate(in, rflags, &out->framestate);

	VectorCopy(in->v->origin, out->origin);
	VectorCopy(in->v->oldorigin, out->oldorigin);
	if (in->v->enemy)
	{
		csqcedict_t *ed = (csqcedict_t*)PROG_TO_EDICT(csqcprogs, in->v->enemy);
		VectorSubtract(out->oldorigin, ed->v->oldorigin, out->oldorigin);
	}

	if (rflags & CSQCRF_USEAXIS)
	{
		VectorCopy(csqcg.forward, out->axis[0]);
		VectorNegate(csqcg.right, out->axis[1]);
		VectorCopy(csqcg.up, out->axis[2]);
		out->scale = 1;
	}
	else
	{
		VectorCopy(in->v->angles, out->angles);
		if (model && model->type == mod_alias)
			out->angles[0] *= r_meshpitch.value;
		AngleVectors(out->angles, out->axis[0], out->axis[1], out->axis[2]);
		VectorInverse(out->axis[1]);

		if (!in->xv->scale)
			out->scale = 1;
		else
			out->scale = in->xv->scale;
	}

	if (csqc_isdarkplaces && in->xv->tag_entity)
	{
#ifdef SKELETALOBJECTS
		csqcedict_t *p = (csqcedict_t*)skel_gettaginfo_args(csqcprogs, out->axis, out->origin, in->xv->tag_entity, in->xv->tag_index);
		if (p && (int)p->xv->renderflags & CSQCRF_VIEWMODEL)
			out->flags |= RF_DEPTHHACK|RF_WEAPONMODEL;
		out->pvscache = p->pvsinfo; //for the areas.
		out->pvscache.num_leafs = -1;	//make visible globally
		out->pvscache.headnode = 0;
#endif
	}

	ival = in->v->colormap;
	out->playerindex = -1;
	if (ival > 0 && ival <= MAX_CLIENTS)
	{
		out->playerindex = ival - 1;
		out->topcolour = cl.players[ival-1].dtopcolor;
		out->bottomcolour = cl.players[ival-1].dbottomcolor;
	}
	else if (ival /*>= 1024*/)
	{
		//DP COLORMAP extension
		out->topcolour = (ival>>4) & 0x0f;
		out->bottomcolour = ival & 0xf;
	}
	else
	{
		out->topcolour = TOP_DEFAULT;
		out->bottomcolour = BOTTOM_DEFAULT;
	}

	if (!in->xv->colormod[0] && !in->xv->colormod[1] && !in->xv->colormod[2])
		VectorSet(out->shaderRGBAf, 1, 1, 1);
	else
	{
		out->flags |= RF_FORCECOLOURMOD;
		VectorCopy(in->xv->colormod, out->shaderRGBAf);
	}
	if (!in->xv->alpha || in->xv->alpha == 1)
		out->shaderRGBAf[3] = 1.0f;
	else
	{
		out->flags |= RF_TRANSLUCENT;
		out->shaderRGBAf[3] = in->xv->alpha;
	}

	if (!in->xv->glowmod[0] && !in->xv->glowmod[1] && !in->xv->glowmod[2])
		VectorSet(out->glowmod, 1, 1, 1);
	else
		VectorCopy(in->xv->glowmod, out->glowmod);

#ifdef HEXEN2
	out->drawflags = in->xv->drawflags;
	out->abslight = in->xv->abslight;
#endif
	out->skinnum = in->v->skin;
	out->fatness = in->xv->fatness;
	ival = in->xv->forceshader;
	if (ival >= 1 && ival <= r_numshaders)
		out->forcedshader = r_shaders[(ival-1)];
	else
		out->forcedshader = NULL;
	out->customskin = (in->skinobject<0)?-in->skinobject:in->skinobject;

	if (in->xv->entnum && !in->xv->camera_transform)	//yes, camera_transform is this hacky
		out->keynum = in->xv->entnum;
	else
		out->keynum = -in->entnum;

	return true;
}

static void QCBUILTIN PF_cs_makestatic (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{	//still does a remove.
	csqcedict_t *in = (void*)G_EDICT(prinst, OFS_PARM0);
	entity_t *ent;

	if (cl.num_statics == cl_max_static_entities)
	{
		cl_max_static_entities += 16;
		cl_static_entities = BZ_Realloc(cl_static_entities, sizeof(*cl_static_entities) * cl_max_static_entities);
	}

	ent = &cl_static_entities[cl.num_statics].ent;
	if (CopyCSQCEdictToEntity(in, ent))
	{
		entity_state_t *state = &cl_static_entities[cl.num_statics].state;
		memset(state, 0, sizeof(*state));
		cl_static_entities[cl.num_statics].emit = NULL;
		cl_static_entities[cl.num_statics].mdlidx = in->v->modelindex;
		if (cl.worldmodel && cl.worldmodel->funcs.FindTouchedLeafs)
			cl.worldmodel->funcs.FindTouchedLeafs(cl.worldmodel, &cl_static_entities[cl.num_statics].ent.pvscache, in->v->absmin, in->v->absmax);
		else
			memset(&cl_static_entities[cl.num_statics].ent.pvscache, 0, sizeof(cl_static_entities[cl.num_statics].ent.pvscache));
		cl.num_statics++;

		//rtlights kinda need all this junk
		VectorCopy(ent->origin, state->origin);
		VectorCopy(ent->angles, state->angles);
		state->modelindex = in->v->modelindex;
		state->light[3] = in->xv->light_lev;
		VectorCopy(in->xv->color, state->light);
		state->lightpflags = in->xv->pflags;
		state->lightstyle = in->xv->style;
		state->skinnum = in->v->skin;
	}

	PF_cs_remove(prinst, pr_globals);
}

static void QCBUILTIN PF_R_AddEntity(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *in = (void*)G_EDICT(prinst, OFS_PARM0);
	entity_t ent;
	if (ED_ISFREE(in) || in->entnum == 0)
	{
		csqc_deprecated("Tried drawing a free/removed/world entity\n");
		return;
	}

	if (CopyCSQCEdictToEntity(in, &ent))
	{
		CLQ1_AddShadow(&ent);
		V_AddAxisEntity(&ent);
	}
}
static void QCBUILTIN PF_R_RemoveEntity(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *in = (void*)G_EDICT(prinst, OFS_PARM0);
	int keynum, i;
	if (ED_ISFREE(in) || in->entnum == 0)
	{
		csqc_deprecated("Tried drawing a free/removed/world entity\n");
		return;
	}

	//work out the internal key that relates to the given ent. we'll remove all ents with the same key.
	if (in->xv->entnum && !in->xv->camera_transform)	//yes, camera_transform is this hacky
		keynum = in->xv->entnum;
	else
		keynum = -in->entnum;

	for (i = 0; i < cl_numvisedicts; )
	{
		if (cl_visedicts[i].keynum == keynum)
		{
			cl_numvisedicts--;
			memmove(&cl_visedicts[i], &cl_visedicts[i+1], sizeof(*cl_visedicts)*(cl_numvisedicts-i));
		}
		else
			i++;
	}
}
void CL_AddDecal(shader_t *shader, vec3_t origin, vec3_t up, vec3_t side, vec3_t rgbvalue, float alphavalue);
static void QCBUILTIN PF_R_AddDecal(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	shader_t *shader = R_RegisterShader(PR_GetStringOfs(prinst, OFS_PARM0), SUF_NONE, 
		"{\n"
			"polygonOffset\n"
			"surfaceparms nodlight\n"
			"{\n"
				"map $diffuse\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
				"blendfunc gl_one gl_one_minus_src_alpha\n"
			"}\n"
		"}\n");
	float *org = G_VECTOR(OFS_PARM1);
	float *up = G_VECTOR(OFS_PARM2);
	float *side = G_VECTOR(OFS_PARM3);
	float *rgb = G_VECTOR(OFS_PARM4);
	float alpha = G_FLOAT(OFS_PARM5);
	if (shader)
		CL_AddDecal(shader, org, up, side, rgb, alpha);
}

static void QCBUILTIN PF_R_DynamicLight_Set(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *s;
	dlight_t *l;
	size_t lno = G_FLOAT(OFS_PARM0);
	int field = G_FLOAT(OFS_PARM1);
	while (lno >= cl_maxdlights)
	{
		if (lno > 1000)
			return;
		CL_AllocSlight();
	}
	l = cl_dlights+lno;
	switch (field)
	{
	case lfield_origin:
		VectorCopy(G_VECTOR(OFS_PARM2), l->origin);
		l->rebuildcache = true;
		break;
	case lfield_colour:
		VectorCopy(G_VECTOR(OFS_PARM2), l->color);
		break;
	case lfield_radius:
		l->radius = G_FLOAT(OFS_PARM2);
		l->rebuildcache = true;
		if (lno >= rtlights_max)
			rtlights_max = lno+1;
		break;
	case lfield_flags:
		l->flags = G_FLOAT(OFS_PARM2);
		l->rebuildcache = true;
		break;
	case lfield_style:
		l->style = G_FLOAT(OFS_PARM2);
		break;
	case lfield_angles:
		VectorCopy(G_VECTOR(OFS_PARM2), l->angles);
		AngleVectors(l->angles, l->axis[0], l->axis[1], l->axis[2]);
		VectorInverse(l->axis[1]);
		break;
	case lfield_fov:
		l->fov = G_FLOAT(OFS_PARM2);
		break;
	case lfield_nearclip:
		l->nearclip = G_FLOAT(OFS_PARM2);
		break;
	case lfield_corona:
		l->corona = G_FLOAT(OFS_PARM2);
		break;
	case lfield_coronascale:
		l->coronascale = G_FLOAT(OFS_PARM2);
		break;
	case lfield_cubemapname:
		s = PR_GetStringOfs(prinst, OFS_PARM2);
		Q_strncpyz(l->cubemapname, s, sizeof(l->cubemapname));
		if (*l->cubemapname)
			l->cubetexture = R_LoadReplacementTexture(l->cubemapname, "", IF_TEXTYPE_CUBE, NULL, 0, 0, TF_INVALID);
		else
			l->cubetexture = r_nulltex;
		break;
#ifdef RTLIGHTS
	case lfield_stylestring:
		s = PR_GetStringOfs(prinst, OFS_PARM2);
		Z_Free(l->customstyle);
		l->customstyle = (s&&*s)?Z_StrDup(s):NULL;
		break;
	case lfield_ambientscale:
		l->lightcolourscales[0] = G_FLOAT(OFS_PARM2);
		break;
	case lfield_diffusescale:
		l->lightcolourscales[1] = G_FLOAT(OFS_PARM2);
		break;
	case lfield_specularscale:
		l->lightcolourscales[2] = G_FLOAT(OFS_PARM2);
		break;
	case lfield_rotation:
		l->rotation[0] = G_FLOAT(OFS_PARM2+0);
		l->rotation[1] = G_FLOAT(OFS_PARM2+1);
		l->rotation[2] = G_FLOAT(OFS_PARM2+2);
		break;
#endif
	case lfield_dietime:
		l->die = G_FLOAT(OFS_PARM2);
		break;
	case lfield_rgbdecay:
		l->channelfade[0] = G_FLOAT(OFS_PARM2+0);
		l->channelfade[1] = G_FLOAT(OFS_PARM2+1);
		l->channelfade[2] = G_FLOAT(OFS_PARM2+2);
		break;
	case lfield_radiusdecay:
		l->decay = G_FLOAT(OFS_PARM2);
		break;
	default:
		break;
	}
}
static void QCBUILTIN PF_R_DynamicLight_Get(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	dlight_t *l;
	unsigned int lno = G_FLOAT(OFS_PARM0);
	enum lightfield_e field = G_FLOAT(OFS_PARM1);
	if (lno >= rtlights_max)
	{
		if ((int)field == -1)
			G_FLOAT(OFS_RETURN) = rtlights_max;
		else
			G_INT(OFS_RETURN) = 0;
		return;
	}
	l = cl_dlights+lno;
	switch (field)
	{
	case lfield_origin:
		VectorCopy(l->origin, G_VECTOR(OFS_RETURN));
		break;
	case lfield_colour:
		VectorCopy(l->color, G_VECTOR(OFS_RETURN));
		break;
	case lfield_radius:
		G_FLOAT(OFS_RETURN) = l->radius;
		break;
	case lfield_flags:
		G_FLOAT(OFS_RETURN) = l->flags;
		break;
	case lfield_style:
		G_FLOAT(OFS_RETURN) = l->style;
		break;
	case lfield_angles:
		VectorCopy(l->angles, G_VECTOR(OFS_RETURN));
		break;
	case lfield_fov:
		G_FLOAT(OFS_RETURN) = l->fov;
		break;
	case lfield_nearclip:
		G_FLOAT(OFS_PARM2) = l->nearclip;
		break;
	case lfield_corona:
		G_FLOAT(OFS_RETURN) = l->corona;
		break;
	case lfield_coronascale:
		G_FLOAT(OFS_RETURN) = l->coronascale;
		break;
	case lfield_cubemapname:
		RETURN_TSTRING(l->cubemapname);
		break;
#ifdef RTLIGHTS
	case lfield_stylestring:
		if (l->customstyle)
			RETURN_TSTRING(l->customstyle);
		else
			RETURN_TSTRING("");
		break;
	case lfield_ambientscale:
		G_FLOAT(OFS_RETURN) = l->lightcolourscales[0];
		break;
	case lfield_diffusescale:
		G_FLOAT(OFS_RETURN) = l->lightcolourscales[1];
		break;
	case lfield_specularscale:
		G_FLOAT(OFS_RETURN) = l->lightcolourscales[2];
		break;
	case lfield_rotation:
		G_FLOAT(OFS_RETURN+0) = l->rotation[0];
		G_FLOAT(OFS_RETURN+1) = l->rotation[1];
		G_FLOAT(OFS_RETURN+2) = l->rotation[2];
		break;
#endif
	case lfield_dietime:
		G_FLOAT(OFS_RETURN) = l->die;
		break;
	case lfield_rgbdecay:
		G_FLOAT(OFS_RETURN+0) = l->channelfade[0];
		G_FLOAT(OFS_RETURN+1) = l->channelfade[1];
		G_FLOAT(OFS_RETURN+2) = l->channelfade[2];
		break;
	case lfield_radiusdecay:
		G_FLOAT(OFS_RETURN) = l->decay;
		break;
	default:
		G_INT(OFS_RETURN) = 0;
		break;
	}
}

static void PF_R_DynamicLight_AddInternal(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals, qboolean isstatic)
{
	float *org = G_VECTOR(OFS_PARM0);
	float radius = G_FLOAT(OFS_PARM1);
	float *rgb = G_VECTOR(OFS_PARM2);
	float style = (prinst->callargc > 3)?G_FLOAT(OFS_PARM3):0;
	const char *cubemapname = (prinst->callargc > 4)?PR_GetStringOfs(prinst, OFS_PARM4):"";
	int pflags = (prinst->callargc > 5)?G_FLOAT(OFS_PARM5):PFLAGS_CORONA;

#ifdef RTLIGHTS
//	float *ambientdiffusespec = (prinst->callargc > 5)?{0,1,1}:G_VECTOR(OFS_PARM6);
//	fov, orientation, corona, coronascale, rotation, die, etc, just use dynamiclight_set
#endif

	wedict_t *self;
	dlight_t *dl;
	int dlkey;
	
	if (prinst == csqc_world.progs)
	{
		self = PROG_TO_WEDICT(prinst, *csqcg.self);
		dlkey = VectorCompare(self->v->origin, org)?-self->entnum:0;
	}
	else
		dlkey = 0;

	if (isstatic)
	{
		dl = CL_AllocSlight();
		dl->die = 0;
		dl->flags = LFLAG_NORMALMODE|LFLAG_REALTIMEMODE;
	}
	else
	{
		dl = CL_AllocDlight(dlkey);
		dl->die = cl.time - 0.1;
		dl->flags = LFLAG_DYNAMIC;
	}

	VectorCopy(org, dl->origin);
	dl->radius = radius;
	VectorCopy(rgb, dl->color);

	if (*dl->cubemapname)
	{
		VectorCopy(csqcg.forward,	dl->axis[0]);
		VectorCopy(csqcg.right,		dl->axis[1]);
		VectorCopy(csqcg.up,		dl->axis[2]);
	}

	if (pflags & PFLAGS_NOSHADOW)
		dl->flags |= LFLAG_NOSHADOWS;
	if (pflags & PFLAGS_CORONA)
		dl->corona = 1;
	else
		dl->corona = 0;
	dl->style = style;
	Q_strncpyz(dl->cubemapname, cubemapname, sizeof(dl->cubemapname));
	if (*dl->cubemapname)
		dl->cubetexture = R_LoadReplacementTexture(dl->cubemapname, "", IF_TEXTYPE_CUBE, NULL, 0, 0, TF_INVALID);
	else
		dl->cubetexture = r_nulltex;

	G_FLOAT(OFS_RETURN) = dl - cl_dlights;
}

void QCBUILTIN PF_R_DynamicLight_AddStatic(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	PF_R_DynamicLight_AddInternal(prinst, pr_globals, true);
}
void QCBUILTIN PF_R_DynamicLight_AddDynamic(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	PF_R_DynamicLight_AddInternal(prinst, pr_globals, false);
}

static void QCBUILTIN PF_R_AddEntityMask(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int mask = G_FLOAT(OFS_PARM0);
	csqcedict_t *ent;
	entity_t rent;
	int e;
	int maxe;

	int oldself = *csqcg.self;
	RSpeedMark();

	if (cl.worldmodel)
	{
		if (mask & MASK_DELTA)
		{
#ifdef Q2CLIENT
			if (cls.protocol == CP_QUAKE2)
				CLQ2_AddEntities();
			else
#endif
			{
				CL_LinkPlayers ();
				CL_LinkPacketEntities ();
			}
		}
	}

	if (csqc_isdarkplaces)
	{
		//hopelessly inefficient version for compat with DP.
		maxe = *prinst->parms->sv_num_edicts;
		for (e=1; e < maxe; e++)
		{
			ent = (void*)EDICT_NUM_PB(prinst, e);
			if (ED_ISFREE(ent))
				continue;
			if (ent->v->think)
			{
				WPhys_RunThink (&csqc_world, (wedict_t*)ent);
				if (ED_ISFREE(ent))
					continue;
			}
			if (ent->xv->predraw)
			{
				*csqcg.self = EDICT_TO_PROG(prinst, (void*)ent);
				PR_ExecuteProgram(prinst, ent->xv->predraw);
				if (ED_ISFREE(ent))
					continue;	//bummer...
			}
			if ((int)ent->xv->drawmask & mask)
			{
				if (CopyCSQCEdictToEntity(ent, &rent))
				{
					CLQ1_AddShadow(&rent);
					V_AddAxisEntity(&rent);
				}
			}
		}
	}
	else
	{
		maxe = *prinst->parms->sv_num_edicts;
		for (e=1; e < maxe; e++)
		{
			ent = (void*)EDICT_NUM_PB(prinst, e);
			if (ED_ISFREE(ent))
				continue;

			if ((int)ent->xv->drawmask & mask)
			{
				if (ent->xv->predraw)
				{
					*csqcg.self = EDICT_TO_PROG(prinst, (void*)ent);
					PR_ExecuteProgram(prinst, ent->xv->predraw);

					if (ED_ISFREE(ent) || G_FLOAT(OFS_RETURN))
						continue;	//bummer...
				}
				if (CopyCSQCEdictToEntity(ent, &rent))
				{
					CLQ1_AddShadow(&rent);
					V_AddAxisEntity(&rent);
				}
			}
		}
	}
	*csqcg.self = oldself;

	if (cl.worldmodel)
	{
		if (mask & MASK_STDVIEWMODEL)
		{
			CL_LinkViewModel ();
		}
		if (mask & MASK_DELTA)
		{
			CL_LinkProjectiles ();
			CL_UpdateTEnts ();
		}
	}

	RSpeedEnd(RSPEED_LINKENTITIES);
}

//enum {vb_vertexcoord, vb_texcoord, vb_rgba, vb_normal, vb_sdir, vb_tdir, vb_indexes, vb_rgb, vb_alpha};
//vboidx = vbuff_create(numverts, numidx, flags)
//vbuff_updateptr(vboidx, datatype, ptr, firstvert, numverts)
//vbuff_updateone(vboidx, datatype, index, __variant data)
//vbuff_render(vboidx, shaderid, uniforms, uniformssize)
//vbuff_delete(vboidx), vboidx=0

static shader_t *csqc_poly_shader;
static int csqc_poly_origvert;
static int csqc_poly_origidx;
static int csqc_poly_startvert;
static int csqc_poly_startidx;
static int csqc_poly_flags;
static int csqc_poly_2d;

#define DRAWFLAG_NORMAL		0
#define DRAWFLAG_ADD		1
#define DRAWFLAG_MODULATE	2
#define DRAWFLAG_MODULATE2	3
#define DRAWFLAG_2D			(1u<<2)
#define DRAWFLAG_TWOSIDED	0x400
#define DRAWFLAG_LINES		0x800

static void CSQC_PolyFlush(void)
{
	mesh_t mesh;
	R2D_Flush = NULL;

	//make sure there's actually something there...
	if (cl_numstrisvert == csqc_poly_origvert)
		return;

	if (!csqc_poly_2d)
	{
		scenetris_t *t;
		if (cl_numstris == cl_maxstris)
		{
			cl_maxstris+=8;
			cl_stris = BZ_Realloc(cl_stris, sizeof(*cl_stris)*cl_maxstris);
		}
		t = &cl_stris[cl_numstris++];
		t->shader = csqc_poly_shader;
		t->flags = csqc_poly_flags;
		t->firstidx = csqc_poly_origidx;
		t->firstvert = csqc_poly_origvert;

		t->numidx = cl_numstrisidx - t->firstidx;
		t->numvert = cl_numstrisvert-t->firstvert;
	}
	else
	{
		/*2d polys need to be flushed now*/
		memset(&mesh, 0, sizeof(mesh));

		mesh.istrifan = (csqc_poly_origvert == csqc_poly_startvert);
		mesh.xyz_array = cl_strisvertv + csqc_poly_origvert;
		mesh.st_array = cl_strisvertt + csqc_poly_origvert;
		mesh.colors4f_array[0] = cl_strisvertc + csqc_poly_origvert;
		mesh.indexes = cl_strisidx + csqc_poly_origidx;
		mesh.numindexes = cl_numstrisidx - csqc_poly_origidx;
		mesh.numvertexes = cl_numstrisvert-csqc_poly_origvert;
		/*undo the positions so we don't draw the same verts more than once*/
		cl_numstrisvert = csqc_poly_origvert;
		cl_numstrisidx = csqc_poly_origvert;

		BE_DrawMesh_Single(csqc_poly_shader, &mesh, NULL, csqc_poly_flags);
	}

	//must call begin before the next poly
	csqc_poly_shader = NULL;
}

static shader_t *PR_R_PolygonShader(const char *shadername, qboolean twod)
{
	extern shader_t *shader_draw_fill_trans;
	shader_t *shader;
	if (!*shadername)
		shader = shader_draw_fill_trans;	//dp compat...
	else if (twod)
		shader = R_RegisterPic(shadername, NULL);
	else
		shader = R_RegisterCustom(shadername, 0, Shader_PolygonShader, NULL);
	return shader;
}

// #306 void(string texturename) R_BeginPolygon (EXT_CSQC_???)
void QCBUILTIN PF_R_PolygonBegin(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *shadername = PR_GetStringOfs(prinst, OFS_PARM0);
	int qcflags = (prinst->callargc > 1)?G_FLOAT(OFS_PARM1):0;
	shader_t *shader;
	int beflags;
	qboolean twod;

	if (prinst->callargc > 2)
		twod = G_FLOAT(OFS_PARM2);
	else if (csqc_isdarkplaces)
	{
		twod = !csqc_dp_lastwas3d;
		csqc_deprecated("guessing 2d mode based upon random builtin calls");
	}
	else
		twod = qcflags & DRAWFLAG_2D;

	if ((qcflags & 3) == DRAWFLAG_ADD)
		beflags = BEF_NOSHADOWS|BEF_FORCEADDITIVE;
	else
		beflags = BEF_NOSHADOWS;
	if (csqc_isdarkplaces || (qcflags & DRAWFLAG_TWOSIDED))
		beflags |= BEF_FORCETWOSIDED;

	shader = PR_R_PolygonShader(shadername, twod);

	if (R2D_Flush && (R2D_Flush != CSQC_PolyFlush || csqc_poly_shader != shader || csqc_poly_flags != beflags || csqc_poly_2d != twod))
		R2D_Flush();
	if (!R2D_Flush)
	{	//this is where our current (2d) batch starts
		csqc_poly_origvert = cl_numstrisvert;
		csqc_poly_origidx = cl_numstrisidx;
	}
	R2D_Flush = CSQC_PolyFlush;
	csqc_poly_shader = shader;
	csqc_poly_flags = beflags;
	csqc_poly_2d = twod;

	//this is where our current poly starts
	csqc_poly_startvert = cl_numstrisvert;
	csqc_poly_startidx = cl_numstrisidx;
}

// #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex (EXT_CSQC_???)
void QCBUILTIN PF_R_PolygonVertex(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (cl_numstrisvert == cl_maxstrisvert)
		cl_stris_ExpandVerts(cl_numstrisvert+64);

	VectorCopy(G_VECTOR(OFS_PARM0), cl_strisvertv[cl_numstrisvert]);
	Vector2Copy(G_VECTOR(OFS_PARM1), cl_strisvertt[cl_numstrisvert]);
	VectorCopy(G_VECTOR(OFS_PARM2), cl_strisvertc[cl_numstrisvert]);
	cl_strisvertc[cl_numstrisvert][3] = G_FLOAT(OFS_PARM3);
	cl_numstrisvert++;
}

// #308 void() R_EndPolygon (EXT_CSQC_???)
void QCBUILTIN PF_R_PolygonEnd(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i;
	int nv;
	int flags = csqc_poly_flags;
	int first;

	if (!csqc_poly_shader)
		return;

	nv = cl_numstrisvert-csqc_poly_startvert;
	if (nv == 2)
		flags |= BEF_LINES;
	else
		flags &= ~BEF_LINES;

	if (flags != csqc_poly_flags || (cl_numstrisvert-csqc_poly_origvert) >= 32768)
	{
		int sv = cl_numstrisvert - nv;
		cl_numstrisvert -= nv;
		CSQC_PolyFlush();

		csqc_poly_origvert = cl_numstrisvert;
		csqc_poly_origidx = cl_numstrisidx;
		R2D_Flush = CSQC_PolyFlush;
		csqc_poly_flags = flags;
		csqc_poly_startvert = cl_numstrisvert;
		csqc_poly_startidx = cl_numstrisidx;

		memcpy(cl_strisvertv+cl_numstrisvert, cl_strisvertv + sv, sizeof(*cl_strisvertv) * nv);
		memcpy(cl_strisvertt+cl_numstrisvert, cl_strisvertt + sv, sizeof(*cl_strisvertt) * nv);
		memcpy(cl_strisvertc+cl_numstrisvert, cl_strisvertc + sv, sizeof(*cl_strisvertc) * nv);
		cl_numstrisvert += nv;
	}

	if (flags & BEF_LINES)
	{
		nv = cl_numstrisvert-csqc_poly_startvert;
		if (cl_numstrisidx+nv > cl_maxstrisidx)
		{
			cl_maxstrisidx=cl_numstrisidx+nv + 64;
			cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx);
		}

		first = csqc_poly_startvert - csqc_poly_origvert;
		/*build the line list fan out of triangles*/
		for (i = 1; i < nv; i++)
		{
			cl_strisidx[cl_numstrisidx++] = first + i-1;
			cl_strisidx[cl_numstrisidx++] = first + i;
		}
	}
	else
	{
		nv = cl_numstrisvert-csqc_poly_startvert;
		if (cl_numstrisidx+(nv-2)*3 > cl_maxstrisidx)
		{
			cl_maxstrisidx=cl_numstrisidx+(nv-2)*3 + 64;
			cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx);
		}

		first = csqc_poly_startvert - csqc_poly_origvert;
		/*build the triangle fan out of triangles*/
		for (i = 2; i < nv; i++)
		{
			cl_strisidx[cl_numstrisidx++] = first + 0;
			cl_strisidx[cl_numstrisidx++] = first + i-1;
			cl_strisidx[cl_numstrisidx++] = first + i;
		}
	}

	/*set up ready for the next poly*/
	csqc_poly_startvert = cl_numstrisvert;
	csqc_poly_startidx = cl_numstrisidx;
}

//input is a line of verts, output is a quad strip
void QCBUILTIN PF_R_PolygonEndRibbon(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i;
	int nv;
	int flags = csqc_poly_flags;
	int first;

	vec3_t tang;
	vec3_t dir;
	vec3_t eyedir;

	float separation = G_FLOAT(OFS_PARM0);
	float sseparation = G_FLOAT(OFS_PARM1+0);
	float tseparation = G_FLOAT(OFS_PARM1+1);

	if (!csqc_poly_shader)
		return;

	nv = cl_numstrisvert-csqc_poly_startvert;
	if (nv < 2)
	{
		csqc_poly_startvert = cl_numstrisvert;
		csqc_poly_startidx = cl_numstrisidx;
		return; //sod off.
	}
	flags &= ~BEF_LINES;

	if (flags != csqc_poly_flags || (cl_numstrisvert-csqc_poly_origvert) >= 32768)
	{
		int sv = cl_numstrisvert - nv;
		cl_numstrisvert -= nv;
		CSQC_PolyFlush();

		csqc_poly_origvert = cl_numstrisvert;
		csqc_poly_origidx = cl_numstrisidx;
		R2D_Flush = CSQC_PolyFlush;
		csqc_poly_flags = flags;
		csqc_poly_startvert = cl_numstrisvert;
		csqc_poly_startidx = cl_numstrisidx;

		memcpy(cl_strisvertv+cl_numstrisvert, cl_strisvertv + sv, sizeof(*cl_strisvertv) * nv);
		memcpy(cl_strisvertt+cl_numstrisvert, cl_strisvertt + sv, sizeof(*cl_strisvertt) * nv);
		memcpy(cl_strisvertc+cl_numstrisvert, cl_strisvertc + sv, sizeof(*cl_strisvertc) * nv);
		cl_numstrisvert += nv;
	}

	nv = cl_numstrisvert-csqc_poly_startvert;
	//dupe the verts
	if (cl_numstrisvert+nv < cl_maxstrisvert)
		cl_stris_ExpandVerts(cl_numstrisvert+nv);
	memcpy(&cl_strisvertv[cl_numstrisvert], &cl_strisvertv[csqc_poly_startvert], sizeof(*cl_strisvertv)*nv);
	memcpy(&cl_strisvertt[cl_numstrisvert], &cl_strisvertt[csqc_poly_startvert], sizeof(*cl_strisvertt)*nv);
	memcpy(&cl_strisvertc[cl_numstrisvert], &cl_strisvertc[csqc_poly_startvert], sizeof(*cl_strisvertc)*nv);

	//apply separation
	VectorSubtract(cl_strisvertv[csqc_poly_startvert+0+1], cl_strisvertv[csqc_poly_startvert+0], dir);
	VectorSubtract(cl_strisvertv[csqc_poly_startvert+0], r_refdef.vieworg, eyedir);
	VectorNormalize(dir); VectorNormalize(eyedir);
	CrossProduct(dir, eyedir, tang);
	VectorMA(cl_strisvertv[csqc_poly_startvert+0], separation, tang, cl_strisvertv[csqc_poly_startvert+0]);
	VectorMA(cl_strisvertv[cl_numstrisvert+0], -separation, tang, cl_strisvertv[cl_numstrisvert+0]);
	cl_strisvertt[csqc_poly_startvert+0][0] += sseparation; //and update the generated s coord.
	cl_strisvertt[csqc_poly_startvert+0][1] += tseparation; //and update the generated t coord.
	for (i = 1; i < nv-1; i++)
	{	//direction comes from its two neighbours, rather than itself and one of those neighbours
		VectorSubtract(cl_strisvertv[csqc_poly_startvert+i+1], cl_strisvertv[csqc_poly_startvert+i-1], dir);
		VectorSubtract(cl_strisvertv[csqc_poly_startvert+i], r_refdef.vieworg, eyedir);
		VectorNormalize(dir); VectorNormalize(eyedir);
		CrossProduct(dir, eyedir, tang);
		VectorMA(cl_strisvertv[csqc_poly_startvert+i], separation, tang, cl_strisvertv[csqc_poly_startvert+i]);
		VectorMA(cl_strisvertv[cl_numstrisvert+i], -separation, tang, cl_strisvertv[cl_numstrisvert+i]);
		cl_strisvertt[csqc_poly_startvert+i][0] += sseparation; //and update the generated s coord.
		cl_strisvertt[csqc_poly_startvert+i][1] += tseparation; //and update the generated t coord.
	}
	//don't wrap over
	VectorSubtract(cl_strisvertv[csqc_poly_startvert+i], cl_strisvertv[csqc_poly_startvert+i-1], dir);
	VectorSubtract(cl_strisvertv[csqc_poly_startvert+i], r_refdef.vieworg, eyedir);
	VectorNormalize(dir); VectorNormalize(eyedir);
	CrossProduct(dir, eyedir, tang);
	VectorMA(cl_strisvertv[csqc_poly_startvert+i], separation, tang, cl_strisvertv[csqc_poly_startvert+i]);
	VectorMA(cl_strisvertv[cl_numstrisvert+i], -separation, tang, cl_strisvertv[cl_numstrisvert+i]);
	cl_strisvertt[csqc_poly_startvert+i][0] += sseparation; //and update the generated s coord.
	cl_strisvertt[csqc_poly_startvert+i][1] += tseparation; //and update the generated t coord.
	//verts are all set up right
	cl_numstrisvert += nv;

	if (cl_numstrisidx+(nv-1)*6 > cl_maxstrisidx)
	{
		cl_maxstrisidx=cl_numstrisidx+(nv-1)*6 + 64;
		cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx);
	}

	first = csqc_poly_startvert - csqc_poly_origvert;
	/*build a double-triangle strip out of our lined verts*/
	for (i = 1; i < nv; i++)
	{
		cl_strisidx[cl_numstrisidx++] = first + i-1;
		cl_strisidx[cl_numstrisidx++] = first + i;
		cl_strisidx[cl_numstrisidx++] = first + i+nv;

		cl_strisidx[cl_numstrisidx++] = first + i+nv;
		cl_strisidx[cl_numstrisidx++] = first + i-1+nv;
		cl_strisidx[cl_numstrisidx++] = first + i-1;
	}

	/*set up ready for the next poly*/
	csqc_poly_startvert = cl_numstrisvert;
	csqc_poly_startidx = cl_numstrisidx;
}

typedef struct
{
	vec3_t xyz;
	vec2_t st;
	vec4_t rgba;
//	vec3_t norm;
//	vec3_t sdir;
//	vec3_t tdir;
} qcvertex_t;
void QCBUILTIN PF_R_AddTrisoup_Simple(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	shader_t *shader;		//parm 0
	const char *shadername = PR_GetStringOfs(prinst, OFS_PARM0);
	unsigned int qcflags	= G_INT(OFS_PARM1);
	unsigned int vertsptr	= G_INT(OFS_PARM2);
	unsigned int indexesptr	= G_INT(OFS_PARM3);
	unsigned int numindexes	= G_INT(OFS_PARM4);
	qboolean twod = qcflags & DRAWFLAG_2D;
	unsigned int beflags;
	unsigned int numverts;
	const qcvertex_t *fte_restrict vert;
	const unsigned int *fte_restrict idx;
	unsigned int i, j, first;

	if ((qcflags & 3) == DRAWFLAG_ADD)
		beflags = BEF_NOSHADOWS|BEF_FORCEADDITIVE;
	else if ((qcflags & 3) == DRAWFLAG_MODULATE)
		beflags = BEF_NOSHADOWS|BEF_FORCETRANSPARENT;
	else
		beflags = BEF_NOSHADOWS;
	if (qcflags & DRAWFLAG_TWOSIDED)
		beflags |= BEF_FORCETWOSIDED;
	if (qcflags & DRAWFLAG_LINES)
		beflags |= BEF_LINES;

	shader = PR_R_PolygonShader(shadername, twod);

	if (R2D_Flush && (R2D_Flush != CSQC_PolyFlush || csqc_poly_shader != shader || csqc_poly_flags != beflags || csqc_poly_2d != twod))
		R2D_Flush();
	if (!R2D_Flush)
	{	//this is where our current (2d) batch starts
		csqc_poly_origvert = cl_numstrisvert;
		csqc_poly_origidx = cl_numstrisidx;
	}

	//validates the pointer.
	numverts = (prinst->stringtablesize - vertsptr) / sizeof(qcvertex_t);
	if (numverts < 1 || vertsptr <= 0 || vertsptr+numverts*sizeof(qcvertex_t) > prinst->stringtablesize)
	{
		PR_BIError(prinst, "PF_R_AddTrisoup: invalid vertexes pointer\n");
		return;
	}
	vert = (const qcvertex_t*)(prinst->stringtable + vertsptr);
	if (indexesptr <= 0 || indexesptr+numindexes*sizeof(int) > prinst->stringtablesize)
	{
		PR_BIError(prinst, "PF_R_AddTrisoup: invalid indexes pointer\n");
		return;
	}
	idx = (const int*)(prinst->stringtable + indexesptr);

	first = cl_numstrisvert - csqc_poly_origvert;
	if (first + numindexes > MAX_INDICIES)
	{
		if (numindexes > MAX_INDICIES)
		{
			PR_BIError(prinst, "PF_R_AddTrisoup: single batch exceeds MAX_INDICIES\n");
			return;
		}
		else if (R2D_Flush)	//should always be true
		{
			R2D_Flush();
			first = 0;
		}
	}

	R2D_Flush = CSQC_PolyFlush;
	csqc_poly_shader = shader;
	csqc_poly_flags = beflags;
	csqc_poly_2d = twod;

	//hacky crappy solution - make a copy of each used vert rather than copying the entire data out.

	if (cl_numstrisidx+numindexes > cl_maxstrisidx)
	{
		cl_maxstrisidx=cl_numstrisidx+numindexes;
		cl_strisidx = BZ_Realloc(cl_strisidx, sizeof(*cl_strisidx)*cl_maxstrisidx);
	}
	if (cl_numstrisvert+numindexes > cl_maxstrisvert)
		cl_stris_ExpandVerts(cl_numstrisvert+numindexes);
	for (i = 0; i < numindexes; i++)
	{
		j = *idx++;
		if (j >= numverts)
			j = 0;	//out of bounds.

		VectorCopy(vert[j].xyz, cl_strisvertv[cl_numstrisvert]);
		Vector2Copy(vert[j].st, cl_strisvertt[cl_numstrisvert]);
		Vector4Copy(vert[j].rgba, cl_strisvertc[cl_numstrisvert]);
		cl_numstrisvert++;

		cl_strisidx[cl_numstrisidx++] = first++;
	}


	//in case someone calls polygonvertex+end without beginpolygon
	csqc_poly_startvert = cl_numstrisvert;
	csqc_poly_startidx = cl_numstrisidx;
}


qboolean csqc_rebuildmatricies;
float csqc_proj_matrix[16];
float csqc_proj_matrix_inverse[16];
float csqc_proj_frustum[2];
void V_ApplyAFov(playerview_t *pv);
void buildmatricies(void)
{
	float modelview[16];
	float proj[16];
	float ofovx = r_refdef.fov_x,ofovy=r_refdef.fov_y;
	float ofovvx = r_refdef.fovv_x,ofovvy=r_refdef.fovv_y;

	V_ApplyAFov(csqc_playerview);

	if (csqc_isdarkplaces)
	{
		/*doesn't bother to use the projection matrix. it isn't much of a transform.*/
		Matrix4x4_CM_ModelViewMatrix(csqc_proj_matrix, r_refdef.viewangles, r_refdef.vieworg);

		csqc_proj_frustum[0] = tan(r_refdef.fov_x * M_PI / 360.0);
		csqc_proj_frustum[1] = tan(r_refdef.fov_y * M_PI / 360.0);
	}
	else
	{
		/*build view and projection matricies*/
		Matrix4x4_CM_ModelViewMatrix(modelview, r_refdef.viewangles, r_refdef.vieworg);
		if (r_refdef.useperspective)
			Matrix4x4_CM_Projection2(proj, r_refdef.fov_x, r_refdef.fov_y, 4);
		else
			Matrix4x4_CM_Orthographic(proj, -r_refdef.fov_x/2, r_refdef.fov_x/2, -r_refdef.fov_y/2, r_refdef.fov_y/2, r_refdef.mindist, r_refdef.maxdist>=1?r_refdef.maxdist:9999);

		/*build the vp matrix*/
		Matrix4_Multiply(proj, modelview, csqc_proj_matrix);
	}

	/*build the unproject matrix (inverted vp matrix)*/
	Matrix4_Invert(csqc_proj_matrix, csqc_proj_matrix_inverse);

	csqc_rebuildmatricies = false;

	r_refdef.fov_x = ofovx,
	r_refdef.fov_y = ofovy;
	r_refdef.fovv_x = ofovvx,
	r_refdef.fovv_y = ofovvy;
}
static void QCBUILTIN PF_cs_project (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (csqc_rebuildmatricies)
		buildmatricies();


	{
		float *in = G_VECTOR(OFS_PARM0);
		float *out = G_VECTOR(OFS_RETURN);
		float v[4], tempv[4];

		v[0] = in[0];
		v[1] = in[1];
		v[2] = in[2];
		v[3] = 1;

		Matrix4x4_CM_Transform4(csqc_proj_matrix, v, tempv);

		tempv[0] /= tempv[3];
		tempv[1] /= tempv[3];
		tempv[2] /= tempv[3];

		if (csqc_isdarkplaces)
		{	/*sigh*/
			tempv[0] = -tempv[0]/tempv[2]/csqc_proj_frustum[0];
			tempv[1] = tempv[1]/tempv[2]/csqc_proj_frustum[1];
			out[0] = (1+tempv[0])/2;
			out[1] = (1+tempv[1])/2;
			out[2] = -tempv[2];

			out[0] = out[0]*vid.width;
			out[1] = out[1]*vid.height;
		}
		else
		{
			out[0] = (1+tempv[0])/2;
			out[1] = 1-(1+tempv[1])/2;
			out[2] = tempv[2];

			out[0] = out[0]*r_refdef.vrect.width + r_refdef.vrect.x;
			out[1] = out[1]*r_refdef.vrect.height + r_refdef.vrect.y;
		}
		if (tempv[3] < 0)
			out[2] *= -1;
	}
}
static void QCBUILTIN PF_cs_unproject (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (csqc_rebuildmatricies)
		buildmatricies();

	{
		float *in = G_VECTOR(OFS_PARM0);
		float *out = G_VECTOR(OFS_RETURN);
		float tx = in[0], ty = in[1];

		float v[4], tempv[4];

		if (csqc_isdarkplaces)
		{	/*sigh*/
			tx = ((tx)/vid.width);
			ty = ((ty)/vid.height);

			//this is kinda screwy
			v[2] = -in[2];
			v[0] = -(tx*2-1)*v[2]*csqc_proj_frustum[0];
			v[1] = (ty*2-1)*v[2]*csqc_proj_frustum[1];
		}
		else
		{
			tx = ((tx-r_refdef.vrect.x)/r_refdef.vrect.width);
			ty = ((ty-r_refdef.vrect.y)/r_refdef.vrect.height);
			ty = 1-ty;
			v[0] = tx*2-1;
			v[1] = ty*2-1;
			v[2] = in[2]*2-1;	//gl projection matrix scales -1 to 1 (unlike d3d, which is 0 to 1)

			//don't use 1, because the far clip plane really is an infinite distance away. and that tends to result division by infinity.
			if (v[2] >= 1)
				v[2] = 0.999999;
		}
		v[3] = 1;

		Matrix4x4_CM_Transform4(csqc_proj_matrix_inverse, v, tempv);

		out[0] = tempv[0]/tempv[3];
		out[1] = tempv[1]/tempv[3];
		out[2] = tempv[2]/tempv[3];
	}
}

//clear scene, and set up the default stuff.
static void QCBUILTIN PF_R_ClearScene (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (R2D_Flush)
		R2D_Flush();

	if (prinst->callargc > 0)
		CSQC_ChangeLocalPlayer(G_FLOAT(OFS_PARM0));

	csqc_rebuildmatricies = true;
	csqc_dp_lastwas3d = true;	//cleared by the next drawpic.
	csqc_poly_shader = NULL;

	CL_DecayLights ();

#if defined(SKELETALOBJECTS) || defined(RAGDOLLS)
	skel_dodelete(&csqc_world);
#endif
	CL_ClearEntityLists();

	V_ClearRefdef(csqc_playerview);
	r_refdef.drawsbar = false;	//csqc defaults to no sbar.
	r_refdef.drawcrosshair = false;

	V_CalcRefdef(csqc_playerview);	//set up the defaults
}

void QCBUILTIN PF_R_GetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	viewflags parametertype = G_FLOAT(OFS_PARM0);
	float *r = G_VECTOR(OFS_RETURN);

	r[0] = 0;
	r[1] = 0;
	r[2] = 0;

	switch(parametertype)
	{
nogameaccess:
		csqc_deprecated("PF_R_GetViewFlag: game access is blocked");
		break;
	case VF_ACTIVESEAT:
		if (prinst == csqc_world.progs)
			*r = csqc_playerseat;
		break;
	case VF_FOV:
		r[0] = r_refdef.fov_x;
		r[1] = r_refdef.fov_y;
		break;

	case VF_FOVX:
		*r = r_refdef.fov_x;
		break;

	case VF_FOVY:
		*r = r_refdef.fov_y;
		break;

	case VF_AFOV:
		*r = r_refdef.afov;
		break;

	case VF_SKYROOM_CAMERA:
		if (r_refdef.skyroom_enabled)
			VectorCopy(r_refdef.skyroom_pos, r);
		else
			VectorClear(r);	//not really correct, but no other way to really signal this. -0? yuck.
		break;

	case VF_ORIGIN:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		VectorCopy(r_refdef.vieworg, r);
		break;

	case VF_ORIGIN_Z:
	case VF_ORIGIN_X:
	case VF_ORIGIN_Y:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		*r = r_refdef.vieworg[parametertype-VF_ORIGIN_X];
		break;

	case VF_ANGLES:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		VectorCopy(r_refdef.viewangles, r);
		break;
	case VF_ANGLES_X:
	case VF_ANGLES_Y:
	case VF_ANGLES_Z:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		*r = r_refdef.viewangles[parametertype-VF_ANGLES_X];
		break;

	case VF_CL_VIEWANGLES_V:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		if (csqc_playerview)
			VectorCopy(csqc_playerview->viewangles, r);
		break;
	case VF_CL_VIEWANGLES_X:
	case VF_CL_VIEWANGLES_Y:
	case VF_CL_VIEWANGLES_Z:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		if (csqc_playerview)
			*r = csqc_playerview->viewangles[parametertype-VF_CL_VIEWANGLES_X];
		break;

	case VF_CARTESIAN_ANGLES:
		if (csqc_nogameaccess && prinst == csqc_world.progs)
			goto nogameaccess;
		Con_Printf(CON_WARNING "WARNING: CARTESIAN ANGLES ARE NOT YET SUPPORTED!\n");
		break;

	case VF_VIEWPORT:
		r[0] = r_refdef.grect.width;
		r[1] = r_refdef.grect.height;
		break;

	case VF_SIZE_X:
		*r = r_refdef.grect.width;
		if (csqc_isdarkplaces && prinst == csqc_world.progs)
			*r *= (float)vid.pixelwidth / vid.width;
		break;
	case VF_SIZE_Y:
		*r = r_refdef.grect.height;
		if (csqc_isdarkplaces && prinst == csqc_world.progs)
			*r *= (float)vid.pixelheight / vid.height;
		break;
	case VF_SIZE:
		r[0] = r_refdef.grect.width;
		r[1] = r_refdef.grect.height;
		r[2] = 0;

		if (csqc_isdarkplaces && prinst == csqc_world.progs)
		{
			r[0] *= (float)vid.pixelwidth / vid.width;
			r[1] *= (float)vid.pixelheight / vid.height;
		}
		break;

	case VF_MIN_X:
		*r = r_refdef.grect.x;
		break;
	case VF_MIN_Y:
		*r = r_refdef.grect.y;
		break;
	case VF_MIN:
		r[0] = r_refdef.grect.x;
		r[1] = r_refdef.grect.y;
		break;

	case VF_MINDIST:
		*r = r_refdef.mindist;
		break;
	case VF_MAXDIST:
		*r = r_refdef.maxdist;
		break;

	case VF_DRAWWORLD:
		*r = !(r_refdef.flags&RDF_NOWORLDMODEL);
		break;
	case VF_ENGINESBAR:
		*r = r_refdef.drawsbar;
		break;
	case VF_DRAWCROSSHAIR:
		*r = r_refdef.drawcrosshair;
		break;

	case VF_PERSPECTIVE:
		*r = r_refdef.useperspective;
		break;

	case VF_SCREENVSIZE:
		r[0] = vid.width;
		r[1] = vid.height;
		break;
	case VF_SCREENPSIZE:
		r[0] = vid.rotpixelwidth;
		r[1] = vid.rotpixelheight;
		break;

	default:
		Con_DPrintf("GetViewFlag: %i not recognised\n", parametertype);
		break;
	}
}
uploadfmt_t PR_TranslateTextureFormat(int qcformat)
{
	switch(qcformat)
	{
	case 1: return PTI_RGBA8;
	case 2: return PTI_RGBA16F;
	case 3: return PTI_RGBA32F;

	case 4: return PTI_DEPTH16;
	case 5: return PTI_DEPTH24;
	case 6: return PTI_DEPTH32;

	case 7: return PTI_R8;
	case 8: return PTI_R16F;
	case 9: return PTI_R32F;

	case 10: return PTI_A2BGR10;
	case 11: return PTI_RGB565;
	case 12: return PTI_RGBA4444;
	case 13: return PTI_RG8;
	case 14: return PTI_RGB32F;

	default:return PTI_INVALID;
	}
}
void QCBUILTIN PF_R_SetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	viewflags parametertype = G_FLOAT(OFS_PARM0);
	float *p = G_VECTOR(OFS_PARM1);

	if (prinst->callargc < 2)
	{
		csqc_deprecated("PF_R_SetViewFlag called with wrong argument count\n");
		PF_R_GetViewFlag(prinst, pr_globals);
		return;
	}

	if (R2D_Flush)
		R2D_Flush();

	csqc_rebuildmatricies = true;

	G_FLOAT(OFS_RETURN) = 1;
	switch(parametertype)
	{
	case VF_ACTIVESEAT:
		if (prinst == csqc_world.progs)
		{
			if (csqc_playerseat != (int)*p)
			{
				CSQC_ChangeLocalPlayer(*p);
				if (prinst->callargc < 3 || G_FLOAT(OFS_PARM2))
					V_CalcRefdef(csqc_playerview);	//set up the default position+angles for the named player.
			}
		}
		break;
	case VF_VIEWENTITY:
		//switches over EXTERNALMODEL flags and clears WEAPONMODEL flagged entities.
		//FIXME: make affect addentities(MASK_ENGINE) calls too.
		V_EditExternalModels(*p, NULL, 0);
		break;
	case VF_FOV:
		//explicit fov overrides aproximate fov
		r_refdef.afov = 0;
		r_refdef.fov_x = p[0];
		r_refdef.fov_y = p[1];
		r_refdef.fovv_x = r_refdef.fovv_y = 0;
		r_refdef.dirty |= RDFD_FOV;
		break;

	case VF_FOVX:
		r_refdef.afov = 0;
		r_refdef.fov_x = *p;
		r_refdef.fovv_x = r_refdef.fovv_y = 0;
		r_refdef.dirty |= RDFD_FOV;
		break;

	case VF_FOVY:
		r_refdef.afov = 0;
		r_refdef.fov_y = *p;
		r_refdef.fovv_x = r_refdef.fovv_y = 0;
		r_refdef.dirty |= RDFD_FOV;
		break;

	case VF_AFOV:
		r_refdef.afov = *p;
		r_refdef.fov_x = r_refdef.fov_y = 0;
		r_refdef.fovv_x = r_refdef.fovv_y = 0;
		r_refdef.dirty |= RDFD_FOV;
		break;

	case VF_SKYROOM_CAMERA:
		r_refdef.skyroom_enabled = true;
		VectorCopy(p, r_refdef.skyroom_pos);
		if (prinst->callargc >= 4)
		{
			VectorCopy(G_VECTOR(OFS_PARM2), r_refdef.skyroom_spin);
			r_refdef.skyroom_spin[3] = G_FLOAT(OFS_PARM3);
		}
		else
			Vector4Set(r_refdef.skyroom_spin, 0, 0, 0, 0);
		break;

	case VF_ORIGIN:
		VectorCopy(p, r_refdef.vieworg);
		if (csqc_playerview)
			csqc_playerview->crouch = 0;
		break;

	case VF_ORIGIN_Z:
		if (csqc_playerview)
			csqc_playerview->crouch = 0;
	case VF_ORIGIN_X:
	case VF_ORIGIN_Y:
		r_refdef.vieworg[parametertype-VF_ORIGIN_X] = *p;
		break;

	case VF_ANGLES:
		VectorCopy(p, r_refdef.viewangles);
		break;
	case VF_ANGLES_X:
	case VF_ANGLES_Y:
	case VF_ANGLES_Z:
		r_refdef.viewangles[parametertype-VF_ANGLES_X] = *p;
		break;

	case VF_CL_VIEWANGLES_V:
		if (csqc_playerview)
			VectorCopy(p, csqc_playerview->viewangles);
		break;
	case VF_CL_VIEWANGLES_X:
	case VF_CL_VIEWANGLES_Y:
	case VF_CL_VIEWANGLES_Z:
		if (csqc_playerview)
			csqc_playerview->viewangles[parametertype-VF_CL_VIEWANGLES_X] = *p;
		break;

	case VF_CARTESIAN_ANGLES:
		Con_Printf(CON_WARNING "WARNING: CARTESIAN ANGLES ARE NOT YET SUPPORTED!\n");
		break;

	case VF_VIEWPORT:
		r_refdef.grect.x = p[0];
		r_refdef.grect.y = p[1];
		p = G_VECTOR(OFS_PARM2);
		r_refdef.grect.width = p[0];
		r_refdef.grect.height = p[1];
		r_refdef.dirty |= RDFD_FOV;
		break;

	case VF_SIZE_X:
		r_refdef.grect.width = *p;
		r_refdef.dirty |= RDFD_FOV;
		break;
	case VF_SIZE_Y:
		r_refdef.grect.height = *p;
		r_refdef.dirty |= RDFD_FOV;
		break;
	case VF_SIZE:
		r_refdef.grect.width = p[0];
		r_refdef.grect.height = p[1];
		r_refdef.dirty |= RDFD_FOV;

		if (csqc_isdarkplaces)
		{
			r_refdef.grect.width *= (float)vid.width / vid.pixelwidth;
			r_refdef.grect.height *= (float)vid.height / vid.pixelheight;
		}
		break;

	case VF_MIN_X:
		r_refdef.grect.x = *p;
		break;
	case VF_MIN_Y:
		r_refdef.grect.y = *p;
		break;
	case VF_MIN:
		r_refdef.grect.x = p[0];
		r_refdef.grect.y = p[1];
		break;
	case VF_MINDIST:
		r_refdef.mindist = *p;
		break;
	case VF_MAXDIST:
		r_refdef.maxdist = *p;
		break;

	case VF_DRAWWORLD:
		r_refdef.flags = (r_refdef.flags&~RDF_NOWORLDMODEL) | (*p?0:RDF_NOWORLDMODEL);
		break;
	case VF_ENGINESBAR:
		r_refdef.drawsbar = !!*p;
		break;
	case VF_DRAWCROSSHAIR:
		r_refdef.drawcrosshair = *p;
		break;

	case VF_PERSPECTIVE:
		r_refdef.useperspective = *p;
		break;

	case VF_RT_DESTCOLOUR0:
	case VF_RT_DESTCOLOUR1:
	case VF_RT_DESTCOLOUR2:
	case VF_RT_DESTCOLOUR3:
	case VF_RT_DESTCOLOUR4:
	case VF_RT_DESTCOLOUR5:
	case VF_RT_DESTCOLOUR6:
	case VF_RT_DESTCOLOUR7:
		{
			int i = parametertype - VF_RT_DESTCOLOUR0;
			Q_strncpyz(r_refdef.rt_destcolour[i].texname, PR_GetStringOfs(prinst, OFS_PARM1), sizeof(r_refdef.rt_destcolour[i].texname));
			if (prinst->callargc >= 4 && *r_refdef.rt_destcolour[i].texname)
			{
				float fmt = G_FLOAT(OFS_PARM2);
				float *size = G_VECTOR(OFS_PARM3);
				if (fmt < 0)
					R2D_RT_Configure(r_refdef.rt_destcolour[i].texname, size[0], size[1], PR_TranslateTextureFormat(-fmt), (RT_IMAGEFLAGS&~IF_LINEAR)|IF_NEAREST);
				else
					R2D_RT_Configure(r_refdef.rt_destcolour[i].texname, size[0], size[1], PR_TranslateTextureFormat(fmt), RT_IMAGEFLAGS);
			}
			BE_RenderToTextureUpdate2d(true);
		}
		break;
	case VF_RT_SOURCECOLOUR:
		Q_strncpyz(r_refdef.rt_sourcecolour.texname, PR_GetStringOfs(prinst, OFS_PARM1), sizeof(r_refdef.rt_sourcecolour));
		if (prinst->callargc >= 4 && *r_refdef.rt_sourcecolour.texname)
		{
			float fmt = G_FLOAT(OFS_PARM2);
			float *size = G_VECTOR(OFS_PARM3);
			if (fmt < 0)
				R2D_RT_Configure(r_refdef.rt_sourcecolour.texname, size[0], size[1], PR_TranslateTextureFormat(-fmt), (RT_IMAGEFLAGS&~IF_LINEAR)|IF_NEAREST);
			else
				R2D_RT_Configure(r_refdef.rt_sourcecolour.texname, size[0], size[1], PR_TranslateTextureFormat(fmt), RT_IMAGEFLAGS);
		}
		BE_RenderToTextureUpdate2d(false);
		break;
	case VF_RT_DEPTH:
		Q_strncpyz(r_refdef.rt_depth.texname, PR_GetStringOfs(prinst, OFS_PARM1), sizeof(r_refdef.rt_depth.texname));
		if (prinst->callargc >= 4 && *r_refdef.rt_depth.texname)
		{
			float fmt = G_FLOAT(OFS_PARM2);
			float *size = G_VECTOR(OFS_PARM3);
			if (fmt < 0)
				R2D_RT_Configure(r_refdef.rt_depth.texname, size[0], size[1], PR_TranslateTextureFormat(-fmt), (RT_IMAGEFLAGS&~IF_LINEAR)|IF_NEAREST);
			else
				R2D_RT_Configure(r_refdef.rt_depth.texname, size[0], size[1], PR_TranslateTextureFormat(fmt), RT_IMAGEFLAGS);
		}
		BE_RenderToTextureUpdate2d(false);
		break;
	case VF_RT_RIPPLE:
		Q_strncpyz(r_refdef.rt_ripplemap.texname, PR_GetStringOfs(prinst, OFS_PARM1), sizeof(r_refdef.rt_ripplemap.texname));
		if (prinst->callargc >= 4 && *r_refdef.rt_ripplemap.texname)
		{
			float fmt = G_FLOAT(OFS_PARM2);
			float *size = G_VECTOR(OFS_PARM3);
			if (fmt < 0)
				R2D_RT_Configure(r_refdef.rt_ripplemap.texname, size[0], size[1], PR_TranslateTextureFormat(-fmt), (RT_IMAGEFLAGS&~IF_LINEAR)|IF_NEAREST);
			else
				R2D_RT_Configure(r_refdef.rt_ripplemap.texname, size[0], size[1], PR_TranslateTextureFormat(fmt), RT_IMAGEFLAGS);
		}
		BE_RenderToTextureUpdate2d(false);
		break;

	case VF_ENVMAP:
		Q_strncpyz(r_refdef.nearenvmap.texname, PR_GetStringOfs(prinst, OFS_PARM1), sizeof(r_refdef.nearenvmap.texname));
		BE_RenderToTextureUpdate2d(false);
		break;

	case VF_USERDATA:
		{
			int qcptr = G_INT(OFS_PARM1);
			int size = G_INT(OFS_PARM2);
			void *ptr;

			if (size > sizeof(r_refdef.userdata))
				size = sizeof(r_refdef.userdata);

			//validates the pointer.
			if (qcptr < 0 || qcptr+size >= prinst->stringtablesize || size < 0)
			{
				PR_BIError(prinst, "PF_R_SetViewFlag: invalid pointer\n");
				return;
			}
			ptr = (struct reverbproperties_s*)(prinst->stringtable + qcptr);
			memcpy(r_refdef.userdata, ptr, size);
		}
		break;

	default:
		Con_DPrintf("SetViewFlag: %i not recognised\n", parametertype);
		G_FLOAT(OFS_RETURN) = 0;
		break;
	}
}

void R2D_PolyBlend (void);
void R_DrawNameTags(void);
static void QCBUILTIN PF_R_RenderScene(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	qboolean scissored;

	if (R2D_Flush)
		R2D_Flush();
	csqc_poly_shader = NULL;

	if (csqc_worldchanged)
	{
		csqc_worldchanged = false;
		Surf_NewMap();
		CL_UpdateWindowTitle();

		World_RBE_Shutdown(&csqc_world);
		World_RBE_Start(&csqc_world);
	}

	if (cl.worldmodel)
		R_PushDlights ();

	r_refdef.playerview = csqc_playerview;

	V_ApplyRefdef();
	V_CalcGunPositionAngle(csqc_playerview, V_CalcBob(csqc_playerview, true));

	R_RenderView();
	R2D_PolyBlend ();

	if (r_refdef.grect.x || r_refdef.grect.y || r_refdef.grect.width != vid.fbvwidth || r_refdef.grect.height != vid.fbvheight)
	{
		srect_t srect;
		srect.x = (float)r_refdef.grect.x / vid.fbvwidth;
		srect.y = (float)r_refdef.grect.y / vid.fbvheight;
		srect.width = (float)r_refdef.grect.width / vid.fbvwidth;
		srect.height = (float)r_refdef.grect.height / vid.fbvheight;
		srect.dmin = -99999;
		srect.dmax = 99999;
		srect.y = (1-srect.y) - srect.height;
		BE_Scissor(&srect);
		scissored = true;
	}
	else
		scissored = false;

	R_DrawNameTags();
#ifdef RTLIGHTS
	R_EditLights_DrawInfo();
#endif

	if (r_refdef.drawsbar)
	{
#ifdef PLUGINS
		Plug_SBar (r_refdef.playerview);
#else
		if (Sbar_ShouldDraw(r_refdef.playerview))
		{
			SCR_TileClear (sb_lines);
			Sbar_Draw (r_refdef.playerview);
			Sbar_DrawScoreboard (r_refdef.playerview);
		}
		else
			SCR_TileClear (0);
#endif

		if (!Key_Dest_Has(kdm_menu|kdm_cwindows))
		{
			if (cl.intermissionmode == IM_NQFINALE || cl.intermissionmode == IM_NQCUTSCENE || cl.intermissionmode == IM_H2FINALE)
			{
				SCR_CheckDrawCenterString ();
			}
			else if (cl.intermissionmode != IM_NONE)
			{
				Sbar_IntermissionOverlay (r_refdef.playerview);
			}
		}

		SCR_ShowPics_Draw();
	}

	if (r_refdef.drawcrosshair)
		R2D_DrawCrosshair();

	if (scissored)
	{
		if (R2D_Flush)
			R2D_Flush();

		BE_Scissor(NULL);
	}
}

static void QCBUILTIN PF_cs_getstati(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int stnum = G_FLOAT(OFS_PARM0);
	if (stnum < 0 || stnum >= MAX_CL_STATS)
	{
		G_INT(OFS_RETURN) = 0;
		PR_RunWarning(prinst, "PF_cs_getstati: invalid stat index (%i)\n", stnum);
	}
	else if (stnum >= 128 && csqc_isdarkplaces && cls.protocol != CP_NETQUAKE && !CPNQ_IS_DP)
	{	//dpp7 stats are fucked.
		G_FLOAT(OFS_RETURN) = csqc_playerview->statsf[stnum];
		csqc_deprecated("hacked stat type");
	}
	else
		G_INT(OFS_RETURN) = csqc_playerview->stats[stnum];
}
static void QCBUILTIN PF_cs_getstatbits(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{	//read a numeric stat into a qc float.
	//if bits offsets are specified, reads explicitly the integer version of the stat, allowing high bits to be read for items2/serverflags. the float stat should have the same value, just with lower precision as a float can't hold a 32bit value. maybe we should just use doubles.

	int stnum = G_FLOAT(OFS_PARM0);
	if (stnum < 0 || stnum >= MAX_CL_STATS)
	{
		G_FLOAT(OFS_RETURN) = 0;
		PR_RunWarning(prinst, "PF_cs_getstatbits: invalid stat index (%i)\n", stnum);
	}
	else if (prinst->callargc > 1)
	{
		int val = csqc_playerview->stats[stnum];
		int first, count;
		first = G_FLOAT(OFS_PARM1);
		if (prinst->callargc > 2)
			count = G_FLOAT(OFS_PARM2);
		else
			count = 1;
		G_FLOAT(OFS_RETURN) = (((unsigned int)val)&(((1<<count)-1)<<first))>>first;
	}
	else if (csqc_isdarkplaces && cls.protocol != CP_NETQUAKE && !CPNQ_IS_DP)
	{
		G_FLOAT(OFS_RETURN) = (int)csqc_playerview->statsf[stnum];	//stupid. mods like xonotic end up with an ugly hud if they're actually given any precision
		if (G_FLOAT(OFS_RETURN) != csqc_playerview->statsf[stnum])
			csqc_deprecated("getstatf stat truncation");	//this is a common call. only get pissy if there's a reason to get pissy.
	}
	else
		G_FLOAT(OFS_RETURN) = csqc_playerview->statsf[stnum];
}
static void QCBUILTIN PF_cs_getstats(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int stnum = G_FLOAT(OFS_PARM0);

	if (stnum < 0 || stnum >= MAX_CL_STATS)
	{
		G_INT(OFS_RETURN) = 0;
		PR_RunWarning(prinst, "PF_cs_getstats: invalid stat index (%i)\n", stnum);
	}
	else if (cls.fteprotocolextensions & PEXT_CSQC)
		RETURN_TSTRING(csqc_playerview->statsstr[stnum]);
	else if (stnum >= MAX_CL_STATS-3)	//we'll be reading the following 3 extra stats too.
	{
		G_INT(OFS_RETURN) = 0;
		PR_RunWarning(prinst, "PF_cs_getstats: invalid packed-string stat index (%i)\n", stnum);
	}
	else
	{
		char out[17];

		//the network protocol byteswaps

		((unsigned int*)out)[0] = LittleLong(csqc_playerview->stats[stnum+0]);
		((unsigned int*)out)[1] = LittleLong(csqc_playerview->stats[stnum+1]);
		((unsigned int*)out)[2] = LittleLong(csqc_playerview->stats[stnum+2]);
		((unsigned int*)out)[3] = LittleLong(csqc_playerview->stats[stnum+3]);
		((unsigned int*)out)[4] = 0;	//make sure it's null terminated

		RETURN_TSTRING(out);
	}
}

static void QCBUILTIN PF_cs_SetOrigin(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	wedict_t *ent = (void*)G_WEDICT(prinst, OFS_PARM0);
	float *org = G_VECTOR(OFS_PARM1);
	if (ent->readonly)
	{
		PR_RunWarning(prinst, "setorigin on entity %i\n", ent->entnum);
		return;
	}
	VectorCopy(org, ent->v->origin);
	World_LinkEdict(w, (wedict_t*)ent, false);
}

static void QCBUILTIN PF_cs_SetSize (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	wedict_t	*e;
	float	*min, *max;

	e = G_WEDICT(prinst, OFS_PARM0);
	if (ED_ISFREE(e))
	{
		PR_RunWarning(prinst, "%s edict was free\n", "setsize");
		return;
	}
	if (e->readonly)
	{
		PR_RunWarning(prinst, "setsize on entity %i\n", e->entnum);
		return;
	}
	min = G_VECTOR(OFS_PARM1);
	max = G_VECTOR(OFS_PARM2);
	VectorCopy (min, e->v->mins);
	VectorCopy (max, e->v->maxs);
	VectorSubtract (max, min, e->v->size);
	World_LinkEdict (w, (wedict_t*)e, false);
}

static void cs_settracevars(pubprogfuncs_t *prinst, trace_t *tr)
{
	*csqcg.trace_allsolid = tr->allsolid;
	*csqcg.trace_startsolid = tr->startsolid;
	*csqcg.trace_fraction = tr->fraction;
	*csqcg.trace_inwater = tr->inwater;
	*csqcg.trace_inopen = tr->inopen;
	VectorCopy (tr->endpos, csqcg.trace_endpos);
	VectorCopy (tr->plane.normal, csqcg.trace_plane_normal);
	*csqcg.trace_plane_dist =  tr->plane.dist;
	*csqcg.trace_surfaceflagsi = tr->surface?tr->surface->flags:0;
	if (csqcg.trace_surfacename)
		prinst->SetStringField(prinst, NULL, csqcg.trace_surfacename, tr->surface?tr->surface->name:NULL, true);
	*csqcg.trace_endcontentsi = tr->contents;
	*csqcg.trace_brush_id = tr->brush_id;
	*csqcg.trace_brush_faceid = tr->brush_face;
	*csqcg.trace_surface_id = tr->surface_id;
	*csqcg.trace_bone_id = tr->bone_id;
	*csqcg.trace_triangle_id = tr->triangle_id;
	if (tr->ent)
		*csqcg.trace_ent = EDICT_TO_PROG(csqcprogs, (void*)tr->ent);
	else
		*csqcg.trace_ent = EDICT_TO_PROG(csqcprogs, (void*)csqc_world.edicts);
	*csqcg.trace_networkentity = tr->entnum;

#ifdef HAVE_LEGACY
	*csqcg.trace_endcontentsf = tr->contents;
	*csqcg.trace_surfaceflagsf = tr->surface?tr->surface->flags:0;

	if (csqcg.trace_dphittexturename)
		prinst->SetStringField(prinst, NULL, csqcg.trace_dphittexturename, tr->surface?tr->surface->name:NULL, true);
	if (csqcg.trace_dpstartcontents)
		*csqcg.trace_dpstartcontents = FTEToDPContents(0);	//fixme, maybe
	if (csqcg.trace_dphitcontents)
		*csqcg.trace_dphitcontents = FTEToDPContents(tr->contents);
	if (csqcg.trace_dphitq3surfaceflags)
		*csqcg.trace_dphitq3surfaceflags = tr->surface?tr->surface->flags:0;
#endif
}

static void QCBUILTIN PF_cs_traceline(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float	*v1, *v2, *mins, *maxs;
	trace_t	trace;
	int		nomonsters;
	csqcedict_t	*ent;

	v1 = G_VECTOR(OFS_PARM0);
	v2 = G_VECTOR(OFS_PARM1);
	nomonsters = G_FLOAT(OFS_PARM2);
	ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM3);

//	if (*prinst->callargc == 6)
//	{
//		mins = G_VECTOR(OFS_PARM4);
//		maxs = G_VECTOR(OFS_PARM5);
//	}
//	else
	{
		mins = vec3_origin;
		maxs = vec3_origin;
	}

	trace = World_Move (&csqc_world, v1, mins, maxs, v2, nomonsters|MOVE_IGNOREHULL, (wedict_t*)ent);

	cs_settracevars(prinst, &trace);
}
static void QCBUILTIN PF_cs_tracebox(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float	*v1, *v2, *mins, *maxs;
	trace_t	trace;
	int		nomonsters;
	csqcedict_t	*ent;

	v1 = G_VECTOR(OFS_PARM0);
	mins = G_VECTOR(OFS_PARM1);
	maxs = G_VECTOR(OFS_PARM2);
	v2 = G_VECTOR(OFS_PARM3);
	nomonsters = G_FLOAT(OFS_PARM4);
	ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM5);

	trace = World_Move (&csqc_world, v1, mins, maxs, v2, nomonsters|MOVE_IGNOREHULL, (wedict_t*)ent);

	cs_settracevars(prinst, &trace);
}

static trace_t CS_Trace_Toss (csqcedict_t *tossent, csqcedict_t *ignore)
{
	int i;
	float gravity;
	vec3_t move, end;
	trace_t trace;
//	float maxvel = Cvar_Get("sv_maxvelocity", "2000", 0, "CSQC physics")->value;

	vec3_t origin, velocity;

	// this has to fetch the field from the original edict, since our copy is truncated
	gravity = 1;//tossent->v->gravity;
	if (!gravity)
		gravity = 1.0;
	gravity *= Cvar_Get("sv_gravity", "800", 0, "CSQC physics")->value * 0.05;

	VectorCopy (tossent->v->origin, origin);
	VectorCopy (tossent->v->velocity, velocity);

	CS_CheckVelocity (tossent);

	for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds
	{
		velocity[2] -= gravity;
		VectorScale (velocity, 0.05, move);
		VectorAdd (origin, move, end);
		trace = World_Move (&csqc_world, origin, tossent->v->mins, tossent->v->maxs, end, MOVE_NORMAL|MOVE_IGNOREHULL, (wedict_t*)tossent);
		VectorCopy (trace.endpos, origin);

		CS_CheckVelocity (tossent);

		if (trace.fraction < 1 && trace.ent && (void*)trace.ent != ignore)
			break;
	}

	trace.fraction = 0; // not relevant
	return trace;
}
static void QCBUILTIN PF_cs_tracetoss (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	trace_t	trace;
	csqcedict_t	*ent;
	csqcedict_t	*ignore;

	ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	if (ent == (csqcedict_t*)csqc_world.edicts)
		Con_DPrintf("tracetoss: can not use world entity\n");
	ignore = (csqcedict_t*)G_EDICT(prinst, OFS_PARM1);

	trace = CS_Trace_Toss (ent, ignore);

	cs_settracevars(prinst, &trace);
}

static void QCBUILTIN PF_cs_pointcontents(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;

	float	*v;
	int cont;

	v = G_VECTOR(OFS_PARM0);

	cont = cl.worldmodel?World_PointContentsWorldOnly(w, v):FTECONTENTS_EMPTY;
	if (cont & FTECONTENTS_SOLID)
		G_FLOAT(OFS_RETURN) = Q1CONTENTS_SOLID;
	else if (cont & FTECONTENTS_SKY)
		G_FLOAT(OFS_RETURN) = Q1CONTENTS_SKY;
	else if (cont & FTECONTENTS_LAVA)
		G_FLOAT(OFS_RETURN) = Q1CONTENTS_LAVA;
	else if (cont & FTECONTENTS_SLIME)
		G_FLOAT(OFS_RETURN) = Q1CONTENTS_SLIME;
	else if (cont & FTECONTENTS_WATER)
		G_FLOAT(OFS_RETURN) = Q1CONTENTS_WATER;
	else
		G_FLOAT(OFS_RETURN) = Q1CONTENTS_EMPTY;
}

static int CS_FindModel(const char *name, int *free)
{
	int i;
	const char *fixedname;

	*free = 0;

	if (!name || !*name)
		return 0;

	fixedname = Mod_FixName(name, csqc_world.worldmodel->publicname);

	for (i = 1; i < MAX_CSMODELS; i++)
	{
		if (!*cl.model_csqcname[i])
		{
			*free = -i;
			break;
		}
		if (!strcmp(cl.model_csqcname[i], fixedname))
			return -i;
	}
	for (i = 1; i < MAX_PRECACHE_MODELS; i++)
	{
		if (!strcmp(cl.model_name[i], name))
			return i;
	}
	return 0;
}

static model_t *csqc_setmodel(pubprogfuncs_t *prinst, csqcedict_t *ent, int modelindex)
{
	model_t *model;
	
	if (ent->readonly)
	{
		Con_Printf("setmodel on readonly entity %i\n", ent->entnum);
		return NULL;
	}

	ent->v->modelindex = modelindex;
	if (modelindex < 0)
	{
		if (modelindex <= -MAX_CSMODELS)
			return NULL;
		prinst->SetStringField(prinst, (void*)ent, &ent->v->model, cl.model_csqcname[-modelindex], true);
		if (!cl.model_csqcprecache[-modelindex])
			cl.model_csqcprecache[-modelindex] = Mod_ForName(Mod_FixName(cl.model_csqcname[-modelindex], csqc_world.worldmodel->publicname), MLV_WARN);
		model = cl.model_csqcprecache[-modelindex];
	}
	else
	{
		if (modelindex >= MAX_PRECACHE_MODELS)
			return NULL;
		prinst->SetStringField(prinst, (void*)ent, &ent->v->model, cl.model_name[modelindex], true);
		model = cl.model_precache[modelindex];
	}
	if (model)
	{
		//csqc probably needs to know the actual model size for any entity. it might as well.
		while(model->loadstate == MLS_LOADING)
			COM_WorkerPartialSync(model, &model->loadstate, MLS_LOADING);

		VectorCopy(model->mins, ent->v->mins);
		VectorCopy(model->maxs, ent->v->maxs);
		VectorSubtract (model->maxs, model->mins, ent->v->size);

		if (!ent->entnum)
		{
			cl.worldmodel = r_worldentity.model = csqc_world.worldmodel = model;
			csqc_worldchanged = true;

			VectorAdd(ent->v->origin, ent->v->mins, ent->v->absmin);
			VectorAdd(ent->v->origin, ent->v->maxs, ent->v->absmax);
		}
	}
	else
	{
		VectorClear(ent->v->mins);
		VectorClear(ent->v->maxs);
	}

	World_LinkEdict(&csqc_world, (wedict_t*)ent, false);

	return model;
}

static void QCBUILTIN PF_cs_SetModel(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	const char *modelname = PR_GetStringOfs(prinst, OFS_PARM1);
	int freei;
	int modelindex = CS_FindModel(modelname, &freei);
	model_t *mod;

	if (!modelindex && modelname && *modelname)
	{
		if (!freei)
			Host_EndGame("CSQC ran out of model slots\n");
		Con_DPrintf("Late caching model \"%s\"\n", modelname);
		Q_strncpyz(cl.model_csqcname[-freei], modelname, sizeof(cl.model_csqcname[-freei]));	//allocate a slot now
		modelindex = freei;

		cl.model_csqcprecache[-freei] = NULL;
	}

	mod = csqc_setmodel(prinst, ent, modelindex);
	if (mod)
		ent->xv->modelflags = mod->flags;
}
static void QCBUILTIN PF_cs_SetModelIndex(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	int modelindex = G_FLOAT(OFS_PARM1);

	csqc_setmodel(prinst, ent, modelindex);
}
static int PF_cs_PrecacheModel_Internal(pubprogfuncs_t *prinst, const char *modelname, qboolean queryonly)
{
	int modelindex, freei;
	const char *fixedname;
	int i;

	if (!*modelname)
		return 0;

	for (i = 1; i < MAX_PRECACHE_MODELS; i++)	//Make sure that the server specified model is loaded..
	{
		if (!*cl.model_name[i])
			break;
		if (!strcmp(cl.model_name[i], modelname))
		{
			if (!cl.model_precache[i])
				cl.model_precache[i] = Mod_ForName(Mod_FixName(modelname, csqc_world.worldmodel->publicname), MLV_WARN);
			break;
		}
	}

	modelindex = CS_FindModel(modelname, &freei);	//now load it

	if (!modelindex && !queryonly)
	{
		if (!freei)
			Host_EndGame("CSQC ran out of model slots\n");
		fixedname = Mod_FixName(modelname, csqc_world.worldmodel->publicname);
		Q_strncpyz(cl.model_csqcname[-freei], fixedname, sizeof(cl.model_csqcname[-freei]));	//allocate a slot now
		modelindex = freei;

		CL_CheckOrEnqueDownloadFile(modelname, modelname, 0);
		cl.model_csqcprecache[-freei] = Mod_ForName(fixedname, MLV_WARN);
	}

	return modelindex;
}
static void QCBUILTIN PF_cs_PrecacheModel (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*s = PR_GetStringOfs(prinst, OFS_PARM0);

	G_INT(OFS_RETURN) = G_INT(OFS_PARM0);
	PF_cs_PrecacheModel_Internal(prinst, s, false);
}

static void QCBUILTIN PF_cs_getmodelindex (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*s = PR_GetStringOfs(prinst, OFS_PARM0);
	qboolean queryonly = (prinst->callargc >= 2)?G_FLOAT(OFS_PARM1):false;

	G_FLOAT(OFS_RETURN) = PF_cs_PrecacheModel_Internal(prinst, s, queryonly);
}
static void QCBUILTIN PF_cs_precachefile(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *filename = PR_GetStringOfs(prinst, OFS_PARM0);
	G_FLOAT(OFS_RETURN) = CL_CheckOrEnqueDownloadFile(filename, NULL, 0);	
}
static void QCBUILTIN PF_cs_PrecacheSound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *soundname = PR_GetStringOfs(prinst, OFS_PARM0);
	if (!*soundname)	//invalid
		return;
	Sound_CheckDownload(soundname);
	S_PrecacheSound(soundname);
}

static void QCBUILTIN PF_cs_ModelnameForIndex(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int modelindex = G_FLOAT(OFS_PARM0);

	if (modelindex < 0)
		G_INT(OFS_RETURN) = (int)PR_SetString(prinst, cl.model_csqcname[-modelindex]);
	else
		G_INT(OFS_RETURN) = (int)PR_SetString(prinst, cl.model_name[modelindex]);
}
void QCBUILTIN PF_cs_setcustomskin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_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);

	if (ent->skinobject > 0)
	{
		Mod_WipeSkin(ent->skinobject, false);
		ent->skinobject = 0;
	}

	if (*fname || *skindata)
	{
		if (*skindata)
			ent->skinobject = Mod_ReadSkinFile(fname, skindata);
		else
			ent->skinobject = -(int)Mod_RegisterSkinFile(fname);
	}
}
void QCBUILTIN PF_cs_loadcustomskin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *fname = PR_GetStringOfs(prinst, OFS_PARM0);
	const char *skindata = PF_VarString(prinst, 1, pr_globals);

	if (*fname || *skindata)
	{
		if (*skindata)
			G_FLOAT(OFS_RETURN) = Mod_ReadSkinFile(fname, skindata);
		else
			G_FLOAT(OFS_RETURN) = -(int)Mod_RegisterSkinFile(fname);
	}
	else
		G_FLOAT(OFS_RETURN) = 0;
}
void QCBUILTIN PF_cs_releasecustomskin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int oldskin = G_FLOAT(OFS_PARM0);
	if (oldskin > 0)
		Mod_WipeSkin(oldskin, false);
}
void QCBUILTIN PF_cs_applycustomskin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	int newskin = G_FLOAT(OFS_PARM1);
	int oldskin = ent->skinobject;
	ent->skinobject = newskin;
	if (oldskin > 0)
		Mod_WipeSkin(oldskin, false);
}

static void QCBUILTIN PF_ReadByte(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadByte is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadByte();
}

static void QCBUILTIN PF_ReadChar(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadChar is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadChar();
}

static void QCBUILTIN PF_ReadShort(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadShort is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadShort();
}

static void QCBUILTIN PF_ReadEntityNum(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	unsigned int val;
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadEntityNum is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	val = MSGCL_ReadEntity();
	G_FLOAT(OFS_RETURN) = val;
}

static void QCBUILTIN PF_ReadLong(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadLong is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadLong();
}

static void QCBUILTIN PF_ReadCoord(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadCoord is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadCoord();
}

static void QCBUILTIN PF_ReadFloat(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadFloat is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadFloat();
}
static void QCBUILTIN PF_ReadInt(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadInt is not valid at this time");
		G_INT(OFS_RETURN) = -1;
		return;
	}
	G_INT(OFS_RETURN) = MSG_ReadLong();
}

static void QCBUILTIN PF_ReadString(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	char *read;
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadString is not valid at this time");
		G_INT(OFS_RETURN) = 0;
		return;
	}
	read = MSG_ReadString();
	RETURN_TSTRING(read);
}

static void QCBUILTIN PF_ReadAngle(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadAngle is not valid at this time");
		G_FLOAT(OFS_RETURN) = -1;
		return;
	}
	G_FLOAT(OFS_RETURN) = MSG_ReadAngle();
}

//basically acts as a readstring, but with added precache (and download)
static void QCBUILTIN PF_ReadPicture(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	char *imagename;
	unsigned short size;
	if (!csqc_mayread)
	{
		CSQC_Abort("PF_ReadPicture is not valid at this time");
		G_INT(OFS_RETURN) = 0;
		return;
	}
	imagename = MSG_ReadString();
	size = MSG_ReadShort();
	MSG_ReadSkip(size);

	//do the precache+download thing
	{
		shader_t *pic = R2D_SafeCachePic(imagename);
		char ext[8];

		//fixme: probably shouldn't block here.
		if ((!pic || !R_GetShaderSizes(pic, NULL, NULL, true)) && cls.state
#ifndef CLIENTONLY
			&& !sv.active
#endif
			&& *COM_FileExtension(imagename, ext, sizeof(ext)))	//only try to download it if it looks as though it contains a path.
			CL_CheckOrEnqueDownloadFile(imagename, imagename, 0);
	}

	RETURN_TSTRING(imagename);
}


static void QCBUILTIN PF_objerror (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char	*s;
	struct edict_s	*ed;

	s = PF_VarString(prinst, 0, pr_globals);
/*	Con_Printf ("======OBJECT ERROR in %s:\n%s\n", PR_GetString(pr_xfunction->s_name),s);
*/	ed = PROG_TO_EDICT(prinst, *csqcg.self);
/*	ED_Print (ed);
*/
	prinst->ED_Print(prinst, ed);
	Con_Printf("%s", s);

	if (developer.value)
		prinst->debug_trace = 2;
	else
	{
		ED_Free (prinst, ed);

		prinst->AbortStack(prinst);

		PR_BIError (prinst, "Program error: %s", s);
	}
}

static void QCBUILTIN PF_cs_setsensitivityscaler (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	in_sensitivityscale = G_FLOAT(OFS_PARM0);
}

static void QCBUILTIN PF_cs_boxparticles(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int effectnum = CL_TranslateParticleFromServer(G_FLOAT(OFS_PARM0));
//	csqcedict_t *ent	= (csqcedict_t*)G_EDICT(prinst, OFS_PARM1);
	float *org_from		= G_VECTOR(OFS_PARM2);
	float *org_to		= G_VECTOR(OFS_PARM3);
	float *vel_from		= G_VECTOR(OFS_PARM4);
	float *vel_to		= G_VECTOR(OFS_PARM5);
	float count			= G_FLOAT(OFS_PARM6);
	int flags			= (prinst->callargc < 7)?0:G_FLOAT(OFS_PARM7);

/*	if (flags & 1)	//PARTICLES_USEALPHA
	{
		float *alphamin = (float*)PR_FindGlobal(csqcprogs, "particles_alphamin", 0, NULL);
		float *alphamax = (float*)PR_FindGlobal(csqcprogs, "particles_alphamax", 0, NULL);
		if (alphamin && alphamax)
			;
	}
	if (flags & 2)	//PARTICLES_USECOLOR
	{	//rgb vectors
		float *colourmin = (float*)PR_FindGlobal(csqcprogs, "particles_colormin", 0, NULL);
		float *colourmax = (float*)PR_FindGlobal(csqcprogs, "particles_colormax", 0, NULL);
		if (colourmin && colourmax)
			;
	}
	if (flags & 4)	//PARTICLES_USEFADE
	{
		float *fade = (float*)PR_FindGlobal(csqcprogs, "particles_fade", 0, NULL);
		if (fade)
			;
	}
*/

	if (flags & 128)	//PARTICLES_DRAWASTRAIL
	{
		flags &= ~128;
		P_ParticleTrail(org_from, org_to, effectnum, 1, 0, NULL, NULL);
	}
	else
	{
		P_RunParticleCube(effectnum, org_from, org_to, vel_from, vel_to, count, 0, true, 0);
	}

	if (flags & ~128)
	{
		static float throttletimer;
		Con_ThrottlePrintf(&throttletimer, 1, "PF_cs_boxparticles: flags & %x is not supported\n", flags);
	}
}

static void QCBUILTIN PF_cs_pointparticles (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int effectnum = G_FLOAT(OFS_PARM0);
	float *org = G_VECTOR(OFS_PARM1);
	float *vel = G_VECTOR(OFS_PARM2);
	float count = G_FLOAT(OFS_PARM3);

	if (prinst->callargc < 3)
		vel = vec3_origin;
	if (prinst->callargc < 4)
		count = 1;

	effectnum = CL_TranslateParticleFromServer(effectnum);
	P_RunParticleEffectType(org, vel, count, effectnum);
}

static void QCBUILTIN PF_cs_trailparticles (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int efnum;
	csqcedict_t *ent;
	float *start = G_VECTOR(OFS_PARM2);
	float *end = G_VECTOR(OFS_PARM3);
	float timestep = host_frametime;

	if ((unsigned int)G_INT(OFS_PARM1) >= MAX_EDICTS)
	{	//ents can't be negative, nor can they be huge (like floats are if expressed as an integer)
		efnum = G_FLOAT(OFS_PARM1);
		ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	}
	else
	{
		efnum = G_FLOAT(OFS_PARM0);
		ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM1);
	}
	efnum = CL_TranslateParticleFromServer(efnum);

	if (!ent->entnum)	//world trails are non-state-based.
		pe->ParticleTrail(start, end, efnum, timestep, 0, NULL, NULL);
	else
		pe->ParticleTrail(start, end, efnum, timestep, -ent->entnum, NULL, &ent->trailstate);
}

void CSQC_ResetTrails(void)
{
	pubprogfuncs_t *prinst = csqc_world.progs;
	int i;
	csqcedict_t	*ent;

	if (!prinst)
		return;

	for (i = 0; i < *prinst->parms->sv_num_edicts; i++)
	{
		ent = (csqcedict_t*)EDICT_NUM_PB(prinst, i);
		ent->trailstate = NULL;
	}
}

//0 for error, non-0 for success.
//>0 match server
//<0 are client-only.
static void QCBUILTIN PF_cs_particleeffectnum (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i;
	const char *effectname = PR_GetStringOfs(prinst, OFS_PARM0);

	G_FLOAT(OFS_RETURN) = 0;	//default to failure.

	//use the server's index first.
	for (i = 1; i < MAX_SSPARTICLESPRE && cl.particle_ssname[i]; i++)
	{
		if (!strcmp(cl.particle_ssname[i], effectname))
		{
			G_FLOAT(OFS_RETURN) = i;
			return;
		}
	}

	//then look for an existing client id
	for (i = 1; i < MAX_CSPARTICLESPRE && cl.particle_csname[i]; i++)
	{
		if (!strcmp(cl.particle_csname[i], effectname))
		{
			//effects can be in the list despite now being stale. they still take up a slot to avoid reuse as the qc can potentially still potentially reference it.
			//csqc needs to be able to detect a now-stale effect
			//if (cl.particle_csprecache[i] != P_INVALID)
				G_FLOAT(OFS_RETURN) = -i;
			return;
		}
	}
	//create if new
	if (i < MAX_CSPARTICLESPRE)
	{
		free(cl.particle_csname[i]);
		cl.particle_csname[i] = NULL;
		cl.particle_csprecache[i] = P_FindParticleType(effectname);
		//if (cl.particle_csprecache[i] != P_INVALID)
		{
			//it exists, allow it.
			cl.particle_csname[i] = strdup(effectname);
			G_FLOAT(OFS_RETURN) = -i;
		}
		cl.particle_csprecaches = true;
	}
}

static void QCBUILTIN PF_cs_particleeffectquery (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int id = G_FLOAT(OFS_PARM0);
	qboolean body = G_FLOAT(OFS_PARM1);
	char retstr[8192];

	id = CL_TranslateParticleFromServer(id);

	if (pe->ParticleQuery && pe->ParticleQuery(id, body, retstr, sizeof(retstr)))
	{
		RETURN_TSTRING(retstr);
	}
	else
		G_INT(OFS_RETURN) = 0;
}

static void QCBUILTIN PF_cs_sendevent (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent;
	int i;
	const char *eventname = PR_GetStringOfs(prinst, OFS_PARM0);
	const char *argtypes = PR_GetStringOfs(prinst, OFS_PARM1);

	if (!cls.state)
		return;

	MSG_WriteByte(&cls.netchan.message, clcfte_qcrequest);

	for (i = 0; i < 6; i++)
	{
		if (argtypes[i] == 's')
		{
			MSG_WriteByte(&cls.netchan.message, ev_string);
			MSG_WriteString(&cls.netchan.message, PR_GetStringOfs(prinst, OFS_PARM2+i*3));
		}
		else if (argtypes[i] == 'f')
		{
			MSG_WriteByte(&cls.netchan.message, ev_float);
			MSG_WriteFloat(&cls.netchan.message, G_FLOAT(OFS_PARM2+i*3));
		}
		else if (argtypes[i] == 'i')
		{
			MSG_WriteByte(&cls.netchan.message, ev_integer);
			MSG_WriteLong(&cls.netchan.message, G_INT(OFS_PARM2+i*3));
		}
		else if (argtypes[i] == 'v')
		{
			MSG_WriteByte(&cls.netchan.message, ev_vector);
			MSG_WriteFloat(&cls.netchan.message, G_FLOAT(OFS_PARM2+i*3+0));
			MSG_WriteFloat(&cls.netchan.message, G_FLOAT(OFS_PARM2+i*3+1));
			MSG_WriteFloat(&cls.netchan.message, G_FLOAT(OFS_PARM2+i*3+2));
		}
		else if (argtypes[i] == 'e')
		{
			ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM2+i*3);
			MSG_WriteByte(&cls.netchan.message, ev_entity);
			MSG_WriteEntity(&cls.netchan.message, ent->xv->entnum);
		}
		else
			break;
	}
	if (csqc_playerseat > 0)
		MSG_WriteByte(&cls.netchan.message, 200+csqc_playerseat);
	MSG_WriteByte(&cls.netchan.message, 0);
	MSG_WriteString(&cls.netchan.message, eventname);
}

static void cs_set_input_state (usercmd_t *cmd)
{
	if (csqcg.input_timelength)
		*csqcg.input_timelength = cmd->msec/1000.0f * cl.gamespeed;
	if (csqcg.input_angles)
	{
		csqcg.input_angles[0] = SHORT2ANGLE(cmd->angles[0]);
		csqcg.input_angles[1] = SHORT2ANGLE(cmd->angles[1]);
		csqcg.input_angles[2] = SHORT2ANGLE(cmd->angles[2]);
	}
	if (csqcg.input_movevalues)
	{
		csqcg.input_movevalues[0] = cmd->forwardmove;
		csqcg.input_movevalues[1] = cmd->sidemove;
		csqcg.input_movevalues[2] = cmd->upmove;
	}
	if (csqcg.input_buttons)
		*csqcg.input_buttons = cmd->buttons;

	if (csqcg.input_impulse)
		*csqcg.input_impulse = cmd->impulse;
	if (csqcg.input_lightlevel)
		*csqcg.input_lightlevel = cmd->lightlevel;
	if (csqcg.input_weapon)
		*csqcg.input_weapon = cmd->weapon;
	if (csqcg.input_servertime)
		*csqcg.input_servertime = cmd->fservertime;
	if (csqcg.input_clienttime)
		*csqcg.input_clienttime = cmd->fclienttime/1000.0f;

	if (csqcg.input_cursor_screen)
	{
		Vector2Copy(cmd->cursor_screen, csqcg.input_cursor_screen);
		csqcg.input_cursor_screen[2] = 0;
	}
	if (csqcg.input_cursor_start)
		VectorCopy(cmd->cursor_start, csqcg.input_cursor_start);
	if (csqcg.input_cursor_impact)
		VectorCopy(cmd->cursor_impact, csqcg.input_cursor_impact);
	if (csqcg.input_cursor_entitynumber)
		*csqcg.input_cursor_entitynumber = cmd->cursor_entitynumber;
}

static void cs_get_input_state (usercmd_t *cmd)
{
	if (csqcg.input_timelength)
		cmd->msec = *csqcg.input_timelength*1000;
	if (csqcg.input_angles)
	{
		cmd->angles[0] = ANGLE2SHORT(csqcg.input_angles[0]);
		cmd->angles[1] = ANGLE2SHORT(csqcg.input_angles[1]);
		cmd->angles[2] = ANGLE2SHORT(csqcg.input_angles[2]);
	}
	if (csqcg.input_movevalues)
	{
		cmd->forwardmove = csqcg.input_movevalues[0];
		cmd->sidemove = csqcg.input_movevalues[1];
		cmd->upmove = csqcg.input_movevalues[2];
	}
	if (csqcg.input_buttons)
		cmd->buttons = *csqcg.input_buttons;

	if (csqcg.input_impulse)
		cmd->impulse = *csqcg.input_impulse;
	if (csqcg.input_lightlevel)
		cmd->lightlevel = *csqcg.input_lightlevel;
	if (csqcg.input_weapon)
		cmd->weapon = *csqcg.input_weapon;
	if (csqcg.input_servertime)
		cmd->fservertime = *csqcg.input_servertime;

	if (csqcg.input_cursor_screen)
		Vector2Copy(csqcg.input_cursor_screen, cmd->cursor_screen);
	if (csqcg.input_cursor_start)
		VectorCopy(csqcg.input_cursor_start, cmd->cursor_start);
	if (csqcg.input_cursor_impact)
		VectorCopy(csqcg.input_cursor_impact, cmd->cursor_impact);
	if (csqcg.input_cursor_entitynumber)
		cmd->cursor_entitynumber = *csqcg.input_cursor_entitynumber;
}

//get the input commands, and stuff them into some globals.
static void QCBUILTIN PF_cs_getinputstate (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	usercmd_t *cmd;
	usercmd_t tmp;
	extern usercmd_t cl_pendingcmd[MAX_SPLITS];
	int f = G_FLOAT(OFS_PARM0);
	int seat = ((prinst->callargc>1)?G_FLOAT(OFS_PARM1):csqc_playerseat);

	if (seat < 0 || seat >= MAX_SPLITS)
	{
		G_FLOAT(OFS_RETURN) = false;
		return;
	}

	f = G_FLOAT(OFS_PARM0);
	if (cl.paused && f >= cl.ackedmovesequence)
	{
		G_FLOAT(OFS_RETURN) = false;
		return;
	}

	/*outgoing_sequence says how many packets have actually been sent, but there's an extra pending packet which has not been sent yet - be warned though, its data will change in the coming frames*/
	if (f == cl.movesequence)
	{
		int i;
		cmd = &cl_pendingcmd[seat];

		tmp = *cmd;
		cmd = &tmp;
		for (i=0 ; i<3 ; i++)
			cmd->angles[i] = ((int)(csqc_playerview->viewangles[i]*65536.0/360)&65535);
		if (!cmd->msec)
			*cmd = cl.outframes[(f-1)&UPDATE_MASK].cmd[seat];
		cmd->msec = (realtime - cl.outframes[(f-1)&UPDATE_MASK].senttime)*1000;
	}
	else
	{
		if (cl.outframes[f&UPDATE_MASK].cmd_sequence != f)
		{
			G_FLOAT(OFS_RETURN) = false;
			return;
		}
		cmd = &cl.outframes[f&UPDATE_MASK].cmd[seat];
	}

	cs_set_input_state(cmd);

	G_FLOAT(OFS_RETURN) = true;
}

//read lots of globals, run the default player physics, write lots of globals.
//not intended to affect client state at all
static void QCBUILTIN PF_cs_runplayerphysics (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float msecs;
	float oldtime = *csqcg.simtime;

	csqcedict_t *ent = (void*)G_EDICT(prinst, OFS_PARM0);
	int mt = ent->v->movetype;
	if (prinst->callargc < 1)
	{
		csqc_deprecated("runplayerphysics with no ent");
		return;
	}
	if (ent->readonly)
	{
		csqc_deprecated("runplayerphysics called on read-only entity");
		return;
	}

	if (!cl.worldmodel)
		return;	//urm..

	VALGRIND_MAKE_MEM_UNDEFINED(&pmove, sizeof(pmove));

	//debugging field
	pmove.sequence = *csqcg.clientcommandframe;

	pmove.jump_msec = 0;//(cls.z_ext & Z_EXT_PM_TYPE) ? 0 : from->jump_msec;

//set up the movement command
	msecs = *csqcg.input_timelength*1000;
	//precision inaccuracies. :(
	pmove.cmd.angles[0] = ANGLE2SHORT(csqcg.input_angles[0]);
	pmove.cmd.angles[1] = ANGLE2SHORT(csqcg.input_angles[1]);
	pmove.cmd.angles[2] = ANGLE2SHORT(csqcg.input_angles[2]);
	VectorCopy(csqcg.input_angles, pmove.angles);

	pmove.cmd.forwardmove = csqcg.input_movevalues[0];
	pmove.cmd.sidemove = csqcg.input_movevalues[1];
	pmove.cmd.upmove = csqcg.input_movevalues[2];
	pmove.cmd.buttons = *csqcg.input_buttons;
	pmove.safeorigin_known = false;
	pmove.capsule = false;	//FIXME

	movevars.coordtype = cls.netchan.message.prim.coordtype;
	if (ent->xv->gravity)
		movevars.entgravity = ent->xv->gravity;
	else if (csqc_playerseat >= 0 && cl.playerview[csqc_playerseat].playernum+1 == ent->xv->entnum)
		movevars.entgravity = cl.playerview[csqc_playerseat].entgravity;
	else
		movevars.entgravity = 1;

	if (ent->xv->entnum)
		pmove.skipent = ent->xv->entnum;
	else
		pmove.skipent = -1;
	mt &= 255;
	switch(mt)
	{
	default:
	case MOVETYPE_WALK:
		pmove.pm_type = PM_NORMAL;
		break;
	case MOVETYPE_NOCLIP:
		pmove.pm_type = PM_SPECTATOR;
		break;
	case MOVETYPE_FLY_WORLDONLY:
	case MOVETYPE_FLY:
		pmove.pm_type = PM_FLY;
		break;
	}
	pmove.jump_held = (int)ent->xv->pmove_flags & PMF_JUMP_HELD;
	pmove.waterjumptime = 0;
	pmove.onground = (int)ent->v->flags & FL_ONGROUND;
	VectorCopy(ent->v->origin, pmove.origin);
	VectorCopy(ent->v->velocity, pmove.velocity);
	VectorCopy(ent->v->maxs, pmove.player_maxs);
	VectorCopy(ent->v->mins, pmove.player_mins);

	CL_SetSolidEntities();

	while(msecs > 0)	//break up longer commands
	{
		pmove.cmd.msec = msecs;
		if (pmove.cmd.msec > 50)
			pmove.cmd.msec = 50;
		msecs -= pmove.cmd.msec;
		PM_PlayerMove(1);
	}

	VectorCopy(pmove.angles, ent->v->angles);
	ent->v->angles[0] *= r_meshpitch.value * 1/3.0f;	//FIXME
	VectorCopy(pmove.origin, ent->v->origin);
	VectorCopy(pmove.velocity, ent->v->velocity);
	if (pmove.onground)
		ent->v->flags = (int)ent->v->flags | FL_ONGROUND;
	else
		ent->v->flags = (int)ent->v->flags & ~FL_ONGROUND;
	ent->xv->pmove_flags = 0;
	ent->xv->pmove_flags += pmove.jump_held ? PMF_JUMP_HELD : 0;
	ent->xv->pmove_flags += pmove.onladder ? PMF_LADDER : 0;

	//fixme: touch triggers?
	World_LinkEdict (&csqc_world, (wedict_t*)ent, true);

	*csqcg.simtime = oldtime;
}

static void QCBUILTIN PF_cs_getentitytoken (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	if (prinst->callargc)
	{
		const char *s = PR_GetStringOfs(prinst, OFS_PARM0);
		if (*s == 0 && cl.worldmodel)
		{
			if (cl.worldmodel->loadstate == MLS_LOADING)
				COM_WorkerPartialSync(cl.worldmodel, &cl.worldmodel->loadstate, MLS_LOADING);
			s = Mod_GetEntitiesString(cl.worldmodel);
		}
		csqcmapentitydata = s;
		G_INT(OFS_RETURN) = 0;
		return;
	}

	if (!csqcmapentitydata)
	{
		//nothing more to parse
		G_INT(OFS_RETURN) = 0;
	}
	else
	{
		char *QCC_COM_Parse (const char *data);
		extern char qcc_token[];
		csqcmapentitydata = QCC_COM_Parse(csqcmapentitydata);

		if (!csqcmapentitydata)	//hit the end
			G_INT(OFS_RETURN) = 0;
		else
			RETURN_TSTRING(qcc_token);
	}
}

static void CheckSendPings(void)
{	//quakeworld sends a 'pings' client command to retrieve the frequently updating stuff
	if (realtime - cl.last_ping_request > 2)
	{
		cl.last_ping_request = realtime;
		CL_SendClientCommand(true, "pings");
	}
}

static const char *PF_cs_serverkey_internal(const char *keyname)
{
	char *ret;
	static char adr[MAX_ADR_SIZE];

	if (!strcmp(keyname, "ip"))
	{
		if (cls.demoplayback)
			ret = cls.lastdemoname;
		else
			ret = NET_AdrToString(adr, sizeof(adr), &cls.netchan.remote_address);
	}
	else if (!strcmp(keyname, "servername"))
		ret = cls.servername;
	else if (!strcmp(keyname, "constate"))
	{
		if (cls.state == ca_disconnected
#ifndef CLIENTONLY 
			&& !sv.state
#endif
				)
			ret = "disconnected";
		else if (cls.state == ca_active)
			ret = "active";
		else
			ret = "connecting";
	}
	else if (!strcmp(keyname, "loadstate"))
	{
		extern int			total_loading_size, current_loading_size, loading_stage;
		extern char			levelshotname[MAX_QPATH];
		ret = va("%i %u %u \"%s\"", loading_stage, current_loading_size, total_loading_size, levelshotname);
	}
	else if (!strcmp(keyname, "transferring"))
	{
		ret = CL_TryingToConnect();
		if (!ret)
			ret = "";
	}
	else if (!strcmp(keyname, "maxplayers"))
	{
		ret = va("%i", cl.allocated_client_slots);
	}
	else if (!strcmp(keyname, "dlstate"))
	{
		if (!cl.downloadlist && !cls.download)
			ret = "";	//nothing being downloaded right now
		else
		{
			unsigned int fcount;
			qofs_t tsize;
			qboolean sizeextra;
			CL_GetDownloadSizes(&fcount, &tsize, &sizeextra);
			if (cls.download)	//downloading something
				ret = va("%u %g %u \"%s\" \"%s\" %g %i %g %g", fcount, (float)tsize, sizeextra?1u:0u, cls.download->localname, cls.download->remotename, cls.download->percent, cls.download->rate, (float)cls.download->completedbytes, (float)cls.download->size);
			else	//not downloading anything right now
				ret = va("%u %g %u", fcount, (float)tsize, sizeextra?1u:0u);
		}
	}
	else if (!strcmp(keyname, "pausestate"))
		ret = cl.paused?"1":"0";
	else if (!strcmp(keyname, "protocol"))
	{	//using this is pretty acedemic, really. Not particuarly portable.
		switch (cls.protocol)
		{	//a tokenizable string
			//first is the base game qw/nq
			//second is branch (custom engine name)
			//third is protocol version.
		default:
		case CP_UNKNOWN:
			ret = "Unknown";
			break;
		case CP_QUAKEWORLD:
			if (cls.fteprotocolextensions||cls.fteprotocolextensions2)
				ret = "QuakeWorld FTE";
			else if (cls.z_ext)
				ret = "QuakeWorld ZQuake";
			else
				ret = "QuakeWorld";
			break;
		case CP_NETQUAKE:
			switch (cls.protocol_nq)
			{
			default:
				ret = "NetQuake";
				break;
			case CPNQ_FITZ666:
				ret = "Fitz666";
				break;
			case CPNQ_DP5:
				ret = "NetQuake DarkPlaces 5";
				break;
			case CPNQ_DP6:
				ret = "NetQuake DarkPlaces 6";
				break;
			case CPNQ_DP7:
				ret = "NetQuake DarkPlaces 7";
				break;
			}
			break;
		case CP_QUAKE2:
			ret = "Quake2";
			break;
		case CP_QUAKE3:
			ret = "Quake3";
			break;
		case CP_PLUGIN:
			ret = "External";
			break;
		}
	}
	else if (!strcmp(keyname, "challenge"))
	{
		ret = va("%u", cls.challenge);
	}
	else
	{
#ifndef CLIENTONLY
		if (sv.state >= ss_loading)
		{
			ret = InfoBuf_ValueForKey(&svs.info, keyname);
			if (!*ret)
				ret = InfoBuf_ValueForKey(&svs.localinfo, keyname);
		}
		else
#endif
			ret = InfoBuf_ValueForKey(&cl.serverinfo, keyname);
	}
	return ret;
}
static const char *PF_cs_getplayerkey_internal (unsigned int pnum, const char *keyname)
{
	static char buffer[64];
	char *ret;

	if ((unsigned int)pnum >= (unsigned int)cl.allocated_client_slots)
		ret = "";
	else if (!strcmp(keyname, "viewentity"))	//compat with DP. Yes, I know this is in the wrong place. This is an evil hack.
	{
		ret = buffer;
		sprintf(ret, "%i", pnum+1);
	}
	else if (!*cl.players[pnum].name)
		ret = "";	//player isn't on the server.
	else if (!strcmp(keyname, "ping"))
	{
		CheckSendPings();

		ret = buffer;
		sprintf(ret, "%i", cl.players[pnum].ping);
	}
	else if (!strcmp(keyname, "frags"))
	{
		ret = buffer;
		sprintf(ret, "%i", cl.players[pnum].frags);
	}
	else if (!strcmp(keyname, "userid"))
	{
		ret = buffer;
		sprintf(ret, "%i", cl.players[pnum].userid);
	}
	else if (!strcmp(keyname, "pl"))	//packet loss
	{
		CheckSendPings();

		ret = buffer;
		sprintf(ret, "%i", cl.players[pnum].pl);
	}
	else if (!strcmp(keyname, "activetime"))	//packet loss
	{
		ret = buffer;
		sprintf(ret, "%f", realtime - cl.players[pnum].realentertime);
	}
//	else if (!strcmp(keyname, "entertime"))	//packet loss
//	{
//		ret = buffer;
//		sprintf(ret, "%i", (int)cl.players[pnum].entertime);
//	}
	else if (!strcmp(keyname, "topcolor_rgb"))	//packet loss
	{
		unsigned int col = cl.players[pnum].dtopcolor;
		ret = buffer;
		if (col < 16)
		{
			col = Sbar_ColorForMap(col);
			sprintf(ret, "'%g %g %g'", host_basepal[col*3+0]/255.0, host_basepal[col*3+1]/255.0, host_basepal[col*3+2]/255.0);
		}
		else
			sprintf(ret, "'%g %g %g'", ((col&0xff0000)>>16)/255.0, ((col&0x00ff00)>>8)/255.0, ((col&0x0000ff)>>0)/255.0);
	}
	else if (!strcmp(keyname, "bottomcolor_rgb"))	//packet loss
	{
		unsigned int col = cl.players[pnum].dbottomcolor;
		ret = buffer;
		if (col < 16)
		{
			col = Sbar_ColorForMap(col);
			sprintf(ret, "'%g %g %g'", host_basepal[col*3+0]/255.0, host_basepal[col*3+1]/255.0, host_basepal[col*3+2]/255.0);
		}
		else
			sprintf(ret, "'%g %g %g'", ((col&0xff0000)>>16)/255.0, ((col&0x00ff00)>>8)/255.0, ((col&0x0000ff)>>0)/255.0);
	}
#ifdef HAVE_LEGACY
	else if (csqc_isdarkplaces && !strcmp(keyname, "colors"))	//checks to see if a player has locally been set to ignored (for text chat)
	{
		ret = buffer;
		sprintf(ret, "%i", cl.players[pnum].dtopcolor + cl.players[pnum].dbottomcolor*16);
	}
#endif
	else if (!strcmp(keyname, "ignored"))	//checks to see if a player has locally been set to ignored (for text chat)
	{
		ret = buffer;
		sprintf(ret, "%i", (int)cl.players[pnum].ignored);
	}
#ifdef VOICECHAT
	else if (!strcmp(keyname, "vignored"))	//checks to see this player's voicechat is ignored.
	{
		ret = buffer;
		sprintf(ret, "%i", (int)cl.players[pnum].vignored);
	}
	else if (!strcmp(keyname, "voipspeaking"))
	{
		ret = buffer;
		sprintf(ret, "%i", S_Voip_Speaking(pnum));
	}
	else if (!strcmp(keyname, "voiploudness"))
	{
		ret = buffer;
		if (pnum == csqc_playerview->playernum)
			sprintf(ret, "%i", S_Voip_Loudness(false));
		else
			sprintf(ret, "%i", S_Voip_ClientLoudness(pnum));
	}
#endif
	else
	{
		ret = InfoBuf_ValueForKey(&cl.players[pnum].userinfo, keyname);
	}
	return ret;
}

//string(string keyname)
static void QCBUILTIN PF_cs_serverkey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *keyname = PF_VarString(prinst, 0, pr_globals);
	const char *ret = PF_cs_serverkey_internal(keyname);
	if (*ret)
		RETURN_TSTRING(ret);
	else
		G_INT(OFS_RETURN) = 0;
}
static void QCBUILTIN PF_cs_serverkeyfloat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *keyname = PR_GetStringOfs(prinst, OFS_PARM0);
	const char *ret = PF_cs_serverkey_internal(keyname);
	if (*ret)
		G_FLOAT(OFS_RETURN) = strtod(ret, NULL);
	else
		G_FLOAT(OFS_RETURN) = (prinst->callargc >= 2)?G_FLOAT(OFS_PARM1):0;
}
static void QCBUILTIN PF_cs_serverkeyblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *keyname = PR_GetStringOfs(prinst, OFS_PARM0);
	int qcptr = G_INT(OFS_PARM1);
	int qcsize = G_INT(OFS_PARM2);
	void *ptr;
	size_t blobsize = 0;
	const char *blob;

	if (qcptr < 0 || qcptr+qcsize >= prinst->stringtablesize)
	{
		PR_BIError(prinst, "PF_cs_serverkeyblob: invalid pointer\n");
		return;
	}
	ptr = (struct reverbproperties_s*)(prinst->stringtable + qcptr);

	blob = InfoBuf_BlobForKey(&cl.serverinfo, keyname, &blobsize, NULL);

	if (qcptr)
	{
		blobsize = min(blobsize, qcsize);
		memcpy(ptr, blob, blobsize);
		G_INT(OFS_RETURN) = blobsize;
	}
	else
		G_INT(OFS_RETURN) = blobsize;
}

//string(float pnum, string keyname)
static void QCBUILTIN PF_cs_getplayerkeystring (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int pnum = G_FLOAT(OFS_PARM0);
	const char *keyname = PR_GetStringOfs(prinst, OFS_PARM1);
	const char *ret;
	if (pnum < 0)
	{
		if (csqc_resortfrags)
		{
			Sbar_SortFrags(true, false);
			csqc_resortfrags = false;
		}
		if (pnum >= -scoreboardlines)
		{//sort by
			pnum = fragsort[-(pnum+1)];
		}
	}

	ret = PF_cs_getplayerkey_internal(pnum, keyname);
	if (*ret)
		RETURN_TSTRING(ret);
	else
		G_INT(OFS_RETURN) = 0;
}
static void QCBUILTIN PF_cs_getplayerkeyfloat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int pnum = G_FLOAT(OFS_PARM0);
	const char *keyname = PR_GetStringOfs(prinst, OFS_PARM1);
	const char *ret;
	if (pnum < 0)
	{
		if (csqc_resortfrags)
		{
			Sbar_SortFrags(true, false);
			csqc_resortfrags = false;
		}
		if (pnum >= -scoreboardlines)
		{//sort by
			pnum = fragsort[-(pnum+1)];
		}
	}

	ret = PF_cs_getplayerkey_internal(pnum, keyname);
	if (*ret)
		G_FLOAT(OFS_RETURN) = strtod(ret, NULL);
	else
		G_FLOAT(OFS_RETURN) = (prinst->callargc >= 3)?G_FLOAT(OFS_PARM2):0;
}
static void QCBUILTIN PF_cs_getplayerkeyblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int pnum = G_FLOAT(OFS_PARM0);
	const char *keyname = PR_GetStringOfs(prinst, OFS_PARM1);
	int qcptr = G_INT(OFS_PARM2);
	int qcsize = G_INT(OFS_PARM3);
	void *ptr;
	if (pnum < 0)
	{
		if (csqc_resortfrags)
		{
			Sbar_SortFrags(true, false);
			csqc_resortfrags = false;
		}
		if (pnum >= -scoreboardlines)
		{//sort by
			pnum = fragsort[-(pnum+1)];
		}
	}

	if (qcptr < 0 || qcptr+qcsize >= prinst->stringtablesize)
	{
		PR_BIError(prinst, "PF_cs_getplayerkeyblob: invalid pointer\n");
		return;
	}
	ptr = (struct reverbproperties_s*)(prinst->stringtable + qcptr);

	if ((unsigned int)pnum >= (unsigned int)cl.allocated_client_slots)
		G_INT(OFS_RETURN) = 0;
	else
	{
		size_t blobsize = 0;
		const char *blob = InfoBuf_BlobForKey(&cl.players[pnum].userinfo, keyname, &blobsize, NULL);

		if (qcptr)
		{
			blobsize = min(blobsize, qcsize);
			memcpy(ptr, blob, blobsize);
			G_INT(OFS_RETURN) = blobsize;
		}
		else
			G_INT(OFS_RETURN) = blobsize;
	}
}

static void QCBUILTIN PF_cs_infokey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	const char *keyname = PR_GetStringOfs(prinst, OFS_PARM1);
	const char *ret;
	if (!ent->entnum)
		ret = PF_cs_serverkey_internal(keyname);
	else
	{
		int pnum = ent->xv->entnum-1;	//figure out which ssqc entity this is meant to be
		if (pnum < 0)
		{
			csqc_deprecated("infokey: entity does not correlate to an ssqc entity\n");
			ret = "";
		}
		else if (pnum >= cl.allocated_client_slots)
		{
			csqc_deprecated("infokey: entity does not correlate to an ssqc player entity\n");
			ret = "";
		}
		else
			ret = PF_cs_getplayerkey_internal(pnum, keyname);
	}
	if (*ret)
		RETURN_TSTRING(ret);
	else
		G_INT(OFS_RETURN) = 0;
}

static void QCBUILTIN PF_checkextension (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *extname = PR_GetStringOfs(prinst, OFS_PARM0);
	int i;
	for (i = 0; i < QSG_Extensions_count; i++)
	{
		if (!QSG_Extensions[i].name)
			continue;

		if (i < 32 && cls.protocol == CP_QUAKEWORLD)
			if (!(cls.fteprotocolextensions & (1<<i)))
				continue;

		if (!strcmp(QSG_Extensions[i].name, extname))
		{
			G_FLOAT(OFS_RETURN) = true;
			return;
		}
	}
	G_FLOAT(OFS_RETURN) = false;
}

void QCBUILTIN PF_soundupdate (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	wedict_t		*entity	= G_WEDICT(prinst, OFS_PARM0);
	int				channel	= G_FLOAT(OFS_PARM1);
	const char		*sample = PR_GetStringOfs(prinst, OFS_PARM2);
	float			volume = G_FLOAT(OFS_PARM3);
	float			attenuation = G_FLOAT(OFS_PARM4);
	float			pitchpct = (prinst->callargc >= 6)?G_FLOAT(OFS_PARM5)*0.01:0;
	unsigned int	flags = (prinst->callargc>=7)?G_FLOAT(OFS_PARM6):0;
	float			startoffset = (prinst->callargc>=8)?G_FLOAT(OFS_PARM7):0;

	sfx_t		*sfx = S_PrecacheSound(sample);
	vec3_t		org;

	VectorCopy(entity->v->origin, org);
	if (entity->v->solid == SOLID_BSP)
	{
		VectorMA(org, 0.5, entity->v->mins, org);
		VectorMA(org, 0.5, entity->v->maxs, org);
	}

	G_FLOAT(OFS_RETURN) = S_UpdateSound(-entity->entnum, channel, sfx, org, entity->v->velocity, volume, attenuation, startoffset, pitchpct, flags);
}
void QCBUILTIN PF_stopsound (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	wedict_t	*entity	= G_WEDICT(prinst, OFS_PARM0);
	int			channel	= G_FLOAT(OFS_PARM1);

	S_StopSound(-entity->entnum, channel);
}
void QCBUILTIN PF_getsoundtime (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	wedict_t	*entity	= G_WEDICT(prinst, OFS_PARM0);
	int			channel	= G_FLOAT(OFS_PARM1);

	G_FLOAT(OFS_RETURN) = S_GetSoundTime(-entity->entnum, channel);
}
void QCBUILTIN PF_getchannellevel (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	wedict_t	*entity	= G_WEDICT(prinst, OFS_PARM0);
	int			channel	= G_FLOAT(OFS_PARM1);

	G_FLOAT(OFS_RETURN) = S_GetChannelLevel(-entity->entnum, channel);
}
static void QCBUILTIN PF_cs_sound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char		*sample;
	int			channel;
	csqcedict_t		*entity;
	float volume;
	float attenuation;
	float pitchpct;
	unsigned int flags;
	float startoffset;

	sfx_t *sfx;
	vec3_t		org;

	entity = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	channel = G_FLOAT(OFS_PARM1);
	sample = PR_GetStringOfs(prinst, OFS_PARM2);
	volume = G_FLOAT(OFS_PARM3);
	attenuation = G_FLOAT(OFS_PARM4);
	pitchpct = (prinst->callargc>=6)?G_FLOAT(OFS_PARM5)*0.01:0;
	flags = (prinst->callargc>=7)?G_FLOAT(OFS_PARM6):0;
	startoffset = (prinst->callargc>=8)?G_FLOAT(OFS_PARM7):0;

	sfx = S_PrecacheSound(sample);

	VectorCopy(entity->v->origin, org);
	if (entity->v->solid == SOLID_BSP)
	{
		VectorMA(org, 0.5, entity->v->mins, org);
		VectorMA(org, 0.5, entity->v->maxs, org);
	}

	if (sfx)
		S_StartSound(-entity->entnum, channel, sfx, org, entity->v->velocity, volume, attenuation, startoffset, pitchpct, flags);
}

static void QCBUILTIN PF_cs_pointsound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *sample;
	float *origin;
	float volume;
	float attenuation;
	float pitchpct;

	sfx_t *sfx;

	origin = G_VECTOR(OFS_PARM0);
	sample = PR_GetStringOfs(prinst, OFS_PARM1);
	volume = G_FLOAT(OFS_PARM2);
	attenuation = G_FLOAT(OFS_PARM3);
	if (prinst->callargc >= 5)
		pitchpct = G_FLOAT(OFS_PARM4)*0.01;
	else
		pitchpct = 0;

	sfx = S_PrecacheSound(sample);
	if (sfx)
		S_StartSound(0, 0, sfx, origin, NULL, volume, attenuation, 0, pitchpct, 0);
}

static void QCBUILTIN PF_cs_particle(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *org = G_VECTOR(OFS_PARM0);
	float *dir = G_VECTOR(OFS_PARM1);
	float colour = G_FLOAT(OFS_PARM2);
	float count = G_FLOAT(OFS_PARM2);

	pe->RunParticleEffect(org, dir, colour, count);
}
static void QCBUILTIN PF_cs_particle2(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float		*org, *dmin, *dmax;
	float		colour;
	float		count;
	float    effect;

	org = G_VECTOR(OFS_PARM0);
	dmin = G_VECTOR(OFS_PARM1);
	dmax = G_VECTOR(OFS_PARM2);
	colour = G_FLOAT(OFS_PARM3);
	effect = G_FLOAT(OFS_PARM4);
	count = G_FLOAT(OFS_PARM5);

	pe->RunParticleEffect2 (org, dmin, dmax, colour, effect, count);
}

static void QCBUILTIN PF_cs_particle3(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float		*org, *box;
	float		colour;
	float		count;
	float    effect;

	org = G_VECTOR(OFS_PARM0);
	box = G_VECTOR(OFS_PARM1);
	colour = G_FLOAT(OFS_PARM2);
	effect = G_FLOAT(OFS_PARM3);
	count = G_FLOAT(OFS_PARM4);

	pe->RunParticleEffect3(org, box, colour, effect, count);
}

static void QCBUILTIN PF_cs_particle4(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float		*org;
	float		radius;
	float		colour;
	float		count;
	float    effect;

	org = G_VECTOR(OFS_PARM0);
	radius = G_FLOAT(OFS_PARM1);
	colour = G_FLOAT(OFS_PARM2);
	effect = G_FLOAT(OFS_PARM3);
	count = G_FLOAT(OFS_PARM4);

	pe->RunParticleEffect4(org, radius, colour, effect, count);
}


void QCBUILTIN PF_cl_effect(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *org = G_VECTOR(OFS_PARM0);
	const char *name = PR_GetStringOfs(prinst, OFS_PARM1);
	float startframe = G_FLOAT(OFS_PARM2);
	float endframe = G_FLOAT(OFS_PARM3);
	float framerate = G_FLOAT(OFS_PARM4);
	model_t *mdl;

	mdl = Mod_ForName(name, MLV_WARN);
	if (mdl)
		CL_SpawnSpriteEffect(org, NULL, NULL, mdl, startframe, endframe, framerate, mdl->type==mod_sprite?-1:1, 1, 0, 0, P_INVALID, 0, 0);
	else
		Con_Printf("PF_cl_effect: Couldn't load model %s\n", name);
}

void QCBUILTIN PF_cl_ambientsound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char		*samp;
	float		*pos;
	float 		vol, attenuation;

	pos = G_VECTOR (OFS_PARM0);
	samp = PR_GetStringOfs(prinst, OFS_PARM1);
	vol = G_FLOAT(OFS_PARM2);
	attenuation = G_FLOAT(OFS_PARM3);

	S_StaticSound (S_PrecacheSound (samp), pos, vol, attenuation);
}

static void QCBUILTIN PF_cs_lightstyle (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int stnum = G_FLOAT(OFS_PARM0);
	const char *str = PR_GetStringOfs(prinst, OFS_PARM1);
	vec3_t rgb = {1,1,1};
	
	if (prinst->callargc >= 3)	//fte is a quakeworld engine
		VectorCopy(G_VECTOR(OFS_PARM2), rgb);

	if ((unsigned)stnum >= cl_max_lightstyles)
	{
		Con_Printf ("PF_cs_lightstyle: stnum > MAX_LIGHTSTYLES");
		return;
	}
	R_UpdateLightStyle(stnum, str, rgb[0],rgb[1],rgb[2]);
}

static void QCBUILTIN PF_getlightstyle (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	unsigned int stnum = G_FLOAT(OFS_PARM0);

	if (stnum >= cl_max_lightstyles)
	{
		VectorSet(G_VECTOR(OFS_PARM1), 0, 0, 0);
		G_INT(OFS_RETURN) = 0;
		return;
	}

	VectorCopy(cl_lightstyle[stnum].colours, G_VECTOR(OFS_PARM1));
	if (!cl_lightstyle[stnum].length)
		G_INT(OFS_RETURN) = 0;
	else
		RETURN_TSTRING(cl_lightstyle[stnum].map);
}
static void QCBUILTIN PF_getlightstylergb (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	unsigned int stnum = G_FLOAT(OFS_PARM0);
	int value;	//could be float, but that would exceed the precision of R_AnimateLight

	if (stnum >= MAX_NET_LIGHTSTYLES)
	{
		Con_Printf ("PF_getlightstyle: stnum > MAX_LIGHTSTYLES");
		return;
	}

	if (stnum < cl_max_lightstyles || !cl_lightstyle[stnum].length)
		value = ('m'-'a')*22 * r_lightstylescale.value;
	else if (cl_lightstyle[stnum].map[0] == '=')
		value = atof(cl_lightstyle[stnum].map+1)*256*r_lightstylescale.value;
	else
	{
		int v1, v2, vd, i;
		float f;
		
		f = (cl.time*r_lightstylespeed.value);
		if (f < 0)
			f = 0;
		i = (int)f;
		f -= i;	//this can require updates at 1000 times a second.. Depends on your framerate of course

		v1 = i % cl_lightstyle[stnum].length;
		v1 = cl_lightstyle[stnum].map[v1] - 'a';
		v2 = (i+1) % cl_lightstyle[stnum].length;
		v2 = cl_lightstyle[stnum].map[v2] - 'a';
		vd = v1 - v2;
		if (!r_lightstylesmooth.ival || vd < -r_lightstylesmooth_limit.ival || vd > r_lightstylesmooth_limit.ival)
			value = v1*22*r_lightstylescale.value;
		else
			value = (v1*(1-f) + v2*(f))*22*r_lightstylescale.value;
	}

	VectorScale(cl_lightstyle[stnum].colours, value*(1.0/256), G_VECTOR(OFS_RETURN));
}

static void QCBUILTIN PF_cl_te_gunshot (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float scaler = 1;
	if (prinst->callargc >= 2)	//fte is a quakeworld engine
		scaler = G_FLOAT(OFS_PARM1);
	if (P_RunParticleEffectType(pos, NULL, scaler, pt_gunshot))
		P_RunParticleEffect (pos, vec3_origin, 0, 20*scaler);
}
static void QCBUILTIN PF_cl_te_bloodqw (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float scaler = 1;
	if (prinst->callargc >= 2)	//fte is a quakeworld engine
		scaler = G_FLOAT(OFS_PARM1);
	if (P_RunParticleEffectType(pos, NULL, scaler, ptqw_blood))
		if (P_RunParticleEffectType(pos, NULL, scaler, ptdp_blood))
			P_RunParticleEffect (pos, vec3_origin, 73, 20*scaler);
}
static void QCBUILTIN PF_cl_te_blooddp (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float *dir = G_VECTOR(OFS_PARM1);
	float scaler = G_FLOAT(OFS_PARM2);

	if (P_RunParticleEffectType(pos, dir, scaler, ptdp_blood))
		if (P_RunParticleEffectType(pos, dir, scaler, ptqw_blood))
			P_RunParticleEffect (pos, dir, 73, 20*scaler);
}
static void QCBUILTIN PF_cl_te_lightningblood (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectType(pos, NULL, 1, ptqw_lightningblood))
		P_RunParticleEffect (pos, vec3_origin, 225, 50);
}
static void QCBUILTIN PF_cl_te_spike (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectType(pos, NULL, 1, pt_spike))
		if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
			P_RunParticleEffect (pos, vec3_origin, 0, 10);
}
static void QCBUILTIN PF_cl_te_superspike (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectType(pos, NULL, 1, pt_superspike))
		if (P_RunParticleEffectType(pos, NULL, 2, pt_spike))
			if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
				P_RunParticleEffect (pos, vec3_origin, 0, 20);
}
static void QCBUILTIN PF_cl_te_explosion (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);

	// light
	if (r_explosionlight.value) {
		dlight_t *dl;

		dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = 150 + r_explosionlight.value*200;
		dl->die = cl.time + 1;
		dl->decay = 300;

		dl->color[0] = 0.2;
		dl->color[1] = 0.155;
		dl->color[2] = 0.05;
		dl->channelfade[0] = 0.196;
		dl->channelfade[1] = 0.23;
		dl->channelfade[2] = 0.12;
	}

	if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
		P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111

	Surf_AddStain(pos, -1, -1, -1, 100);

	S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
}
static void QCBUILTIN PF_cl_te_tarexplosion (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	P_RunParticleEffectType(pos, NULL, 1, pt_tarexplosion);

	S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
}
static void QCBUILTIN PF_cl_te_wizspike (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectType(pos, NULL, 1, pt_wizspike))
		P_RunParticleEffect (pos, vec3_origin, 20, 30);

	S_StartSound (0, 0, cl_sfx_knighthit, pos, NULL, 1, 1, 0, 0, 0);
}
static void QCBUILTIN PF_cl_te_knightspike (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectType(pos, NULL, 1, pt_knightspike))
		P_RunParticleEffect (pos, vec3_origin, 226, 20);

	S_StartSound (0, 0, cl_sfx_knighthit, pos, NULL, 1, 1, 0, 0, 0);
}
static void QCBUILTIN PF_cl_te_lavasplash (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	P_RunParticleEffectType(pos, NULL, 1, pt_lavasplash);
}
static void QCBUILTIN PF_cl_te_teleport (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	P_RunParticleEffectType(pos, NULL, 1, pt_teleportsplash);
}
static void QCBUILTIN PF_cl_te_gunshotquad (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectTypeString(pos, vec3_origin, 1, "te_gunshotquad"))
		if (P_RunParticleEffectType(pos, NULL, 1, pt_gunshot))
			P_RunParticleEffect (pos, vec3_origin, 0, 20);
}
static void QCBUILTIN PF_cl_te_spikequad (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectTypeString(pos, vec3_origin, 1, "te_spikequad"))
		if (P_RunParticleEffectType(pos, NULL, 1, pt_spike))
			if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
				P_RunParticleEffect (pos, vec3_origin, 0, 10);
}
static void QCBUILTIN PF_cl_te_superspikequad (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectTypeString(pos, vec3_origin, 1, "te_superspikequad"))
		if (P_RunParticleEffectType(pos, NULL, 1, pt_superspike))
			if (P_RunParticleEffectType(pos, NULL, 2, pt_spike))
				if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
					P_RunParticleEffect (pos, vec3_origin, 0, 20);
}
static void QCBUILTIN PF_cl_te_explosionquad (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	if (P_RunParticleEffectTypeString(pos, vec3_origin, 1, "te_explosionquad"))
		if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
			P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111

	Surf_AddStain(pos, -1, -1, -1, 100);

	// light
	if (r_explosionlight.value) {
		dlight_t *dl;

		dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = 150 + r_explosionlight.value*200;
		dl->die = cl.time + 1;
		dl->decay = 300;

		dl->color[0] = 0.2;
		dl->color[1] = 0.155;
		dl->color[2] = 0.05;
		dl->channelfade[0] = 0.196;
		dl->channelfade[1] = 0.23;
		dl->channelfade[2] = 0.12;
	}

	S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
}

//void(vector org, float radius, float lifetime, vector color) te_customflash
static void QCBUILTIN PF_cl_te_customflash (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *org = G_VECTOR(OFS_PARM0);
	float radius = G_FLOAT(OFS_PARM1);
	float lifetime = G_FLOAT(OFS_PARM2);
	float *colour = G_VECTOR(OFS_PARM3);

	dlight_t *dl;
	// light
	dl = CL_AllocDlight (0);
	VectorCopy (org, dl->origin);
	dl->radius = radius;
	dl->die = cl.time + lifetime;
	dl->decay = dl->radius / lifetime;
	dl->color[0] = colour[0]*0.5f;
	dl->color[1] = colour[1]*0.5f;
	dl->color[2] = colour[2]*0.5f;
}

static void QCBUILTIN PF_cl_te_bloodshower (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *minb = G_VECTOR(OFS_PARM0);
	float *maxb = G_VECTOR(OFS_PARM1);
	vec3_t vel = {0,0,-G_FLOAT(OFS_PARM2)};
	float howmany = G_FLOAT(OFS_PARM3);
	
	P_RunParticleCube(P_FindParticleType("te_bloodshower"), minb, maxb, vel, vel, howmany, 0, false, 0);
}
static void QCBUILTIN PF_cl_te_particlecube (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *minb = G_VECTOR(OFS_PARM0);
	float *maxb = G_VECTOR(OFS_PARM1);
	float *vel = G_VECTOR(OFS_PARM2);
	float howmany = G_FLOAT(OFS_PARM3);
	float color = G_FLOAT(OFS_PARM4);
	float gravity = G_FLOAT(OFS_PARM5);
	float jitter = G_FLOAT(OFS_PARM6);

	P_RunParticleCube(P_INVALID, minb, maxb, vel, vel, howmany, color, gravity, jitter);
}
static void QCBUILTIN PF_cl_te_spark (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	float *pos2 = G_VECTOR(OFS_PARM1);
	float cnt = G_FLOAT(OFS_PARM2);
	P_RunParticleEffectType(pos, pos2, cnt, ptdp_spark);
}
static void QCBUILTIN PF_cl_te_smallflash (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	dlight_t *dl = CL_AllocDlight (0);
	VectorCopy (pos, dl->origin);
	dl->radius = 200;
	dl->decay = 1000;
	dl->die = cl.time + 0.2;
	dl->color[0] = 2.0;
	dl->color[1] = 2.0;
	dl->color[2] = 2.0;
}
static void QCBUILTIN PF_cl_te_explosion2 (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);
	int colorStart = G_FLOAT(OFS_PARM1);
	int colorLength = G_FLOAT(OFS_PARM2);
	int ef = P_FindParticleType(va("TE_EXPLOSION2_%i_%i", colorStart, colorLength));
	if (ef == P_INVALID)
		ef = pt_explosion;
	P_RunParticleEffectType(pos, NULL, 1, ef);
	if (r_explosionlight.value)
	{
		dlight_t *dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = 350;
		dl->die = cl.time + 0.5;
		dl->decay = 300;
	}
	S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
}

static void QCBUILTIN PF_cl_te_flamejet (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	vec3_t pos, vel;
	int count;

	// origin
	pos[0] = MSG_ReadCoord ();
	pos[1] = MSG_ReadCoord ();
	pos[2] = MSG_ReadCoord ();

	// velocity
	vel[0] = MSG_ReadCoord ();
	vel[1] = MSG_ReadCoord ();
	vel[2] = MSG_ReadCoord ();

	// count
	count = MSG_ReadByte ();

	if (P_RunParticleEffectType(pos, vel, count, P_FindParticleType("TE_FLAMEJET")))
		P_RunParticleEffect (pos, vel, 232, count);
}

static void QCBUILTIN PF_cl_te_lightning1 (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	float *start = G_VECTOR(OFS_PARM1);
	float *end = G_VECTOR(OFS_PARM2);

	CL_AddBeam(BT_Q1LIGHTNING1, ent->entnum+(ent->entnum?MAX_EDICTS:0), start, end);
}
static void QCBUILTIN PF_cl_te_lightning2 (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	float *start = G_VECTOR(OFS_PARM1);
	float *end = G_VECTOR(OFS_PARM2);

	CL_AddBeam(BT_Q1LIGHTNING2, ent->entnum+(ent->entnum?MAX_EDICTS:0), start, end);
}
static void QCBUILTIN PF_cl_te_lightning3 (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	float *start = G_VECTOR(OFS_PARM1);
	float *end = G_VECTOR(OFS_PARM2);

	CL_AddBeam(BT_Q1LIGHTNING3, ent->entnum+(ent->entnum?MAX_EDICTS:0), start, end);
}
static void QCBUILTIN PF_cl_te_beam (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	float *start = G_VECTOR(OFS_PARM1);
	float *end = G_VECTOR(OFS_PARM2);

	CL_AddBeam(BT_Q1BEAM, ent->entnum+(ent->entnum?MAX_EDICTS:0), start, end);
}
static void QCBUILTIN PF_cl_te_plasmaburn (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *pos = G_VECTOR(OFS_PARM0);

	if (P_RunParticleEffectType(pos, NULL, 1, P_FindParticleType("te_plasmaburn")))
		P_RunParticleEffect(pos, vec3_origin, 15, 50);
}
static void QCBUILTIN PF_cl_te_explosionrgb (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *org = G_VECTOR(OFS_PARM0);
	float *colour = G_VECTOR(OFS_PARM1);

	dlight_t *dl;

	if (P_RunParticleEffectType(org, NULL, 1, pt_explosion))
		P_RunParticleEffect(org, NULL, 107, 1024); // should be 97-111

	Surf_AddStain(org, -1, -1, -1, 100);

	// light
	if (r_explosionlight.value)
	{
		dl = CL_AllocDlight (0);
		VectorCopy (org, dl->origin);
		dl->radius = 150 + r_explosionlight.value*200;
		dl->die = cl.time + 0.5;
		dl->decay = 300;

		dl->color[0] = 0.4f*colour[0];
		dl->color[1] = 0.4f*colour[1];
		dl->color[2] = 0.4f*colour[2];
		dl->channelfade[0] = 0;
		dl->channelfade[1] = 0;
		dl->channelfade[2] = 0;
	}

	S_StartSound (0, 0, cl_sfx_r_exp3, org, NULL, 1, 1, 0, 0, 0);
}
static void QCBUILTIN PF_cl_te_particlerain (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *min = G_VECTOR(OFS_PARM0);
	float *max = G_VECTOR(OFS_PARM1);
	float *vel = G_VECTOR(OFS_PARM2);
	float howmany = G_FLOAT(OFS_PARM3);
	float colour = G_FLOAT(OFS_PARM4);

	P_RunParticleWeather(min, max, vel, howmany, colour, "rain");
}
static void QCBUILTIN PF_cl_te_particlesnow (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *min = G_VECTOR(OFS_PARM0);
	float *max = G_VECTOR(OFS_PARM1);
	float *vel = G_VECTOR(OFS_PARM2);
	float howmany = G_FLOAT(OFS_PARM3);
	float colour = G_FLOAT(OFS_PARM4);

	P_RunParticleWeather(min, max, vel, howmany, colour, "snow");
}

void CSQC_RunThreads(void)
{
	csqctreadstate_t *state = csqcthreads, *next;
	csqcthreads = NULL;
	while(state)
	{
		next = state->next;

		if (state->resumetime > cl.servertime)
		{	//not time yet, reform original list.
			state->next = csqcthreads;
			csqcthreads = state;
		}
		else
		{	//call it and forget it ever happened. The Sleep biltin will recreate if needed.


			*csqcg.self = EDICT_TO_PROG(csqcprogs, EDICT_NUM_UB(csqcprogs, state->self));
			*csqcg.other = EDICT_TO_PROG(csqcprogs, EDICT_NUM_UB(csqcprogs, state->other));

			csqcprogs->RunThread(csqcprogs, state->thread);
			csqcprogs->parms->memfree(state->thread);
			csqcprogs->parms->memfree(state);
		}

		state = next;
	}
}

static void QCBUILTIN PF_cs_addprogs (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	const char *s = PR_GetStringOfs(prinst, OFS_PARM0);
	int newp;
	if (!s || !*s)
		newp = -1;
	else
	{
		newp = PR_LoadProgs(prinst, s);
		if (newp >= 0)
			PR_ProgsAdded(csqcprogs, newp, s);
	}
	G_FLOAT(OFS_RETURN) = newp;
}

static void QCBUILTIN PF_cs_OpenPortal (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
/*
#ifdef Q2BSPS
	if (cl.worldmodel->fromgame == fg_quake2)
	{
		int portal;
		int state	= G_FLOAT(OFS_PARM1)!=0;
		if (G_INT(OFS_PARM1) >= MAX_EDICTS)
			portal = G_FLOAT(OFS_PARM0);	//old legacy crap.
		else
			portal = G_WEDICT(prinst, OFS_PARM0)->xv->style;	//read the func_areaportal's style field.
		CMQ2_SetAreaPortalState(cl.worldmodel, portal, state);
	}
#endif
*/
#ifdef Q3BSPS
	if (cl.worldmodel->fromgame == fg_quake3)
	{
		int state	= G_FLOAT(OFS_PARM1)!=0;
		wedict_t *portal = G_WEDICT(prinst, OFS_PARM0);
		int area1 = portal->pvsinfo.areanum, area2 = portal->pvsinfo.areanum2;
		if (area1 == area2 || area1<0 || area2<0)
			return;
		CMQ3_SetAreaPortalState(cl.worldmodel, portal->pvsinfo.areanum, portal->pvsinfo.areanum2, state);
	}
#endif
}

static void QCBUILTIN PF_cs_droptofloor (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t		*ent;
	vec3_t		end;
	vec3_t		start;
	trace_t		trace;

	ent = (csqcedict_t*)PROG_TO_EDICT(prinst, *csqcg.self);

	VectorCopy (ent->v->origin, end);
	end[2] -= 512;

	VectorCopy (ent->v->origin, start);
	trace = World_Move (&csqc_world, start, ent->v->mins, ent->v->maxs, end, MOVE_NORMAL, (wedict_t*)ent);

	if (trace.fraction == 1 || trace.allsolid)
		G_FLOAT(OFS_RETURN) = 0;
	else
	{
		VectorCopy (trace.endpos, ent->v->origin);
		World_LinkEdict(&csqc_world, (wedict_t*)ent, false);
		ent->v->flags = (int)ent->v->flags | FL_ONGROUND;
		ent->v->groundentity = EDICT_TO_PROG(prinst, trace.ent);
		G_FLOAT(OFS_RETURN) = 1;
	}
}

static void QCBUILTIN PF_cl_getlight (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	vec3_t ambient, diffuse, dir;
	if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED || !cl.worldmodel->funcs.LightPointValues)
		VectorSet(G_VECTOR(OFS_RETURN), 0, 0, 0);
	else
	{
		cl.worldmodel->funcs.LightPointValues(cl.worldmodel, G_VECTOR(OFS_PARM0), ambient, diffuse, dir);
		VectorMA(ambient, 0.5, diffuse, G_VECTOR(OFS_RETURN));
	}
}

/*
static void QCBUILTIN PF_Stub (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	Con_Printf("Obsolete csqc builtin (%i) executed\n", prinst->lastcalledbuiltinnumber);
}
*/

static void QCBUILTIN PF_rotatevectorsbytag (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	int tagnum = G_FLOAT(OFS_PARM1);

	float *srcorg = ent->v->origin;
	int modelindex = ent->v->modelindex;

	float *retorg = G_VECTOR(OFS_RETURN);

	model_t *mod = CSQC_GetModelForIndex(modelindex);
	float transforms[12];
	float src[12];
	float dest[12];
	int i;
	framestate_t fstate;

	cs_getframestate(ent, ent->xv->renderflags, &fstate);

	if (Mod_GetTag(mod, tagnum, &fstate, transforms))
	{
		VectorCopy(csqcg.forward, src+0);
		src[3] = 0;
		VectorNegate(csqcg.right, src+4);
		src[7] = 0;
		VectorCopy(csqcg.up, src+8);
		src[11] = 0;

		if (ent->xv->scale)
		{
			for (i = 0; i < 12; i+=4)
			{
				transforms[i+0] *= ent->xv->scale;
				transforms[i+1] *= ent->xv->scale;
				transforms[i+2] *= ent->xv->scale;
				transforms[i+3] *= ent->xv->scale;
			}
		}

		R_ConcatRotationsPad((void*)transforms, (void*)src, (void*)dest);

		VectorCopy(dest+0, csqcg.forward);
		VectorNegate(dest+4, csqcg.right);
		VectorCopy(dest+8, csqcg.up);

		VectorCopy(srcorg, retorg);
		for (i = 0 ; i < 3 ; i++)
		{
			retorg[0] += transforms[i*4+3]*src[4*i+0];
			retorg[1] += transforms[i*4+3]*src[4*i+1];
			retorg[2] += transforms[i*4+3]*src[4*i+2];
		}
		return;
	}

	VectorCopy(srcorg, retorg);
}




static void QCBUILTIN PF_cs_break (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	PR_RunWarning (prinst, "break statement\n");
}

//fixme merge with ssqc
static void QCBUILTIN PF_cs_walkmove (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	wedict_t	*ent;
	float	yaw, dist;
	vec3_t	move;
//	dfunction_t	*oldf;
	int 	oldself;
	qboolean settrace;
	vec3_t axis[3];
	float s;

	ent = PROG_TO_WEDICT(prinst, *csqcg.self);
	yaw = G_FLOAT(OFS_PARM0);
	dist = G_FLOAT(OFS_PARM1);
	if (prinst->callargc >= 3 && G_FLOAT(OFS_PARM2))
		settrace = true;
	else
		settrace = false;

	if ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) )
	{
		G_FLOAT(OFS_RETURN) = 0;
		return;
	}

	World_GetEntGravityAxis(ent, axis);

	yaw = yaw*M_PI*2 / 360;

	s = cos(yaw)*dist;
	VectorScale(axis[0], s, move);
	s = sin(yaw)*dist;
	VectorMA(move, s, axis[1], move);

// save program state, because CS_movestep may call other progs
	oldself = *csqcg.self;

	G_FLOAT(OFS_RETURN) = World_movestep(&csqc_world, (wedict_t*)ent, move, axis, true, false, settrace?cs_settracevars:NULL);

// restore program state
	*csqcg.self = oldself;
}

//fixme merge with ssqc
static void QCBUILTIN PF_cs_movetogoal (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	wedict_t	*ent;
	float dist;
	ent = (wedict_t*)PROG_TO_EDICT(prinst, *csqcg.self);
	dist = G_FLOAT(OFS_PARM0);
	World_MoveToGoal (&csqc_world, ent, dist);
}

static void CS_ConsoleCommand_f(void)
{
	char cmd[2048];
	int seat = CL_TargettedSplit(false);
	Q_snprintfz(cmd, sizeof(cmd), "%s %s", Cmd_Argv(0), Cmd_Args());
	CSQC_ConsoleCommand(seat, cmd);
}
static void QCBUILTIN PF_cs_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, CS_ConsoleCommand_f);
}

static void QCBUILTIN PF_cs_setlistener (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	float *origin = G_VECTOR(OFS_PARM0);
	float *forward = G_VECTOR(OFS_PARM1);
	float *right = G_VECTOR(OFS_PARM2);
	float *up = G_VECTOR(OFS_PARM3);
	size_t reverbtype = (prinst->callargc>4)?G_FLOAT(OFS_PARM4):0;
	float *velocity = (prinst->callargc>5)?G_VECTOR(OFS_PARM5):NULL;

	int i = (csqc_playerseat>=0)?csqc_playerseat:0;

	cl.playerview[i].audio.defaulted = false;
	cl.playerview[i].audio.entnum = csqcg.player_localentnum?*csqcg.player_localentnum:cl.playerview[i].viewentity;
	VectorCopy(origin, cl.playerview[i].audio.origin);
	VectorCopy(forward, cl.playerview[i].audio.forward);
	VectorCopy(right, cl.playerview[i].audio.right);
	VectorCopy(up, cl.playerview[i].audio.up);
	cl.playerview[i].audio.reverbtype = reverbtype;
	if (velocity)
		VectorCopy(velocity, cl.playerview[i].audio.velocity);
	else
		VectorClear(cl.playerview[i].audio.velocity);
}
static void QCBUILTIN PF_cs_setupreverb (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int reverbslot = G_FLOAT(OFS_PARM0);
	int qcptr = G_INT(OFS_PARM1);
	int size = G_INT(OFS_PARM2);
	struct reverbproperties_s *ptr;

	//validates the pointer.
	if (qcptr < 0 || qcptr+size >= prinst->stringtablesize)
	{
		PR_BIError(prinst, "PF_cs_setupreverb: invalid reverb pointer\n");
		return;
	}
	ptr = (struct reverbproperties_s*)(prinst->stringtable + qcptr);

	//let the sound system do its thing.
	S_UpdateReverb(reverbslot, ptr, size);
}

#define RSES_NOLERP 1
#define RSES_NOROTATE 2
#define RSES_NOTRAILS 4
#define RSES_NOLIGHTS 8

static void CSQC_LerpStateToCSQC(lerpents_t *le, csqcedict_t *ent, qboolean nolerp)
{
	ent->v->frame = le->newframe[FS_REG];
	ent->xv->frame1time = max(0, cl.servertime - le->newframestarttime[FS_REG]);
	ent->xv->frame2 = le->oldframe[FS_REG];
	ent->xv->frame2time = max(0, cl.servertime - le->oldframestarttime[FS_REG]);
	ent->xv->lerpfrac = bound(0, 1-(ent->xv->frame1time) / le->framelerpdeltatime[FS_REG], 1);

	ent->xv->baseframe = le->newframe[FST_BASE];
	ent->xv->baseframe1time = max(0, cl.servertime - le->newframestarttime[FST_BASE]);
	ent->xv->baseframe2 = le->oldframe[FST_BASE];
	ent->xv->baseframe2time = max(0, cl.servertime - le->oldframestarttime[FST_BASE]);
	ent->xv->baselerpfrac = bound(0, 1-(ent->xv->baseframe1time) / le->framelerpdeltatime[FST_BASE], 1);
	ent->xv->basebone = le->basebone;


	if (nolerp)
	{
		ent->v->origin[0] = le->neworigin[0];
		ent->v->origin[1] = le->neworigin[1];
		ent->v->origin[2] = le->neworigin[2];
		ent->v->angles[0] = le->newangle[0];
		ent->v->angles[1] = le->newangle[1];
		ent->v->angles[2] = le->newangle[2];
	}
	else
	{
		ent->v->origin[0] = le->origin[0];
		ent->v->origin[1] = le->origin[1];
		ent->v->origin[2] = le->origin[2];
		ent->v->angles[0] = le->angles[0];
		ent->v->angles[1] = le->angles[1];
		ent->v->angles[2] = le->angles[2];
	}
}

void CSQC_EntStateToCSQC(unsigned int flags, float lerptime, entity_state_t *src, csqcedict_t *ent)
{
	model_t *model;
	lerpents_t		*le;

	le = &cl.lerpents[src->number];

	CSQC_LerpStateToCSQC(le, ent, flags & RSES_NOLERP);


	model = cl.model_precache[src->modelindex];
	if (!(flags & RSES_NOTRAILS))
	{
		//use entnum as a test to see if its new (if the old origin isn't usable)
		if (ent->xv->entnum)
		{
			if (model->particletrail == P_INVALID || pe->ParticleTrail (ent->v->origin, src->origin, model->particletrail, host_frametime, src->number, NULL, &(le->trailstate)))
				if (model->traildefaultindex >= 0)
					pe->ParticleTrailIndex(ent->v->origin, src->origin, P_INVALID, host_frametime, model->traildefaultindex, 0, &(le->trailstate));
		}
	}

	ent->xv->entnum = src->number;
	ent->v->modelindex = src->modelindex;
//	ent->xv->vw_index = src->modelindex2;
//	ent->v->flags = src->flags;
	ent->v->effects = src->effects;

//we ignore the q2 state fields

	ent->v->colormap = src->colormap;
	ent->v->skin = src->skinnum;
	ent->xv->scale = src->scale/16.0f;
	ent->xv->fatness = src->fatness/16.0f;
#ifdef HEXEN2
	ent->xv->drawflags = src->hexen2flags;
	ent->xv->abslight = src->abslight;
#endif
//	ent->xv->abslight = src->abslight;
//	ent->v->dpflags = src->dpflags;
	ent->xv->colormod[0] = src->colormod[0]*(8/256.0f);
	ent->xv->colormod[1] = src->colormod[1]*(8/256.0f);
	ent->xv->colormod[2] = src->colormod[2]*(8/256.0f);
	ent->xv->glowmod[0] = src->glowmod[0]*(8/256.0f);
	ent->xv->glowmod[1] = src->glowmod[1]*(8/256.0f);
	ent->xv->glowmod[2] = src->glowmod[2]*(8/256.0f);
//	ent->xv->glow_size = src->glowsize*4;
//	ent->xv->glow_color = src->glowcolour;
//	ent->xv->glow_trail = !!(state->dpflags & RENDER_GLOWTRAIL);
	ent->xv->alpha = (src->trans==255)?0:src->trans/254.0f;
//	ent->v->solid = src->solid;
//	ent->v->color[0] = src->light[0]/255.0;
//	ent->v->color[1] = src->light[1]/255.0;
//	ent->v->color[2] = src->light[2]/255.0;
//	ent->v->light_lev = src->light[3];
//	ent->xv->style = src->lightstyle;
//	ent->xv->pflags = src->lightpflags;

	ent->xv->tag_entity = src->tagentity;
	ent->xv->tag_index = src->tagindex;

	if (src->solidsize == ES_SOLID_BSP)
	{
		ent->v->solid = SOLID_BSP;
		VectorCopy(model->mins, ent->v->mins);
		VectorCopy(model->maxs, ent->v->maxs);
	}
	else if (src->solidsize)
	{
		ent->v->solid = SOLID_BBOX;
		COM_DecodeSize(src->solidsize, ent->v->mins, ent->v->maxs);
	}
	else
		ent->v->solid = SOLID_NOT;

	ent->v->movetype = src->u.q1.pmovetype;
	ent->v->velocity[0] = src->u.q1.velocity[0] * (1/8.0);
	ent->v->velocity[1] = src->u.q1.velocity[1] * (1/8.0);
	ent->v->velocity[2] = src->u.q1.velocity[2] * (1/8.0);

	if (model)
	{
		if (!(flags & RSES_NOROTATE) && (model->flags & MF_ROTATE))
		{
			ent->v->angles[0] = 0;
			ent->v->angles[1] = 100*lerptime;
			ent->v->angles[2] = 0;
		}
	}
}
void CSQC_PlayerStateToCSQC(int pnum, player_state_t *srcp, csqcedict_t *ent)
{
	ent->xv->entnum = pnum+1;

	ent->v->modelindex = srcp->modelindex;
//	ent->xv->vw_index = srcp->modelindex2;
	ent->v->skin = srcp->skinnum;

	CSQC_LerpStateToCSQC(&cl.lerpplayers[pnum], ent, true);
	ent->xv->lerpfrac = 1-(ent->xv->frame1time) / cl.lerpplayers[pnum].framelerpdeltatime[FS_REG];
	ent->xv->lerpfrac = bound(0, ent->xv->lerpfrac, 1);


	VectorCopy(srcp->origin, ent->v->origin);
	VectorCopy(srcp->viewangles, ent->v->angles);

	VectorCopy(srcp->velocity, ent->v->velocity);
	ent->v->angles[0] *= r_meshpitch.value * 0.333;
	ent->v->colormap = pnum+1;
	ent->xv->scale = srcp->scale;
	//ent->v->fatness = srcp->fatness;
	ent->xv->alpha = (srcp->alpha==255)?0:(srcp->alpha/254.0f);

//	ent->v->colormod[0] = srcp->colormod[0]*(8/256.0f);
//	ent->v->colormod[1] = srcp->colormod[1]*(8/256.0f);
//	ent->v->colormod[2] = srcp->colormod[2]*(8/256.0f);
//	ent->v->effects = srcp->effects;
}

unsigned int deltaflags[MAX_PRECACHE_MODELS];
func_t deltafunction[MAX_PRECACHE_MODELS];

typedef struct
{
	unsigned int readpos;	//pos
	unsigned int numents;	//present
	unsigned int maxents;	//buffer size
	struct
	{
		unsigned int n;	//don't rely on the ent->v->entnum, as csqc can corrupt that
		csqcedict_t *e;	//the csqc ent
	} *e;
} csqcdelta_pack_t;
static csqcdelta_pack_t csqcdelta_pack_new;
static csqcdelta_pack_t csqcdelta_pack_old;
float csqcdelta_time;

static csqcedict_t *csqcdelta_playerents[MAX_CLIENTS];

static void CSQC_EntRemove(csqcedict_t *ed)
{
	if (csqcg.ent_remove)
	{
		*csqcg.self = EDICT_TO_PROG(csqcprogs, ed);
		PR_ExecuteProgram(csqcprogs, csqcg.ent_remove);
	}
	else
	{
		pe->DelinkTrailstate(&ed->trailstate);
		World_UnlinkEdict((wedict_t*)ed);
		ED_Free (csqcprogs, (void*)ed);
	}
}

qboolean CSQC_DeltaPlayer(int playernum, player_state_t *state)
{
	func_t func;

	if (!state || state->modelindex <= 0 || state->modelindex >= MAX_PRECACHE_MODELS)
	{
		if (csqcdelta_playerents[playernum])
		{
			CSQC_EntRemove(csqcdelta_playerents[playernum]);
			csqcdelta_playerents[playernum] = NULL;
		}
		return false;
	}

	func = deltafunction[state->modelindex];
	if (func)
	{
		void *pr_globals;
		csqcedict_t *ent;

		ent = csqcdelta_playerents[playernum];
		if (!ent)
		{
			ent = (csqcedict_t *)ED_Alloc(csqcprogs, false, 0);
			ent->xv->drawmask = MASK_DELTA;
		}

		CSQC_PlayerStateToCSQC(playernum, state, ent);

		*csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent);
		pr_globals = PR_globals(csqcprogs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = !csqcdelta_playerents[playernum];
		PR_ExecuteProgram(csqcprogs, func);

		csqcdelta_playerents[playernum] = ent;

		return G_FLOAT(OFS_RETURN);
	}
	else if (csqcdelta_playerents[playernum])
	{
		CSQC_EntRemove(csqcdelta_playerents[playernum]);
		csqcdelta_playerents[playernum] = NULL;
	}
	return false;
}

void CSQC_DeltaStart(float time)
{
	csqcdelta_pack_t tmp;
	csqcdelta_time = time;

	tmp = csqcdelta_pack_new;
	csqcdelta_pack_new = csqcdelta_pack_old;
	csqcdelta_pack_old = tmp;

	csqcdelta_pack_new.numents = 0;

	csqcdelta_pack_new.readpos = 0;
	csqcdelta_pack_old.readpos = 0;
}
qboolean CSQC_DeltaUpdate(entity_state_t *src)
{
	//FTE ensures that this function is called with increasing ent numbers each time
	func_t func;
	func = deltafunction[src->modelindex];
	if (func)
	{
		void *pr_globals;
		csqcedict_t *ent, *oldent;

		while (csqcdelta_pack_old.readpos < csqcdelta_pack_old.numents && csqcdelta_pack_old.e[csqcdelta_pack_old.readpos].n < src->number)
		{
			//this entity is stale, remove it.
			CSQC_EntRemove(csqcdelta_pack_old.e[csqcdelta_pack_old.readpos].e);
			csqcdelta_pack_old.readpos++;
		}

		if (csqcdelta_pack_old.readpos >= csqcdelta_pack_old.numents)
			oldent = NULL;	//reached the end of the old frame's ents (so we must be new)
		else if (src->number < csqcdelta_pack_old.e[csqcdelta_pack_old.readpos].n)
			oldent = NULL;	//there's a gap, this one must be new.
		else
		{	//already known.
			oldent = csqcdelta_pack_old.e[csqcdelta_pack_old.readpos].e;
			csqcdelta_pack_old.readpos++;
		}

		if (src->number < maxcsqcentities && csqcent[src->number])
		{
			//in the csqc list (don't permit in the delta list too)
			if (oldent)
				CSQC_EntRemove(oldent);
			return false;
		}

		if (oldent)
			ent = oldent;
		else
		{
			ent = (csqcedict_t *)ED_Alloc(csqcprogs, false, 0);
			if (!csqc_isdarkplaces)
				ent->xv->drawmask = MASK_DELTA;
		}

		CSQC_EntStateToCSQC(deltaflags[src->modelindex], csqcdelta_time, src, ent);


		*csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent);
		pr_globals = PR_globals(csqcprogs, PR_CURRENT);
		G_FLOAT(OFS_PARM0) = !oldent;
		PR_ExecuteProgram(csqcprogs, func);


		if (csqcdelta_pack_new.maxents <= csqcdelta_pack_new.numents)
		{
			csqcdelta_pack_new.maxents = csqcdelta_pack_new.numents + 64;
			csqcdelta_pack_new.e = BZ_Realloc(csqcdelta_pack_new.e, sizeof(*csqcdelta_pack_new.e)*csqcdelta_pack_new.maxents);
		}
		csqcdelta_pack_new.e[csqcdelta_pack_new.numents].e = ent;
		csqcdelta_pack_new.e[csqcdelta_pack_new.numents].n = src->number;
		csqcdelta_pack_new.numents++;

		return G_FLOAT(OFS_RETURN);
	}
	return false;
}

void CSQC_DeltaEnd(void)
{
	//remove any unreferenced ents stuck on the end
	while (csqcdelta_pack_old.readpos < csqcdelta_pack_old.numents)
	{
		CSQC_EntRemove(csqcdelta_pack_old.e[csqcdelta_pack_old.readpos].e);
		csqcdelta_pack_old.readpos++;
	}
}

static void QCBUILTIN PF_DeltaListen(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int i;
	const char *mname = PR_GetStringOfs(prinst, OFS_PARM0);
	func_t func = G_INT(OFS_PARM1);
	unsigned int flags = G_FLOAT(OFS_PARM2);

	if (csqc_nogameaccess)
	{
		csqc_deprecated("PF_DeltaListen: game access is blocked");
		return;
	}

	if (!prinst->GetFunctionInfo(prinst, func, NULL, NULL, NULL, NULL, 0))
	{
		Con_Printf("PF_DeltaListen: Bad function index\n");
		return;
	}

	if (!strcmp(mname, "*"))
	{
		//yes, even things that are not allocated yet
		for (i = 0; i < MAX_PRECACHE_MODELS; i++)
		{
			deltafunction[i] = func;
			deltaflags[i] = flags;
		}
	}
	else
	{
		for (i = 1; i < MAX_PRECACHE_MODELS; i++)
		{
			if (!*cl.model_name[i])
				break;
			if (!strcmp(cl.model_name[i], mname))
			{
				deltafunction[i] = func;
				deltaflags[i] = flags;
				break;
			}
		}
	}
}

static void AngleVectorsIndex (const vec3_t angles, int modelindex, vec3_t forward, vec3_t right, vec3_t up)
{
	vec3_t fixedangles;
	fixedangles[0] = angles[0] * CSQC_PitchScaleForModelIndex(modelindex);
	fixedangles[1] = angles[1];
	fixedangles[2] = angles[2];
	AngleVectors(fixedangles, forward, right, up);
}
static void QCBUILTIN PF_getentity(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int entnum = G_FLOAT(OFS_PARM0);
	int fldnum = G_FLOAT(OFS_PARM1);
	lerpents_t *le;
	entity_state_t *es;

	if (csqc_nogameaccess)
	{
		G_FLOAT(OFS_RETURN+0) = 0;
		G_FLOAT(OFS_RETURN+1) = 0;
		G_FLOAT(OFS_RETURN+2) = 0;
		return;
	}

	if (fldnum == GE_MAXENTS)
	{
		G_FLOAT(OFS_RETURN) = cl.maxlerpents;
		return;
	}

	if (entnum > 0 && entnum <= cl.allocated_client_slots && cl.lerpplayers[entnum-1].sequence == cl.lerpentssequence)
	{
		player_state_t *ps = &cl.inframes[cl.validsequence&UPDATE_MASK].playerstate[entnum-1];
		le = &cl.lerpplayers[entnum-1];

		switch(fldnum)
		{
		case GE_ACTIVE:
			G_FLOAT(OFS_RETURN) = 1;
			break;

		case GE_ORIGIN:
			/*lerped position*/
			VectorCopy(le->origin, G_VECTOR(OFS_RETURN));
			break;
		case GE_SCALE:
			G_FLOAT(OFS_RETURN) = ps->scale / 16.0f;
			break;
		case GE_ALPHA:
			G_FLOAT(OFS_RETURN) = ps->alpha / 255.0f;
			break;
		case GE_COLORMOD:
			G_FLOAT(OFS_RETURN+0) = ps->colourmod[0] / 8.0f;
			G_FLOAT(OFS_RETURN+1) = ps->colourmod[1] / 8.0f;
			G_FLOAT(OFS_RETURN+2) = ps->colourmod[2] / 8.0f;
			break;
		case GE_SKIN:
			G_FLOAT(OFS_RETURN) = ps->skinnum;
			break;
		case GE_MINS:
			VectorCopy(ps->szmins, G_VECTOR(OFS_RETURN));
			break;
		case GE_MAXS:
			VectorCopy(ps->szmaxs, G_VECTOR(OFS_RETURN));
			break;

		case GE_ABSMIN:
			VectorAdd(ps->szmins, le->origin, G_VECTOR(OFS_RETURN));
			break;
		case GE_ABSMAX:
			VectorAdd(ps->szmaxs, le->origin, G_VECTOR(OFS_RETURN));
			break;
		case GE_ORIGINANDVECTORS:
			VectorCopy(le->origin, G_VECTOR(OFS_RETURN));
			AngleVectorsIndex(le->angles, ps->modelindex, csqcg.forward, csqcg.right, csqcg.up);
			break;
		case GE_FORWARD:
			AngleVectorsIndex(le->angles, ps->modelindex, G_VECTOR(OFS_RETURN), NULL, NULL);
			break;
		case GE_RIGHT:
			AngleVectorsIndex(le->angles, ps->modelindex, NULL, G_VECTOR(OFS_RETURN), NULL);
			break;
		case GE_UP:
			AngleVectorsIndex(le->angles, ps->modelindex, NULL, NULL, G_VECTOR(OFS_RETURN));
			break;
		case GE_PANTSCOLOR:
			G_FLOAT(OFS_RETURN) = cl.players[entnum-1].dbottomcolor;
			break;
		case GE_SHIRTCOLOR:
			G_FLOAT(OFS_RETURN) = cl.players[entnum-1].dtopcolor;
			break;
		case GE_LIGHT:
			G_FLOAT(OFS_RETURN) = 0;
			break;

		case GE_MODELINDEX:
			G_FLOAT(OFS_RETURN) = ps->modelindex;
			break;
		case GE_MODELINDEX2:
			G_FLOAT(OFS_RETURN) = ps->command.impulse;	//evil hack
			break;
		case GE_EFFECTS:
			G_FLOAT(OFS_RETURN) = ps->effects;
			break;
		case GE_FRAME:
			G_FLOAT(OFS_RETURN) = ps->frame;
			break;
		case GE_ANGLES:
			VectorCopy(le->angles, G_VECTOR(OFS_RETURN));
			break;
		case GE_FATNESS:
			G_FLOAT(OFS_RETURN) = ps->fatness;
			break;
		case GE_DRAWFLAGS:
			G_FLOAT(OFS_RETURN) = SCALE_ORIGIN_ORIGIN;
			break;
		case GE_ABSLIGHT:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_GLOWMOD:
			VectorSet(G_VECTOR(OFS_RETURN), 1, 1, 1);
			break;
		case GE_GLOWSIZE:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_GLOWCOLOUR:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_RTSTYLE:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_RTPFLAGS:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_RTCOLOUR:
			VectorSet(G_VECTOR(OFS_RETURN), 1, 1, 1);
			break;
		case GE_RTRADIUS:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_TAGENTITY:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_TAGINDEX:
			G_FLOAT(OFS_RETURN) = 0;
			break;
		case GE_GRAVITYDIR:
			VectorCopy(ps->gravitydir, G_VECTOR(OFS_RETURN));
			break;
		case GE_TRAILEFFECTNUM:
			G_FLOAT(OFS_RETURN) = 0;
			break;

		default:
			VectorCopy(vec3_origin, G_VECTOR(OFS_RETURN));
			break;
		}
		return;
	}

	if (entnum >= cl.maxlerpents || !cl.lerpentssequence || cl.lerpents[entnum].sequence != cl.lerpentssequence)
	{
		if (fldnum != GE_ACTIVE)
			Con_DPrintf("PF_getentity: entity %i is not valid\n", entnum);
		VectorCopy(vec3_origin, G_VECTOR(OFS_RETURN));
		return;
	}
	le = &cl.lerpents[entnum];
	es = le->entstate;
	switch(fldnum)
	{
	case GE_ACTIVE:
		G_FLOAT(OFS_RETURN) = 1;
		break;
	case GE_ORIGIN:
		/*lerped position*/
		VectorCopy(le->origin, G_VECTOR(OFS_RETURN));
		break;
	case GE_SCALE:
		G_FLOAT(OFS_RETURN) = es->scale / 16.0f;
		break;
	case GE_ALPHA:
		G_FLOAT(OFS_RETURN) = es->trans / 255.0f;
		break;
	case GE_COLORMOD:
		G_FLOAT(OFS_RETURN+0) = es->colormod[0] / 8.0f;
		G_FLOAT(OFS_RETURN+1) = es->colormod[1] / 8.0f;
		G_FLOAT(OFS_RETURN+2) = es->colormod[2] / 8.0f;
		break;
	case GE_SKIN:
		G_FLOAT(OFS_RETURN) = es->skinnum;
		break;
	case GE_MINS:
		{
			vec3_t maxs;
			COM_DecodeSize(es->solidsize, G_VECTOR(OFS_RETURN), maxs);
		}
		break;
	case GE_MAXS:
		{
			vec3_t mins;
			COM_DecodeSize(es->solidsize, mins, G_VECTOR(OFS_RETURN));
		}
		break;

	case GE_ABSMIN:
		{
			vec3_t maxs;
			COM_DecodeSize(es->solidsize, G_VECTOR(OFS_RETURN), maxs);
			VectorAdd(G_VECTOR(OFS_RETURN), le->origin, G_VECTOR(OFS_RETURN));
		}
		break;
	case GE_ABSMAX:
		{
			vec3_t mins;
			COM_DecodeSize(es->solidsize, mins, G_VECTOR(OFS_RETURN));
			VectorAdd(G_VECTOR(OFS_RETURN), le->origin, G_VECTOR(OFS_RETURN));
		}
		break;
	case GE_ORIGINANDVECTORS:
		VectorCopy(le->origin, G_VECTOR(OFS_RETURN));
		AngleVectorsIndex(le->angles, es->modelindex, csqcg.forward, csqcg.right, csqcg.up);
		break;
	case GE_FORWARD:
		AngleVectorsIndex(le->angles, es->modelindex, G_VECTOR(OFS_RETURN), NULL, NULL);
		break;
	case GE_RIGHT:
		AngleVectorsIndex(le->angles, es->modelindex, NULL, G_VECTOR(OFS_RETURN), NULL);
		break;
	case GE_UP:
		AngleVectorsIndex(le->angles, es->modelindex, NULL, NULL, G_VECTOR(OFS_RETURN));
		break;
	case GE_PANTSCOLOR:
		if (es->colormap <= cl.allocated_client_slots && !(es->dpflags & RENDER_COLORMAPPED))
			G_FLOAT(OFS_RETURN) = cl.players[es->colormap].dbottomcolor;
		else
			G_FLOAT(OFS_RETURN) = es->colormap & 15;
		break;
	case GE_SHIRTCOLOR:
		if (es->colormap <= cl.allocated_client_slots && !(es->dpflags & RENDER_COLORMAPPED))
			G_FLOAT(OFS_RETURN) = cl.players[es->colormap].dtopcolor;
		else
			G_FLOAT(OFS_RETURN) = es->colormap>>4;
		break;
	case GE_LIGHT:
		G_FLOAT(OFS_RETURN) = 0;
		break;

	case GE_MODELINDEX:
		G_FLOAT(OFS_RETURN) = es->modelindex;
		break;
	case GE_MODELINDEX2:
		G_FLOAT(OFS_RETURN) = es->modelindex2;
		break;
	case GE_EFFECTS:
		G_FLOAT(OFS_RETURN) = es->effects;
		break;
	case GE_FRAME:
		G_FLOAT(OFS_RETURN) = es->frame;
		break;
	case GE_ANGLES:
		VectorCopy(le->angles, G_VECTOR(OFS_RETURN));
		break;
	case GE_FATNESS:
		G_FLOAT(OFS_RETURN) = es->fatness;
		break;
	case GE_DRAWFLAGS:
		G_FLOAT(OFS_RETURN) = es->hexen2flags;
		break;
	case GE_ABSLIGHT:
		G_FLOAT(OFS_RETURN) = es->abslight;
		break;
	case GE_GLOWMOD:
		VectorScale(es->glowmod, 1/8.0, G_VECTOR(OFS_RETURN));
		break;
	case GE_GLOWSIZE:
		G_FLOAT(OFS_RETURN) = es->glowsize;
		break;
	case GE_GLOWCOLOUR:
		G_FLOAT(OFS_RETURN) = es->glowcolour;
		break;
	case GE_RTSTYLE:
		G_FLOAT(OFS_RETURN) = es->lightstyle;
		break;
	case GE_RTPFLAGS:
		G_FLOAT(OFS_RETURN) = es->lightpflags;
		break;
	case GE_RTCOLOUR:
		VectorScale(es->light, 1/1024.0, G_VECTOR(OFS_RETURN));
		break;
	case GE_RTRADIUS:
		G_FLOAT(OFS_RETURN) = es->light[3];
		break;
	case GE_TAGENTITY:
		G_FLOAT(OFS_RETURN) = es->tagentity;
		break;
	case GE_TAGINDEX:
		G_FLOAT(OFS_RETURN) = es->tagindex;
		break;
	case GE_GRAVITYDIR:
		{
			vec3_t a;
			a[0] = ((192+es->u.q1.gravitydir[0])/256.0f) * 360;
			a[1] = (es->u.q1.gravitydir[1]/256.0f) * 360;
			a[2] = 0;
			AngleVectors(a, G_VECTOR(OFS_RETURN), NULL, NULL);
		}
		break;
	case GE_TRAILEFFECTNUM:
		G_FLOAT(OFS_RETURN) = es->u.q1.traileffectnum;
		break;

	default:
		Con_Printf("PF_getentity: field %i is not supported\n", fldnum);
		VectorCopy(vec3_origin, G_VECTOR(OFS_RETURN));
		break;
	}
}


static void QCBUILTIN PF_cs_getplayerstat(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	unsigned int playernum = G_FLOAT(OFS_PARM0);
	unsigned int statnum = G_FLOAT(OFS_PARM1);
	unsigned int stattype = G_FLOAT(OFS_PARM2);
	unsigned int i, j;
	if (playernum >= cl.allocated_client_slots || statnum >= MAX_CL_STATS)
		stattype = ev_void;

	switch(stattype)
	{
	default:
	case ev_void:
		G_FLOAT(OFS_RETURN+0) = 0;
		G_FLOAT(OFS_RETURN+1) = 0;
		G_FLOAT(OFS_RETURN+2) = 0;
		break;

	case ev_integer:
	case ev_field:		//Hopefully NOT useful, certainly not reliable
	case ev_function:	//Hopefully NOT useful
	case ev_pointer:	//NOT useful in a networked capacity.
		G_INT(OFS_RETURN) = cl.players[playernum].stats[statnum];
		break;

	case ev_float:
		G_FLOAT(OFS_RETURN) = cl.players[playernum].statsf[statnum];
		break;
	case ev_vector:
		G_FLOAT(OFS_RETURN+0) = (statnum+0 >= MAX_CL_STATS)?0:cl.players[playernum].statsf[statnum+0];
		G_FLOAT(OFS_RETURN+1) = (statnum+1 >= MAX_CL_STATS)?0:cl.players[playernum].statsf[statnum+1];
		G_FLOAT(OFS_RETURN+2) = (statnum+2 >= MAX_CL_STATS)?0:cl.players[playernum].statsf[statnum+2];
		break;
	case ev_entity:
		j = cl.players[playernum].stats[statnum];
		if (j < maxcsqcentities && csqcent[j])
			G_INT(OFS_RETURN) = EDICT_TO_PROG(csqcprogs, csqcent[j]);
		else if (j <= cl.allocated_client_slots && j > 0 && csqcdelta_playerents[j])
			G_INT(OFS_RETURN) = EDICT_TO_PROG(csqcprogs, csqcdelta_playerents[j]);
		else
		{
			G_INT(OFS_RETURN) = 0;
			//scan for the delta entity reference.
			for (i = 0; i < csqcdelta_pack_new.numents; i++)
			{
				if (csqcdelta_pack_old.e[i].n == j && csqcdelta_pack_old.e[i].e)
				{
					G_INT(OFS_RETURN) = EDICT_TO_PROG(csqcprogs, csqcdelta_pack_old.e[i].e);
					break;
				}
			}
		}
		break;
	case ev_string:
		G_INT(OFS_RETURN) = 0;	//FIXME: no info, these are not currently tracked in mvds apparently.
		break;
	}
}

void CL_CalcCrouch (playerview_t *pv);
static void QCBUILTIN PF_V_CalcRefdef(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{	//this function is essentially an overcomplicated way to shirk from defining your own view bobbing.
	csqcedict_t *ent = (csqcedict_t*)G_EDICT(prinst, OFS_PARM0);
	vec3_t savedvel;
	/*enum
	{
		TELEPORTED,
		JUMPING,
		DEAD,
		INTERMISSION
	} flags = G_FLOAT(OFS_PARM1);*/
//	csqc_deprecated("V_CalcRefdef has too much undefined behaviour.\n");
//	if (ent->xv->entnum >= 1 && ent->xv->entnum <= MAX_CLIENTS)
//		CSQC_ChangeLocalPlayer(ent->xv->entnum-1);

	/* xonotic requires:
	   cl_followmodel
	   cl_smoothviewheight
	   cl_bobfall
	*/

	csqc_rebuildmatricies = true;

	CL_DecayLights ();

//	CL_ClearEntityLists();

	V_ClearRefdef(csqc_playerview);
	r_refdef.drawsbar = false;	//csqc defaults to no sbar.
	r_refdef.drawcrosshair = false;

	VectorCopy(csqc_playerview->simvel, savedvel);
	VectorCopy(ent->v->origin, csqc_playerview->simorg);
	VectorCopy (ent->v->velocity, csqc_playerview->simvel);
	csqc_playerview->onground = !!((int)ent->v->flags & FL_ONGROUND);
	CL_CalcCrouch (csqc_playerview);
	V_CalcRefdef(csqc_playerview);	//set up the defaults

	VectorCopy(savedvel, csqc_playerview->simvel);
}

#if 1
//static void QCBUILTIN PF_ReadServerEntityState(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
//{
//}
#else
packet_entities_t *CL_ProcessPacketEntities(float *servertime, qboolean nolerp);
static void QCBUILTIN PF_ReadServerEntityState(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//read the arguments the csqc gave us
	unsigned int flags = G_FLOAT(OFS_PARM0);
	float servertime = G_FLOAT(OFS_PARM1);

	//locals
	packet_entities_t *pack;
	csqcedict_t *ent;
	entity_state_t *src;
	unsigned int i;
	lerpents_t		*le;
	csqcedict_t *oldent;
	oldcsqcpack_t *oldlist, *newlist;
	int oldidx = 0, newidx = 0;
	model_t *model;
	player_state_t *srcp;

	//setup
	servertime += cl.servertime;
	pack = CL_ProcessPacketEntities(&servertime, (flags & RSES_NOLERP));
	if (!pack)
		return;	//we're lagging. can't do anything, just don't update

	for (i = 0; i < cl.allocated_client_slots; i++)
	{
		srcp = &cl.frames[cl.validsequence&UPDATE_MASK].playerstate[i];
		ent = deltaedplayerents[i];
		if (srcp->messagenum == cl.validsequence && (i+1 >= maxcsqcentities || !csqcent[i+1]))
		{
			if (!ent)
			{
				ent = (csqcedict_t *)ED_Alloc(prinst);
				deltaedplayerents[i] = ent;
				G_FLOAT(OFS_PARM0) = true;
			}
			else
			{
				G_FLOAT(OFS_PARM0) = false;
			}

			CSQC_PlayerStateToCSQC(i, srcp, ent);

			if (csqcg.delta_update)
			{
				*csqcg.self = EDICT_TO_PROG(prinst, (void*)ent);
				PR_ExecuteProgram(prinst, csqcg.delta_update);
			}
		}
		else if (ent)
		{
			*csqcg.self = EDICT_TO_PROG(prinst, (void*)ent);
			PR_ExecuteProgram(prinst, csqcg.delta_remove);
			deltaedplayerents[i] = NULL;
		}
	}

	oldlist = &loadedcsqcpack[loadedcsqcpacknum];
	loadedcsqcpacknum ^= 1;
	newlist = &loadedcsqcpack[loadedcsqcpacknum];
	newlist->numents = 0;

	for (i = 0; i < pack->num_entities; i++)
	{
		src = &pack->entities[i];
// CL_LinkPacketEntities

#ifdef warningmsg
#pragma warningmsg("what to do here?")
#endif
//		if (csqcent[src->number])
//			continue;	//don't add the entity if we have one sent specially via csqc protocols.

		if (oldidx == oldlist->numents)
		{	//reached the end of the old frame's ents
			oldent = NULL;
		}
		else
		{
			while (oldidx < oldlist->numents && oldlist->entnum[oldidx] < src->number)
			{
				//this entity is stale, remove it.
				oldent = oldlist->entptr[oldidx];
				*csqcg.self = EDICT_TO_PROG(prinst, (void*)oldent);
				PR_ExecuteProgram(prinst, csqcg.delta_remove);
				oldidx++;
			}

			if (src->number < oldlist->entnum[oldidx])
				oldent = NULL;
			else
			{
				oldent = oldlist->entptr[oldidx];
				oldidx++;
			}
		}

		if (src->number < maxcsqcentities && csqcent[src->number])
		{
			//in the csqc list
			if (oldent)
			{
				*csqcg.self = EDICT_TO_PROG(prinst, (void*)oldent);
				PR_ExecuteProgram(prinst, csqcg.delta_remove);
			}
			continue;
		}

		//note: we don't delta the state here. we just replace the old.
		//its already lerped

		if (oldent)
			ent = oldent;
		else
			ent = (csqcedict_t *)ED_Alloc(prinst);

		CSQC_EntStateToCSQC(flags, servertime, src, ent);

		if (csqcg.delta_update)
		{
			*csqcg.self = EDICT_TO_PROG(prinst, (void*)ent);
			G_FLOAT(OFS_PARM0) = !oldent;
			PR_ExecuteProgram(prinst, csqcg.delta_update);
		}

		if (newlist->maxents <= newidx)
		{
			newlist->maxents = newidx + 64;
			newlist->entptr = BZ_Realloc(newlist->entptr, sizeof(*newlist->entptr)*newlist->maxents);
			newlist->entnum = BZ_Realloc(newlist->entnum, sizeof(*newlist->entnum)*newlist->maxents);
		}
		newlist->entptr[newidx] = ent;
		newlist->entnum[newidx] = src->number;
		newidx++;

	}

	//remove any unreferenced ents stuck on the end
	while (oldidx < oldlist->numents)
	{
		oldent = oldlist->entptr[oldidx];
		*csqcg.self = EDICT_TO_PROG(prinst, (void*)oldent);
		PR_ExecuteProgram(prinst, csqcg.delta_remove);
		oldidx++;
	}

	newlist->numents = newidx;
}
#endif
//be careful to not touch the resource unless we're meant to, to avoid stalling
static void QCBUILTIN PF_resourcestatus(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int restype = G_FLOAT(OFS_PARM0);
	int doload = G_FLOAT(OFS_PARM1);
	const char *resname = PR_GetStringOfs(prinst, OFS_PARM2);
	int idx, idx2;
	model_t *mod;
	image_t *img;
//	shader_t *sh;
	sfx_t *sfx;
	G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
	switch(restype)
	{
	case RESTYPE_MODEL:
		idx = CS_FindModel(resname, &idx2);
		if (idx < 0)
		{
			mod = cl.model_csqcprecache[-idx];
			if (!cl.model_csqcprecache[-idx] && doload)
				mod = cl.model_csqcprecache[-idx] = Mod_ForName(Mod_FixName(cl.model_csqcname[-idx], cl.model_name[1]), MLV_WARN);
		}
		else if (idx > 0)
		{
			mod = cl.model_precache[idx];
			if (!cl.model_precache[idx] && doload)
				mod = cl.model_precache[idx] = Mod_ForName(Mod_FixName(cl.model_name[idx], cl.model_name[1]), MLV_WARN);
		}
		else
			return;

		if (!mod)
			G_FLOAT(OFS_RETURN) = RESSTATE_NOTLOADED;
		else
		{
			if (doload && mod->loadstate == MLS_NOTLOADED)
				Mod_LoadModel (mod, MLV_SILENT);	//should avoid blocking.
			switch(mod->loadstate)
			{
			default:
			case MLS_NOTLOADED:
				G_FLOAT(OFS_RETURN) = RESSTATE_NOTLOADED;
				break;
			case MLS_LOADING:
				G_FLOAT(OFS_RETURN) = RESSTATE_LOADING;
				break;
			case MLS_LOADED:
				G_FLOAT(OFS_RETURN) = RESSTATE_LOADED;
				break;
			case MLS_FAILED:
				G_FLOAT(OFS_RETURN) = RESSTATE_FAILED;
				break;
			}
		}
		return;
	case RESTYPE_SOUND:
		sfx = NULL;
		for (idx=1 ; idx<MAX_PRECACHE_SOUNDS && cl.sound_name[idx] ; idx++)
		{
			if (!strcmp(cl.sound_name[idx], resname))
			{
				sfx = cl.sound_precache[idx];
				break;
			}
		}
		if (!sfx)
			sfx = S_FindName(resname, doload, false);
		if (!sfx)
			G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
		else
		{
			if (doload && sfx->loadstate == SLS_NOTLOADED)
				S_LoadSound(sfx, true);
			switch(sfx->loadstate)
			{
			case SLS_NOTLOADED:
				G_FLOAT(OFS_RETURN) = RESSTATE_NOTLOADED;
				break;
			case SLS_LOADING:
				G_FLOAT(OFS_RETURN) = RESSTATE_LOADING;
				break;
			case SLS_LOADED:
				G_FLOAT(OFS_RETURN) = RESSTATE_LOADED;
				break;
			case SLS_FAILED:
				G_FLOAT(OFS_RETURN) = RESSTATE_FAILED;
				break;
			}
		}
		break;
/*
	case RESTYPE_PARTICLE:
		G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
		break;
	case RESTYPE_SHADER:
		sh = R_RegisterCustom(resname, 0, NULL, NULL);
		if (sh)
		{
			//FIXME: scan through the images.
		}
		else
			G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
		break;
	case RESTYPE_SKIN:
		G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
		break;
*/
	case RESTYPE_TEXTURE:
		G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
		img = Image_FindTexture(resname, NULL, 0);
		if (!img)
			G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN;
		else
		{
			switch(img->status)
			{
			case TEX_NOTLOADED:
				G_FLOAT(OFS_RETURN) = RESSTATE_NOTLOADED;
				break;
			case TEX_LOADING:
				G_FLOAT(OFS_RETURN) = RESSTATE_LOADING;
				break;
			case TEX_LOADED:
				G_FLOAT(OFS_RETURN) = RESSTATE_LOADED;
				break;
			case TEX_FAILED:
				G_FLOAT(OFS_RETURN) = RESSTATE_FAILED;
				break;
			}
		}
		break;
	default:
		G_FLOAT(OFS_RETURN) = RESSTATE_UNSUPPORTED;
		break;
	}
}

void QCBUILTIN PF_CL_DrawTextField (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);

//prefixes:
//PF_ - common, works on any vm
//PF_cs_ - works in csqc only (dependant upon globals or fields)
//PF_cl_ - works in csqc and menu (if needed...)

//warning: functions that depend on globals are bad, mkay?
static struct {
	char *name;
	builtin_t bifunc;
	int ebfsnum;
}  BuiltinList[] = {
//0
	{"makevectors",				PF_cs_makevectors, 1},		// #1 void() makevectors (QUAKE)
	{"setorigin",				PF_cs_SetOrigin, 2},		// #2 void(entity e, vector org) setorigin (QUAKE)
	{"setmodel",				PF_cs_SetModel, 3},			// #3 void(entity e, string modl) setmodel (QUAKE)
	{"setsize",					PF_cs_SetSize, 4},			// #4 void(entity e, vector mins, vector maxs) setsize (QUAKE)
//5
	{"debugbreak",				PF_cs_break, 6},			// #6 void() debugbreak (QUAKE)
	{"random",					PF_random,	7},				// #7 float() random (QUAKE)
	{"sound",					PF_cs_sound,	8},			// #8 void(entity e, float chan, string samp, float vol, float atten) sound (QUAKE)
	{"normalize",				PF_normalize,	9},			// #9 vector(vector in) normalize (QUAKE)
//10
	{"error",					PF_error,	10},				// #10 void(string errortext) error (QUAKE)
	{"objerror",				PF_objerror,	11},			// #11 void(string errortext) onjerror (QUAKE)
	{"vlen",					PF_vlen,	12},				// #12 float(vector v) vlen (QUAKE)
	{"vectoyaw",				PF_vectoyaw,	13},			// #13 float(vector v) vectoyaw (QUAKE)
	{"spawn",					PF_Spawn,	14},				// #14 entity() spawn (QUAKE)
	{"remove",					PF_cs_remove,	15},			// #15 void(entity e) remove (QUAKE)
	{"traceline",				PF_cs_traceline,	16},		// #16 void(vector v1, vector v2, float nomonst, entity forent) traceline (QUAKE)
	{"checkclient",				PF_NoCSQC,	17},				// #17 entity() checkclient (QUAKE) (don't support)
	{"find",					PF_FindString,	18},			// #18 entity(entity start, .string fld, string match) findstring (QUAKE)
	{"precache_sound",			PF_cs_PrecacheSound,	19},	// #19 void(string str) precache_sound (QUAKE)
//20
	{"precache_model",			PF_cs_PrecacheModel,	20},	// #20 void(string str) precache_model (QUAKE)
	{"stuffcmd",				PF_NoCSQC,	21},		// #21 void(entity client, string s) stuffcmd (QUAKE) (don't support)
	{"findradius",				PF_findradius,	22},		// #22 entity(vector org, float rad) findradius (QUAKE)
	{"bprint",					PF_NoCSQC,	23},				// #23 void(string s, ...) bprint (QUAKE) (don't support)
	{"sprint",					PF_NoCSQC,	24},				// #24 void(entity e, string s, ...) sprint (QUAKE) (don't support)
	{"dprint",					PF_dprint,	25},				// #25 void(string s, ...) dprint (QUAKE)
	{"ftos",					PF_ftos,	26},				// #26 string(float f) ftos (QUAKE)
	{"vtos",					PF_vtos,	27},				// #27 string(vector f) vtos (QUAKE)
	{"coredump",				PF_coredump,	28},			// #28 void(void) coredump (QUAKE)
	{"traceon",					PF_traceon,	29},				// #29 void() traceon (QUAKE)
//30
	{"traceoff",				PF_traceoff,	30},			// #30 void() traceoff (QUAKE)
	{"eprint",					PF_eprint,	31},				// #31 void(entity e) eprint (QUAKE)
	{"walkmove",				PF_cs_walkmove,	32},			// #32 float(float yaw, float dist) walkmove (QUAKE)
	{"?",						PF_Fixme,	33},				// #33
	{"droptofloor",				PF_cs_droptofloor,	34},		// #34
	{"lightstyle",				PF_cs_lightstyle,	35},		// #35 void(float lightstyle, string stylestring) lightstyle (QUAKE)
	{"rint",					PF_rint,	36},				// #36 float(float f) rint (QUAKE)
	{"floor",					PF_floor,	37},				// #37 float(float f) floor (QUAKE)
	{"ceil",					PF_ceil,	38},				// #38 float(float f) ceil (QUAKE)
//	{"?",						PF_Fixme,	39},				// #39
//40
	{"checkbottom",				PF_checkbottom,	40},	// #40 float(entity e) checkbottom (QUAKE)
	{"pointcontents",			PF_cs_pointcontents,	41},	// #41 float(vector org) pointcontents (QUAKE)
//	{"?",						PF_Fixme,	42},				// #42
	{"fabs",					PF_fabs,	43},				// #43 float(float f) fabs (QUAKE)
	{"aim",						PF_NoCSQC,	44},				// #44 vector(entity e, float speed) aim (QUAKE) (don't support)
	{"cvar",					PF_cvar,	45},				// #45 float(string cvarname) cvar (QUAKE)
	{"localcmd",				PF_localcmd,	46},			// #46 void(string str) localcmd (QUAKE)
	{"nextent",					PF_nextent,	47},				// #47 entity(entity e) nextent (QUAKE)
	{"particle",				PF_cs_particle,	48},		// #48 void(vector org, vector dir, float colour, float count) particle (QUAKE)
	{"changeyaw",				PF_changeyaw,	49},			// #49 void() changeyaw (QUAKE)
//50
//	{"?",	PF_Fixme,	50},				// #50
	{"vectoangles",				PF_vectoangles,	51},			// #51 vector(vector v) vectoangles (QUAKE)
//	{"WriteByte",				PF_Fixme,	52},				// #52 void(float to, float f) WriteByte (QUAKE)
//	{"WriteChar",				PF_Fixme,	53},				// #53 void(float to, float f) WriteChar (QUAKE)
//	{"WriteShort",				PF_Fixme,	54},				// #54 void(float to, float f) WriteShort (QUAKE)

//	{"WriteLong",				PF_Fixme,	55},				// #55 void(float to, float f) WriteLong (QUAKE)
//	{"WriteCoord",				PF_Fixme,	56},				// #56 void(float to, float f) WriteCoord (QUAKE)
//	{"WriteAngle",				PF_Fixme,	57},				// #57 void(float to, float f) WriteAngle (QUAKE)
//	{"WriteString",				PF_Fixme,	58},				// #58 void(float to, float f) WriteString (QUAKE)
//	{"WriteEntity",				PF_Fixme,	59},				// #59 void(float to, float f) WriteEntity (QUAKE)

//60
	{"sin",						PF_Sin,	60},					// #60 float(float angle) sin (DP_QC_SINCOSSQRTPOW)
	{"cos",						PF_Cos,	61},					// #61 float(float angle) cos (DP_QC_SINCOSSQRTPOW)
	{"sqrt",					PF_Sqrt,	62},				// #62 float(float value) sqrt (DP_QC_SINCOSSQRTPOW)
	{"changepitch",				PF_changepitch,	63},			// #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH)
	{"tracetoss",				PF_cs_tracetoss,	64},		// #64 void(entity ent, entity ignore) tracetoss (DP_QC_TRACETOSS)

	{"etos",					PF_etos,	65},				// #65 string(entity ent) etos (DP_QC_ETOS)
	{"?",						PF_Fixme,	66},				// #66
	{"movetogoal",				PF_cs_movetogoal,	67},		// #67 void(float step) movetogoal (QUAKE)
	{"precache_file",			PF_cs_precachefile,	68},		// #68 void(string s) precache_file (QUAKE) (don't support)
	{"makestatic",				PF_cs_makestatic,	69},		// #69 void(entity e) makestatic (QUAKE)
//70
	{"changelevel",				PF_NoCSQC,	70},				// #70 void(string mapname) changelevel (QUAKE) (don't support)
//	{"?",						PF_Fixme,	71},				// #71
	{"cvar_set",				PF_cvar_set,	72},			// #72 void(string cvarname, string valuetoset) cvar_set (QUAKE)
	{"centerprint",				PF_NoCSQC,	73},				// #73 void(entity ent, string text) centerprint (QUAKE) (don't support - cprint is supported instead)
	{"ambientsound",			PF_cl_ambientsound,	74},		// #74 void (vector pos, string samp, float vol, float atten) ambientsound (QUAKE)

	{"precache_model2",			PF_cs_PrecacheModel,	75},	// #75 void(string str) precache_model2 (QUAKE)
	{"precache_sound2",			PF_cs_PrecacheSound,	76},	// #76 void(string str) precache_sound2 (QUAKE)
	{"precache_file2",			PF_cs_precachefile,	77},		// #77 void(string str) precache_file2 (QUAKE)
	{"setspawnparms",			PF_NoCSQC,	78},				// #78 void() setspawnparms (QUAKE) (don't support)
	{"logfrag",					PF_NoCSQC,	79},				// #79 void(entity killer, entity killee) logfrag (QW_ENGINE) (don't support)

//80
	{"infokey",					PF_cs_infokey,	80},			// #80 string(entity e, string keyname) infokey (QW_ENGINE) (don't support)
	{"stof",					PF_stof,	81},				// #81 float(string s) stof (FRIK_FILE or QW_ENGINE)
	{"multicast",				PF_NoCSQC,	82},				// #82 void(vector where, float set) multicast (QW_ENGINE) (don't support)

	{"getlightstyle",			PF_getlightstyle,		0},
	{"getlightstylergb",		PF_getlightstylergb,		0},

//90
	{"tracebox",				PF_cs_tracebox,	90},
	{"randomvec",				PF_randomvector,	91},		// #91 vector() randomvec (DP_QC_RANDOMVEC)
	{"getlight",				PF_cl_getlight,	92},			// #92 vector(vector org) getlight (DP_QC_GETLIGHT)
	{"registercvar",			PF_registercvar,	93},		// #93 void(string cvarname, string defaultvalue) registercvar (DP_QC_REGISTERCVAR)
	{"min",						PF_min,	94},				// #94 float(float a, floats) min (DP_QC_MINMAXBOUND)

	{"max",						PF_max,	95},					// #95 float(float a, floats) max (DP_QC_MINMAXBOUND)
	{"bound",					PF_bound,	96},				// #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND)
	{"pow",						PF_pow,	97},					// #97 float(float value) pow (DP_QC_SINCOSSQRTPOW)
	{"logarithm",				PF_Logarithm,	0},
	{"findfloat",				PF_FindFloat,	98},			// #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT)
	{"findentity",				PF_FindFloat,	98},			// #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT)
	{"checkextension",			PF_checkextension,	99},		// #99 float(string extname) checkextension (EXT_CSQC)
	{"checkbuiltin",			PF_checkbuiltin,	0},
	{"anglemod",				PF_anglemod,		102},

//110
	{"fopen",					PF_fopen,	110},				// #110 float(string strname, float accessmode) fopen (FRIK_FILE)
	{"fclose",					PF_fclose,	111},				// #111 void(float fnum) fclose (FRIK_FILE)
	{"fgets",					PF_fgets,	112},				// #112 string(float fnum) fgets (FRIK_FILE)
	{"fputs",					PF_fputs,	113},				// #113 void(float fnum, string str) fputs (FRIK_FILE)
	{"fread",					PF_fread,	0},
	{"fwrite",					PF_fwrite,	0},
	{"fseek",					PF_fseek,	0},
	{"fsize",					PF_fsize,	0},
	{"strlen",					PF_strlen,	114},				// #114 float(string str) strlen (FRIK_FILE)

	{"strcat",					PF_strcat,		115},			// #115 string(string str1, string str2, ...) strcat (FRIK_FILE)
	{"substring",				PF_substring,	116},			// #116 string(string str, float start, float length) substring (FRIK_FILE)
	{"stov",					PF_stov,		117},			// #117 vector(string str) stov (FRIK_FILE)
	{"strzone",					PF_strzone,		118},			// #118 string(string str) dupstring (FRIK_FILE)
	{"strunzone",				PF_strunzone,	119},			// #119 void(string str) freestring (FRIK_FILE)

	{"localsound",				PF_cl_localsound,	177},

//200
	{"getmodelindex",			PF_cs_getmodelindex,	200},
	{"externcall",				PF_externcall,	201},
	{"addprogs",				PF_cs_addprogs,	202},
	{"externvalue",				PF_externvalue,	203},
	{"externset",				PF_externset,	204},

	{"externrefcall",			PF_externrefcall,	205},
	{"instr",					PF_instr,	206},
	{"openportal",				PF_cs_OpenPortal,	207},	//q2bsps
	{"registertempent",			PF_NoCSQC,	208},//{"RegisterTempEnt", PF_RegisterTEnt,	0,		0,		0,		208},
	{"customtempent",			PF_NoCSQC,	209},//{"CustomTempEnt",	PF_CustomTEnt,		0,		0,		0,		209},
//210
//	{"fork",					PF_Fixme,	210},//{"fork",			PF_Fork,			0,		0,		0,		210},
	{"abort",					PF_Abort,	211}, //#211 void() abort (FTE_MULTITHREADED)
//	{"sleep",					PF_Fixme,	212},//{"sleep",			PF_Sleep,			0,		0,		0,		212},
	{"forceinfokey",			PF_NoCSQC,	213},//{"forceinfokey",	PF_ForceInfoKey,	0,		0,		0,		213},
	{"forceinfokeyblob",		PF_NoCSQC,	0},//{"forceinfokey",	PF_ForceInfoKey,	0,		0,		0,		213},
	{"chat",					PF_NoCSQC,	214},//{"chat",			PF_chat,			0,		0,		0,		214},// #214 void(string filename, float starttag, entity edict) SV_Chat (FTE_NPCCHAT)

	{"particle2",				PF_cs_particle2,	215}, //215 (FTE_PEXT_HEXEN2)
	{"particle3",				PF_cs_particle3,	216}, //216 (FTE_PEXT_HEXEN2)
	{"particle4",				PF_cs_particle4,	217}, //217 (FTE_PEXT_HEXEN2)

//EXT_DIMENSION_PLANES
	{"bitshift",				PF_bitshift,	218},		//#218 bitshift (EXT_DIMENSION_PLANES)
	{"te_lightningblood",		PF_cl_te_lightningblood,	219},// #219 te_lightningblood void(vector org) (FTE_TE_STANDARDEFFECTBUILTINS)

//220
//	{"map_builtin",				PF_Fixme,	220},	//like #100 - takes 2 args. arg0 is builtinname, 1 is number to map to.
	{"strstrofs",				PF_strstrofs,	221},	// #221 float(string s1, string sub) strstrofs (FTE_STRINGS)
	{"str2chr",					PF_str2chr,	222},		// #222 float(string str, float index) str2chr (FTE_STRINGS)
	{"chr2str",					PF_chr2str,	223},		// #223 string(float chr, ...) chr2str (FTE_STRINGS)
	{"strconv",					PF_strconv,	224},		// #224 string(float ccase, float redalpha, float redchars, string str, ...) strconv (FTE_STRINGS)

	{"strpad",					PF_strpad,	225},		// #225 string strpad(float pad, string str1, ...) strpad (FTE_STRINGS)
	{"infoadd",					PF_infoadd,	226},		// #226 string(string old, string key, string value) infoadd
	{"infoget",					PF_infoget,	227},		// #227 string(string info, string key) infoget
	{"strcmp",					PF_strncmp,	228},		// #228 float(string s1, string s2) strcmp (FTE_STRINGS)
	{"strncmp",					PF_strncmp,	228},		// #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS)
	{"strcasecmp",				PF_strncasecmp,	229},	// #229 float(string s1, string s2) strcasecmp (FTE_STRINGS)

//230
	{"strncasecmp",				PF_strncasecmp,	230},	// #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS)
	{"strtrim",					PF_strtrim,		0},
	{"calltimeofday",			PF_calltimeofday,	231},
	{"clientstat",				PF_NoCSQC,	232},		// #231 clientstat
	{"runclientphys",			PF_NoCSQC,	233},		// #232 runclientphys
//	{"isbackbuffered",			PF_NoCSQC,	234},		// #233 float(entity ent) isbackbuffered
//I messed up, 234 is meant to be isbackbuffered. luckily that's not present in csqc, but still, this is messy. Don't document this.
	{"rotatevectorsbytag",		PF_rotatevectorsbytag,	234},	// #234

	{"rotatevectorsbyangle",	PF_rotatevectorsbyangles,	235}, // #235
	{"rotatevectorsbyvectors",	PF_rotatevectorsbymatrix,	236}, // #236
	{"skinforname",				PF_skinforname,	237},		// #237
	{"shaderforname",			PF_shaderforname,	238},	// #238
	{"te_bloodqw",				PF_cl_te_bloodqw,	239},	// #239 void te_bloodqw(vector org[, float count]) (FTE_TE_STANDARDEFFECTBUILTINS)

	{"checkpvs",				PF_checkpvs,		240},
//	{"matchclientname",			PF_matchclient,		241},
	{"sendpacket",				PF_cl_SendPacket,	242},	//void(string dest, string content) sendpacket = #242; (FTE_QC_SENDPACKET)

//	{"bulleten",				PF_bulleten,		243}, (removed builtin)
	{"rotatevectorsbytag",		PF_rotatevectorsbytag,	244},
	{"mod",						PF_mod,				245},

	{"sqlconnect",				PF_NoCSQC,			250},	// #250 float([string host], [string user], [string pass], [string defaultdb], [string driver]) sqlconnect (FTE_SQL)
	{"sqldisconnect",			PF_NoCSQC,			251},	// #251 void(float serveridx) sqldisconnect (FTE_SQL)
	{"sqlopenquery",			PF_NoCSQC,			252},	// #252 float(float serveridx, void(float serveridx, float queryidx, float rows, float columns, float eof) callback, float querytype, string query) sqlopenquery (FTE_SQL)
	{"sqlclosequery",			PF_NoCSQC,			253},	// #253 void(float serveridx, float queryidx) sqlclosequery (FTE_SQL)
	{"sqlreadfield",			PF_NoCSQC,			254},	// #254 string(float serveridx, float queryidx, float row, float column) sqlreadfield (FTE_SQL)
	{"sqlerror",				PF_NoCSQC,			255},	// #255 string(float serveridx, [float queryidx]) sqlerror (FTE_SQL)
	{"sqlescape",				PF_NoCSQC,			256},	// #256 string(float serveridx, string data) sqlescape (FTE_SQL)
	{"sqlversion",				PF_NoCSQC,			257},	// #257 string(float serveridx) sqlversion (FTE_SQL)
	{"sqlreadfloat",			PF_NoCSQC,			258},	// #258 float(float serveridx, float queryidx, float row, float column) sqlreadfloat (FTE_SQL)

	{"stoi",					PF_stoi,			259},
	{"itos",					PF_itos,			260},
	{"stoh",					PF_stoh,			261},
	{"htos",					PF_htos,			262},
	{"ftoi",					PF_ftoi,			0},
	{"itof",					PF_itof,			0},

	{"skel_create",				PF_skel_create,			263},//float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS)
	{"skel_build",				PF_skel_build,			264},//float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addition) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS)
	{"skel_build_ptr",			PF_skel_build_ptr,		0},//float(float skel, int numblends, __variant *blends, int blendsize) skel_build_ptr = #0;
	{"skel_get_numbones",		PF_skel_get_numbones,	265},//float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS)
	{"skel_get_bonename",		PF_skel_get_bonename,	266},//string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) (returns tempstring)
	{"skel_get_boneparent",		PF_skel_get_boneparent,	267},//float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS)
	{"skel_find_bone",			PF_skel_find_bone,		268},//float(float skel, string tagname) skel_get_boneidx = #268; // (FTE_CSQC_SKELETONOBJECTS)
	{"skel_get_bonerel",		PF_skel_get_bonerel,	269},//vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) (sets v_forward etc)
	{"skel_get_boneabs",		PF_skel_get_boneabs,	270},//vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) (sets v_forward etc)
	{"skel_set_bone",			PF_skel_set_bone,		271},//void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
	{"skel_premul_bone",		PF_skel_premul_bone,	272},//void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
	{"skel_premul_bones",		PF_skel_premul_bones,	273},//void(float skel, float startbone, float endbone, vector org) skel_mul_bone = #273; // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
	{"skel_postmul_bone",		PF_skel_postmul_bone,	0},//void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
//	{"skel_postmul_bones",		PF_skel_postmul_bones,	0},//void(float skel, float startbone, float endbone, vector org) skel_mul_bone = #273; // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
	{"skel_copybones",			PF_skel_copybones,		274},//void(float skeldst, float skelsrc, float startbone, float entbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS)
	{"skel_delete",				PF_skel_delete,			275},//void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS)
	{"frameforname",			PF_frameforname,		276},//void(float modidx, string framename) frameforname = #276 (FTE_CSQC_SKELETONOBJECTS)
	{"frameduration",			PF_frameduration,		277},//void(float modidx, float framenum) frameduration = #277 (FTE_CSQC_SKELETONOBJECTS)
	{"processmodelevents",		PF_processmodelevents,	0},
	{"getnextmodelevent",		PF_getnextmodelevent,	0},
	{"getmodeleventidx",		PF_getmodeleventidx,	0},

	{"crossproduct",			PF_crossproduct,		0},
	{"pushmove", 				PF_pushmove, 			0},
#ifdef TERRAIN
	{"terrain_edit",			PF_terrain_edit,		278},//void(float action, vector pos, float radius, float quant) terrain_edit = #278 (??FTE_TERRAIN_EDIT??)
	{"brush_get",				PF_brush_get,			0},
	{"brush_create",			PF_brush_create,		0},
	{"brush_delete",			PF_brush_delete,		0},
	{"brush_selected",			PF_brush_selected,		0},
	{"brush_getfacepoints",		PF_brush_getfacepoints,	0},
	{"brush_calcfacepoints",	PF_brush_calcfacepoints,0},
	{"brush_findinvolume",		PF_brush_findinvolume,	0},
#endif

#ifdef ENGINE_ROUTING
	{"route_calculate",		PF_route_calculate,		0},
#endif

	{"touchtriggers",			PF_touchtriggers,		279},//void() touchtriggers = #279;
	{"skel_ragupdate",			PF_skel_ragedit,		281},// (FTE_QC_RAGDOLL)
	{"skel_mmap",				PF_skel_mmap,			282},// (FTE_QC_RAGDOLL)
	{"skel_set_bone_world",		PF_skel_set_bone_world,	283},
	{"frametoname",				PF_frametoname,			284},//string(float modidx, float framenum) frametoname
	{"skintoname",				PF_skintoname,			285},//string(float modidx, float skin) skintoname
	{"resourcestatus",			PF_resourcestatus,		286},

	{"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},

	{"modelframecount",			PF_modelframecount,			0},

//300
	{"clearscene",				PF_R_ClearScene,	300},				// #300 void() clearscene (EXT_CSQC)
	{"addentities",				PF_R_AddEntityMask,	301},				// #301 void(float mask) addentities (EXT_CSQC)
	{"addentity",				PF_R_AddEntity,		302},					// #302 void(entity ent) addentity (EXT_CSQC)
	{"removeentity",			PF_R_RemoveEntity,	0},
	{"setproperty",				PF_R_SetViewFlag,	303},				// #303 float(float property, ...) setproperty (EXT_CSQC)
	{"renderscene",				PF_R_RenderScene,	304},				// #304 void() renderscene (EXT_CSQC)

	{"dynamiclight_add",		PF_R_DynamicLight_AddDynamic,	305},			// #305 float(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC)

	{"R_BeginPolygon",			PF_R_PolygonBegin,	306},				// #306 void(string texturename) R_BeginPolygon (EXT_CSQC_???)
	{"R_PolygonVertex",			PF_R_PolygonVertex,	307},				// #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex (EXT_CSQC_???)
	{"R_EndPolygon",			PF_R_PolygonEnd,	308},				// #308 void() R_EndPolygon (EXT_CSQC_???)
	{"R_EndPolygonRibbon",		PF_R_PolygonEndRibbon, 0},
	{"addtrisoup_simple",		PF_R_AddTrisoup_Simple,	0},

	{"getproperty",				PF_R_GetViewFlag,	309},				// #309 vector/float(float property) getproperty (EXT_CSQC_1)

//310
//maths stuff that uses the current view settings.
	{"unproject",				PF_cs_unproject,	310},				// #310 vector (vector v) unproject (EXT_CSQC)
	{"project",					PF_cs_project,		311},				// #311 vector (vector v) project (EXT_CSQC)

//	{"?",	PF_Fixme,			312},				// #312
//	{"?",	PF_Fixme,		313},					// #313

//2d (immediate) operations
	{"drawtextfield",			PF_CL_DrawTextField,			0/*314*/},
	{"drawline",				PF_CL_drawline,					315},			// #315 void(float width, vector pos1, vector pos2) drawline (EXT_CSQC)
	{"iscachedpic",				PF_CL_is_cached_pic,			316},		// #316 float(string name) iscachedpic (EXT_CSQC)
	{"precache_pic",			PF_CL_precache_pic,				317},		// #317 string(string name, float trywad) precache_pic (EXT_CSQC)
	{"r_uploadimage",			PF_CL_uploadimage,				0},
	{"r_readimage",				PF_CL_readimage,				0},
	{"drawgetimagesize",		PF_CL_drawgetimagesize,			318},		// #318 vector(string picname) draw_getimagesize (EXT_CSQC)
	{"freepic",					PF_CL_free_pic,					319},		// #319 void(string name) freepic (EXT_CSQC)
//320
	{"drawcharacter",			PF_CL_drawcharacter,			320},		// #320 float(vector position, float character, vector scale, vector rgb, float alpha [, float flag]) drawcharacter (EXT_CSQC, [EXT_CSQC_???])
	{"drawrawstring",			PF_CL_drawrawstring,			321},	// #321 float(vector position, string text, vector scale, vector rgb, float alpha [, float flag]) drawstring (EXT_CSQC, [EXT_CSQC_???])
	{"drawpic",					PF_CL_drawpic,					322},		// #322 float(vector position, string pic, vector size, vector rgb, float alpha [, float flag]) drawpic (EXT_CSQC, [EXT_CSQC_???])
	{"drawrotpic",				PF_CL_drawrotpic,				0},
	{"drawfill",				PF_CL_drawfill,					323},		// #323 float(vector position, vector size, vector rgb, float alpha [, float flag]) drawfill (EXT_CSQC, [EXT_CSQC_???])
	{"drawsetcliparea",			PF_CL_drawsetcliparea,			324},	// #324 void(float x, float y, float width, float height) drawsetcliparea (EXT_CSQC_???)
	{"drawresetcliparea",		PF_CL_drawresetcliparea,		325},		// #325 void(void) drawresetcliparea (EXT_CSQC_???)

	{"drawstring",				PF_CL_drawcolouredstring,		326},	// #326
	{"stringwidth",				PF_CL_stringwidth,				327},	// #327 EXT_CSQC_'DARKPLACES'
	{"drawsubpic",				PF_CL_drawsubpic,				328},	// #328 EXT_CSQC_'DARKPLACES'
	{"drawrotsubpic",			PF_CL_drawrotsubpic,			0},
#ifdef HAVE_LEGACY
	{"drawrotpic_dp",			PF_CL_drawrotpic_dp,			329},
#endif

//330
	{"getstati",				PF_cs_getstati,					330},	// #330 float(float stnum) getstati (EXT_CSQC)
	{"getstatf",				PF_cs_getstatbits,				331},	// #331 float(float stnum) getstatf (EXT_CSQC)
	{"getstats",				PF_cs_getstats,					332},	// #332 string(float firststnum) getstats (EXT_CSQC)
	{"getplayerstat",			PF_cs_getplayerstat,			0},		// #0 __variant(float playernum, float statnum, float stattype) getplayerstat
	{"setmodelindex",			PF_cs_SetModelIndex,			333},	// #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC)
	{"modelnameforindex",		PF_cs_ModelnameForIndex,		334},	// #334 string(float mdlindex) modelnameforindex (EXT_CSQC)

	{"particleeffectnum",		PF_cs_particleeffectnum,		335},	// #335 float(string effectname) particleeffectnum (EXT_CSQC)
	{"trailparticles",			PF_cs_trailparticles,			336},	// #336 void(float effectnum, entity ent, vector start, vector end) trailparticles (EXT_CSQC),
	{"trailparticles_dp",		PF_cs_trailparticles,			336},	// #336 DP sucks
	{"pointparticles",			PF_cs_pointparticles,			337},	// #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC)

	{"cprint",					PF_cl_cprint,					338},	// #338 void(string s) cprint (EXT_CSQC)
	{"print",					PF_print,						339},	// #339 void(string s) print (EXT_CSQC)

//340
	{"keynumtostring",			PF_cl_keynumtostring,			340},	// #340 string(float keynum) keynumtostring (EXT_CSQC)
	{"stringtokeynum",			PF_cl_stringtokeynum,			341},	// #341 float(string keyname) stringtokeynum (EXT_CSQC)
	{"getkeybind",				PF_cl_getkeybind,				342},	// #342 string(float keynum) getkeybind (EXT_CSQC)

	{"setcursormode",			PF_cl_setcursormode,			343},	// #343 This is originally a DP extension
	{"getcursormode",			PF_cl_getcursormode,			0},		//
	{"setmousepos",				PF_cl_setmousepos,				0},		//
	{"getmousepos",				PF_cl_getmousepos,				344},	// #344 This is a DP extension

	{"getinputstate",			PF_cs_getinputstate,			345},	// #345 float(float framenum) getinputstate (EXT_CSQC)
	{"setsensitivityscaler",	PF_cs_setsensitivityscaler, 	346},	// #346 void(float sens) setsensitivityscaler (EXT_CSQC)

	{"runstandardplayerphysics",PF_cs_runplayerphysics,			347},	// #347 void() runstandardplayerphysics (EXT_CSQC)

	{"getplayerkeyvalue",		PF_cs_getplayerkeystring,		348},	// #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC)
	{"getplayerkeyfloat",		PF_cs_getplayerkeyfloat,		0},		// #348 string(float playernum, string keyname) getplayerkeyvalue
	{"getplayerkeyblob",		PF_cs_getplayerkeyblob,			0},		// #0   int(float playernum, string keyname, optional void *outptr, int size) getplayerkeyblob
	{"setlocaluserinfo",		PF_cl_setlocaluserinfo,			0},
	{"getlocaluserinfo",		PF_cl_getlocaluserinfostring,	0},
	{"setlocaluserinfoblob",	PF_cl_setlocaluserinfo,			0},
	{"getlocaluserinfoblob",	PF_cl_getlocaluserinfoblob,		0},

	{"isdemo",					PF_cl_playingdemo,				349},	// #349 float() isdemo (EXT_CSQC)
//350
	{"isserver",				PF_cl_runningserver,			350},	// #350 float() isserver (EXT_CSQC)

	{"SetListener",				PF_cs_setlistener, 				351},	// #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC)
	{"setup_reverb",			PF_cs_setupreverb, 				0},
	{"registercommand",			PF_cs_registercommand,			352},	// #352 void(string cmdname) registercommand (EXT_CSQC)
	{"wasfreed",				PF_WasFreed,					353},	// #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too)

	{"serverkey",				PF_cs_serverkey,				354},	// #354 string(string key) serverkey;
	{"serverkeyfloat",			PF_cs_serverkeyfloat,			0},		// #0 float(string key) serverkeyfloat;
	{"serverkeyblob",			PF_cs_serverkeyblob,			0},
	{"getentitytoken",			PF_cs_getentitytoken,			355},	// #355 string() getentitytoken;
	{"findfont",				PF_CL_findfont,					356},
	{"loadfont",				PF_CL_loadfont,					357},
//	{"?",						PF_Fixme,						358},	// #358
	{"sendevent",				PF_cs_sendevent,				359},	// #359	void(string evname, string evargs, ...) (EXT_CSQC_1)

//360
//note that 'ReadEntity' is pretty hard to implement reliably. Modders should use a combination of ReadShort, and findfloat, and remember that it might not be known clientside (pvs culled or other reason)
	{"readbyte",				PF_ReadByte,					360},	// #360 float() readbyte (EXT_CSQC)
	{"readchar",				PF_ReadChar,					361},	// #361 float() readchar (EXT_CSQC)
	{"readshort",				PF_ReadShort,					362},	// #362 float() readshort (EXT_CSQC)
	{"readlong",				PF_ReadLong,					363},	// #363 float() readlong (EXT_CSQC)
	{"readcoord",				PF_ReadCoord,					364},	// #364 float() readcoord (EXT_CSQC)

	{"readangle",				PF_ReadAngle,					365},	// #365 float() readangle (EXT_CSQC)
	{"readstring",				PF_ReadString,					366},	// #366 string() readstring (EXT_CSQC)
	{"readfloat",				PF_ReadFloat,					367},	// #367 string() readfloat (EXT_CSQC)
	{"readint",					PF_ReadInt,						0},		// #0 string() readint
	{"readentitynum",			PF_ReadEntityNum,				368},	// #368 float() readentitynum (EXT_CSQC)

//	{"readserverentitystate",	PF_ReadServerEntityState,		369},	// #369 void(float flags, float simtime) readserverentitystate (EXT_CSQC_1)
//	{"readsingleentitystate",	PF_ReadSingleEntityState,		370},
	{"deltalisten",				PF_DeltaListen,					371},		// #371 float(string modelname, float flags) deltalisten  (EXT_CSQC_1)

	{"dynamiclight_spawnstatic",PF_R_DynamicLight_AddStatic,0},
	{"dynamiclight_get",		PF_R_DynamicLight_Get,		372},
	{"dynamiclight_set",		PF_R_DynamicLight_Set,		373},
	{"particleeffectquery",		PF_cs_particleeffectquery,	374},
	{"adddecal",				PF_R_AddDecal,				375},
	{"setcustomskin",			PF_cs_setcustomskin,		376},
	{"loadcustomskin",			PF_cs_loadcustomskin,		377},
	{"applycustomskin",			PF_cs_applycustomskin,		378},
	{"releasecustomskin",		PF_cs_releasecustomskin,	379},

	{"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},
	{"entityprotection",		PF_entityprotection,		0},
//400
	{"copyentity",				PF_copyentity,				400},	// #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY)
	{"setcolors",				PF_NoCSQC,					401},	// #401 void(entity cl, float colours) setcolors (DP_SV_SETCOLOR) (don't implement)
	{"findchain",				PF_findchain,				402},	// #402 entity(string field, string match) findchain (DP_QC_FINDCHAIN)
	{"findchainfloat",			PF_findchainfloat,			403},	// #403 entity(float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT)
	{"effect",					PF_cl_effect,				404},		// #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT)

	{"te_blood",				PF_cl_te_blooddp,			405},	// #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD)
	{"te_bloodshower",			PF_cl_te_bloodshower,		406},		// #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER)
	{"te_explosionrgb",			PF_cl_te_explosionrgb,		407},	// #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB)
	{"te_particlecube",			PF_cl_te_particlecube,		408},		// #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE)
	{"te_particlerain",			PF_cl_te_particlerain,		409},	// #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN)

	{"te_particlesnow",			PF_cl_te_particlesnow,		410},		// #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW)
	{"te_spark",				PF_cl_te_spark,				411},		// #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK)
	{"te_gunshotquad",			PF_cl_te_gunshotquad,		412},	// #412 void(vector org) te_gunshotquad (DP_TE_QUADEFFECTS1)
	{"te_spikequad",			PF_cl_te_spikequad,			413},		// #413 void(vector org) te_spikequad (DP_TE_QUADEFFECTS1)
	{"te_superspikequad",		PF_cl_te_superspikequad,	414},	// #414 void(vector org) te_superspikequad (DP_TE_QUADEFFECTS1)

	{"te_explosionquad",		PF_cl_te_explosionquad,		415},	// #415 void(vector org) te_explosionquad (DP_TE_QUADEFFECTS1)
	{"te_smallflash",			PF_cl_te_smallflash,		416},	// #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH)
	{"te_customflash",			PF_cl_te_customflash,		417},	// #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH)
	{"te_gunshot",				PF_cl_te_gunshot,			418},		// #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_spike",				PF_cl_te_spike,				419},		// #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS)

	{"te_superspike",			PF_cl_te_superspike,420},		// #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_explosion",			PF_cl_te_explosion,	421},		// #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_tarexplosion",			PF_cl_te_tarexplosion,422},		// #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_wizspike",				PF_cl_te_wizspike,	423},		// #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_knightspike",			PF_cl_te_knightspike,424},		// #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS)

	{"te_lavasplash",			PF_cl_te_lavasplash,425},		// #425 void(vector org) te_lavasplash  (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_teleport",				PF_cl_te_teleport,	426},		// #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_explosion2",			PF_cl_te_explosion2,427},		// #427 void(vector org, float color, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_lightning1",			PF_cl_te_lightning1,	428},	// #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_lightning2",			PF_cl_te_lightning2,429},		// #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS)

	{"te_lightning3",			PF_cl_te_lightning3,				430},		// #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS)
	{"te_beam",					PF_cl_te_beam,						431},		// #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS)
	{"vectorvectors",			PF_vectorvectors,					432},		// #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS)
	{"te_plasmaburn",			PF_cl_te_plasmaburn,				433},		// #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN)
	{"getsurfacenumpoints",		PF_getsurfacenumpoints,				434},		// #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE)

	{"getsurfacepoint",			PF_getsurfacepoint,					435},		// #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE)
	{"getsurfacenormal",		PF_getsurfacenormal,				436},		// #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE)
	{"getsurfacetexture",		PF_getsurfacetexture,				437},			// #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE)
	{"getsurfacenearpoint",		PF_getsurfacenearpoint,				438},		// #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE)
	{"getsurfaceclippedpoint",	PF_getsurfaceclippedpoint,			439},			// #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE)

	{"clientcommand",			PF_NoCSQC,			440},		// #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) (don't implement)
	{"tokenize",				PF_Tokenize,		441},		// #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND)
	{"argv",					PF_ArgV,			442},		// #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND)
	{"argc",					PF_ArgC,			0},			// #0 float() argc pointless, but whatever
	{"setattachment",			PF_setattachment,	443},		// #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS)
	{"search_begin",			PF_search_begin,	444},		// #444 float	search_begin(string pattern, float caseinsensitive, float quiet) (DP_QC_FS_SEARCH)

	{"search_end",				PF_search_end,			445},	// #445 void	search_end(float handle) (DP_QC_FS_SEARCH)
	{"search_getsize",			PF_search_getsize,	446},		// #446 float	search_getsize(float handle) (DP_QC_FS_SEARCH)
	{"search_getfilename",		PF_search_getfilename,447},		// #447 string	search_getfilename(float handle, float num) (DP_QC_FS_SEARCH)
	{"search_getfilesize",		PF_search_getfilesize,	0},
	{"search_getfilemtime",		PF_search_getfilemtime,	0},
	{"cvar_string",				PF_cvar_string,		448},		// #448 string(float n) cvar_string (DP_QC_CVAR_STRING)
	{"findflags",				PF_FindFlags,		449},		// #449 entity(entity start, .entity fld, float match) findflags (DP_QC_FINDFLAGS)

	{"findchainflags",			PF_findchainflags,	450},		// #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS)
	{"gettagindex",				PF_gettagindex,		451},		// #451 float(entity ent, string tagname) gettagindex (DP_MD3_TAGSINFO)
	{"gettaginfo",				PF_gettaginfo,		452},		// #452 vector(entity ent, float tagindex) gettaginfo (DP_MD3_TAGSINFO)
	{"dropclient",				PF_NoCSQC,			453},		// #453 void(entity player) dropclient (DP_SV_BOTCLIENT) (don't implement)
	{"spawnclient",				PF_NoCSQC,			454},		// #454	entity() spawnclient (DP_SV_BOTCLIENT) (don't implement)

	{"clienttype",				PF_NoCSQC,			455},		// #455 float(entity client) clienttype (DP_SV_BOTCLIENT) (don't implement)


//	{"WriteUnterminatedString",PF_WriteString2,		456},	//writestring but without the null terminator. makes things a little nicer.

//DP_TE_FLAMEJET
	{"te_flamejet",				PF_cl_te_flamejet,			457},	// #457 void(vector org, vector vel, float howmany) te_flamejet

	//no 458 documented.

//DP_QC_EDICT_NUM
	{"edict_num",				PF_edict_for_num,		459},	// #459 entity(float entnum) edict_num

//DP_QC_STRINGBUFFERS
	{"buf_create",				PF_buf_create,		460},	// #460 float() buf_create
	{"buf_del",					PF_buf_del,				461},	// #461 void(float bufhandle) buf_del
	{"buf_getsize",				PF_buf_getsize,		462},	// #462 float(float bufhandle) buf_getsize
	{"buf_copy",				PF_buf_copy,		463},	// #463 void(float bufhandle_from, float bufhandle_to) buf_copy
	{"buf_sort",				PF_buf_sort,		464},	// #464 void(float bufhandle, float sortpower, float backward) buf_sort
	{"buf_implode",				PF_buf_implode,		465},	// #465 string(float bufhandle, string glue) buf_implode
	{"bufstr_get",				PF_bufstr_get,		466},	// #466 string(float bufhandle, float string_index) bufstr_get
	{"bufstr_set",				PF_bufstr_set,		467},	// #467 void(float bufhandle, float string_index, string str) bufstr_set
	{"bufstr_add",				PF_bufstr_add,		468},	// #468 float(float bufhandle, string str, float order) bufstr_add
	{"bufstr_free",				PF_bufstr_free,			469},	// #469 void(float bufhandle, float string_index) bufstr_free

	//no 470 documented

//DP_QC_ASINACOSATANATAN2TAN
	{"asin",					PF_asin,			471},	// #471 float(float s) asin
	{"acos",					PF_acos,			472},	// #472 float(float c) acos
	{"atan",					PF_atan,			473},	// #473 float(float t) atan
	{"atan2",					PF_atan2,			474},	// #474 float(float c, float s) atan2
	{"tan",						PF_tan,				475},	// #475 float(float a) tan


//DP_QC_STRINGCOLORFUNCTIONS
	{"strlennocol",				PF_strlennocol,		476},	// #476 float(string s) strlennocol
	{"strdecolorize",			PF_strdecolorize,	477},	// #477 string(string s) strdecolorize

//DP_QC_STRFTIME
	{"strftime",				PF_strftime,		478},	// #478 string(float uselocaltime, string format, ...) strftime

//DP_QC_TOKENIZEBYSEPARATOR
	{"tokenizebyseparator",		PF_tokenizebyseparator,	479},	// #479 float(string s, string separator1, ...) tokenizebyseparator

//DP_QC_STRING_CASE_FUNCTIONS
	{"strtolower",				PF_strtolower,		480},	// #476 string(string s) strtolower
	{"strtoupper",				PF_strtoupper,		481},	// #476 string(string s) strlennocol

//DP_QC_CVAR_DEFSTRING
	{"cvar_defstring",			PF_cvar_defstring,	482},	// #482 string(string s) cvar_defstring

//DP_SV_POINTSOUND
	{"pointsound",				PF_cs_pointsound,		483},	// #483 void(vector origin, string sample, float volume, float attenuation) pointsound

//DP_QC_STRREPLACE
	{"strreplace",				PF_strreplace,		484},	// #484 string(string search, string replace, string subject) strreplace
	{"strireplace",				PF_strireplace,		485},	// #485 string(string search, string replace, string subject) strireplace


//DP_QC_GETSURFACEPOINTATTRIBUTE
	{"getsurfacepointattribute",PF_getsurfacepointattribute,	486},	// #486vector(entity e, float s, float n, float a) getsurfacepointattribute

#ifdef HAVE_MEDIA_DECODER
//DP_GECKO_SUPPORT
	{"gecko_create",			PF_cs_media_create,				487},	// #487 float(string name)
	{"gecko_destroy",			PF_cs_media_destroy,			488},	// #488 void(string name)
	{"gecko_navigate",			PF_cs_media_command,			489},	// #489 void(string name, string URI)
	{"gecko_keyevent",			PF_cs_media_keyevent,			490},	// #490 float(string name, float key, float eventtype)
	{"gecko_mousemove",			PF_cs_media_mousemove,			491},	// #491 void(string name, float x, float y)
	{"gecko_resize",			PF_cs_media_resize,				492},	// #492 void(string name, float w, float h)
	{"gecko_get_texture_extent",PF_cs_media_get_texture_extent,	493},	// #493 vector(string name)
	{"gecko_getproperty",		PF_cs_media_getproperty},
	{"cin_open",				PF_cs_media_create},
	{"cin_close",				PF_cs_media_destroy},
	{"cin_setstate",			PF_cs_media_setstate},
	{"cin_getstate",			PF_cs_media_getstate},
	{"cin_restart",				PF_cs_media_restart},
#endif

//DP_QC_CRC16
	{"crc16",					PF_crc16,					494},	// #494 float(float caseinsensitive, string s, ...) crc16

//DP_QC_CVAR_TYPE
	{"cvar_type",				PF_cvar_type,				495},	// #495 float(string name) cvar_type

//DP_QC_ENTITYDATA
	{"numentityfields",			PF_numentityfields,			496},	// #496 float() numentityfields
	{"findentityfield",			PF_findentityfield,			0},
	{"entityfieldref",			PF_entityfieldref,			0},
	{"entityfieldname",			PF_entityfieldname,			497},	// #497 string(float fieldnum) entityfieldname
	{"entityfieldtype",			PF_entityfieldtype,			498},	// #498 float(float fieldnum) entityfieldtype
	{"getentityfieldstring",	PF_getentityfieldstring,	499},	// #499 string(float fieldnum, entity ent) getentityfieldstring
	{"putentityfieldstring",	PF_putentityfieldstring,	500},	// #500 float(float fieldnum, entity ent, string s) putentityfieldstring

//DP_SV_WRITEPICTURE
	{"ReadPicture",				PF_ReadPicture,				501},	// #501 void(float to, string s, float sz) WritePicture

	{"boxparticles",			PF_cs_boxparticles,			502},

//DP_QC_WHICHPACK
	{"whichpack",				PF_whichpack,				503},	// #503 string(string filename) whichpack

//DP_CSQC_QUERYRENDERENTITY
	{"getentity",				PF_getentity,				504},	// #504 __variant(float entnum, fload fieldnum) getentity

//DP_QC_URI_ESCAPE
	{"uri_escape",				PF_uri_escape,				510},	// #510 string(string in) uri_escape
	{"uri_unescape",			PF_uri_unescape,			511},	// #511 string(string in) uri_unescape = #511;

//DP_QC_NUM_FOR_EDICT
	{"num_for_edict",			PF_num_for_edict,			512},	// #512 float(entity ent) num_for_edict

//DP_QC_URI_GET
	{"uri_get",					PF_uri_get,					513},	// #513 float(string uril, float id) uri_get
	{"uri_post",				PF_uri_get,					513},	// #513 float(string uril, float id) uri_post

	{"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},

	{"gettime",					PF_gettime,					519},

	{"keynumtostring_omgwtf",	PF_cl_keynumtostring,		520},
	{"findkeysforcommand",		PF_cl_findkeysforcommand,	521},
	{"findkeysforcommandex",	PF_cl_findkeysforcommandex,	0},

	{"loadfromdata",			PF_loadfromdata,			529},
	{"loadfromfile",			PF_loadfromfile,			530},
	{"log",						PF_Logarithm,				532},

	{"stopsound",				PF_stopsound,				0},
	{"soundupdate",				PF_soundupdate,				0},
	{"getsoundtime",			PF_getsoundtime,			533},
	{"getchannellevel",			PF_getchannellevel,			0},
	{"soundlength",				PF_soundlength,				534},
	{"buf_loadfile",			PF_buf_loadfile,			535},
	{"buf_writefile",			PF_buf_writefile,			536},
//	{"bufstr_find",				PF_Fixme,					537},
//	{"matchpattern",			PF_Fixme,					538},
//	{"undefined",				PF_Fixme,					539},

#ifdef USERBE
	{"physics_enable",			PF_physics_enable,			540},
	{"physics_addforce",		PF_physics_addforce,		541},
	{"physics_addtorque",		PF_physics_addtorque,		542},
#endif

	{"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_menu",		PF_cl_keynumtostring,		609},	//while present in dp's menuqc, dp doesn't actually support keynumtostring=609 in csqc. Which is probably a good thing because csqc would have 3 separate versions if it did.

	{"findkeysforcommand_menu",	PF_cl_findkeysforcommand,	610},
	{"gethostcachevalue",		PF_cl_gethostcachevalue,	611},
	{"gethostcachestring",		PF_cl_gethostcachestring,	612},
	{"parseentitydata",			PF_parseentitydata,			613},
	{"generateentitydata",		PF_generateentitydata,		0},
	{"stringtokeynum_menu",		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},
	{"sprintf",					PF_sprintf,					627},
	{"getsurfacenumtriangles",	PF_getsurfacenumtriangles,	628},
	{"getsurfacetriangle",		PF_getsurfacetriangle,		629},

	{"setkeybind",				PF_cl_setkeybind,			630},
	{"getbindmaps",				PF_cl_GetBindMap,			631},
	{"setbindmaps",				PF_cl_SetBindMap,			632},
//	{NULL,						PF_Fixme,					643},
//	{NULL,						PF_Fixme,					644},
//	{NULL,						PF_Fixme,					645},
//	{NULL,						PF_Fixme,					646},
//	{NULL,						PF_Fixme,					647},
//	{NULL,						PF_Fixme,					648},
	{"digest_hex",				PF_digest_hex,				639},
	{"digest_ptr",				PF_digest_ptr,				0},
	{"V_CalcRefdef",			PF_V_CalcRefdef,			640},
//	{NULL,						PF_Fixme,					641},
//	{NULL,						PF_Fixme,					642},
//	{NULL,						PF_Fixme,					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},

	{NULL}
};

int PR_CSQC_BuiltinValid(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_NoCSQC || BuiltinList[i].bifunc == PF_Fixme)
					return false;
				else
					return true;
			}
		}
	}
	return false;
}

static builtin_t csqc_builtin[800];

static int PDECL PR_CSQC_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(csqc_builtin)/sizeof(csqc_builtin[0]); --binum; )
			{
				if (csqc_builtin[binum] && csqc_builtin[binum] != PF_Fixme && BuiltinList[i].bifunc)
					continue;
				csqc_builtin[binum] = BuiltinList[i].bifunc;
				return binum;
			}
			Con_Printf("No more builtin slots to allocate for %s\n", builtinname);
			break;
		}
	}
	if (!csqc_nogameaccess)
		Con_DPrintf("Unknown csqc builtin: %s\n", builtinname);
	return 0;
}




static jmp_buf csqc_abort;
static progparms_t csqcprogparms;


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

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

	Con_Printf("CSQC_Abort: %s\nShutting down csqc\n", string);

	if (pr_csqc_coreonerror.value)
	{
		size_t size = 1024*1024*8;
		char *buffer = BZ_Malloc(size);
		csqcprogs->save_ents(csqcprogs, buffer, &size, size, 3);
		COM_WriteFile("csqccore.txt", FS_GAMEONLY, buffer, size);
		BZ_Free(buffer);
	}

	Host_EndGame("csqc error");
}

static void CSQC_ForgetThreads(void)
{
	csqctreadstate_t *state = csqcthreads, *next;
	csqcthreads = NULL;
	while(state)
	{
		next = state->next;

		csqcprogs->parms->memfree(state->thread);
		csqcprogs->parms->memfree(state);

		state = next;
	}
}

static void PDECL CSQC_EntSpawn (struct edict_s *e, int loading)
{
	struct csqcedict_s *ent = (csqcedict_t*)e;
#ifdef VM_Q1
	if (!ent->xv)
		ent->xv = (csqcextentvars_t *)(ent->v+1);
#endif

	if (1)
	{
//		ent->xv->dimension_see = csqc_world.dimension_default;
//		ent->xv->dimension_seen = csqc_world.dimension_default;
//		ent->xv->dimension_ghost = 0;
		ent->xv->dimension_solid = *csqcg.dimension_default;
		ent->xv->dimension_hit = *csqcg.dimension_default;

#ifdef HEXEN2
		ent->xv->drawflags = SCALE_ORIGIN_ORIGIN;
#endif
	}
}

static pbool QDECL CSQC_EntFree (struct edict_s *e)
{
	struct csqcedict_s *ent = (csqcedict_t*)e;
	ent->v->solid = SOLID_NOT;
	ent->v->movetype = 0;
	ent->v->modelindex = 0;
	ent->v->think = 0;
	ent->v->nextthink = 0;
	ent->xv->predraw = 0;
	ent->xv->drawmask = 0;
	ent->xv->renderflags = 0;

	if (ent->skinobject>0)
		Mod_WipeSkin(ent->skinobject, false);
	ent->skinobject = 0;

#ifdef USERBE
	if (csqc_world.rbe)
	{
		csqc_world.rbe->RemoveFromEntity(&csqc_world, (wedict_t*)ent);
		csqc_world.rbe->RemoveJointFromEntity(&csqc_world, (wedict_t*)ent);
	}
#endif

	return true;
}

static void QDECL CSQC_Event_Touch(world_t *w, wedict_t *s, wedict_t *o, trace_t *trace)
{
	int oself = *csqcg.self;
	int oother = *csqcg.other;

	*csqcg.self = EDICT_TO_PROG(w->progs, (edict_t*)s);
	*csqcg.other = EDICT_TO_PROG(w->progs, (edict_t*)o);
	*csqcg.simtime = w->physicstime;

	PR_ExecuteProgram (w->progs, s->v->touch);

	*csqcg.self = oself;
	*csqcg.other = oother;
}

static void QDECL CSQC_Event_Think(world_t *w, wedict_t *s)
{
	*csqcg.self = EDICT_TO_PROG(w->progs, (edict_t*)s);
	*csqcg.other = EDICT_TO_PROG(w->progs, (edict_t*)w->edicts);
	*csqcg.simtime = w->physicstime;

	if (!s->v->think)
		Con_Printf("CSQC entity \"%s\" has nextthink with no think function\n", PR_GetString(w->progs, s->v->classname));
	else
		PR_ExecuteProgram (w->progs, s->v->think);
}

static void QDECL CSQC_Event_Sound (float *origin, wedict_t *wentity, int channel, const char *sample, int volume, float attenuation, float pitchadj, float timeoffset, unsigned int flags)
{
	int i;
	vec3_t originbuf;
	if (!origin)
	{
		if (wentity->v->solid == SOLID_BSP)
		{
			origin = originbuf;
			for (i=0 ; i<3 ; i++)
				origin[i] = wentity->v->origin[i]+0.5*(wentity->v->mins[i]+wentity->v->maxs[i]);
		}
		else
			origin = wentity->v->origin;
	}

	S_StartSound(NUM_FOR_EDICT(csqcprogs, (edict_t*)wentity), channel, S_PrecacheSound(sample), origin, NULL, volume/255.0, attenuation, timeoffset, pitchadj, flags);
}

static qboolean QDECL CSQC_Event_ContentsTransition(world_t *w, wedict_t *ent, int oldwatertype, int newwatertype)
{
	if (ent->xv->contentstransition)
	{
		void *pr_globals = PR_globals(w->progs, PR_CURRENT);
		*csqcg.self = EDICT_TO_PROG(w->progs, ent);
		*csqcg.simtime = w->physicstime;
		G_FLOAT(OFS_PARM0) = oldwatertype;
		G_FLOAT(OFS_PARM1) = newwatertype;
		PR_ExecuteProgram (w->progs, ent->xv->contentstransition);
		return true;
	}
	return false;	//do legacy behaviour
}

static model_t *QDECL CSQC_World_ModelForIndex(world_t *w, int modelindex)
{
	model_t *mod = CSQC_GetModelForIndex(modelindex);
	if (mod && mod->loadstate != MLS_LOADED)
	{
		if (mod->loadstate == MLS_NOTLOADED)
			Mod_LoadModel(mod, MLV_SILENT);
		if (mod->loadstate == MLS_LOADING)
			COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);
		if (mod->loadstate != MLS_LOADED)
			mod = NULL;	//gah, it failed!
	}
	return mod;
}
static void QDECL CSQC_World_GetFrameState(world_t *w, wedict_t *win, framestate_t *out)
{
	csqcedict_t *in = (csqcedict_t *)win;
	cs_getframestate(in, in->xv->renderflags, out);
}

void CSQC_Shutdown(void)
{
	int i;
	if (csqcprogs)
	{
		if (csqcg.shutdown_function)
			PR_ExecuteProgram(csqcprogs, csqcg.shutdown_function);

		key_dest_absolutemouse &= ~kdm_game;
		CSQC_ForgetThreads();
		PR_ReleaseFonts(kdm_game);
		PR_Common_Shutdown(csqcprogs, false);
		World_Destroy(&csqc_world);
		csqcprogs->Shutdown(csqcprogs);
		csqc_world.progs = csqcprogs = NULL;
	}
	else
		World_Destroy(&csqc_world);

	Cmd_RemoveCommands(CS_ConsoleCommand_f);


	Z_Free(csqcdelta_pack_new.e);
	memset(&csqcdelta_pack_new, 0, sizeof(csqcdelta_pack_new));
	Z_Free(csqcdelta_pack_old.e);
	memset(&csqcdelta_pack_old, 0, sizeof(csqcdelta_pack_old));

	memset(&deltafunction, 0, sizeof(deltafunction));
	memset(csqcdelta_playerents, 0, sizeof(csqcdelta_playerents));

	Z_Free(csqcent);
	csqcent = NULL;
	maxcsqcentities = 0;

	for (i = 0; i < MAX_CSPARTICLESPRE && cl.particle_csname[i]; i++)
	{
		free(cl.particle_csname[i]);
		cl.particle_csname[i] = NULL;
	}

	csqcmapentitydata = NULL;
	csqcmapentitydataloaded = false;

	in_sensitivityscale = 1;
	csqc_world.num_edicts = 0;
	memset(&csqc_world, 0, sizeof(csqc_world));
	memset(&csqcg, 0, sizeof(csqcg));

	if (csqc_deprecated_warned>1)
	{
		if (!cl_csqc_nodeprecate.ival)
			Con_Printf("total %u csqc deprecation warnings suppressed\n", csqc_deprecated_warned-1);
		csqc_deprecated_warned = 0;
	}
}

static qboolean CSQC_ValidateMainCSProgs(void *file, size_t filesize, unsigned int checksum, size_t checksize)
{
	if (!file)
		return false;
	if (checksize && filesize != checksize)
		return false;
	if (cls.protocol == CP_NETQUAKE && !(cls.fteprotocolextensions2 & PEXT2_PREDINFO))
	{	//DP uses really lame checksums.
		if (QCRC_Block(file, filesize) != checksum)
			return false;
	}
	else
	{	//FTE uses folded-md4. yeah, its broken but at least its still more awkward
		if (LittleLong(Com_BlockChecksum(file, filesize)) != checksum)
			return false;
	}
	return true;
}
static void *CSQC_FindMainProgs(size_t *sz, const char *name, unsigned int checksum, size_t checksize)
{	//returns a TempFile
	char newname[MAX_QPATH];
	void *file = NULL;

	//the filename we'll cache to
	snprintf(newname, MAX_QPATH, "csprogsvers/%x.dat", checksum);

	//we can use FSLF_IGNOREPURE because we have our own hashes/size checks instead.
	//this should make it slightly easier for server admins
	if (checksum)
	{
		file = COM_LoadTempFile (newname, FSLF_IGNOREPURE, sz);
		if (!CSQC_ValidateMainCSProgs(file, *sz, checksum, checksize))
			file = NULL;
	}

	if (!file)
	{
		const char *progsname = InfoBuf_ValueForKey(&cl.serverinfo, "*csprogsname");
		flocation_t loc={0};
		vfsfile_t *f;
		qboolean found = false;
		if (!found && *progsname && cls.state)
			found = FS_FLocateFile(progsname, FSLF_IGNOREPURE, &loc);
		if (!found && strcmp(progsname, "csprogs.dat"))
			found = FS_FLocateFile("csprogs.dat", FSLF_IGNOREPURE, &loc);
		if (found && (f=FS_OpenReadLocation(&loc)))
		{
			*sz = VFS_GETLEN(f);
			file = Hunk_TempAlloc (*sz);
			VFS_READ(f, file, *sz);
			VFS_CLOSE(f);

			if (file && !cls.demoplayback)	//allow them to use csprogs.dat if playing a demo, and don't care about the checksum
			{
				if (checksum && !csprogs_promiscuous)
				{
					if (!CSQC_ValidateMainCSProgs(file, *sz, checksum, checksize))
						file = NULL;

					//we write the csprogs into our archive if it was loaded from outside of there.
					//this is to ensure that demos will play on the same machine later on...
					//this is unreliable though, and redundant if we're writing the csqc into the demos themselves.
					//also kinda irrelevant with sv_pure.
					//FIXME: don't back up if it was in a package.
#ifndef FTE_TARGET_WEB
					if (file && !sv_state && !FS_WhichPackForLocation(&loc, false)
#if !defined(CLIENTONLY) && defined(MVD_RECORDING)
						&& !sv_demo_write_csqc.ival
#endif
						)
						//back it up
						COM_WriteFile(newname, FS_GAMEONLY, file, *sz);
#endif
				}
			}
		}
	}
	return file;
}
qboolean CSQC_CheckDownload(const char *name, unsigned int checksum, size_t checksize)
{
	size_t sz;
	if (CSQC_FindMainProgs(&sz, name, checksum, checksize))
		return true;
	return false;
}

//when the qclib needs a file, it calls out to this function.
void *PDECL CSQC_PRLoadFile (const char *path, unsigned char *(PDECL *buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *sz, pbool issource)
{
	qbyte *file;

	if (!strcmp(path, csprogs_checkname))
		file = CSQC_FindMainProgs(sz, csprogs_checkname, csprogs_checksum, csprogs_checksize);
	else
		file = COM_LoadTempFile (path, 0, sz);

	if (file)
	{
		qbyte *buffer = buf_get(buf_ctx, *sz);
		memcpy(buffer, file, *sz);
		return buffer;
	}
	return NULL;
}

int QDECL CSQC_PRFileSize (const char *path)
{
	return COM_FileSize(path);
}

qboolean CSQC_Inited(void)
{
	if (csqcprogs)
		return true;
	return false;
}

qboolean CSQC_UnconnectedOkay(qboolean inprinciple)
{
	if (!pr_csqc_formenus.ival)
		return false;

	if (!inprinciple)
	{
		if (!csqcprogs)
			return false;
	}
	return true;
}
qboolean CSQC_UnconnectedInit(void)
{
	if (!CSQC_UnconnectedOkay(true))
		return false;

	if (csqcprogs)
		return true;
	return CSQC_Init(true, "csprogs.dat", 0, 0);
}
void ASMCALL CSQC_StateOp(pubprogfuncs_t *prinst, float var, func_t func)
{
	world_t *w = prinst->parms->user;
	stdentvars_t *vars = PROG_TO_EDICT(prinst, *w->g.self)->v;
	vars->nextthink = *w->g.time+0.1;
	vars->think = func;
	vars->frame = var;
}
void ASMCALL CSQC_CStateOp(pubprogfuncs_t *progs, float first, float last, func_t currentfunc)
{
	float min, max;
	float step;
	world_t *w = progs->parms->user;
	wedict_t *e = PROG_TO_WEDICT(progs, *w->g.self);
	float frame = e->v->frame;

//	if (progstype == PROG_H2)
//		e->v->nextthink = *w->g.time+0.05;
//	else
		e->v->nextthink = *w->g.time+0.1;
	e->v->think = currentfunc;

	if (csqcg.cycle_wrapped)
		*csqcg.cycle_wrapped = false;

	if (first > last)
	{	//going backwards
		min = last;
		max = first;
		step = -1.0;
	}
	else
	{	//forwards
		min = first;
		max = last;
		step = 1.0;
	}
	if (frame < min || frame > max)
		frame = first;	//started out of range, must have been a different animation
	else
	{
		frame += step;
		if (frame < min || frame > max)
		{	//became out of range, must have wrapped
			if (csqcg.cycle_wrapped)
				*csqcg.cycle_wrapped = true;
			frame = first;
		}
	}
	e->v->frame = frame;
}
static void ASMCALL CSQC_CWStateOp (pubprogfuncs_t *prinst, float first, float last, func_t currentfunc)
{
	float min, max;
	float step;
	world_t *w = prinst->parms->user;
	wedict_t *e = PROG_TO_WEDICT(prinst, *w->g.self);
	float frame = e->v->weaponframe;

//	if (progstype == PROG_H2)
//		e->v->nextthink = *w->g.time+0.05;
//	else
		e->v->nextthink = *w->g.time+0.1;
	e->v->think = currentfunc;

	if (csqcg.cycle_wrapped)
		*csqcg.cycle_wrapped = false;

	if (first > last)
	{	//going backwards
		min = last;
		max = first;
		step = -1.0;
	}
	else
	{	//forwards
		min = first;
		max = last;
		step = 1.0;
	}
	if (frame < min || frame > max)
		frame = first;	//started out of range, must have been a different animation
	else
	{
		frame += step;
		if (frame < min || frame > max)
		{	//became out of range, must have wrapped
			if (csqcg.cycle_wrapped)
				*csqcg.cycle_wrapped = true;
			frame = first;
		}
	}
	e->v->weaponframe = frame;
}
void ASMCALL CSQC_ThinkTimeOp(pubprogfuncs_t *progs, edict_t *ed, float var)
{
	world_t *w = progs->parms->user;
	stdentvars_t *vars = ed->v;
	vars->nextthink = *w->g.time+var;
}

pbool PDECL CSQC_CheckHeaderCrc(pubprogfuncs_t *progs, progsnum_t num, int crc, const char *filename)
{
	if (!num)
	{
		switch(crc)
		{
		case PROGHEADER_CRC_CSQC:
			break;	//fte's full csqc stuff
		case PROGHEADER_CRC_QW:
		case PROGHEADER_CRC_NQ:
		case PROGHEADER_CRC_PREREL:
		case PROGHEADER_CRC_TENEBRAE:
		case PROGHEADER_CRC_H2:
		case PROGHEADER_CRC_H2MP:
		case PROGHEADER_CRC_H2DEMO:
			break;	//simple csqc. but only if it has the right entry points.
#ifndef csqc_isdarkplaces
		case PROGHEADER_CRC_CSQC_DP:
			csqc_isdarkplaces = true;
			Con_DPrintf(CON_WARNING "Running darkplaces csprogs.dat version\n");
			break;
		case 23147:
			csqc_isdarkplaces = true;
			Con_DPrintf(CON_WARNING "Running ^aINVALID^a csprogs.dat version\n");
			break;
#endif
		default:
			Con_Printf(CON_WARNING "Running unknown csprogs.dat version\n");
			break;
		}
	}
	return true;
}

double  csqctime;
qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int checksum, size_t progssize)
{
	int i;
	string_t *str;
	csqcedict_t *worldent;
	char *cheats;
	qboolean csdatenabled = true;
	if (!csprogsname)
	{
		csdatenabled = false;
		csprogsname = "csprogs.dat";
	}
	if (!*csprogsname)
		csprogsname = "csprogs.dat";
	if (csprogs_promiscuous != anycsqc || csprogs_checksum != checksum || csprogs_checksize != progssize || strcmp(csprogs_checkname,csprogsname))
		CSQC_Shutdown();
	csprogs_promiscuous = anycsqc;
	csprogs_checksum = checksum;
	csprogs_checksize = progssize;
	Q_strncpyz(csprogs_checkname, csprogsname, sizeof(csprogs_checkname));

	csqc_mayread = false;

	csqc_singlecheats = cls.demoplayback;
#ifdef HAVE_SERVER
	if (sv.state == ss_active)
	{
		cheats = InfoBuf_ValueForKey(&svs.info, "*cheats");
		if (!*cheats && sv.allocated_client_slots == 1)
			cheats = "ON";
	}
	else
#endif
		cheats = InfoBuf_ValueForKey(&cl.serverinfo, "*cheats");
	if (!Q_strcasecmp(cheats, "ON") || atoi(cheats))
		csqc_singlecheats = true;

	//its already running...
	if (csqcprogs)
		return false;

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

	if (cl_nocsqc.value)
		return false;

	if (cls.state == ca_disconnected)
	{
		movevars.gravity = 800;
		movevars.entgravity = 1;
		movevars.maxspeed = 320;
		movevars.bunnyspeedcap = 0;//pm_bunnyspeedcap.value;
		movevars.ktjump = false;//pm_ktjump.value;
		movevars.slidefix = true;//(pm_slidefix.value != 0);
		movevars.airstep = true;//(pm_airstep.value != 0);
		movevars.pground = true;
		movevars.stepdown = true;
		movevars.walljump = false;//(pm_walljump.value);
		movevars.slidyslopes = false;//(pm_slidyslopes.value!=0);
		movevars.watersinkspeed = 60;//*pm_watersinkspeed.string?pm_watersinkspeed.value:60;
		movevars.flyfriction = 4;//*pm_flyfriction.string?pm_flyfriction.value:4;
		movevars.edgefriction = 2;//*pm_edgefriction.string?pm_edgefriction.value:2;
		movevars.stepheight = PM_DEFAULTSTEPHEIGHT;
		movevars.coordtype = COORDTYPE_FLOAT_32;
		movevars.flags = MOVEFLAG_NOGRAVITYONGROUND;
	}

	for (i = 0; i < sizeof(csqc_builtin)/sizeof(csqc_builtin[0]); i++)
		csqc_builtin[i] = PF_Fixme;
	for (i = 0; BuiltinList[i].bifunc; i++)
	{
		if (BuiltinList[i].ebfsnum)
			csqc_builtin[BuiltinList[i].ebfsnum] = BuiltinList[i].bifunc;
	}

	csqc_deprecated_warned = !!cl_csqc_nodeprecate.ival;
	memset(cl.model_csqcname, 0, sizeof(cl.model_csqcname));
	memset(cl.model_csqcprecache, 0, sizeof(cl.model_csqcprecache));

	csqcprogparms.progsversion = PROGSTRUCT_VERSION;
	csqcprogparms.ReadFile = CSQC_PRLoadFile;
	csqcprogparms.FileSize = CSQC_PRFileSize;//int (*FileSize) (char *fname);	//-1 if file does not exist
	csqcprogparms.WriteFile = QC_WriteFile;//bool (*WriteFile) (char *name, void *data, int len);
	csqcprogparms.Printf = PR_Printf;//Con_Printf;//void (*printf) (char *, ...);
	csqcprogparms.DPrintf = PR_DPrintf;//Con_Printf;//void (*printf) (char *, ...);
	csqcprogparms.Sys_Error = Sys_Error;
	csqcprogparms.Abort = CSQC_Abort;
	csqcprogparms.CheckHeaderCrc = CSQC_CheckHeaderCrc;
	csqcprogparms.edictsize = sizeof(csqcedict_t);

	csqcprogparms.entspawn = CSQC_EntSpawn;//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
	csqcprogparms.entcanfree = CSQC_EntFree;//bool (*entcanfree) (struct edict_s *ent);	//return true to stop ent from being freed
	csqcprogparms.stateop = CSQC_StateOp;//StateOp;//void (*stateop) (float var, func_t func);
	csqcprogparms.cstateop = CSQC_CStateOp;//CStateOp;
	csqcprogparms.cwstateop = CSQC_CWStateOp;//CWStateOp;
	csqcprogparms.thinktimeop = CSQC_ThinkTimeOp;//ThinkTimeOp;

	csqcprogparms.MapNamedBuiltin = PR_CSQC_MapNamedBuiltin;
	csqcprogparms.loadcompleate = NULL;//void (*loadcompleate) (int edictsize);	//notification to reset any pointers.

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


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

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

	csqcprogparms.gametime = &csqctime;

	csqcprogparms.sv_edicts = (struct edict_s **)&csqc_world.edicts;
	csqcprogparms.sv_num_edicts = &csqc_world.num_edicts;

	csqcprogparms.useeditor = QCEditor;//void (*useeditor) (char *filename, int line, int nump, char **parms);
	csqcprogparms.user = &csqc_world;
	csqc_world.keydestmask = kdm_game;

	csqctime = Sys_DoubleTime();
	if (!csqcprogs)
	{
		int csprogsnum = -1;
		int csaddonnum = -1;
		in_sensitivityscale = 1;
		csqcmapentitydataloaded = true;
		csqc_starttime = realtime;
		csqcprogs = InitProgs(&csqcprogparms);
		csqc_world.progs = csqcprogs;
		csqc_world.usesolidcorpse = true;
		PR_Configure(csqcprogs, PR_ReadBytesString(pr_csqc_memsize.string), MAX_PROGS, pr_enable_profiling.ival);
		csqc_world.worldmodel = cl.worldmodel;
		csqc_world.Event_Touch = CSQC_Event_Touch;
		csqc_world.Event_Think = CSQC_Event_Think;
		csqc_world.Event_Sound = CSQC_Event_Sound;
		csqc_world.Event_ContentsTransition = CSQC_Event_ContentsTransition;
		csqc_world.Get_CModel = CSQC_World_ModelForIndex;
		csqc_world.Get_FrameState = CSQC_World_GetFrameState;
		World_ClearWorld(&csqc_world, false);
		CSQC_InitFields();	//let the qclib know the field order that the engine needs.

		if (setjmp(csqc_abort))
		{
			CSQC_Shutdown();
			return false;
		}

#ifndef csqc_isdarkplaces
		csqc_isdarkplaces = false;
#endif
		if (csdatenabled || csqc_singlecheats || anycsqc)
			csqc_nogameaccess = false;
		else
			csqc_nogameaccess = true;

		if (!csqc_nogameaccess)
		{	//only load csprogs if its expected to be able to work without failing for game access reasons
			csprogsnum = PR_LoadProgs(csqcprogs, csprogs_checkname);
			if (csprogsnum >= 0)
				Con_DPrintf("Loaded csprogs.dat\n");
		}
		
		if (csqc_singlecheats || anycsqc)
		{
			csaddonnum = PR_LoadProgs(csqcprogs, "csaddon.dat");
			if (csaddonnum >= 0)
				Con_DPrintf("loaded csaddon.dat...\n");
			else
				Con_DPrintf("unable to find csaddon.dat.\n");
		}
		else
			Con_DPrintf("skipping csaddon.dat due to cheat restrictions\n");

		if (csprogsnum < 0 && csaddonnum < 0)		//simple csqc optionally uses the nq progs, but its explicitly limited
		{
			csqc_nogameaccess = true;
			csprogsnum = PR_LoadProgs(csqcprogs, "progs.dat");
		}

		if (csprogsnum == -1 && csaddonnum == -1)
		{
			CSQC_Shutdown();
			return false;
		}

		if (csqc_nogameaccess && !PR_FindFunction (csqcprogs, "CSQC_DrawHud", PR_ANY) && !PR_FindFunction (csqcprogs, "CSQC_DrawScores", PR_ANY))
		{	//simple csqc module is not csqc. abort now.
			CSQC_Shutdown();
			Con_DPrintf("progs.dat is not suitable for SimpleCSQC - no CSQC_DrawHud\n");
			return false;
		}
		else if (csqc_nogameaccess)
			Con_DPrintf("Loaded [csqc]progs.dat\n");

		PR_ProgsAdded(csqcprogs, csprogsnum, "csprogs.dat");
		PR_ProgsAdded(csqcprogs, csaddonnum, "csaddon.dat");

		PF_InitTempStrings(csqcprogs);

		csqc_world.physicstime = 0;
		if (!csqc_world.worldmodel)
			csqc_world.worldmodel = Mod_ForName("", MLV_SILENT); 
		csqc_worldchanged = false;

		memset(csqcent, 0, sizeof(*csqcent)*maxcsqcentities);	//clear the server->csqc entity translations.

		for (i = 0; i < csqcprogs->numprogs; i++)
		{
			func_t f = PR_FindFunction (csqcprogs, "init", i);
			if (f)
			{
				void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);
				G_PROG(OFS_PARM0) = i-1;
				PR_ExecuteProgram(csqcprogs, f);
			}
		}

		if (csqc_isdarkplaces)
			CSQC_FindGlobals(true);
		else
			CSQC_FindGlobals(false);

		csqcentsize = PR_InitEnts(csqcprogs, pr_csqc_maxedicts.value);

		//world edict becomes readonly
		worldent = (csqcedict_t *)EDICT_NUM_PB(csqcprogs, 0);
		worldent->ereftype = ER_ENTITY;

		for (i = 0; i < csqcprogs->numprogs; i++)
		{
			func_t f = PR_FindFunction (csqcprogs, "initents", i);
			if (f)
			{
				void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);
				G_PROG(OFS_PARM0) = i-1;
				PR_ExecuteProgram(csqcprogs, f);
			}
		}

		/*DP compat*/
		str = (string_t*)csqcprogs->GetEdictFieldValue(csqcprogs, (edict_t*)worldent, "message", ev_string, NULL);
		if (str)
			*str = PR_NewString(csqcprogs, cl.levelname);

		str = (string_t*)PR_FindGlobal(csqcprogs, "mapname", 0, NULL);
		if (str)
		{
			char *s = InfoBuf_ValueForKey(&cl.serverinfo, "map");
			if (!*s)
				s = cl.model_name[1];
			if (!*s)
				s = "unknown";
			*str = PR_NewString(csqcprogs, s);
		}

		if (csqcg.deathmatch)
			*csqcg.deathmatch = cl.deathmatch;
		if (csqcg.coop)
			*csqcg.coop = !cl.deathmatch && cl.allocated_client_slots > 1;

		if (csqcg.init_function)
		{
			void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);
			G_FLOAT(OFS_PARM0) = CSQC_API_VERSION;	//api version
			(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, FULLENGINENAME));
			G_FLOAT(OFS_PARM2) = version_number();
			PR_ExecuteProgram(csqcprogs, csqcg.init_function);
		}
/*
		{
			char *watchname = "something";
			void *dbg = PR_FindGlobal(csqcprogs, watchname, 0, NULL);
			if (!csqcprogs->SetWatchPoint(csqcprogs, watchname))
				Con_Printf("Unable to watch %s\n", watchname);
		}
*/
//		csqcprogs->ToggleBreak(csqcprogs, "something", 0, 2);

		Con_DPrintf("Loaded csqc\n");
		csqcmapentitydataloaded = false;

		csqc_world.physicstime = 0.1;

		CSQC_RendererRestarted();

		if (cls.state == ca_disconnected)
			CSQC_WorldLoaded();
	}

	return true; //success!
}

void CSQC_RendererRestarted(void)
{
	int i;
	if (!csqcprogs)
		return;

	csqc_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 (csqcg.rendererrestarted)
	{
		void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);
		(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, rf->description));
		PR_ExecuteProgram(csqcprogs, csqcg.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 CSQC_WorldLoaded(void)
{
	csqcedict_t *worldent;
	int tmp;
	int wmodelindex;

	if (!csqcprogs)
		return;
	if (csqcmapentitydataloaded)
		return;

	if (csqc_isdarkplaces)
		CSQC_FindGlobals(false);

	csqcmapentitydataloaded = true;
	csqcmapentitydata = Mod_GetEntitiesString(cl.worldmodel);

	csqc_world.worldmodel = cl.worldmodel;
	World_RBE_Start(&csqc_world);

	worldent = (csqcedict_t *)EDICT_NUM_PB(csqcprogs, 0);
	worldent->v->solid = SOLID_BSP;
	wmodelindex = CS_FindModel(cl.worldmodel?cl.worldmodel->name:"", &tmp);
	tmp = csqc_worldchanged;
	csqc_setmodel(csqcprogs, worldent, wmodelindex);
	csqc_worldchanged = tmp;

	worldent->readonly = false;	//just in case

	World_ClearWorld(&csqc_world, true);

	if (csqc_isdarkplaces)
	{
		if (csqcg.init_function)
		{
			void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);
			G_FLOAT(OFS_PARM0) = CSQC_API_VERSION;	//api version
			(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, FULLENGINENAME));
			G_FLOAT(OFS_PARM2) = version_number();
			PR_ExecuteProgram(csqcprogs, csqcg.init_function);
		}
	}

	if (csqcg.worldloaded)
		PR_ExecuteProgram(csqcprogs, csqcg.worldloaded);
	csqcmapentitydata = NULL;

	worldent->readonly = true;
}

void CSQC_CoreDump(void)
{
	if (!csqcprogs)
	{
		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);
		csqcprogs->save_ents(csqcprogs, buffer, &size, size, 3);
		COM_WriteFile("csqccore.txt", FS_GAMEONLY, buffer, size);
		BZ_Free(buffer);
	}

}

void PR_CSExtensionList_f(void)
{
	int i;
	int ebi;
	int bi;
	lh_extension_t *extlist;

#define SHOW_ACTIVEEXT 1
#define SHOW_ACTIVEBI 2
#define SHOW_NOTSUPPORTEDEXT 4
#define SHOW_NOTACTIVEEXT 8
#define SHOW_NOTACTIVEBI 16

	int showflags = atoi(Cmd_Argv(1));
	if (!showflags)
		showflags = SHOW_ACTIVEEXT|SHOW_NOTACTIVEEXT;

	//make sure the info is valid
	if (!csqc_builtin[0])
	{
		for (i = 0; i < sizeof(csqc_builtin)/sizeof(csqc_builtin[0]); i++)
			csqc_builtin[i] = PF_Fixme;
		for (i = 0; BuiltinList[i].bifunc; i++)
		{
			if (BuiltinList[i].ebfsnum)
				csqc_builtin[BuiltinList[i].ebfsnum] = BuiltinList[i].bifunc;
		}
	}


	if (showflags & (SHOW_ACTIVEBI|SHOW_NOTACTIVEBI))
	for (i = 0; BuiltinList[i].name; i++)
	{
		if (!BuiltinList[i].ebfsnum)
			continue;	//a reserved builtin.
		if (BuiltinList[i].bifunc == PF_Fixme)
			Con_Printf("^1%s:%i needs to be added\n", BuiltinList[i].name, BuiltinList[i].ebfsnum);
		else if (csqc_builtin[BuiltinList[i].ebfsnum] == BuiltinList[i].bifunc)
		{
			if (showflags & SHOW_ACTIVEBI)
				Con_Printf("%s is active on %i\n", BuiltinList[i].name, BuiltinList[i].ebfsnum);
		}
		else
		{
			if (showflags & SHOW_NOTACTIVEBI)
				Con_Printf("^4%s is NOT active (%i)\n", BuiltinList[i].name, BuiltinList[i].ebfsnum);
		}
	}

	if (showflags & (SHOW_NOTSUPPORTEDEXT|SHOW_NOTACTIVEEXT|SHOW_ACTIVEEXT))
	{
		extlist = QSG_Extensions;

		for (i = 0; i < QSG_Extensions_count; i++)
		{
			if (!extlist[i].name)
				continue;

			if (i < 32)
			{
				if (!(cls.fteprotocolextensions & (1<<i)))
				{
					if (showflags & SHOW_NOTSUPPORTEDEXT)
						Con_Printf("^4protocol %s is not supported\n", extlist[i].name);
					continue;
				}
			}

			for (ebi = 0; ebi < extlist[i].numbuiltins; ebi++)
			{
				for (bi = 0; BuiltinList[bi].name; bi++)
				{
					if (!strcmp(BuiltinList[bi].name, extlist[i].builtinnames[ebi]))
						break;
				}

				if (!BuiltinList[bi].name)
				{
					if (showflags & SHOW_NOTSUPPORTEDEXT)
						Con_Printf("^4%s is not supported\n", extlist[i].name);
					break;
				}
				if (csqc_builtin[BuiltinList[bi].ebfsnum] != BuiltinList[bi].bifunc)
				{
					if (csqc_builtin[BuiltinList[bi].ebfsnum] == PF_Fixme)
					{
						if (showflags & SHOW_NOTACTIVEEXT)
							Con_Printf("^4%s is not currently active (builtin: %s#%i)\n", extlist[i].name, BuiltinList[bi].name, BuiltinList[bi].ebfsnum);
					}
					else
					{
						if (showflags & SHOW_NOTACTIVEEXT)
							Con_Printf("^4%s was overridden (builtin: %s#%i)\n", extlist[i].name, BuiltinList[bi].name, BuiltinList[bi].ebfsnum);
					}
					break;
				}
			}
			if (ebi == extlist[i].numbuiltins)
			{
				if (showflags & SHOW_ACTIVEEXT)
				{
					if (!extlist[i].numbuiltins)
						Con_Printf("%s is supported\n", extlist[i].name);
					else
						Con_Printf("%s is currently active\n", extlist[i].name);
				}
			}
		}
	}
}

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

	if (!csqcprogs)
	{
		Con_Printf("CSQC not running\n");
		return;
	}
	wasset = csqcprogs->ToggleBreak(csqcprogs, filename, line, 3);
	isset = csqcprogs->ToggleBreak(csqcprogs, 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");
}

static void CSQC_Poke_f(void)
{
	if (!csqc_singlecheats && cls.state)
		Con_Printf("%s is a cheat command\n", Cmd_Argv(0));
	else if (csqcprogs)
		Con_Printf("Result: %s\n", csqcprogs->EvaluateDebugString(csqcprogs, Cmd_Args()));
	else
		Con_Printf("csqc not running\n");
}
void CSQC_WatchPoint_f(void)
{
	char *variable = Cmd_Argv(1);
	if (!*variable)
		variable = NULL;

	if (!csqc_singlecheats)
		Con_Printf("%s is a cheat command\n", Cmd_Argv(0));
	else if (!csqcprogs)
	{
		Con_Printf("csqc not running\n");
		return;
	}
	if (csqcprogs->SetWatchPoint(csqcprogs, variable))
		Con_Printf("Watchpoint set\n");
	else
		Con_Printf("Watchpoint cleared\n");
}
void PR_CSProfile_f(void)
{
	if (csqcprogs && csqcprogs->DumpProfile)
		if (!csqcprogs->DumpProfile(csqcprogs, !atof(Cmd_Argv(1))))
			Con_Printf("Enabled csqc Profiling.\n");
}

static void CSQC_GameCommand_f(void);
void CSQC_RegisterCvarsAndThings(void)
{
	Cmd_AddCommand("coredump_csqc", CSQC_CoreDump);
	Cmd_AddCommand ("extensionlist_csqc", PR_CSExtensionList_f);
	Cmd_AddCommandD("cl_cmd", CSQC_GameCommand_f, "Calls the csqc's GameCommand function");
	Cmd_AddCommand("breakpoint_csqc", CSQC_Breakpoint_f);
	Cmd_AddCommand ("watchpoint_csqc", CSQC_WatchPoint_f);
	Cmd_AddCommandD("poke_csqc", CSQC_Poke_f, "Allows you to inspect/debug ");
	Cmd_AddCommand ("profile_csqc", PR_CSProfile_f);

	Cvar_Register(&pr_csqc_formenus, CSQCPROGSGROUP);
	Cvar_Register(&pr_csqc_memsize, CSQCPROGSGROUP);
	Cvar_Register(&pr_csqc_maxedicts, CSQCPROGSGROUP);
	Cvar_Register(&cl_csqcdebug, CSQCPROGSGROUP);
	Cvar_Register(&cl_nocsqc, CSQCPROGSGROUP);
	Cvar_Register(&pr_csqc_coreonerror, CSQCPROGSGROUP);
	Cvar_Register(&cl_csqc_nodeprecate, CSQCPROGSGROUP);
	Cvar_Register(&dpcompat_csqcinputeventtypes, CSQCPROGSGROUP);
}

void CSQC_CvarChanged(cvar_t *var)
{
	if (csqcprogs)
	{
		PR_AutoCvar(csqcprogs, var);
	}
}

qboolean CSQC_UseGamecodeLoadingScreen(void)
{
	return csqcprogs && csqcg.f_updateviewloading;
}

//evil evil function. calling qc from inside the renderer is BAD.
qboolean CSQC_SetupToRenderPortal(int entkeynum)
{
#ifdef TEXTEDITOR
	if (editormodal)
		return false;
#endif

	if (csqcprogs && entkeynum < 0)
	{
		csqcedict_t *e = (void*)EDICT_NUM_UB(csqcprogs, -entkeynum);
		if (e->xv->camera_transform)
		{
			int oself = *csqcg.self;
			void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);

			*csqcg.self = EDICT_TO_PROG(csqcprogs, e);
			VectorCopy(r_refdef.vieworg, G_VECTOR(OFS_PARM0));
			VectorAngles(vpn, vup, G_VECTOR(OFS_PARM1), true/*FIXME*/);
			VectorCopy(vpn, csqcg.forward);
			VectorCopy(vright, csqcg.right);
			VectorCopy(vup, csqcg.up);
			VectorCopy(r_refdef.vieworg/*r_refdef.pvsorigin*/, csqcg.trace_endpos);

			PR_ExecuteProgram (csqcprogs, e->xv->camera_transform);

			VectorCopy(csqcg.forward, vpn);
			VectorCopy(csqcg.right, vright);
			VectorCopy(csqcg.up, vup);
			VectorCopy(G_VECTOR(OFS_RETURN), r_refdef.vieworg);
			VectorCopy(csqcg.trace_endpos, r_refdef.pvsorigin);

			*csqcg.self = oself;
			return true;
		}
	}
	return false;
}

qboolean CSQC_DrawView(void)
{
	int ticlimit = 10;
	float mintic = 0.01;
	float maxtic = 0.1;
	double clframetime = host_frametime;
	RSpeedLocals();

	csqc_resortfrags = true;
	csqctime = Sys_DoubleTime();

	if (!csqcg.f_updateview || !csqcprogs)
		return false;

	if (cls.state < ca_active && !CSQC_UnconnectedOkay(false))
		return false;

	r_secondaryview = 0;

	if (csqcg.frametime)
		*csqcg.frametime = host_frametime;

	csqc_dp_lastwas3d = false;

	RSpeedRemark();
	if (csqc_isdarkplaces && *csqc_world.g.physics_mode == 1)
	{
		csqc_world.physicstime = cl.servertime;
	}
	else
	{
#ifdef USERBE
		if (csqc_world.rbe)
			maxtic = mintic;	//physics engines need a fixed tick rate.
#endif
		while(1)
		{
			host_frametime = cl.servertime - csqc_world.physicstime;
			if (host_frametime < mintic)
				break;
			if (!--ticlimit)
			{
				csqc_world.physicstime = cl.servertime;
				break;
			}
			if (host_frametime > maxtic)
				host_frametime = maxtic;

#ifdef USERBE
			if (csqc_world.rbe)
			{
#ifdef RAGDOLL
				rag_doallanimations(&csqc_world);
#endif
				csqc_world.rbe->RunFrame(&csqc_world, host_frametime, 800);
			}
#endif

			World_Physics_Frame(&csqc_world);
			csqc_world.physicstime += host_frametime;
		}
	}
	RSpeedEnd(RSPEED_CSQCPHYSICS);

	RSpeedRemark();

	host_frametime = clframetime;

	if (csqcg.frametime)
	{
		if (1)//csqc_isdarkplaces)
		{
			if (cl.paused)
				*csqcg.frametime = 0;	//apparently people can't cope with microstutter when they're using this as a test to see if the game is paused.
			else
				*csqcg.frametime = bound(0, cl.time - cl.lasttime, 0.1);
		}
		else
			*csqcg.frametime = host_frametime;
	}

	if (csqcg.clframetime)
		*csqcg.clframetime = host_frametime;

	if (csqcg.numclientseats)
		*csqcg.numclientseats = cl.splitclients;

	if (csqcg.intermission)
		*csqcg.intermission = cl.intermissionmode;
	if (csqcg.intermission_time)
		*csqcg.intermission_time = cl.completed_time;

	//work out which packet entities are solid
	CL_SetSolidEntities ();
	CL_TransitionEntities();
	if (cl.worldmodel)
		CL_PredictMove ();

	if (csqcg.cltime)
		*csqcg.cltime = realtime-csqc_starttime;
	if (csqcg.simtime)
		*csqcg.simtime = cl.servertime;
	if (csqcg.clientcommandframe)
		*csqcg.clientcommandframe = cl.movesequence;
	if (csqcg.servercommandframe)
		*csqcg.servercommandframe = cl.ackedmovesequence;
	if (csqcg.gamespeed)
		*csqcg.gamespeed = cl.gamespeed;
	if (cl.paused)
	{
		if (csqcg.gamespeed)
			*csqcg.gamespeed = 0;
	}
	if (cl.currentpackentities && cl.previouspackentities)
	{
		if (csqcg.netnewtime)
			*csqcg.netnewtime = cl.currentpackentities->servertime;
		if (csqcg.netoldtime)
			*csqcg.netoldtime = cl.previouspackentities->servertime;
		if (csqcg.netdeltatime)
			*csqcg.netdeltatime = cl.currentpackentities->servertime - cl.previouspackentities->servertime;
	}

	//always revert to a usable default.
	CSQC_ChangeLocalPlayer(cl_forceseat.ival?(cl_forceseat.ival - 1) % cl.splitclients:0);
	DropPunchAngle (csqc_playerview);	//FIXME: this seems like the wrong place for this.
	if (cl.worldmodel)
		Surf_LessenStains();

	CSQC_RunThreads();	//wake up any qc threads

#ifdef HAVE_LEGACY
	if (csqcg.autocvar_vid_conwidth)
		*csqcg.autocvar_vid_conwidth = vid.width;
	if (csqcg.autocvar_vid_conheight)
		*csqcg.autocvar_vid_conheight = vid.height;
#endif

	{
		void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);
		if (csqc_isdarkplaces)
		{	//fucked for compatibility.
			G_FLOAT(OFS_PARM0) = vid.pixelwidth;
			G_FLOAT(OFS_PARM1) = vid.pixelheight;
		}
		else
		{
			G_FLOAT(OFS_PARM0) = vid.width;
			G_FLOAT(OFS_PARM1) = vid.height;
		}
		G_FLOAT(OFS_PARM2) = !Key_Dest_Has(kdm_menu|kdm_cwindows) && !r_refdef.eyeoffset[0] && !r_refdef.eyeoffset[1];

		if (csqcg.f_updateviewloading && cls.state && cls.state < ca_active)
			PR_ExecuteProgram(csqcprogs, csqcg.f_updateviewloading);
		else
			PR_ExecuteProgram(csqcprogs, csqcg.f_updateview);
	}

	if (*r_refdef.rt_destcolour[0].texname)
	{
		if (R2D_Flush)
			R2D_Flush();
		Q_strncpyz(r_refdef.rt_destcolour[0].texname, "", sizeof(r_refdef.rt_destcolour[0].texname));
		BE_RenderToTextureUpdate2d(true);
	}

	RSpeedEnd(RSPEED_CSQCREDRAW);


	return true;
}

qboolean CSQC_DrawHud(playerview_t *pv)
{
	if (csqcg.f_drawhud && (pv==cl.playerview/* || csqcg.numclientseats*/))
	{
		RSpeedMark();
		void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);

		//set csqc globals
		CSQC_ChangeLocalPlayer(pv-cl.playerview);
		if (csqcg.numclientseats)
			*csqcg.numclientseats = cl.splitclients;
		if (csqcg.simtime)
			*csqcg.simtime = cl.time;
		if (csqcg.frametime)
			*csqcg.frametime = host_frametime;
		if (csqcg.cltime)
			*csqcg.cltime = realtime-csqc_starttime;

		G_FLOAT(OFS_PARM0+0) = r_refdef.grect.width;
		G_FLOAT(OFS_PARM0+1) = r_refdef.grect.height;
		G_FLOAT(OFS_PARM0+2) = 0;
#ifdef QUAKEHUD
		G_FLOAT(OFS_PARM1) = (pv->sb_showscores?1:0) | (pv->sb_showteamscores?2:0);
#else
		G_FLOAT(OFS_PARM1) = false;	//hmm
#endif
		G_FLOAT(OFS_PARM2+0) = 0;//r_refdef.grect.x;
		G_FLOAT(OFS_PARM2+1) = 0;//r_refdef.grect.y;
		G_FLOAT(OFS_PARM2+2) = 0;//pv-cl.playerview;
		PR_ExecuteProgram(csqcprogs, csqcg.f_drawhud);

		if (*r_refdef.rt_destcolour[0].texname)
		{
			if (R2D_Flush)
				R2D_Flush();
			Q_strncpyz(r_refdef.rt_destcolour[0].texname, "", sizeof(r_refdef.rt_destcolour[0].texname));
			BE_RenderToTextureUpdate2d(true);
		}

		RSpeedEnd(RSPEED_CSQCREDRAW);
		return true;
	}
	return false;
}
qboolean CSQC_DrawScores(playerview_t *pv)
{
	if (csqcg.f_drawscores && (pv==cl.playerview/* || csqcg.numclientseats*/))
	{
		RSpeedMark();
		void *pr_globals = PR_globals(csqcprogs, PR_CURRENT);

		//set csqc globals (in case CSQC_DrawHud wasn't implemented)
		CSQC_ChangeLocalPlayer(pv-cl.playerview);
		if (csqcg.numclientseats)
			*csqcg.numclientseats = cl.splitclients;
		if (csqcg.simtime)
			*csqcg.simtime = cl.time;
		if (csqcg.frametime)
			*csqcg.frametime = host_frametime;
		if (csqcg.cltime)
			*csqcg.cltime = realtime-csqc_starttime;

		G_FLOAT(OFS_PARM0+0) = r_refdef.grect.width;
		G_FLOAT(OFS_PARM0+1) = r_refdef.grect.height;
		G_FLOAT(OFS_PARM0+2) = 0;
#ifdef QUAKEHUD
		G_FLOAT(OFS_PARM1) = (pv->sb_showscores?1:0) | (pv->sb_showteamscores?2:0);
#else
		G_FLOAT(OFS_PARM1) = false;	//hmm
#endif
		G_FLOAT(OFS_PARM2+0) = 0;//r_refdef.grect.x;
		G_FLOAT(OFS_PARM2+1) = 0;//r_refdef.grect.y;
		G_FLOAT(OFS_PARM2+2) = 0;//pv-cl.playerview;
		PR_ExecuteProgram(csqcprogs, csqcg.f_drawscores);

		if (*r_refdef.rt_destcolour[0].texname)
		{
			if (R2D_Flush)
				R2D_Flush();
			Q_strncpyz(r_refdef.rt_destcolour[0].texname, "", sizeof(r_refdef.rt_destcolour[0].texname));
			BE_RenderToTextureUpdate2d(true);
		}

		RSpeedEnd(RSPEED_CSQCREDRAW);
		return true;
	}
	return false;
}

qboolean CSQC_KeyPress(int key, int unicode, qboolean down, unsigned int devid)
{
	static qbyte csqckeysdown[K_MAX];
	void *pr_globals;
	int ie = down?CSIE_KEYDOWN:CSIE_KEYUP;

	if (!csqcprogs || !csqcg.input_event || ie >= dpcompat_csqcinputeventtypes.ival)
		return false;
#ifdef TEXTEDITOR
	if (editormodal)
		return false;
#endif

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	G_FLOAT(OFS_PARM0) = ie;
	G_FLOAT(OFS_PARM1) = MP_TranslateFTEtoQCCodes(key);
	G_FLOAT(OFS_PARM2) = unicode;
	G_FLOAT(OFS_PARM3) = devid;

	//small sanity check, so things don't break too much if things get big.
	if ((unsigned)devid >= sizeof(csqckeysdown[0])*8)
		devid = sizeof(csqckeysdown[0])*8-1;
	if (key < 0 || key >= K_MAX)
		key = 0;	//panic. everyone panic.

	if (down)
	{
		qcinput_scan = G_FLOAT(OFS_PARM1);
		qcinput_unicode = G_FLOAT(OFS_PARM2);

		csqckeysdown[key] |= (1u<<devid);
	}
	else
	{
		if (key && !(csqckeysdown[key] & (1u<<devid)))
			return false;	//prevent up events being able to leak 
		csqckeysdown[key] &= ~(1u<<devid);
	}
	PR_ExecuteProgram (csqcprogs, csqcg.input_event);
	qcinput_scan = 0;	//and stop replay attacks
	qcinput_unicode = 0;

	return G_FLOAT(OFS_RETURN);
}
qboolean CSQC_MousePosition(float xabs, float yabs, unsigned int devid)
{
	void *pr_globals;

	if (!csqcprogs || !csqcg.input_event || CSIE_MOUSEABS >= dpcompat_csqcinputeventtypes.ival)
		return false;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	G_FLOAT(OFS_PARM0) = CSIE_MOUSEABS;
	G_FLOAT(OFS_PARM1) = (xabs * vid.width) / vid.pixelwidth;
	G_FLOAT(OFS_PARM2) = (yabs * vid.height) / vid.pixelheight;
	G_FLOAT(OFS_PARM3) = devid;

	PR_ExecuteProgram (csqcprogs, csqcg.input_event);

	return G_FLOAT(OFS_RETURN);
}
qboolean CSQC_MouseMove(float xdelta, float ydelta, unsigned int devid)
{
	void *pr_globals;

	if (!csqcprogs || !csqcg.input_event || CSIE_MOUSEDELTA >= dpcompat_csqcinputeventtypes.ival)
		return false;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	G_FLOAT(OFS_PARM0) = 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 (csqcprogs, csqcg.input_event);

	return G_FLOAT(OFS_RETURN);
}

qboolean CSQC_JoystickAxis(int axis, float value, unsigned int devid)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.input_event || CSIE_JOYAXIS >= dpcompat_csqcinputeventtypes.ival)
		return false;
	pr_globals = PR_globals(csqcprogs, 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 (csqcprogs, csqcg.input_event);
	return G_FLOAT(OFS_RETURN);
}

qboolean CSQC_Accelerometer(float x, float y, float z)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.input_event || CSIE_ACCELEROMETER >= dpcompat_csqcinputeventtypes.ival)
		return false;
	pr_globals = PR_globals(csqcprogs, PR_CURRENT);

	G_FLOAT(OFS_PARM0) = CSIE_ACCELEROMETER;
	G_FLOAT(OFS_PARM1) = x;
	G_FLOAT(OFS_PARM2) = y;
	G_FLOAT(OFS_PARM3) = z;
	PR_ExecuteProgram (csqcprogs, csqcg.input_event);
	return G_FLOAT(OFS_RETURN);
}
qboolean CSQC_Gyroscope(float x, float y, float z)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.input_event || CSIE_GYROSCOPE >= dpcompat_csqcinputeventtypes.ival)
		return false;
	pr_globals = PR_globals(csqcprogs, PR_CURRENT);

	G_FLOAT(OFS_PARM0) = CSIE_GYROSCOPE;
	G_FLOAT(OFS_PARM1) = x;
	G_FLOAT(OFS_PARM2) = y;
	G_FLOAT(OFS_PARM3) = z;
	PR_ExecuteProgram (csqcprogs, csqcg.input_event);
	return G_FLOAT(OFS_RETURN);
}

qboolean CSQC_ConsoleLink(char *text, char *info)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.console_link)
		return false;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, info));
	*info = 0;
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, text));
	*info = '\\';
	PR_ExecuteProgram (csqcprogs, csqcg.console_link);
	return G_FLOAT(OFS_RETURN);
}

qboolean CSQC_ConsoleCommand(int seat, const char *cmd)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.console_command)
		return false;
#ifdef TEXTEDITOR
	if (editormodal)
		return false;
#endif

	if (seat < 0)
		seat = CL_TargettedSplit(false);
	CSQC_ChangeLocalPlayer(seat);

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, cmd));

	PR_ExecuteProgram (csqcprogs, csqcg.console_command);
	return G_FLOAT(OFS_RETURN);
}
static void CSQC_GameCommand_f(void)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.gamecommand)
		return;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, Cmd_Args()));

	PR_ExecuteProgram (csqcprogs, csqcg.gamecommand);
}

void CSQC_PlayerInfoChanged(int player)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.playerinfochanged)
		return;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	G_FLOAT(OFS_PARM0) = player;
//	(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, keyname));
	PR_ExecuteProgram (csqcprogs, csqcg.playerinfochanged);
}
void CSQC_ServerInfoChanged(void)
{
//	void *pr_globals;
	if (!csqcprogs || !csqcg.serverinfochanged)
		return;

//	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
//	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, keyname));
	PR_ExecuteProgram (csqcprogs, csqcg.serverinfochanged);
}

qboolean CSQC_ParseTempEntity(void)
{
	int orc;
	void *pr_globals;
	if (!csqcprogs || !csqcg.parse_tempentity)
		return false;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	csqc_mayread = true;
	orc = msg_readcount;
	PR_ExecuteProgram (csqcprogs, csqcg.parse_tempentity);
	csqc_mayread = false;
	if (G_FLOAT(OFS_RETURN))
		return true;
	//failed. reset the read position.
	msg_readcount = orc;
	msg_badread = false;
	return false;
}

qboolean CSQC_ParseGamePacket(int seat)
{
	int parsefnc = csqcg.parse_event?csqcg.parse_event:csqcg.parse_tempentity;

	if (cl.csqcdebug)
	{
		int len = (unsigned short)MSG_ReadShort();
		int start = msg_readcount;

		if (!csqcprogs || !parsefnc)
		{
			MSG_ReadSkip(len);
			return false;
		}

		csqc_mayread = true;
		CSQC_ChangeLocalPlayer(seat);
		PR_ExecuteProgram (csqcprogs, parsefnc);

		if (msg_readcount != start + len)
		{
			Con_Printf("Gamecode misread a gamecode packet (%i bytes too much)\n", msg_readcount - (start+len));
			msg_readcount = start + len;
		}
	}
	else
	{
		if (!csqcprogs || !parsefnc)
		{
			Host_EndGame("CSQC not running or is unable to parse events.\n");
			return false;
		}
		csqc_mayread = true;
		CSQC_ChangeLocalPlayer(seat);
		PR_ExecuteProgram (csqcprogs, parsefnc);
	}
	csqc_mayread = false;
	return true;
}

void CSQC_MapEntityEdited(int modelindex, int idx, const char *newe)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.mapentityedited)
		return;

	if (modelindex != 1)
		return;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	G_INT(OFS_PARM0) = idx;
	(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, newe));
	PR_ExecuteProgram (csqcprogs, csqcg.mapentityedited);
}

/*qboolean CSQC_LoadResource(char *resname, char *restype)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.loadresource)
		return true;

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, resname));
	(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, restype));

	PR_ExecuteProgram (csqcprogs, csqcg.loadresource);

	return !!G_FLOAT(OFS_RETURN);
}*/

qboolean CSQC_Parse_Damage(int seat, float save, float take, vec3_t source)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.parse_damage)
		return false;

	CSQC_ChangeLocalPlayer(seat);
	
	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	((float *)pr_globals)[OFS_PARM0] = save;
	((float *)pr_globals)[OFS_PARM1] = take;
	((float *)pr_globals)[OFS_PARM2+0] = source[0];
	((float *)pr_globals)[OFS_PARM2+1] = source[1];
	((float *)pr_globals)[OFS_PARM2+2] = source[2];
	PR_ExecuteProgram (csqcprogs, csqcg.parse_damage);

	return G_FLOAT(OFS_RETURN);
}

qboolean CSQC_ParsePrint(char *message, int printlevel)
{
	void *pr_globals;
	int bufferpos;
	char *nextline;
	qboolean doflush;
	if (!csqcprogs || !csqcg.parse_print)
	{
		return false;
	}

	bufferpos = strlen(csqc_printbuffer);

	//fix-up faked bot chat
	if (*message == '\1' && *csqc_printbuffer == '\1')
		message++;

	while(*message)
	{
		nextline = strchr(message, '\n');
		if (nextline)
		{
			nextline+=1;
			doflush = true;
		}
		else
		{
			nextline = message+strlen(message);
			doflush = false;
		}

		if (bufferpos + nextline-message >= sizeof(csqc_printbuffer))
		{
			//if this would overflow the buffer, cap its length and flush the buffer
			//this copes with too many strings and too long strings.
			nextline = message + sizeof(csqc_printbuffer)-1 - bufferpos;
			doflush = true;
		}

		memcpy(csqc_printbuffer+bufferpos, message, nextline-message);
		bufferpos += nextline-message;
		csqc_printbuffer[bufferpos] = '\0';
		message = nextline;

		if (doflush)
		{
			pr_globals = PR_globals(csqcprogs, PR_CURRENT);
			(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, csqc_printbuffer));
			G_FLOAT(OFS_PARM1) = printlevel;
			PR_ExecuteProgram (csqcprogs, csqcg.parse_print);

			bufferpos = 0;
			csqc_printbuffer[bufferpos] = 0;
		}
	}
	return true;
}

qboolean CSQC_StuffCmd(int lplayernum, char *cmd, char *cmdend)
{
	void *pr_globals;
	char tmp[2];
	if (!csqcprogs || !csqcg.parse_stuffcmd)
		return false;

	CSQC_ChangeLocalPlayer(lplayernum);

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	tmp[0] = cmdend[0];
	tmp[1] = cmdend[1];
	cmdend[0] = '\n';
	cmdend[1] = 0;
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, cmd));
	cmdend[0] = tmp[0];
	cmdend[1] = tmp[1];

	PR_ExecuteProgram (csqcprogs, csqcg.parse_stuffcmd);
	return true;
}
qboolean CSQC_CenterPrint(int seat, const char *cmd)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.parse_centerprint)
		return false;

	CSQC_ChangeLocalPlayer(seat);

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, cmd));

	PR_ExecuteProgram (csqcprogs, csqcg.parse_centerprint);
	return G_FLOAT(OFS_RETURN) || csqc_isdarkplaces;
}

qboolean CSQC_Parse_SetAngles(int seat, vec3_t newangles, qboolean wasdelta)
{
	void *pr_globals;
	if (!csqcprogs || !csqcg.parse_setangles)
		return false;

	CSQC_ChangeLocalPlayer(seat);

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
	((float *)pr_globals)[OFS_PARM0+0] = newangles[0];
	((float *)pr_globals)[OFS_PARM0+1] = newangles[1];
	((float *)pr_globals)[OFS_PARM0+2] = newangles[2];
	((float *)pr_globals)[OFS_PARM1] = wasdelta;

	PR_ExecuteProgram (csqcprogs, csqcg.parse_setangles);
	return G_FLOAT(OFS_RETURN);
}

void CSQC_Input_Frame(int seat, usercmd_t *cmd)
{
	if (!csqcprogs || !csqcg.input_frame)
		return;

	CSQC_ChangeLocalPlayer(seat);

	if (csqcg.simtime)
		*csqcg.simtime = cl.servertime;
	if (csqcg.cltime)
		*csqcg.cltime = realtime-csqc_starttime;

	if (csqcg.clientcommandframe)
		*csqcg.clientcommandframe = cl.movesequence;

	cs_set_input_state(cmd);
	PR_ExecuteProgram (csqcprogs, csqcg.input_frame);
	cs_get_input_state(cmd);
}

//this protocol allows up to 32767 edicts.
#ifdef PEXT_CSQC
static void CSQC_EntityCheck(unsigned int entnum)
{
	unsigned int newmax;

	if (entnum >= maxcsqcentities)
	{
		newmax = entnum+64;
		csqcent = BZ_Realloc(csqcent, sizeof(*csqcent)*newmax);
		memset(csqcent + maxcsqcentities, 0, (newmax - maxcsqcentities)*sizeof(csqcent));
		maxcsqcentities = newmax;
	}
}

int CSQC_StartSound(int entnum, int channel, char *soundname, vec3_t pos, float vol, float attenuation, float pitchmod, float timeofs, unsigned int flags)
{
	void *pr_globals;
	csqcedict_t *ent;

	if (!csqcprogs)
		return false;
	if (csqcg.event_sound)
	{
		pr_globals = PR_globals(csqcprogs, PR_CURRENT);

		CSQC_EntityCheck(entnum);
		ent = csqcent[entnum];
		if (ent)
			*csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent);
		else
			*csqcg.self = 0;

		G_FLOAT(OFS_PARM0) = entnum;
		G_FLOAT(OFS_PARM1) = channel;
		G_INT(OFS_PARM2) = PR_TempString(csqcprogs, soundname);
		G_FLOAT(OFS_PARM3) = vol;
		G_FLOAT(OFS_PARM4) = attenuation;
		VectorCopy(pos, G_VECTOR(OFS_PARM5));
		G_FLOAT(OFS_PARM6) = pitchmod*100;
		G_FLOAT(OFS_PARM7) = flags;
//		G_FLOAT(OFS_PARM8) = timeofs;

		PR_ExecuteProgram(csqcprogs, csqcg.event_sound);

		return G_FLOAT(OFS_RETURN);
	}
	else if (csqcg.serversound)
	{
		CSQC_EntityCheck(entnum);
		ent = csqcent[entnum];
		if (!ent)
			return false;

		pr_globals = PR_globals(csqcprogs, PR_CURRENT);

		*csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent);
		G_FLOAT(OFS_PARM0) = channel;
		G_INT(OFS_PARM1) = PR_TempString(csqcprogs, soundname);
		VectorCopy(pos, G_VECTOR(OFS_PARM2));
		G_FLOAT(OFS_PARM3) = vol;
		G_FLOAT(OFS_PARM4) = attenuation;
		G_FLOAT(OFS_PARM5) = flags;
		G_FLOAT(OFS_PARM6) = timeofs;

		PR_ExecuteProgram(csqcprogs, csqcg.serversound);

		return G_FLOAT(OFS_RETURN);
	}
	return false;
}

void CSQC_GetEntityOrigin(unsigned int csqcent, float *out)
{
	wedict_t *ent;
	if (!csqcprogs)
		return;
	ent = WEDICT_NUM_UB(csqcprogs, csqcent);
	VectorCopy(ent->v->origin, out);
}

void CSQC_ParseEntities(void)
{
	csqcedict_t *ent;
	unsigned int entnum;
	void *pr_globals;
	int packetsize;
	int packetstart;
	qboolean removeflag;

	if (!csqcprogs)
		Host_EndGame("CSQC needs to be initialized for this server.\n");

	if (!csqcg.ent_update || !csqcg.self)
		Host_EndGame("CSQC has no CSQC_Ent_Update function\n");
	if (!csqc_world.worldmodel || csqc_world.worldmodel->loadstate != MLS_LOADED)
		Host_EndGame("world is not yet initialised\n");

	pr_globals = PR_globals(csqcprogs, PR_CURRENT);

	CL_CalcClientTime();
	if (csqcg.simtime)		//estimated server time
		*csqcg.simtime = cl.servertime;
	if (csqcg.cltime)	//smooth client time.
		*csqcg.cltime = realtime-csqc_starttime;

	if (csqcg.netnewtime)
		*csqcg.netnewtime = cl.gametime;
	if (csqcg.netoldtime)
		*csqcg.netoldtime = cl.oldgametime;
	if (csqcg.netdeltatime)
		*csqcg.netdeltatime = cl.gametime - cl.oldgametime;

	if (!csqc_isdarkplaces)
	{
		if (csqcg.clientcommandframe)
			*csqcg.clientcommandframe = cl.movesequence;
		if (csqcg.servercommandframe)
			*csqcg.servercommandframe = cl.ackedmovesequence;
	}

	for(;;)
	{
		//replacement deltas now also includes 22bit entity num indicies.
		if (cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
		{
			entnum = (unsigned short)MSG_ReadShort();
			removeflag = !!(entnum & 0x8000);
			if (entnum & 0x4000)
				entnum = (entnum & 0x3fff) | (MSG_ReadByte()<<14);
			else
				entnum &= ~0x8000;
		}
		else
		{
			entnum = (unsigned short)MSG_ReadShort();
			removeflag = !!(entnum & 0x8000);
			entnum &= ~0x8000;
		}

		if ((!entnum && !removeflag) || msg_badread)
			break;

		if (removeflag)
		{	//remove
			if (!entnum)
				Host_EndGame("CSQC cannot remove world!\n");

			CSQC_EntityCheck(entnum);

			if (cl_csqcdebug.ival)
				Con_Printf("Remove %i\n", entnum);

			ent = csqcent[entnum];
			csqcent[entnum] = NULL;

			if (!ent)	//hrm.
				continue;

			CSQC_EntRemove(ent);
			//the csqc is expected to call the remove builtin.
		}
		else
		{
			CSQC_EntityCheck(entnum);

			if (cl.csqcdebug)
			{
				packetsize = MSG_ReadShort();
				packetstart = msg_readcount;
			}
			else
			{
				packetsize = 0;
				packetstart = 0;
			}

			ent = csqcent[entnum];
			if (!ent)
			{
				if (csqcg.ent_spawn)
				{
					*csqcg.self = 0;
					G_FLOAT(OFS_PARM0) = entnum;
					PR_ExecuteProgram(csqcprogs, csqcg.ent_spawn);
					ent = csqcent[entnum] = (csqcedict_t*)PROG_TO_WEDICT(csqcprogs, *csqcg.self);	//allow the mod to change the ent.
				}
				else
				{
					ent = (csqcedict_t*)ED_Alloc(csqcprogs, false, 0);
					csqcent[entnum] = ent;
					ent->xv->entnum = entnum;
				}

				G_FLOAT(OFS_PARM0) = true;

				if (cl_csqcdebug.ival)
					Con_Printf("Add %i\n", entnum);
			}
			else
			{
				G_FLOAT(OFS_PARM0) = false;
				if (cl_csqcdebug.ival)
					Con_Printf("Update %i\n", entnum);
			}

			*csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent);
			csqc_mayread = true;
			PR_ExecuteProgram(csqcprogs, csqcg.ent_update);
			csqc_mayread = false;
			if (csqcg.ent_spawn)
				csqcent[entnum] = (csqcedict_t*)PROG_TO_WEDICT(csqcprogs, *csqcg.self);	//allow the mod to change the ent.

			if (cl.csqcdebug)
			{
				if (msg_readcount != packetstart+packetsize)
				{
					if (msg_readcount > packetstart+packetsize)
						Con_Printf("CSQC overread entity %i. Size %i, read %i", entnum, packetsize, msg_readcount - packetstart);
					else
						Con_Printf("CSQC underread entity %i. Size %i, read %i", entnum, packetsize, msg_readcount - packetstart);
					Con_Printf(", first byte is %i(%x)\n", net_message.data[msg_readcount], net_message.data[msg_readcount]);
#ifndef CLIENTONLY
					if (sv.state)
					{
						Con_Printf("Server classname: \"%s\"\n", PR_GetString(svprogfuncs, EDICT_NUM_UB(svprogfuncs, entnum)->v->classname));
					}
#endif
				}
				msg_readcount = packetstart+packetsize;	//leetism.
			}
		}
	}
}
#endif

#endif