/*
 * Wolfenstein: Enemy Territory GPL Source Code
 * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
 *
 * ET: Legacy
 * Copyright (C) 2012-2024 ET:Legacy team <mail@etlegacy.com>
 *
 * This file is part of ET: Legacy - http://www.etlegacy.com
 *
 * ET: Legacy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ET: Legacy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, Wolfenstein: Enemy Territory GPL Source Code is also
 * subject to certain additional terms. You should have received a copy
 * of these additional terms immediately following the terms and conditions
 * of the GNU General Public License which accompanied the source code.
 * If not, please request a copy in writing from id Software at the address below.
 *
 * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
 */
/**
 * @file cg_main.c
 * @brief Initialization and primary entry point for cgame
 */

#include "cg_local.h"
#include "cg_cvars.h"

displayContextDef_t cgDC;

void CG_Init(int serverMessageNum, int serverCommandSequence, int clientNum, qboolean demoPlayback, int etLegacyClient, demoPlayInfo_t *info, int clientVersion);
void CG_Shutdown(void);
qboolean CG_CheckExecKey(int key);
extern itemDef_t *g_bindItem;
extern qboolean  g_waitingForKey;

int dll_com_trapGetValue;
int dll_trap_SysFlashWindow;
int dll_trap_CommandComplete;
int dll_trap_CmdBackup_Ext;
int dll_trap_MatchPaused;
int dll_trap_CvarSetDescription;

/**
 * @brief This is the only way control passes into the module.
 * @details This must be the very first function compiled into the .q3vm file
 * @param command
 * @param[in] arg0
 * @param[in] arg1
 * @param[in] arg2
 * @param[in] arg3
 * @param[in] arg4
 * @param[in] arg5
 * @param[in] arg6
 * @param[in] arg7 - unused
 * @param[in] arg8 - unused
 * @param[in] arg9 - unused
 * @param[in] arg10 - unused
 * @param[in] arg11 - unused
 * @return
 */
Q_EXPORT intptr_t vmMain(intptr_t command, intptr_t arg0, intptr_t arg1, intptr_t arg2, intptr_t arg3, intptr_t arg4, intptr_t arg5, intptr_t arg6, intptr_t arg7, intptr_t arg8, intptr_t arg9, intptr_t arg10, intptr_t arg11)
{
	switch (command)
	{
	case CG_INIT:
		CG_Init(arg0, arg1, arg2, (qboolean)arg3, arg4, (demoPlayInfo_t *)arg5, arg6);
		cgs.initing = qfalse;
		return 0;
	case CG_SHUTDOWN:
		CG_Shutdown();
		return 0;
	case CG_CONSOLE_COMMAND:
		return CG_ConsoleCommand();
	case CG_DRAW_ACTIVE_FRAME:
		CG_DrawActiveFrame(arg0, (qboolean)arg2); // arg1 removed, order kept for vanilla client compatibility
		return 0;
	case CG_CROSSHAIR_PLAYER:
		return CG_CrosshairPlayer();
	case CG_LAST_ATTACKER:
		return CG_LastAttacker();
	case CG_KEY_EVENT:
		CG_KeyEvent(arg0, (qboolean)arg1);
		return 0;
	case CG_MOUSE_EVENT:
		cgDC.cursorx = cgs.cursorX;
		cgDC.cursory = cgs.cursorY;
		CG_MouseEvent(arg0, arg1);
		return 0;
	case CG_EVENT_HANDLING:
		CG_EventHandling(arg0, qtrue);
		return 0;
	case CG_GET_TAG:
		return CG_GetTag(arg0, (char *)arg1, (orientation_t *)arg2);
	case CG_CHECKEXECKEY:
		return CG_CheckExecKey(arg0);
	case CG_WANTSBINDKEYS:
		return (g_waitingForKey && g_bindItem) ? qtrue : qfalse;
	case CG_MESSAGERECEIVED:
		return -1;
	case CG_CONSOLE_COMPLETE_ARGUMENT:
		return CG_ConsoleCompleteArgument();
	default:
		CG_Error("vmMain: unknown command %li", (long)command);
		break;
	}
	return -1;
}

cg_t         cg;
cgs_t        cgs;
centity_t    cg_entities[MAX_GENTITIES];
weaponInfo_t cg_weapons[MAX_WEAPONS];

/**
 * @brief CG_RestoreProfile
 */
void CG_RestoreProfile(void)
{
	int i;

	for (i = 0; i < cg.cvarBackupsCount; ++i)
	{
		if (i == 0)
		{
			CG_Printf(S_COLOR_GREEN "Restoring CVARS forced by server\n");
		}
		trap_Cvar_Set(cg.cvarBackups[i].cvarName, cg.cvarBackups[i].cvarValue);
		CG_Printf(S_COLOR_YELLOW "cvar: %s %s\n", cg.cvarBackups[i].cvarName, cg.cvarBackups[i].cvarValue);
	}
}

/**
 * @brief Checks whether a cfg file exists or not
 * @param[in] filename
 * @return qtrue if file exists
 */
qboolean CG_ConfigFileExists(const char *filename)
{
	int handle = trap_PC_LoadSource(va("%s.cfg", filename));
	trap_PC_FreeSource(handle);
	return handle ? qtrue : qfalse;
}

/**
 * @brief Execs a .cfg file
 * @param[in] filename
 */
void CG_execFile(const char *filename)
{
	trap_SendConsoleCommand(va("exec %s.cfg\n", filename));
}

/**
 * @brief Executes map-specific autoexec files
 * @param[in] cheats
 */
void CG_MapAutoexec(qboolean cheats)
{
	char       filename[128];
	const char *basename = cheats ? "autoexec_devmap_" : "autoexec_";

	Q_strncpyz(filename, basename, sizeof(filename));
	Q_strcat(filename, sizeof(filename), cgs.rawmapname);

	if (CG_ConfigFileExists(filename))
	{
		CG_execFile(filename);
		cg.mapConfigLoaded = qtrue;
	}
	else
	{
		Q_strncpyz(filename, basename, sizeof(filename));
		Q_strcat(filename, sizeof(filename), "default");

		if (CG_ConfigFileExists(filename))
		{
			CG_execFile(filename);
			cg.mapConfigLoaded = qtrue;
		}
	}
}

/**
 * @brief CG_setClientFlags
 */
void CG_setClientFlags(void)
{
	if (cg.demoPlayback)
	{
		return;
	}

	cg.pmext.bAutoReload = (qboolean)(cg_autoReload.integer > 0);
	trap_Cvar_Set("cg_uinfo", va("%d %d %d",
	                             // Client Flags
								 (
									 ((cg_autoReload.integer > 0) ? CGF_AUTORELOAD : 0) |
									 ((cg_autoAction.integer & AA_STATSDUMP) ? CGF_STATSDUMP : 0) |
									 ((cg_autoactivate.integer > 0) ? CGF_AUTOACTIVATE : 0) |
									 ((cg_predictItems.integer > 0) ? CGF_PREDICTITEMS : 0) |
									 ((cg_activateLean.integer > 0) ? CGF_ACTIVATELEAN : 0)
									 // Add more in here, as needed
	                             ),

	                             // Timenudge
	                             int_cl_timenudge.integer,
	                             // MaxPackets
	                             int_cl_maxpackets.integer
	                             ));
}

/**
 * @brief CG_CrosshairPlayer
 * @return
 */
int CG_CrosshairPlayer(void)
{
	if (cg.time > (cg.crosshairEntTime + 1000))
	{
		return -1;
	}
	return cg.crosshairEntNum;
}

/**
 * @brief CG_LastAttacker
 * @return
 */
int CG_LastAttacker(void)
{
#ifdef FEATURE_MULTIVIEW
	// used for messaging clients in the currect active window
	if (cg.mvTotalClients > 0)
	{
		return(cg.mvCurrentActive->mvInfo & MV_PID);
	}
#endif

	return((!cg.attackerTime) ? -1 : cg.snap->ps.persistant[PERS_ATTACKER]);
}

/**
 * @brief CG_Printf
 * @param[in] msg
 */
void QDECL CG_Printf(const char *msg, ...)
{
	va_list argptr;
	char    text[1024];

	va_start(argptr, msg);
	Q_vsnprintf(text, sizeof(text), msg, argptr);
	va_end(argptr);
	if (!Q_strncmp(text, "[cgnotify]", 10))
	{
		char buf[1024];

		if (!cg_drawNotifyText.integer)
		{
			Q_strncpyz(buf, &text[10], 1013);
			trap_Print(buf);
			return;
		}

		CG_AddToNotify(&text[10]);
		Q_strncpyz(buf, &text[10], 1013);
		Q_strncpyz(text, "[skipnotify]", 13);
		Q_strcat(text, 1011, buf);
	}

	trap_Print(text);
}

/**
 * @brief CG_DPrintf
 * @param[in] msg
 */
void QDECL CG_DPrintf(const char *msg, ...)
{
	va_list argptr;
	char    text[1024];

	if (developer.value == 0.f)
	{
		return;
	}

	va_start(argptr, msg);
	Q_vsnprintf(text, sizeof(text), msg, argptr);
	va_end(argptr);
	if (!Q_strncmp(text, "[cgnotify]", 10))
	{
		char buf[1024];

		if (!cg_drawNotifyText.integer)
		{
			Q_strncpyz(buf, &text[10], 1013);
			trap_Print(buf);
			return;
		}

		Q_strncpyz(buf, &text[10], 1013);
		Q_strncpyz(text, "[skipnotify]", 13);
		Q_strcat(text, 1011, buf);
	}

	trap_Print(text);
}

/**
 * @brief CG_Error
 * @param[in] msg
 */
void QDECL CG_Error(const char *msg, ...)
{
	va_list argptr;
	char    text[1024];

	va_start(argptr, msg);
	Q_vsnprintf(text, sizeof(text), msg, argptr);
	va_end(argptr);

	trap_Error(text);
}

#ifndef CGAME_HARD_LINKED

/**
 * @brief Com_Error
 * @param[in] code - unused
 * @param[in] error
 * @note FIXME: this is only here so the functions in q_shared.c and bg_*.c can link
 */
void QDECL Com_Error(int code, const char *error, ...)
{
	va_list argptr;
	char    text[1024];

	va_start(argptr, error);
	Q_vsnprintf(text, sizeof(text), error, argptr);
	va_end(argptr);

	CG_Error("%s", text);
}

/**
 * @brief Com_Printf
 * @param[in] msg
 */
void QDECL Com_Printf(const char *msg, ...)
{
	va_list argptr;
	char    text[1024];

	va_start(argptr, msg);
	Q_vsnprintf(text, sizeof(text), msg, argptr);
	va_end(argptr);

	CG_Printf("%s", text);
}

#endif

/**
 * @brief CG_Argv
 * @param[in] arg
 * @return
 */
const char *CG_Argv(int arg)
{
	static char buffer[MAX_STRING_CHARS];

	trap_Argv(arg, buffer, sizeof(buffer));

	return buffer;
}

/**
 * @brief Generate standard naming for screenshots/demos
 * @return
 */
char *CG_generateFilename(void)
{
	static char fullFilename[MAX_OSPATH];
	char        prefix[MAX_QPATH];
	qtime_t     ct;
	const char  *pszServerInfo = CG_ConfigString(CS_SERVERINFO);

	trap_RealTime(&ct);
	fullFilename[0] = '\0';
	prefix[0]       = '\0';

	if (cg_autoFolders.integer)
	{
		Com_sprintf(prefix, sizeof(prefix), "%d-%02d/", 1900 + ct.tm_year, ct.tm_mon + 1);
	}

	Com_sprintf(fullFilename, sizeof(fullFilename), "%s%d-%02d-%02d-%02d%02d%02d-%s%s", prefix,
	            1900 + ct.tm_year, ct.tm_mon + 1, ct.tm_mday,
	            ct.tm_hour, ct.tm_min, ct.tm_sec,
	            Info_ValueForKey(pszServerInfo, "mapname"),
#ifdef FEATURE_MULTIVIEW
	            (cg.mvTotalClients < 1) ?
#endif
	            ""
#ifdef FEATURE_MULTIVIEW
	          : "-MVD"
#endif
	            );

	return fullFilename;
}

/**
 * @brief Strip colors and control codes, copying up to dwMaxLength-1 "good" chars and nul-terminating
 * @param[in] pszIn
 * @param[out] pszOut
 * @param[in] dwMaxLength
 * @param[in] fCRLF
 * @return The length of the cleaned string
 */
int CG_cleanName(const char *pszIn, char *pszOut, int dwMaxLength, qboolean fCRLF)
{
	const char *pInCopy     = pszIn;
	const char *pszOutStart = pszOut;

	while (*pInCopy && (pszOut - pszOutStart < dwMaxLength - 1))
	{
		if (*pInCopy == '^')
		{
			pInCopy += ((pInCopy[1] == 0) ? 1 : 2);
		}
		else if ((*pInCopy < 32 && (!fCRLF || *pInCopy != '\n')) || (*pInCopy > 126))
		{
			pInCopy++;
		}
		else
		{
			*pszOut++ = *pInCopy++;
		}
	}

	*pszOut = 0;
	return(pszOut - pszOutStart);
}

/**
 * @brief CG_findClientNum
 * @param[in] s
 * @return
 */
