/*
   Copyright (C) 2006 Pekka Lampila ("Medar"), Damien Deville ("Pb")
   and German Garcia Fernandez ("Jal") for Chasseur de bots association.


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

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

   See the GNU General Public License for more details.

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

/*
 * Warsow hud scripts
 */

#include "cg_local.h"

#define TEAM_OWN    ( GS_MAX_TEAMS + 1 )
#define TEAM_ENEMY  ( GS_MAX_TEAMS + 2 )

extern cvar_t *cg_weaponlist;
extern cvar_t *cg_debug_HUD;
extern cvar_t *cg_clientHUD;
cvar_t *cg_showminimap;
cvar_t *cg_placebo;

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

typedef struct
{
	char *name;
	int value;
} constant_numeric_t;

static const constant_numeric_t cg_numeric_constants[] = {
	{ "NOTSET", STAT_NOTSET },

	// teams
	{ "TEAM_SPECTATOR", TEAM_SPECTATOR },
	{ "TEAM_PLAYERS", TEAM_PLAYERS },
	{ "TEAM_ALPHA", TEAM_ALPHA },
	{ "TEAM_BETA", TEAM_BETA },
	{ "TEAM_GAMMA", TEAM_GAMMA },
	{ "TEAM_DELTA", TEAM_DELTA },

	// align
	{ "LEFT", 1 },
	{ "CENTER", 2 },
	{ "RIGHT", 3 },
	{ "TOP", 1 },
	{ "MIDDLE", 2 },
	{ "BOTTOM", 3 },

	{ "WIDTH", 800 },
	{ "HEIGHT", 600 },

	// gametypes
	{ "GAMETYPE_DM", GAMETYPE_DM },
	{ "GAMETYPE_DUEL", GAMETYPE_DUEL },
	{ "GAMETYPE_TDM", GAMETYPE_TDM },
	{ "GAMETYPE_CTF", GAMETYPE_CTF },
	{ "GAMETYPE_RACE", GAMETYPE_RACE },
	{ "GAMETYPE_CA", GAMETYPE_CA },
#ifdef DUEL_ARENA
	{ "GAMETYPE_DA", GAMETYPE_DA },
#endif

	// match states
	{ "MATCH_STATE_NONE", MATCH_STATE_NONE },
	{ "MATCH_STATE_WARMUP", MATCH_STATE_WARMUP },
	{ "MATCH_STATE_COUNTDOWN", MATCH_STATE_COUNTDOWN },
	{ "MATCH_STATE_PLAYTIME", MATCH_STATE_PLAYTIME },
	{ "MATCH_STATE_POSTMATCH", MATCH_STATE_POSTMATCH },
	{ "MATCH_STATE_WAITEXIT", MATCH_STATE_WAITEXIT },

	// ctf
	{ "FLAG_SAFE", FLAG_STATE_SAFE },
	{ "FLAG_UNLOCKING", FLAG_STATE_UNLOCKING },
	{ "FLAG_STOLEN", FLAG_STATE_STOLEN },
	{ "FLAG_STOLEN_TIMER", FLAG_STATE_STOLEN_TIMER },
	{ "FLAG_CAPTURING", FLAG_STATE_CAPTURING },
	{ "FLAG_DROPPED", FLAG_STATE_DROPPED },


	// weapon
	{ "WEAP_GUNBLADE", WEAP_GUNBLADE },
	{ "WEAP_SHOCKWAVE", WEAP_SHOCKWAVE },
	{ "WEAP_RIOTGUN", WEAP_RIOTGUN },
	{ "WEAP_GRENADELAUNCHER", WEAP_GRENADELAUNCHER },
	{ "WEAP_ROCKETLAUNCHER", WEAP_ROCKETLAUNCHER },
	{ "WEAP_PLASMAGUN", WEAP_PLASMAGUN },
	{ "WEAP_LASERGUN", WEAP_LASERGUN },
	{ "WEAP_ELECTROBOLT", WEAP_ELECTROBOLT },

	{ "NOGUN", 0 },
	{ "GUN", 1 },

	{ NULL, 0 }
};

#if 0 // not used yet at least
typedef struct
{
	char *name;
	char *value;
} constant_string_t;

static const constant_string_t cg_string_constants[] = {
	{ NULL, 0 }
};
#endif

// picnames for custom icons (weaponlist)
static char *customWeaponPics[8];
static char *customNoGunWeaponPics[8];
static char *customWeaponSelectPic;

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

static int CG_GetStatTeamValue( void *parameter )
{
	if( (qintptr)parameter == STAT_TEAM )
	{
		return cg.frame.playerState.stats[STAT_TEAM];
	}
	else
	{
		return ( ( cg.frame.playerState.stats[STAT_TEAM] == TEAM_ALPHA ) ? TEAM_BETA :
		        ( ( cg.frame.playerState.stats[STAT_TEAM] == TEAM_BETA ) ? TEAM_ALPHA : TEAM_SPECTATOR ) );
	}
}
static int CG_GetStatValue( void *parameter )
{
	assert( (qintptr)parameter >= 0 && (qintptr)parameter < MAX_STATS );

	return cg.frame.playerState.stats[(qintptr)parameter];
}

static int CG_GetRaceStatValue( void *parameter )
{
	if( cg.frame.playerState.stats[STAT_GAMETYPE] != GAMETYPE_RACE )
		return STAT_NOTSET;

	return CG_GetStatValue( parameter );
}

static int CG_GetSpeed( void *parameter )
{
	vec3_t hvel;

	VectorSet( hvel, cg.frame.playerState.pmove.velocity[0], cg.frame.playerState.pmove.velocity[1], 0 );

	return (int)VectorLength( hvel );
}

static int CG_GetSpeedVertical( void *parameter )
{
	return cg.frame.playerState.pmove.velocity[2];
}

static int CG_GetFPS( void *parameter )
{
#define FPSSAMPLESCOUNT 32
#define FPSSAMPLESMASK ( FPSSAMPLESCOUNT-1 )
	static int fps;
	static double oldtime;
	static int oldframecount;
	static float frameTimes[FPSSAMPLESCOUNT];
	static float avFrameTime;
	int i;
	double t;

	frameTimes[cg.frameCount & FPSSAMPLESMASK] = cg.realFrameTime;

	if( cg_showFPS->integer != 2 )
	{
		for( avFrameTime = 0.0f, i = 0; i < FPSSAMPLESCOUNT; i++ )
		{
			avFrameTime += frameTimes[( cg.frameCount-i ) & FPSSAMPLESMASK];
		}
		avFrameTime /= FPSSAMPLESCOUNT;
		fps = (int)( 1.0f/avFrameTime );
	}
	else
	{
		t = cg.realTime * 0.001f;
		if( ( t - oldtime ) >= 0.25 )
		{                       // updates 4 times a second
			fps = ( cg.frameCount - oldframecount ) / ( t - oldtime ) + 0.5;
			oldframecount = cg.frameCount;
			oldtime = t;
		}
	}

	return fps;
}

static int CG_GetPowerupTime( void *parameter )
{
	int powerup = (qintptr)parameter;
	int stat = cg.frame.playerState.stats[STAT_POWERUP_TIMES];

	while( powerup < POWERUP_SHELL )
	{
		stat >>= 8;
		powerup++;
	}

	return stat & 255;
}

// Function used by CG_GetFlagState, CG_GetFlagStateSelfCause and CG_GetCTFFlagTimer
// to get the flag-status from a certain team.
// Flag status includes: timer, the status and if the current player caused that status
// see gs_public.h for more details
static int CG_GetFlagFullState( int team )
{
	short stats = 0;
	if( team == TEAM_OWN )
	{
		team = cg.frame.playerState.stats[STAT_TEAM];
	}
	else if( team == TEAM_ENEMY )
	{
		// If spectator team: No stats
		team = ( ( cg.frame.playerState.stats[STAT_TEAM] == TEAM_ALPHA ) ? TEAM_BETA :
		        ( ( cg.frame.playerState.stats[STAT_TEAM] == TEAM_BETA ) ? TEAM_ALPHA : TEAM_SPECTATOR )
		         );
	}

	switch( team )
	{
	case TEAM_ALPHA:
		stats = cg.frame.playerState.stats[STAT_CTF_FLAG_ALPHA];
		break;
	case TEAM_BETA:
		stats = cg.frame.playerState.stats[STAT_CTF_FLAG_BETA];
		break;
		// KoFFiE: Delta & Gamma not supported in CTF!
		//case TEAM_DELTA:
		//case TEAM_GAMMA:
	case TEAM_SPECTATOR: // No stats for spectator team
	default:
		stats = 0;
	}
	return ( stats & 0xFFFF );
}

static int CG_GetFlagState( void *parameter )
{
	return ( ( CG_GetFlagFullState( (qintptr)parameter ) & ~FLAG_STATE_TIMERMASK ) & ~FLAG_STATE_SELF_MASK );
}

static int CG_GetFlagStateSelfCause( void *parameter )
{
	return ( CG_GetFlagFullState( (qintptr)parameter ) & FLAG_STATE_SELF_MASK ) ? 1 : STAT_NOTSET;
}

static int CG_GetCTFFlagTimer( void *parameter )
{
	int stats = CG_GetFlagFullState( (qintptr)parameter ) & FLAG_STATE_TIMERMASK;
	return ( stats ) ? stats : STAT_NOTSET;
}

static int CG_GetMatchState( void *parameter )
{
	return cg.frame.match.state;
}

static int CG_GetOvertime( void *parameter )
{
	return cg.frame.match.extendedtime;
}

static int CG_GetInstagib( void *parameter )
{
	return cgs.serverRules.instagib;
}
static int CG_GetZoom( void *parameter )
{
	return ( !cg.view.thirdperson && ( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_ZOOM ) );
}

static int CG_GetVidWidth( void *parameter )
{
	return cgs.vidWidth;
}

static int CG_GetVidHeight( void *parameter )
{
	return cgs.vidHeight;
}

static int CG_GetDamageGiven( void *parameter )
{
	return cg.damage_given;
}

static int CG_GetDamageReceived( void *parameter )
{
	return cg.damage_received;
}

static int CG_GetCvar( void *parameter )
{
	return trap_Cvar_Value( (char *)parameter );
}

static int CG_GetDamageIndicatorHealthValue( void *parameter )
{
	return CG_GetDamageIndicatorHealth();
}

static int CG_GetDamageIndicatorDirValue( void *parameter )
{
	return CG_GetDamageIndicatorDir( (qintptr)parameter );
}

typedef struct
{
	char *name;
	int ( *func )( void *parameter );
	void *parameter;
} reference_numeric_t;

