/*
===========================================================================
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_weapons.c -- events and effects dealing with weapons


#include "cg_local.h"

/*
=================
CG_RegisterUpgrade

The server says this item is used on this level
=================
*/
void CG_RegisterUpgrade( int upgradeNum )
{
  upgradeInfo_t   *upgradeInfo;
  char            *icon;

  if( upgradeNum <= UP_NONE || upgradeNum >= UP_NUM_UPGRADES )
  {
    CG_Error( "CG_RegisterUpgrade: out of range: %d", upgradeNum );
    return;
  }

  upgradeInfo = &cg_upgrades[ upgradeNum ];

  if( upgradeInfo->registered )
  {
    CG_Printf( "CG_RegisterUpgrade: already registered: (%d) %s\n", upgradeNum,
      BG_Upgrade( upgradeNum )->name );
    return;
  }

  upgradeInfo->registered = qtrue;

  if( !BG_Upgrade( upgradeNum )->name[ 0 ] )
    CG_Error( "Couldn't find upgrade %i", upgradeNum );

  upgradeInfo->humanName = BG_Upgrade( upgradeNum )->humanName;

  //la la la la la, i'm not listening!
  if( upgradeNum == UP_GRENADE )
    upgradeInfo->upgradeIcon = cg_weapons[ WP_GRENADE ].weaponIcon;
  else if( ( icon = BG_Upgrade( upgradeNum )->icon ) )
    upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon );
}

/*
===============
CG_InitUpgrades

Precaches upgrades
===============
*/
void CG_InitUpgrades( void )
{
  int   i;

  Com_Memset( cg_upgrades, 0, sizeof( cg_upgrades ) );

  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
    CG_RegisterUpgrade( i );
}


