/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2009 Darklegion Development

This file is part of Tremulous.

Tremulous 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.

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

// cg_draw.c -- draw all of the graphical elements during
// active (after loading) gameplay


#include "cg_local.h"
#include "../ui/ui_shared.h"

menuDef_t *menuScoreboard = NULL;

static void CG_AlignText( rectDef_t *rect, const char *text, float scale,
                          float w, float h,
                          int align, int valign,
                          float *x, float *y )
{
  float tx, ty;

  if( scale > 0.0f )
  {
    w = UI_Text_Width( text, scale );
    h = UI_Text_Height( text, scale );
  }

  switch( align )
  {
    default:
    case ALIGN_LEFT:
      tx = 0.0f;
      break;

    case ALIGN_RIGHT:
      tx = rect->w - w;
      break;

    case ALIGN_CENTER:
      tx = ( rect->w - w ) / 2.0f;
      break;

    case ALIGN_NONE:
      tx = 0;
      break;
  }

  switch( valign )
  {
    default:
    case VALIGN_BOTTOM:
      ty = rect->h;
      break;

    case VALIGN_TOP:
      ty = h;
      break;

    case VALIGN_CENTER:
      ty = h + ( ( rect->h - h ) / 2.0f );
      break;

    case VALIGN_NONE:
      ty = 0;
      break;
  }

  if( x )
    *x = rect->x + tx;

  if( y )
    *y = rect->y + ty;
}

/*
==============
CG_DrawFieldPadded

Draws large numbers for status bar
==============
*/
static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value )
{
  char  num[ 16 ], *ptr;
  int   l, orgL;
  int   frame;
  int   charWidth, charHeight;

  if( !( charWidth = cw ) )
    charWidth = CHAR_WIDTH;

  if( !( charHeight = ch ) )
    charHeight = CHAR_HEIGHT;

  if( width < 1 )
    return;

  // draw number string
  if( width > 4 )
    width = 4;

  switch( width )
  {
    case 1:
      value = value > 9 ? 9 : value;
      value = value < 0 ? 0 : value;
      break;
    case 2:
      value = value > 99 ? 99 : value;
      value = value < -9 ? -9 : value;
      break;
    case 3:
      value = value > 999 ? 999 : value;
      value = value < -99 ? -99 : value;
      break;
    case 4:
      value = value > 9999 ? 9999 : value;
      value = value < -999 ? -999 : value;
      break;
  }

  Com_sprintf( num, sizeof( num ), "%d", value );
  l = strlen( num );

  if( l > width )
    l = width;

  orgL = l;

  x += ( 2.0f * cgDC.aspectScale );

  ptr = num;
  while( *ptr && l )
  {
    if( width > orgL )
    {
      CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ 0 ] );
      width--;
      x += charWidth;
      continue;
    }

    if( *ptr == '-' )
      frame = STAT_MINUS;
    else
      frame = *ptr - '0';

    CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] );
    x += charWidth;
    ptr++;
    l--;
  }
}

/*
==============
CG_DrawField

Draws large numbers for status bar
==============
*/
void CG_DrawField( float x, float y, int width, float cw, float ch, int value )
{
  char  num[ 16 ], *ptr;
  int   l;
  int   frame;
  float charWidth, charHeight;

  if( !( charWidth = cw ) )
    charWidth = CHAR_WIDTH;

  if( !( charHeight = ch ) )
    charHeight = CHAR_HEIGHT;

  if( width < 1 )
    return;

  // draw number string
  if( width > 4 )
    width = 4;

  switch( width )
  {
    case 1:
      value = value > 9 ? 9 : value;
      value = value < 0 ? 0 : value;
      break;
    case 2:
      value = value > 99 ? 99 : value;
      value = value < -9 ? -9 : value;
      break;
    case 3:
      value = value > 999 ? 999 : value;
      value = value < -99 ? -99 : value;
      break;
    case 4:
      value = value > 9999 ? 9999 : value;
      value = value < -999 ? -999 : value;
      break;
  }

  Com_sprintf( num, sizeof( num ), "%d", value );
  l = strlen( num );

  if( l > width )
    l = width;

  x += ( 2.0f * cgDC.aspectScale ) + charWidth * ( width - l );

  ptr = num;
  while( *ptr && l )
  {
    if( *ptr == '-' )
      frame = STAT_MINUS;
    else
      frame = *ptr -'0';

    CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] );
    x += charWidth;
    ptr++;
    l--;
  }
}

static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale,
                                int align, int textalign, int textStyle,
                                float borderSize, float progress )
{
  float   rimWidth;
  float   doneWidth, leftWidth;
  float   tx, ty;
  char    textBuffer[ 8 ];

  if( borderSize >= 0.0f )
    rimWidth = borderSize;
  else
  {
    rimWidth = rect->h / 20.0f;
    if( rimWidth < 0.6f )
      rimWidth = 0.6f;
  }

  if( progress < 0.0f )
    progress = 0.0f;
  else if( progress > 1.0f )
    progress = 1.0f;

  doneWidth = ( rect->w - 2 * rimWidth ) * progress;
  leftWidth = ( rect->w - 2 * rimWidth ) - doneWidth;

  trap_R_SetColor( color );

  //draw rim and bar
  if( align == ALIGN_RIGHT )
  {
    CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader );
    CG_DrawPic( rect->x + rimWidth, rect->y,
      leftWidth, rimWidth, cgs.media.whiteShader );
    CG_DrawPic( rect->x + rimWidth, rect->y + rect->h - rimWidth,
      leftWidth, rimWidth, cgs.media.whiteShader );
    CG_DrawPic( rect->x + rimWidth + leftWidth, rect->y,
      rimWidth + doneWidth, rect->h, cgs.media.whiteShader );
  }
  else
  {
    CG_DrawPic( rect->x, rect->y, rimWidth + doneWidth, rect->h, cgs.media.whiteShader );
    CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y,
      leftWidth, rimWidth, cgs.media.whiteShader );
    CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y + rect->h - rimWidth,
      leftWidth, rimWidth, cgs.media.whiteShader );
    CG_DrawPic( rect->x + rect->w - rimWidth, rect->y, rimWidth, rect->h, cgs.media.whiteShader );
  }

  trap_R_SetColor( NULL );

  //draw text
  if( scale > 0.0 )
  {
    Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) );
    CG_AlignText( rect, textBuffer, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty );

    UI_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle );
  }
}

//=============== TA: was cg_newdraw.c

#define NO_CREDITS_TIME 2000

static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean padding )
{
  int           value;
  playerState_t *ps;
  centity_t     *cent;

  cent = &cg_entities[ cg.snap->ps.clientNum ];
  ps = &cg.snap->ps;

  //if the build timer pie is showing don't show this
  if( ( cent->currentState.weapon == WP_ABUILD ||
      cent->currentState.weapon == WP_ABUILD2 ) && ps->stats[ STAT_MISC ] )
    return;

  value = ps->persistant[ PERS_CREDIT ];
  if( value > -1 )
  {
    if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS )
    {
      if( !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ],
                              value, cgs.alienStage ) &&
          cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME &&
          ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) & 1 )
      {
        color[ 3 ] = 0.0f;
      }

      value /= ALIEN_CREDITS_PER_KILL;
    }

    trap_R_SetColor( color );

    if( padding )
      CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
    else
      CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value );

    trap_R_SetColor( NULL );
  }
}

static void CG_DrawPlayerCreditsFraction( rectDef_t *rect, vec4_t color, qhandle_t shader )
{
  float fraction;
  float height;

  if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS )
    return;

  fraction = ((float)(cg.predictedPlayerState.persistant[ PERS_CREDIT ] %
    ALIEN_CREDITS_PER_KILL)) / ALIEN_CREDITS_PER_KILL;

  CG_AdjustFrom640( &rect->x, &rect->y, &rect->w, &rect->h );
  height = rect->h * fraction;

  trap_R_SetColor( color );
  trap_R_DrawStretchPic( rect->x, rect->y - height + rect->h, rect->w,
    height, 0.0f, 1.0f - fraction, 1.0f, 1.0f, shader );
  trap_R_SetColor( NULL );
}


/*
==============
CG_DrawPlayerStamina
==============
*/
static void CG_DrawPlayerStamina( int ownerDraw, rectDef_t *rect,
                                  vec4_t backColor, vec4_t foreColor,
                                  qhandle_t shader )
{
  playerState_t *ps = &cg.snap->ps;
  float         stamina = ps->stats[ STAT_STAMINA ];
  float         maxStaminaBy3 = (float)STAMINA_MAX / 3.0f;
  float         progress;
  vec4_t        color;

  switch( ownerDraw )
  {
    case CG_PLAYER_STAMINA_1:
      progress = ( stamina - 2 * (int)maxStaminaBy3 ) / maxStaminaBy3;
      break;
    case CG_PLAYER_STAMINA_2:
      progress = ( stamina - (int)maxStaminaBy3 ) / maxStaminaBy3;
      break;
    case CG_PLAYER_STAMINA_3:
  progress = stamina / maxStaminaBy3;
      break;
    case CG_PLAYER_STAMINA_4:
      progress = ( stamina + STAMINA_MAX ) / STAMINA_MAX;
      break;
    default:
      return;
  }

  if( progress > 1.0f )
    progress  = 1.0f;
  else if( progress < 0.0f )
    progress = 0.0f;

  Vector4Lerp( progress, backColor, foreColor, color );

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerStaminaBolt
==============
*/
static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t backColor,
                                      vec4_t foreColor, qhandle_t shader )
{
  float         stamina = cg.snap->ps.stats[ STAT_STAMINA ];
  vec4_t        color;

  if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_SPEEDBOOST )
  {
    if( stamina >= 0 )
      Vector4Lerp( ( sin( cg.time / 150.0f ) + 1 ) / 2,
                   backColor, foreColor, color );
    else
      Vector4Lerp( ( sin( cg.time / 2000.0f ) + 1 ) / 2,
                   backColor, foreColor, color );
  }
  else
  {
    if( stamina < 0 )
      Vector4Copy( backColor, color );
    else
      Vector4Copy( foreColor, color );
  }

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerClipsRing
==============
*/
static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t backColor,
                                    vec4_t foreColor, qhandle_t shader )
{
  playerState_t *ps = &cg.snap->ps;
  centity_t     *cent;
  float         buildTime = ps->stats[ STAT_MISC ];
  float         progress;
  float         maxDelay;
  weapon_t      weapon;
  vec4_t        color;

  cent = &cg_entities[ cg.snap->ps.clientNum ];
  weapon = BG_GetPlayerWeapon( ps );

  switch( weapon )
  {
    case WP_ABUILD:
    case WP_ABUILD2:
    case WP_HBUILD:
      if( buildTime > MAXIMUM_BUILD_TIME )
        buildTime = MAXIMUM_BUILD_TIME;
      progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME;

      Vector4Lerp( progress, backColor, foreColor, color );
      break;

    default:
      if( ps->weaponstate == WEAPON_RELOADING )
      {
        maxDelay = (float)BG_Weapon( cent->currentState.weapon )->reloadTime;
        progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay;

        Vector4Lerp( progress, backColor, foreColor, color );
      }
      else
        Com_Memcpy( color, foreColor, sizeof( color ) );
      break;
  }

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerBuildTimerRing
==============
*/
static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t backColor,
                                         vec4_t foreColor, qhandle_t shader )
{
  playerState_t *ps = &cg.snap->ps;
  centity_t     *cent;
  float         buildTime = ps->stats[ STAT_MISC ];
  float         progress;
  vec4_t        color;

  cent = &cg_entities[ cg.snap->ps.clientNum ];

  if( buildTime > MAXIMUM_BUILD_TIME )
    buildTime = MAXIMUM_BUILD_TIME;

  progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME;

  Vector4Lerp( progress, backColor, foreColor, color );

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerBoosted
==============
*/
static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t backColor,
                                  vec4_t foreColor, qhandle_t shader )
{
  if( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED )
    trap_R_SetColor( foreColor );
  else
    trap_R_SetColor( backColor );

  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerBoosterBolt
==============
*/
static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t backColor,
                                      vec4_t foreColor, qhandle_t shader )
{
  vec4_t color;

  // Flash bolts when the boost is almost out
  if( ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) &&
      ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTEDWARNING ) )
    Vector4Lerp( ( sin( cg.time / 100.0f ) + 1 ) / 2,
                 backColor, foreColor, color );
  else
    Vector4Copy( foreColor, color );

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerPoisonBarbs
==============
*/
static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t shader )
{
  qboolean vertical;
  float    x = rect->x, y = rect->y;
  float    width = rect->w, height = rect->h;
  float    diff;
  int      iconsize, numBarbs, maxBarbs;

  maxBarbs = BG_Weapon( cg.snap->ps.weapon )->maxAmmo;
  numBarbs = cg.snap->ps.ammo;
  if( maxBarbs <= 0 || numBarbs <= 0 )
    return;

  // adjust these first to ensure the aspect ratio of the barb image is
  // preserved
  CG_AdjustFrom640( &x, &y, &width, &height );

  if( height > width )
  {
    vertical = qtrue;
    iconsize = width;
    if( maxBarbs != 1 ) // avoid division by zero
      diff = ( height - iconsize ) / (float)( maxBarbs - 1 );
    else
      diff = 0; // doesn't matter, won't be used
  }
  else
  {
    vertical = qfalse;
    iconsize = height;
    if( maxBarbs != 1 )
      diff = ( width - iconsize ) / (float)( maxBarbs - 1 );
    else
      diff = 0;
  }

  trap_R_SetColor( color );

  for( ; numBarbs > 0; numBarbs-- )
  {
    trap_R_DrawStretchPic( x, y, iconsize, iconsize, 0, 0, 1, 1, shader );
    if( vertical )
      y += diff;
    else
      x += diff;
  }

  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerWallclimbing
==============
*/
static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t backColor, vec4_t foreColor, qhandle_t shader )
{
  if( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
    trap_R_SetColor( foreColor );
  else
    trap_R_SetColor( backColor );

  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

static void CG_DrawPlayerAmmoValue( rectDef_t *rect, vec4_t color )
{
  int value;
  qboolean bp = qfalse;

  switch( BG_PrimaryWeapon( cg.snap->ps.stats ) )
  {
    case WP_NONE:
    case WP_BLASTER:
      return;

    case WP_ABUILD:
    case WP_ABUILD2:
    case WP_HBUILD:
      value = cg.snap->ps.persistant[ PERS_BUILDPOINTS ];
      bp = qtrue;
      break;

    default:
      value = cg.snap->ps.ammo;
      break;
  }

  if( value > 999 )
    value = 999;

  if( value > -1 )
  {
    float tx, ty;
    char *text;
    float scale;
    int len;

    trap_R_SetColor( color );
    if( !bp )
    {
      CG_DrawField( rect->x - 5, rect->y, 4, rect->w / 4, rect->h, value );
      trap_R_SetColor( NULL );
      return;
    }

    text = va( "%d", value );

    len = strlen( text );

    if( len <= 4 )
      scale = 0.50;
    else if( len <= 6 )
      scale = 0.43;
    else if( len == 7 ) 
      scale = 0.36; 
    else if( len == 8 )
      scale = 0.33;
    else
      scale = 0.31;

    CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_RIGHT, VALIGN_CENTER, &tx, &ty );
    UI_Text_Paint( tx + 1, ty, scale, color, text, 0, 0, ITEM_TEXTSTYLE_NORMAL );
    trap_R_SetColor( NULL );
  }
}


/*
==============
CG_DrawAlienSense
==============
*/
static void CG_DrawAlienSense( rectDef_t *rect )
{
  if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_CLASS ], SCA_ALIENSENSE ) )
    CG_AlienSense( rect );
}


