/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// hud.cpp: New HUD handler for MoHAA
//


#include "glb_local.h"
#include "hud.h"

#ifdef CGAME_DLL
#include "cgamex86.h"
#include "cgame/cg_hud.h"
#include "script/centity.h"
#include "archive.h"
#endif

#ifdef GAME_DLL
#include "../fgame/player.h"
#endif

#include "game.h"
#include "level.h"
#include "scriptexception.h"

Container< Hud * > hudElements;
int lastNumberRemoved = -1;

Event EV_HUD_GetAlignX
(
	"alignx",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the horizontal alignment for the HUD. Specified by 'left', 'center', or 'right'",
	EV_GETTER
);

Event EV_HUD_GetAlignY
(
	"aligny",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the vertical alignment for the HUD",
	EV_GETTER
);

Event EV_HUD_GetAlpha
(
	"alpha",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD alpha",
	EV_GETTER
);

Event EV_HUD_GetColor
(
	"color",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD colors",
	EV_GETTER
);

Event EV_HUD_GetFont
(
	"font",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD font",
	EV_GETTER
);

Event EV_HUD_GetRectX
(
	"x",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD horizontal position",
	EV_GETTER
);

Event EV_HUD_GetRectY
(
	"y",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD vertical position",
	EV_GETTER
);

Event EV_HUD_GetTime
(
	"gettime",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD  time",
	EV_RETURN
);

Event EV_HUD_GetHeight
(
	"getheight",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD height",
	EV_RETURN
);

Event EV_HUD_GetWidth
(
	"getwidth",
	EV_DEFAULT,
	NULL,
	NULL,
	"Gets the HUD width",
	EV_RETURN
);

Event EV_HUD_Set3D
(
	"setdimension",
	EV_DEFAULT,
	"vbbE",
	"vector_or_offset always_show depth entity",
	"Sets this huddraw element to be a 3D world icon and can specify if this icon is always shown on-screen even if the player isn't looking at.\n"
	"It uses xy pos from huddraw_rect.\n"
	"If entity is specified, the vector will be an offset relative to the entity.\n"
	"depth specify if the icon is shown through walls.",
	EV_NORMAL
);

Event EV_HUD_SetNon3D
(
	"resetdimension",
	EV_DEFAULT,
	NULL,
	NULL,
	"Bring the HUD back to normal dimension (non-3D)",
	EV_NORMAL
);

Event EV_HUD_SetAlignX
(
	"alignx",
	EV_DEFAULT,
	"s",
	"align",
	"Sets the horizontal alignment for the HUD. Specified by 'left', 'center', or 'right'",
	EV_SETTER
);

Event EV_HUD_SetAlignY
(
	"aligny",
	EV_DEFAULT,
	"s",
	"align",
	"Sets the vertical alignment for the HUD. Specified by 'top', 'center', or 'bottom'",
	EV_SETTER
);

Event EV_HUD_SetAlpha
(
	"alpha",
	EV_DEFAULT,
	"f",
	"alpha",
	"Sets the HUD alpha",
	EV_SETTER
);

Event EV_HUD_SetColor
(
	"color",
	EV_DEFAULT,
	"v",
	"color",
	"Sets the HUD colors",
	EV_SETTER
);

Event EV_HUD_SetFont
(
	"font",
	EV_DEFAULT,
	"s",
	"font",
	"Sets the HUD font",
	EV_SETTER
);

Event EV_HUD_SetPlayer
(
	"setplayer",
	EV_DEFAULT,
	"eB",
	"entity clears",
	"Sets to which player this HUD is shown and possibly clears the HUD to the previous player(s) it was shown. A NULL/NIL entity means the HUD will be shown to all players\n"
	"Note : you will need to call in HUD functions again as it won't refresh to the player(s)",
	EV_NORMAL
);

Event EV_HUD_SetRectX
(
	"x",
	EV_DEFAULT,
	"i",
	"x",
	"Sets the HUD horizontal position",
	EV_SETTER
);

Event EV_HUD_SetRectY
(
	"y",
	EV_DEFAULT,
	"i",
	"y",
	"Sets the HUD vertical position",
	EV_SETTER
);

Event EV_HUD_SetShader
(
	"setshader",
	EV_DEFAULT,
	"sii",
	"shader width height",
	"Sets the HUD shader",
	EV_NORMAL
);

Event EV_HUD_SetText
(
	"settext",
	EV_DEFAULT,
	"s",
	"text",
	"Sets the HUD text",
	EV_NORMAL
);

Event EV_HUD_SetTimer
(
	"settimer",
	EV_DEFAULT,
	"fF",
	"time fade_at_time",
	"Sets a timer to count down an optionally fade at the specified time",
	EV_NORMAL
);

Event EV_HUD_SetTimerUp
(
	"settimerup",
	EV_DEFAULT,
	"fF",
	"time fade_at_time",
	"Sets a timer to count up an optionally fade at the specified time",
	EV_NORMAL
);

Event EV_HUD_SetVirtualSize
(
	"setvirtualsize",
	EV_DEFAULT,
	"b",
	"virtual",
	"Sets if the HUD should use virtual screen resolution for positioning and size",
	EV_NORMAL
);

Event EV_HUD_FadeOverTime
(
	"fadeovertime",
	EV_DEFAULT,
	"f",
	"time",
	"Sets the HUD to transition in color (or alpha) over time",
	EV_NORMAL
);

