// Copyright (C) 1999-2000 Id Software, Inc.
//
// cg_weapons.c -- events and effects dealing with weapons
#include "cg_local.h"

/*
==========================
CG_MachineGunEjectBrass
==========================
*/
static void CG_MachineGunEjectBrass( centity_t *cent ) {
    localEntity_t	*le;
    refEntity_t		*re;
    vec3_t			velocity, xvelocity;
    vec3_t			offset, xoffset;
    float			waterScale = 1.0f;
    vec3_t			v[3];
    int				i;

    if ( cg_brassTime.integer <= 0 ) {
        return;
    }

    le = CG_AllocLocalEntity();
    re = &le->refEntity;

    velocity[0] = 0;
    velocity[1] = -50 + 40 * crandom();
    velocity[2] = 100 + 50 * crandom();

    le->leType = LE_FRAGMENT;
    le->startTime = cg.time;
    le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random();

    le->pos.trType = TR_GRAVITY;
    le->pos.trTime = cg.time - (rand()&15);

    AnglesToAxis( cent->lerpAngles, v );

    offset[0] = 8;
    offset[1] = -4;
    offset[2] = 24;

    xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
    xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
    xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
    VectorAdd( cent->lerpOrigin, xoffset, re->origin );

    VectorCopy( re->origin, le->pos.trBase );

    if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) {
        waterScale = 0.10f;
    }

    xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
    xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
    xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
    VectorScale( xvelocity, waterScale, le->pos.trDelta );

    AxisCopy( axisDefault, re->axis );
    re->hModel = cgs.media.machinegunBrassModel;

    le->bounceFactor = 0.4 * waterScale;

    //BB
    /*
        le->angles.trType = TR_LINEAR;
        le->angles.trTime = cg.time;
        le->angles.trBase[0] = rand()&31;
        le->angles.trBase[1] = rand()&31;
        le->angles.trBase[2] = rand()&31;
        le->angles.trDelta[0] = 2;
        le->angles.trDelta[1] = 1;
        le->angles.trDelta[2] = 0;
        le->leFlags = LEF_TUMBLE;
    */

    for (i = 0; i < 3; i++) {
        // random number between -1 and 1
        le->rotation[i] = 2 * random() - 1;
    }
    VectorNormalize(le->rotation);
    //BB end

    le->leBounceSoundType = LEBS_BRASS;
    le->leMarkType = LEMT_NONE;
}

/*
==========================
CG_ShotgunEjectBrass
==========================
*/
static void CG_ShotgunEjectBrass( centity_t *cent ) {
    localEntity_t	*le;
    refEntity_t		*re;
    vec3_t			velocity, xvelocity;
    vec3_t			offset, xoffset;
    vec3_t			v[3];
    int				i, j;

    if ( cg_brassTime.integer <= 0 ) {
        return;
    }

    for ( i = 0; i < 2; i++ ) {
        float	waterScale = 1.0f;

        le = CG_AllocLocalEntity();
        re = &le->refEntity;

        velocity[0] = 60 + 60 * crandom();
        if ( i == 0 ) {
            velocity[1] = 40 + 10 * crandom();
        } else {
            velocity[1] = -40 + 10 * crandom();
        }
        velocity[2] = 100 + 50 * crandom();

        le->leType = LE_FRAGMENT;
        le->startTime = cg.time;
        le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random();

        le->pos.trType = TR_GRAVITY;
        le->pos.trTime = cg.time;

        AnglesToAxis( cent->lerpAngles, v );

        offset[0] = 8;
        offset[1] = 0;
        offset[2] = 24;

        xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
        xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
        xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
        VectorAdd( cent->lerpOrigin, xoffset, re->origin );
        VectorCopy( re->origin, le->pos.trBase );
        if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) {
            waterScale = 0.10f;
        }

        xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
        xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
        xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
        VectorScale( xvelocity, waterScale, le->pos.trDelta );

        AxisCopy( axisDefault, re->axis );
        re->hModel = cgs.media.shotgunBrassModel;
        le->bounceFactor = 0.3f;
        //BB
        /*
                le->angles.trType = TR_LINEAR;
                le->angles.trTime = cg.time;
                le->angles.trBase[0] = rand()&31;
                le->angles.trBase[1] = rand()&31;
                le->angles.trBase[2] = rand()&31;
                le->angles.trDelta[0] = 1;
                le->angles.trDelta[1] = 0.5;
                le->angles.trDelta[2] = 0;
                le->leFlags = LEF_TUMBLE;
        */

        for (j = 0; j < 3; j++) {
            // random number between -1 and 1
            le->rotation[j] = 2 * random() - 1;
        }
        VectorNormalize(le->rotation);
        //BB end

        le->leBounceSoundType = LEBS_BRASS;
        le->leMarkType = LEMT_NONE;
    }
}


#ifdef MISSIONPACK
/*
==========================
CG_NailgunEjectBrass
==========================
*/
static void CG_NailgunEjectBrass( centity_t *cent ) {
    localEntity_t	*smoke;
    vec3_t			origin;
    vec3_t			v[3];
    vec3_t			offset;
    vec3_t			xoffset;
    vec3_t			up;

    AnglesToAxis( cent->lerpAngles, v );

    offset[0] = 0;
    offset[1] = -12;
    offset[2] = 24;

    xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
    xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
    xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
    VectorAdd( cent->lerpOrigin, xoffset, origin );

    VectorSet( up, 0, 0, 64 );

    smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader );
    // use the optimized local entity add
    smoke->leType = LE_SCALE_FADE;
}
#endif


/*
==========================
CG_RailTrail
==========================
*/
void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) {
    vec3_t axis[36], move, move2, next_move, vec, temp;
    float  len;
    int    i, j, skip;

    localEntity_t *le;
    refEntity_t   *re;

#define RADIUS   4
#define ROTATION 1
#define SPACING  5

    start[2] -= 4;
    VectorCopy (start, move);
    VectorSubtract (end, start, vec);
    len = VectorNormalize (vec);
    PerpendicularVector(temp, vec);
    for (i = 0 ; i < 36; i++) {
        RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10
    }

    le = CG_AllocLocalEntity();
    re = &le->refEntity;

    le->leType = LE_FADE_RGB;
    le->startTime = cg.time;
    le->endTime = cg.time + cg_railTrailTime.value;
    le->lifeRate = 1.0 / (le->endTime - le->startTime);

    re->shaderTime = cg.time / 1000.0f;
    re->reType = RT_RAIL_CORE;
    re->customShader = cgs.media.railCoreShader;

    VectorCopy(start, re->origin);
    VectorCopy(end, re->oldorigin);

    re->shaderRGBA[0] = ci->color1[0] * 255;
    re->shaderRGBA[1] = ci->color1[1] * 255;
    re->shaderRGBA[2] = ci->color1[2] * 255;
    re->shaderRGBA[3] = 255;

    le->color[0] = ci->color1[0] * 0.75;
    le->color[1] = ci->color1[1] * 0.75;
    le->color[2] = ci->color1[2] * 0.75;
    le->color[3] = 1.0f;

    // BB COLORS
    if (cgs.gametype >= GT_TEAM) {
        if ( ci->team == TEAM_BLUE ) {
            VectorSet( le->color, 0, 0, 1 );
        } else {
            VectorSet( le->color, 1, 0, 0 );
        }

        GetTeamColorsVEC3(le->color, le->color, ci->team);

        re->shaderRGBA[0] = le->color[0] * 255;
        re->shaderRGBA[1] = le->color[1] * 255;
        re->shaderRGBA[2] = le->color[2] * 255;
        re->shaderRGBA[3] = 255;
    }

    // BB COLORS
    AxisClear( re->axis );

    VectorMA(move, 20, vec, move);
    VectorCopy(move, next_move);
    VectorScale (vec, SPACING, vec);

    if (cg_oldRail.integer != 0) {
        // nudge down a bit so it isn't exactly in center
        re->origin[2] -= 8;
        re->oldorigin[2] -= 8;

        //BB
        //
        // rings
        //
        le = CG_AllocLocalEntity();
        re = &le->refEntity;

        le->leType = LE_FADE_RGB;
        le->startTime = cg.time;
        le->endTime = cg.time + cg_railTrailTime.value;
        le->lifeRate = 1.0 / ( le->endTime - le->startTime );

        re->shaderTime = cg.time / 1000.0f;
        re->reType = RT_RAIL_RINGS;
        re->customShader = cgs.media.railRingsShader;

        VectorCopy( start, re->origin );
        VectorCopy( end, re->oldorigin );

        le->color[0] = ci->color2[0] * 0.75;
        le->color[1] = ci->color2[1] * 0.75;
        le->color[2] = ci->color2[2] * 0.75;
        le->color[3] = 1.0f;

        // BB COLORS
        if (cgs.gametype >= GT_TEAM) {
            if ( ci->team == TEAM_BLUE ) {
                VectorSet( le->color, 0, 0, 1 );
            } else {
                VectorSet( le->color, 1, 0, 0 );
            }

            GetTeamColorsVEC3(le->color, le->color, ci->team);

            re->shaderRGBA[0] = le->color[0] * 255;
            re->shaderRGBA[1] = le->color[1] * 255;
            re->shaderRGBA[2] = le->color[2] * 255;
            re->shaderRGBA[3] = 255;

        }
        // BB COLORS

        re->origin[2] -= 8;
        re->oldorigin[2] -= 8;

        AxisClear( re->axis );
        //BB end

        return;
    }
    skip = -1;

    j = 18;
    for (i = 0; i < len; i += SPACING) {
        if (i != skip) {
            skip = i + SPACING;
            le = CG_AllocLocalEntity();
            re = &le->refEntity;
            le->leFlags = LEF_PUFF_DONT_SCALE;
            le->leType = LE_MOVE_SCALE_FADE;
            le->startTime = cg.time;
            le->endTime = cg.time + (i>>1) + 600;
            le->lifeRate = 1.0 / (le->endTime - le->startTime);

            re->shaderTime = cg.time / 1000.0f;
            re->reType = RT_SPRITE;
            re->radius = 0.5f;
            re->customShader = cgs.media.railRingsShader;

            re->shaderRGBA[0] = ci->color2[0] * 255;
            re->shaderRGBA[1] = ci->color2[1] * 255;
            re->shaderRGBA[2] = ci->color2[2] * 255;
            re->shaderRGBA[3] = 255;

            le->color[0] = ci->color2[0] * 0.75;
            le->color[1] = ci->color2[1] * 0.75;
            le->color[2] = ci->color2[2] * 0.75;
            le->color[3] = 1.0f;

            // BB COLORS
            if ( cgs.gametype >= GT_TEAM ) {
                if ( ci->team == TEAM_BLUE ) {
                    VectorSet( le->color, 0, 0, 1 );
                } else {
                    VectorSet( le->color, 1, 0, 0 );
                }

                GetTeamColorsVEC3(le->color, le->color, ci->team);

                re->shaderRGBA[0] = le->color[0] * 255;
                re->shaderRGBA[1] = le->color[1] * 255;
                re->shaderRGBA[2] = le->color[2] * 255;
                re->shaderRGBA[3] = 255;
            }
            // BB

            le->pos.trType = TR_LINEAR;
            le->pos.trTime = cg.time;

            VectorCopy( move, move2);
            VectorMA(move2, RADIUS , axis[j], move2);
            VectorCopy(move2, le->pos.trBase);

            le->pos.trDelta[0] = axis[j][0]*6;
            le->pos.trDelta[1] = axis[j][1]*6;
            le->pos.trDelta[2] = axis[j][2]*6;
        }

        VectorAdd (move, vec, move);

        j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36;
    }
}

