1810 lines
42 KiB
C
1810 lines
42 KiB
C
// Copyright (C) 2001-2002 Raven Software.
|
|
//
|
|
// cg_weapons.c -- events and effects dealing with weapons
|
|
|
|
#include "cg_local.h"
|
|
#include "../game/bg_weapons.h"
|
|
|
|
// set up the appropriate ghoul2 info to a refent
|
|
void CG_SetGhoul2InfoRef( refEntity_t *ent, refEntity_t *s1)
|
|
{
|
|
ent->ghoul2 = s1->ghoul2;
|
|
VectorCopy( s1->modelScale, ent->modelScale);
|
|
ent->radius = s1->radius;
|
|
VectorCopy( s1->angles, ent->angles);
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_CalcMuzzlePoint
|
|
======================
|
|
*/
|
|
static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzlePoint)
|
|
{
|
|
weaponInfo_t *weaponInfo;
|
|
centity_t *cent;
|
|
refEntity_t muzzle;
|
|
int muzzleBolt;
|
|
attackInfo_t *attackInfo;
|
|
vec3_t rootAngles;
|
|
vec3_t lowerTorsoAngles;
|
|
vec3_t upperTorsoAngles;
|
|
vec3_t headAngles;
|
|
|
|
cent = CG_GetEntity ( entityNum );
|
|
if ( !cent->currentValid )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
BG_PlayerAngles( cent->lerpAngles,
|
|
NULL,
|
|
|
|
rootAngles,
|
|
lowerTorsoAngles,
|
|
upperTorsoAngles,
|
|
headAngles,
|
|
|
|
cent->currentState.leanOffset - LEAN_OFFSET,
|
|
|
|
cent->pe.painTime,
|
|
cent->pe.painDirection,
|
|
cg.time,
|
|
|
|
¢->pe.torso,
|
|
¢->pe.legs,
|
|
|
|
cg.frametime,
|
|
cent->lerpVelocity,
|
|
(cent->currentState.eFlags & EF_DEAD),
|
|
cent->currentState.angles2[YAW],
|
|
cent->ghoul2 );
|
|
|
|
memset( &muzzle, 0, sizeof( muzzle ) );
|
|
|
|
if (!trap_G2_HaveWeGhoul2Models(cent->ghoul2))
|
|
{ // no player model and/or no gun!
|
|
return qfalse;
|
|
}
|
|
|
|
weaponInfo = &cg_weapons[cent->currentState.weapon];
|
|
|
|
if ( cent->currentState.eFlags & EF_ALT_FIRING )
|
|
{
|
|
attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE];
|
|
}
|
|
else
|
|
{
|
|
attackInfo = &weaponInfo->attack[ATTACK_NORMAL];
|
|
}
|
|
|
|
muzzleBolt = attackInfo->muzzleFlashBoltWorld;
|
|
|
|
if ( muzzleBolt == -1 )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( G2_PositionEntityOnBolt(&muzzle,
|
|
cent->ghoul2, cent->pe.weaponModelSpot, muzzleBolt,
|
|
cent->lerpOrigin, rootAngles, cent->modelScale))
|
|
{
|
|
VectorCopy(muzzle.origin, muzzlePoint);
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CG_PlayerWeaponEffects
|
|
|
|
Add any weapon effects like muzzle flash or ejecting brass
|
|
=============
|
|
*/
|
|
void CG_PlayerWeaponEffects ( refEntity_t *parent, centity_t *cent, int team, vec3_t newAngles )
|
|
{
|
|
weapon_t weaponNum;
|
|
const weaponInfo_t *weaponInfo;
|
|
const weaponData_t *weaponDat;
|
|
const attackInfo_t *attackInfo;
|
|
const attackData_t *attackDat;
|
|
refEntity_t flash;
|
|
|
|
cent->flashBoltInterface.isValid = qfalse;
|
|
cent->ejectBoltInterface.isValid = qfalse;
|
|
weaponNum = cent->currentState.weapon;
|
|
|
|
weaponInfo = &cg_weapons[weaponNum];
|
|
weaponDat = &weaponData[weaponNum];
|
|
|
|
// Dont do this for the player unless they are in 3rd person
|
|
if ( !cg.renderingThirdPerson )
|
|
{
|
|
if ( cent->currentState.number == cg.predictedPlayerState.clientNum )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Make sure the player model is valid to attach to
|
|
if ( !trap_G2_HaveWeGhoul2Models(cent->ghoul2) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// The muzzle flash attach position needs to be updated for the duration of the effect
|
|
if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_EFFECT_TIME )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Alt fire or standard fire?
|
|
attackInfo = &weaponInfo->attack[cent->muzzleFlashAttack];
|
|
attackDat = &weaponDat->attack[cent->muzzleFlashAttack];
|
|
|
|
// Position the flash entity on the muzzle flash bolt
|
|
memset (&flash, 0, sizeof(flash));
|
|
if ( !G2_PositionEntityOnBolt( &flash,
|
|
cent->ghoul2,
|
|
cent->pe.weaponModelSpot,
|
|
attackInfo->muzzleFlashBoltWorld,
|
|
cent->lerpOrigin,
|
|
newAngles,
|
|
cent->modelScale ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Keep the muzzle flash moving
|
|
cent->flashBoltInterface.isValid = qtrue;
|
|
cent->flashBoltInterface.ghoul2 = cent->ghoul2;
|
|
cent->flashBoltInterface.modelNum = cent->pe.weaponModelSpot;
|
|
cent->flashBoltInterface.boltNum = attackInfo->muzzleFlashBoltWorld;
|
|
|
|
VectorCopy ( flash.origin, cent->flashBoltInterface.origin );
|
|
VectorCopy ( flash.axis[0], cent->flashBoltInterface.dir );
|
|
VectorCopy ( flash.axis[0], cent->flashBoltInterface.forward );
|
|
VectorCopy ( cent->modelScale, cent->flashBoltInterface.scale );
|
|
|
|
// No more flash
|
|
if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// First frame of the flash?
|
|
if ( cg.time == cent->muzzleFlashTime )
|
|
{
|
|
// Ejecting brass
|
|
if ( attackInfo->shellEject && cg_shellEjection.integer )
|
|
{
|
|
refEntity_t eject;
|
|
|
|
memset ( &eject, 0, sizeof(eject) );
|
|
|
|
// Now position the flash entity on the bolt itself.
|
|
if ( G2_PositionEntityOnBolt( &eject,
|
|
cent->ghoul2,
|
|
cent->pe.weaponModelSpot,
|
|
attackInfo->shellEjectBoltWorld,
|
|
cent->lerpOrigin,
|
|
newAngles,
|
|
cent->modelScale ) )
|
|
{
|
|
vec3_t angles;
|
|
|
|
eject.axis[0][0] = -eject.axis[0][0];
|
|
eject.axis[0][1] = -eject.axis[0][1];
|
|
eject.axis[0][2] = -eject.axis[0][2];
|
|
|
|
vectoangles ( eject.axis[0], angles );
|
|
angles[YAW] += 90;
|
|
AnglesToAxis ( angles, eject.axis );
|
|
|
|
trap_FX_PlayEntityEffectID ( attackInfo->shellEject, eject.origin, eject.axis, -1, -1, -1, -1 );
|
|
}
|
|
}
|
|
|
|
// Muzzle Flash
|
|
if ( attackInfo->muzzleEffect )
|
|
{
|
|
|
|
if (attackDat->weaponFlags & UNLOCK_MUZZLEFLASH )
|
|
{
|
|
// Muzzle flash not locked to barrel.
|
|
trap_FX_PlayEffectID(attackInfo->muzzleEffectInWorld, flash.origin, flash.axis[0], -1, -1 );
|
|
}
|
|
else
|
|
{
|
|
trap_FX_PlayBoltedEffectID ( attackInfo->muzzleEffectInWorld, ¢->flashBoltInterface, -1, -1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a dlight to the scene if it has a muzzle effect
|
|
if ( attackInfo->muzzleEffect && weaponNum != WP_MP5 )
|
|
{
|
|
trap_R_AddLightToScene( flash.origin, 200, 0.6f, 0.4f, 0.2f );
|
|
}
|
|
}
|
|
|
|
// FIMXE: This is defined in a C++ header file.
|
|
// We need to break it up or somethin so we don't have mutliple defs.
|
|
#define GHOUL2_NORENDER 2
|
|
|
|
/*
|
|
==============
|
|
CG_StartViewAnimation
|
|
|
|
Start a view animation for the given weapon and model index
|
|
==============
|
|
*/
|
|
void CG_StartViewWeaponAnimation ( int weapon, int modelindex, int choice, TAnimInfoWeapon* aIW )
|
|
{
|
|
weaponInfo_t *weaponInfo;
|
|
int boneMode;
|
|
|
|
weaponInfo = &cg_weapons[weapon];
|
|
if ( weaponInfo->viewG2Indexes[modelindex] == -1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
boneMode = BONE_ANIM_OVERRIDE_DEFAULT;
|
|
|
|
/*
|
|
if (cg_animBlend.integer)
|
|
{
|
|
boneMode |= BONE_ANIM_BLEND;
|
|
}
|
|
*/
|
|
|
|
trap_G2API_SetBoneAnim( weaponInfo->viewG2Model,
|
|
weaponInfo->viewG2Indexes[modelindex],
|
|
"model_root",
|
|
aIW->mStartFrame[choice],
|
|
aIW->mStartFrame[choice] + aIW->mNumFrames[choice],
|
|
boneMode,
|
|
50.0f / (1000.0f / aIW->mFPS[choice]) * aIW->mSpeed,
|
|
cg.time,
|
|
aIW->mStartFrame[choice],
|
|
150);
|
|
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CG_AnimateViewWeapon
|
|
|
|
Animation the view weapon
|
|
==============
|
|
*/
|
|
void CG_AnimateViewWeapon ( playerState_t *ps )
|
|
{
|
|
int i;
|
|
int flags;
|
|
weaponInfo_t *weaponInfo;
|
|
|
|
if( ps->pm_type == PM_SPECTATOR )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < 5; i ++ )
|
|
{
|
|
if ( !cg.viewWeaponAnim[i] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CG_StartViewWeaponAnimation ( ps->weapon, i, i==0?ps->weaponAnimIdChoice:0, cg.viewWeaponAnim[i] );
|
|
cg.viewWeaponAnim[i] = NULL;
|
|
}
|
|
|
|
// Need to turn on/off anything?
|
|
if(!cg.weaponHideModels)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Ok... turn on/off models.
|
|
weaponInfo=&cg_weapons[ps->weapon];
|
|
for ( i=0; i < 8; i++ )
|
|
{
|
|
if( weaponInfo->viewG2Indexes[i] == -1 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
flags=trap_G2API_GetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i]);
|
|
if(cg.weaponHideModels&(1<<i))
|
|
{
|
|
trap_G2API_SetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i],
|
|
flags|GHOUL2_NORENDER);
|
|
}
|
|
else
|
|
{
|
|
trap_G2API_SetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i],
|
|
flags&=~GHOUL2_NORENDER);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CG_UpdateViewWeaponSurfaces
|
|
|
|
Update the on/off bullet suraces of the inview weapon
|
|
==============
|
|
*/
|
|
#define MAX_VIEWWEAPON_BULLETS 6
|
|
void CG_UpdateViewWeaponSurfaces ( playerState_t* ps )
|
|
{
|
|
int numBullets;
|
|
int i;
|
|
const weaponInfo_t *weaponInfo;
|
|
|
|
weaponInfo = &cg_weapons[ps->weapon];
|
|
|
|
numBullets = ps->clip[ATTACK_NORMAL][ps->weapon];
|
|
|
|
// When reloading just show it at zero, its easier that way
|
|
if ( ps->weaponstate == WEAPON_RELOADING || ps->weaponstate == WEAPON_RELOADING_ALT )
|
|
{
|
|
numBullets = 0;
|
|
}
|
|
// Dont have more than the max bullets
|
|
else if (numBullets > MAX_VIEWWEAPON_BULLETS)
|
|
{
|
|
// toggle all of them
|
|
numBullets = MAX_VIEWWEAPON_BULLETS;
|
|
}
|
|
|
|
// Make sure its registered
|
|
if ( !weaponInfo->registered )
|
|
{
|
|
CG_RegisterWeapon ( ps->weapon );
|
|
}
|
|
|
|
// No model, nothing to do
|
|
if ( !weaponInfo->viewG2Model )
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch ( ps->weapon )
|
|
{
|
|
case WP_RPG7_LAUNCHER:
|
|
trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],"rockethead",numBullets?0:G2SURFACEFLAG_OFF);
|
|
trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],"rockettail",numBullets?0:G2SURFACEFLAG_OFF);
|
|
break;
|
|
|
|
case WP_M1911A1_PISTOL:
|
|
case WP_USSOCOM_PISTOL:
|
|
{
|
|
int forward;
|
|
int backward;
|
|
|
|
if ( numBullets == 0 )
|
|
{
|
|
forward = G2SURFACEFLAG_OFF;
|
|
backward = 0;
|
|
}
|
|
else
|
|
{
|
|
forward = 0;
|
|
backward = G2SURFACEFLAG_OFF;
|
|
}
|
|
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slide", forward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidef", forward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidel", forward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slider", forward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slideb", forward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slide_off", backward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidef_off", backward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidel_off", backward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slider_off", backward );
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slideb_off", backward );
|
|
|
|
break;
|
|
}
|
|
|
|
case WP_M60_MACHINEGUN:
|
|
// Turn on whats available
|
|
for ( i = 0; i < numBullets; i++ )
|
|
{
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], va("bullet%d", i + 1), 0 );
|
|
}
|
|
|
|
// Turn the rest off
|
|
for (; i < MAX_VIEWWEAPON_BULLETS; i++)
|
|
{
|
|
trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], va("bullet%d", i + 1), G2SURFACEFLAG_OFF );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CG_AddViewWeapon
|
|
|
|
Add the weapon, and flash for the player's view
|
|
==============
|
|
*/
|
|
void CG_AddViewWeapon(playerState_t *ps)
|
|
{
|
|
refEntity_t gun;
|
|
refEntity_t flash;
|
|
centity_t *cent;
|
|
float fovOffset;
|
|
const weaponInfo_t *weaponInfo;
|
|
const weaponData_t *weaponDat;
|
|
const attackInfo_t *attackInfo;
|
|
const attackData_t *attackDat;
|
|
vec3_t angles;
|
|
int delta;
|
|
vec3_t forward;
|
|
vec3_t vangles;
|
|
int speed;
|
|
|
|
cg.flashBoltInterface.isValid = qfalse;
|
|
|
|
if(ps->pm_type == PM_SPECTATOR || ps->persistant[PERS_TEAM] == TEAM_SPECTATOR )
|
|
{
|
|
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;
|
|
}
|
|
|
|
cent = &cg_entities[ps->clientNum];
|
|
|
|
// allow the gun to be completely removed
|
|
if( !cg_drawGun.integer || (ps->pm_flags & PMF_ZOOMED) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// drop gun lower at higher fov
|
|
if ( cg_fov.integer > 90 )
|
|
{
|
|
fovOffset = -0.2 * ( cg_fov.integer - 90 );
|
|
}
|
|
else
|
|
{
|
|
fovOffset = 0;
|
|
}
|
|
|
|
CG_RegisterWeapon(ps->weapon);
|
|
weaponInfo = &cg_weapons[ps->weapon];
|
|
weaponDat = &weaponData[ps->weapon];
|
|
|
|
// Add the weapon and hands models.
|
|
memset(&gun,0,sizeof(gun));
|
|
gun.ghoul2 = weaponInfo->viewG2Model;
|
|
if(!trap_G2_HaveWeGhoul2Models(gun.ghoul2))
|
|
{
|
|
// No weapon to draw!
|
|
return;
|
|
}
|
|
|
|
VectorCopy ( cg.refdef.vieworg, gun.origin );
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
|
|
{
|
|
VectorMA ( gun.origin, (-20.0f * (float)cg.predictedPlayerState.stats[STAT_USEWEAPONDROP]/300.0f), cg.refdef.viewaxis[0], gun.origin );
|
|
VectorMA ( gun.origin, (-20.0f * (float)cg.predictedPlayerState.stats[STAT_USEWEAPONDROP]/300.0f), cg.refdef.viewaxis[2], gun.origin );
|
|
}
|
|
|
|
// Add movement bobbing
|
|
gun.origin[2] += cg.xyspeed * cg.bobfracsin * 0.0015f;
|
|
|
|
// Add landing offset
|
|
delta = cg.time - cg.landTime;
|
|
if ( delta < LAND_DEFLECT_TIME )
|
|
{
|
|
gun.origin[2] += cg.landChange * 0.25f * delta / LAND_DEFLECT_TIME;
|
|
}
|
|
else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME )
|
|
{
|
|
gun.origin[2] += cg.landChange * 0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME;
|
|
}
|
|
|
|
// Add movement offset
|
|
speed = sqrt( cg.predictedPlayerState.velocity[0] * cg.predictedPlayerState.velocity[0] +
|
|
cg.predictedPlayerState.velocity[1] * cg.predictedPlayerState.velocity[1] +
|
|
cg.predictedPlayerState.velocity[2] * cg.predictedPlayerState.velocity[2]);
|
|
|
|
vectoangles ( cg.predictedPlayerState.velocity, vangles );
|
|
vangles[1] += (360 - cg.predictedPlayerState.viewangles[1]);
|
|
vangles[2] += (360 - cg.predictedPlayerState.viewangles[2]);
|
|
AngleVectors ( vangles, forward, NULL, NULL);
|
|
|
|
VectorScale ( forward, speed, forward );
|
|
|
|
VectorMA( gun.origin, forward[1] * 0.003f, cg.refdef.viewaxis[1], gun.origin );
|
|
|
|
// Set model scale
|
|
VectorSet ( gun.modelScale,weaponParseInfo[ps->weapon].mForeshorten,1.0f,1.0f );
|
|
|
|
vectoangles(cg.refdef.viewaxis[0],angles);
|
|
AnglesToAxis(angles,gun.axis);
|
|
CG_ScaleModelAxis(&gun);
|
|
|
|
VectorCopy(gun.origin,gun.oldorigin);
|
|
gun.renderfx=RF_DEPTHHACK|RF_FIRST_PERSON|RF_MINLIGHT|RF_NO_FOG;
|
|
gun.radius=64;
|
|
|
|
trap_R_AddRefEntityToScene(&gun);
|
|
|
|
// The muzzle flash attach position needs to be updated for the duration of the effect
|
|
if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_EFFECT_TIME )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Grab the proper attack info
|
|
if(cent->muzzleFlashAttack )
|
|
{
|
|
attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE];
|
|
attackDat = &weaponDat->attack[ATTACK_ALTERNATE];
|
|
}
|
|
else
|
|
{
|
|
attackInfo = &weaponInfo->attack[ATTACK_NORMAL];
|
|
attackDat = &weaponDat->attack[ATTACK_NORMAL];
|
|
}
|
|
|
|
// Update the muzzle flashes origins
|
|
memset(&flash,0,sizeof(flash));
|
|
VectorCopy ( gun.modelScale, flash.modelScale );
|
|
if(!G2_PositionEntityOnBolt( &flash,
|
|
gun.ghoul2,
|
|
1,
|
|
attackInfo->muzzleFlashBoltView,
|
|
gun.origin,
|
|
angles,
|
|
flash.modelScale ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Keep the muzzle flash origins updated
|
|
cg.flashBoltInterface.isValid = qtrue;
|
|
cg.flashBoltInterface.ghoul2 = gun.ghoul2;
|
|
cg.flashBoltInterface.modelNum = 1;
|
|
cg.flashBoltInterface.boltNum = attackInfo->muzzleFlashBoltView;
|
|
|
|
VectorCopy(flash.axis[0],cg.flashBoltInterface.dir );
|
|
VectorCopy(flash.axis[0],cg.flashBoltInterface.forward );
|
|
VectorCopy(flash.origin,cg.flashBoltInterface.origin);
|
|
VectorCopy(flash.modelScale,cg.flashBoltInterface.scale);
|
|
|
|
// If no muzzle flash to handle then there is nothing left to do
|
|
if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// First frame for the muzzle flash is when the effects are added
|
|
if ( cg.time == cent->muzzleFlashTime )
|
|
{
|
|
// Add the muzzle flash
|
|
if( attackInfo->muzzleEffect )
|
|
{
|
|
// Handle locked and unlocked muzzle flashes
|
|
if(attackDat->weaponFlags & UNLOCK_MUZZLEFLASH )
|
|
{
|
|
trap_FX_PlayEffectID(attackInfo->muzzleEffect,flash.origin,flash.axis[0], -1, -1 );
|
|
}
|
|
else
|
|
{
|
|
trap_FX_PlayBoltedEffectID(attackInfo->muzzleEffect,&cg.flashBoltInterface, -1, -1 );
|
|
}
|
|
}
|
|
|
|
// Add shell ejection
|
|
if ( attackInfo->shellEject && cg_shellEjection.integer )
|
|
{
|
|
refEntity_t shellEject;
|
|
|
|
// Handle muzzle flashes
|
|
memset(&shellEject,0,sizeof(shellEject));
|
|
|
|
if(G2_PositionEntityOnBolt( &shellEject,
|
|
weaponInfo->viewG2Model,
|
|
1,
|
|
attackInfo->shellEjectBoltView,
|
|
cg.refdef.vieworg,
|
|
cg.refdef.viewangles,
|
|
gun.modelScale ) )
|
|
{
|
|
// Flip the forward axis so it goes backwards rather than forwards
|
|
shellEject.axis[0][0] = -shellEject.axis[0][0];
|
|
shellEject.axis[0][1] = -shellEject.axis[0][1];
|
|
shellEject.axis[0][2] = -shellEject.axis[0][2];
|
|
|
|
// Play the entity with a full axis
|
|
trap_FX_PlayEntityEffectID(attackInfo->shellEject,shellEject.origin,shellEject.axis, -1, -1, -1, -1 );
|
|
}
|
|
}
|
|
|
|
// Add tracers
|
|
if ( attackInfo->tracerEffect && !(attackDat->projectileLifetime & PROJECTILE_FIRE))
|
|
{
|
|
// Lower the amount of tracers
|
|
if ( rand()%100 < (cg_tracerChance.value * 100.0f) )
|
|
{
|
|
vec3_t origin;
|
|
VectorMA ( flash.origin, 500, gun.axis[0], origin );
|
|
trap_FX_PlayEffectID ( attackInfo->tracerEffect, origin, gun.axis[0], -1, -1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a dlight when there is a muzzle effect
|
|
if ( attackInfo->muzzleEffect && ps->weapon != WP_MP5 )
|
|
{
|
|
trap_R_AddLightToScene( flash.origin, 200, 0.6f, 0.4f, 0.2f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
WEAPON SELECTION
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
CG_WeaponSelectable
|
|
===============
|
|
*/
|
|
qboolean CG_WeaponSelectable( int i, qboolean allowEmpty )
|
|
{
|
|
int ammo;
|
|
|
|
if ( ! (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << i ) ) )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( allowEmpty && (cg.predictedPlayerState.pm_flags & PMF_LIMITED_INVENTORY) )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if ( BG_WeaponHasAlternateAmmo ( i ) )
|
|
{
|
|
if ( cg.predictedPlayerState.ammo[ weaponData[i].attack[ATTACK_ALTERNATE].ammoIndex ] ||
|
|
cg.predictedPlayerState.clip[ATTACK_ALTERNATE][ i ] )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
// Start with normal ammo
|
|
ammo = 0;
|
|
ammo += cg.predictedPlayerState.clip[ATTACK_NORMAL][i];
|
|
ammo += cg.predictedPlayerState.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex];
|
|
|
|
if ( !ammo )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_NextWeapon
|
|
|
|
selects the next weapon in the players inventory
|
|
===============
|
|
*/
|
|
void CG_NextWeapon ( qboolean allowEmpty, int exclude )
|
|
{
|
|
int i;
|
|
int original;
|
|
int selected;
|
|
|
|
if ( !cg.snap )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.pm_flags & PMF_FOLLOW )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING ||
|
|
cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// When the weapon select menu is up the next and prev move through it
|
|
if ( cg.weaponMenuUp )
|
|
{
|
|
selected = cg.weaponMenuSelect;
|
|
}
|
|
else
|
|
{
|
|
selected = cg.weaponSelect;
|
|
}
|
|
|
|
original = selected;
|
|
|
|
for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS; i++ )
|
|
{
|
|
selected++;
|
|
if ( selected == WP_NUM_WEAPONS )
|
|
{
|
|
selected = WP_NONE + 1;
|
|
}
|
|
if ( selected != exclude && CG_WeaponSelectable( selected, allowEmpty ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i == WP_NUM_WEAPONS )
|
|
{
|
|
selected = original;
|
|
}
|
|
|
|
// When the weapon select menu is up the next and prev move through it
|
|
if ( cg.weaponMenuUp )
|
|
{
|
|
cg.weaponMenuSelect = selected;
|
|
}
|
|
else
|
|
{
|
|
cg.weaponSelect = selected;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_PrevWeapon
|
|
|
|
Selects the previous weapon in the players inventory
|
|
===============
|
|
*/
|
|
void CG_PrevWeapon ( qboolean allowEmpty, int exclude )
|
|
{
|
|
int i;
|
|
int original;
|
|
int selected;
|
|
|
|
if ( !cg.snap )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.pm_flags & PMF_FOLLOW )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING ||
|
|
cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// When the weapon select menu is up the next and prev move through it
|
|
if ( cg.weaponMenuUp )
|
|
{
|
|
selected = cg.weaponMenuSelect;
|
|
}
|
|
else
|
|
{
|
|
selected = cg.weaponSelect;
|
|
}
|
|
|
|
original = selected;
|
|
|
|
for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS ; i++ )
|
|
{
|
|
selected--;
|
|
if ( selected == WP_NONE )
|
|
{
|
|
selected = WP_NUM_WEAPONS-1;
|
|
}
|
|
if ( selected != exclude && CG_WeaponSelectable( selected, allowEmpty ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i == WP_NUM_WEAPONS )
|
|
{
|
|
selected = original;
|
|
}
|
|
|
|
// When the weapon select menu is up the next and prev move through it
|
|
if ( cg.weaponMenuUp )
|
|
{
|
|
cg.weaponMenuSelect = selected;
|
|
}
|
|
else
|
|
{
|
|
cg.weaponSelect = selected;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Weapon_f
|
|
===============
|
|
*/
|
|
void CG_Weapon_f( void )
|
|
{
|
|
int num;
|
|
int category;
|
|
int last;
|
|
|
|
if ( !cg.snap )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( (cg.snap->ps.pm_flags & PMF_FOLLOW) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING ||
|
|
cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT )
|
|
{
|
|
return;
|
|
}
|
|
|
|
category = atoi( CG_Argv( 1 ) );
|
|
|
|
if (category < 0 || category > CAT_MAX)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !cg_weaponMenuFast.integer )
|
|
{
|
|
if ( !cg.weaponMenuUp )
|
|
{
|
|
cg.weaponMenuSelect = cg.predictedPlayerState.weapon;
|
|
cg.weaponMenuUp = qtrue; // show weapon menu
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cg.weaponMenuSelect = cg.predictedPlayerState.weapon;
|
|
}
|
|
|
|
last = -1;
|
|
if (category == weaponData[cg.weaponMenuSelect].category)
|
|
{
|
|
last = cg.weaponMenuSelect;
|
|
}
|
|
|
|
// Find next weapon in currently active category.
|
|
for (num = WP_KNIFE; num < WP_NUM_WEAPONS; num++)
|
|
{
|
|
if(num>last && category==weaponData[num].category && (cg.predictedPlayerState.stats[STAT_WEAPONS]&(1<<num)))
|
|
{
|
|
if ( (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << num ) ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ok... wrap around.. and try again.
|
|
if (WP_NUM_WEAPONS == num)
|
|
{
|
|
for (num = WP_KNIFE; num < WP_NUM_WEAPONS; num++)
|
|
{
|
|
if(num!=last && category == weaponData[num].category && (cg.snap->ps.stats[STAT_WEAPONS]&(1<<num)))
|
|
{
|
|
if ( (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << num ) ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (WP_NUM_WEAPONS == num)
|
|
{
|
|
// Couldn't find one!
|
|
return;
|
|
}
|
|
|
|
if ( !cg_weaponMenuFast.integer )
|
|
{
|
|
cg.weaponMenuSelect = num;
|
|
}
|
|
else
|
|
{
|
|
cg.weaponSelect = num;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_OutOfAmmoChange
|
|
|
|
The current weapon has just run out of ammo
|
|
===================
|
|
*/
|
|
void CG_OutOfAmmoChange( int weapon )
|
|
{
|
|
// Get the best weapon thats not a grenade
|
|
cg.weaponSelect = WP_M84_GRENADE;
|
|
CG_PrevWeapon ( qfalse, weapon );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_PredictedBullet
|
|
|
|
Fires a bullet client side and produces all the effects that would be produced
|
|
by the server if it had done it
|
|
================
|
|
*/
|
|
void CG_PredictedBullet ( centity_t* cent, attackType_t attack )
|
|
{
|
|
vec3_t fireAngs;
|
|
float inaccuracy;
|
|
qboolean detailed;
|
|
vec3_t end;
|
|
vec3_t start;
|
|
trace_t tr;
|
|
int i;
|
|
int pellets;
|
|
entityState_t *ent;
|
|
int seed;
|
|
|
|
ent = ¢->currentState;
|
|
|
|
// Dont bother if antilag is turned off or its a projectile weapon
|
|
if ( cg_antiLag.integer < 1 || !cg_impactPrediction.integer || (weaponData[ent->weapon].attack[attack].weaponFlags & PROJECTILE_FIRE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Calculate the muzzle point
|
|
VectorCopy( cg.predictedPlayerState.origin, start );
|
|
start[2] += cg.predictedPlayerState.viewheight;
|
|
|
|
CG_UpdateViewWeaponSurfaces ( &cg.predictedPlayerState );
|
|
|
|
detailed = qtrue;
|
|
pellets = (weaponData[ent->weapon].attack[attack].pellets / 2) + 1;
|
|
pellets = weaponData[ent->weapon].attack[attack].pellets;
|
|
|
|
// Handle leaning
|
|
VectorCopy(cg.predictedPlayerState.viewangles, fireAngs);
|
|
if ( cg.predictedPlayerState.pm_flags & PMF_LEANING )
|
|
{
|
|
vec3_t right;
|
|
float leanOffset;
|
|
|
|
leanOffset = (float)(cg.predictedPlayerState.leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET;
|
|
AngleVectors( fireAngs, NULL, right, NULL );
|
|
VectorMA( start, leanOffset, right, start );
|
|
}
|
|
|
|
seed = cg.predictedPlayerState.stats[STAT_SEED];
|
|
|
|
// Current inaccuracy
|
|
inaccuracy = (float)cg.predictedPlayerState.inaccuracy / 1000.0f;
|
|
if ( detailed )
|
|
{
|
|
if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED )
|
|
{
|
|
inaccuracy *= DUCK_ACCURACY_MODIFIER;
|
|
}
|
|
else if ( cg.predictedPlayerState.pm_flags & PMF_JUMPING )
|
|
{
|
|
inaccuracy *= JUMP_ACCURACY_MODIFIER;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < pellets; i ++ )
|
|
{
|
|
BG_CalculateBulletEndpoint ( start, fireAngs, inaccuracy, weaponData[ent->weapon].attack[attack].rV.range, end, &seed );
|
|
|
|
// See if it hit a wall
|
|
CG_Trace ( &tr, start, NULL, NULL, end, cent->currentState.number, MASK_SHOT&~CONTENTS_BODY );
|
|
|
|
if ( tr.fraction >= 0.0f && tr.fraction <= 1.0f )
|
|
{
|
|
VectorCopy ( tr.endpos, end );
|
|
}
|
|
|
|
CG_PlayerTrace ( &tr, start, end, cent->currentState.number );
|
|
|
|
if ( tr.entityNum >= cgs.maxclients && tr.entityNum != ENTITYNUM_NONE )
|
|
{
|
|
CG_Bullet( tr.endpos, ent->number, ent->weapon, tr.plane.normal, ENTITYNUM_WORLD,
|
|
tr.surfaceFlags & MATERIAL_MASK,
|
|
attack );
|
|
}
|
|
#ifdef _SOF2_FLESHIMPACTPREDICTION
|
|
else if ( tr.entityNum < cgs.maxclients )
|
|
{
|
|
qboolean blood = qtrue;
|
|
vec3_t dir;
|
|
|
|
if ( cg_impactPrediction.integer < 2 )
|
|
{
|
|
return;
|
|
}
|
|
// If invulerable then no blood
|
|
if ( cg_entities[tr.entityNum].currentState.eFlags & EF_INVULNERABLE )
|
|
{
|
|
blood = qfalse;
|
|
}
|
|
// If on the same team in friendly fire then no blood
|
|
else if ( cgs.gametypeData->teams && !cgs.friendlyFire && cgs.clientinfo[tr.entityNum].team == cgs.clientinfo[cg.snap->ps.clientNum].team )
|
|
{
|
|
blood = qfalse;
|
|
}
|
|
|
|
VectorSubtract ( end, start, dir );
|
|
VectorNormalize ( dir );
|
|
|
|
// If no blood then just make a little white puff
|
|
if ( !blood || cg_lockBlood.integer )
|
|
{
|
|
CG_Bullet( tr.endpos, ent->number, ent->weapon, dir, ENTITYNUM_WORLD, 0, attack );
|
|
}
|
|
else
|
|
{
|
|
CG_PredictedProcGore ( ent->weapon, attack, start, end, &cg_entities[tr.entityNum] );
|
|
|
|
CG_MissileHitPlayer( ent->weapon, tr.endpos, dir, tr.entityNum, qfalse );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_FireWeapon
|
|
|
|
Caused by an EV_FIRE_WEAPON event
|
|
================
|
|
*/
|
|
void CG_FireWeapon( centity_t *cent, attackType_t attack )
|
|
{
|
|
entityState_t *ent;
|
|
int c;
|
|
weaponInfo_t *weaponInfo;
|
|
attackInfo_t *attackInfo;
|
|
|
|
ent = ¢->currentState;
|
|
|
|
if ( ent->weapon == WP_NONE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( ent->weapon >= WP_NUM_WEAPONS )
|
|
{
|
|
Com_Error( ERR_FATAL, "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
|
|
return;
|
|
}
|
|
|
|
weaponInfo = &cg_weapons[ ent->weapon ];
|
|
attackInfo = &weaponInfo->attack[attack];
|
|
|
|
// mark the entity as muzzle flashing, so when it is added it will
|
|
// append the flash to the weapon model
|
|
cent->muzzleFlashTime = cg.time;
|
|
cent->muzzleFlashAttack = attack;
|
|
|
|
// play a sound
|
|
for ( c = 0 ; c < 4 ; c++ )
|
|
{
|
|
if ( !attackInfo->flashSound[c] )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( c > 0 )
|
|
{
|
|
c = rand() % c;
|
|
if ( attackInfo->flashSound[c] )
|
|
{
|
|
int radius = 2000;
|
|
|
|
if ( ent->weapon == WP_MP5 && cent->currentState.number != cg.snap->ps.clientNum)
|
|
{
|
|
radius = 1250;
|
|
}
|
|
|
|
trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, attackInfo->flashSound[c], -1, radius );
|
|
}
|
|
}
|
|
|
|
// Handle dissapearing bullets if this is the main guy firing
|
|
if ( cent->currentState.number == cg.snap->ps.clientNum && cg.hitModel )
|
|
{
|
|
CG_PredictedBullet ( cent, attack );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_ProjectileThink
|
|
|
|
Create trail fx for projectiles
|
|
================
|
|
*/
|
|
void CG_ProjectileThink( centity_t *cent, int weaponNum )
|
|
{
|
|
const weaponInfo_t *weaponInfo;
|
|
const weaponData_t *weaponDat;
|
|
vec3_t forward;
|
|
int trailEffectId;
|
|
|
|
weaponInfo = &cg_weapons[weaponNum];
|
|
weaponDat = &weaponData[weaponNum];
|
|
|
|
if ( cent->currentState.eFlags & EF_ALT_FIRING )
|
|
{
|
|
trailEffectId = weaponInfo->attack[ATTACK_ALTERNATE].tracerEffect;
|
|
}
|
|
else
|
|
{
|
|
trailEffectId = weaponInfo->attack[ATTACK_NORMAL].tracerEffect;
|
|
}
|
|
|
|
// use tracer effect for projectile trails
|
|
if (trailEffectId)
|
|
{
|
|
// only calc normalize if there IS an effect
|
|
if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f )
|
|
{
|
|
forward[2] = 1.0f;
|
|
}
|
|
|
|
trap_FX_PlayEffectID(trailEffectId, cent->lerpOrigin, forward, -1, -1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_FlashBang
|
|
|
|
Blinds a player and makes them loose sound for a bit
|
|
=================
|
|
*/
|
|
|
|
#define MAX_FLASHBANG_AFFECT_DISTANCE 1750
|
|
#define MAX_FLASHBANG_DISTANCE 3000
|
|
#define MAX_FLASHBANG_TIME 11000
|
|
|
|
void CG_FlashBang ( vec3_t origin, vec3_t dir )
|
|
{
|
|
trace_t trace;
|
|
vec3_t start;
|
|
vec3_t end;
|
|
vec3_t delta;
|
|
float distance;
|
|
vec3_t fromangles;
|
|
int angle;
|
|
int time;
|
|
|
|
// Dont flash grenade dead people
|
|
if ( (cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) || cg.predictedPlayerState.pm_type != PM_NORMAL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy ( origin, start );
|
|
start[2] += cg.snap->ps.viewheight;
|
|
|
|
VectorCopy ( cg.refdef.vieworg, end );
|
|
end[2] += cg.snap->ps.viewheight;
|
|
|
|
VectorSubtract( cg.refdef.vieworg, origin, delta );
|
|
|
|
// distance to the flash bang
|
|
distance = VectorLength( delta );
|
|
|
|
// Make sure its not too far away
|
|
if(distance > MAX_FLASHBANG_DISTANCE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Can the player see the flashbang?
|
|
CG_Trace ( &trace, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT );
|
|
if ( trace.contents & (CONTENTS_SOLID|CONTENTS_TERRAIN) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorNormalize ( delta );
|
|
vectoangles ( delta, fromangles );
|
|
|
|
angle = cg.snap->ps.viewangles[1] - fromangles[1];
|
|
angle += 180;
|
|
|
|
if(angle < 0)
|
|
{
|
|
angle += 360;
|
|
}
|
|
|
|
// add distance if not facing the grenade..
|
|
if(!(angle > 300 || angle < 60 ))
|
|
{
|
|
angle = 120 - abs(angle-180);
|
|
distance += (MAX_FLASHBANG_AFFECT_DISTANCE * angle / 240);
|
|
}
|
|
|
|
if(distance > MAX_FLASHBANG_AFFECT_DISTANCE - 50 )
|
|
{
|
|
distance = MAX_FLASHBANG_AFFECT_DISTANCE - 50;
|
|
}
|
|
|
|
distance = MAX_FLASHBANG_AFFECT_DISTANCE - distance;
|
|
time = MAX_FLASHBANG_TIME;
|
|
|
|
cg.flashbangTime = cg.time;
|
|
cg.flashbangFadeTime = (MAX_FLASHBANG_TIME * distance / MAX_FLASHBANG_AFFECT_DISTANCE);
|
|
cg.flashbangAlpha = 1.0f * distance / MAX_FLASHBANG_AFFECT_DISTANCE;
|
|
|
|
// If NVG or thermals were on then blast them a bit more
|
|
if ( (cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON) )
|
|
{
|
|
cg.flashbangFadeTime += (cg.flashbangFadeTime / 4);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_MissileHitWall
|
|
|
|
Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
|
|
=================
|
|
*/
|
|
void CG_MissileHitWall (
|
|
int weapon,
|
|
vec3_t origin,
|
|
vec3_t dir,
|
|
int material,
|
|
attackType_t attack
|
|
)
|
|
{
|
|
qhandle_t impactEffect;
|
|
int ammoIndex;
|
|
|
|
// Flash grenades are handled very differnetly
|
|
if ( weapon == WP_M84_GRENADE )
|
|
{
|
|
CG_FlashBang ( origin, dir );
|
|
}
|
|
|
|
// Melee is handled specialol, the standard bullet routines are used
|
|
// to handle hit detection and what not, but the effects are handled
|
|
// separately.
|
|
if ( weaponData[weapon].attack[attack].melee )
|
|
{
|
|
impactEffect = trap_MAT_GetEffect( (char*)weaponData[weapon].attack[attack].melee, material&MATERIAL_MASK);
|
|
if (impactEffect)
|
|
{
|
|
trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ammoIndex = weaponData[weapon].attack[attack].ammoIndex;
|
|
|
|
switch( ammoIndex )
|
|
{
|
|
default:
|
|
case AMMO_KNIFE:
|
|
case AMMO_045:
|
|
case AMMO_556:
|
|
case AMMO_9 :
|
|
case AMMO_12 :
|
|
case AMMO_762:
|
|
impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, material&MATERIAL_MASK);
|
|
if (impactEffect)
|
|
{
|
|
trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1 );
|
|
}
|
|
break;
|
|
|
|
case AMMO_40:
|
|
case AMMO_M15:
|
|
case AMMO_M84:
|
|
case AMMO_RPG7:
|
|
case AMMO_SMOHG92:
|
|
case AMMO_ANM14:
|
|
|
|
impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, material&MATERIAL_MASK);
|
|
if (impactEffect)
|
|
{
|
|
trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1 );
|
|
}
|
|
|
|
// grenade!
|
|
trap_FX_PlayEffectID( cg_weapons[weapon].attack[attack].explosionEffect, origin, dir, -1, 3000 );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_MissileHitPlayer
|
|
=================
|
|
*/
|
|
void CG_MissileHitPlayer (
|
|
int weapon,
|
|
vec3_t origin,
|
|
vec3_t dir,
|
|
int entityNum,
|
|
attackType_t attack
|
|
)
|
|
{
|
|
qhandle_t impactEffect = 0;
|
|
int ammoIndex;
|
|
|
|
ammoIndex = weaponData[weapon].attack[attack].ammoIndex;
|
|
|
|
switch( ammoIndex )
|
|
{
|
|
default:
|
|
case AMMO_KNIFE:
|
|
case AMMO_045:
|
|
case AMMO_556:
|
|
case AMMO_9 :
|
|
case AMMO_12 :
|
|
case AMMO_762:
|
|
|
|
if ( entityNum == cg.clientNum )
|
|
{ // The person hit is the local player.
|
|
if (!cg_lockBlood.integer)
|
|
{ // Only play the exit wound effect
|
|
trap_FX_PlayEffectID( cgs.media.playerFleshImpactEffect, origin, dir, -1, -1 );
|
|
}
|
|
}
|
|
else if (cg_lockBlood.integer)
|
|
{ // Only play the generic hit effect.
|
|
trap_FX_PlayEffectID( trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_NONE),
|
|
origin, dir, -1, -1 );
|
|
}
|
|
else
|
|
{
|
|
trap_FX_PlayEffectID( cgs.media.mBloodSmall, origin, dir, -1, -1 );
|
|
trap_FX_PlayEffectID( cgs.media.playerFleshImpactEffect, origin, dir, -1, -1 ); // Exit wound too.
|
|
}
|
|
|
|
break;
|
|
|
|
case AMMO_RPG7:
|
|
case AMMO_40:
|
|
case AMMO_M15:
|
|
case AMMO_M84:
|
|
case AMMO_SMOHG92:
|
|
case AMMO_ANM14:
|
|
if (cg_lockBlood.integer)
|
|
{ // Play the generic hit effect.
|
|
impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_NONE);
|
|
}
|
|
else
|
|
{
|
|
impactEffect = cgs.media.mBloodSmall; // trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_FLESH);
|
|
}
|
|
|
|
if (impactEffect)
|
|
{
|
|
trap_FX_PlayEffectID( impactEffect, origin, dir, -1, 3000 );
|
|
}
|
|
|
|
trap_FX_PlayEffectID( cg_weapons[weapon].attack[attack].explosionEffect, origin, dir, -1, 3000 );
|
|
/*
|
|
trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_WEAPON, cg_weapons[weapon].attack[attack].explosionSound, -1, -1);
|
|
*/
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CG_HandleStickyMissile(centity_t *cent,entityState_t *es,vec3_t dir,int targetEnt)
|
|
{
|
|
int hitLoc;
|
|
qboolean altFire;
|
|
int ammoIndex;
|
|
weaponInfo_t *weaponInfo;
|
|
qboolean addBoltedMiss;
|
|
int missileIndex;
|
|
int boltIndex;
|
|
|
|
hitLoc=es->otherEntityNum2;
|
|
altFire=(cent->currentState.eFlags & EF_ALT_FIRING)!=0;
|
|
|
|
if ( cent->currentState.eFlags & EF_ALT_FIRING )
|
|
{
|
|
ammoIndex = weaponData[es->weapon].attack[ATTACK_ALTERNATE].ammoIndex;
|
|
}
|
|
else
|
|
{
|
|
ammoIndex = weaponData[es->weapon].attack[ATTACK_NORMAL].ammoIndex;
|
|
}
|
|
|
|
// Currently the only type of sticky missile is the thrown knife. Others might be added in future though.
|
|
addBoltedMiss=qfalse;
|
|
switch(es->weapon)
|
|
{
|
|
case AMMO_KNIFE:
|
|
if(altFire)
|
|
{
|
|
addBoltedMiss=qtrue;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(addBoltedMiss==qtrue)
|
|
{
|
|
centity_t* centTarget;
|
|
|
|
weaponInfo=&cg_weapons[es->weapon];
|
|
if(!trap_G2_HaveWeGhoul2Models(weaponInfo->weaponG2Model))
|
|
{
|
|
return;
|
|
}
|
|
|
|
centTarget = CG_GetEntity ( targetEnt );
|
|
|
|
missileIndex=trap_G2API_CopySpecificGhoul2Model(weaponInfo->weaponG2Model,0,centTarget->ghoul2,-1);
|
|
if(missileIndex!=-1)
|
|
{
|
|
// Com_Printf("Missile index=%i\n",missileIndex);
|
|
boltIndex=trap_G2API_FindBoltIndex(centTarget->ghoul2,0,"rhand");
|
|
if(boltIndex==-1)
|
|
{
|
|
boltIndex=trap_G2API_AddBolt(centTarget->ghoul2,0,"rhand");
|
|
}
|
|
if(boltIndex!=-1)
|
|
{
|
|
// Com_Printf("Bolt index=%i\n",boltIndex);
|
|
if(!trap_G2API_AttachG2Model(centTarget->ghoul2,missileIndex,centTarget->ghoul2,boltIndex,0))
|
|
{
|
|
// Com_Printf("Couldn't attach!\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============================================================================
|
|
|
|
BULLETS
|
|
|
|
============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_Tracer
|
|
===============
|
|
*/
|
|
void CG_Tracer(int tracerEffectID, vec3_t source, vec3_t dest )
|
|
{
|
|
vec3_t forward;
|
|
float len;
|
|
float chance;
|
|
|
|
if ( !tracerEffectID )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Lower the amount of tracers
|
|
chance = cg_tracerChance.value * 100.0f;
|
|
if ( rand()%100 > chance )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorSubtract( dest, source, forward );
|
|
len = VectorNormalize( forward );
|
|
if (len > 100)
|
|
{
|
|
VectorMA( source, 100, forward, source );
|
|
trap_FX_PlayEffectID( tracerEffectID, source, forward, -1, -1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_Bullet
|
|
|
|
Renders bullet effects.
|
|
======================
|
|
*/
|
|
void CG_Bullet(
|
|
vec3_t end,
|
|
int sourceEntityNum,
|
|
int weapon,
|
|
vec3_t normal,
|
|
int fleshEntityNum,
|
|
int material,
|
|
attackType_t attack
|
|
)
|
|
{
|
|
trace_t trace;
|
|
int sourceContentType;
|
|
int destContentType;
|
|
vec3_t start;
|
|
|
|
// if the shooter is currently valid, calc a source point and possibly
|
|
// do trail effects
|
|
if ( sourceEntityNum >= 0 )
|
|
{
|
|
if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) )
|
|
{
|
|
int tracerEffectID = cg_weapons[weapon].attack[attack].tracerEffect;;
|
|
|
|
if (0 != tracerEffectID )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
// Tracers on enemies only
|
|
if ( sourceEntityNum != cg.snap->ps.clientNum || cg.renderingThirdPerson )
|
|
{
|
|
// MP5's have no tracers
|
|
CG_Tracer(tracerEffectID, start, end );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( sourceEntityNum != cg.snap->ps.clientNum )
|
|
{
|
|
// Melee attacks should not cause the bullet fly by sound
|
|
if ( !weaponData[weapon].attack[attack].melee )
|
|
{
|
|
CG_BulletFlyBySound (start, end);
|
|
}
|
|
}
|
|
}
|
|
|
|
// impact splash and mark
|
|
if ( fleshEntityNum != ENTITYNUM_WORLD )
|
|
{
|
|
CG_MissileHitPlayer(weapon, end, normal, fleshEntityNum, qfalse);
|
|
}
|
|
else
|
|
{
|
|
CG_MissileHitWall( weapon, end, normal, material, attack );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_StartModelAnims
|
|
===============
|
|
*/
|
|
static void CG_StartModelAnims ( TAnimWeapon *aW, playerState_t *ps )
|
|
{
|
|
TAnimInfoWeapon *aIW;
|
|
|
|
if(!aW)
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
cg.weaponHideModels=-1;
|
|
|
|
// Loop for all models affected by this anim.
|
|
aIW=aW->mInfos;
|
|
while(aIW)
|
|
{
|
|
// Ignore if no anims available.
|
|
if ( !aIW->mNumChoices )
|
|
{
|
|
aIW=aIW->mNext;
|
|
continue;
|
|
}
|
|
|
|
if(!Q_stricmp(aIW->mType,"hands"))
|
|
{
|
|
// Hands... now which one?
|
|
if(!Q_stricmp(aIW->mName,"right"))
|
|
{
|
|
// Right.
|
|
cg.viewWeaponAnim[2] = aIW;
|
|
cg.weaponHideModels&=~(1<<2);
|
|
}
|
|
else if(!Q_stricmp(aIW->mName,"left"))
|
|
{
|
|
// Left.
|
|
cg.viewWeaponAnim[3] = aIW;
|
|
cg.weaponHideModels&=~(1<<3);
|
|
}
|
|
}
|
|
else if(!Q_stricmp(aIW->mType,"weaponmodel"))
|
|
{
|
|
cg.viewWeaponAnim[0] = aIW;
|
|
cg.weaponHideModels&=~(1<<0);
|
|
}
|
|
else if(!Q_stricmp(aIW->mType,"bolton"))
|
|
{
|
|
// Weapon model.
|
|
cg.viewWeaponAnim[4] = aIW;
|
|
cg.weaponHideModels&=~(1<<4);
|
|
}
|
|
else
|
|
{
|
|
Com_Printf("Anim unknown for model %s\n",aIW->mType);
|
|
}
|
|
|
|
// Follow link to next model.
|
|
aIW=aIW->mNext;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_SetWeaponAnim
|
|
===============
|
|
*/
|
|
void CG_SetWeaponAnim(int weaponAction,playerState_t *ps)
|
|
{
|
|
TAnimWeapon *aW;
|
|
aW=BG_GetInviewAnimFromIndex(ps->weapon,weaponAction);
|
|
if(aW)
|
|
{
|
|
CG_StartModelAnims(aW,ps);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_WeaponCallback
|
|
===============
|
|
*/
|
|
void CG_WeaponCallback ( playerState_t* ps, centity_t* cent, int weapon, int anim, int animChoice, int step )
|
|
{
|
|
int i,j;
|
|
TNoteTrack *note;
|
|
entityState_t *ent;
|
|
weaponInfo_t *weaponInfo;
|
|
char *surfName;
|
|
int surfFlags;
|
|
|
|
ent = ¢->currentState;
|
|
|
|
// Make sure the weapon number is valid
|
|
if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS )
|
|
{
|
|
Com_Error( ERR_FATAL, "CG_WeaponCallabck: ps->weapon >= WP_NUM_WEAPONS");
|
|
return;
|
|
}
|
|
|
|
weaponInfo = &cg_weapons[ weapon ];
|
|
|
|
note = BG_GetWeaponNote ( ps, weapon, anim, animChoice, step );
|
|
if ( !note )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!strcmp(note->mNote,"fire"))
|
|
{
|
|
// Callback is type fire = muzzle flash + sound + shell eject.
|
|
// TODO.
|
|
}
|
|
else if(!strcmp(note->mNote,"altfire"))
|
|
{
|
|
// Callback is type altfire = muzzle flash + sound + shell eject.
|
|
// TODO.
|
|
}
|
|
else
|
|
{
|
|
// Search the sound list... is it a sound callback?
|
|
for(i=0;i<MAX_WEAPON_SOUNDS;i++)
|
|
{
|
|
if(!weaponParseInfo[weapon].mSoundNames[i][0])
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(!strcmp(weaponParseInfo[weapon].mSoundNames[i],note->mNote))
|
|
{
|
|
// Play sound.
|
|
// FIXME: randomly select sound??
|
|
trap_S_StartSound(NULL,ent->number,CHAN_WEAPON,weaponInfo->otherWeaponSounds[i][0], -1, -1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Search the surfaces list... is it a surfaces callback?
|
|
for(i=0;i<MAX_SURFACE_CALLBACKS;i++)
|
|
{
|
|
if(!weaponParseInfo[weapon].mSurfaceCallbacks[i].mName[0])
|
|
{
|
|
break;
|
|
}
|
|
if(!strcmp(weaponParseInfo[weapon].mSurfaceCallbacks[i].mName,note->mNote))
|
|
{
|
|
for(j=0;j<MAX_CALLBACK_SURFACES;j++)
|
|
{
|
|
surfName=weaponParseInfo[weapon].mSurfaceCallbacks[i].mOnOffSurfaces[j].mName;
|
|
if(!surfName[0])
|
|
{
|
|
break;
|
|
}
|
|
// Turn Ghoul2 surface on/off.
|
|
surfFlags=weaponParseInfo[weapon].mSurfaceCallbacks[i].mOnOffSurfaces[j].mStatus?0:GHOUL2_NORENDER;
|
|
trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],surfName,surfFlags);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|