Event EV_HUD_MoveOverTime
(
	"moveovertime",
	EV_DEFAULT,
	"f",
	"time",
	"Sets the HUD to move over time",
	EV_NORMAL
);

Event EV_HUD_ScaleOverTime
(
	"scaleovertime",
	EV_DEFAULT,
	"fii",
	"time width height",
	"Sets the HUD to scale over time",
	EV_NORMAL
);

Event EV_HUD_Refresh
(
	"refresh",
	EV_DEFAULT,
	NULL,
	NULL,
	"Refresh the HUD",
	EV_NORMAL
);

Hud *Hud::Find( int index )
{
	for( int i = 0; i < hudElements.NumObjects(); i++ )
	{
		Hud *hud = hudElements[ i ];

		if( hud->number == index ) {
			return hud;
		}
	}

	return NULL;
}

Hud *Hud::FindOrCreate( int index )
{
	Hud *hud = Hud::Find( index );

	if( hud == NULL ) {
		hud = new Hud( index );
	}

	return hud;
}

void Hud::ProcessThink()
{
#ifdef CGAME_DLL
	if( !ui_hud->integer && !cg_hud->integer ) {
		return;
	}
#endif

	for( int i = 0; i < hudElements.NumObjects(); i++ ) {
		hudElements[ i ]->Think();
	}
}

int Hud::Sort( const void *elem1, const void *elem2 )
{
	const Hud *hud1 = *( Hud ** )elem1;
	const Hud *hud2 = *( Hud ** )elem2;

	// Sort HUDs by their number
	if ( hud1->number < hud2->number ) {
		return -1;
	} else if ( hud1->number > hud2->number ) {
		return 1;
	} else {
		return 0;
	}
}

int Hud::GetFreeNumber()
{
	for( int i = 0; i < hudElements.NumObjects(); i++ )
	{
		Hud *hud = hudElements[ i ];

		if( hud->number != i ) {
			return i;
		}
	}

	return hudElements.NumObjects();
}

#ifdef GAME_DLL
Hud::Hud( int client )
#else
Hud::Hud( int index )
#endif
{
#ifdef GAME_DLL
	if( client < -1 || client > game.maxclients )
	{
		delete this;
		ScriptError( "Invalid client number %d !\n", client );

		return;
	}
#endif

#ifdef CGAME_DLL
	if( index == -1 )
	{
#endif
		number = GetFreeNumber();
	/*if( lastNumberRemoved != -1 )
	{
		number = GetFreeNumber();
		lastNumberRemoved = -1;
	} else {
		number = hudElements.NumObjects();
	}*/
#ifdef CGAME_DLL
	} else {
		number = index;
	}
#endif

#ifdef GAME_DLL
	clientnum = client;
#endif

	alignX = HUD_CENTER;
	alignY = HUD_CENTER;

	color = Vector( 1.f, 1.f, 1.f );
	alpha = 0.0f;

	x = 0;
	y = 0;

	width = 8;
	height = 8;

#ifdef CGAME_DLL
	font = NULL;
	shaderHandle = NULL;
#endif

	fade_alpha = false;
	fade_move = false;
	fade_scale = false;
	fade_timer_flags = 0;

	fade_alpha_first = false;
	fade_move_x_first = false;
	fade_move_y_first = false;

	fade_alpha_current = 0.f;
	fade_move_current = 0.f;
	fade_scale_current = 0.f;
	fade_time_current = 0.f;

	isDimensional = false;
	org = Vector( 0, 0, 0 );
	lastOrg = org;
	always_show = false;
	depth = false;
	enttarget = -1;

	virtualSize = false;

	fontName = "";
	shader = "";
	text = "";

	hudElements.AddObject( this );

	hudElements.Sort( Hud::Sort );
}

Hud::~Hud()
{
	lastNumberRemoved = number;

	SetAlpha( 0.f );

	if( isDimensional ) {
		SetNon3D();
	}

	/*
#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_DELETE );
		WriteNumber();
	gi.MSG_EndCGM();
#endif
*/

	hudElements.RemoveObject( this );
}

#ifdef CGAME_DLL
qboolean CG_WorldToScreen( Vector pt, float *x, float *y, qboolean bOptionalSeen, float shader_width, float shader_height )
{
	float	xzi, yzi;
	Vector	local, transformed;
	Vector	vfwd, vright, vup;
	qboolean bNotShown = false;

	AngleVectors( cg->refdefViewAngles, vfwd, vright, vup );

	//VectorSub( pt, cg->refdef.viewOrg, local );

	local = pt - cg->refdef.viewOrg;

	transformed[ 0 ] = DotProduct( local, vright );
	transformed[ 1 ] = DotProduct( local, vup );
	transformed[ 2 ] = DotProduct( local, vfwd );

	if( transformed[ 2 ] < 0.01f )
	{
		if( !bOptionalSeen ) {
			return false;
		}

		bNotShown = true;
	}

	float centerX = ( ( float )cgs->glConfig.vidWidth / 2 );
	float centerY = ( ( float )cgs->glConfig.vidHeight / 2 );
	xzi = centerX / transformed[ 2 ] * ( 100.0f / cg->refdef.fovX );
	yzi = centerY / transformed[ 2 ] * ( 100.0f / cg->refdef.fovY );
	*x = centerX + ( xzi * transformed[ 0 ] ) - ( shader_width / 2.0f );
	*y = centerY - ( yzi * transformed[ 1 ] ) - ( shader_height / 2.0f );

	if( !bOptionalSeen ) {
		return true;
	}

	float limitX = ( ( float )cgs->glConfig.vidWidth - shader_width );
	float limitY = ( ( float )cgs->glConfig.vidHeight - shader_height );

	if( bNotShown )
	{
		if( *x < limitX ) {
			*x = limitX;
		} else if( *x > 0 ) {
			*x = 0;
		} else if( *y < limitY ) {
			*y = limitY;
		} else if( *y > 0 ) {
			*y = 0;
		}
	}

	if( *x > limitX ) {
		*x = limitX;
	} else if( *x < 0 ) {
		*x = 0;
	}

	if( *y > limitY ) {
		*y = limitY;
	} else if( *y < 0 ) {
		*y = 0;
	}

	return true;
}