/*
==================
CG_ParticleSparkTrail
BattleBots
Rocket spark trails
==================
*/
void CG_ParticleSparkTrail( vec3_t start, vec3_t end, int team) {
    vec3_t		move;
    vec3_t		vec;
    float		len;
    float		i;

    float spacing = 8;
    float gravity = 200;
    float vel_rand = 40;
    float vel_z = 15;
    int normTime = 400;
    int randTime = 200;
    float radius = 1.5;
    vec3_t color;

    VectorCopy (start, move);
    VectorSubtract (end, start, vec);
    len = VectorNormalize (vec);

    // advance a random amount first
    i = random() * spacing;
    VectorMA( move, i, vec, move );

    VectorScale (vec, spacing, vec);

    for ( ; i < len; i += spacing ) {
        localEntity_t	*le;
        refEntity_t		*re;

        le = CG_AllocLocalEntity();
        le->leFlags = LEF_PUFF_DONT_SCALE;
        le->leType = LE_MOVE_SCALE_FADE;
        le->startTime = cg.time;
        le->endTime = cg.time + normTime + random() * randTime;
        le->lifeRate = 1.0 / ( le->endTime - le->startTime );

        re = &le->refEntity;
        re->shaderTime = cg.time / 1000.0f;

        re->reType = RT_SPRITE;
        re->rotation = 0;
        re->radius = radius;
        re->customShader = cgs.media.tracerShader;
        re->shaderRGBA[0] = 0xff;
        re->shaderRGBA[1] = 0xff;
        re->shaderRGBA[2] = 0xff;
        re->shaderRGBA[3] = 0xff;

        le->color[3] = 0.2f;

        //BB COLORS
        color[0] = re->shaderRGBA[0] / 255;
        color[1] = re->shaderRGBA[1] / 255;
        color[2] = re->shaderRGBA[2] / 255;

        if (cgs.gametype >= GT_TEAM) {
            //BB COLORS
            GetTeamColorsVEC3(color, color, team);
            re->shaderRGBA[0] = color[0] * 255;
            re->shaderRGBA[0] = color[1] * 255;
            re->shaderRGBA[0] = color[2] * 255;
        }


        le->pos.trTime = cg.time;
        VectorCopy( move, le->pos.trBase );
        if (gravity) {
            le->pos.trType = TR_GRAVITY;
            le->gravity = gravity;
            le->customGravity = qtrue;
        } else {
            le->pos.trType = TR_LINEAR;
        }
        le->pos.trDelta[0] = crandom()*vel_rand;
        le->pos.trDelta[1] = crandom()*vel_rand;
        le->pos.trDelta[2] = crandom()*vel_rand + vel_z;

        VectorAdd (move, vec, move);
    }
}


/*
==================
CG_ParticleSmokeTrail
 BattleBots
Rocket and grenade smoke trails
==================
*/
void CG_ParticleSmokeTrail(vec3_t start, vec3_t end, int team) {
    vec3_t		move;
    vec3_t		vec;
    float		len;
    float		i;

    float spacing = 8;
    float gravity = -100;
    float vel_rand = 20;
    float vel_z = 0;
    int normTime = 400;
    int randTime = 400;
    float radius = 1.5;
    vec3_t color;


    VectorCopy (start, move);
    VectorSubtract (end, start, vec);
    len = VectorNormalize (vec);

    // advance a random amount first
    i = random() * spacing;
    VectorMA( move, i, vec, move );

    VectorScale (vec, spacing, vec);

    for ( ; i < len; i += spacing ) {
        localEntity_t	*le;
        refEntity_t		*re;

        le = CG_AllocLocalEntity();
        le->leFlags = LEF_PUFF_DONT_SCALE;
        le->leType = LE_MOVE_SCALE_FADE;
        le->startTime = cg.time;

        //BB - Make a quike laser beam for combat rail
        if (BG_battleWeaponsEnabled()) {
            le->endTime = cg.time + 50;
        } else {
            le->endTime = cg.time + normTime + random() * randTime;
        }
        //BB

        le->lifeRate = 1.0 / ( le->endTime - le->startTime );

        re = &le->refEntity;
        re->shaderTime = cg.time / 1000.0f;

        re->reType = RT_SPRITE;
        re->rotation = 0;
        re->radius = radius;
        re->customShader = trap_R_RegisterShader( "gfx/misc/particle" );
        re->shaderRGBA[0] = 0xff;
        re->shaderRGBA[1] = 0xff;
        re->shaderRGBA[2] = 0xff;
        re->shaderRGBA[3] = 0xff;

        le->color[3] = 1.0;

        //BB COLORS
        color[0] = re->shaderRGBA[0] / 255;
        color[1] = re->shaderRGBA[1] / 255;
        color[2] = re->shaderRGBA[2] / 255;

        if (cgs.gametype >= GT_TEAM) {
            //BB COLORS
            GetTeamColorsVEC3(color, color, team);
            re->shaderRGBA[0] = color[0] * 255;
            re->shaderRGBA[0] = color[1] * 255;
            re->shaderRGBA[0] = color[2] * 255;
        }

        le->pos.trTime = cg.time;
        VectorCopy( move, le->pos.trBase );
        if (gravity) {
            le->pos.trType = TR_GRAVITY;
            le->gravity = gravity;
            le->customGravity = qtrue;
        } else {
            le->pos.trType = TR_LINEAR;
        }
        le->pos.trDelta[0] = crandom()*vel_rand;
        le->pos.trDelta[1] = crandom()*vel_rand;
        le->pos.trDelta[2] = crandom()*vel_rand + vel_z;

        VectorAdd (move, vec, move);
    }
}


/*
==========================
CG_RocketTrail
==========================
*/
static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) {
    int		step;
    vec3_t	origin, lastPos;
    int		t;
    int		startTime, contents;
    int		lastContents;
    entityState_t	*es;
    vec3_t	up;
    localEntity_t	*smoke;
    int team;

    if ( cg_noProjectileTrail.integer ) {
        return;
    }

    up[0] = 0;
    up[1] = 0;
    up[2] = 0;

    step = 50;

    es = &ent->currentState;
    startTime = ent->trailTime;
    t = step * ( (startTime + step) / step );

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );
    contents = CG_PointContents( origin, -1 );

    // if object (e.g. grenade) is stationary, don't toss up smoke
    if ( es->pos.trType == TR_STATIONARY ) {
        ent->trailTime = cg.time;
        return;
    }

    BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
    lastContents = CG_PointContents( lastPos, -1 );

    ent->trailTime = cg.time;

    if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
        if ( contents & lastContents & CONTENTS_WATER ) {
            CG_BubbleTrail( lastPos, origin, 8 );
        }
        return;
    }

    // BB COLORS, team tinting trails
    if (cgs.gametype >= GT_TEAM) {
        // owner clientNum smuggled in otherEntityNum
        // changed to clientNum
        team = cgs.clientinfo[es->clientNum].team;

        for ( ; t <= ent->trailTime ; t += step ) {

            BG_EvaluateTrajectory( &es->pos, t, lastPos );

            smoke = CG_TeamSmokePuff( lastPos, up,
                                      wi->trailRadius,         // radius
                                      1, 1, 1, 0.33f,           // color
                                      wi->wiTrailTime,         // duration
                                      t,                       // starttime
                                      0,                       // flags
                                      cgs.media.smokePuffShader,  // shader
                                      team);                   // team for tinting the color

            // use the optimized local entity add
            smoke->leType = LE_SCALE_FADE;

        }
    } else {
        // end Joe

        for ( ; t <= ent->trailTime ; t += step ) {
            BG_EvaluateTrajectory( &es->pos, t, lastPos );

            smoke = CG_SmokePuff( lastPos, up,
                                  wi->trailRadius,
                                  1, 1, 1, 0.33f,
                                  wi->wiTrailTime,
                                  t,
                                  0,
                                  0,
                                  cgs.media.smokePuffShader );
            // use the optimized local entity add
            smoke->leType = LE_SCALE_FADE;
        }
    }  // Joe
}

#ifdef MISSIONPACK
/*
==========================
CG_NailTrail
==========================
*/
static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) {
    int		step;
    vec3_t	origin, lastPos;
    int		t;
    int		startTime, contents;
    int		lastContents;
    entityState_t	*es;
    vec3_t	up;
    localEntity_t	*smoke;

    if ( cg_noProjectileTrail.integer ) {
        return;
    }

    up[0] = 0;
    up[1] = 0;
    up[2] = 0;

    step = 50;

    es = &ent->currentState;
    startTime = ent->trailTime;
    t = step * ( (startTime + step) / step );

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );
    contents = CG_PointContents( origin, -1 );

    // if object (e.g. grenade) is stationary, don't toss up smoke
    if ( es->pos.trType == TR_STATIONARY ) {
        ent->trailTime = cg.time;
        return;
    }

    BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
    lastContents = CG_PointContents( lastPos, -1 );

    ent->trailTime = cg.time;

    if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
        if ( contents & lastContents & CONTENTS_WATER ) {
            CG_BubbleTrail( lastPos, origin, 8 );
        }
        return;
    }

    for ( ; t <= ent->trailTime ; t += step ) {
        BG_EvaluateTrajectory( &es->pos, t, lastPos );

        smoke = CG_SmokePuff( lastPos, up,
                              wi->trailRadius,
                              1, 1, 1, 0.33f,
                              wi->wiTrailTime,
                              t,
                              0,
                              0,
                              cgs.media.nailPuffShader );
        // use the optimized local entity add
        smoke->leType = LE_SCALE_FADE;
    }

}
#endif

/*
==========================
CG_NailTrail
==========================
*/
static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) {
    localEntity_t	*le;
    refEntity_t		*re;
    entityState_t	*es;
    vec3_t			velocity, xvelocity, origin;
    vec3_t			offset, xoffset;
    vec3_t			v[3];
    int				t, startTime, step;

    float	waterScale = 1.0f;

    if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) {
        return;
    }

    step = 50;

    es = &cent->currentState;
    startTime = cent->trailTime;
    t = step * ( (startTime + step) / step );

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );

    le = CG_AllocLocalEntity();
    re = &le->refEntity;

    velocity[0] = 60 - 120 * crandom();
    velocity[1] = 40 - 80 * crandom();
    velocity[2] = 100 - 200 * crandom();

    le->leType = LE_MOVE_SCALE_FADE;
    le->leFlags = LEF_TUMBLE;
    le->leBounceSoundType = LEBS_NONE;
    le->leMarkType = LEMT_NONE;

    le->startTime = cg.time;
    le->endTime = le->startTime + 600;

    le->pos.trType = TR_GRAVITY;
    le->pos.trTime = cg.time;

    AnglesToAxis( cent->lerpAngles, v );

    offset[0] = 2;
    offset[1] = 2;
    offset[2] = 2;

    xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
    xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
    xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];

    VectorAdd( origin, xoffset, re->origin );
    VectorCopy( re->origin, le->pos.trBase );

    if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) {
        waterScale = 0.10f;
    }

    xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
    xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
    xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
    VectorScale( xvelocity, waterScale, le->pos.trDelta );

    AxisCopy( axisDefault, re->axis );
    re->shaderTime = cg.time / 1000.0f;
    re->reType = RT_SPRITE;
    re->radius = 0.25f;
    re->customShader = cgs.media.railRingsShader;
    le->bounceFactor = 0.3f;

    re->shaderRGBA[0] = wi->flashDlightColor[0] * 63;
    re->shaderRGBA[1] = wi->flashDlightColor[1] * 63;
    re->shaderRGBA[2] = wi->flashDlightColor[2] * 63;
    re->shaderRGBA[3] = 63;

    le->color[0] = wi->flashDlightColor[0] * 0.2;
    le->color[1] = wi->flashDlightColor[1] * 0.2;
    le->color[2] = wi->flashDlightColor[2] * 0.2;
    le->color[3] = 0.25f;

    le->angles.trType = TR_LINEAR;
    le->angles.trTime = cg.time;
    le->angles.trBase[0] = rand()&31;
    le->angles.trBase[1] = rand()&31;
    le->angles.trBase[2] = rand()&31;
    le->angles.trDelta[0] = 1;
    le->angles.trDelta[1] = 0.5;
    le->angles.trDelta[2] = 0;

}

extern vmCvar_t cg_ropeShader;

/*
==========================
CG_Offhand_GrappleTrail
==========================
*/
void CG_Offhand_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) {
    vec3_t	origin;
    entityState_t	*es;
    vec3_t			forward, up;
    refEntity_t		beam;
    vec3_t color;
    int team;

    es = &ent->currentState;

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );
    ent->trailTime = cg.time;

    memset( &beam, 0, sizeof( beam ) );
    //FIXME adjust for muzzle position
    VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin );

    beam.origin[2] += 26;
    AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up );
    VectorMA( beam.origin, -6, up, beam.origin );
    VectorCopy( origin, beam.oldorigin );

    if (Distance( beam.origin, beam.oldorigin ) < 64 )
        return; // Don't draw if close

    beam.reType = RT_LIGHTNING;
    //beam.customShader = cgs.media.lightningShader;

    //if (cg_serverConfig.grappletype == 0)
    if (cg_serverConfig.gamemode != GM_BATTLE)
        beam.customShader = trap_R_RegisterShader("grapplerope2");
    else
        beam.customShader = trap_R_RegisterShader(cg_ropeShader.string);

    AxisClear( beam.axis );

    // BB COLORS tint tech hook, doesn't work
    //cg_serverConfig.grappletype == 1 &&
    if ( cgs.gametype >= GT_TEAM) {
        int i;

        // owner smuggled here
        team = cgs.clientinfo[ent->currentState.otherEntityNum].team;

        GetTeamColorsVEC3(color, color, team);

        // owner smuggled here
        for ( i = 0; i < 4; i++)
            beam.shaderRGBA[i] = color[i] * 255;


    } else {
        // BB COLORS
        beam.shaderRGBA[0] = 0xff;
        beam.shaderRGBA[1] = 0xff;
        beam.shaderRGBA[2] = 0xff;
        beam.shaderRGBA[3] = 0xff;
    } // BB COLORS

    trap_R_AddRefEntityToScene( &beam );
}