/*
===============
CG_ParseWeaponModeSection

Parse a weapon mode section
===============
*/
static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p )
{
  char  *token;
  int   i;

  // read optional parameters
  while( 1 )
  {
    token = COM_Parse( text_p );

    if( !token )
      break;

    if( !Q_stricmp( token, "" ) )
      return qfalse;

    if( !Q_stricmp( token, "missileModel" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileModel = trap_R_RegisterModel( token );

      if( !wim->missileModel )
        CG_Printf( S_COLOR_RED "ERROR: missile model not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "missileSprite" ) )
    {
      int size = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      size = atoi( token );

      if( size < 0 )
        size = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileSprite = trap_R_RegisterShader( token );
      wim->missileSpriteSize = size;
      wim->usesSpriteMissle = qtrue;

      if( !wim->missileSprite )
        CG_Printf( S_COLOR_RED "ERROR: missile sprite not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "missileSpriteCharge" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileSpriteCharge = atof( token );

      continue;
    }
    else if( !Q_stricmp( token, "missileRotates" ) )
    {
      wim->missileRotates = qtrue;

      continue;
    }
    else if( !Q_stricmp( token, "missileAnimates" ) )
    {
      wim->missileAnimates = qtrue;

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileAnimStartFrame = atoi( token );

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileAnimNumFrames = atoi( token );

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileAnimFrameRate = atoi( token );

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileAnimLooping = atoi( token );

      continue;
    }
    else if( !Q_stricmp( token, "missileParticleSystem" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileParticleSystem = CG_RegisterParticleSystem( token );

      if( !wim->missileParticleSystem )
        CG_Printf( S_COLOR_RED "ERROR: missile particle system not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "missileTrailSystem" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileTrailSystem = CG_RegisterTrailSystem( token );

      if( !wim->missileTrailSystem )
        CG_Printf( S_COLOR_RED "ERROR: missile trail system not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "muzzleParticleSystem" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->muzzleParticleSystem = CG_RegisterParticleSystem( token );

      if( !wim->muzzleParticleSystem )
        CG_Printf( S_COLOR_RED "ERROR: muzzle particle system not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "impactParticleSystem" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->impactParticleSystem = CG_RegisterParticleSystem( token );

      if( !wim->impactParticleSystem )
        CG_Printf( S_COLOR_RED "ERROR: impact particle system not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "impactMark" ) )
    {
      int size = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      size = atoi( token );

      if( size < 0 )
        size = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->impactMark = trap_R_RegisterShader( token );
      wim->impactMarkSize = size;

      if( !wim->impactMark )
        CG_Printf( S_COLOR_RED "ERROR: impact mark shader not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "impactSound" ) )
    {
      int index = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      index = atoi( token );

      if( index < 0 )
        index = 0;
      else if( index > 3 )
        index = 3;

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->impactSound[ index ] = trap_S_RegisterSound( token, qfalse );

      continue;
    }
    else if( !Q_stricmp( token, "impactFleshSound" ) )
    {
      int index = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      index = atoi( token );

      if( index < 0 )
        index = 0;
      else if( index > 3 )
        index = 3;

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->impactFleshSound[ index ] = trap_S_RegisterSound( token, qfalse );

      continue;
    }
    else if( !Q_stricmp( token, "alwaysImpact" ) )
    {
      wim->alwaysImpact = qtrue;

      continue;
    }
    else if( !Q_stricmp( token, "flashDLightColor" ) )
    {
      for( i = 0 ; i < 3 ; i++ )
      {
        token = COM_Parse( text_p );
        if( !token )
          break;

        wim->flashDlightColor[ i ] = atof( token );
      }

      continue;
    }
    else if( !Q_stricmp( token, "continuousFlash" ) )
    {
      wim->continuousFlash = qtrue;

      continue;
    }
    else if( !Q_stricmp( token, "missileDlightColor" ) )
    {
      for( i = 0 ; i < 3 ; i++ )
      {
        token = COM_Parse( text_p );
        if( !token )
          break;

        wim->missileDlightColor[ i ] = atof( token );
      }

      continue;
    }
    else if( !Q_stricmp( token, "missileDlight" ) )
    {
      int size = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      size = atoi( token );

      if( size < 0 )
        size = 0;

      wim->missileDlight = size;

      continue;
    }
    else if( !Q_stricmp( token, "firingSound" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->firingSound = trap_S_RegisterSound( token, qfalse );

      continue;
    }
    else if( !Q_stricmp( token, "missileSound" ) )
    {
      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->missileSound = trap_S_RegisterSound( token, qfalse );

      continue;
    }
    else if( !Q_stricmp( token, "flashSound" ) )
    {
      int index = 0;

      token = COM_Parse( text_p );
      if( !token )
        break;

      index = atoi( token );

      if( index < 0 )
        index = 0;
      else if( index > 3 )
        index = 3;

      token = COM_Parse( text_p );
      if( !token )
        break;

      wim->flashSound[ index ] = trap_S_RegisterSound( token, qfalse );

      continue;
    }
    else if( !Q_stricmp( token, "impactMarkAlphaFade" ) )
    {
      wim->impactMarkAlphaFade = qtrue;
      continue;
    }
    else if( !Q_stricmp( token, "}" ) )
      return qtrue; //reached the end of this weapon section
    else
    {
      CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in weapon section\n", token );
      return qfalse;
    }
  }

  return qfalse;
}


/*
======================
CG_ParseWeaponFile

Parses a configuration file describing a weapon
======================
*/
static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi )
{
  char          *text_p;
  int           len;
  char          *token;
  char          text[ 20000 ];
  fileHandle_t  f;
  weaponMode_t  weaponMode = WPM_NONE;

  // load the file
  len = trap_FS_FOpenFile( filename, &f, FS_READ );
  if( len < 0 )
    return qfalse;

  if( len == 0 || len >= sizeof( text ) - 1 )
  {
    trap_FS_FCloseFile( f );
    CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" );
    return qfalse;
  }

  trap_FS_Read( text, len, f );
  text[ len ] = 0;
  trap_FS_FCloseFile( f );

  // parse the text
  text_p = text;

  // read optional parameters
  while( 1 )
  {
    token = COM_Parse( &text_p );

    if( !token )
      break;

    if( !Q_stricmp( token, "" ) )
      break;

    if( !Q_stricmp( token, "{" ) )
    {
      if( weaponMode == WPM_NONE )
      {
        CG_Printf( S_COLOR_RED "ERROR: weapon mode section started without a declaration\n" );
        return qfalse;
      }
      else if( !CG_ParseWeaponModeSection( &wi->wim[ weaponMode ], &text_p ) )
      {
        CG_Printf( S_COLOR_RED "ERROR: failed to parse weapon mode section\n" );
        return qfalse;
      }

      //start parsing ejectors again
      weaponMode = WPM_NONE;

      continue;
    }
    else if( !Q_stricmp( token, "primary" ) )
    {
      weaponMode = WPM_PRIMARY;
      continue;
    }
    else if( !Q_stricmp( token, "secondary" ) )
    {
      weaponMode = WPM_SECONDARY;
      continue;
    }
    else if( !Q_stricmp( token, "tertiary" ) )
    {
      weaponMode = WPM_TERTIARY;
      continue;
    }
    else if( !Q_stricmp( token, "weaponModel" ) )
    {
      char path[ MAX_QPATH ];

      token = COM_Parse( &text_p );
      if( !token )
        break;

      wi->weaponModel = trap_R_RegisterModel( token );

      if( !wi->weaponModel )
        CG_Printf( S_COLOR_RED "ERROR: weapon model not found %s\n", token );

      strcpy( path, token );
      COM_StripExtension( path, path, MAX_QPATH );
      strcat( path, "_flash.md3" );
      wi->flashModel = trap_R_RegisterModel( path );

      strcpy( path, token );
      COM_StripExtension( path, path, MAX_QPATH );
      strcat( path, "_barrel.md3" );
      wi->barrelModel = trap_R_RegisterModel( path );

      strcpy( path, token );
      COM_StripExtension( path, path, MAX_QPATH );
      strcat( path, "_hand.md3" );
      wi->handsModel = trap_R_RegisterModel( path );

      continue;
    }
    else if( !Q_stricmp( token, "weaponModel3rdPerson" ) )
    {
      char path[ MAX_QPATH ];

      token = COM_Parse( &text_p );
      if( !token )
        break;

      wi->weaponModel3rdPerson = trap_R_RegisterModel( token );

      if( !wi->weaponModel3rdPerson )
      {
        CG_Printf( S_COLOR_RED "ERROR: 3rd person weapon "
            "model not found %s\n", token );
      }

      strcpy( path, token );
      COM_StripExtension( path, path, MAX_QPATH );
      strcat( path, "_flash.md3" );
      wi->flashModel3rdPerson = trap_R_RegisterModel( path );

      strcpy( path, token );
      COM_StripExtension( path, path, MAX_QPATH );
      strcat( path, "_barrel.md3" );
      wi->barrelModel3rdPerson = trap_R_RegisterModel( path );

      continue;
    }
    else if( !Q_stricmp( token, "idleSound" ) )
    {
      token = COM_Parse( &text_p );
      if( !token )
        break;

      wi->readySound = trap_S_RegisterSound( token, qfalse );

      continue;
    }
    else if( !Q_stricmp( token, "icon" ) )
    {
      token = COM_Parse( &text_p );
      if( !token )
        break;

      wi->weaponIcon = wi->ammoIcon = trap_R_RegisterShader( token );

      if( !wi->weaponIcon )
        CG_Printf( S_COLOR_RED "ERROR: weapon icon not found %s\n", token );

      continue;
    }
    else if( !Q_stricmp( token, "crosshair" ) )
    {
      int size = 0;

      token = COM_Parse( &text_p );
      if( !token )
        break;

      size = atoi( token );

      if( size < 0 )
        size = 0;

      wi->crossHairSize = size;

      token = COM_Parse( &text_p );
      if( !token )
        break;

      if( !Q_stricmp( token, "none" ) )
        wi->crossHairType = CH_NONE;
      else if( !Q_stricmp( token, "dot" ) )
        wi->crossHairType = CH_DOT;
      else if( !Q_stricmp( token, "circle" ) )
        wi->crossHairType = CH_CIRCLE;
      else if( !Q_stricmp( token, "circleddot" ) )
        wi->crossHairType = CH_CIRCLEDDOT;
      else if( !Q_stricmp( token, "alien" ) )
        wi->crossHairType = CH_ALIEN;
      else
      {
        Com_Printf( S_COLOR_YELLOW "WARNING: unknown cross hair type '%s'\n", token );
        wi->crossHairType = CH_NONE;
      }

      continue;
    }
    else if( !Q_stricmp( token, "disableIn3rdPerson" ) )
    {
      wi->disableIn3rdPerson = qtrue;

      continue;
    }

    Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token );
    return qfalse;
  }

  return qtrue;
}

/*
=================
CG_RegisterWeapon
=================
*/
void CG_RegisterWeapon( int weaponNum )
{
  weaponInfo_t  *weaponInfo;
  char          path[ MAX_QPATH ];
  vec3_t        mins, maxs;
  int           i;

  if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS )
  {
    CG_Error( "CG_RegisterWeapon: out of range: %d", weaponNum );
    return;
  }

  weaponInfo = &cg_weapons[ weaponNum ];

  if( weaponInfo->registered )
  {
    CG_Printf( "CG_RegisterWeapon: already registered: (%d) %s\n", weaponNum,
      BG_Weapon( weaponNum )->name );
    return;
  }

  weaponInfo->registered = qtrue;

  if( !BG_Weapon( weaponNum )->name[ 0 ] )
    CG_Error( "Couldn't find weapon %i", weaponNum );

  Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_Weapon( weaponNum )->name );

  weaponInfo->humanName = BG_Weapon( weaponNum )->humanName;

  if( !CG_ParseWeaponFile( path, weaponInfo ) )
    Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path );

  // calc midpoint for rotation
  trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs );
  for( i = 0 ; i < 3 ; i++ )
    weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] );
}

/*
===============
CG_InitWeapons

Precaches weapons
===============
*/
void CG_InitWeapons( void )
{
  int   i;

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

  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
    CG_RegisterWeapon( i );

  cgs.media.level2ZapTS = CG_RegisterTrailSystem( "models/weapons/lev2zap/lightning" );
}


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

VIEW WEAPON

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

/*
===============
CG_SetWeaponLerpFrameAnimation

may include ANIM_TOGGLEBIT
===============
*/
static void CG_SetWeaponLerpFrameAnimation( weapon_t weapon, lerpFrame_t *lf, int newAnimation )
{
  animation_t *anim;

  lf->animationNumber = newAnimation;
  newAnimation &= ~ANIM_TOGGLEBIT;

  if( newAnimation < 0 || newAnimation >= MAX_WEAPON_ANIMATIONS )
    CG_Error( "Bad animation number: %i", newAnimation );

  anim = &cg_weapons[ weapon ].animations[ newAnimation ];

  lf->animation = anim;
  lf->animationTime = lf->frameTime + anim->initialLerp;

  if( cg_debugAnim.integer )
    CG_Printf( "Anim: %i\n", newAnimation );
}

/*
===============
CG_WeaponAnimation
===============
*/
static void CG_WeaponAnimation( centity_t *cent, int *old, int *now, float *backLerp )
{
  lerpFrame_t   *lf = &cent->pe.weapon;
  entityState_t *es = &cent->currentState;

  // see if the animation sequence is switching
  if( es->weaponAnim != lf->animationNumber || !lf->animation )
    CG_SetWeaponLerpFrameAnimation( es->weapon, lf, es->weaponAnim );

  CG_RunLerpFrame( lf, 1.0f );

  *old      = lf->oldFrame;
  *now      = lf->frame;
  *backLerp = lf->backlerp;
}

/*
=================
CG_MapTorsoToWeaponFrame

=================
*/
static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame )
{

  // change weapon
  if( frame >= ci->animations[ TORSO_DROP ].firstFrame &&
      frame < ci->animations[ TORSO_DROP ].firstFrame + 9 )
    return frame - ci->animations[ TORSO_DROP ].firstFrame + 6;

  // stand attack
  if( frame >= ci->animations[ TORSO_ATTACK ].firstFrame &&
      frame < ci->animations[ TORSO_ATTACK ].firstFrame + 6 )
    return 1 + frame - ci->animations[ TORSO_ATTACK ].firstFrame;

  // stand attack 2
  if( frame >= ci->animations[ TORSO_ATTACK2 ].firstFrame &&
      frame < ci->animations[ TORSO_ATTACK2 ].firstFrame + 6 )
    return 1 + frame - ci->animations[ TORSO_ATTACK2 ].firstFrame;

  return 0;
}


/*
==============
CG_CalculateWeaponPosition
==============
*/
static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles )
{
  float         scale;
  int           delta;
  float         fracsin;
  float         bob;
  weaponInfo_t  *weapon;

  weapon = &cg_weapons[ cg.predictedPlayerState.weapon ];

  VectorCopy( cg.refdef.vieworg, origin );
  VectorCopy( cg.refdefViewAngles, angles );

  // on odd legs, invert some angles
  if( cg.bobcycle & 1 )
    scale = -cg.xyspeed;
  else
    scale = cg.xyspeed;

  // gun angles from bobbing
  // bob amount is class dependant
  bob = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob;

  if( bob != 0 )
  {
    angles[ ROLL ] += scale * cg.bobfracsin * 0.005;
    angles[ YAW ] += scale * cg.bobfracsin * 0.01;
    angles[ PITCH ] += cg.xyspeed * cg.bobfracsin * 0.005;
  }

  // drop the weapon when landing
  if( !weapon->noDrift )
  {
    delta = cg.time - cg.landTime;
    if( delta < LAND_DEFLECT_TIME )
      origin[ 2 ] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME;
    else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME )
      origin[ 2 ] += cg.landChange*0.25 *
        ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME;

    // idle drift
    scale = cg.xyspeed + 40;
    fracsin = sin( cg.time * 0.001 );
    angles[ ROLL ] += scale * fracsin * 0.01;
    angles[ YAW ] += scale * fracsin * 0.01;
    angles[ PITCH ] += scale * fracsin * 0.01;
  }
}


/*
======================
CG_MachinegunSpinAngle
======================
*/
#define   SPIN_SPEED  0.9
#define   COAST_TIME  1000
static float CG_MachinegunSpinAngle( centity_t *cent, qboolean firing )
{
  int   delta;
  float angle;
  float speed;

  delta = cg.time - cent->pe.barrelTime;
  if( cent->pe.barrelSpinning )
    angle = cent->pe.barrelAngle + delta * SPIN_SPEED;
  else
  {
    if( delta > COAST_TIME )
      delta = COAST_TIME;

    speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
    angle = cent->pe.barrelAngle + delta * speed;
  }

  if( cent->pe.barrelSpinning == !firing )
  {
    cent->pe.barrelTime = cg.time;
    cent->pe.barrelAngle = AngleMod( angle );
    cent->pe.barrelSpinning = firing;
  }

  return angle;
}

/*
=============
CG_GenerateCKitDisplay

Figures out what should be displayed on the CKit's display
=============
*/
static void CG_GenerateCKitDisplay( void )
{
  int row = 0;
  ckitDisplay_t *cd = &cg.ckitDisp;
  entityState_t *es;
  playerState_t *ps = &cg.predictedPlayerState;
  qboolean target = qfalse; // true if looking at something
  qboolean player = qfalse; // true if looking at a player (buildable otherwise)
  int buildable; // buildable type
  qboolean probing = qfalse; // measures voltages and currents
  int powerNetwork;
  int probeEntity;
  qboolean building; // displays buildable's info
  qboolean cuboid; // true if building cuboid
  const buildableAttributes_t *battr;
  int buildTimer;
  
  memset( &cg.ckitDisp, 0, sizeof( cg.ckitDisp ) );

  /* 
     part 1: figure out what we're dealing with
  */
  
  powerNetwork = ps->misc[ MISC_POWER_NETWORK ] & 1023;
  
  probeEntity =  ps->misc[ MISC_PROBE_ENTITY ] & 1023;
  if( probeEntity != 1023 )
  {
    target = qtrue;
    if( probeEntity < MAX_CLIENTS )
    {
      target = qtrue;
      player = qtrue;
      probing = ( cg.crosshairClientNum == probeEntity );
    }
    else 
      probing = ( cg.crosshairBuildable == probeEntity );
      
    es = &cg_entities[ probeEntity ].currentState;
  }
  
  buildable = cg.predictedPlayerState.stats[ STAT_BUILDABLE ] &~ SB_VALID_TOGGLEBIT;
  building = ( buildable != BA_NONE );
  if( building )
  {
    battr = BG_Buildable( buildable, cg.cuboidSelection );
    cuboid = battr->cuboid;
  }
  
  buildTimer = ps->stats[ STAT_MISC ];
  
  /* 
     part 2: set up icons & text 
  */
  
  cd->background = cgs.media.ckitBackgroundShader;
  
  // first big icon indicates if power is available
  if( powerNetwork )
    cd->bigicona = cgs.media.ckit_icon_power;
  else
    cd->bigicona = cgs.media.ckit_icon_nopower;
  
  // second big icon always shows the building's state
  if( target && !player )
    cd->bigiconb = CG_BuildablePowerStatusIcon( es );
  
  if( building )
  {
    if( cuboid )
    {
      //width
      cd->lines[ row ].icon = cgs.media.ckit_icon_width;
      Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.1f", cg.cuboidSelection[ 0 ] );
      
      //height
      cd->lines[ row ].icon = cgs.media.ckit_icon_depth;
      Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.1f", cg.cuboidSelection[ 1 ] );
      
      //depth
      cd->lines[ row ].icon = cgs.media.ckit_icon_height;
      Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.1f", cg.cuboidSelection[ 2 ] );
    }

    //build points
    cd->lines[ row ].icon = cgs.media.ckit_icon_bp;
    Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", battr->buildPoints );
    
    //health
    cd->lines[ row ].icon = cgs.media.ckit_icon_health;
    Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", battr->health );
    
    //build time
    cd->lines[ row ].icon = cgs.media.ckit_icon_time;
    Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.1f", 0.001f * battr->buildTime );
  }
  else
  {
    if( buildTimer )
    {
      //build timer
      cd->lines[ row ].icon = cgs.media.ckit_icon_time;
      Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.1f", 0.001f * buildTimer );
    }
    
    // power network detector
    cd->lines[ row ].icon = cgs.media.ckit_icon_network;
    if( powerNetwork )
      Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", powerNetwork );
    else
      Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "--------------------" );

    
    if( target )
    {
      battr = BG_Buildable( es->modelindex, es->angles );
      
      if( probing )
      {
        if( !player )
        {
          // ammeter
          cd->lines[ row ].icon = cgs.media.ckit_icon_current;
          Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.2f", 0.001f * ps->misc[ MISC_INFO1 ] );
        
          // voltmeter
          cd->lines[ row ].icon = cgs.media.ckit_icon_voltage;
          Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.2f", 0.001f * ps->misc[ MISC_INFO2 ] );

          // stored BP
          if( BG_Buildable( es->modelindex, NULL )->hasStorage )
          {
            cd->lines[ row ].icon = cgs.media.ckit_icon_storedbp;
            Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%.1f", 0.1f * ps->misc[ MISC_INFO3 ] );
          }
        }
        else
        {
          // health
          cd->lines[ row ].icon = cgs.media.ckit_icon_health;
          Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", ps->misc[ MISC_INFO1 ] );
                    
          // stored BP
          if( ps->misc[ MISC_INFO3 ] != -1 )
          {
            cd->lines[ row ].icon = cgs.media.ckit_icon_storedbp;
            Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", ps->misc[ MISC_INFO2 ] );
          }
        }
      }
      
      if( !player )
      {
        //health
        cd->lines[ row ].icon = cgs.media.ckit_icon_health;
        if( battr->cuboid )
          Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", es->constantLight );
        else
          Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", es->generic1 );

        //build points
        cd->lines[ row ].icon = cgs.media.ckit_icon_bp;
        Com_sprintf( cd->lines[ row++ ].text, MAX_CKIT_TEXT, "%i", battr->buildPoints );
      }
    }
  }
}

/*
=============
CG_AddPlayerWeapon

Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
The main player will have this called for BOTH cases, so effects like light and
sound should only be done on the world model case.
=============
*/
void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent )
{
  refEntity_t   gun;
  refEntity_t   barrel;
  refEntity_t   flash;
  vec3_t        angles;
  weapon_t      weaponNum;
  weaponMode_t  weaponMode;
  weaponInfo_t  *weapon;
  qboolean      noGunModel;
  qboolean      firing;

  weaponNum = cent->currentState.weapon;
  weaponMode = cent->currentState.generic1;

  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
    weaponMode = WPM_PRIMARY;

  if( ( ( cent->currentState.eFlags & EF_FIRING ) && weaponMode == WPM_PRIMARY ) ||
      ( ( cent->currentState.eFlags & EF_FIRING2 ) && weaponMode == WPM_SECONDARY ) ||
      ( ( cent->currentState.eFlags & EF_FIRING3 ) && weaponMode == WPM_TERTIARY ) )
    firing = qtrue;
  else
    firing = qfalse;

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

  // add the weapon
  Com_Memset( &gun, 0, sizeof( gun ) );
  Com_Memset( &barrel, 0, sizeof( barrel ) );
  Com_Memset( &flash, 0, sizeof( flash ) );

  VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
  gun.shadowPlane = parent->shadowPlane;
  gun.renderfx = parent->renderfx;

  if( ps )
  {
    gun.shaderRGBA[ 0 ] = 255;
    gun.shaderRGBA[ 1 ] = 255;
    gun.shaderRGBA[ 2 ] = 255;
    gun.shaderRGBA[ 3 ] = 255;

    //set weapon[1/2]Time when respective buttons change state
    if( cg.weapon1Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING ) )
    {
      cg.weapon1Time = cg.time;
      cg.weapon1Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING );
    }

    if( cg.weapon2Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING2 ) )
    {
      cg.weapon2Time = cg.time;
      cg.weapon2Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING2 );
    }

    if( cg.weapon3Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING3 ) )
    {
      cg.weapon3Time = cg.time;
      cg.weapon3Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING3 );
    }
  }

  if( !ps )
  {
    gun.hModel = weapon->weaponModel3rdPerson;

    if( !gun.hModel )
      gun.hModel = weapon->weaponModel;
  }
  else
    gun.hModel = weapon->weaponModel;

  noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel;

  if( !ps )
  {
    // add weapon ready sound
    if( firing && weapon->wim[ weaponMode ].firingSound )
    {
      trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
                              weapon->wim[ weaponMode ].firingSound );
    }
    else if( weapon->readySound )
      trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound );
  }

  // Lucifer cannon charge warning beep
  if( weaponNum == WP_LUCIFER_CANNON &&
      ( cent->currentState.eFlags & EF_WARN_CHARGE ) &&
      cg.snap->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )
  {
    trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
                            vec3_origin, ps ? cgs.media.lCannonWarningSound :
                                              cgs.media.lCannonWarningSound2 );
  }

  if( !noGunModel )
  {
    CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" );
    CG_WeaponAnimation( cent, &gun.oldframe, &gun.frame, &gun.backlerp );

    trap_R_AddRefEntityToScene( &gun );

    if( !ps )
    {
      barrel.hModel = weapon->barrelModel3rdPerson;

      if( !barrel.hModel )
        barrel.hModel = weapon->barrelModel;
    }
    else
      barrel.hModel = weapon->barrelModel;

    // add the spinning barrel
    if( barrel.hModel )
    {
      VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
      barrel.shadowPlane = parent->shadowPlane;
      barrel.renderfx = parent->renderfx;

      angles[ YAW ] = 0;
      angles[ PITCH ] = 0;
      angles[ ROLL ] = CG_MachinegunSpinAngle( cent, firing );
      AnglesToAxis( angles, barrel.axis );

      CG_PositionRotatedEntityOnTag( &barrel, &gun, gun.hModel, "tag_barrel" );

      trap_R_AddRefEntityToScene( &barrel );
    }
  }

  if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
  {
    if( ps || cg.renderingThirdPerson ||
        cent->currentState.number != cg.predictedPlayerState.clientNum )
    {
      if( noGunModel )
        CG_SetAttachmentTag( &cent->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" );
      else
        CG_SetAttachmentTag( &cent->muzzlePS->attachment, gun, gun.hModel, "tag_flash" );
    }

    //if the PS is infinite disable it when not firing
    if( !firing && CG_IsParticleSystemInfinite( cent->muzzlePS ) )
      CG_DestroyParticleSystem( &cent->muzzlePS );
  }

  // NOTE: this thing is full of magic numbers - they were determined by trial and error, do not modify
  if( ps && weaponNum == WP_HBUILD )
  {
    refEntity_t ent;
    vec3_t origin;
    float y;
    int i, j, l;
    
    CG_GenerateCKitDisplay( );
    
    memset( &ent, 0, sizeof( ent ) );
    ent.renderfx = parent->renderfx;
    CG_PositionEntityOnTag( &ent, parent, parent->hModel, "tag_weapon" );
    
    // offset it a bit
    VectorMA( ent.origin, -0.1f, ent.axis[ 0 ], ent.origin );

    // background
    if( ( ent.customShader = cg.ckitDisp.background ) )
    {
      ent.hModel = cgs.media.ckit_background;
      trap_R_AddRefEntityToScene( &ent ); 
    }
    
    // overlay
    ent.hModel = cgs.media.ckit_overlay;
    ent.customShader = cgs.media.ckitOverlayShader;
    trap_R_AddRefEntityToScene( &ent );

    // big icons
    if( ( ent.customShader = cg.ckitDisp.bigicona ) )
    {
      ent.hModel = cgs.media.ckit_bigicona;
      trap_R_AddRefEntityToScene( &ent ); 
    }

    if( ( ent.customShader = cg.ckitDisp.bigiconb ) )
    {
      ent.hModel = cgs.media.ckit_bigiconb;
      trap_R_AddRefEntityToScene( &ent ); 
    }
    
    // backup the origin
    VectorCopy( ent.origin, origin );
    
    // draw all 6 rows
    for( y = 0.0f, i = 0; i < MAX_CKIT_ROWS; i++, y += -0.48f )
    {
      VectorMA( origin, y * 0.42f, ent.axis[ 0 ], ent.origin );
      VectorMA( ent.origin, y, ent.axis[ 2 ], ent.origin );
      
      if( ( ent.customShader = cg.ckitDisp.lines[ i ].icon ) )
      {
        ent.hModel = cgs.media.ckit_icon;
        trap_R_AddRefEntityToScene( &ent );
      }
      
      ent.hModel = cgs.media.ckit_digit;
      l = strlen( cg.ckitDisp.lines[ i ].text );
      
      if( l > MAX_CKIT_COLUMNS )
        l = MAX_CKIT_COLUMNS; 
      
      VectorMA( ent.origin, -0.3f * ( MAX_CKIT_COLUMNS - l - 1 ), ent.axis[ 1 ], ent.origin );
      
      for( j = 0; j < l && j < MAX_CKIT_COLUMNS; j++ )
      {
        int index;
        char ch;
        
        ch = cg.ckitDisp.lines[ i ].text[ j ];
        
        if( ch >= '0' && ch <= '9' )
          index = ch - '0';
        else if( ch == '-' )
          index = 10;
        else if( ch == '.' )
          index = 11;
        else
          index = 0;

        if( random() > 0.995f )
        {
          index += (int)( random() * 11 );
          index %= 11;
        }
        
        ent.customShader = cgs.media.digitalNumberShaders[ index ];
        VectorMA( ent.origin, -0.3f, ent.axis[ 1 ], ent.origin );
        trap_R_AddRefEntityToScene( &ent );
      }
    }
  }

  // add the flash
  if( !weapon->wim[ weaponMode ].continuousFlash || !firing )
  {
    // impulse flash
    if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
      return;
  }

  VectorCopy( parent->lightingOrigin, flash.lightingOrigin );
  flash.shadowPlane = parent->shadowPlane;
  flash.renderfx = parent->renderfx;

  if( !ps )
  {
    flash.hModel = weapon->flashModel3rdPerson;

    if( !flash.hModel )
      flash.hModel = weapon->flashModel;
  }
  else
    flash.hModel = weapon->flashModel;

  if( flash.hModel )
  {
    angles[ YAW ] = 0;
    angles[ PITCH ] = 0;
    angles[ ROLL ] = crandom( ) * 10;
    AnglesToAxis( angles, flash.axis );

    if( noGunModel )
      CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" );
    else
      CG_PositionRotatedEntityOnTag( &flash, &gun, gun.hModel, "tag_flash" );

    trap_R_AddRefEntityToScene( &flash );
  }

  if( ps || cg.renderingThirdPerson ||
      cent->currentState.number != cg.predictedPlayerState.clientNum )
  {
    if( weapon->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger )
    {
      cent->muzzlePS = CG_SpawnNewParticleSystem( weapon->wim[ weaponMode ].muzzleParticleSystem );

      if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
      {
        if( noGunModel )
          CG_SetAttachmentTag( &cent->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" );
        else
          CG_SetAttachmentTag( &cent->muzzlePS->attachment, gun, gun.hModel, "tag_flash" );

        CG_SetAttachmentCent( &cent->muzzlePS->attachment, cent );
        CG_AttachToTag( &cent->muzzlePS->attachment );
      }

      cent->muzzlePsTrigger = qfalse;
    }

    // make a dlight for the flash
    if( weapon->wim[ weaponMode ].flashDlightColor[ 0 ] ||
        weapon->wim[ weaponMode ].flashDlightColor[ 1 ] ||
        weapon->wim[ weaponMode ].flashDlightColor[ 2 ] )
    {
      trap_R_AddLightToScene( flash.origin, 300 + ( rand( ) & 31 ),
          weapon->wim[ weaponMode ].flashDlightColor[ 0 ],
          weapon->wim[ weaponMode ].flashDlightColor[ 1 ],
          weapon->wim[ weaponMode ].flashDlightColor[ 2 ] );
    }
  }
}

/*
==============
CG_AddViewWeapon

Add the weapon, and flash for the player's view
==============
*/

#define WEAPON_CLICK_REPEAT 500

void CG_AddViewWeapon( playerState_t *ps )
{
  refEntity_t   hand;
  centity_t     *cent;
  clientInfo_t  *ci;
  float         fovOffset;
  vec3_t        angles;
  weaponInfo_t  *wi;
  weapon_t      weapon = ps->weapon;
  weaponMode_t  weaponMode = ps->generic1;

  // no weapon carried - can't draw it
  if( weapon == WP_NONE )
    return;

  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
    weaponMode = WPM_PRIMARY;

  wi = &cg_weapons[ weapon ];
  if( !wi->registered )
  {
    Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddViewWeapon: weapon %d (%s) "
        "is not registered\n", weapon, BG_Weapon( weapon )->name );
    return;
  }
  cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum];

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

  if( ps->pm_type == PM_INTERMISSION )
    return;

  // draw a prospective buildable infront of the player
  if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
   CG_GhostBuildable( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, cg.cuboidSelection );

  // no gun if in third person view
  if( cg.renderingThirdPerson )
    return;

  // allow the gun to be completely removed
  if( !cg_drawGun.integer )
  {
    vec3_t origin;

    VectorCopy( cg.refdef.vieworg, origin );
    VectorMA( origin, -8, cg.refdef.viewaxis[ 2 ], origin );

    if( cent->muzzlePS )
      CG_SetAttachmentPoint( &cent->muzzlePS->attachment, origin );

    //check for particle systems
    if( wi->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger )
    {
      cent->muzzlePS = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].muzzleParticleSystem );

      if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
      {
        CG_SetAttachmentPoint( &cent->muzzlePS->attachment, origin );
        CG_SetAttachmentCent( &cent->muzzlePS->attachment, cent );
        CG_AttachToPoint( &cent->muzzlePS->attachment );
      }
      cent->muzzlePsTrigger = qfalse;
    }

    return;
  }

  // don't draw if testing a gun model
  if( cg.testGun )
    return;

  // drop gun lower at higher fov
  if( cg.refdef.fov_y > 90 )
    fovOffset = -0.4 * ( cg.refdef.fov_y - 90 );
  else
    fovOffset = 0;

  Com_Memset( &hand, 0, sizeof( hand ) );

  // set up gun position
  CG_CalculateWeaponPosition( hand.origin, angles );

  VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[ 0 ], hand.origin );
  VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[ 1 ], hand.origin );
  VectorMA( hand.origin, ( cg_gun_z.value + fovOffset ), cg.refdef.viewaxis[ 2 ], hand.origin );

  // Lucifer Cannon vibration effect
  if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 )
  {
    float fraction;

    fraction = (float)ps->stats[ STAT_MISC ] / LCANNON_CHARGE_TIME_MAX;
    VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ],
              hand.origin );
    VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ],
              hand.origin );
  }

  AnglesToAxis( angles, hand.axis );

  // map torso animations to weapon animations
  if( cg_gun_frame.integer )
  {
    // development tool
    hand.frame = hand.oldframe = cg_gun_frame.integer;
    hand.backlerp = 0;
  }
  else
  {
    // get clientinfo for animation map
    ci = &cgs.clientinfo[ cent->currentState.clientNum ];
    hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame );
    hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame );
    hand.backlerp = cent->pe.torso.backlerp;
  }

  hand.hModel = wi->handsModel;
  hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT;

  // add everything onto the hand
  CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity );
}

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