void Hud::Draw3D()
{
	float tmpX = 0, tmpY = 0;
	Vector loc;

	alignX = HUD_LEFT;
	alignY = HUD_TOP;
	virtualSize = false;

	if( enttarget != -1 )
	{
		centity_t *ce = CG_GetEntity( enttarget );

		if( ce != NULL )
		{
			loc = ce->currentState.origin + org;
			lastOrg = loc;
		} else {
			loc = lastOrg;
		}
	} else {
		loc = org;
	}

	if( !shaderHandle && text[ 0 ] != '\0' )
	{
		height = 5;
		width = re.GetFontStringWidth( font == NULL ? facfont20 : font, text );
	}

	if( !CG_WorldToScreen( loc, &tmpX, &tmpY, always_show, width, height ) ) {
		return;
	}

	x = tmpX;
	y = tmpY;
}

#endif

void Hud::FadeThink()
{
#ifdef CGAME_DLL
	float frametime = ( float )cg->frametime;
#else
	float frametime = level.frametime * 1000.0f;
#endif

	fade_alpha_current += frametime;

	float ratio = fade_alpha_current / fade_alpha_time;

	if( ratio >= 1.0f )
	{
		fade_alpha = false;
		fade_alpha_current = 0.f;
		fade_alpha_first = false;
		alpha = fade_alpha_target;

		return;
	}

	alpha = fade_alpha_start + ( fade_alpha_target - fade_alpha_start ) * ratio;
}

void Hud::MoveThink()
{
#ifdef CGAME_DLL
	float frametime = ( float )cg->frametime;
#else
	float frametime = level.frametime * 1000.0f;
#endif

	fade_move_current += frametime;

	float ratio = fade_move_current / fade_move_time;

	if( ratio >= 1.0f )
	{
		fade_move = false;
		fade_move_current = 0.f;
		fade_move_x_first = false;
		fade_move_y_first = false;

		x = fade_move_x_target;
		y = fade_move_y_target;

		return;
	}

	x = ( short )( fade_move_x_start + ( fade_move_x_target - fade_move_x_start ) * ratio );
	y = ( short )( fade_move_y_start + ( fade_move_y_target - fade_move_y_start ) * ratio );
}

void Hud::ScaleThink()
{
#ifdef CGAME_DLL
	float frametime = ( float )cg->frametime;
#else
	float frametime = level.frametime * 1000.0f;
#endif

	fade_scale_current += frametime;

	float ratio = fade_scale_current / fade_scale_time;

	if( ratio >= 1.0f )
	{
		fade_scale = false;
		fade_scale_current = 0.f;
		width = fade_scale_w_target;
		height = fade_scale_h_target;

		return;
	}

	width = ( short )( fade_scale_w_start + ( fade_scale_w_target - fade_scale_w_start ) * ratio );
	height = ( short )( fade_scale_h_start + ( fade_scale_h_target - fade_scale_h_start ) * ratio );
}