/*
==============
CG_DrawHumanScanner
==============
*/
static void CG_DrawHumanScanner( rectDef_t *rect, qhandle_t shader, vec4_t color )
{
  if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, cg.snap->ps.stats ) )
    CG_Scanner( rect, shader, color );
}


/*
==============
CG_DrawUsableBuildable
==============
*/
static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t color )
{
  vec3_t        view, point;
  trace_t       trace;
  entityState_t *es;

  AngleVectors( cg.refdefViewAngles, view, NULL, NULL );
  VectorMA( cg.refdef.vieworg, 64, view, point );
  CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL,
            point, cg.predictedPlayerState.clientNum, MASK_SHOT );

  es = &cg_entities[ trace.entityNum ].currentState;

  if( es->eType == ET_BUILDABLE && BG_Buildable( es->modelindex, NULL )->usable &&
      cg.predictedPlayerState.stats[ STAT_TEAM ] == BG_Buildable( es->modelindex, NULL )->team )
  {
    //hack to prevent showing the usable buildable when you aren't carrying an energy weapon
    if( ( es->modelindex == BA_H_REACTOR || es->modelindex == BA_H_REPEATER ) &&
        ( !BG_Weapon( cg.snap->ps.weapon )->usesEnergy ||
          BG_Weapon( cg.snap->ps.weapon )->infiniteAmmo ) )
    {
      cg.nearUsableBuildable = BA_NONE;
      return;
    }

    trap_R_SetColor( color );
    CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
    trap_R_SetColor( NULL );
    cg.nearUsableBuildable = es->modelindex;
  }
  else
    cg.nearUsableBuildable = BA_NONE;
}


#define BUILD_DELAY_TIME  2000

static void CG_DrawPlayerBuildTimer( rectDef_t *rect, vec4_t color )
{
  int           index;
  playerState_t *ps;

  ps = &cg.snap->ps;

  if( ps->stats[ STAT_MISC ] <= 0 )
    return;

  switch( BG_PrimaryWeapon( ps->stats ) )
  {
    case WP_ABUILD:
    case WP_ABUILD2:
    case WP_HBUILD:
      break;

    default:
      return;
  }

  index = 8 * ( ps->stats[ STAT_MISC ] - 1 ) / MAXIMUM_BUILD_TIME;
  if( index > 7 )
    index = 7;
  else if( index < 0 )
    index = 0;

  if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME &&
      ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 )
  {
    color[ 0 ] = 1.0f;
    color[ 1 ] = color[ 2 ] = 0.0f;
    color[ 3 ] = 1.0f;
  }

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h,
              cgs.media.buildWeaponTimerPie[ index ] );
  trap_R_SetColor( NULL );
}

static void CG_DrawPlayerClipsValue( rectDef_t *rect, vec4_t color )
{
  int           value;
  playerState_t *ps = &cg.snap->ps;

  switch( BG_PrimaryWeapon( ps->stats ) )
  {
    case WP_NONE:
    case WP_BLASTER:
    case WP_ABUILD:
    case WP_ABUILD2:
    case WP_HBUILD:
      return;

    default:
      value = ps->clips;

      if( value > -1 )
      {
        trap_R_SetColor( color );
        CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
        trap_R_SetColor( NULL );
      }
      break;
  }
}

static void CG_DrawPlayerHealthValue( rectDef_t *rect, vec4_t color )
{
  trap_R_SetColor( color );
  CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h,
                cg.snap->ps.stats[ STAT_HEALTH ] );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawPlayerHealthCross
==============
*/
static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t ref_color )
{
  qhandle_t shader;
  vec4_t color;
  float ref_alpha;

  // Pick the current icon
  shader = cgs.media.healthCross;
  if( cg.snap->ps.stats[ STAT_STATE ] & SS_IMPLANTED )
    shader = cgs.media.healthCrossImplanted;
  if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_3X )
    shader = cgs.media.healthCross3X;
  else if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_2X )
  {
    if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )
      shader = cgs.media.healthCross2X;
    else
      shader = cgs.media.healthCrossMedkit;
  }
  else if( cg.snap->ps.stats[ STAT_STATE ] & SS_POISONED )
    shader = cgs.media.healthCrossPoisoned;

  // Pick the alpha value
  Vector4Copy( ref_color, color );
  if( cg.snap->ps.stats[ STAT_STATE ] & SS_IMPLANTED )
  {
    color[ 0 ] = 0.0f;
    color[ 1 ] = 0.8f;
    color[ 2 ] = 0.0f;
  }
  else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS &&
      cg.snap->ps.stats[ STAT_HEALTH ] < 10 )
  {
    color[ 0 ] = 1.0f;
    color[ 1 ] = color[ 2 ] = 0.0f;
  }
  ref_alpha = ref_color[ 3 ];
  if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_ACTIVE )
    ref_alpha = 1.0f;

  // Don't fade from nothing
  if( !cg.lastHealthCross )
    cg.lastHealthCross = shader;

  // Fade the icon during transition
  if( cg.lastHealthCross != shader )
  {
    cg.healthCrossFade += cg.frametime / 500.0f;
    if( cg.healthCrossFade > 1.0f )
    {
      cg.healthCrossFade = 0.0f;
      cg.lastHealthCross = shader;
    }
    else
    {
      // Fading between two icons
      color[ 3 ] = ref_alpha * cg.healthCrossFade;
      trap_R_SetColor( color );
      CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
      color[ 3 ] = ref_alpha * ( 1.0f - cg.healthCrossFade );
      trap_R_SetColor( color );
      CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg.lastHealthCross );
      trap_R_SetColor( NULL );
      return;
    }
  }

  // Not fading, draw a single icon
  color[ 3 ] = ref_alpha;
  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

static float CG_ChargeProgress( void )
{
  float progress;
  int min = 0, max = 0;

  if( cg.snap->ps.weapon == WP_ALEVEL3 )
  {
    min = LEVEL3_POUNCE_TIME_MIN;
    max = LEVEL3_POUNCE_TIME;
  }
  else if( cg.snap->ps.weapon == WP_ALEVEL3_UPG )
  {
    min = LEVEL3_POUNCE_TIME_MIN;
    max = LEVEL3_POUNCE_TIME_UPG;
  }
  else if( cg.snap->ps.weapon == WP_ALEVEL4 )
  {
    if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_CHARGING )
    {
      min = 0;
      max = LEVEL4_TRAMPLE_DURATION;
    }
    else
    {
      min = LEVEL4_TRAMPLE_CHARGE_MIN;
      max = LEVEL4_TRAMPLE_CHARGE_MAX;
    }
  }
  else if( cg.snap->ps.weapon == WP_LUCIFER_CANNON )
  {
    min = LCANNON_CHARGE_TIME_MIN;
    max = LCANNON_CHARGE_TIME_MAX;
  }

  if( max - min <= 0.0f )
    return 0.0f;

  progress = ( (float)cg.predictedPlayerState.stats[ STAT_MISC ] - min ) /
             ( max - min );

  if( progress > 1.0f )
    return 1.0f;

  if( progress < 0.0f )
    return 0.0f;

  return progress;
}

#define CHARGE_BAR_FADE_RATE 0.002f

static void CG_DrawPlayerChargeBarBG( rectDef_t *rect, vec4_t ref_color,
                                      qhandle_t shader )
{
  vec4_t color;

  if( !cg_drawChargeBar.integer || cg.chargeMeterAlpha <= 0.0f )
    return;

  color[ 0 ] = ref_color[ 0 ];
  color[ 1 ] = ref_color[ 1 ];
  color[ 2 ] = ref_color[ 2 ];
  color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha;

  // Draw meter background
  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

// FIXME: This should come from the element info
#define CHARGE_BAR_CAP_SIZE 3

static void CG_DrawPlayerChargeBar( rectDef_t *rect, vec4_t ref_color,
                                    qhandle_t shader )
{
  vec4_t color;
  float x, y, width, height, cap_size, progress;

  if( !cg_drawChargeBar.integer )
    return;

  // Get progress proportion and pump fade
  progress = CG_ChargeProgress();
  if( progress <= 0.0f )
  {
    cg.chargeMeterAlpha -= CHARGE_BAR_FADE_RATE * cg.frametime;
    if( cg.chargeMeterAlpha <= 0.0f )
    {
      cg.chargeMeterAlpha = 0.0f;
      return;
    }
  }
  else
  {
    cg.chargeMeterValue = progress;
    cg.chargeMeterAlpha += CHARGE_BAR_FADE_RATE * cg.frametime;
    if( cg.chargeMeterAlpha > 1.0f )
      cg.chargeMeterAlpha = 1.0f;
  }

  color[ 0 ] = ref_color[ 0 ];
  color[ 1 ] = ref_color[ 1 ];
  color[ 2 ] = ref_color[ 2 ];
  color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha;

  // Flash red for Lucifer Cannon warning
  if( cg.snap->ps.weapon == WP_LUCIFER_CANNON &&
      cg.snap->ps.stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_WARN &&
      ( cg.time & 128 ) )
  {
    color[ 0 ] = 1.0f;
    color[ 1 ] = 0.0f;
    color[ 2 ] = 0.0f;
  }

  x = rect->x;
  y = rect->y;

  // Horizontal charge bar
  if( rect->w >= rect->h )
  {
    width = ( rect->w - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue;
    height = rect->h;
    CG_AdjustFrom640( &x, &y, &width, &height );
    cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenXScale;

    // Draw the meter
    trap_R_SetColor( color );
    trap_R_DrawStretchPic( x, y, cap_size, height, 0, 0, 1, 1, shader );
    trap_R_DrawStretchPic( x + width + cap_size, y, cap_size, height,
                           1, 0, 0, 1, shader );
    trap_R_DrawStretchPic( x + cap_size, y, width, height, 1, 0, 1, 1, shader );
    trap_R_SetColor( NULL );
  }

  // Vertical charge bar
  else
  {
    y += rect->h;
    width = rect->w;
    height = ( rect->h - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue;
    CG_AdjustFrom640( &x, &y, &width, &height );
    cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenYScale;

    // Draw the meter
    trap_R_SetColor( color );
    trap_R_DrawStretchPic( x, y - cap_size, width, cap_size,
                           0, 1, 1, 0, shader );
    trap_R_DrawStretchPic( x, y - height - cap_size * 2, width,
                           cap_size, 0, 0, 1, 1, shader );
    trap_R_DrawStretchPic( x, y - height - cap_size, width, height,
                           0, 1, 1, 1, shader );
    trap_R_SetColor( NULL );
  }
}

static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color,
                                  float scale, int textalign, int textvalign,
                                  const char *s, float fraction )
{
  vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
  float tx, ty;

  CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty );

  if( fraction < 1.0f )
    UI_Text_Paint( text_x + tx, text_y + ty, scale, white,
      s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
  else
    UI_Text_Paint( text_x + tx, text_y + ty, scale, color,
      s, 0, 0, ITEM_TEXTSTYLE_NEON );
}

static void CG_DrawMediaProgress( rectDef_t *rect, vec4_t color, float scale,
                                  int align, int textalign, int textStyle,
                                  float borderSize )
{
  CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle,
                      borderSize, cg.mediaFraction );
}

static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y,
                                       vec4_t color, float scale, int textalign, int textvalign )
{
  CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign,
                        "Map and Textures", cg.mediaFraction );
}