int CG_findClientNum(const char *s)
{
	int      id;
	char     s2[MAX_NAME_LENGTH], n2[MAX_NAME_LENGTH];
	qboolean fIsNumber = qtrue;

	// See if its a number or string
	for (id = 0; id < (int)strlen(s) && s[id] != 0; id++)
	{
		if (s[id] < '0' || s[id] > '9')
		{
			fIsNumber = qfalse;
			break;
		}
	}

	// numeric values are just slot numbers
	if (fIsNumber)
	{
		id = Q_atoi(s);
		if (id >= 0 && id < cgs.maxclients && cgs.clientinfo[id].infoValid)
		{
			return(id);
		}
	}

	// check for a name match
	CG_cleanName(s, s2, sizeof(s2), qfalse);
	for (id = 0; id < cgs.maxclients; id++)
	{
		if (!cgs.clientinfo[id].infoValid)
		{
			continue;
		}

		CG_cleanName(cgs.clientinfo[id].name, n2, sizeof(n2), qfalse);
		if (!Q_stricmp(n2, s2))
		{
			return(id);
		}
	}

	CG_Printf("[cgnotify]%s ^3%s^7 %s.\n", CG_TranslateString("User"), s, CG_TranslateString("is not on the server"));
	return(-1);
}

/**
 * @brief CG_printConsoleString
 * @param[in] str
 */
void CG_printConsoleString(const char *str)
{
	CG_Printf("[skipnotify]%s", str);
}

/**
 * @brief CG_LoadObjectiveData
 */
void CG_LoadObjectiveData(void)
{
	pc_token_t token, token2;
	int        handle;

	if (cg_gameType.integer == GT_WOLF_LMS)
	{
		handle = trap_PC_LoadSource(va("maps/%s_lms.objdata", Q_strlwr(cgs.rawmapname)));
	}
	else
	{
		handle = trap_PC_LoadSource(va("maps/%s.objdata", Q_strlwr(cgs.rawmapname)));
	}

	if (!handle)
	{
		return;
	}

	while (1)
	{
		if (!trap_PC_ReadToken(handle, &token))
		{
			break;
		}

		if (!Q_stricmp(token.string, "wm_mapdescription"))
		{
			if (!trap_PC_ReadToken(handle, &token))
			{
				CG_Printf("^1ERROR: bad objdata line : team parameter required\n");
				break;
			}

			if (!trap_PC_ReadToken(handle, &token2))
			{
				CG_Printf("^1ERROR: bad objdata line : description parameter required\n");
				break;
			}

			if (!Q_stricmp(token.string, "axis"))
			{
				Q_strncpyz(cg.objMapDescription_Axis, token2.string, sizeof(cg.objMapDescription_Axis));
			}
			else if (!Q_stricmp(token.string, "allied"))
			{
				Q_strncpyz(cg.objMapDescription_Allied, token2.string, sizeof(cg.objMapDescription_Allied));
			}
			else if (!Q_stricmp(token.string, "neutral"))
			{
				Q_strncpyz(cg.objMapDescription_Neutral, token2.string, sizeof(cg.objMapDescription_Neutral));
			}
		}
		else if (!Q_stricmp(token.string, "wm_objective_axis_desc"))
		{
			int i;

			if (!PC_Int_Parse(handle, &i))
			{
				CG_Printf("^1ERROR: bad objdata line : number parameter required\n");
				break;
			}

			if (!trap_PC_ReadToken(handle, &token))
			{
				CG_Printf("^1ERROR: bad objdata line :  description parameter required\n");
				break;
			}

			i--;

			if (i < 0 || i >= MAX_OBJECTIVES)
			{
				CG_Printf("^1ERROR: bad objdata line : invalid objective number\n");
				break;
			}

			Q_strncpyz(cg.objDescription_Axis[i], token.string, sizeof(cg.objDescription_Axis[i]));
		}
		else if (!Q_stricmp(token.string, "wm_objective_allied_desc"))
		{
			int i;

			if (!PC_Int_Parse(handle, &i))
			{
				CG_Printf("^1ERROR: bad objdata line : number parameter required\n");
				break;
			}

			if (!trap_PC_ReadToken(handle, &token))
			{
				CG_Printf("^1ERROR: bad objdata line :  description parameter required\n");
				break;
			}

			i--;

			if (i < 0 || i >= MAX_OBJECTIVES)
			{
				CG_Printf("^1ERROR: bad objdata line : invalid objective number\n");
				break;
			}

			Q_strncpyz(cg.objDescription_Allied[i], token.string, sizeof(cg.objDescription_Allied[i]));
		}
	}

	trap_PC_FreeSource(handle);
}

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

/**
 * @brief CG_SetupDlightstyles
 */
void CG_SetupDlightstyles(void)
{
	int       i, j;
	char      *str;
	char      *token;
	int       entnum;
	centity_t *cent;

	cg.lightstylesInited = qtrue;

	for (i = 1; i < MAX_DLIGHT_CONFIGSTRINGS; i++)
	{
		str = (char *) CG_ConfigString(CS_DLIGHTS + i);
		if (!strlen(str))
		{
			break;
		}

		token  = COM_Parse(&str);    // ent num
		entnum = Q_atoi(token);
		cent   = &cg_entities[entnum];

		token = COM_Parse(&str);     // stylestring
		Q_strncpyz(cent->dl_stylestring, token, sizeof(cent->dl_stylestring));

		token             = COM_Parse(&str); // offset
		cent->dl_frame    = Q_atoi(token);
		cent->dl_oldframe = cent->dl_frame - 1;
		if (cent->dl_oldframe < 0)
		{
			cent->dl_oldframe = strlen(cent->dl_stylestring);
		}

		token          = COM_Parse(&str); // sound id
		cent->dl_sound = Q_atoi(token);

		token          = COM_Parse(&str); // attenuation
		cent->dl_atten = Q_atoi(token);

		for (j = 0; j < (int)strlen(cent->dl_stylestring); j++)
		{

			cent->dl_stylestring[j] += cent->dl_atten;  // adjust character for attenuation/amplification

			// clamp result
			if (cent->dl_stylestring[j] < 'a')
			{
				cent->dl_stylestring[j] = 'a';
			}
			if (cent->dl_stylestring[j] > 'z')
			{
				cent->dl_stylestring[j] = 'z';
			}
		}

		cent->dl_backlerp = 0.0;
		cent->dl_time     = cg.time;
	}
}

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

/**
 * @brief The server says this item is used on this level
 * @param[in] itemNum
 */
static void CG_RegisterItemSounds(int itemNum)
{
	gitem_t *item = BG_GetItem(itemNum);

	if (item->pickup_sound && *item->pickup_sound)
	{
		cgs.media.itemPickUpSounds[itemNum] = trap_S_RegisterSound(item->pickup_sound, qfalse);
	}
}

/**
 * @brief CG_RegisterGameSounds
 */