void Hud::TimerThink()
{
	int minutes;
	float seconds;
	const char *string;
	char buffer[ 128 ];

#ifdef CGAME_DLL
	float frametime = ( float )cg->frametime;
#else
	float frametime = level.frametime * 1000.0f;
#endif

	if( fade_timer_flags & TIMER_UP ) {
		fade_time_current += 0.001f * frametime;
	} else {
		fade_time_current -= 0.001f * frametime;
	}

	if( fade_time_current <= 0.0f )
	{
		if( fade_out_time > 0.0f ) {
			alpha = 0.0f;
		}

		fade_time_current = 0.0f;
	}

	if( fade_time_current >= 60.0f )
	{
		seconds = ( float )( ( int )fade_time_current % 60 );
		minutes = ( int )( fade_time_current / 60.0f );
	}
	else
	{
		if( fade_time_current >= 30.0f ) {
			seconds = ( float )( int )fade_time_current;
		} else {
			seconds = fade_time_current;
		}

		minutes = 0;
	}

	if( fade_time_current >= 30.0f )
	{
		string = "%d:%02.0f";
	}
	else
	{
		if( fade_time_current >= 10.0f ) {
			string = "%d:%02.1f";
		} else {
			string = "%d:0%.1f";
		}
	}

	if( ( !( fade_timer_flags & TIMER_UP ) && fade_time_current * 1000.0f < fade_out_time ) ||
		( ( fade_timer_flags & TIMER_UP ) && fade_time_current * 1000.0f > fade_out_time && fade_out_time >= 0.0f ) )
	{
		alpha -= ( 2.0f - fade_time_alpha_start ) / fade_out_time * frametime;

		if( alpha < 0.0f ) {
			alpha = 0.0f;
		}
	} else {
		fade_time_alpha_start = alpha;
	}

#ifdef CGAME_DLL
	if( !shaderHandle ) {
#else
	if( !*shader ) {
#endif
		Com_sprintf( buffer, sizeof( buffer ), string, minutes, seconds );

		SetText( buffer );
	} else {
		SetText( "" );
	}
}

void Hud::Think( void )
{
	if( !text.c_str() && !shader.c_str() ) {
		return;
	}

	if( fade_alpha ) {
		FadeThink();
	}

	if( fade_move ) {
		MoveThink();
	}

	if( fade_scale ) {
		ScaleThink();
	}

	if( fade_timer_flags & TIMER_ACTIVE ) {
		TimerThink();
	}

	if( alpha <= 0.0f ) {
		return;
	}

#ifdef CGAME_DLL
	if( isDimensional ) {
		Draw3D();
	}

	int vidWidth = virtualSize ? 640 : cgs->glConfig.vidWidth;
	int vidHeight = virtualSize ? 480 : cgs->glConfig.vidHeight;

	float hudX = x;
	float hudY = y;
	float hudW = width;
	float hudH = height;

	if( alignX == HUD_CENTER ) {
		hudX += vidWidth * 0.5f - hudW * 0.5f;
	} else if( alignX == HUD_RIGHT ) {
		hudX += vidWidth;
	}

	if( alignY == HUD_CENTER ) {
		hudY += vidHeight * 0.5f - hudH * 0.5f;
	} else if( alignY == HUD_BOTTOM ) {
		hudY += vidHeight;
	}

	vec4_t col = { color[ 0 ], color[ 1 ], color[ 2 ], alpha };

	cgi.R_SetColor( col );

	if( shaderHandle == NULL )
	{
		const char *Localized = cgi.LV_ConvertString( text );
		fontHeader_t **f = ( fontHeader_t ** )0x302B7D28;

		if( font == NULL && *f == NULL ) {
			return;
		}

		cgi.R_DrawString( font == NULL ? *f : font, Localized, hudX, hudY, -1, virtualSize );
	}
	else
	{
		if( virtualSize )
		{
			hudX *= cgs->screenXScale;
			hudY *= cgs->screenYScale;
			hudW *= cgs->screenXScale;
			hudH *= cgs->screenYScale;
		}

		cgi.R_DrawStretchPic( hudX, hudY, hudW, hudH, 0.0f, 0.0f, 1.0f, 1.0f, shaderHandle );
	}
#endif
}

void Hud::Archive( Archiver &arc )
{
	Listener::Archive( arc );
#ifdef CGAME_DLL
	arc.ArchiveInteger( ( int * )&number );

	arc.ArchiveInteger( ( int * )&alignX );
	arc.ArchiveInteger( ( int * )&alignY );

	arc.ArchiveFloat( &x );
	arc.ArchiveFloat( &y );
	arc.ArchiveFloat( &width );
	arc.ArchiveFloat( &height );

	arc.ArchiveVector( &color );
	arc.ArchiveFloat( &alpha );
	arc.ArchiveString( &fontName );
	arc.ArchiveString( &shader );
	arc.ArchiveString( &text );

	arc.ArchiveBool( &virtualSize );

	arc.ArchiveBool( &fade_alpha );
	arc.ArchiveBool( &fade_move );
	arc.ArchiveBool( &fade_scale );

	arc.ArchiveFloat( &fade_alpha_current );
	arc.ArchiveFloat( &fade_move_current );
	arc.ArchiveFloat( &fade_scale_current );

	arc.ArchiveFloat( &fade_alpha_time );
	arc.ArchiveFloat( &fade_move_time );
	arc.ArchiveFloat( &fade_scale_time );

	arc.ArchiveFloat( &fade_alpha_start );
	arc.ArchiveFloat( &fade_move_x_start );
	arc.ArchiveFloat( &fade_move_y_start );
	arc.ArchiveFloat( &fade_scale_w_start );
	arc.ArchiveFloat( &fade_scale_h_start );

	arc.ArchiveFloat( &fade_alpha_target );
	arc.ArchiveFloat( &fade_move_x_target );
	arc.ArchiveFloat( &fade_move_y_target );
	arc.ArchiveFloat( &fade_scale_w_target );
	arc.ArchiveFloat( &fade_scale_h_target );

	if( arc.Loading() )
	{
		if( fontName[ 0 ] != '\0' ) {
			SetFont( fontName );
		}

		if( shader[ 0 ] != '\0' ) {
			SetShader( shader, width, height );
		}

		hudElements.AddObject( this );

		hudElements.Sort( Hud::Sort );
	}
#endif
}

#ifdef GAME_DLL

int Hud::GetClient( void )
{
	return clientnum;
}

#endif

void Hud::FadeOverTime( float time )
{
	if( time <= 0.0f ) {
		return;
	}

	fade_alpha = true;
	fade_alpha_first = true;

	fade_alpha_time = time * 1000.0f;
	fade_alpha_current = 0.f;

	fade_alpha_start = alpha;
	fade_alpha_target = alpha;
	
	// FIXME: delete
	/*
#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_FADE ));
		WriteNumber();
		gi.MSG_WriteFloat( time );
	gi.MSG_EndCGM();
#endif
*/
}

void Hud::MoveOverTime( float time )
{
	if( time <= 0.0f ) {
		return;
	}

	fade_move = true;
	fade_move_x_first = true;
	fade_move_y_first = true;

	fade_move_time = time * 1000.0f;
	fade_move_current = 0.f;

	fade_move_x_start = x;
	fade_move_y_start = y;

	fade_move_x_target = x;
	fade_move_y_target = y;

	/*
#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_MOVE ));
		WriteNumber();
		gi.MSG_WriteFloat( time );
	gi.MSG_EndCGM();
#endif
*/
}

void Hud::Refresh( int clientNumber )
{
	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALIGN ));
		WriteNumber();
		gi.MSG_WriteBits( alignX, 2 );
		gi.MSG_WriteBits( alignY, 2 );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALPHA ));
		WriteNumber();
		gi.MSG_WriteByte( ( uchar )( alpha * 255.0f ) );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_COLOR ));
		WriteNumber();
		gi.MSG_WriteByte( ( uchar )( color.x * 255.0f ) );
		gi.MSG_WriteByte( ( uchar )( color.y * 255.0f ) );
		gi.MSG_WriteByte( ( uchar )( color.z * 255.0f ) );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_FONT ));
		WriteNumber();
		gi.MSG_WriteString( fontName );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	/*if( sv_reborn->integer )
	{
		gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECTX ));
			WriteNumber();
			gi.MSG_WriteShort( ( short )x );
		gi.MSG_EndCGM();

		gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECTY ));
			WriteNumber();
			gi.MSG_WriteShort( ( short )y );
		gi.MSG_EndCGM();

		gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECTWH ));
			WriteNumber();
			gi.MSG_WriteShort( ( short )width );
			gi.MSG_WriteShort( ( short )height );
		gi.MSG_EndCGM();
	}
	else
	{
		gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECT ));
			WriteNumber();
			gi.MSG_WriteShort( ( short )x );
			gi.MSG_WriteShort( ( short )y );
			gi.MSG_WriteShort( ( short )width );
			gi.MSG_WriteShort( ( short )height );
		gi.MSG_EndCGM();
	}*/

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECT ));
		WriteNumber();
		gi.MSG_WriteShort( ( short )x );
		gi.MSG_WriteShort( ( short )y );
		gi.MSG_WriteShort( ( short )width );
		gi.MSG_WriteShort( ( short )height );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_SHADER ));
		WriteNumber();
		gi.MSG_WriteString( shader );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_STRING ));
		WriteNumber();
		gi.MSG_WriteString( text );
	gi.MSG_EndCGM();

	SetBroadcast( clientNumber );

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_VIRTUALSIZE ));
		WriteNumber();
		gi.MSG_WriteBits( virtualSize, 1 );
	gi.MSG_EndCGM();