WEAPON SELECTION

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

/*
===============
CG_WeaponSelectable
===============
*/
static qboolean CG_WeaponSelectable( weapon_t weapon )
{
  if( !BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ) )
    return qfalse;

  // needed for basilisk nightvision
  if( BG_Weapon( weapon )->team != TEAM_HUMANS )
    return qfalse;

  return qtrue;
}


/*
===============
CG_UpgradeSelectable
===============
*/
static qboolean CG_UpgradeSelectable( upgrade_t upgrade )
{
  if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) )
    return qfalse;

  return BG_Upgrade( upgrade )->usable;
}


#define ICON_BORDER 4

/*
===================
CG_DrawItemSelect
===================
*/
void CG_DrawItemSelect( rectDef_t *rect, vec4_t color )
{
  int           i;
  float         x = rect->x;
  float         y = rect->y;
  float         width = rect->w;
  float         height = rect->h;
  float         iconWidth;
  float         iconHeight;
  int           items[ 64 ];
  int           colinfo[ 64 ];
  int           numItems = 0, selectedItem = 0;
  int           length;
  qboolean      vertical;
  centity_t *cent;
  playerState_t *ps;

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

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

  if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
  {
    // first make sure that whatever it selected is actually selectable
    if( cg.weaponSelect < 32 )
    {
      if( !CG_WeaponSelectable( cg.weaponSelect ) )
        CG_NextWeapon_f( );
    }
    else
    {
      if( !CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
        CG_NextWeapon_f( );
    }
  }

  // showing weapon select clears pickup item display, but not the blend blob
  cg.itemPickupTime = 0;

  // put all weapons in the items list
  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
  {
    if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) )
      continue;

    if( !ps->ammo && !ps->clips && !BG_Weapon( i )->infiniteAmmo )
      colinfo[ numItems ] = 1;
    else
      colinfo[ numItems ] = 0;

    if( i == cg.weaponSelect )
      selectedItem = numItems;

    if( !cg_weapons[ i ].registered )
    {
      Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: weapon %d (%s) "
  	"is not registered\n", i, BG_Weapon( i )->name );
      continue;
    }
    items[ numItems ] = i;
    numItems++;
  }

  // put all upgrades in the weapons list
  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
  {
    if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) )
      continue;
    colinfo[ numItems ] = 0;
    if( !BG_Upgrade( i )->usable )
      colinfo[ numItems ] = 2;

    if( i == cg.weaponSelect - 32 )
      selectedItem = numItems;

    if( !cg_upgrades[ i ].registered )
    {
      Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: upgrade %d (%s) "
  	"is not registered\n", i, BG_Upgrade( i )->name );
      continue;
    }
    items[ numItems ] = i + 32;
    numItems++;
  }

  // compute the length of the display window and determine orientation
  vertical = height > width;
  if( vertical )
  {
    iconWidth = width * cgDC.aspectScale;
    iconHeight = width;
    length = height / ( width * cgDC.aspectScale );
  }
  else
  {
    iconWidth = height * cgDC.aspectScale;
    iconHeight = height;
    length = width / ( height * cgDC.aspectScale );
  }

  // render icon ring
  for( i = 0; i < length; i++ )
  {
    int item = i - length / 2 + selectedItem;

    if( item < 0 )
      item += length;
    else if( item >= length )
      item -= length;
    if( item >= 0 && item < numItems )
    {
      switch( colinfo[ item ] )
      {
       case 0:
         color = colorCyan;
         break;
       case 1:
         color = colorRed;
         break;
       case 2:
         color = colorMdGrey;
         break;
      }
      color[3] = 0.5;
      trap_R_SetColor( color );

      if( items[ item ] < 32 )
        CG_DrawPic( x, y, iconWidth, iconHeight,
                    cg_weapons[ items[ item ] ].weaponIcon );
      else
        CG_DrawPic( x, y, iconWidth, iconHeight,
                    cg_upgrades[ items[ item ] - 32 ].upgradeIcon );
    }
    if( vertical )
      y += iconHeight;
    else
      x += iconWidth;
  }
  trap_R_SetColor( NULL );
}


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

  color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME );
  if( !color )
    return;

  trap_R_SetColor( color );

  // draw the selected name
  if( cg.weaponSelect < 32 )
  {
    if( cg_weapons[ cg.weaponSelect ].registered &&
        BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) )
    {
      if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) )
      {
        w = UI_Text_Width( name, scale );
        x = rect->x + rect->w / 2;
        UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
      }
    }
  }
  else
  {
    if( cg_upgrades[ cg.weaponSelect - 32 ].registered &&
        BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) )
    {
      if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) )
      {
        w = UI_Text_Width( name, scale );
        x = rect->x + rect->w / 2;
        UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
      }
    }
  }

  trap_R_SetColor( NULL );
}