/*
==========================
CG_GrappleTrail
==========================
*/
void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) {
    vec3_t	origin;
    entityState_t	*es;
    vec3_t			forward, up;
    refEntity_t		beam;
    //	vec3_t color;

    //    int team;  // Joe grapple tint

    es = &ent->currentState;

    BG_EvaluateTrajectory( &es->pos, cg.time, origin );


    ent->trailTime = cg.time;

    memset( &beam, 0, sizeof( beam ) );
    //FIXME adjust for muzzle position
    VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin );

    // commenting gets rid of trail but makes rope thinner
    beam.origin[2] += 20;

    AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up );
    VectorMA( beam.origin, -6, up, beam.origin );
    VectorMA( beam.origin, 14, forward, beam.origin );
    VectorCopy( origin, beam.oldorigin );


    if (Distance( beam.origin, beam.oldorigin ) < 64 )
        return; // Don't draw if close

    beam.reType = RT_LIGHTNING;

    // debugging line
    //beam.customShader = trap_R_RegisterShader(cg_ropeShader.string);

    //if (cg_serverConfig.grappletype != 0)
    //    beam.customShader = trap_R_RegisterShader("grapplerope2");
    switch(cg_serverConfig.grappletype) {
    case 0:
        beam.customShader = trap_R_RegisterShader("grapplerope2");
        break;
    case 1:
        beam.customShader = trap_R_RegisterShader(cg_ropeShader.string);//grapplerope
        break;
    case 2:
        beam.customShader = cgs.media.lightningShader;//lightning
        break;
    default:
        beam.customShader = trap_R_RegisterShader("grapplerope2");
        break;
    }


    AxisClear( beam.axis );


    beam.shaderRGBA[0] = 0xff;
    beam.shaderRGBA[1] = 0xff;
    beam.shaderRGBA[2] = 0xff;
    beam.shaderRGBA[3] = 0xff;

    trap_R_AddRefEntityToScene( &beam );
}

/*
==========================
CG_GrenadeTrail
==========================
*/
static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) {
    CG_RocketTrail( ent, wi );
}

#include "../game/bb_combat_shotgun.h"  // Joe combat weapons

/*
=================
CG_RegisterWeapon
 
The server says this item is used on this level
=================
*/
void CG_RegisterWeapon( int weaponNum ) {
    weaponInfo_t	*weaponInfo;
    gitem_t			*item, *ammo;
    char			path[MAX_QPATH];
    vec3_t			mins, maxs;
    int				i;

    weaponInfo = &cg_weapons[weaponNum];

    if ( weaponNum == 0 ) {
        return;
    }

    if ( weaponInfo->registered ) {
        return;
    }

    memset( weaponInfo, 0, sizeof( *weaponInfo ) );
    weaponInfo->registered = qtrue;

    for ( item = bg_itemlist + 1 ; item->classname ; item++ ) {
        if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) {
            weaponInfo->item = item;
            break;
        }
    }
    if ( !item->classname ) {
        CG_Error( "Couldn't find weapon %i", weaponNum );
    }
    CG_RegisterItemVisuals( item - bg_itemlist );

    // load cmodel before model so filecache works
    weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] );


    // Joe
    // substitute
    // can only see in 3rd person
    if (! weaponInfo->weaponModel && weaponNum == WP_HANDGRENADE)
        weaponInfo->weaponModel = trap_R_RegisterModel(
                                      "models/ammo/grenade1.md3" );
    // end Joe


    // 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] );
    }

    weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon );

    // begin Joe - check if we have hg icon
    if (! weaponInfo->weaponIcon && weaponNum == WP_HANDGRENADE)
        weaponInfo->weaponIcon = trap_R_RegisterShader("icons/iconw_grenade");
    // end Joe

    weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon );

    for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) {
        if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) {
            break;
        }
    }
    if ( ammo->classname && ammo->world_model[0] ) {
        weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] );
    }

    // begin Joe
    // actually we don't have a custom ammo model for the hg
    if ( ! weaponInfo->ammoModel && weaponNum == WP_HANDGRENADE )
        weaponInfo->ammoModel =
            trap_R_RegisterModel("models/powerups/ammo/grenadeam.md3");
    // end Joe

    Q_strncpyz( path, item->world_model[0], sizeof(path));
    COM_StripExtension( path, path );
    Q_strcat( path,sizeof(path), "_flash.md3" );
    weaponInfo->flashModel = trap_R_RegisterModel( path );

    Q_strncpyz( path, item->world_model[0], sizeof(path));
    COM_StripExtension( path, path );
    Q_strcat( path, sizeof(path),"_barrel.md3" );
    weaponInfo->barrelModel = trap_R_RegisterModel( path );

    Q_strncpyz( path, item->world_model[0], sizeof(path));
    COM_StripExtension( path, path );
    Q_strcat( path, sizeof(path), "_hand.md3" );
    weaponInfo->handsModel = trap_R_RegisterModel( path );

    if ( !weaponInfo->handsModel ) {
        weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );
    }

    weaponInfo->loopFireSound = qfalse;

    switch ( weaponNum ) {
    case WP_GAUNTLET:
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse );
        break;

    case WP_LIGHTNING:
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse );
        weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse );

        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse );
        // BattleBots was "lightningBoltNew"
        cgs.media.lightningShader = trap_R_RegisterShader( "lightningBolt");
        cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" );
        cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse );
        cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse );
        cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse );

        break;

    case WP_GRAPPLING_HOOK:
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons3/hook/hook1.md3" );
        weaponInfo->missileTrailFunc = CG_GrappleTrail;
        weaponInfo->missileDlight = 200;
        weaponInfo->wiTrailTime = 2000;
        weaponInfo->trailRadius = 64;
        MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 );
		
		if(cg_serverConfig.grappletype == 2)
			cgs.media.lightningShader = trap_R_RegisterShader( "lightningBolt");

        //weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse );
        //weaponInfo->firingSound = trap_S_RegisterSound( "sound/grapple/midevil/grfire.wav", qfalse );
        break;

#ifdef MISSIONPACK

    case WP_CHAINGUN:
        weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse );
        weaponInfo->loopFireSound = qtrue;
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse );
        weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse );
        weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse );
        weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse );
        weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
        cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" );
        break;
#endif

    case WP_MACHINEGUN:
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse );
        weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse );
        weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse );
        weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse );
        weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
        cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" );

        break;

    case WP_SHOTGUN:
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse );
        weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass;
        // Joe combat shotgun
        initPellets(pellets);

        break;

    case WP_ROCKET_LAUNCHER:
        weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" );
        weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse );
        weaponInfo->missileTrailFunc = CG_RocketTrail;
        weaponInfo->missileDlight = 200;
        //Crock - Lower radius/duration of rocket smoke to increase visablity
        if (BG_battleWeaponsEnabled()) {
            weaponInfo->wiTrailTime = 500; //Was 2000
            weaponInfo->trailRadius = 24;   //Was 64
        }
        else {
            weaponInfo->wiTrailTime = 2000;
            weaponInfo->trailRadius = 64;
        }
        //Crock - End rocket trail

        MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 );

        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse );
        cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" );

        break;

#ifdef MISSIONPACK

    case WP_PROX_LAUNCHER:
        weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" );
        weaponInfo->missileTrailFunc = CG_GrenadeTrail;
        weaponInfo->wiTrailTime = 700;
        weaponInfo->trailRadius = 32;
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse );
        cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" );
        break;
#endif

    case WP_GRENADE_LAUNCHER:
        weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" );
        weaponInfo->missileTrailFunc = CG_GrenadeTrail;
        weaponInfo->wiTrailTime = 700;
        weaponInfo->trailRadius = 32;
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse );
        cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" );
        break;

        //Alliance - Hand Grenades - Banshee
    case WP_HANDGRENADE:

        //weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" );
        weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/handgr/handgr.md3" );
        weaponInfo->missileTrailFunc = CG_GrenadeTrail;
        weaponInfo->wiTrailTime = 300;
        weaponInfo->trailRadius = 20;
        cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" );

        // Joe
        cgs.media.handgrenadeStartSound = trap_S_RegisterSound("sound/weapons2/handgr/handgr_start.wav", qfalse);
        cgs.media.handgrenadeTickSound = trap_S_RegisterSound("sound/weapons2/handgr/handgr_tick.wav", qfalse);
        cgs.media.handgrenadeChargedSound = trap_S_RegisterSound("sound/weapons2/handgr/handgr_charged.wav", qfalse);
        break;
        //Alliance - End

#ifdef MISSIONPACK

    case WP_NAILGUN:
        weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass;
        weaponInfo->missileTrailFunc = CG_NailTrail;
        //		weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse );
        weaponInfo->trailRadius = 16;
        weaponInfo->wiTrailTime = 250;
        weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse );
        break;
#endif

    case WP_PLASMAGUN:
        //		weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel;
        weaponInfo->missileTrailFunc = CG_PlasmaTrail;
        weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse );
        MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse );
        cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" );
        cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" );
        break;

    case WP_RAILGUN:
        weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse );
        cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" );
        cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" );
        cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" );
        break;

    case WP_BFG:
        weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse );
        MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse );
        cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" );
        weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" );
        weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse );
        break;

    default:
        MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 );
        weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse );
        break;
    }
}

// Alliance
/*
=================
CG_Register_Offhand_Grapple
=================
*/
void CG_Register_Offhand_Grapple(void) {

    /*
    	// Joe - new red and blue grapple images
    // trap_R_RegisterShader returns 0 if it doesn't exist
    if(fileExists("scripts/grapple.shader"))
    {
        CG_Printf("grapple.shader *** Exists\n");
    }
    else
    {
        CG_Printf("grapple.shader *** Does not exists\n");
    }
    // end Joe
     
    */


    if (cg_grapple.registered == qtrue)
        return;

    MAKERGB( cg_grapple.flashDlightColor, 0.6f, 0.6f, 1 );

    // Joe - hook model

    if (cg_serverConfig.gamemode == GM_BATTLE) {
        if (fileExists("models/weapons3/hook/hook1.md3")) {
            cg_grapple.missileModel =
                trap_R_RegisterModel("models/weapons3/hook/hook1.md3" );
            //CG_Printf("hook1.md3 *** exists\n");
        }
        else
            cg_grapple.missileModel =
                trap_R_RegisterModel("models/ammo/rocket/rocket.md3");
    } else {
        switch( cg_serverConfig.grappletype ) {
        case 0:
            if (fileExists("models/weapons3/hook/bit1.md3")) {
                cg_grapple.missileModel =
                    trap_R_RegisterModel("models/weapons3/hook/bit1.md3");
                //CG_Printf("bit1.md3 *** exists\n");
            }
            else
                cg_grapple.missileModel =
                    trap_R_RegisterModel("models/ammo/rocket/rocket.md3" );
            break;
        default:
            if (fileExists("models/weapons3/hook/hook1.md3")) {
                cg_grapple.missileModel =
                    trap_R_RegisterModel("models/weapons3/hook/hook1.md3" );
                //CG_Printf("hook1.md3 *** exists\n");
            }
            else
                cg_grapple.missileModel =
                    trap_R_RegisterModel("models/ammo/rocket/rocket.md3");
        }
    }

    // should make defines for these
    // Joe - grapple trail funcs
    /*
       if (cg_serverConfig.gamemode == GM_BATTLE) {
           switch( cg_serverConfig.grappletype ) {
           case 0:
               cg_grapple.missileTrailFunc = CG_GrappleBubbleTrail;
               break;
           case 1:
               cg_grapple.missileTrailFunc = CG_GrappleBloodTrail;
               break;
           case 2:
               cg_grapple.missileTrailFunc = CG_GrappleCapEffectTrail;//we dont have a shader for this one
               break;
           default:
               cg_grapple.missileTrailFunc = CG_GrappleBubbleTrail;
               break;
           }
       } else {
           switch( cg_serverConfig.grappletype ) {
           case 0:
           case 1:
               cg_grapple.missileTrailFunc = CG_Offhand_GrappleTrail;
               break;
           case 2:
               cg_grapple.missileTrailFunc = CG_RocketTrail;
               break;
           default:
               cg_grapple.missileTrailFunc = CG_Offhand_GrappleTrail;
               break;
           }
       }
    */
    //if (cg_serverConfig.gamemode == GM_BATTLE) {
    switch( cg_serverConfig.grappletype ) {
    case 0:
        cg_grapple.missileTrailFunc = CG_GrappleBubbleTrail;
        break;
    case 1:
        cg_grapple.missileTrailFunc = CG_GrappleBloodTrail;
        break;
    case 2:
        cg_grapple.missileTrailFunc = CG_GrappleCapEffectTrail;//we dont have a shader for this one
        break;
    case 3:
        cg_grapple.missileTrailFunc = CG_Offhand_GrappleTrail;
        break;
    case 4:
        cg_grapple.missileTrailFunc = CG_RocketTrail;
        break;
    default:
        cg_grapple.missileTrailFunc = CG_Offhand_GrappleTrail;
        break;
    }
    // end Joe new grapples

    //CG_Printf("CG_Register_Offhand_Grapple() ***\n");
    cg_grapple.registered = qtrue;
}