static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color,
                                       float scale, int align, int textalign,
                                       int textStyle, float borderSize )
{
  CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle,
                      borderSize, cg.buildablesFraction );
}

static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y,
                                            vec4_t color, float scale, int textalign, int textvalign )
{
  CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign,
                        "Buildable Models", cg.buildablesFraction );
}

static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color,
                                      float scale, int align, int textalign,
                                      int textStyle, float borderSize )
{
  CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle,
                      borderSize, cg.charModelFraction );
}

static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y,
                                           vec4_t color, float scale, int textalign, int textvalign )
{
  CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign,
                        "Character Models", cg.charModelFraction );
}

static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale,
                                    int align, int textalign, int textStyle,
                                    float borderSize )
{
  float total;

  total = cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction;
  total /= 3.0f;

  CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle,
                      borderSize, total );
}

static void CG_DrawLevelShot( rectDef_t *rect )
{
  const char  *s;
  const char  *info;
  qhandle_t   levelshot;
  qhandle_t   detail;

  info = CG_ConfigString( CS_SERVERINFO );
  s = Info_ValueForKey( info, "mapname" );
  levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) );

  if( !levelshot )
    levelshot = trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" );

  trap_R_SetColor( NULL );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, levelshot );

  // blend a detail texture over it
  detail = trap_R_RegisterShader( "gfx/misc/detail" );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, detail );
}

static void CG_DrawLevelName( rectDef_t *rect, float text_x, float text_y,
                              vec4_t color, float scale,
                              int textalign, int textvalign, int textStyle )
{
  const char  *s;

  s = CG_ConfigString( CS_MESSAGE );

  UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, s );
}

static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y,
                         vec4_t color, float scale,
                         int textalign, int textvalign, int textStyle )
{
  const char  *s;
  char parsed[ MAX_STRING_CHARS ];

  s = CG_ConfigString( CS_MOTD );

  Q_ParseNewlines( parsed, s, sizeof( parsed ) );

  UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, parsed );
}

static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y,
                             vec4_t color, float scale,
                             int textalign, int textvalign, int textStyle )
{
  char buffer[ 1024 ];
  const char  *info;

  info = CG_ConfigString( CS_SERVERINFO );

  UI_EscapeEmoticons( buffer, Info_ValueForKey( info, "sv_hostname" ), sizeof( buffer ) );
  Q_CleanStr( buffer );

  UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, buffer );
}

/*
==============
CG_DrawDemoPlayback
==============
*/
static void CG_DrawDemoPlayback( rectDef_t *rect, vec4_t color, qhandle_t shader )
{
  if( !cg_drawDemoState.integer )
    return;

  if( trap_GetDemoState( ) != DS_PLAYBACK )
    return;

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
==============
CG_DrawDemoRecording
==============
*/
static void CG_DrawDemoRecording( rectDef_t *rect, vec4_t color, qhandle_t shader )
{
  if( !cg_drawDemoState.integer )
    return;

  if( trap_GetDemoState( ) != DS_RECORDING )
    return;

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
  trap_R_SetColor( NULL );
}

/*
======================
CG_UpdateMediaFraction

======================
*/
void CG_UpdateMediaFraction( float newFract )
{
  cg.mediaFraction = newFract;

  trap_UpdateScreen( );
}

/*
====================
CG_DrawLoadingScreen

Draw all the status / pacifier stuff during level loading
====================
*/
void CG_DrawLoadingScreen( void )
{
  menuDef_t *menu = Menus_FindByName( "Loading" );

  Menu_Update( menu );
  Menu_Paint( menu, qtrue );
}

float CG_GetValue( int ownerDraw )
{
  centity_t *cent;
  playerState_t *ps;
  weapon_t weapon;

  cent = &cg_entities[ cg.snap->ps.clientNum ];
  ps = &cg.snap->ps;
  weapon = BG_GetPlayerWeapon( ps );

  switch( ownerDraw )
  {
    case CG_PLAYER_AMMO_VALUE:
      if( weapon )
        return ps->ammo;
      break;
    case CG_PLAYER_CLIPS_VALUE:
      if( weapon )
        return ps->clips;
      break;
    case CG_PLAYER_HEALTH:
      return ps->stats[ STAT_HEALTH ];
      break;
    default:
      break;
  }

  return -1;
}

const char *CG_GetKillerText( )
{
  const char *s = "";
  if( cg.killerName[ 0 ] )
    s = va( "Fragged by %s", cg.killerName );

  return s;
}


static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color,
                           qhandle_t shader, int textStyle )
{
  // fragged by ... line
 if( cg.killerName[ 0 ] )
 {
   int x = rect->x + rect->w / 2;
   UI_Text_Paint( x - UI_Text_Width( CG_GetKillerText( ), scale ) / 2,
     rect->y + rect->h, scale, color, CG_GetKillerText( ), 0, 0, textStyle );
  }
}


#define SPECTATORS_PIXELS_PER_SECOND 30.0f

/*
==================
CG_DrawTeamSpectators
==================
*/
static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, int textvalign, vec4_t color, qhandle_t shader )
{
  float y;
  char  *text = cg.spectatorList;
  float textWidth = UI_Text_Width( text, scale );

  CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_LEFT, textvalign, NULL, &y );

  if( textWidth > rect->w )
  {
    // The text is too wide to fit, so scroll it
    int now = trap_Milliseconds( );
    int delta = now - cg.spectatorTime;

    CG_SetClipRegion( rect->x, rect->y, rect->w, rect->h );

    UI_Text_Paint( rect->x - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 );
    UI_Text_Paint( rect->x + textWidth - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 );

    CG_ClearClipRegion( );

    cg.spectatorOffset += ( delta / 1000.0f ) * SPECTATORS_PIXELS_PER_SECOND;

    while( cg.spectatorOffset > textWidth )
      cg.spectatorOffset -= textWidth;

    cg.spectatorTime = now;
  }
  else
  {
    UI_Text_Paint( rect->x, y, scale, color, text, 0, 0, 0 );
  }
}

#define FOLLOWING_STRING "following "
#define CHASING_STRING "chasing "

/*
==================
CG_DrawFollow
==================
*/
static void CG_DrawFollow( rectDef_t *rect, float text_x, float text_y,
    vec4_t color, float scale, int textalign, int textvalign, int textStyle )
{
  float tx, ty;

  if( cg.snap && cg.snap->ps.pm_flags & PMF_FOLLOW )
  {
    char buffer[ MAX_STRING_CHARS ];

    if( !cg.chaseFollow )
      strcpy( buffer, FOLLOWING_STRING );
    else
      strcpy( buffer, CHASING_STRING );

    strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name );

    CG_AlignText( rect, buffer, scale, 0, 0, textalign, textvalign, &tx, &ty );
    UI_Text_Paint( text_x + tx, text_y + ty, scale, color, buffer, 0, 0,
                   textStyle );
  }
}

/*
==================
CG_DrawTeamLabel
==================
*/
static void CG_DrawTeamLabel( rectDef_t *rect, team_t team, float text_x, float text_y,
    vec4_t color, float scale, int textalign, int textvalign, int textStyle )
{
  char  *t;
  char  stage[ MAX_TOKEN_CHARS ];
  char  *s;
  float tx, ty;

  stage[ 0 ] = '\0';

  switch( team )
  {
    case TEAM_ALIENS:
      t = "Aliens";
      if( cg.intermissionStarted )
        Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.alienStage + 1 );
      break;

    case TEAM_HUMANS:
      t = "Humans";
      if( cg.intermissionStarted )
        Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.humanStage + 1 );
      break;

    default:
      t = "";
      break;
  }

  switch( textalign )
  {
    default:
    case ALIGN_LEFT:
      s = va( "%s %s", t, stage );
      break;

    case ALIGN_RIGHT:
      s = va( "%s %s", stage, t );
      break;
  }

  CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty );
  UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle );
}

/*
==================
CG_DrawStageReport
==================
*/
static const char *CG_SpawnReport( qboolean eggs )
{
  const char *s;
  
  s = ( cg.snap->ps.persistant[ PERS_SPAWNS ] + cg.snap->ps.persistant[ PERS_SPAWNS_IMPLANTED ] == 1 ? "" : "s" );
  
  if( cg.snap->ps.persistant[ PERS_SPAWNS_IMPLANTED ] )
    return va( "%d(+%d) %s%s left",
               cg.snap->ps.persistant[ PERS_SPAWNS ],
               cg.snap->ps.persistant[ PERS_SPAWNS_IMPLANTED ],
               ( eggs ? "egg" : "telenode" ),
               s );
  else
    return va( "%d %s%s left",
               cg.snap->ps.persistant[ PERS_SPAWNS ],
               ( eggs ? "egg" : "telenode" ),
               s );
}

static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y,
    vec4_t color, float scale, int textalign, int textvalign, int textStyle )
{
  char  s[ MAX_TOKEN_CHARS ];
  float tx, ty;

  if( cg.intermissionStarted )
    return;

  if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_NONE )
    return;

  if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )
  {
    int kills = ceil( (float)(cgs.alienNextStageThreshold - cgs.alienCredits) / ALIEN_CREDITS_PER_KILL );
    if( kills < 0 )
      kills = 0;

    if( cgs.alienNextStageThreshold < 0 )
      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %s", cgs.alienStage + 1, CG_SpawnReport( qtrue ) );
    else if( kills == 1 )
      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 frag for next stage, %s",
          cgs.alienStage + 1, CG_SpawnReport( qtrue ) );
    else
      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d frags for next stage, %s",
          cgs.alienStage + 1, kills, CG_SpawnReport( qtrue ) );
  }
  else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
  {
    int credits = cgs.humanNextStageThreshold - cgs.humanCredits;

    if( credits < 0 )
      credits = 0;

    if( cgs.humanNextStageThreshold < 0 )
      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %s", cgs.humanStage + 1, CG_SpawnReport( qfalse ) );
    else if( credits == 1 )
      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 credit for next stage, %s",
          cgs.humanStage + 1, CG_SpawnReport( qfalse ) );
    else
      Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d credits for next stage, %s",
          cgs.humanStage + 1, credits, CG_SpawnReport( qfalse ) );
  }

  CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty );

  UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle );
}

