#include "quakedef.h"

/*
I think globals.maxentities is the hard cap, rather than current max like in q1.
*/

#ifdef HLSERVER

#include "winquake.h"
#include "svhl_gcapi.h"
#include "pr_common.h"

#include "crc.h"
#include "model_hl.h"

#if defined(_MSC_VER)
 #if _MSC_VER >= 1300
  #define __func__ __FUNCTION__
 #else
  #define __func__ "unknown"
 #endif
#else
 //I hope you're c99 and have a __func__
#endif

#define ignore(s) Con_Printf("Fixme: " s "\n")
#define notimpl(l) Con_Printf("halflife sv builtin not implemented on line %i\n", l)
#define notimpf(f) Con_Printf("halflife sv builtin %s not implemented\n", f)


dllhandle_t *hlgamecode;
SVHL_Globals_t SVHL_Globals;
SVHL_GameFuncs_t SVHL_GameFuncs;

#define MAX_HL_EDICTS 2048
hledict_t *SVHL_Edict;
int SVHL_NumActiveEnts;

int lastusermessage;




string_t QDECL GHL_AllocString(char *string)
{
	char *news;
	news = Hunk_Alloc(strlen(string)+1);
	memcpy(news, string, strlen(string)+1);
	return news - SVHL_Globals.stringbase;
}
int QDECL GHL_PrecacheModel(char *name)
{
	int		i;

	if (name[0] <= ' ')
	{
		Con_Printf ("precache_model: empty string\n");
		return 0;
	}

	for (i=1 ; i<MAX_MODELS ; i++)
	{
		if (!sv.strings.model_precache[i])
		{
			if (strlen(name)>=MAX_QPATH-1)	//probably safest to keep this.
			{
				SV_Error ("Precache name too long");
				return 0;
			}
			name = sv.strings.model_precache[i] = SVHL_Globals.stringbase+GHL_AllocString(name);

			if (!strcmp(name + strlen(name) - 4, ".bsp"))
				sv.models[i] = Mod_FindName(name);

			if (sv.state != ss_loading)
			{
				Con_DPrintf("Delayed model precache: %s\n", name);
				MSG_WriteByte(&sv.reliable_datagram, svcfte_precache);
				MSG_WriteShort(&sv.reliable_datagram, i);
				MSG_WriteString(&sv.reliable_datagram, name);
#ifdef NQPROT
				MSG_WriteByte(&sv.nqreliable_datagram, svcdp_precache);
				MSG_WriteShort(&sv.nqreliable_datagram, i);
				MSG_WriteString(&sv.nqreliable_datagram, name);
#endif
			}

			return i;
		}
		if (!strcmp(sv.strings.model_precache[i], name))
		{
			return i;
		}
	}
	SV_Error ("GHL_precache_model: overflow");
	return 0;
}
int QDECL GHL_PrecacheSound(char *name)
{
	int		i;

	if (name[0] <= ' ')
	{
		Con_Printf ("precache_sound: empty string\n");
		return 0;
	}

	for (i=1 ; i<MAX_SOUNDS ; i++)
	{
		if (!*sv.strings.sound_precache[i])
		{
			if (strlen(name)>=MAX_QPATH-1)	//probably safest to keep this.
			{
				SV_Error ("Precache name too long");
				return 0;
			}
			strcpy(sv.strings.sound_precache[i], name);
			name = sv.strings.sound_precache[i];

			if (sv.state != ss_loading)
			{
				Con_DPrintf("Delayed sound precache: %s\n", name);
				MSG_WriteByte(&sv.reliable_datagram, svcfte_precache);
				MSG_WriteShort(&sv.reliable_datagram, -i);
				MSG_WriteString(&sv.reliable_datagram, name);
#ifdef NQPROT
				MSG_WriteByte(&sv.nqreliable_datagram, svcdp_precache);
				MSG_WriteShort(&sv.nqreliable_datagram, -i);
				MSG_WriteString(&sv.nqreliable_datagram, name);
#endif
			}

			return i;
		}
		if (!strcmp(sv.strings.sound_precache[i], name))
		{
			return i;
		}
	}
	SV_Error ("GHL_precache_sound: overflow");
	return 0;
}
void QDECL GHL_SetModel(hledict_t *ed, char *modelname)
{
	model_t *mod;
	int mdlidx = GHL_PrecacheModel(modelname);
	ed->v.modelindex = mdlidx;
	ed->v.model = sv.strings.model_precache[mdlidx] - SVHL_Globals.stringbase;

	mod = sv.models[mdlidx];
	if (mod)
	{
		VectorCopy(mod->mins, ed->v.mins);
		VectorCopy(mod->maxs, ed->v.maxs);
		VectorSubtract(mod->maxs, mod->mins, ed->v.size);
	}
	SVHL_LinkEdict(ed, false);
}
unk QDECL GHL_ModelIndex(unk){notimpf(__func__);}
int QDECL GHL_ModelFrames(int midx)
{
	//returns the number of frames(sequences I assume) this model has
	ignore("ModelFrames");
	return 1;
}
void QDECL GHL_SetSize(hledict_t *ed, float *mins, float *maxs)
{
	VectorCopy(mins, ed->v.mins);
	VectorCopy(maxs, ed->v.maxs);
	SVHL_LinkEdict(ed, false);
}
void QDECL GHL_ChangeLevel(char *nextmap, char *startspot)
{
	Cbuf_AddText(va("changelevel %s %s@%f@%f@%f\n", nextmap, startspot, SVHL_Globals.landmark[0], SVHL_Globals.landmark[1], SVHL_Globals.landmark[2]), RESTRICT_PROGS);
}
unk QDECL GHL_GetSpawnParms(unk){notimpf(__func__);}
unk QDECL GHL_SaveSpawnParms(unk){notimpf(__func__);}
float QDECL GHL_VecToYaw(float *inv)
{
	vec3_t outa;

	VectorAngles(inv, NULL, outa);
	return outa[1];
}
void QDECL GHL_VecToAngles(float *inv, float *outa)
{
	VectorAngles(inv, NULL, outa);
}
unk QDECL GHL_MoveToOrigin(unk){notimpf(__func__);}
unk QDECL GHL_ChangeYaw(unk){notimpf(__func__);}
unk QDECL GHL_ChangePitch(unk){notimpf(__func__);}
hledict_t *QDECL GHL_FindEntityByString(hledict_t *last, char *field, char *value)
{
	hledict_t *ent;
	int i;
	int ofs;
	string_t str;
	if (!strcmp(field, "targetname"))
		ofs = (char*)&((hledict_t *)NULL)->v.targetname - (char*)NULL;
	else if (!strcmp(field, "classname"))
		ofs = (char*)&((hledict_t *)NULL)->v.classname - (char*)NULL;
	else
	{
		Con_Printf("Fixme: Look for %s=%s\n", field, value);
		return NULL;
	}
	
	if (last)
		i=last-SVHL_Edict+1;
	else
		i = 0;
	for (; i<SVHL_Globals.maxentities; i++)
	{
		ent = &SVHL_Edict[i];
		if (ent->isfree)
			continue;
		str = *(string_t*)((char*)ent+ofs);
		if (str && !strcmp(SVHL_Globals.stringbase+str, value))
			return ent;
	}
	return SVHL_Edict;
}
unk QDECL GHL_GetEntityIllum(unk){notimpf(__func__);}
hledict_t *QDECL GHL_FindEntityInSphere(hledict_t *last, float *org, float radius)
{
	int i, j;
	vec3_t eorg;
	hledict_t *ent;

	radius = radius*radius;

	if (last)
		i=last-SVHL_Edict+1;
	else
		i = 0;
	for (; i<SVHL_Globals.maxentities; i++)
	{
		ent = &SVHL_Edict[i];
		if (ent->isfree)
			continue;
		if (!ent->v.solid)
			continue;
		for (j=0; j<3; j++)
			eorg[j] = org[j] - (ent->v.origin[j] + (ent->v.mins[j] + ent->v.maxs[j])*0.5);
		if (DotProduct(eorg,eorg) > radius)
			continue;

		//its close enough
		return ent;
	}
	return NULL;
}
hledict_t *QDECL GHL_FindClientInPVS(hledict_t *ed)
{
	qbyte	*viewerpvs;
	int best = 0, i;
	float bestdist = 99999999;	//HL maps are limited in size anyway
	float d;
	int leafnum;
	vec3_t ofs;
	hledict_t *other;

	//fixme: we need to track some state
	//a different client should be returned each call _per ent_ (so it can be used once per frame)

	viewerpvs = sv.world.worldmodel->funcs.LeafPVS(sv.world.worldmodel, sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, ed->v.origin), NULL, 0);

	for (i = 0; i < MAX_CLIENTS; i++)
	{
		if (svs.clients[i].state == cs_spawned)
		{
			other = &SVHL_Edict[i+1];
			if (ed == other)
				continue;	//ignore yourself.
			if (svs.clients[i].spectator)
				continue;	//ignore spectators

			leafnum = sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, other->v.origin)-1;/*pvs is 1 based, leafs are 0 based*/
			if (viewerpvs[leafnum>>3] & (1<<(leafnum&7)))
			{
				VectorSubtract(ed->v.origin, other->v.origin, ofs);
				d = DotProduct(ofs, ofs);

				if (d < bestdist)
				{
					bestdist = d;
					best = i+1;
				}
			}
		}
	}

	if (best)
		return &SVHL_Edict[best];
	return NULL;
}
unk QDECL GHL_EntitiesInPVS(unk){notimpf(__func__);}
void QDECL GHL_MakeVectors(float *angles)
{
	AngleVectors(angles, SVHL_Globals.v_forward, SVHL_Globals.v_right, SVHL_Globals.v_up);
}
void QDECL GHL_AngleVectors(float *angles, float *forward, float *right, float *up)
{
	AngleVectors(angles, forward, right, up);
}