/*
===============
CG_NextWeapon_f
===============
*/
void CG_NextWeapon_f( void )
{
  int   i;
  int   original;

  if( !cg.snap )
    return;

  if( cg.snap->ps.pm_flags & PMF_FOLLOW )
  {
    trap_SendClientCommand( "followprev\n" );
    return;
  }
  
  if( BG_Buildable(cg.snap->ps.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid )
  {
   CG_CuboidResize(qtrue);
   return;
  }
  
  cg.weaponSelectTime = cg.time;
  original = cg.weaponSelect;

  for( i = 0; i < 64; i++ )
  {
    cg.weaponSelect++;
    if( cg.weaponSelect == 64 )
      cg.weaponSelect = 0;

    if( cg.weaponSelect < 32 )
    {
      if( CG_WeaponSelectable( cg.weaponSelect ) )
        break;
    }
    else
    {
      if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
        break;
    }
  }

  if( i == 64 )
    cg.weaponSelect = original;
}

/*
===============
CG_PrevWeapon_f
===============
*/
void CG_PrevWeapon_f( void )
{
  int   i;
  int   original;

  if( !cg.snap )
    return;

  if( cg.snap->ps.pm_flags & PMF_FOLLOW )
  {
    trap_SendClientCommand( "follownext\n" );
    return;
  }
  
  if( BG_Buildable(cg.snap->ps.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid )
  {
   CG_CuboidResize(qfalse);
   return;
  }

  cg.weaponSelectTime = cg.time;
  original = cg.weaponSelect;

  for( i = 0; i < 64; i++ )
  {
    cg.weaponSelect--;
    if( cg.weaponSelect == -1 )
      cg.weaponSelect = 63;

    if( cg.weaponSelect < 32 )
    {
      if( CG_WeaponSelectable( cg.weaponSelect ) )
        break;
    }
    else
    {
      if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
        break;
    }
  }

  if( i == 64 )
    cg.weaponSelect = original;
}

/*
===============
CG_Weapon_f
===============
*/
void CG_Weapon_f( void )
{
  int   num;

  if( !cg.snap )
    return;

  if( cg.snap->ps.pm_flags & PMF_FOLLOW )
    return;

  num = atoi( CG_Argv( 1 ) );

  if( num < 1 || num > 31 )
    return;

  cg.weaponSelectTime = cg.time;

  if( !BG_InventoryContainsWeapon( num, cg.snap->ps.stats ) )
    return;   // don't have the weapon

  cg.weaponSelect = num;
}


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

WEAPON EVENTS

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

/*
================
CG_FireWeapon

Caused by an EV_FIRE_WEAPON event
================
*/
void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode )
{
  entityState_t     *es;
  int               c;
  weaponInfo_t      *wi;
  weapon_t          weaponNum;

  es = &cent->currentState;

  weaponNum = es->weapon;

  if( weaponNum == WP_NONE )
    return;

  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
    weaponMode = WPM_PRIMARY;

  if( weaponNum >= WP_NUM_WEAPONS )
  {
    CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
    return;
  }

  wi = &cg_weapons[ weaponNum ];

  // mark the entity as muzzle flashing, so when it is added it will
  // append the flash to the weapon model
  cent->muzzleFlashTime = cg.time;

  if( wi->wim[ weaponMode ].muzzleParticleSystem )
  {
    if( !CG_IsParticleSystemValid( &cent->muzzlePS ) ||
        !CG_IsParticleSystemInfinite( cent->muzzlePS ) )
      cent->muzzlePsTrigger = qtrue;
  }

  // play a sound
  for( c = 0; c < 4; c++ )
  {
    if( !wi->wim[ weaponMode ].flashSound[ c ] )
      break;
  }

  if( c > 0 )
  {
    c = rand( ) % c;
    if( wi->wim[ weaponMode ].flashSound[ c ] )
      trap_S_StartSound( NULL, es->number, CHAN_WEAPON, wi->wim[ weaponMode ].flashSound[ c ] );
  }
}


/*
=================
CG_MissileHitWall

Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
=================
*/
void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientNum,
                        vec3_t origin, vec3_t dir, impactSound_t soundType, int charge )
{
  qhandle_t           mark = 0;
  qhandle_t           ps = 0;
  int                 c;
  float               radius = 1.0f;
  weaponInfo_t        *weapon = &cg_weapons[ weaponNum ];

  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
    weaponMode = WPM_PRIMARY;

  mark = weapon->wim[ weaponMode ].impactMark;
  radius = weapon->wim[ weaponMode ].impactMarkSize;
  ps = weapon->wim[ weaponMode ].impactParticleSystem;

  if( soundType == IMPACTSOUND_FLESH )
  {
    //flesh sound
    for( c = 0; c < 4; c++ )
    {
      if( !weapon->wim[ weaponMode ].impactFleshSound[ c ] )
        break;
    }

    if( c > 0 )
    {
      c = rand( ) % c;
      if( weapon->wim[ weaponMode ].impactFleshSound[ c ] )
        trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactFleshSound[ c ] );
    }
  }
  else
  {
    //normal sound
    for( c = 0; c < 4; c++ )
    {
      if( !weapon->wim[ weaponMode ].impactSound[ c ] )
        break;
    }

    if( c > 0 )
    {
      c = rand( ) % c;
      if( weapon->wim[ weaponMode ].impactSound[ c ] )
        trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactSound[ c ] );
    }
  }

  //create impact particle system
  if( ps )
  {
    particleSystem_t *partSystem = CG_SpawnNewParticleSystem( ps );

    if( CG_IsParticleSystemValid( &partSystem ) )
    {
      CG_SetAttachmentPoint( &partSystem->attachment, origin );
      CG_SetParticleSystemNormal( partSystem, dir );
      CG_AttachToPoint( &partSystem->attachment );
      partSystem->charge = charge;
    }
  }

  //
  // impact mark
  //
  if( radius > 0.0f )
    CG_ImpactMark( mark, origin, dir, random( ) * 360, 1, 1, 1, 1, 
                   weapon->wim[ weaponMode ].impactMarkAlphaFade, radius, qfalse );
}