static const reference_numeric_t cg_numeric_references[] =
{
	// stats
	{ "GAMETYPE", CG_GetStatValue, (void *)STAT_GAMETYPE },
	{ "HEALTH", CG_GetStatValue, (void *)STAT_HEALTH },
	{ "AMMO", CG_GetStatValue, (void *)STAT_AMMO },
	{ "AMMO_ITEM", CG_GetStatValue, (void *)STAT_AMMO_ITEM },
	{ "WEAK_AMMO", CG_GetStatValue, (void *)STAT_WEAK_AMMO },
	{ "ARMOR", CG_GetStatValue, (void *)STAT_ARMOR },
	{ "ARMOR_ITEM", CG_GetStatValue, (void *)STAT_ARMOR_ITEM },
	{ "SELECTED_ITEM", CG_GetStatValue, (void *)STAT_SELECTED_ITEM },
	{ "PICKUP_ITEM", CG_GetStatValue, (void *)STAT_PICKUP_ITEM },
	{ "WEAPON_ITEM", CG_GetStatValue, (void *)STAT_WEAPON_ITEM },

	{ "FRAGS", CG_GetStatValue, (void *)STAT_FRAGS },
	{ "TEAM", CG_GetStatValue, (void *)STAT_TEAM },
	{ "CHASING", CG_GetStatValue, (void *)STAT_CHASING },

	{ "TEAM_OWN", CG_GetStatTeamValue, (void *)STAT_TEAM },
	{ "TEAM_ENEMY", CG_GetStatTeamValue, (void *)( !STAT_TEAM ) },

	{ "POINTED_PLAYER", CG_GetStatValue, (void *)STAT_POINTED_PLAYER },
	{ "POINTED_TEAMPLAYER", CG_GetStatValue, (void *)STAT_POINTED_TEAMPLAYER },

	{ "TEAM_ALPHA_SCORE", CG_GetStatValue, (void *)STAT_TEAM_ALPHA_SCORE },
	{ "TEAM_BETA_SCORE", CG_GetStatValue, (void *)STAT_TEAM_BETA_SCORE },
	{ "TEAM_DELTA_SCORE", CG_GetStatValue, (void *)STAT_TEAM_DELTA_SCORE },
	{ "TEAM_GAMMA_SCORE", CG_GetStatValue, (void *)STAT_TEAM_GAMMA_SCORE },

	{ "REALTEAM", CG_GetStatValue, (void *)STAT_REALTEAM },

	{ "RACE_TIME", CG_GetRaceStatValue, (void *)STAT_RACE_TIME },
	{ "RACE_PLAYERBESTTIME", CG_GetRaceStatValue, (void *)STAT_RACE_PLAYERBESTTIME },
	{ "RACE_MATCHBESTTIME", CG_GetRaceStatValue, (void *)STAT_RACE_MATCHBESTTIME },

	// other
	{ "SPEED", CG_GetSpeed, NULL },
	{ "SPEED_VERTICAL", CG_GetSpeedVertical, NULL },
	{ "FPS", CG_GetFPS, NULL },
	{ "MATCH_STATE", CG_GetMatchState, NULL },
	{ "OVERTIME", CG_GetOvertime, NULL },
	{ "INSTAGIB", CG_GetInstagib, NULL },
	{ "ZOOM", CG_GetZoom, NULL },
	{ "VIDWIDTH", CG_GetVidWidth, NULL },
	{ "VIDHEIGHT", CG_GetVidHeight, NULL },
	{ "DAMAGE_GIVEN", CG_GetDamageGiven, NULL },
	{ "DAMAGE_RECEIVED", CG_GetDamageReceived, NULL },

	{ "POWERUP_QUAD_TIME", CG_GetPowerupTime, (void *)POWERUP_QUAD },
	{ "POWERUP_WARSHELL_TIME", CG_GetPowerupTime, (void *)POWERUP_SHELL },

	{ "DAMAGE_INDICATOR_HEALTH", CG_GetDamageIndicatorHealthValue, NULL },

	{ "DAMAGE_INDICATOR_LEFT", CG_GetDamageIndicatorDirValue, (void *)DAMAGE_INDICATOR_DIR_LEFT },
	{ "DAMAGE_INDICATOR_RIGHT", CG_GetDamageIndicatorDirValue, (void *)DAMAGE_INDICATOR_DIR_RIGHT },
	{ "DAMAGE_INDICATOR_FRONT", CG_GetDamageIndicatorDirValue, (void *)DAMAGE_INDICATOR_DIR_FRONT },
	{ "DAMAGE_INDICATOR_BEHIND", CG_GetDamageIndicatorDirValue, (void *)DAMAGE_INDICATOR_DIR_BEHIND },

	// CTF HUD Stuff
	{ "CTF_FLAG_OWN", CG_GetFlagState, (void *)( TEAM_OWN ) },
	{ "CTF_FLAG_ENEMY", CG_GetFlagState, (void *)( TEAM_ENEMY ) },
	{ "CTF_FLAG_ALPHA", CG_GetFlagState, (void *)( TEAM_ALPHA ) },
	{ "CTF_FLAG_BETA", CG_GetFlagState, (void *)( TEAM_BETA ) },
	{ "CTF_FLAG_GAMMA", CG_GetFlagState, (void *)( TEAM_GAMMA ) },
	{ "CTF_FLAG_DELTA", CG_GetFlagState, (void *)( TEAM_DELTA ) },

	{ "CTF_TIMER_OWN", CG_GetCTFFlagTimer, (void *)( TEAM_OWN ) },
	{ "CTF_TIMER_ENEMY", CG_GetCTFFlagTimer, (void *)( TEAM_ENEMY ) },
	{ "CTF_FLAG_TIMER", CG_GetCTFFlagTimer, (void *)( TEAM_ENEMY ) },       // for backwards compatability
	{ "CTF_TIMER_ALPHA", CG_GetCTFFlagTimer, (void *)( TEAM_ALPHA ) },
	{ "CTF_TIMER_BETA", CG_GetCTFFlagTimer, (void *)( TEAM_BETA ) },
	{ "CTF_TIMER_GAMMA", CG_GetCTFFlagTimer, (void *)( TEAM_GAMMA ) },
	{ "CTF_TIMER_DELTA", CG_GetCTFFlagTimer, (void *)( TEAM_DELTA ) },
	{ "CTF_FLAG_ME", CG_GetFlagStateSelfCause, (void *)( TEAM_ENEMY ) },    // Current player caused flag state

	// cvars
	{ "SHOW_FPS", CG_GetCvar, "cg_showFPS" },
	{ "SHOW_OBITUARIES", CG_GetCvar, "cg_showObituaries" },
	{ "SHOW_PICKUP", CG_GetCvar, "cg_showPickup" },
	{ "SHOW_POINTED_PLAYER", CG_GetCvar, "cg_showPointedPlayer" },
	{ "SHOW_PRESSED_KEYS", CG_GetCvar, "cg_showPressedKeys" },
	{ "SHOW_SPEED", CG_GetCvar, "cg_showSpeed" },
	{ "SHOW_TEAM_LOCATIONS", CG_GetCvar, "cg_showTeamLocations" },
	{ "SHOW_TIMER", CG_GetCvar, "cg_showTimer" },
	{ "SHOW_AWARDS", CG_GetCvar, "cg_showAwards" },
	{ "SHOW_ZOOM_EFFECT", CG_GetCvar, "cg_showZoomEffect" },
	{ "SHOW_R_SPEEDS", CG_GetCvar, "r_speeds" },

	{ NULL, NULL, NULL }
};

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

#define MAX_OBITUARIES 32

typedef enum { OBITUARY_NONE, OBITUARY_NORMAL, OBITUARY_TEAM, OBITUARY_SUICIDE, OBITUARY_ACCIDENT } obituary_type_t;

typedef struct obituary_s
{
	obituary_type_t	type;
	unsigned int time;
	char victim[MAX_INFO_VALUE];
	int victim_team;
	char attacker[MAX_INFO_VALUE];
	int attacker_team;
	int mod;
} obituary_t;

static obituary_t cg_obituaries[MAX_OBITUARIES];
static int cg_obituaries_current = -1;

//==================
//CG_SC_Obituary
//==================
void CG_SC_Obituary( void )
{
	char message[128];
	char message2[128];
	cg_clientInfo_t *victim, *attacker;
	int victimNum = atoi( trap_Cmd_Argv( 1 ) );
	int attackerNum = atoi( trap_Cmd_Argv( 2 ) );
	int mod = atoi( trap_Cmd_Argv( 3 ) );
	int victim_gender = GENDER_MALE;
	obituary_t *current;

	// wsw : jal : extract gender from their player model info, if any
	if( victimNum >= 0 && victimNum < MAX_EDICTS && cg_entPModels[victimNum].pmodelinfo )
		victim_gender = cg_entPModels[victimNum].pmodelinfo->sex;

	victim = &cgs.clientInfo[victimNum - 1];

	if( attackerNum )
	{
		attacker = &cgs.clientInfo[attackerNum - 1];
	}
	else
	{
		attacker = NULL;
	}

	cg_obituaries_current++;
	if( cg_obituaries_current >= MAX_OBITUARIES )
		cg_obituaries_current = 0;
	current = &cg_obituaries[cg_obituaries_current];

	current->time = cg.time;
	if( victim )
	{
		Q_strncpyz( current->victim, victim->name, sizeof( current->victim ) );
		current->victim_team = cg_entities[victimNum].current.team;
	}
	if( attacker )
	{
		Q_strncpyz( current->attacker, attacker->name, sizeof( current->attacker ) );
		current->attacker_team = cg_entities[attackerNum].current.team;
	}
	current->mod = mod;

	GS_Obituary( victim, victim_gender, attacker, mod, message, message2 );

	if( attackerNum )
	{
		if( victimNum != attackerNum )
		{
			// teamkill
			if( cg_entities[attackerNum].current.team == cg_entities[victimNum].current.team &&
			   GS_Gametype_IsTeamBased( cg.frame.playerState.stats[STAT_GAMETYPE] ) )
			{
				current->type = OBITUARY_TEAM;
				if( cg_showObituaries->integer & CG_OBITUARY_CONSOLE )
					CG_Printf( "%s%s%s %s %s%s %s%s%s\n", S_COLOR_RED, "TEAMKILL:", S_COLOR_WHITE, victim->name,
					           S_COLOR_WHITE, message, attacker->name, S_COLOR_WHITE, message2 );

				if( ISVIEWERENTITY( attackerNum ) && cg_showObituaries->integer & CG_OBITUARY_CENTER )
					CG_CenterPrintToUpper( va( "%s%s%s %s\n", S_COLOR_RED, "YOU TEAMKILLED", S_COLOR_WHITE,
					                           victim->name ) );
			}
			else // good kill
			{
				current->type = OBITUARY_NORMAL;
				if( cg_showObituaries->integer & CG_OBITUARY_CONSOLE )
					CG_Printf( "%s %s%s %s%s%s\n", victim->name, S_COLOR_WHITE, message, attacker->name, S_COLOR_WHITE,
					           message2 );

				if( ISVIEWERENTITY( attackerNum ) && cg_showObituaries->integer & CG_OBITUARY_CENTER )
					CG_CenterPrintToUpper( va( "%s%s %s\n", S_COLOR_WHITE, "YOU KILLED", victim->name ) );
			}
		}
		else // suicide
		{
			current->type = OBITUARY_SUICIDE;
			if( cg_showObituaries->integer & CG_OBITUARY_CONSOLE )
				CG_Printf( "%s %s%s\n", victim->name, S_COLOR_WHITE, message );
		}
	}
	else // world accidents
	{
		current->type = OBITUARY_ACCIDENT;
		if( cg_showObituaries->integer & CG_OBITUARY_CONSOLE )
			CG_Printf( "%s %s%s\n", victim->name, S_COLOR_WHITE, message );
	}
}

static void CG_DrawObituaries( int x, int y, int align, struct mufont_s *font, vec4_t color, int width, int height,
                               int internal_align, unsigned int icon_size )
{
	int i, num, skip, next, w, num_max;
	size_t line_height;
	int xoffset, yoffset;
	obituary_t *obr;
	struct shader_s *pic;
	vec4_t teamcolor;

	if( !( cg_showObituaries->integer & CG_OBITUARY_HUD ) )
		return;

	line_height = max( trap_SCR_strHeight( font ), icon_size );
	num_max = height / line_height;

	if( width < (int)icon_size || !num_max )
		return;

	next = cg_obituaries_current + 1;
	if( next >= MAX_OBITUARIES )
		next = 0;

	num = 0;
	i = next;
	do
	{
		if( cg_obituaries[i].type != OBITUARY_NONE && cg.time - cg_obituaries[i].time <= 5000 )
			num++;
		if( ++i >= MAX_OBITUARIES )
			i = 0;
	}
	while( i != next );

	if( num > num_max )
	{
		skip = num - num_max;
		num = num_max;
	}
	else
	{
		skip = 0;
	}

	y = CG_VerticalAlignForHeight( y, align, height );
	x = CG_HorizontalAlignForWidth( x, align, width );

	xoffset = 0;
	yoffset = 0;

	i = next;
	do
	{
		obr = &cg_obituaries[i];
		if( ++i >= MAX_OBITUARIES )
			i = 0;

		if( obr->type == OBITUARY_NONE || cg.time - obr->time > 5000 )
			continue;

		if( skip > 0 )
		{
			skip--;
			continue;
		}

		switch( obr->mod )
		{
		case MOD_GUNBLADE_W:
		case MOD_GUNBLADE_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_GUNBLADE-1] );
			break;
		case MOD_RIOTGUN_W:
		case MOD_RIOTGUN_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_RIOTGUN-1] );
			break;
		case MOD_GRENADE_W:
		case MOD_GRENADE_S:
		case MOD_GRENADE_SPLASH_W:
		case MOD_GRENADE_SPLASH_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_GRENADELAUNCHER-1] );
			break;
		case MOD_ROCKET_W:
		case MOD_ROCKET_S:
		case MOD_ROCKET_SPLASH_W:
		case MOD_ROCKET_SPLASH_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_ROCKETLAUNCHER-1] );
			break;
		case MOD_PLASMA_W:
		case MOD_PLASMA_S:
		case MOD_PLASMA_SPLASH_W:
		case MOD_PLASMA_SPLASH_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_PLASMAGUN-1] );
			break;
		case MOD_ELECTROBOLT_W:
		case MOD_ELECTROBOLT_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_ELECTROBOLT-1] );
			break;
		case MOD_INSTAGUN_W:
		case MOD_INSTAGUN_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_INSTAGUN-1] );
			break;
		case MOD_LASERGUN_W:
		case MOD_LASERGUN_S:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_LASERGUN-1] );
			break;
		default:
			pic = CG_MediaShader( cgs.media.shaderWeaponIcon[WEAP_GUNBLADE-1] ); // FIXME
			break;
		}

		w = 0;
		if( obr->type != OBITUARY_ACCIDENT )
			w += min( trap_SCR_strWidth( obr->attacker, font, 0 ), ( width - icon_size * cgs.vidWidth/800 ) / 2 );
		w += icon_size * cgs.vidWidth/800;
		w += min( trap_SCR_strWidth( obr->victim, font, 0 ), ( width - icon_size * cgs.vidWidth/800 ) / 2 );

		if( internal_align == 1 )
		{                   // left
			xoffset = 0;
		}
		else if( internal_align == 1 )
		{                          // center
			xoffset = ( width - w ) / 2;
		}
		else
		{ // right
			xoffset = width - w;
		}


		if( obr->type != OBITUARY_ACCIDENT )
		{
			if( GS_Gametype_IsTeamBased( cg.frame.playerState.stats[STAT_GAMETYPE] ) )
			{
				CG_TeamColor( obr->attacker_team, teamcolor );
			}
			else
			{
				Vector4Set( teamcolor, 255, 255, 255, 255 );
			}
			trap_SCR_DrawStringWidth( x + xoffset, y + yoffset + ( line_height - trap_SCR_strHeight( font ) ) / 2,
			                          ALIGN_LEFT_TOP, COM_RemoveColorTokensExt( obr->attacker, qtrue ), ( width - icon_size * cgs.vidWidth/800 ) / 2,
			                          font, teamcolor );
			xoffset += min( trap_SCR_strWidth( obr->attacker, font, 0 ), ( width - icon_size * cgs.vidWidth/800 ) / 2 );
		}
		trap_R_DrawStretchPic( x + xoffset, y + yoffset + ( line_height - icon_size ) / 2, icon_size * cgs.vidWidth/800,
		                       icon_size * cgs.vidHeight/600, 0, 0, 1, 1, color, pic );
		xoffset += icon_size * cgs.vidWidth/800;
		if( GS_Gametype_IsTeamBased( cg.frame.playerState.stats[STAT_GAMETYPE] ) )
		{
			CG_TeamColor( obr->victim_team, teamcolor );
		}
		else
		{
			Vector4Set( teamcolor, 255, 255, 255, 255 );
		}
		trap_SCR_DrawStringWidth( x + xoffset, y + yoffset + ( (int) line_height ) / 2, ALIGN_LEFT_MIDDLE,
		                          COM_RemoveColorTokensExt( obr->victim, qtrue ), ( width - icon_size * cgs.vidWidth/800 ) / 2, font, teamcolor );
		xoffset += min( trap_SCR_strWidth( obr->victim, font, 0 ), ( width - icon_size * cgs.vidWidth/800 ) / 2 );

		yoffset += line_height;
	}
	while( i != next );
}