///////////////////////////////////////////////////////////

hledict_t *QDECL GHL_CreateEntity(void)
{	
	int i;
	static int spawnnumber;
	spawnnumber++;

	for (i = sv.allocated_client_slots; i < SVHL_NumActiveEnts; i++)
	{
		if (SVHL_Edict[i].isfree)
		{
			if (SVHL_Edict[i].freetime > sv.time)
				continue;

			memset(&SVHL_Edict[i], 0, sizeof(SVHL_Edict[i]));
			SVHL_Edict[i].spawnnumber = spawnnumber;
			SVHL_Edict[i].v.edict = &SVHL_Edict[i];
			return &SVHL_Edict[i];
		}
	}
	if (i < MAX_HL_EDICTS)
	{
		SVHL_NumActiveEnts++;
		memset(&SVHL_Edict[i], 0, sizeof(SVHL_Edict[i]));
		SVHL_Edict[i].spawnnumber = spawnnumber;
		SVHL_Edict[i].v.edict = &SVHL_Edict[i];
		return &SVHL_Edict[i];
	}
	SV_Error("Ran out of free edicts");
	return NULL;
}
void QDECL GHL_RemoveEntity(hledict_t *ed)
{
	SVHL_UnlinkEdict(ed);
	ed->isfree = true;
	ed->freetime = sv.time+2;
}
hledict_t *QDECL GHL_CreateNamedEntity(string_t name)
{
	void (QDECL *spawnfunc)(hlentvars_t *evars);
	hledict_t *ed;
	ed = GHL_CreateEntity();
	if (!ed)
		return NULL;
	ed->v.classname = name;

	spawnfunc = (void(QDECL *)(hlentvars_t*))GetProcAddress((HINSTANCE)hlgamecode, name+SVHL_Globals.stringbase);
	if (spawnfunc)
		spawnfunc(&ed->v);
	return ed;
}
void *QDECL GHL_PvAllocEntPrivateData(hledict_t *ed, long quant)
{
	if (!ed)
		return NULL;
	ed->moddata = Z_Malloc(quant);
	return ed->moddata;
}
unk QDECL GHL_PvEntPrivateData(unk)
{
	notimpf(__func__);
}
unk QDECL GHL_FreeEntPrivateData(unk)
{
	notimpf(__func__);
}
unk QDECL GHL_GetVarsOfEnt(unk)
{
	notimpf(__func__);
}
hledict_t *QDECL GHL_PEntityOfEntOffset(int ednum)
{
	return (hledict_t *)(ednum + (char*)SVHL_Edict);
}
int QDECL GHL_EntOffsetOfPEntity(hledict_t *ed)
{
	return (char*)ed - (char*)SVHL_Edict;
}
int QDECL GHL_IndexOfEdict(hledict_t *ed)
{
	return ed - SVHL_Edict;
}
hledict_t *QDECL GHL_PEntityOfEntIndex(int idx)
{
	return &SVHL_Edict[idx];
}
unk QDECL GHL_FindEntityByVars(unk)
{
	notimpf(__func__);
}

///////////////////////////////////////////////////////

unk QDECL GHL_MakeStatic(unk){notimpf(__func__);}
unk QDECL GHL_EntIsOnFloor(unk){notimpf(__func__);}
int QDECL GHL_DropToFloor(hledict_t *ed)
{
	vec3_t top;
	vec3_t bottom;
	trace_t tr;
	VectorCopy(ed->v.origin, top);
	VectorCopy(ed->v.origin, bottom);
	top[2] += 1;
	bottom[2] -= 256;
	tr = SVHL_Move(top, ed->v.mins, ed->v.maxs, bottom, 0, 0, ed);
	VectorCopy(tr.endpos, ed->v.origin);
	return tr.fraction != 0 && tr.fraction != 1;
}
int QDECL GHL_WalkMove(hledict_t *ed, float yaw, float dist, int mode)
{
	ignore("walkmove");
	return 1;
}
void QDECL GHL_SetOrigin(hledict_t *ed, float *neworg)
{
	VectorCopy(neworg, ed->v.origin);
	SVHL_LinkEdict(ed, false);
}
void QDECL GHL_EmitSound(hledict_t *ed, int chan, char *soundname, float vol, float atten, int flags, int pitch)
{
	SV_StartSound(ed-SVHL_Edict, ed->v.origin, ~0, chan, soundname, vol*255, atten, pitch);
}
void QDECL GHL_EmitAmbientSound(hledict_t *ed, float *org, char *soundname, float vol, float atten, unsigned int flags, int pitch)
{
	SV_StartSound(0, org, ~0, 0, soundname, vol*255, atten, 0);
}
void QDECL GHL_TraceLine(float *start, float *end, int flags, hledict_t *ignore, hltraceresult_t *result)
{
	trace_t t;
	
	t = SVHL_Move(start, vec3_origin, vec3_origin, end, flags, 0, ignore);

	result->allsolid = t.allsolid;
	result->startsolid = t.startsolid;
	result->inopen = t.inopen;
	result->inwater = t.inwater;
	result->fraction = t.fraction;
	VectorCopy(t.endpos, result->endpos);
	result->planedist = t.plane.dist;
	VectorCopy(t.plane.normal, result->planenormal);
	result->touched = t.ent;
	if (!result->touched)
		result->touched = &SVHL_Edict[0];
	result->hitgroup = 0;
}
unk QDECL GHL_TraceToss(unk){notimpf(__func__);}
unk QDECL GHL_TraceMonsterHull(unk){notimpf(__func__);}
void QDECL GHL_TraceHull(float *start, float *end, int flags, int hullnum, hledict_t *ignore, hltraceresult_t *result)
{
	trace_t t;

	t = SVHL_Move(start, sv.world.worldmodel->hulls[hullnum].clip_mins, sv.world.worldmodel->hulls[hullnum].clip_maxs, end, flags, 0, ignore);

	result->allsolid = t.allsolid;
	result->startsolid = t.startsolid;
	result->inopen = t.inopen;
	result->inwater = t.inwater;
	result->fraction = t.fraction;
	VectorCopy(t.endpos, result->endpos);
	result->planedist = t.plane.dist;
	VectorCopy(t.plane.normal, result->planenormal);
	result->touched = t.ent;
	result->hitgroup = 0;
}
unk QDECL GHL_TraceModel(unk){notimpf(__func__);}
char *QDECL GHL_TraceTexture(hledict_t *againstent, vec3_t start, vec3_t end)
{
	trace_t tr;
	sv.world.worldmodel->funcs.NativeTrace(sv.world.worldmodel, 0, 0, NULL, start, end, vec3_origin, vec3_origin, MASK_WORLDSOLID, &tr);
	return tr.surface->name;
}
unk QDECL GHL_TraceSphere(unk){notimpf(__func__);}
unk QDECL GHL_GetAimVector(unk){notimpf(__func__);}
void QDECL GHL_ServerCommand(char *cmd)
{
	Cbuf_AddText(cmd, RESTRICT_PROGS);
}
void QDECL GHL_ServerExecute(void)
{
	Cbuf_ExecuteLevel(RESTRICT_PROGS);
}
unk QDECL GHL_ClientCommand(unk){notimpf(__func__);}
unk QDECL GHL_ParticleEffect(unk){notimpf(__func__);}
void QDECL GHL_LightStyle(int stylenum, char *stylestr)
{
	PF_applylightstyle(stylenum, stylestr, 7);
}
int QDECL GHL_DecalIndex(char *decalname)
{
	Con_Printf("Fixme: precache decal %s\n", decalname);
	return 0;
}
int QDECL GHL_PointContents(float *org)
{
	return Q1CONTENTS_EMPTY;
}

