/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Wolf ET Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /* * name: cg_weapons.c * * desc: events and effects dealing with weapons * */ #include "cg_local.h" vec3_t ejectBrassCasingOrigin; //----(SA) // forward decs static int getAltWeapon( int weapnum ); int getEquivWeapon( int weapnum ); int CG_WeaponIndex( int weapnum, int *bank, int *cycle ); static qboolean CG_WeaponHasAmmo( int i ); char cg_fxflags; extern int weapBanksMultiPlayer[MAX_WEAP_BANKS_MP][MAX_WEAPS_IN_BANK_MP]; // JPW NERVE moved to bg_misc.c so I can get a droplist // jpw //----(SA) end /* ============== CG_StartWeaponAnim ============== */ static void CG_StartWeaponAnim( int anim ) { if ( cg.predictedPlayerState.pm_type >= PM_DEAD ) { return; } if ( cg.pmext.weapAnimTimer > 0 ) { return; } if ( cg.predictedPlayerState.weapon == WP_NONE ) { return; } cg.predictedPlayerState.weapAnim = ( ( cg.predictedPlayerState.weapAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } static void CG_ContinueWeaponAnim( int anim ) { if ( cg.predictedPlayerState.weapon == WP_NONE ) { return; } if ( ( cg.predictedPlayerState.weapAnim & ~ANIM_TOGGLEBIT ) == anim ) { return; } if ( cg.pmext.weapAnimTimer > 0 ) { return; // a high priority animation is running } CG_StartWeaponAnim( anim ); } /* ============== CG_MachineGunEjectBrassNew ============== */ void CG_MachineGunEjectBrassNew( centity_t *cent ) { localEntity_t *le; refEntity_t *re; vec3_t velocity, xvelocity; float waterScale = 1.0f; vec3_t v[3]; if ( cg_brassTime.integer <= 0 ) { return; } le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = -50 + 25 * crandom(); // JPW NERVE velocity[1] = -100 + 40 * crandom(); // JPW NERVE velocity[2] = 200 + 50 * random(); // JPW NERVE 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 ); VectorCopy( ejectBrassCasingOrigin, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { //----(SA) modified since slime is no longer deadly // if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10; } 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.smallgunBrassModel; le->bounceFactor = 0.4 * waterScale; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = ( rand() & 31 ) + 60; // bullets should come out horizontal not vertical JPW NERVE le->angles.trBase[1] = rand() & 255; // random spin from extractor 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; { int contents; vec3_t end; VectorCopy( cent->lerpOrigin, end ); end[2] -= 24; contents = CG_PointContents( end, 0 ); if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { le->leBounceSoundType = LEBS_NONE; } else { le->leBounceSoundType = LEBS_BRASS; } } le->leMarkType = LEMT_NONE; } /* ========================== CG_MachineGunEjectBrass ========================== */ 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]; if ( cg_brassTime.integer <= 0 ) { return; } if ( !( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) && ( cent->currentState.clientNum == cg.snap->ps.clientNum ) && ( !( cent->currentState.eFlags & EF_MG42_ACTIVE || cent->currentState.eFlags & EF_AAGUN_ACTIVE ) ) ) { CG_MachineGunEjectBrassNew( cent ); return; } le = CG_AllocLocalEntity(); re = &le->refEntity; 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 ); // JPW NERVE new brass handling behavior because the SP stuff just doesn't cut it for MP if ( cent->currentState.eFlags & EF_MG42_ACTIVE || cent->currentState.eFlags & EF_AAGUN_ACTIVE ) { offset[0] = 25; offset[1] = -4; offset[2] = 28; velocity[0] = -20 + 40 * crandom(); // JPW NERVE -- more reasonable brass ballistics for a machinegun velocity[1] = -150 + 40 * crandom(); // JPW NERVE velocity[2] = 100 + 50 * crandom(); // JPW NERVE re->hModel = cgs.media.machinegunBrassModel; le->angles.trBase[0] = 90; //rand()&31; // JPW NERVE belt-fed rounds should come out horizontal le->angles.trBase[1] = rand() & 255; le->angles.trBase[2] = rand() & 31; le->angles.trDelta[0] = 2; le->angles.trDelta[1] = 1; le->angles.trDelta[2] = 0; } else { re->hModel = cgs.media.smallgunBrassModel; switch ( cent->currentState.weapon ) { case WP_LUGER: case WP_COLT: case WP_SILENCER: case WP_SILENCED_COLT: offset[0] = 24; offset[1] = -4; offset[2] = 36; break; case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: offset[0] = 12; offset[1] = -4; offset[2] = 24; re->hModel = cgs.media.machinegunBrassModel; break; case WP_KAR98: case WP_CARBINE: case WP_K43: re->hModel = cgs.media.machinegunBrassModel; case WP_MP40: case WP_THOMPSON: case WP_STEN: default: offset[0] = 16; offset[1] = -4; offset[2] = 24; break; } velocity[0] = -50 + 25 * crandom(); velocity[1] = -100 + 40 * crandom(); velocity[2] = 200 + 50 * random(); le->angles.trBase[0] = ( rand() & 15 ) + 82; // bullets should come out horizontal not vertical JPW NERVE le->angles.trBase[1] = rand() & 255; // random spin from extractor le->angles.trBase[2] = rand() & 31; le->angles.trDelta[0] = 2; le->angles.trDelta[1] = 1; le->angles.trDelta[2] = 0; } // jpw 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 | CONTENTS_SLIME ) ) { //----(SA) modified since slime is no longer deadly // if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10; } 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 ); le->bounceFactor = 0.4 * waterScale; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->leFlags = LEF_TUMBLE; { int contents; vec3_t end; VectorCopy( cent->lerpOrigin, end ); end[2] -= 24; contents = CG_PointContents( end, 0 ); if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { le->leBounceSoundType = LEBS_NONE; } else { le->leBounceSoundType = LEBS_BRASS; } } le->leMarkType = LEMT_NONE; } //----(SA) added /* ============== CG_PanzerFaustEjectBrass toss the 'used' panzerfaust casing (unit is one-shot, disposable) ============== */ static void CG_PanzerFaustEjectBrass( 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]; le = CG_AllocLocalEntity(); re = &le->refEntity; // velocity[0] = 16; // velocity[1] = -50 + 40 * crandom(); // velocity[2] = 100 + 50 * crandom(); velocity[0] = 16; velocity[1] = -200; velocity[2] = 0; le->leType = LE_FRAGMENT; le->startTime = cg.time; // le->startTime = cg.time + 2000; le->endTime = le->startTime + ( cg_brassTime.integer * 8 ) + ( cg_brassTime.integer * random() ); le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time - ( rand() & 15 ); // le->pos.trTime = cg.time - 2000; AnglesToAxis( cent->lerpAngles, v ); // offset[0] = 12; // offset[1] = -4; // offset[2] = 24; offset[0] = -24; // forward offset[1] = -4; // left offset[2] = 24; // up 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 | CONTENTS_SLIME ) ) { waterScale = 0.10; } 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 ); // (SA) make it bigger le->sizeScale = 3.0f; re->hModel = cgs.media.panzerfaustBrassModel; le->bounceFactor = 0.4 * waterScale; 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.trBase[0] = 0; le->angles.trBase[1] = cent->currentState.apos.trBase[1]; // rotate to match the player le->angles.trBase[2] = 0; // le->angles.trDelta[0] = 2; // le->angles.trDelta[1] = 1; // le->angles.trDelta[2] = 0; le->angles.trDelta[0] = 0; le->angles.trDelta[1] = 0; le->angles.trDelta[2] = 0; le->leFlags = LEF_TUMBLE | LEF_SMOKING; // (SA) probably doesn't need to be 'tumble' since it doesn't really rotate much when flying le->leBounceSoundType = LEBS_NONE; le->leMarkType = LEMT_NONE; } /* ============== CG_SpearTrail simple bubble trail behind a missile ============== */ /*void CG_SpearTrail(centity_t *ent, const weaponInfo_t *wi ) { int contents, lastContents; vec3_t origin, lastPos; entityState_t *es; es = &ent->currentState; BG_EvaluateTrajectory( &es->pos, cg.time, origin ); contents = CG_PointContents( origin, -1 ); 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, 1, 8 ); } } }*/ // JPW NERVE -- compute random wind vector for smoke puff /* ========================== CG_GetWindVector ========================== */ void CG_GetWindVector( vec3_t dir ) { dir[0] = random() * 0.25; dir[1] = cgs.smokeWindDir; // simulate a little wind so it looks natural dir[2] = random(); // one direction (so smoke goes side-like) VectorNormalize( dir ); } // jpw // JPW NERVE -- LT pyro for marking air strikes /* ========================== CG_PyroSmokeTrail ========================== */ void CG_PyroSmokeTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos, dir; int contents; int lastContents, startTime; entityState_t *es; int t; float rnd; localEntity_t *le; team_t team; if ( ent->currentState.weapon == WP_LANDMINE ) { if ( ent->currentState.teamNum < 8 ) { ent->miscTime = 0; return; } else if ( ent->currentState.teamNum < 12 ) { if ( !ent->miscTime ) { ent->trailTime = cg.time; ent->miscTime = cg.time; // Arnout: play the armed sound - weird place to do it but saves us sending an event trap_S_StartSound( NULL, ent->currentState.number, CHAN_WEAPON, cgs.media.minePrimedSound ); } } if ( cg.time - ent->miscTime > 1000 ) { return; } if ( ent->currentState.otherEntityNum2 ) { team = TEAM_AXIS; } else { team = TEAM_ALLIES; } } else { team = ent->currentState.teamNum; } step = 30; es = &ent->currentState; startTime = ent->trailTime; t = step * ( ( startTime + step ) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin, qfalse, es->effect2Time ); contents = CG_PointContents( origin, -1 ); BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos, qfalse, es->effect2Time ); lastContents = CG_PointContents( lastPos, -1 ); ent->trailTime = cg.time; /* smoke pyro works fine in water (well, it's dye in real life, might wanna change this in-game) if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) return; */ // drop fire trail sprites for ( ; t <= ent->trailTime ; t += step ) { BG_EvaluateTrajectory( &es->pos, t, lastPos, qfalse, es->effect2Time ); rnd = random(); //VectorCopy (ent->lerpOrigin, lastPos); if ( ent->currentState.density ) { // corkscrew effect vec3_t right; vec3_t angles; VectorCopy( ent->currentState.apos.trBase, angles ); angles[ROLL] += cg.time % 360; AngleVectors( angles, NULL, right, NULL ); VectorMA( lastPos, ent->currentState.density, right, lastPos ); } dir[0] = crandom() * 5; // compute offset from flare base dir[1] = crandom() * 5; dir[2] = 0; VectorAdd( lastPos,dir,origin ); // store in origin rnd = random(); CG_GetWindVector( dir ); if ( ent->currentState.weapon == WP_LANDMINE ) { VectorScale( dir,45,dir ); } else { VectorScale( dir,65,dir ); } if ( team == TEAM_ALLIES ) { // allied team, generate blue smoke le = CG_SmokePuff( origin, dir, 25 + rnd * 110, // width rnd * 0.5 + 0.5, rnd * 0.5 + 0.5, 1, 0.5, 4800 + ( rand() % 2800 ), // duration was 2800+ t, 0, 0, cgs.media.smokePuffShader ); } else { le = CG_SmokePuff( origin, dir, 25 + rnd * 110, // width 1.0, rnd * 0.5 + 0.5, rnd * 0.5 + 0.5, 0.5, 4800 + ( rand() % 2800 ), // duration was 2800+ t, 0, 0, cgs.media.smokePuffShader ); } // CG_ParticleExplosion( "expblue", lastPos, vec3_origin, 100 + (int)(rnd*400), 4, 4 ); // fire "flare" // use the optimized local entity add // le->leType = LE_SCALE_FADE; /* this one works if (rand()%4) CG_ParticleExplosion( "blacksmokeanim", origin, dir, 2800+(int)(random()*1500), 15, 45+(int)(rnd*90) ); // smoke blacksmokeanim else CG_ParticleExplosion( "expblue", lastPos, vec3_origin, 100 + (int)(rnd*400), 4, 4 ); // fire "flare" */ } } // jpw // Ridah, new trail effects /* ========================== CG_RocketTrail ========================== */ void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos; int contents; int lastContents, startTime; entityState_t *es; int t; // localEntity_t *le; if ( ent->currentState.eType == ET_FLAMEBARREL ) { step = 30; } else if ( ent->currentState.eType == ET_FP_PARTS ) { step = 50; } else if ( ent->currentState.eType == ET_RAMJET ) { step = 10; } else { step = 10; } es = &ent->currentState; startTime = ent->trailTime; t = step * ( ( startTime + step ) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin, qfalse, es->effect2Time ); contents = CG_PointContents( origin, -1 ); // if object (e.g. grenade) is stationary, don't toss up smoke if ( ( ent->currentState.eType != ET_RAMJET ) && es->pos.trType == TR_STATIONARY ) { ent->trailTime = cg.time; return; } BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos, qfalse, es->effect2Time ); 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, 3, 8 ); } return; } // drop fire trail sprites for ( ; t <= ent->trailTime ; t += step ) { float rnd; BG_EvaluateTrajectory( &es->pos, t, lastPos, qfalse, es->effect2Time ); rnd = random(); if ( ent->currentState.eType == ET_FLAMEBARREL ) { if ( ( rand() % 100 ) > 50 ) { CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, 100 + (int)( rnd * 400 ), 5, 7 + (int)( rnd * 10 ), qfalse ); // fire } CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ), qfalse ); // smoke } else if ( ent->currentState.eType == ET_FP_PARTS ) { if ( ( rand() % 100 ) > 50 ) { CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, 100 + (int)( rnd * 400 ), 5, 7 + (int)( rnd * 10 ), qfalse ); // fire } CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ), qfalse ); // smoke } else if ( ent->currentState.eType == ET_RAMJET ) { int duration; VectorCopy( ent->lerpOrigin, lastPos ); duration = 100; CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, duration + (int)( rnd * 100 ), 5, 5 + (int)( rnd * 10 ), qfalse ); // fire CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 400 + (int)( rnd * 750 ), 12, 24 + (int)( rnd * 30 ), qfalse ); // smoke } else if ( ent->currentState.eType == ET_FIRE_COLUMN || ent->currentState.eType == ET_FIRE_COLUMN_SMOKE ) { int duration; int sizeStart; int sizeEnd; //VectorCopy (ent->lerpOrigin, lastPos); if ( ent->currentState.density ) { // corkscrew effect vec3_t right; vec3_t angles; VectorCopy( ent->currentState.apos.trBase, angles ); angles[ROLL] += cg.time % 360; AngleVectors( angles, NULL, right, NULL ); VectorMA( lastPos, ent->currentState.density, right, lastPos ); } duration = ent->currentState.angles[0]; sizeStart = ent->currentState.angles[1]; sizeEnd = ent->currentState.angles[2]; if ( !duration ) { duration = 100; } if ( !sizeStart ) { sizeStart = 5; } if ( !sizeEnd ) { sizeEnd = 7; } CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, duration + (int)( rnd * 400 ), sizeStart, sizeEnd + (int)( rnd * 10 ), qfalse ); // fire if ( ent->currentState.eType == ET_FIRE_COLUMN_SMOKE && ( rand() % 100 ) > 50 ) { CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ), qfalse ); // smoke } } else { //CG_ParticleExplosion( "twiltb", lastPos, vec3_origin, 300+(int)(rnd*100), 4, 14+(int)(rnd*8) ); // fire CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ), qfalse ); // smoke } } /* // spawn a smoke junction if ((cg.time - ent->lastTrailTime) >= 50 + rand()%50) { ent->headJuncIndex = CG_AddSmokeJunc( ent->headJuncIndex, ent, // rain - zinx's trail fix cgs.media.smokeTrailShader, origin, 4500, 0.4, 20, 80 ); ent->lastTrailTime = cg.time; } */ // done. } // JPW NERVE /* ========================== CG_DynamiteTrail ========================== */ static void CG_DynamiteTrail( centity_t *ent, const weaponInfo_t *wi ) { vec3_t origin; float mult; BG_EvaluateTrajectory( &ent->currentState.pos, cg.time, origin, qfalse, ent->currentState.effect2Time ); if ( ent->currentState.teamNum < 4 ) { mult = 0.004f * ( cg.time - ent->currentState.effect1Time ) / 30000.0f; trap_R_AddLightToScene( origin, 320, fabs( sin( ( cg.time - ent->currentState.effect1Time ) * mult ) ), 1.0, 0.0, 0.0, 0, REF_FORCE_DLIGHT ); } else { mult = 1 - ( ( cg.time - ent->trailTime ) / 15500.0f ); //% trap_R_AddLightToScene( origin, 10 + 300 * mult, 1.f, 1.f, 0, REF_FORCE_DLIGHT); trap_R_AddLightToScene( origin, 320, mult, 1.0, 1.0, 0, 0, REF_FORCE_DLIGHT ); } } // jpw // Ridah /* ========================== CG_GrenadeTrail ========================== */ static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos; int contents; int lastContents, startTime; entityState_t *es; int t; step = 15; // nice and smooth curves es = &ent->currentState; startTime = ent->trailTime; t = step * ( ( startTime + step ) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin, qfalse, es->effect2Time ); 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, qfalse, es->effect2Time ); 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, 2, 8 ); } return; } //----(SA) trying this back on for DM // spawn smoke junctions for ( ; t <= ent->trailTime ; t += step ) { BG_EvaluateTrajectory( &es->pos, t, origin, qfalse, es->effect2Time ); ent->headJuncIndex = CG_AddSmokeJunc( ent->headJuncIndex, ent, // rain - zinx's trail fix cgs.media.smokeTrailShader, origin, // 1500, 0.3, 10, 50 ); 1000, 0.3, 2, 20 ); ent->lastTrailTime = cg.time; } //----(SA) end } // done. /* ========================== CG_NailgunEjectBrass ========================== */ /* // TTimo: defined but not used 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; } */ /* ========================== CG_RailTrail SA: re-inserted this as a debug mechanism for bullets ========================== */ void CG_RailTrail2( clientInfo_t *ci, vec3_t start, vec3_t end ) { localEntity_t *le; refEntity_t *re; 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 ); // // still allow different colors so we can tell AI shots from player shots, etc. /* if(ci) { le->color[0] = ci->color[0] * 0.75; le->color[1] = ci->color[1] * 0.75; le->color[2] = ci->color[2] * 0.75; } else {*/ le->color[0] = 1; le->color[1] = 0; le->color[2] = 0; // } le->color[3] = 1.0f; AxisClear( re->axis ); } //void CG_RailTrailBox( clientInfo_t *ci, vec3_t start, vec3_t end) { /* ============== CG_RailTrail modified so we could draw boxes for debugging as well ============== */ void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end, int type ) { //----(SA) added 'type' vec3_t diff, v1, v2, v3, v4, v5, v6; if ( !type ) { // just a line CG_RailTrail2( ci, start, end ); return; } // type '1' (box) VectorSubtract( start, end, diff ); VectorCopy( start, v1 ); VectorCopy( start, v2 ); VectorCopy( start, v3 ); v1[0] -= diff[0]; v2[1] -= diff[1]; v3[2] -= diff[2]; CG_RailTrail2( ci, start, v1 ); CG_RailTrail2( ci, start, v2 ); CG_RailTrail2( ci, start, v3 ); VectorCopy( end, v4 ); VectorCopy( end, v5 ); VectorCopy( end, v6 ); v4[0] += diff[0]; v5[1] += diff[1]; v6[2] += diff[2]; CG_RailTrail2( ci, end, v4 ); CG_RailTrail2( ci, end, v5 ); CG_RailTrail2( ci, end, v6 ); CG_RailTrail2( ci, v2, v6 ); CG_RailTrail2( ci, v6, v1 ); CG_RailTrail2( ci, v1, v5 ); CG_RailTrail2( ci, v2, v4 ); CG_RailTrail2( ci, v4, v3 ); CG_RailTrail2( ci, v3, v5 ); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /* ====================== CG_ParseWeaponConfig read information for weapon animations (first/length/fps) ====================== */ static qboolean CG_ParseWeaponConfig( const char *filename, weaponInfo_t *wi ) { char *text_p, *prev; int len; int i; float fps; char *token; qboolean newfmt = qfalse; //----(SA) char text[20000]; fileHandle_t f; // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( text ) - 1 ) { CG_Printf( "File %s too long\n", filename ); return qfalse; } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = text; // read optional parameters while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); if ( !token ) { // get the variable break; } /* if ( !Q_stricmp( token, "whatever_variable" ) ) { token = COM_Parse( &text_p ); // get the value if ( !token ) { break; } continue; }*/ if ( !Q_stricmp( token, "newfmt" ) ) { newfmt = qtrue; continue; } // if it is a number, start parsing animations if ( token[0] >= '0' && token[0] <= '9' ) { text_p = prev; // unget the token break; } Com_Printf( "unknown token in weapon cfg '%s' is %s\n", token, filename ); } for ( i = 0 ; i < MAX_WP_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); // first frame if ( !token ) { break; } wi->weapAnimations[i].firstFrame = atoi( token ); token = COM_Parse( &text_p ); // length if ( !token ) { break; } wi->weapAnimations[i].numFrames = atoi( token ); token = COM_Parse( &text_p ); // fps if ( !token ) { break; } fps = atof( token ); if ( fps == 0 ) { fps = 1; } wi->weapAnimations[i].frameLerp = 1000 / fps; wi->weapAnimations[i].initialLerp = 1000 / fps; token = COM_Parse( &text_p ); // looping frames if ( !token ) { break; } wi->weapAnimations[i].loopFrames = atoi( token ); if ( wi->weapAnimations[i].loopFrames > wi->weapAnimations[i].numFrames ) { wi->weapAnimations[i].loopFrames = wi->weapAnimations[i].numFrames; } else if ( wi->weapAnimations[i].loopFrames < 0 ) { wi->weapAnimations[i].loopFrames = 0; } // store animation/draw bits in '.moveSpeed' wi->weapAnimations[i].moveSpeed = 0; if ( newfmt ) { token = COM_Parse( &text_p ); // barrel anim bits if ( !token ) { break; } wi->weapAnimations[i].moveSpeed = atoi( token ); token = COM_Parse( &text_p ); // animated weapon if ( !token ) { break; } if ( atoi( token ) ) { wi->weapAnimations[i].moveSpeed |= ( 1 << W_MAX_PARTS ); // set the bit one higher than can be set by the barrel bits } token = COM_Parse( &text_p ); // barrel hide bits (so objects can be flagged to not be drawn during all sequences (a reloading hand that comes in from off screen for that one animation for example) if ( !token ) { break; } wi->weapAnimations[i].moveSpeed |= ( ( atoi( token ) ) << 8 ); // use 2nd byte for draw bits } } if ( i != MAX_WP_ANIMATIONS ) { CG_Printf( "Error parsing weapon animation file: %s", filename ); return qfalse; } return qtrue; } static qboolean CG_RW_ParseError( int handle, char *format, ... ) { int line; char filename[128]; va_list argptr; static char string[4096]; va_start( argptr, format ); Q_vsnprintf( string, sizeof( string ), format, argptr ); va_end( argptr ); filename[0] = '\0'; line = 0; trap_PC_SourceFileAndLine( handle, filename, &line ); Com_Printf( S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string ); trap_PC_FreeSource( handle ); return qfalse; } static qboolean CG_RW_ParseWeaponLinkPart( int handle, weaponInfo_t *weaponInfo, modelViewType_t viewType ) { pc_token_t token; char filename[MAX_QPATH]; int part; partModel_t *partModel; if ( !PC_Int_Parse( handle, &part ) ) { return CG_RW_ParseError( handle, "expected part index" ); } if ( part < 0 || part >= W_MAX_PARTS ) { return CG_RW_ParseError( handle, "part index out of bounds" ); } partModel = &weaponInfo->partModels[viewType][part]; memset( partModel, 0, sizeof( *partModel ) ); if ( !trap_PC_ReadToken( handle, &token ) || Q_stricmp( token.string, "{" ) ) { return CG_RW_ParseError( handle, "expected '{'" ); } while ( 1 ) { if ( !trap_PC_ReadToken( handle, &token ) ) { break; } if ( token.string[0] == '}' ) { break; } if ( !Q_stricmp( token.string, "tag" ) ) { if ( !PC_String_ParseNoAlloc( handle, partModel->tagName, sizeof( partModel->tagName ) ) ) { return CG_RW_ParseError( handle, "expected tag name" ); } } else if ( !Q_stricmp( token.string, "model" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected model filename" ); } else { partModel->model = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "skin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { partModel->skin[0] = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "axisSkin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { partModel->skin[TEAM_AXIS] = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "alliedSkin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { partModel->skin[TEAM_ALLIES] = trap_R_RegisterSkin( filename ); } } else { return CG_RW_ParseError( handle, "unknown token '%s'", token.string ); } } return qtrue; } static qboolean CG_RW_ParseWeaponLink( int handle, weaponInfo_t *weaponInfo, modelViewType_t viewType ) { pc_token_t token; if ( !trap_PC_ReadToken( handle, &token ) || Q_stricmp( token.string, "{" ) ) { return CG_RW_ParseError( handle, "expected '{'" ); } while ( 1 ) { if ( !trap_PC_ReadToken( handle, &token ) ) { break; } if ( token.string[0] == '}' ) { break; } if ( !Q_stricmp( token.string, "part" ) ) { if ( !CG_RW_ParseWeaponLinkPart( handle, weaponInfo, viewType ) ) { return qfalse; } } else { return CG_RW_ParseError( handle, "unknown token '%s'", token.string ); } } return qtrue; } static qboolean CG_RW_ParseViewType( int handle, weaponInfo_t *weaponInfo, modelViewType_t viewType ) { pc_token_t token; char filename[MAX_QPATH]; if ( !trap_PC_ReadToken( handle, &token ) || Q_stricmp( token.string, "{" ) ) { return CG_RW_ParseError( handle, "expected '{'" ); } while ( 1 ) { if ( !trap_PC_ReadToken( handle, &token ) ) { break; } if ( token.string[0] == '}' ) { break; } if ( !Q_stricmp( token.string, "model" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected model filename" ); } else { weaponInfo->weaponModel[viewType].model = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "skin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { weaponInfo->weaponModel[viewType].skin[0] = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "axisSkin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { weaponInfo->weaponModel[viewType].skin[TEAM_AXIS] = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "alliedSkin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { weaponInfo->weaponModel[viewType].skin[TEAM_ALLIES] = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "flashModel" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected flashModel filename" ); } else { weaponInfo->flashModel[viewType] = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "weaponLink" ) ) { if ( !CG_RW_ParseWeaponLink( handle, weaponInfo, viewType ) ) { return qfalse; } } else { return CG_RW_ParseError( handle, "unknown token '%s'", token.string ); } } return qtrue; } static qboolean CG_RW_ParseModModel( int handle, weaponInfo_t *weaponInfo ) { char filename[MAX_QPATH]; int mod; if ( !PC_Int_Parse( handle, &mod ) ) { return CG_RW_ParseError( handle, "expected mod index" ); } if ( mod < 0 || mod >= 6 ) { return CG_RW_ParseError( handle, "mod index out of bounds" ); } if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected model filename" ); } else { weaponInfo->modModels[mod] = trap_R_RegisterModel( filename ); if ( !weaponInfo->modModels[mod] ) { // maybe it's a shader weaponInfo->modModels[mod] = trap_R_RegisterShader( filename ); } } return qtrue; } static qboolean CG_RW_ParseClient( int handle, weaponInfo_t *weaponInfo ) { pc_token_t token; char filename[MAX_QPATH]; int i; if ( !trap_PC_ReadToken( handle, &token ) || Q_stricmp( token.string, "{" ) ) { return CG_RW_ParseError( handle, "expected '{'" ); } while ( 1 ) { if ( !trap_PC_ReadToken( handle, &token ) ) { break; } if ( token.string[0] == '}' ) { break; } if ( !Q_stricmp( token.string, "standModel" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected standModel filename" ); } else { weaponInfo->standModel = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "droppedAnglesHack" ) ) { weaponInfo->droppedAnglesHack = qtrue; } else if ( !Q_stricmp( token.string, "pickupModel" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected pickupModel filename" ); } else { weaponInfo->weaponModel[W_PU_MODEL].model = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "pickupSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected pickupSound filename" ); } else { //weaponInfo->pickupSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "weaponConfig" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected weaponConfig filename" ); } else { if ( !CG_ParseWeaponConfig( filename, weaponInfo ) ) { // CG_Error( "Couldn't register weapon %i (failed to parse %s)", weaponNum, filename ); } } } else if ( !Q_stricmp( token.string, "handsModel" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected handsModel filename" ); } else { weaponInfo->handsModel = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "flashDlightColor" ) ) { if ( !PC_Vec_Parse( handle, &weaponInfo->flashDlightColor ) ) { return CG_RW_ParseError( handle, "expected flashDlightColor as r g b" ); } } else if ( !Q_stricmp( token.string, "flashSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected flashSound filename" ); } else { for ( i = 0; i < 4; i++ ) { if ( !weaponInfo->flashSound[i] ) { weaponInfo->flashSound[i] = trap_S_RegisterSound( filename, qfalse ); break; } } if ( i == 4 ) { CG_Printf( S_COLOR_YELLOW "WARNING: only up to 4 flashSounds supported per weapon\n" ); } } } else if ( !Q_stricmp( token.string, "flashEchoSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected flashEchoSound filename" ); } else { for ( i = 0; i < 4; i++ ) { if ( !weaponInfo->flashEchoSound[i] ) { weaponInfo->flashEchoSound[i] = trap_S_RegisterSound( filename, qfalse ); break; } } if ( i == 4 ) { CG_Printf( S_COLOR_YELLOW "WARNING: only up to 4 flashEchoSounds supported per weapon\n" ); } } } else if ( !Q_stricmp( token.string, "lastShotSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected lastShotSound filename" ); } else { for ( i = 0; i < 4; i++ ) { if ( !weaponInfo->lastShotSound[i] ) { weaponInfo->lastShotSound[i] = trap_S_RegisterSound( filename, qfalse ); break; } } if ( i == 4 ) { CG_Printf( S_COLOR_YELLOW "WARNING: only up to 4 lastShotSound supported per weapon\n" ); } } } else if ( !Q_stricmp( token.string, "readySound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected readySound filename" ); } else { weaponInfo->readySound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "firingSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected firingSound filename" ); } else { weaponInfo->firingSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "overheatSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected overheatSound filename" ); } else { weaponInfo->overheatSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "reloadSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected reloadSound filename" ); } else { weaponInfo->reloadSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "reloadFastSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected reloadFastSound filename" ); } else { weaponInfo->reloadFastSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "spinupSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected spinupSound filename" ); } else { weaponInfo->spinupSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "spindownSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected spindownSound filename" ); } else { weaponInfo->spindownSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "switchSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected switchSound filename" ); } else { weaponInfo->switchSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "weaponIcon" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected weaponIcon filename" ); } else { weaponInfo->weaponIcon[0] = trap_R_RegisterShader( filename ); } } else if ( !Q_stricmp( token.string, "weaponSelectedIcon" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected weaponSelectedIcon filename" ); } else { weaponInfo->weaponIcon[1] = trap_R_RegisterShader( filename ); } /*} else if( !Q_stricmp( token.string, "ammoIcon" ) ) { if( !PC_String_ParseNoAlloc( handle, filename, sizeof(filename) ) ) { return CG_RW_ParseError( handle, "expected ammoIcon filename" ); } else { weaponInfo->ammoIcon = trap_R_RegisterShader( filename ); }*/ } else if ( !Q_stricmp( token.string, "missileModel" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected missileModel filename" ); } else { weaponInfo->missileModel = trap_R_RegisterModel( filename ); } } else if ( !Q_stricmp( token.string, "missileAlliedSkin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { weaponInfo->missileAlliedSkin = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "missileAxisSkin" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected skin filename" ); } else { weaponInfo->missileAxisSkin = trap_R_RegisterSkin( filename ); } } else if ( !Q_stricmp( token.string, "missileSound" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected missileSound filename" ); } else { weaponInfo->missileSound = trap_S_RegisterSound( filename, qfalse ); } } else if ( !Q_stricmp( token.string, "missileTrailFunc" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected missileTrailFunc" ); } else { if ( !Q_stricmp( filename, "GrenadeTrail" ) ) { weaponInfo->missileTrailFunc = CG_GrenadeTrail; } else if ( !Q_stricmp( filename, "RocketTrail" ) ) { weaponInfo->missileTrailFunc = CG_RocketTrail; } else if ( !Q_stricmp( filename, "PyroSmokeTrail" ) ) { weaponInfo->missileTrailFunc = CG_PyroSmokeTrail; } else if ( !Q_stricmp( filename, "DynamiteTrail" ) ) { weaponInfo->missileTrailFunc = CG_DynamiteTrail; } } } else if ( !Q_stricmp( token.string, "missileDlight" ) ) { if ( !PC_Float_Parse( handle, &weaponInfo->missileDlight ) ) { return CG_RW_ParseError( handle, "expected missileDlight value" ); } } else if ( !Q_stricmp( token.string, "missileDlightColor" ) ) { if ( !PC_Vec_Parse( handle, &weaponInfo->missileDlightColor ) ) { return CG_RW_ParseError( handle, "expected missileDlightColor as r g b" ); } } else if ( !Q_stricmp( token.string, "ejectBrassFunc" ) ) { if ( !PC_String_ParseNoAlloc( handle, filename, sizeof( filename ) ) ) { return CG_RW_ParseError( handle, "expected ejectBrassFunc" ); } else { if ( !Q_stricmp( filename, "MachineGunEjectBrass" ) ) { weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; } else if ( !Q_stricmp( filename, "PanzerFaustEjectBrass" ) ) { weaponInfo->ejectBrassFunc = CG_PanzerFaustEjectBrass; } } } else if ( !Q_stricmp( token.string, "modModel" ) ) { if ( !CG_RW_ParseModModel( handle, weaponInfo ) ) { return qfalse; } } else if ( !Q_stricmp( token.string, "firstPerson" ) ) { if ( !CG_RW_ParseViewType( handle, weaponInfo, W_FP_MODEL ) ) { return qfalse; } } else if ( !Q_stricmp( token.string, "thirdPerson" ) ) { if ( !CG_RW_ParseViewType( handle, weaponInfo, W_TP_MODEL ) ) { return qfalse; } } else { return CG_RW_ParseError( handle, "unknown token '%s'", token.string ); } } return qtrue; } static qboolean CG_RegisterWeaponFromWeaponFile( const char *filename, weaponInfo_t *weaponInfo ) { pc_token_t token; int handle; handle = trap_PC_LoadSource( filename ); if ( !handle ) { return qfalse; } if ( !trap_PC_ReadToken( handle, &token ) || Q_stricmp( token.string, "weaponDef" ) ) { return CG_RW_ParseError( handle, "expected 'weaponDef'" ); } if ( !trap_PC_ReadToken( handle, &token ) || Q_stricmp( token.string, "{" ) ) { return CG_RW_ParseError( handle, "expected '{'" ); } while ( 1 ) { if ( !trap_PC_ReadToken( handle, &token ) ) { break; } if ( token.string[0] == '}' ) { break; } if ( !Q_stricmp( token.string, "client" ) ) { if ( !CG_RW_ParseClient( handle, weaponInfo ) ) { return qfalse; } } else { return CG_RW_ParseError( handle, "unknown token '%s'", token.string ); } } trap_PC_FreeSource( handle ); return qtrue; } /* ================= CG_RegisterWeapon ================= */ void CG_RegisterWeapon( int weaponNum, qboolean force ) { weaponInfo_t *weaponInfo; char *filename; if ( weaponNum <= 0 || weaponNum >= WP_NUM_WEAPONS ) { return; } weaponInfo = &cg_weapons[weaponNum]; if ( weaponInfo->registered && !force ) { 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 ); }*/ switch ( weaponNum ) { case WP_KNIFE: filename = "knife.weap"; break; case WP_LUGER: filename = "luger.weap"; break; case WP_COLT: filename = "colt.weap"; break; case WP_MP40: filename = "mp40.weap"; break; case WP_THOMPSON: filename = "thompson.weap"; break; case WP_STEN: filename = "sten.weap"; break; case WP_GRENADE_LAUNCHER: filename = "grenade.weap"; break; case WP_GRENADE_PINEAPPLE: filename = "pineapple.weap"; break; case WP_PANZERFAUST: filename = "panzerfaust.weap"; break; case WP_FLAMETHROWER: filename = "flamethrower.weap"; break; case WP_AMMO: filename = "ammopack.weap"; break; case WP_SMOKETRAIL: filename = "smoketrail.weap"; break; case WP_MEDKIT: filename = "medpack.weap"; break; case WP_PLIERS: filename = "pliers.weap"; break; case WP_SMOKE_MARKER: filename = "smokemarker.weap"; break; case WP_DYNAMITE: filename = "dynamite.weap"; break; case WP_MEDIC_ADRENALINE: filename = "adrenaline.weap"; break; case WP_MEDIC_SYRINGE: filename = "syringe.weap"; break; case WP_BINOCULARS: filename = "binocs.weap"; break; case WP_KAR98: filename = "kar98.weap"; break; case WP_GPG40: filename = "gpg40.weap"; break; case WP_CARBINE: filename = "m1_garand.weap"; break; case WP_M7: filename = "m7.weap"; break; case WP_GARAND: case WP_GARAND_SCOPE: filename = "m1_garand_s.weap"; break; case WP_FG42: case WP_FG42SCOPE: filename = "fg42.weap"; break; case WP_LANDMINE: filename = "landmine.weap"; break; case WP_SATCHEL: filename = "satchel.weap"; break; case WP_SATCHEL_DET: filename = "satchel_det.weap"; break; case WP_SMOKE_BOMB: filename = "smokegrenade.weap"; break; case WP_MOBILE_MG42_SET: // do we need a seperate file for this? case WP_MOBILE_MG42: filename = "mg42.weap"; break; case WP_SILENCER: filename = "silenced_luger.weap"; break; case WP_SILENCED_COLT: filename = "silenced_colt.weap"; break; case WP_K43: case WP_K43_SCOPE: filename = "k43.weap"; break; case WP_MORTAR: filename = "mortar.weap"; break; case WP_MORTAR_SET: filename = "mortar_set.weap"; break; case WP_AKIMBO_LUGER: filename = "akimbo_luger.weap"; break; case WP_AKIMBO_SILENCEDLUGER: filename = "akimbo_silenced_luger.weap"; break; case WP_AKIMBO_COLT: filename = "akimbo_colt.weap"; break; case WP_AKIMBO_SILENCEDCOLT: filename = "akimbo_silenced_colt.weap"; break; case WP_MAPMORTAR: filename = "mapmortar.weap"; break; // do we really need this? case WP_ARTY: return; // to shut the game up default: CG_Printf( S_COLOR_RED "WARNING: trying to register weapon %i but there is no weapon file entry for it.\n", weaponNum ); return; } if ( !CG_RegisterWeaponFromWeaponFile( va( "weapons/%s", filename ), weaponInfo ) ) { CG_Printf( S_COLOR_RED "WARNING: failed to register media for weapon %i from %s\n", weaponNum, filename ); } } /* ================= CG_RegisterItemVisuals The server says this item is used on this level ================= */ void CG_RegisterItemVisuals( int itemNum ) { itemInfo_t *itemInfo; gitem_t *item; int i; itemInfo = &cg_items[ itemNum ]; if ( itemInfo->registered ) { return; } item = &bg_itemlist[ itemNum ]; memset( itemInfo, 0, sizeof( &itemInfo ) ); if ( item->giType == IT_WEAPON ) { return; } for ( i = 0; i < MAX_ITEM_MODELS; i++ ) { itemInfo->models[i] = trap_R_RegisterModel( item->world_model[i] ); } if ( item->icon ) { itemInfo->icons[0] = trap_R_RegisterShader( item->icon ); if ( item->giType == IT_HOLDABLE ) { // (SA) register alternate icons (since holdables can have multiple uses, they might have different icons to represent how many uses are left) for ( i = 1; i < MAX_ITEM_ICONS; i++ ) itemInfo->icons[i] = trap_R_RegisterShader( va( "%s%i", item->icon, i + 1 ) ); } } itemInfo->registered = qtrue; //----(SA) moved this down after the registerweapon() } /* ======================================================================================== VIEW WEAPON ======================================================================================== */ // // weapon animations // /* ============== CG_GetPartFramesFromWeap get animation info from the parent if necessary ============== */ qboolean CG_GetPartFramesFromWeap( centity_t *cent, refEntity_t *part, refEntity_t *parent, int partid, weaponInfo_t *wi ) { int i; int frameoffset = 0; animation_t *anim; anim = cent->pe.weap.animation; if ( partid == W_MAX_PARTS ) { return qtrue; // primary weap model drawn for all frames right now } // check draw bit if ( anim->moveSpeed & ( 1 << ( partid + 8 ) ) ) { // hide bits are in high byte return qfalse; // not drawn for current sequence } // find part's start frame for this animation sequence // rain - & out ANIM_TOGGLEBIT or we'll go way out of bounds for ( i = 0; i < ( cent->pe.weap.animationNumber & ~ANIM_TOGGLEBIT ); i++ ) { if ( wi->weapAnimations[i].moveSpeed & ( 1 << partid ) ) { // this part has animation for this sequence frameoffset += wi->weapAnimations[i].numFrames; } } // now set the correct frame into the part if ( anim->moveSpeed & ( 1 << partid ) ) { part->backlerp = parent->backlerp; part->oldframe = frameoffset + ( parent->oldframe - anim->firstFrame ); part->frame = frameoffset + ( parent->frame - anim->firstFrame ); } return qtrue; } /* =============== CG_SetWeapLerpFrameAnimation may include ANIM_TOGGLEBIT =============== */ static void CG_SetWeapLerpFrameAnimation( weaponInfo_t *wi, lerpFrame_t *lf, int newAnimation ) { animation_t *anim; lf->animationNumber = newAnimation; newAnimation &= ~ANIM_TOGGLEBIT; if ( newAnimation < 0 || newAnimation >= MAX_WP_ANIMATIONS ) { CG_Error( "Bad animation number (CG_SWLFA): %i", newAnimation ); } anim = &wi->weapAnimations[ newAnimation ]; lf->animation = anim; lf->animationTime = lf->frameTime + anim->initialLerp; if ( cg_debugAnim.integer & 2 ) { CG_Printf( "Weap Anim: %d\n", newAnimation ); } } /* =============== CG_ClearWeapLerpFrame =============== */ void CG_ClearWeapLerpFrame( weaponInfo_t *wi, lerpFrame_t *lf, int animationNumber ) { lf->frameTime = lf->oldFrameTime = cg.time; CG_SetWeapLerpFrameAnimation( wi, lf, animationNumber ); lf->oldFrame = lf->frame = lf->animation->firstFrame; lf->oldFrameModel = lf->frameModel = lf->animation->mdxFile; } /* =============== CG_RunWeapLerpFrame Sets cg.snap, cg.oldFrame, and cg.backlerp cg.time should be between oldFrameTime and frameTime after exit =============== */ static void CG_RunWeapLerpFrame( clientInfo_t *ci, weaponInfo_t *wi, lerpFrame_t *lf, int newAnimation, float speedScale ) { int f; animation_t *anim; // debugging tool to get no animations if ( cg_animSpeed.integer == 0 ) { lf->oldFrame = lf->frame = lf->backlerp = 0; return; } // see if the animation sequence is switching if ( !lf->animation ) { CG_ClearWeapLerpFrame( wi, lf, newAnimation ); } else if ( newAnimation != lf->animationNumber ) { if ( ( newAnimation & ~ANIM_TOGGLEBIT ) == PM_RaiseAnimForWeapon( cg.snap->ps.nextWeapon ) ) { CG_ClearWeapLerpFrame( wi, lf, newAnimation ); // clear when switching to raise (since it should be out of view anyway) } else { CG_SetWeapLerpFrameAnimation( wi, lf, newAnimation ); } } // if we have passed the current frame, move it to // oldFrame and calculate a new frame if ( cg.time >= lf->frameTime ) { lf->oldFrame = lf->frame; lf->oldFrameTime = lf->frameTime; lf->oldFrameModel = lf->frameModel; // get the next frame based on the animation anim = lf->animation; if ( !anim->frameLerp ) { return; // shouldn't happen } if ( cg.time < lf->animationTime ) { lf->frameTime = lf->animationTime; // initial lerp } else { lf->frameTime = lf->oldFrameTime + anim->frameLerp; } f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; f *= speedScale; // adjust for haste, etc if ( f >= anim->numFrames ) { f -= anim->numFrames; if ( anim->loopFrames ) { f %= anim->loopFrames; f += anim->numFrames - anim->loopFrames; } else { f = anim->numFrames - 1; // the animation is stuck at the end, so it // can immediately transition to another sequence lf->frameTime = cg.time; } } lf->frame = anim->firstFrame + f; lf->frameModel = anim->mdxFile; if ( cg.time > lf->frameTime ) { lf->frameTime = cg.time; if ( cg_debugAnim.integer ) { CG_Printf( "Clamp lf->frameTime\n" ); } } } if ( lf->frameTime > cg.time + 200 ) { lf->frameTime = cg.time; } if ( lf->oldFrameTime > cg.time ) { lf->oldFrameTime = cg.time; } // calculate current lerp value if ( lf->frameTime == lf->oldFrameTime ) { lf->backlerp = 0; } else { lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); } } /* ============== CG_WeaponAnimation ============== */ //----(SA) modified. this is now client-side only (server does not dictate weapon animation info) static void CG_WeaponAnimation( playerState_t *ps, weaponInfo_t *weapon, int *weapOld, int *weap, float *weapBackLerp ) { centity_t *cent = &cg.predictedPlayerEntity; clientInfo_t *ci = &cgs.clientinfo[ ps->clientNum ]; if ( cg_noPlayerAnims.integer ) { *weapOld = *weap = 0; return; } CG_RunWeapLerpFrame( ci, weapon, ¢->pe.weap, ps->weapAnim, 1 ); *weapOld = cent->pe.weap.oldFrame; *weap = cent->pe.weap.frame; *weapBackLerp = cent->pe.weap.backlerp; if ( cg_debugAnim.integer == 3 ) { CG_Printf( "oldframe: %d frame: %d backlerp: %f\n", cent->pe.weap.oldFrame, cent->pe.weap.frame, cent->pe.weap.backlerp ); } } //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // (SA) it wasn't used anyway /* ============== CG_CalculateWeaponPosition ============== */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { float scale; int delta; float fracsin; VectorCopy( cg.refdef_current->vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); if ( cg.predictedPlayerState.eFlags & EF_MOUNTEDTANK ) { angles[PITCH] = cg.refdefViewAngles[PITCH] / 1.2; } if ( !cg.renderingThirdPerson && ( cg.predictedPlayerState.weapon == WP_MORTAR_SET || cg.predictedPlayerState.weapon == WP_MOBILE_MG42_SET ) && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { angles[PITCH] = cg.pmext.mountedWeaponAngles[PITCH]; } if ( cg.predictedPlayerState.eFlags & EF_PRONE_MOVING ) { int pronemovingtime = cg.time - cg.proneMovingTime; if ( pronemovingtime > 0 ) { // div by 0 float factor = pronemovingtime > 200 ? 1.f : 1.f / ( 200.f / (float)pronemovingtime ); VectorMA( origin, factor * -20, cg.refdef_current->viewaxis[0], origin ); VectorMA( origin, factor * 3, cg.refdef_current->viewaxis[1], origin ); } } else { int pronenomovingtime = cg.time - -cg.proneMovingTime; if ( pronenomovingtime < 200 ) { float factor = pronenomovingtime == 0 ? 1.f : 1.f - ( 1.f / ( 200.f / (float)pronenomovingtime ) ); VectorMA( origin, factor * -20, cg.refdef_current->viewaxis[0], origin ); VectorMA( origin, factor * 3, cg.refdef_current->viewaxis[1], origin ); } } // adjust 'lean' into weapon if ( cg.predictedPlayerState.leanf != 0 ) { vec3_t right, up; float myfrac = 1.0f; switch ( cg.predictedPlayerState.weapon ) { case WP_FLAMETHROWER: case WP_KAR98: case WP_CARBINE: case WP_GPG40: case WP_M7: case WP_K43: myfrac = 2.0f; break; case WP_GARAND: myfrac = 3.0f; break; } // reverse the roll on the weapon so it stays relatively level angles[ROLL] -= cg.predictedPlayerState.leanf / ( myfrac * 2.0f ); AngleVectors( angles, NULL, right, up ); VectorMA( origin, angles[ROLL], right, origin ); // pitch the gun down a bit to show that firing is not allowed when leaning angles[PITCH] += ( abs( cg.predictedPlayerState.leanf ) / 2.0f ); // this gives you some impression that the weapon stays in relatively the same // position while you lean, so you appear to 'peek' over the weapon AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); VectorMA( origin, -cg.predictedPlayerState.leanf / 4.0f, right, origin ); } // 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 if ( ( !( cg.predictedPlayerState.eFlags & EF_MOUNTEDTANK ) ) && ( cg.predictedPlayerState.weapon != WP_MORTAR_SET ) && ( cg.predictedPlayerState.weapon != WP_MOBILE_MG42_SET ) ) { //----(SA) adjustment for MAX KAUFMAN // scale = cg.xyspeed + 40; scale = 80; //----(SA) end fracsin = sin( cg.time * 0.001 ); angles[ROLL] += scale * fracsin * 0.01; angles[YAW] += scale * fracsin * 0.01; angles[PITCH] += scale * fracsin * 0.01; } // RF, subtract the kickAngles VectorMA( angles, -1.0, cg.kickAngles, angles ); } // Ridah /* =============== CG_FlamethrowerFlame =============== */ static void CG_FlamethrowerFlame( centity_t *cent, vec3_t origin ) { if ( cent->currentState.weapon != WP_FLAMETHROWER ) { return; } CG_FireFlameChunks( cent, origin, cent->lerpAngles, 1.0, qtrue ); return; } // done. /* ======================== CG_AddWeaponWithPowerups ======================== */ static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups, playerState_t *ps, centity_t *cent ) { // add powerup effects // DHM - Nerve :: no powerup effects on weapons trap_R_AddRefEntityToScene( gun ); } /* ============= 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. ============= */ static qboolean debuggingweapon = qfalse; void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) { refEntity_t gun; refEntity_t barrel; refEntity_t flash; vec3_t angles; weapon_t weaponNum; weaponInfo_t *weapon; centity_t *nonPredictedCent; qboolean firing; // Ridah qboolean akimboFire = qfalse; // qboolean playerScaled; qboolean drawpart; int i; qboolean isPlayer; bg_playerclass_t* classInfo; classInfo = BG_GetPlayerClassInfo( cgs.clientinfo[cent->currentState.clientNum].team, cgs.clientinfo[cent->currentState.clientNum].cls ); // (SA) might as well have this check consistant throughout the routine isPlayer = (qboolean)( cent->currentState.clientNum == cg.snap->ps.clientNum ); weaponNum = cent->currentState.weapon; if ( ps && cg.cameraMode ) { return; } // don't draw any weapons when the binocs are up if ( cent->currentState.eFlags & EF_ZOOMING ) { return; } // don't draw weapon stuff when looking through a scope if ( weaponNum == WP_FG42SCOPE || weaponNum == WP_GARAND_SCOPE || weaponNum == WP_K43_SCOPE ) { if ( isPlayer && !cg.renderingThirdPerson ) { return; } } if ( weaponNum == WP_GRENADE_PINEAPPLE || weaponNum == WP_GRENADE_LAUNCHER ) { if ( ps && !ps->ammoclip[ weaponNum ] ) { return; } } // no weapon when on mg_42 if ( cent->currentState.eFlags & EF_MOUNTEDTANK ) { if ( isPlayer && !cg.renderingThirdPerson ) { return; } if ( cg.time - cent->muzzleFlashTime < MUZZLE_FLASH_TIME ) { memset( &flash, 0, sizeof( flash ) ); flash.renderfx = RF_LIGHTING_ORIGIN; flash.hModel = cgs.media.mg42muzzleflash; VectorCopy( cg_entities[cg_entities[ cent->currentState.number ].tagParent].mountedMG42Flash.origin, flash.origin ); AxisCopy( cg_entities[cg_entities[ cent->currentState.number ].tagParent].mountedMG42Flash.axis, flash.axis ); trap_R_AddRefEntityToScene( &flash ); // ydnar: add dynamic light trap_R_AddLightToScene( flash.origin, 320, 1.25 + ( rand() & 31 ) / 128, 1.0, 0.6, 0.23, 0, 0 ); } return; } if ( cent->currentState.eFlags & EF_MG42_ACTIVE || cent->currentState.eFlags & EF_AAGUN_ACTIVE ) { // Arnout: MG42 Muzzle Flash if ( cg.time - cent->muzzleFlashTime < MUZZLE_FLASH_TIME ) { CG_MG42EFX( cent ); } return; } if ( ( !ps || cg.renderingThirdPerson ) && cent->currentState.eFlags & EF_PRONE_MOVING ) { return; } weapon = &cg_weapons[weaponNum]; if ( BG_IsAkimboWeapon( weaponNum ) ) { if ( isPlayer ) { akimboFire = BG_AkimboFireSequence( weaponNum, cg.predictedPlayerState.ammoclip[BG_FindClipForWeapon( weaponNum )], cg.predictedPlayerState.ammoclip[BG_FindClipForWeapon( BG_AkimboSidearm( weaponNum ) )] ); } else if ( ps ) { akimboFire = BG_AkimboFireSequence( weaponNum, ps->ammoclip[BG_FindClipForWeapon( weaponNum )], ps->ammoclip[BG_FindClipForWeapon( BG_AkimboSidearm( weaponNum ) )] ); } // Gordon: FIXME: alternate for other clients, store flip-flop on cent or smuffin } // add the weapon memset( &gun, 0, sizeof( gun ) ); VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); gun.shadowPlane = parent->shadowPlane; gun.renderfx = parent->renderfx; if ( ps ) { team_t team = ps->persistant[PERS_TEAM]; if ( ( weaponNum != WP_SATCHEL ) && ( cent->currentState.powerups & ( 1 << PW_OPS_DISGUISED ) ) ) { team = team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS; } gun.hModel = weapon->weaponModel[W_FP_MODEL].model; if ( ( team == TEAM_AXIS ) && weapon->weaponModel[W_FP_MODEL].skin[TEAM_AXIS] ) { gun.customSkin = weapon->weaponModel[W_FP_MODEL].skin[TEAM_AXIS]; } else if ( ( team == TEAM_ALLIES ) && weapon->weaponModel[W_FP_MODEL].skin[TEAM_ALLIES] ) { gun.customSkin = weapon->weaponModel[W_FP_MODEL].skin[TEAM_ALLIES]; } else { gun.customSkin = weapon->weaponModel[W_FP_MODEL].skin[0]; // if not loaded it's 0 so doesn't do any harm } } else { team_t team = cgs.clientinfo[cent->currentState.clientNum].team; if ( ( weaponNum != WP_SATCHEL ) && cent->currentState.powerups & ( 1 << PW_OPS_DISGUISED ) ) { team = team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS; } gun.hModel = weapon->weaponModel[W_TP_MODEL].model; if ( ( team == TEAM_AXIS ) && weapon->weaponModel[W_TP_MODEL].skin[TEAM_AXIS] ) { gun.customSkin = weapon->weaponModel[W_FP_MODEL].skin[TEAM_AXIS]; } else if ( ( team == TEAM_ALLIES ) && weapon->weaponModel[W_TP_MODEL].skin[TEAM_ALLIES] ) { gun.customSkin = weapon->weaponModel[W_TP_MODEL].skin[TEAM_ALLIES]; } else { gun.customSkin = weapon->weaponModel[W_TP_MODEL].skin[0]; // if not loaded it's 0 so doesn't do any harm } } if ( !gun.hModel ) { if ( debuggingweapon ) { CG_Printf( "returning due to: !gun.hModel\n" ); } return; } if ( !ps && cg.snap->ps.pm_flags & PMF_LADDER && isPlayer ) { //----(SA) player on ladder if ( debuggingweapon ) { CG_Printf( "returning due to: !ps && cg.snap->ps.pm_flags & PMF_LADDER\n" ); } return; } if ( !ps ) { // add weapon ready sound cent->pe.lightningFiring = 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->lerpOrigin, vec3_origin, weapon->firingSound, 255, 0 ); cent->pe.lightningFiring = qtrue; } else if ( weapon->readySound ) { trap_S_AddLoopingSound( cent->lerpOrigin, vec3_origin, weapon->readySound, 255, 0 ); } } // Ridah firing = ( ( cent->currentState.eFlags & EF_FIRING ) != 0 ); if ( ps && !cg.renderingThirdPerson && cg.predictedPlayerState.weapon == WP_MORTAR_SET && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { vec3_t angles; angles[YAW] = angles[ROLL] = 0.f; angles[PITCH] = -.4f * AngleNormalize180( cg.pmext.mountedWeaponAngles[PITCH] - ps->viewangles[PITCH] ); AnglesToAxis( angles, gun.axis ); CG_PositionRotatedEntityOnTag( &gun, parent, "tag_weapon" ); } else if ( ( !ps || cg.renderingThirdPerson ) && ( weaponNum == WP_MORTAR_SET || weaponNum == WP_MORTAR ) ) { CG_PositionEntityOnTag( &gun, parent, "tag_weapon2", 0, NULL ); } else { CG_PositionEntityOnTag( &gun, parent, "tag_weapon", 0, NULL ); } /* playerScaled = (qboolean)(cgs.clientinfo[ cent->currentState.clientNum ].playermodelScale[0] != 0); if(!ps && playerScaled) { // don't "un-scale" weap up in 1st person for(i=0;i<3;i++) { // scale weapon back up so it doesn't pick up the adjusted scale of the character models. // this will affect any parts attached to the gun as well (barrel/bolt/flash/brass/etc.) VectorScale( gun.axis[i], 1.0/(cgs.clientinfo[ cent->currentState.clientNum ].playermodelScale[i]), gun.axis[i]); } }*/ if ( ps ) { drawpart = CG_GetPartFramesFromWeap( cent, &gun, parent, W_MAX_PARTS, weapon ); // W_MAX_PARTS specifies this as the primary view model } else { drawpart = qtrue; } if ( drawpart ) { if ( weaponNum == WP_AMMO ) { if ( ps ) { if ( cgs.clientinfo[ ps->clientNum ].skill[ SK_SIGNALS ] >= 1 ) { gun.customShader = weapon->modModels[0]; } } else { if ( cgs.clientinfo[ cent->currentState.clientNum ].skill[ SK_SIGNALS ] >= 1 ) { gun.customShader = weapon->modModels[0]; } } } if ( !ps ) { if ( weaponNum == WP_MEDIC_SYRINGE ) { if ( cgs.clientinfo[ cent->currentState.clientNum ].skill[ SK_FIRST_AID ] >= 3 ) { gun.customShader = weapon->modModels[ 0 ]; } } } CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ps, cent ); } if ( ( !ps || cg.renderingThirdPerson ) && ( weaponNum == WP_AKIMBO_COLT || weaponNum == WP_AKIMBO_SILENCEDCOLT || weaponNum == WP_AKIMBO_LUGER || weaponNum == WP_AKIMBO_SILENCEDLUGER ) ) { // add to other hand as well CG_PositionEntityOnTag( &gun, parent, "tag_weapon2", 0, NULL ); CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ps, cent ); } // ydnar: test hack //% if( weaponNum == WP_KNIFE ) //% trap_R_AddLightToScene( gun.origin, 512, 1.5, 1.0, 1.0, 1.0, 0, 0 ); if ( isPlayer ) { refEntity_t brass; if ( BG_IsAkimboWeapon( weaponNum ) && akimboFire ) { CG_PositionRotatedEntityOnTag( &brass, parent, "tag_brass2" ); } else { CG_PositionRotatedEntityOnTag( &brass, parent, "tag_brass" ); } VectorCopy( brass.origin, ejectBrassCasingOrigin ); } memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; // add barrels // attach generic weapon parts to the first person weapon. // if a barrel should be attached for third person, add it in the (!ps) section below angles[YAW] = angles[PITCH] = 0; if ( ps ) { qboolean spunpart; for ( i = W_PART_1; i < W_MAX_PARTS; i++ ) { if ( weaponNum == WP_MORTAR_SET && ( i == W_PART_4 || i == W_PART_5 ) ) { if ( ps && !cg.renderingThirdPerson && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { continue; } } spunpart = qfalse; barrel.hModel = weapon->partModels[W_FP_MODEL][i].model; if ( weaponNum == WP_MORTAR_SET ) { if ( i == W_PART_3 ) { if ( ps && !cg.renderingThirdPerson && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { angles[PITCH] = angles[YAW] = 0.f; angles[ROLL] = .8f * AngleNormalize180( cg.pmext.mountedWeaponAngles[YAW] - ps->viewangles[YAW] ); spunpart = qtrue; } } else if ( i == W_PART_1 || i == W_PART_2 ) { if ( ps && !cg.renderingThirdPerson && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { angles[YAW] = angles[ROLL] = 0.f; angles[PITCH] = -.4f * AngleNormalize180( cg.pmext.mountedWeaponAngles[PITCH] - ps->viewangles[PITCH] ); spunpart = qtrue; } } } else if ( weaponNum == WP_MOBILE_MG42_SET ) { } if ( spunpart ) { AnglesToAxis( angles, barrel.axis ); } if ( barrel.hModel ) { if ( spunpart ) { CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->partModels[W_FP_MODEL][i].tagName ); } else { CG_PositionEntityOnTag( &barrel, parent, weapon->partModels[W_FP_MODEL][i].tagName, 0, NULL ); } drawpart = CG_GetPartFramesFromWeap( cent, &barrel, parent, i, weapon ); if ( weaponNum == WP_MORTAR_SET && ( i == W_PART_1 || i == W_PART_2 ) ) { if ( ps && !cg.renderingThirdPerson && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { VectorMA( barrel.origin, .5f * angles[PITCH], cg.refdef_current->viewaxis[0], barrel.origin ); } } if ( drawpart ) { if ( ( ps->persistant[PERS_TEAM] == TEAM_AXIS || ( ps->persistant[PERS_TEAM] == TEAM_ALLIES && cent->currentState.powerups & ( 1 << PW_OPS_DISGUISED ) ) ) && weapon->partModels[W_FP_MODEL][i].skin[TEAM_AXIS] ) { barrel.customSkin = weapon->partModels[W_FP_MODEL][i].skin[TEAM_AXIS]; } else if ( ( ps->persistant[PERS_TEAM] == TEAM_ALLIES || ( ps->persistant[PERS_TEAM] == TEAM_AXIS && cent->currentState.powerups & ( 1 << PW_OPS_DISGUISED ) ) ) && weapon->partModels[W_FP_MODEL][i].skin[TEAM_ALLIES] ) { barrel.customSkin = weapon->partModels[W_FP_MODEL][i].skin[TEAM_ALLIES]; } else { barrel.customSkin = weapon->partModels[W_FP_MODEL][i].skin[0]; // if not loaded it's 0 so doesn't do any harm } if ( weaponNum == WP_MEDIC_SYRINGE && i == W_PART_1 ) { if ( cgs.clientinfo[ ps->clientNum ].skill[ SK_FIRST_AID ] >= 3 ) { barrel.customShader = weapon->modModels[ 0 ]; } } CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); if ( weaponNum == WP_SATCHEL_DET && i == W_PART_1 ) { float rangeSquared; qboolean inRange; refEntity_t satchelDetPart; if ( cg.satchelCharge ) { rangeSquared = DistanceSquared( cg.satchelCharge->lerpOrigin, cg.predictedPlayerEntity.lerpOrigin ); } else { rangeSquared = Square( 2001.f ); } if ( rangeSquared <= Square( 2000 ) ) { inRange = qtrue; } else { inRange = qfalse; } memset( &satchelDetPart, 0, sizeof( satchelDetPart ) ); VectorCopy( parent->lightingOrigin, satchelDetPart.lightingOrigin ); satchelDetPart.shadowPlane = parent->shadowPlane; satchelDetPart.renderfx = parent->renderfx; satchelDetPart.hModel = weapon->modModels[0]; CG_PositionEntityOnTag( &satchelDetPart, &barrel, "tag_rlight", 0, NULL ); satchelDetPart.customShader = inRange ? weapon->modModels[2] : weapon->modModels[3]; CG_AddWeaponWithPowerups( &satchelDetPart, cent->currentState.powerups, ps, cent ); CG_PositionEntityOnTag( &satchelDetPart, &barrel, "tag_glight", 0, NULL ); satchelDetPart.customShader = inRange ? weapon->modModels[5] : weapon->modModels[4]; CG_AddWeaponWithPowerups( &satchelDetPart, cent->currentState.powerups, ps, cent ); satchelDetPart.hModel = weapon->modModels[1]; angles[PITCH] = angles[ROLL] = 0.f; if ( inRange ) { angles[YAW] = -30.f + ( 60.f * ( rangeSquared / Square( 2000 ) ) ); } else { angles[YAW] = 30.f; } AnglesToAxis( angles, satchelDetPart.axis ); CG_PositionRotatedEntityOnTag( &satchelDetPart, &barrel, "tag_needle" ); satchelDetPart.customShader = weapon->modModels[2]; CG_AddWeaponWithPowerups( &satchelDetPart, cent->currentState.powerups, ps, cent ); } else if ( weaponNum == WP_MORTAR_SET && i == W_PART_3 ) { if ( ps && !cg.renderingThirdPerson && cg.predictedPlayerState.weaponstate != WEAPON_RAISING ) { refEntity_t bipodLeg; memset( &bipodLeg, 0, sizeof( bipodLeg ) ); VectorCopy( parent->lightingOrigin, bipodLeg.lightingOrigin ); bipodLeg.shadowPlane = parent->shadowPlane; bipodLeg.renderfx = parent->renderfx; bipodLeg.hModel = weapon->partModels[W_FP_MODEL][3].model; CG_PositionEntityOnTag( &bipodLeg, &barrel, "tag_barrel4", 0, NULL ); CG_AddWeaponWithPowerups( &bipodLeg, cent->currentState.powerups, ps, cent ); bipodLeg.hModel = weapon->partModels[W_FP_MODEL][4].model; CG_PositionEntityOnTag( &bipodLeg, &barrel, "tag_barrel5", 0, NULL ); CG_AddWeaponWithPowerups( &bipodLeg, cent->currentState.powerups, ps, cent ); } } } } } } // add the scope model to the rifle if you've got it if ( isPlayer && !cg.renderingThirdPerson ) { // (SA) for now just do it on the first person weapons if ( weaponNum == WP_CARBINE || weaponNum == WP_KAR98 || weaponNum == WP_GPG40 || weaponNum == WP_M7 ) { if ( ( cg.snap->ps.ammo[BG_FindAmmoForWeapon( WP_GPG40 )] || cg.snap->ps.ammo[BG_FindAmmoForWeapon( WP_M7 )] || cg.snap->ps.ammoclip[BG_FindAmmoForWeapon( WP_GPG40 )] || cg.snap->ps.ammoclip[BG_FindAmmoForWeapon( WP_M7 )] ) ) { int anim = cg.snap->ps.weapAnim & ~ANIM_TOGGLEBIT; if ( anim == PM_AltSwitchFromForWeapon( weaponNum ) || anim == PM_AltSwitchToForWeapon( weaponNum ) || anim == PM_IdleAnimForWeapon( weaponNum ) ) { barrel.hModel = weapon->modModels[0]; if ( barrel.hModel ) { CG_PositionEntityOnTag( &barrel, parent, "tag_scope", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } } } } else if ( weaponNum == WP_GARAND ) { barrel.hModel = weapon->modModels[0]; if ( barrel.hModel ) { CG_PositionEntityOnTag( &barrel, &gun, "tag_scope2", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } barrel.hModel = weapon->modModels[1]; // if(barrel.hModel) { CG_PositionEntityOnTag( &barrel, &gun, "tag_flash", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); // } } else if ( weaponNum == WP_K43 ) { barrel.hModel = weapon->modModels[0]; if ( barrel.hModel ) { CG_PositionEntityOnTag( &barrel, &gun, "tag_scope", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } barrel.hModel = weapon->modModels[1]; // if(barrel.hModel) { CG_PositionEntityOnTag( &barrel, &gun, "tag_flash", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); // } } } // 3rd person attachements else { if ( weaponNum == WP_M7 || weaponNum == WP_GPG40 /* || weaponNum == WP_CARBINE || weaponNum == WP_KAR98*/ ) { // the holder barrel.hModel = weapon->modModels[1]; CG_PositionEntityOnTag( &barrel, &gun, "tag_flash", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); // the grenade - have to always enabled it, no means of telling if another person has a grenade loaded or not atm :/ //if( cg.snap->ps.weaponstate != WEAPON_FIRING && cg.snap->ps.weaponstate != WEAPON_RELOADING ) { if ( weaponNum == WP_M7 /*|| weaponNum == WP_CARBINE*/ ) { barrel.hModel = weapon->missileModel; CG_PositionEntityOnTag( &barrel, &barrel, "tag_prj", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } } else if ( weaponNum == WP_GARAND || weaponNum == WP_GARAND_SCOPE || weaponNum == WP_K43 || weaponNum == WP_K43_SCOPE ) { // the holder barrel.hModel = weapon->modModels[2]; CG_PositionEntityOnTag( &barrel, &gun, "tag_scope", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } else if ( weaponNum == WP_MOBILE_MG42 ) { barrel.hModel = weapon->modModels[0]; barrel.frame = 1; CG_PositionEntityOnTag( &barrel, &gun, "tag_bipod", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } else if ( weaponNum == WP_MOBILE_MG42_SET ) { barrel.hModel = weapon->modModels[0]; barrel.frame = 0; CG_PositionEntityOnTag( &barrel, &gun, "tag_bipod", 0, NULL ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); } } // 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 the single player podiums), so // go ahead and use the cent if ( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { nonPredictedCent = cent; } // add the flash memset( &flash, 0, sizeof( flash ) ); VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); flash.shadowPlane = parent->shadowPlane; flash.renderfx = parent->renderfx; if ( ps ) { flash.hModel = weapon->flashModel[W_FP_MODEL]; } else { flash.hModel = weapon->flashModel[W_TP_MODEL]; } angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = crandom() * 10; AnglesToAxis( angles, flash.axis ); if ( /*isPlayer &&*/ BG_IsAkimboWeapon( weaponNum ) ) { if ( !ps || cg.renderingThirdPerson ) { if ( !cent->akimboFire ) { CG_PositionRotatedEntityOnTag( &flash, parent, "tag_weapon" ); VectorMA( flash.origin, 10, flash.axis[0], flash.origin ); } else { CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash" ); } } else { if ( !cent->akimboFire ) { CG_PositionRotatedEntityOnTag( &flash, parent, "tag_flash2" ); } else { CG_PositionRotatedEntityOnTag( &flash, parent, "tag_flash" ); } } } else { CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash" ); } // store this position for other cgame elements to access cent->pe.gunRefEnt = gun; cent->pe.gunRefEntFrame = cg.clientFrame; if ( ( weaponNum == WP_FLAMETHROWER ) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) { // continuous flash } else { // continuous smoke after firing #define BARREL_SMOKE_TIME 1000 if ( ps || cg.renderingThirdPerson || !isPlayer ) { if ( weaponNum == WP_STEN || weaponNum == WP_MOBILE_MG42 || weaponNum == WP_MOBILE_MG42_SET ) { // hot smoking gun if ( cg.time - cent->overheatTime < 3000 ) { if ( !( rand() % 3 ) ) { float alpha; alpha = 1.0f - ( (float)( cg.time - cent->overheatTime ) / 3000.0f ); alpha *= 0.25f; // .25 max alpha CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 30, alpha, 8.f ); } } } else if ( weaponNum == WP_PANZERFAUST ) { if ( cg.time - cent->muzzleFlashTime < BARREL_SMOKE_TIME ) { if ( !( rand() % 5 ) ) { float alpha; alpha = 1.0f - ( (float)( cg.time - cent->muzzleFlashTime ) / (float)BARREL_SMOKE_TIME ); // what fraction of BARREL_SMOKE_TIME are we at alpha *= 0.25f; // .25 max alpha CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 30, alpha, 8.f ); } } } } if ( weaponNum == WP_MORTAR_SET ) { if ( ps && !cg.renderingThirdPerson && cg.time - cent->muzzleFlashTime < 800 ) { CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 700, 16, 20, 30, .12f, 4.f ); } } // impulse flash if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) { // Ridah, blue ignition flame if not firing flamer if ( weaponNum != WP_FLAMETHROWER ) { return; } } } // weapons that don't need to go any further as they have no flash or light if ( weaponNum == WP_GRENADE_LAUNCHER || weaponNum == WP_GRENADE_PINEAPPLE || weaponNum == WP_KNIFE || weaponNum == WP_DYNAMITE || weaponNum == WP_GPG40 || weaponNum == WP_M7 || weaponNum == WP_LANDMINE || weaponNum == WP_SATCHEL || weaponNum == WP_SATCHEL_DET || weaponNum == WP_TRIPMINE || weaponNum == WP_SMOKE_BOMB || weaponNum == WP_MEDIC_SYRINGE || weaponNum == WP_MEDIC_ADRENALINE ) { return; } // weaps with barrel smoke if ( ps || cg.renderingThirdPerson || !isPlayer ) { if ( weaponNum == WP_STEN ) { if ( cg.time - cent->muzzleFlashTime < 100 ) { // CG_ParticleImpactSmokePuff (cgs.media.smokeParticleShader, flash.origin); CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 500, 8, 20, 30, 0.25f, 8.f ); } } } if ( flash.hModel ) { if ( weaponNum != WP_FLAMETHROWER ) { //Ridah, hide the flash also for now // RF, changed this so the muzzle flash stays onscreen for long enough to be seen if ( cg.time - cent->muzzleFlashTime < MUZZLE_FLASH_TIME ) { //if (firing) { // Ridah trap_R_AddRefEntityToScene( &flash ); } } } // Ridah, zombie fires from his head //if (CG_MonsterUsingWeapon( cent, AICHAR_ZOMBIE, WP_MONSTER_ATTACK1 )) { // CG_PositionEntityOnTag( &flash, parent, parent->hModel, "tag_head", NULL); //} if ( ps || cg.renderingThirdPerson || !isPlayer ) { // ydnar: no flamethrower flame on prone moving // ydnar: or dead players if ( firing && !( cent->currentState.eFlags & ( EF_PRONE_MOVING | EF_DEAD ) ) ) { // Ridah, Flamethrower effect CG_FlamethrowerFlame( cent, flash.origin ); // make a dlight for the flash if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { //% trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), weapon->flashDlightColor[0], //% weapon->flashDlightColor[1], weapon->flashDlightColor[2], 0 ); trap_R_AddLightToScene( flash.origin, 320, 1.25 + ( rand() & 31 ) / 128, weapon->flashDlightColor[0], weapon->flashDlightColor[1], weapon->flashDlightColor[2], 0, 0 ); } } else { if ( weaponNum == WP_FLAMETHROWER ) { vec3_t angles; AxisToAngles( flash.axis, angles ); // JPW NERVE weaponNum = BG_FindAmmoForWeapon( WP_FLAMETHROWER ); if ( ps ) { if ( ps->ammoclip[weaponNum] ) { CG_FireFlameChunks( cent, flash.origin, angles, 1.0, qfalse ); } } else { CG_FireFlameChunks( cent, flash.origin, angles, 1.0, qfalse ); } // jpw } } } } /* ============== CG_AddViewWeapon Add the weapon, and flash for the player's view ============== */ void CG_AddViewWeapon( playerState_t *ps ) { refEntity_t hand; float fovOffset; vec3_t angles; vec3_t gunoff; weaponInfo_t *weapon; if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; } if ( ps->pm_type == PM_INTERMISSION ) { return; } // no gun if in third person view if ( cg.renderingThirdPerson ) { return; } if ( cg.editingSpeakers ) { return; } // allow the gun to be completely removed if ( ( !cg_drawGun.integer ) ) { vec3_t origin; //bani - #589 if ( cg.predictedPlayerState.eFlags & EF_FIRING && !( cg.predictedPlayerState.eFlags & ( EF_MG42_ACTIVE | EF_MOUNTEDTANK ) ) ) { // special hack for flamethrower... VectorCopy( cg.refdef_current->vieworg, origin ); VectorMA( origin, 18, cg.refdef_current->viewaxis[0], origin ); VectorMA( origin, -7, cg.refdef_current->viewaxis[1], origin ); VectorMA( origin, -4, cg.refdef_current->viewaxis[2], origin ); // Ridah, Flamethrower effect CG_FlamethrowerFlame( &cg.predictedPlayerEntity, origin ); } if ( cg.binocZoomTime ) { if ( cg.binocZoomTime < 0 ) { if ( -cg.binocZoomTime + 500 + 200 < cg.time ) { cg.binocZoomTime = 0; } } else { if ( cg.binocZoomTime + 500 < cg.time ) { trap_SendConsoleCommand( "+zoom\n" ); cg.binocZoomTime = 0; } else { } } } return; } // don't draw if testing a gun model if ( cg.testGun ) { return; } if ( ps->eFlags & EF_MG42_ACTIVE || ps->eFlags & EF_AAGUN_ACTIVE ) { return; } // drop gun lower at higher fov if ( cg_fov.integer > 90 ) { fovOffset = -0.2 * ( cg_fov.integer - 90 ); } else { fovOffset = 0; } // Gordon: mounted gun drawing if ( ps->eFlags & EF_MOUNTEDTANK ) { // FIXME: Arnout: HACK dummy model to just draw _something_ refEntity_t flash; memset( &hand, 0, sizeof( hand ) ); CG_CalculateWeaponPosition( hand.origin, angles ); AnglesToAxis( angles, hand.axis ); hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; if ( cg_entities[cg_entities[cg_entities[ ps->clientNum ].tagParent].tankparent].currentState.density & 8 ) { // should we use a browning? hand.hModel = cgs.media.hMountedFPBrowning; } else { hand.hModel = cgs.media.hMountedFPMG42; } //gunoff[0] = cg_gun_x.value; //gunoff[1] = cg_gun_y.value; //gunoff[2] = cg_gun_z.value; gunoff[0] = 20; if ( cg.time - cg.predictedPlayerEntity.muzzleFlashTime < MUZZLE_FLASH_TIME ) { gunoff[0] += random() * 2.f; } VectorMA( hand.origin, gunoff[0], cg.refdef_current->viewaxis[0], hand.origin ); VectorMA( hand.origin, -10, cg.refdef_current->viewaxis[1], hand.origin ); VectorMA( hand.origin, ( -8 + fovOffset ), cg.refdef_current->viewaxis[2], hand.origin ); CG_AddWeaponWithPowerups( &hand, cg.predictedPlayerEntity.currentState.powerups, ps, &cg.predictedPlayerEntity ); if ( cg.time - cg.predictedPlayerEntity.overheatTime < 3000 ) { if ( !( rand() % 3 ) ) { float alpha; alpha = 1.0f - ( (float)( cg.time - cg.predictedPlayerEntity.overheatTime ) / 3000.0f ); alpha *= 0.25f; // .25 max alpha CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, cg.tankflashorg, 1000, 8, 20, 30, alpha, 8.f ); } } { memset( &flash, 0, sizeof( flash ) ); flash.renderfx = ( RF_LIGHTING_ORIGIN | RF_DEPTHHACK ); flash.hModel = cgs.media.mg42muzzleflash; angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = crandom() * 10; AnglesToAxis( angles, flash.axis ); CG_PositionRotatedEntityOnTag( &flash, &hand, "tag_flash" ); VectorMA( flash.origin, 22, flash.axis[0], flash.origin ); VectorCopy( flash.origin, cg.tankflashorg ); if ( cg.time - cg.predictedPlayerEntity.muzzleFlashTime < MUZZLE_FLASH_TIME ) { trap_R_AddRefEntityToScene( &flash ); } } return; } if ( ps->weapon > WP_NONE ) { weapon = &cg_weapons[ ps->weapon ]; memset( &hand, 0, sizeof( hand ) ); // set up gun position CG_CalculateWeaponPosition( hand.origin, angles ); gunoff[0] = cg_gun_x.value; gunoff[1] = cg_gun_y.value; gunoff[2] = cg_gun_z.value; //----(SA) removed VectorMA( hand.origin, gunoff[0], cg.refdef_current->viewaxis[0], hand.origin ); VectorMA( hand.origin, gunoff[1], cg.refdef_current->viewaxis[1], hand.origin ); VectorMA( hand.origin, ( gunoff[2] + fovOffset ), cg.refdef_current->viewaxis[2], hand.origin ); AnglesToAxis( angles, hand.axis ); if ( cg_gun_frame.integer ) { hand.frame = hand.oldframe = cg_gun_frame.integer; hand.backlerp = 0; } else { // get the animation state if ( cg.binocZoomTime ) { if ( cg.binocZoomTime < 0 ) { if ( -cg.binocZoomTime + 500 + 200 < cg.time ) { cg.binocZoomTime = 0; } else { if ( -cg.binocZoomTime + 200 < cg.time ) { CG_ContinueWeaponAnim( WEAP_ALTSWITCHFROM ); } else { CG_ContinueWeaponAnim( WEAP_IDLE2 ); } } } else { if ( cg.binocZoomTime + 500 < cg.time ) { trap_SendConsoleCommand( "+zoom\n" ); cg.binocZoomTime = 0; CG_ContinueWeaponAnim( WEAP_IDLE2 ); } else { CG_ContinueWeaponAnim( WEAP_ALTSWITCHTO ); } } } CG_WeaponAnimation( ps, weapon, &hand.oldframe, &hand.frame, &hand.backlerp ); //----(SA) changed } hand.hModel = weapon->handsModel; hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; //----(SA) // add everything onto the hand CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity ); // Ridah } } /* ============================================================================== WEAPON SELECTION ============================================================================== */ #define WP_ICON_X 38 // new sizes per MK #define WP_ICON_X_WIDE 72 // new sizes per MK #define WP_ICON_Y 38 #define WP_ICON_SPACE_Y 10 #define WP_DRAW_X 640 - WP_ICON_X - 4 // 4 is 'selected' border width #define WP_DRAW_X_WIDE 640 - WP_ICON_X_WIDE - 4 #define WP_DRAW_Y 4 // secondary fire icons #define WP_ICON_SEC_X 18 // new sizes per MK #define WP_ICON_SEC_Y 18 /* ============== CG_WeaponHasAmmo check for ammo ============== */ static qboolean CG_WeaponHasAmmo( int i ) { // ydnar: certain weapons don't have ammo if ( i == WP_KNIFE || i == WP_PLIERS ) { return qtrue; } if ( !( cg.predictedPlayerState.ammo[BG_FindAmmoForWeapon( i )] ) && !( cg.predictedPlayerState.ammoclip[BG_FindClipForWeapon( i )] ) ) { return qfalse; } return qtrue; } /* =============== CG_WeaponSelectable =============== */ qboolean CG_WeaponSelectable( int i ) { // allow the player to unselect all weapons // if(i == WP_NONE) // return qtrue; // if holding a melee weapon (chair/shield/etc.) only allow single-handed weapons /* if(cg.snap->ps.eFlags & EF_MELEE_ACTIVE) { if(!(WEAPS_ONE_HANDED & (1<= WP_NUM_WEAPONS ) { if ( bank ) { *bank = 0; } if ( cycle ) { *cycle = 0; } return 0; } for ( bnk = 0; bnk < MAX_WEAP_BANKS_MP; bnk++ ) { for ( cyc = 0; cyc < MAX_WEAPS_IN_BANK_MP; cyc++ ) { if ( !weapBanksMultiPlayer[bnk][cyc] ) { break; } // found the current weapon if ( weapBanksMultiPlayer[bnk][cyc] == weapnum ) { if ( bank ) { *bank = bnk; } if ( cycle ) { *cycle = cyc; } return 1; } // jpw } } // failed to find the weapon in the table // probably an alternate return 0; } /* ============== getNextWeapInBank Pass in a bank and cycle and this will return the next valid weapon higher in the cycle. if the weap passed in is above highest in a cycle (MAX_WEAPS_IN_BANK), this will safely loop around ============== */ static int getNextWeapInBank( int bank, int cycle ) { cycle++; cycle = cycle % MAX_WEAPS_IN_BANK_MP; if ( weapBanksMultiPlayer[bank][cycle] ) { // return next weapon in bank if there is one return weapBanksMultiPlayer[bank][cycle]; } else { // return first in bank return weapBanksMultiPlayer[bank][0]; } } static int getNextWeapInBankBynum( int weapnum ) { int bank, cycle; if ( !CG_WeaponIndex( weapnum, &bank, &cycle ) ) { return weapnum; } return getNextWeapInBank( bank, cycle ); } /* ============== getPrevWeapInBank Pass in a bank and cycle and this will return the next valid weapon lower in the cycle. if the weap passed in is the lowest in a cycle (0), this will loop around to the top (MAX_WEAPS_IN_BANK-1) and start down from there looking for a valid weapon position ============== */ static int getPrevWeapInBank( int bank, int cycle ) { cycle--; if ( cycle < 0 ) { cycle = MAX_WEAPS_IN_BANK_MP - 1; } while ( !weapBanksMultiPlayer[bank][cycle] ) { cycle--; if ( cycle < 0 ) { cycle = MAX_WEAPS_IN_BANK_MP - 1; } } return weapBanksMultiPlayer[bank][cycle]; } static int getPrevWeapInBankBynum( int weapnum ) { int bank, cycle; if ( !CG_WeaponIndex( weapnum, &bank, &cycle ) ) { return weapnum; } return getPrevWeapInBank( bank, cycle ); } /* ============== getNextBankWeap Pass in a bank and cycle and this will return the next valid weapon in a higher bank. sameBankPosition: if there's a weapon in the next bank at the same cycle, return that (colt returns thompson for example) rather than the lowest weapon ============== */ static int getNextBankWeap( int bank, int cycle, qboolean sameBankPosition ) { bank++; bank = bank % MAX_WEAP_BANKS_MP; if ( sameBankPosition && weapBanksMultiPlayer[bank][cycle] ) { return weapBanksMultiPlayer[bank][cycle]; } else { return weapBanksMultiPlayer[bank][0]; } } /* ============== getPrevBankWeap Pass in a bank and cycle and this will return the next valid weapon in a lower bank. sameBankPosition: if there's a weapon in the prev bank at the same cycle, return that (thompson returns colt for example) rather than the highest weapon ============== */ static int getPrevBankWeap( int bank, int cycle, qboolean sameBankPosition ) { int i; bank--; if ( bank < 0 ) { // don't go below 0, cycle up to top bank += MAX_WEAP_BANKS_MP; // JPW NERVE } bank = bank % MAX_WEAP_BANKS_MP; if ( sameBankPosition && weapBanksMultiPlayer[bank][cycle] ) { return weapBanksMultiPlayer[bank][cycle]; } else { // find highest weap in bank for ( i = MAX_WEAPS_IN_BANK_MP - 1; i >= 0; i-- ) { if ( weapBanksMultiPlayer[bank][i] ) { return weapBanksMultiPlayer[bank][i]; } } // if it gets to here, no valid weaps in this bank, go down another bank return getPrevBankWeap( bank, cycle, sameBankPosition ); } } /* ============== getAltWeapon ============== */ static int getAltWeapon( int weapnum ) { /* if(weapnum > MAX_WEAP_ALTS) Gordon: seems unneeded return weapnum;*/ if ( weapAlts[weapnum] ) { return weapAlts[weapnum]; } return weapnum; } /* ============== getEquivWeapon return the id of the opposite team's weapon. Passing the weapnum of the mp40 returns the id of the thompson, and likewise passing the weapnum of the thompson returns the id of the mp40. No equivalent available will return the weapnum passed in. ============== */ int getEquivWeapon( int weapnum ) { int num = weapnum; switch ( weapnum ) { // going from german to american case WP_LUGER: num = WP_COLT; break; case WP_MP40: num = WP_THOMPSON; break; case WP_GRENADE_LAUNCHER: num = WP_GRENADE_PINEAPPLE; break; case WP_KAR98: num = WP_CARBINE; break; case WP_SILENCER: num = WP_SILENCED_COLT; break; // going from american to german case WP_COLT: num = WP_LUGER; break; case WP_THOMPSON: num = WP_MP40; break; case WP_GRENADE_PINEAPPLE: num = WP_GRENADE_LAUNCHER; break; case WP_CARBINE: num = WP_KAR98; break; case WP_SILENCED_COLT: num = WP_SILENCER; break; } return num; } /* ============== CG_SetSniperZoom ============== */ void CG_SetSniperZoom( int lastweap, int newweap ) { int zoomindex; if ( lastweap == newweap ) { return; } // Keep binocs swaying if ( !( cg.predictedPlayerState.eFlags & EF_ZOOMING ) ) { cg.zoomval = 0; } cg.zoomedScope = 0; // check for fade-outs switch ( lastweap ) { case WP_FG42SCOPE: // cg.zoomedScope = 1; // TODO: add to zoomTable // cg.zoomTime = cg.time; break; case WP_GARAND_SCOPE: // cg.zoomedScope = 500; // TODO: add to zoomTable // cg.zoomTime = cg.time; break; case WP_K43_SCOPE: // cg.zoomedScope = 500; // TODO: add to zoomTable // cg.zoomTime = cg.time; break; } switch ( newweap ) { default: return; // no sniper zoom, get out. case WP_FG42SCOPE: cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE changed from defaultFG per atvi req cg.zoomedScope = 1; // TODO: add to zoomTable zoomindex = ZOOM_SNIPER; // JPW NERVE was FG42SCOPE break; case WP_GARAND_SCOPE: cg.zoomval = cg_zoomDefaultSniper.value; cg.zoomedScope = 900; // TODO: add to zoomTable zoomindex = ZOOM_SNIPER; break; case WP_K43_SCOPE: cg.zoomval = cg_zoomDefaultSniper.value; cg.zoomedScope = 900; // TODO: add to zoomTable zoomindex = ZOOM_SNIPER; break; } // constrain user preferred fov to weapon limitations if ( cg.zoomval > zoomTable[zoomindex][ZOOM_OUT] ) { cg.zoomval = zoomTable[zoomindex][ZOOM_OUT]; } if ( cg.zoomval < zoomTable[zoomindex][ZOOM_IN] ) { cg.zoomval = zoomTable[zoomindex][ZOOM_IN]; } cg.zoomTime = cg.time; } /* ============== CG_PlaySwitchSound Get special switching sounds if they're there ============== */ void CG_PlaySwitchSound( int lastweap, int newweap ) { // weaponInfo_t *weap; // weap = &cg_weapons[ ent->weapon ]; sfxHandle_t switchsound; switchsound = cgs.media.selectSound; if ( getAltWeapon( lastweap ) == newweap ) { // alt switch switch ( newweap ) { case WP_SILENCER: case WP_LUGER: case WP_SILENCED_COLT: case WP_COLT: case WP_GPG40: case WP_M7: case WP_MORTAR: case WP_MORTAR_SET: case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: switchsound = cg_weapons[newweap].switchSound; break; case WP_CARBINE: case WP_KAR98: if ( cg.predictedPlayerState.ammoclip[lastweap] ) { switchsound = cg_weapons[newweap].switchSound; } break; default: return; } } else { return; } trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_WEAPON, switchsound ); } /* ============== CG_FinishWeaponChange ============== */ void CG_FinishWeaponChange( int lastweap, int newweap ) { int newbank; if ( cg.binocZoomTime ) { return; } cg.mortarImpactTime = -2; switch ( newweap ) { case WP_LUGER: if ( ( cg.pmext.silencedSideArm & 1 ) && lastweap != WP_SILENCER ) { newweap = WP_SILENCER; cg.weaponSelect = newweap; } break; case WP_SILENCER: if ( !( cg.pmext.silencedSideArm & 1 ) && lastweap != WP_LUGER ) { newweap = WP_LUGER; cg.weaponSelect = newweap; } break; case WP_COLT: if ( ( cg.pmext.silencedSideArm & 1 ) && lastweap != WP_SILENCED_COLT ) { newweap = WP_SILENCED_COLT; cg.weaponSelect = newweap; } break; case WP_SILENCED_COLT: if ( !( cg.pmext.silencedSideArm & 1 ) && lastweap != WP_COLT ) { newweap = WP_COLT; cg.weaponSelect = newweap; } break; case WP_CARBINE: if ( ( cg.pmext.silencedSideArm & 2 ) && lastweap != WP_M7 ) { newweap = WP_M7; cg.weaponSelect = newweap; } break; case WP_M7: if ( !( cg.pmext.silencedSideArm & 2 ) && lastweap != WP_CARBINE ) { newweap = WP_CARBINE; cg.weaponSelect = newweap; } break; case WP_KAR98: if ( ( cg.pmext.silencedSideArm & 2 ) && lastweap != WP_GPG40 ) { newweap = WP_GPG40; cg.weaponSelect = newweap; } break; case WP_GPG40: if ( !( cg.pmext.silencedSideArm & 2 ) && lastweap != WP_KAR98 ) { newweap = WP_KAR98; cg.weaponSelect = newweap; } break; // case WP_MEDIC_SYRINGE: // if((cg.pmext.silencedSideArm & 4) && lastweap != WP_MEDIC_ADRENALINE) { // newweap = WP_MEDIC_ADRENALINE; // cg.weaponSelect = newweap; // } // break; // case WP_MEDIC_ADRENALINE: // if(!(cg.pmext.silencedSideArm & 4) && lastweap != WP_MEDIC_SYRINGE) { // newweap = WP_MEDIC_SYRINGE; // cg.weaponSelect = newweap; // } // break; } if ( lastweap == WP_BINOCULARS && cg.snap->ps.eFlags & EF_ZOOMING ) { trap_SendConsoleCommand( "-zoom\n" ); } cg.weaponSelectTime = cg.time; // flash the weapon icon // NERVE - SMF if ( cg.newCrosshairIndex ) { trap_Cvar_Set( "cg_drawCrossHair", va( "%d", cg.newCrosshairIndex - 1 ) ); } cg.newCrosshairIndex = 0; // -NERVE - SMF // remember which weapon in this bank was last selected so when cycling back // to this bank, that weap will be highlighted first if ( CG_WeaponIndex( newweap, &newbank, NULL ) ) { cg.lastWeapSelInBank[newbank] = newweap; } if ( lastweap == newweap ) { // no need to do any more than flash the icon return; } CG_PlaySwitchSound( lastweap, newweap ); // Gordon: grabbed from SP CG_SetSniperZoom( lastweap, newweap ); // setup for a user call to CG_LastWeaponUsed_f() if ( lastweap == cg.lastFiredWeapon ) { // don't set switchback for some weaps... switch ( lastweap ) { case WP_FG42SCOPE: case WP_GARAND_SCOPE: case WP_K43_SCOPE: break; default: cg.switchbackWeapon = lastweap; break; } } else { // if this ended up having the switchback be the same // as the new weapon, set the switchback to the prev // selected weapon will become the switchback if ( cg.switchbackWeapon == newweap ) { cg.switchbackWeapon = lastweap; } } cg.weaponSelect = newweap; } extern pmove_t cg_pmove; /* ============== CG_AltfireWeapon_f for example, switching between WP_MAUSER and WP_SNIPERRIFLE ============== */ void CG_AltWeapon_f( void ) { int original, num; if ( !cg.snap ) { return; } // Overload for spec mode when following if ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || cg.mvTotalClients > 0 ) { return; } // Need ground for this if ( cg.weaponSelect == WP_MORTAR ) { int contents; vec3_t point; if ( cg.predictedPlayerState.groundEntityNum == ENTITYNUM_NONE ) { return; } if ( !cg.predictedPlayerState.ammoclip[WP_MORTAR] ) { return; } if ( cg.predictedPlayerState.eFlags & EF_PRONE ) { return; } if ( cg_pmove.waterlevel == 3 ) { return; } // ydnar: don't allow set if moving if ( VectorLengthSquared( cg.snap->ps.velocity ) ) { return; } // eurgh, need it here too else we play sounds :/ point[0] = cg.snap->ps.origin[0]; point[1] = cg.snap->ps.origin[1]; point[2] = cg.snap->ps.origin[2] + cg.snap->ps.crouchViewHeight; contents = CG_PointContents( point, cg.snap->ps.clientNum ); if ( contents & MASK_WATER ) { return; } } else if ( cg.weaponSelect == WP_MOBILE_MG42 ) { if ( !( cg.predictedPlayerState.eFlags & EF_PRONE ) ) { return; } } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } // Don't try to switch when in the middle of reloading. if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { return; } original = cg.weaponSelect; num = getAltWeapon( original ); if ( original == WP_BINOCULARS ) { /*if(cg.snap->ps.eFlags & EF_ZOOMING) { trap_SendConsoleCommand( "-zoom\n" ); } else { trap_SendConsoleCommand( "+zoom\n" ); }*/ if ( cg.snap->ps.eFlags & EF_ZOOMING ) { trap_SendConsoleCommand( "-zoom\n" ); cg.binocZoomTime = -cg.time; } else { if ( !cg.binocZoomTime ) { cg.binocZoomTime = cg.time; } } } // Arnout: don't allow another weapon switch when we're still swapping the gpg40, to prevent animation breaking if ( ( cg.snap->ps.weaponstate == WEAPON_RAISING || cg.snap->ps.weaponstate == WEAPON_DROPPING ) && ( ( original == WP_GPG40 || num == WP_GPG40 || original == WP_M7 || num == WP_M7 ) || ( original == WP_SILENCER || num == WP_SILENCER || original == WP_SILENCED_COLT || num == WP_SILENCED_COLT ) || ( original == WP_AKIMBO_SILENCEDCOLT || num == WP_AKIMBO_SILENCEDCOLT || original == WP_AKIMBO_SILENCEDLUGER || num == WP_AKIMBO_SILENCEDLUGER ) || ( original == WP_MORTAR_SET || num == WP_MORTAR_SET ) || ( original == WP_MOBILE_MG42_SET || num == WP_MOBILE_MG42_SET ) ) ) { return; } if ( CG_WeaponSelectable( num ) ) { // new weapon is valid CG_FinishWeaponChange( original, num ); } } /* ============== CG_NextWeap switchBanks - curweap is the last in a bank, 'qtrue' means go to the next available bank, 'qfalse' means loop to the head of the bank ============== */ void CG_NextWeap( qboolean switchBanks ) { int bank = 0, cycle = 0, newbank = 0, newcycle = 0; int num, curweap; qboolean nextbank = qfalse; // need to switch to the next bank of weapons? int i, j; num = curweap = cg.weaponSelect; if ( curweap == WP_MORTAR_SET || curweap == WP_MOBILE_MG42_SET ) { return; } switch ( num ) { case WP_SILENCER: curweap = num = WP_LUGER; break; case WP_SILENCED_COLT: curweap = num = WP_COLT; break; case WP_GPG40: curweap = num = WP_KAR98; break; case WP_M7: curweap = num = WP_CARBINE; break; case WP_MORTAR_SET: curweap = num = WP_MORTAR; break; } CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon // if you're using an alt mode weapon, try switching back to the parent first if ( curweap >= WP_BEGINSECONDARY && curweap <= WP_LASTSECONDARY ) { num = getAltWeapon( curweap ); // base any further changes on the parent if ( CG_WeaponSelectable( num ) ) { // the parent was selectable, drop back to that CG_FinishWeaponChange( curweap, num ); return; } } if ( cg_cycleAllWeaps.integer || !switchBanks ) { for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { num = getNextWeapInBankBynum( num ); CG_WeaponIndex( num, NULL, &newcycle ); // get cycle of new weapon. if it's lower than the original, then it cycled around if ( switchBanks ) { if ( newcycle <= cycle ) { nextbank = qtrue; break; } } else { // don't switch banks if you get to the end if ( num == curweap ) { // back to start, just leave it where it is return; } } if ( CG_WeaponSelectable( num ) ) { break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } } } else { nextbank = qtrue; } if ( nextbank ) { for ( i = 0; i < MAX_WEAP_BANKS_MP; i++ ) { if ( cg_cycleAllWeaps.integer ) { num = getNextBankWeap( bank + i, cycle, qfalse ); // cycling all weaps always starts the next bank at the bottom } else { if ( cg.lastWeapSelInBank[bank + i + 1] ) { num = cg.lastWeapSelInBank[bank + i + 1]; } else { num = getNextBankWeap( bank + i, cycle, qtrue ); } } if ( num == 0 ) { continue; } // if(num == WP_BINOCULARS) { // continue; // } if ( CG_WeaponSelectable( num ) ) { // first entry in bank was selectable, no need to scan the bank break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } CG_WeaponIndex( num, &newbank, &newcycle ); // get the bank of the new weap for ( j = newcycle; j < MAX_WEAPS_IN_BANK_MP; j++ ) { num = getNextWeapInBank( newbank, j ); /* if(num == WP_BINOCULARS) { continue; }*/ if ( CG_WeaponSelectable( num ) ) { // found selectable weapon break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } num = 0; } if ( num ) { // a selectable weapon was found in the current bank break; } } } CG_FinishWeaponChange( curweap, num ); //----(SA) } /* ============== CG_PrevWeap switchBanks - curweap is the last in a bank 'qtrue' - go to the next available bank 'qfalse' - loop to the head of the bank ============== */ void CG_PrevWeap( qboolean switchBanks ) { int bank = 0, cycle = 0, newbank = 0, newcycle = 0; int num, curweap; qboolean prevbank = qfalse; // need to switch to the next bank of weapons? int i, j; num = curweap = cg.weaponSelect; if ( curweap == WP_MORTAR_SET || curweap == WP_MOBILE_MG42_SET ) { return; } switch ( num ) { case WP_SILENCER: curweap = num = WP_LUGER; break; case WP_SILENCED_COLT: curweap = num = WP_COLT; break; case WP_GPG40: curweap = num = WP_KAR98; break; case WP_M7: curweap = num = WP_CARBINE; break; case WP_MORTAR_SET: curweap = num = WP_MORTAR; break; } CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon // if you're using an alt mode weapon, try switching back to the parent first if ( curweap >= WP_BEGINSECONDARY && curweap <= WP_LASTSECONDARY ) { num = getAltWeapon( curweap ); // base any further changes on the parent if ( CG_WeaponSelectable( num ) ) { // the parent was selectable, drop back to that CG_FinishWeaponChange( curweap, num ); return; } } // initially, just try to find a lower weapon in the current bank if ( cg_cycleAllWeaps.integer || !switchBanks ) { for ( i = cycle; i >= 0; i-- ) { num = getPrevWeapInBankBynum( num ); CG_WeaponIndex( num, NULL, &newcycle ); // get cycle of new weapon. if it's greater than the original, then it cycled around if ( switchBanks ) { if ( newcycle > ( cycle - 1 ) ) { prevbank = qtrue; break; } } else { // don't switch banks if you get to the end if ( num == curweap ) { // back to start, just leave it where it is return; } } // if(num == WP_BINOCULARS) { // continue; // } if ( CG_WeaponSelectable( num ) ) { break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } } } else { prevbank = qtrue; } // cycle to previous bank. // if cycleAllWeaps: find highest weapon in bank // else: try to find weap in bank that matches cycle position // else: use base weap in bank if ( prevbank ) { for ( i = 0; i < MAX_WEAP_BANKS_MP; i++ ) { if ( cg_cycleAllWeaps.integer ) { num = getPrevBankWeap( bank - i, cycle, qfalse ); // cycling all weaps always starts the next bank at the bottom } else { num = getPrevBankWeap( bank - i, cycle, qtrue ); } if ( num == 0 ) { continue; } if ( CG_WeaponSelectable( num ) ) { // first entry in bank was selectable, no need to scan the bank break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } CG_WeaponIndex( num, &newbank, &newcycle ); // get the bank of the new weap for ( j = MAX_WEAPS_IN_BANK_MP; j > 0; j-- ) { num = getPrevWeapInBank( newbank, j ); if ( CG_WeaponSelectable( num ) ) { // found selectable weapon break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } num = 0; } if ( num ) { // a selectable weapon was found in the current bank break; } } } CG_FinishWeaponChange( curweap, num ); //----(SA) } /* ============== CG_LastWeaponUsed_f ============== */ void CG_LastWeaponUsed_f( void ) { int lastweap; //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } if ( cg.weaponSelect == WP_MORTAR_SET || cg.weaponSelect == WP_MOBILE_MG42_SET ) { return; } cg.weaponSelectTime = cg.time; // flash the current weapon icon // don't switchback if reloading (it nullifies the reload) if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { return; } if ( !cg.switchbackWeapon ) { cg.switchbackWeapon = cg.weaponSelect; return; } if ( CG_WeaponSelectable( cg.switchbackWeapon ) ) { lastweap = cg.weaponSelect; CG_FinishWeaponChange( cg.weaponSelect, cg.switchbackWeapon ); } else { // switchback no longer selectable, reset cycle cg.switchbackWeapon = 0; } } /* ============== CG_NextWeaponInBank_f ============== */ void CG_NextWeaponInBank_f( void ) { //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) // for zooming (binocs/snooper/sniper/etc.) if ( cg.zoomval ) { if ( cg_useWeapsForZoom.integer == 1 ) { CG_ZoomIn_f(); return; } else if ( cg_useWeapsForZoom.integer == 2 ) { CG_ZoomOut_f(); return; } } cg.weaponSelectTime = cg.time; // flash the current weapon icon CG_NextWeap( qfalse ); } /* ============== CG_PrevWeaponInBank_f ============== */ void CG_PrevWeaponInBank_f( void ) { //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) // for zooming (binocs/snooper/sniper/etc.) if ( cg.zoomval ) { if ( cg_useWeapsForZoom.integer == 2 ) { CG_ZoomIn_f(); return; } else if ( cg_useWeapsForZoom.integer == 1 ) { CG_ZoomOut_f(); return; } } cg.weaponSelectTime = cg.time; // flash the current weapon icon CG_PrevWeap( qfalse ); } /* ============== CG_NextWeapon_f ============== */ void CG_NextWeapon_f( void ) { if ( !cg.snap ) { return; } // Overload for MV clients if ( cg.mvTotalClients > 0 ) { CG_mvToggleView_f(); return; } //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) // for zooming (binocs/snooper/sniper/etc.) if ( cg.zoomval ) { if ( cg_useWeapsForZoom.integer == 1 ) { CG_ZoomIn_f(); return; } else if ( cg_useWeapsForZoom.integer == 2 ) { CG_ZoomOut_f(); return; } } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } cg.weaponSelectTime = cg.time; // flash the current weapon icon // Don't try to switch when in the middle of reloading. // cheatinfo: The server actually would let you switch if this check were not // present, but would discard the reload. So the when you switched // back you'd have to start the reload over. This seems bad, however // the delay for the current reload is already in effect, so you'd lose // the reload time twice. (the first pause for the current weapon reload, // and the pause when you have to reload again 'cause you canceled this one) if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { return; } CG_NextWeap( qtrue ); } /* ============== CG_PrevWeapon_f ============== */ void CG_PrevWeapon_f( void ) { if ( !cg.snap ) { return; } // Overload for MV clients if ( cg.mvTotalClients > 0 ) { CG_mvSwapViews_f(); return; } //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) // for zooming (binocs/snooper/sniper/etc.) if ( cg.zoomval ) { if ( cg_useWeapsForZoom.integer == 1 ) { CG_ZoomOut_f(); return; } else if ( cg_useWeapsForZoom.integer == 2 ) { CG_ZoomIn_f(); return; } } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } cg.weaponSelectTime = cg.time; // flash the current weapon icon // Don't try to switch when in the middle of reloading. if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { return; } CG_PrevWeap( qtrue ); } /* ============== CG_WeaponBank_f weapon keys are not generally bound directly('bind 1 weapon 1'), rather the key is bound to a given bank ('bind 1 weaponbank 1') ============== */ void CG_WeaponBank_f( void ) { int num, i, curweap; int curbank = 0, curcycle = 0, bank = 0, cycle = 0; if ( !cg.snap ) { return; } //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { return; // force pause so holding it down won't go too fast } if ( cg.weaponSelect == WP_MORTAR_SET || cg.weaponSelect == WP_MOBILE_MG42_SET ) { return; } cg.weaponSelectTime = cg.time; // flash the current weapon icon // Don't try to switch when in the middle of reloading. if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { return; } bank = atoi( CG_Argv( 1 ) ); if ( bank <= 0 || bank > MAX_WEAP_BANKS_MP ) { return; } curweap = cg.weaponSelect; CG_WeaponIndex( curweap, &curbank, &curcycle ); // get bank/cycle of current weapon if ( !cg.lastWeapSelInBank[bank] ) { num = weapBanksMultiPlayer[bank][0]; cycle -= 1; // cycle up to first weap } else { num = cg.lastWeapSelInBank[bank]; CG_WeaponIndex( num, &bank, &cycle ); if ( bank != curbank ) { cycle -= 1; } } for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { num = getNextWeapInBank( bank, cycle + i ); if ( CG_WeaponSelectable( num ) ) { break; } else { qboolean found = qfalse; switch ( num ) { case WP_CARBINE: if ( ( found = CG_WeaponSelectable( WP_M7 ) ) ) { num = WP_M7; } break; case WP_KAR98: if ( ( found = CG_WeaponSelectable( WP_GPG40 ) ) ) { num = WP_GPG40; } break; } if ( found ) { break; } } } if ( i == MAX_WEAPS_IN_BANK_MP ) { return; } // Arnout: don't allow another weapon switch when we're still swapping the gpg40, to prevent animation breaking if ( ( cg.snap->ps.weaponstate == WEAPON_RAISING || cg.snap->ps.weaponstate == WEAPON_DROPPING ) && ( ( curweap == WP_GPG40 || num == WP_GPG40 || curweap == WP_M7 || num == WP_M7 ) || ( curweap == WP_SILENCER || num == WP_SILENCER || curweap == WP_SILENCED_COLT || num == WP_SILENCED_COLT ) || ( curweap == WP_MORTAR_SET || num == WP_MORTAR_SET ) ) ) { return; } CG_FinishWeaponChange( curweap, num ); } /* =============== CG_Weapon_f =============== */ void CG_Weapon_f( void ) { int num; // int bank = 0, cycle = 0, newbank = 0, newcycle = 0; // qboolean banked = qfalse; if ( !cg.snap ) { return; } //fretn - #447 //osp-rtcw & et pause bug if ( cg.snap->ps.pm_type == PM_FREEZE ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } if ( cg.weaponSelect == WP_MORTAR_SET || cg.weaponSelect == WP_MOBILE_MG42_SET ) { return; } num = atoi( CG_Argv( 1 ) ); // JPW NERVE // weapon bind should execute weaponbank instead -- for splitting out class weapons, per Id request if ( num < MAX_WEAP_BANKS_MP ) { CG_WeaponBank_f(); } return; // jpw /* cg.weaponSelectTime = cg.time; // flash the current weapon icon // Don't try to switch when in the middle of reloading. if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) return; if ( num <= WP_NONE || num > WP_NUM_WEAPONS ) { return; } curweap = cg.weaponSelect; CG_WeaponIndex(curweap, &bank, &cycle); // get bank/cycle of current weapon banked = CG_WeaponIndex(num, &newbank, &newcycle); // get bank/cycle of requested weapon // the new weapon was not found in the reglar banks // assume the player want's to go directly to it if possible if(!banked) { if(CG_WeaponSelectable(num)) { CG_FinishWeaponChange(curweap, num); return; } } if(bank != newbank) cycle = newcycle - 1; // drop down one from the requested weap's cycle so it will // try to initially cycle up to the requested weapon for(i = 0; i < MAX_WEAPS_IN_BANK; i++) { num = getNextWeapInBank(newbank, cycle+i); if(num == curweap) // no other weapons in bank return; if(CG_WeaponSelectable(num)) { break; } } if(i == MAX_WEAPS_IN_BANK) return; CG_FinishWeaponChange(curweap, num);*/ } /* =================== CG_OutOfAmmoChange The current weapon has just run out of ammo =================== */ void CG_OutOfAmmoChange( qboolean allowforceswitch ) { int i; int bank, cycle; int equiv = WP_NONE; // // trivial switching // if ( cg.weaponSelect == WP_PLIERS || ( cg.weaponSelect == WP_SATCHEL_DET && cg.predictedPlayerState.ammo[WP_SATCHEL_DET] ) ) { return; } if ( allowforceswitch ) { if ( cg.weaponSelect == WP_SMOKE_BOMB ) { if ( CG_WeaponSelectable( WP_LUGER ) ) { cg.weaponSelect = WP_LUGER; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, WP_LUGER ); return; } else if ( CG_WeaponSelectable( WP_COLT ) ) { cg.weaponSelect = WP_COLT; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, WP_COLT ); return; } } else if ( cg.weaponSelect == WP_LANDMINE ) { if ( CG_WeaponSelectable( WP_PLIERS ) ) { cg.weaponSelect = WP_PLIERS; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, WP_PLIERS ); return; } } else if ( cg.weaponSelect == WP_SATCHEL ) { if ( CG_WeaponSelectable( WP_SATCHEL_DET ) ) { cg.weaponSelect = WP_SATCHEL_DET; return; } } else if ( cg.weaponSelect == WP_MORTAR_SET ) { cg.weaponSelect = WP_MORTAR; return; } else if ( cg.weaponSelect == WP_MOBILE_MG42_SET ) { cg.weaponSelect = WP_MOBILE_MG42; return; } // JPW NERVE -- early out if we just dropped dynamite, go to pliers if ( cg.weaponSelect == WP_DYNAMITE ) { if ( CG_WeaponSelectable( WP_PLIERS ) ) { cg.weaponSelect = WP_PLIERS; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, WP_PLIERS ); return; } } // JPW NERVE -- early out if we just fired Panzerfaust, go to pistola, then grenades if ( cg.weaponSelect == WP_PANZERFAUST ) { for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { if ( CG_WeaponSelectable( weapBanksMultiPlayer[2][i] ) ) { // find a pistol cg.weaponSelect = weapBanksMultiPlayer[2][i]; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); return; } } for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { if ( CG_WeaponSelectable( weapBanksMultiPlayer[4][i] ) ) { // find a grenade cg.weaponSelect = weapBanksMultiPlayer[4][i]; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); return; } } } // if you're using an alt mode weapon, try switching back to the parent // otherwise, switch to the equivalent if you've got it if ( cg.weaponSelect >= WP_BEGINSECONDARY && cg.weaponSelect <= WP_LASTSECONDARY ) { cg.weaponSelect = equiv = getAltWeapon( cg.weaponSelect ); // base any further changes on the parent if ( CG_WeaponSelectable( equiv ) ) { // the parent was selectable, drop back to that CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA) return; } } // now try the opposite team's equivalent weap equiv = getEquivWeapon( cg.weaponSelect ); if ( equiv != cg.weaponSelect && CG_WeaponSelectable( equiv ) ) { cg.weaponSelect = equiv; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA) return; } } // // more complicated selection // // didn't have available alternative or equivalent, try another weap in the bank CG_WeaponIndex( cg.weaponSelect, &bank, &cycle ); // get bank/cycle of current weapon // JPW NERVE -- more useful weapon changes -- check if rifle or pistol is still working, and use that if available for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { if ( CG_WeaponSelectable( weapBanksMultiPlayer[3][i] ) ) { // find a rifle cg.weaponSelect = weapBanksMultiPlayer[3][i]; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); return; } } for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { if ( CG_WeaponSelectable( weapBanksMultiPlayer[2][i] ) ) { // find a pistol cg.weaponSelect = weapBanksMultiPlayer[2][i]; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); return; } } // otherwise just do something for ( i = cycle; i < MAX_WEAPS_IN_BANK_MP; i++ ) { equiv = getNextWeapInBank( bank, i ); if ( CG_WeaponSelectable( equiv ) ) { // found a reasonable replacement cg.weaponSelect = equiv; CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA) return; } } // still nothing available, just go to the next // available weap using the regular selection scheme CG_NextWeap( qtrue ); } /* =================================================================================================== WEAPON EVENTS =================================================================================================== */ void CG_MG42EFX( centity_t *cent ) { // Arnout: complete overhaul of this one centity_t *mg42; int num; vec3_t forward, point; refEntity_t flash; // find the mg42 we're attached to for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { mg42 = &cg_entities[ cg.snap->entities[ num ].number ]; if ( mg42->currentState.eType == ET_MG42_BARREL && mg42->currentState.otherEntityNum == cent->currentState.number ) { // found it, clamp behind gun VectorCopy( mg42->currentState.pos.trBase, point ); //AngleVectors (mg42->s.apos.trBase, forward, NULL, NULL); AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorMA( point, 40, forward, point ); memset( &flash, 0, sizeof( flash ) ); flash.renderfx = RF_LIGHTING_ORIGIN; flash.hModel = cgs.media.mg42muzzleflash; VectorCopy( point, flash.origin ); AnglesToAxis( cent->lerpAngles, flash.axis ); trap_R_AddRefEntityToScene( &flash ); // ydnar: add dynamic light trap_R_AddLightToScene( flash.origin, 320, 1.25 + ( rand() & 31 ) / 128, 1.0, 0.6, 0.23, 0, 0 ); return; } } } // Note to self this is dead code /*void CG_FLAKEFX (centity_t *cent, int whichgun) { entityState_t *ent; vec3_t forward, right, up; vec3_t point; refEntity_t flash; ent = ¢->currentState; VectorCopy (cent->currentState.pos.trBase, point); AngleVectors (cent->currentState.apos.trBase, forward, right, up); // gun 1 and 2 were switched if (whichgun == 2) { VectorMA (point, 136, forward, point); VectorMA (point, 31, up, point); VectorMA (point, 22, right, point); } else if (whichgun == 1) { VectorMA (point, 136, forward, point); VectorMA (point, 31, up, point); VectorMA (point, -22, right, point); } else if (whichgun == 3) { VectorMA (point, 136, forward, point); VectorMA (point, 10, up, point); VectorMA (point, 22, right, point); } else if (whichgun == 4) { VectorMA (point, 136, forward, point); VectorMA (point, 10, up, point); VectorMA (point, -22, right, point); } trap_R_AddLightToScene( point, 200 + (rand()&31),1.0, 0.6, 0.23, 0 ); memset (&flash, 0, sizeof (flash)); flash.renderfx = RF_LIGHTING_ORIGIN; flash.hModel = cgs.media.mg42muzzleflash; VectorCopy( point, flash.origin ); AnglesToAxis (cg.refdefViewAngles, flash.axis); trap_R_AddRefEntityToScene( &flash ); trap_S_StartSound( NULL, ent->number , CHAN_WEAPON, hflakWeaponSnd ); }*/ //----(SA) /* ============== CG_MortarEFX Right now mostly copied directly from Raf's MG42 FX, but with the optional addtion of smoke ============== */ void CG_MortarEFX( centity_t *cent ) { refEntity_t flash; if ( cent->currentState.density & 1 ) { // smoke CG_ParticleImpactSmokePuff( cgs.media.smokePuffShader, cent->currentState.origin ); } if ( cent->currentState.density & 2 ) { // light //% trap_R_AddLightToScene( cent->currentState.origin, 200 + (rand()&31), 1.0, 1.0, 1.0, 0 ); trap_R_AddLightToScene( cent->currentState.origin, 256, 0.75 + 8.0 / ( rand() & 31 ), 1.0, 1.0, 1.0, 0, 0 ); // muzzle flash memset( &flash, 0, sizeof( flash ) ); flash.renderfx = RF_LIGHTING_ORIGIN; flash.hModel = cgs.media.mg42muzzleflash; VectorCopy( cent->currentState.origin, flash.origin ); AnglesToAxis( cg.refdefViewAngles, flash.axis ); trap_R_AddRefEntityToScene( &flash ); } } //----(SA) end // RF /* ============== CG_WeaponFireRecoil ============== */ void CG_WeaponFireRecoil( int weapon ) { // const vec3_t maxKickAngles = {25, 30, 25}; float pitchRecoilAdd, pitchAdd; float yawRandom; vec3_t recoil; // pitchRecoilAdd = 0; pitchAdd = 0; yawRandom = 0; // switch ( weapon ) { case WP_LUGER: case WP_SILENCER: case WP_AKIMBO_LUGER: case WP_AKIMBO_SILENCEDLUGER: case WP_COLT: case WP_SILENCED_COLT: case WP_AKIMBO_COLT: case WP_AKIMBO_SILENCEDCOLT: //pitchAdd = 2+rand()%3; //yawRandom = 2; break; case WP_GARAND: case WP_KAR98: case WP_CARBINE: case WP_K43: //pitchAdd = 4+rand()%3; //yawRandom = 4; pitchAdd = 2; //----(SA) for DM yawRandom = 1; //----(SA) for DM break; case WP_GARAND_SCOPE: case WP_K43_SCOPE: pitchAdd = 0.3; break; case WP_FG42SCOPE: case WP_FG42: case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: case WP_MP40: case WP_THOMPSON: case WP_STEN: //pitchRecoilAdd = 1; pitchAdd = 1 + rand() % 3; yawRandom = 2; pitchAdd *= 0.3; yawRandom *= 0.3; break; case WP_PANZERFAUST: //pitchAdd = 12+rand()%3; //yawRandom = 6; // push the player back instead break; default: return; } // calc the recoil recoil[YAW] = crandom() * yawRandom; recoil[ROLL] = -recoil[YAW]; // why not recoil[PITCH] = -pitchAdd; // scale it up a bit (easier to modify this while tweaking) VectorScale( recoil, 30, recoil ); // set the recoil VectorCopy( recoil, cg.kickAVel ); // set the recoil cg.recoilPitch -= pitchRecoilAdd; } /* ================ CG_FireWeapon Caused by an EV_FIRE_WEAPON event ================ */ void CG_FireWeapon( centity_t *cent ) { entityState_t *ent; int c; weaponInfo_t *weap; sfxHandle_t *firesound; sfxHandle_t *fireEchosound; ent = ¢->currentState; // Arnout: quick hack for EF_MOUNTEDTANK, need to change this - likely it needs to use viewlocked as well if ( cent->currentState.eFlags & EF_MOUNTEDTANK ) { if ( cg_entities[cg_entities[cg_entities[ cent->currentState.number ].tagParent].tankparent].currentState.density & 8 ) { // should we use a browning? trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cgs.media.hWeaponSnd_2 ); } else { trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cgs.media.hWeaponSnd ); } cent->muzzleFlashTime = cg.time; return; } // Rafael - mg42 if ( BG_PlayerMounted( cent->currentState.eFlags ) ) { if ( cent->currentState.eFlags & EF_AAGUN_ACTIVE ) { // trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cgs.media.hflakWeaponSnd ); } else { trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cgs.media.hWeaponSnd ); } if ( cg_brassTime.integer > 0 ) { CG_MachineGunEjectBrass( cent ); } cent->muzzleFlashTime = cg.time; return; } 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 ]; if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) { cg.lastFiredWeapon = ent->weapon; //----(SA) added } // mark the entity as muzzle flashing, so when it is added it will // append the flash to the weapon model cent->muzzleFlashTime = cg.time; // RF, kick angles if ( ent->number == cg.snap->ps.clientNum ) { CG_WeaponFireRecoil( ent->weapon ); } if ( ent->weapon == WP_MORTAR_SET ) { if ( ent->clientNum == cg.snap->ps.clientNum ) { cg.mortarImpactTime = -1; cg.mortarFireAngles[PITCH] = cg.predictedPlayerState.viewangles[PITCH]; cg.mortarFireAngles[YAW] = cg.predictedPlayerState.viewangles[YAW]; } } // lightning gun only does this this on initial press if ( ent->weapon == WP_FLAMETHROWER ) { if ( cent->pe.lightningFiring ) { return; } } else if ( ent->weapon == WP_GRENADE_LAUNCHER || ent->weapon == WP_GRENADE_PINEAPPLE || ent->weapon == WP_DYNAMITE || ent->weapon == WP_SMOKE_MARKER || ent->weapon == WP_LANDMINE || ent->weapon == WP_SATCHEL || ent->weapon == WP_TRIPMINE || ent->weapon == WP_SMOKE_BOMB ) { // JPW NERVE if ( ent->apos.trBase[0] > 0 ) { // underhand return; } } if ( ent->weapon == WP_GPG40 ) { if ( ent->clientNum == cg.snap->ps.clientNum ) { cg.weaponSelect = WP_KAR98; } } else if ( ent->weapon == WP_M7 ) { if ( ent->clientNum == cg.snap->ps.clientNum ) { cg.weaponSelect = WP_CARBINE; } } if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == EV_FIRE_WEAPON_LASTSHOT ) { firesound = &weap->lastShotSound[0]; fireEchosound = &weap->flashEchoSound[0]; // try to use the lastShotSound, but don't assume it's there. // if a weapon without the sound calls it, drop back to regular fire sound for ( c = 0; c < 4; c++ ) { if ( !firesound[c] ) { break; } } if ( !c ) { firesound = &weap->flashSound[0]; fireEchosound = &weap->flashEchoSound[0]; } } else { firesound = &weap->flashSound[0]; fireEchosound = &weap->flashEchoSound[0]; } /* // JPW NERVE -- special case medic tool if( ent->weapon == WP_MEDKIT ) { firesound = &cg_weapons[ WP_MEDKIT ].flashSound[0]; }*/ if ( !( cent->currentState.eFlags & EF_ZOOMING ) ) { // JPW NERVE -- don't play sounds or eject brass if zoomed in // play a sound for ( c = 0 ; c < 4 ; c++ ) { if ( !firesound[c] ) { break; } } if ( c > 0 ) { c = rand() % c; if ( firesound[c] ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, firesound[c] ); if ( fireEchosound && fireEchosound[c] ) { // check for echo centity_t *cent; vec3_t porg, gorg, norm; // player/gun origin float gdist; cent = &cg_entities[ent->number]; VectorCopy( cent->currentState.pos.trBase, gorg ); VectorCopy( cg.refdef_current->vieworg, porg ); VectorSubtract( gorg, porg, norm ); gdist = VectorNormalize( norm ); if ( gdist > 512 && gdist < 4096 ) { // temp dist. TODO: use numbers that are weapon specific // use gorg as the new sound origin VectorMA( cg.refdef_current->vieworg, 64, norm, gorg ); // sound-on-a-stick trap_S_StartSoundEx( gorg, ent->number, CHAN_WEAPON, fireEchosound[c], SND_NOCUT ); } } } } // do brass ejection if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { weap->ejectBrassFunc( cent ); } } // jpw } // Ridah /* ================= CG_AddSparks ================= */ void CG_AddSparks( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) { localEntity_t *le; refEntity_t *re; vec3_t velocity; int i; for ( i = 0; i < count; i++ ) { le = CG_AllocLocalEntity(); re = &le->refEntity; VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale ); VectorScale( velocity, (float)speed, velocity ); le->leType = LE_SPARK; le->startTime = cg.time; le->endTime = le->startTime + duration - (int)( 0.5 * random() * duration ); le->lastTrailTime = cg.time; VectorCopy( origin, re->origin ); AxisCopy( axisDefault, re->axis ); le->pos.trType = TR_GRAVITY_LOW; VectorCopy( origin, le->pos.trBase ); VectorMA( le->pos.trBase, 2 + random() * 4, dir, le->pos.trBase ); VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; le->refEntity.customShader = cgs.media.sparkParticleShader; le->bounceFactor = 0.9; // le->leBounceSoundType = LEBS_BLOOD; // le->leMarkType = LEMT_BLOOD; } } /* ================= CG_AddBulletParticles ================= */ void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) { // localEntity_t *le; // refEntity_t *re; vec3_t velocity, pos; int i; /* // add the falling streaks for (i=0; irefEntity; VectorSet( velocity, dir[0] + crandom()*randScale, dir[1] + crandom()*randScale, dir[2] + crandom()*randScale ); VectorScale( velocity, (float)speed*3, velocity ); le->leType = LE_SPARK; le->startTime = cg.time; le->endTime = le->startTime + duration - (int)(0.5 * random() * duration); le->lastTrailTime = cg.time; VectorCopy( origin, re->origin ); AxisCopy( axisDefault, re->axis ); le->pos.trType = TR_GRAVITY; VectorCopy( origin, le->pos.trBase ); VectorMA( le->pos.trBase, 2 + random()*4, dir, le->pos.trBase ); VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; le->refEntity.customShader = cgs.media.bulletParticleTrailShader; // le->refEntity.customShader = cgs.media.sparkParticleShader; le->bounceFactor = 0.9; // le->leBounceSoundType = LEBS_BLOOD; // le->leMarkType = LEMT_BLOOD; } */ // add the falling particles for ( i = 0; i < count; i++ ) { VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale ); VectorScale( velocity, (float)speed, velocity ); VectorCopy( origin, pos ); VectorMA( pos, 2 + random() * 4, dir, pos ); CG_ParticleBulletDebris( pos, velocity, 300 + rand() % 300 ); } } /* ================= CG_AddDirtBulletParticles ================= */ void CG_AddDirtBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale, float width, float height, float alpha, qhandle_t shader ) { vec3_t velocity, pos; int i; // add the big falling particle VectorSet( velocity, 0, 0, (float)speed ); VectorCopy( origin, pos ); CG_ParticleDirtBulletDebris_Core( pos, velocity, duration, width,height, alpha, shader ); //600 + rand()%300 ); // keep central one for ( i = 0; i < count; i++ ) { VectorSet( velocity, dir[0] * crandom() * speed * randScale, dir[1] * crandom() * speed * randScale, dir[2] * random() * speed ); CG_ParticleDirtBulletDebris_Core( pos, velocity, duration + ( rand() % ( duration >> 1 ) ), width,height, alpha, shader ); } } /* ================= CG_AddDebris ================= */ void CG_AddDebris( vec3_t origin, vec3_t dir, int speed, int duration, int count ) { localEntity_t *le; refEntity_t *re; vec3_t velocity, unitvel; float timeAdd; int i; for ( i = 0; i < count; i++ ) { le = CG_AllocLocalEntity(); re = &le->refEntity; VectorSet( unitvel, dir[0] + crandom() * 0.9, dir[1] + crandom() * 0.9, fabs( dir[2] ) > 0.5 ? dir[2] * ( 0.2 + 0.8 * random() ) : random() * 0.6 ); VectorScale( unitvel, (float)speed + (float)speed * 0.5 * crandom(), velocity ); le->leType = LE_DEBRIS; le->startTime = cg.time; le->endTime = le->startTime + duration + (int)( (float)duration * 0.8 * crandom() ); le->lastTrailTime = cg.time; VectorCopy( origin, re->origin ); AxisCopy( axisDefault, re->axis ); le->pos.trType = TR_GRAVITY_LOW; VectorCopy( origin, le->pos.trBase ); VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; timeAdd = 10.0 + random() * 40.0; BG_EvaluateTrajectory( &le->pos, cg.time + (int)timeAdd, le->pos.trBase, qfalse, -1 ); le->bounceFactor = 0.5; // if (!rand()%2) // le->effectWidth = 0; // no flame // else le->effectWidth = 5 + random() * 5; // if (rand()%3) le->effectFlags |= 1; // smoke trail // le->leBounceSoundType = LEBS_BLOOD; // le->leMarkType = LEMT_BLOOD; } } // done. /* ============== CG_WaterRipple ============== */ void CG_WaterRipple( qhandle_t shader, vec3_t loc, vec3_t dir, int size, int lifetime ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); le->leType = LE_SCALE_FADE; le->leFlags = LEF_PUFF_DONT_SCALE; le->startTime = cg.time; le->endTime = cg.time + lifetime; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); re = &le->refEntity; VectorCopy( loc, re->origin ); re->shaderTime = cg.time / 1000.0f; re->reType = RT_SPLASH; re->radius = size; re->customShader = shader; re->shaderRGBA[0] = 0xff; re->shaderRGBA[1] = 0xff; re->shaderRGBA[2] = 0xff; re->shaderRGBA[3] = 0xff; le->color[3] = 1.0; } /* ================= CG_MissileHitWall Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ClientNum is a dummy field used to define what sort of effect to spawn ================= */ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, int surfFlags ) { // (SA) modified to send missilehitwall surface parameters qhandle_t mod, mark, shader; sfxHandle_t sfx, sfx2; localEntity_t *le; qboolean isSprite, alphaFade = qfalse; int r, duration, lightOverdraw, i, j, markDuration, volume; trace_t trace; vec3_t lightColor, tmpv, tmpv2, sprOrg, sprVel; float radius, light, sfx2range = 0; vec4_t projection, color; vec3_t markOrigin; mark = 0; radius = 32; sfx = 0; sfx2 = 0; mod = 0; shader = 0; light = 0; VectorSet( lightColor, 1, 1, 0 ); // Ridah lightOverdraw = 0; volume = 127; // set defaults isSprite = qfalse; duration = 600; markDuration = -1; if ( surfFlags & SURF_SKY ) { return; } switch ( weapon ) { case WP_KNIFE: i = rand() % 4; if ( !surfFlags ) { sfx = cgs.media.sfx_knifehit[4]; // different values for different types (stone/metal/wood/etc.) mark = cgs.media.bulletMarkShader; radius = 1 + rand() % 2; CG_AddBulletParticles( origin, dir, 20, 800, 3 + rand() % 6, 1.0 ); } else { sfx = cgs.media.sfx_knifehit[i]; } // ydnar: set mark duration markDuration = cg_markTime.integer; break; case WP_LUGER: case WP_SILENCER: case WP_AKIMBO_LUGER: case WP_AKIMBO_SILENCEDLUGER: case WP_COLT: case WP_SILENCED_COLT: case WP_AKIMBO_COLT: case WP_AKIMBO_SILENCEDCOLT: case WP_MP40: case WP_THOMPSON: case WP_STEN: case WP_GARAND: case WP_FG42: case WP_FG42SCOPE: case WP_KAR98: case WP_CARBINE: case WP_MOBILE_MG42: case WP_MOBILE_MG42_SET: case WP_K43: case WP_GARAND_SCOPE: case WP_K43_SCOPE: // actually yeah. meant that. very rare. r = ( rand() & 3 ) + 1; // JPW NERVE increased spark frequency so players can tell where rounds are coming from in MP volume = 64; /* if ( r == 3 ) { sfx = cgs.media.sfx_ric1; } else if ( r == 2 ) { sfx = cgs.media.sfx_ric2; } else if ( r == 1 ) { sfx = cgs.media.sfx_ric3; }*/ // clientNum is a dummy field used to define what sort of effect to spawn if ( !clientNum ) { // RF, why is this here? we need sparks if clientNum = 0, used for warzombie CG_AddSparks( origin, dir, 350, 200, 15 + rand() % 7, 0.2 ); } else if ( clientNum == 1 ) { // just do a little smoke puff vec3_t d, o; VectorMA( origin, 12, dir, o ); VectorScale( dir, 7, d ); d[2] += 16; // DHM - Nerve :: use dirt images if ( ( surfFlags & SURF_GRASS || surfFlags & SURF_GRAVEL || surfFlags & SURF_SNOW ) ) { // JPW NERVE added SURF_SNOW // some debris particles // JPW NERVE added surf_snow if ( surfFlags & SURF_SNOW ) { CG_AddDirtBulletParticles( origin, dir, 190, 900, 5, 0.25, 80, 32, 0.5, cgs.media.dirtParticle2Shader ); } else { CG_AddDirtBulletParticles( origin, dir, 190, 900, 5, 0.5, 80, 16, 0.5, cgs.media.dirtParticle1Shader ); } } else { CG_ParticleImpactSmokePuff( cgs.media.smokeParticleShader, o ); // some debris particles CG_AddBulletParticles( origin, dir, 20, 800, 3 + rand() % 6, 1.0 ); // rand scale // just do a little one if ( sfx && ( rand() % 3 == 0 ) ) { CG_AddSparks( origin, dir, 450, 300, 3 + rand() % 3, 0.5 ); // rand scale } } } else if ( clientNum == 2 ) { sfx = 0; mark = 0; // (SA) needed to do the CG_WaterRipple using a localent since I needed the timer reset on the shader for each shot CG_WaterRipple( cgs.media.wakeMarkShaderAnim, origin, tv( 0, 0, 1 ), 32, 1000 ); CG_AddDirtBulletParticles( origin, dir, 190, 900, 5, 0.5, 80, 16, 0.125, cgs.media.dirtParticle2Shader ); break; // play a water splash mod = cgs.media.waterSplashModel; shader = cgs.media.waterSplashShader; duration = 250; } // Ridah, optimization, only spawn the bullet hole if we are close // enough to see it, this way we can leave other marks around a lot // longer, since most of the time we can't actually see the bullet holes // (SA) small modification. only do this for non-rifles (so you can see your shots hitting when you're zooming with a rifle scope) if ( weapon == WP_FG42SCOPE || weapon == WP_GARAND_SCOPE || weapon == WP_K43_SCOPE || ( Distance( cg.refdef_current->vieworg, origin ) < 384 ) ) { if ( clientNum ) { // mark and sound can potentially use the surface for override values mark = cgs.media.bulletMarkShader; // default alphaFade = qtrue; // max made the bullet mark alpha (he'll make everything in the game out of 1024 textures, all with alpha blend funcs yet...) //% radius = 1.5f + rand()%2; // slightly larger for DM radius = 1.0f + 0.5f * ( rand() % 2 ); #define MAX_IMPACT_SOUNDS 5 if ( surfFlags & SURF_METAL || surfFlags & SURF_ROOF ) { sfx = cgs.media.sfx_bullet_metalhit[rand() % MAX_IMPACT_SOUNDS]; mark = cgs.media.bulletMarkShaderMetal; alphaFade = qtrue; } else if ( surfFlags & SURF_WOOD ) { sfx = cgs.media.sfx_bullet_woodhit[rand() % MAX_IMPACT_SOUNDS]; mark = cgs.media.bulletMarkShaderWood; alphaFade = qtrue; radius += 0.4f; // experimenting with different mark sizes per surface } else if ( surfFlags & SURF_GLASS ) { sfx = cgs.media.sfx_bullet_glasshit[rand() % MAX_IMPACT_SOUNDS]; mark = cgs.media.bulletMarkShaderGlass; alphaFade = qtrue; } else { sfx = cgs.media.sfx_bullet_stonehit[rand() % MAX_IMPACT_SOUNDS]; mark = cgs.media.bulletMarkShader; alphaFade = qtrue; } // ydnar: set mark duration markDuration = cg_markTime.integer; } } break; case WP_MAPMORTAR: sfx = cgs.media.sfx_rockexp; sfx2 = cgs.media.sfx_rockexpDist; sfx2range = 1200; mark = cgs.media.burnMarkShader; markDuration = cg_markTime.integer * 3; radius = 96; // ydnar: bigger mark radius light = 300; isSprite = qtrue; duration = 1000; lightColor[0] = 0.75; lightColor[1] = 0.5; lightColor[2] = 0.1; VectorScale( dir, 16, sprVel ); if ( CG_PointContents( origin, 0 ) & CONTENTS_WATER ) { VectorCopy( origin,tmpv ); tmpv[2] += 10000; trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 150, 1000 ); CG_AddDirtBulletParticles( trace.endpos, dir, 900, 1800, 15, 0.5, 350, 128, 0.125, cgs.media.dirtParticle2Shader ); } else { VectorCopy( origin,tmpv ); tmpv[2] += 20; VectorCopy( origin,tmpv2 ); tmpv2[2] -= 20; trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { CG_AddDirtBulletParticles( origin, dir, 600, 2000, 10, 0.5, 275,125, 0.25, cgs.media.dirtParticle1Shader ); } for ( i = 0; i < 5; i++ ) { for ( j = 0; j < 3; j++ ) { sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom(); } sprVel[2] += rand() % 50; CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 3500 + rand() % 250, 10, 250 + rand() % 60, qfalse ); } VectorMA( origin, 24, dir, sprOrg ); VectorScale( dir, 64, sprVel ); CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1000, 20, 300, qtrue ); } break; case WP_DYNAMITE: case WP_TRIPMINE: shader = cgs.media.rocketExplosionShader; sfx = cgs.media.sfx_dynamiteexp; sfx2 = cgs.media.sfx_dynamiteexpDist; sfx2range = 400; mark = cgs.media.burnMarkShader; markDuration = cg_markTime.integer * 3; radius = 128; // ydnar: bigger mark radius light = 300; isSprite = qtrue; duration = 1000; lightColor[0] = 0.75; lightColor[1] = 0.5; lightColor[2] = 0.1; // JPW NERVE // biggie dynamite explosions that mean it -- dynamite is biggest explode, so it gets extra crap thrown on // check for water/dirt spurt if ( CG_PointContents( origin, 0 ) & CONTENTS_WATER ) { VectorCopy( origin,tmpv ); tmpv[2] += 10000; trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 300, 2000 ); CG_AddDirtBulletParticles( trace.endpos, dir, 400 + random() * 200, 900, 15, 0.5, 512, 128, 0.125, cgs.media.dirtParticle2Shader ); CG_AddDirtBulletParticles( trace.endpos, dir, 400 + random() * 600, 1400, 15, 0.5, 128, 512, 0.125, cgs.media.dirtParticle2Shader ); } else { VectorSet( tmpv, origin[0], origin[1], origin[2] + 20 ); VectorSet( tmpv2, origin[0], origin[1], origin[2] - 20 ); trap_CM_BoxTrace( &trace, tmpv, tmpv2, NULL, NULL, 0, MASK_SHOT ); if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { CG_AddDirtBulletParticles( origin, dir, 400 + random() * 200, 3000, 10, 0.5, 400,256, 0.25, cgs.media.dirtParticle1Shader ); } for ( i = 0; i < 3; i++ ) { for ( j = 0; j < 3; j++ ) { sprOrg[j] = origin[j] + 150 * crandom(); sprVel[j] = 0.35 * crandom(); } VectorAdd( sprVel, trace.plane.normal, sprVel ); VectorScale( sprVel,130,sprVel ); CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 6000 + random() * 2000, 40, 400 + random() * 200, qfalse ); // JPW NERVE was blacksmokeanimb } for ( i = 0; i < 4; i++ ) { // JPW random vector based on plane normal so explosions move away from walls/dirt/etc for ( j = 0; j < 3; j++ ) { sprOrg[j] = origin[j] + 100 * crandom(); sprVel[j] = 0.65 * crandom(); // wider fireball spread } VectorAdd( sprVel, trace.plane.normal, sprVel ); VectorScale( sprVel,random() * 100 + 300,sprVel ); CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1000 + rand() % 1450, 40, 400 + random() * 200, ( i == 0 ? qtrue : qfalse ) ); } CG_AddDebris( origin, dir, 400 + random() * 200, rand() % 2000 + 1400, 12 + rand() % 12 ); } break; case WP_GRENADE_LAUNCHER: case WP_GRENADE_PINEAPPLE: case WP_GPG40: case WP_M7: case WP_SATCHEL: case WP_LANDMINE: case WP_MORTAR_SET: sfx2range = 1200; if ( weapon == WP_GPG40 || weapon == WP_M7 ) { sfx2range = 800; } if ( weapon == WP_SATCHEL ) { sfx = cgs.media.sfx_satchelexp; sfx2 = cgs.media.sfx_satchelexpDist; } else if ( weapon == WP_LANDMINE ) { sfx = cgs.media.sfx_landmineexp; sfx2 = cgs.media.sfx_landmineexpDist; } else if ( weapon == WP_MORTAR_SET ) { sfx = sfx2 = 0; } else if ( weapon == WP_GRENADE_LAUNCHER || weapon == WP_GRENADE_PINEAPPLE || weapon == WP_GPG40 || weapon == WP_M7 ) { sfx = cgs.media.sfx_grenexp; sfx2 = cgs.media.sfx_grenexpDist; } else { sfx = cgs.media.sfx_rockexp; sfx2 = cgs.media.sfx_rockexpDist; } shader = cgs.media.rocketExplosionShader; // copied from RL sfx2range = 400; mark = cgs.media.burnMarkShader; markDuration = cg_markTime.integer * 3; radius = 64; light = 300; isSprite = qtrue; duration = 1000; lightColor[0] = 0.75; lightColor[1] = 0.5; lightColor[2] = 0.1; // Ridah, explosion sprite animation VectorMA( origin, 16, dir, sprOrg ); VectorScale( dir, 100, sprVel ); if ( CG_PointContents( origin, 0 ) & CONTENTS_WATER ) { sfx = cgs.media.sfx_rockexpWater; VectorCopy( origin,tmpv ); tmpv[2] += 10000; trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 150, 1000 ); CG_AddDirtBulletParticles( trace.endpos, dir, 400, 900, 15, 0.5, 256,128, 0.125, cgs.media.dirtParticle2Shader ); } else { VectorCopy( origin,tmpv ); tmpv[2] += 20; VectorCopy( origin,tmpv2 ); tmpv2[2] -= 20; trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { CG_AddDirtBulletParticles( origin, dir, 400, 2000, 10, 0.5, 200,75, 0.25, cgs.media.dirtParticle1Shader ); } CG_ParticleExplosion( "explode1", sprOrg, sprVel, 700, 60, 240, qtrue ); CG_AddDebris( origin, dir, 280, 1400, 7 + rand() % 2 ); } break; case WP_PANZERFAUST: case VERYBIGEXPLOSION: case WP_ARTY: case WP_SMOKE_MARKER: sfx = cgs.media.sfx_rockexp; sfx2 = cgs.media.sfx_rockexpDist; if ( weapon == VERYBIGEXPLOSION || weapon == WP_ARTY ) { sfx = cgs.media.sfx_artilleryExp[rand() % 3]; sfx2 = cgs.media.sfx_artilleryDist; } else if ( weapon == WP_SMOKE_MARKER ) { sfx = cgs.media.sfx_airstrikeExp[rand() % 3]; sfx2 = cgs.media.sfx_airstrikeDist; } sfx2range = 800; mark = cgs.media.burnMarkShader; markDuration = cg_markTime.integer * 3; radius = 128; // ydnar: bigger mark radius light = 600; isSprite = qtrue; duration = 1000; // Ridah, changed to flamethrower colors lightColor[0] = 0.75; lightColor[1] = 0.5; lightColor[2] = 0.1; // explosion sprite animation VectorMA( origin, 24, dir, sprOrg ); VectorScale( dir, 64, sprVel ); if ( CG_PointContents( origin, 0 ) & CONTENTS_WATER ) { VectorCopy( origin,tmpv ); tmpv[2] += 10000; trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 300, 2000 ); CG_AddDirtBulletParticles( trace.endpos, dir, 400 + random() * 200, 900, 15, 0.5, 512, 128, 0.125, cgs.media.dirtParticle2Shader ); CG_AddDirtBulletParticles( trace.endpos, dir, 400 + random() * 600, 1400, 15, 0.5, 128, 512, 0.125, cgs.media.dirtParticle2Shader ); } else { VectorCopy( origin,tmpv ); tmpv[2] += 20; VectorCopy( origin,tmpv2 ); tmpv2[2] -= 20; trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { CG_AddDirtBulletParticles( origin, dir, 400 + random() * 200, 3000, 10, 0.5, 400,256, 0.25, cgs.media.dirtParticle1Shader ); } CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1600, 20, 200 + random() * 400, qtrue ); for ( i = 0; i < 4; i++ ) { // JPW random vector based on plane normal so explosions move away from walls/dirt/etc for ( j = 0; j < 3; j++ ) { sprOrg[j] = origin[j] + 50 * crandom(); sprVel[j] = 0.35 * crandom(); } VectorAdd( sprVel, trace.plane.normal, sprVel ); VectorScale( sprVel,300,sprVel ); CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1600, 40, 260 + rand() % 120, qfalse ); } CG_AddDebris( origin, dir, 400 + random() * 200, rand() % 2000 + 1000, 5 + rand() % 5 ); } break; default: case WP_FLAMETHROWER: return; break; } // done. if ( sfx ) { trap_S_StartSoundVControl( origin, -1, CHAN_AUTO, sfx, volume ); } if ( sfx2 ) { // distant sounds for weapons with a broadcast fire sound (so you /always/ hear dynamite explosions) vec3_t porg, gorg, norm; // player/gun origin float gdist; VectorCopy( origin, gorg ); VectorCopy( cg.refdef_current->vieworg, porg ); VectorSubtract( gorg, porg, norm ); gdist = VectorNormalize( norm ); if ( gdist > 1200 && gdist < 8000 ) { // 1200 is max cam shakey dist (2*600) use gorg as the new sound origin VectorMA( cg.refdef_current->vieworg, sfx2range, norm, gorg ); // JPW NERVE non-distance falloff makes more sense; sfx2range was gdist*0.2 // sfx2range is variable to give us minimum volume control different explosion sizes (see mortar, panzerfaust, and grenade) trap_S_StartSoundEx( gorg, -1, CHAN_WEAPON, sfx2, SND_NOCUT ); } } if ( mod ) { le = CG_MakeExplosion( origin, dir, mod, shader, duration, isSprite ); le->light = light; le->lightOverdraw = lightOverdraw; VectorCopy( lightColor, le->lightColor ); } // ydnar: omnidirectional explosion marks if ( mark == cgs.media.burnMarkShader ) { VectorSet( projection, 0, 0, -1 ); projection[ 3 ] = radius; Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f ); trap_R_ProjectDecal( mark, 1, (vec3_t*) origin, projection, color, markDuration, ( markDuration >> 4 ) ); } else if ( mark ) { VectorSubtract( vec3_origin, dir, projection ); projection[ 3 ] = radius * 32; VectorMA( origin, -16.0f, projection, markOrigin ); // jitter markorigin a bit so they don't end up on an ordered grid markOrigin[ 0 ] += ( random() - 0.5f ); markOrigin[ 1 ] += ( random() - 0.5f ); markOrigin[ 2 ] += ( random() - 0.5f ); CG_ImpactMark( mark, markOrigin, projection, radius, random() * 360.0f, 1.0f, 1.0f, 1.0f, 1.0f, markDuration ); } } /* ============== CG_MissileHitWallSmall ============== */ void CG_MissileHitWallSmall( int weapon, int clientNum, vec3_t origin, vec3_t dir ) { qhandle_t mod; qhandle_t mark; qhandle_t shader; sfxHandle_t sfx; float radius; float light; vec3_t lightColor; localEntity_t *le; qboolean isSprite; int duration; int lightOverdraw; vec3_t sprOrg, sprVel; vec4_t projection, color; mark = 0; radius = 32; sfx = 0; mod = 0; shader = 0; light = 0; lightColor[0] = 1; lightColor[1] = 1; lightColor[2] = 0; // Ridah lightOverdraw = 0; // set defaults isSprite = qfalse; duration = 600; shader = cgs.media.rocketExplosionShader; // copied from RL sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 80; light = 300; isSprite = qtrue; duration = 1000; lightColor[0] = 0.75; lightColor[1] = 0.5; lightColor[2] = 0.1; // Ridah, explosion sprite animation VectorMA( origin, 16, dir, sprOrg ); VectorScale( dir, 64, sprVel ); CG_ParticleExplosion( "explode1", sprOrg, sprVel, 600, 6, 50, qtrue ); // Ridah, throw some debris CG_AddDebris( origin, dir, 280, // speed 1400, // duration // 15 + rand()%5 ); // count 7 + rand() % 2 ); // count if ( sfx ) { trap_S_StartSound( origin, -1, CHAN_AUTO, sfx ); } // // create the explosion // if ( mod ) { le = CG_MakeExplosion( origin, dir, mod, shader, duration, isSprite ); le->light = light; // Ridah le->lightOverdraw = lightOverdraw; VectorCopy( lightColor, le->lightColor ); } // // impact mark // // ydnar: testing omnidirectional marks #if 0 VectorSubtract( vec3_origin, dir, projection ); projection[ 3 ] = radius * 3; VectorMA( origin, -4.0f, projection, markOrigin ); CG_ImpactMark( mark, markOrigin, projection, radius, random() * 360.0f, 1.0f, 1.0f, 1.0f, 1.0f, cg_markTime.integer ); #else VectorSet( projection, 0, 0, -1 ); projection[ 3 ] = radius; Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f ); trap_R_ProjectDecal( mark, 1, (vec3_t*) origin, projection, color, cg_markTime.integer, ( cg_markTime.integer >> 4 ) ); #endif } /* ================= CG_MissileHitPlayer ================= */ void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir, int entityNum ) { CG_Bleed( origin, entityNum ); // some weapons will make an explosion with the blood, while // others will just make the blood switch ( weapon ) { case WP_GRENADE_LAUNCHER: case WP_PANZERFAUST: CG_MissileHitWall( weapon, 0, origin, dir, 0 ); // JPW NERVE like the old one break; case WP_KNIFE: CG_MissileHitWall( weapon, 0, origin, dir, 1 ); // JPW NERVE this one makes the hitting fleshy sound. whee break; default: break; } } /* ============================================================================ VENOM GUN TRACING ============================================================================ */ //----(SA) all changes to venom below should be mine #define DEFAULT_VENOM_COUNT 10 //#define DEFAULT_VENOM_SPREAD 20 //#define DEFAULT_VENOM_SPREAD 400 #define DEFAULT_VENOM_SPREAD 700 /* ============================================================================ BULLETS ============================================================================ */ /* =============== CG_SpawnTracer =============== */ void CG_SpawnTracer( int sourceEnt, vec3_t pstart, vec3_t pend ) { localEntity_t *le; float dist; vec3_t dir, ofs; orientation_t or; vec3_t start, end; VectorCopy( pstart, start ); VectorCopy( pend, end ); // DHM - make MG42 tracers line up if ( cg_entities[sourceEnt].currentState.eFlags & EF_MG42_ACTIVE ) { start[2] -= 42; } VectorSubtract( end, start, dir ); dist = VectorNormalize( dir ); if ( dist < 2.0 * cg_tracerLength.value ) { return; // segment isnt long enough, dont bother } if ( sourceEnt < cgs.maxclients ) { // for visual purposes, find the actual tag_weapon for this client // and offset the start and end accordingly if ( !( cg_entities[sourceEnt].currentState.eFlags & EF_MG42_ACTIVE || cg_entities[sourceEnt].currentState.eFlags & EF_AAGUN_ACTIVE ) ) { // not MG42 if ( CG_GetWeaponTag( sourceEnt, "tag_flash", &or ) ) { VectorSubtract( or.origin, start, ofs ); if ( VectorLength( ofs ) < 64 ) { VectorAdd( start, ofs, start ); } } } } // subtract the length of the tracer from the end point, so we dont go through the end point VectorMA( end, -cg_tracerLength.value, dir, end ); dist = VectorDistance( start, end ); le = CG_AllocLocalEntity(); le->leType = LE_MOVING_TRACER; le->startTime = cg.time - ( cg.frametime ? ( rand() % cg.frametime ) / 2 : 0 ); le->endTime = le->startTime + 1000.0 * dist / cg_tracerSpeed.value; le->pos.trType = TR_LINEAR; le->pos.trTime = le->startTime; VectorCopy( start, le->pos.trBase ); VectorScale( dir, cg_tracerSpeed.value, le->pos.trDelta ); } /* =============== CG_DrawTracer =============== */ void CG_DrawTracer( vec3_t start, vec3_t finish ) { vec3_t forward, right; polyVert_t verts[4]; vec3_t line; VectorSubtract( finish, start, forward ); line[0] = DotProduct( forward, cg.refdef_current->viewaxis[1] ); line[1] = DotProduct( forward, cg.refdef_current->viewaxis[2] ); VectorScale( cg.refdef_current->viewaxis[1], line[1], right ); VectorMA( right, -line[0], cg.refdef_current->viewaxis[2], right ); VectorNormalize( right ); VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); verts[0].st[0] = 1; 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] = 0; verts[2].st[1] = 0; 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] = 1; 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 ); } /* =============== CG_Tracer =============== */ void CG_Tracer( vec3_t source, vec3_t dest, int sparks ) { float len, begin, end; vec3_t start, finish; vec3_t midpoint; vec3_t forward; // tracer VectorSubtract( dest, source, forward ); len = VectorNormalize( forward ); // start at least a little ways from the muzzle if ( len < 100 && !sparks ) { 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 ); CG_DrawTracer( start, finish ); midpoint[0] = ( start[0] + finish[0] ) * 0.5; midpoint[1] = ( start[1] + finish[1] ) * 0.5; midpoint[2] = ( start[2] + finish[2] ) * 0.5; } /* ====================== CG_CalcMuzzlePoint ====================== */ qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { vec3_t forward, right, up; centity_t *cent; // int anim; if ( entityNum == cg.snap->ps.clientNum ) { // Arnout: see if we're attached to a gun if ( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) { centity_t *mg42 = &cg_entities[cg.snap->ps.viewlocked_entNum]; vec3_t forward; AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); VectorMA( mg42->currentState.pos.trBase, 40, forward, muzzle ); // wsa -36, made 40 to be in sync with the actual muzzleflash drawing muzzle[2] += cg.snap->ps.viewheight; } else if ( cg.snap->ps.eFlags & EF_AAGUN_ACTIVE ) { centity_t *aagun = &cg_entities[cg.snap->ps.viewlocked_entNum]; vec3_t forward, right, up; AngleVectors( cg.snap->ps.viewangles, forward, right, up ); VectorCopy( aagun->lerpOrigin, muzzle ); // Gordon: modelindex2 will already have been incremented on the server, so work out what it WAS then BG_AdjustAAGunMuzzleForBarrel( muzzle, forward, right, up, ( aagun->currentState.modelindex2 + 3 ) % 4 ); } else if ( cg.snap->ps.eFlags & EF_MOUNTEDTANK ) { if ( cg.renderingThirdPerson ) { centity_t* tank = &cg_entities[cg_entities[cg.snap->ps.clientNum].tagParent]; VectorCopy( tank->mountedMG42Flash.origin, muzzle ); AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 14, forward, muzzle ); } else { //bani - fix firstperson tank muzzle origin if drawgun is off if ( !cg_drawGun.integer ) { VectorCopy( cg.snap->ps.origin, muzzle ); AngleVectors( cg.snap->ps.viewangles, forward, right, up ); VectorMA( muzzle, 48, forward, muzzle ); muzzle[2] += cg.snap->ps.viewheight; VectorMA( muzzle, 8, right, muzzle ); } else { VectorCopy( cg.tankflashorg, muzzle ); } } } else { VectorCopy( cg.snap->ps.origin, muzzle ); muzzle[2] += cg.snap->ps.viewheight; AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); if ( cg.snap->ps.weapon == WP_MOBILE_MG42_SET ) { VectorMA( muzzle, 36, forward, muzzle ); } else { VectorMA( muzzle, 14, forward, muzzle ); } } return qtrue; } cent = &cg_entities[entityNum]; //----(SA) removed check. is this still necessary? (this way works for ai's firing mg42) should I check for mg42? // if ( !cent->currentValid ) { // return qfalse; // } //----(SA) end if ( cent->currentState.eFlags & EF_MG42_ACTIVE ) { // centity_t *mg42; // int num; vec3_t forward; // ydnar: this is silly--the entity is a mg42 barrel, so just use itself #if 0 // find the mg42 we're attached to for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { mg42 = &cg_entities[ cg.snap->entities[ num ].number ]; if ( mg42->currentState.eType == ET_MG42_BARREL ) { if ( mg42->currentState.number == cent->currentState.number ) { // found it VectorCopy( mg42->currentState.pos.trBase, muzzle ); AngleVectors( cent->lerpAngles, forward, NULL, NULL ); //VectorMA( muzzle, -36, forward, muzzle ); VectorMA( muzzle, 40, forward, muzzle ); muzzle[2] += DEFAULT_VIEWHEIGHT; break; } } } #else if ( cent->currentState.eType == ET_MG42_BARREL ) { VectorCopy( cent->currentState.pos.trBase, muzzle ); AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorMA( muzzle, 40, forward, muzzle ); muzzle[ 2 ] += DEFAULT_VIEWHEIGHT; } #endif } else if ( cent->currentState.eFlags & EF_MOUNTEDTANK ) { centity_t* tank = &cg_entities[cent->tagParent]; VectorCopy( tank->mountedMG42Flash.origin, muzzle ); } else if ( cent->currentState.eFlags & EF_AAGUN_ACTIVE ) { centity_t *aagun; int num; // find the mg42 we're attached to for ( num = 0; num < cg.snap->numEntities; num++ ) { aagun = &cg_entities[ cg.snap->entities[ num ].number ]; if ( aagun->currentState.eType == ET_AAGUN && aagun->currentState.otherEntityNum == cent->currentState.number ) { // found it vec3_t forward, right, up; AngleVectors( cg.snap->ps.viewangles, forward, right, up ); VectorCopy( aagun->lerpOrigin, muzzle ); // Gordon: modelindex2 will already have been incremented on the server, so work out what it WAS then BG_AdjustAAGunMuzzleForBarrel( muzzle, forward, right, up, ( aagun->currentState.modelindex2 + 3 ) % 4 ); } } } else { VectorCopy( cent->currentState.pos.trBase, muzzle ); AngleVectors( cent->currentState.apos.trBase, forward, right, up ); if ( cent->currentState.eFlags & EF_PRONE ) { muzzle[2] += PRONE_VIEWHEIGHT; if ( cent->currentState.weapon == WP_MOBILE_MG42_SET ) { VectorMA( muzzle, 36, forward, muzzle ); } else { VectorMA( muzzle, 14, forward, muzzle ); } } else { muzzle[2] += DEFAULT_VIEWHEIGHT; VectorMA( muzzle, 14, forward, muzzle ); } } return qtrue; } void SnapVectorTowards( vec3_t v, vec3_t to ) { int i; for ( i = 0 ; i < 3 ; i++ ) { if ( to[i] <= v[i] ) { // v[i] = (int)v[i]; v[i] = floor( v[i] ); } else { // v[i] = (int)v[i] + 1; v[i] = ceil( v[i] ); } } } /* ====================== CG_Bullet Renders bullet effects. ====================== */ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, int otherEntNum2, float waterfraction, int seed ) { trace_t trace,trace2; int sourceContentType, destContentType; vec3_t dir; vec3_t start, trend; // JPW vec4_t projection; static int lastBloodSpat; centity_t *cent; cent = &cg_entities[fleshEntityNum]; // JPW NERVE -- don't ever shoot if we're binoced in if ( cg_entities[sourceEntityNum].currentState.eFlags & EF_ZOOMING ) { return; } // Arnout: snap tracers for MG42 to viewangle of client when antilag is enabled if ( cgs.antilag && otherEntNum2 == cg.snap->ps.clientNum && cg_entities[otherEntNum2].currentState.eFlags & EF_MG42_ACTIVE ) { vec3_t muzzle, forward, right, up; float r, u; trace_t tr; AngleVectors( cg.predictedPlayerState.viewangles, forward, right, up ); VectorCopy( cg_entities[cg.snap->ps.viewlocked_entNum].currentState.pos.trBase, muzzle ); if ( cg_entities[cg.snap->ps.viewlocked_entNum].currentState.onFireStart ) { VectorMA( muzzle, 16, up, muzzle ); } r = Q_crandom( &seed ) * MG42_SPREAD_MP; u = Q_crandom( &seed ) * MG42_SPREAD_MP; VectorMA( muzzle, 8192, forward, end ); VectorMA( end, r, right, end ); VectorMA( end, u, up, end ); CG_Trace( &tr, muzzle, NULL, NULL, end, otherEntNum2, MASK_SHOT ); SnapVectorTowards( tr.endpos, muzzle ); VectorCopy( tr.endpos, end ); } // 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 = CG_PointContents( start, 0 ); destContentType = CG_PointContents( end, 0 ); // do a complete bubble trail if necessary if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { CG_BubbleTrail( start, end, .5, 8 ); } else if ( ( sourceContentType & CONTENTS_WATER ) ) { // bubble trail from water into air trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( start, trace.endpos, .5, 8 ); } else if ( ( destContentType & CONTENTS_WATER ) ) { // bubble trail from air into water // only add bubbles if effect is close to viewer if ( Distance( cg.snap->ps.origin, end ) < 1024 ) { trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( end, trace.endpos, .5, 8 ); } } // if not flesh, then do a moving tracer if ( flesh ) { // draw a tracer if ( random() < cg_tracerChance.value ) { CG_Tracer( start, end, 0 ); } } else { // (not flesh) if ( otherEntNum2 >= 0 && otherEntNum2 != ENTITYNUM_NONE ) { CG_SpawnTracer( otherEntNum2, start, end ); } else { CG_SpawnTracer( sourceEntityNum, start, end ); } } } } // impact splash and mark if ( flesh ) { vec3_t origin; localEntity_t *le; // JPW NERVE float rnd, tmpf; // JPW NERVE vec3_t smokedir, tmpv, tmpv2; // JPW NERVE int i,headshot; // JPW NERVE if ( fleshEntityNum < MAX_CLIENTS ) { CG_Bleed( end, fleshEntityNum ); } // JPW NERVE smoke puffs (sometimes with some blood) VectorSubtract( end,start,smokedir ); // get a nice "through the body" vector VectorNormalize( smokedir ); // all this to come up with a decent center-body displacement of bullet impact point VectorSubtract( cent->currentState.pos.trBase,end,tmpv ); tmpv[2] = 0; tmpf = VectorLength( tmpv ); VectorScale( smokedir,tmpf,tmpv ); VectorAdd( end,tmpv,origin ); // whee, got a bullet impact point projected to center body CG_GetOriginForTag( cent,¢->pe.headRefEnt, "tag_mouth", 0, tmpv, NULL ); tmpv[2] += 5; VectorSubtract( tmpv, origin, tmpv2 ); headshot = ( VectorLength( tmpv2 ) < 10 ); if ( headshot && cg_blood.integer ) { for ( i = 0; i < 5; i++ ) { rnd = random(); VectorScale( smokedir,25.0 + random() * 25,tmpv ); tmpv[0] += crandom() * 25.0f; tmpv[1] += crandom() * 25.0f; tmpv[2] += crandom() * 25.0f; CG_GetWindVector( tmpv2 ); VectorScale( tmpv2,35,tmpv2 ); // was 75, before that 55 tmpv2[2] = 0; VectorAdd( tmpv,tmpv2,tmpv ); le = CG_SmokePuff( origin, tmpv, 5 + rnd * 10, 1, rnd * 0.8, rnd * 0.8, 0.5, 500 + ( rand() % 800 ), cg.time, 0, 0, cgs.media.fleshSmokePuffShader ); } } else { // puff out the front (more dust no blood) for ( i = 0; i < 10; i++ ) { rnd = random(); VectorScale( smokedir,-35.0 + random() * 25,tmpv ); tmpv[0] += crandom() * 25.0f; tmpv[1] += crandom() * 25.0f; tmpv[2] += crandom() * 25.0f; CG_GetWindVector( tmpv2 ); VectorScale( tmpv2,35,tmpv2 ); // was 75, before that 55 tmpv2[2] = 0; VectorAdd( tmpv,tmpv2,tmpv ); le = CG_SmokePuff( origin, tmpv, 5 + rnd * 10, rnd * 0.3f + 0.5f, rnd * 0.3f + 0.5f, rnd * 0.3f + 0.5f, 0.125f, 500 + ( rand() % 300 ), cg.time, 0, 0, cgs.media.smokePuffShader ); } } // jpw // play the bullet hit flesh sound // HACK, if this is not us getting hit, make it quieter // JPW NERVE pulled hack, we like loud impact sounds for MP if ( fleshEntityNum == cg.snap->ps.clientNum ) { //CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshScript, NULL, fleshEntityNum ); trap_S_StartSound( NULL, fleshEntityNum, CHAN_BODY, cgs.media.sfx_bullet_stonehit[rand() % MAX_IMPACT_SOUNDS] ); } else { //CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshScript, cg_entities[fleshEntityNum].currentState.origin, ENTITYNUM_WORLD ); // JPW NERVE changed from ,origin, to this trap_S_StartSound( cg_entities[fleshEntityNum].currentState.origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.sfx_bullet_stonehit[rand() % MAX_IMPACT_SOUNDS] ); } // if we haven't dropped a blood spat in a while, check if this is a good scenario if ( cg_blood.integer && ( lastBloodSpat > cg.time || lastBloodSpat < cg.time - 500 ) ) { vec4_t color; if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { VectorSubtract( end, start, dir ); VectorNormalize( dir ); VectorMA( end, 128, dir, trend ); trap_CM_BoxTrace( &trace, end, trend, NULL, NULL, 0, MASK_SHOT & ~CONTENTS_BODY ); if ( trace.fraction < 1 ) { //% CG_ImpactMark( cgs.media.bloodDotShaders[rand()%5], trace.endpos, trace.plane.normal, random()*360, //% 1,1,1,1, qtrue, 15+random()*20, qfalse, cg_bloodTime.integer * 1000 ); #if 0 VectorSubtract( vec3_origin, dir, projection ); projection[ 3 ] = 64; VectorMA( trace.endpos, -8.0f, projection, markOrigin ); CG_ImpactMark( cgs.media.bloodDotShaders[ rand() % 5 ], markOrigin, projection, 15.0f + random() * 20.0f, 360.0f * random(), 1.0f, 1.0f, 1.0f, 1.0f, cg_bloodTime.integer * 1000 ); #else VectorSet( projection, 0, 0, -1 ); projection[ 3 ] = 15.0f + random() * 20.0f; Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f ); trap_R_ProjectDecal( cgs.media.bloodDotShaders[ rand() % 5 ], 1, (vec3_t*) origin, projection, color, cg_bloodTime.integer * 1000, ( cg_bloodTime.integer * 1000 ) >> 4 ); #endif lastBloodSpat = cg.time; } else if ( lastBloodSpat < cg.time - 1000 ) { // drop one on the ground? VectorCopy( end, trend ); trend[ 2 ] -= 64; trap_CM_BoxTrace( &trace, end, trend, NULL, NULL, 0, MASK_SHOT & ~CONTENTS_BODY ); if ( trace.fraction < 1 ) { //% CG_ImpactMark( cgs.media.bloodDotShaders[rand()%5], trace.endpos, trace.plane.normal, random()*360, //% 1,1,1,1, qtrue, 15+random()*10, qfalse, cg_bloodTime.integer * 1000 ); #if 0 VectorSubtract( vec3_origin, dir, projection ); projection[ 3 ] = 64; VectorMA( trace.endpos, -8.0f, projection, markOrigin ); CG_ImpactMark( cgs.media.bloodDotShaders[ rand() % 5 ], markOrigin, projection, 15.0f + random() * 10.0f, 360.0f * random(), 1.0f, 1.0f, 1.0f, 1.0f, cg_bloodTime.integer * 1000 ); #else VectorSet( projection, 0, 0, -1 ); projection[ 3 ] = 15.0f + random() * 20.0f; Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f ); trap_R_ProjectDecal( cgs.media.bloodDotShaders[ rand() % 5 ], 1, (vec3_t*) origin, projection, color, cg_bloodTime.integer * 1000, ( cg_bloodTime.integer * 1000 ) >> 4 ); #endif lastBloodSpat = cg.time; } } } } } else { // (not flesh) // Gordon: all bullet weapons have the same fx, and this stops pvs issues causing grenade explosions int fromweap = WP_MP40; // cg_entities[sourceEntityNum].currentState.weapon; if ( !fromweap || cg_entities[sourceEntityNum].currentState.eFlags & EF_MG42_ACTIVE || cg_entities[sourceEntityNum].currentState.eFlags & EF_MOUNTEDTANK ) { // mounted fromweap = WP_MP40; } if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) || cg.snap->ps.persistant[PERS_HWEAPON_USE] ) { if ( waterfraction ) { vec3_t dist; vec3_t end2; vec3_t dir = {0, 0, 1}; VectorSubtract( end, start, dist ); VectorMA( start, waterfraction, dist, end2 ); trap_S_StartSound( end, -1, CHAN_AUTO, cgs.media.sfx_bullet_waterhit[rand() % 5] ); CG_MissileHitWall( fromweap, 2, end2, dir, 0 ); //CG_MissileHitWall( fromweap, 1, end, normal, trace.surfaceFlags); CG_MissileHitWall( fromweap, 1, end, trace.plane.normal, 0 ); } else { // Gordon: um.... WTF? this doenst even make sense... /* vec3_t start2; VectorSubtract( end, start, dir ); VectorNormalize( dir );*/ /* VectorMA( end, -4, dir, start2 ); // back off a little so it doesn't start in solid VectorMA( end, 64, dir, dir );*/ // Arnout: but this does! VectorSubtract( end, start, dir ); VectorNormalizeFast( dir ); VectorMA( end, 4, dir, end ); //CG_RailTrail2( NULL, start, end ); CG_Trace( &trace, start, NULL, NULL, end, 0, MASK_SHOT ); // JPW NERVE -- water check CG_Trace( &trace2, start, NULL, NULL, end, 0, MASK_WATER | MASK_SHOT ); if ( trace.fraction != trace2.fraction ) { //trap_CM_BoxTrace( &trace2, start, end, NULL, NULL, -1, MASK_WATER ); trap_S_StartSound( end, -1, CHAN_AUTO, cgs.media.sfx_bullet_waterhit[rand() % 5] ); CG_Trace( &trace2, start, NULL, NULL, end, -1, MASK_WATER ); CG_MissileHitWall( fromweap, 2, trace2.endpos, trace2.plane.normal, trace2.surfaceFlags ); return; } //CG_MissileHitWall( fromweap, 1, end, normal, trace.surfaceFlags); // smoke puff // (SA) modified to send missilehitwall surface parameters //% CG_MissileHitWall( fromweap, 1, trace.endpos, trace.plane.normal, trace.surfaceFlags); // smoke puff // (SA) modified to send missilehitwall surface parameters // ydnar: better bullet marks VectorSubtract( vec3_origin, dir, dir ); CG_MissileHitWall( fromweap, 1, trace.endpos, dir, trace.surfaceFlags ); //CG_RailTrail2( NULL, start, trace.endpos ); } } } }