// Copyright (C) 1999-2000 Id Software, Inc. // // cg_weapons.c -- events and effects dealing with weapons #include "cg_local.h" #include "fx_local.h" /* ================= CG_RegisterWeapon The server says this item is used on this level ================= */ // kef -- sad? yep. typedef struct wpnBarrelInfo_s { weapon_t giTag; int numBarrels; } wpnBarrelInfo_t; wpnBarrelInfo_t wpnBarrelData[] = { {WP_PHASER, 0}, {WP_COMPRESSION_RIFLE, 0}, {WP_IMOD, 0}, {WP_SCAVENGER_RIFLE, 0}, {WP_STASIS, 1}, {WP_GRENADE_LAUNCHER, 2}, {WP_TETRION_DISRUPTOR, 1}, {WP_QUANTUM_BURST, 1}, {WP_DREADNOUGHT, 4}, // make sure this is the last entry in this array, please {WP_NONE, 0}, }; void CG_RegisterWeapon( int weaponNum ) { weaponInfo_t *weaponInfo; gitem_t *item, *ammo; char path[MAX_QPATH]; vec3_t mins, maxs; int i; int numBarrels = 0; wpnBarrelInfo_t *barrelInfo = NULL; weaponInfo = &cg_weapons[weaponNum]; if ( weaponNum == 0 ) { return; } if ( weaponInfo->registered ) { return; } memset( weaponInfo, 0, sizeof( *weaponInfo ) ); weaponInfo->registered = qtrue; for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { weaponInfo->item = item; break; } } if ( !item->classname ) { CG_Error( "Couldn't find weapon %i", weaponNum ); } CG_RegisterItemVisuals( item - bg_itemlist ); weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model ); // kef -- load in-view model weaponInfo->viewModel = trap_R_RegisterModel(item->view_model); // calc midpoint for rotation trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); for ( i = 0 ; i < 3 ; i++ ) { weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); } weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { break; } } // if ( ammo->classname && ammo->world_model ) { // weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model ); // } strcpy( path, item->view_model ); COM_StripExtension( path, path ); strcat( path, "_flash.md3" ); weaponInfo->flashModel = trap_R_RegisterModel( path ); for (barrelInfo = wpnBarrelData; barrelInfo->giTag != WP_NONE; barrelInfo++) { if (barrelInfo->giTag == weaponNum) { numBarrels = barrelInfo->numBarrels; break; } } for (i=0; i< numBarrels; i++) { Q_strncpyz( path, item->view_model, MAX_QPATH ); COM_StripExtension( path, path ); if (i) { strcat( path, va("_barrel%d.md3", i+1)); } else strcat( path, "_barrel.md3" ); weaponInfo->barrelModel[i] = trap_R_RegisterModel( path ); } strcpy( path, item->view_model ); COM_StripExtension( path, path ); strcat( path, "_hand.md3" ); weaponInfo->handsModel = trap_R_RegisterModel( path ); if ( !weaponInfo->handsModel ) { weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_hand.md3" ); } switch ( weaponNum ) { case WP_PHASER: MAKERGB( weaponInfo->flashDlightColor, 0, 0, 0 ); weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "phaser/phaserfiring.wav" ); weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "phaser/altphaserfiring.wav" ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "phaser/phaserstart.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "phaser/altphaserstart.wav" ); weaponInfo->stopSound = trap_S_RegisterSound(SOUND_DIR "phaser/phaserstop.wav"); weaponInfo->altStopSound = trap_S_RegisterSound(SOUND_DIR "phaser/altphaserstop.wav"); cgs.media.phaserShader = trap_R_RegisterShader( "gfx/misc/phaser" ); cgs.media.phaserEmptyShader = trap_R_RegisterShader( "gfx/misc/phaserempty" ); cgs.media.phaserAltShader = trap_R_RegisterShader("gfx/effects/whitelaser"); // "gfx/misc/phaser_alt" ); cgs.media.phaserAltEmptyShader = trap_R_RegisterShader( "gfx/misc/phaser_altempty" ); cgs.media.phaserMuzzleEmptyShader= trap_R_RegisterShader( "models/weapons2/phaser/muzzle_empty" ); break; case WP_DREADNOUGHT: MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); MAKERGB( weaponInfo->missileDlightColor, 0.6, 0.6, 1 ); weaponInfo->alt_missileDlight = 200; weaponInfo->alt_missileTrailFunc = FX_DreadnoughtProjectileThink;; weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "dreadnought/dn_firing.wav" ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "dreadnought/dn_start.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "dreadnought/dn_altfire.wav" ); weaponInfo->stopSound = trap_S_RegisterSound(SOUND_DIR "dreadnought/dn_stop.wav"); weaponInfo->alt_missileSound = trap_S_RegisterSound(SOUND_DIR "dreadnought/dn_altmissile.wav"); weaponInfo->altHitSound = trap_S_RegisterSound(SOUND_DIR "dreadnought/dn_althit.wav"); cgs.media.dnBoltShader = trap_R_RegisterShader( "gfx/misc/dnBolt" ); cgs.media.stasisRingShader = trap_R_RegisterShader( "gfx/misc/stasis_ring" ); break; case WP_STASIS: weaponInfo->missileTrailFunc = FX_StasisProjectileThink; weaponInfo->missileDlight = 200; MAKERGB( weaponInfo->missileDlightColor, 0.6, 0.6, 1 ); MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "stasis/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "stasis/alt_fire.wav" ); weaponInfo->mainHitSound = trap_S_RegisterSound(SOUND_DIR "stasis/hit_wall.wav"); cgs.media.stasisRingShader = trap_R_RegisterShader( "gfx/misc/stasis_ring" ); cgs.media.stasisAltShader = trap_R_RegisterShader( "gfx/misc/stasis_altfire" ); cgs.media.altIMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2alt" ); cgs.media.dnBoltShader = trap_R_RegisterShader( "gfx/misc/dnBolt" ); break; case WP_GRENADE_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile.md3" ); weaponInfo->alt_missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile2.md3" ); weaponInfo->missileTrailFunc = FX_GrenadeThink; MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "glauncher/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "glauncher/alt_fire.wav" ); weaponInfo->altHitSound = trap_S_RegisterSound( SOUND_DIR "glauncher/beep.wav" ); cgs.media.grenadeAltStickSound = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_stick.wav"); cgs.media.grenadeBounceSound1 = trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav"); cgs.media.grenadeBounceSound2 = trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav"); cgs.media.grenadeExplodeSound = trap_S_RegisterSound(SOUND_DIR "glauncher/explode.wav"); cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" ); cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); break; case WP_SCAVENGER_RIFLE: MAKERGB( weaponInfo->flashDlightColor, 1, 0.6, 0.6 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "scavenger/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "scavenger/alt_fire.wav" ); weaponInfo->mainHitSound = trap_S_RegisterSound(SOUND_DIR "scavenger/hit_wall.wav"); weaponInfo->altHitSound = trap_S_RegisterSound(SOUND_DIR "scavenger/alt_explode.wav"); weaponInfo->missileTrailFunc = FX_ScavengerProjectileThink; weaponInfo->alt_missileTrailFunc = FX_ScavengerAltFireThink; // weaponInfo->wiTrailTime = 100; // weaponInfo->trailRadius = 8; cgs.media.tetrionFlareShader = trap_R_RegisterShader( "gfx/misc/tet1" ); cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" ); cgs.media.redFlareShader = trap_R_RegisterShader( "gfx/misc/red_flare" ); cgs.media.scavengerAltShader = trap_R_RegisterShader( "gfx/misc/scavaltfire" ); cgs.media.scavExplosionFastShader = trap_R_RegisterShader( "scavExplosionFast" ); cgs.media.scavExplosionSlowShader = trap_R_RegisterShader( "scavExplosionSlow" ); cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); break; case WP_QUANTUM_BURST: MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); //Bluish weaponInfo->missileTrailFunc = FX_QuantumThink; weaponInfo->alt_missileTrailFunc = FX_QuantumAltThink; weaponInfo->missileDlight = 75; weaponInfo->alt_missileDlight = 100; MAKERGB( weaponInfo->missileDlightColor, 1.0, 1.0, 0.5); //yellowish weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "quantum/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "quantum/alt_fire.wav" ); weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "quantum/hit_wall.wav" );; weaponInfo->altHitSound = trap_S_RegisterSound( SOUND_DIR "quantum/alt_hit_wall.wav" );; cgs.media.whiteRingShader = trap_R_RegisterShader( "gfx/misc/whitering" ); cgs.media.orangeRingShader = trap_R_RegisterShader( "gfx/misc/orangering" ); cgs.media.quantumExplosionShader = trap_R_RegisterShader( "quantumExplosion" ); cgs.media.quantumFlashShader = trap_R_RegisterShader( "yellowflash" ); cgs.media.bigBoomShader = trap_R_RegisterShader( "gfx/misc/bigboom" ); cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" ); cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" ); cgs.media.quantumRingShader = trap_R_RegisterShader( "gfx/misc/detpack3" ); cgs.media.quantumBoom = trap_S_RegisterSound ( SOUND_DIR "explosions/explode5.wav" ); break; case WP_IMOD: MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "IMOD/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "IMOD/alt_fire.wav" ); cgs.media.IMODShader = trap_R_RegisterShader( "gfx/misc/IMOD" ); cgs.media.IMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2" ); cgs.media.altIMODShader = trap_R_RegisterShader( "gfx/misc/IMODalt" ); cgs.media.altIMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2alt" ); cgs.media.imodExplosionShader = trap_R_RegisterShader( "imodExplosion" ); break; case WP_COMPRESSION_RIFLE: MAKERGB( weaponInfo->flashDlightColor, 0.16, 0.16, 1 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "prifle/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "prifle/alt_fire_new.wav" ); weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "prifle/impact.wav" );; cgs.media.prifleImpactShader = trap_R_RegisterShader( "gfx/effects/prifle_hit" ); cgs.media.compressionAltBeamShader = trap_R_RegisterShader( "gfx/effects/prifle_altbeam" ); cgs.media.compressionAltBlastShader = trap_R_RegisterShader( "gfx/effects/prifle_altblast" ); cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); break; case WP_TETRION_DISRUPTOR: MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "tetrion/fire.wav" ); weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "tetrion/alt_fire.wav" ); cgs.media.tetrionRicochetSound1 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet1.wav"); cgs.media.tetrionRicochetSound2 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet2.wav"); cgs.media.tetrionRicochetSound3 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet3.wav"); weaponInfo->missileTrailFunc = FX_TetrionProjectileThink; weaponInfo->alt_missileTrailFunc = FX_TetrionProjectileThink; cgs.media.greenBurstShader = trap_R_RegisterShader( "gfx/misc/greenburst" ); cgs.media.greenTrailShader = trap_R_RegisterShader( "gfx/misc/greentrail" ); cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" ); cgs.media.tetrionFlareShader = trap_R_RegisterShader( "gfx/misc/tet1" ); cgs.media.borgFlareShader = trap_R_RegisterShader( "gfx/misc/borgflare" ); cgs.media.bulletmarksShader = trap_R_RegisterShader( "textures/decals/bulletmark4" ); break; default: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "prifle/fire.wav" ); break; } } /* ================= CG_RegisterItemVisuals The server says this item is used on this level ================= */ void CG_RegisterItemVisuals( int itemNum ) { itemInfo_t *itemInfo; gitem_t *item; itemInfo = &cg_items[ itemNum ]; if ( itemInfo->registered ) { return; } item = &bg_itemlist[ itemNum ]; memset( itemInfo, 0, sizeof( &itemInfo ) ); itemInfo->registered = qtrue; itemInfo->model = trap_R_RegisterModel( item->world_model ); itemInfo->icon = trap_R_RegisterShader( item->icon ); if ( item->giType == IT_WEAPON ) { CG_RegisterWeapon( item->giTag ); } // since the seeker uses the scavenger rifes sounds, we must precache the scavenger rifle stuff if we hit the item seeker if ( item->giTag == PW_SEEKER) { CG_RegisterWeapon( WP_SCAVENGER_RIFLE ); } // hang onto the handles for holdable items in case they're useable (e.g. detpack) /* if (IT_HOLDABLE == item->giType) { // sanity check if ( (item->giTag < HI_NUM_HOLDABLE) && (item->giTag > 0) ) // use "> 0" cuz first slot should be empty { if (item->world_model[1]) { cgs.useableModels[item->giTag] = trap_R_RegisterModel( item->useablemodel ); } else { cgs.useableModels[item->giTag] = itemInfo->model]; } } } */ } /* ======================================================================================== VIEW WEAPON ======================================================================================== */ /* ================= CG_MapTorsoToWeaponFrame ================= */ static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { // change weapon if ( frame >= ci->animations[TORSO_DROP].firstFrame && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { return frame - ci->animations[TORSO_DROP].firstFrame + 6; } // stand attack if ( frame >= ci->animations[TORSO_ATTACK].firstFrame && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; } // stand attack 2 if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; } return 0; } /* ============== CG_CalculateWeaponPosition ============== */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { float scale; int delta; float fracsin; VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); // on odd legs, invert some angles if ( cg.bobcycle & 1 ) { scale = -cg.xyspeed; } else { scale = cg.xyspeed; } // gun angles from bobbing angles[ROLL] += scale * cg.bobfracsin * 0.005; angles[YAW] += scale * cg.bobfracsin * 0.01; angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; // drop the weapon when landing delta = cg.time - cg.landTime; if ( delta < LAND_DEFLECT_TIME ) { origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { origin[2] += cg.landChange*0.25 * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; } #if 0 // drop the weapon when stair climbing delta = cg.time - cg.stepTime; if ( delta < STEP_TIME/2 ) { origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); } else if ( delta < STEP_TIME ) { origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); } #endif // idle drift scale = cg.xyspeed + 40; fracsin = sin( cg.time * 0.001 ); angles[ROLL] += scale * fracsin * 0.01; angles[YAW] += scale * fracsin * 0.01; angles[PITCH] += scale * fracsin * 0.01; } /* =============== CG_LightningBolt Origin will be the exact tag point, which is slightly different than the muzzle point used for determining hits. The cent should be the non-predicted cent if it is from the player, so the endpoint will reflect the simulated strike (lagging the predicted angle) =============== */ #define RANGE_BEAM (2048.0) #define BEAM_VARIATION 6 void CG_LightningBolt( centity_t *cent, vec3_t origin ) { trace_t trace; // gentity_t *traceEnt; vec3_t startpos, endpos, forward; qboolean spark = qfalse, impact = qtrue; int i; if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { return; // Don't draw a phaser during an intermission you crezzy mon! } //Must be a durational weapon if ( cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { // different checks for first person view if ( ( cg.snap->ps.weapon == WP_DREADNOUGHT && !( cg.snap->ps.eFlags & EF_ALT_FIRING )) || cg.snap->ps.weapon == WP_PHASER) { /*continue*/ } else return; } else { if ( ( cent->currentState.weapon == WP_DREADNOUGHT && !( cent->currentState.eFlags & EF_ALT_FIRING )) || cent->currentState.weapon == WP_PHASER) { /*continue*/ } else return; } // Find the impact point of the beam if ( cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { // take origin from view /* VectorCopy( cg.refdef.vieworg, origin ); VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); VectorMA( origin, 8, cg.refdef.viewaxis[0], origin ); VectorMA( origin, -2, cg.refdef.viewaxis[1], origin ); */ VectorCopy( cg.refdef.viewaxis[0], forward ); VectorCopy( cg.refdef.vieworg, startpos); } else { // take origin from entity AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy( origin, startpos); // Check first from the center to the muzzle. CG_Trace(&trace, cent->lerpOrigin, vec3_origin, vec3_origin, origin, cent->currentState.number, MASK_SHOT); if (trace.fraction < 1.0) { // We hit something here... Stomp the muzzle back to the eye... VectorCopy(cent->lerpOrigin, startpos); startpos[2] += cg.snap->ps.viewheight; } } VectorMA( startpos, RANGE_BEAM, forward, endpos ); // Add a subtle variation to the beam weapon's endpoint for (i = 0; i < 3; i ++ ) { endpos[i] += crandom() * BEAM_VARIATION; } CG_Trace( &trace, startpos, vec3_origin, vec3_origin, endpos, cent->currentState.number, MASK_SHOT ); // traceEnt = &g_entities[ trace.entityNum ]; // Make sparking be a bit less frame-rate dependent..also never add sparking when we hit a surface with a NOIMPACT flag if (!(trace.surfaceFlags & SURF_NOIMPACT)) { spark = qtrue; } // Don't draw certain kinds of impacts when it hits a player and such..or when we hit a surface with a NOIMPACT flag if ( cg_entities[trace.entityNum].currentState.eType == ET_PLAYER || (trace.surfaceFlags & SURF_NOIMPACT) ) { impact = qfalse; } // Add in the effect switch ( cent->currentState.weapon ) { case WP_PHASER: if (cg.snap->ps.rechargeTime == 0) { if ( cent->currentState.eFlags & EF_ALT_FIRING ) FX_PhaserAltFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty ); else FX_PhaserFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty ); } break; case WP_DREADNOUGHT: if (!(cent->currentState.eFlags & EF_ALT_FIRING)) { vec3_t org; // Move the beam back a bit to help cover up the poly edges on the fire beam VectorMA( origin, -4, forward, org ); FX_DreadnoughtFire( org, trace.endpos, trace.plane.normal, spark, impact ); } break; } } /* ====================== CG_MachinegunSpinAngle ====================== */ #define SPIN_SPEED 0.9 #define COAST_TIME 1000 static float CG_MachinegunSpinAngle( centity_t *cent ) { int delta; float angle; float speed; delta = cg.time - cent->pe.barrelTime; if ( cent->pe.barrelSpinning ) { angle = cent->pe.barrelAngle + delta * SPIN_SPEED; } else { if ( delta > COAST_TIME ) { delta = COAST_TIME; } speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); angle = cent->pe.barrelAngle + delta * speed; } if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { cent->pe.barrelTime = cg.time; cent->pe.barrelAngle = AngleMod( angle ); cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); } return angle; } /* ======================== CG_AddWeaponWithPowerups ======================== */ static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { // add powerup effects if ( powerups & ( 1 << PW_INVIS ) ) { gun->customShader = cgs.media.invisShader; trap_R_AddRefEntityToScene( gun ); } else { trap_R_AddRefEntityToScene( gun ); if ( powerups & ( 1 << PW_BATTLESUIT ) ) { gun->customShader = cgs.media.battleWeaponShader; trap_R_AddRefEntityToScene( gun ); } if ( powerups & ( 1 << PW_QUAD ) ) { gun->customShader = cgs.media.quadWeaponShader; trap_R_AddRefEntityToScene( gun ); } if (powerups & (1 << PW_OUCH)) { gun->customShader = cgs.media.holoOuchShader; // set rgb to 1 of 16 values from 0 to 255. don't use random so that the three //parts of the player model as well as the gun will all look the same gun->shaderRGBA[0] = gun->shaderRGBA[1] = gun->shaderRGBA[2] = ((cg.time % 17)*0.0625)*255;//irandom(0,255); 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. ============= */ 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; int i = 0, numBarrels = 0; wpnBarrelInfo_t *barrelInfo = NULL; weaponNum = cent->currentState.weapon; CG_RegisterWeapon( weaponNum ); weapon = &cg_weapons[weaponNum]; // add the weapon memset( &gun, 0, sizeof( gun ) ); VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); gun.shadowPlane = parent->shadowPlane; gun.renderfx = parent->renderfx; // set custom shading for railgun refire rate if ( ps ) { if ( cg.predictedPlayerState.weapon == WP_IMOD && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { float f; f = (float)cg.predictedPlayerState.weaponTime / 1500; gun.shaderRGBA[1] = 0; gun.shaderRGBA[0] = gun.shaderRGBA[2] = 255 * ( 1.0 - f ); } else { gun.shaderRGBA[0] = 255; gun.shaderRGBA[1] = 255; gun.shaderRGBA[2] = 255; gun.shaderRGBA[3] = 255; } } if (ps) { gun.hModel = weapon->viewModel; } else { gun.hModel = weapon->weaponModel; } if (!gun.hModel) { return; } if ( !ps ) { // add weapon stop sound if ( !( cent->currentState.eFlags & EF_FIRING ) && cent->pe.lightningFiring && cg.predictedPlayerState.ammo[cg.predictedPlayerState.weapon] ) { if (weapon->stopSound) { trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->stopSound ); } } cent->pe.lightningFiring = qfalse; if ( cent->currentState.eFlags & EF_ALT_FIRING ) { // hark, I smell hackery afoot if ((weaponNum == WP_PHASER) && !(cg.predictedPlayerState.ammo[WP_PHASER])) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.phaserEmptySound ); cent->pe.lightningFiring = qtrue; } else if ( weapon->altFiringSound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->altFiringSound ); cent->pe.lightningFiring = qtrue; } } else if ( cent->currentState.eFlags & EF_FIRING ) { if ((weaponNum == WP_PHASER) && !(cg.predictedPlayerState.ammo[WP_PHASER])) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.phaserEmptySound ); cent->pe.lightningFiring = qtrue; } else if ( weapon->firingSound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); cent->pe.lightningFiring = qtrue; } } } CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); // add the spinning barrel // // for (barrelInfo = wpnBarrelData; barrelInfo->giTag != WP_NONE; barrelInfo++) { if (barrelInfo->giTag == weaponNum) { numBarrels = barrelInfo->numBarrels; break; } } // don't add barrels to world model...only viewmodels if (ps) { for (i = 0; i < numBarrels; i++) { memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; barrel.hModel = weapon->barrelModel[i]; angles[YAW] = 0; angles[PITCH] = 0; if ( weaponNum == WP_TETRION_DISRUPTOR) { angles[ROLL] = CG_MachinegunSpinAngle( cent ); } else { angles[ROLL] = 0;//CG_MachinegunSpinAngle( cent ); } AnglesToAxis( angles, barrel.axis ); if (!i) { CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->handsModel, "tag_barrel" ); } else { CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->handsModel, va("tag_barrel%d",i+1) ); } CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); } } // make sure we aren't looking at cg.predictedPlayerEntity for LG nonPredictedCent = &cg_entities[cent->currentState.clientNum]; // if the index of the nonPredictedCent is not the same as the clientNum // then this is a fake player (like on teh single player podiums), so // go ahead and use the cent if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { nonPredictedCent = cent; } // add the flash if ( ( weaponNum == WP_PHASER || weaponNum == WP_DREADNOUGHT) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) { // continuous flash } else { // impulse flash if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME) { return; } } memset( &flash, 0, sizeof( flash ) ); VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); flash.shadowPlane = parent->shadowPlane; flash.renderfx = parent->renderfx; flash.hModel = weapon->flashModel; if (!flash.hModel) { return; } angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = crandom() * 10; AnglesToAxis( angles, flash.axis ); if (cent->pe.empty) { // Make muzzle flash wussy when empty. flash.customShader = cgs.media.phaserMuzzleEmptyShader; } if (ps) { // Rendering inside the head... CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->viewModel, "tag_flash"); } else { // Rendering outside the head... CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); } trap_R_AddRefEntityToScene( &flash ); if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum ) { // add phaser/dreadnought // grrr nonPredictedCent doesn't have the proper empty setting nonPredictedCent->pe.empty = cent->pe.empty; CG_LightningBolt( nonPredictedCent, 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] ); } } } /* ============== CG_AddViewWeapon Add the weapon, and flash for the player's view ============== */ void CG_AddViewWeapon( playerState_t *ps ) { refEntity_t hand; centity_t *cent; clientInfo_t *ci; float fovOffset; vec3_t angles; weaponInfo_t *weapon; if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; } if ( ps->pm_type == PM_INTERMISSION ) { return; } // no gun if in third person view if ( cg.renderingThirdPerson ) { return; } // allow the gun to be completely removed if ( !cg_drawGun.integer ) { vec3_t origin; if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { // special hack for phaser/dreadnought... VectorCopy( cg.refdef.vieworg, origin ); VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); CG_LightningBolt( &cg_entities[ps->clientNum], origin ); } return; } // don't draw if testing a gun model if ( cg.testGun ) { return; } // drop gun lower at higher fov if ( cg_fov.integer > 80 ) { fovOffset = -0.2 * ( cg_fov.integer - 80 ); } else { fovOffset = 0; } cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; CG_RegisterWeapon( ps->weapon ); weapon = &cg_weapons[ ps->weapon ]; memset (&hand, 0, sizeof(hand)); // set up gun position CG_CalculateWeaponPosition( hand.origin, angles ); VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); AnglesToAxis( angles, hand.axis ); // map torso animations to weapon animations if ( cg_gun_frame.integer ) { // development tool hand.frame = hand.oldframe = cg_gun_frame.integer; hand.backlerp = 0; } else { // get clientinfo for animation map ci = &cgs.clientinfo[ cent->currentState.clientNum ]; hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); hand.backlerp = cent->pe.torso.backlerp; } hand.hModel = weapon->handsModel; hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; // add everything onto the hand CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity ); } /* ============================================================================== WEAPON SELECTION ============================================================================== */ void static CG_RegisterWeaponIcon( int weaponNum ) { weaponInfo_t *weaponInfo; gitem_t *item; weaponInfo = &cg_weapons[weaponNum]; if ( weaponNum == 0 ) { return; } if ( weaponInfo->registered ) { return; } 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 ); } weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); } /* =================== CG_DrawWeaponSelect =================== */ void CG_DrawWeaponSelect( void ) { int i; int bits; int count; int x, y, w; char *name; float *color; // don't display if dead if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { return; } color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); if ( !color ) { return; } trap_R_SetColor( color ); // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; // count the number of weapons owned bits = cg.snap->ps.stats[ STAT_WEAPONS ]; count = 0; for ( i = 1 ; i < 16 ; i++ ) { if ( bits & ( 1 << i ) ) { count++; } } // x = 320 - count * 20; // y = 432; x = 364 - (count * 20); y = 432; // Left end cap trap_R_SetColor(colorTable[CT_LTPURPLE2]); CG_DrawPic( x - 16, y - 4, 16, 50, cgs.media.weaponcap1); trap_R_SetColor(NULL); for ( i = 1 ; i < 16 ; i++ ) { if ( !( bits & ( 1 << i ) ) ) { continue; } CG_RegisterWeaponIcon( i ); //short version // draw selection marker if ( i == cg.weaponSelect ) { trap_R_SetColor(colorTable[CT_LTPURPLE1]); } else { trap_R_SetColor(colorTable[CT_DKPURPLE1]); } CG_DrawPic( x-4,y-4,38, 38, cgs.media.weaponbox); // draw weapon icon trap_R_SetColor(colorTable[CT_WHITE]); CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); // draw selection marker if ( i == cg.weaponSelect ) { CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); } // no ammo cross on top if ( !cg.snap->ps.ammo[ i ] ) { CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); } x += 40; } // Right end cap trap_R_SetColor(colorTable[CT_LTPURPLE2]); CG_DrawPic( x - 20 + 18, y - 4, 16, 50, cgs.media.weaponcap2); trap_R_SetColor(NULL); // draw the selected name if ( cg_weapons[ cg.weaponSelect ].item ) { name = cg_weapons[ cg.weaponSelect ].item->pickup_name; if ( name ) { // w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; w= UI_ProportionalStringWidth(name,UI_SMALLFONT); x = ( SCREEN_WIDTH - w ) / 2; // CG_DrawBigStringColor(x, y - 22, name, color); UI_DrawProportionalString(x, y - 22, name, UI_SMALLFONT,color); } } trap_R_SetColor( NULL ); } /* =============== CG_WeaponSelectable =============== */ static qboolean CG_WeaponSelectable( int i ) { if ( !cg.snap->ps.ammo[i] ) { return qfalse; } if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { return qfalse; } return qtrue; } extern int altAmmoUsage[]; /* { 0, //WP_NONE, 2, //WP_PHASER, 10, //WP_COMPRESSION_RIFLE, 3, //WP_IMOD, 5, //WP_SCAVENGER_RIFLE, 1, //WP_STASIS, 1, //WP_GRENADE_LAUNCHER, 2, //WP_TETRION_DISRUPTOR, 2, //WP_QUANTUM_BURST, 5 //WP_DREADNOUGHT, }; */ /* =============== CG_WeaponAltSelectable =============== */ static qboolean CG_WeaponAltSelectable( int i ) { if ( cg.snap->ps.ammo[i] < altAmmoUsage[cg.snap->ps.weapon]) { return qfalse; } if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { return qfalse; } return qtrue; } /* =============== CG_NextWeapon_f =============== */ void CG_NextWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < 16 ; i++ ) { cg.weaponSelect++; if ( cg.weaponSelect == 16 ) { cg.weaponSelect = 0; } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == 16 ) { cg.weaponSelect = original; } } /* =============== CG_PrevWeapon_f =============== */ void CG_PrevWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < 16 ; i++ ) { cg.weaponSelect--; if ( cg.weaponSelect == -1 ) { cg.weaponSelect = 15; } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == 16 ) { cg.weaponSelect = original; } } /* =============== CG_Weapon_f =============== */ void CG_Weapon_f( void ) { int num; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } num = atoi( CG_Argv( 1 ) ); if ( num < 1 || num > 15 ) { return; } cg.weaponSelectTime = cg.time; if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { return; // don't have the weapon } cg.weaponSelect = num; } /* =================== CG_OutOfAmmoChange The current weapon has just run out of ammo =================== */ void CG_OutOfAmmoChange( qboolean altfire ) { int i; cg.weaponSelectTime = cg.time; for ( i = 15 ; i > 0 ; i-- ) { if (altfire) { if ( CG_WeaponAltSelectable( i ) ) { cg.weaponSelect = i; break; } } else { if ( CG_WeaponSelectable( i ) ) { cg.weaponSelect = i; break; } } } } /* =================================================================================================== WEAPON EVENTS =================================================================================================== */ /* ================ CG_FireWeapon Caused by an EV_FIRE_WEAPON event ================ */ void CG_FireWeapon( centity_t *cent, qboolean alt_fire ) { entityState_t *ent; weaponInfo_t *weap; ent = ¢->currentState; if ( ent->weapon == WP_NONE ) { return; } if ( ent->weapon >= WP_NUM_WEAPONS ) { CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); return; } weap = &cg_weapons[ ent->weapon ]; // mark the entity as muzzle flashing, so when it is added it will // append the flash to the weapon model cent->muzzleFlashTime = cg.time; // lightning gun only does this this on initial press if ( ent->weapon == WP_PHASER || ent->weapon == WP_DREADNOUGHT) { if ( cent->pe.lightningFiring ) { return; } } // play quad sound if needed if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); } // play a sound if (alt_fire) { if ( weap->altFlashSnd ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd ); } } else { if ( weap->flashSound ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound ); } } } /* ================ CG_FireSeeker Caused by an EV_FIRE_WEAPON event ================ */ void CG_FireSeeker( centity_t *cent ) { entityState_t *ent; weaponInfo_t *weap; ent = ¢->currentState; weap = &cg_weapons[ WP_SCAVENGER_RIFLE ]; trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound ); } /* ================= CG_MissileHitWall Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ================= */ void CG_MissileHitWall( centity_t *cent, int weapon, 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; qboolean alphaFade; // weaponInfo_t *weaponInfo = &cg_weapons[weapon]; mark = 0; radius = 32; sfx = 0; mod = 0; shader = 0; light = 0; lightColor[0] = 1; lightColor[1] = 1; lightColor[2] = 0; // set defaults isSprite = qfalse; duration = 600; switch ( weapon ) { default: case WP_PHASER: // no explosion at LG impact, it is added with the beam mark = cgs.media.holeMarkShader; radius = 12; break; case WP_DREADNOUGHT: // no explosion at LG impact, it is added with the beam mark = cgs.media.holeMarkShader; radius = 12; break; case WP_GRENADE_LAUNCHER: FX_GrenadeExplode( origin, dir ); return; break; case WP_STASIS: FX_StasisWeaponHitWall( origin, dir, cent->currentState.time2 ); return; break; case WP_IMOD: mod = cgs.media.ringFlashModel; shader = cgs.media.imodExplosionShader; mark = cgs.media.energyMarkShader; radius = 24; break; case WP_COMPRESSION_RIFLE: mod = cgs.media.ringFlashModel; shader = cgs.media.imodExplosionShader; mark = cgs.media.energyMarkShader; radius = 24; break; case WP_TETRION_DISRUPTOR: FX_TetrionAltHitWall( origin, dir ); return; break; case WP_SCAVENGER_RIFLE: if (cent->currentState.eFlags & EF_ALT_FIRING) { FX_ScavengerAltExplode( origin, dir ); } else { FX_ScavengerWeaponHitWall( origin, dir, qfalse /*Not shot by NPC*/ ); } return; break; case WP_QUANTUM_BURST: if ( cent->currentState.eFlags & EF_ALT_FIRING ) { FX_QuantumAltHitWall( origin, dir ); } else { FX_QuantumHitWall( origin, dir ); } return; break; } if ( sfx ) { trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); } // // create the explosion // if ( mod ) { le = CG_MakeExplosion( origin, dir, mod, shader, duration, isSprite ); le->light = light; VectorCopy( lightColor, le->lightColor ); } // // impact mark // alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); } /* ================= CG_MissileHitPlayer ================= */ void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir) { if (cent) { // Showing blood is a no-no. // CG_Bleed( origin, cent->currentState.otherEntityNum ); } CG_MissileHitWall( cent, weapon, origin, dir ); } /* ================= CG_BounceEffect Caused by an EV_BOUNCE | EV_BOUNCE_HALF event ================= */ // big fixme. none of these sounds should be registered at runtime void CG_BounceEffect( centity_t *cent, int weapon, vec3_t origin, vec3_t normal ) { switch( weapon ) { case WP_GRENADE_LAUNCHER: if ( rand() & 1 ) { trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav") ); } else { trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav") ); } break; case WP_TETRION_DISRUPTOR: trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound ( va(SOUND_DIR "tetrion/ricochet%d.wav", irandom(1, 3)) ) ); FX_TetrionRicochet( origin, normal ); break; default: if ( rand() & 1 ) { trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav") ); } else { trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav") ); } break; } } /* ============================================================================ BULLETS ============================================================================ */ /* ====================== CG_CalcMuzzlePoint ====================== */ /* static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { vec3_t forward; centity_t *cent; int anim; if ( entityNum == cg.snap->ps.clientNum ) { VectorCopy( cg.snap->ps.origin, muzzle ); muzzle[2] += cg.snap->ps.viewheight; AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } cent = &cg_entities[entityNum]; if ( !cent->currentValid ) { return qfalse; } VectorCopy( cent->currentState.pos.trBase, muzzle ); AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { muzzle[2] += CROUCH_VIEWHEIGHT; } else { muzzle[2] += DEFAULT_VIEWHEIGHT; } VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } */ /* ================ CG_SurfaceExplosion Adds an explosion to a surface ================ */ #define NUM_SPARKS 12 #define NUM_PUFFS 1 #define NUM_EXPLOSIONS 4 void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ) { localEntity_t *le; vec3_t direction, new_org; vec3_t sprayvel, velocity = { 0, 0, 0 }; vec3_t temp_org, temp_vel; float scale, dscale; int i, numSparks; //Sparks numSparks = 32 + (random() * 16.0f); //VectorSet( normal, 0, 0, 1 ); for ( i = 0; i < numSparks; i++ ) { scale = 0.25f + (random() * 2.0f); dscale = -scale*0.5; FXE_Spray( normal, 500, 150, 1.0f, sprayvel); FX_AddTrail( origin, sprayvel, qtrue, 32.0f, -64.0f, scale, -scale, 1.0f, 0.0f, 0.25f, 4000.0f, cgs.media.sparkShader); } //Smoke //Move this out a little from the impact surface VectorMA( origin, 4, normal, new_org ); VectorSet( velocity, 0.0f, 0.0f, 16.0f ); for ( i = 0; i < 4; i++ ) { VectorSet( temp_org, new_org[0] + (crandom() * 16.0f), new_org[1] + (crandom() * 16.0f), new_org[2] + (random() * 4.0f) ); VectorSet( temp_vel, velocity[0] + (crandom() * 8.0f), velocity[1] + (crandom() * 8.0f), velocity[2] + (crandom() * 8.0f) ); FX_AddSprite( temp_org, temp_vel, qfalse, 96.0f + (random() * 32.0f), 16.0f, 1.0f, 0.0f, 20.0f + (crandom() * 90.0f), 0.5f, 2000.0f, cgs.media.smokeShader); } //Core of the explosion //Orient the explosions to face the camera VectorSubtract( cg.refdef.vieworg, origin, direction ); VectorNormalize( direction ); //Tag the last one with a light le = CG_MakeExplosion2( origin, direction, cgs.media.explosionModel, 5, cgs.media.surfaceExplosionShader, 500, qfalse, radius * 0.02f + (random() * 0.3f), LEF_NONE); le->light = 150; VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f ); for ( i = 0; i < NUM_EXPLOSIONS-1; i ++) { VectorSet( new_org, (origin[0] + (32 + (crandom() * 8))*crandom()), (origin[1] + (32 + (crandom() * 8))*crandom()), (origin[2] + (32 + (crandom() * 8))*crandom()) ); le = CG_MakeExplosion2( new_org, direction, cgs.media.explosionModel, 5, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, radius * 0.05f + (crandom() *0.3f), LEF_NONE); } //Shake the camera CG_ExplosionEffects( origin, shake_speed, 350 ); }