/*
==================
CG_DrawFPS
==================
*/
#define FPS_FRAMES  20
#define FPS_STRING  "fps"
static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y,
                        float scale, vec4_t color,
                        int textalign, int textvalign, int textStyle,
                        qboolean scalableText )
{
  char        *s;
  float       tx, ty;
  float       w, h, totalWidth;
  int         strLength;
  static int  previousTimes[ FPS_FRAMES ];
  static int  index;
  int         i, total;
  int         fps;
  static int  previous;
  int         t, frameTime;

  if( !cg_drawFPS.integer )
    return;

  // don't use serverTime, because that will be drifting to
  // correct for internet lag changes, timescales, timedemos, etc
  t = trap_Milliseconds( );
  frameTime = t - previous;
  previous = t;

  previousTimes[ index % FPS_FRAMES ] = frameTime;
  index++;

  if( index > FPS_FRAMES )
  {
    // average multiple frames together to smooth changes out a bit
    total = 0;

    for( i = 0 ; i < FPS_FRAMES ; i++ )
      total += previousTimes[ i ];

    if( !total )
      total = 1;

    fps = 1000 * FPS_FRAMES / total;

    s = va( "%d", fps );
    w = UI_Text_Width( "0", scale );
    h = UI_Text_Height( "0", scale );
    strLength = CG_DrawStrlen( s );
    totalWidth = UI_Text_Width( FPS_STRING, scale ) + w * strLength;

    CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty );

    if( scalableText )
    {
      for( i = 0; i < strLength; i++ )
      {
        char c[ 2 ];

        c[ 0 ] = s[ i ];
        c[ 1 ] = '\0';

        UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle );
      }

      UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, FPS_STRING, 0, 0, textStyle );
    }
    else
    {
      trap_R_SetColor( color );
      CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, fps );
      trap_R_SetColor( NULL );
    }
  }
}


/*
=================
CG_DrawTimerMins
=================
*/
static void CG_DrawTimerMins( rectDef_t *rect, vec4_t color )
{
  int     mins, seconds;
  int     msec;

  if( !cg_drawTimer.integer )
    return;

  msec = cg.time - cgs.levelStartTime;

  seconds = msec / 1000;
  mins = seconds / 60;
  seconds -= mins * 60;

  trap_R_SetColor( color );
  CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, mins );
  trap_R_SetColor( NULL );
}


/*
=================
CG_DrawTimerSecs
=================
*/
static void CG_DrawTimerSecs( rectDef_t *rect, vec4_t color )
{
  int     mins, seconds;
  int     msec;

  if( !cg_drawTimer.integer )
    return;

  msec = cg.time - cgs.levelStartTime;

  seconds = msec / 1000;
  mins = seconds / 60;
  seconds -= mins * 60;

  trap_R_SetColor( color );
  CG_DrawFieldPadded( rect->x, rect->y, 2, rect->w / 2, rect->h, seconds );
  trap_R_SetColor( NULL );
}


/*
=================
CG_DrawTimer
=================
*/
static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y,
                          float scale, vec4_t color,
                          int textalign, int textvalign, int textStyle )
{
  char    *s;
  float   tx, ty;
  int     i, strLength;
  float   w, h, totalWidth;
  int     mins, seconds, tens;
  int     msec;

  if( !cg_drawTimer.integer )
    return;

  msec = cg.time - cgs.levelStartTime;

  seconds = msec / 1000;
  mins = seconds / 60;
  seconds -= mins * 60;
  tens = seconds / 10;
  seconds -= tens * 10;

  s = va( "%d:%d%d", mins, tens, seconds );
  w = UI_Text_Width( "0", scale );
  h = UI_Text_Height( "0", scale );
  strLength = CG_DrawStrlen( s );
  totalWidth = w * strLength;

  CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty );

  for( i = 0; i < strLength; i++ )
  {
    char c[ 2 ];

    c[ 0 ] = s[ i ];
    c[ 1 ] = '\0';

    UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle );
  }
}

/*
=================
CG_DrawTeamOverlay
=================
*/

typedef enum
{
  TEAMOVERLAY_OFF,
  TEAMOVERLAY_ALL,
  TEAMOVERLAY_SUPPORT,
  TEAMOVERLAY_NEARBY,
} teamOverlayMode_t;

typedef enum
{
  TEAMOVERLAY_SORT_NONE,
  TEAMOVERLAY_SORT_SCORE,
  TEAMOVERLAY_SORT_WEAPONCLASS,
} teamOverlaySort_t;

static int QDECL SortScore( const void *a, const void *b )
{
  int na = *(int *)a;
  int nb = *(int *)b;

  return( cgs.clientinfo[ nb ].score - cgs.clientinfo[ na ].score );
}

static int QDECL SortWeaponClass( const void *a, const void *b )
{
  int out;
  clientInfo_t *ca = cgs.clientinfo + *(int *)a;
  clientInfo_t *cb = cgs.clientinfo + *(int *)b;

  out = cb->curWeaponClass - ca->curWeaponClass;

  // We want grangers on top. ckits are already on top without the special case.
  if( ca->team == TEAM_ALIENS )
  {
    if( ca->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || 
        cb->curWeaponClass == PCL_ALIEN_BUILDER0_UPG ||
        ca->curWeaponClass == PCL_ALIEN_BUILDER0 || 
        cb->curWeaponClass == PCL_ALIEN_BUILDER0 )
    {
      out = -out;
    }
  }

  return( out );
}

static void CG_DrawTeamOverlay( rectDef_t *rect, float scale, vec4_t color )
{
  char              *s;
  int               i;
  float             x = rect->x;
  float             y;
  clientInfo_t      *ci, *pci;
  vec4_t            tcolor;
  float             iconSize = rect->h / 8.0f;
  float             leftMargin = 4.0f;
  float             iconTopMargin = 2.0f;
  float             midSep = 2.0f;
  float             backgroundWidth = rect->w;
  float             fontScale = 0.30f;
  float             vPad = 0.0f;
  float             nameWidth = 0.5f * rect->w;
  char              name[ MAX_NAME_LENGTH + 2 ];
  int               maxDisplayCount = 0;
  int               displayCount = 0;
  float             nameMaxX, nameMaxXCp;
  float             maxX = rect->x + rect->w;
  float             maxXCp = maxX;
  weapon_t          curWeapon = WP_NONE;
  teamOverlayMode_t mode = cg_drawTeamOverlay.integer;
  teamOverlaySort_t sort = cg_teamOverlaySortMode.integer;
  int               displayClients[ MAX_CLIENTS ];

  if( cg.predictedPlayerState.pm_type == PM_SPECTATOR )
    return;

  if( mode == TEAMOVERLAY_OFF || !cg_teamOverlayMaxPlayers.integer )
    return;

  if( !cgs.teaminfoReceievedTime )
    return;

  if( cg.showScores ||
      cg.predictedPlayerState.pm_type == PM_INTERMISSION )
    return;

  pci = cgs.clientinfo + cg.snap->ps.clientNum;

  if( mode == TEAMOVERLAY_ALL || mode == TEAMOVERLAY_SUPPORT )
  {
    for( i = 0; i < MAX_CLIENTS; i++ )
    {
      ci = cgs.clientinfo + i;
      if( ci->infoValid && pci != ci && ci->team == pci->team )
      {
        if( mode == TEAMOVERLAY_ALL )
          displayClients[ maxDisplayCount++ ] = i;
        else
        {
          if( ci->curWeaponClass == PCL_ALIEN_BUILDER0 || 
              ci->curWeaponClass == PCL_ALIEN_BUILDER0_UPG ||
              ci->curWeaponClass == PCL_ALIEN_LEVEL1 || 
              ci->curWeaponClass == PCL_ALIEN_LEVEL1_UPG ||
              ci->curWeaponClass == WP_HBUILD )
          {
            displayClients[ maxDisplayCount++ ] = i;
          }
        }
      }
    }
  }
  else // find nearby
  {
    for( i = 0; i < cg.snap->numEntities; i++ )
    {
      centity_t *cent = &cg_entities[ cg.snap->entities[ i ].number ];
      vec3_t relOrigin = { 0.0f, 0.0f, 0.0f };
      int team = cent->currentState.misc & 0x00FF;

      if( cent->currentState.eType != ET_PLAYER || 
          team != pci->team ||
          cent->currentState.eFlags & EF_DEAD )
      {
        continue;
      }

      VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, relOrigin );

      if( VectorLength( relOrigin ) < HELMET_RANGE )
        displayClients[ maxDisplayCount++ ] = cg.snap->entities[ i ].number;
    }
  }

  // Sort
  if( sort == TEAMOVERLAY_SORT_SCORE )
  {
    qsort( displayClients, maxDisplayCount,
      sizeof( displayClients[ 0 ] ), SortScore );
  }
  else if( sort == TEAMOVERLAY_SORT_WEAPONCLASS )
  {
    qsort( displayClients, maxDisplayCount,
      sizeof( displayClients[ 0 ] ), SortWeaponClass );
  }

  if( maxDisplayCount > cg_teamOverlayMaxPlayers.integer )
    maxDisplayCount = cg_teamOverlayMaxPlayers.integer;

  iconSize *= scale;
  leftMargin *= scale;
  iconTopMargin *= scale;
  midSep *= scale;
  backgroundWidth *= scale;
  fontScale *= scale;
  nameWidth *= scale;

  vPad = ( rect->h - ( (float) maxDisplayCount * iconSize ) ) / 2.0f;
  y = rect->y + vPad;

  tcolor[ 0 ] = 1.0f;
  tcolor[ 1 ] = 1.0f;
  tcolor[ 2 ] = 1.0f;
  tcolor[ 3 ] = color[ 3 ];

  for( i = 0; i < MAX_CLIENTS && displayCount < maxDisplayCount; i++ )
  {
    ci = cgs.clientinfo + displayClients[ i ];

    if( !ci->infoValid || pci == ci || ci->team != pci->team )
      continue;

    Com_sprintf( name, sizeof( name ), "%s^7", ci->name );

    trap_R_SetColor( color );
    CG_DrawPic( x, y, backgroundWidth,
                iconSize, cgs.media.teamOverlayShader );
    trap_R_SetColor( tcolor );
    if( ci->health <= 0 || !ci->curWeaponClass )
      s = "";
    else
    {
      if( ci->team == TEAM_HUMANS )
        curWeapon = ci->curWeaponClass;
      else if( ci->team == TEAM_ALIENS )
        curWeapon = BG_Class( ci->curWeaponClass )->startWeapon;

      CG_DrawPic( x + leftMargin, y, iconSize, iconSize,
                  cg_weapons[ curWeapon ].weaponIcon );
      if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS )
      {
        if( ci->upgrade != UP_NONE )
        {
          CG_DrawPic( x + iconSize + leftMargin, y, iconSize, 
                      iconSize, cg_upgrades[ ci->upgrade ].upgradeIcon );
        }
      }
      else
      {
        if( curWeapon == WP_ABUILD2 || curWeapon == WP_ALEVEL1_UPG ||
            curWeapon == WP_ALEVEL2_UPG || curWeapon == WP_ALEVEL3_UPG )
        {
          CG_DrawPic( x + iconSize + leftMargin, y, iconSize, 
                      iconSize, cgs.media.upgradeClassIconShader );
        }
      }

      s = va( " [^%c%3d^7] ^7%s",
              CG_GetColorCharForHealth( displayClients[ i ] ),
              ci->health,
              CG_ConfigString( CS_LOCATIONS + ci->location ) );
    }

    trap_R_SetColor( NULL );
    nameMaxX = nameMaxXCp = x + 2.0f * iconSize +
                            leftMargin + midSep + nameWidth;
    UI_Text_Paint_Limit( &nameMaxXCp, x + 2.0f * iconSize + leftMargin + midSep, 
                         y + iconSize - iconTopMargin, fontScale, tcolor, name,
                         0, 0 );

    maxXCp = maxX;

    UI_Text_Paint_Limit( &maxXCp, nameMaxX, y + iconSize - iconTopMargin, 
                         fontScale, tcolor, s, 0, 0 );
    y += iconSize;
    displayCount++;
  }
}

/*
=================
CG_DrawClock
=================
*/
static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y,
                          float scale, vec4_t color,
                          int textalign, int textvalign, int textStyle )
{
  char    *s;
  float   tx, ty;
  int     i, strLength;
  float   w, h, totalWidth;
  qtime_t qt;
  int     t;

  if( !cg_drawClock.integer )
    return;

  t = trap_RealTime( &qt );

  if( cg_drawClock.integer == 2 )
  {
    s = va( "%02d%s%02d", qt.tm_hour, ( qt.tm_sec % 2 ) ? ":" : " ",
      qt.tm_min );
  }
  else
  {
    char *pm = "am";
    int h = qt.tm_hour;

    if( h == 0 )
      h = 12;
    else if( h == 12 )
      pm = "pm";
    else if( h > 12 )
    {
      h -= 12;
      pm = "pm";
    }

    s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm );
  }
  w = UI_Text_Width( "0", scale );
  h = UI_Text_Height( "0", scale );
  strLength = CG_DrawStrlen( s );
  totalWidth = w * strLength;

  CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty );

  for( i = 0; i < strLength; i++ )
  {
    char c[ 2 ];

    c[ 0 ] = s[ i ];
    c[ 1 ] = '\0';

    UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle );
  }
}