//=============================================================================
//
//AWARDS
//

#define NB_MESSAGE		5
#define AWARD_LINES		3
#define AWARD_DISPLAYTIME	5000

typedef struct
{
	int type;
	int level;
	unsigned int time;
} cg_award_t;

typedef struct
{
	int nbmsg;
	const char *msg[NB_MESSAGE];
	const char *color;
} cg_award_msg_t;

static const cg_award_msg_t award_msg[TOTAL_AWARD-1] = {
	{ 1, { "Accuracy!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "Rocket Direct Hit!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "Grenade Direct Hit!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "Air Rocket!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "Air Grenade!", "", "", "", "" }, S_COLOR_BLUE },
	{ 5, { "", "Double Frag!", "Triple Frag!", "Quadruple Frag!", "BLOODBATH! %d in a row!" }, S_COLOR_GREEN },
	{ 4, { "On Fire!", "Raging!", "Fraglord!", "God Mode!", "" }, S_COLOR_YELLOW },
	{ 1, { "RL to EB!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "RL to LG!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "RL to RG!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "RL to GL!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "RL to RL!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "GL to EB!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "GL to LG!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "GL to RG!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "GL to RL!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "GL to GL!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "LG to EB!", "", "", "", "" }, S_COLOR_BLUE },
	{ 1, { "Mega-Health Control!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "Red Armor Control!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "New Record!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "Flag Capture!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "Flag Recovery!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "Area captured!", "", "", "", "" }, S_COLOR_WHITE },
	{ 1, { "Maplock!", "", "", "", "" }, S_COLOR_WHITE },
	{ 1, { "Flag Carrier Kill!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "Flag Carrier Defense!", "", "", "", "" }, S_COLOR_CYAN },
	{ 1, { "Flag Defense!", "", "", "", "" }, S_COLOR_CYAN },
};

cg_award_t awards[AWARD_LINES] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
qbyte current_award = AWARD_LINES - 1;

//==================
//CG_SC_Award
// Show awards.
// cg_showAwards: 0 nothing, 1 console messages, 2 also the HUD messages
//==================
void CG_SC_Award( void )
{
	int playernum = atoi( trap_Cmd_Argv( 1 ) );
	int awardtype = atoi( trap_Cmd_Argv( 2 ) );
	int level = atoi( trap_Cmd_Argv( 3 ) );

	if( !cg_showAwards->integer )
		return;

	if( playernum == cg.view.POVent-1 && cg.frame.playerState.pmove.pm_type != PM_SPECTATOR && cg.frame.playerState.pmove.pm_type != PM_DEAD && cg_showAwards->integer == 2 )
	{
		current_award++;
		current_award %= AWARD_LINES;
		awards[current_award].type = awardtype;
		awards[current_award].level = level;
		awards[current_award].time = cg.time;
	}

	if( awardtype == SPREE_AWARD )
	{
		if( level == 1 ) CG_Printf( "%s %sis on fire!\n", cgs.clientInfo[playernum].name, S_COLOR_YELLOW );
		else if( level == 2 ) CG_Printf( "%s %sis raging!\n", cgs.clientInfo[playernum].name, S_COLOR_YELLOW );
		else if( level == 3 ) CG_Printf( "%s %sis the Fraglord!\n", cgs.clientInfo[playernum].name, S_COLOR_YELLOW );
		else if( level >= 4 ) CG_Printf( "%s %sis in God Mode!\n", cgs.clientInfo[playernum].name, S_COLOR_YELLOW );

	}
	else if( awardtype == RACERECORD_AWARD )
	{
		CG_Printf( "%s %smade a new record !\n", cgs.clientInfo[playernum].name, S_COLOR_YELLOW );
	}
}

static void CG_DrawAwards( int x, int y, int align, struct mufont_s *font )
{
	qbyte i;
	char message[128], *msgformat;
	int cury;
	int msglevel;
	int nb_to_display = 0;
	vec4_t color;

	for( i = 0; i < TOTAL_AWARD-1; i++ ) ;
	if( awards[i].time && cg.time - awards[i].time < AWARD_DISPLAYTIME )
		nb_to_display++;

	i = current_award;

	cury = CG_VerticalAlignForHeight( y, align, nb_to_display * trap_SCR_strHeight( font ) );

	do
	{
		if( awards[i].time == 0 || cg.time - awards[i].time > AWARD_DISPLAYTIME )
			break; // No (more) award to draw

		msglevel = min( awards[i].level - 1, award_msg[awards[i].type-1].nbmsg - 1 );
		msgformat = va( "%s%s", award_msg[awards[i].type-1].color,
		                award_msg[awards[i].type-1].msg[msglevel] );
		Q_snprintfz( message, sizeof( message ), msgformat, awards[i].level );

		color[3] = 2.0f - 2.0f * (float)( cg.time - awards[i].time ) / (float)AWARD_DISPLAYTIME;
		clamp( color[3], 0.0f, 1.0f );

		trap_SCR_DrawStringWidth( x, cury, align % 3, message, 0, font, color );

		if( i == 0 )
			i = AWARD_LINES - 1;
		else
			i--;

		cury += trap_SCR_strHeight( font );

	}
	while( i != current_award );
}
//=============================================================================

static qboolean CG_IsWeaponSelected( int weapon )
{
	qboolean selected_weapon = qfalse;
	if( cgs.demoPlaying && cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
	{
		if( weapon == cg.frame.playerState.stats[STAT_SELECTED_ITEM] )
			selected_weapon = qtrue;
	}
	else
	{
		if( ( cg.latched_weapon != WEAP_NONE && weapon == cg.latched_weapon ) ||
		   ( cg.latched_weapon == WEAP_NONE && weapon == cg.frame.playerState.stats[STAT_SELECTED_ITEM] ) )
			selected_weapon = qtrue;
	}
	return selected_weapon;
}

//================
//CG_DrawWeaponIcons
//================
static void CG_DrawWeaponIcons( int x, int y, int offx, int offy, int iw, int ih, int align )
{
	int curx, cury, curw, curh;
	int i, j, n;
	float fj, fn;
	vec4_t color;
	qboolean selected_weapon;

	if( !cg_weaponlist || !cg_weaponlist->integer )
		return;

	if( iw > 0 )
		curw = iw;
	else
		curw = 32 * cgs.vidWidth/800; // 32 = default size for icons
	if( ih > 0 )
		curh = ih;
	else
		curh = 32 * cgs.vidHeight/600; // 32 = default size for icons

	n = 0;

	for( i = 1; i < WEAP_TOTAL-1; i++ ) // starting at 1 cause we surely got the GB
	{
		if( ( cg.frame.playerState.weaponlist[i][0] || cg.frame.playerState.weaponlist[i][1] || cg.frame.playerState.weaponlist[i][2] ) )
			n++;
	}

	VectorCopy( colorWhite, color );

	for( i = j = 0; i < WEAP_TOTAL-1; i++ )
	{
		// if player doesnt have this weapon, skip it
		if( !( cg.frame.playerState.weaponlist[i][0] || cg.frame.playerState.weaponlist[i][1] || cg.frame.playerState.weaponlist[i][2] ) )
			continue;

		selected_weapon = CG_IsWeaponSelected( i+1 );
		if( !selected_weapon )
			color[3] = 1.0;
		else
			color[3] = 0.5;

		fj = (float)j;
		fn = (float)n;
		curx = CG_HorizontalAlignForWidth( x + (int)( offx * ( fj - fn / 2.0f ) ), align, curw );
		cury = CG_VerticalAlignForHeight( y + (int)( offy * ( fj - fn / 2.0f ) ), align, curh );

		if( cg.frame.playerState.weaponlist[i][0] )
		{
			// wsw : pb : display a little box around selected weapon in weaponlist
			if( selected_weapon )
			{
				if( customWeaponSelectPic )
					trap_R_DrawStretchPic( curx, cury, curw, curh, 0, 0, 1, 1, color, trap_R_RegisterPic( customWeaponSelectPic ) );
				else
					trap_R_DrawStretchPic( curx, cury, curw, curh, 0, 0, 1, 1, color, CG_MediaShader( cgs.media.shaderSelect ) );
			}
			if( customWeaponPics[i] )
				trap_R_DrawStretchPic( curx, cury, curw, curh, 0, 0, 1, 1, color, trap_R_RegisterPic( customWeaponPics[i] ) );
			else
				trap_R_DrawStretchPic( curx, cury, curw, curh, 0, 0, 1, 1, color, CG_MediaShader( cgs.media.shaderWeaponIcon[i] ) );
		}
		else
		if( customNoGunWeaponPics[i] )
			trap_R_DrawStretchPic( curx, cury, curw, curh, 0, 0, 1, 1, color, trap_R_RegisterPic( customNoGunWeaponPics[i] ) );
		else
			trap_R_DrawStretchPic( curx, cury, curw, curh, 0, 0, 1, 1, color, CG_MediaShader( cgs.media.shaderNoGunWeaponIcon[i] ) );
		j++;
	}
}

//================
//CG_DrawWeaponAmmos
//================
static void CG_DrawWeaponAmmos( int x, int y, int offx, int offy, int fontsize, int ammotype, int align )
{
	int curx, cury, curw, curh;
	int i, j, n, fs;
	float fj, fn;
	vec4_t color;

	if( !cg_weaponlist || !cg_weaponlist->integer )
		return;

	if( fontsize > 0 )
		fs = fontsize;
	else
		fs = 12; // 12 = default size for font
	curw = (int)( fs * cgs.vidWidth/800 );
	curh = (int)( fs * cgs.vidHeight/600 );

	n = 0;

	for( i = 1; i < WEAP_TOTAL-1; i++ ) // starting at 1 cause we surely got the GB
	{
		if( ( cg.frame.playerState.weaponlist[i][0] || cg.frame.playerState.weaponlist[i][1] || cg.frame.playerState.weaponlist[i][2] ) )
			n++;
	}

	VectorCopy( colorWhite, color );

	for( i = j = 0; i < WEAP_TOTAL-1; i++ )
	{
		// if player doesnt have this weapon, skip it
		if( !( cg.frame.playerState.weaponlist[i][0] || cg.frame.playerState.weaponlist[i][1] || cg.frame.playerState.weaponlist[i][2] ) )
			continue;

		if( CG_IsWeaponSelected( i+1 ) )
			color[3] = 1.0;
		else
			color[3] = 0.5;

		fj = (float)j;
		fn = (float)n;
		curx = x + (int)( offx * ( fj - fn / 2.0f ) );
		cury = y + (int)( offy * ( fj - fn / 2.0f ) );

		if( cg.frame.playerState.weaponlist[i][ammotype] )
			CG_DrawHUDNumeric( curx, cury, align, color, curw, curh, cg.frame.playerState.weaponlist[i][ammotype] );
		j++;
	}
}

static void CG_DrawCaptureAreas( int x, int y, int w, int h, int alignment )
{
	vec4_t currentcolour;
	int i, state, team, cteam;
	float percentage;

	if( !cg_showCaptureAreas->integer )
		return;

	for( i = 0; i < MAX_CAPTURE_AREAS; i++ )
	{
		CG_GetCaptureAreaInfo( i, &state, &team, &cteam, &percentage );

		if( !state )
			continue;

		if( state == AREA_STATE_FREE )
			Vector4Copy( TDM_FreeCAColour, currentcolour );
		else
			CG_TeamColor( team , currentcolour );

		trap_R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, currentcolour,
		                      trap_R_RegisterPic( capture_area_icons[i] ) );

		// area is being captured
		if( percentage )
		{
			CG_TeamColor( cteam, currentcolour );

			trap_R_DrawStretchPic( x, y, w * percentage, h, 0, 0, percentage, 1,
			                      currentcolour, trap_R_RegisterPic( capture_area_icons[i] ) );
		}
		if( alignment == 1 )
			x += w + 10;
		else
			y += h + 10;
	}
}

//=============================================================================
//	STATUS BAR PROGRAMS
//=============================================================================


// we will always operate with floats so we don't have to code 2 different numeric paths
// it's not like using float or ints would make a difference in this simple-scripting case.

static float CG_OpFuncAdd( const float a, const float b )
{
	return a + b;
}

static float CG_OpFuncSubtract( const float a, const float b )
{
	return a - b;
}

static float CG_OpFuncMultiply( const float a, const float b )
{
	return a * b;
}

static float CG_OpFuncDivide( const float a, const float b )
{
	return a / b;
}

static float CG_OpFuncAND( const float a, const float b )
{
	return (int)a & (int)b;
}

static float CG_OpFuncOR( const float a, const float b )
{
	return (int)a | (int)b;
}

static float CG_OpFuncXOR( const float a, const float b )
{
	return (int)a ^ (int)b;
}

static float CG_OpFuncCompareEqual( const float a, const float b )
{
	return ( a == b );
}

static float CG_OpFuncCompareNotEqual( const float a, const float b )
{
	return ( a != b );
}

static float CG_OpFuncCompareGreater( const float a, const float b )
{
	return ( a > b );
}

static float CG_OpFuncCompareGreaterOrEqual( const float a, const float b )
{
	return ( a >= b );
}

static float CG_OpFuncCompareSmaller( const float a, const float b )
{
	return ( a < b );
}

static float CG_OpFuncCompareSmallerOrEqual( const float a, const float b )
{
	return ( a <= b );
}

static float CG_OpFuncCompareAnd( const float a, const float b )
{
	return ( a && b );
}

static float CG_OpFuncCompareOr( const float a, const float b )
{
	return ( a || b );
}

typedef struct cg_layoutoperators_s
{
	char *name;
	float ( *opFunc )( const float a, const float b );
} cg_layoutoperators_t;

static cg_layoutoperators_t cg_LayoutOperators[] =
{
	{
		"+",
		CG_OpFuncAdd
	},

	{
		"-",
		CG_OpFuncSubtract
	},

	{
		"*",
		CG_OpFuncMultiply
	},

	{
		"/",
		CG_OpFuncDivide
	},

	{
		"&",
		CG_OpFuncAND
	},

	{
		"|",
		CG_OpFuncOR
	},

	{
		"^",
		CG_OpFuncXOR
	},

	{
		"==",
		CG_OpFuncCompareEqual
	},

	{
		"!=",
		CG_OpFuncCompareNotEqual
	},

	{
		">",
		CG_OpFuncCompareGreater
	},

	{
		">=",
		CG_OpFuncCompareGreaterOrEqual
	},

	{
		"<",
		CG_OpFuncCompareSmaller
	},

	{
		"<=",
		CG_OpFuncCompareSmallerOrEqual
	},

	{
		"&&",
		CG_OpFuncCompareAnd
	},

	{
		"||",
		CG_OpFuncCompareOr
	},

	{
		NULL,
		NULL
	},
};

//================
//CG_OperatorFuncForArgument
//================
static void *CG_OperatorFuncForArgument( char *token )
{
	cg_layoutoperators_t *op;

	while( *token == ' ' )
		token++;

	for( op = cg_LayoutOperators; op->name; op++ )
	{
		if( !Q_stricmp( token, op->name ) )
			return op->opFunc;
	}

	return NULL;
}

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

static char *CG_GetStringArg( struct cg_layoutnode_s **argumentsnode );
static float CG_GetNumericArg( struct cg_layoutnode_s **argumentsnode );

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

static int layout_cursor_x = 400;
static int layout_cursor_y = 300;
static int layout_cursor_width = 100;
static int layout_cursor_height = 100;
static int layout_cursor_align = ALIGN_LEFT_TOP;
static vec4_t layout_cursor_color = { 1, 1, 1, 1 };
static vec3_t layout_cursor_rotation = { 0, 0, 0 };
static struct mufont_s *layout_cursor_font;

enum
{
	LNODE_NUMERIC,
	LNODE_STRING,
	LNODE_REFERENCE_NUMERIC,
	LNODE_COMMAND
};

//=============================================================================
// Commands' Functions
//=============================================================================
static int CG_LFuncDrawTimer( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	char time[64];
	int min, sec, milli;

	milli = (int)CG_GetNumericArg( &argumentnode );
	if( milli == STAT_NOTSET )
		return qfalse;

	// stat is in milliseconds/100.0f
	min = milli/600;
	milli -= min*600;
	sec = milli/10;
	milli -= sec*10;
	// we want MM:SS:m
	Q_snprintfz( time, sizeof( time ), "%02d:%02d.%1d", min, sec, milli );
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, time, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawPicVar( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int min, max, val, firstimg, lastimg, imgcount;
	static char filefmt[MAX_QPATH], filenm[MAX_QPATH], *ptr;
	int x, y, filenr;
	int cnt = 1;

	// get tje arguments
	val      = (int)( CG_GetNumericArg( &argumentnode ) );
	min      = (int)( CG_GetNumericArg( &argumentnode ) );
	max      = (int)( CG_GetNumericArg( &argumentnode ) );
	firstimg = (int)( CG_GetNumericArg( &argumentnode ) );
	lastimg  = (int)( CG_GetNumericArg( &argumentnode ) );

	if( min > max )
	{            // swap min and max and count downwards
		max ^= min;
		min ^= max;
		max ^= min;
		cnt = -cnt;
	}
	if( firstimg > lastimg )
	{                     // swap firstimg and lastimg and count the other way around
		firstimg ^= lastimg;
		lastimg  ^= firstimg;
		firstimg ^= lastimg;
		cnt = -cnt;
	}

	if( val < min )
		val = min;
	if( val > max )
		val = max;
	val -= min;
	max -= min;
	min = 0;

	imgcount = lastimg - firstimg + 1;

	if( ( max != 0 ) && ( imgcount != 0 ) )
	{                                // Check for division by 0
		filenr =  (int)( ( (double)val / ( (double)max / imgcount ) ) );
	}
	else
	{
		filenr = 0;
	}
	if( filenr >= imgcount )
		filenr = ( imgcount - 1 );
	if( filenr < 0 )
		filenr = 0;

	if( cnt < 0 )
	{
		filenr = ( imgcount - filenr ) - 1;
	}
	filenr += firstimg;

	filefmt[0] = '\0';
	Q_strncpyz( filefmt, CG_GetStringArg( &argumentnode ), sizeof( filenm ) );
	ptr = filefmt;
	while( ( ptr[0] ) && ( ptr[1] ) )
	{
		if( ( ptr[0] == '#' ) && ( ptr[1] == '#' ) )
		{
			ptr[0] = '%';
			ptr[1] = 'd';
			break; // Only replace first occurance?
		}
		ptr++;
	}
	if( ( ptr[0] != '%' ) && ( ptr[1] != 'd' ) )
	{
		CG_Printf( "WARNING 'CG_LFuncDrawPicVar' Invalid file string parameter, no '##' present!" );
		return qfalse;
	}
	Q_snprintfz( filenm, sizeof( filenm ), filefmt, filenr );
	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
	trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic( filenm ) );
	return qtrue;
}

static int CG_LFuncDrawPicByIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );
	int x, y;

	if( value >= 0 && value < MAX_IMAGES )
	{
		if( cgs.configStrings[CS_IMAGES + value][0] )
		{
			x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
			y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
			trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic( cgs.configStrings[CS_IMAGES+value] ) );
			return qtrue;
		}
	}

	return qfalse;
}

static int CG_LFuncDrawPicByItemIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int itemindex = (int)CG_GetNumericArg( &argumentnode );
	int x, y;
	gitem_t	*item;

	item = GS_FindItemByTag( itemindex );
	if( !item )
		return qfalse;
	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
	trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic( item->icon ) );
	return qtrue;
}