/*
=================
CG_MissileHitEntity
=================
*/
void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode,
    vec3_t origin, vec3_t dir, int entityNum, int charge )
{
  vec3_t        normal;
  weaponInfo_t  *weapon = &cg_weapons[ weaponNum ];

  VectorCopy( dir, normal );
  VectorInverse( normal );

  CG_Bleed( origin, normal, entityNum );

  if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
    weaponMode = WPM_PRIMARY;

  if( weapon->wim[ weaponMode ].alwaysImpact )
  {
    int sound;

    if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER )
    {
      // Players
      sound = IMPACTSOUND_FLESH;
    }
    else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE &&
             BG_Buildable( cg_entities[ entityNum ].currentState.modelindex, NULL )->team == TEAM_ALIENS )
    {
      // Alien buildables
      sound = IMPACTSOUND_FLESH;
    }
    else
      sound = IMPACTSOUND_DEFAULT;
          
    CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, sound, charge );
  }
}


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

BULLETS

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


/*
===============
CG_Tracer
===============
*/
void CG_Tracer( vec3_t source, vec3_t dest )
{
  vec3_t      forward, right;
  polyVert_t  verts[ 4 ];
  vec3_t      line;
  float       len, begin, end;
  vec3_t      start, finish;
  vec3_t      midpoint;

  // tracer
  VectorSubtract( dest, source, forward );
  len = VectorNormalize( forward );

  // start at least a little ways from the muzzle
  if( len < 100 )
    return;

  begin = 50 + random( ) * ( len - 60 );
  end = begin + cg_tracerLength.value;
  if( end > len )
    end = len;

  VectorMA( source, begin, forward, start );
  VectorMA( source, end, forward, finish );

  line[ 0 ] = DotProduct( forward, cg.refdef.viewaxis[ 1 ] );
  line[ 1 ] = DotProduct( forward, cg.refdef.viewaxis[ 2 ] );

  VectorScale( cg.refdef.viewaxis[ 1 ], line[ 1 ], right );
  VectorMA( right, -line[ 0 ], cg.refdef.viewaxis[ 2 ], right );
  VectorNormalize( right );

  VectorMA( finish, cg_tracerWidth.value, right, verts[ 0 ].xyz );
  verts[ 0 ].st[ 0 ] = 0;
  verts[ 0 ].st[ 1 ] = 1;
  verts[ 0 ].modulate[ 0 ] = 255;
  verts[ 0 ].modulate[ 1 ] = 255;
  verts[ 0 ].modulate[ 2 ] = 255;
  verts[ 0 ].modulate[ 3 ] = 255;

  VectorMA( finish, -cg_tracerWidth.value, right, verts[ 1 ].xyz );
  verts[ 1 ].st[ 0 ] = 1;
  verts[ 1 ].st[ 1 ] = 0;
  verts[ 1 ].modulate[ 0 ] = 255;
  verts[ 1 ].modulate[ 1 ] = 255;
  verts[ 1 ].modulate[ 2 ] = 255;
  verts[ 1 ].modulate[ 3 ] = 255;

  VectorMA( start, -cg_tracerWidth.value, right, verts[ 2 ].xyz );
  verts[ 2 ].st[ 0 ] = 1;
  verts[ 2 ].st[ 1 ] = 1;
  verts[ 2 ].modulate[ 0 ] = 255;
  verts[ 2 ].modulate[ 1 ] = 255;
  verts[ 2 ].modulate[ 2 ] = 255;
  verts[ 2 ].modulate[ 3 ] = 255;

  VectorMA( start, cg_tracerWidth.value, right, verts[ 3 ].xyz );
  verts[ 3 ].st[ 0 ] = 0;
  verts[ 3 ].st[ 1 ] = 0;
  verts[ 3 ].modulate[ 0 ] = 255;
  verts[ 3 ].modulate[ 1 ] = 255;
  verts[ 3 ].modulate[ 2 ] = 255;
  verts[ 3 ].modulate[ 3 ] = 255;

  trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts );

  midpoint[ 0 ] = ( start[ 0 ] + finish[ 0 ] ) * 0.5;
  midpoint[ 1 ] = ( start[ 1 ] + finish[ 1 ] ) * 0.5;
  midpoint[ 2 ] = ( start[ 2 ] + finish[ 2 ] ) * 0.5;

  // add the tracer sound
  trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound );
}