void CG_RegisterGameSounds()
{
	cgs.cachedSounds[GAMESOUND_BLANK]         = trap_S_RegisterSound("sound/player/default/blank.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_PLAYER_GURP1]  = trap_S_RegisterSound("sound/player/gurp1.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_PLAYER_GURP2]  = trap_S_RegisterSound("sound/player/gurp2.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_PLAYER_BUBBLE] = trap_S_RegisterSound("sound/world/bubbles.wav", qfalse);

	cgs.cachedSounds[GAMESOUND_WORLD_CHAIRCREAK]     = trap_S_RegisterSound("sound/world/chaircreak.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_WORLD_BUILD]          = trap_S_RegisterSound("sound/world/build.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_WORLD_MG_CONSTRUCTED] = trap_S_RegisterSound("sound/world/mg_constructed.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_WPN_AIRSTRIKE_PLANE]  = trap_S_RegisterSound("sound/weapons/airstrike/airstrike_plane.wav", qfalse);
	//cgs.cachedSounds[GAMESOUND_WPN_ARTILLERY_FLY_1]  = trap_S_RegisterSound("sound/weapons/artillery/artillery_fly_1.wav", qfalse);   // moved in weap file
	//cgs.cachedSounds[GAMESOUND_WPN_ARTILLERY_FLY_2]  = trap_S_RegisterSound("sound/weapons/artillery/artillery_fly_2.wav", qfalse);   // moved in weap file
	//cgs.cachedSounds[GAMESOUND_WPN_ARTILLERY_FLY_3]  = trap_S_RegisterSound("sound/weapons/artillery/artillery_fly_3.wav", qfalse);   // moved in weap file
	cgs.cachedSounds[GAMESOUND_MISC_REVIVE]  = trap_S_RegisterSound("sound/misc/vo_revive.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_MISC_REFEREE] = trap_S_RegisterSound("sound/misc/referee.wav", qfalse);
	cgs.cachedSounds[GAMESOUND_MISC_VOTE]    = trap_S_RegisterSound("sound/misc/vote.wav", qfalse);
	//cgs.cachedSounds[GAMESOUND_MISC_BANNED] = trap_S_RegisterSound("sound/osp/banned.wav", qfalse);
	//cgs.cachedSounds[GAMESOUND_MISC_KICKED] = trap_S_RegisterSound("sound/osp/kicked.wav", qfalse);
}

/**
 * @brief Called during a precache command
 */
static void CG_RegisterSounds(void)
{
	int          i;
	char         name[MAX_QPATH];
	const char   *soundName;
	bg_speaker_t *speaker;

	// voice commands
	CG_LoadVoiceChats();

	// init sound scripts
	CG_SoundInit();

	BG_ClearScriptSpeakerPool();

	BG_LoadSpeakerScript(va("sound/maps/%s.sps", cgs.rawmapname));

	for (i = 0; i < BG_NumScriptSpeakers(); i++)
	{
		speaker        = BG_GetScriptSpeaker(i);
		speaker->noise = trap_S_RegisterSound(speaker->filename, qfalse);
	}

	cgs.media.noAmmoSound      = trap_S_RegisterSound("sound/weapons/misc/fire_dry.wav", qfalse);
	cgs.media.noFireUnderwater = trap_S_RegisterSound("sound/weapons/misc/fire_water.wav", qfalse);
	cgs.media.selectSound      = trap_S_RegisterSound("sound/weapons/misc/change.wav", qfalse);
	cgs.media.landHurt         = trap_S_RegisterSound("sound/player/land_hurt.wav", qfalse);
	cgs.media.gibSound         = trap_S_RegisterSound("sound/player/gib.wav", qfalse);

	cgs.media.watrInSound     = trap_S_RegisterSound("sound/player/water_in.wav", qfalse);
	cgs.media.watrOutSound    = trap_S_RegisterSound("sound/player/water_out.wav", qfalse);
	cgs.media.watrUnSound     = trap_S_RegisterSound("sound/player/water_un.wav", qfalse);
	cgs.media.watrGaspSound   = trap_S_RegisterSound("sound/player/gasp.wav", qfalse);
	cgs.media.underWaterSound = trap_S_RegisterSound("sound/player/underwater.wav", qfalse);

	cgs.media.landSound[FOOTSTEP_NORMAL] = trap_S_RegisterSound("sound/player/footsteps/stone_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_SPLASH] = trap_S_RegisterSound("sound/player/footsteps/water_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_METAL]  = trap_S_RegisterSound("sound/player/footsteps/metal_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_WOOD]   = trap_S_RegisterSound("sound/player/footsteps/wood_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_GRASS]  = trap_S_RegisterSound("sound/player/footsteps/grass_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_GRAVEL] = trap_S_RegisterSound("sound/player/footsteps/gravel_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_ROOF]   = trap_S_RegisterSound("sound/player/footsteps/roof_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_SNOW]   = trap_S_RegisterSound("sound/player/footsteps/snow_jump.wav", qfalse);
	cgs.media.landSound[FOOTSTEP_CARPET] = trap_S_RegisterSound("sound/player/footsteps/carpet_jump.wav", qfalse);

	for (i = 0; i < 4; i++)
	{
		Com_sprintf(name, sizeof(name), "sound/player/footsteps/stone%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound(name, qfalse);

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

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/metal%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound(name, qfalse);

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/wood%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_WOOD][i] = trap_S_RegisterSound(name, qfalse);

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/grass%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_GRASS][i] = trap_S_RegisterSound(name, qfalse);

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/gravel%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_GRAVEL][i] = trap_S_RegisterSound(name, qfalse);

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/roof%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_ROOF][i] = trap_S_RegisterSound(name, qfalse);

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/snow%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_SNOW][i] = trap_S_RegisterSound(name, qfalse);

		Com_sprintf(name, sizeof(name), "sound/player/footsteps/carpet%i.wav", i + 1);
		cgs.media.footsteps[FOOTSTEP_CARPET][i] = trap_S_RegisterSound(name, qfalse);
	}

	for (i = 1 ; i < ITEM_MAX_ITEMS ; i++)
	{
		CG_RegisterItemSounds(i);
	}

	for (i = 1 ; i < MAX_SOUNDS ; i++)
	{
		soundName = CG_ConfigString(CS_SOUNDS + i);
		if (!soundName[0])
		{
			break;
		}
		if (soundName[0] == '*')
		{
			continue;   // custom sound
		}

		// register sound scripts seperately
		if (!strstr(soundName, ".wav") && !strstr(soundName, ".ogg"))
		{
			CG_SoundScriptPrecache(soundName);
		}
		else
		{
			cgs.gameSounds[i] = trap_S_RegisterSound(soundName, qfalse);
		}
	}

	cgs.media.countFight             = trap_S_RegisterSound("sound/osp/fight.wav", qfalse);
	cgs.media.countPrepare           = trap_S_RegisterSound("sound/osp/prepare.wav", qfalse);
	cgs.media.goatAxis               = trap_S_RegisterSound("sound/osp/goat.wav", qfalse);
	cgs.media.reinforceTickSound     = trap_S_RegisterSound("sound/multiplayer/tick.wav", qfalse);
	cgs.media.reinforceTockSound     = trap_S_RegisterSound("sound/multiplayer/tock.wav", qfalse);
	cgs.media.reinforceTickLoudSound = trap_S_RegisterSound("sound/multiplayer/tickLoud.wav", qfalse);
	cgs.media.reinforceTockLoudSound = trap_S_RegisterSound("sound/multiplayer/tockLoud.wav", qfalse);

	cgs.media.headShot = trap_S_RegisterSound("sound/hitsounds/hithead.wav", qfalse);
	cgs.media.bodyShot = trap_S_RegisterSound("sound/hitsounds/hit.wav", qfalse);
	cgs.media.teamShot = trap_S_RegisterSound("sound/hitsounds/hitteam.wav", qfalse);

	cgs.media.flameSound       = trap_S_RegisterSound("sound/weapons/flamethrower/flame_burn.wav", qfalse);
	cgs.media.flameBlowSound   = trap_S_RegisterSound("sound/weapons/flamethrower/flame_pilot.wav", qfalse);
	cgs.media.flameStartSound  = trap_S_RegisterSound("sound/weapons/flamethrower/flame_up.wav", qfalse);
	cgs.media.flameStreamSound = trap_S_RegisterSound("sound/weapons/flamethrower/flame_fire.wav", qfalse);

	cgs.media.grenadePulseSound[0] = trap_S_RegisterSound("sound/weapons/grenade/gren_timer1.wav", qfalse);
	cgs.media.grenadePulseSound[1] = trap_S_RegisterSound("sound/weapons/grenade/gren_timer2.wav", qfalse);
	cgs.media.grenadePulseSound[2] = trap_S_RegisterSound("sound/weapons/grenade/gren_timer3.wav", qfalse);
	cgs.media.grenadePulseSound[3] = trap_S_RegisterSound("sound/weapons/grenade/gren_timer4.wav", qfalse);

	cgs.media.boneBounceSound = trap_S_RegisterSound("sound/world/boardbreak.wav", qfalse);          // TODO: need a real sound for this

	cgs.media.sfx_rockexp = trap_S_RegisterSound("sound/weapons/rocket/rocket_expl.wav", qfalse);

	for (i = 0; i < 3; i++)
	{
		// bouncy shell sounds \o/
		cgs.media.sfx_brassSound[BRASSSOUND_METAL][i][0] = trap_S_RegisterSound(va("sound/weapons/misc/shell_metal%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_METAL][i][1] = trap_S_RegisterSound(va("sound/weapons/misc/sg_shell_metal%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_SOFT][i][0]  = trap_S_RegisterSound(va("sound/weapons/misc/shell_soft%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_SOFT][i][1]  = trap_S_RegisterSound(va("sound/weapons/misc/sg_shell_soft%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_STONE][i][0] = trap_S_RegisterSound(va("sound/weapons/misc/shell_stone%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_STONE][i][1] = trap_S_RegisterSound(va("sound/weapons/misc/sg_shell_stone%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_WOOD][i][0]  = trap_S_RegisterSound(va("sound/weapons/misc/shell_wood%i.wav", i + 1), qfalse);
		cgs.media.sfx_brassSound[BRASSSOUND_WOOD][i][1]  = trap_S_RegisterSound(va("sound/weapons/misc/sg_shell_wood%i.wav", i + 1), qfalse);
		cgs.media.sfx_rubbleBounce[i]                    = trap_S_RegisterSound(va("sound/world/debris%i.wav", i + 1), qfalse);
	}

	cgs.media.uniformPickup     = trap_S_RegisterSound("sound/misc/body_pickup.wav", qfalse);
	cgs.media.buildDecayedSound = trap_S_RegisterSound("sound/world/build_abort.wav", qfalse);

	cgs.media.sndLimboSelect = trap_S_RegisterSound("sound/menu/select.wav", qfalse);
	cgs.media.sndLimboFilter = trap_S_RegisterSound("sound/menu/filter.wav", qfalse);
	//cgs.media.sndLimboCancel = trap_S_RegisterSound("sound/menu/cancel.wav", qfalse);

	cgs.media.sndRankUp  = trap_S_RegisterSound("sound/misc/rank_up.wav", qfalse);
	cgs.media.sndSkillUp = trap_S_RegisterSound("sound/misc/skill_up.wav", qfalse);

	cgs.media.sndMedicCall[0] = trap_S_RegisterSound("sound/chat/axis/medic.wav", qfalse);
	cgs.media.sndMedicCall[1] = trap_S_RegisterSound("sound/chat/allies/medic.wav", qfalse);

	cgs.media.shoveSound = trap_S_RegisterSound("sound/weapons/impact/flesh1.wav", qfalse);

	CG_RegisterGameSounds();

	CG_PrecacheFXSounds();
}

/**
* @brief CG_RegisterFonts
*/
void CG_RegisterFonts(void)
{
	RegisterSharedFonts();

	cgs.media.limboFont1    = cgDC.Assets.limboFont1;
	cgs.media.limboFont1_lo = cgDC.Assets.limboFont1_lo;
	cgs.media.limboFont2    = cgDC.Assets.limboFont2;
	cgs.media.limboFont2_lo = cgDC.Assets.limboFont2_lo;

	cgs.media.bg_loadscreenfont1 = cgDC.Assets.bg_loadscreenfont1;
	cgs.media.bg_loadscreenfont2 = cgDC.Assets.bg_loadscreenfont2;
}

/**
 * @brief CG_GetGameSound
 * @param[in] index
 * @return
 */
sfxHandle_t CG_GetGameSound(int index)
{
	// Cached game file
	if (index < GAMESOUND_MAX)
	{
		return cgs.cachedSounds[index];
	}
	return cgs.gameSounds[index - GAMESOUND_MAX] ? cgs.gameSounds[index - GAMESOUND_MAX] : 0;
}

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

/**
 * @brief CG_RegisterGraphics
 * @details This function may execute for a couple of minutes with a slow disk.
 */
static void CG_RegisterGraphics(void)
{
	char              name[MAX_QPATH];
	int               i;
	static const char *sb_nums[11] =
	{
		"gfx/2d/numbers/zero_32b",
		"gfx/2d/numbers/one_32b",
		"gfx/2d/numbers/two_32b",
		"gfx/2d/numbers/three_32b",
		"gfx/2d/numbers/four_32b",
		"gfx/2d/numbers/five_32b",
		"gfx/2d/numbers/six_32b",
		"gfx/2d/numbers/seven_32b",
		"gfx/2d/numbers/eight_32b",
		"gfx/2d/numbers/nine_32b",
		"gfx/2d/numbers/minus_32b",
	};

	CG_LoadingString(va(" - %s -", cgs.mapname));

	trap_R_LoadWorldMap(cgs.mapname);

	CG_LoadingString(" - entities -");

	numSplinePaths = 0;
	numPathCorners = 0;

	cg.numOIDtriggers2 = 0;

	BG_ClearAnimationPool();

	BG_ClearCharacterPool();

	BG_InitWeaponStrings();

	CG_ParseEntitiesFromString();

	CG_LoadObjectiveData();

	// precache status bar pics
	CG_LoadingString(" - game media -");

	CG_LoadingString(" - textures -");

	// dynamic shader api example
	// replaces a fueldump texture with a dynamically generated one.
#ifdef TEST_API_DYNAMICSHADER
	trap_R_LoadDynamicShader("my_terrain1_2",
	                         "my_terrain1_2\n"
	                         "{\n"
	                         "    qer_editorimage textures/stone/mxsnow3.tga\n"
	                         "    q3map_baseshader textures/fueldump/terrain_base\n"
	                         "    {\n"
	                         "        map textures/stone/mxrock1aa.tga\n"
	                         "        rgbGen identity\n"
	                         "        tcgen environment\n"
	                         "    }\n"
	                         "    {\n"
	                         "        lightmap $lightmap\n"
	                         "        blendFunc GL_DST_COLOR GL_ZERO\n"
	                         "        rgbgen identity\n"
	                         "    }\n"
	                         "}\n");
	trap_R_RegisterShader("my_terrain1_2");
	trap_R_RemapShader("textures/fueldump/terrain1_2", "my_terrain1_2", "0");
#endif

	for (i = 0 ; i < 11 ; i++)
	{
		cgs.media.numberShaders[i] = trap_R_RegisterShader(sb_nums[i]);
	}

	cgs.media.fleshSmokePuffShader = trap_R_RegisterShader("fleshimpactsmokepuff");

	// blood cloud
	cgs.media.bloodCloudShader = trap_R_RegisterShader("bloodCloud");

#ifdef FEATURE_MULTIVIEW // commented, kept as reminder
	// MV cursor @multiview
	//cgs.media.cursor = trap_R_RegisterShaderNoMip( "ui/assets/mvcursor.tga" );
#endif

	// cannon
	cgs.media.smokePuffShaderdirty = trap_R_RegisterShader("smokePuffdirty");

	for (i = 0; i < 5; i++)
	{
		cgs.media.smokePuffShaderb[i] = trap_R_RegisterShader(va("smokePuffblack%i", i + 1));
	}

	// bleedanim
	for (i = 0; i < 5; i++)
	{
		cgs.media.viewBloodAni[i] = trap_R_RegisterShader(va("viewBloodBlend%i", i + 1));
	}

	for (i = 0; i < 16; i++)
	{
		cgs.media.viewFlashFire[i] = trap_R_RegisterShader(va("viewFlashFire%i", i + 1));
	}

	// cgs.media.smokePuffRageProShader = trap_R_RegisterShader("smokePuffRagePro"); // unused FIXME: remove from shader def
	cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader("shotgunSmokePuff");
	cgs.media.bloodTrailShader       = trap_R_RegisterShader("bloodTrail");
	cgs.media.lagometerShader        = trap_R_RegisterShader("lagometer");
	cgs.media.reticleShaderSimple    = trap_R_RegisterShader("gfx/misc/reticlesimple");
	cgs.media.binocShaderSimple      = trap_R_RegisterShader("gfx/misc/binocsimple");
	cgs.media.snowShader             = trap_R_RegisterShader("snow_tri");
	cgs.media.oilParticle            = trap_R_RegisterShader("oilParticle");
	cgs.media.oilSlick               = trap_R_RegisterShader("oilSlick");
	cgs.media.waterBubbleShader      = trap_R_RegisterShader("waterBubble");
	cgs.media.tracerShader           = trap_R_RegisterShader("gfx/misc/tracer");

	// hint icon, some of these were never used in default wolf
	cgs.media.usableHintShader        = trap_R_RegisterShader("gfx/2d/legacy_usableHint");
	cgs.media.notUsableHintShader     = trap_R_RegisterShader("gfx/2d/legacy_notUsableHint");
	cgs.media.doorHintShader          = trap_R_RegisterShader("gfx/2d/doorHint");
	cgs.media.doorRotateHintShader    = trap_R_RegisterShader("gfx/2d/doorRotateHint");             // TODO: no icon, add it ?
	cgs.media.doorLockHintShader      = trap_R_RegisterShader("gfx/2d/legacy_lockedhint");
	cgs.media.mg42HintShader          = trap_R_RegisterShader("gfx/2d/mg42Hint");                   // TODO: no icon, add it ?
	cgs.media.breakableHintShader     = trap_R_RegisterShader("gfx/2d/legacy_breakableHint");
	cgs.media.healthHintShader        = trap_R_RegisterShader("gfx/2d/legacy_healthHint");          // TODO: no icon, add it ?
	cgs.media.knifeHintShader         = trap_R_RegisterShader("gfx/2d/knifeHint");
	cgs.media.ladderHintShader        = trap_R_RegisterShader("gfx/2d/ladderHint");
	cgs.media.buttonHintShader        = trap_R_RegisterShader("gfx/2d/legacy_buttonHint");          // TODO: no icon, add it ?
	cgs.media.waterHintShader         = trap_R_RegisterShader("gfx/2d/waterHint");
	cgs.media.weaponHintShader        = trap_R_RegisterShader("gfx/2d/legacy_weaponHint");          // TODO: no icon, add it ?
	cgs.media.ammoHintShader          = trap_R_RegisterShader("gfx/2d/legacy_ammoHint");            // TODO: no icon, add it ?
	cgs.media.powerupHintShader       = trap_R_RegisterShader("gfx/2d/legacy_powerupHint");         // TODO: no icon, add it ?
	cgs.media.friendShader            = trap_R_RegisterShaderNoMip("gfx/2d/friendlycross.tga");
	cgs.media.buildHintShader         = trap_R_RegisterShader("gfx/2d/buildHint");
	cgs.media.disarmHintShader        = trap_R_RegisterShader("gfx/2d/legacy_disarmHint");          // TODO: no icon, add it ?
	cgs.media.reviveHintShader        = trap_R_RegisterShader("gfx/2d/reviveHint");
	cgs.media.dynamiteHintShader      = trap_R_RegisterShader("gfx/2d/dynamiteHint");
	cgs.media.tankHintShader          = trap_R_RegisterShaderNoMip("gfx/2d/tankHint");
	cgs.media.satchelchargeHintShader = trap_R_RegisterShaderNoMip("gfx/2d/satchelchargeHint");
	cgs.media.landmineHintShader      = trap_R_RegisterShaderNoMip("gfx/2d/landmineHint");
	cgs.media.uniformHintShader       = trap_R_RegisterShaderNoMip("gfx/2d/uniformHint");

	if (cgs.ccLayers)
	{
		for (i = 0; i < cgs.ccLayers; i++)
		{
			cgs.media.commandCentreMapShader[i]      = trap_R_RegisterShaderNoMip(va("levelshots/%s_%i_cc.tga", cgs.rawmapname, i));
			cgs.media.commandCentreMapShaderTrans[i] = trap_R_RegisterShaderNoMip(va("levelshots/%s_%i_cc_trans", cgs.rawmapname, i));
			cgs.media.commandCentreAutomapShader[i]  = trap_R_RegisterShaderNoMip(va("levelshots/%s_%i_cc_automap", cgs.rawmapname, i));
		}
	}
	else
	{
		cgs.media.commandCentreMapShader[0]      = trap_R_RegisterShaderNoMip(va("levelshots/%s_cc.tga", cgs.rawmapname));
		cgs.media.commandCentreMapShaderTrans[0] = trap_R_RegisterShaderNoMip(va("levelshots/%s_cc_trans", cgs.rawmapname));
		cgs.media.commandCentreAutomapShader[0]  = trap_R_RegisterShaderNoMip(va("levelshots/%s_cc_automap", cgs.rawmapname));
	}
	cgs.media.commandCentreAutomapMaskShader    = trap_R_RegisterShaderNoMip("levelshots/automap_mask");
	cgs.media.commandCentreAutomapBorderShader  = trap_R_RegisterShaderNoMip("ui/assets2/maptrim_long");
	cgs.media.commandCentreAutomapBorder2Shader = trap_R_RegisterShaderNoMip("ui/assets2/maptrim_long2");
	cgs.media.commandCentreAutomapCornerShader  = trap_R_RegisterShaderNoMip("ui/assets2/maptrim_edge.tga");
	cgs.media.commandCentreAxisMineShader       = trap_R_RegisterShaderNoMip("sprites/landmine_axis");
	cgs.media.commandCentreAlliedMineShader     = trap_R_RegisterShaderNoMip("sprites/landmine_allied");
	cgs.media.commandCentreSpawnShader[0]       = trap_R_RegisterShaderNoMip("gfx/limbo/cm_flagaxis");
	cgs.media.commandCentreSpawnShader[1]       = trap_R_RegisterShaderNoMip("gfx/limbo/cm_flagallied");
	cgs.media.commandCentreSpawnShadow          = trap_R_RegisterShaderNoMip("gfx/limbo/cm_flagshadow");
	cgs.media.compassConstructShader            = trap_R_RegisterShaderNoMip("sprites/construct.tga");
	cgs.media.blackmask                         = trap_R_RegisterShaderNoMip("images/blackmask"); // etpro icons support

	cgs.media.countryFlags = trap_R_RegisterShaderNoMip("gfx/flags/world_flags");

	cgs.media.slashShader             = trap_R_RegisterShaderNoMip("gfx/2d/numbers/slash");
	cgs.media.compass2Shader          = trap_R_RegisterShaderNoMip("gfx/2d/compass2.tga");
	cgs.media.compassShader           = trap_R_RegisterShaderNoMip("gfx/2d/compass.tga");
	cgs.media.compassDirectionShader  = trap_R_RegisterShaderNoMip("gfx/2d/compassdirection");
	cgs.media.compassCircleTickShader = trap_R_RegisterShaderNoMip("gfx/2d/compasscircletick");
	cgs.media.buddyShader             = trap_R_RegisterShaderNoMip("sprites/buddy.tga");

	for (i = 0 ; i < NUM_CROSSHAIRS ; i++)
	{
		cgs.media.crosshairShader[i] = trap_R_RegisterShader(va("gfx/2d/crosshair%c", 'a' + i));
		cg.crosshairShaderAlt[i]     = trap_R_RegisterShader(va("gfx/2d/crosshair%c_alt", 'a' + i));
	}

	for (i = 0 ; i < SK_NUM_SKILLS ; i++)
	{
		cgs.media.medals[i] = trap_R_RegisterShaderNoMip(va("gfx/limbo/medals0%i", i));
	}

	cgs.media.backTileShader = trap_R_RegisterShader("gfx/2d/backtile");
	//cgs.media.noammoShader   = trap_R_RegisterShader("icons/noammo");  // unused FIXME: remove from shader def

	cgs.media.teamStatusBar = trap_R_RegisterShader("gfx/2d/colorbar.tga");

	cgs.media.hudSprintBar           = trap_R_RegisterShader("sprintbar");
	cgs.media.hudSprintBarHorizontal = trap_R_RegisterShader("sprintbarhorizontal");

	cgs.media.hudAlliedHelmet = trap_R_RegisterShader("AlliedHelmet");
	cgs.media.hudAxisHelmet   = trap_R_RegisterShader("AxisHelmet");
	cgs.media.hudAdrenaline   = trap_R_RegisterShaderNoMip("gfx/hud/adrenaline");

	CG_LoadingString(" - models -");

	cgs.media.machinegunBrassModel  = trap_R_RegisterModel("models/weapons2/shells/m_shell.md3");
	cgs.media.panzerfaustBrassModel = trap_R_RegisterModel("models/weapons2/shells/pf_shell.md3");

	cgs.media.smallgunBrassModel = trap_R_RegisterModel("models/weapons2/shells/sm_shell.md3");

	// wolf debris
	cgs.media.debBlock[0] = trap_R_RegisterModel("models/mapobjects/debris/brick1.md3");
	cgs.media.debBlock[1] = trap_R_RegisterModel("models/mapobjects/debris/brick2.md3");
	cgs.media.debBlock[2] = trap_R_RegisterModel("models/mapobjects/debris/brick3.md3");
	cgs.media.debBlock[3] = trap_R_RegisterModel("models/mapobjects/debris/brick4.md3");
	cgs.media.debBlock[4] = trap_R_RegisterModel("models/mapobjects/debris/brick5.md3");
	cgs.media.debBlock[5] = trap_R_RegisterModel("models/mapobjects/debris/brick6.md3");

	cgs.media.debRock[0] = trap_R_RegisterModel("models/mapobjects/debris/rubble1.md3");
	cgs.media.debRock[1] = trap_R_RegisterModel("models/mapobjects/debris/rubble2.md3");
	cgs.media.debRock[2] = trap_R_RegisterModel("models/mapobjects/debris/rubble3.md3");

	cgs.media.debWood[0] = trap_R_RegisterModel("models/gibs/wood/wood1.md3");
	cgs.media.debWood[1] = trap_R_RegisterModel("models/gibs/wood/wood2.md3");
	cgs.media.debWood[2] = trap_R_RegisterModel("models/gibs/wood/wood3.md3");
	cgs.media.debWood[3] = trap_R_RegisterModel("models/gibs/wood/wood4.md3");
	cgs.media.debWood[4] = trap_R_RegisterModel("models/gibs/wood/wood5.md3");
	cgs.media.debWood[5] = trap_R_RegisterModel("models/gibs/wood/wood6.md3");

	cgs.media.debFabric[0] = trap_R_RegisterModel("models/shards/fabric1.md3");
	cgs.media.debFabric[1] = trap_R_RegisterModel("models/shards/fabric2.md3");
	cgs.media.debFabric[2] = trap_R_RegisterModel("models/shards/fabric3.md3");

	cgs.media.gibLeg       = trap_R_RegisterModel("models/gibs/leg.md3");
	cgs.media.gibIntestine = trap_R_RegisterModel("models/gibs/intestine.md3");
	cgs.media.gibChest     = trap_R_RegisterModel("models/gibs/skull.md3");

	cgs.media.spawnInvincibleShader = trap_R_RegisterShader("sprites/shield");
	cgs.media.scoreEliminatedShader = trap_R_RegisterShader("sprites/skull");
	cgs.media.medicReviveShader     = trap_R_RegisterShader("sprites/medic_revive");
	cgs.media.medicReviveShader2    = trap_R_RegisterShader("sprites/medic_revive2");
	cgs.media.disguisedShader       = trap_R_RegisterShader("sprites/undercover");

	cgs.media.constructShader = trap_R_RegisterShaderNoMip("sprites/construct");
	cgs.media.destroyShader   = trap_R_RegisterShaderNoMip("sprites/destroy");
	cgs.media.escortShader    = trap_R_RegisterShaderNoMip("sprites/escort");
	cgs.media.attackShader    = trap_R_RegisterShaderNoMip("sprites/attack");
	cgs.media.defendShader    = trap_R_RegisterShaderNoMip("sprites/defend");
	cgs.media.regroupShader   = trap_R_RegisterShaderNoMip("sprites/regroup");

	cgs.media.voiceChatShader       = trap_R_RegisterShader("sprites/voiceChat");
	cgs.media.voiceChatOrangeShader = trap_R_RegisterShader("sprites/voiceChat_orange");
	cgs.media.balloonShader         = trap_R_RegisterShader("sprites/balloon3");
	cgs.media.greenTick             = trap_R_RegisterShader("sprites/greentick");
	cgs.media.redCross              = trap_R_RegisterShader("sprites/redcross");

	cgs.media.objectiveShader        = trap_R_RegisterShader("sprites/objective");
	cgs.media.objectiveBlueShader    = trap_R_RegisterShaderNoMip("sprites/objective_blue");
	cgs.media.objectiveRedShader     = trap_R_RegisterShaderNoMip("sprites/objective_red");
	cgs.media.objectiveTeamShader    = trap_R_RegisterShaderNoMip("sprites/objective_team");
	cgs.media.objectiveDroppedShader = trap_R_RegisterShaderNoMip("sprites/objective_dropped");
	cgs.media.objectiveEnemyShader   = trap_R_RegisterShaderNoMip("sprites/objective_enemy");
	cgs.media.objectiveBothTEShader  = trap_R_RegisterShaderNoMip("sprites/objective_both_te");
	cgs.media.objectiveBothTDShader  = trap_R_RegisterShaderNoMip("sprites/objective_both_td");
	cgs.media.objectiveBothDEShader  = trap_R_RegisterShaderNoMip("sprites/objective_both_de");
	cgs.media.objectiveSimpleIcon    = trap_R_RegisterShader("simpleicons/objective");
	cgs.media.readyShader            = trap_R_RegisterShader("sprites/ready");
#ifdef LEGACY_AUTH
	cgs.media.authenticatedShader = trap_R_RegisterShader("sprites/authenticated");
#endif

	//cgs.media.bloodExplosionShader = trap_R_RegisterShader("bloodExplosion"); // unused FIXME: remove from shader def

	// water splash
	cgs.media.waterSplashModel  = trap_R_RegisterModel("models/weaphits/bullet.md3");
	cgs.media.waterSplashShader = trap_R_RegisterShader("waterSplash");

	// spark particles
	cgs.media.sparkParticleShader = trap_R_RegisterShader("sparkParticle");
	cgs.media.smokeTrailShader    = trap_R_RegisterShader("smokeTrail");

	cgs.media.flamethrowerFireStream = trap_R_RegisterShader("flamethrowerFireStream");
	//cgs.media.flamethrowerBlueStream    = trap_R_RegisterShader("flamethrowerBlueStream"); // unused FIXME: remove from shader def
	cgs.media.onFireShader2 = trap_R_RegisterShader("entityOnFire1");
	cgs.media.onFireShader  = trap_R_RegisterShader("entityOnFire2");
	//cgs.media.viewFadeBlack             = trap_R_RegisterShader("viewFadeBlack"); // unused FIXME: remove from shader def
	cgs.media.sparkFlareShader    = trap_R_RegisterShader("sparkFlareParticle");
	cgs.media.spotLightShader     = trap_R_RegisterShader("spotLight");
	cgs.media.spotLightBeamShader = trap_R_RegisterShader("lightBeam");
	//cgs.media.bulletParticleTrailShader = trap_R_RegisterShader("bulletParticleTrail"); // unused FIXME: remove from shader def
	cgs.media.smokeParticleShader = trap_R_RegisterShader("smokeParticle");

	cgs.media.genericConstructionShader = trap_R_RegisterShader("textures/sfx/construction");
	cgs.media.shoutcastLandmineShader   = trap_R_RegisterShader("textures/sfx/shoutcast_landmine");

	cgs.media.alliedUniformShader = trap_R_RegisterShader("sprites/uniform_allied_hud");
	cgs.media.axisUniformShader   = trap_R_RegisterShader("sprites/uniform_axis_hud");

	// used in: command map
	cgs.media.ccFilterPics[0] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_axis");
	cgs.media.ccFilterPics[1] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_allied");
	cgs.media.ccFilterPics[2] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_spawn");

	cgs.media.ccFilterPics[3] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_bo");
	cgs.media.ccFilterPics[4] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_healthammo");
	cgs.media.ccFilterPics[5] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_construction");
	cgs.media.ccFilterPics[6] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_destruction");
	cgs.media.ccFilterPics[7] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_landmine");
	cgs.media.ccFilterPics[8] = trap_R_RegisterShaderNoMip("gfx/limbo/filter_objective");

	cgs.media.ccFilterBackOn  = trap_R_RegisterShaderNoMip("gfx/limbo/filter_back_on");
	cgs.media.ccFilterBackOff = trap_R_RegisterShaderNoMip("gfx/limbo/filter_back_off");

	// class icons
	cgs.media.skillPics[SK_BATTLE_SENSE]                             = trap_R_RegisterShaderNoMip("gfx/limbo/ic_battlesense");
	cgs.media.skillPics[SK_EXPLOSIVES_AND_CONSTRUCTION]              = trap_R_RegisterShaderNoMip("gfx/limbo/ic_engineer");
	cgs.media.skillPics[SK_FIRST_AID]                                = trap_R_RegisterShaderNoMip("gfx/limbo/ic_medic");
	cgs.media.skillPics[SK_SIGNALS]                                  = trap_R_RegisterShaderNoMip("gfx/limbo/ic_fieldops");
	cgs.media.skillPics[SK_LIGHT_WEAPONS]                            = trap_R_RegisterShaderNoMip("gfx/limbo/ic_lightweap");
	cgs.media.skillPics[SK_HEAVY_WEAPONS]                            = trap_R_RegisterShaderNoMip("gfx/limbo/ic_soldier");
	cgs.media.skillPics[SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS] = trap_R_RegisterShaderNoMip("gfx/limbo/ic_covertops");

	// used in:
	//  statsranksmedals
	//  command map
	//  limbo menu
	cgs.media.ccStamps[0] = trap_R_RegisterShaderNoMip("ui/assets2/stamp_complete");
	cgs.media.ccStamps[1] = trap_R_RegisterShaderNoMip("ui/assets2/stamp_failed");

	cgs.media.ccPlayerHighlight    = trap_R_RegisterShaderNoMip("ui/assets/mp_player_highlight.tga");
	cgs.media.ccConstructIcon[0]   = trap_R_RegisterShaderNoMip("gfx/limbo/cm_constaxis");
	cgs.media.ccConstructIcon[1]   = trap_R_RegisterShaderNoMip("gfx/limbo/cm_constallied");
	cgs.media.ccDestructIcon[0][0] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_axisgren");
	cgs.media.ccDestructIcon[0][1] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_alliedgren");
	cgs.media.ccDestructIcon[1][0] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_satchel");
	cgs.media.ccDestructIcon[1][1] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_satchel");
	cgs.media.ccDestructIcon[2][0] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_dynamite");
	cgs.media.ccDestructIcon[2][1] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_dynamite");
	cgs.media.ccTankIcon           = trap_R_RegisterShaderNoMip("gfx/limbo/cm_churchill"); // FIXME: add gfx/limbo/cm_jagdpanther?

	cgs.media.ccCmdPost[0] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_bo_axis");
	cgs.media.ccCmdPost[1] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_bo_allied");

	cgs.media.ccMortarHit       = trap_R_RegisterShaderNoMip("gfx/limbo/cm_mort_hit");
	cgs.media.ccMortarTarget    = trap_R_RegisterShaderNoMip("gfx/limbo/cm_mort_target");
	cgs.media.mortarTarget      = trap_R_RegisterShaderNoMip("gfx/limbo/mort_target");
	cgs.media.mortarTargetArrow = trap_R_RegisterShaderNoMip("gfx/limbo/mort_targetarrow");

	//cgs.media.ccskillPics[SK_BATTLE_SENSE]                             = trap_R_RegisterShaderNoMip("gfx/limbo/cm_ic_battlesense"); // no usage and not declared in legacy_sprite shader
	cgs.media.ccskillPics[SK_EXPLOSIVES_AND_CONSTRUCTION] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_ic_engineer");
	cgs.media.ccskillPics[SK_FIRST_AID]                   = trap_R_RegisterShaderNoMip("gfx/limbo/cm_ic_medic");
	cgs.media.ccskillPics[SK_SIGNALS]                     = trap_R_RegisterShaderNoMip("gfx/limbo/cm_ic_fieldops");
	//cgs.media.ccskillPics[SK_LIGHT_WEAPONS]                            = trap_R_RegisterShaderNoMip("gfx/limbo/cc_im_lightweap"); // no usage and not declared in legacy_sprite shader
	cgs.media.ccskillPics[SK_HEAVY_WEAPONS]                            = trap_R_RegisterShaderNoMip("gfx/limbo/cm_ic_soldier");
	cgs.media.ccskillPics[SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS] = trap_R_RegisterShaderNoMip("gfx/limbo/cm_ic_covertops");

	cgs.media.ccMedicIcon             = trap_R_RegisterShaderNoMip("sprites/cm_medic_icon");
	cgs.media.ccMedicReviveShader     = trap_R_RegisterShaderNoMip("sprites/cm_medic_revive");
	cgs.media.ccAmmoIcon              = trap_R_RegisterShaderNoMip("sprites/cm_ammo_icon");
	cgs.media.ccVoiceChatShader       = trap_R_RegisterShaderNoMip("sprites/cm_voicechat_icon");
	cgs.media.ccVoiceChatOrangeShader = trap_R_RegisterShaderNoMip("sprites/cm_voicechat_orange_icon");
	cgs.media.ccGreenTick             = trap_R_RegisterShaderNoMip("sprites/cm_greentick");
	cgs.media.ccRedCross              = trap_R_RegisterShaderNoMip("sprites/cm_redcross");
	cgs.media.ccFriendShader          = trap_R_RegisterShaderNoMip("sprites/cm_friendlycross");

#ifdef FEATURE_PRESTIGE
	cgs.media.prestigePics[0] = trap_R_RegisterShaderNoMip("gfx/hud/prestige/prestige");
	cgs.media.prestigePics[1] = trap_R_RegisterShaderNoMip("gfx/hud/prestige/prestige_stamp");
	cgs.media.prestigePics[2] = trap_R_RegisterShaderNoMip("gfx/hud/prestige/prestige_collect");
#endif

	CG_LoadRankIcons();

	// limbo menu setup
	CG_LimboPanel_Init();

	CG_ChatPanel_Setup();

	CG_Fireteams_Setup();

	CG_Spawnpoints_Setup();

	cgs.media.railCoreShader = trap_R_RegisterShaderNoMip("railCore");       // for debugging server traces
	cgs.media.ropeShader     = trap_R_RegisterShader("textures/props/cable_m01");

	cgs.media.thirdPersonBinocModel = trap_R_RegisterModel("models/multiplayer/binocs/binocs.md3");
	cgs.media.flamebarrel           = trap_R_RegisterModel("models/furniture/barrel/barrel_a.md3");
	cgs.media.mg42muzzleflash       = trap_R_RegisterModel("models/weapons2/machinegun/mg42_flash.md3");

	// shards
	cgs.media.shardGlass1 = trap_R_RegisterModel("models/shards/glass1.md3");
	cgs.media.shardGlass2 = trap_R_RegisterModel("models/shards/glass2.md3");
	cgs.media.shardWood1  = trap_R_RegisterModel("models/shards/wood1.md3");
	cgs.media.shardWood2  = trap_R_RegisterModel("models/shards/wood2.md3");
	cgs.media.shardMetal1 = trap_R_RegisterModel("models/shards/metal1.md3");
	cgs.media.shardMetal2 = trap_R_RegisterModel("models/shards/metal2.md3");
	//cgs.media.shardCeramic1 = trap_R_RegisterModel( "models/shards/ceramic1.md3" );
	//cgs.media.shardCeramic2 = trap_R_RegisterModel( "models/shards/ceramic2.md3" );

	// see see cgs.media.debBlock
	//cgs.media.shardRubble1 = trap_R_RegisterModel("models/mapobjects/debris/brick000.md3");
	//cgs.media.shardRubble2 = trap_R_RegisterModel("models/mapobjects/debris/brick001.md3");
	//cgs.media.shardRubble3 = trap_R_RegisterModel("models/mapobjects/debris/brick002.md3");

	for (i = 0; i < MAX_LOCKER_DEBRIS; i++)
	{
		Com_sprintf(name, sizeof(name), "models/mapobjects/debris/personal%i.md3", i + 1);
		cgs.media.shardJunk[i] = trap_R_RegisterModel(name);
	}

	CG_LoadingString(" - heads-up display -");
	CG_Hud_Setup();

	Com_Memset(cg_weapons, 0, sizeof(cg_weapons));

	CG_LoadingString(" - weapons -");
	for (i = WP_KNIFE; i < WP_NUM_WEAPONS; i++)
	{
		CG_RegisterWeapon(i, qfalse);
	}

	CG_LoadingString(" - items -");
	for (i = 1 ; i < ITEM_MAX_ITEMS ; i++)
	{
		CG_RegisterItemVisuals(i);
	}

	// cgs.media.grenadeExplosionShader = trap_R_RegisterShader("grenadeExplosion"); // unused FIXME: remove from shader def
	// cgs.media.rocketExplosionShader = trap_R_RegisterShader("rocketExplosion");   // unused

	cgs.media.hWeaponSnd     = trap_S_RegisterSound("sound/weapons/mg42/mg42_fire.wav", qfalse);
	cgs.media.hWeaponEchoSnd = trap_S_RegisterSound("sound/weapons/mg42/mg42_far.wav", qfalse);
	cgs.media.hWeaponHeatSnd = trap_S_RegisterSound("sound/weapons/mg42/mg42_heat.wav", qfalse);

	cgs.media.hWeaponSnd_2     = trap_S_RegisterSound("sound/weapons/browning/browning_fire.wav", qfalse);
	cgs.media.hWeaponEchoSnd_2 = trap_S_RegisterSound("sound/weapons/browning/browning_far.wav", qfalse);
	cgs.media.hWeaponHeatSnd_2 = trap_S_RegisterSound("sound/weapons/browning/browning_heat.wav", qfalse);

	// FIXME - find a better sound
	cgs.media.hflakWeaponSnd = trap_S_RegisterSound("sound/vehicles/misc/20mm_fire.wav", qfalse);

	cgs.media.minePrimedSound = trap_S_RegisterSound("sound/weapons/landmine/mine_on.wav", qfalse);

	// wall marks
	cgs.media.burnMarkShader     = trap_R_RegisterShaderNoMip("gfx/damage/burn_med_mrk");
	cgs.media.shadowFootShader   = trap_R_RegisterShaderNoMip("markShadowFoot");
	cgs.media.shadowTorsoShader  = trap_R_RegisterShaderNoMip("markShadowTorso");
	cgs.media.wakeMarkShader     = trap_R_RegisterShaderNoMip("wake");
	cgs.media.wakeMarkShaderAnim = trap_R_RegisterShaderNoMip("wakeAnim");

	for (i = 0 ; i < 5 ; i++)
	{
		//Com_sprintf( name, sizeof(name), "textures/decals/blood%i", i+1 );
		//cgs.media.bloodMarkShaders[i] = trap_R_RegisterShader( name );
		Com_sprintf(name, sizeof(name), "blood_dot%i", i + 1);
		cgs.media.bloodDotShaders[i] = trap_R_RegisterShader(name);
	}

	CG_LoadingString(" - inline models -");

	// register the inline models
	cgs.numInlineModels = trap_CM_NumInlineModels();
	// as a safety check, let's not let the number of models exceed MAX_MODELS
	if (cgs.numInlineModels > MAX_MODELS)
	{
		CG_Error("CG_RegisterGraphics: Too many inline models: %i\n", cgs.numInlineModels);
		//cgs.numInlineModels = MAX_MODELS;
	}

	for (i = 1 ; i < cgs.numInlineModels ; i++)
	{
		vec3_t mins, maxs;
		int    j;

		Com_sprintf(name, sizeof(name), "*%i", i);
		cgs.inlineDrawModel[i] = trap_R_RegisterModel(name);
		trap_R_ModelBounds(cgs.inlineDrawModel[i], mins, maxs);
		for (j = 0 ; j < 3 ; j++)
		{
			cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5f * (maxs[j] - mins[j]);
		}
	}

	CG_LoadingString(" - server 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.gameModels[i] = trap_R_RegisterModel(modelName);

		if (!cgs.gameModels[i])
		{
			CG_Printf("^3Warning: Register server model '%s' failed - no valid file in paths.\n", modelName);
		}
	}

	for (i = 1 ; i < MAX_CS_SKINS ; i++)
	{
		const char *skinName;

		skinName = CG_ConfigString(CS_SKINS + i);
		if (!skinName[0])
		{
			break;
		}
		cgs.gameModelSkins[i] = trap_R_RegisterSkin(skinName);
	}

	for (i = 1 ; i < MAX_CS_SHADERS ; i++)
	{
		const char *shaderName;

		shaderName = CG_ConfigString(CS_SHADERS + i);
		if (!shaderName[0])
		{
			break;
		}
		cgs.gameShaders[i] = shaderName[0] == '*' ? trap_R_RegisterShader(shaderName + 1) : trap_R_RegisterShaderNoMip(shaderName);
		Q_strncpyz(cgs.gameShaderNames[i], shaderName[0] == '*' ? shaderName + 1 : shaderName, MAX_QPATH);
	}

	for (i = 1 ; i < MAX_CHARACTERS ; i++)
	{
		const char *characterName;

		characterName = CG_ConfigString(CS_CHARACTERS + i);
		if (!characterName[0])
		{
			break;
		}

		if (!BG_FindCharacter(characterName))
		{
			cgs.gameCharacters[i] = BG_FindFreeCharacter(characterName);

			Q_strncpyz(cgs.gameCharacters[i]->characterFile, characterName, sizeof(cgs.gameCharacters[i]->characterFile));

			if (!CG_RegisterCharacter(characterName, cgs.gameCharacters[i]))
			{
				CG_Error("ERROR: CG_RegisterGraphics: failed to load character file '%s'\n", characterName);
			}
		}
	}

	CG_LoadingString(" - particles -");
	CG_InitParticles();

	InitSmokeSprites();

	CG_LoadingString(" - classes -");

	CG_RegisterPlayerClasses();

	CG_InitPMGraphics();

	// mounted gun on tank models
	cgs.media.hMountedMG42Base = trap_R_RegisterModel("models/mapobjects/tanks_sd/mg42nestbase.md3");
	cgs.media.hMountedMG42Nest = trap_R_RegisterModel("models/mapobjects/tanks_sd/mg42nest.md3");
	cgs.media.hMountedMG42     = trap_R_RegisterModel("models/mapobjects/tanks_sd/mg42.md3");
	cgs.media.hMountedBrowning = trap_R_RegisterModel("models/multiplayer/browning/thirdperson.md3");

	// FIXME: temp models
	// these 1st p models are registered twice ... MG and browning use it ...
	cgs.media.hMountedFPMG42     = trap_R_RegisterModel("models/multiplayer/mg42/v_mg42.md3");
	cgs.media.hMountedFPBrowning = trap_R_RegisterModel("models/multiplayer/browning/tankmounted.md3");

	cgs.media.medicIcon = trap_R_RegisterShaderNoMip("sprites/voiceMedic");
	cgs.media.ammoIcon  = trap_R_RegisterShaderNoMip("sprites/voiceAmmo");

	CG_RegisterFonts();

	cgs.media.medal_back = trap_R_RegisterShaderNoMip("gfx/limbo/medal_back");

	cgs.media.limboNumber_roll = trap_R_RegisterShaderNoMip("gfx/limbo/number_roll");
	cgs.media.limboNumber_back = trap_R_RegisterShaderNoMip("gfx/limbo/number_back");
	cgs.media.limboStar_roll   = trap_R_RegisterShaderNoMip("gfx/limbo/skill_roll");
	cgs.media.limboStar_back   = trap_R_RegisterShaderNoMip("gfx/limbo/skill_back");
	cgs.media.limboLight_on    = trap_R_RegisterShaderNoMip("gfx/limbo/redlight_on");
	cgs.media.limboLight_on2   = trap_R_RegisterShaderNoMip("gfx/limbo/redlight_on02");
	cgs.media.limboLight_off   = trap_R_RegisterShaderNoMip("gfx/limbo/redlight_off");

	cgs.media.limboWeaponNumber_off = trap_R_RegisterShaderNoMip("gfx/limbo/but_weap_off");
	cgs.media.limboWeaponNumber_on  = trap_R_RegisterShaderNoMip("gfx/limbo/but_weap_on");
	cgs.media.limboWeaponCard       = trap_R_RegisterShaderNoMip("gfx/limbo/weap_card");

	cgs.media.limboWeaponCardSurroundH = trap_R_RegisterShaderNoMip("gfx/limbo/butsur_hor");
	cgs.media.limboWeaponCardSurroundV = trap_R_RegisterShaderNoMip("gfx/limbo/butsur_vert");
	cgs.media.limboWeaponCardSurroundC = trap_R_RegisterShaderNoMip("gfx/limbo/butsur_corn");

	cgs.media.limboWeaponCardOOS = trap_R_RegisterShaderNoMip("gfx/limbo/outofstock");

	cgs.media.limboClassButtons[PC_ENGINEER]  = trap_R_RegisterShaderNoMip("gfx/limbo/ic_engineer");
	cgs.media.limboClassButtons[PC_SOLDIER]   = trap_R_RegisterShaderNoMip("gfx/limbo/ic_soldier");
	cgs.media.limboClassButtons[PC_COVERTOPS] = trap_R_RegisterShaderNoMip("gfx/limbo/ic_covertops");
	cgs.media.limboClassButtons[PC_FIELDOPS]  = trap_R_RegisterShaderNoMip("gfx/limbo/ic_fieldops");
	cgs.media.limboClassButtons[PC_MEDIC]     = trap_R_RegisterShaderNoMip("gfx/limbo/ic_medic");
	cgs.media.limboSkillsBS                   = trap_R_RegisterShaderNoMip("gfx/limbo/ic_battlesense");
	cgs.media.limboSkillsLW                   = trap_R_RegisterShaderNoMip("gfx/limbo/ic_lightweap");

	cgs.media.limboClassButton2Back_on         = trap_R_RegisterShaderNoMip("gfx/limbo/skill_back_on");
	cgs.media.limboClassButton2Back_off        = trap_R_RegisterShaderNoMip("gfx/limbo/skill_back_off");
	cgs.media.limboClassButton2Wedge_off       = trap_R_RegisterShaderNoMip("gfx/limbo/skill_4pieces_off");
	cgs.media.limboClassButton2Wedge_on        = trap_R_RegisterShaderNoMip("gfx/limbo/skill_4pieces_on");
	cgs.media.limboClassButtons2[PC_ENGINEER]  = trap_R_RegisterShaderNoMip("gfx/limbo/skill_engineer");
	cgs.media.limboClassButtons2[PC_SOLDIER]   = trap_R_RegisterShaderNoMip("gfx/limbo/skill_soldier");
	cgs.media.limboClassButtons2[PC_COVERTOPS] = trap_R_RegisterShaderNoMip("gfx/limbo/skill_covops");
	cgs.media.limboClassButtons2[PC_FIELDOPS]  = trap_R_RegisterShaderNoMip("gfx/limbo/skill_fieldops");
	cgs.media.limboClassButtons2[PC_MEDIC]     = trap_R_RegisterShaderNoMip("gfx/limbo/skill_medic");

	cgs.media.limboTeamButtonBack_on  = trap_R_RegisterShaderNoMip("gfx/limbo/but_team_on");
	cgs.media.limboTeamButtonBack_off = trap_R_RegisterShaderNoMip("gfx/limbo/but_team_off");
	cgs.media.limboTeamButtonAllies   = trap_R_RegisterShaderNoMip("gfx/limbo/but_team_allied");
	cgs.media.limboTeamButtonAxis     = trap_R_RegisterShaderNoMip("gfx/limbo/but_team_axis");
	cgs.media.limboTeamButtonSpec     = trap_R_RegisterShaderNoMip("gfx/limbo/but_team_spec");

	cgs.media.limboBlendThingy       = trap_R_RegisterShaderNoMip("gfx/limbo/cc_blend");
	cgs.media.limboWeaponBlendThingy = trap_R_RegisterShaderNoMip("gfx/limbo/weap_blend");

	cgs.media.limboCounterBorder = trap_R_RegisterShaderNoMip("gfx/limbo/number_border");

	cgs.media.hudPowerIcon  = trap_R_RegisterShaderNoMip("gfx/hud/ic_power");
	cgs.media.hudSprintIcon = trap_R_RegisterShaderNoMip("gfx/hud/ic_stamina");
	cgs.media.hudHealthIcon = trap_R_RegisterShaderNoMip("gfx/hud/ic_health");

	cgs.media.limboWeaponCardArrow = trap_R_RegisterShaderNoMip("gfx/limbo/weap_dnarrow.tga");

	cgs.media.limboObjectiveBack[0] = trap_R_RegisterShaderNoMip("gfx/limbo/objective_back_axis");
	cgs.media.limboObjectiveBack[1] = trap_R_RegisterShaderNoMip("gfx/limbo/objective_back_allied");
	cgs.media.limboObjectiveBack[2] = trap_R_RegisterShaderNoMip("gfx/limbo/objective_back");

	cgs.media.limboClassBar = trap_R_RegisterShaderNoMip("gfx/limbo/lightup_bar");

	cgs.media.limboBriefingButtonOn      = trap_R_RegisterShaderNoMip("gfx/limbo/but_play_on");
	cgs.media.limboBriefingButtonOff     = trap_R_RegisterShaderNoMip("gfx/limbo/but_play_off");
	cgs.media.limboBriefingButtonStopOn  = trap_R_RegisterShaderNoMip("gfx/limbo/but_stop_on");
	cgs.media.limboBriefingButtonStopOff = trap_R_RegisterShaderNoMip("gfx/limbo/but_stop_off");

	cgs.media.limboSpectator      = trap_R_RegisterShaderNoMip("gfx/limbo/spectator");
	cgs.media.limboShoutcaster    = trap_R_RegisterShaderNoMip("gfx/limbo/shoutcaster");
	cgs.media.limboRadioBroadcast = trap_R_RegisterShaderNoMip("ui/assets/radio_tower");

	cgs.media.limboTeamLocked = trap_R_RegisterShaderNoMip("gfx/limbo/lock");

	cgs.media.cursorIcon = trap_R_RegisterShaderNoMip("ui/assets/3_cursor3");

	cgs.media.hudDamagedStates[0] = trap_R_RegisterSkin("models/players/hud/damagedskins/blood01.skin");
	cgs.media.hudDamagedStates[1] = trap_R_RegisterSkin("models/players/hud/damagedskins/blood02.skin");
	cgs.media.hudDamagedStates[2] = trap_R_RegisterSkin("models/players/hud/damagedskins/blood03.skin");
	cgs.media.hudDamagedStates[3] = trap_R_RegisterSkin("models/players/hud/damagedskins/blood04.skin");

	cgs.media.axisFlag       = trap_R_RegisterShaderNoMip("gfx/limbo/flag_axis");
	cgs.media.alliedFlag     = trap_R_RegisterShaderNoMip("gfx/limbo/flag_allied");
	cgs.media.spectatorFlag  = trap_R_RegisterShaderNoMip("gfx/limbo/flag_spectator");
	cgs.media.disconnectIcon = trap_R_RegisterShaderNoMip("gfx/2d/net");

	cgs.media.cm_spec_icon  = trap_R_RegisterShaderNoMip("ui/assets/mp_spec");
	cgs.media.cm_arrow_spec = trap_R_RegisterShaderNoMip("ui/assets/mp_arrow_spec");

	cgs.media.fireteamIcon = trap_R_RegisterShaderNoMip("sprites/fireteam");

	cgs.media.spawnpointMarker = trap_R_RegisterShaderNoMip("textures/sfx/spawnpoint_marker");

	cgs.media.demoPlay        = trap_R_RegisterShaderNoMip("gfx/2d/demo_controls_play");
	cgs.media.demoPause       = trap_R_RegisterShaderNoMip("gfx/2d/demo_controls_pause");
	cgs.media.demoRewind      = trap_R_RegisterShaderNoMip("gfx/2d/demo_controls_rewind");
	cgs.media.demoFastForward = trap_R_RegisterShaderNoMip("gfx/2d/demo_controls_fastforward");

	// NOTE: load smoke puff as last shader to always draw on top of other shaders
	// because renderer order the draw depth level by register index
	// i.e: this allow smoke grenade to hide players icons over head
	// or simple item icon on ground
	cgs.media.smokePuffShader = trap_R_RegisterShader("smokePuff");

	CG_LoadingString(" - game media -");
}

/**
 * @brief CG_RegisterClients
 */
static void CG_RegisterClients(void)
{
	int i;

	for (i = 0 ; i < MAX_CLIENTS ; i++)
	{
		const char *clientInfo;

		clientInfo = CG_ConfigString(CS_PLAYERS + i);
		if (!clientInfo[0])
		{
			continue;
		}
		CG_NewClientInfo(i);
	}
}

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

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

/**
 * @brief CG_ConfigStringCopy
 * @param[in] index
 * @param[out] buff
 * @param[in] buffsize
 * @return
 */
int CG_ConfigStringCopy(int index, char *buff, size_t buffsize)
{
	Q_strncpyz(buff, CG_ConfigString(index), buffsize);
	return strlen(buff);
}

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

/**
 * @brief CG_StartMusic
 */
void CG_StartMusic(void)
{
	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));

	if (strlen(parm1))
	{
		trap_S_StartBackgroundTrack(parm1, parm2, 0);
	}
}

/**
 * @brief CG_QueueMusic
 */
void CG_QueueMusic(void)
{
	char *s;
	char parm[MAX_QPATH];

	// prepare the next background track
	s = (char *)CG_ConfigString(CS_MUSIC_QUEUE);
	Q_strncpyz(parm, COM_Parse(&s), sizeof(parm));

	// even if no strlen(parm).  we want to be able to clear the queue

	// TODO: \/     the values stored in here will be made accessable so
	//              it doesn't have to go through startbackgroundtrack() (which is stupid)
	trap_S_StartBackgroundTrack(parm, "", -2);    // '-2' for 'queue looping track' (QUEUED_PLAY_LOOPED)
}

/**
 * @brief CG_OwnerDrawHandleKey
 * @param ownerDraw - unused
 * @param flags - unused
 * @param special - unused
 * @param key - unused
 * @return TODO: always true
 * @todo not yet implemented ? referenced in cgDC.ownerDrawHandleKey and return always true
 */
static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, int *special, int key)
{
	return qfalse;
}