static int CG_LFuncDrawPicByName( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int x, y;

	x = CG_HorizontalAlignForWidth( layout_cursor_x, layout_cursor_align, layout_cursor_width );
	y = CG_VerticalAlignForHeight( layout_cursor_y, layout_cursor_align, layout_cursor_height );
	trap_R_DrawStretchPic( x, y, layout_cursor_width, layout_cursor_height, 0, 0, 1, 1, layout_cursor_color, trap_R_RegisterPic( CG_GetStringArg( &argumentnode ) ) );
	return qtrue;
}

static int CG_LFuncDrawModelByIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	struct model_s *model;
	int value = (int)CG_GetNumericArg( &argumentnode );

	if( value >= 0 && value < MAX_MODELS )
	{
		model = value > 1 ? CG_RegisterModel( cgs.configStrings[CS_MODELS+value] ) : NULL;
		CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, NULL, layout_cursor_rotation[YAW] );
		return qtrue;
	}

	return qfalse;
}

static int CG_LFuncDrawModelByName( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	struct model_s *model;
	struct shader_s *shader;
	char *shadername;

	model = CG_RegisterModel( CG_GetStringArg( &argumentnode ) );
	shadername = CG_GetStringArg( &argumentnode );
	shader = Q_stricmp( shadername, "NULL" ) ? trap_R_RegisterPic( shadername ) : NULL;
	CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, shader, layout_cursor_rotation[YAW] );
	return qtrue;
}

static int CG_LFuncDrawModelByItemIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int i;
	gitem_t	*item;
	struct model_s *model;
	int itemindex = (int)CG_GetNumericArg( &argumentnode );

	item = GS_FindItemByTag( itemindex );
	if( !item )
		return qfalse;
	for( i = 0; i < MAX_ITEM_MODELS; i++ )
	{
		if( item->world_model[i] != NULL )
		{
			model = itemindex >= 1 ? CG_RegisterModel( item->world_model[i] ) : NULL;
			CG_DrawHUDModel( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_width, layout_cursor_height, model, NULL, layout_cursor_rotation[YAW] );
		}
	}
	return qtrue;
}

static int CG_LFuncCursor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_x = (int)( CG_GetNumericArg( &argumentnode )*cgs.vidWidth/800 );
	layout_cursor_y = (int)( CG_GetNumericArg( &argumentnode )*cgs.vidHeight/600 );
	return qtrue;
}

static int CG_LFuncSize( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_width = (int)( CG_GetNumericArg( &argumentnode )*cgs.vidWidth/800 );
	layout_cursor_height = (int)( CG_GetNumericArg( &argumentnode )*cgs.vidHeight/600 );
	return qtrue;
}

static int CG_LFuncColor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int i;
	for( i = 0; i < 4; i++ )
	{
		layout_cursor_color[i] = CG_GetNumericArg( &argumentnode );
		clamp( layout_cursor_color[i], 0, 1 );
	}
	return qtrue;
}

static int CG_LFuncColorToTeamColor( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_TeamColor( CG_GetNumericArg( &argumentnode ), layout_cursor_color );
	return qtrue;
}

static int CG_LFuncColorAlpha( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	layout_cursor_color[3] = CG_GetNumericArg( &argumentnode );
	return qtrue;
}

static int CG_LFuncColorAlphaToDamageAlpha( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	float value = CG_GetNumericArg( &argumentnode );
	if( value <= 1 )
		layout_cursor_color[3] = CG_GetDamageIndicatorAlpha();
	else if( value <= 2 )
		layout_cursor_color[3] = CG_GetDamageIndicatorDirAlpha( DAMAGE_INDICATOR_DIR_LEFT );
	else if( value <= 3 )
		layout_cursor_color[3] = CG_GetDamageIndicatorDirAlpha( DAMAGE_INDICATOR_DIR_RIGHT );
	else if( value <= 4 )
		layout_cursor_color[3] = CG_GetDamageIndicatorDirAlpha( DAMAGE_INDICATOR_DIR_FRONT );
	else if( value <= 5 )
		layout_cursor_color[3] = CG_GetDamageIndicatorDirAlpha( DAMAGE_INDICATOR_DIR_BEHIND );
	else
		layout_cursor_color[3] = CG_GetDamageIndicatorAlpha();
	return qtrue;
}

static int CG_LFuncRotationSpeed( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int i;
	for( i = 0; i < 3; i++ )
	{
		layout_cursor_rotation[i] = CG_GetNumericArg( &argumentnode );
		clamp( layout_cursor_rotation[i], 0, 999 );
	}
	return qtrue;
}

static int CG_LFuncAlign( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int v, h;

	h = (int)CG_GetNumericArg( &argumentnode );
	v = (int)CG_GetNumericArg( &argumentnode );
	if( h < 1 ) h = 1;
	if( v < 1 ) v = 1;
	layout_cursor_align = ( h-1 )+( 3*( v-1 ) );
	return qtrue;
}

static int CG_LFuncFont( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	struct mufont_s *font = NULL;
	char *fontname = CG_GetStringArg( &argumentnode );

	if( !Q_stricmp( fontname, "con_fontsystemsmall" ) )
	{
		font = cgs.fontSystemSmall;
	}
	else if( !Q_stricmp( fontname, "con_fontsystemmedium" ) )
	{
		font = cgs.fontSystemMedium;
	}
	else if( !Q_stricmp( fontname, "con_fontsystembig" ) )
	{
		font = cgs.fontSystemBig;
	}
	else
	{
		font = trap_SCR_RegisterFont( fontname );
	}

	if( font )
	{
		layout_cursor_font = font;
		return qtrue;
	}

	return qfalse;
}

static int CG_LFuncDrawObituaries( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int internal_align = (int)CG_GetNumericArg( &argumentnode );
	int icon_size = (int)CG_GetNumericArg( &argumentnode );

	CG_DrawObituaries( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color,
	                   layout_cursor_width, layout_cursor_height, internal_align, icon_size );
	return qtrue;
}

static int CG_LFuncDrawAwards( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawAwards( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font );
	return qtrue;
}