int svhl_messagedest;
vec3_t svhl_messageorigin;
hledict_t *svhl_messageent;
void QDECL GHL_MessageBegin(int dest, int type, float *org, hledict_t *ent)
{
	svhl_messagedest = dest;
	if (org)
		VectorCopy(org, svhl_messageorigin);
	else
		VectorClear(svhl_messageorigin);
	svhl_messageent = ent;

	if (sv.multicast.cursize)
	{
		Con_Printf("MessageBegin called without MessageEnd\n");
		SZ_Clear (&sv.multicast);
	}
	MSG_WriteByte(&sv.multicast, svcfte_cgamepacket);
	MSG_WriteShort(&sv.multicast, 0);
	MSG_WriteByte(&sv.multicast, type);
}
void QDECL GHL_MessageEnd(unk)
{
	unsigned short len;
	client_t *cl;

	if (!sv.multicast.cursize)
	{
		Con_Printf("MessageEnd called without MessageBegin\n");
		return;
	}

	//update the length
	len = sv.multicast.cursize - 3;
	sv.multicast.data[1] = len&0xff;
	sv.multicast.data[2] = len>>8;

	switch(svhl_messagedest)
	{
	case MSG_BROADCAST:
		SZ_Write(&sv.datagram, sv.multicast.data, sv.multicast.cursize);
		break;
	case MSG_ONE:
		cl = &svs.clients[svhl_messageent - SVHL_Edict - 1];
		if (cl->state >= cs_spawned)
		{
			ClientReliableCheckBlock(cl, sv.multicast.cursize);
			ClientReliableWrite_SZ(cl, sv.multicast.data, sv.multicast.cursize);
			ClientReliable_FinishWrite(cl);
		}
		break;
	case MSG_ALL:
		SZ_Write(&sv.reliable_datagram, sv.multicast.data, sv.multicast.cursize);
		break;
	case MSG_MULTICAST:
		SV_Multicast(svhl_messageorigin, MULTICAST_PVS);
		break;
	case MSG_MULTICAST+1:
		SV_Multicast(svhl_messageorigin, MULTICAST_PHS);
		break;
	default:
		Con_Printf("GHL_MessageEnd: dest type %i not supported\n", svhl_messagedest);
		break;
	}

	SZ_Clear (&sv.multicast);
}
void QDECL GHL_WriteByte(int value)
{
	MSG_WriteByte(&sv.multicast, value);
}
void QDECL GHL_WriteChar(int value)
{
	MSG_WriteChar(&sv.multicast, value);
}
void QDECL GHL_WriteShort(int value)
{
	MSG_WriteShort(&sv.multicast, value);
}
void QDECL GHL_WriteLong(int value)
{
	MSG_WriteLong(&sv.multicast, value);
}
void QDECL GHL_WriteAngle(float value)
{
	MSG_WriteAngle8(&sv.multicast, value);
}
void QDECL GHL_WriteCoord(float value)
{
	coorddata i = MSG_ToCoord(value, 2);
	SZ_Write (&sv.multicast, (void*)&i, 2);
}
void QDECL GHL_WriteString(char *string)
{
	MSG_WriteString(&sv.multicast, string);
}
void QDECL GHL_WriteEntity(int entnum)
{
	MSG_WriteEntity(&sv.multicast, entnum);
}


void QDECL GHL_AlertMessage(int level, char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];

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

	Con_Printf("%s\n", string);
}
void QDECL GHL_EngineFprintf(FILE *f, char *fmt, ...)
{
	SV_Error("Halflife gamecode tried to use EngineFprintf\n");
}
unk QDECL GHL_SzFromIndex(unk){notimpf(__func__);}
void *QDECL GHL_GetModelPtr(hledict_t *ed)
{
#ifdef SERVERONLY
	return NULL;
#else
	if (!ed->v.modelindex)
		return NULL;
	if (!sv.models[ed->v.modelindex])
		sv.models[ed->v.modelindex] = Mod_ForName(sv.strings.model_precache[ed->v.modelindex], false);
	return Mod_GetHalfLifeModelData(sv.models[ed->v.modelindex]);
#endif
}
int QDECL GHL_RegUserMsg(char *msgname, int msgsize)
{
	//we use 1 as the code to choose others.
	if (lastusermessage <= 1)
		return -1;

	SV_FlushSignon ();

	//for new clients
	MSG_WriteByte(&sv.signon, svcfte_cgamepacket);
	MSG_WriteShort(&sv.signon, strlen(msgname)+3);
	MSG_WriteByte(&sv.signon, 1);
	MSG_WriteByte(&sv.signon, lastusermessage);
	MSG_WriteString(&sv.signon, msgname);

	//and if the client is already spawned...
	MSG_WriteByte(&sv.reliable_datagram, svcfte_cgamepacket);
	MSG_WriteShort(&sv.reliable_datagram, strlen(msgname)+3);
	MSG_WriteByte(&sv.reliable_datagram, 1);
	MSG_WriteByte(&sv.reliable_datagram, lastusermessage);
	MSG_WriteString(&sv.reliable_datagram, msgname);

	return lastusermessage--;
}
unk QDECL GHL_AnimationAutomove(unk){notimpf(__func__);}
unk QDECL GHL_GetBonePosition(unk){notimpf(__func__);}

hlintptr_t QDECL GHL_FunctionFromName(char *name)
{
	return (hlintptr_t)Sys_GetAddressForName(hlgamecode, name);
}
char *QDECL GHL_NameForFunction(hlintptr_t function)
{
	return Sys_GetNameForAddress(hlgamecode, (void*)function);
}