/**
 * @brief CG_FeederCount
 * @param[in] feederID
 * @return
 */
static int CG_FeederCount(int feederID)
{
	int i, count = 0;

	if (feederID == FEEDER_REDTEAM_LIST)
	{
		for (i = 0; i < cg.numScores; i++)
		{
			if (cg.scores[i].team == TEAM_AXIS)
			{
				count++;
			}
		}
	}
	else if (feederID == FEEDER_BLUETEAM_LIST)
	{
		for (i = 0; i < cg.numScores; i++)
		{
			if (cg.scores[i].team == TEAM_ALLIES)
			{
				count++;
			}
		}
	}
	else if (feederID == FEEDER_SCOREBOARD)
	{
		return cg.numScores;
	}

	return count;
}

/**
 * @brief CG_InfoFromScoreIndex
 * @param[in] index
 * @param[in] team
 * @param[out] scoreIndex
 * @return
 */
static clientInfo_t *CG_InfoFromScoreIndex(int index, int team, int *scoreIndex)
{
	int i, count = 0;

	for (i = 0; i < cg.numScores; i++)
	{
		if (cg.scores[i].team == team)
		{
			if (count == index)
			{
				*scoreIndex = i;
				return &cgs.clientinfo[cg.scores[i].client];
			}
			count++;
		}
	}

	*scoreIndex = index;

	return &cgs.clientinfo[cg.scores[index].client];
}