static int CG_LFuncDrawClock( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawClock( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawHelpMessage( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	// hide this one when scoreboard is up
	if( !( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD ) )
	{
		if( !cgs.demoPlaying )
		{
			int i;
			int y = layout_cursor_y;
			const char *helpmessage = "";

			for( i = 0; i < 3; i++ )
			{
				size_t len;

				switch( i )
				{
				case 0:
					helpmessage = ( cg_showhelp->integer && cg.matchmessage ? cg.matchmessage : "" );
					break;
				case 1:
					if( !cg.motd )
						return qtrue;
					helpmessage = "\nMessage of the Day:";
					break;
				case 2:
					helpmessage = cg.motd;
					break;
				default:
					return qtrue;
				}

				if( helpmessage[0] )
				{
					do
					{
						len = trap_SCR_DrawStringWidth( layout_cursor_x, y, layout_cursor_align, helpmessage, layout_cursor_width, layout_cursor_font, layout_cursor_color );
						if( !len )
						{
							if( *helpmessage == '\r' || *helpmessage == '\n' )
								len = 1;
							else
								break;
						}
						if( helpmessage[len-1] == '\n' )
						{
							y += trap_SCR_strHeight( layout_cursor_font );
						}
						helpmessage += len;
					}
					while( helpmessage[0] );

					y += trap_SCR_strHeight( layout_cursor_font );
				}
			}
		}
	}
	return qtrue;
}

static int CG_LFuncDrawPointed( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawPointed( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawString( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	char *string = CG_GetStringArg( &argumentnode );

	if( !string || !string[0] )
		return qfalse;
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, string, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawItemNameFromIndex( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	gitem_t	*item;
	int itemindex = CG_GetNumericArg( &argumentnode );

	item = GS_FindItemByTag( itemindex );
	if( !item || !item->pickup_name )
		return qfalse;
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, item->pickup_name, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawConfigstring( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int index = (int)CG_GetNumericArg( &argumentnode );

	if( index < 0 || index >= MAX_CONFIGSTRINGS )
	{
		CG_Printf( "WARNING 'CG_LFuncDrawConfigstring' Bad stat_string index" );
		return qfalse;
	}
	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, cgs.configStrings[index], layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawPlayername( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int index = (int)CG_GetNumericArg( &argumentnode ) - 1;

	if( index >= 0 && index < MAX_CLIENTS && cgs.clientInfo[index].name )
	{
		trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, cgs.clientInfo[index].name, layout_cursor_font, layout_cursor_color );
		return qtrue;
	}
	return qfalse;
}

static int CG_LFuncDrawNumeric( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );
	CG_DrawHUDNumeric( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_color, layout_cursor_width, layout_cursor_height, value );
	return qtrue;
}

static int CG_LFuncDrawStretchNum( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	static char num[16];
	int len;
	int value = (int)CG_GetNumericArg( &argumentnode );

	Q_snprintfz( num, sizeof( num ), "%i", value );
	len = strlen( num );
	if( len * layout_cursor_height <= layout_cursor_width )
	{
		CG_DrawHUDNumeric( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_color, layout_cursor_height, layout_cursor_height, value );
	}
	else
	{    //stretch numbers
		CG_DrawHUDNumeric( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_color, layout_cursor_width / len, layout_cursor_height, value );
	}
	return qtrue;
}

static int CG_LFuncDrawNumeric2( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );

	trap_SCR_DrawString( layout_cursor_x, layout_cursor_y, layout_cursor_align, va( "%i", value ), layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawBar( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );
	int maxvalue = (int)CG_GetNumericArg( &argumentnode );
	CG_DrawHUDRect( layout_cursor_x, layout_cursor_y, layout_cursor_align,
	                layout_cursor_width, layout_cursor_height, value, maxvalue,
	                layout_cursor_color, NULL );
	return qtrue;
}

static int CG_LFuncDrawPicBar( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int value = (int)CG_GetNumericArg( &argumentnode );
	int maxvalue = (int)CG_GetNumericArg( &argumentnode );

	CG_DrawHUDRect( layout_cursor_x, layout_cursor_y, layout_cursor_align,
	               layout_cursor_width, layout_cursor_height, value, maxvalue,
	               layout_cursor_color, trap_R_RegisterPic( CG_GetStringArg( &argumentnode ) ) );
	return qtrue;
}

static int CG_LFuncCustomWeaponIcons( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int weapon = (int)CG_GetNumericArg( &argumentnode );
	int hasgun = (int)CG_GetNumericArg( &argumentnode );

	if( weapon <= WEAP_NONE || weapon >= WEAP_TOTAL )
		return qfalse;

	if( hasgun )
		customWeaponPics[weapon-1] = CG_GetStringArg( &argumentnode );
	else
		customNoGunWeaponPics[weapon-1] = CG_GetStringArg( &argumentnode );

	return qtrue;
}

static int CG_LFuncCustomWeaponSelect( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	customWeaponSelectPic = CG_GetStringArg( &argumentnode );
	return qtrue;
}

static int CG_LFuncDrawWeaponIcons( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int offx, offy, w, h;

	offx = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidWidth/800 );
	offy = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidHeight/600 );
	w = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidWidth/800 );
	h = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidHeight/600 );

	CG_DrawWeaponIcons( layout_cursor_x, layout_cursor_y, offx, offy, w, h, layout_cursor_align );

	return qtrue;
}
static int CG_LFuncDrawCaptureAreas( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int w = CG_GetNumericArg( &argumentnode ),
	    h = CG_GetNumericArg( &argumentnode ),
			alignment;
	char *alignmentstr = CG_GetStringArg( &argumentnode );

	if( cg.frame.playerState.stats[STAT_GAMETYPE] != GAMETYPE_TDM )
		return qtrue;

	if( !strcmp( alignmentstr, "vertical" ) )
		alignment = 2;
	else
		alignment = 1;


	CG_DrawCaptureAreas( layout_cursor_x, layout_cursor_y, w, h, alignment );

	return qtrue;
}

//================
//CG_DrawMiniMap
//DO NOT CALL when not a spectator!
//================
static void CG_DrawMiniMap( int x, int y, int iw, int ih, qboolean draw_playernames, qboolean draw_itemnames, vec4_t color )
{
	int i, entnum;
	centity_t *cent;
	vec3_t coords;
	vec4_t tmp_col, tmp_white_alpha, tmp_yellow_alpha;		// background color of the map
	vec3_t mins, maxs;
	int map_w, map_h, map_z;
	int x_lefttop, y_lefttop, z_lefttop;	// the negative y coordinate (bottom of the map)
	float map_div_w, map_div_h;

	Vector4Copy( color, tmp_col );
	Vector4Copy( colorWhite, tmp_white_alpha );
	Vector4Copy( colorYellow, tmp_yellow_alpha );
	tmp_white_alpha[3] = color[3];
	tmp_yellow_alpha[3] = color[3];

	// Get Worldmodel bounds...
	trap_R_ModelBounds( NULL, mins, maxs ); // NULL for world model...

	map_w = maxs[0] - mins[0];      // map width (in game units)
	map_h = maxs[1] - mins[1];
	map_z = maxs[2] - mins[2];
	x_lefttop = -mins[0];   // the negative x coordinate ( left of the map )
	y_lefttop = -mins[1];   // the negative y coordinate (bottom of the map)
	z_lefttop = -mins[2];   // the negative y coordinate (bottom of the map)

	map_div_w = (float)map_w / (float)iw;
	map_div_h = (float)map_h / (float)ih;

	CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, iw, ih, 1, 1, tmp_col, cgs.shaderMiniMap );

	//alignment test, to display green dot at 0,0
	//CG_DrawHUDRect( x + x_lefttop/map_div_w -1, y + y_lefttop/map_div_h -1,ALIGN_LEFT_TOP,3,3,1,1, colorGreen, CG_MediaShader( cgs.media.shaderMiniMap ) );

	for( i = 0; i < cg.frame.numEntities; i++ )
	{
		entnum = cg.frame.parsedEntities[i&( MAX_PARSE_ENTITIES-1 )].number;

		// filter invalid ents
		if( entnum < 1 || entnum >= MAX_EDICTS )
			continue;
		// retrieve the centity_t
		cent = &cg_entities[entnum];

		// filter the current player
		//if( cent->current.number == cg.chasedNum_latched + 1 )
		//	continue;

		if( ( cent->current.type != ET_PLAYER ) && !( cent->item ) )
			continue;

		coords[0] = ( cent->current.origin[0] + x_lefttop ) / map_div_w;
		coords[1] = ih - ( cent->current.origin[1] + y_lefttop ) / map_div_h;
		coords[2] = ( cent->current.origin[2] + (float)z_lefttop ) / (float)map_z;

		// is it a player?
		if( ( cent->current.type == ET_PLAYER ) )
		{
			int box_size = (int)( 3.0 + coords[2] * 10.0 );

			// check if we're allowed to see team members only (coaches, CA)
			if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SPECTEAMONLY )
			{
				if( cg.frame.playerState.stats[STAT_REALTEAM] != cent->current.team )
					continue;
			}

			if( cent->current.team == TEAM_SPECTATOR )
			{
				if( entnum != cg.view.POVent )
					continue;
				VectorSet( tmp_col, 1, 1, 1 );
			}
			else
			{
				CG_TeamColor( cent->current.team, tmp_col );
			}

			// get color
			tmp_col[3] = bound( 0, color[3] + 0.3f, 1 );
			CG_DrawHUDRect( x + (int)coords[0] -box_size/2, y + (int)coords[1] -box_size/2,
				ALIGN_LEFT_TOP, box_size, box_size, box_size, box_size, tmp_col, NULL );

			// do we want names too?
			if( draw_playernames == qtrue )
				trap_SCR_DrawString( x + (int)coords[0] + 8,
				y + (int)coords[1] - 4,	ALIGN_LEFT_TOP,	COM_RemoveColorTokensExt( cgs.clientInfo[cent->current.number-1].name, qtrue ),
				cgs.fontSystemSmall, tmp_yellow_alpha );
		}
		else if( cent->item && cent->item->icon )
		{
			// if ALIGN_CENTER_MIDDLE or something is used, images are fucked
			// so thats why they are set manually at the correct pos with -n
			CG_DrawHUDRect( x+(int)coords[0]-8, y+(int)coords[1]-8, ALIGN_LEFT_TOP, 15, 15, 1, 1, tmp_white_alpha, trap_R_RegisterPic( cent->item->icon ) );
			if( draw_itemnames == qtrue )
				trap_SCR_DrawString( x + (int)coords[0] + 16, y + (int)coords[1] - 8, ALIGN_LEFT_TOP, cent->item->short_name, cgs.fontSystemSmall, tmp_yellow_alpha );
		}
	}
}

static int CG_LFuncDrawMiniMap( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int w, h, draw_playernames, draw_itemnames;
	int myteam = cg.frame.playerState.stats[STAT_TEAM];

	w = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidWidth/800 );
	h = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidHeight/600 );
	draw_playernames = (int)( CG_GetNumericArg( &argumentnode ) );
	draw_itemnames = (int)( CG_GetNumericArg( &argumentnode ) );

	//CG_DrawWeaponIcons( layout_cursor_x, layout_cursor_y, offx, offy, w, h, layout_cursor_align );

	if( ( myteam == TEAM_SPECTATOR ) || ( cg.frame.playerState.POVnum != cgs.playerNum + 1 ) )
		if( cg_showminimap->integer )
			CG_DrawMiniMap( layout_cursor_x, layout_cursor_y, w, h, draw_playernames, draw_itemnames, layout_cursor_color );

	return qtrue;
}

static int CG_LFuncDrawWeaponWeakAmmo( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int offx, offy, fontsize;

	offx = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidWidth/800 );
	offy = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidHeight/600 );
	fontsize = (int)CG_GetNumericArg( &argumentnode );

	CG_DrawWeaponAmmos( layout_cursor_x, layout_cursor_y, offx, offy, fontsize, 2, layout_cursor_align );

	return qtrue;
}

static int CG_LFuncDrawWeaponStrongAmmo( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	int offx, offy, fontsize;

	offx = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidWidth/800 );
	offy = (int)( CG_GetNumericArg( &argumentnode ) * cgs.vidHeight/600 );
	fontsize = (int)CG_GetNumericArg( &argumentnode );

	CG_DrawWeaponAmmos( layout_cursor_x, layout_cursor_y, offx, offy, fontsize, 1, layout_cursor_align );

	return qtrue;
}

static int CG_LFuncDrawTeamInfo( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawTeamInfo( layout_cursor_x, layout_cursor_y, layout_cursor_align, layout_cursor_font, layout_cursor_color );
	return qtrue;
}

static int CG_LFuncDrawCrossHair( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawCrosshair( layout_cursor_x, layout_cursor_y, layout_cursor_align );
	return qtrue;
}

static int CG_LFuncDrawKeyState( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	char *key = CG_GetStringArg( &argumentnode );

	CG_DrawKeyState( layout_cursor_x, layout_cursor_y, layout_cursor_width, layout_cursor_height, layout_cursor_align, key );
	return qtrue;
}

static int CG_LFuncDrawNet( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	CG_DrawNet( layout_cursor_x, layout_cursor_y, layout_cursor_width, layout_cursor_height, layout_cursor_align, layout_cursor_color );
	return qtrue;
}


static int CG_LFuncIf( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments )
{
	return (int)CG_GetNumericArg( &argumentnode );
}


typedef struct cg_layoutcommand_s
{
	char *name;
	int ( *func )( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments );
	int numparms;
	char *help;
} cg_layoutcommand_t;