#ifdef OPM_FEATURES
	if( sv_specialgame->integer )
	{
		if( isDimensional )
		{
			SetBroadcast();

			gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_3D ));
				WriteNumber();
				gi.MSG_WriteCoord( org[ 0 ] );
				gi.MSG_WriteCoord( org[ 1 ] );
				gi.MSG_WriteCoord( org[ 2 ] );

				gi.MSG_WriteShort( enttarget );

				gi.MSG_WriteBits( !!always_show, 1 );
				gi.MSG_WriteBits( !!depth, 1 );
			gi.MSG_EndCGM();
		}

		if( fade_alpha )
		{
			SetBroadcast();

			gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_FADE ));
				WriteNumber();
				gi.MSG_WriteFloat( fade_alpha_current );
			gi.MSG_EndCGM();

			SetBroadcast();

			gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALPHA ));
				WriteNumber();
				gi.MSG_WriteByte( ( uchar )( fade_alpha_target * 255.0f ) );
			gi.MSG_EndCGM();
		}

		if( fade_move )
		{
			SetBroadcast();

			gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_MOVE ));
				WriteNumber();
				gi.MSG_WriteFloat( fade_move_current );
			gi.MSG_EndCGM();

			SetBroadcast();

			gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECT ));
				WriteNumber();
				gi.MSG_WriteShort( ( short )fade_move_x_target );
				gi.MSG_WriteShort( ( short )fade_move_y_target );
			gi.MSG_EndCGM();
		}

		if( fade_timer_flags & TIMER_ACTIVE )
		{
			SetBroadcast();

			gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_TIMER ));
				WriteNumber();
				gi.MSG_WriteFloat( fade_time_current );
				gi.MSG_WriteFloat( fade_out_time );

				if( fade_timer_flags & TIMER_UP ) {
					gi.MSG_WriteBits( 1, 1 );
				} else {
					gi.MSG_WriteBits( 0, 1 );
				}

			gi.MSG_EndCGM();
		}
	}
#endif
}

void Hud::ScaleOverTime( float time, short w, short h )
{
	if( time <= 0.0f )
	{
		width = w;
		height = h;

		return;
	}

	fade_scale = true;

	fade_scale_time = time * 1000.0f;
	fade_scale_current = 0.f;

	fade_scale_w_start = width;
	fade_scale_h_start = height;

	fade_scale_w_target = w;
	fade_scale_h_target = h;

	// FIXME: delete
/*
	width = w;
	height = h;

	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_SCALE ));
		WriteNumber();
		gi.MSG_WriteFloat( time );
		gi.MSG_WriteShort( w );
		gi.MSG_WriteShort( h );
	gi.MSG_EndCGM();
*/
}

#ifdef GAME_DLL
void Hud::SetBroadcast( int clientNumber )
{
	if( clientNumber == -1 ) {
		clientNumber = clientnum;
	}

	if(clientnum == char(-1)) {
		gi.SetBroadcastAll();
	} else {
		gi.MSG_SetClient( clientnum );
	}
}
#endif

#ifdef GAME_DLL