/**
 * @brief CG_FeederItemText
 * @param[in] feederID
 * @param[in] index
 * @param[in] column
 * @param[out] handle
 * @param numhandles - unused
 * @return
 */
static const char *CG_FeederItemText(int feederID, int index, int column, qhandle_t *handle, int *numhandles)
{
	int          scoreIndex = 0;
	clientInfo_t *info      = NULL;
	int          team       = -1;
	score_t      *sp        = NULL;

	*handle = -1;

	if (feederID == FEEDER_REDTEAM_LIST)
	{
		team = TEAM_AXIS;
	}
	else if (feederID == FEEDER_BLUETEAM_LIST)
	{
		team = TEAM_ALLIES;
	}

	info = CG_InfoFromScoreIndex(index, team, &scoreIndex);
	sp   = &cg.scores[scoreIndex];

	if (info && info->infoValid)
	{
		switch (column)
		{
		case 0:
			break;
		case 3:
			return info->name;
		case 4:
			return va("%i", info->score);
		case 5:
			return va("%4i", sp->time);
		case 6:
			if (sp->ping == -1)
			{
				return "connecting";
			}
			return va("%4i", sp->ping);
		}
	}

	return "";
}

/**
 * @brief CG_FeederItemImage
 * @param feederID - unused
 * @param index - unused
 * @return
 * @note not yet implemented. referenced in cgDC.feederItemImage but return always 0
 */