unk QDECL GHL_ClientPrintf(unk)
{
//	SV_ClientPrintf(
	notimpf(__func__);
}
void QDECL GHL_ServerPrint(char *msg)
{
	Con_Printf("%s", msg);	
}
char *QDECL GHL_Cmd_Args(void)
{
	return Cmd_Args();
}
char *QDECL GHL_Cmd_Argv(int arg)
{
	return Cmd_Argv(arg);
}
int QDECL GHL_Cmd_Argc(unk)
{
	return Cmd_Argc();
}
unk QDECL GHL_GetAttachment(unk){notimpf(__func__);}
void QDECL GHL_CRC32_Init(hlcrc_t *crc)
{
	unsigned short crc16 = *crc;
	QCRC_Init(&crc16);
	*crc = crc16;
}
void QDECL GHL_CRC32_ProcessBuffer(hlcrc_t *crc, qbyte *p, int len)
{
	unsigned short crc16 = *crc;
	while(len-->0)
	{
		QCRC_ProcessByte(&crc16, *p++);
	}
	*crc = crc16;
}
void QDECL GHL_CRC32_ProcessByte(hlcrc_t *crc, qbyte b)
{
	unsigned short crc16 = *crc;
	QCRC_ProcessByte(&crc16, b);
	*crc = crc16;
}
hlcrc_t QDECL GHL_CRC32_Final(hlcrc_t crc)
{
	unsigned short crc16 = crc;
	return QCRC_Value(crc16);
}
long QDECL GHL_RandomLong(long minv, long maxv)
{
	return minv + frandom()*(maxv-minv);
}
float QDECL GHL_RandomFloat(float minv, float maxv)
{
	return minv + frandom()*(maxv-minv);
}
unk QDECL GHL_SetView(unk){notimpf(__func__);}
unk QDECL GHL_Time(unk){notimpf(__func__);}
unk QDECL GHL_CrosshairAngle(unk){notimpf(__func__);}
void *QDECL GHL_LoadFileForMe(char *name, int *size_out)
{
	int fsize;
	void *fptr;
	fsize = FS_LoadFile(name, &fptr);
	if (size_out)
		*size_out = fsize;
	if (fsize == -1)
		return NULL;
	return fptr;
}
void QDECL GHL_FreeFile(void *fptr)
{
	FS_FreeFile(fptr);
}
unk QDECL GHL_EndSection(unk){notimpf(__func__);}
#include <sys/stat.h>
int QDECL GHL_CompareFileTime(char *fname1, char *fname2, int *result)
{
	flocation_t loc1, loc2;
	struct stat stat1, stat2;
	//results:
	//1 = f1 is newer
	//0 = equal age
	//-1 = f2 is newer
	//at least I think that's what it means.
	*result = 0;
	if (!FS_FLocateFile(fname1, FSLFRT_IFFOUND, &loc1) || !FS_FLocateFile(fname2, FSLFRT_IFFOUND, &loc2))
		return 0;

	if (stat(loc1.rawname, &stat1) || stat(loc2.rawname, &stat2))
		return 0;

	if (stat1.st_mtime > stat2.st_mtime)
		*result = 1;
	else if (stat1.st_mtime < stat2.st_mtime)
		*result = -1;
	else
		*result = 0;

	return 1;
}
void QDECL GHL_GetGameDir(char *gamedir)
{
	extern char gamedirfile[];
	//warning: the output buffer size is not specified!
	Q_strncpyz(gamedir, gamedirfile, MAX_QPATH);
}
unk QDECL GHL_Cvar_RegisterVariable(unk){notimpf(__func__);}
unk QDECL GHL_FadeClientVolume(unk){notimpf(__func__);}
unk QDECL GHL_SetClientMaxspeed(unk)
{
	notimpf(__func__);
}
unk QDECL GHL_CreateFakeClient(unk){notimpf(__func__);}
unk QDECL GHL_RunPlayerMove(unk){notimpf(__func__);}
int QDECL GHL_NumberOfEntities(void)
{
	return 0;
}
char *QDECL GHL_GetInfoKeyBuffer(hledict_t *ed)
{
	if (!ed)
		return svs.info;

	return svs.clients[ed - SVHL_Edict - 1].userinfo;
}
char *QDECL GHL_InfoKeyValue(char *infostr, char *key)
{
	return Info_ValueForKey(infostr, key);
}
unk QDECL GHL_SetKeyValue(unk){notimpf(__func__);}
unk QDECL GHL_SetClientKeyValue(unk){notimpf(__func__);}
unk QDECL GHL_IsMapValid(unk){notimpf(__func__);}
unk QDECL GHL_StaticDecal(unk){notimpf(__func__);}
unk QDECL GHL_PrecacheGeneric(unk){notimpf(__func__);}
int QDECL GHL_GetPlayerUserId(hledict_t *ed)
{
	unsigned int clnum = (ed - SVHL_Edict) - 1;
	if (clnum >= sv.allocated_client_slots)
		return -1;
	return svs.clients[clnum].userid;
}
unk QDECL GHL_BuildSoundMsg(unk){notimpf(__func__);}

int QDECL GHL_IsDedicatedServer(void)
{
#ifdef SERVERONLY
	return 1;
#else
	return qrenderer == QR_NONE;
#endif
}

hlcvar_t *hlcvar_malloced;
hlcvar_t *hlcvar_stored;
void SVHL_UpdateCvar(cvar_t *var)
{
	if (!var->hlcvar)
		return;	//nothing to do
	var->hlcvar->string = var->string;
	var->hlcvar->value = var->value;
}

void SVHL_FreeCvars(void)
{
	cvar_t *nc;
	hlcvar_t *n;
	//forget all
	while (hlcvar_malloced)
	{
		n = hlcvar_malloced;
		hlcvar_malloced = n->next;

		nc = Cvar_FindVar(n->name);
		if (nc)
			nc->hlcvar = NULL;
		Z_Free(n);
	}

	while (hlcvar_stored)
	{
		n = hlcvar_stored;
		hlcvar_stored = n->next;

		nc = Cvar_FindVar(n->name);
		if (nc)
			nc->hlcvar = NULL;
	}
}
void SVHL_FreeCvar(hlcvar_t *var)
{
	cvar_t *nc;
	hlcvar_t **ref;
	//unlink (free if it was malloced)

	ref = &hlcvar_malloced;
	while (*ref)
	{
		if (*ref == var)
		{
			(*ref) = (*ref)->next;

			nc = Cvar_FindVar(var->name);
			if (nc)
				nc->hlcvar = NULL;
			Z_Free(var);
			return;
		}
		ref = &(*ref)->next;
	}

	ref = &hlcvar_stored;
	while (*ref)
	{
		if (*ref == var)
		{
			(*ref) = (*ref)->next;

			nc = Cvar_FindVar(var->name);
			if (nc)
				nc->hlcvar = NULL;
			return;
		}
		ref = &(*ref)->next;
	}
}