/*
======================
CG_CalcMuzzlePoint
======================
*/
static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle )
{
  vec3_t    forward;
  centity_t *cent;
  int       anim;

  if( entityNum == cg.snap->ps.clientNum )
  {
    VectorCopy( cg.snap->ps.origin, muzzle );
    muzzle[ 2 ] += cg.snap->ps.viewheight;
    AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL );
    VectorMA( muzzle, 14, forward, muzzle );
    return qtrue;
  }

  cent = &cg_entities[entityNum];

  if( !cent->currentValid )
    return qfalse;

  VectorCopy( cent->currentState.pos.trBase, muzzle );

  AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL );
  anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;

  if( anim == LEGS_WALKCR || anim == LEGS_IDLECR )
    muzzle[ 2 ] += CROUCH_VIEWHEIGHT;
  else
    muzzle[ 2 ] += DEFAULT_VIEWHEIGHT;

  VectorMA( muzzle, 14, forward, muzzle );

  return qtrue;

}


/*
======================
CG_Bullet

Renders bullet effects.
======================
*/
void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum )
{
  vec3_t  start;

  // if the shooter is currently valid, calc a source point and possibly
  // do trail effects
  if( sourceEntityNum >= 0 && cg_tracerChance.value > 0 )
  {
    if( CG_CalcMuzzlePoint( sourceEntityNum, start ) )
    {
      // draw a tracer
      if( random( ) < cg_tracerChance.value )
        CG_Tracer( start, end );
    }
  }

  // impact splash and mark
  if( flesh )
    CG_Bleed( end, normal, fleshEntityNum );
  else
    CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT, 0 );
}

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

