// this line must stay at top so the whole PCH thing works...
#include "cg_headers.h"
//#include "../ui/ui_shared.h"

//#include "cg_local.h"
#include "cg_media.h"
#include "FxScheduler.h"

#include "..\client\vmachine.h"
#include "cg_lights.h"

#ifdef _IMMERSION
#include "../ff/ff.h"
#endif // _IMMERSION
#include "../qcommon/sstring.h"
//NOTENOTE: Be sure to change the mirrored code in g_shared.h
typedef	map< sstring_t, unsigned char, less<sstring_t>, allocator< unsigned char >  >	namePrecache_m;
extern namePrecache_m	*as_preCacheMap;
extern void CG_RegisterNPCCustomSounds( clientInfo_t *ci );
extern qboolean G_AddSexToMunroString ( char *string, qboolean qDoBoth );
//extern void CG_RegisterNPCEffects( team_t team );
extern int G_ParseAnimFileSet( const char *skeletonName, const char *modelName=0);
extern void CG_DrawDataPadInventorySelect( void );

void CG_Init( int serverCommandSequence );
qboolean CG_ConsoleCommand( void );
void CG_Shutdown( void );
int CG_GetCameraPos( vec3_t camerapos );
int CG_GetCameraAng( vec3_t cameraang );
void UseItem(int itemNum);
const char *CG_DisplayBoxedText(int iBoxX, int iBoxY, int iBoxWidth, int iBoxHeight, 
								const char *psText, int iFontHandle, float fScale,
								const vec4_t v4Color);

#define NUM_CHUNKS		6
/*
Ghoul2 Insert Start
*/

void CG_ResizeG2Bolt(boltInfo_v *bolt, int newCount);
void CG_ResizeG2Surface(surfaceInfo_v *surface, int newCount);
void CG_ResizeG2Bone(boneInfo_v *bone, int newCount);
void CG_ResizeG2(CGhoul2Info_v *ghoul2, int newCount);
void CG_ResizeG2TempBone(mdxaBone_v *tempBone, int newCount);
/*
Ghoul2 Insert End
*/


#ifdef _XBOX
extern bool CL_ExtendSelectTime(void);
#endif



void CG_LoadHudMenu(void);
int inv_icons[INV_MAX];
char *inv_names[] =
{
"ELECTROBINOCULARS",
"BACTA CANISTER",
"SEEKER",
"LIGHT AMP GOGGLES",
"ASSAULT SENTRY",
"GOODIE KEY",
"GOODIE KEY",
"GOODIE KEY",
"GOODIE KEY",
"GOODIE KEY",
"SECURITY KEY",
"SECURITY KEY",
"SECURITY KEY",
"SECURITY KEY",
"SECURITY KEY",
};

int	force_icons[NUM_FORCE_POWERS];


void CG_DrawDataPadHUD( centity_t *cent );
void CG_DrawDataPadObjectives(const centity_t *cent );
void CG_DrawDataPadIconBackground(const int backgroundType);
void CG_DrawDataPadWeaponSelect( void );
void CG_DrawDataPadForceSelect( void );

/*
================
vmMain

This is the only way control passes into the cgame module.
This must be the very first function compiled into the .q3vm file
================
*/
int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7 ) {
	centity_t		*cent;

	switch ( command ) {
	case CG_INIT:
		CG_Init( arg0 );
		return 0;
	case CG_SHUTDOWN:
		CG_Shutdown();
		return 0;
	case CG_CONSOLE_COMMAND:
		return CG_ConsoleCommand();
	case CG_DRAW_ACTIVE_FRAME:
		CG_DrawActiveFrame( arg0, (stereoFrame_t) arg1 );
		return 0;
	case CG_CROSSHAIR_PLAYER:
		return CG_CrosshairPlayer();
	case CG_CAMERA_POS:
		return CG_GetCameraPos( (float*)arg0);
	case CG_CAMERA_ANG:
		return CG_GetCameraAng( (float*)arg0);
/*
Ghoul2 Insert Start
*/
	case CG_RESIZE_G2:
		CG_ResizeG2((CGhoul2Info_v *)arg0, arg1);
		return 0;
	case CG_RESIZE_G2_BOLT:
		CG_ResizeG2Bolt((boltInfo_v *)arg0, arg1);
		return 0;
	case CG_RESIZE_G2_BONE:
		CG_ResizeG2Bone((boneInfo_v *)arg0, arg1);
		return 0;
	case CG_RESIZE_G2_SURFACE:
		CG_ResizeG2Surface((surfaceInfo_v *)arg0, arg1);
		return 0;
	case CG_RESIZE_G2_TEMPBONE:
		CG_ResizeG2TempBone((mdxaBone_v *)arg0, arg1);
		return 0;

/*
Ghoul2 Insert End
*/
	case CG_DRAW_DATAPAD_HUD:
		if (cg.snap)
		{
			cent = &cg_entities[cg.snap->ps.clientNum];
			CG_DrawDataPadHUD(cent);
		}
		return 0;

	case CG_DRAW_DATAPAD_OBJECTIVES:
		if (cg.snap)
		{
			cent = &cg_entities[cg.snap->ps.clientNum];
			CG_DrawDataPadObjectives(cent);
		}
		return 0;

	case CG_DRAW_DATAPAD_WEAPONS:
		if (cg.snap)
		{
			CG_DrawDataPadIconBackground(ICON_WEAPONS);
			CG_DrawDataPadWeaponSelect();
		}
		return 0;
	case CG_DRAW_DATAPAD_INVENTORY:
		if (cg.snap)
		{
			CG_DrawDataPadIconBackground(ICON_INVENTORY);
			CG_DrawDataPadInventorySelect();
		}
		return 0;
	case CG_DRAW_DATAPAD_FORCEPOWERS:
		if (cg.snap)
		{
			CG_DrawDataPadIconBackground(ICON_FORCE);
			CG_DrawDataPadForceSelect();
		}
		return 0;
	}
	return -1;
}

/*
Ghoul2 Insert Start
*/

void CG_ResizeG2Bolt(boltInfo_v *bolt, int newCount)
{
	bolt->resize(newCount);
}

void CG_ResizeG2Surface(surfaceInfo_v *surface, int newCount)
{
	surface->resize(newCount);
}

void CG_ResizeG2Bone(boneInfo_v *bone, int newCount)
{
	bone->resize(newCount);
}

void CG_ResizeG2(CGhoul2Info_v *ghoul2, int newCount)
{
	ghoul2->resize(newCount);
}

void CG_ResizeG2TempBone(mdxaBone_v *tempBone, int newCount)
{
	tempBone->resize(newCount);
}
/*
Ghoul2 Insert End
*/

cg_t				cg;
cgs_t				cgs;
centity_t			cg_entities[MAX_GENTITIES];

centity_t			*cg_permanents[MAX_GENTITIES];
int					cg_numpermanents = 0;

weaponInfo_t		cg_weapons[MAX_WEAPONS];
itemInfo_t			cg_items[MAX_ITEMS];

typedef struct {
	qboolean		registered;		// Has the player picked it up
	qboolean		active;			// Is it the chosen inventory item
	int				count;			// Count of items.
	char			description[128];
} inventoryInfo_t;

inventoryInfo_t		cg_inventory[INV_MAX];

vmCvar_t	cg_runpitch;
vmCvar_t	cg_runroll;
vmCvar_t	cg_bobup;
vmCvar_t	cg_bobpitch;
vmCvar_t	cg_bobroll;
vmCvar_t	cg_shadows;
vmCvar_t	cg_renderToTextureFX;
vmCvar_t	cg_shadowCullDistance;
vmCvar_t	cg_footsteps;
vmCvar_t	cg_saberEntMarks;
vmCvar_t	cg_paused;
vmCvar_t	cg_drawTimer;
vmCvar_t	cg_drawFPS;
vmCvar_t	cg_drawSnapshot;
vmCvar_t	cg_drawAmmoWarning;
vmCvar_t	cg_drawCrosshair;
vmCvar_t	cg_crosshairIdentifyTarget;
//vmCvar_t	cg_dynamicCrosshair;
vmCvar_t	cg_crosshairForceHint;
vmCvar_t	cg_crosshairX;
vmCvar_t	cg_crosshairY;
vmCvar_t	cg_crosshairSize;
vmCvar_t	cg_draw2D;
vmCvar_t	cg_drawStatus;
vmCvar_t	cg_drawHUD;
vmCvar_t	cg_debugAnim;
#ifndef FINAL_BUILD
vmCvar_t	cg_debugAnimTarget;
vmCvar_t	cg_gun_frame;
#endif
vmCvar_t	cg_debugSaber;
vmCvar_t	cg_debugEvents;
vmCvar_t	cg_errorDecay;
vmCvar_t	cg_addMarks;
vmCvar_t	cg_drawGun;
vmCvar_t	cg_autoswitch;
vmCvar_t	cg_simpleItems;
vmCvar_t	cg_fov;
vmCvar_t	cg_endcredits;
vmCvar_t	cg_updatedDataPadForcePower1;
vmCvar_t	cg_updatedDataPadForcePower2;
vmCvar_t	cg_updatedDataPadForcePower3;
vmCvar_t	cg_updatedDataPadObjective;
vmCvar_t	cg_drawBreath;
vmCvar_t	cg_roffdebug;
#ifndef FINAL_BUILD
vmCvar_t	cg_roffval1;
vmCvar_t	cg_roffval2;
vmCvar_t	cg_roffval3;
vmCvar_t	cg_roffval4;
#endif
vmCvar_t	cg_thirdPerson;
vmCvar_t	cg_thirdPersonRange;
vmCvar_t	cg_thirdPersonMaxRange;
vmCvar_t	cg_thirdPersonAngle;
vmCvar_t	cg_thirdPersonPitchOffset;
vmCvar_t	cg_thirdPersonVertOffset;
vmCvar_t	cg_thirdPersonCameraDamp;
vmCvar_t	cg_thirdPersonTargetDamp;
vmCvar_t	cg_gunAutoFirst;

vmCvar_t	cg_thirdPersonAlpha;
vmCvar_t	cg_thirdPersonAutoAlpha;
vmCvar_t	cg_thirdPersonHorzOffset;

vmCvar_t	cg_stereoSeparation;
vmCvar_t 	cg_developer;
vmCvar_t 	cg_timescale;
vmCvar_t	cg_skippingcin;

vmCvar_t	cg_pano;
vmCvar_t	cg_panoNumShots;

vmCvar_t	fx_freeze;
vmCvar_t	fx_debug;

vmCvar_t	cg_missionInfoFlashTime;
vmCvar_t	cg_hudFiles;

vmCvar_t	cg_neverHearThatDumbBeepingSoundAgain;

vmCvar_t	cg_VariantSoundCap;	// 0 = no capping, else cap to (n) max (typically just 1, but allows more)
vmCvar_t	cg_turnAnims;
vmCvar_t	cg_motionBoneComp;
vmCvar_t	cg_distributeMBCorrection;
vmCvar_t	cg_reliableAnimEvents;

vmCvar_t	cg_smoothPlayerPos;
vmCvar_t	cg_smoothPlayerPlat;
vmCvar_t	cg_smoothPlayerPlatAccel;
vmCvar_t	cg_g2Marks;
vmCvar_t	fx_expensivePhysics;
vmCvar_t	cg_debugHealthBars;

typedef struct {
	vmCvar_t	*vmCvar;
	char		*cvarName;
	char		*defaultString;
	int			cvarFlags;
} cvarTable_t;

static cvarTable_t cvarTable[] = {
	{ &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE },
	{ &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE },
	{ &cg_fov, "cg_fov", "80", 0 },//must be 80
	{ &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE  },
	{ &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE  },
	{ &cg_renderToTextureFX, "cg_renderToTextureFX", "1", CVAR_ARCHIVE  },
	{ &cg_shadowCullDistance, "r_shadowRange", "1000", CVAR_ARCHIVE },
	{ &cg_footsteps, "cg_footsteps", "3", CVAR_ARCHIVE  },//1 = sounds, 2 = sounds & effects, 3 = sounds, effects & marks, 4 = always
	{ &cg_saberEntMarks, "cg_saberEntMarks", "1", CVAR_ARCHIVE  },

	{ &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE  },
	{ &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE  },
	{ &cg_drawHUD, "cg_drawHUD", "1", 0  },
	{ &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE  },
	{ &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE  },
	{ &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE  },
	{ &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE  },
	{ &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE },
//	{ &cg_dynamicCrosshair, "cg_dynamicCrosshair", "1", CVAR_ARCHIVE },
	// NOTE : I also create this in UI_Init()
	{ &cg_crosshairIdentifyTarget, "cg_crosshairIdentifyTarget", "1", CVAR_ARCHIVE },
	{ &cg_crosshairForceHint, "cg_crosshairForceHint", "1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART },
	{ &cg_endcredits, "cg_endcredits", "0", 0},
	{ &cg_updatedDataPadForcePower1, "cg_updatedDataPadForcePower1", "0", 0},
	{ &cg_updatedDataPadForcePower2, "cg_updatedDataPadForcePower2", "0", 0},
	{ &cg_updatedDataPadForcePower3, "cg_updatedDataPadForcePower3", "0", 0},
	{ &cg_updatedDataPadObjective, "cg_updatedDataPadObjective", "0", 0},

	{ &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE },
	{ &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE },
	{ &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE },
	{ &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE },
	// NOTE : I also create this in UI_Init()
	{ &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE },
	// NOTE : I also create these weapon sway cvars in UI_Init()
	{ &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE},
	{ &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE },
	{ &cg_bobup , "cg_bobup", "0.005", CVAR_ARCHIVE },
	{ &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE },
	{ &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE },

	{ &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT },
#ifndef FINAL_BUILD
	{ &cg_gun_frame, "gun_frame", "0", CVAR_CHEAT },
	{ &cg_debugAnimTarget, "cg_debugAnimTarget", "0", CVAR_CHEAT },
#endif	
	{ &cg_debugSaber, "cg_debugsaber", "0", CVAR_CHEAT },
	{ &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT },
	{ &cg_errorDecay, "cg_errordecay", "100", 0 },

	{ &cg_drawBreath, "cg_drawBreath", "0", CVAR_ARCHIVE },		// Added 11/07/02
	{ &cg_roffdebug, "cg_roffdebug", "0" },
#ifndef FINAL_BUILD
	{ &cg_roffval1, "cg_roffval1", "0" },
	{ &cg_roffval2, "cg_roffval2", "0" },
	{ &cg_roffval3, "cg_roffval3", "0" },
	{ &cg_roffval4, "cg_roffval4", "0" },
#endif
	{ &cg_thirdPerson, "cg_thirdPerson", "1", CVAR_SAVEGAME },
	{ &cg_thirdPersonRange, "cg_thirdPersonRange", "80", 0 },
	{ &cg_thirdPersonMaxRange, "cg_thirdPersonMaxRange", "150", 0 },
	{ &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", 0 },
	{ &cg_thirdPersonPitchOffset, "cg_thirdPersonPitchOffset", "0", 0 },
	{ &cg_thirdPersonVertOffset, "cg_thirdPersonVertOffset", "16", 0},
	{ &cg_thirdPersonCameraDamp, "cg_thirdPersonCameraDamp", "0.3", 0},
	{ &cg_thirdPersonTargetDamp, "cg_thirdPersonTargetDamp", "0.5", 0},
	
	{ &cg_thirdPersonHorzOffset, "cg_thirdPersonHorzOffset", "0", 0},
	{ &cg_thirdPersonAlpha,	"cg_thirdPersonAlpha",	"1.0", CVAR_CHEAT },
	{ &cg_thirdPersonAutoAlpha,	"cg_thirdPersonAutoAlpha",	"0", 0 },
	// NOTE: also declare this in UI_Init
	{ &cg_gunAutoFirst, "cg_gunAutoFirst", "1", CVAR_ARCHIVE },

	{ &cg_pano, "pano", "0", 0 },
	{ &cg_panoNumShots, "panoNumShots", "10", 0 },

	{ &fx_freeze, "fx_freeze", "0", 0 },
	{ &fx_debug, "fx_debug", "0", 0 },
	// the following variables are created in other parts of the system,
	// but we also reference them here

	{ &cg_paused, "cl_paused", "0", CVAR_ROM },
	{ &cg_developer, "developer", "", 0 }, 
	{ &cg_timescale, "timescale", "1", 0 }, 	
	{ &cg_skippingcin, "skippingCinematic", "0", CVAR_ROM},
	{ &cg_missionInfoFlashTime, "cg_missionInfoFlashTime", "10000", 0  },
	{ &cg_hudFiles, "cg_hudFiles", "ui/jahud.txt", CVAR_ARCHIVE},

	{ &cg_VariantSoundCap, "cg_VariantSoundCap", "0", 0 },
	{ &cg_turnAnims, "cg_turnAnims", "0", 0 },
	{ &cg_motionBoneComp, "cg_motionBoneComp", "2", 0 },
	{ &cg_distributeMBCorrection, "cg_distributeMBCorrection", "1", 0 },
	{ &cg_reliableAnimEvents, "cg_reliableAnimEvents", "1", CVAR_ARCHIVE },
	{ &cg_smoothPlayerPos, "cg_smoothPlayerPos", "0.5", 0},
	{ &cg_smoothPlayerPlat, "cg_smoothPlayerPlat", "0.75", 0},
	{ &cg_smoothPlayerPlatAccel, "cg_smoothPlayerPlatAccel", "3.25", 0},
	{ &cg_g2Marks, "cg_g2Marks", "1", CVAR_ARCHIVE },
	{ &fx_expensivePhysics, "fx_expensivePhysics", "1", CVAR_ARCHIVE },
	{ &cg_debugHealthBars,	"cg_debugHealthBars",	"0", CVAR_CHEAT },
};

static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] );