/*
==================
CG_DrawSnapshot
==================
*/
static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y,
                             float scale, vec4_t color,
                             int textalign, int textvalign, int textStyle )
{
  char    *s;
  float   tx, ty;

  if( !cg_drawSnapshot.integer )
    return;

  s = va( "time:%d snap:%d cmd:%d", cg.snap->serverTime,
    cg.latestSnapshotNum, cgs.serverCommandSequence );

  CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty );

  UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle );
}

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

LAGOMETER

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

#define LAG_SAMPLES   128

typedef struct
{
  int frameSamples[ LAG_SAMPLES ];
  int frameCount;
  int snapshotFlags[ LAG_SAMPLES ];
  int snapshotSamples[ LAG_SAMPLES ];
  int snapshotCount;
} lagometer_t;

lagometer_t   lagometer;

/*
==============
CG_AddLagometerFrameInfo

Adds the current interpolate / extrapolate bar for this frame
==============
*/
void CG_AddLagometerFrameInfo( void )
{
  int     offset;

  offset = cg.time - cg.latestSnapshotTime;
  lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1 ) ] = offset;
  lagometer.frameCount++;
}

/*
==============
CG_AddLagometerSnapshotInfo

Each time a snapshot is received, log its ping time and
the number of snapshots that were dropped before it.

Pass NULL for a dropped packet.
==============
*/
#define PING_FRAMES 40
void CG_AddLagometerSnapshotInfo( snapshot_t *snap )
{
  static int  previousPings[ PING_FRAMES ];
  static int  index;
  int         i;

  // dropped packet
  if( !snap )
  {
    lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = -1;
    lagometer.snapshotCount++;
    return;
  }

  // add this snapshot's info
  lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->ping;
  lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->snapFlags;
  lagometer.snapshotCount++;

  cg.ping = 0;
  if( cg.snap )
  {
    previousPings[ index++ ] = cg.snap->ping;
    index = index % PING_FRAMES;

    for( i = 0; i < PING_FRAMES; i++ )
    {
      cg.ping += previousPings[ i ];
    }

    cg.ping /= PING_FRAMES;
  }
}

/*
==============
CG_DrawDisconnect

Should we draw something differnet for long lag vs no packets?
==============
*/
static void CG_DrawDisconnect( void )
{
  float       x, y;
  int         cmdNum;
  usercmd_t   cmd;
  const char  *s;
  int         w;
  vec4_t      color = { 1.0f, 1.0f, 1.0f, 1.0f };

  // draw the phone jack if we are completely past our buffers
  cmdNum = trap_GetCurrentCmdNumber( ) - CMD_BACKUP + 1;
  trap_GetUserCmd( cmdNum, &cmd );

  // special check for map_restart
  if( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time )
    return;

  // also add text in center of screen
  s = "Connection Interrupted";
  w = UI_Text_Width( s, 0.7f );
  UI_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED );

  // blink the icon
  if( ( cg.time >> 9 ) & 1 )
    return;

  x = 640 - 48;
  y = 480 - 48;

  CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader( "gfx/2d/net.tga" ) );
}

#define MAX_LAGOMETER_PING  900
#define MAX_LAGOMETER_RANGE 300


/*
==============
CG_DrawLagometer
==============
*/
static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y,
    float scale, vec4_t textColor )
{
  int     a, x, y, i;
  float   v;
  float   ax, ay, aw, ah, mid, range;
  int     color;
  vec4_t  adjustedColor;
  float   vscale;
  char    *ping;

  if( cg.snap->ps.pm_type == PM_INTERMISSION )
    return;

  if( !cg_lagometer.integer )
    return;

  if( cg.demoPlayback )
    return;

  Vector4Copy( textColor, adjustedColor );
  adjustedColor[ 3 ] = 0.25f;

  trap_R_SetColor( adjustedColor );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader );
  trap_R_SetColor( NULL );

  //
  // draw the graph
  //
  ax = x = rect->x;
  ay = y = rect->y;
  aw = rect->w;
  ah = rect->h;

  trap_R_SetColor( NULL );

  CG_AdjustFrom640( &ax, &ay, &aw, &ah );

  color = -1;
  range = ah / 3;
  mid = ay + range;

  vscale = range / MAX_LAGOMETER_RANGE;

  // draw the frame interpoalte / extrapolate graph
  for( a = 0 ; a < aw ; a++ )
  {
    i = ( lagometer.frameCount - 1 - a ) & ( LAG_SAMPLES - 1 );
    v = lagometer.frameSamples[ i ];
    v *= vscale;

    if( v > 0 )
    {
      if( color != 1 )
      {
        color = 1;
        trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] );
      }

      if( v > range )
        v = range;

      trap_R_DrawStretchPic( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
    }
    else if( v < 0 )
    {
      if( color != 2 )
      {
        color = 2;
        trap_R_SetColor( g_color_table[ ColorIndex( COLOR_BLUE ) ] );
      }

      v = -v;
      if( v > range )
        v = range;

      trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
    }
  }

  // draw the snapshot latency / drop graph
  range = ah / 2;
  vscale = range / MAX_LAGOMETER_PING;

  for( a = 0 ; a < aw ; a++ )
  {
    i = ( lagometer.snapshotCount - 1 - a ) & ( LAG_SAMPLES - 1 );
    v = lagometer.snapshotSamples[ i ];

    if( v > 0 )
    {
      if( lagometer.snapshotFlags[ i ] & SNAPFLAG_RATE_DELAYED )
      {
        if( color != 5 )
        {
          color = 5;  // YELLOW for rate delay
          trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] );
        }
      }
      else
      {
        if( color != 3 )
        {
          color = 3;

          trap_R_SetColor( g_color_table[ ColorIndex( COLOR_GREEN ) ] );
        }
      }

      v = v * vscale;

      if( v > range )
        v = range;

      trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
    }
    else if( v < 0 )
    {
      if( color != 4 )
      {
        color = 4;    // RED for dropped snapshots
        trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] );
      }

      trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader );
    }
  }

  trap_R_SetColor( NULL );

  if( cg_nopredict.integer || cg_synchronousClients.integer )
    ping = "snc";
  else
    ping = va( "%d", cg.ping );
  ax = rect->x + ( rect->w / 2.0f ) -
       ( UI_Text_Width( ping, scale ) / 2.0f ) + text_x;
  ay = rect->y + ( rect->h / 2.0f ) +
       ( UI_Text_Height( ping, scale ) / 2.0f ) + text_y;

  Vector4Copy( textColor, adjustedColor );
  adjustedColor[ 3 ] = 0.5f;
  UI_Text_Paint( ax, ay, scale, adjustedColor, ping, 0, 0,
                 ITEM_TEXTSTYLE_NORMAL );

  CG_DrawDisconnect( );
}

#define SPEEDOMETER_NUM_SAMPLES 160
#define SPEEDOMETER_DRAW_TEXT   0x1
#define SPEEDOMETER_DRAW_GRAPH  0x2
#define SPEEDOMETER_IGNORE_Z    0x4
float speedSamples[ SPEEDOMETER_NUM_SAMPLES ];
// array indices
int oldestSpeedSample = 0;
int maxSpeedSample = 0;

/*
===================
CG_AddSpeed

append a speed to the sample history
===================
*/
void CG_AddSpeed( void )
{
  float speed;
  vec3_t vel;

  VectorCopy( cg.snap->ps.velocity, vel );

  if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z )
    vel[ 2 ] = 0;

  speed = VectorLength( vel );

  if( speed > speedSamples[ maxSpeedSample ] )
  {
    maxSpeedSample = oldestSpeedSample;
    speedSamples[ oldestSpeedSample++ ] = speed;
    oldestSpeedSample %= SPEEDOMETER_NUM_SAMPLES;
    return;
  }

  speedSamples[ oldestSpeedSample ] = speed;
  if( maxSpeedSample == oldestSpeedSample++ )
  {
    // if old max was overwritten find a new one
    int i;
    for( maxSpeedSample = 0, i = 1; i < SPEEDOMETER_NUM_SAMPLES; i++ )
    {
      if( speedSamples[ i ] > speedSamples[ maxSpeedSample ] )
        maxSpeedSample = i;
    }
  }

  oldestSpeedSample %= SPEEDOMETER_NUM_SAMPLES;
}

#define SPEEDOMETER_MIN_RANGE 900
#define SPEED_MED 1000.f
#define SPEED_FAST 1600.f

/*
===================
CG_DrawSpeedGraph
===================
*/
static void CG_DrawSpeedGraph( rectDef_t *rect, vec4_t foreColor,
                               vec4_t backColor )
{
  int i;
  float val, max, top;
  // colour of graph is interpolated between these values
  const vec3_t slow = { 0.0, 0.0, 1.0 };
  const vec3_t medium = { 0.0, 1.0, 0.0 };
  const vec3_t fast = { 1.0, 0.0, 0.0 };
  vec4_t color;

  max = speedSamples[ maxSpeedSample ];
  if( max < SPEEDOMETER_MIN_RANGE )
    max = SPEEDOMETER_MIN_RANGE;

  trap_R_SetColor( backColor );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader );

  Vector4Copy( foreColor, color );

  for( i = 1; i < SPEEDOMETER_NUM_SAMPLES; i++ )
  {
    val = speedSamples[ ( oldestSpeedSample + i ) % SPEEDOMETER_NUM_SAMPLES ];
    if( val < SPEED_MED )
      VectorLerp( val / SPEED_MED, slow, medium, color );
    else if( val < SPEED_FAST )
      VectorLerp( ( val - SPEED_MED ) / ( SPEED_FAST - SPEED_MED ),
                  medium, fast, color );
    else
      VectorCopy( fast, color );
    trap_R_SetColor( color );
    top = rect->y + ( 1 - val / max ) * rect->h;
    CG_DrawPic( rect->x + ( i / (float)SPEEDOMETER_NUM_SAMPLES ) * rect->w, top,
                rect->w / (float)SPEEDOMETER_NUM_SAMPLES, val * rect->h / max,
                cgs.media.whiteShader );
  }
  trap_R_SetColor( NULL );
}

/*
===================
CG_DrawSpeedText
===================
*/
static void CG_DrawSpeedText( rectDef_t *rect, float text_x, float text_y,
                              float scale, vec4_t foreColor )
{
  char speedstr[ 16 ];
  float val;
  vec4_t color;

  VectorCopy( foreColor, color );
  color[ 3 ] = 1;
  if( cg.predictedPlayerState.clientNum == cg.clientNum )
  {
    vec3_t vel;
    VectorCopy( cg.predictedPlayerState.velocity, vel );
    if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z )
      vel[ 2 ] = 0;
    val = VectorLength( vel );
  }
  else if( oldestSpeedSample == 0 )
    val = speedSamples[ SPEEDOMETER_NUM_SAMPLES - 1 ];
  else
    val = speedSamples[ oldestSpeedSample - 1 ];

  Com_sprintf( speedstr, sizeof( speedstr ), "%d", (int)val );

  UI_Text_Paint(
      rect->x + ( rect->w - UI_Text_Width( speedstr, scale ) ) / 2.0f,
      rect->y + ( rect->h + UI_Text_Height( speedstr, scale ) ) / 2.0f,
      scale, color, speedstr, 0, 0, ITEM_TEXTSTYLE_NORMAL );
}

/*
===================
CG_DrawSpeed
===================
*/
static void CG_DrawSpeed( rectDef_t *rect, float text_x, float text_y,
                          float scale, vec4_t foreColor, vec4_t backColor )
{
  if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_GRAPH )
    CG_DrawSpeedGraph( rect, foreColor, backColor );
  if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_TEXT )
    CG_DrawSpeedText( rect, text_x, text_y, scale, foreColor );
}

/*
===================
CG_DrawConsole
===================
*/
static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color,
                            float scale, int textalign, int textvalign, int textStyle )
{
  UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, cg.consoleText );
}

/*
===================
CG_DrawTutorial
===================
*/
static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color,
                            float scale, int textalign, int textvalign, int textStyle )
{
  if( !cg_tutorial.integer )
    return;

  UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, CG_TutorialText( ) );
}