void Hud::WriteNumber()
{
#ifdef OPM_FEATURES
	if(sv_specialgame->integer ) {
		gi.MSG_WriteShort( number );
	} else {
		gi.MSG_WriteByte( number );
	}
#else
	gi.MSG_WriteByte(number);
#endif
}

#endif

void Hud::Set3D( Vector vector_or_offset, qboolean alwaysOnScreen, qboolean hasDepth, int entnum )
{
	org = vector_or_offset;
	lastOrg = vector_or_offset;
	always_show = alwaysOnScreen;
	depth = hasDepth;
	enttarget = entnum;

	isDimensional = true;

	/*
#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_3D ));
		WriteNumber();
		gi.MSG_WriteCoord( vector_or_offset[ 0 ] );
		gi.MSG_WriteCoord( vector_or_offset[ 1 ] );
		gi.MSG_WriteCoord( vector_or_offset[ 2 ] );

		gi.MSG_WriteShort( entnum );

		gi.MSG_WriteBits( !!alwaysOnScreen, 1 );
		gi.MSG_WriteBits( !!hasDepth, 1 );
	gi.MSG_EndCGM();
#endif
*/
}
void Hud::SetNon3D()
{
	isDimensional = false;

	// FIXME?
/*
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_BREAK3D ));
		WriteNumber();
	gi.MSG_EndCGM();
*/
}

void Hud::SetAlignX( hudAlign_t align )
{
	if( align >= HUD_INVALID ) {
		ScriptError( "Wrong %d X align!", align );
	}

	alignX = align;

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALIGN ));
		WriteNumber();
		gi.MSG_WriteBits( alignX, 2 );
		gi.MSG_WriteBits( alignY, 2 );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetAlignY( hudAlign_t align )
{
	if( align >= HUD_INVALID ) {
		ScriptError( "Wrong %d Y align!", align );
	}

	alignY = align;

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALIGN ));
		WriteNumber();
		gi.MSG_WriteBits( alignX, 2 );
		gi.MSG_WriteBits( alignY, 2 );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetAlpha( float value )
{
//#ifdef CGAME_DLL
	if( fade_alpha_first )
	{
		fade_alpha_target = value;
		fade_alpha_first = false;
	}
	else
	{
		alpha = value;
		fade_alpha = false;
	}
//#else
//	alpha = value;
//#endif

#ifdef GAME_DLL
	long int ucharvalue;
	ucharvalue = ( long int )( value * 255.0f );

	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALPHA ));
		WriteNumber();
		gi.MSG_WriteByte( ucharvalue );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetColor( Vector c )
{
	color = c;

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_COLOR ));
		WriteNumber();
		gi.MSG_WriteByte( ( uchar )( c.x * 255.0f ) );
		gi.MSG_WriteByte( ( uchar )( c.y * 255.0f ) );
		gi.MSG_WriteByte( ( uchar )( c.z * 255.0f ) );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetClient( int c, qboolean clears )
{
#ifdef GAME_DLL
	if( c == -1 ) {
		clears = false;
	}

	if( clears ) {
#ifdef GAME_DLL
		SetBroadcast();

		gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_ALPHA ));
			WriteNumber();
			gi.MSG_WriteByte( 0 );
		gi.MSG_EndCGM();
#endif
	}

	clientnum = c;
#endif
}

void Hud::SetFont( const char * f )
{
	if( f != NULL && strlen( f ) > 0 ) {
		fontName = f;
	} else {
		fontName = "";
	}

#ifdef CGAME_DLL
	if( fontName[ 0 ] != '\0' ) {
		font = cgi.R_LoadFont( fontName );
	} else {
		font = NULL;
	}

#endif

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_FONT ));
		WriteNumber();
		gi.MSG_WriteString( f );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetRectX( short value )
{
//#ifdef CGAME_DLL
	if( fade_move_x_first )
	{
		fade_move_x_target = value;
		fade_move_x_first = false;
	}
	else
	{
		x = value;
		fade_move = false;
	}
//#else
//	x = value;
//#endif

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECT));
		WriteNumber();
		gi.MSG_WriteShort((short)fade_move_x_target);
		gi.MSG_WriteShort((short)fade_move_y_target);
		gi.MSG_WriteShort((short)width);
		gi.MSG_WriteShort((short)height);
	gi.MSG_EndCGM();
#endif
}

void Hud::SetRectY( short value )
{
	if( fade_move_y_first )
	{
		fade_move_y_target = value;
		fade_move_y_first = false;
	}
	else
	{
		y = value;
		fade_move = false;
	}

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECT));
		WriteNumber();
		gi.MSG_WriteShort((short)fade_move_x_target);
		gi.MSG_WriteShort((short)fade_move_y_target);
		gi.MSG_WriteShort((short)width);
		gi.MSG_WriteShort((short)height);
	gi.MSG_EndCGM();
#endif
}

void Hud::SetRectHeight( short h )
{
	height = h;
}

void Hud::SetRectWidth( short w )
{
	width = w;
}