/*
=================
CG_RegisterCvars
=================
*/
void CG_RegisterCvars( void ) {
	int			i;
	cvarTable_t	*cv;

	for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
		cgi_Cvar_Register( cv->vmCvar, cv->cvarName,
			cv->defaultString, cv->cvarFlags );
	}
}

/*
=================
CG_UpdateCvars
=================
*/
void CG_UpdateCvars( void ) {
	int			i;
	cvarTable_t	*cv;

	for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
		cgi_Cvar_Update( cv->vmCvar );
	}
}


int CG_CrosshairPlayer( void ) 
{
	if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) 
	{
		return -1;
	}
	return cg.crosshairClientNum;
}


int CG_GetCameraPos( vec3_t camerapos ) {
	if ( in_camera) {
		VectorCopy(client_camera.origin, camerapos);
		return 1;
	}
	else if ( cg_entities[0].gent && cg_entities[0].gent->client && cg_entities[0].gent->client->ps.viewEntity > 0 && cg_entities[0].gent->client->ps.viewEntity < ENTITYNUM_WORLD )
	//else if ( cg.snap && cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
	{//in an entity camera view
		if ( g_entities[cg_entities[0].gent->client->ps.viewEntity].client && cg.renderingThirdPerson )
		{
			VectorCopy( g_entities[cg_entities[0].gent->client->ps.viewEntity].client->renderInfo.eyePoint, camerapos );
		}
		else
		{
			VectorCopy( g_entities[cg_entities[0].gent->client->ps.viewEntity].currentOrigin, camerapos );
		}
		//VectorCopy( cg_entities[cg_entities[0].gent->client->ps.viewEntity].lerpOrigin, camerapos );
		/*
		if ( g_entities[cg.snap->ps.viewEntity].client && cg.renderingThirdPerson )
		{
			VectorCopy( g_entities[cg.snap->ps.viewEntity].client->renderInfo.eyePoint, camerapos );
		}
		else
		{//use the g_ent because it may not have gotten over to the client yet...
			VectorCopy( g_entities[cg.snap->ps.viewEntity].currentOrigin, camerapos );
		}
		*/
		return 1;
	}
	else if ( cg.renderingThirdPerson )
	{//in third person
		//FIXME: what about hacks that render in third person regardless of this value?
		VectorCopy( cg.refdef.vieworg, camerapos );
		return 1;
	}
	else if (cg.snap && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )//implied: !cg.renderingThirdPerson 
	{//first person saber hack
		VectorCopy( cg.refdef.vieworg, camerapos );
		return 1;
	}
	return 0;
}

int CG_GetCameraAng( vec3_t cameraang ) 
{
	if ( in_camera) 
	{
		VectorCopy(client_camera.angles, cameraang);
		return 1;
	}
	else
	{
		VectorCopy( cg.refdefViewAngles, cameraang );
		return 1;
	}
}

void CG_TargetCommand_f( void ) {
	int		targetNum;
	char	test[4];

	targetNum = CG_CrosshairPlayer();
	if (targetNum <= 0) {
		return;
	}

	cgi_Argv( 1, test, 4 );	//FIXME: this is now an exec_now command - in case we start using it... JFM
	cgi_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) );
}



void CG_Printf( const char *msg, ... ) {
	va_list		argptr;
	char		text[1024];

	va_start (argptr, msg);
	vsprintf (text, msg, argptr);
	va_end (argptr);

	cgi_Printf( text );
}

void CG_Error( const char *msg, ... ) {
	va_list		argptr;
	char		text[1024];

	va_start (argptr, msg);
	vsprintf (text, msg, argptr);
	va_end (argptr);

	cgi_Error( text );
}


/*
================
CG_Argv
================
*/
const char *CG_Argv( int arg ) {
	static char	buffer[MAX_STRING_CHARS];

	cgi_Argv( arg, buffer, sizeof( buffer ) );

	return buffer;
}


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

/*
=================
CG_RegisterItemSounds

The server says this item is used on this level
=================
*/
void CG_RegisterItemSounds( int itemNum ) {
	gitem_t			*item;
	char			data[MAX_QPATH];
	char			*s, *start;
	int				len;

	item = &bg_itemlist[ itemNum ];

	if (item->pickup_sound)
	{
		cgi_S_RegisterSound( item->pickup_sound );
	}

	// parse the space seperated precache string for other media
	s = item->sounds;
	if (!s || !s[0])
		return;

	while (*s) {
		start = s;
		while (*s && *s != ' ') {
			s++;
		}

		len = s-start;
		if (len >= MAX_QPATH || len < 5) {
			CG_Error( "PrecacheItem: %s has bad precache string", 
				item->classname);
			return;
		}
		memcpy (data, start, len);
		data[len] = 0;
		if ( *s ) {
			s++;
		}

		if ( !strcmp(data+len-3, "wav" )) {
			cgi_S_RegisterSound( data );
		}
	}
}

#ifdef _IMMERSION
/*
=================
CG_RegisterItemForces

The server says this item is used on this level
=================
*/
void CG_RegisterItemForces( int itemNum ) {
	gitem_t			*item;
	char			data[MAX_QPATH];
	char			*s, *start;
	int				len;

	item = &bg_itemlist[ itemNum ];

	if (item->pickup_force)
	{
		cgi_FF_Register( item->pickup_force, FF_CHANNEL_TOUCH );
	}

	// parse the space seperated precache string for other media
	s = item->forces;
	if (!s || !s[0])
		return;

	while (*s) {
		start = s;
		while (*s && *s != ' ') {
			s++;
		}

		len = s-start;
		if (len >= MAX_QPATH) {
			CG_Error( "PrecacheItem: %s has bad precache string", 
				item->classname);
			return;
		}
		memcpy (data, start, len);
		data[len] = 0;
		if ( *s ) {
			s++;
		}

		cgi_FF_Register( data, FF_CHANNEL_TOUCH );
	}
}
#endif // _IMMERSION

/*
======================
CG_LoadingString

======================
*/
void CG_LoadingString( const char *s ) {
	Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) );
	cgi_UpdateScreen();
}


static inline void CG_AS_Register(void)
{
	CG_LoadingString( "ambient sound sets" );

	assert(as_preCacheMap);

	//Load the ambient sets

	cgi_AS_AddPrecacheEntry( "#clear" );	// ;-)
	//FIXME: Don't ask... I had to get around a really nasty MS error in the templates with this...
	namePrecache_m::iterator	pi;
	STL_ITERATE( pi, (*as_preCacheMap) )
	{
		cgi_AS_AddPrecacheEntry( ((*pi).first).c_str() );
	}

	cgi_AS_ParseSets();
}


/*
=================
CG_RegisterSounds

called during a precache command
=================
*/
static void CG_RegisterSounds( void ) {
	int		i;
	char	name[MAX_QPATH];
	const char	*soundName;

	CG_AS_Register();

	CG_LoadingString( "general sounds" );

	//FIXME: add to cg.media?
	cgi_S_RegisterSound( "sound/player/fallsplat.wav" );

	cgs.media.selectSound = cgi_S_RegisterSound( "sound/weapons/change.wav" );
	cgs.media.selectSound2 = cgi_S_RegisterSound( "sound/interface/sub_select.wav" );
//	cgs.media.useNothingSound = cgi_S_RegisterSound( "sound/items/use_nothing.wav" );

	cgs.media.noAmmoSound = cgi_S_RegisterSound( "sound/weapons/noammo.wav" );
//	cgs.media.talkSound = 	cgi_S_RegisterSound( "sound/interface/communicator.wav" );
	cgs.media.landSound =	cgi_S_RegisterSound( "sound/player/land1.wav");
	cgs.media.rollSound =	cgi_S_RegisterSound( "sound/player/roll1.wav");
	theFxScheduler.RegisterEffect( "env/slide_dust" );

	cgs.media.overchargeFastSound	= cgi_S_RegisterSound("sound/weapons/overchargeFast.wav" );
	cgs.media.overchargeSlowSound	= cgi_S_RegisterSound("sound/weapons/overchargeSlow.wav" );
	cgs.media.overchargeLoopSound	= cgi_S_RegisterSound("sound/weapons/overchargeLoop.wav");
	cgs.media.overchargeEndSound	= cgi_S_RegisterSound("sound/weapons/overchargeEnd.wav");

	cgs.media.batteryChargeSound	= cgi_S_RegisterSound( "sound/interface/pickup_battery.wav" );

//	cgs.media.tedTextSound = cgi_S_RegisterSound( "sound/interface/tedtext.wav" );
	cgs.media.messageLitSound = cgi_S_RegisterSound( "sound/interface/update" );

	cgs.media.noforceSound = cgi_S_RegisterSound( "sound/weapons/force/noforce" );
	
	cgs.media.watrInSound = cgi_S_RegisterSound ("sound/player/watr_in.wav");
	cgs.media.watrOutSound = cgi_S_RegisterSound ("sound/player/watr_out.wav");
	cgs.media.watrUnSound = cgi_S_RegisterSound ("sound/player/watr_un.wav");

	if ( (gi.totalMapContents()&CONTENTS_LAVA) )
	{
		cgs.media.lavaInSound = cgi_S_RegisterSound ("sound/player/inlava.wav");
		cgs.media.lavaOutSound = cgi_S_RegisterSound ("sound/player/watr_out.wav");
		cgs.media.lavaUnSound = cgi_S_RegisterSound ("sound/player/muckexit.wav");
	}
	// Zoom
	cgs.media.zoomStart = cgi_S_RegisterSound( "sound/interface/zoomstart.wav" );
	cgs.media.zoomLoop = cgi_S_RegisterSound( "sound/interface/zoomloop.wav" );
	cgs.media.zoomEnd = cgi_S_RegisterSound( "sound/interface/zoomend.wav" );

	cgi_S_RegisterSound( "sound/chars/turret/startup.wav" );
	cgi_S_RegisterSound( "sound/chars/turret/shutdown.wav" );
	cgi_S_RegisterSound( "sound/chars/turret/ping.wav" );
	cgi_S_RegisterSound( "sound/chars/turret/move.wav" );
	cgi_S_RegisterSound( "sound/player/use_sentry" );
	cgi_R_RegisterModel( "models/items/psgun.glm" );
	theFxScheduler.RegisterEffect( "turret/explode" );
	theFxScheduler.RegisterEffect( "sparks/spark_exp_nosnd" );

	for (i=0 ; i<4 ; i++) {
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/stone_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_STONEWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/stone_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_STONERUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/metal_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_METALWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/metal_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_METALRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/pipe_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_PIPEWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/pipe_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_PIPERUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_SPLASH][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_walk%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_WADE][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_wade_0%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_SWIM][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/snow_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_SNOWWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/snow_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_SNOWRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/sand_walk%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_SANDWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/sand_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_SANDRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/grass_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_GRASSWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/grass_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_GRASSRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/dirt_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_DIRTWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/dirt_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_DIRTRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/mud_walk%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_MUDWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/mud_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_MUDRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/gravel_walk%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_GRAVELWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/gravel_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_GRAVELRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/rug_step%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_RUGWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/rug_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_RUGRUN][i] = cgi_S_RegisterSound (name);

		Com_sprintf (name, sizeof(name), "sound/player/footsteps/wood_walk%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_WOODWALK][i] = cgi_S_RegisterSound (name);
		Com_sprintf (name, sizeof(name), "sound/player/footsteps/wood_run%i.wav", i+1);
		cgs.media.footsteps[FOOTSTEP_WOODRUN][i] = cgi_S_RegisterSound (name);
	}

	cg.loadLCARSStage = 1;
	CG_LoadingString( "item sounds" );

	// only register the items that the server says we need
	char	items[MAX_ITEMS+1];
	strcpy( items, CG_ConfigString( CS_ITEMS ) );

	for ( i = 1 ; i < bg_numItems ; i++ ) {
		if ( items[ i ] == '1' )	//even with sound pooling, don't clutter it for low end machines
		{
			CG_RegisterItemSounds( i );
		}
	}

	cg.loadLCARSStage = 2;
	CG_LoadingString( "preregistered sounds" );

	for ( i = 1 ; i < MAX_SOUNDS ; i++ ) {
		soundName = CG_ConfigString( CS_SOUNDS+i );
		if ( !soundName[0] ) {
			break;
		}
		if ( soundName[0] == '*' ) {
			continue;	// custom sound
		}
		if (!(i&7)) {
			CG_LoadingString( soundName );
		}
		cgs.sound_precache[i] = cgi_S_RegisterSound( soundName );
	}
}

#ifdef _IMMERSION
extern qboolean CG_ConfigForce( int index, const char *&name, int &channel );
/*
=================
CG_RegisterForces

called during a precache command
=================
*/
static void CG_RegisterForces( void )
{
	char name[512];
	int i, channel;
	const char *forceName;

	cgs.media.selectForce = cgi_FF_Register( "fffx/weapons/change", FF_CHANNEL_WEAPON );

	for (i=0 ; i<4 ; i++) {
		Com_sprintf (name, sizeof(name), "fffx/player/footsteps/step%i", i+1);
		cgs.media.footstepForces[FOOTSTEP_STONEWALK][i] = cgi_FF_Register( name, FF_CHANNEL_FOOT );

		Com_sprintf (name, sizeof(name), "fffx/player/footsteps/splash%i", i+1);
		cgs.media.footstepForces[FOOTSTEP_SPLASH][i] = cgi_FF_Register( name, FF_CHANNEL_FOOT );

		Com_sprintf (name, sizeof(name), "fffx/player/footsteps/clank%i", i+1);
		cgs.media.footstepForces[FOOTSTEP_METALWALK][i] = cgi_FF_Register( name, FF_CHANNEL_FOOT );
	}

	cgs.media.noAmmoForce = cgi_FF_Register( "fffx/weapons/noammo", FF_CHANNEL_WEAPON );
	cgs.media.landForce = cgi_FF_Register( "fffx/player/land1", FF_CHANNEL_BODY );

	cgs.media.watrInForce = cgi_FF_Register( "fffx/player/watr_in", FF_CHANNEL_BODY );
	cgs.media.watrOutForce = cgi_FF_Register( "fffx/player/watr_out", FF_CHANNEL_BODY );
	cgs.media.watrUnForce = cgi_FF_Register( "fffx/player/watr_un", FF_CHANNEL_BODY );

	cgs.media.zoomStartForce = cgi_FF_Register( "fffx/interface/zoomstart", FF_CHANNEL_WEAPON );
	cgs.media.zoomLoopForce = cgi_FF_Register( "fffx/interface/zoomloop", FF_CHANNEL_WEAPON );
	cgs.media.zoomEndForce = cgi_FF_Register( "fffx/interface/zoomend", FF_CHANNEL_WEAPON );

	cgs.media.grenadeBounce1Force = cgi_FF_Register( "fffx/weapons/thermal/bounce1", FF_CHANNEL_WEAPON );
	cgs.media.grenadeBounce2Force = cgi_FF_Register( "fffx/weapons/thermal/bounce2", FF_CHANNEL_WEAPON );

	for ( i = 1 ; i < bg_numItems ; i++ )
	{
		CG_RegisterItemForces( i );
	}

	for ( i = 1 ; i < MAX_FORCES ; i++ )
	{
		if ( !CG_ConfigForce( i, forceName, channel ) )
			break;

		if ( forceName[0] == '*' ) {
			continue;	// custom force???
		}
		if (!(i&7)) {
			CG_LoadingString( forceName );
		}

		cgs.force_precache[i] = cgi_FF_Register( forceName, channel );
	}
}
#endif // _IMMERSION
/*
=============================================================================

CLIENT INFO

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

qhandle_t CG_RegisterHeadSkin( const char *headModelName, const char *headSkinName, qboolean *extensions )
{
	char		hfilename[MAX_QPATH];
	qhandle_t	headSkin;

	Com_sprintf( hfilename, sizeof( hfilename ), "models/players/%s/head_%s.skin", headModelName, headSkinName );
	headSkin = cgi_R_RegisterSkin( hfilename );
	if ( headSkin < 0 ) 
	{	//have extensions
		*extensions = qtrue;
		headSkin = -headSkin;
	} 
	else 
	{
		*extensions = qfalse;	//just to be sure.
	}

	if ( !headSkin )
	{
		Com_Printf( "Failed to load skin file: %s : %s\n", headModelName, headSkinName );
	}
	return headSkin;
}

/*
==========================
CG_RegisterClientSkin
==========================
*/
qboolean	CG_RegisterClientSkin( clientInfo_t *ci,
								  const char *headModelName, const char *headSkinName,
								  const char *torsoModelName, const char *torsoSkinName,
								  const char *legsModelName, const char *legsSkinName) 
{
	char		hfilename[MAX_QPATH];
	char		tfilename[MAX_QPATH];
	char		lfilename[MAX_QPATH];

	Com_sprintf( lfilename, sizeof( lfilename ), "models/players/%s/lower_%s.skin", legsModelName, legsSkinName );
	ci->legsSkin = cgi_R_RegisterSkin( lfilename );

	if ( !ci->legsSkin )
	{
//		Com_Printf( "Failed to load skin file: %s : %s\n", legsModelName, legsSkinName );
		//return qfalse;
	}

	if(torsoModelName && torsoSkinName && torsoModelName[0] && torsoSkinName[0])
	{
		Com_sprintf( tfilename, sizeof( tfilename ), "models/players/%s/upper_%s.skin", torsoModelName, torsoSkinName );
		ci->torsoSkin = cgi_R_RegisterSkin( tfilename );

		if ( !ci->torsoSkin )
		{
			Com_Printf( "Failed to load skin file: %s : %s\n", torsoModelName, torsoSkinName );
			return qfalse;
		}
	}

	if(headModelName && headSkinName && headModelName[0] && headSkinName[0])
	{
		Com_sprintf( hfilename, sizeof( hfilename ), "models/players/%s/head_%s.skin", headModelName, headSkinName );
		ci->headSkin = cgi_R_RegisterSkin( hfilename );

		if ( !ci->headSkin )
		{
			Com_Printf( "Failed to load skin file: %s : %s\n", headModelName, headSkinName );
			return qfalse;
		}
	}

	return qtrue;
}