hlcvar_t *QDECL GHL_CVarGetPointer(char *varname)
{
	cvar_t *var;
	hlcvar_t *hlvar;
	var = Cvar_Get(varname, "", 0, "HalfLife");
	if (!var)
	{
		Con_Printf("Not giving cvar \"%s\" to game\n", varname);
		return NULL;
	}
	hlvar = var->hlcvar;
	if (!hlvar)
	{
		hlvar = var->hlcvar = Z_Malloc(sizeof(hlcvar_t));
		hlvar->name = var->name;
		hlvar->string = var->string;
		hlvar->value = var->value;

		hlvar->next = hlcvar_malloced;
		hlcvar_malloced = hlvar;
	}
	return hlvar;
}
void QDECL GHL_CVarRegister(hlcvar_t *hlvar)
{
	cvar_t *var;
	var = Cvar_Get(hlvar->name, hlvar->string, 0, "HalfLife");
	if (!var)
	{
		Con_Printf("Not giving cvar \"%s\" to game\n", hlvar->name);
		return;
	}
	if (var->hlcvar)
	{
		SVHL_FreeCvar(var->hlcvar);

		hlvar->next = hlcvar_stored;
		hlcvar_stored = hlvar;
	}
	var->hlcvar = hlvar;
}
float QDECL GHL_CVarGetFloat(char *vname)
{
	cvar_t *var = Cvar_FindVar(vname);
	if (var)
		return var->value;
	Con_Printf("cvar %s does not exist\n", vname);
	return 0;
}
char *QDECL GHL_CVarGetString(char *vname)
{
	cvar_t *var = Cvar_FindVar(vname);
	if (var)
		return var->string;
	Con_Printf("cvar %s does not exist\n", vname);
	return "";
}
void QDECL GHL_CVarSetFloat(char *vname, float value)
{
	cvar_t *var = Cvar_FindVar(vname);
	if (var)
		Cvar_SetValue(var, value);
	else
		Con_Printf("cvar %s does not exist\n", vname);
}
void QDECL GHL_CVarSetString(char *vname, char *value)
{
	cvar_t *var = Cvar_FindVar(vname);
	if (var)
		Cvar_Set(var, value);
	else
		Con_Printf("cvar %s does not exist\n", vname);
}

unk QDECL GHL_GetPlayerWONId(unk){notimpf(__func__);}
unk QDECL GHL_Info_RemoveKey(unk){notimpf(__func__);}
unk QDECL GHL_GetPhysicsKeyValue(unk){notimpf(__func__);}
unk QDECL GHL_SetPhysicsKeyValue(unk){notimpf(__func__);}
unk QDECL GHL_GetPhysicsInfoString(unk){notimpf(__func__);}
unsigned short QDECL GHL_PrecacheEvent(int eventtype, char *eventname)
{
	Con_Printf("Fixme: GHL_PrecacheEvent: %s\n", eventname);
	return 0;
}
void QDECL GHL_PlaybackEvent(int flags, hledict_t *ent, unsigned short eventidx, float delay, float *origin, float *angles, float f1, float f2, int i1, int i2, int b1, int b2)
{
	ignore("GHL_PlaybackEvent not implemented");
}
unk QDECL GHL_SetFatPVS(unk){notimpf(__func__);}
unk QDECL GHL_SetFatPAS(unk){notimpf(__func__);}
unk QDECL GHL_CheckVisibility(unk){notimpf(__func__);}
unk QDECL GHL_DeltaSetField(unk){notimpf(__func__);}
unk QDECL GHL_DeltaUnsetField(unk){notimpf(__func__);}
unk QDECL GHL_DeltaAddEncoder(unk){notimpf(__func__);}
unk QDECL GHL_GetCurrentPlayer(unk){notimpf(__func__);}
int QDECL GHL_CanSkipPlayer(hledict_t *playerent)
{
	return false;
//	notimpf(__func__);
}
unk QDECL GHL_DeltaFindField(unk){notimpf(__func__);}
unk QDECL GHL_DeltaSetFieldByIndex(unk){notimpf(__func__);}
unk QDECL GHL_DeltaUnsetFieldByIndex(unk){notimpf(__func__);}
unk QDECL GHL_SetGroupMask(unk){notimpf(__func__);}
unk QDECL GHL_CreateInstancedBaseline(unk){notimpf(__func__);}
unk QDECL GHL_Cvar_DirectSet(unk){notimpf(__func__);}
unk QDECL GHL_ForceUnmodified(unk){notimpf(__func__);}
unk QDECL GHL_GetPlayerStats(unk){notimpf(__func__);}
unk QDECL GHL_AddServerCommand(unk){notimpf(__func__);}
unk QDECL GHL_Voice_GetClientListening(unk){notimpf(__func__);}
qboolean QDECL GHL_Voice_SetClientListening(int listener, int sender, int shouldlisten)
{
	return false;
}
unk QDECL GHL_GetPlayerAuthId(unk){notimpf(__func__);}
unk QDECL GHL_SequenceGet(unk){notimpf(__func__);}
unk QDECL GHL_SequencePickSentence(unk){notimpf(__func__);}
unk QDECL GHL_GetFileSize(unk){notimpf(__func__);}
unk QDECL GHL_GetApproxWavePlayLen(unk){notimpf(__func__);}
unk QDECL GHL_IsCareerMatch(unk){notimpf(__func__);}
unk QDECL GHL_GetLocalizedStringLength(unk){notimpf(__func__);}
unk QDECL GHL_RegisterTutorMessageShown(unk){notimpf(__func__);}
unk QDECL GHL_GetTimesTutorMessageShown(unk){notimpf(__func__);}
unk QDECL GHL_ProcessTutorMessageDecayBuffer(unk){notimpf(__func__);}
unk QDECL GHL_ConstructTutorMessageDecayBuffer(unk){notimpf(__func__);}
unk QDECL GHL_ResetTutorMessageDecayData(unk){notimpf(__func__);}
unk QDECL GHL_QueryClientCvarValue(unk){notimpf(__func__);}
unk QDECL GHL_QueryClientCvarValue2(unk){notimpf(__func__);}




//====================================================================================================