SHOTGUN TRACING

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

/*
================
CG_ShotgunPattern

Perform the same traces the server did to locate the
hit splashes
================
*/
static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum )
{
  int       i;
  float     r, u;
  vec3_t    end;
  vec3_t    forward, right, up;
  trace_t   tr;

  // derive the right and up vectors from the forward vector, because
  // the client won't have any other information
  VectorNormalize2( origin2, forward );
  PerpendicularVector( right, forward );
  CrossProduct( forward, right, up );

  // generate the "random" spread pattern
  for( i = 0; i < SHOTGUN_PELLETS; i++ )
  {
    r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
    u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
    VectorMA( origin, 8192 * 16, forward, end );
    VectorMA( end, r, right, end );
    VectorMA( end, u, up, end );

    CG_Trace( &tr, origin, NULL, NULL, end, otherEntNum, MASK_SHOT );

    if( !( tr.surfaceFlags & SURF_NOIMPACT ) )
    {
      if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER ||
          cg_entities[ tr.entityNum ].currentState.eType == ET_BUILDABLE )
        CG_MissileHitEntity( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum, 0 );
      else if( tr.surfaceFlags & SURF_METALSTEPS )
        CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0 );
      else
        CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT, 0 );
    }
  }
}

/*
==============
CG_ShotgunFire
==============
*/
void CG_ShotgunFire( entityState_t *es )
{
  vec3_t  v;

  VectorSubtract( es->origin2, es->pos.trBase, v );
  VectorNormalize( v );
  VectorScale( v, 32, v );
  VectorAdd( es->pos.trBase, v, v );

  CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum );
}