/*
==========================
CG_RegisterClientModelname
==========================
*/
qboolean CG_RegisterClientModelname( clientInfo_t *ci,
									const char *headModelName, const char *headSkinName,
									const char *torsoModelName, const char *torsoSkinName,
									const char *legsModelName, const char *legsSkinName ) 
{
/*
Ghoul2 Insert Start
*/
#if 1
	char		filename[MAX_QPATH];


	if ( !legsModelName || !legsModelName[0] )
	{
		return qtrue;
	}
	Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.mdr", legsModelName );
	ci->legsModel = cgi_R_RegisterModel( filename );
	if ( !ci->legsModel ) 
	{//he's not skeletal, try the old way
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", legsModelName );
		ci->legsModel = cgi_R_RegisterModel( filename );
		if ( !ci->legsModel )
		{
			Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename );
			return qfalse;
		}
	}

	if(torsoModelName && torsoModelName[0])
	{//You are trying to set one
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.mdr", torsoModelName );
		ci->torsoModel = cgi_R_RegisterModel( filename );
		if ( !ci->torsoModel ) 
		{//he's not skeletal, try the old way
			Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", torsoModelName );
			ci->torsoModel = cgi_R_RegisterModel( filename );
			if ( !ci->torsoModel ) 
			{
				Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename );
				return qfalse;
			}
		}
	}
	else
	{
		ci->torsoModel = 0;
	}

	if(headModelName && headModelName[0])
	{//You are trying to set one
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName );
		ci->headModel = cgi_R_RegisterModel( filename );
		if ( !ci->headModel ) 
		{
			Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename );
			return qfalse;
		}
	}
	else
	{
		ci->headModel = 0;
	}


	// if any skins failed to load, return failure
	if ( !CG_RegisterClientSkin( ci, headModelName, headSkinName, torsoModelName, torsoSkinName, legsModelName, legsSkinName ) ) 
	{
		//Com_Printf( "Failed to load skin file: %s : %s/%s : %s/%s : %s\n", headModelName, headSkinName, torsoModelName, torsoSkinName, legsModelName, legsSkinName );
		return qfalse;
	}

	//FIXME: for now, uses the legs model dir for anim cfg, but should we set this in some sort of NPCs.cfg?
	// load the animation file set
	ci->animFileIndex = G_ParseAnimFileSet(legsModelName);
	if (ci->animFileIndex<0) 
	{
		Com_Printf( S_COLOR_RED"Failed to load animation file set models/players/%s\n", legsModelName );
		return qfalse;
	}
#endif
/*
Ghoul2 Insert End
*/
	return qtrue;
}


void CG_RegisterClientRenderInfo(clientInfo_t *ci, renderInfo_t *ri)
{
	char			*slash;
	char			headModelName[MAX_QPATH];
	char			torsoModelName[MAX_QPATH];
	char			legsModelName[MAX_QPATH];
	char			headSkinName[MAX_QPATH];
	char			torsoSkinName[MAX_QPATH];
	char			legsSkinName[MAX_QPATH];

	if(!ri->legsModelName || !ri->legsModelName[0])
	{//Must have at LEAST a legs model
		return;
	}

	Q_strncpyz( legsModelName, ri->legsModelName, sizeof( legsModelName ) );
	//Legs skin
	slash = strchr( legsModelName, '/' );
	if ( !slash ) 
	{
		// modelName didn not include a skin name
		Q_strncpyz( legsSkinName, "default", sizeof( legsSkinName ) );
	} 
	else 
	{
		Q_strncpyz( legsSkinName, slash + 1, sizeof( legsSkinName ) );
		// truncate modelName
		*slash = 0;
	}

	if(ri->torsoModelName && ri->torsoModelName[0])
	{
		Q_strncpyz( torsoModelName, ri->torsoModelName, sizeof( torsoModelName ) );
		//Torso skin
		slash = strchr( torsoModelName, '/' );
		if ( !slash ) 
		{
			// modelName didn't include a skin name
			Q_strncpyz( torsoSkinName, "default", sizeof( torsoSkinName ) );
		} 
		else 
		{
			Q_strncpyz( torsoSkinName, slash + 1, sizeof( torsoSkinName ) );
			// truncate modelName
			*slash = 0;
		}
	}
	else
	{
		torsoModelName[0] = 0;
	}

	//Head
	if(ri->headModelName && ri->headModelName[0])
	{
		Q_strncpyz( headModelName, ri->headModelName, sizeof( headModelName ) );
		//Head skin
		slash = strchr( headModelName, '/' );
		if ( !slash ) 
		{
			// modelName didn not include a skin name
			Q_strncpyz( headSkinName, "default", sizeof( headSkinName ) );
		} 
		else 
		{
			Q_strncpyz( headSkinName, slash + 1, sizeof( headSkinName ) );
			// truncate modelName
			*slash = 0;
		}
	}
	else
	{
		headModelName[0] = 0;
	}

	if ( !CG_RegisterClientModelname( ci, headModelName, headSkinName, torsoModelName, torsoSkinName, legsModelName, legsSkinName) ) 
	{
		if ( !CG_RegisterClientModelname( ci, DEFAULT_HEADMODEL, "default", DEFAULT_TORSOMODEL, "default", DEFAULT_LEGSMODEL, "default" ) ) 
		{
			CG_Error( "DEFAULT_MODELS failed to register");
		}
	}
}

//-------------------------------------
// CG_RegisterEffects
// 
// Handles precaching all effect files
//	and any shader, model, or sound
//	files an effect may use.
//-------------------------------------
extern void CG_InitGlass( void );
extern void	cgi_R_WorldEffectCommand( const char *command );

extern cvar_t *g_delayedShutdown;
static void CG_RegisterEffects( void )
{
	char	*effectName;
	int		i, numFailed=0;

	// Register external effects
	for ( i = 1 ; i < MAX_FX ; i++ ) 
	{
		effectName = ( char *)CG_ConfigString( CS_EFFECTS + i );

		if ( !effectName[0] ) 
		{
			break;
		}

		if (!theFxScheduler.RegisterEffect( (const char*)effectName ))
		{
			assert(0);
			numFailed++;
		}
	}
	if (numFailed && g_delayedShutdown->integer)
	{
		assert(0);
		CG_Error( "CG_RegisterEffects: %i Effects failed to load.  Please fix, or ask Aurelio.", numFailed );
	}

	// Start world effects
	for ( i = 1 ; i < MAX_WORLD_FX ; i++ ) 
	{
		effectName = ( char *)CG_ConfigString( CS_WORLD_FX + i );

		if ( !effectName[0] ) 
		{
			break;
		}

		cgi_R_WorldEffectCommand( effectName );
	}

	// Set up the glass effects mini-system.
	CG_InitGlass();

	//footstep effects
	cgs.effects.footstepMud = theFxScheduler.RegisterEffect( "materials/mud" );
	cgs.effects.footstepSand = theFxScheduler.RegisterEffect( "materials/sand" );
	cgs.effects.footstepSnow = theFxScheduler.RegisterEffect( "materials/snow" );
	cgs.effects.footstepGravel = theFxScheduler.RegisterEffect( "materials/gravel" );
	//landing effects
	cgs.effects.landingMud = theFxScheduler.RegisterEffect( "materials/mud_large" );
	cgs.effects.landingSand = theFxScheduler.RegisterEffect( "materials/sand_large" );
	cgs.effects.landingDirt = theFxScheduler.RegisterEffect( "materials/dirt_large" );
	cgs.effects.landingSnow = theFxScheduler.RegisterEffect( "materials/snow_large" );
	cgs.effects.landingGravel = theFxScheduler.RegisterEffect( "materials/gravel_large" );
	//splashes
	if ( (gi.totalMapContents()&CONTENTS_WATER) )
	{
		theFxScheduler.RegisterEffect( "env/water_impact" );
		theFxScheduler.RegisterEffect( "misc/waterbreath" );
	}
	if ( (gi.totalMapContents()&CONTENTS_LAVA) )
	{
		theFxScheduler.RegisterEffect( "env/lava_splash" );
	}
	if ( (gi.totalMapContents()&CONTENTS_SLIME) )
	{
		theFxScheduler.RegisterEffect( "env/acid_splash" );
	}
	theFxScheduler.RegisterEffect( "misc/breath" );
}

/*
void CG_RegisterClientModels (int entityNum)

Only call if clientInfo->infoValid is not true

For players and NPCs to register their models
*/
void CG_RegisterClientModels (int entityNum)
{
	gentity_t		*ent;

	if(entityNum < 0 || entityNum > ENTITYNUM_WORLD)
	{
		return;
	}

	ent = &g_entities[entityNum];

	if(!ent->client)
	{
		return;
	}

	ent->client->clientInfo.infoValid = qtrue;

	if ( ent->playerModel != -1 && ent->ghoul2.size() )
	{
		return;
	}

	CG_RegisterClientRenderInfo(&ent->client->clientInfo, &ent->client->renderInfo);

	if(entityNum < MAX_CLIENTS)
	{
		memcpy(&cgs.clientinfo[entityNum], &ent->client->clientInfo, sizeof(clientInfo_t));
	}
}

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


HUDMenuItem_t forceTics[] = 
{
"rightHUD", "force_tic1", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"rightHUD", "force_tic2", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"rightHUD", "force_tic3", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"rightHUD", "force_tic4", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
};

HUDMenuItem_t ammoTics[] = 
{
"rightHUD", "ammo_tic1", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"rightHUD", "ammo_tic2", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"rightHUD", "ammo_tic3", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"rightHUD", "ammo_tic4", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
};

HUDMenuItem_t armorTics[] = 
{
"leftHUD", "armor_tic1", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"leftHUD", "armor_tic2", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	
"leftHUD", "armor_tic3", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	
"leftHUD", "armor_tic4", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	
};

HUDMenuItem_t healthTics[] = 
{
"leftHUD", "health_tic1", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL, 	// Top 
"leftHUD", "health_tic2", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// 
"leftHUD", "health_tic3", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// 
"leftHUD", "health_tic4", 0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// Bottom
};


HUDMenuItem_t otherHUDBits[] = 
{
"lefthud", "healthamount",			0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_HEALTHAMOUNT
"lefthud", "armoramount",			0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_ARMORAMOUNT
"righthud", "forceamount",			0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_FORCEAMOUNT 
"righthud", "ammoamount",			0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_AMMOAMOUNT
"righthud", "saberstyle_strong",	0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_SABERSTYLE_STRONG
"righthud", "saberstyle_medium",	0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_SABERSTYLE_MEDIUM
"righthud", "saberstyle_fast",		0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_SABERSTYLE_FAST
"lefthud",	"scanline",				0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_SCANLINE_LEFT
"righthud",	"scanline",				0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_SCANLINE_RIGHT
"lefthud",	"frame",				0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_FRAME_LEFT
"righthud",	"frame",				0,  0,  0,  0, 0.0f, 0.0f, 0.0f, 0.0f, NULL,	// OHB_FRAME_RIGHT
};

/*const char *HolocronIcons[] = {
	"gfx/mp/f_icon_lt_heal",		//FP_HEAL,
	"gfx/mp/f_icon_levitation",		//FP_LEVITATION,
	"gfx/mp/f_icon_speed",			//FP_SPEED,
	"gfx/mp/f_icon_push",			//FP_PUSH,
	"gfx/mp/f_icon_pull",			//FP_PULL,
	"gfx/mp/f_icon_lt_telepathy",	//FP_TELEPATHY,
	"gfx/mp/f_icon_dk_grip",		//FP_GRIP,
	"gfx/mp/f_icon_dk_l1",			//FP_LIGHTNING,
	"gfx/mp/f_icon_dk_rage",		//FP_RAGE,
	"gfx/mp/f_icon_lt_protect",		//FP_PROTECT,
	"gfx/mp/f_icon_lt_absorb",		//FP_ABSORB,
	"gfx/mp/f_icon_lt_healother",	//FP_TEAM_HEAL,
	"gfx/mp/f_icon_dk_forceother",	//FP_TEAM_FORCE,
	"gfx/mp/f_icon_dk_drain",		//FP_DRAIN,
	"gfx/mp/f_icon_sight",			//FP_SEE,
	"gfx/mp/f_icon_saber_attack",	//FP_SABERATTACK,
	"gfx/mp/f_icon_saber_defend",	//FP_SABERDEFEND,
	"gfx/mp/f_icon_saber_throw"		//FP_SABERTHROW
};
*/
extern void CG_NPC_Precache ( gentity_t *spawner );
qboolean NPCsPrecached = qfalse;
/*
=================
CG_PrepRefresh

Call before entering a new level, or after changing renderers
This function may execute for a couple of minutes with a slow disk.
=================
*/
void CG_CreateMiscEnts(void);
static void CG_RegisterGraphics( void ) {
	int			i;
	char		items[MAX_ITEMS+1];
	int			breakPoint = 0;
	const char		*sb_nums[11] = {
		"gfx/2d/numbers/zero",
		"gfx/2d/numbers/one",
		"gfx/2d/numbers/two",
		"gfx/2d/numbers/three",
		"gfx/2d/numbers/four",
		"gfx/2d/numbers/five",
		"gfx/2d/numbers/six",
		"gfx/2d/numbers/seven",
		"gfx/2d/numbers/eight",
		"gfx/2d/numbers/nine",
		"gfx/2d/numbers/minus",
	};

	const char		*sb_t_nums[11] = {
		"gfx/2d/numbers/t_zero",
		"gfx/2d/numbers/t_one",
		"gfx/2d/numbers/t_two",
		"gfx/2d/numbers/t_three",
		"gfx/2d/numbers/t_four",
		"gfx/2d/numbers/t_five",
		"gfx/2d/numbers/t_six",
		"gfx/2d/numbers/t_seven",
		"gfx/2d/numbers/t_eight",
		"gfx/2d/numbers/t_nine",
		"gfx/2d/numbers/t_minus",
	};

	const char		*sb_c_nums[11] = {
		"gfx/2d/numbers/c_zero",
		"gfx/2d/numbers/c_one",
		"gfx/2d/numbers/c_two",
		"gfx/2d/numbers/c_three",
		"gfx/2d/numbers/c_four",
		"gfx/2d/numbers/c_five",
		"gfx/2d/numbers/c_six",
		"gfx/2d/numbers/c_seven",
		"gfx/2d/numbers/c_eight",
		"gfx/2d/numbers/c_nine",
		"gfx/2d/numbers/t_minus", //?????
	};

	// Clean, then register...rinse...repeat...
	CG_LoadingString( "effects" );
	FX_Init();
	CG_RegisterEffects();

	// clear any references to old media
	memset( &cg.refdef, 0, sizeof( cg.refdef ) );
	cgi_R_ClearScene();

	cg.loadLCARSStage = 3;
	CG_LoadingString( cgs.mapname );

#ifndef _XBOX // this gets handled elsewhere on Xbox
	cgi_R_LoadWorldMap( cgs.mapname );
#endif

	cg.loadLCARSStage = 4;
	CG_LoadingString( "game media shaders" );

	for ( i=0; i < 11; i++ )
	{
		cgs.media.numberShaders[i]			= cgi_R_RegisterShaderNoMip( sb_nums[i] );
		cgs.media.smallnumberShaders[i]		= cgi_R_RegisterShaderNoMip( sb_t_nums[i] );
		cgs.media.chunkyNumberShaders[i]	= cgi_R_RegisterShaderNoMip( sb_c_nums[i] );
	}

	// FIXME: conditionally do this??  Something must be wrong with inventory item caching..?
	cgi_R_RegisterModel( "models/items/remote.md3" );

	cgs.media.explosionModel				= cgi_R_RegisterModel ( "models/weaphits/explosion.md3" );
	cgs.media.surfaceExplosionShader		= cgi_R_RegisterShader( "surfaceExplosion" );

	cgs.media.halfShieldModel				= cgi_R_RegisterModel( "models/weaphits/testboom.md3" );

	cgs.media.solidWhiteShader			= cgi_R_RegisterShader( "gfx/effects/solidWhite" );
	cgs.media.refractShader				= cgi_R_RegisterShader( "effects/refraction" );

	//on players
	cgs.media.personalShieldShader		= cgi_R_RegisterShader( "gfx/misc/personalshield" );
	cgs.media.cloakedShader				= cgi_R_RegisterShader( "gfx/effects/cloakedShader" );
											cgi_R_RegisterShader( "gfx/misc/ion_shield" );

	cgs.media.boltShader				= cgi_R_RegisterShader( "gfx/misc/blueLine" );

	// FIXME: do these conditionally
	cgi_R_RegisterShader( "gfx/2d/workingCamera" );
	cgi_R_RegisterShader( "gfx/2d/brokenCamera" );
	//cgi_R_RegisterShader( "gfx/effects/irid_shield" ); // for galak, but he doesn't have his own weapon so I can't register the shader there.

	//interface
	for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) {
		cgs.media.crosshairShader[i] = cgi_R_RegisterShaderNoMip( va("gfx/2d/crosshair%c", 'a'+i) );
	}
	cgs.media.backTileShader		= cgi_R_RegisterShader( "gfx/2d/backtile" );