/*
=================
CG_RegisterItemVisuals
 
The server says this item is used on this level
=================
*/
void CG_RegisterItemVisuals( int itemNum ) {
    itemInfo_t		*itemInfo;
    gitem_t			*item;

    if ( itemNum < 0 || itemNum >= bg_numItems ) {
        CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 );
    }

    itemInfo = &cg_items[ itemNum ];
    if ( itemInfo->registered ) {
        return;
    }

    item = &bg_itemlist[ itemNum ];

    memset( itemInfo, 0, sizeof( &itemInfo ) );
    itemInfo->registered = qtrue;

    itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] );

    itemInfo->icon = trap_R_RegisterShader( item->icon );


    if ( item->giType == IT_WEAPON && (item->giTag != WP_GRAPPLING_HOOK)) {
        CG_RegisterWeapon( item->giTag );
    }

    // Alliance
	if(item->giTag == WP_GRAPPLING_HOOK &&
		cg_serverConfig.grapple_onhand == 0){
    //if (item->giType == IT_GRAPPLE) {
        //CG_Printf("CG_Register_Offhand_Grapple *** Exists\n");
        CG_Register_Offhand_Grapple();
    }

    //
    // powerups have an accompanying ring or sphere
    //
    if ( item->giType == IT_POWERUP ||
            item->giType == IT_PERSISTANT_POWERUP ||
            item->giType == IT_HEALTH ||
            item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) {
        if ( item->world_model[1] ) {
            itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] );
        }
    }

    //BB Team Colors
    //if ( item->giTag == PW_BLUEFLAG
    //	|| item->giTag == PW_REDFLAG) {
    //	itemInfo->icon = trap_R_RegisterShader( getItemIcon(item->icon) );
    //}
}


/*
========================================================================================
 
VIEW WEAPON
 
========================================================================================
*/

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

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

#if 0
    // drop the weapon when stair climbing
    delta = cg.time - cg.stepTime;
    if ( delta < STEP_TIME/2 ) {
        origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2);
    } else if ( delta < STEP_TIME ) {
        origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2);
    }
#endif

    // 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_LightningBolt
 
Origin will be the exact tag point, which is slightly
different than the muzzle point used for determining hits.
The cent should be the non-predicted cent if it is from the player,
so the endpoint will reflect the simulated strike (lagging the predicted
angle)
===============
*/
static void CG_LightningBolt( centity_t *cent, vec3_t origin ) {
    trace_t  trace;
    refEntity_t  beam;
    vec3_t   forward;
    vec3_t   muzzlePoint, endPoint;
    vec3_t color;


    if (cent->currentState.weapon != WP_LIGHTNING) {
        return;
    }

    memset( &beam, 0, sizeof( beam ) );

    //unlagged - attack prediction #1
    // if the entity is us, unlagged is on server-side, and we've got it on for the lightning gun
    if ( (cent->currentState.number == cg.predictedPlayerState.clientNum) && cgs.delagHitscan &&
            ( cg_delag.integer & 1 || cg_delag.integer & 8 ) ) {
        // always shoot straight forward from our current position
        AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL );
        VectorCopy( cg.predictedPlayerState.origin, muzzlePoint );
    } else
        //unlagged - attack prediction #1
        // CPMA  "true" lightning
        if ((cent->currentState.number == cg.predictedPlayerState.clientNum)
                && (cg_trueLightning.value != 0)) {
            vec3_t angle;
            int i;

            //unlagged - true lightning
            // might as well fix up true lightning while we're at it
            vec3_t viewangles;
            VectorCopy( cg.predictedPlayerState.viewangles, viewangles );
            //unlagged - true lightning

            for (i = 0; i < 3; i++) {
                float a = cent->lerpAngles[i] - viewangles[i]; //unlagged: was cg.refdefViewAngles[i];
                if (a > 180) {
                    a -= 360;
                }
                if (a < -180) {
                    a += 360;
                }

                angle[i] = viewangles[i] /*unlagged: was cg.refdefViewAngles[i]*/ + a * (1.0 - cg_trueLightning.value);
                if (angle[i] < 0) {
                    angle[i] += 360;
                }
                if (angle[i] > 360) {
                    angle[i] -= 360;
                }
            }

            AngleVectors(angle, forward, NULL, NULL );
            //unlagged - true lightning
            //		VectorCopy(cent->lerpOrigin, muzzlePoint );
            //		VectorCopy(cg.refdef.vieworg, muzzlePoint );
            // *this* is the correct origin for true lightning
            VectorCopy(cg.predictedPlayerState.origin, muzzlePoint );
            //unlagged - true lightning
        }
        else {
            // !CPMA
            AngleVectors( cent->lerpAngles, forward, NULL, NULL );
            VectorCopy(cent->lerpOrigin, muzzlePoint );
        }

    // FIXME: crouch
    muzzlePoint[2] += DEFAULT_VIEWHEIGHT;

    VectorMA( muzzlePoint, 14, forward, muzzlePoint );

    // project forward by the lightning range
    VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint );

    // see if it hit a wall
    CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint,
              cent->currentState.number, MASK_SHOT );

    // this is the endpoint
    VectorCopy( trace.endpos, beam.oldorigin );

    // use the provided origin, even though it may be slightly
    // different than the muzzle origin
    VectorCopy( origin, beam.origin );

    beam.reType = RT_LIGHTNING;

    //BB COLORS
    if (cgs.gametype >= GT_TEAM) {

        if(cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE)
            beam.customShader = cgs.media.lightningShader_blue;
        else
            beam.customShader = cgs.media.lightningShader_red;
    } else
        beam.customShader = cgs.media.lightningShader;

    trap_R_AddRefEntityToScene( &beam );

    // add the impact flare if it hit something
    if ( trace.fraction < 1.0 ) {
        vec3_t	angles;
        vec3_t	dir;

        VectorSubtract( beam.oldorigin, beam.origin, dir );
        VectorNormalize( dir );

        memset( &beam, 0, sizeof( beam ) );

        // BB COLORS: team-colored lightning beam
        if (cgs.gametype >= GT_TEAM) {


            if(cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {

                color[0] = 1;
                color[1] = 0;
                color[2] = 0;
		beam.hModel = cgs.media.lightningExplosionModel_blue;
				

            } else {

                color[0] = 0;
                color[1] = 0;
                color[2] = 1;
		beam.hModel = cgs.media.lightningExplosionModel_red;

            }

            GetTeamColorsVEC3(color, color, cgs.clientinfo[cent->currentState.clientNum].team);

            beam.shaderRGBA[0] = color[0] * 255;
            beam.shaderRGBA[1] = color[1] * 255;
            beam.shaderRGBA[2] = color[2] * 255;


        }
	else 
	    beam.hModel = cgs.media.lightningExplosionModel;

        VectorMA( trace.endpos, -16, dir, beam.origin );

        // make a random orientation
        angles[0] = rand() % 360;
        angles[1] = rand() % 360;
        angles[2] = rand() % 360;
        AnglesToAxis( angles, beam.axis );
        trap_R_AddRefEntityToScene( &beam );
    }
}

/*
===============
CG_SpawnRailTrail
 
Origin will be the exact tag point, which is slightly
different than the muzzle point used for determining hits.
===============
*/
static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) {
    clientInfo_t	*ci;

    if ( cent->currentState.weapon != WP_RAILGUN ) {
        return;
    }
    if ( !cent->pe.railgunFlash ) {
        return;
    }
    cent->pe.railgunFlash = qtrue;
    ci = &cgs.clientinfo[ cent->currentState.clientNum ];
    CG_RailTrail( ci, origin, cent->pe.railgunImpact );
}


/*
======================
CG_MachinegunSpinAngle
======================
*/
#define		SPIN_SPEED	0.9
#define		COAST_TIME	1000
static float	CG_MachinegunSpinAngle( centity_t *cent ) {
    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 == !(cent->currentState.eFlags & EF_FIRING) ) {
        cent->pe.barrelTime = cg.time;
        cent->pe.barrelAngle = AngleMod( angle );
        cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING);
#ifdef MISSIONPACK

        if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) {
            trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) );
        }
#endif

    }

    return angle;
}


/*
========================
CG_AddWeaponWithPowerups
========================
*/
static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) {
    // add powerup effects
    if ( powerups & ( 1 << PW_INVIS ) ) {
        gun->customShader = cgs.media.invisShader;
        trap_R_AddRefEntityToScene( gun );
    } else {
        trap_R_AddRefEntityToScene( gun );

        if ( powerups & ( 1 << PW_BATTLESUIT ) ) {
            gun->customShader = cgs.media.battleWeaponShader;
            trap_R_AddRefEntityToScene( gun );
        }
        if ( powerups & ( 1 << PW_QUAD ) ) {
            gun->customShader = cgs.media.quadWeaponShader;
            trap_R_AddRefEntityToScene( gun );
        }

#ifndef MISSIONPACK
        if ( powerups & ( 1 << PW_RUNEDAMAGE ) ) {
			if ( ( ( cg.time / 100 ) % 25 ) == 1 ) {
            gun->customShader = cgs.media.runeDamageShader;
            trap_R_AddRefEntityToScene( gun );
        }
        }
        if ( powerups & ( 1 << PW_RUNEHASTE ) ) {
			if ( ( ( cg.time / 100 ) % 25 ) == 1 ) {
            gun->customShader = cgs.media.runeHasteShader;
            trap_R_AddRefEntityToScene( gun );
        }
        }
        if ( powerups & ( 1 << PW_RUNEREGEN ) ) {
			if ( ( ( cg.time / 100 ) % 25 ) == 1 ) {
            gun->customShader = cgs.media.runeRegenShader;
            trap_R_AddRefEntityToScene( gun );
        }
        }
        if ( powerups & ( 1 << PW_RUNERESIST ) ) {
        if ( ( ( cg.time / 100 ) % 25 ) == 1 ) {    
            gun->customShader = cgs.media.runeResistShader;
            trap_R_AddRefEntityToScene( gun );
        }
        }
        if ( powerups & ( 1 << PW_RUNEVAMPIRE ) ) {
			if ( ( ( cg.time / 100 ) % 25 ) == 1 ) {
            gun->customShader = cgs.media.runeVampireShader;
            trap_R_AddRefEntityToScene( gun );
        }
        }
        if ( powerups & ( 1 << PW_RUNEAMMOREGEN ) ) {
			if ( ( ( cg.time / 100 ) % 25 ) == 1 ) {
            gun->customShader = cgs.media.runeAmmoRegenShader;
            trap_R_AddRefEntityToScene( gun );
        }
    }
#endif
}
}