static qhandle_t CG_FeederItemImage(int feederID, int index)
{
	return 0;
}

/**
 * @brief CG_FeederSelection
 * @param[in] feederID
 * @param[in] index
 */
static void CG_FeederSelection(int feederID, int index)
{
	int i, count = 0;
	int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_AXIS : TEAM_ALLIES;

	for (i = 0; i < cg.numScores; i++)
	{
		if (cg.scores[i].team == team)
		{
			if (index == count)
			{
				cg.selectedScore = i;
			}
			count++;
		}
	}
}

/**
 * @brief CG_Cvar_Get
 * @param[in] cvar
 * @return
 */
float CG_Cvar_Get(const char *cvar)
{
	char buff[128];

	Com_Memset(buff, 0, sizeof(buff));
	trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff));
	return Q_atof(buff);
}

/**
 * @brief CG_OwnerDrawWidth
 * @param ownerDraw - unused
 * @param scale - unused
 * @return
 * @note
 */
static int CG_OwnerDrawWidth(int ownerDraw, float scale)
{
	return 0;
}

/**
 * @brief CG_PlayCinematic
 * @param[in] name
 * @param[in] x
 * @param[in] y
 * @param[in] w
 * @param[in] h
 * @return
 */
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);
}

/**
 * @brief CG_StopCinematic
 * @param[in] handle
 */