SVHL_Builtins_t SVHL_Builtins =
{
	GHL_PrecacheModel,
	GHL_PrecacheSound,
	GHL_SetModel,
	GHL_ModelIndex,
	GHL_ModelFrames,
	GHL_SetSize,
	GHL_ChangeLevel,
	GHL_GetSpawnParms,
	GHL_SaveSpawnParms,
	GHL_VecToYaw,
	GHL_VecToAngles,
	GHL_MoveToOrigin,
	GHL_ChangeYaw,
	GHL_ChangePitch,
	GHL_FindEntityByString,
	GHL_GetEntityIllum,
	GHL_FindEntityInSphere,
	GHL_FindClientInPVS,
	GHL_EntitiesInPVS,
	GHL_MakeVectors,
	GHL_AngleVectors,
	GHL_CreateEntity,
	GHL_RemoveEntity,
	GHL_CreateNamedEntity,
	GHL_MakeStatic,
	GHL_EntIsOnFloor,
	GHL_DropToFloor,
	GHL_WalkMove,
	GHL_SetOrigin,
	GHL_EmitSound,
	GHL_EmitAmbientSound,
	GHL_TraceLine,
	GHL_TraceToss,
	GHL_TraceMonsterHull,
	GHL_TraceHull,
	GHL_TraceModel,
	GHL_TraceTexture,
	GHL_TraceSphere,
	GHL_GetAimVector,
	GHL_ServerCommand,
	GHL_ServerExecute,
	GHL_ClientCommand,
	GHL_ParticleEffect,
	GHL_LightStyle,
	GHL_DecalIndex,
	GHL_PointContents,
	GHL_MessageBegin,
	GHL_MessageEnd,
	GHL_WriteByte,
	GHL_WriteChar,
	GHL_WriteShort,
	GHL_WriteLong,
	GHL_WriteAngle,
	GHL_WriteCoord,
	GHL_WriteString,
	GHL_WriteEntity,
	GHL_CVarRegister,
	GHL_CVarGetFloat,
	GHL_CVarGetString,
	GHL_CVarSetFloat,
	GHL_CVarSetString,
	GHL_AlertMessage,
	GHL_EngineFprintf,
	GHL_PvAllocEntPrivateData,
	GHL_PvEntPrivateData,
	GHL_FreeEntPrivateData,
	GHL_SzFromIndex,
	GHL_AllocString,
	GHL_GetVarsOfEnt,
	GHL_PEntityOfEntOffset,
	GHL_EntOffsetOfPEntity,
	GHL_IndexOfEdict,
	GHL_PEntityOfEntIndex,
	GHL_FindEntityByVars,
	GHL_GetModelPtr,
	GHL_RegUserMsg,
	GHL_AnimationAutomove,
	GHL_GetBonePosition,
	GHL_FunctionFromName,
	GHL_NameForFunction,
	GHL_ClientPrintf,
	GHL_ServerPrint,
	GHL_Cmd_Args,
	GHL_Cmd_Argv,
	GHL_Cmd_Argc,
	GHL_GetAttachment,
	GHL_CRC32_Init,
	GHL_CRC32_ProcessBuffer,
	GHL_CRC32_ProcessByte,
	GHL_CRC32_Final,
	GHL_RandomLong,
	GHL_RandomFloat,
	GHL_SetView,
	GHL_Time,
	GHL_CrosshairAngle,
	GHL_LoadFileForMe,
	GHL_FreeFile,
	GHL_EndSection,
	GHL_CompareFileTime,
	GHL_GetGameDir,
	GHL_Cvar_RegisterVariable,
	GHL_FadeClientVolume,
	GHL_SetClientMaxspeed,
	GHL_CreateFakeClient,
	GHL_RunPlayerMove,
	GHL_NumberOfEntities,
	GHL_GetInfoKeyBuffer,
	GHL_InfoKeyValue,
	GHL_SetKeyValue,
	GHL_SetClientKeyValue,
	GHL_IsMapValid,
	GHL_StaticDecal,
	GHL_PrecacheGeneric,
	GHL_GetPlayerUserId,
	GHL_BuildSoundMsg,
	GHL_IsDedicatedServer,
#if HALFLIFE_API_VERSION > 138
	GHL_CVarGetPointer,
	GHL_GetPlayerWONId,
	GHL_Info_RemoveKey,
	GHL_GetPhysicsKeyValue,
	GHL_SetPhysicsKeyValue,
	GHL_GetPhysicsInfoString,
	GHL_PrecacheEvent,
	GHL_PlaybackEvent,
	GHL_SetFatPVS,
	GHL_SetFatPAS,
	GHL_CheckVisibility,
	GHL_DeltaSetField,
	GHL_DeltaUnsetField,
	GHL_DeltaAddEncoder,
	GHL_GetCurrentPlayer,
	GHL_CanSkipPlayer,
	GHL_DeltaFindField,
	GHL_DeltaSetFieldByIndex,
	GHL_DeltaUnsetFieldByIndex,
	GHL_SetGroupMask,
	GHL_CreateInstancedBaseline,
	GHL_Cvar_DirectSet,
	GHL_ForceUnmodified,
	GHL_GetPlayerStats,
	GHL_AddServerCommand,
	GHL_Voice_GetClientListening,
	GHL_Voice_SetClientListening,
	GHL_GetPlayerAuthId,
	GHL_SequenceGet,
	GHL_SequencePickSentence,
	GHL_GetFileSize,
	GHL_GetApproxWavePlayLen,
	GHL_IsCareerMatch,
	GHL_GetLocalizedStringLength,
	GHL_RegisterTutorMessageShown,
	GHL_GetTimesTutorMessageShown,
	GHL_ProcessTutorMessageDecayBuffer,
	GHL_ConstructTutorMessageDecayBuffer,
	GHL_ResetTutorMessageDecayData,
	GHL_QueryClientCvarValue,
	GHL_QueryClientCvarValue2, 
#endif

	0xdeadbeef
};

void SV_ReadLibListDotGam(void)
{
	char key[1024];
	char value[1024];
	char *file;
	char *s;

	Info_SetValueForStarKey(svs.info, "*gamedll", "", sizeof(svs.info));
	Info_SetValueForStarKey(svs.info, "*cldll", "", sizeof(svs.info));

	file = COM_LoadTempFile("liblist.gam");
	if (!file)
		return;

	Info_SetValueForStarKey(svs.info, "*cldll", "1", sizeof(svs.info));

	while ((file = COM_ParseOut(file, key, sizeof(key))))
	{
		file = COM_ParseOut(file, value, sizeof(value));

		while((s = strchr(value, '\\')))
			*s = '/';

		if (!strcmp(key, "gamedll"
#ifdef __linux__
			"_linux"
#endif
			))
			Info_SetValueForStarKey(svs.info, "*gamedll", value, sizeof(svs.info));
		if (!strcmp(key, "cldll"))
			Info_SetValueForStarKey(svs.info, "*cldll", atoi(value)?"1":"", sizeof(svs.info));
	}
}

int SVHL_InitGame(void)
{
	char *gamedll;
	char *path;
	char fullname[MAX_OSPATH];
	void (WINAPI *GiveFnptrsToDll) (funcs, globals);
	int (QDECL *GetEntityAPI)(SVHL_GameFuncs_t *pFunctionTable, int apivers);

	dllfunction_t hlgamefuncs[] =
	{
		{(void**)&GiveFnptrsToDll, "GiveFnptrsToDll"},
		{(void**)&GetEntityAPI, 	"GetEntityAPI"},
		{NULL, NULL}
	};

	if (sizeof(long) != sizeof(void*))
	{
		Con_Printf("sizeof(long)!=sizeof(ptr): Cannot run halflife gamecode on this platform\n");
		return 0;
	}

	SV_ReadLibListDotGam();

	if (hlgamecode)
	{
		SVHL_Edict = Hunk_Alloc(sizeof(*SVHL_Edict) * MAX_HL_EDICTS);
		SVHL_Globals.maxentities = MAX_HL_EDICTS;	//I think this is correct
		return 1;
	}

	hlgamecode = NULL;//Sys_LoadLibrary("C:/Incoming/d/Half-Life/sdks/hlsdk-2.3-p3/hlsdk-2.3-p3/multiplayer/dlls/debugmp/mp.dll", hlgamefuncs);
	if (!hlgamecode)
	{
		gamedll = Info_ValueForKey(svs.info, "*gamedll");
		path = NULL;
		while((path = COM_NextPath (path)))
		{
			if (!path)
				return 0;		// couldn't find one anywhere
			snprintf (fullname, sizeof(fullname), "%s/%s", path, gamedll);
			hlgamecode = Sys_LoadLibrary(fullname, hlgamefuncs);
			if (hlgamecode)
				break;
		}
	}

	if (!hlgamecode)
		return 0;

	SVHL_Edict = Hunk_Alloc(sizeof(*SVHL_Edict) * MAX_HL_EDICTS);
	SVHL_Globals.maxentities = MAX_HL_EDICTS;	//I think this is correct
	GiveFnptrsToDll(&SVHL_Builtins, &SVHL_Globals);

	if (!GetEntityAPI(&SVHL_GameFuncs, HALFLIFE_API_VERSION))
	{
		Con_Printf(CON_ERROR "Error: %s is incompatible (FTE is %i)\n", fullname, HALFLIFE_API_VERSION);
		if (GetEntityAPI(&SVHL_GameFuncs, 138))
			Con_Printf(CON_ERROR "mod is 138\n");
		Sys_CloseLibrary(hlgamecode);
		hlgamecode = NULL;
		return 0;
	}

	SVHL_GameFuncs.GameDLLInit();
	return 1;
}

void SVHL_SaveLevelCache(char *filename)
{

}

void SVHL_SetupGame(void)
{
	lastusermessage = 255;
	//called on new map
}