static cg_layoutcommand_t cg_LayoutCommands[] =
{
	{
		"setCursor",
		CG_LFuncCursor,
		2,
		"Moves the cursor to x and y coordinates."
	},

	{
		"setAlign",
		CG_LFuncAlign,
		2,
		"Changes align setting. Parameters: horizontal alignment, vertical alignment"
	},

	{
		"setSize",
		CG_LFuncSize,
		2,
		"Sets width and height. Used for pictures and models."
	},

	{
		"setFont",
		CG_LFuncFont,
		1,
		"Sets font by font name. Accepts 'con_fontsystemsmall', 'con_fontsystemmedium' and 'con_fontsystembig' as shortcut to default game fonts."
	},

	{
		"setColor",
		CG_LFuncColor,
		4,
		"Sets color setting in RGBA mode. Used for text and pictures"
	},

	{
		"setColorToTeamColor",
		CG_LFuncColorToTeamColor,
		1,
		"Sets cursor color to the color of the team provided in the argument"
	},

	{
		"setColorAlpha",
		CG_LFuncColorAlpha,
		1,
		"Changes the alpha value of the current color"
	},

	{
		"setRotationSpeed",
		CG_LFuncRotationSpeed,
		3,
		"Sets rotation speeds as vector. Used for models"
	},

	{
		"setCustomWeaponIcons",
		CG_LFuncCustomWeaponIcons,
		3,
		"Sets a custom shader for weapon icons"
	},

	{
		"setCustomWeaponSelect",
		CG_LFuncCustomWeaponSelect,
		1,
		"Sets a custom shader for weapon icons"
	},

	{
		"setColorAlphaToDamageIndicator",
		CG_LFuncColorAlphaToDamageAlpha,
		1,
		"Changes the alpha value of the current color controlld by the damage indicator. Parameters: type (1 = global, 2 = left, 3 = right, 4 = front, 5 = behind"
	},

	{
		"drawObituaries",
		CG_LFuncDrawObituaries,
		2,
		"Draws graphical death messages"
	},

	{
		"drawAwards",
		CG_LFuncDrawAwards,
		0,
		"Draws award messages"
	},

	{
		"drawClock",
		CG_LFuncDrawClock,
		0,
		"Draws clock"
	},

	{
		"drawHelpString",
		CG_LFuncDrawHelpMessage,
		0,
		"Draws the help message"
	},

	{
		"drawPlayername",
		CG_LFuncDrawPlayername,
		1,
		"Draws the name of the player with id provided by the argument"
	},

	{
		"drawPointing",
		CG_LFuncDrawPointed,
		0,
		"Draws the name of the player in the crosshair"
	},

	{
		"drawStatString",
		CG_LFuncDrawConfigstring,
		1,
		"Draws configstring of argument id"
	},

	{
		"drawItemName",
		CG_LFuncDrawItemNameFromIndex,
		1,
		"Draws the name of the item with given item index"
	},

	{
		"drawString",
		CG_LFuncDrawString,
		1,
		"Draws the string in the argument"
	},

	{
		"drawStringNum",
		CG_LFuncDrawNumeric2,
		1,
		"Draws numbers as text"
	},

	{
		"drawNum",
		CG_LFuncDrawNumeric,
		1,
		"Draws numbers of given character size"
	},

	{
		"drawStretchNum",
		CG_LFuncDrawStretchNum,
		1,
		"Draws numbers stretch inside a given size"
	},

	{
		"drawBar",
		CG_LFuncDrawBar,
		2,
		"Draws a bar of size setting, the bar is filled in proportion to the arguments"
	},

	{
		"drawPicBar",
		CG_LFuncDrawPicBar,
		3,
		"Draws a picture of size setting, is filled in proportion to the 2 arguments (value, maxvalue). 3rd argument is the picture path"
	},

	{
		"drawCrosshair",
		CG_LFuncDrawCrossHair,
		0,
		"Draws the game crosshair"
	},

	{
		"drawKeyState",
		CG_LFuncDrawKeyState,
		1,
		"Draws icons showing if the argument key is pressed. Possible arg: forward, backward, left, right, fire, jump, crouch, special"
	},

	{
		"drawNetIcon",
		CG_LFuncDrawNet,
		0,
		"Draws the disconnection icon"
	},

	{
		"drawPicByIndex",
		CG_LFuncDrawPicByIndex,
		1,
		"Draws a pic with argument as imageIndex"
	},

	{
		"drawPicByItemIndex",
		CG_LFuncDrawPicByItemIndex,
		1,
		"Draws a item icon pic with argument as itemIndex"
	},

	{
		"drawPicByName",
		CG_LFuncDrawPicByName,
		1,
		"Draws a pic with argument being the file path"
	},

	{
		"drawModelByIndex",
		CG_LFuncDrawModelByIndex,
		1,
		"Draws a model with argument being the modelIndex"
	},

	{
		"drawModelByName",
		CG_LFuncDrawModelByName,
		2,
		"Draws a model with argument being the path to the model file"
	},

	{
		"drawModelByItemIndex",
		CG_LFuncDrawModelByItemIndex,
		1,
		"Draws a item model with argument being the item index"
	},

	{
		"drawWeaponIcons",
		CG_LFuncDrawWeaponIcons,
		4,
		"Draws the icons of weapon/ammo owned by the player, arguments are offset x, offset y, size x, size y"
	},

	{
		"drawCaptureAreas",
		CG_LFuncDrawCaptureAreas,
		3,
		"Draws the capture areas for iTDM"
	},

	{
		"drawMiniMap",
		CG_LFuncDrawMiniMap,
		4,
		"Draws a minimap (radar). size x, size y, draw_playernames, draw_itemnames"
	},

	{
		"drawWeaponWeakAmmo",
		CG_LFuncDrawWeaponWeakAmmo,
		3,
		"Draws the amount of weak ammo owned by the player, arguments are offset x, offset y, fontsize"
	},
	{
		"drawWeaponStrongAmmo",
		CG_LFuncDrawWeaponStrongAmmo,
		3,
		"Draws the amount of strong ammo owned by the player,  arguments are offset x, offset y, fontsize"
	},

	{
		"drawTeamInfo",
		CG_LFuncDrawTeamInfo,
		0,
		"Draws the Team Info (locations) box"
	},

	{
		"if",
		CG_LFuncIf,
		1,
		"Conditional expression. Argument accepts operations >, <, ==, >=, <=, etc"
	},

	{
		"endif",
		NULL,
		0,
		"End of conditional expression block"
	},

	{
		"drawTimer",
		CG_LFuncDrawTimer,
		1,
		"Draws a timer clock for the race gametype"
	},
	{
		"drawPicVar",
		CG_LFuncDrawPicVar,
		6,
		"Draws a picture from a sequence, depending on the value of a given parameter. Parameters: minval, maxval, value, firstimg, lastimg, imagename (replacing ## by the picture number, no leading zeros), starting at 0)"
	},

	{
		NULL,
		NULL,
		0,
		NULL
	}
};