static void CG_StopCinematic(int handle)
{
	trap_CIN_StopCinematic(handle);
}

/**
 * @brief CG_DrawCinematic
 * @param[in] handle
 * @param[in] x
 * @param[in] y
 * @param[in] w
 * @param[in] h
 */
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);
}

/**
 * @brief CG_RunCinematicFrame
 * @param[in] handle
 */
static void CG_RunCinematicFrame(int handle)
{
	trap_CIN_RunCinematic(handle);
}

/**
 * @brief CG_LoadHudMenu
 */
void CG_LoadHudMenu(void)
{
	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.drawTextExt           = &CG_Text_Paint_Ext;
	cgDC.textWidth             = &CG_Text_Width;
	cgDC.textWidthExt          = &CG_Text_Width_Ext;
	cgDC.textHeight            = &CG_Text_Height;
	cgDC.textHeightExt         = &CG_Text_Height_Ext;
	cgDC.textFont              = &CG_Text_SetActiveFont;
	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         = NULL;
	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.drawTextWithCursorExt = &CG_Text_PaintWithCursor_Ext;
	cgDC.setOverstrikeMode     = &trap_Key_SetOverstrikeMode;
	cgDC.getOverstrikeMode     = &trap_Key_GetOverstrikeMode;
	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.setBinding            = &trap_Key_SetBinding;
	cgDC.getBindingBuf         = &trap_Key_GetBindingBuf;
	cgDC.getKeysForBinding     = &trap_Key_KeysForBinding;
	cgDC.keynumToStringBuf     = &trap_Key_KeynumToStringBuf;
	cgDC.translateString       = &CG_TranslateString;
	//cgDC.executeText = &trap_Cmd_ExecuteText;
	cgDC.Error          = &Com_Error;
	cgDC.Print          = &Com_Printf;
	cgDC.ownerDrawWidth = &CG_OwnerDrawWidth;
	//cgDC.Pause = &CG_Pause;
	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;
	cgDC.descriptionForCampaign = &CG_DescriptionForCampaign;
	cgDC.nameForCampaign        = &CG_NameForCampaign;
	cgDC.add2dPolys             = &trap_R_Add2dPolys;
	cgDC.updateScreen           = &trap_UpdateScreen;
	cgDC.getHunkData            = &trap_GetHunkData;
	cgDC.getConfigString        = &CG_ConfigStringCopy;

	cgDC.xscale = cgs.screenXScale;
	cgDC.yscale = cgs.screenYScale;

	Init_Display(&cgDC);

	Menu_Reset();

	CG_Text_SetActiveFont(0);
}

/**
 * @brief
 * Shim to load a path in a compatibility subdir first, before falling back to
 * passed path.
 */
int CG_LoadCompatSource(const char *filename)
{
	if (cg.demoBackwardsCompat.compatPath[0] != '\0')
	{
		int res = trap_PC_LoadSource(va("%s%c%s", cg.demoBackwardsCompat.compatPath, PATH_SEP, filename));
		if (res != 0)
		{
			if (developer.integer)
			{
				CG_Printf("CG_LoadCompatSource: %s\n", va("%s%c%s", cg.demoBackwardsCompat.compatPath, PATH_SEP, filename));
			}
			return res;
		}
	}

	return trap_PC_LoadSource(filename);
}

/**
 * @brief
 * Shim to load a path in a compatibility subdir first, before falling back to
 * passed path.
 */
int CG_FOpenCompatFile(const char *qpath, fileHandle_t *f, fsMode_t mode)
{
	if (cg.demoBackwardsCompat.compatPath[0] != '\0')
	{
		int res = trap_FS_FOpenFile(va("%s%c%s", cg.demoBackwardsCompat.compatPath, PATH_SEP, qpath), f, mode);
		if (res != -1)
		{
			if (developer.integer)
			{
				CG_Printf("CG_FOpenCompatFile: %s\n", va("%s%c%s", cg.demoBackwardsCompat.compatPath, PATH_SEP, qpath));
			}
			return res;
		}
	}

	return trap_FS_FOpenFile(qpath, f, mode);
}

/**
 * @brief CG_AssetCache
 */
void CG_AssetCache(void)
{
	cgDC.Assets.gradientBar         = trap_R_RegisterShaderNoMip(ASSET_GRADIENTBAR);
	cgDC.Assets.gradientRound       = trap_R_RegisterShaderNoMip(ASSET_GRADIENTROUND);
	cgDC.Assets.fxBasePic           = trap_R_RegisterShaderNoMip(ART_FX_BASE);
	cgDC.Assets.fxPic[0]            = trap_R_RegisterShaderNoMip(ART_FX_RED);
	cgDC.Assets.fxPic[1]            = trap_R_RegisterShaderNoMip(ART_FX_YELLOW);
	cgDC.Assets.fxPic[2]            = trap_R_RegisterShaderNoMip(ART_FX_GREEN);
	cgDC.Assets.fxPic[3]            = trap_R_RegisterShaderNoMip(ART_FX_TEAL);
	cgDC.Assets.fxPic[4]            = trap_R_RegisterShaderNoMip(ART_FX_BLUE);
	cgDC.Assets.fxPic[5]            = trap_R_RegisterShaderNoMip(ART_FX_CYAN);
	cgDC.Assets.fxPic[6]            = trap_R_RegisterShaderNoMip(ART_FX_WHITE);
	cgDC.Assets.scrollBar           = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR);
	cgDC.Assets.scrollBarArrowDown  = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWDOWN);
	cgDC.Assets.scrollBarArrowUp    = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWUP);
	cgDC.Assets.scrollBarArrowLeft  = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWLEFT);
	cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip(ASSET_SCROLLBAR_ARROWRIGHT);
	cgDC.Assets.scrollBarThumb      = trap_R_RegisterShaderNoMip(ASSET_SCROLL_THUMB);
	cgDC.Assets.sliderBar           = trap_R_RegisterShaderNoMip(ASSET_SLIDER_BAR);
	cgDC.Assets.sliderThumb         = trap_R_RegisterShaderNoMip(ASSET_SLIDER_THUMB);
}

#ifdef ETLEGACY_DEBUG
#define DEBUG_INITPROFILE_INIT int elapsed, dbgTime = trap_Milliseconds();
#define DEBUG_INITPROFILE_EXEC(f) if (developer.integer) { CG_Printf("^5%s passed in %i msec\n", f, elapsed = trap_Milliseconds() - dbgTime);  dbgTime += elapsed; }
#else
#define DEBUG_INITPROFILE_INIT
#define DEBUG_INITPROFILE_EXEC(f)
#endif // ETLEGACY_DEBUG

static ID_INLINE void CG_SetupExtensionTrap(char *value, int valueSize, int *trap, const char *name)
{
	if (trap_GetValue(value, valueSize, name))
	{
		*trap = Q_atoi(value);
	}
	else
	{
		*trap = qfalse;
	}
}

static ID_INLINE void CG_SetupExtensions(void)
{
	char value[MAX_CVAR_VALUE_STRING];

	trap_Cvar_VariableStringBuffer("//trap_GetValue", value, sizeof(value));
	if (value[0])
	{
		dll_com_trapGetValue = Q_atoi(value);

		CG_SetupExtensionTrap(value, MAX_CVAR_VALUE_STRING, &dll_trap_SysFlashWindow, "trap_SysFlashWindow_Legacy");
		CG_SetupExtensionTrap(value, MAX_CVAR_VALUE_STRING, &dll_trap_CommandComplete, "trap_CommandComplete_Legacy");
		CG_SetupExtensionTrap(value, MAX_CVAR_VALUE_STRING, &dll_trap_CmdBackup_Ext, "trap_CmdBackup_Ext_Legacy");
		CG_SetupExtensionTrap(value, MAX_CVAR_VALUE_STRING, &dll_trap_MatchPaused, "trap_MatchPaused_Legacy");
		CG_SetupExtensionTrap(value, MAX_CVAR_VALUE_STRING, &dll_trap_CvarSetDescription, "trap_CvarSetDescription_Legacy");
	}
}

/**
 * @brief
 * Initialize 'cg.demoBackwardsCompat'.
 */
void CG_DemoBackwardsCompatInit()
{
	if (CG_IsDemoVersionBelow(2, 83, 0))
	{
		Q_strncpyz(cg.demoBackwardsCompat.compatPath, "compat/2_82_1", sizeof(cg.demoBackwardsCompat.compatPath));
	}
}

/**
 * @brief Called after every level change or subsystem restart
 * Will perform callbacks to make the loading info screen update.
 * @param serverMessageNum
 * @param serverCommandSequence
 * @param clientNum
 * @param demoPlayback
 * @param etLegacyClient
 * @param info
 * @param clientVersion
 */