/*
=============
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, int team ) {
    refEntity_t	gun;
    refEntity_t	barrel;
    refEntity_t	flash;
    vec3_t		angles;
    weapon_t	weaponNum;
    weaponInfo_t	*weapon;
    centity_t	*nonPredictedCent;
    //	int	col;
    vec3_t color;
    int teamside;

    teamside = cgs.clientinfo[cent->currentState.clientNum].team;

    weaponNum = cent->currentState.weapon;

    CG_RegisterWeapon( weaponNum );
    weapon = &cg_weapons[weaponNum];

    // add the weapon
    memset( &gun, 0, sizeof( gun ) );
    VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
    gun.shadowPlane = parent->shadowPlane;
    gun.renderfx = parent->renderfx;

    // set custom shading for railgun refire rate
    //if ( ps ) {
        if ( cg.predictedPlayerState.weapon == WP_RAILGUN &&
                cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) {
            float	f;

            f = (float)cg.predictedPlayerState.weaponTime / 1500;
            gun.shaderRGBA[1] = 0;
            gun.shaderRGBA[0] =
                gun.shaderRGBA[2] = 255 * ( 1.0 - f );

            // BB COLORS: in teamplay, color-code rail
            if (cgs.gametype >= GT_TEAM) {

                if (team == TEAM_BLUE) {
                    color[0] = 0;
                    color[1] = 0;
                    color[2] = 1;
                } else {

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

                GetTeamColorsVEC3(color, color, team);

                gun.shaderRGBA[0] = color[0] * 255;
                gun.shaderRGBA[1] = color[1] * 255;
                gun.shaderRGBA[2] = color[2] * 255;

            }

        } else {

            gun.shaderRGBA[0] = 255;
            gun.shaderRGBA[1] = 255;
            gun.shaderRGBA[2] = 255;
            gun.shaderRGBA[3] = 255;

            // BB COLOR: in teamplay, color-code rail
            if (cgs.gametype >= GT_TEAM) {
                if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                    // lower all but red
                    color[0] = 0;
                    color[1] = 0;
                    color[2] = 1;

                    GetTeamColorsVEC3(color, color, TEAM_BLUE);

                    gun.shaderRGBA[0] = color[0] * 255;
                    gun.shaderRGBA[1] = color[1] * 255;
                    gun.shaderRGBA[2] = color[2] * 255;

                    //gun.shaderRGBA[1] = gun.shaderRGBA[2] = 190;
                }
                else {
                    // lower all but blue
                    color[0] = 1;
                    color[1] = 0;
                    color[2] = 0;

                    GetTeamColorsVEC3(color, color, TEAM_RED);

                    gun.shaderRGBA[0] = color[0] * 255;
                    gun.shaderRGBA[1] = color[1] * 255;
                    gun.shaderRGBA[2] = color[2] * 255;
                    //gun.shaderRGBA[0] = gun.shaderRGBA[1] = 190;
                }
            }

        }
    //} // end if ( ps )


    gun.hModel = weapon->weaponModel;

    // BB COLORS: Weapon Skins
    if (cgs.gametype >= GT_TEAM) {

        if (cent->currentState.weapon == WP_HANDGRENADE) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.handgrenadeSkin_blue;
            } else
                gun.customSkin = cgs.media.handgrenadeSkin_red;
        } else if (cent->currentState.weapon == WP_PLASMAGUN) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.plasmagunSkin_blue;
            } else
                gun.customSkin = cgs.media.plasmagunSkin_red;
        } else if (cent->currentState.weapon == WP_LIGHTNING) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.lightninggunSkin_blue;
            } else
                gun.customSkin = cgs.media.lightninggunSkin_red;

        } else if (cent->currentState.weapon == WP_ROCKET_LAUNCHER) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.rocketgunSkin_blue;
            } else
                gun.customSkin = cgs.media.rocketgunSkin_red;

        } else if (cent->currentState.weapon == WP_BFG) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.bfgSkin_blue;
            } else
                gun.customSkin = cgs.media.bfgSkin_red;

        }
        else if (cent->currentState.weapon == WP_MACHINEGUN) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.machinegunSkin_blue;
            } else
                gun.customSkin = cgs.media.machinegunSkin_red;

        }
        else if (cent->currentState.weapon == WP_SHOTGUN) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.shotgunSkin_blue;
            } else
                gun.customSkin = cgs.media.shotgunSkin_red;

        } else if (cent->currentState.weapon == WP_RAILGUN) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.railgunSkin_blue;
            } else
                gun.customSkin = cgs.media.railgunSkin_red;

        } else if (cent->currentState.weapon == WP_GRENADE_LAUNCHER) {
            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                gun.customSkin = cgs.media.grenadelSkin_blue;
            } else
                gun.customSkin = cgs.media.grenadelSkin_red;

        }
    }

    if (!gun.hModel) {
        return;
    }

    if ( !ps ) {
        // add weapon ready sound
        cent->pe.lightningFiring = qfalse;
        cent->pe.grappleFiring = qfalse;

        if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) {
            // lightning gun and guantlet make a different sound when fire is held down
            trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound );
            cent->pe.lightningFiring = qtrue;
            if (cg.predictedPlayerState.weapon == WP_GRAPPLING_HOOK)
                cent->pe.grappleFiring = qtrue;

        } else if ( weapon->readySound ) {
            trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound );
        }
    }

    CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon");

    CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups );

    // add the spinning barrel
    if ( weapon->barrelModel ) {
        memset( &barrel, 0, sizeof( barrel ) );
        VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
        barrel.shadowPlane = parent->shadowPlane;
        barrel.renderfx = parent->renderfx;

        barrel.hModel = weapon->barrelModel;
        angles[YAW] = 0;
        angles[PITCH] = 0;
        angles[ROLL] = CG_MachinegunSpinAngle( cent );
        AnglesToAxis( angles, barrel.axis );

        CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" );

        CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups );
    }

    // make sure we aren't looking at cg.predictedPlayerEntity for LG
    nonPredictedCent = &cg_entities[cent->currentState.clientNum];

    // if the index of the nonPredictedCent is not the same as the clientNum
    // then this is a fake player (like on teh single player podiums), so
    // go ahead and use the cent
    if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) {
        nonPredictedCent = cent;
    }

    //BB
    // check if we should draw a beam (need to calc muzzle point)
    if ( ps || cg.renderingThirdPerson ||
            cent->currentState.number != cg.predictedPlayerState.clientNum ) {
        int clientNum = cent->currentState.clientNum;
        memset( &flash, 0, sizeof( flash ) );
        flash.hModel = weapon->flashModel;
        if (!flash.hModel) {
            return;
        }
        angles[YAW] = 0;
        angles[PITCH] = 0;
        angles[ROLL] = crandom() * 10;
        AnglesToAxis( angles, flash.axis );
        CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash");

        if (cg.time < cg.shotgunTime[clientNum]) {
            vec3_t up;
            localEntity_t *smoke;

            if (cg.shotgunTime2[clientNum] < cg.time) {
                cg.shotgunTime2[clientNum] = cg.time + 50;
                VectorSet(up, 0, 0, 16 + random() * 10);

                //BB COLORS
                if (cgs.gametype >= GT_TEAM) {

                    smoke = CG_TeamSmokePuff(flash.origin, up, 16,
                                             1, 1, 1, 0.2f,
                                             1300, cg.time, 0, cgs.media.smokePuffShader, teamside);

                } else
                    smoke = CG_SmokePuff(flash.origin, up, 16, 1, 1, 1, 0.2f, 1300, cg.time, 0, 0, trap_R_RegisterShader("smokePuff2")); // cgs.media.smokePuffShader );

                // use the optimized local entity add
                smoke->leType = LE_MOVE_SCALE_FADE;
            }
        }
    }
    //BB end


    // add the flash
    if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET )
            && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) {
        // continuous flash
    }
    else {
        // impulse flash
        if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME
                && !cent->pe.railgunFlash ) {

            return;
        }
    }


    memset( &flash, 0, sizeof( flash ) );
    VectorCopy( parent->lightingOrigin, flash.lightingOrigin );
    flash.shadowPlane = parent->shadowPlane;
    flash.renderfx = parent->renderfx;

    if ( cg_noMuzzleFlash.integer)
        return;

    flash.hModel = weapon->flashModel;

    // BB COLORS: Weapon Flashes
    if (cgs.gametype >= GT_TEAM) {

        if (weaponNum == WP_PLASMAGUN) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.plasmagunFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.plasmagunFlashSkin_red;

        }
        else if (weaponNum == WP_LIGHTNING) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.lightningFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.lightningFlashSkin_red;

        } else if (weaponNum == WP_ROCKET_LAUNCHER) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.rocketgunFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.rocketgunFlashSkin_red;

        } else if (weaponNum == WP_BFG) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.bfgFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.bfgFlashSkin_red;

        }
        else if (weaponNum == WP_MACHINEGUN) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.machinegunFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.machinegunFlashSkin_red;

        }
        else if (weaponNum == WP_SHOTGUN) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.shotgunFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.shotgunFlashSkin_red;

        } else if (weaponNum == WP_RAILGUN) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.railgunFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.railgunFlashSkin_red;

        } else if (weaponNum == WP_GRENADE_LAUNCHER) {

            if (cgs.clientinfo[cent->currentState.clientNum].team == TEAM_BLUE) {
                flash.customSkin = cgs.media.grenadelFlashSkin_blue;
            } else
                flash.customSkin = cgs.media.grenadelFlashSkin_red;

        }

    }

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

    // colorize the railgun blast
    if ( weaponNum == WP_RAILGUN ) {
        clientInfo_t	*ci;

        ci = &cgs.clientinfo[ cent->currentState.clientNum ];

        if (cgs.gametype >= GT_TEAM) {

            if (cgs.clientinfo[ci->clientNum].team == TEAM_BLUE) {

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

                GetTeamColorsVEC3(color, color, TEAM_BLUE);

                flash.shaderRGBA[0] = color[0] * 255;
                flash.shaderRGBA[1] = color[1] * 255;
                flash.shaderRGBA[2] = color[2] * 255;

            } else {

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

                GetTeamColorsVEC3(color, color, TEAM_RED);

                flash.shaderRGBA[0] = color[0] * 255;
                flash.shaderRGBA[1] = color[1] * 255;
                flash.shaderRGBA[2] = color[2] * 255;

            }



        } else {

            flash.shaderRGBA[0] = 255 * ci->color1[0];
            flash.shaderRGBA[1] = 255 * ci->color1[1];
            flash.shaderRGBA[2] = 255 * ci->color1[2];
        }
    }

    CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash");
    trap_R_AddRefEntityToScene( &flash );

    if ( ps || cg.renderingThirdPerson ||
            cent->currentState.number != cg.predictedPlayerState.clientNum ) {
        // add lightning bolt
        CG_LightningBolt( nonPredictedCent, flash.origin );

        // add rail trail
        CG_SpawnRailTrail( cent, flash.origin );

        // make a dlight for the flash
        if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) {
            // BB COLORS: team tinting firing flashes
            if (cgs.gametype >= GT_TEAM) {

                if (team == TEAM_BLUE) {
                    color[0] = 0;
                    color[1] = 0;
                    color[2] = 1;

                    GetTeamColorsVEC3(color, color, TEAM_BLUE);
                } else {

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

                    GetTeamColorsVEC3(color, color, TEAM_RED);
                }



                trap_R_AddLightToScene( flash.origin, 200 + (rand()&31),
                                        color[0], color[1], color[2] );
            } else {
                trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0],
                                        weapon->flashDlightColor[1], weapon->flashDlightColor[2] );
            }
        }
    }
}  // end CG_AddPlayerWeapon()

/*
==============
CG_AddViewWeapon
 
Add the weapon, and flash for the player's view
==============
*/
void CG_AddViewWeapon( playerState_t *ps ) {
    refEntity_t	hand;
    centity_t	*cent;
    clientInfo_t	*ci;
    float		fovOffset;
    vec3_t		angles;
    weaponInfo_t	*weapon;

    if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
        return;
    }

    if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR_RED ) {
        return;
    }

    if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR_BLUE ) {
        return;
    }

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

    // no gun if in third person view or a camera is active
    //if ( cg.renderingThirdPerson || cg.cameraMode) {
    if ( cg.renderingThirdPerson ) {
        return;
    }


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

        if ( cg.predictedPlayerState.eFlags & EF_FIRING ) {
            // special hack for lightning gun...
            VectorCopy( cg.refdef.vieworg, origin );
            VectorMA( origin, -8, cg.refdef.viewaxis[2], origin );
            CG_LightningBolt( &cg_entities[ps->clientNum], origin );
        }
        return;
    }

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

    // drop gun lower at higher fov
    if ( cg_fov.integer > 90 ) {
        fovOffset = -0.2 * ( cg_fov.integer - 90 );
    } else {
        fovOffset = 0;
    }

    cent = &cg.predictedPlayerEntity;	// &cg_entities[cg.snap->ps.clientNum];
    CG_RegisterWeapon( ps->weapon );
    weapon = &cg_weapons[ ps->weapon ];

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

    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 = weapon->handsModel;
    hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT;

    // add everything onto the hand
    CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] );
}

/*
==============================================================================
 
WEAPON SELECTION
 
==============================================================================
*/

/*
===================
CG_DrawWeaponSelect
===================
*/
void CG_DrawWeaponSelect( void ) {
    int		i;
    int		bits;
    int		count;
    int		x, y, w;
    char	*name;
    float	*color;

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

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

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

    // count the number of weapons owned
    bits = cg.snap->ps.stats[ STAT_WEAPONS ];
    count = 0;
    for ( i = 1 ; i < 16 ; i++ ) {
        if ( bits & ( 1 << i ) ) {
            count++;
        }
    }

    x = 320 - count * 20;
    y = 380;

    for ( i = 1 ; i < 16 ; i++ ) {
        if ( !( bits & ( 1 << i ) ) ) {
            continue;
        }

        CG_RegisterWeapon( i );

        // draw weapon icon
        CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon );

        // draw selection marker
        if ( i == cg.weaponSelect ) {
            CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader );
        }

        // no ammo cross on top
        if ( !cg.snap->ps.ammo[ i ] ) {
            CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader );
        }

        x += 40;
    }

    // draw the selected name
    if ( cg_weapons[ cg.weaponSelect ].item ) {
        name = cg_weapons[ cg.weaponSelect ].item->pickup_name;
        if ( name ) {
            w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH;
            x = ( SCREEN_WIDTH - w ) / 2;
            CG_DrawBigStringColor(x, y - 22, name, color);
        }
    }

    trap_R_SetColor( NULL );
}