/*
===================
CG_DrawWeaponIcon
===================
*/
void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color )
{
  int           maxAmmo;
  centity_t     *cent;
  playerState_t *ps;
  weapon_t      weapon;

  cent = &cg_entities[ cg.snap->ps.clientNum ];
  ps = &cg.snap->ps;
  weapon = BG_GetPlayerWeapon( ps );

  maxAmmo = BG_Weapon( weapon )->maxAmmo;

  // don't display if dead
  if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
    return;

  if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS )
  {
    CG_Error( "CG_DrawWeaponIcon: weapon out of range: %d\n", weapon );
    return;
  }

  if( !cg_weapons[ weapon ].registered )
  {
    Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawWeaponIcon: weapon %d (%s) "
        "is not registered\n", weapon, BG_Weapon( weapon )->name );
    return;
  }

  if( ps->clips == 0 && !BG_Weapon( weapon )->infiniteAmmo )
  {
    float ammoPercent = (float)ps->ammo / (float)maxAmmo;

    if( ammoPercent < 0.33f )
    {
      color[ 0 ] = 1.0f;
      color[ 1 ] = color[ 2 ] = 0.0f;
    }
  }

  if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS &&
      !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ],
                          ps->persistant[ PERS_CREDIT ], cgs.alienStage ) )
  {
    if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME )
    {
      if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 )
        color[ 3 ] = 0.0f;
    }
  }

  trap_R_SetColor( color );
  CG_DrawPic( rect->x, rect->y, rect->w, rect->h,
              cg_weapons[ weapon ].weaponIcon );
  trap_R_SetColor( NULL );
}



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

CROSSHAIR

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



/*
=================
CG_DrawCrosshair
=================
*/
static void CG_DrawCrosshair( rectDef_t *rect, vec4_t color )
{
  float         w, h;
  float         x, y;
  weaponInfo_t  *wi;
  weapon_t      weapon;
  qboolean      hit, ff;

  weapon = BG_GetPlayerWeapon( &cg.snap->ps );

  if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF )
    return;

  if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY &&
      !BG_Weapon( weapon )->longRanged )
    return;

  if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT )
    return;

  if( cg.renderingThirdPerson )
    return;

  if( cg.snap->ps.pm_type == PM_INTERMISSION )
    return;

  wi = &cg_weapons[ weapon ];

  w = h = wi->crossHairSize * cg_crosshairSize.value;
  w *= cgDC.aspectScale;

  //FIXME: this still ignores the width/height of the rect, but at least it's
  //neater than cg_crosshairX/cg_crosshairY
  x = rect->x + ( rect->w / 2 ) - ( w / 2 );
  y = rect->y + ( rect->h / 2 ) - ( h / 2 );

  hit = ( cg.time <= cg.lastHitTime + 75 );
  ff = ( cg.time == cg.crosshairClientTime || cg.crosshairBuildable >= 0 );

  color[ 3 ] = 0.6f;

  trap_R_SetColor( color );
  
  switch( wi->crossHairType )
  {
    case CH_NONE:
      break;
    
    case CH_DOT:
      if( ff )
        CG_DrawPic( x, y, w, h, cgs.media.ch_friendly );
      else
        CG_DrawPic( x, y, w, h, cgs.media.ch_dot );

      if( hit )        
        CG_DrawPic( x, y, w, h, cgs.media.ch_dothit );
        
      break;
    
    case CH_CIRCLE:
      if( ff )
        CG_DrawPic( x, y, w, h, cgs.media.ch_friendly );
      
      if( hit )
        CG_DrawPic( x, y, w, h, cgs.media.ch_circlehit );
      else
        CG_DrawPic( x, y, w, h, cgs.media.ch_circle );
        
      break;
    
    case CH_CIRCLEDDOT:
      if( ff )
        CG_DrawPic( x, y, w, h, cgs.media.ch_friendly );
      else
        CG_DrawPic( x, y, w, h, cgs.media.ch_dot );
      
      if( hit )
        CG_DrawPic( x, y, w, h, cgs.media.ch_circlehit );
      else
        CG_DrawPic( x, y, w, h, cgs.media.ch_circle );
        
      break;
      
    case CH_ALIEN:
      if( ff )
        CG_DrawPic( x, y, w, h, cgs.media.ch_afriendly );
      else
        CG_DrawPic( x, y, w, h, cgs.media.ch_adot );

      if( hit )        
        CG_DrawPic( x, y, w, h, cgs.media.ch_acircle );
      
      break;
  }

  trap_R_SetColor( NULL );
}



/*
=================
CG_ScanForCrosshairEntity
=================
*/
static void CG_ScanForCrosshairEntity( void )
{
  trace_t   trace;
  vec3_t    start, end;
  int       content;
  team_t    team;

  VectorCopy( cg.refdef.vieworg, start );
  VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end );

  CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
    cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY );

  cg.crosshairDistance = Distance( start, trace.endpos );

  // if the player is in fog, don't show it
  content = trap_CM_PointContents( trace.endpos, 0 );
  if( content & CONTENTS_FOG )
    return;

  if( trace.entityNum >= MAX_CLIENTS )
  {
    entityState_t *s = &cg_entities[ trace.entityNum ].currentState;
    if( s->eType == ET_BUILDABLE && BG_Buildable( s->modelindex, NULL )->team ==
        cg.snap->ps.stats[ STAT_TEAM ] )
      cg.crosshairBuildable = trace.entityNum;
    else
      cg.crosshairBuildable = -1;

    return;
  }

  team = cgs.clientinfo[ trace.entityNum ].team;

  if( cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE )
  {
    //only display team names of those on the same team as this player
    if( team != cg.snap->ps.stats[ STAT_TEAM ] )
      return;
  }

  // update the fade timer
  cg.crosshairClientNum = trace.entityNum;
  cg.crosshairClientTime = cg.time;
}


/*
=====================
CG_DrawLocation
=====================
*/
static void CG_DrawLocation( rectDef_t *rect, float scale, int textalign, vec4_t color )
{
  const char    *location;
  centity_t     *locent;
  float         maxX;
  float         tx = rect->x, ty = rect->y;

  if( cg.intermissionStarted )
    return;

  maxX = rect->x + rect->w;

  locent = CG_GetPlayerLocation( );
  if( locent )
    location = CG_ConfigString( CS_LOCATIONS + locent->currentState.generic1 );
  else
    location = CG_ConfigString( CS_LOCATIONS );

  // need to skip horiz. align if it's too long, but valign must be run either way
  if( UI_Text_Width( location, scale ) < rect->w )
  {
    CG_AlignText( rect, location, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty );
    UI_Text_Paint( tx, ty, scale, color, location, 0, 0, ITEM_TEXTSTYLE_NORMAL );
  }
  else
  {
    CG_AlignText( rect, location, scale, 0.0f, 0.0f, ALIGN_NONE, VALIGN_CENTER, &tx, &ty );
    UI_Text_Paint_Limit( &maxX, tx, ty, scale, color, location, 0, 0 );
  }

  trap_R_SetColor( NULL );
}

