/*
===========================================================================
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"
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
);

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

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                     }
};