void SVHL_SpawnEntities(char *entstring)
{
	char key[256];
	char value[1024];
	char classname[1024];
	hlfielddef_t fdef;
	hledict_t *ed, *world;
	extern cvar_t	coop;	//who'd have thought it, eh?
	char *ts;
	int i;

	//initialise globals
	SVHL_Globals.stringbase = "";
	SVHL_Globals.maxclients = MAX_CLIENTS;
	SVHL_Globals.deathmatch = deathmatch.value;
	SVHL_Globals.coop = coop.value;
	SVHL_Globals.serverflags = 0;
	SVHL_Globals.mapname = GHL_AllocString(sv.name);

	SVHL_NumActiveEnts = 0;


	//touch world.
	world = GHL_CreateNamedEntity(GHL_AllocString("worldspawn"));
	world->v.solid = SOLID_BSP;
	GHL_SetModel(world, sv.modelname);

	//spawn player ents
	sv.allocated_client_slots = 0;
	for (i = 0; i < SVHL_Globals.maxclients; i++)
	{
		sv.allocated_client_slots++;
		ed = GHL_CreateNamedEntity(GHL_AllocString("player"));
	}
	for (i = 0; i < sv.allocated_client_slots; i++)
	{
		SVHL_Edict[i].isfree = true;
	}
	sv.allocated_client_slots = i;

	//precache the inline models (and touch them).
	sv.strings.model_precache[0] = "";
	sv.strings.model_precache[1] = sv.modelname;	//the qvm doesn't have access to this array
	for (i=1 ; i<sv.world.worldmodel->numsubmodels ; i++)
	{
		sv.strings.model_precache[1+i] = localmodels[i];
		sv.models[i+1] = Mod_ForName (localmodels[i], false);
	}

	while (entstring)
	{
		entstring = COM_ParseOut(entstring, key, sizeof(key));
		if (strcmp(key, "{"))
			break;

		*classname = 0;

		ts = entstring;
		while (ts)
		{
			ts = COM_ParseOut(ts, key, sizeof(key));
			if (!strcmp(key, "}"))
				break;
			ts = COM_ParseOut(ts, value, sizeof(value));

			if (!strcmp(key, "classname"))
			{
				memcpy(classname, value, strlen(value)+1);
				break;
			}
		}

		if (world)
		{
			if (strcmp(classname, "worldspawn"))
				SV_Error("first entity is not worldspawn");
			ed = world;
			world = NULL;
		}
		else
			ed = GHL_CreateNamedEntity(GHL_AllocString(classname));

		while (entstring)
		{
			entstring = COM_ParseOut(entstring, key, sizeof(key));
			if (!strcmp(key, "}"))
				break;
			entstring = COM_ParseOut(entstring, value, sizeof(value));

			if (*key == '_')
				continue;

			if (!strcmp(key, "classname"))
				memcpy(classname, value, strlen(value)+1);

			fdef.handled = false;
			if (!*classname)
				fdef.classname = NULL;
			else
				fdef.classname = classname;
			fdef.key = key;
			fdef.value = value;
			SVHL_GameFuncs.DispatchKeyValue(ed, &fdef);
			if (!fdef.handled)
			{
				if (!strcmp(key, "angle"))
				{
					float a = atof(value);
					sprintf(value, "%f %f %f", 0.0f, a, 0.0f);
					strcpy(key, "angles");
					SVHL_GameFuncs.DispatchKeyValue(ed, &fdef);
				}
				if (!fdef.handled)
					Con_Printf("Bad field on %s, %s\n", classname, key);
			}
		}
		SVHL_GameFuncs.DispatchSpawn(ed);
	}

	SVHL_GameFuncs.ServerActivate(SVHL_Edict, SVHL_NumActiveEnts, sv.allocated_client_slots);
}

void SVHL_ShutdownGame(void)
{
	SVHL_FreeCvars();

	//gametype changed, or server shutdown
	Sys_CloseLibrary(hlgamecode);
	hlgamecode = NULL;

	SVHL_Edict = NULL;
	memset(&SVHL_Globals, 0, sizeof(SVHL_Globals));
	memset(&SVHL_GameFuncs, 0, sizeof(SVHL_GameFuncs));
	memset(&SVHL_GameFuncsEx, 0, sizeof(SVHL_GameFuncsEx));
}

qboolean HLSV_ClientCommand(client_t *client)
{
	hledict_t *ed = &SVHL_Edict[client - svs.clients + 1];
	if (!hlgamecode)
		return false;
	SVHL_GameFuncs.ClientCommand(ed);
	return true;
}

qboolean SVHL_ClientConnect(client_t *client, netadr_t adr, char rejectmessage[128])
{
	char ipadr[256];
	NET_AdrToString(ipadr, sizeof(ipadr), adr);
	strcpy(rejectmessage, "Rejected by gamecode");

	if (!SVHL_GameFuncs.ClientConnect(&SVHL_Edict[client-svs.clients+1], client->name, ipadr, rejectmessage))
		return false;

	return true;
}

void SVHL_BuildStats(client_t *client, int *si, float *sf, char **ss)
{
	hledict_t *ed = &SVHL_Edict[client - svs.clients + 1];

	si[STAT_HEALTH] = ed->v.health;
	si[STAT_VIEWHEIGHT] = ed->v.view_ofs[2];
	si[STAT_WEAPON] = SV_ModelIndex(SVHL_Globals.stringbase+ed->v.vmodelindex);
	si[STAT_ITEMS] = ed->v.weapons;
}

void SVHL_PutClientInServer(client_t *client)
{
	hledict_t *ed = &SVHL_Edict[client - svs.clients + 1];
	ed->isfree = false;
	SVHL_GameFuncs.ClientPutInServer(&SVHL_Edict[client-svs.clients+1]);
}

void SVHL_DropClient(client_t *drop)
{
	hledict_t *ed = &SVHL_Edict[drop - svs.clients + 1];
	SVHL_GameFuncs.ClientDisconnect(&SVHL_Edict[drop-svs.clients+1]);
	ed->isfree = true;
}