/*
=====================
CG_DrawCrosshairNames
=====================
*/
static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle )
{
  float   *color;
  char    *name;
  float   w, x;

  if( !cg_drawCrosshairNames.integer )
    return;

  if( cg.renderingThirdPerson )
    return;

  // scan the known entities to see if the crosshair is sighted on one
  CG_ScanForCrosshairEntity( );

  // draw the name of the player being looked at
  color = CG_FadeColor( cg.crosshairClientTime, CROSSHAIR_CLIENT_TIMEOUT );
  if( !color )
  {
    trap_R_SetColor( NULL );
    return;
  }

  // add health from overlay info to the crosshair client name
  name = cgs.clientinfo[ cg.crosshairClientNum ].name;
  if( cg_teamOverlayUserinfo.integer &&
      cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE &&
      cgs.teaminfoReceievedTime &&
      cgs.clientinfo[ cg.crosshairClientNum ].health > 0 )
  {
    name = va( "%s ^7[^%c%d^7]", name,
               CG_GetColorCharForHealth( cg.crosshairClientNum ),
               cgs.clientinfo[ cg.crosshairClientNum ].health );
  }

  w = UI_Text_Width( name, scale );
  x = rect->x + rect->w / 2.0f;
  UI_Text_Paint( x - w / 2.0f, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
  trap_R_SetColor( NULL );
}

/*
===============
CG_OwnerDraw

Draw an owner drawn item
===============
*/
void CG_OwnerDraw( float x, float y, float w, float h, float text_x,
                   float text_y, int ownerDraw, int ownerDrawFlags,
                   int align, int textalign, int textvalign, float borderSize,
                   float scale, vec4_t foreColor, vec4_t backColor,
                   qhandle_t shader, int textStyle )
{
  rectDef_t rect;

  rect.x = x;
  rect.y = y;
  rect.w = w;
  rect.h = h;

  switch( ownerDraw )
  {
    case CG_PLAYER_CREDITS_VALUE:
      CG_DrawPlayerCreditsValue( &rect, foreColor, qtrue );
      break;
    case CG_PLAYER_CREDITS_FRACTION:
      CG_DrawPlayerCreditsFraction( &rect, foreColor, shader );
      break;
    case CG_PLAYER_CREDITS_VALUE_NOPAD:
      CG_DrawPlayerCreditsValue( &rect, foreColor, qfalse );
      break;
    case CG_PLAYER_STAMINA_1:
    case CG_PLAYER_STAMINA_2:
    case CG_PLAYER_STAMINA_3:
    case CG_PLAYER_STAMINA_4:
      CG_DrawPlayerStamina( ownerDraw, &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_STAMINA_BOLT:
      CG_DrawPlayerStaminaBolt( &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_AMMO_VALUE:
      CG_DrawPlayerAmmoValue( &rect, foreColor );
      break;
    case CG_PLAYER_CLIPS_VALUE:
      CG_DrawPlayerClipsValue( &rect, foreColor );
      break;
    case CG_PLAYER_BUILD_TIMER:
      CG_DrawPlayerBuildTimer( &rect, foreColor );
      break;
    case CG_PLAYER_HEALTH:
      CG_DrawPlayerHealthValue( &rect, foreColor );
      break;
    case CG_PLAYER_HEALTH_CROSS:
      CG_DrawPlayerHealthCross( &rect, foreColor );
      break;
    case CG_PLAYER_CHARGE_BAR_BG:
      CG_DrawPlayerChargeBarBG( &rect, foreColor, shader );
      break;
    case CG_PLAYER_CHARGE_BAR:
      CG_DrawPlayerChargeBar( &rect, foreColor, shader );
      break;
    case CG_PLAYER_CLIPS_RING:
      CG_DrawPlayerClipsRing( &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_BUILD_TIMER_RING:
      CG_DrawPlayerBuildTimerRing( &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_WALLCLIMBING:
      CG_DrawPlayerWallclimbing( &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_BOOSTED:
      CG_DrawPlayerBoosted( &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_BOOST_BOLT:
      CG_DrawPlayerBoosterBolt( &rect, backColor, foreColor, shader );
      break;
    case CG_PLAYER_POISON_BARBS:
      CG_DrawPlayerPoisonBarbs( &rect, foreColor, shader );
      break;
    case CG_PLAYER_ALIEN_SENSE:
      CG_DrawAlienSense( &rect );
      break;
    case CG_PLAYER_HUMAN_SCANNER:
      CG_DrawHumanScanner( &rect, shader, foreColor );
      break;
    case CG_PLAYER_USABLE_BUILDABLE:
      CG_DrawUsableBuildable( &rect, shader, foreColor );
      break;
    case CG_KILLER:
      CG_DrawKiller( &rect, scale, foreColor, shader, textStyle );
      break;
    case CG_PLAYER_SELECT:
      CG_DrawItemSelect( &rect, foreColor );
      break;
    case CG_PLAYER_WEAPONICON:
      CG_DrawWeaponIcon( &rect, foreColor );
      break;
    case CG_PLAYER_SELECTTEXT:
      CG_DrawItemSelectText( &rect, scale, textStyle );
      break;
    case CG_SPECTATORS:
      CG_DrawTeamSpectators( &rect, scale, textvalign, foreColor, shader );
      break;
    case CG_PLAYER_LOCATION:
      CG_DrawLocation( &rect, scale, textalign, foreColor );
      break;
    case CG_FOLLOW:
      CG_DrawFollow( &rect, text_x, text_y, foreColor, scale,
                     textalign, textvalign, textStyle );
      break;
    case CG_PLAYER_CROSSHAIRNAMES:
      CG_DrawCrosshairNames( &rect, scale, textStyle );
      break;
    case CG_PLAYER_CROSSHAIR:
      CG_DrawCrosshair( &rect, foreColor );
      break;
    case CG_STAGE_REPORT_TEXT:
      CG_DrawStageReport( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;
    case CG_ALIENS_SCORE_LABEL:
      CG_DrawTeamLabel( &rect, TEAM_ALIENS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;
    case CG_HUMANS_SCORE_LABEL:
      CG_DrawTeamLabel( &rect, TEAM_HUMANS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;

    //loading screen
    case CG_LOAD_LEVELSHOT:
      CG_DrawLevelShot( &rect );
      break;
    case CG_LOAD_MEDIA:
      CG_DrawMediaProgress( &rect, foreColor, scale, align, textalign, textStyle,
                            borderSize );
      break;
    case CG_LOAD_MEDIA_LABEL:
      CG_DrawMediaProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign );
      break;
    case CG_LOAD_BUILDABLES:
      CG_DrawBuildablesProgress( &rect, foreColor, scale, align, textalign,
                                 textStyle, borderSize );
      break;
    case CG_LOAD_BUILDABLES_LABEL:
      CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign );
      break;
    case CG_LOAD_CHARMODEL:
      CG_DrawCharModelProgress( &rect, foreColor, scale, align, textalign,
                                textStyle, borderSize );
      break;
    case CG_LOAD_CHARMODEL_LABEL:
      CG_DrawCharModelProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign );
      break;
    case CG_LOAD_OVERALL:
      CG_DrawOverallProgress( &rect, foreColor, scale, align, textalign, textStyle,
                              borderSize );
      break;
    case CG_LOAD_LEVELNAME:
      CG_DrawLevelName( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;
    case CG_LOAD_MOTD:
      CG_DrawMOTD( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;
    case CG_LOAD_HOSTNAME:
      CG_DrawHostname( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;

    case CG_FPS:
      CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qtrue );
      break;
    case CG_FPS_FIXED:
      CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qfalse );
      break;
    case CG_TIMER:
      CG_DrawTimer( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle );
      break;
    case CG_CLOCK:
      CG_DrawClock( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle );
      break;
    case CG_TIMER_MINS:
      CG_DrawTimerMins( &rect, foreColor );
      break;
    case CG_TIMER_SECS:
      CG_DrawTimerSecs( &rect, foreColor );
      break;
    case CG_SNAPSHOT:
      CG_DrawSnapshot( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle );
      break;
    case CG_LAGOMETER:
      CG_DrawLagometer( &rect, text_x, text_y, scale, foreColor );
      break;
    case CG_TEAMOVERLAY:
      CG_DrawTeamOverlay( &rect, scale, foreColor );
      break;
    case CG_SPEEDOMETER:
      CG_DrawSpeed( &rect, text_x, text_y, scale, foreColor, backColor );
      break;

    case CG_DEMO_PLAYBACK:
      CG_DrawDemoPlayback( &rect, foreColor, shader );
      break;
    case CG_DEMO_RECORDING:
      CG_DrawDemoRecording( &rect, foreColor, shader );
      break;

    case CG_CONSOLE:
      CG_DrawConsole( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;

    case CG_TUTORIAL:
      CG_DrawTutorial( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle );
      break;

    default:
      break;
  }
}

void CG_MouseEvent( int x, int y )
{
  int n;

  if( ( cg.predictedPlayerState.pm_type == PM_NORMAL ||
        cg.predictedPlayerState.pm_type == PM_SPECTATOR ) &&
        cg.showScores == qfalse )
  {
    trap_Key_SetCatcher( 0 );
    return;
  }

  cgs.cursorX += x;
  if( cgs.cursorX < 0 )
    cgs.cursorX = 0;
  else if( cgs.cursorX > 640 )
    cgs.cursorX = 640;

  cgs.cursorY += y;
  if( cgs.cursorY < 0 )
    cgs.cursorY = 0;
  else if( cgs.cursorY > 480 )
    cgs.cursorY = 480;

  n = Display_CursorType( cgs.cursorX, cgs.cursorY );
  cgs.activeCursor = 0;
  if( n == CURSOR_ARROW )
    cgs.activeCursor = cgs.media.selectCursor;
  else if( n == CURSOR_SIZER )
    cgs.activeCursor = cgs.media.sizeCursor;

  if( cgs.capturedItem )
    Display_MouseMove( cgs.capturedItem, x, y );
  else
    Display_MouseMove( NULL, cgs.cursorX, cgs.cursorY );
}

/*
==================
CG_HideTeamMenus
==================

*/
void CG_HideTeamMenu( void )
{
  Menus_CloseByName( "teamMenu" );
  Menus_CloseByName( "getMenu" );
}

/*
==================
CG_ShowTeamMenus
==================

*/
void CG_ShowTeamMenu( void )
{
  Menus_ActivateByName( "teamMenu" );
}

/*
==================
CG_EventHandling

type 0 - no event handling
     1 - team menu
     2 - hud editor
==================
*/
void CG_EventHandling( int type )
{
  cgs.eventHandling = type;

  if( type == CGAME_EVENT_NONE )
    CG_HideTeamMenu( );
}



void CG_KeyEvent( int key, qboolean down )
{
  if( !down )
    return;

  if( cg.predictedPlayerState.pm_type == PM_NORMAL ||
      ( cg.predictedPlayerState.pm_type == PM_SPECTATOR &&
        cg.showScores == qfalse ) )
  {
    CG_EventHandling( CGAME_EVENT_NONE );
    trap_Key_SetCatcher( 0 );
    return;
  }

  Display_HandleKey( key, down, cgs.cursorX, cgs.cursorY );

  if( cgs.capturedItem )
    cgs.capturedItem = NULL;
  else
  {
    if( key == K_MOUSE2 && down )
      cgs.capturedItem = Display_CaptureItem( cgs.cursorX, cgs.cursorY );
  }
}

int CG_ClientNumFromName( const char *p )
{
  int i;

  for( i = 0; i < cgs.maxclients; i++ )
  {
    if( cgs.clientinfo[ i ].infoValid &&
        Q_stricmp( cgs.clientinfo[ i ].name, p ) == 0 )
      return i;
  }

  return -1;
}

void CG_RunMenuScript( char **args )
{
}
//END TA UI


/*
================
CG_DrawLighting

================
*/
static void CG_DrawLighting( void )
{
  centity_t   *cent;

  cent = &cg_entities[ cg.snap->ps.clientNum ];

  //fade to black if stamina is low
  if( ( cg.snap->ps.stats[ STAT_STAMINA ] < STAMINA_BLACKOUT_LEVEL ) &&
      ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) )
  {
    vec4_t black = { 0, 0, 0, 0 };
    black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f );
    trap_R_SetColor( black );
    CG_DrawPic( 0, 0, 640, 480, cgs.media.whiteShader );
    trap_R_SetColor( NULL );
  }
  
  if( BG_UpgradeIsActive( UP_NIGHTVISION, cg.predictedPlayerState.stats ) )
    CG_DrawPic( 0, 0, 640, 480, cgs.media.basivisionShader );
}

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

CENTER PRINTING

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


/*
==============
CG_CenterPrint

Called for important messages that should stay in the center of the screen
for a few moments
==============
*/
void CG_CenterPrint( const char *str, int y, int charWidth )
{
  char  *s;
  char newlineParsed[ MAX_STRING_CHARS ];
  const char *wrapped;
  static int maxWidth = (int)( ( 2.0f / 3.0f ) * (float)SCREEN_WIDTH );

  Q_ParseNewlines( newlineParsed, str, sizeof( newlineParsed ) );

  wrapped = Item_Text_Wrap( newlineParsed, 0.5f, maxWidth );

  Q_strncpyz( cg.centerPrint, wrapped, sizeof( cg.centerPrint ) );

  cg.centerPrintTime = cg.time;
  cg.centerPrintY = y;
  cg.centerPrintCharWidth = charWidth;

  // count the number of lines for centering
  cg.centerPrintLines = 1;
  s = cg.centerPrint;
  while( *s )
  {
    if( *s == '\n' )
      cg.centerPrintLines++;

    s++;
  }
}


/*
===================
CG_DrawCenterString
===================
*/
static void CG_DrawCenterString( void )
{
  char  *start;
  int   l;
  int   x, y, w;
  int h;
  float *color;

  if( !cg.centerPrintTime )
    return;

  color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value );
  if( !color )
    return;

  trap_R_SetColor( color );

  start = cg.centerPrint;

  y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2;

  while( 1 )
  {
    char linebuffer[ MAX_STRING_CHARS ];

    for( l = 0; l < sizeof(linebuffer) - 1; l++ )
    {
      if( !start[ l ] || start[ l ] == '\n' )
        break;

      linebuffer[ l ] = start[ l ];
    }

    linebuffer[ l ] = 0;

    w = UI_Text_Width( linebuffer, 0.5 );
    h = UI_Text_Height( linebuffer, 0.5 );
    x = ( SCREEN_WIDTH - w ) / 2;
    UI_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE );
    y += h + 6;

    while( *start && ( *start != '\n' ) )
      start++;

    if( !*start )
      break;

    start++;
  }

  trap_R_SetColor( NULL );
}





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

//FIXME: both vote notes are hardcoded, change to ownerdrawn?

/*
=================
CG_DrawVote
=================
*/
static void CG_DrawVote( team_t team )
{
  char    *s;
  int     sec;
  int     offset = 0;
  vec4_t  white = { 1.0f, 1.0f, 1.0f, 1.0f };
  char    yeskey[ 32 ] = "", nokey[ 32 ] = "";

  if( !cgs.voteTime[ team ] )
    return;

  // play a talk beep whenever it is modified
  if( cgs.voteModified[ team ] )
  {
    cgs.voteModified[ team ] = qfalse;
    trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
  }

  sec = ( VOTE_TIME - ( cg.time - cgs.voteTime[ team ] ) ) / 1000;

  if( sec < 0 )
    sec = 0;

  if( cg_tutorial.integer )
  {
    Com_sprintf( yeskey, sizeof( yeskey ), "[%s]", 
      CG_KeyBinding( va( "%svote yes", team == TEAM_NONE ? "" : "team" ) ) );
    Com_sprintf( nokey, sizeof( nokey ), "[%s]", 
      CG_KeyBinding( va( "%svote no", team == TEAM_NONE ? "" : "team" ) ) );
  }

  if( team != TEAM_NONE )
    offset = 80;

  s = va( "%sVOTE(%i): %s", 
    team == TEAM_NONE ? "" : "TEAM", sec, cgs.voteString[ team ] );

  UI_Text_Paint( 8, 300 + offset, 0.3f, white, s, 0, 0,
    ITEM_TEXTSTYLE_NORMAL );

  s = va( "  Called by: \"%s\"", cgs.voteCaller[ team ] );

  UI_Text_Paint( 8, 320 + offset, 0.3f, white, s, 0, 0,
    ITEM_TEXTSTYLE_NORMAL );

  s = va( "  %sYes:%i %sNo:%i",
    yeskey, cgs.voteYes[ team ], nokey, cgs.voteNo[ team ] );

  UI_Text_Paint( 8, 340 + offset, 0.3f, white, s, 0, 0,
    ITEM_TEXTSTYLE_NORMAL );
}


static qboolean CG_DrawScoreboard( void )
{
  static qboolean firstTime = qtrue;
  float fade, *fadeColor;

  if( menuScoreboard )
    menuScoreboard->window.flags &= ~WINDOW_FORCED;

  if( cg_paused.integer )
  {
    cg.deferredPlayerLoading = 0;
    firstTime = qtrue;
    return qfalse;
  }

  if( cg.showScores ||
      cg.predictedPlayerState.pm_type == PM_INTERMISSION )
  {
    fade = 1.0;
    fadeColor = colorWhite;
  }
  else
  {
    cg.deferredPlayerLoading = 0;
    cg.killerName[ 0 ] = 0;
    firstTime = qtrue;
    return qfalse;
  }


  if( menuScoreboard == NULL )
    menuScoreboard = Menus_FindByName( "teamscore_menu" );

  if( menuScoreboard )
  {
    if( firstTime )
    {
      cg.spectatorTime = trap_Milliseconds();
      CG_SetScoreSelection( menuScoreboard );
      firstTime = qfalse;
    }

    Menu_Update( menuScoreboard );
    Menu_Paint( menuScoreboard, qtrue );
  }

  return qtrue;
}

/*
=================
CG_DrawIntermission
=================
*/
static void CG_DrawIntermission( void )
{
  menuDef_t *menu = Menus_FindByName( "default_hud" );

  Menu_Update( menu );
  Menu_Paint( menu, qtrue );

  cg.scoreFadeTime = cg.time;
  cg.scoreBoardShowing = CG_DrawScoreboard( );
}

/*
=================
CG_DrawQueue
=================
*/
static qboolean CG_DrawQueue( void )
{
  float       w;
  vec4_t      color;
  int         position;
  char        *ordinal, buffer[ MAX_STRING_CHARS ];

  if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) )
    return qfalse;

  color[ 0 ] = 1;
  color[ 1 ] = 1;
  color[ 2 ] = 1;
  color[ 3 ] = 1;

  position = cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1;
  if( position < 1 )
    return qfalse;

  switch( position % 100 )
  {
    case 11:
    case 12:
    case 13:
      ordinal = "th";
      break;
    default:
      switch( position % 10 )
      {
        case 1:  ordinal = "st"; break;
        case 2:  ordinal = "nd"; break;
        case 3:  ordinal = "rd"; break;
        default: ordinal = "th"; break;
      }
      break;
  }

  Com_sprintf( buffer, MAX_STRING_CHARS, "You are %d%s in the spawn queue",
               position, ordinal );

  w = UI_Text_Width( buffer, 0.7f );
  UI_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );

  if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 0 )
    Com_sprintf( buffer, MAX_STRING_CHARS, "There are no spawns remaining" );
  else if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 1 )
    Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining" );
  else
    Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining",
                 cg.snap->ps.persistant[ PERS_SPAWNS ] );

  w = UI_Text_Width( buffer, 0.7f );
  UI_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );

  return qtrue;
}