void Hud::SetShader( const char * s, float w, float h )
{
	if( s != NULL && strlen( s ) > 0 )
	{
		shader = s;
		text = "";
	} else {
		shader = "";
	}

	fade_scale = false;

	if( w != -1 ) {
		width = w;
	}

	if( h != -1 ) {
		height = h;
	}

#ifdef CGAME_DLL
	if( shader[ 0 ] != '\0' ) {
		shaderHandle = cgi.R_RegisterShaderNoMip( shader );
	} else {
		shaderHandle = NULL;
	}
#endif

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM(BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_RECT));
	WriteNumber();
	gi.MSG_WriteShort((short)x);
	gi.MSG_WriteShort((short)y);
	gi.MSG_WriteShort((short)width);
	gi.MSG_WriteShort((short)height);
	gi.MSG_EndCGM();

	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_SHADER ));
		WriteNumber();
		gi.MSG_WriteString( s );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetText( const char * t )
{
	if( t != NULL && strlen( t ) > 0 )
	{
		//width = strlen( t );
		//height = 32;
		text = t;
		shader = "";
#ifdef CGAME_DLL
		shaderHandle = NULL;
#endif
	} else {
		text = "";
	}

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_STRING ));
		WriteNumber();
		gi.MSG_WriteString( t );
	gi.MSG_EndCGM();
#endif
}

void Hud::SetTimer( float time, float fade_at_time )
{
	fade_time_current = time;

	if( fade_at_time >= 0.0f ) {
		fade_out_time = fade_at_time * 1000.0f;
	} else {
		fade_out_time = -1.0f;
	}

	fade_time_target = 0.0f;

	fade_timer_flags = TIMER_ACTIVE;

	// FIXME...
/*
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_TIMER ));
		WriteNumber();
		gi.MSG_WriteFloat( time );
		gi.MSG_WriteFloat( fade_at_time );
		gi.MSG_WriteBits( 0, 1 );
	gi.MSG_EndCGM();
*/
}

void Hud::SetTimerUp( float time, float fade_at_time )
{
	fade_time_current = 0.0f;

	if( fade_at_time >= 0.0f ) {
		fade_out_time = fade_at_time * 1000.0f;
	} else {
		fade_out_time = -1.0f;
	}

	fade_time_target = time;

	fade_timer_flags = TIMER_ACTIVE | TIMER_UP;

	/*
#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_TIMER ));
		WriteNumber();
		gi.MSG_WriteFloat( time );
		gi.MSG_WriteFloat( fade_at_time );
		gi.MSG_WriteBits( 1, 1 );
	gi.MSG_EndCGM();
#endif
*/
}

void Hud::SetVirtualSize( qboolean v )
{
	virtualSize = !!v;

#ifdef GAME_DLL
	SetBroadcast();

	gi.MSG_StartCGM( BG_MapCGMToProtocol(g_protocol, CGM_HUDDRAW_VIRTUALSIZE ));
		WriteNumber();
		gi.MSG_WriteBits( virtualSize, 1 );
	gi.MSG_EndCGM();
#endif
}

// Events

void Hud::EventGetAlignX( Event * ev )
{
	switch (alignX)
	{
	case HUD_LEFT:
		return ev->AddString("left");

	case HUD_CENTER:
		return ev->AddString("center");

	case HUD_RIGHT:
		return ev->AddString("right");

	default:
		return ev->AddString("none");
	}
}

void Hud::EventGetAlignY( Event * ev )
{
	switch (alignY)
	{
	case HUD_TOP:
		return ev->AddString("top");

	case HUD_CENTER:
		return ev->AddString("center");

	case HUD_BOTTOM:
		return ev->AddString("bottom");

	default:
		return ev->AddString("none");
	}
}

void Hud::EventGetAlpha( Event * ev )
{
	ev->AddFloat( alpha );
}

void Hud::EventGetColor( Event * ev )
{
	ev->AddVector( color );
}

void Hud::EventGetFont( Event * ev )
{
	ev->AddString( fontName );
}

void Hud::EventGetHeight( Event * ev )
{
	ev->AddFloat( height );
}

void Hud::EventGetRectX( Event * ev )
{
	ev->AddFloat( x );
}

void Hud::EventGetRectY( Event * ev )
{
	ev->AddFloat( y );
}

void Hud::EventGetTime( Event *ev )
{
	ev->AddFloat( fade_time_current );
}

void Hud::EventGetWidth( Event * ev )
{
	ev->AddFloat( width );
}

void Hud::EventFadeOverTime( Event * ev )
{
	FadeOverTime( ev->GetFloat( 1 ) );
}

void Hud::EventMoveOverTime( Event * ev )
{
	MoveOverTime( ev->GetFloat( 1 ) );
}

void Hud::EventRefresh( Event * ev )
{
	Refresh();
}

void Hud::EventScaleOverTime( Event * ev )
{
	ScaleOverTime( ev->GetFloat( 1 ), ev->GetInteger( 2 ), ev->GetInteger( 3 ) );
}

void Hud::EventSet3D( Event *ev )
{
	Vector vector;
	int ent_num = -1;
	qboolean bAlwaysShow, bDepth;

	vector = ev->GetVector( 1 );

	bAlwaysShow = ev->GetInteger( 2 );
	bDepth = ev->GetInteger( 3 );

	if( ev->NumArgs() > 3 )
	{
		Entity *entity = ev->GetEntity( 4 );
		ent_num = entity->entnum;
	}

	Set3D( vector, bAlwaysShow, bDepth, ent_num );
}

void Hud::EventSetNon3D( Event *ev )
{
	SetNon3D();
}

void Hud::EventSetAlignX( Event * ev )
{
	str name = ev->GetString( 1 );
	hudAlign_t alignment = HUD_INVALID;

	if( name == "left" ) {
		alignment = HUD_LEFT;
	}
	else if( name == "center" ) {
		alignment = HUD_CENTER;
	}
	else if( name == "right" ) {
		alignment = HUD_RIGHT;
	} else {
		ScriptError( "Invalid alignment %s!\n", name.c_str() );
	}

	SetAlignX( alignment );
}