/*
===============
CG_WeaponSelectable
===============
*/
static qboolean CG_WeaponSelectable( int i ) {
   
	if ( !cg.snap->ps.ammo[i] ) {
        return qfalse;
    }

    if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) {
        return qfalse;
    }

    return qtrue;
}

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

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

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

    for ( i = 0 ; i < 16 ; i++ ) {
        cg.weaponSelect++;
        if ( cg.weaponSelect == 16 ) {
            cg.weaponSelect = 0;
        }
        if ( cg.weaponSelect == WP_GAUNTLET ) {
            continue;		// never cycle to gauntlet
        }
        if ( CG_WeaponSelectable( cg.weaponSelect ) ) {
            break;
        }
    }
    if ( i == 16 ) {
        cg.weaponSelect = original;
    }

    // Joe lastweap
    if (cg.weaponSelect != original)
        cg.lastWeap = original;
    // end Joe
}

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

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

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

    for ( i = 0 ; i < 16 ; i++ ) {
        cg.weaponSelect--;
        if ( cg.weaponSelect == -1 ) {
            cg.weaponSelect = 15;
        }
        if ( cg.weaponSelect == WP_GAUNTLET ) {
            continue;		// never cycle to gauntlet
        }
        if ( CG_WeaponSelectable( cg.weaponSelect ) ) {
            break;
        }
    }
    if ( i == 16 ) {
        cg.weaponSelect = original;
    }

    // Joe lastweap
    if (cg.weaponSelect != original)
        cg.lastWeap = original;
    // end Joe
}

/*
===============
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 > 15 ) {
        return;
    }

	if(!CG_WeaponSelectable(num)){
		return;
	}

    /*
	if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) {
        return;		// don't have the weapon
    }
	*/

	cg.weaponSelectTime = cg.time;
	//if(cg.snap->ps.ammo[num] == 0)
	//	return;

	cg.lastWeap = cg.weaponSelect;  // Joe lastweap

    cg.weaponSelect = num;
}

/*
===================
CG_OutOfAmmoChange
 
The current weapon has just run out of ammo
===================
*/
void CG_OutOfAmmoChange( void ) {
    int		i;

    cg.weaponSelectTime = cg.time;

    //for ( i = 10/*alliance - banshee - was 15 , cutoff autoswitch at weapon10*/ ; i > 0 ; i-- ) {
	for ( i = 15 ; i > 0 ; i-- ) {
        if ( CG_WeaponSelectable( i ) ) {
            cg.lastWeap = cg.weaponSelect;  // Joe lastweap
            cg.weaponSelect = i;
            break;
        }
    }
}



/*
===================================================================================================
 
WEAPON EVENTS
 
===================================================================================================
*/

/*
================
CG_FireWeapon
 
Caused by an EV_FIRE_WEAPON event
================
*/
void CG_FireWeapon( centity_t *cent ) {
    entityState_t *ent;
    int				c;
    weaponInfo_t	*weap;

    ent = &cent->currentState;
    if ( ent->weapon == WP_NONE ) {
        return;
    }
    if ( ent->weapon >= WP_NUM_WEAPONS ) {
        CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
        return;
    }
    weap = &cg_weapons[ ent->weapon ];

    // mark the entity as muzzle flashing, so when it is added it will
    // append the flash to the weapon model

    // Joe - no muzzle flash
    if (! BG_battleWeaponsEnabled()) // Joe combat weapons
        cent->muzzleFlashTime = cg.time;

    // lightning gun only does this this on initial press
    if ( ent->weapon == WP_LIGHTNING ) {
        if ( cent->pe.lightningFiring ) {
            return;
        }
    }

    // play quad sound if needed
    if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) {
        trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound );
    }

    // play a sound
    for ( c = 0 ; c < 4 ; c++ ) {
        if ( !weap->flashSound[c] ) {
            break;
        }
    }
    if ( c > 0 ) {
        c = rand() % c;
        if ( weap->flashSound[c] ) {
            trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] );
        }
    }

    // do brass ejection
    if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) {
        weap->ejectBrassFunc( cent );
    }

    //unlagged - attack prediction #1
    CG_PredictWeaponEffects( cent );
    //unlagged - attack prediction #1
}

/*
==================
CG_LightningSpark
==================
*/ //BB ADDED TEAM FOR COLORS

void CG_LightningSpark(vec3_t origin, vec3_t dir, int team) {
    int i, j;
    int count = random() * 2 + 1;
    float begin = 0;
    float gravity = 500;
    int normTime = 500;
    int randTime = 700;
    float normVel = 100;
    float randVel = 150;
    float radius = 3;
        vec3_t color;

    for (i = 0; i < count; i++) {
        localEntity_t	*le;
        refEntity_t		*re;

        le = CG_AllocLocalEntity();
        le->leFlags = LEF_PUFF_DONT_SCALE;
        le->leType = LE_MOVE_SCALE_FADE;
        le->startTime = cg.time;
        le->endTime = cg.time + normTime + random() * randTime;
        le->lifeRate = 1.0 / ( le->endTime - le->startTime );

        re = &le->refEntity;
        re->shaderTime = cg.time / 1000.0f;

        re->reType = RT_SPRITE;
        re->rotation = 0;
        re->radius = radius;

        //BB COLORS Sparks
        if (cgs.gametype >= GT_TEAM) {

            if (team == TEAM_BLUE)
                re->customShader = cgs.media.bulletSpark_blue;
            else
                re->customShader = cgs.media.bulletSpark_red;
        } else
            re->customShader = cgs.media.tracerShader;
        //re->customShader = trap_R_RegisterShader( "gfx/misc/bluespark" );

		if (cgs.gametype >= GT_TEAM) {

            if (team == TEAM_BLUE) {

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

                GetTeamColorsVEC3(color, color, TEAM_BLUE);

                re->shaderRGBA[0] = color[0] * 255;
                re->shaderRGBA[1] = color[1] * 255;
                re->shaderRGBA[2] = color[2] * 255;

            } else {

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

                GetTeamColorsVEC3(color, color, TEAM_RED);

                re->shaderRGBA[0] = color[0] * 255;
                re->shaderRGBA[1] = color[1] * 255;
                re->shaderRGBA[2] = color[2] * 255;

            }



        } else {
        re->shaderRGBA[0] = 255;
        re->shaderRGBA[1] = 255;
        re->shaderRGBA[2] = 255;
        re->shaderRGBA[3] = 255;
		}

        if (gravity) {
            le->pos.trType = TR_GRAVITY;
            le->gravity = gravity;
            le->customGravity = qtrue;
        } else {
            le->pos.trType = TR_LINEAR;
        }
        le->pos.trTime = cg.time;
        VectorCopy( origin, le->pos.trBase );

        VectorCopy(dir, le->pos.trDelta);
        for (j = 0; j < 3; j++) {
            le->pos.trDelta[j] += crandom() * 0.7;
        }
        VectorNormalize(le->pos.trDelta);
        VectorMA(le->pos.trBase, begin, le->pos.trDelta, le->pos.trBase);
        VectorScale(le->pos.trDelta, normVel + random() * randVel, le->pos.trDelta);
        le->pos.trDelta[2] += random() * 100;
    }
}

/*
==================
CG_BulletSpark
==================
*/
void CG_BulletSpark(vec3_t origin, vec3_t dir, int team) {
    int i, j;
    int count = 1;
    float begin = 0;
    float gravity = 500;
    int normTime = 500;
    int randTime = 700;
    float normVel = 100;
    float randVel = 150;
    float radius = 3;
    localEntity_t *smoke;
    vec3_t smokeOrigin, up;

    for (i = 0; i < count; i++) {
        localEntity_t	*le;
        refEntity_t		*re;

        le = CG_AllocLocalEntity();
        le->leFlags = LEF_PUFF_DONT_SCALE;
        le->leType = LE_MOVE_SCALE_FADE;
        le->startTime = cg.time;
        le->endTime = cg.time + normTime + random() * randTime;
        le->lifeRate = 1.0 / ( le->endTime - le->startTime );

        re = &le->refEntity;
        re->shaderTime = cg.time / 1000.0f;

        re->reType = RT_SPRITE;
        re->rotation = 0;
        re->radius = radius;

        //BB COLORS Sparks
        if (cgs.gametype >= GT_TEAM) {

            if (team == TEAM_BLUE)
                re->customShader = cgs.media.bulletSpark_blue;
            else
                re->customShader = cgs.media.bulletSpark_red;
        } else
            re->customShader = cgs.media.tracerShader;

        re->shaderRGBA[0] = 0xff;
        re->shaderRGBA[1] = 0xff;
        re->shaderRGBA[2] = 0xff;
        re->shaderRGBA[3] = 0xff;

        le->color[3] = 1.0;

        if (gravity) {
            le->pos.trType = TR_GRAVITY;
            le->gravity = gravity;
            le->customGravity = qtrue;
        } else {
            le->pos.trType = TR_LINEAR;
        }
        le->pos.trTime = cg.time;
        VectorCopy( origin, le->pos.trBase );

        VectorCopy(dir, le->pos.trDelta);
        for (j = 0; j < 3; j++) {
            le->pos.trDelta[j] += crandom() * 0.7;
        }
        VectorNormalize(le->pos.trDelta);
        VectorMA(le->pos.trBase, begin, le->pos.trDelta, le->pos.trBase);
        VectorScale(le->pos.trDelta, normVel + random() * randVel, le->pos.trDelta);
        le->pos.trDelta[2] += random() * 100;
    }

    VectorMA(origin, 4 + random() * 4, dir, smokeOrigin);
    VectorSet(up, 0, 0, random() * 12);

    //BB COLORS
    if (cgs.gametype >= GT_TEAM) {

        smoke = CG_TeamSmokePuff(
                    smokeOrigin, up, 10,
                    1, 1, 1, 0.6f,
                    700, cg.time, 0, cgs.media.smokePuffShader, team);

    } else
        smoke = CG_SmokePuff(smokeOrigin, up, 10,
                             1, 1, 1, 0.6f,
                             700, cg.time, 0, 0, cgs.media.smokePuffShader );

    // use the optimized local entity add
    smoke->leType = LE_MOVE_SCALE_FADE;
}


/*
===============
CG_BulletSplash
===============
*/
static qboolean	CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle );

void CG_BulletSplash(int clientNum, vec3_t end) {
    int i, j;
    int count = 5;
    float begin = 0;
    float gravity = 200;
    int normTime = 500;
    int randTime = 500;
    float normVel = 35;
    float randVel = 35;
    float radius = 1.5;
    int contents1, contents2, contents3;
    vec3_t start, origin, dir;
    trace_t trace;

    if (!CG_CalcMuzzlePoint(clientNum, start)) {
        return;
    }

    contents1 = CG_PointContents(start, clientNum);
    contents2 = CG_PointContents(end, clientNum);

    if (contents1 == contents2) {
        return;
    }

    if (contents1 & MASK_WATER) {
        trap_CM_BoxTrace(&trace, end, start, NULL, NULL, 0, MASK_WATER);
        VectorCopy(trace.endpos, origin);
        VectorCopy(trace.plane.normal, dir);
        contents3 = contents1;
    } else if (contents2 & MASK_WATER) {
        trap_CM_BoxTrace(&trace, start, end, NULL, NULL, 0, MASK_WATER);
        VectorCopy(trace.endpos, origin);
        VectorCopy(trace.plane.normal, dir);
        contents3 = contents2;
    } else {
        return;
    }


    for (i = 0; i < count; i++) {
        localEntity_t	*le;
        refEntity_t		*re;

        le = CG_AllocLocalEntity();
        le->leFlags = LEF_PUFF_DONT_SCALE;
        le->leType = LE_MOVE_SCALE_FADE;
        le->startTime = cg.time;
        le->endTime = cg.time + normTime + random() * randTime;
        le->lifeRate = 1.0 / ( le->endTime - le->startTime );

        re = &le->refEntity;
        re->shaderTime = cg.time / 1000.0f;

        re->reType = RT_SPRITE;
        re->rotation = 0;
        re->radius = radius;
        re->customShader = trap_R_RegisterShader( "gfx/misc/particle2" );

        if (contents3 & CONTENTS_LAVA) {
            re->shaderRGBA[0] = 100 + random() * 155;
            re->shaderRGBA[1] = 50;
            re->shaderRGBA[2] = 50;
        } else if (contents3 & CONTENTS_SLIME) {
            re->shaderRGBA[0] = 50;
            re->shaderRGBA[1] = 55 + random() * 200;
            re->shaderRGBA[2] = 50;
        } else {
            re->shaderRGBA[0] = 125;
            re->shaderRGBA[1] = 125;
            re->shaderRGBA[2] = 125 + random() * 130;
        }
        re->shaderRGBA[3] = 255;

        if (gravity) {
            le->pos.trType = TR_GRAVITY;
            le->gravity = gravity;
            le->customGravity = qtrue;
        } else {
            le->pos.trType = TR_LINEAR;
        }
        le->pos.trTime = cg.time;
        VectorCopy( origin, le->pos.trBase );

        VectorCopy(dir, le->pos.trDelta);
        for (j = 0; j < 3; j++) {
            le->pos.trDelta[j] += crandom() * 0.7;
        }
        VectorNormalize(le->pos.trDelta);
        VectorMA(le->pos.trBase, begin, le->pos.trDelta, le->pos.trBase);
        VectorScale(le->pos.trDelta, normVel + random() * randVel, le->pos.trDelta);
        le->pos.trDelta[2] += random() * randVel;
    }
}