/*
=================
CG_DrawWarmup
=================
*/
static void CG_DrawWarmup( void )
{
  int    sec = 0;
  int    w;
  int    h;
  float  size = 0.5f;
  char   text[ MAX_STRING_CHARS ] = "Warmup Time:";

  if( !cg.warmupTime )
    return;

  sec = ( cg.warmupTime - cg.time ) / 1000;

  if( sec < 0 )
    return;

  w = UI_Text_Width( text, size );
  h = UI_Text_Height( text, size );
  UI_Text_Paint( 320 - w / 2, 200, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED );

  Com_sprintf( text, sizeof( text ), "%s", sec ? va( "%d", sec ) : "FIGHT!" );

  w = UI_Text_Width( text, size );
  UI_Text_Paint( 320 - w / 2, 200 + 1.5f * h, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
}

/*
======================
CG_DrawFuel
======================
*/
void CG_DrawFuel( void )
{
  int i;
  float x, y, w, h, scale, fuel;
  vec4_t color;
  char text[20];
  qboolean active;
 
 
  if( !BG_InventoryContainsUpgrade( UP_JETPACK, cg.predictedPlayerState.stats ) )
    return;
 
  active = BG_UpgradeIsActive( UP_JETPACK, cg.predictedPlayerState.stats ) ||
           cg.predictedPlayerEntity.jetPackJumpTime + 100 > cg.time;
 
  fuel = (float)cg.predictedPlayerState.stats[ STAT_FUEL ] / JETPACK_FUEL_FULL;
  fuel = ceil( fuel * 100 );
 
  Com_sprintf( text, sizeof(text), "%.0f%%", fuel );
 
  scale = cg_fuelInfoScale.value;
 
  w = UI_Text_Width( text, scale );
  h = UI_Text_Height( text, scale );
  x = 320.0f - w / 2.0f + cg_fuelInfoX.value;
  y = 240.0f - h / 2.0f + cg_fuelInfoY.value;
 
  for( i = 0; i < 3; i++ )
    color[ i ] = ( active ? 1 : 0.5 );
  color[ 3 ] = 1.0f;
 
  UI_Text_Paint( x, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE );
}

/*
======================
CG_DrawSplash
======================
*/
void CG_DrawSplash( void )
{
  float t, x, y, w, h, ar;
  vec4_t color;
  int i;
  static qboolean first = qtrue;
  
  if( first )
  {
    cg.splashTime = cg.time;
    first = qfalse;
  }
 
  if( cg.splashTime + 4000 < cg.time )
    return;

  t = (float)( cg.time - cg.splashTime ) / 1000;

  ar = ( 4.0f / 3.0f ) / ( (float)cgs.glconfig.vidWidth / cgs.glconfig.vidHeight );
  h = 40;
  w = h * ar;
  x = 320 - w/2;
  y = 240 - h/2;

  for( i = 0; i < 3; i++ )
    color[ i ] = 1.0f;   
  
  if( t > 1.0f )
  {
    float tw, th, ts;
    
    if( t < 2.0f )
     color[ 3 ] = t - 1.0f;
    else if ( t > 3.5f )
     color[ 3 ] = ( 3.5f - t ) * 2;
    else
     color[ 3 ] = 1;
  
    trap_R_SetColor( color );

    ts = 0.33f;
    tw = UI_Text_Width( SPLASH_VERSION, ts );
    th = UI_Text_Height( SPLASH_VERSION, ts );    
    UI_Text_Paint( x + w/2 - tw/2, y + h + th, ts, color, SPLASH_VERSION, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE );
  }
  
  color[ 3 ] = ( t > 3.0f ) ? 4.0f - t : 1.0f;
  
  trap_R_SetColor( color );
  
  CG_DrawPic( x, y, w, h, cgs.media.splashLogo[ cg.time / 25 % 15 ] );

  for( i = 0; i < 3; i++ )
    color[ i ] = 0.7f;
  
  trap_R_SetColor( color );
  
  x += w;
  if( t < 1.0f )
    x += ( 1.0f - t ) * 400.0f * ar;
  w *= 251.0f / 181.0f;
  CG_DrawPic( x, y, w, h, cgs.media.splashRight );
  

  w = h * ar;
  x = 320 - w/2;
  if( t < 1.0f ) 
   x -= ( 1.0f - t ) * 400.0f * ar;
  w *= 512.0f / 181.0f;
  x -= w;
 
  CG_DrawPic( x, y, w, h, cgs.media.splashLeft );
  
  trap_R_SetColor( NULL );
}

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


/*
=================
CG_Draw2D
=================
*/
static void CG_Draw2D( void )
{
  menuDef_t *menu = NULL;

  // if we are taking a levelshot for the menu, don't draw anything
  if( cg.levelShot )
    return;

  // fading to black if stamina runs out
  // (only 2D that can't be disabled)
  CG_DrawLighting( );

  if( cg_draw2D.integer == 0 )
    return;

  if( cg.snap->ps.pm_type == PM_INTERMISSION )
  {
    CG_DrawIntermission( );
    return;
  }

  if( cg.snap->ps.persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT &&
      cg.snap->ps.stats[ STAT_HEALTH ] > 0 )
  {
    menu = Menus_FindByName( BG_ClassConfig(
      cg.predictedPlayerState.stats[ STAT_CLASS ] )->hudName );

    CG_DrawBuildableStatus( );
    CG_Cuboid_DrawInfo( );
    CG_DrawFuel( );
  }

  if( !menu )
  {
    menu = Menus_FindByName( "default_hud" );

    if( !menu ) // still couldn't find it
      CG_Error( "Default HUD could not be found" );
  }

  Menu_Update( menu );
  Menu_Paint( menu, qtrue );

  CG_DrawVote( TEAM_NONE );
  CG_DrawVote( cg.predictedPlayerState.stats[ STAT_TEAM ] );
  CG_DrawWarmup( );
  CG_DrawQueue( );

  // don't draw center string if scoreboard is up
  cg.scoreBoardShowing = CG_DrawScoreboard( );

  if( !cg.scoreBoardShowing )
    CG_DrawCenterString( );

  CG_DrawSplash( );
}

/*
===============
CG_ScalePainBlendTCs
===============
*/
static void CG_ScalePainBlendTCs( float* s1, float *t1, float *s2, float *t2 )
{
  *s1 -= 0.5f;
  *t1 -= 0.5f;
  *s2 -= 0.5f;
  *t2 -= 0.5f;

  *s1 *= cg_painBlendZoom.value;
  *t1 *= cg_painBlendZoom.value;
  *s2 *= cg_painBlendZoom.value;
  *t2 *= cg_painBlendZoom.value;

  *s1 += 0.5f;
  *t1 += 0.5f;
  *s2 += 0.5f;
  *t2 += 0.5f;
}

#define PAINBLEND_BORDER    0.15f

/*
===============
CG_PainBlend
===============
*/
static void CG_PainBlend( void )
{
  vec4_t      color;
  int         damage;
  float       damageAsFracOfMax;
  qhandle_t   shader = cgs.media.viewBloodShader;
  float       x, y, w, h;
  float       s1, t1, s2, t2;

  if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || cg.intermissionStarted )
    return;

  damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ];

  if( damage < 0 )
    damage = 0;

  damageAsFracOfMax = (float)damage / cg.snap->ps.stats[ STAT_MAX_HEALTH ];
  cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ];

  cg.painBlendValue += damageAsFracOfMax * cg_painBlendScale.value;

  if( cg.painBlendValue > 0.0f )
  {
    cg.painBlendValue -= ( cg.frametime / 1000.0f ) *
      cg_painBlendDownRate.value;
  }

  if( cg.painBlendValue > 1.0f )
    cg.painBlendValue = 1.0f;
  else if( cg.painBlendValue <= 0.0f )
  {
    cg.painBlendValue = 0.0f;
    return;
  }

  if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )
    VectorSet( color, 0.43f, 0.8f, 0.37f );
  else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
    VectorSet( color, 0.8f, 0.0f, 0.0f );

  if( cg.painBlendValue > cg.painBlendTarget )
  {
    cg.painBlendTarget += ( cg.frametime / 1000.0f ) *
      cg_painBlendUpRate.value;
  }
  else if( cg.painBlendValue < cg.painBlendTarget )
    cg.painBlendTarget = cg.painBlendValue;

  if( cg.painBlendTarget > cg_painBlendMax.value )
    cg.painBlendTarget = cg_painBlendMax.value;

  color[ 3 ] = cg.painBlendTarget;

  trap_R_SetColor( color );

  //left
  x = 0.0f; y = 0.0f;
  w = PAINBLEND_BORDER * 640.0f; h = 480.0f;
  CG_AdjustFrom640( &x, &y, &w, &h );
  s1 = 0.0f; t1 = 0.0f;
  s2 = PAINBLEND_BORDER; t2 = 1.0f;
  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );

  //right
  x = 640.0f - ( PAINBLEND_BORDER * 640.0f ); y = 0.0f;
  w = PAINBLEND_BORDER * 640.0f; h = 480.0f;
  CG_AdjustFrom640( &x, &y, &w, &h );
  s1 = 1.0f - PAINBLEND_BORDER; t1 = 0.0f;
  s2 = 1.0f; t2 =1.0f;
  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );

  //top
  x = PAINBLEND_BORDER * 640.0f; y = 0.0f;
  w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f;
  CG_AdjustFrom640( &x, &y, &w, &h );
  s1 = PAINBLEND_BORDER; t1 = 0.0f;
  s2 = 1.0f - PAINBLEND_BORDER; t2 = PAINBLEND_BORDER;
  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );

  //bottom
  x = PAINBLEND_BORDER * 640.0f; y = 480.0f - ( PAINBLEND_BORDER * 480.0f );
  w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f;
  CG_AdjustFrom640( &x, &y, &w, &h );
  s1 = PAINBLEND_BORDER; t1 = 1.0f - PAINBLEND_BORDER;
  s2 = 1.0f - PAINBLEND_BORDER; t2 = 1.0f;
  CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
  trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );

  trap_R_SetColor( NULL );
}

/*
=====================
CG_ResetPainBlend
=====================
*/
void CG_ResetPainBlend( void )
{
  cg.painBlendValue = 0.0f;
  cg.painBlendTarget = 0.0f;
  cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ];
}

/*
=====================
CG_DrawActive

Perform all drawing needed to completely fill the screen
=====================
*/
void CG_DrawActive( stereoFrame_t stereoView )
{
  float   separation;
  vec3_t    baseOrg;

  // optionally draw the info screen instead
  if( !cg.snap )
    return;

  switch ( stereoView )
  {
    case STEREO_CENTER:
      separation = 0;
      break;
    case STEREO_LEFT:
      separation = -cg_stereoSeparation.value / 2;
      break;
    case STEREO_RIGHT:
      separation = cg_stereoSeparation.value / 2;
      break;
    default:
      separation = 0;
      CG_Error( "CG_DrawActive: Undefined stereoView" );
  }

  // clear around the rendered view if sized down
  CG_TileClear( );

  // offset vieworg appropriately if we're doing stereo separation
  VectorCopy( cg.refdef.vieworg, baseOrg );

  if( separation != 0 )
    VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ],
              cg.refdef.vieworg );

  // draw 3D view
  trap_R_RenderScene( &cg.refdef );

  // restore original viewpoint if running stereo
  if( separation != 0 )
    VectorCopy( baseOrg, cg.refdef.vieworg );

  // first person blend blobs, done after AnglesToAxis
  if( !cg.renderingThirdPerson )
    CG_PainBlend( );

  // draw status bar and other floating elements
  CG_Draw2D( );
}