void SVHL_RunCmdR(hledict_t *ed, usercmd_t *ucmd)
{
	int i;
	hledict_t *other;
extern cvar_t temp1;
extern vec3_t	player_mins;
extern vec3_t	player_maxs;

	// chop up very long commands
	if (ucmd->msec > 50)
	{
		usercmd_t cmd = *ucmd;

		cmd.msec = ucmd->msec/2;
		SVHL_RunCmdR (ed, &cmd);
		cmd.msec = ucmd->msec/2 + (ucmd->msec&1);	//give them back their msec.
		cmd.impulse = 0;
		SVHL_RunCmdR (ed, &cmd);
		return;
	}

	host_frametime = ucmd->msec * 0.001;
	host_frametime *= sv.gamespeed;
	if (host_frametime > 0.1)
		host_frametime = 0.1;

	pmove.cmd = *ucmd;
	pmove.pm_type = temp1.value;//PM_NORMAL;//FLY;
	pmove.numphysent = 1;
	pmove.physents[0].model = sv.world.worldmodel;
	pmove.physents[0].info = 0;
	pmove.skipent = -1;
	pmove.onladder = false;

	if (ed->v.flags & (1<<24))
	{
		pmove.cmd.forwardmove = 0;
		pmove.cmd.sidemove = 0;
		pmove.cmd.upmove = 0;
	}

	{
		hledict_t *list[256];
		int count;
		physent_t *pe;
		vec3_t	pmove_mins, pmove_maxs;

		for (i = 0; i < 3; i++)
		{
			pmove_mins[i] = pmove.origin[i] - 256;
			pmove_maxs[i] = pmove.origin[i] + 256;
		}

		count = SVHL_AreaEdicts(pmove_mins, pmove_maxs, list, sizeof(list)/sizeof(list[0]));
		for (i = 0; i < count; i++)
		{
			other = list[i];
			if (other == ed)
				continue;
			if (other->v.owner == ed)
				continue;
			if (other->v.flags & (1<<23))	//has monsterclip flag set, so ignore it
				continue;

			pe = &pmove.physents[pmove.numphysent];
			switch(other->v.skin)
			{
			case Q1CONTENTS_EMPTY:
				pe->forcecontentsmask = FTECONTENTS_EMPTY;
				break;
			case Q1CONTENTS_SOLID:
				pe->forcecontentsmask = FTECONTENTS_SOLID;
				break;
			case Q1CONTENTS_WATER:
				pe->forcecontentsmask = FTECONTENTS_WATER;
				break;
			case Q1CONTENTS_SLIME:
				pe->forcecontentsmask = FTECONTENTS_SLIME;
				break;
			case Q1CONTENTS_LAVA:
				pe->forcecontentsmask = FTECONTENTS_LAVA;
				break;
			case Q1CONTENTS_SKY:
				pe->forcecontentsmask = FTECONTENTS_SKY;
				break;
			case -16:
				pe->forcecontentsmask = FTECONTENTS_LADDER;
				break;
			default:
				pe->forcecontentsmask = 0;
				break;
			}

			if (other->v.solid == SOLID_NOT || other->v.solid == SOLID_TRIGGER)
			{
				if (!pe->forcecontentsmask)
					continue;
				pe->nonsolid = true;
			}
			else
				pe->nonsolid = false;

			if (other->v.modelindex)
			{
				pe->model = sv.models[other->v.modelindex];
				if (pe->model && pe->model->type != mod_brush)
					pe->model = NULL;
			}
			else
				pe->model = NULL;
			pmove.numphysent++;
			pe->info = other - SVHL_Edict;
			VectorCopy(other->v.origin, pe->origin);
			VectorCopy(other->v.mins, pe->mins);
			VectorCopy(other->v.maxs, pe->maxs);
			VectorCopy(other->v.angles, pe->angles);
		}
	}

	VectorCopy(ed->v.mins, player_mins);
	VectorCopy(ed->v.maxs, player_maxs);

	VectorCopy(ed->v.origin, pmove.origin);
	VectorCopy(ed->v.velocity, pmove.velocity);
	if (ed->v.flags & (1<<22))
	{
		VectorCopy(ed->v.basevelocity, pmove.basevelocity);
	}
	else
		VectorClear(pmove.basevelocity);

	PM_PlayerMove(sv.gamespeed);

	VectorCopy(pmove.origin, ed->v.origin);
	VectorCopy(pmove.velocity, ed->v.velocity);

	if (pmove.onground)
	{
		ed->v.flags |= FL_ONGROUND;
		ed->v.groundentity = &SVHL_Edict[pmove.physents[pmove.groundent].info];
	}
	else
		ed->v.flags &= ~FL_ONGROUND;

	for (i = 0; i < pmove.numtouch; i++)
	{
		other = &SVHL_Edict[pmove.physents[pmove.touchindex[i]].info];
		SVHL_GameFuncs.DispatchTouch(other, ed);
	}

	SVHL_LinkEdict(ed, true);
}

void SVHL_RunCmd(client_t *cl, usercmd_t *ucmd)
{
	hledict_t *ed = &SVHL_Edict[cl - svs.clients + 1];

#if HALFLIFE_API_VERSION >= 140
	ed->v.buttons = ucmd->buttons;
#else
	//assume they're not running halflife cgame
	ed->v.buttons = 0;

	if (ucmd->buttons & 1)
		ed->v.buttons |= (1<<0);	//shoot
	if (ucmd->buttons & 2)
		ed->v.buttons |= (1<<1);	//jump
	if (ucmd->buttons & 8)
		ed->v.buttons |= (1<<2);	//duck
	if (ucmd->forwardmove > 0)
		ed->v.buttons |= (1<<3);	//forward
	if (ucmd->forwardmove < 0)
		ed->v.buttons |= (1<<4);	//back
	if (ucmd->buttons & 4)
		ed->v.buttons |= (1<<5);	//use
	//ed->v.buttons |= (1<<6);	//cancel
	//ed->v.buttons |= (1<<7);	//turn left
	//ed->v.buttons |= (1<<8);	//turn right
	if (ucmd->sidemove > 0)
		ed->v.buttons |= (1<<9);	//move left
	if (ucmd->sidemove < 0)
		ed->v.buttons |= (1<<10);	//move right
	//ed->v.buttons |= (1<<11);	//shoot2
	//ed->v.buttons |= (1<<12);	//run
	if (ucmd->buttons & 16)
		ed->v.buttons |= (1<<13);	//reload
	//ed->v.buttons |= (1<<14);	//alt1
	//ed->v.buttons |= (1<<15);	//alt2
#endif

	if (ucmd->impulse)
		ed->v.impulse = ucmd->impulse;
	ed->v.v_angle[0] = SHORT2ANGLE(ucmd->angles[0]);
	ed->v.v_angle[1] = SHORT2ANGLE(ucmd->angles[1]);
	ed->v.v_angle[2] = SHORT2ANGLE(ucmd->angles[2]);

	ed->v.angles[0] = 0;
	ed->v.angles[1] = SHORT2ANGLE(ucmd->angles[1]);
	ed->v.angles[2] = SHORT2ANGLE(ucmd->angles[2]);

	SVHL_GameFuncs.PlayerPreThink(ed);
	SVHL_RunCmdR(ed, ucmd);

	if (ed->v.nextthink && ed->v.nextthink < sv.time)
	{
		ed->v.nextthink = 0;
		SVHL_GameFuncs.DispatchThink(ed);
	}

	SVHL_GameFuncs.PlayerPostThink(ed);
}


void SVHL_RunPlayerCommand(client_t *cl, usercmd_t *oldest, usercmd_t *oldcmd, usercmd_t *newcmd)
{
	hledict_t *e = &SVHL_Edict[cl - svs.clients + 1];

	SVHL_Globals.time = sv.time;
	if (net_drop < 20)
	{
		while (net_drop > 2)
		{
			SVHL_RunCmd (cl, &cl->lastcmd);
			net_drop--;
		}
		if (net_drop > 1)
			SVHL_RunCmd (cl, oldest);
		if (net_drop > 0)
			SVHL_RunCmd (cl, oldcmd);
	}
	SVHL_RunCmd (cl, newcmd);
}

void SVHL_Snapshot_Build(client_t *client, packet_entities_t *pack, qbyte *pvs, edict_t *clent, qboolean ignorepvs)
{
	hledict_t *e;
	entity_state_t *s;
	int i;

	pack->servertime = sv.time;
	pack->num_entities = 0;

	for (i = 1; i < MAX_HL_EDICTS; i++)
	{
		e = &SVHL_Edict[i];
		if (!e)
			break;
		if (e->isfree)
			continue;

		if (!e->v.modelindex || !e->v.model)
			continue;
		if (e->v.effects & 128)
			continue;

		if (pack->num_entities == pack->max_entities)
			break;

		s = &pack->entities[pack->num_entities++];

		s->number = i;
		s->modelindex = e->v.modelindex;
		s->frame = e->v.sequence1;
		s->effects = e->v.effects;
		s->skinnum = e->v.skin;
		VectorCopy(e->v.angles, s->angles);
		VectorCopy(e->v.origin, s->origin);
	}
}

qbyte *SVHL_Snapshot_SetupPVS(client_t *client, qbyte *pvs, unsigned int pvsbufsize)
{
	vec3_t org;

	if (client->hledict)
	{
		VectorAdd (client->hledict->v.origin, client->hledict->v.view_ofs, org);
		sv.world.worldmodel->funcs.FatPVS(sv.world.worldmodel, org, pvs, pvsbufsize, false);
	}

	return pvs;
}

#endif