/*
==================
CG_ParticleExplosion
 BattleBots
Particle explosion
==================
*/
/*
void CG_ParticleExplosion(vec3_t origin) {
    int			i, j;
    int count = 100;
    float begin = 20;
    float gravity = 500;
    int normTime = 500;
    int randTime = 1000;
    float normVel = 130;
    float randVel = 200;
    float normZVel = 100;
    float randZVel = 100;
    float radius = 2;
 
    for (i = 0; i < count; i++) {
        localEntity_t	*le;
        refEntity_t		*re;
 
        le = CG_AllocLocalEntity();
        le->leFlags = LEF_PUFF_DONT_SCALE;
        le->leType = LE_MOVE_SCALE_FADE;
        le->startTime = cg.time;
        le->endTime = cg.time + normTime + random() * randTime;
        le->lifeRate = 1.0 / ( le->endTime - le->startTime );
 
        re = &le->refEntity;
        re->shaderTime = cg.time / 1000.0f;
 
        re->reType = RT_SPRITE;
        re->rotation = 0;
        re->radius = radius;
        re->customShader = cgs.media.tracerShader;
        re->shaderRGBA[0] = 255;
        re->shaderRGBA[1] = 255;
        re->shaderRGBA[2] = 255;
        re->shaderRGBA[3] = 255;
        le->color[0] = re->shaderRGBA[0] / 255;
        le->color[1] = re->shaderRGBA[1] / 255;
        le->color[2] = re->shaderRGBA[2] / 255;
        le->color[3] = re->shaderRGBA[3] / 255;
 
        if (gravity) {
            le->pos.trType = TR_GRAVITY;
            le->gravity = gravity;
            le->customGravity = qtrue;
        } else {
            le->pos.trType = TR_LINEAR;
        }
        le->pos.trTime = cg.time;
        VectorCopy( origin, le->pos.trBase );
 
        for (j = 0; j < 3; j++) {
            // random number between -1 and 1
            le->pos.trDelta[j] = 2 * random() - 1;
        }
        VectorNormalize(le->pos.trDelta);
        VectorMA(le->pos.trBase, begin, le->pos.trDelta, le->pos.trBase);
        VectorScale(le->pos.trDelta, normVel + random() * randVel, le->pos.trDelta);
        le->pos.trDelta[2] += normZVel + random() * randZVel;
    }
}
*/

/*
=================
CG_MissileHitWall
 
Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
=================
*/
void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) {
    qhandle_t		mod;
    qhandle_t		mark;
    qhandle_t		shader;
    sfxHandle_t		sfx;
    float			radius;
    float			light;
    vec3_t			lightColor;
    localEntity_t	*le;
    int				r;
    qboolean		alphaFade;
    qboolean		isSprite;
    int				duration;
    vec3_t			sprOrg;
    vec3_t			sprVel;
    vec3_t railcolor;

    mark = 0;
    radius = 32;
    sfx = 0;
    mod = 0;
    shader = 0;
    light = 0;
    lightColor[0] = 1;
    lightColor[1] = 1;
    lightColor[2] = 0;


    // set defaults
    isSprite = qfalse;
    duration = 600;

    switch ( weapon ) {
    default:
#ifdef MISSIONPACK

    case WP_NAILGUN:
        if( soundType == IMPACTSOUND_FLESH ) {
            sfx = cgs.media.sfx_nghitflesh;
        } else if( soundType == IMPACTSOUND_METAL ) {
            sfx = cgs.media.sfx_nghitmetal;
        } else {
            sfx = cgs.media.sfx_nghit;
        }
        mark = cgs.media.holeMarkShader;
        radius = 12;
        break;
#endif

    case WP_LIGHTNING:
        // no explosion at LG impact, it is added with the beam
        r = rand() & 3;
        if ( r < 2 ) {
            sfx = cgs.media.sfx_lghit2;
        } else if ( r == 2 ) {
            sfx = cgs.media.sfx_lghit1;
        } else {
            sfx = cgs.media.sfx_lghit3;
        }

        // BB COLORS tint explosion
        // taking away check for combatweapons and putting one for gametype
        /*if (cgs.gametype >= GT_TEAM) {
            if (cgs.clientinfo[clientNum].team == TEAM_BLUE)
                shader = cgs.media.lightningExplosionShader_blue;
            else
                shader = cgs.media.lightningExplosionShader_red;

        } else
            shader = cgs.media.plasmaExplosionShader;*/

        mark = cgs.media.holeMarkShader;
        radius = 12;
        CG_LightningSpark(origin, dir, cgs.clientinfo[clientNum].team);
        break; 
#ifdef MISSIONPACK

    case WP_PROX_LAUNCHER:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.grenadeExplosionShader;
        sfx = cgs.media.sfx_proxexp;
        mark = cgs.media.burnMarkShader;
        radius = 64;
        light = 300;
        isSprite = qtrue;
        break;
#endif

    case WP_GRENADE_LAUNCHER:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.grenadeExplosionShader;

        // BB COLORS: tint explosion and explosion light
        if (cgs.gametype >= GT_TEAM &&
                cgs.clientinfo[clientNum].team == TEAM_BLUE
                && cgs.media.rocketExplosionShader_blue) // Joe
        {
            shader = cgs.media.rocketExplosionShader_blue;
            //lightColor[0] = 0.3f;
            //lightColor[1] = 0.3f;
            //lightColor[2] = 1;
            GetTeamColorsVEC3(lightColor, lightColor, TEAM_BLUE);
        } else if (cgs.gametype >= GT_TEAM &&
                   cgs.clientinfo[clientNum].team == TEAM_RED
                   && cgs.media.rocketExplosionShader_red) {

            shader = cgs.media.rocketExplosionShader_red;
            //lightColor[0] = 1;
            //lightColor[1] = 0.3f;
            //lightColor[2] = 0.3f;
            GetTeamColorsVEC3(lightColor, lightColor, TEAM_RED);
        }
        // BB COLORS END

        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 64;
        light = 300;
        isSprite = qtrue;
        break;

        //Alliance - Hand Grenades - Banshee
    case WP_HANDGRENADE:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.grenadeExplosionShader;

        // BB COLORS: tint explosion and explosion light
        if (cgs.gametype >= GT_TEAM &&
                cgs.clientinfo[clientNum].team == TEAM_BLUE
                && cgs.media.rocketExplosionShader_blue) // Joe
        {
            shader = cgs.media.rocketExplosionShader_blue;
            //lightColor[0] = 0.3f;
            //lightColor[1] = 0.3f;
            //lightColor[2] = 1;
            GetTeamColorsVEC3(lightColor, lightColor, TEAM_BLUE);
        } else if (cgs.gametype >= GT_TEAM &&
                   cgs.clientinfo[clientNum].team == TEAM_RED
                   && cgs.media.rocketExplosionShader_red) {

            shader = cgs.media.rocketExplosionShader_red;
            //lightColor[0] = 1;
            //lightColor[1] = 0.3f;
            //lightColor[2] = 0.3f;
            GetTeamColorsVEC3(lightColor, lightColor, TEAM_RED);
        }
        // BB COLORS END
        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 64;
        light = 300;
        isSprite = qtrue;
        break;
        //Alliance _ end

    case WP_ROCKET_LAUNCHER:
        mod = cgs.media.dishFlashModel;
        shader = cgs.media.rocketExplosionShader;

        lightColor[0] = 1;
        lightColor[1] = 0.75f;
        lightColor[2] = 0.0;

        // BB COLORS: tint explosion and explosion light
        if (cgs.gametype >= GT_TEAM &&
                cgs.clientinfo[clientNum].team == TEAM_BLUE
                && cgs.media.rocketExplosionShader_blue) // Joe
        {
            shader = cgs.media.rocketExplosionShader_blue;
            //lightColor[0] = 0.3f;
            //lightColor[1] = 0.3f;
            //lightColor[2] = 1;
            GetTeamColorsVEC3(lightColor, lightColor, TEAM_BLUE);
        } else if (cgs.gametype >= GT_TEAM &&
                   cgs.clientinfo[clientNum].team == TEAM_RED
                   && cgs.media.rocketExplosionShader_red) {

            shader = cgs.media.rocketExplosionShader_red;
            //lightColor[0] = 1;
            //lightColor[1] = 0.3f;
            //lightColor[2] = 0.3f;
            GetTeamColorsVEC3(lightColor, lightColor, TEAM_RED);
        }
        // BB COLORS END

        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 64;
        light = 300;
        isSprite = qtrue;
        duration = 1000;
        // Alliance: Myrkul: shorten explosion duration in combat mode
        if (BG_gamemode() == GM_BATTLE)
            duration = 800;
        //lightColor[0] = 1;
        //lightColor[1] = 0.75;
        //lightColor[2] = 0.0;

        if (cg_oldRocket.integer == 0) {
            // explosion sprite animation
            VectorMA( origin, 24, dir, sprOrg );
            VectorScale( dir, 64, sprVel );

            CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 );
        }

        break;
    case WP_RAILGUN:
        mod = cgs.media.ringFlashModel;
        shader = cgs.media.railExplosionShader;
        sfx = cgs.media.sfx_plasmaexp;
        mark = cgs.media.energyMarkShader;
        radius = 24;
        break;
    case WP_PLASMAGUN:
        mod = cgs.media.ringFlashModel;

        // BB COLORS tint explosion
        // taking away check for combatweapons and putting one for gametype
        if (cgs.gametype >= GT_TEAM) {
            if (cgs.clientinfo[clientNum].team == TEAM_BLUE)
                shader = cgs.media.plasmaExplosionShader_blue;
            else
                shader = cgs.media.plasmaExplosionShader_red;

        } else
            shader = cgs.media.plasmaExplosionShader;

        sfx = cgs.media.sfx_plasmaexp;
        mark = cgs.media.energyMarkShader;
        radius = 16;
        break;
    case WP_BFG:
        mod = cgs.media.dishFlashModel;

        // BB COLORS tint explosion
        if (cgs.gametype >= GT_TEAM) {
            if (cgs.clientinfo[clientNum].team == TEAM_BLUE)
                shader = cgs.media.bfgExplosionShader_blue;
            else
                shader = cgs.media.bfgExplosionShader_red;

        } else //BB end COLORS
            shader = cgs.media.bfgExplosionShader;


        sfx = cgs.media.sfx_rockexp;
        mark = cgs.media.burnMarkShader;
        radius = 32;
        isSprite = qtrue;
        break;
    case WP_SHOTGUN:
        mod = cgs.media.bulletFlashModel;

        // BB COLORS: Bullets
        if (cgs.gametype >= GT_TEAM) {

            if (cgs.clientinfo[clientNum].team == TEAM_BLUE) {
                shader =  cgs.media.bulletExplosionShader_blue;
            } else
                shader =  cgs.media.bulletExplosionShader_red;

        } else
            shader = cgs.media.bulletExplosionShader;

        mark = cgs.media.bulletMarkShader;
        sfx = 0;
        radius = 4;
        //BB
        CG_BulletSpark(origin, dir, cgs.clientinfo[clientNum].team);
        //BB end
        break;

#ifdef MISSIONPACK

    case WP_CHAINGUN:
        mod = cgs.media.bulletFlashModel;
        if( soundType == IMPACTSOUND_FLESH ) {
            sfx = cgs.media.sfx_chghitflesh;
        } else if( soundType == IMPACTSOUND_METAL ) {
            sfx = cgs.media.sfx_chghitmetal;
        } else {
            sfx = cgs.media.sfx_chghit;
        }
        mark = cgs.media.bulletMarkShader;

        r = rand() & 3;
        if ( r < 2 ) {
            sfx = cgs.media.sfx_ric1;
        } else if ( r == 2 ) {
            sfx = cgs.media.sfx_ric2;
        } else {
            sfx = cgs.media.sfx_ric3;
        }

        radius = 8;
        break;
#endif

    case WP_MACHINEGUN:
        mod = cgs.media.bulletFlashModel;

        // BB COLORS: Bullets
        if (cgs.gametype >= GT_TEAM) {

            if (cgs.clientinfo[clientNum].team == TEAM_BLUE) {
                shader =  cgs.media.bulletExplosionShader_blue;
            } else
                shader =  cgs.media.bulletExplosionShader_red;

        } else
            shader = cgs.media.bulletExplosionShader;

        mark = cgs.media.bulletMarkShader;

        r = rand() & 3;
        if ( r == 0 ) {
            sfx = cgs.media.sfx_ric1;
        } else if ( r == 1 ) {
            sfx = cgs.media.sfx_ric2;
        } else {
            sfx = cgs.media.sfx_ric3;
        }

        radius = 8;
        //BB
        CG_BulletSpark(origin, dir, cgs.clientinfo[clientNum].team);
        //BB end
        break;

	case WP_GRAPPLING_HOOK:

        switch(cg_serverConfig.grapple_onhand) 
		{

			case 1: //g_grappleonhand 1
			{
	            switch(cg_serverConfig.grappletype) 
				{
					case 0:
						sfx = cgs.media.grappleHit2;
						break;
					case 1:
						sfx = cgs.media.grappleHit1;
						break;
					case 2://lightning 
						sfx = cgs.media.sfx_lghit1;
						break;
					default:
						sfx = cgs.media.grappleHit1;
						break;
				}	
				mod = cgs.media.ringFlashModel;          // railgun explosion
				shader = cgs.media.railExplosionShader;
				mark = cgs.media.energyMarkShader;       // railgun wall mark
				radius = 24;
				CG_LightningSpark(origin, dir, cgs.clientinfo[clientNum].team);
			}
            break;

			case 0:	//WP_OFFHAND_GRAPPLING_HOOK:
			{
				switch(cg_serverConfig.grappletype) 
				{
					case 0:
					case 1:
					case 2:
					case 4:
					case 5:
						sfx = cgs.media.grappleHit2; //grapple trails -- see ui for grappletypes startserver_battle.c
						break;
					case 3:
						sfx = cgs.media.grappleHit1; //gamemode set uses chain or rope
						break;

					default:
						sfx = cgs.media.grappleHitSound;
						break;
				}

				mod = cgs.media.ringFlashModel;          // railgun explosion
				shader = cgs.media.railExplosionShader;
				mark = cgs.media.energyMarkShader;       // railgun wall mark
				radius = 24;
                CG_LightningSpark(origin, dir, cgs.clientinfo[clientNum].team);
            }
			break;
        }
   
    }

    if ( sfx ) {
        trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
    }

    //
    // create the explosion
    //
    if ( mod ) {
        le = CG_MakeExplosion( origin, dir,
                               mod,	shader,
                               duration, isSprite );
        le->light = light;

        //BB COLORS
        if (cgs.gametype >= GT_TEAM) {
            if ( cgs.clientinfo[clientNum].team == TEAM_RED ) {

                lightColor[0] = 1;
                lightColor[1] = 0;
                lightColor[2] = 0;
                GetTeamColorsVEC3(lightColor, lightColor, TEAM_RED);

            } else if ( cgs.clientinfo[clientNum].team == TEAM_BLUE ) {

                lightColor[0] = 0;
                lightColor[1] = 0;
                lightColor[2] = 1;
                GetTeamColorsVEC3(lightColor, lightColor, TEAM_BLUE);

            }

        }

        VectorCopy( lightColor, le->lightColor );

        if ( weapon == WP_RAILGUN ) {
            //BB COLORS
            if (cgs.gametype >= GT_TEAM) {
                if ( cgs.clientinfo[clientNum].team == TEAM_RED ) {

                    railcolor[0] = 1;
                    railcolor[1] = 0;
                    railcolor[2] = 0;
                    GetTeamColorsVEC3(railcolor, railcolor, TEAM_RED);

                    VectorCopy( railcolor, le->color );

                } else if ( cgs.clientinfo[clientNum].team == TEAM_BLUE ) {

                    railcolor[0] = 0;
                    railcolor[1] = 0;
                    railcolor[2] = 1;
                    GetTeamColorsVEC3(railcolor, railcolor, TEAM_BLUE);

                    VectorCopy( railcolor, le->color );
                }

            } else {
                // colorize with client color
                VectorCopy( cgs.clientinfo[clientNum].color1, le->color );
            }
        }
    }

    //
    // impact mark
    //
    alphaFade = (mark == cgs.media.energyMarkShader);	// plasma fades alpha, all others fade color
    if ( weapon == WP_RAILGUN ) {
        float	*color;

        // colorize with client color
        //BB COLORS
        if (cgs.gametype >= GT_TEAM) {
            if ( cgs.clientinfo[clientNum].team == TEAM_RED ) {

                railcolor[0] = 1;
                railcolor[1] = 0;
                railcolor[2] = 0;
                GetTeamColorsVEC3(railcolor, railcolor, TEAM_RED);

                CG_ImpactMark( mark, origin, dir, random()*360,
                               railcolor[0],railcolor[1],railcolor[2],1,
                               alphaFade, radius, qfalse );

            } else if ( cgs.clientinfo[clientNum].team == TEAM_BLUE ) {

                railcolor[0] = 0;
                railcolor[1] = 0;
                railcolor[2] = 1;
                GetTeamColorsVEC3(railcolor, railcolor, TEAM_BLUE);

                CG_ImpactMark( mark, origin, dir, random()*360,
                               railcolor[0],railcolor[1],railcolor[2],1,
                               alphaFade, radius, qfalse );
            }

        } else {

            color = cgs.clientinfo[clientNum].color2;

            CG_ImpactMark( mark, origin, dir, random()*360,
                           color[0],color[1], color[2],1, alphaFade,
                           radius, qfalse );
        }

    } else {

        CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse );

    }
}