//	cgs.media.noammoShader			= cgi_R_RegisterShaderNoMip( "gfx/hud/noammo");
	cgs.media.weaponIconBackground	= cgi_R_RegisterShaderNoMip( "gfx/hud/background");
	cgs.media.forceIconBackground	= cgi_R_RegisterShaderNoMip( "gfx/hud/background_f");
	cgs.media.inventoryIconBackground= cgi_R_RegisterShaderNoMip( "gfx/hud/background_i");
	cgs.media.dataPadFrame			= cgi_R_RegisterShaderNoMip( "gfx/menus/datapad");

	//gore decal shaders -rww
	cgs.media.bdecal_burnmark1		= cgi_R_RegisterShader( "gfx/damage/burnmark1" );
	cgs.media.bdecal_saberglowmark	= cgi_R_RegisterShader( "gfx/damage/saberglowmark" );

	cg.loadLCARSStage = 5;
	CG_LoadingString( "game media models" );
	
	// Chunk models
	//FIXME: jfm:? bother to conditionally load these if an ent has this material type?
	for ( i = 0; i < NUM_CHUNK_MODELS; i++ )
	{
		cgs.media.chunkModels[CHUNK_METAL2][i]	= cgi_R_RegisterModel( va( "models/chunks/metal/metal1_%i.md3", i+1 ) ); //_ /switched\ _
		cgs.media.chunkModels[CHUNK_METAL1][i]	= cgi_R_RegisterModel( va( "models/chunks/metal/metal2_%i.md3", i+1 ) ); //  \switched/
		cgs.media.chunkModels[CHUNK_ROCK1][i]	= cgi_R_RegisterModel( va( "models/chunks/rock/rock1_%i.md3", i+1 ) );
		cgs.media.chunkModels[CHUNK_ROCK2][i]	= cgi_R_RegisterModel( va( "models/chunks/rock/rock2_%i.md3", i+1 ) );
		cgs.media.chunkModels[CHUNK_ROCK3][i]	= cgi_R_RegisterModel( va( "models/chunks/rock/rock3_%i.md3", i+1 ) );
		cgs.media.chunkModels[CHUNK_CRATE1][i]	= cgi_R_RegisterModel( va( "models/chunks/crate/crate1_%i.md3", i+1 ) );
		cgs.media.chunkModels[CHUNK_CRATE2][i]	= cgi_R_RegisterModel( va( "models/chunks/crate/crate2_%i.md3", i+1 ) );
		cgs.media.chunkModels[CHUNK_WHITE_METAL][i]	= cgi_R_RegisterModel( va( "models/chunks/metal/wmetal1_%i.md3", i+1 ) );
	}

	cgs.media.chunkSound			= cgi_S_RegisterSound("sound/weapons/explosions/glasslcar");
	cgs.media.grateSound			= cgi_S_RegisterSound( "sound/effects/grate_destroy" );
	cgs.media.rockBreakSound		= cgi_S_RegisterSound("sound/effects/wall_smash");
	cgs.media.rockBounceSound[0]	= cgi_S_RegisterSound("sound/effects/stone_bounce");
	cgs.media.rockBounceSound[1]	= cgi_S_RegisterSound("sound/effects/stone_bounce2");
	cgs.media.metalBounceSound[0]	= cgi_S_RegisterSound("sound/effects/metal_bounce");
	cgs.media.metalBounceSound[1]	= cgi_S_RegisterSound("sound/effects/metal_bounce2");
	cgs.media.glassChunkSound		= cgi_S_RegisterSound("sound/weapons/explosions/glassbreak1");
	cgs.media.crateBreakSound[0]	= cgi_S_RegisterSound("sound/weapons/explosions/crateBust1" );
	cgs.media.crateBreakSound[1]	= cgi_S_RegisterSound("sound/weapons/explosions/crateBust2" );

	cgs.media.weaponbox	 = cgi_R_RegisterShaderNoMip( "gfx/interface/weapon_box");

	//Models & Shaders
	cgs.media.damageBlendBlobShader	= cgi_R_RegisterShader( "gfx/misc/borgeyeflare" );

	cg.loadLCARSStage = 6;

	cgs.media.messageLitOn = cgi_R_RegisterShaderNoMip( "gfx/hud/message_on" );
	cgs.media.messageLitOff = cgi_R_RegisterShaderNoMip( "gfx/hud/message_off" );
	cgs.media.messageObjCircle = cgi_R_RegisterShaderNoMip( "gfx/hud/objective_circle" );

	cgs.media.DPForcePowerOverlay = cgi_R_RegisterShader( "gfx/hud/force_swirl" );

	//NOTE: we should only cache this if there is a vehicle or emplaced gun somewhere on the map
	cgs.media.emplacedHealthBarShader = cgi_R_RegisterShaderNoMip( "gfx/hud/health_frame" );

	// battery charge shader when using a gonk
	cgs.media.batteryChargeShader = cgi_R_RegisterShader( "gfx/2d/battery" );
	cgi_R_RegisterShader( "gfx/2d/droid_view" );
	cgs.media.useableHint = cgi_R_RegisterShader("gfx/hud/useableHint");

	// Load up other HUD bits
	for (i=0;i<OHB_MAX;i++)
	{
		cgi_UI_GetMenuItemInfo(
			otherHUDBits[i].menuName,
			otherHUDBits[i].itemName,
			&otherHUDBits[i].xPos,
			&otherHUDBits[i].yPos,
			&otherHUDBits[i].width,
			&otherHUDBits[i].height,
			otherHUDBits[i].color,
			&otherHUDBits[i].background);
	}

	// Get all the info for each HUD piece
	for (i=0;i<MAX_HUD_TICS;i++)
	{
		cgi_UI_GetMenuItemInfo(
			healthTics[i].menuName,
			healthTics[i].itemName,
			&healthTics[i].xPos,
			&healthTics[i].yPos,
			&healthTics[i].width,
			&healthTics[i].height,
			healthTics[i].color,
			&healthTics[i].background);

		cgi_UI_GetMenuItemInfo(
			armorTics[i].menuName,
			armorTics[i].itemName,
			&armorTics[i].xPos,
			&armorTics[i].yPos,
			&armorTics[i].width,
			&armorTics[i].height,
			armorTics[i].color,
			&armorTics[i].background);

		cgi_UI_GetMenuItemInfo(
			forceTics[i].menuName,
			forceTics[i].itemName,
			&forceTics[i].xPos,
			&forceTics[i].yPos,
			&forceTics[i].width,
			&forceTics[i].height,
			forceTics[i].color,
			&forceTics[i].background);

		cgi_UI_GetMenuItemInfo(
			ammoTics[i].menuName,
			ammoTics[i].itemName,
			&ammoTics[i].xPos,
			&ammoTics[i].yPos,
			&ammoTics[i].width,
			&ammoTics[i].height,
			ammoTics[i].color,
			&ammoTics[i].background);

	}


	memset( cg_items, 0, sizeof( cg_items ) );
	memset( cg_weapons, 0, sizeof( cg_weapons ) );

	// only register the items that the server says we need
	strcpy( items, CG_ConfigString( CS_ITEMS) );

	for ( i = 1 ; i < bg_numItems ; i++ ) {
		if ( items[ i ] == '1' ) 
		{
			if (bg_itemlist[i].classname)
			{
				CG_LoadingString( bg_itemlist[i].classname );
				CG_RegisterItemVisuals( i );
			}
		}
		if (bg_itemlist[i].giType == IT_HOLDABLE)
		{
			if (bg_itemlist[i].giTag < INV_MAX)
			{
				inv_icons[bg_itemlist[i].giTag] = cgi_R_RegisterShaderNoMip( bg_itemlist[i].icon );
			}
		}
	}

	//precache force power icons here
/*	i = 0;
	while (i < NUM_FORCE_POWERS)
	{
		cgs.media.forcePowerIcons[i] = cgi_R_RegisterShaderNoMip(HolocronIcons[i]);
		i++;
	}
*/
	cgs.media.rageRecShader = cgi_R_RegisterShaderNoMip("gfx/mp/f_icon_ragerec");
	//etc.
	cgi_R_RegisterShader( "gfx/misc/test_crackle" );	//CG_DoGlassQuad

	// wall marks
	cgs.media.scavMarkShader				= cgi_R_RegisterShader( "gfx/damage/burnmark4" );
	cgs.media.rivetMarkShader				= cgi_R_RegisterShader( "gfx/damage/rivetmark" );

	// doing one shader just makes it look like a shell.  By using two shaders with different bulge offsets and different texture scales, it has a much more chaotic look
	cgs.media.electricBodyShader			= cgi_R_RegisterShader( "gfx/misc/electric" );
	cgs.media.electricBody2Shader			= cgi_R_RegisterShader( "gfx/misc/fullbodyelectric2" );

	cgs.media.shadowMarkShader	= cgi_R_RegisterShader( "markShadow" );
	cgs.media.wakeMarkShader	= cgi_R_RegisterShader( "wake" );
	cgs.media.fsrMarkShader	= cgi_R_RegisterShader( "footstep_r" );
	cgs.media.fslMarkShader	= cgi_R_RegisterShader( "footstep_l" );
	cgs.media.fshrMarkShader	= cgi_R_RegisterShader( "footstep_heavy_r" );
	cgs.media.fshlMarkShader	= cgi_R_RegisterShader( "footstep_heavy_l" );
	cgi_S_RegisterSound( "sound/effects/energy_crackle.wav" );

	CG_LoadingString("map brushes");
	// register the inline models
	breakPoint = cgs.numInlineModels = cgi_CM_NumInlineModels();
	assert (cgs.numInlineModels < sizeof(cgs.inlineDrawModel)/sizeof(cgs.inlineDrawModel[0]) );
	for ( i = 1 ; i < cgs.numInlineModels ; i++ ) {
		char	name[10];
		vec3_t			mins, maxs;
		int				j;

		Com_sprintf( name, sizeof(name), "*%i", i );
		cgs.inlineDrawModel[i] = cgi_R_RegisterModel( name );

		if (!cgs.inlineDrawModel[i])
		{
			breakPoint = i;
			break;
		}

		cgi_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs );
		for ( j = 0 ; j < 3 ; j++ ) {
			cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] );
		}
	}

	cg.loadLCARSStage = 7;
	CG_LoadingString("map models");
	// register all the server specified models
	for (i=1 ; i<MAX_MODELS ; i++) {
		const char		*modelName;

		modelName = CG_ConfigString( CS_MODELS+i );
		if ( !modelName[0] ) {
			break;
		}
		cgs.model_draw[i] = cgi_R_RegisterModel( modelName );
//		OutputDebugString(va("### CG_RegisterGraphics(): cgs.model_draw[%d] = \"%s\"\n",i,modelName));
	}

	cg.loadLCARSStage = 8;

/*
Ghoul2 Insert Start
*/
	CG_LoadingString("skins");
	// register all the server specified models
	for (i=1 ; i<MAX_CHARSKINS ; i++) {
		const char		*modelName;

		modelName = CG_ConfigString( CS_CHARSKINS+i );
		if ( !modelName[0] ) {
			break;
		}
		cgs.skins[i] = cgi_R_RegisterSkin( modelName );
	}

/*
Ghoul2 Insert End
*/

	for (i=0 ; i<MAX_CLIENTS ; i++) 
	{
		//feedback( va("client %i", i ) );
		CG_NewClientinfo( i );
	}

	for (i=0 ; i < ENTITYNUM_WORLD ; i++)
	{
		if(&g_entities[i])
		{
			if(g_entities[i].client)
			{
				//if(!g_entities[i].client->clientInfo.infoValid)
				//We presume this
				{
					CG_LoadingString( va("client %s", g_entities[i].client->clientInfo.name ) );
					CG_RegisterClientModels(i);
					if ( i != 0 )
					{//Client weapons already precached
						CG_RegisterWeapon( g_entities[i].client->ps.weapon );
						if ( g_entities[i].client->ps.saber[0].g2MarksShader[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[0].g2MarksShader );
						}
						if ( g_entities[i].client->ps.saber[0].g2MarksShader2[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[0].g2MarksShader2 );
						}
						if ( g_entities[i].client->ps.saber[0].g2WeaponMarkShader[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[0].g2WeaponMarkShader );
						}
						if ( g_entities[i].client->ps.saber[0].g2WeaponMarkShader2[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[0].g2WeaponMarkShader2 );
						}
						if ( g_entities[i].client->ps.saber[1].g2MarksShader[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[1].g2MarksShader );
						}
						if ( g_entities[i].client->ps.saber[1].g2MarksShader2[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[1].g2MarksShader2 );
						}
						if ( g_entities[i].client->ps.saber[1].g2WeaponMarkShader[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[1].g2WeaponMarkShader );
						}
						if ( g_entities[i].client->ps.saber[1].g2WeaponMarkShader2[0] )
						{
							cgi_R_RegisterShader( g_entities[i].client->ps.saber[1].g2WeaponMarkShader2 );
						}
						CG_RegisterNPCCustomSounds( &g_entities[i].client->clientInfo );
						//CG_RegisterNPCEffects( g_entities[i].client->playerTeam );
					}
				}
			}
			else if ( g_entities[i].svFlags & SVF_NPC_PRECACHE && g_entities[i].NPC_type && g_entities[i].NPC_type[0] )
			{//Precache the NPC_type
				//FIXME: make sure we didn't precache this NPC_type already
				CG_LoadingString( va("NPC %s", g_entities[i].NPC_type ) );
				/*
				if (g_entities[i].classname && g_entities[i].classname[0] && Q_stricmp( g_entities[i].classname, "NPC_Vehicle" ) == 0)
				{
					// Get The Index, And Make Sure To Register All The Skins
					int iVehIndex = BG_VehicleGetIndex( g_entities[i].NPC_type );
				}
				else
				*/
				{
					CG_NPC_Precache( &g_entities[i] );
				}
			}
		}
	}

	CG_LoadingString( "static models" );
	CG_CreateMiscEnts();

	cg.loadLCARSStage = 9;

	NPCsPrecached = qtrue;

	extern	cvar_t	*com_buildScript;

	if (com_buildScript->integer) {
		cgi_R_RegisterShader( "gfx/misc/nav_cpoint" );
		cgi_R_RegisterShader( "gfx/misc/nav_line" );
		cgi_R_RegisterShader( "gfx/misc/nav_arrow" );
		cgi_R_RegisterShader( "gfx/misc/nav_node" );
	}

	for(i = 1; i < MAX_SUB_BSP; i++)
	{
		const char		*bspName = 0;
		vec3_t			mins, maxs;
		int				j = 0;
		int				sub = 0;
		char			temp[MAX_QPATH];

		bspName = CG_ConfigString( CS_BSP_MODELS+i );
		if ( !bspName[0] ) 
		{
			break;
		}
		CG_LoadingString( "BSP instances" );

		cgi_CM_LoadMap( bspName, qtrue );
		cgs.inlineDrawModel[breakPoint] = cgi_R_RegisterModel( bspName );
		cgi_R_ModelBounds( cgs.inlineDrawModel[breakPoint], mins, maxs );
		for ( j = 0 ; j < 3 ; j++ ) 
		{
			cgs.inlineModelMidpoints[breakPoint][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] );
		}
		breakPoint++;
		for(sub=1;sub<MAX_MODELS;sub++)
		{
			Com_sprintf(temp, MAX_QPATH, "*%d-%d", i, sub);
			cgs.inlineDrawModel[breakPoint] = cgi_R_RegisterModel( temp );
			if (!cgs.inlineDrawModel[breakPoint])
			{
				break;
			}
			cgi_R_ModelBounds( cgs.inlineDrawModel[breakPoint], mins, maxs );
			for ( j = 0 ; j < 3 ; j++ ) 
			{
				cgs.inlineModelMidpoints[breakPoint][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] );
			}
			breakPoint++;
		}
	}

	const char	*terrainInfo;
	int			terrainID;

	for(i = 1; i < MAX_TERRAINS; i++)
	{
		terrainInfo = CG_ConfigString( CS_TERRAINS + i );
		if ( !terrainInfo[0] )
		{
			break;
		}
		CG_LoadingString("Creating terrain");

		terrainID = cgi_CM_RegisterTerrain(terrainInfo);

		cgi_RMG_Init(terrainID, terrainInfo);

		// Send off the terrainInfo to the renderer
		cgi_RE_InitRendererTerrain( terrainInfo );
	}
}

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

/*
=================
CG_ConfigString
=================
*/
const char *CG_ConfigString( int index ) {
	if ( index < 0 || index >= MAX_CONFIGSTRINGS ) {
		CG_Error( "CG_ConfigString: bad index: %i", index );
	}
	return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ];
}

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

void CG_LinkCentsToGents(void)
{
	int	i;

	for(i = 0; i < MAX_GENTITIES; i++)
	{
		cg_entities[i].gent = &g_entities[i];
	}
}

/*
======================
CG_StartMusic

======================
*/
void CG_StartMusic( qboolean bForceStart ) {
	const char	*s;
	char	parm1[MAX_QPATH], parm2[MAX_QPATH];

	// start the background music
	s = (char *)CG_ConfigString( CS_MUSIC );
	Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) );
	Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) );

	cgi_S_StartBackgroundTrack( parm1, parm2, !bForceStart );
}

/*
======================
CG_GameStateReceived

Displays the info screen while loading media
======================
*/

int iCGResetCount=0;
qboolean qbVidRestartOccured = qfalse;

//===================
qboolean gbUseTheseValuesFromLoadSave = qfalse;	// MUST default to this
int gi_cg_forcepowerSelect;
int gi_cg_inventorySelect;
//===================