void CG_Init(int serverMessageNum, int serverCommandSequence, int clientNum, qboolean demoPlayback, int etLegacyClient, demoPlayInfo_t *info, int clientVersion)
{
	const char *s;
	int        i;
	char       versionString[128];
	DEBUG_INITPROFILE_INIT

	//int startat = trap_Milliseconds();

	Com_Printf(S_COLOR_MDGREY "Initializing %s cgame " S_COLOR_GREEN "%s\n", MODNAME, ETLEGACY_VERSION);

	// clean up the config backup if one exists
	CG_RestoreProfile();

	// clear everything
	Com_Memset(&cgs, 0, sizeof(cgs));
	Com_Memset(&cg, 0, sizeof(cg));
	Com_Memset(cg_entities, 0, sizeof(cg_entities));
	Com_Memset(cg_weapons, 0, sizeof(cg_weapons));

	// Clear camera information
	CG_ClearCamera();

	cgs.initing = qtrue;

	if (demoPlayback && info)
	{
		cg.demoinfo = info;
	}

	for (i = 0; i < MAX_CLIENTS; i++)
	{
		cg.artilleryRequestTime[i] = -99999;
	}

	CG_InitStatsDebug();

	cgs.ccZoomFactor = 1.f;

	// sync to main refdef
	cg.refdef_current = &cg.refdef;

	cg.demoPlayback = demoPlayback;

	MOD_CHECK_ETLEGACY(clientVersion, cg.etLegacyClient);

	I18N_Init();

	// get the rendering configuration from the client system
	trap_GetGlconfig(&cgs.glconfig);
	cgs.screenXScale = (float)cgs.glconfig.vidWidth / 640.0f;
	cgs.screenYScale = (float)cgs.glconfig.vidHeight / 480.0f;

	cgDC.etLegacyClient = cg.etLegacyClient;

	if (cg.etLegacyClient <= 0)
	{
		cgs.glconfig.windowAspect = (float)cgs.glconfig.vidWidth / (float)cgs.glconfig.vidHeight;
	}

	// screen support ...
	cgs.adr43       = cgs.glconfig.windowAspect * RPRATIO43;       // aspectratio / (4/3)
	cgs.r43da       = RATIO43 * 1.0f / cgs.glconfig.windowAspect;  // (4/3) / aspectratio
	cgs.wideXoffset = (cgs.glconfig.windowAspect > RATIO43) ? (640.0f * cgs.adr43 - 640.0f) * 0.5f : 0.0f;

	// default to visible mouse cursor so that we have cursor on intermission,
	// in case we never call event handling change during a match
	cgDC.cursorVisible = qtrue;

	// DEBUG
	//CG_Printf("Screen[%f][%f]: as: %f   adr43: %f  r43da: %f off: %f\n", cgs.screenXScale, cgs.screenYScale, cgs.glconfig.windowAspect, cgs.adr43, cgs.r43da, cgs.wideXoffset);

	// init the anim scripting
	cgs.animScriptData.soundIndex = CG_SoundScriptPrecache;
	cgs.animScriptData.playSound  = CG_SoundPlayIndexedScript;

	cg.clientNum = clientNum;

	cgs.processedSnapshotNum  = serverMessageNum;
	cgs.serverCommandSequence = serverCommandSequence;

	cgs.ccRequestedObjective  = -1;
	cgs.ccCurrentCamObjective = -2;

	CG_SetupExtensions();
	trap_CmdBackup_Ext();

	// Background images on the loading screen were not visible on the first call
	trap_R_SetColor(NULL);

	// load a few needed things before we do any screen updates
	cgs.media.charsetShader     = trap_R_RegisterShader("gfx/2d/hudchars");   //trap_R_RegisterShader( "gfx/2d/bigchars" );
	cgs.media.menucharsetShader = trap_R_RegisterShader("gfx/2d/hudchars");
	cgs.media.whiteShader       = trap_R_RegisterShader("white");
	cgs.media.charsetProp       = trap_R_RegisterShaderNoMip("menu/art/font1_prop.tga");
	cgs.media.charsetPropGlow   = trap_R_RegisterShaderNoMip("menu/art/font1_prop_glo.tga");
	cgs.media.charsetPropB      = trap_R_RegisterShaderNoMip("menu/art/font2_prop.tga");

	CG_RegisterCvars();

	if (cg_logFile.string[0])
	{
		if (trap_FS_FOpenFile(cg_logFile.string, &cg.logFile, FS_APPEND) < 0)
		{
			CG_Printf("^3WARNING: Couldn't open client log: %s\n", cg_logFile.string);
		}
	}
	else
	{
		CG_Printf("Not logging client output to disk.\n");
	}

	// get the gamestate from the client system
	trap_GetGameState(&cgs.currentGameState);

	cg.warmupCount = -1;

	CG_ParseSysteminfo();
	CG_ParseServerinfo();
	CG_DemoBackwardsCompatInit();
	CG_ParseWolfinfo();
	CG_ParseServerToggles();

	CG_InitConsoleCommands();

	// moved this up so it's initialized for the loading screen
	CG_LoadHudMenu();      // load new hud stuff
	CG_AssetCache();

	// try execing map autoexec scripts
	CG_MapAutoexec(cgs.sv_cheats);

	// if cheats are enabled but devmap-specific configs aren't found,
	// fallback to regular configs
	if (cgs.sv_cheats && !cg.mapConfigLoaded)
	{
		CG_MapAutoexec(qfalse);
	}

	cgs.campaignInfoLoaded = qfalse;
	cgs.arenaInfoLoaded    = qfalse;

	if (cgs.gametype == GT_WOLF_CAMPAIGN)
	{
		CG_LocateCampaign();
	}
	else if (cgs.gametype == GT_WOLF_STOPWATCH || cgs.gametype == GT_WOLF_LMS || cgs.gametype == GT_WOLF || cgs.gametype == GT_WOLF_MAPVOTE)
	{
		CG_LocateArena();
	}

	CG_ClearTrails();

	// check version
	s = CG_ConfigString(CS_GAME_VERSION);
	if (strcmp(s, GAME_VERSION))
	{
		CG_Error("Client/Server game mismatch: '%s/%s'\n", GAME_VERSION, s);
	}

	/* mark old and new clients */
	if (cg.etLegacyClient <= 0)
	{
		trap_Cvar_VariableStringBuffer("version", versionString, sizeof(versionString));
		trap_Cvar_Set("cg_etVersion", versionString[0] ? versionString : "(undetected)");
	}
	else
	{
		Com_sprintf(versionString, sizeof(versionString), "%i", clientVersion);
		trap_Cvar_Set("cg_etVersion", va(PRODUCT_LABEL " v%c.%s %s", versionString[0], versionString + 1, CPUSTRING));
	}

#if 0
	trap_Cvar_Set("cg_legacyVersion", ETLEGACY_VERSION);
#endif

	s                  = CG_ConfigString(CS_LEVEL_START_TIME);
	cgs.levelStartTime = Q_atoi(s);

	s                         = CG_ConfigString(CS_INTERMISSION_START_TIME);
	cgs.intermissionStartTime = Q_atoi(s);

	CG_ParseServerVersionInfo(CG_ConfigString(CS_VERSIONINFO));
	CG_ParseReinforcementTimes(CG_ConfigString(CS_REINFSEEDS));

	CG_initStrings();
	CG_windowInit();

	DEBUG_INITPROFILE_EXEC("initialization");

	// load the new map
	CG_LoadingString(" - collision map -");

	trap_CM_LoadMap(cgs.mapname);

	DEBUG_INITPROFILE_EXEC("loadmap");

	String_Init();

	cg.loading = qtrue;     // force players to load instead of defer

	CG_LoadingString(" - sounds -");

	CG_RegisterSounds();

	DEBUG_INITPROFILE_EXEC("sounds");

	CG_LoadingString(" - graphics -");

	CG_RegisterGraphics();

	CG_LoadingString(" - flamechunks -");

	CG_InitFlameChunks();       // register and clear all flamethrower resources

	DEBUG_INITPROFILE_EXEC("graphics");

	CG_LoadingString(" - clients -");

	CG_RegisterClients();       // if low on memory, some clients will be deferred

	DEBUG_INITPROFILE_EXEC("clients");

	cg.loading = qfalse;    // future players will be deferred

	CG_InitLocalEntities();

	BG_BuildSplinePaths();

	CG_InitMarkPolys();

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

	// Make sure we have update values (scores)
	CG_SetConfigValues();

	CG_StartMusic();

	cg.lightstylesInited = qfalse;

	CG_LoadingString("");

	CG_ShaderStateChanged();

	CG_ChargeTimesChanged();

	CG_TeamRestrictionsChanged();

	CG_SkillLevelsChanged();

	trap_S_ClearLoopingSounds();
	trap_S_ClearSounds(qfalse);

	cg.teamWonRounds[1] = Q_atoi(CG_ConfigString(CS_ROUNDSCORES1));
	cg.teamWonRounds[0] = Q_atoi(CG_ConfigString(CS_ROUNDSCORES2));

	cg.filtercams = Q_atoi(CG_ConfigString(CS_FILTERCAMS)) ? qtrue : qfalse;

	CG_ParseFireteams();

	CG_ParseOIDInfos();

	CG_InitPM();

	CG_ParseSpawns();

	CG_ParseTagConnects();

	DEBUG_INITPROFILE_EXEC("misc");

	CG_ParseSkyBox();

	CG_SetupCabinets();

	trap_S_FadeAllSound(1.0f, 0, qfalse);     // fade sound up

	cgs.dumpStatsFile = 0;
	cgs.dumpStatsTime = 0;

	CG_LoadLocations();
	Com_Memset(cgs.clientLocation, 0, sizeof(cgs.clientLocation));

	CG_UpdateSvCvars();

	CG_ParseModInfo();

	if (demoPlayback)
	{
		CG_ParseDemoVersion();
	}

	//CG_Printf("Time taken: %i\n", trap_Milliseconds() - startat);

#ifdef FEATURE_EDV
	VectorSet(cgs.demoCamera.velocity, 0.0, 0.0, 0.0);
	cgs.demoCamera.startLean = qfalse;
	cgs.demoCamera.noclip    = qfalse;
	cgs.currentMenuLevel     = ML_MAIN;
#endif
}

/**
 * @brief Called before every level change or subsystem restart
 */
void CG_Shutdown(void)
{
	// some mods may need to do cleanup work here,
	// like closing files or archiving session data

	CG_EventHandling(CGAME_EVENT_NONE, qtrue);
	if (cg.demoPlayback)
	{
		trap_Cvar_Set("timescale", "1");
	}

	CG_RestoreProfile();

	if (cg.logFile)
	{
		trap_FS_FCloseFile(cg.logFile);
		cg.logFile = 0;
	}

	Q_UTF8_FreeFont(&cgs.media.limboFont1);
	Q_UTF8_FreeFont(&cgs.media.limboFont1_lo);
	Q_UTF8_FreeFont(&cgs.media.limboFont2);
	Q_UTF8_FreeFont(&cgs.media.limboFont2_lo);

	Q_UTF8_FreeFont(&cgs.media.bg_loadscreenfont1);
	Q_UTF8_FreeFont(&cgs.media.bg_loadscreenfont2);
}

/**
 * @brief CG_CheckExecKey
 * @param[in] key
 * @return
 */
qboolean CG_CheckExecKey(int key)
{
	if (cg.showFireteamMenu)
	{
		return CG_FireteamCheckExecKey(key, qfalse);
	}

	if (cg.showSpawnpointsMenu)
	{
		return CG_SpawnpointsCheckExecKey(key, qfalse);
	}

	if (cgs.clientinfo[cg.clientNum].shoutcaster)
	{
		return CG_ShoutcastCheckExecKey(key, qfalse);
	}

	if (cg.editingCameras)
	{
		return CG_CameraCheckExecKey(key, qtrue, qfalse);
	}

	return qfalse;
}

/**
 * @brief Real time stamp
 * @return
 */
char *CG_GetRealTime(void)
{
	qtime_t tm;

	trap_RealTime(&tm);
	return va("%02i:%02i:%02i",
	          tm.tm_hour,
	          tm.tm_min,
	          tm.tm_sec);
}

/**
 * @brief CG_WriteToLog
 * @param[in] fmt
 */
void QDECL CG_WriteToLog(const char *fmt, ...)
{
	if (cg.logFile)
	{
		va_list argptr;
		char    string[1024];
		size_t  l;

		Com_sprintf(string, sizeof(string), "%s ", CG_GetRealTime());

		l = strlen(string);

		va_start(argptr, fmt);
		Q_vsnprintf(string + l, sizeof(string) - l, fmt, argptr);
		va_end(argptr);

		trap_FS_Write(string, (int)strlen(string), cg.logFile);
	}
}

int CG_RoundTime(qtime_t *qtime)
{
	int msec;
	int seconds;
	int mins;
	int hours;
	int tens;

	msec = cgs.timelimit * 60000.f;
	if (cgs.gamestate == GS_PLAYING)
	{
		msec -= cg.time - cgs.levelStartTime;
	}

	seconds        = msec / 1000;
	mins           = seconds / 60;
	hours          = mins / 60;
	seconds       -= mins * 60;
	tens           = seconds / 10;
	seconds       -= tens * 10;
	seconds        = Q_atoi(va("%i%i", tens, seconds));
	qtime->tm_sec  = seconds;
	qtime->tm_min  = mins;
	qtime->tm_hour = hours;

	return msec;
}

/**
 * @brief
 * Checks if the ETL version of the demo that was recorded (which we are
 * currently replaying) is below a passed version.
 */
qboolean CG_IsDemoVersionBelow(int major, int minor, int patch)
{
	if (cg.demoVersion.major == 0 && cg.demoVersion.minor == 0 && cg.demoVersion.patch == 0)
	{
		return qfalse;
	}

	if (cg.demoVersion.major < major)
	{
		return qtrue;
	}
	else if (cg.demoVersion.minor < minor)
	{
		return qtrue;
	}
	else if (cg.demoVersion.patch < patch)
	{
		return qtrue;
	}

	return qfalse;
}