/*
=================
CG_MissileHitPlayer
=================
*/
void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum, int team ) {

    CG_Bleed( origin, entityNum );

    // some weapons will make an explosion with the blood, while
    // others will just make the blood
    switch ( weapon ) {

        // Joe combat weapons, have bfg explode when it hits a player
    case WP_BFG:
        if (BG_battleWeaponsEnabled())
            // Joe - arg2 is clientNum, might as well use it!!
            CG_MissileHitWall( weapon, team, origin, dir, IMPACTSOUND_FLESH );
        else
            ;  // do nothing
        break;
        // end Joe

    case WP_GRENADE_LAUNCHER:
		 CG_MissileHitWall( weapon, team, origin, dir, IMPACTSOUND_FLESH );
        break;

        //Alliance -Hand Grenade - Banshee
    case WP_HANDGRENADE:
        //Alliance - End
		 CG_MissileHitWall( weapon, team, origin, dir, IMPACTSOUND_FLESH );
        break;

    case WP_ROCKET_LAUNCHER:
        // Joe - sending in owner instead of 0 and the code actually makes
        // sense
        CG_MissileHitWall( weapon, team, origin, dir, IMPACTSOUND_FLESH );
        break;

    case WP_GRAPPLING_HOOK:
        trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.gibBounce1Sound );
        break;
#ifdef MISSIONPACK

    case WP_NAILGUN:
    case WP_CHAINGUN:
    case WP_PROX_LAUNCHER:
        CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH );
        break;
#endif

    default:
        break;
    }
}



/*
============================================================================
 
SHOTGUN TRACING
 
============================================================================
*/

/*
================
CG_ShotgunPellet
================
*/
static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) {
    trace_t		tr;
    int sourceContentType, destContentType;

    CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT );

    sourceContentType = trap_CM_PointContents( start, 0 );
    destContentType = trap_CM_PointContents( tr.endpos, 0 );

    // FIXME: should probably move this cruft into CG_BubbleTrail
    if ( sourceContentType == destContentType ) {
        if ( sourceContentType & CONTENTS_WATER ) {
            CG_BubbleTrail( start, tr.endpos, 32 );
        }
    } else if ( sourceContentType & CONTENTS_WATER ) {
        trace_t trace;

        trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER );
        CG_BubbleTrail( start, trace.endpos, 32 );
    } else if ( destContentType & CONTENTS_WATER ) {
        trace_t trace;

        trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER );
        CG_BubbleTrail( tr.endpos, trace.endpos, 32 );
    }

    if (  tr.surfaceFlags & SURF_NOIMPACT ) {
        return;
    }

    if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) {
        CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum, 0 );
    } else {
        if ( tr.surfaceFlags & SURF_NOIMPACT ) {
            // SURF_NOIMPACT will not make a flame puff or a mark
            return;
        }
        if ( tr.surfaceFlags & SURF_METALSTEPS ) {
            CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL );
        } else {
            CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT );
        }
    }
}

/*
================
CG_ShotgunPattern
 
Perform the same traces the server did to locate the
hit splashes
================
*/
//unlagged - attack prediction
// made this non-static for access from cg_unlagged.c
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;

    int shotgun_count, shotgun_spread;

    // 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 < DEFAULT_SHOTGUN_COUNT ; i++ ) {
            r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
            u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
            VectorMA( origin, 8192 * 16, forward, end);
            VectorMA (end, r, right, end);
            VectorMA (end, u, up, end);
     
            CG_ShotgunPellet( origin, end, otherEntNum );
        }
    */

    // begin Joe combat weapons
    // not random
    if (BG_battleWeaponsEnabled() &&
            BG_superShotgun()  // last 3 pellets are random
       ) {
        for ( i = 0 ; i < BATTLE_SHOTGUN_COUNT ; i++ ) {
            // pellets set in initPellets() in CG_RegisterWeapon()
            r = pellets[i][0];
            u = pellets[i][1];

            VectorMA( origin, 8192, forward, end);
            VectorMA (end, r, right, end);
            VectorMA (end, u, up, end);

            CG_ShotgunPellet( origin, end, otherEntNum );
        }
        //CG_Printf("crock mode\n");
    }
    else {
        if (BG_superShotgun()) {
            //CG_Printf("crock mode\n");

            shotgun_spread = SUPER_SHOTGUN_SPREAD;
            shotgun_count  = SUPER_SHOTGUN_COUNT;

        } else {
            shotgun_spread = DEFAULT_SHOTGUN_SPREAD;
            shotgun_count  = DEFAULT_SHOTGUN_COUNT;
        }

        // end Joe combat weapons

        for ( i = 0 ; i < shotgun_count ; i++ ) {
            r = crandom() * shotgun_spread;
            u = crandom() * shotgun_spread;

            VectorMA( origin, 8192, forward, end);
            VectorMA (end, r, right, end);
            VectorMA (end, u, up, end);

            CG_ShotgunPellet( origin, end, otherEntNum );
        }
    }

}

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

    VectorSubtract( es->origin2, es->pos.trBase, v );
    VectorNormalize( v );
    VectorScale( v, 32, v );
    VectorAdd( es->pos.trBase, v, v );
    if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) {
        // ragepro can't alpha fade, so don't even bother with smoke
        //        vec3_t			up;

        contents = trap_CM_PointContents( es->pos.trBase, 0 );
        if ( !( contents & CONTENTS_WATER ) ) {
            //BB
            cg.shotgunTime[es->otherEntityNum] = cg.time + 350;
            /*
            VectorSet( up, 0, 0, 8 );
            CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader );
            */
            //BB end
        }
    }
    CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum );
}

/*
============================================================================
 
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 ) {
    trace_t trace;
    int sourceContentType, destContentType;
    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 ) ) {
            sourceContentType = trap_CM_PointContents( start, 0 );
            destContentType = trap_CM_PointContents( end, 0 );

            // do a complete bubble trail if necessary
            if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) {
                CG_BubbleTrail( start, end, 32 );
            }
            // bubble trail from water into air
            else if ( ( sourceContentType & CONTENTS_WATER ) ) {
                trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER );
                CG_BubbleTrail( start, trace.endpos, 32 );
            }
            // bubble trail from air into water
            else if ( ( destContentType & CONTENTS_WATER ) ) {
                trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER );
                CG_BubbleTrail( trace.endpos, end, 32 );
            }

            // draw a tracer
            if ( random() < cg_tracerChance.value ) {
                CG_Tracer( start, end );
            }
        }
    }

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

}