static void CG_GameStateReceived( void ) {
	// clear everything
	
	extern void CG_ClearAnimEvtCache( void );
	CG_ClearAnimEvtCache();	// else sound handles wrong after vid_restart

	qbVidRestartOccured = qtrue;
	iCGResetCount++;
	if (iCGResetCount == 1)	// this will only equal 1 first time, after each vid_restart it just gets higher.
	{						//	This non-clear is so the user can vid_restart during scrolling text without losing it.
		qbVidRestartOccured = qfalse;		
	}

	if (!qbVidRestartOccured)
	{
		CG_Init_CG();
		cg.weaponSelect = WP_NONE;
		cg.forcepowerSelect = FP_HEAL;
	}

	memset( cg_weapons, 0, sizeof(cg_weapons) );
	memset( cg_items, 0, sizeof(cg_items) );
	
	CG_LinkCentsToGents();

	if (gbUseTheseValuesFromLoadSave)
	{
		gbUseTheseValuesFromLoadSave = qfalse;	// ack
		cg.forcepowerSelect = gi_cg_forcepowerSelect;
		cg.inventorySelect	= gi_cg_inventorySelect;
	}


	// get the rendering configuration from the client system
	cgi_GetGlconfig( &cgs.glconfig );

/*	cgs.charScale = cgs.glconfig.vidHeight * (1.0/480.0);
	if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) {
		// wide screen
		cgs.bias = 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * (640.0/480.0) ) );
	}
	else {
		// no wide screen
		cgs.bias = 0;
	}
*/
	// get the gamestate from the client system
	cgi_GetGameState( &cgs.gameState );

	CG_ParseServerinfo();

	// load the new map
	cgs.media.levelLoad = cgi_R_RegisterShaderNoMip( "gfx/hud/mp_levelload" );
	CG_LoadingString( "collision map" );

	cgi_CM_LoadMap( cgs.mapname, qfalse );

	CG_RegisterSounds();

#ifdef _IMMERSION
	CG_RegisterForces();
#endif // _IMMERSION
	CG_RegisterGraphics();

	//jfm: moved down to preinit
//	CG_InitLocalEntities();
//	CG_InitMarkPolys();

	CG_LoadingString( "music" );
	CG_StartMusic( qfalse );

	// remove the last loading update
	cg.infoScreenText[0] = 0;

	CGCam_Init();

	CG_ClearLightStyles();

	// Okay so this doesn't exactly belong here
	cg.messageLitActive = qfalse;
	cg.forceHUDActive = qtrue;
	cg.forceHUDTotalFlashTime = 0;
	cg.forceHUDNextFlashTime = 0;

}

void CG_WriteTheEvilCGHackStuff(void)
{
	gi.AppendToSaveGame('FPSL', &cg.forcepowerSelect, sizeof(cg.forcepowerSelect));
	gi.AppendToSaveGame('IVSL', &cg.inventorySelect,  sizeof(cg.inventorySelect));

}
void CG_ReadTheEvilCGHackStuff(void)
{
	gi.ReadFromSaveGame('FPSL', (void *)&gi_cg_forcepowerSelect, sizeof(gi_cg_forcepowerSelect));
	gi.ReadFromSaveGame('IVSL', (void *)&gi_cg_inventorySelect,  sizeof(gi_cg_inventorySelect));
	gbUseTheseValuesFromLoadSave = qtrue;
}

/*
Ghoul2 Insert Start
*/

// initialise the cg_entities structure
void CG_Init_CG(void)
{
#ifdef _XBOX
	qboolean widescreen = cg.widescreen;
#endif
	memset( &cg, 0, sizeof(cg));
#ifdef _XBOX
	cg.widescreen = widescreen;
#endif
}


#ifdef _XBOX	// Enough for all maps right now
#define MAX_MISC_ENTS	1000
#else
#define MAX_MISC_ENTS	2000
#endif

typedef struct cgMiscEntData_s
{
	char		model[MAX_QPATH];
	qhandle_t	hModel;
	vec3_t		origin;
	vec3_t		angles;
	vec3_t		scale;
	float		radius;
	float		zOffset; //some models need a z offset for culling, because of stupid wrong model origins
} cgMiscEntData_t;

static cgMiscEntData_t	MiscEnts[MAX_MISC_ENTS]; //statically allocated for now.
static int				NumMiscEnts=0;

void CG_CreateMiscEntFromGent(gentity_t *ent, const vec3_t scale, float zOff)
{ //store the model data
	if (NumMiscEnts == MAX_MISC_ENTS)
	{
		Com_Error(ERR_DROP,"Maximum misc_model_static reached (%d)\n",MAX_MISC_ENTS);
		return;
	}

	if (!ent || !ent->model || !ent->model[0])
	{
		Com_Error(ERR_DROP, "misc_model_static with no model.");
		return;
	}
	const int len = strlen(ent->model);
	if (len < 4 || stricmp(&ent->model[len-4],".md3")!=0)
	{
		Com_Error(ERR_DROP, "misc_model_static model(%s) is not an md3.",ent->model);
		return;
	}
	cgMiscEntData_t	*MiscEnt = &MiscEnts[NumMiscEnts++];
	memset(MiscEnt, 0, sizeof(*MiscEnt));

	strcpy(MiscEnt->model, ent->model);
	VectorCopy(ent->s.angles,	MiscEnt->angles);
	VectorCopy(scale,			MiscEnt->scale);
	VectorCopy(ent->s.origin,	MiscEnt->origin);
	MiscEnt->zOffset = zOff;
}

#define VectorScaleVector(a,b,c)		(((c)[0]=(a)[0]*(b)[0]),((c)[1]=(a)[1]*(b)[1]),((c)[2]=(a)[2]*(b)[2]))
//call on standard model load to spawn the queued stuff
void CG_CreateMiscEnts(void)
{
	vec3_t		mins, maxs;

	int i;
	for (i=0; i < NumMiscEnts; i++)
	{
		cgMiscEntData_t	*MiscEnt = &MiscEnts[i];

		MiscEnt->hModel = cgi_R_RegisterModel(MiscEnt->model);
		if (MiscEnt->hModel == 0)
		{
			Com_Error(ERR_DROP, "misc_model_static failed to load model '%s'",MiscEnt->model);
			continue;
		}

		cgi_R_ModelBounds(MiscEnt->hModel, mins, maxs);

		VectorScaleVector(mins, MiscEnt->scale, mins);
		VectorScaleVector(maxs, MiscEnt->scale, maxs);
		MiscEnt->radius = DistanceSquared(mins, maxs);
	}
}

void CG_DrawMiscEnts(void)
{
	int			i;
	cgMiscEntData_t	*MiscEnt = MiscEnts;
	refEntity_t	refEnt; 
	vec3_t		difference;
	vec3_t		cullOrigin;

	memset (&refEnt, 0, sizeof(refEnt));
	refEnt.reType = RT_MODEL;
	refEnt.frame = 0;
	refEnt.renderfx = RF_LIGHTING_ORIGIN;
	for(i=0;i<NumMiscEnts;i++)
	{
		VectorCopy(MiscEnt->origin, cullOrigin);
		cullOrigin[2] += MiscEnt->zOffset+1.0f;

		if (gi.inPVS(cg.refdef.vieworg, cullOrigin))
		{
			VectorSubtract(MiscEnt->origin, cg.refdef.vieworg, difference);
			if (VectorLengthSquared(difference)-(MiscEnt->radius) <= 8192*8192/*RMG_distancecull.value*/)
			{ //fixme: need access to the real cull dist here
				refEnt.hModel = MiscEnt->hModel;
				AnglesToAxis( MiscEnt->angles, refEnt.axis );
				VectorCopy(MiscEnt->scale, refEnt.modelScale);
				VectorCopy(MiscEnt->origin, refEnt.origin);
				VectorCopy(cullOrigin, refEnt.lightingOrigin);
				ScaleModelAxis(&refEnt);
				cgi_R_AddRefEntityToScene(&refEnt);
			}
		}
		MiscEnt++;
	}
}

void CG_TransitionPermanent(void)
{
	centity_t	*cent = cg_entities;
	int			i;

	cg_numpermanents = 0;
	for(i=0;i<MAX_GENTITIES;i++,cent++)
	{
		if (cgi_GetDefaultState(i, &cent->currentState))
		{
			cent->nextState = &cent->currentState;
			VectorCopy (cent->currentState.origin, cent->lerpOrigin);
			VectorCopy (cent->currentState.angles, cent->lerpAngles);
			cent->currentValid = qtrue;

			cg_permanents[cg_numpermanents++] = cent;
		}
	}
}

/*
Ghoul2 Insert End
*/


/*
=================
CG_PreInit

Called when DLL loads (after subsystem restart, but before gamestate is received)
=================
*/
void CG_PreInit() {
	CG_Init_CG();

	memset( &cgs, 0, sizeof( cgs ) );
	iCGResetCount = 0;

	CG_RegisterCvars();

//moved from CG_GameStateReceived because it's loaded sooner now
	CG_InitLocalEntities();

#ifdef _XBOX	// I can't believe that this isn't necessary on PC, but I'll hold off
	NumMiscEnts = 0;
	memset( MiscEnts, 0, sizeof(MiscEnts) );
#endif

	CG_InitMarkPolys();


}

/*
=================
CG_Init

Called after every level change or subsystem restart
=================
*/
void CG_Init( int serverCommandSequence ) {
	cgs.serverCommandSequence = serverCommandSequence;

	cgi_Cvar_Set( "cg_drawHUD", "1" );

	// fonts...
	//
	cgs.media.charsetShader = cgi_R_RegisterShaderNoMip("gfx/2d/charsgrid_med");

	cgs.media.qhFontSmall = cgi_R_RegisterFont("ocr_a");
	cgs.media.qhFontMedium= cgi_R_RegisterFont("ergoec");

	cgs.media.whiteShader   = cgi_R_RegisterShader( "white" );
	cgs.media.loadTick		= cgi_R_RegisterShaderNoMip( "gfx/hud/load_tick" );
	cgs.media.loadTickCap	= cgi_R_RegisterShaderNoMip( "gfx/hud/load_tick_cap" );
	
	const char	*force_icon_files[NUM_FORCE_POWERS] = 
	{//icons matching enums forcePowers_t
		"gfx/mp/f_icon_lt_heal",		//FP_HEAL,
		"gfx/mp/f_icon_levitation",		//FP_LEVITATION,
		"gfx/mp/f_icon_speed",			//FP_SPEED,
		"gfx/mp/f_icon_push",			//FP_PUSH,
		"gfx/mp/f_icon_pull",			//FP_PULL,
		"gfx/mp/f_icon_lt_telepathy",	//FP_TELEPATHY,
		"gfx/mp/f_icon_dk_grip",		//FP_GRIP,
		"gfx/mp/f_icon_dk_l1",			//FP_LIGHTNING,
		"gfx/mp/f_icon_saber_throw",	//FP_SABERTHROW
		"gfx/mp/f_icon_saber_defend",	//FP_SABERDEFEND,
		"gfx/mp/f_icon_saber_attack",	//FP_SABERATTACK,
		"gfx/mp/f_icon_dk_rage",		//FP_RAGE,
		"gfx/mp/f_icon_lt_protect",		//FP_PROTECT,
		"gfx/mp/f_icon_lt_absorb",		//FP_ABSORB,
		"gfx/mp/f_icon_dk_drain",		//FP_DRAIN,
		"gfx/mp/f_icon_sight",			//FP_SEE,
	};

	// Precache inventory icons
	for ( int i=0;i<NUM_FORCE_POWERS;i++)
	{
		if (force_icon_files[i])
		{
			force_icons[i] = cgi_R_RegisterShaderNoMip( force_icon_files[i] );
		}
	}

	CG_LoadHudMenu();      // load new hud stuff

	cgi_UI_Menu_OpenByName("loadscreen");

	//rww - Moved from CG_GameStateReceived (we don't want to clear perm ents)
	memset( cg_entities, 0, sizeof(cg_entities) );

	CG_TransitionPermanent();

	cg.loadLCARSStage		= 0;

	CG_GameStateReceived();

	CG_InitConsoleCommands();

	//
	// the game server will interpret these commands, which will be automatically
	// forwarded to the server after they are not recognized locally
	//
	cgi_AddCommand ("kill");
	cgi_AddCommand ("give");
	cgi_AddCommand ("god");
	cgi_AddCommand ("notarget");
	cgi_AddCommand ("noclip");
	cgi_AddCommand ("undying");
	cgi_AddCommand ("setviewpos");
	cgi_AddCommand ("setobjective");
	cgi_AddCommand ("viewobjective");


//ConsoleCommand in g_svcmds.cpp
	cgi_AddCommand ("entitylist");
	cgi_AddCommand ("nav");
	cgi_AddCommand ("npc");

	cgi_AddCommand ("saberColor");
	cgi_AddCommand ("saber");
	cgi_AddCommand ("saberblade");

	cgi_AddCommand ("runscript");

	cgi_AddCommand ("playerteam");
	cgi_AddCommand ("playermodel");

	cgi_AddCommand ("saberAttackCycle");

	cg.weaponPickupTextTime = 0;

	cg.missionInfoFlashTime = 0;
	cg.missionStatusShow = qfalse;

	cg.missionFailedScreen = qfalse;	// Screen hasn't been opened.
	cgi_UI_MenuCloseAll();	// So the loadscreen menu will turn off just after the opening snapshot
}

/*
=================
CG_Shutdown

Called before every level change or subsystem restart
=================
*/
void CG_Shutdown( void ) 
{
	in_camera = false;
	FX_Free();
}

//// DEBUG STUFF
/*
-------------------------
CG_DrawNode
-------------------------
*/
void CG_DrawNode( vec3_t origin, int type )
{
	localEntity_t	*ex;

	ex = CG_AllocLocalEntity();

	ex->leType = LE_SPRITE;
	ex->startTime = cg.time;
	ex->endTime = ex->startTime + 51;
	VectorCopy( origin, ex->refEntity.origin );
		
	ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/misc/nav_node" );

	float	scale = 16.0f;

	switch ( type )
	{
	case NODE_NORMAL:
		ex->color[0] = 255;
		ex->color[1] = 255;
		ex->color[2] = 0;
		break;

	case NODE_FLOATING:
		ex->color[0] = 0;
		ex->color[1] = 255;
		ex->color[2] = 255;
		scale += 16.0f;
		break;

	case NODE_GOAL:
		ex->color[0] = 255;
		ex->color[1] = 0;
		ex->color[2] = 0;
		scale += 16.0f;
		break;

	case NODE_NAVGOAL:
		ex->color[0] = 0;
		ex->color[1] = 255;
		ex->color[2] = 0;
		break;
	}
	
	ex->radius = scale;
}

/*
-------------------------
CG_DrawRadius
-------------------------
*/

void CG_DrawRadius( vec3_t origin, unsigned int radius, int type )
{
	localEntity_t	*ex;

	ex = CG_AllocLocalEntity();

	ex->leType = LE_QUAD;
	ex->radius = radius;
	ex->startTime = cg.time;
	ex->endTime = ex->startTime + 51;
	VectorCopy( origin, ex->refEntity.origin );
		
	ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/misc/nav_radius" );

	switch ( type )
	{
	case NODE_NORMAL:
		ex->color[0] = 255;
		ex->color[1] = 255;
		ex->color[2] = 0;
		break;

	case NODE_FLOATING:
		ex->color[0] = 0;
		ex->color[1] = 255;
		ex->color[2] = 255;
		break;

	case NODE_GOAL:
		ex->color[0] = 255;
		ex->color[1] = 0;
		ex->color[2] = 0;
		break;

	case NODE_NAVGOAL:
		ex->color[0] = 0;
		ex->color[1] = 255;
		ex->color[2] = 0;
		break;
	}
}

/*
-------------------------
CG_DrawEdge
-------------------------
*/