void Cmd_CG_PrintHudHelp_f( void )
{
	cg_layoutcommand_t *cmd;
	cg_layoutoperators_t *op;
	int i;
	gitem_t	*item;
	char *name, *p;

	CG_Printf( "- %sHUD scripts commands\n-------------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	for( cmd = cg_LayoutCommands; cmd->name; cmd++ )
	{
		CG_Printf( "- cmd: %s%s%s expected arguments: %s%i%s\n- desc: %s%s%s\n",
		           S_COLOR_YELLOW, cmd->name, S_COLOR_WHITE,
		           S_COLOR_YELLOW, cmd->numparms, S_COLOR_WHITE,
		           S_COLOR_BLUE, cmd->help, S_COLOR_WHITE );
	}
	CG_Printf( "\n" );

	CG_Printf( "- %sHUD scripts operators\n------------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	CG_Printf( "- " );
	for( op = cg_LayoutOperators; op->name; op++ )
	{
		CG_Printf( "%s%s%s, ", S_COLOR_YELLOW, op->name, S_COLOR_WHITE );
	}
	CG_Printf( "\n\n" );

	CG_Printf( "- %sHUD scripts CONSTANT names\n-------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	for( item = &itemdefs[1]; item->classname; item++ )
	{
		name = Q_strupr( CG_CopyString( item->pickup_name ) );
		p = name;
		while( ( p = strchr( p, ' ' ) ) )
		{
			*p = '_';
		}

		CG_Printf( "%sITEM_%s%s, ", S_COLOR_YELLOW, name, S_COLOR_WHITE );
	}
	for( i = 0; cg_numeric_constants[i].name != NULL; i++ )
	{
		CG_Printf( "%s%s%s, ", S_COLOR_YELLOW, cg_numeric_constants[i].name, S_COLOR_WHITE );
	}
	CG_Printf( "\n\n" );

	CG_Printf( "- %sHUD scripts REFERENCE names\n------------------------------%s\n", S_COLOR_YELLOW, S_COLOR_WHITE );
	for( i = 0; cg_numeric_references[i].name != NULL; i++ )
	{
		CG_Printf( "%s%s%s, ", S_COLOR_YELLOW, cg_numeric_references[i].name, S_COLOR_WHITE );
	}
	CG_Printf( "\n" );
}


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

typedef struct cg_layoutnode_s
{
	int ( *func )( struct cg_layoutnode_s *commandnode, struct cg_layoutnode_s *argumentnode, int numArguments );
	int type;
	char *string;
	int integer;
	float value;
	float ( *opFunc )( const float a, float b );
	struct cg_layoutnode_s *parent;
	struct cg_layoutnode_s *next;
	struct cg_layoutnode_s *ifthread;
} cg_layoutnode_t;

//================
//CG_GetStringArg
//================
static char *CG_GetStringArg( struct cg_layoutnode_s **argumentsnode )
{
	struct cg_layoutnode_s *anode = *argumentsnode;

	if( !anode || anode->type == LNODE_COMMAND )
		CG_Error( "'CG_LayoutGetIntegerArg': bad arg count" );

	// we can return anything as string
	*argumentsnode = anode->next;
	return anode->string;
}

//================
//CG_GetNumericArg
//can use recursion for mathematical operations
//================
static float CG_GetNumericArg( struct cg_layoutnode_s **argumentsnode )
{
	struct cg_layoutnode_s *anode = *argumentsnode;
	float value;

	if( !anode || anode->type == LNODE_COMMAND )
		CG_Error( "'CG_LayoutGetIntegerArg': bad arg count" );

	if( anode->type != LNODE_NUMERIC && anode->type != LNODE_REFERENCE_NUMERIC )
		CG_Printf( "WARNING: 'CG_LayoutGetIntegerArg': arg %s is not numeric", anode->string );

	*argumentsnode = anode->next;
	if( anode->type == LNODE_REFERENCE_NUMERIC )
	{
		value = cg_numeric_references[anode->integer].func( cg_numeric_references[anode->integer].parameter );
	}
	else
	{
		value = anode->value;
	}

	// recurse if there are operators
	if( anode->opFunc != NULL )
	{
		value = anode->opFunc( value, CG_GetNumericArg( argumentsnode ) );
	}

	return value;
}

//================
//CG_LayoutParseCommandNode
//alloc a new node for a command
//================
static cg_layoutnode_t *CG_LayoutParseCommandNode( char *token )
{

	int i = 0;
	cg_layoutcommand_t *command = NULL;
	cg_layoutnode_t *node;

	for( i = 0; cg_LayoutCommands[i].name; i++ )
	{
		if( !Q_stricmp( token, cg_LayoutCommands[i].name ) )
		{
			command = &cg_LayoutCommands[i];
			break;
		}
	}

	if( command == NULL )
		return NULL;

	node = CG_Malloc( sizeof( cg_layoutnode_t ) );
	node->type = LNODE_COMMAND;
	node->integer = command->numparms;
	node->value = 0.0f;
	node->string = CG_CopyString( command->name );
	node->func = command->func;
	node->ifthread = NULL;

	return node;
}

//================
//CG_LayoutParseArgumentNode
//alloc a new node for an argument
//================
static cg_layoutnode_t *CG_LayoutParseArgumentNode( char *token )
{
	cg_layoutnode_t *node;
	int type = LNODE_NUMERIC;
	char *valuetok, *p;
	static char tmpstring[8];
	gitem_t	*item;

	// find what's it
	if( !token )
		return NULL;

	valuetok = token;

	if( token[0] == '%' )
	{                  // it's a stat parm
		int i;
		type = LNODE_REFERENCE_NUMERIC;
		valuetok++; // skip %

		// replace stat names by values
		for( i = 0; cg_numeric_references[i].name != NULL; i++ )
		{
			if( !Q_stricmp( valuetok, cg_numeric_references[i].name ) )
			{
				Q_snprintfz( tmpstring, sizeof( tmpstring ), "%i", i );
				valuetok = tmpstring;
				break;
			}
		}
		if( cg_numeric_references[i].name == NULL )
		{
			CG_Printf( "Warning: HUD: %s is not valid numeric reference\n", valuetok );
			valuetok--;
			valuetok = "0";
		}
	}
	else if( token[0] == '#' )
	{                         // it's a integer constant
		int i;
		type = LNODE_NUMERIC;
		valuetok++; // skip #

		// replace constants names by values
		if( !strncmp( valuetok, "ITEM_", strlen( "ITEM_" ) ) )
		{
			p = valuetok;
			while( ( p = strchr( p, '_' ) ) )
			{
				*p = ' ';
			}
			if( ( item = GS_FindItemByName( valuetok + strlen( "ITEM_" ) ) ) )
			{
				Q_snprintfz( tmpstring, sizeof( tmpstring ), "%i", item->tag );
				valuetok = tmpstring;
			}
			if( item == NULL )
			{
				CG_Printf( "Warning: HUD: %s is not valid numeric constant\n", valuetok );
				valuetok = "0";
			}
		}
		else
		{
			for( i = 0; cg_numeric_constants[i].name != NULL; i++ )
			{
				if( !Q_stricmp( valuetok, cg_numeric_constants[i].name ) )
				{
					Q_snprintfz( tmpstring, sizeof( tmpstring ), "%i", cg_numeric_constants[i].value );
					valuetok = tmpstring;
					break;
				}
			}
			if( cg_numeric_constants[i].name == NULL )
			{
				CG_Printf( "Warning: HUD: %s is not valid numeric constant\n", valuetok );
				valuetok = "0";
			}
		}

#if 0 // not used yet at least
	}
	else if( token[0] == '$' )
	{                         // it's a string constant
		int i;
		type = LNODE_STRING;
		valuetok++; // skip $

		// replace stat names by values
		for( i = 0; cg_string_constants[i].name != NULL; i++ )
		{
			if( !Q_stricmp( valuetok, cg_string_constants[i].name ) )
			{
				Q_snprintfz( tmpstring, sizeof( tmpstring ), "%s", cg_string_constants[i].value );
				valuetok = tmpstring;
				break;
			}
		}
#endif

	}
	else if( token[0] < '0' && token[0] > '9' && token[0] != '.' )
	{
		type = LNODE_STRING;
	}

	// alloc
	node = CG_Malloc( sizeof( cg_layoutnode_t ) );
	node->type = type;
	node->integer = atoi( valuetok );
	node->value = atof( valuetok );
	node->string = CG_CopyString( token );
	node->func = NULL;
	node->ifthread = NULL;

	// return it
	return node;
}

//================
//CG_LayoutCathegorizeToken
//================
static int CG_LayoutCathegorizeToken( char *token )
{
	int i = 0;

	for( i = 0; cg_LayoutCommands[i].name; i++ )
	{
		if( !Q_stricmp( token, cg_LayoutCommands[i].name ) )
			return LNODE_COMMAND;
	}

	if( token[0] == '%' )
	{                  // it's a numerical reference
		return LNODE_REFERENCE_NUMERIC;
	}
	else if( token[0] == '#' )
	{                         // it's a numerical constant
		return LNODE_NUMERIC;
#if 0
	}
	else if( token[0] == '$' )
	{                         // it's a string constant
		return LNODE_STRING;
#endif
	}
	else if( token[0] < '0' && token[0] > '9' && token[0] != '.' )
	{
		return LNODE_STRING;
	}

	return LNODE_NUMERIC;
}

//================
//CG_RecurseFreeLayoutThread
//recursive for freeing "if" subtrees
//================
static void CG_RecurseFreeLayoutThread( cg_layoutnode_t *rootnode )
{
	cg_layoutnode_t *node;

	if( !rootnode )
		return;

	while( rootnode )
	{
		node = rootnode;
		rootnode = rootnode->parent;

		if( node->ifthread )
			CG_RecurseFreeLayoutThread( node->ifthread );

		if( node->string )
			CG_Free( node->string );

		CG_Free( node );
	}
}

//================
//CG_LayoutFixCommasInToken
//commas are accepted in the scripts. They actually do nothing, but are good for readability
//================
static qboolean CG_LayoutFixCommasInToken( char **ptr, char **backptr )
{
	char *token;
	char *back;
	int offset, count;
	qboolean stepback = qfalse;

	token = *ptr;
	back = *backptr;

	if( !token || !strlen( token ) ) return qfalse;

	// check that sizes match (quotes are removed from tokens)
	offset = count = strlen( token );
	back = *backptr;
	while( count-- )
	{
		if( *back == '"' )
		{
			count++;
			offset++;
		}
		back--;
	}

	back = *backptr - offset;
	while( offset )
	{
		if( *back == '"' )
		{
			offset--;
			back++;
			continue;
		}

		if( *token != *back )
			CG_Printf( "Token and Back mismatch %c - %c\n", *token, *back );

		if( *back == ',' )
		{
			*back = ' ';
			stepback = qtrue;
		}

		offset--;
		token++;
		back++;
	}

	return stepback;
}

//================
//CG_RecurseParseLayoutScript
//recursive for generating "if" subtrees
//================
static cg_layoutnode_t *CG_RecurseParseLayoutScript( char **ptr, int level )
{
	cg_layoutnode_t	*command = NULL;
	cg_layoutnode_t	*node = NULL;
	cg_layoutnode_t	*rootnode = NULL;
	int expecArgs = 0, numArgs = 0;
	int token_type;
	qboolean add;
	char *token, *s_tokenback;

	if( !ptr )
		return NULL;

	if( !*ptr || !*ptr[0] )
		return NULL;

	while( *ptr )
	{
		s_tokenback = *ptr;

		token = COM_Parse( ptr );
		while( *token == ' ' ) token++; // eat up whitespaces
		if( !Q_stricmp( ",", token ) ) continue; // was just a comma
		if( CG_LayoutFixCommasInToken( &token, ptr ) )
		{
			*ptr = s_tokenback; // step back
			continue;
		}

		if( !*token ) continue;
		if( !strlen( token ) ) continue;

		add = qfalse;
		token_type = CG_LayoutCathegorizeToken( token );

		// if it's an operator, we don't create a node, but add the operation to the last one
		if( CG_OperatorFuncForArgument( token ) != NULL )
		{
			if( !node )
			{
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" Operator hasn't any prior argument\n", level, token );
				continue;
			}
			if( node->type == LNODE_COMMAND || node->type == LNODE_STRING )
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" Operator was assigned to a command node\n", level, token );
			else
				expecArgs++; // we now expect one extra argument (not counting the operator one)

			node->opFunc = CG_OperatorFuncForArgument( token );
			continue; // skip and continue
		}

		if( expecArgs > numArgs )
		{                   // we are expecting an argument
			switch( token_type )
			{
			case LNODE_NUMERIC:
			case LNODE_STRING:
			case LNODE_REFERENCE_NUMERIC:
				break;
			case LNODE_COMMAND:
			{
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" is not a valid argument for \"%s\"\n", level, token, command ? command->string : "" );
				continue;
			}
				break;
			default:
			{
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i) skip and continue: Unrecognized token \"%s\"\n", level, token );
				continue;
			}
				break;
			}
		}
		else
		{
			if( token_type != LNODE_COMMAND )
			{                       // we are expecting a command
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): unrecognized command \"%s\"\n", level, token );
				continue;
			}

			//special case: endif commands interrupt the thread and are not saved
			if( !Q_stricmp( token, "endif" ) )
			{
				//finish the last command properly
				if( command )
					command->integer = expecArgs;
				return rootnode;
			}

			//special case: last command was "if", we create a new sub-thread and ignore the new command
			if( command && !Q_stricmp( command->string, "if" ) )
			{
				*ptr = s_tokenback; // step back one token
				command->ifthread = CG_RecurseParseLayoutScript( ptr, level + 1 );
			}
		}

		// things look fine, proceed creating the node
		switch( token_type )
		{
		case LNODE_NUMERIC:
		case LNODE_STRING:
		case LNODE_REFERENCE_NUMERIC:
		{
			node = CG_LayoutParseArgumentNode( token );
			if( !node )
			{
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" is not a valid argument for \"%s\"\n", level, token, command ? command->string : "" );
				break;
			}
			numArgs++;
			add = qtrue;
		}
			break;
		case LNODE_COMMAND:
		{
			node = CG_LayoutParseCommandNode( token );
			if( !node )
			{
				CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): \"%s\" is not a valid command\n", level, token );
				break; // skip and continue
			}

			// expected arguments could have been extended by the operators
			if( command )
				command->integer = expecArgs;

			// move on into the new command
			command = node;
			numArgs = 0;
			expecArgs = command->integer;
			add = qtrue;
		}
			break;
		default:
			break;
		}

		if( add == qtrue )
		{
			if( rootnode )
				rootnode->next = node;
			node->parent = rootnode;
			rootnode = node;
		}
	}

	if( level > 0 )
		CG_Printf( "WARNING 'CG_RecurseParseLayoutScript'(level %i): If without endif\n", level );

	return rootnode;
}

#if 0
static void CG_RecursePrintLayoutThread( cg_layoutnode_t *rootnode, int level )
{
	int i;
	cg_layoutnode_t *node;

	node = rootnode;
	while( node->parent )
		node = node->parent;

	while( node )
	{
		for( i = 0; i < level; i++ )
			CG_Printf( "   " );
		CG_Printf( "%s\n", node->string );

		if( node->ifthread )
			CG_RecursePrintLayoutThread( node->ifthread, level+1 );

		node = node->next;
	}
}
#endif

//================
//CG_ParseLayoutScript
//================
static void CG_ParseLayoutScript( char *string, cg_layoutnode_t *rootnode )
{

	CG_RecurseFreeLayoutThread( cg.statusBar );
	cg.statusBar = CG_RecurseParseLayoutScript( &string, 0 );

#if 0
	CG_RecursePrintLayoutThread( cg_layoutRootNode, 0 );
#endif
}

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

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

//================
//CG_RecurseExecuteLayoutThread
// Execution works like this: First node (on backwards) is expected to be the command, followed by arguments nodes.
// we keep a pointer to the command and run the tree counting arguments until we reach the next command,
// then we call the command function sending the pointer to first argument and the pointer to the command.
// At return we advance one node (we stopped at last argument node) so it starts again from the next command (if any).
//
// When finding an "if" command with a subtree, we execute the "if" command. In the case it
// returns any value, we recurse execute the subtree
//================
static void CG_RecurseExecuteLayoutThread( cg_layoutnode_t *rootnode )
{
	cg_layoutnode_t	*argumentnode = NULL;
	cg_layoutnode_t	*commandnode = NULL;
	int numArguments;

	if( !rootnode )
		return;

	// run until the real root
	commandnode = rootnode;
	while( commandnode->parent )
	{
		commandnode = commandnode->parent;
	}

	// now run backwards up to the next command node
	while( commandnode )
	{
		argumentnode = commandnode->next;

		// we could trust the parser, but I prefer counting the arguments here
		numArguments = 0;
		while( argumentnode )
		{
			if( argumentnode->type == LNODE_COMMAND )
				break;

			argumentnode = argumentnode->next;
			numArguments++;
		}

		// reset
		argumentnode = commandnode->next;

		// Execute the command node
		if( commandnode->integer != numArguments )
		{
			CG_Printf( "ERROR: Layout command %s: invalid argument count (expecting %i, found %i)\n", commandnode->string, commandnode->integer, numArguments );
			return;
		}
		if( commandnode->func )
		{
			//special case for if commands
			if( commandnode->func( commandnode, argumentnode, numArguments ) )
			{
				// execute the "if" thread when command returns a value
				if( commandnode->ifthread )
					CG_RecurseExecuteLayoutThread( commandnode->ifthread );
			}
		}

		//move up to next command node
		commandnode = argumentnode;
		if( commandnode == rootnode )
			return;

		while( commandnode && commandnode->type != LNODE_COMMAND )
		{
			commandnode = commandnode->next;
		}
	}
}

//================
//CG_ExecuteLayoutProgram
//================
void CG_ExecuteLayoutProgram( struct cg_layoutnode_s *rootnode )
{
	CG_RecurseExecuteLayoutThread( rootnode );
}

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

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



