/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena 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 2 of the License, or (at your option) any later version. Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_weapons.c -- events and effects dealing with weapons #include "cg_local.h" #include "../vr/vr_clientinfo.h" #include "../vr/vr_types.h" extern vr_clientinfo_t *vr; #define M_PI2 (float)6.28318530717958647692 /* ================= SinCos ================= */ void SinCos( float radians, float *sine, float *cosine ) { #if _MSC_VER == 1200 _asm { fld dword ptr [radians] fsincos mov edx, dword ptr [cosine] mov eax, dword ptr [sine] fstp dword ptr [edx] fstp dword ptr [eax] } #else // I think, better use math.h function, instead of ^ #if defined (__linux__) && !defined (__ANDROID__) sincosf(radians, sine, cosine); #else *sine = sinf(radians); *cosine = cosf(radians); #endif #endif } void Matrix4x4_Concat (matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2) { out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0] + in1[0][3] * in2[3][0]; out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1] + in1[0][3] * in2[3][1]; out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + in1[0][3] * in2[3][2]; out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3] * in2[3][3]; out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0] + in1[1][3] * in2[3][0]; out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1] + in1[1][3] * in2[3][1]; out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + in1[1][3] * in2[3][2]; out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3] * in2[3][3]; out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0] + in1[2][3] * in2[3][0]; out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1] + in1[2][3] * in2[3][1]; out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + in1[2][3] * in2[3][2]; out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3] * in2[3][3]; out[3][0] = in1[3][0] * in2[0][0] + in1[3][1] * in2[1][0] + in1[3][2] * in2[2][0] + in1[3][3] * in2[3][0]; out[3][1] = in1[3][0] * in2[0][1] + in1[3][1] * in2[1][1] + in1[3][2] * in2[2][1] + in1[3][3] * in2[3][1]; out[3][2] = in1[3][0] * in2[0][2] + in1[3][1] * in2[1][2] + in1[3][2] * in2[2][2] + in1[3][3] * in2[3][2]; out[3][3] = in1[3][0] * in2[0][3] + in1[3][1] * in2[1][3] + in1[3][2] * in2[2][3] + in1[3][3] * in2[3][3]; } void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ) { float angle, sr, sp, sy, cr, cp, cy; if( angles[ROLL] ) { #ifdef XASH_VECTORIZE_SINCOS SinCosFastVector3( DEG2RAD(angles[YAW]), DEG2RAD(angles[PITCH]), DEG2RAD(angles[ROLL]), &sy, &sp, &sr, &cy, &cp, &cr); #else angle = angles[YAW] * (M_PI2 / 360.0f); SinCos( angle, &sy, &cy ); angle = angles[PITCH] * (M_PI2 / 360.0f); SinCos( angle, &sp, &cp ); angle = angles[ROLL] * (M_PI2 / 360.0f); SinCos( angle, &sr, &cr ); #endif out[0][0] = (cp*cy) * scale; out[0][1] = (sr*sp*cy+cr*-sy) * scale; out[0][2] = (cr*sp*cy+-sr*-sy) * scale; out[0][3] = origin[0]; out[1][0] = (cp*sy) * scale; out[1][1] = (sr*sp*sy+cr*cy) * scale; out[1][2] = (cr*sp*sy+-sr*cy) * scale; out[1][3] = origin[1]; out[2][0] = (-sp) * scale; out[2][1] = (sr*cp) * scale; out[2][2] = (cr*cp) * scale; out[2][3] = origin[2]; out[3][0] = 0.0f; out[3][1] = 0.0f; out[3][2] = 0.0f; out[3][3] = 1.0f; } else if( angles[PITCH] ) { #ifdef XASH_VECTORIZE_SINCOS SinCosFastVector2( DEG2RAD(angles[YAW]), DEG2RAD(angles[PITCH]), &sy, &sp, &cy, &cp); #else angle = angles[YAW] * (M_PI2 / 360.0f); SinCos( angle, &sy, &cy ); angle = angles[PITCH] * (M_PI2 / 360.0f); SinCos( angle, &sp, &cp ); #endif out[0][0] = (cp*cy) * scale; out[0][1] = (-sy) * scale; out[0][2] = (sp*cy) * scale; out[0][3] = origin[0]; out[1][0] = (cp*sy) * scale; out[1][1] = (cy) * scale; out[1][2] = (sp*sy) * scale; out[1][3] = origin[1]; out[2][0] = (-sp) * scale; out[2][1] = 0.0f; out[2][2] = (cp) * scale; out[2][3] = origin[2]; out[3][0] = 0.0f; out[3][1] = 0.0f; out[3][2] = 0.0f; out[3][3] = 1.0f; } else if( angles[YAW] ) { angle = angles[YAW] * (M_PI2 / 360.0f); SinCos( angle, &sy, &cy ); out[0][0] = (cy) * scale; out[0][1] = (-sy) * scale; out[0][2] = 0.0f; out[0][3] = origin[0]; out[1][0] = (sy) * scale; out[1][1] = (cy) * scale; out[1][2] = 0.0f; out[1][3] = origin[1]; out[2][0] = 0.0f; out[2][1] = 0.0f; out[2][2] = scale; out[2][3] = origin[2]; out[3][0] = 0.0f; out[3][1] = 0.0f; out[3][2] = 0.0f; out[3][3] = 1.0f; } else { out[0][0] = scale; out[0][1] = 0.0f; out[0][2] = 0.0f; out[0][3] = origin[0]; out[1][0] = 0.0f; out[1][1] = scale; out[1][2] = 0.0f; out[1][3] = origin[1]; out[2][0] = 0.0f; out[2][1] = 0.0f; out[2][2] = scale; out[2][3] = origin[2]; out[3][0] = 0.0f; out[3][1] = 0.0f; out[3][2] = 0.0f; out[3][3] = 1.0f; } } void Matrix4x4_ConvertToEntity( vec4_t *in, vec3_t angles, vec3_t origin ) { float xyDist = sqrt( in[0][0] * in[0][0] + in[1][0] * in[1][0] ); // enough here to get angles? if( xyDist > 0.001f ) { angles[0] = RAD2DEG( atan2( -in[2][0], xyDist )); angles[1] = RAD2DEG( atan2( in[1][0], in[0][0] )); angles[2] = RAD2DEG( atan2( in[2][1], in[2][2] )); } else // forward is mostly Z, gimbal lock { angles[0] = RAD2DEG( atan2( -in[2][0], xyDist )); angles[1] = RAD2DEG( atan2( -in[0][1], in[1][1] )); angles[2] = 0.0f; } origin[0] = in[0][3]; origin[1] = in[1][3]; origin[2] = in[2][3]; } void rotateAboutOrigin(float x, float y, float rotation, vec2_t out) { out[0] = cosf(DEG2RAD(-rotation)) * x + sinf(DEG2RAD(-rotation)) * y; out[1] = cosf(DEG2RAD(-rotation)) * y - sinf(DEG2RAD(-rotation)) * x; } float trap_Cvar_VariableValue( const char *var_name ) { char buf[128]; trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); return atof(buf); } void CG_ConvertFromVR(vec3_t in, vec3_t offset, vec3_t out) { vec3_t vrSpace; VectorSet(vrSpace, in[2], in[0], in[1] ); vec2_t r; if (!cgs.localServer) { //We are connected to a multiplayer server, so make the appropriate adjustment to the view //angles as we send orientation to the server that includes the weapon angles float deltaYaw = SHORT2ANGLE(cg.predictedPlayerState.delta_angles[YAW]); if (cg.snap->ps.pm_flags & PMF_FOLLOW) { //Don't include delta if following another player deltaYaw = 0.0f; } float angleYaw = deltaYaw + (vr->clientviewangles[YAW] - vr->hmdorientation[YAW]); rotateAboutOrigin(vrSpace[0], vrSpace[1], angleYaw, r); } else { rotateAboutOrigin(vrSpace[0], vrSpace[1], cg.refdefViewAngles[YAW] - vr->hmdorientation[YAW], r); } vrSpace[0] = -r[0]; vrSpace[1] = -r[1]; vec3_t temp; VectorScale(vrSpace, cg.worldscale, temp); if (offset) { VectorAdd(temp, offset, out); } else { VectorCopy(temp, out); } } static void CG_CalculateVRPositionInWorld( vec3_t in_position, vec3_t in_offset, vec3_t in_orientation, vec3_t origin, vec3_t angles ) { if (!cgs.localServer) { //Use absolute position for the faked 6DoF for multiplayer vec3_t offset; VectorSubtract(in_position, vr->hmdorigin, offset); offset[1] = 0; // up/down is index 1 in this case CG_ConvertFromVR(offset, cg.refdef.vieworg, origin); origin[2] -= PLAYER_HEIGHT; origin[2] += in_position[1] * cg.worldscale; } else { //Local server - true 6DoF offset from HMD vec3_t offset; VectorCopy(in_offset, offset); offset[1] = 0; // up/down is index 1 in this case CG_ConvertFromVR(offset, cg.refdef.vieworg, origin); origin[2] -= PLAYER_HEIGHT; origin[2] += in_position[1] * cg.worldscale; } VectorCopy(in_orientation, angles); if ( !cgs.localServer ) { //Calculate the offhand angles from "first principles" float deltaYaw = SHORT2ANGLE(cg.predictedPlayerState.delta_angles[YAW]); angles[YAW] = deltaYaw + (vr->clientviewangles[YAW] - vr->hmdorientation[YAW]) + in_orientation[YAW]; } else { angles[YAW] += (cg.refdefViewAngles[YAW] - vr->hmdorientation[YAW]); } } void CG_CalculateVROffHandPosition( vec3_t origin, vec3_t angles ) { CG_CalculateVRPositionInWorld(vr->offhandposition, vr->offhandoffset, vr->offhandangles, origin, angles); } static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ); void CG_CalculateVRWeaponPosition( vec3_t origin, vec3_t angles ) { if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW)) { CG_CalculateWeaponPosition(origin, angles); return; } CG_CalculateVRPositionInWorld(vr->weaponposition, vr->weaponoffset, vr->weaponangles, origin, angles); } /* ========================== CG_MachineGunEjectBrass ========================== */ static void CG_MachineGunEjectBrass( centity_t *cent ) { localEntity_t *le; refEntity_t *re; vec3_t velocity, xvelocity; vec3_t offset, xoffset; float waterScale = 1.0f; vec3_t v[3]; if ( cg_brassTime.integer <= 0 ) { return; } le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = 0; velocity[1] = -50 + 40 * crandom(); velocity[2] = 100 + 50 * crandom(); le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time - (rand()&15); AnglesToAxis( cent->lerpAngles, v ); offset[0] = 8; offset[1] = -4; offset[2] = 24; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( cent->lerpOrigin, xoffset, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10f; } xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; VectorScale( xvelocity, waterScale, le->pos.trDelta ); AxisCopy( axisDefault, re->axis ); re->hModel = cgs.media.machinegunBrassModel; le->bounceFactor = 0.4 * waterScale; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = rand()&31; le->angles.trBase[1] = rand()&31; le->angles.trBase[2] = rand()&31; le->angles.trDelta[0] = 2; le->angles.trDelta[1] = 1; le->angles.trDelta[2] = 0; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_BRASS; le->leMarkType = LEMT_NONE; } /* ========================== CG_ShotgunEjectBrass ========================== */ static void CG_ShotgunEjectBrass( centity_t *cent ) { localEntity_t *le; refEntity_t *re; vec3_t velocity, xvelocity; vec3_t offset, xoffset; vec3_t v[3]; int i; if ( cg_brassTime.integer <= 0 ) { return; } for ( i = 0; i < 2; i++ ) { float waterScale = 1.0f; le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = 60 + 60 * crandom(); if ( i == 0 ) { velocity[1] = 40 + 10 * crandom(); } else { velocity[1] = -40 + 10 * crandom(); } velocity[2] = 100 + 50 * crandom(); le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time; AnglesToAxis( cent->lerpAngles, v ); offset[0] = 8; offset[1] = 0; offset[2] = 24; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( cent->lerpOrigin, xoffset, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10f; } xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; VectorScale( xvelocity, waterScale, le->pos.trDelta ); AxisCopy( axisDefault, re->axis ); re->hModel = cgs.media.shotgunBrassModel; le->bounceFactor = 0.3f; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = rand()&31; le->angles.trBase[1] = rand()&31; le->angles.trBase[2] = rand()&31; le->angles.trDelta[0] = 1; le->angles.trDelta[1] = 0.5; le->angles.trDelta[2] = 0; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_BRASS; le->leMarkType = LEMT_NONE; } } #ifdef MISSIONPACK /* ========================== CG_NailgunEjectBrass ========================== */ static void CG_NailgunEjectBrass( centity_t *cent ) { localEntity_t *smoke; vec3_t origin; vec3_t v[3]; vec3_t offset; vec3_t xoffset; vec3_t up; AnglesToAxis( cent->lerpAngles, v ); offset[0] = 0; offset[1] = -12; offset[2] = 24; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( cent->lerpOrigin, xoffset, origin ); VectorSet( up, 0, 0, 64 ); smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } #endif /* ========================== CG_LaserSight ========================== */ void CG_LaserSight( vec3_t start, vec3_t end, byte colour[4], float width ) { refEntity_t re; memset( &re, 0, sizeof( re ) ); //Ensure shader is loaded cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); re.reType = RT_LASERSIGHT; re.customShader = cgs.media.railCoreShader; VectorCopy( start, re.origin ); VectorCopy( end, re.oldorigin ); //radius is used to store width info re.radius = width; AxisClear( re.axis ); re.shaderRGBA[0] = colour[0]; re.shaderRGBA[1] = colour[1]; re.shaderRGBA[2] = colour[2]; re.shaderRGBA[3] = colour[3]; trap_R_AddRefEntityToScene(&re); } /* ========================== CG_RailTrail ========================== */ void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { vec3_t axis[36], move, move2, vec, temp; float len; int i, j, skip; localEntity_t *le; refEntity_t *re; #define RADIUS 4 #define ROTATION 1 #define SPACING 5 start[2] -= 4; le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FADE_RGB; le->startTime = cg.time; le->endTime = cg.time + cg_railTrailTime.value; le->lifeRate = 1.0 / (le->endTime - le->startTime); re->shaderTime = cg.time / 1000.0f; re->reType = RT_RAIL_CORE; re->customShader = cgs.media.railCoreShader; VectorCopy(start, re->origin); VectorCopy(end, re->oldorigin); re->shaderRGBA[0] = ci->color1[0] * 255; re->shaderRGBA[1] = ci->color1[1] * 255; re->shaderRGBA[2] = ci->color1[2] * 255; re->shaderRGBA[3] = 255; le->color[0] = ci->color1[0] * 0.75; le->color[1] = ci->color1[1] * 0.75; le->color[2] = ci->color1[2] * 0.75; le->color[3] = 1.0f; AxisClear( re->axis ); if (cg_oldRail.integer) { // nudge down a bit so it isn't exactly in center //re->origin[2] -= 8; //re->oldorigin[2] -= 8; return; } VectorCopy (start, move); VectorSubtract (end, start, vec); len = VectorNormalize (vec); PerpendicularVector(temp, vec); for (i = 0 ; i < 36; i++) { RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 } VectorMA(move, 20, vec, move); VectorScale (vec, SPACING, vec); skip = -1; j = 18; for (i = 0; i < len; i += SPACING) { if (i != skip) { skip = i + SPACING; le = CG_AllocLocalEntity(); re = &le->refEntity; le->leFlags = LEF_PUFF_DONT_SCALE; le->leType = LE_MOVE_SCALE_FADE; le->startTime = cg.time; le->endTime = cg.time + (i>>1) + 600; le->lifeRate = 1.0 / (le->endTime - le->startTime); re->shaderTime = cg.time / 1000.0f; re->reType = RT_SPRITE; re->radius = 1.1f; re->customShader = cgs.media.railRingsShader; re->shaderRGBA[0] = ci->color2[0] * 255; re->shaderRGBA[1] = ci->color2[1] * 255; re->shaderRGBA[2] = ci->color2[2] * 255; re->shaderRGBA[3] = 255; le->color[0] = ci->color2[0] * 0.75; le->color[1] = ci->color2[1] * 0.75; le->color[2] = ci->color2[2] * 0.75; le->color[3] = 1.0f; le->pos.trType = TR_LINEAR; le->pos.trTime = cg.time; VectorCopy( move, move2); VectorMA(move2, RADIUS , axis[j], move2); VectorCopy(move2, le->pos.trBase); le->pos.trDelta[0] = axis[j][0]*6; le->pos.trDelta[1] = axis[j][1]*6; le->pos.trDelta[2] = axis[j][2]*6; } VectorAdd (move, vec, move); j = (j + ROTATION) % 36; } } /* ========================== CG_RocketTrail ========================== */ static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos; int t; int startTime, contents; int lastContents; entityState_t *es; vec3_t up; localEntity_t *smoke; if ( cg_noProjectileTrail.integer ) { return; } up[0] = 0; up[1] = 0; up[2] = 0; step = 50; es = &ent->currentState; startTime = ent->trailTime; t = step * ( (startTime + step) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin ); contents = CG_PointContents( origin, -1 ); // if object (e.g. grenade) is stationary, don't toss up smoke if ( es->pos.trType == TR_STATIONARY ) { ent->trailTime = cg.time; return; } BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); lastContents = CG_PointContents( lastPos, -1 ); ent->trailTime = cg.time; if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { if ( contents & lastContents & CONTENTS_WATER ) { CG_BubbleTrail( lastPos, origin, 8 ); } return; } for ( ; t <= ent->trailTime ; t += step ) { BG_EvaluateTrajectory( &es->pos, t, lastPos ); smoke = CG_SmokePuff( lastPos, up, wi->trailRadius, 1, 1, 1, 0.33f, wi->wiTrailTime, t, 0, 0, cgs.media.smokePuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } } #ifdef MISSIONPACK /* ========================== CG_NailTrail ========================== */ static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos; int t; int startTime, contents; int lastContents; entityState_t *es; vec3_t up; localEntity_t *smoke; if ( cg_noProjectileTrail.integer ) { return; } up[0] = 0; up[1] = 0; up[2] = 0; step = 50; es = &ent->currentState; startTime = ent->trailTime; t = step * ( (startTime + step) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin ); contents = CG_PointContents( origin, -1 ); // if object (e.g. grenade) is stationary, don't toss up smoke if ( es->pos.trType == TR_STATIONARY ) { ent->trailTime = cg.time; return; } BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); lastContents = CG_PointContents( lastPos, -1 ); ent->trailTime = cg.time; if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { if ( contents & lastContents & CONTENTS_WATER ) { CG_BubbleTrail( lastPos, origin, 8 ); } return; } for ( ; t <= ent->trailTime ; t += step ) { BG_EvaluateTrajectory( &es->pos, t, lastPos ); smoke = CG_SmokePuff( lastPos, up, wi->trailRadius, 1, 1, 1, 0.33f, wi->wiTrailTime, t, 0, 0, cgs.media.nailPuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } } #endif /* ========================== CG_PlasmaTrail ========================== */ static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { localEntity_t *le; refEntity_t *re; entityState_t *es; vec3_t velocity, xvelocity, origin; vec3_t offset, xoffset; vec3_t v[3]; float waterScale = 1.0f; if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { return; } es = ¢->currentState; BG_EvaluateTrajectory( &es->pos, cg.time, origin ); le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = 60 - 120 * crandom(); velocity[1] = 40 - 80 * crandom(); velocity[2] = 100 - 200 * crandom(); le->leType = LE_MOVE_SCALE_FADE; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_NONE; le->leMarkType = LEMT_NONE; le->startTime = cg.time; le->endTime = le->startTime + 600; le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time; AnglesToAxis( cent->lerpAngles, v ); offset[0] = 2; offset[1] = 2; offset[2] = 2; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( origin, xoffset, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10f; } xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; VectorScale( xvelocity, waterScale, le->pos.trDelta ); AxisCopy( axisDefault, re->axis ); re->shaderTime = cg.time / 1000.0f; re->reType = RT_SPRITE; re->radius = 0.25f; re->customShader = cgs.media.railRingsShader; le->bounceFactor = 0.3f; re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; re->shaderRGBA[3] = 63; le->color[0] = wi->flashDlightColor[0] * 0.2; le->color[1] = wi->flashDlightColor[1] * 0.2; le->color[2] = wi->flashDlightColor[2] * 0.2; le->color[3] = 0.25f; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = rand()&31; le->angles.trBase[1] = rand()&31; le->angles.trBase[2] = rand()&31; le->angles.trDelta[0] = 1; le->angles.trDelta[1] = 0.5; le->angles.trDelta[2] = 0; } /* ========================== CG_GrappleTrail ========================== */ void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { vec3_t origin; entityState_t *es; vec3_t forward, up; refEntity_t beam; es = &ent->currentState; BG_EvaluateTrajectory( &es->pos, cg.time, origin ); ent->trailTime = cg.time; memset( &beam, 0, sizeof( beam ) ); //FIXME adjust for muzzle position VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); beam.origin[2] += 26; AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); VectorMA( beam.origin, -6, up, beam.origin ); VectorCopy( origin, beam.oldorigin ); if (Distance( beam.origin, beam.oldorigin ) < 64 ) return; // Don't draw if close beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; AxisClear( beam.axis ); beam.shaderRGBA[0] = 0xff; beam.shaderRGBA[1] = 0xff; beam.shaderRGBA[2] = 0xff; beam.shaderRGBA[3] = 0xff; trap_R_AddRefEntityToScene( &beam ); } /* ========================== CG_GrenadeTrail ========================== */ static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { CG_RocketTrail( ent, wi ); } /* ================= CG_RegisterWeapon The server says this item is used on this level ================= */ void CG_RegisterWeapon( int weaponNum ) { weaponInfo_t *weaponInfo; gitem_t *item, *ammo; char path[MAX_QPATH]; vec3_t mins, maxs; int i; weaponInfo = &cg_weapons[weaponNum]; if ( weaponNum == 0 ) { return; } if ( weaponInfo->registered ) { return; } memset( weaponInfo, 0, sizeof( *weaponInfo ) ); weaponInfo->registered = qtrue; for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { weaponInfo->item = item; break; } } if ( !item->classname ) { CG_Error( "Couldn't find weapon %i", weaponNum ); } CG_RegisterItemVisuals( item - bg_itemlist ); // load cmodel before model so filecache works weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); // 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 ); weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { break; } } if ( ammo->classname && ammo->world_model[0] ) { weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); } COM_StripExtension( item->world_model[0], path, sizeof(path) ); Q_strcat( path, sizeof(path), "_flash.md3" ); weaponInfo->flashModel = trap_R_RegisterModel( path ); COM_StripExtension( item->world_model[0], path, sizeof(path) ); Q_strcat( path, sizeof(path), "_barrel.md3" ); weaponInfo->barrelModel = trap_R_RegisterModel( path ); COM_StripExtension( item->world_model[0], path, sizeof(path) ); Q_strcat( path, sizeof(path), "_hand.md3" ); weaponInfo->handsModel = trap_R_RegisterModel( path ); if ( !weaponInfo->handsModel ) { weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); } switch ( weaponNum ) { case WP_GAUNTLET: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); break; case WP_LIGHTNING: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); break; case WP_GRAPPLING_HOOK: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); weaponInfo->missileTrailFunc = CG_GrappleTrail; weaponInfo->missileDlight = 200; MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); break; #ifdef MISSIONPACK case WP_CHAINGUN: weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); break; #endif case WP_MACHINEGUN: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); break; case WP_SHOTGUN: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; break; case WP_ROCKET_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); weaponInfo->missileTrailFunc = CG_RocketTrail; weaponInfo->missileDlight = 200; weaponInfo->wiTrailTime = 2000; weaponInfo->trailRadius = 64; MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); break; #ifdef MISSIONPACK case WP_PROX_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); weaponInfo->missileTrailFunc = CG_GrenadeTrail; weaponInfo->wiTrailTime = 700; weaponInfo->trailRadius = 32; MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); break; #endif case WP_GRENADE_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); weaponInfo->missileTrailFunc = CG_GrenadeTrail; weaponInfo->wiTrailTime = 700; weaponInfo->trailRadius = 32; MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); break; #ifdef MISSIONPACK case WP_NAILGUN: weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; weaponInfo->missileTrailFunc = CG_NailTrail; // weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); weaponInfo->trailRadius = 16; weaponInfo->wiTrailTime = 250; weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); break; #endif case WP_PLASMAGUN: // weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; weaponInfo->missileTrailFunc = CG_PlasmaTrail; weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); break; case WP_RAILGUN: weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); break; case WP_BFG: weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); break; default: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); break; } } /* ================= CG_RegisterItemVisuals The server says this item is used on this level ================= */ void CG_RegisterItemVisuals( int itemNum ) { itemInfo_t *itemInfo; gitem_t *item; if ( itemNum < 0 || itemNum >= bg_numItems ) { CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); } itemInfo = &cg_items[ itemNum ]; if ( itemInfo->registered ) { return; } item = &bg_itemlist[ itemNum ]; memset( itemInfo, 0, sizeof( *itemInfo ) ); itemInfo->registered = qtrue; itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); itemInfo->icon = trap_R_RegisterShader( item->icon ); if ( item->giType == IT_WEAPON ) { CG_RegisterWeapon( item->giTag ); } // // powerups have an accompanying ring or sphere // if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { if ( item->world_model[1] ) { itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); } } } /* ======================================================================================== 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 if (cg_weaponbob.value != 0) { angles[ROLL] += scale * cg.bobfracsin * 0.005; angles[YAW] += scale * cg.bobfracsin * 0.01; angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; } // drop the weapon when landing delta = cg.time - cg.landTime; if ( delta < LAND_DEFLECT_TIME ) { origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { origin[2] += cg.landChange*0.25 * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; } #if 0 // drop the weapon when stair climbing delta = cg.time - cg.stepTime; if ( delta < STEP_TIME/2 ) { origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); } else if ( delta < STEP_TIME ) { origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); } #endif // idle drift scale = cg.xyspeed + 40; fracsin = sin( cg.time * 0.001 ); angles[ROLL] += scale * fracsin * 0.01; angles[YAW] += scale * fracsin * 0.01; angles[PITCH] += scale * fracsin * 0.01; } /* =============== CG_LightningBolt Origin will be the exact tag point, which is slightly different than the muzzle point used for determining hits. The cent should be the non-predicted cent if it is from the player, so the endpoint will reflect the simulated strike (lagging the predicted angle) =============== */ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { trace_t trace; refEntity_t beam; vec3_t forward; vec3_t muzzlePoint, endPoint; int anim; if (cent->currentState.weapon != WP_LIGHTNING) { return; } memset( &beam, 0, sizeof( beam ) ); // CPMA "true" lightning if (cent->currentState.number == cg.predictedPlayerState.clientNum)// && (cg_trueLightning.value != 0)) { vec3_t angle; CG_CalculateVRWeaponPosition(muzzlePoint, angle); AngleVectors(angle, forward, NULL, NULL ); } else { // !CPMA AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); } if (cent->currentState.number != cg.predictedPlayerState.clientNum) { anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; if (anim == LEGS_WALKCR || anim == LEGS_IDLECR) { muzzlePoint[2] += CROUCH_VIEWHEIGHT; } else { muzzlePoint[2] += DEFAULT_VIEWHEIGHT; } } VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // project forward by the lightning range VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); // see if it hit a wall CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, cent->currentState.number, MASK_SHOT ); // this is the endpoint VectorCopy( trace.endpos, beam.oldorigin ); // use the provided origin, even though it may be slightly // different than the muzzle origin VectorCopy( origin, beam.origin ); beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; trap_R_AddRefEntityToScene( &beam ); // add the impact flare if it hit something if ( trace.fraction < 1.0 ) { vec3_t angles; vec3_t dir; VectorSubtract( beam.oldorigin, beam.origin, dir ); VectorNormalize( dir ); memset( &beam, 0, sizeof( beam ) ); beam.hModel = cgs.media.lightningExplosionModel; VectorMA( trace.endpos, -16, dir, beam.origin ); // make a random orientation angles[0] = rand() % 360; angles[1] = rand() % 360; angles[2] = rand() % 360; AnglesToAxis( angles, beam.axis ); trap_R_AddRefEntityToScene( &beam ); } } /* static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { trace_t trace; refEntity_t beam; vec3_t forward; vec3_t muzzlePoint, endPoint; if ( cent->currentState.weapon != WP_LIGHTNING ) { return; } memset( &beam, 0, sizeof( beam ) ); // find muzzle point for this frame VectorCopy( cent->lerpOrigin, muzzlePoint ); AngleVectors( cent->lerpAngles, forward, NULL, NULL ); // FIXME: crouch muzzlePoint[2] += DEFAULT_VIEWHEIGHT; VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // project forward by the lightning range VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); // see if it hit a wall CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, cent->currentState.number, MASK_SHOT ); // this is the endpoint VectorCopy( trace.endpos, beam.oldorigin ); // use the provided origin, even though it may be slightly // different than the muzzle origin VectorCopy( origin, beam.origin ); beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; trap_R_AddRefEntityToScene( &beam ); // add the impact flare if it hit something if ( trace.fraction < 1.0 ) { vec3_t angles; vec3_t dir; VectorSubtract( beam.oldorigin, beam.origin, dir ); VectorNormalize( dir ); memset( &beam, 0, sizeof( beam ) ); beam.hModel = cgs.media.lightningExplosionModel; VectorMA( trace.endpos, -16, dir, beam.origin ); // make a random orientation angles[0] = rand() % 360; angles[1] = rand() % 360; angles[2] = rand() % 360; AnglesToAxis( angles, beam.axis ); trap_R_AddRefEntityToScene( &beam ); } } */ /* ====================== CG_MachinegunSpinAngle ====================== */ #define SPIN_SPEED 0.9 #define COAST_TIME 1000 static float CG_MachinegunSpinAngle( centity_t *cent ) { int delta; float angle; float speed; delta = cg.time - cent->pe.barrelTime; if ( cent->pe.barrelSpinning ) { angle = cent->pe.barrelAngle + delta * SPIN_SPEED; } else { if ( delta > COAST_TIME ) { delta = COAST_TIME; } speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); angle = cent->pe.barrelAngle + delta * speed; } if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { cent->pe.barrelTime = cg.time; cent->pe.barrelAngle = AngleMod( angle ); cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); #ifdef MISSIONPACK if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); } #endif } return angle; } /* ======================== CG_AddWeaponWithPowerups ======================== */ static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { // add powerup effects if ( powerups & ( 1 << PW_INVIS ) ) { gun->customShader = cgs.media.invisShader; trap_R_AddRefEntityToScene( gun ); } else { trap_R_AddRefEntityToScene( gun ); if ( powerups & ( 1 << PW_BATTLESUIT ) ) { gun->customShader = cgs.media.battleWeaponShader; trap_R_AddRefEntityToScene( gun ); } if ( powerups & ( 1 << PW_QUAD ) ) { gun->customShader = cgs.media.quadWeaponShader; trap_R_AddRefEntityToScene( gun ); } } } /* ============= CG_AddPlayerWeapon Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) The main player will have this called for BOTH cases, so effects like light and sound should only be done on the world model case. ============= */ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { refEntity_t gun; refEntity_t barrel; refEntity_t flash; vec3_t angles; weapon_t weaponNum; weaponInfo_t *weapon; centity_t *nonPredictedCent; orientation_t lerped; 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( weaponNum == WP_RAILGUN ) { clientInfo_t *ci = &cgs.clientinfo[cent->currentState.clientNum]; if( cent->pe.railFireTime + 1500 > cg.time ) { int scale = 255 * ( cg.time - cent->pe.railFireTime ) / 1500; gun.shaderRGBA[0] = ( ci->c1RGBA[0] * scale ) >> 8; gun.shaderRGBA[1] = ( ci->c1RGBA[1] * scale ) >> 8; gun.shaderRGBA[2] = ( ci->c1RGBA[2] * scale ) >> 8; gun.shaderRGBA[3] = 255; } else { Byte4Copy( ci->c1RGBA, gun.shaderRGBA ); } } gun.hModel = weapon->weaponModel; if (!gun.hModel) { 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->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); cent->pe.lightningFiring = qtrue; } else if ( weapon->readySound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } } trap_R_LerpTag(&lerped, parent->hModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, "tag_weapon"); VectorCopy(parent->origin, gun.origin); VectorMA(gun.origin, lerped.origin[0], parent->axis[0], gun.origin); // Make weapon appear left-handed for 2 and centered for 3 if(ps && cg_drawGun.integer == 2) VectorMA(gun.origin, -lerped.origin[1], parent->axis[1], gun.origin); else if(!ps || cg_drawGun.integer != 3) VectorMA(gun.origin, lerped.origin[1], parent->axis[1], gun.origin); VectorMA(gun.origin, lerped.origin[2], parent->axis[2], gun.origin); MatrixMultiply(lerped.axis, ((refEntity_t *)parent)->axis, gun.axis); gun.backlerp = parent->backlerp; CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); // add the spinning barrel if ( weapon->barrelModel ) { memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; barrel.hModel = weapon->barrelModel; angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = CG_MachinegunSpinAngle( cent ); AnglesToAxis( angles, barrel.axis ); CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); } // make sure we aren't looking at cg.predictedPlayerEntity for LG nonPredictedCent = &cg_entities[cent->currentState.number]; // 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 if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) && ( 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 ); // colorize the railgun blast if ( weaponNum == WP_RAILGUN ) { clientInfo_t *ci; ci = &cgs.clientinfo[ cent->currentState.clientNum ]; flash.shaderRGBA[0] = 255 * ci->color1[0]; flash.shaderRGBA[1] = 255 * ci->color1[1]; flash.shaderRGBA[2] = 255 * ci->color1[2]; } CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); trap_R_AddRefEntityToScene( &flash ); if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum ) { // add lightning bolt CG_LightningBolt( nonPredictedCent, flash.origin ); if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { trap_R_AddLightToScene( flash.origin, 300 + (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 or a camera is active //if ( cg.renderingThirdPerson || cg.cameraMode) { if ( cg.renderingThirdPerson ) { return; } // allow the gun to be completely removed if ( !cg_drawGun.integer ) { vec3_t origin; if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { // special hack for lightning gun... VectorCopy( cg.refdef.vieworg, origin ); VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); CG_LightningBolt( &cg_entities[ps->clientNum], origin ); } return; } // don't draw if testing a gun model if ( cg.testGun ) { return; } // drop gun lower at higher fov /* if ( cg_fov.integer > 90 ) { fovOffset = -0.2 * ( cg_fov.integer - 90 ); } else { fovOffset = 0; } */ if (vr->weapon_select) { CG_DrawWeaponSelector(); return; } 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_CalculateVRWeaponPosition( hand.origin, angles ); if (trap_Cvar_VariableValue("vr_lasersight") != 0.0f) { vec3_t forward, end, dir; AngleVectors(angles, forward, NULL, NULL); VectorMA(hand.origin, 4096, forward, end); trace_t trace; CG_Trace(&trace, hand.origin, NULL, NULL, end, cg.predictedPlayerState.clientNum, MASK_SOLID); byte colour[4]; colour[0] = 0xff; colour[1] = 0x00; colour[2] = 0x00; colour[3] = 0x40; CG_LaserSight(hand.origin, trace.endpos, colour, 1.0f); } //Scale / Move gun etc float scale = 1.0f; if (!(cg.snap->ps.pm_flags & PMF_FOLLOW && vr->follow_mode == VRFM_FIRSTPERSON)) { char cvar_name[64]; Com_sprintf(cvar_name, sizeof(cvar_name), "vr_weapon_adjustment_%i", ps->weapon); char weapon_adjustment[256]; trap_Cvar_VariableStringBuffer(cvar_name, weapon_adjustment, 256); if (strlen(weapon_adjustment) > 0) { vec3_t offset; vec3_t adjust; vec3_t temp_offset; VectorClear(temp_offset); VectorClear(adjust); sscanf(weapon_adjustment, "%f,%f,%f,%f,%f,%f,%f", &scale, &(temp_offset[0]), &(temp_offset[1]), &(temp_offset[2]), &(adjust[PITCH]), &(adjust[YAW]), &(adjust[ROLL])); VectorScale(temp_offset, scale, offset); if (!vr->right_handed) { //yaw needs to go in the other direction as left handed model is reversed adjust[YAW] *= -1.0f; } //Adjust angles for weapon models that aren't aligned very well matrix4x4 m1, m2, m3; vec3_t zero; VectorClear(zero); Matrix4x4_CreateFromEntity(m1, angles, zero, 1.0); Matrix4x4_CreateFromEntity(m2, adjust, zero, 1.0); Matrix4x4_Concat(m3, m1, m2); Matrix4x4_ConvertToEntity(m3, angles, zero); //Now move weapon closer to proper origin vec3_t forward, right, up; AngleVectors( angles, forward, right, up ); VectorMA( hand.origin, offset[2], forward, hand.origin ); VectorMA( hand.origin, offset[1], up, hand.origin ); if (vr->right_handed) { VectorMA(hand.origin, offset[0], right, hand.origin); } else { VectorMA(hand.origin, -offset[0], right, hand.origin); } } } //Move gun a bit 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, cg.refdef.viewaxis[2], hand.origin ); AnglesToAxis( angles, hand.axis ); // map torso animations to weapon animations if ( cg_gun_frame.integer ) { // development tool hand.frame = hand.oldframe = cg_gun_frame.integer; hand.backlerp = 0; } else { // get clientinfo for animation map ci = &cgs.clientinfo[ cent->currentState.clientNum ]; hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); hand.backlerp = cent->pe.torso.backlerp; } hand.hModel = weapon->handsModel; hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; //scale the whole model for ( int i = 0; i < 3; i++ ) { VectorScale( hand.axis[i], vr->right_handed || i != 1 ? scale : -scale, hand.axis[i] ); } // add everything onto the hand CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); } /* ============================================================================== WEAPON SELECTION ============================================================================== */ /* =================== CG_DrawWeaponSelect =================== */ void CG_DrawWeaponSelect( void ) { int i; int bits; int count; int x, y, w; char *name; float *color; // don't display if dead if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { return; } color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); if ( !color ) { return; } trap_R_SetColor( color ); // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; // count the number of weapons owned bits = cg.snap->ps.stats[ STAT_WEAPONS ]; count = 0; for ( i = 1 ; i < MAX_WEAPONS ; i++ ) { if ( bits & ( 1 << i ) ) { count++; } } x = 320 - count * 20; y = 380; for ( i = 1 ; i < MAX_WEAPONS ; i++ ) { if ( !( bits & ( 1 << i ) ) ) { continue; } CG_RegisterWeapon( i ); // draw weapon icon CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); // draw selection marker if ( i == cg.weaponSelect ) { CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); } // no ammo cross on top if ( !cg.snap->ps.ammo[ i ] ) { CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); } x += 40; } // draw the selected name if ( cg_weapons[ cg.weaponSelect ].item ) { name = cg_weapons[ cg.weaponSelect ].item->pickup_name; if ( name ) { w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; CG_DrawBigStringColor(x, y - 22, name, color); } } trap_R_SetColor( NULL ); } /* =============== CG_WeaponSelectable =============== */ static qboolean CG_WeaponSelectable( int i ) { if ( !cg.snap->ps.ammo[i] ) { return qfalse; } if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { return qfalse; } return qtrue; } /* =============== CG_NextWeapon_f =============== */ void CG_NextWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { cg.weaponSelect++; if ( cg.weaponSelect == MAX_WEAPONS ) { cg.weaponSelect = 0; } if ( cg.weaponSelect == WP_GAUNTLET ) { continue; // never cycle to gauntlet } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == MAX_WEAPONS ) { 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 < MAX_WEAPONS ; i++ ) { cg.weaponSelect--; if ( cg.weaponSelect == -1 ) { cg.weaponSelect = MAX_WEAPONS - 1; } if ( cg.weaponSelect == WP_GAUNTLET ) { continue; // never cycle to gauntlet } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == MAX_WEAPONS ) { 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 > MAX_WEAPONS-1 ) { return; } cg.weaponSelectTime = cg.time; if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { return; // don't have the weapon } cg.weaponSelect = num; } //Selects the currently selected weapon (if one _is_ selected) void CG_WeaponSelectorSelect_f( void ) { cg.weaponSelectorTime = 0; if (cg.weaponSelectorSelection == WP_NONE || cg.weaponSelect == cg.weaponSelectorSelection) { return; } cg.weaponSelectTime = cg.time; cg.weaponSelect = cg.weaponSelectorSelection; cg.weaponSelectorSelection = WP_NONE; } static float length(float x, float y) { return sqrtf(powf(x, 2.0f) + powf(y, 2.0f)); } void CG_DrawWeaponSelector( void ) { if (cg.weaponSelectorTime == 0) { cg.weaponSelectorTime = cg.time; VectorCopy(vr->weaponangles, cg.weaponSelectorAngles); VectorCopy(vr->weaponposition, cg.weaponSelectorOrigin); VectorCopy(vr->weaponoffset, cg.weaponSelectorOffset); } const int selectorMode = (int)trap_Cvar_VariableValue("vr_weaponSelectorMode"); float DEPTH = 5.0f; float RAD = 5.0f; if (selectorMode == WS_HMD) // HMD locked { VectorCopy(vr->hmdorientation, cg.weaponSelectorAngles); VectorCopy(vr->hmdposition, cg.weaponSelectorOrigin); VectorClear(cg.weaponSelectorOffset); DEPTH = 7.0f; RAD = 4.0f; } float SCALE = 0.05f; const float SEP = 360.0f / (WP_NUM_WEAPONS - 2); // Exclude grappling hook float frac = (cg.time - cg.weaponSelectorTime) / (20 * DEPTH); if (frac > 1.0f) frac = 1.0f; vec3_t controllerOrigin, controllerAngles, controllerOffset, selectorOrigin; CG_CalculateVRWeaponPosition(controllerOrigin, controllerAngles); VectorSubtract(vr->weaponposition, cg.weaponSelectorOrigin, controllerOffset); vec3_t holsterAngles, holsterOrigin, beamOrigin, holsterForward, holsterRight, holsterUp; CG_CalculateVRPositionInWorld(cg.weaponSelectorOrigin, cg.weaponSelectorOffset, cg.weaponSelectorAngles, holsterOrigin, holsterAngles); AngleVectors(holsterAngles, holsterForward, holsterRight, holsterUp); if (selectorMode == WS_CONTROLLER) { VectorCopy(controllerOrigin, holsterOrigin); } else { VectorMA(holsterOrigin, -2.0f, holsterUp, holsterOrigin); } VectorCopy(holsterOrigin, beamOrigin); VectorMA(holsterOrigin, ((DEPTH*2.0f)*((selectorMode == WS_CONTROLLER) ? frac : 1.0f)), holsterForward, holsterOrigin); VectorCopy(holsterOrigin, selectorOrigin); float x = 0.0f; float y = 0.0f; if (selectorMode == WS_CONTROLLER) { x = (sinf(DEG2RAD(holsterAngles[YAW] - controllerAngles[YAW])) / sinf(DEG2RAD(22.5f))); y = ((holsterAngles[PITCH] - controllerAngles[PITCH]) / 22.5f); float len = length(x, y); if (len > 1.0f) { x *= (1.0f / len); y *= (1.0f / len); } } else //selectorMode == WS_HMD { if (length(vr->thumbstick_location[0], vr->thumbstick_location[1]) > 0.95f) { float a = atan2(vr->thumbstick_location[0], vr->thumbstick_location[1]); x = sinf(a) * 0.95f; y = cosf(a) * 0.95f; } } VectorMA(selectorOrigin, RAD * x, holsterRight, selectorOrigin); VectorMA(selectorOrigin, RAD * y, holsterUp, selectorOrigin); { refEntity_t blob; memset( &blob, 0, sizeof( blob ) ); VectorCopy( selectorOrigin, blob.origin ); AnglesToAxis(vec3_origin, blob.axis); VectorScale( blob.axis[0], SCALE - 0.01f, blob.axis[0] ); VectorScale( blob.axis[1], SCALE - 0.01f, blob.axis[1] ); VectorScale( blob.axis[2], SCALE - 0.01f, blob.axis[2] ); blob.nonNormalizedAxes = qtrue; blob.hModel = cgs.media.smallSphereModel; trap_R_AddRefEntityToScene( &blob ); if (selectorMode == WS_CONTROLLER) { byte colour[4]; colour[0] = 0x00; colour[1] = 0x00; colour[2] = 0xff; colour[3] = 0x40; CG_LaserSight(beamOrigin, selectorOrigin, colour, 0.1f); } } qboolean selected = qfalse; int w = 1; for (int c = 0; c < WP_NUM_WEAPONS-2; ++c, ++w) { if (w == WP_GRAPPLING_HOOK) { continue; } CG_RegisterWeapon(w); { qboolean selectable = CG_WeaponSelectable(w) && cg.snap->ps.ammo[ w ]; //first calculate holster slot position vec3_t angles, iconOrigin,iconBackground,iconForeground; VectorClear(angles); angles[YAW] = holsterAngles[YAW]; angles[PITCH] = holsterAngles[PITCH]; angles[ROLL] = AngleNormalize360((w * SEP)); vec3_t forward, up; AngleVectors(angles, forward, NULL, up); VectorMA(holsterOrigin, (RAD*frac), up, iconOrigin); VectorMA(iconOrigin, 0.1f, forward, iconBackground); VectorMA(iconOrigin, -0.1f, forward, iconForeground); //Float sprite above selected weapon vec3_t diff; VectorSubtract(selectorOrigin, iconOrigin, diff); float length = VectorLength(diff); if (length <= 1.5f && frac == 1.0f && selectable) { if (cg.weaponSelectorSelection != w) { cg.weaponSelectorSelection = w; trap_HapticEvent("selector_icon", 0, 0, 100, 0, 0); } selected = qtrue; } if (cg.weaponSelectorSelection == w) { refEntity_t sprite; memset( &sprite, 0, sizeof( sprite ) ); VectorCopy( iconOrigin, sprite.origin ); sprite.origin[2] += 2.5f + (0.5f * sinf(DEG2RAD(AngleNormalize360((cg.time - cg.weaponSelectorTime)/4)))); sprite.reType = RT_SPRITE; sprite.customShader = cgs.media.friendShader; sprite.radius = 0.5f; sprite.shaderRGBA[0] = 255; sprite.shaderRGBA[1] = 255; sprite.shaderRGBA[2] = 255; sprite.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene( &sprite ); } if (!cg_weaponSelectorSimple2DIcons.integer) { /* if (selectable) { refEntity_t blob; memset( &blob, 0, sizeof( blob ) ); VectorCopy( iconOrigin, blob.origin ); AnglesToAxis(vec3_origin, blob.axis); float sphereScale = (SCALE*frac) + 0.05f + (cg.weaponSelectorSelection == w ? 0.035f : 0); VectorScale( blob.axis[0], sphereScale, blob.axis[0] ); VectorScale( blob.axis[1], sphereScale, blob.axis[1] ); VectorScale( blob.axis[2], sphereScale, blob.axis[2] ); blob.nonNormalizedAxes = qtrue; blob.hModel = cgs.media.smallSphereModel; blob.customShader = cgs.media.invisShader; trap_R_AddRefEntityToScene( &blob ); }*/ refEntity_t ent; memset(&ent, 0, sizeof(ent)); VectorCopy(iconOrigin, ent.origin); //Shift the weapon model a bit to be in the sphere if (w == WP_GAUNTLET) { SCALE = 0.065f; VectorMA(ent.origin, 0.3f, holsterUp, ent.origin); VectorMA(ent.origin, 0.15f, holsterRight, ent.origin); VectorMA(ent.origin, -0.15f, holsterForward, ent.origin); } else { VectorMA(ent.origin, 0.3f, holsterForward, ent.origin); VectorMA(ent.origin, -0.2f, holsterRight, ent.origin); VectorMA(ent.origin, 0.5f, holsterUp, ent.origin); } vec3_t iconAngles; VectorCopy(holsterAngles, iconAngles); iconAngles[PITCH] = 10; iconAngles[YAW] -= 145.0f; if (w == WP_GAUNTLET) { iconAngles[ROLL] -= 90.0f; } float weaponScale = ((SCALE+0.02f)*frac) + (cg.weaponSelectorSelection == w ? 0.04f : 0); AnglesToAxis(iconAngles, ent.axis); VectorScale(ent.axis[0], weaponScale, ent.axis[0]); VectorScale(ent.axis[1], weaponScale, ent.axis[1]); VectorScale(ent.axis[2], weaponScale, ent.axis[2]); ent.nonNormalizedAxes = qtrue; if( w == WP_RAILGUN ) { clientInfo_t *ci = &cgs.clientinfo[cg.predictedPlayerState.clientNum]; if( cg_entities[cg.predictedPlayerState.clientNum].pe.railFireTime + 1500 > cg.time ) { int scale = 255 * ( cg.time - cg_entities[cg.predictedPlayerState.clientNum].pe.railFireTime ) / 1500; ent.shaderRGBA[0] = ( ci->c1RGBA[0] * scale ) >> 8; ent.shaderRGBA[1] = ( ci->c1RGBA[1] * scale ) >> 8; ent.shaderRGBA[2] = ( ci->c1RGBA[2] * scale ) >> 8; ent.shaderRGBA[3] = 255; } else { Byte4Copy( ci->c1RGBA, ent.shaderRGBA ); } } ent.hModel = cg_weapons[w].weaponModel; if (!selectable) { ent.customShader = cgs.media.invisShader; } trap_R_AddRefEntityToScene(&ent); if ( cg_weapons[w].barrelModel ) { refEntity_t barrel; memset(&barrel, 0, sizeof(barrel)); barrel.hModel = cg_weapons[w].barrelModel; vec3_t barrelAngles; VectorClear(barrelAngles); barrelAngles[ROLL] = AngleNormalize360((cg.time - cg.weaponSelectorTime) * 0.9f); AnglesToAxis(barrelAngles, barrel.axis); CG_PositionRotatedEntityOnTag(&barrel, &ent, cg_weapons[w].weaponModel, "tag_barrel"); if (!selectable) { barrel.customShader = cgs.media.invisShader; } trap_R_AddRefEntityToScene(&barrel); } } else { refEntity_t sprite; memset( &sprite, 0, sizeof( sprite ) ); VectorCopy(iconOrigin, sprite.origin); sprite.reType = RT_SPRITE; sprite.customShader = cg_weapons[w].weaponIcon; sprite.radius = 0.6f + (cg.weaponSelectorSelection == w ? 0.1f : 0); sprite.shaderRGBA[0] = 255; sprite.shaderRGBA[1] = 255; sprite.shaderRGBA[2] = 255; sprite.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene(&sprite); //And now the selection background memset( &sprite, 0, sizeof( sprite ) ); VectorCopy( iconBackground, sprite.origin ); sprite.reType = RT_SPRITE; sprite.customShader = cgs.media.selectShader; sprite.radius = 0.7f + (cg.weaponSelectorSelection == w ? 0.1f : 0); sprite.shaderRGBA[0] = 255; sprite.shaderRGBA[1] = 255; sprite.shaderRGBA[2] = 255; sprite.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene( &sprite ); if (!selectable) { memset(&sprite, 0, sizeof(sprite)); VectorCopy(iconForeground, sprite.origin); sprite.reType = RT_SPRITE; sprite.customShader = cgs.media.noammoShader; sprite.radius = 0.7f; sprite.shaderRGBA[0] = 255; sprite.shaderRGBA[1] = 255; sprite.shaderRGBA[2] = 255; sprite.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene(&sprite); } } } } //Only reset selection if using controller pointer if (!selected && selectorMode == WS_CONTROLLER) { cg.weaponSelectorSelection = WP_NONE; } } /* =================== CG_OutOfAmmoChange The current weapon has just run out of ammo =================== */ void CG_OutOfAmmoChange( void ) { int i; cg.weaponSelectTime = cg.time; for ( i = MAX_WEAPONS-1 ; i > 0 ; i-- ) { 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 ) { entityState_t *ent; int c; 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_LIGHTNING ) { if ( cent->pe.lightningFiring ) { return; } } if( ent->weapon == WP_RAILGUN ) { cent->pe.railFireTime = cg.time; } // play quad sound if needed if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); } // play a sound for ( c = 0 ; c < 4 ; c++ ) { if ( !weap->flashSound[c] ) { break; } } if ( c > 0 ) { c = rand() % c; if ( weap->flashSound[c] ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); } } // do brass ejection if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { weap->ejectBrassFunc( cent ); } //Are we the player? if (cent->currentState.number == cg.predictedPlayerState.clientNum) { int position = vr->weapon_stabilised ? 4 : (vr->right_handed ? 1 : 2); static int haptic_skip = 0; // This is to adjust the excessive fire rate of the plasma-gun/machine-gun, everything else fires slower (or has haptics that compensate) // so this will just fire every other time for the affected weapons ++haptic_skip; //Haptics switch (ent->weapon) { case WP_GAUNTLET: trap_HapticEvent("chainsaw_fire", position, 0, 50, 0, 0); break; case WP_MACHINEGUN: if (haptic_skip & 1) { trap_HapticEvent("machinegun_fire", position, 0, 100, 0, 0); } break; case WP_SHOTGUN: trap_HapticEvent("shotgun_fire", position, 0, 100, 0, 0); break; case WP_GRENADE_LAUNCHER: trap_HapticEvent("handgrenade_fire", position, 0, 80, 0, 0); break; case WP_ROCKET_LAUNCHER: trap_HapticEvent("rocket_fire", position, 0, 100, 0, 0); break; case WP_LIGHTNING: trap_HapticEvent("RTCWQuest:fire_tesla", position, 0, 100, 0, 0); break; case WP_RAILGUN: trap_HapticEvent("RTCWQuest:fire_sniper", position, 0, 100, 0, 0); break; case WP_PLASMAGUN: if (haptic_skip & 1) { trap_HapticEvent("plasmagun_fire", position, 0, 100, 0, 0); } break; case WP_BFG: trap_HapticEvent("bfg_fire", position, 0, 100, 0, 0); break; case WP_GRAPPLING_HOOK: trap_HapticEvent("chainsaw_fire", position, 0, 100, 0, 0); break; #ifdef MISSIONPACK case WP_NAILGUN: trap_HapticEvent("shotgun_fire", position, 0, 100, 0, 0); break; case WP_PROX_LAUNCHER: trap_HapticEvent("handgrenade_fire", position, 0, 100, 0, 0); break; case WP_CHAINGUN: if (haptic_skip & 1) { trap_HapticEvent("chaingun_fire", position, 0, 100, 0, 0); } break; #endif } } } /* ================= CG_MissileHitWall Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ================= */ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { qhandle_t mod; qhandle_t mark; qhandle_t shader; sfxHandle_t sfx; float radius; float light; vec3_t lightColor; localEntity_t *le; int r; qboolean alphaFade; qboolean isSprite; int duration; vec3_t sprOrg; vec3_t sprVel; mod = 0; shader = 0; light = 0; lightColor[0] = 1; lightColor[1] = 1; lightColor[2] = 0; // set defaults isSprite = qfalse; duration = 600; switch ( weapon ) { default: #ifdef MISSIONPACK case WP_NAILGUN: if( soundType == IMPACTSOUND_FLESH ) { sfx = cgs.media.sfx_nghitflesh; } else if( soundType == IMPACTSOUND_METAL ) { sfx = cgs.media.sfx_nghitmetal; } else { sfx = cgs.media.sfx_nghit; } mark = cgs.media.holeMarkShader; radius = 12; break; #endif case WP_LIGHTNING: // no explosion at LG impact, it is added with the beam r = rand() & 3; if ( r < 2 ) { sfx = cgs.media.sfx_lghit2; } else if ( r == 2 ) { sfx = cgs.media.sfx_lghit1; } else { sfx = cgs.media.sfx_lghit3; } mark = cgs.media.holeMarkShader; radius = 12; break; #ifdef MISSIONPACK case WP_PROX_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.grenadeExplosionShader; sfx = cgs.media.sfx_proxexp; mark = cgs.media.burnMarkShader; radius = 64; light = 300; isSprite = qtrue; break; #endif case WP_GRENADE_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.grenadeExplosionShader; sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 64; light = 300; isSprite = qtrue; break; case WP_ROCKET_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.rocketExplosionShader; sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 64; light = 300; isSprite = qtrue; duration = 1000; lightColor[0] = 1; lightColor[1] = 0.75; lightColor[2] = 0.0; if (cg_oldRocket.integer == 0) { // explosion sprite animation VectorMA( origin, 24, dir, sprOrg ); VectorScale( dir, 64, sprVel ); CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); } break; case WP_RAILGUN: mod = cgs.media.ringFlashModel; shader = cgs.media.railExplosionShader; //sfx = cgs.media.sfx_railg; sfx = cgs.media.sfx_plasmaexp; mark = cgs.media.energyMarkShader; radius = 24; break; case WP_PLASMAGUN: mod = cgs.media.ringFlashModel; shader = cgs.media.plasmaExplosionShader; sfx = cgs.media.sfx_plasmaexp; mark = cgs.media.energyMarkShader; radius = 16; break; case WP_BFG: mod = cgs.media.dishFlashModel; shader = cgs.media.bfgExplosionShader; sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 32; isSprite = qtrue; break; case WP_SHOTGUN: mod = cgs.media.bulletFlashModel; shader = cgs.media.bulletExplosionShader; mark = cgs.media.bulletMarkShader; sfx = 0; radius = 4; break; #ifdef MISSIONPACK case WP_CHAINGUN: mod = cgs.media.bulletFlashModel; if( soundType == IMPACTSOUND_FLESH ) { sfx = cgs.media.sfx_chghitflesh; } else if( soundType == IMPACTSOUND_METAL ) { sfx = cgs.media.sfx_chghitmetal; } else { sfx = cgs.media.sfx_chghit; } mark = cgs.media.bulletMarkShader; radius = 8; break; #endif case WP_MACHINEGUN: mod = cgs.media.bulletFlashModel; shader = cgs.media.bulletExplosionShader; mark = cgs.media.bulletMarkShader; r = rand() & 3; if ( r == 0 ) { sfx = cgs.media.sfx_ric1; } else if ( r == 1 ) { sfx = cgs.media.sfx_ric2; } else { sfx = cgs.media.sfx_ric3; } radius = 8; 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 ); if ( weapon == WP_RAILGUN ) { // colorize with client color VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); le->refEntity.shaderRGBA[0] = le->color[0] * 0xff; le->refEntity.shaderRGBA[1] = le->color[1] * 0xff; le->refEntity.shaderRGBA[2] = le->color[2] * 0xff; le->refEntity.shaderRGBA[3] = 0xff; } } // // impact mark // alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color if ( weapon == WP_RAILGUN ) { float *color; // colorize with client color color = cgs.clientinfo[clientNum].color1; CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); } else { CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); } } /* ================= CG_MissileHitPlayer ================= */ void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { CG_Bleed( origin, entityNum ); if ( entityNum == vr->clientNum ) { trap_HapticEvent("fireball", 0, 0, 80, 0, 0); } // some weapons will make an explosion with the blood, while // others will just make the blood switch ( weapon ) { case WP_GRENADE_LAUNCHER: case WP_ROCKET_LAUNCHER: case WP_PLASMAGUN: case WP_BFG: #ifdef MISSIONPACK case WP_NAILGUN: case WP_CHAINGUN: case WP_PROX_LAUNCHER: #endif CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); break; default: break; } } /* ============================================================================ SHOTGUN TRACING ============================================================================ */ /* ================ CG_ShotgunPellet ================ */ static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { trace_t tr; int sourceContentType, destContentType; CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); sourceContentType = CG_PointContents( start, 0 ); destContentType = CG_PointContents( tr.endpos, 0 ); // FIXME: should probably move this cruft into CG_BubbleTrail if ( sourceContentType == destContentType ) { if ( sourceContentType & CONTENTS_WATER ) { CG_BubbleTrail( start, tr.endpos, 32 ); } } else if ( sourceContentType & CONTENTS_WATER ) { trace_t trace; trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( start, trace.endpos, 32 ); } else if ( destContentType & CONTENTS_WATER ) { trace_t trace; trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); } if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; } if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum ); } else { if ( tr.surfaceFlags & SURF_NOIMPACT ) { // SURF_NOIMPACT will not make a flame puff or a mark return; } if ( tr.surfaceFlags & SURF_METALSTEPS ) { CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); } else { CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); } } } /* ================ CG_ShotgunPattern Perform the same traces the server did to locate the hit splashes ================ */ static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) { int i; float r, u; vec3_t end; vec3_t forward, right, up; // derive the right and up vectors from the forward vector, because // the client won't have any other information VectorNormalize2( origin2, forward ); PerpendicularVector( right, forward ); CrossProduct( forward, right, up ); // generate the "random" spread pattern for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; VectorMA( origin, 8192 * 16, forward, end); VectorMA (end, r, right, end); VectorMA (end, u, up, end); CG_ShotgunPellet( origin, end, otherEntNum ); } } /* ============== CG_ShotgunFire ============== */ void CG_ShotgunFire( entityState_t *es ) { vec3_t v; int contents; VectorSubtract( es->origin2, es->pos.trBase, v ); VectorNormalize( v ); VectorScale( v, 32, v ); VectorAdd( es->pos.trBase, v, v ); if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { // ragepro can't alpha fade, so don't even bother with smoke vec3_t up; contents = CG_PointContents( es->pos.trBase, 0 ); if ( !( contents & CONTENTS_WATER ) ) { VectorSet( up, 0, 0, 8 ); CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); } } CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); } /* ============================================================================ BULLETS ============================================================================ */ /* =============== CG_Tracer =============== */ void CG_Tracer( vec3_t source, vec3_t dest ) { vec3_t forward, right; polyVert_t verts[4]; vec3_t line; float len, begin, end; vec3_t start, finish; vec3_t midpoint; // tracer VectorSubtract( dest, source, forward ); len = VectorNormalize( forward ); // start at least a little ways from the muzzle if ( len < 100 ) { return; } begin = 50 + random() * (len - 60); end = begin + cg_tracerLength.value; if ( end > len ) { end = len; } VectorMA( source, begin, forward, start ); VectorMA( source, end, forward, finish ); line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); VectorScale( cg.refdef.viewaxis[1], line[1], right ); VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); VectorNormalize( right ); VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); verts[0].st[0] = 0; verts[0].st[1] = 1; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); verts[1].st[0] = 1; verts[1].st[1] = 0; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); verts[3].st[0] = 0; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); midpoint[0] = ( start[0] + finish[0] ) * 0.5; midpoint[1] = ( start[1] + finish[1] ) * 0.5; midpoint[2] = ( start[2] + finish[2] ) * 0.5; // add the tracer sound trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); } /* ====================== CG_CalcMuzzlePoint ====================== */ static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { vec3_t forward; centity_t *cent; int anim; if ( entityNum == cg.snap->ps.clientNum ) { VectorCopy( cg.snap->ps.origin, muzzle ); muzzle[2] += cg.snap->ps.viewheight; AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } cent = &cg_entities[entityNum]; if ( !cent->currentValid ) { return qfalse; } VectorCopy( cent->currentState.pos.trBase, muzzle ); AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { muzzle[2] += CROUCH_VIEWHEIGHT; } else { muzzle[2] += DEFAULT_VIEWHEIGHT; } VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } /* ====================== CG_Bullet Renders bullet effects. ====================== */ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { trace_t trace; int sourceContentType, destContentType; vec3_t start; // if the shooter is currently valid, calc a source point and possibly // do trail effects if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { sourceContentType = 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, 32 ); } // bubble trail from water into air else if ( ( sourceContentType & CONTENTS_WATER ) ) { trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( start, trace.endpos, 32 ); } // bubble trail from air into water else if ( ( destContentType & CONTENTS_WATER ) ) { trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( trace.endpos, end, 32 ); } // draw a tracer if ( random() < cg_tracerChance.value ) { CG_Tracer( start, end ); } } } // impact splash and mark if ( flesh ) { CG_Bleed( end, fleshEntityNum ); } else { CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); } }