/*
=================
CG_Bleed

This is the spurt of blood when a character gets hit
=================
*/
void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum )
{
  team_t            team;
  qhandle_t         bleedPS;
  particleSystem_t  *ps;

  if( !cg_blood.integer )
    return;

  if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER )
  {
    team = cgs.clientinfo[ entityNum ].team;
    if( team == TEAM_ALIENS )
      bleedPS = cgs.media.alienBleedPS;
    else if( team == TEAM_HUMANS )
      bleedPS = cgs.media.humanBleedPS;
    else
      return;
  }
  else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE )
  {
    //ew
    team = BG_Buildable( cg_entities[ entityNum ].currentState.modelindex, NULL )->team;
    if( team == TEAM_ALIENS )
      bleedPS = cgs.media.alienBuildableBleedPS;
    else if( team == TEAM_HUMANS )
      bleedPS = cgs.media.humanBuildableBleedPS;
    else
      return;
  }
  else
    return;

  ps = CG_SpawnNewParticleSystem( bleedPS );

  if( CG_IsParticleSystemValid( &ps ) )
  {
    CG_SetAttachmentPoint( &ps->attachment, origin );
    CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] );
    CG_AttachToPoint( &ps->attachment );

    CG_SetParticleSystemNormal( ps, normal );
  }
}