//================
//CG_LoadHUDFile
//================
// Loads the HUD-file recursively. Recursive includes now supported
// Also processes "preload" statements for graphics pre-loading
#define HUD_MAX_LVL 16 // maximum levels of recursive file loading
static char *CG_LoadHUDFile( char *path )
{
	char *rec_fn[HUD_MAX_LVL]; // Recursive filenames...
	char *rec_buf[HUD_MAX_LVL]; // Recursive file contents buffers
	char *rec_ptr[HUD_MAX_LVL]; // Recursive file position buffers
	char *token = NULL, *tmpbuf = NULL, *retbuf = NULL;
	int rec_lvl = 0, rec_plvl = -1;
	int retuse = 0, retlen = 0;
	int f, i, len;

	// Check if path is correct
	if( path == NULL )
		return NULL;
	memset( rec_ptr, 0, sizeof( rec_ptr ) );
	memset( rec_buf, 0, sizeof( rec_buf ) );
	memset( rec_ptr, 0, sizeof( rec_ptr ) );

	// Copy the path of the file to the first recursive level filename :)
	rec_fn[rec_lvl] = CG_Malloc( strlen( path ) + 1 );
	Q_strncpyz( rec_fn[rec_lvl], path, strlen( path ) + 1 );
	while( 1 )
	{
		if( rec_lvl > rec_plvl )
		{
			// We went a recursive level higher, our filename should have been filled in :)
			if( !rec_fn[rec_lvl] )
			{
				rec_lvl--;
			}
			else if( rec_fn[rec_lvl][0] == '\0' )
			{
				CG_Free( rec_fn[rec_lvl] );
				rec_fn[rec_lvl] = NULL;
				rec_lvl--;
			}
			else
			{
				// First check if this file hadn't been included already by one of the previous files
				// in the current file-stack to prevent problems :)
				for( i = 0; i < rec_lvl; i++ )
				{
					if( !Q_stricmp( rec_fn[rec_lvl], rec_fn[i] ) )
					{
						// Recursive file loading detected!!
						CG_Printf( "HUD: WARNING: Detected recursive file inclusion: %s\n", rec_fn[rec_lvl] );
						CG_Free( rec_fn[rec_lvl] );
						rec_fn[rec_lvl] = NULL;
					}
				}
			}
			// File was OK :)
			if( rec_fn[rec_lvl] != NULL )
			{
				len = trap_FS_FOpenFile( rec_fn[rec_lvl], &f, FS_READ );
				if( len > 0 )
				{
					rec_plvl = rec_lvl;
					rec_buf[rec_lvl] = CG_Malloc( len + 1 );
					rec_buf[rec_lvl][len] = '\0';
					rec_ptr[rec_lvl] = rec_buf[rec_lvl];
					// Now read the file
					if( trap_FS_Read( rec_buf[rec_lvl], len, f ) <= 0 )
					{
						CG_Free( rec_fn[rec_lvl] );
						CG_Free( rec_buf[rec_lvl] );
						rec_fn[rec_lvl] = NULL;
						rec_buf[rec_lvl] = NULL;
						if( rec_lvl > 0 )
						{
							CG_Printf( "HUD: WARNING: Read error while loading file: %s\n", rec_fn[rec_lvl] );
						}
						rec_lvl--;
					}
					trap_FS_FCloseFile( f );
				}
				else
				{
					if( !len )
					{
						// File was empty - still have to close
						trap_FS_FCloseFile( f );
					}
					else if( rec_lvl > 0 )
					{
						CG_Printf( "HUD: WARNING: Could not include file: %s\n", rec_fn[rec_lvl] );
					}
					CG_Free( rec_fn[rec_lvl] );
					rec_fn[rec_lvl] = NULL;
					rec_lvl--;
				}
			}
			else
			{
				// Skip this file, go down one level
				rec_lvl--;
			}
			rec_plvl = rec_lvl;
		}
		else if( rec_lvl < rec_plvl )
		{
			// Free previous level buffer
			if( rec_fn[rec_plvl] ) CG_Free( rec_fn[rec_plvl] );
			if( rec_buf[rec_plvl] ) CG_Free( rec_buf[rec_plvl] );
			rec_buf[rec_plvl] = NULL;
			rec_ptr[rec_plvl] = NULL;
			rec_fn[rec_plvl] = NULL;
			rec_plvl = rec_lvl;
			if( rec_lvl < 0 )
			{
				// Break - end of recursive looping
				if( retbuf == NULL )
				{
					CG_Printf( "HUD: ERROR: Could not load empty HUD-script: %s\n", path );
				}
				break;
			}
		}
		if( rec_lvl < 0 )
		{
			break;
		}
		token = COM_ParseExt2( ( const char ** )&rec_ptr[rec_lvl], qtrue, qfalse );
		if( !Q_stricmp( "include", token ) )
		{
			// Handle include
			token = COM_ParseExt2( ( const char ** )&rec_ptr[rec_lvl], qfalse, qfalse );
			if( ( ( rec_lvl + 1 ) < HUD_MAX_LVL ) && ( rec_ptr[rec_lvl] ) && ( token ) && ( token[0] != '\0' ) )
			{
				// Go to next recursive level and prepare it's filename :)
				rec_lvl++;
				i = strlen( "huds/" ) + strlen( token ) + strlen( ".hud" ) + 1;
				rec_fn[rec_lvl] = CG_Malloc( i );
				Q_snprintfz( rec_fn[rec_lvl], i, "huds/%s", token );
				COM_DefaultExtension( rec_fn[rec_lvl], ".hud", i );
				if( trap_FS_FOpenFile( rec_fn[rec_lvl], NULL, FS_READ ) < 0 )
				{
					// File doesn't exist!
					CG_Free( rec_fn[rec_lvl] );
					i = strlen( "huds/inc/" ) + strlen( token ) + strlen( ".hud" ) + 1;
					rec_fn[rec_lvl] = CG_Malloc( i );
					Q_snprintfz( rec_fn[rec_lvl], i, "huds/inc/%s", token );
					COM_DefaultExtension( rec_fn[rec_lvl], ".hud", i );
					if( trap_FS_FOpenFile( rec_fn[rec_lvl], NULL, FS_READ ) < 0 )
					{
						CG_Free( rec_fn[rec_lvl] );
						rec_fn[rec_lvl] = NULL;
						rec_lvl--;
					}
				}
			}
		}
		else if( !Q_stricmp( "precache", token ) )
		{
			// Handle graphics precaching
			if( rec_ptr[rec_lvl] == NULL )
			{
				CG_Printf( "HUD: ERROR: EOF instead of file argument for preload\n" );
			}
			else
			{
				token = COM_ParseExt2( ( const char ** )&rec_ptr[rec_lvl], qfalse, qfalse );
				if( ( token ) && ( token[0] != '\0' ) )
				{
					if( developer->integer )
						CG_Printf( "HUD: INFO: Precaching image '%s'\n", token );
					trap_R_RegisterPic( token );
				}
				else
				{
					CG_Printf( "HUD: ERROR: Missing argument for preload\n" );
				}
			}
		}
		else if( ( len = strlen( token ) ) > 0 )
		{
			// Normal token, add to token-pool.
			if( ( retuse + len + 1 ) >= retlen )
			{
				// Enlarge token buffer by 1kb
				retlen += 1024;
				tmpbuf = CG_Malloc( retlen );
				if( retbuf )
				{
					memcpy( tmpbuf, retbuf, retuse );
					CG_Free( retbuf );
				}
				retbuf = tmpbuf;
				retbuf[retuse] = '\0';
			}
			strcat( &retbuf[retuse], token );
			retuse += len;
			strcat( &retbuf[retuse], " " );
			retuse++;
			retbuf[retuse] = '\0';
		}
		// Detect "end-of-file" of included files and go down 1 level.
		if( ( rec_lvl <= rec_plvl ) && ( !rec_ptr[rec_lvl] ) )
		{
			rec_plvl = rec_lvl;
			rec_lvl--;
		}
	}
	if( retbuf == NULL )
	{
		CG_Printf( "HUD: ERROR: Could not load file: %s\n", path );
	}
	return retbuf;
}

/*
   //================
   //CG_OptimizeStatusBarFile
   //================

   static char *CG_OptimizeStatusBarFile( char *path, qboolean skip_include )
   {
    int length, f;
    char *temp_buffer;
    char *opt_buffer;
    char *parse, *token, *toinclude;
    int optimized_length, included_length;
    int fi, fi_length;
    size_t fipath_size;
    char *fipath;

    // load the file
    length = trap_FS_FOpenFile( path, &f, FS_READ );
    if( length == -1 )
   	return NULL;
    if( !length ) {
   	trap_FS_FCloseFile( f );
   	return NULL;
    }

    // alloc a temp buffer according to size
    temp_buffer = CG_Malloc( length + 1 );

    // load layout file in memory
    trap_FS_Read( temp_buffer, length, f );
    trap_FS_FCloseFile( f );

    // first pass: scan buffer line by line and check for include lines
    // if found compute needed length for included files
    // else count token length as Com_Parse as skipped comments and stuff like that
    parse=temp_buffer;
    optimized_length=0;
    included_length=0;
    while ( parse )
    {
   	token=COM_ParseExt2( &parse, qtrue, qfalse );

   	if( (!Q_stricmp( token, "include" )) && skip_include == qfalse)
   	{
   	    toinclude=COM_ParseExt2( &parse, qtrue, qfalse );
   	    //if( cg_debug_HUD && cg_debug_HUD->integer )
   	    //CG_Printf( "included: %s \n", toinclude );

   	    fipath_size = strlen("huds/inc/") + strlen(toinclude) + strlen(".hud") + 1;
   	    fipath = CG_Malloc( fipath_size );
   	    Q_snprintfz( fipath, fipath_size, "huds/inc/%s", toinclude );
   	    COM_DefaultExtension( fipath, ".hud", fipath_size );
   	    fi_length = trap_FS_FOpenFile( fipath, &fi, FS_READ );

   	    if( fi_length == -1 )
   	    {
   		// failed to include file
   		CG_Printf( "HUD: Failed to include hud subfile: %s \n", fipath );
   	    }

   	    if( fi_length > 0)
   	    {
   		// not an empty file
   		// we have the size we can close it
   		included_length+=fi_length;
   	    }
   	    trap_FS_FCloseFile( fi );

   	    CG_Free( fipath );
   	    fipath = NULL;
   	    fipath_size = 0;
   	} else {
   	    // not an include line
   	    // simply count token size for optimized hud layout
   	    optimized_length+=strlen( token ) + 1; // for spaces
   	}
    }

    // second pass: we now have the needed size
    // alloc optimized buffer
    opt_buffer = CG_Malloc( optimized_length + included_length + 1 );

    // reparse all file and copy it
    parse=temp_buffer;
    while ( parse )
    {
   	token=COM_ParseExt2( &parse, qtrue, qfalse );

   	if( (!Q_stricmp( token, "include" )) && skip_include == qfalse)
   	{
   	    toinclude=COM_ParseExt2( &parse, qtrue, qfalse );

   	    fipath_size = strlen("huds/inc/") + strlen(toinclude) + strlen(".hud") + 1;
   	    fipath = CG_Malloc( fipath_size );
   	    Q_snprintfz( fipath, fipath_size, "huds/inc/%s", toinclude );
   	    COM_ReplaceExtension( fipath, ".hud", fipath_size );
   	    fi_length = trap_FS_FOpenFile( fipath, &fi, FS_READ );

   	    if( fi_length == -1 )
   	    {
   		// failed to include file
   		CG_Printf( "HUD: Failed to include hud subfile: %s \n", path );
   	    }

   	    if( fi_length > 0)
   	    {
   		char *fi_parse;
   		char *include_buffer;

   		// not an empty file
   		if( cg_debug_HUD && cg_debug_HUD->integer )
   		    CG_Printf( "HUD: Including sub hud file: %s \n", toinclude );

   		// reparse all lines from included file to skip include commands

   		// alloc a temp buffer according to size
   		include_buffer = CG_Malloc( fi_length + 1 );

   		// load included layout file in memory
   		trap_FS_Read( include_buffer, fi_length, fi );

   		fi_parse=include_buffer;
   		while ( fi_parse )
   		{
   		    token=COM_ParseExt2( &fi_parse, qtrue, qfalse );

   		    if( !Q_stricmp( token, "include" ) )
   		    {
   			// skip recursive include
   			toinclude=COM_ParseExt2( &fi_parse, qtrue, qfalse );
   			CG_Printf( "HUD: No recursive include allowed: huds/inc/%s \n", toinclude );
   		    }
   		    else
   		    {
   			// normal token
   			strcat( opt_buffer, token );
   			strcat( opt_buffer, " ");
   		    }
   		}

   		// release memory
   		CG_Free( include_buffer );
   	    }

   	    // close included file
   	    trap_FS_FCloseFile( fi );

   	    CG_Free( fipath );
   	    fipath = NULL;
   	    fipath_size = 0;
   	} else {
   	    // normal token
   	    strcat( opt_buffer, token );
   	    strcat( opt_buffer, " ");
   	}
    }

    // free temp buffer
    CG_Free( temp_buffer );

    return opt_buffer;
   }
 */

//================
//CG_LoadStatusBarFile
//================
static void CG_LoadStatusBarFile( char *path )
{
	char *opt;
	int i;

	assert( path && path[0] );

	//opt = CG_OptimizeStatusBarFile( path, qfalse );
	opt = CG_LoadHUDFile( path );

	if( opt == NULL )
	{
		CG_Printf( "HUD: failed to load %s file\n", path );
		return;
	}
	// load the new status bar program
	CG_ParseLayoutScript( opt, cg.statusBar );
	// Free the opt buffer!
	CG_Free( opt );

	// set up layout font as default system font
	layout_cursor_font = trap_SCR_RegisterFont( DEFAULT_FONT_SMALL );

	for( i = 0; i < WEAP_TOTAL-1; i++ )
	{
		customWeaponPics[i] = NULL;
		customNoGunWeaponPics[i] = NULL;
	}
	customWeaponSelectPic = NULL;

	for( i = 0; i < AWARD_LINES-1; i++ )
		memset( &awards[i], 0, sizeof( cg_award_t ) );
}

//================
//CG_LoadStatusBar
//================
void CG_LoadStatusBar( void )
{
	size_t filename_size;
	char *filename;

	assert( cg_clientHUD && cg_clientHUD->dvalue[0] );

	// buffer for filenames
	filename_size = strlen( "huds/" ) + max( strlen( cg_clientHUD->dvalue ), strlen( cg_clientHUD->string ) ) + 4 + 1;
	filename = CG_Malloc( filename_size );

	// always load default first. Custom second if needed
	if( cg_debug_HUD && cg_debug_HUD->integer )
		CG_Printf( "HUD: Loading default clientHUD huds/%s\n", cg_clientHUD->dvalue );
	Q_snprintfz( filename, filename_size, "huds/%s", cg_clientHUD->dvalue );
	COM_DefaultExtension( filename, ".hud", filename_size );
	CG_LoadStatusBarFile( filename );

	if( cg_clientHUD->string[0] && Q_stricmp( cg_clientHUD->string, cg_clientHUD->dvalue ) )
	{
		if( cg_debug_HUD && cg_debug_HUD->integer )
			CG_Printf( "HUD: Loading custom clientHUD huds/%s\n", cg_clientHUD->string );
		Q_snprintfz( filename, filename_size, "huds/%s", cg_clientHUD->string );
		COM_DefaultExtension( filename, ".hud", filename_size );
		CG_LoadStatusBarFile( filename );
	}

	CG_Free( filename );
}