void CG_DrawEdge( vec3_t start, vec3_t end, int type )
{
	switch ( type )
	{	
		// NAVIGATION EDGES BETWEEN POINTS
		//=====================================
	case EDGE_NORMAL:
		{
			FX_AddLine( start, end, 8.0f, 4.0f, 0.0f, 0.5f, 0.5f, 51, cgi_R_RegisterShader( "gfx/misc/nav_line" ), 0 );
		}
		break;
	case EDGE_LARGE:
		{
			FX_AddLine( start, end, 8.0f, 15.0f, 0.0f, 0.5f, 0.5f, 51, cgi_R_RegisterShader( "gfx/misc/nav_line" ), 0 );
		}
		break;	
	case EDGE_BLOCKED:
		{
			vec3_t	color = { 255, 0, 0 };	// RED
			FX_AddLine( start, end, 8.0f, 4.0f, 0.0f, 0.5f, 0.5f, color, color, 51, cgi_R_RegisterShader( "gfx/misc/nav_line" ), 0 );
		}
		break;
	case EDGE_FLY:
		{
			vec3_t	color = { 0, 255, 255 };// GREEN
			FX_AddLine( start, end, 8.0f, 4.0f, 0.0f, 0.5f, 0.5f, color, color, 51, cgi_R_RegisterShader( "gfx/misc/nav_line" ), 0 );
		}
		break;
	case EDGE_JUMP:
		{
			vec3_t	color = { 0, 0, 255 };	// BLUE
			FX_AddLine( start, end, 8.0f, 4.0f, 0.0f, 0.5f, 0.5f, color, color, 51, cgi_R_RegisterShader( "gfx/misc/nav_line" ), 0 );
		}
		break;



		// EDGE NODES
		//=====================================
	case EDGE_NODE_NORMAL:
		{
			vec3_t	color = { 155, 155, 155 };
			FX_AddLine( start, end, 2.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_NODE_FLOATING:
		{
			vec3_t	color = { 155, 155, 0 };
			FX_AddLine( start, end, 2.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_NODE_GOAL:
		{
			vec3_t	color = { 0, 0, 155 };
			FX_AddLine( start, end, 2.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_NODE_COMBAT:
		{
			vec3_t	color = { 155, 0, 0 };
			FX_AddLine( start, end, 2.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;




		// NEAREST NAV 
		//=====================================
	case EDGE_NEARESTVALID:
		{
			vec3_t	color = { 155, 155, 155 };	// WHITE
			FX_AddLine( -1, start, end, 1.0f, 1.0f, 0, 1.0f, 1.0f, FX_ALPHA_LINEAR, color, color, 0, 51, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0, 0 );
		}
		break;

	case EDGE_NEARESTINVALID:
		{
			vec3_t	color = { 155, 0, 0 };	// WHITE
			FX_AddLine( -1, start, end, 1.0f, 1.0f, 0, 1.0f, 1.0f, FX_ALPHA_LINEAR, color, color, 0, 51, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0, 0 );
		}
		break;


		// NEAREST NAV CELLS
		//=====================================
	case EDGE_CELL:
		{
			vec3_t	color = { 155, 155, 155 };	// WHITE
			FX_AddLine( -1, start, end, 1.0f, 1.0f, 0, 1.0f, 1.0f, FX_ALPHA_LINEAR, color, color, 0, 51, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0, 0 );
		}
		break;
	case EDGE_CELL_EMPTY:
		{	
			vec3_t	color = { 255, 0, 0 };		// RED
			FX_AddLine( -1, start, end, 1.0f, 1.0f, 0, 1.0f, 1.0f, FX_ALPHA_LINEAR, color, color, 0, 51, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0, 0 );
		}
		break;


		// ACTOR PATHS
		//=============
	case EDGE_PATH:
		{
  			vec3_t	color = { 0, 0, 155 };		// WHITE
 			FX_AddLine( start, end, 5.0f, 5.0f, 0.0f, 0.5f, 0.5f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/nav_arrow_new" ), 0 );
		}
		break;

	case EDGE_PATHBLOCKED:
		{
 			vec3_t	color = { 255, 0, 0 };		// RED
			FX_AddLine( start, end, 5.0f, 5.0f, 0.0f, 0.5f, 0.5f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/nav_arrow_new" ), 0 );
			break;
		}

	case EDGE_FOLLOWPOS:
		{
			vec3_t	color = { 0, 255, 0 };		// GREEN
			FX_AddLine( start, end, 5.0f, 5.0f, 0.0f, 0.5f, 0.5f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/nav_arrow_new" ), 0 );
			break;
		}


		// STEERING
		//=====================================
	case EDGE_IMPACT_SAFE:
		{
			vec3_t	color = { 155, 155, 155 };	// WHITE
			FX_AddLine( start, end, 2.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_IMPACT_POSSIBLE:
		{
			vec3_t	color = { 255, 0, 0 };		// RED
			FX_AddLine( start, end, 2.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_VELOCITY:
		{
			vec3_t	color = { 0, 255, 0 };		// GREEN
			FX_AddLine( start, end, 4.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_THRUST:
		{
			vec3_t	color = { 0, 0, 255 };		// BLUE
			FX_AddLine( start, end, 3.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 151, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;


		// MISC Colored Lines
		//=====================================
	case EDGE_WHITE_ONESECOND:
		{
			vec3_t	color = { 155, 155, 155 };	// WHITE
			FX_AddLine( start, end, 3.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 1051, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_WHITE_TWOSECOND:
		{
			vec3_t	color = { 155, 155, 155 };	// WHITE
			FX_AddLine( start, end, 3.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 1051, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_RED_ONESECOND:
		{
			vec3_t	color = { 255, 0, 0 };		// RED
			FX_AddLine( start, end, 3.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 2051, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;
	case EDGE_RED_TWOSECOND:
		{
			vec3_t	color = { 255, 0, 0 };		// RED
			FX_AddLine( start, end, 3.0f, 1.0f, 0.0f, 1.0f, 1.0f, color, color, 2051, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), 0 );
		}
		break;

	default:
		break;
	}
}

/*
-------------------------
CG_DrawCombatPoint
-------------------------
*/

void CG_DrawCombatPoint( vec3_t origin, int type )
{
	localEntity_t	*ex;

	ex = CG_AllocLocalEntity();

	ex->leType = LE_SPRITE;
	ex->startTime = cg.time;
	ex->radius = 8;
	ex->endTime = ex->startTime + 51;
	VectorCopy( origin, ex->refEntity.origin );
		
	ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/misc/nav_cpoint" );

	ex->color[0] = 255;
	ex->color[1] = 0;
	ex->color[2] = 255;

/*
	switch( type )
	{
	case 0:	//FIXME: To shut up the compiler warning (more will be added here later of course)
	default:
		FX_AddSprite( origin, NULL, NULL, 8.0f, 0.0f, 1.0f, 1.0f, color, color, 0.0f, 0.0f, 51, cgi_R_RegisterShader( "gfx/misc/nav_cpoint" ) );
		break;
	}
*/
}

/*
-------------------------
CG_DrawAlert
-------------------------
*/

void CG_DrawAlert( vec3_t origin, float rating )
{
	vec3_t	drawPos;

	VectorCopy( origin, drawPos );
	drawPos[2] += 48;

	vec3_t	startRGB;

	//Fades from green at 0, to red at 1
	startRGB[0] = rating;
	startRGB[1] = 1 - rating;
	startRGB[2] = 0;

	FX_AddSprite( drawPos, NULL, NULL, 16, 0.0f, 1.0f, 1.0f, startRGB, startRGB, 0, 0, 50, cgs.media.whiteShader );
}


#define MAX_MENUDEFFILE				4096

//
// ==============================
// new hud stuff ( mission pack )
// ==============================
//
qboolean CG_Asset_Parse(const char **p) 
{
	const char *token;
	const char *tempStr;
	int pointSize;

	token = COM_ParseExt(p, qtrue);

	if (!token)
	{
		return qfalse;
	}

	if (Q_stricmp(token, "{") != 0) 
	{
		return qfalse;
	}
    
	while ( 1 ) 
	{
		token = COM_ParseExt(p, qtrue);
		if (!token)
		{
			return qfalse;
		}

		if (Q_stricmp(token, "}") == 0) 
		{
			return qtrue;
		}

		// font
		if (Q_stricmp(token, "font") == 0) 
		{
/*
			int pointSize;

			cgi_UI_Parse_String(tempStr);
			cgi_UI_Parse_Int(&pointSize);

			if (!tempStr || !pointSize) 
			{
				return qfalse;
			}
*/
//			cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont);
			continue;
		}

		// smallFont
		if (Q_stricmp(token, "smallFont") == 0) 
		{
			if (!COM_ParseString(p, &tempStr) || !COM_ParseInt(p, &pointSize)) 
			{
				return qfalse;
			}
//			cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont);
			continue;
		}

		// smallFont - because the HUD file needs it for MP.
		if (Q_stricmp(token, "small2Font") == 0) 
		{
			if (!COM_ParseString(p, &tempStr) || !COM_ParseInt(p, &pointSize)) 
			{
				return qfalse;
			}
//			cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont);
			continue;
		}

		// font
		if (Q_stricmp(token, "bigfont") == 0) 
		{
			int pointSize;
			if (!COM_ParseString(p, &tempStr) || !COM_ParseInt(p, &pointSize)) 
			{
				return qfalse;
			}
//			cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont);
			continue;
		}

		// gradientbar
		if (Q_stricmp(token, "gradientbar") == 0) 
		{
			if (!COM_ParseString(p, &tempStr)) 
			{
				return qfalse;
			}
//			cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr);
			continue;
		}

		// enterMenuSound
		if (Q_stricmp(token, "menuEnterSound") == 0) 
		{
			if (!COM_ParseString(p, &tempStr)) 
			{
				return qfalse;
			}
//			cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr );
			continue;
		}

		// exitMenuSound
		if (Q_stricmp(token, "menuExitSound") == 0) 
		{
			if (!COM_ParseString(p, &tempStr)) 
			{
				return qfalse;
			}
//			cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr );
			continue;
		}

		// itemFocusSound
		if (Q_stricmp(token, "itemFocusSound") == 0) 
		{
			if (!COM_ParseString(p, &tempStr)) 
			{
				return qfalse;
			}
//			cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr );
			continue;
		}

		// menuBuzzSound
		if (Q_stricmp(token, "menuBuzzSound") == 0) 
		{
			if (!COM_ParseString(p, &tempStr)) 
			{
				return qfalse;
			}
//			cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr );
			continue;
		}

		if (Q_stricmp(token, "cursor") == 0) 
		{
//			if (!COM_ParseString(p, &cgDC.Assets.cursorStr)) 
//			{
//				return qfalse;
//			}
//			cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr);
			continue;
		}

		if (Q_stricmp(token, "fadeClamp") == 0) 
		{
//			if (!COM_ParseFloat(p, &cgDC.Assets.fadeClamp)) 
//			{
//				return qfalse;
//			}
			continue;
		}

		if (Q_stricmp(token, "fadeCycle") == 0) 
		{
//			if (!COM_ParseInt(p, &cgDC.Assets.fadeCycle)) 
//			{
//				return qfalse;
//			}
			continue;
		}

		if (Q_stricmp(token, "fadeAmount") == 0) 
		{
//			if (!COM_ParseFloat(p, &cgDC.Assets.fadeAmount)) 
//			{
//				return qfalse;
//			}
			continue;
		}

		if (Q_stricmp(token, "shadowX") == 0) 
		{
//			if (!COM_ParseFloat(p, &cgDC.Assets.shadowX)) 
//			{
//				return qfalse;
//			}
			continue;
		}

		if (Q_stricmp(token, "shadowY") == 0) 
		{
//			if (!COM_ParseFloat(p, &cgDC.Assets.shadowY)) 
//			{
//				return qfalse;
//			}
			continue;
		}

		if (Q_stricmp(token, "shadowColor") == 0) 
		{
			/*
			if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) 
			{
				return qfalse;
			}
			cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3];
			*/
			continue;
		}
	}
	return qfalse; // bk001204 - why not?
}

void cgi_UI_EndParseSession(char *buf);

/*
=================
CG_ParseMenu();
=================
*/
void CG_ParseMenu(const char *menuFile) 
{
	char			*token;
	int				result;
	char			*buf,*p;

	//Com_Printf("Parsing menu file:%s\n", menuFile);

	result = cgi_UI_StartParseSession((char *) menuFile,&buf);

	if (!result)
	{
		Com_Printf("Unable to load hud menu file:%s. Using default ui/testhud.menu.\n", menuFile);
		result = cgi_UI_StartParseSession("ui/testhud.menu",&buf);
		if (!result)
		{
			Com_Printf("Unable to load default ui/testhud.menu.\n");
			return;
		}
	}

	p = buf;
	while ( 1 ) 
	{
		cgi_UI_ParseExt(&token);

		if (!*token)	// All done?
		{
			break;
		}

		//if ( Q_stricmp( token, "{" ) ) {
		//	Com_Printf( "Missing { in menu file\n" );
		//	break;
		//}

		//if ( menuCount == MAX_MENUS ) {
		//	Com_Printf( "Too many menus!\n" );
		//	break;
		//}

//		if ( *token == '}' ) 
//		{
//			break;
//		}

		if (Q_stricmp(token, "assetGlobalDef") == 0) 
		{
			/*
			if (CG_Asset_Parse(handle)) 
			{
				continue;
			} 
			else 
			{
				break;
			}
			*/
		}


		if (Q_stricmp(token, "menudef") == 0) 
		{
			// start a new menu
			cgi_UI_Menu_New(p);
		}
	}

	cgi_UI_EndParseSession(buf);

}

/*
=================
CG_Load_Menu();

=================
*/
qboolean CG_Load_Menu( const char **p) 
{
	const char *token;

	token = COM_ParseExt(p, qtrue);

	if (token[0] != '{') 
	{
		return qfalse;
	}

	while ( 1 ) 
	{

		token = COM_ParseExt(p, qtrue);
    
		if (Q_stricmp(token, "}") == 0) 
		{
			return qtrue;
		}

		if ( !token || token[0] == 0 ) 
		{
			return qfalse;
		}

		CG_ParseMenu(token); 
	}
	return qfalse;
}

/*
=================
CG_LoadMenus();

=================
*/
void CG_LoadMenus(const char *menuFile) 
{
	const char	*token;
	const char	*p;
	int	len, start;
	fileHandle_t	f;
	char buf[MAX_MENUDEFFILE];

	start = cgi_Milliseconds();

	len = cgi_FS_FOpenFile( menuFile, &f, FS_READ );
	if ( !f ) 
	{
		cgi_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) );
		len = cgi_FS_FOpenFile( "ui/jahud.txt", &f, FS_READ );
		if (!f) 
		{
			cgi_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) );
		}
	}

	if ( len >= MAX_MENUDEFFILE ) 
	{
		cgi_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) );
		cgi_FS_FCloseFile( f );
		return;
	}

	cgi_FS_Read( buf, len, f );
	buf[len] = 0;
	cgi_FS_FCloseFile( f );
	
//	COM_Compress(buf);

//	cgi_UI_Menu_Reset();

	p = buf;

	while ( 1 ) 
	{
		token = COM_ParseExt( &p, qtrue );
		if( !token || token[0] == 0 || token[0] == '}') 
		{
			break;
		}

		if ( Q_stricmp( token, "}" ) == 0 ) 
		{
			break;
		}

		if (Q_stricmp(token, "loadmenu") == 0) 
		{
			if (CG_Load_Menu(&p)) 
			{
				continue;
			} 
			else 
			{
				break;
			}
		}
	}

	//Com_Printf("UI menu load time = %d milli seconds\n", cgi_Milliseconds() - start);
}

/*
=================
CG_LoadHudMenu();

=================
*/
void CG_LoadHudMenu(void) 
{
	const char *hudSet;
/*
	cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
	cgDC.setColor = &trap_R_SetColor;
	cgDC.drawHandlePic = &CG_DrawPic;
	cgDC.drawStretchPic = &trap_R_DrawStretchPic;
	cgDC.drawText = &CG_Text_Paint;
	cgDC.textWidth = &CG_Text_Width;
	cgDC.textHeight = &CG_Text_Height;
	cgDC.registerModel = &trap_R_RegisterModel;
	cgDC.modelBounds = &trap_R_ModelBounds;
	cgDC.fillRect = &CG_FillRect;
	cgDC.drawRect = &CG_DrawRect;   
	cgDC.drawSides = &CG_DrawSides;
	cgDC.drawTopBottom = &CG_DrawTopBottom;
	cgDC.clearScene = &trap_R_ClearScene;
	cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene;
	cgDC.renderScene = &trap_R_RenderScene;
	cgDC.registerFont = &trap_R_RegisterFont;
	cgDC.ownerDrawItem = &CG_OwnerDraw;
	cgDC.getValue = &CG_GetValue;
	cgDC.ownerDrawVisible = &CG_OwnerDrawVisible;
	cgDC.runScript = &CG_RunMenuScript;
	cgDC.getTeamColor = &CG_GetTeamColor;
	cgDC.setCVar = trap_Cvar_Set;
	cgDC.getCVarString = trap_Cvar_VariableStringBuffer;
	cgDC.getCVarValue = CG_Cvar_Get;
	cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor;
	cgDC.startLocalSound = &trap_S_StartLocalSound;
	cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey;
	cgDC.feederCount = &CG_FeederCount;
	cgDC.feederItemImage = &CG_FeederItemImage;
	cgDC.feederItemText = &CG_FeederItemText;
	cgDC.feederSelection = &CG_FeederSelection;
	cgDC.Error = &Com_Error; 
	cgDC.Print = &Com_Printf; 
	cgDC.ownerDrawWidth = &CG_OwnerDrawWidth;
	cgDC.registerSound = &trap_S_RegisterSound;
	cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack;
	cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack;
	cgDC.playCinematic = &CG_PlayCinematic;
	cgDC.stopCinematic = &CG_StopCinematic;
	cgDC.drawCinematic = &CG_DrawCinematic;
	cgDC.runCinematicFrame = &CG_RunCinematicFrame;
*/	
//	Init_Display(&cgDC);

//	cgi_UI_String_Init();

//	cgi_UI_Menu_Reset();
	
	hudSet = cg_hudFiles.string;
	if (hudSet[0] == '\0') 
	{
		hudSet = "ui/jahud.txt";
	}

	CG_LoadMenus(hudSet);
}


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

INVENTORY SELECTION

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

/*
===============
CG_InventorySelectable
===============
*/
static inline qboolean CG_InventorySelectable( int index) 
{
	if (cg.snap->ps.inventory[index])	// Is there any in the inventory?
	{
		return qtrue;
	}

	return qfalse;
}


/*
===============
SetInventoryTime
===============
*/
static inline void SetInventoryTime(void)
{
	if (((cg.weaponSelectTime + WEAPON_SELECT_TIME) > cg.time) ||	// The Weapon HUD was currently active to just swap it out with Force HUD
		((cg.forcepowerSelectTime + WEAPON_SELECT_TIME) > cg.time))	// The Force HUD was currently active to just swap it out with Force HUD
	{
		cg.weaponSelectTime = 0;
		cg.forcepowerSelectTime = 0;
		cg.inventorySelectTime = cg.time + 130.0f;
	}
	else
	{
		cg.inventorySelectTime = cg.time;
	}
}

/*
===============
CG_DPPrevInventory_f
===============
*/
void CG_DPPrevInventory_f( void ) 
{
	int		i;

	if ( !cg.snap ) 
	{
		return;
	}

	const int original = cg.DataPadInventorySelect;

	for ( i = 0 ; i < INV_MAX ; i++ ) 
	{
		cg.DataPadInventorySelect--;

		if ((cg.DataPadInventorySelect < INV_ELECTROBINOCULARS) || (cg.DataPadInventorySelect >= INV_MAX))
		{ 
			cg.DataPadInventorySelect = (INV_MAX - 1); 
		}
		
		if ( CG_InventorySelectable( cg.DataPadInventorySelect ) )
		{	
			return;
		}
	}

	cg.DataPadInventorySelect = original;
}
/*
===============
CG_DPNextInventory_f
===============
*/
void CG_DPNextInventory_f( void ) 
{
	int		i;

	if ( !cg.snap ) 
	{
		return;
	}

	const int original = cg.DataPadInventorySelect;

	for ( i = 0 ; i < INV_MAX ; i++ ) 
	{
		cg.DataPadInventorySelect++;

		if ((cg.DataPadInventorySelect < INV_ELECTROBINOCULARS) || (cg.DataPadInventorySelect >= INV_MAX))
		{ 
			cg.DataPadInventorySelect = INV_ELECTROBINOCULARS; 
		}

		if ( CG_InventorySelectable( cg.DataPadInventorySelect ) && (inv_icons[cg.DataPadInventorySelect])) 
		{	
			return;
		}
	}

	cg.DataPadInventorySelect = original;
}

/*
===============
CG_NextInventory_f
===============
*/
void CG_NextInventory_f( void ) 
{
	int		i;
	float	*color;

	if ( !cg.snap ) 
	{
		return;
	}

	// The first time it's been hit so just show inventory but don't advance in inventory.
	color = CG_FadeColor( cg.inventorySelectTime, WEAPON_SELECT_TIME );	
	if ( !color )	
	{
		SetInventoryTime();
		return;
	}

	const int original = cg.inventorySelect;

	for ( i = 0 ; i < INV_MAX ; i++ ) 
	{
		cg.inventorySelect++;

		if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
		{ 
			cg.inventorySelect = INV_ELECTROBINOCULARS; 
		}

		if ( CG_InventorySelectable( cg.inventorySelect ) && (inv_icons[cg.inventorySelect])) 
		{	
			cgi_S_StartSound (NULL, 0, CHAN_AUTO, cgs.media.selectSound2 );
			SetInventoryTime();
			return;
		}
	}

	cg.inventorySelect = original;
}

/*
===============
CG_UseInventory_f
===============
*/
/*
this func was moved to Cmd_UseInventory_f in g_cmds.cpp
*/

/*
===============
CG_PrevInventory_f
===============
*/
void CG_PrevInventory_f( void ) 
{
	int		i;
	float	*color;

	if ( !cg.snap ) 
	{
		return;
	}

	// The first time it's been hit so just show inventory but don't advance in inventory.
	color = CG_FadeColor( cg.inventorySelectTime, WEAPON_SELECT_TIME );	
	if ( !color )	
	{
		SetInventoryTime();
		return;
	}

	const int original = cg.inventorySelect;

	for ( i = 0 ; i < INV_MAX ; i++ ) 
	{
		cg.inventorySelect--;

		if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
		{ 
			cg.inventorySelect = (INV_MAX - 1); 
		}
		
		if ( CG_InventorySelectable( cg.inventorySelect ) && (inv_icons[cg.inventorySelect])) 
		{	
			cgi_S_StartSound (NULL, 0, CHAN_AUTO, cgs.media.selectSound2 );
			SetInventoryTime();
			return;
		}
	}

	cg.inventorySelect = original;
}


/*
===================
FindInventoryItemTag
===================
*/
gitem_t *FindInventoryItemTag(int tag)
{
	int		i;

	for ( i = 1 ; i < bg_numItems ; i++ ) 
	{
		if ( bg_itemlist[i].giTag == tag && bg_itemlist[i].giType == IT_HOLDABLE ) // I guess giTag's aren't unique amongst items..must also make sure it's a holdable
		{
			return &bg_itemlist[i];
		}
	}

	return (0);
}




/*
===================
CG_DrawInventorySelect
===================
*/
void CG_DrawInventorySelect( void ) 
{
	int				i;
	int				holdCount,iconCnt;
	int				sideLeftIconCnt,sideRightIconCnt;
	int				count;
	int				holdX;
	int				height;
//	int				tag;
	float			addX;
	vec4_t			textColor = { .312f, .75f, .621f, 1.0f };
	char			text[1024]={0};

	// don't display if dead
	if ( cg.predicted_player_state.stats[STAT_HEALTH] <= 0 || ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )) 
	{
		return;
	}

	if ((cg.inventorySelectTime+WEAPON_SELECT_TIME)<cg.time)	// Time is up for the HUD to display
	{
		return;
	}

	int x2,y2,w2,h2;
	if (!cgi_UI_GetMenuInfo("inventoryselecthud",&x2,&y2,&w2,&h2))
	{
		return;
	}

	cg.iconSelectTime = cg.inventorySelectTime;

	// showing weapon select clears pickup item display, but not the blend blob
	cg.itemPickupTime = 0;

	// count the number of items owned
	count = 0;
	for ( i = 0 ; i < INV_MAX ; i++ ) 
	{
		if (CG_InventorySelectable(i) && inv_icons[i]) 
		{
			count++;
		}
	}

	if (!count)
	{
		cgi_SP_GetStringTextString("SP_INGAME_EMPTY_INV",text, sizeof(text) );
		int w = cgi_R_Font_StrLenPixels( text, cgs.media.qhFontSmall, 1.0f );	
		int x = ( SCREEN_WIDTH - w ) / 2;
		CG_DrawProportionalString(x, y2 + 22, text, CG_CENTER | CG_SMALLFONT, colorTable[CT_ICON_BLUE]);
		return;
	}

	const int sideMax = 3;	// Max number of icons on the side

	// Calculate how many icons will appear to either side of the center one
	holdCount = count - 1;	// -1 for the center icon
	if (holdCount == 0)			// No icons to either side
	{
		sideLeftIconCnt = 0;
		sideRightIconCnt = 0;
	}
	else if (count > (2*sideMax))	// Go to the max on each side
	{
		sideLeftIconCnt = sideMax;
		sideRightIconCnt = sideMax;
	}
	else							// Less than max, so do the calc
	{
		sideLeftIconCnt = holdCount/2;
		sideRightIconCnt = holdCount - sideLeftIconCnt;
	}

	i = cg.inventorySelect - 1;
	if (i<0)
	{
		i = INV_MAX-1;
	}

	const int smallIconSize = 40;
	const int bigIconSize = 80;
	const int pad = 16;

	const int x = 320;
	const int y = 410;

	// Left side ICONS
	// Work backwards from current icon
	holdX = x - ((bigIconSize/2) + pad + smallIconSize);
	height = smallIconSize * cg.iconHUDPercent;
	addX = (float) smallIconSize * .75;

	for (iconCnt=0;iconCnt<sideLeftIconCnt;i--)
	{
		if (i<0)
		{
			i = INV_MAX-1;
		}

		if ((!CG_InventorySelectable(i)) || (!inv_icons[i]))
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (inv_icons[i])
		{
			cgi_R_SetColor(NULL);
			CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, inv_icons[i] );

			cgi_R_SetColor(colorTable[CT_ICON_BLUE]);
			CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, 
				NUM_FONT_SMALL,qfalse);

			holdX -= (smallIconSize+pad);
		}
	}

	// Current Center Icon
	height = bigIconSize * cg.iconHUDPercent;
	if (inv_icons[cg.inventorySelect])
	{
		cgi_R_SetColor(NULL);
		CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, inv_icons[cg.inventorySelect] );
		addX = (float) bigIconSize * .75;
		cgi_R_SetColor(colorTable[CT_ICON_BLUE]);
		CG_DrawNumField ((x-(bigIconSize/2)) + addX, y, 2, cg.snap->ps.inventory[cg.inventorySelect], 6, 12, 
			NUM_FONT_SMALL,qfalse);

		if (inv_names[cg.inventorySelect])
		{
			// FIXME: This is ONLY a temp solution, the icon stuff, etc, should all just use items.dat for everything
			gitem_t *item = FindInventoryItemTag( cg.inventorySelect );

			if ( item && item->classname && item->classname[0] )
			{
				char itemName[256], data[1024]; // FIXME: do these really need to be this large??  does it matter?

				sprintf( itemName, "SP_INGAME_%s",	item->classname );
			
				if ( cgi_SP_GetStringTextString( itemName, data, sizeof( data )))
				{
					int w = cgi_R_Font_StrLenPixels( data, cgs.media.qhFontSmall, 1.0f );	
					int x = ( SCREEN_WIDTH - w ) / 2;

					cgi_R_Font_DrawString( x, (SCREEN_HEIGHT - 24), data, textColor, cgs.media.qhFontSmall, -1, 1.0f);
				}
			}
		}
	}

	i = cg.inventorySelect + 1;
	if (i> INV_MAX-1)
	{
		i = 0;
	}

	// Right side ICONS
	// Work forwards from current icon
	holdX = x + (bigIconSize/2) + pad;
	height = smallIconSize * cg.iconHUDPercent;
	addX = (float) smallIconSize * .75;
	for (iconCnt=0;iconCnt<sideRightIconCnt;i++)
	{
		if (i> INV_MAX-1)
		{
			i = 0;
		}

		if ((!CG_InventorySelectable(i)) || (!inv_icons[i]))
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (inv_icons[i])
		{
			cgi_R_SetColor(NULL);
			CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, inv_icons[i] );

			cgi_R_SetColor(colorTable[CT_ICON_BLUE]);
			CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, 
				NUM_FONT_SMALL,qfalse);

			holdX += (smallIconSize+pad);
		}
	}
}

int cgi_UI_GetItemText(char *menuFile,char *itemName, char *text);

char *inventoryDesc[15] = 
{
"NEURO_SAAV_DESC",
"BACTA_DESC",
"INQUISITOR_DESC",
"LA_GOGGLES_DESC",
"PORTABLE_SENTRY_DESC",
"GOODIE_KEY_DESC",
"SECURITY_KEY_DP_DESC",
};


/*
===================
CG_DrawDataPadInventorySelect
===================
*/
void CG_DrawDataPadInventorySelect( void ) 
{
	int				i;
	int				holdCount,iconCnt;
	int				sideLeftIconCnt,sideRightIconCnt;
	int				count;
	int				holdX;
	int				height;
	float			addX;
	char			text[1024]={0};
	vec4_t			textColor = { .312f, .75f, .621f, 1.0f };


	// count the number of items owned
	count = 0;
	for ( i = 0 ; i < INV_MAX ; i++ ) 
	{
		if (CG_InventorySelectable(i) && inv_icons[i]) 
		{
			count++;
		}
	}


	if (!count)
	{
		cgi_SP_GetStringTextString("SP_INGAME_EMPTY_INV",text, sizeof(text) );
		int w = cgi_R_Font_StrLenPixels( text, cgs.media.qhFontSmall, 1.0f );	
		int x = ( SCREEN_WIDTH - w ) / 2;
		CG_DrawProportionalString(x, 300 + 22, text, CG_CENTER | CG_SMALLFONT, colorTable[CT_ICON_BLUE]);
		return;
	}

	const int sideMax = 3;	// Max number of icons on the side

	// Calculate how many icons will appear to either side of the center one
	holdCount = count - 1;	// -1 for the center icon
	if (holdCount == 0)			// No icons to either side
	{
		sideLeftIconCnt = 0;
		sideRightIconCnt = 0;
	}
	else if (count > (2*sideMax))	// Go to the max on each side
	{
		sideLeftIconCnt = sideMax;
		sideRightIconCnt = sideMax;
	}
	else							// Less than max, so do the calc
	{
		sideLeftIconCnt = holdCount/2;
		sideRightIconCnt = holdCount - sideLeftIconCnt;
	}

	i = cg.DataPadInventorySelect - 1;
	if (i<0)
	{
		i = INV_MAX-1;
	}


	const int smallIconSize = 40;
	const int bigIconSize = 80;
	const int bigPad = 64;
	const int pad = 32;

	const int centerXPos = 320;
	const int graphicYPos = 340;


	// Left side ICONS
	// Work backwards from current icon
	holdX = centerXPos - ((bigIconSize/2) + bigPad + smallIconSize);
	height = smallIconSize * cg.iconHUDPercent;
	addX = (float) smallIconSize * .75;

	for (iconCnt=0;iconCnt<sideLeftIconCnt;i--)
	{
		if (i<0)
		{
			i = INV_MAX-1;
		}

		if ((!CG_InventorySelectable(i)) || (!inv_icons[i]))
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (inv_icons[i])
		{
			cgi_R_SetColor(colorTable[CT_WHITE]);
			CG_DrawPic( holdX, graphicYPos+10, smallIconSize, smallIconSize, inv_icons[i] );

			cgi_R_SetColor(colorTable[CT_ICON_BLUE]);
			CG_DrawNumField (holdX + addX, graphicYPos + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, 
				NUM_FONT_SMALL,qfalse);

			holdX -= (smallIconSize+pad);
		}
	}

	// Current Center Icon
	height = bigIconSize * cg.iconHUDPercent;
	if (inv_icons[cg.DataPadInventorySelect])
	{
		cgi_R_SetColor(colorTable[CT_WHITE]);
		CG_DrawPic( centerXPos-(bigIconSize/2), (graphicYPos-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, inv_icons[cg.DataPadInventorySelect] );
		addX = (float) bigIconSize * .75;
		cgi_R_SetColor(colorTable[CT_ICON_BLUE]);
		CG_DrawNumField ((centerXPos-(bigIconSize/2)) + addX, graphicYPos, 2, cg.snap->ps.inventory[cg.DataPadInventorySelect], 6, 12, 
			NUM_FONT_SMALL,qfalse);

	}

	i = cg.DataPadInventorySelect + 1;
	if (i> INV_MAX-1)
	{
		i = 0;
	}

	// Right side ICONS
	// Work forwards from current icon
	holdX = centerXPos + (bigIconSize/2) + bigPad;
	height = smallIconSize * cg.iconHUDPercent;
	addX = (float) smallIconSize * .75;
	for (iconCnt=0;iconCnt<sideRightIconCnt;i++)
	{
		if (i> INV_MAX-1)
		{
			i = 0;
		}

		if ((!CG_InventorySelectable(i)) || (!inv_icons[i]))
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (inv_icons[i])
		{
			cgi_R_SetColor(colorTable[CT_WHITE]);
			CG_DrawPic( holdX, graphicYPos+10, smallIconSize, smallIconSize, inv_icons[i] );

			cgi_R_SetColor(colorTable[CT_ICON_BLUE]);
			CG_DrawNumField (holdX + addX, graphicYPos + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, 
				NUM_FONT_SMALL,qfalse);

			holdX += (smallIconSize+pad);
		}
	}

	// draw the weapon description
	if ((cg.DataPadInventorySelect>=0) && (cg.DataPadInventorySelect<13))
	{
		cgi_SP_GetStringTextString( va("SP_INGAME_%s",inventoryDesc[cg.DataPadInventorySelect]), text, sizeof(text) );

		if (text)
		{
			CG_DisplayBoxedText(70,50,500,300,text,
											cgs.media.qhFontSmall,
											0.7f,
											textColor	
											);
		}
	}
}

/*
===============
SetForcePowerTime
===============
*/
void SetForcePowerTime(void)
{
	if (((cg.weaponSelectTime + WEAPON_SELECT_TIME) > cg.time) ||	// The Weapon HUD was currently active to just swap it out with Force HUD
		((cg.inventorySelectTime + WEAPON_SELECT_TIME) > cg.time))	// The Inventory HUD was currently active to just swap it out with Force HUD
	{
		cg.weaponSelectTime = 0;
		cg.inventorySelectTime = 0;
		cg.forcepowerSelectTime = cg.time + 130.0f;
	}
	else
	{
		cg.forcepowerSelectTime = cg.time;
	}
}

int showPowers[MAX_SHOWPOWERS] = 
{
	FP_ABSORB,
	FP_HEAL,
	FP_PROTECT,
	FP_TELEPATHY,

	FP_SPEED,
	FP_PUSH,
	FP_PULL,
	FP_SEE,

	FP_DRAIN,
	FP_LIGHTNING,
	FP_RAGE,
	FP_GRIP,
};

char *showPowersName[MAX_SHOWPOWERS] = 
{
	"SP_INGAME_ABSORB2",
	"SP_INGAME_HEAL2",
	"SP_INGAME_PROTECT2",
	"SP_INGAME_MINDTRICK2",

	"SP_INGAME_SPEED2",
	"SP_INGAME_PUSH2",
	"SP_INGAME_PULL2",
	"SP_INGAME_SEEING2",

	"SP_INGAME_DRAIN2",
	"SP_INGAME_LIGHTNING2",
	"SP_INGAME_DARK_RAGE2",
	"SP_INGAME_GRIP2",
};

// Keep these with groups light side, core, and dark side
int showDataPadPowers[MAX_DPSHOWPOWERS] = 
{
	// Light side
	FP_ABSORB,
	FP_HEAL,
	FP_PROTECT,
	FP_TELEPATHY,

	// Core Powers
	FP_LEVITATION,
	FP_SPEED,
	FP_PUSH,
	FP_PULL,
	FP_SABERTHROW,
	FP_SABER_DEFENSE,
	FP_SABER_OFFENSE,
	FP_SEE,

	//Dark Side
	FP_DRAIN,
	FP_LIGHTNING,
	FP_RAGE,
	FP_GRIP,
};

/*
===============
ForcePower_Valid
===============
*/
qboolean ForcePower_Valid(int index)
{
	gentity_t	*player = &g_entities[0];

	assert (MAX_SHOWPOWERS == ( sizeof(showPowers)/sizeof(showPowers[0]) ));
	assert (index < MAX_SHOWPOWERS );	//is this a valid index?
	if (player->client->ps.forcePowersKnown & (1 << showPowers[index]) && 
		player->client->ps.forcePowerLevel[showPowers[index]])	// Does he have the force power?
	{
		return qtrue;
	}

	return qfalse;
}

/*
===============
CG_NextForcePower_f
===============
*/
void CG_NextForcePower_f( void ) 
{
	int		i;

	if ( !cg.snap || in_camera ) 
	{
		return;
	}

	SetForcePowerTime();

	if ((cg.forcepowerSelectTime + WEAPON_SELECT_TIME) < cg.time)
	{
		return;
	}

	const int original = cg.forcepowerSelect;

	for ( i = 0; i < MAX_SHOWPOWERS; i++ ) 
	{
		cg.forcepowerSelect++;

		if (cg.forcepowerSelect >= MAX_SHOWPOWERS)
		{ 
			cg.forcepowerSelect = 0; 
		}

		if (ForcePower_Valid(cg.forcepowerSelect))	// Does he have the force power?
		{
			cgi_S_StartSound (NULL, 0, CHAN_AUTO, cgs.media.selectSound2 );
			return;
		}
	}

	cg.forcepowerSelect = original;
}

/*
===============
CG_PrevForcePower_f
===============
*/
void CG_PrevForcePower_f( void ) 
{
	int		i;

	if ( !cg.snap || in_camera ) 
	{
		return;
	}

	SetForcePowerTime();

	if ((cg.forcepowerSelectTime + WEAPON_SELECT_TIME) < cg.time)
	{
		return;
	}

	const int original = cg.forcepowerSelect;

	for ( i = 0; i < MAX_SHOWPOWERS; i++ ) 
	{
		cg.forcepowerSelect--;

		if (cg.forcepowerSelect < 0)
		{ 
			cg.forcepowerSelect = MAX_SHOWPOWERS - 1; 
		}

		if (ForcePower_Valid(cg.forcepowerSelect))	// Does he have the force power?
		{
			cgi_S_StartSound (NULL, 0, CHAN_AUTO, cgs.media.selectSound2 );
			return;
		}
	}


	cg.forcepowerSelect = original;
}

/*
===================
CG_DrawForceSelect
===================
*/
void CG_DrawForceSelect( void ) 
{
	int		i;
	int		count;
	int		holdX;
	int		sideLeftIconCnt,sideRightIconCnt;
	int		holdCount,iconCnt;
	char	text[1024]={0};
	int		yOffset = 0;


	// don't display if dead
	if ( cg.predicted_player_state.stats[STAT_HEALTH] <= 0 || ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )) 
	{
		return;
	}

	if ((cg.forcepowerSelectTime+WEAPON_SELECT_TIME)<cg.time)	// Time is up for the HUD to display
	{
		return;
	}

#ifdef _XBOX
	if(CL_ExtendSelectTime()) {
		cg.forcepowerSelectTime = cg.time;
	}

	yOffset = -36;
#endif

	// count the number of powers owned
	count = 0;

	for (i=0; i<MAX_SHOWPOWERS; ++i)
	{
		if (ForcePower_Valid(i))
		{
			count++;
		}
	}

	if (count == 0)	// If no force powers, don't display
	{
		return;
	}

/*
	int x2,y2;
	if (!cgi_UI_GetMenuInfo("forceselecthud",&x2,&y2,&w2,&h2))
	{
		return;
	}
*/
	cg.iconSelectTime = cg.forcepowerSelectTime;

	// showing weapon select clears pickup item display, but not the blend blob
	cg.itemPickupTime = 0;

	const int sideMax = 3;	// Max number of icons on the side

	// Calculate how many icons will appear to either side of the center one
	holdCount = count - 1;	// -1 for the center icon
	if (holdCount == 0)			// No icons to either side
	{
		sideLeftIconCnt = 0;
		sideRightIconCnt = 0;
	}
	else if (count > (2*sideMax))	// Go to the max on each side
	{
		sideLeftIconCnt = sideMax;
		sideRightIconCnt = sideMax;
	}
	else							// Less than max, so do the calc
	{
		sideLeftIconCnt = holdCount/2;
		sideRightIconCnt = holdCount - sideLeftIconCnt;
	}

	const int smallIconSize = 30;
	const int bigIconSize = 60;
	const int pad = 12;

	const int x = 320;
	const int y = 425;

	i = cg.forcepowerSelect - 1;
	if (i < 0)
	{
		i = MAX_SHOWPOWERS-1;
	}

	cgi_R_SetColor(NULL);
	// Work backwards from current icon
	holdX = x - ((bigIconSize/2) + pad + smallIconSize);
	for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--)
	{
		if (i < 0)
		{
			i = MAX_SHOWPOWERS-1;
		}

		if (!ForcePower_Valid(i))	// Does he have this power?
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (force_icons[showPowers[i]])
		{
			CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, force_icons[showPowers[i]] ); 
			holdX -= (smallIconSize+pad);
		}
	}

	// Current Center Icon
	if (force_icons[showPowers[cg.forcepowerSelect]])
	{
		CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2)) + yOffset, bigIconSize, bigIconSize, force_icons[showPowers[cg.forcepowerSelect]] );
	}


	i = cg.forcepowerSelect + 1;
	if (i>=MAX_SHOWPOWERS)
	{
		i = 0;
	}

	// Work forwards from current icon
	holdX = x + (bigIconSize/2) + pad;
	for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++)
	{
		if (i>=MAX_SHOWPOWERS)
		{
			i = 0;
		}

		if (!ForcePower_Valid(i))	// Does he have this power?
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (force_icons[showPowers[i]])
		{
			CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, force_icons[showPowers[i]] );
			holdX += (smallIconSize+pad);
		}
	}

	// This only a temp solution.
	if (cgi_SP_GetStringTextString( showPowersName[cg.forcepowerSelect], text, sizeof(text) ))
	{
			int w = cgi_R_Font_StrLenPixels(text, cgs.media.qhFontSmall, 1.0f);	
			int x = ( SCREEN_WIDTH - w ) / 2;
			cgi_R_Font_DrawString(x, (SCREEN_HEIGHT - 24) + yOffset, text, colorTable[CT_ICON_BLUE], cgs.media.qhFontSmall, -1, 1.0f);
	}
}

/*
===============
ForcePowerDataPad_Valid
===============
*/
qboolean ForcePowerDataPad_Valid(int index)
{
	gentity_t	*player = &g_entities[0];

	assert (index < MAX_DPSHOWPOWERS);
	if (player->client->ps.forcePowersKnown & (1 << showDataPadPowers[index]) && 
		player->client->ps.forcePowerLevel[showDataPadPowers[index]])	// Does he have the force power?
	{
		return qtrue;
	}

	return qfalse;
}

/*
===============
CG_DPNextForcePower_f
===============
*/
void CG_DPNextForcePower_f( void ) 
{
	int		i;
	int		original;

	if ( !cg.snap ) 
	{
		return;
	}

	original = cg.DataPadforcepowerSelect;

	for ( i = 0; i<MAX_DPSHOWPOWERS; i++ ) 
	{
		cg.DataPadforcepowerSelect++;

		if (cg.DataPadforcepowerSelect >= MAX_DPSHOWPOWERS)
		{ 
			cg.DataPadforcepowerSelect = 0; 
		}

		if (ForcePowerDataPad_Valid(cg.DataPadforcepowerSelect))	// Does he have the force power?
		{
			return;
		}
	}

	cg.DataPadforcepowerSelect = original;
}

/*
===============
CG_DPPrevForcePower_f
===============
*/
void CG_DPPrevForcePower_f( void ) 
{
	int		i;
	int		original;

	if ( !cg.snap ) 
	{
		return;
	}

	original = cg.DataPadforcepowerSelect;

	for ( i = 0; i<MAX_DPSHOWPOWERS; i++ ) 
	{
		cg.DataPadforcepowerSelect--;

		if (cg.DataPadforcepowerSelect < 0)
		{ 
			cg.DataPadforcepowerSelect = MAX_DPSHOWPOWERS-1; 
		}

		if (ForcePowerDataPad_Valid(cg.DataPadforcepowerSelect))	// Does he have the force power?
		{
			return;
		}
	}


	cg.DataPadforcepowerSelect = original;
}

char *forcepowerDesc[NUM_FORCE_POWERS] = 
{
"FORCE_ABSORB_DESC",
"FORCE_HEAL_DESC",
"FORCE_PROTECT_DESC",
"FORCE_MIND_TRICK_DESC",

"FORCE_JUMP_DESC",
"FORCE_SPEED_DESC",
"FORCE_PUSH_DESC",
"FORCE_PULL_DESC",
"FORCE_SABER_THROW_DESC",
"FORCE_SABER_DEFENSE_DESC",
"FORCE_SABER_OFFENSE_DESC",
"FORCE_SENSE_DESC",

"FORCE_DRAIN_DESC",
"FORCE_LIGHTNING_DESC",
"FORCE_RAGE_DESC",
"FORCE_GRIP_DESC",
};


char *forcepowerLvl1Desc[NUM_FORCE_POWERS] = 
{
"FORCE_ABSORB_LVL1_DESC",
"FORCE_HEAL_LVL1_DESC",
"FORCE_PROTECT_LVL1_DESC",
"FORCE_MIND_TRICK_LVL1_DESC",

"FORCE_JUMP_LVL1_DESC",
"FORCE_SPEED_LVL1_DESC",
"FORCE_PUSH_LVL1_DESC",
"FORCE_PULL_LVL1_DESC",
"FORCE_SABER_THROW_LVL1_DESC",
"FORCE_SABER_DEFENSE_LVL1_DESC",
"FORCE_SABER_OFFENSE_LVL1_DESC",
"FORCE_SENSE_LVL1_DESC",

"FORCE_DRAIN_LVL1_DESC",
"FORCE_LIGHTNING_LVL1_DESC",
"FORCE_RAGE_LVL1_DESC",
"FORCE_GRIP_LVL1_DESC",
};

char *forcepowerLvl2Desc[NUM_FORCE_POWERS] = 
{
"FORCE_ABSORB_LVL2_DESC",
"FORCE_HEAL_LVL2_DESC",
"FORCE_PROTECT_LVL2_DESC",
"FORCE_MIND_TRICK_LVL2_DESC",

"FORCE_JUMP_LVL2_DESC",
"FORCE_SPEED_LVL2_DESC",
"FORCE_PUSH_LVL2_DESC",
"FORCE_PULL_LVL2_DESC",
"FORCE_SABER_THROW_LVL2_DESC",
"FORCE_SABER_DEFENSE_LVL2_DESC",
"FORCE_SABER_OFFENSE_LVL2_DESC",
"FORCE_SENSE_LVL2_DESC",

"FORCE_DRAIN_LVL2_DESC",
"FORCE_LIGHTNING_LVL2_DESC",
"FORCE_RAGE_LVL2_DESC",
"FORCE_GRIP_LVL2_DESC",
};

char *forcepowerLvl3Desc[NUM_FORCE_POWERS] = 
{
"FORCE_ABSORB_LVL3_DESC",
"FORCE_HEAL_LVL3_DESC",
"FORCE_PROTECT_LVL3_DESC",
"FORCE_MIND_TRICK_LVL3_DESC",

"FORCE_JUMP_LVL3_DESC",
"FORCE_SPEED_LVL3_DESC",
"FORCE_PUSH_LVL3_DESC",
"FORCE_PULL_LVL3_DESC",
"FORCE_SABER_THROW_LVL3_DESC",
"FORCE_SABER_DEFENSE_LVL3_DESC",
"FORCE_SABER_OFFENSE_LVL3_DESC",
"FORCE_SENSE_LVL3_DESC",

"FORCE_DRAIN_LVL3_DESC",
"FORCE_LIGHTNING_LVL3_DESC",
"FORCE_RAGE_LVL3_DESC",
"FORCE_GRIP_LVL3_DESC",
};

/*
===================
CG_DrawDataPadForceSelect
===================
*/
void CG_DrawDataPadForceSelect( void ) 
{
	int		i;
	int		count;
	int		holdX;
	int		sideLeftIconCnt,sideRightIconCnt;
	int		holdCount,iconCnt;
	char	text[1024]={0};
	char	text2[1024]={0};

	// count the number of powers known
	count = 0;
	for (i=0;i<MAX_DPSHOWPOWERS;i++)
	{
		if (ForcePowerDataPad_Valid(i))
		{
			count++;
		}
	}

	if (count < 1)	// If no force powers, don't display
	{
		return;
	}


	// Time to switch new icon colors
	// Faded side icon color
//	memcpy(fadeColor, colorTable[CT_WHITE], sizeof(vec4_t));

	cg.iconSelectTime = cg.forcepowerSelectTime;

	const int sideMax = 3;	// Max number of icons on the side

	// Calculate how many icons will appear to either side of the center one
	holdCount = count - 1;	// -1 for the center icon
	if (holdCount == 0)			// No icons to either side
	{
		sideLeftIconCnt = 0;
		sideRightIconCnt = 0;
	}
	else if (count > (2*sideMax))	// Go to the max on each side
	{
		sideLeftIconCnt = sideMax;
		sideRightIconCnt = sideMax;
	}
	else							// Less than max, so do the calc
	{
		sideLeftIconCnt = holdCount/2;
		sideRightIconCnt = holdCount - sideLeftIconCnt;
	}


	const int smallIconSize = 40;
	const int bigIconSize = 70;
	const int bigPad = 64;
	const int pad = 32;

	const int centerXPos = 320;
	const int graphicYPos = 340;

	i = cg.DataPadforcepowerSelect - 1;
	if (i < 0)
	{
		i = MAX_DPSHOWPOWERS-1;
	}

	// Print icons to the left of the center 

	cgi_R_SetColor(colorTable[CT_WHITE]);
	// Work backwards from current icon
	holdX = centerXPos - ((bigIconSize/2) + bigPad + smallIconSize);
	for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--)
	{
		if (i < 0)
		{
			i = MAX_DPSHOWPOWERS-1;
		}

		if (!ForcePowerDataPad_Valid(i))	// Does he have this power?
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (force_icons[showDataPadPowers[i]])
		{
			CG_DrawPic( holdX, graphicYPos, smallIconSize, smallIconSize, force_icons[showDataPadPowers[i]] ); 
		}

		// A new force power
		if (((cg_updatedDataPadForcePower1.integer - 1) == showDataPadPowers[i]) ||
			((cg_updatedDataPadForcePower2.integer - 1) == showDataPadPowers[i]) ||
			((cg_updatedDataPadForcePower3.integer - 1) == showDataPadPowers[i]))
		{
			CG_DrawPic( holdX, graphicYPos, smallIconSize, smallIconSize, cgs.media.DPForcePowerOverlay ); 
		}

		if (force_icons[showDataPadPowers[i]])
		{
			holdX -= (smallIconSize+pad);
		}
	}

	// Current Center Icon
	if (force_icons[showDataPadPowers[cg.DataPadforcepowerSelect]])
	{

		cgi_R_SetColor(colorTable[CT_WHITE]);
		CG_DrawPic( centerXPos-(bigIconSize/2), (graphicYPos-((bigIconSize-smallIconSize)/2)), bigIconSize, bigIconSize, force_icons[showDataPadPowers[cg.DataPadforcepowerSelect]] );

		// New force power
		if (((cg_updatedDataPadForcePower1.integer - 1) == showDataPadPowers[cg.DataPadforcepowerSelect]) ||
			((cg_updatedDataPadForcePower2.integer - 1) == showDataPadPowers[cg.DataPadforcepowerSelect]) ||
			((cg_updatedDataPadForcePower3.integer - 1) == showDataPadPowers[cg.DataPadforcepowerSelect]))
		{			
			CG_DrawPic( centerXPos-(bigIconSize/2), (graphicYPos-((bigIconSize-smallIconSize)/2)), bigIconSize, bigIconSize, cgs.media.DPForcePowerOverlay ); 
		}
	}


	i = cg.DataPadforcepowerSelect + 1;
	if (i>=MAX_DPSHOWPOWERS)
	{
		i = 0;
	}

	cgi_R_SetColor(colorTable[CT_WHITE]);

	// Work forwards from current icon
	holdX = centerXPos + (bigIconSize/2) + bigPad;
	for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++)
	{
		if (i>=MAX_DPSHOWPOWERS)
		{
			i = 0;
		}

		if (!ForcePowerDataPad_Valid(i))	// Does he have this power?
		{
			continue;
		}

		++iconCnt;					// Good icon

		if (force_icons[showDataPadPowers[i]])
		{
			CG_DrawPic( holdX, graphicYPos, smallIconSize, smallIconSize, force_icons[showDataPadPowers[i]] );
		}

		// A new force power
		if (((cg_updatedDataPadForcePower1.integer - 1) == showDataPadPowers[i]) ||
			((cg_updatedDataPadForcePower2.integer - 1) == showDataPadPowers[i]) ||
			((cg_updatedDataPadForcePower3.integer - 1) == showDataPadPowers[i]))
		{
			CG_DrawPic( holdX, graphicYPos, smallIconSize, smallIconSize, cgs.media.DPForcePowerOverlay );
		}

		if (force_icons[showDataPadPowers[i]])
		{
			holdX += (smallIconSize+pad);
		}
	}

	cgi_SP_GetStringTextString( va("SP_INGAME_%s",forcepowerDesc[cg.DataPadforcepowerSelect]), text, sizeof(text) );

	if (player->client->ps.forcePowerLevel[showDataPadPowers[cg.DataPadforcepowerSelect]]==1)
	{
		cgi_SP_GetStringTextString( va("SP_INGAME_%s",forcepowerLvl1Desc[cg.DataPadforcepowerSelect]), text2, sizeof(text2) );
	}
	else if (player->client->ps.forcePowerLevel[showDataPadPowers[cg.DataPadforcepowerSelect]]==2)
	{
		cgi_SP_GetStringTextString( va("SP_INGAME_%s",forcepowerLvl2Desc[cg.DataPadforcepowerSelect]), text2, sizeof(text2) );
	}
	else 
	{
		cgi_SP_GetStringTextString( va("SP_INGAME_%s",forcepowerLvl3Desc[cg.DataPadforcepowerSelect]), text2, sizeof(text2) );
	}

	const short textboxXPos = 40;
	const short textboxYPos = 60;
	const int	textboxWidth = 560;
	const int	textboxHeight = 300;
	const float	textScale = 1.0f;

	if (text)
	{

		CG_DisplayBoxedText(textboxXPos,textboxYPos,textboxWidth,textboxHeight,va("%s%s",text,text2),
													4,
													textScale,
													colorTable[CT_WHITE]	
													);
	}
}

// actually, these are pretty pointless so far in CHC, since in TA codebase they were used only so init some HUD
//	function ptrs to allow cinematics in onscreen displays. So far, we don't use those, but here they are anyway...
//
/*	These stupid pragmas don't work, they still give the warning. Forget it, REM the lot.

#pragma warning ( disable : 4505)		// unreferenced local function has been removed
static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) {
  return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop);
}

static void CG_StopCinematic(int handle) {
  trap_CIN_StopCinematic(handle);
}

static void CG_DrawCinematic(int handle, float x, float y, float w, float h) {
  trap_CIN_SetExtents(handle, x, y, w, h);
  trap_CIN_DrawCinematic(handle);
}

static void CG_RunCinematicFrame(int handle) {
  trap_CIN_RunCinematic(handle);
}
#pragma warning ( default : 4505)
*/