void Hud::EventSetAlignY( Event * ev )
{
	str name = ev->GetString( 1 );
	hudAlign_t alignment = HUD_INVALID;

	if( name == "top" ) {
		alignment = HUD_TOP;
	}
	else if( name == "center" ) {
		alignment = HUD_CENTER;
	}
	else if( name == "bottom" ) {
		alignment = HUD_BOTTOM;
	} else {
		ScriptError( "Invalid alignment %s!\n", name.c_str() );
	}

	SetAlignY( alignment );
}

void Hud::EventSetAlpha( Event * ev )
{
	float a = ev->GetFloat( 1 );

	SetAlpha( a );
}

void Hud::EventSetColor( Event * ev )
{
	Vector c = ev->GetVector( 1 );

	SetColor( c );
}

void Hud::EventSetFont( Event * ev )
{
	str font = ev->GetString( 1 );

	SetFont( font );
}

void Hud::EventSetPlayer( Event * ev )
{
#ifdef GAME_DLL
	int clientNum = -1;
	qboolean clears = false;

	if( !ev->IsNilAt( 1 ) )
	{
		Player *player = ( Player * )ev->GetEntity( 1 );

		clientNum = player->edict - g_entities;

		if( ev->NumArgs() > 1 ) {
			clears = ev->GetBoolean( 2 );
		} else {
			clears = false;
		}
	}

	SetClient( clientNum, clears );
#endif
}

void Hud::EventSetRectX( Event * ev )
{
	int val = ev->GetInteger( 1 );

	SetRectX( val );
}

void Hud::EventSetRectY( Event * ev )
{
	int val = ev->GetInteger( 1 );

	SetRectY( val );
}

void Hud::EventSetShader( Event * ev )
{
	str shader = ev->GetString( 1 );
	float w = ev->GetFloat( 2 );
	float h = ev->GetFloat( 3 );

	SetShader( shader, w, h );
}

void Hud::EventSetText( Event * ev )
{
	str text = ev->GetString( 1 );

	SetText( text );
}

void Hud::EventSetTimer( Event *ev )
{
	float time = ev->GetFloat( 1 );
	float fade_at_time = -1.0f;

	if( ev->NumArgs() > 1 ) {
		fade_at_time = ev->GetFloat( 2 );
	}

	SetTimer( time, fade_at_time );
}

void Hud::EventSetTimerUp( Event *ev )
{
	float time = ev->GetFloat( 1 );
	float fade_at_time = -1.0f;

	if( ev->NumArgs() > 1 ) {
		fade_at_time = ev->GetFloat( 2 );
	}

	SetTimerUp( time, fade_at_time );
}

void Hud::EventSetVirtualSize( Event * ev )
{
	qboolean bVirtual = ev->GetBoolean( 1 );

	SetVirtualSize( bVirtual );
}

CLASS_DECLARATION( Listener, Hud, NULL )
{
	{ &EV_HUD_GetAlignX,				&Hud::EventGetAlignX },
	{ &EV_HUD_GetAlignY,				&Hud::EventGetAlignY },
	{ &EV_HUD_GetAlpha,					&Hud::EventGetAlpha },
	{ &EV_HUD_GetColor,					&Hud::EventGetColor },
	{ &EV_HUD_GetFont,					&Hud::EventGetFont },
	{ &EV_HUD_GetHeight,				&Hud::EventGetHeight },
	{ &EV_HUD_GetRectX,					&Hud::EventGetRectX },
	{ &EV_HUD_GetRectY,					&Hud::EventGetRectY },
	{ &EV_HUD_GetTime,					&Hud::EventGetTime },
	{ &EV_HUD_GetWidth,					&Hud::EventGetWidth },
	{ &EV_HUD_FadeOverTime,				&Hud::EventFadeOverTime },
	{ &EV_HUD_MoveOverTime,				&Hud::EventMoveOverTime },
	{ &EV_HUD_Refresh,					&Hud::EventRefresh },
	{ &EV_HUD_ScaleOverTime,			&Hud::EventScaleOverTime },
	{ &EV_HUD_Set3D,					&Hud::EventSet3D },
	{ &EV_HUD_SetNon3D,					&Hud::EventSetNon3D },
	{ &EV_HUD_SetAlignX,				&Hud::EventSetAlignX },
	{ &EV_HUD_SetAlignY,				&Hud::EventSetAlignY },
	{ &EV_HUD_SetAlpha,					&Hud::EventSetAlpha },
	{ &EV_HUD_SetColor,					&Hud::EventSetColor },
	{ &EV_HUD_SetFont,					&Hud::EventSetFont },
	{ &EV_HUD_SetPlayer,				&Hud::EventSetPlayer },
	{ &EV_HUD_SetRectX,					&Hud::EventSetRectX },
	{ &EV_HUD_SetRectY,					&Hud::EventSetRectY },
	{ &EV_HUD_SetShader,				&Hud::EventSetShader },
	{ &EV_HUD_SetText,					&Hud::EventSetText },
	{ &EV_HUD_SetTimer,					&Hud::EventSetTimer },
	{ &EV_HUD_SetTimerUp,				&Hud::EventSetTimerUp },
	{ &EV_HUD_SetVirtualSize,			&Hud::EventSetVirtualSize },
	{ NULL, NULL }
};
