reaction/code/cgame/cg_weapons.c
2013-01-04 13:27:22 +00:00

3487 lines
99 KiB
C

//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.120 2005/02/15 16:33:38 makro
// Tons of updates (entity tree attachment system, UI vectors)
//
// Revision 1.119 2004/01/26 21:26:08 makro
// no message
//
// Revision 1.118 2003/07/30 16:05:46 makro
// no message
//
// Revision 1.117 2003/04/26 15:23:57 jbravo
// grenade replacement fix. Version bumped to 3.1
//
// Revision 1.116 2003/04/13 21:58:00 slicer
// Added a Zoom Sensitivity Lock during fire sequence of ssg; removed unnecessary code
//
// Revision 1.115 2003/04/06 23:20:18 niceass
// changes to what models 3rd person akimbos use
//
// Revision 1.114 2003/04/04 16:38:47 niceass
// fixed gun2 lighting, possibly
//
// Revision 1.113 2003/04/02 22:23:51 jbravo
// More replacements tweaks. Added zcam_stfu
//
// Revision 1.112 2003/04/02 17:58:03 jbravo
// Ammo skin only replacements now work.
//
// Revision 1.111 2003/03/29 23:04:56 jbravo
// More post _skin cvar fixes
//
// Revision 1.110 2003/03/29 18:53:41 jbravo
// Fixed ammo bug when dropping bandolier. Added color to more errormessages
//
// Revision 1.109 2003/03/29 17:00:11 jbravo
// More replacement hiccups after skin cvars where removed
//
// Revision 1.108 2003/03/29 15:04:03 jbravo
// Removing skin replacement cvars
//
// Revision 1.107 2003/03/28 10:36:02 jbravo
// Tweaking the replacement system a bit. Reactionmale now the default model
//
// Revision 1.106 2003/03/09 21:30:38 jbravo
// Adding unlagged. Still needs work.
//
// Revision 1.105 2003/03/08 10:04:12 niceass
// small code change. should make no difference
//
// Revision 1.104 2003/02/05 04:44:47 niceass
// added support for tag_weapon2 (2 pistols for akimbo)
//
// Revision 1.103 2003/02/01 02:15:31 jbravo
// Replacement models and items
//
// Revision 1.102 2003/01/08 04:46:26 jbravo
// Wrote a new hackish model replacement system
//
// Revision 1.101 2002/12/05 23:11:29 blaze
// Added item replacement code
//
// Revision 1.100 2002/12/02 19:52:05 niceass
// water pressure & shell ejection stuff
//
// Revision 1.99 2002/11/18 04:39:47 jbravo
// Cleanup of cg_weapons.c
//
// Revision 1.98 2002/10/26 00:37:18 jbravo
// New multiple item code and added PB support to the UI
//
// Revision 1.97 2002/10/20 21:24:32 blaze
// Added cg_RQ3_gunname cvars. These can be used to choose weapon replacements. You will need 3 icons that I will upload to the ftp. These should go in the next pk3
//
// Revision 1.96 2002/09/09 06:40:35 niceass
// smoke puff less intrusive
//
// Revision 1.95 2002/09/08 23:23:49 niceass
// no first person pistol action when firing in semi-auto with predictweapons 0 fixed
//
// Revision 1.94 2002/09/08 03:14:17 niceass
// tracerchance now can be 0.0 to 0.4
//
// Revision 1.93 2002/08/29 04:42:41 blaze
// Anti OGC code
//
// Revision 1.92 2002/08/25 03:34:11 niceass
// puff added to silencer
//
// Revision 1.91 2002/07/22 06:30:12 niceass
// cleaned up the powerup code
//
// Revision 1.90 2002/07/13 22:42:18 makro
// Semi-working fog hull, semi-working sky portals (cgame code commented out)
// Basically, semi-working stuff :P
//
// Revision 1.89 2002/07/08 04:41:12 niceass
// no more shell ejection on LCA, I hope
//
// Revision 1.88 2002/06/29 21:57:18 niceass
// shotgun fixes
//
// Revision 1.87 2002/06/29 20:56:23 niceass
// Change to the way HC/Shotty act
//
// Revision 1.86 2002/06/29 04:15:15 jbravo
// CTF is now CTB. no weapons while the case is in hand other than pistol or knife
//
// Revision 1.85 2002/06/23 04:34:54 niceass
// change to foglaser
//
// Revision 1.84 2002/06/21 04:09:44 niceass
// fog laser
//
// Revision 1.83 2002/06/17 03:53:31 niceass
// m4 muzzle flash is smaller
//
// Revision 1.82 2002/06/16 20:06:13 jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.81 2002/06/16 19:12:52 jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
// Revision 1.80 2002/06/12 17:05:14 slicer
// Fixed a couple of problems related to the sniper zooming
//
// Revision 1.79 2002/06/11 21:50:51 niceass
// HC changes
//
// Revision 1.78 2002/06/06 00:29:14 blaze
// got rid of the out of ammo shader
//
// Revision 1.77 2002/06/05 09:40:10 niceass
// tracer fix
//
// Revision 1.76 2002/06/03 01:09:33 niceass
// silencer scale changes
//
// Revision 1.75 2002/05/29 03:52:21 niceass
// LAM code
//
// Revision 1.74 2002/05/27 17:47:19 jbravo
// Fixes and cleanups
//
// Revision 1.73 2002/05/27 06:58:40 niceass
// removed reflection stuff. Silencer stuff added
//
// Revision 1.72 2002/05/18 03:55:35 niceass
// many misc. changes
//
// Revision 1.71 2002/05/12 14:39:48 makro
// Wood, brick & ceramic impact sounds
//
// Revision 1.70 2002/05/09 06:40:37 niceass
// New tracers
//
// Revision 1.69 2002/05/01 17:46:00 slicer
// Only calling "unzoom" when switching from ssg ( less lag )
//
// Revision 1.68 2002/05/01 03:28:07 niceass
// mp5 scale change for silencer
//
// Revision 1.67 2002/04/29 06:10:09 niceass
// visible silencer
//
// Revision 1.66 2002/04/07 02:10:42 niceass
// shell ejection bug fixed
//
// Revision 1.65 2002/04/06 21:43:59 makro
// New surfaceparm system
//
// Revision 1.64 2002/04/05 18:53:26 jbravo
// Warning fixes
//
// Revision 1.63 2002/04/03 03:13:49 blaze
// NEW BREAKABLE CODE - will break all old breakables(wont appear in maps)
//
// Revision 1.62 2002/04/01 22:23:38 slicer
// Added "weapon" command buffering
//
// Revision 1.61 2002/03/31 13:52:48 jbravo
// More cleanups
//
// Revision 1.60 2002/03/31 03:31:24 jbravo
// Compiler warning cleanups
//
// Revision 1.59 2002/03/31 02:00:42 niceass
// shell stuff
//
// Revision 1.58 2002/03/24 22:46:23 niceass
// shell ejection stuff/handcannon fix
//
// Revision 1.57 2002/03/21 02:17:39 blaze
// more func_explosive goodness
//
// Revision 1.56 2002/03/21 00:26:11 blaze
// some fixing of func_explosive
//
// Revision 1.55 2002/03/18 19:19:08 slicer
// Fixed bandage bugs ( i hope )
//
// Revision 1.54 2002/03/16 21:48:39 niceass
// All new shell ejection code
//
// Revision 1.53 2002/03/13 18:41:18 slicer
// Adjusted some of elder's unzoom code for the new sniper system ( server side )
//
// Revision 1.52 2002/03/11 03:25:41 jbravo
// Minor fix for NA
//
// Revision 1.51 2002/03/11 02:22:45 niceass
// tag shell ejections
//
// Revision 1.50 2002/02/24 20:49:07 slicer
// Follow Spect Players- Draws the gun
//
// Revision 1.49 2002/02/23 18:07:46 slicer
// Changed Sniper code and Cam code
//
// Revision 1.48 2002/02/08 18:00:34 jbravo
// Fixing "No newline at end of file" Warnings Linux keeps giving me
//
// Revision 1.47 2002/01/14 01:19:23 niceass
// No more default 800 gravity on items - NiceAss
//
// Revision 1.46 2002/01/12 15:18:20 jbravo
// Added a fix from Niceass. (cg_drawgun affecting other player models)
//
// Revision 1.45 2002/01/11 19:48:29 jbravo
// Formatted the source in non DOS format.
//
// Revision 1.44 2001/12/31 16:28:41 jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// Copyright (C) 1999-2000 Id Software, Inc.
//
// cg_weapons.c -- events and effects dealing with weapons
#include "cg_local.h"
/*
* ============
* COM_StripExtensionInPlace
* ============
* */
void COM_StripExtensionInPlace(char *name)
{
char* ext = strrchr(name, '.');
if (ext)
*ext = 0;
}
/*
==========================
CG_ParseWeaponSoundFile
Added by Elder
Reads information for frame-sound timing
==========================
*/
static qboolean CG_ParseWeaponSoundFile(const char *filename, weaponInfo_t * weapon)
{
char *text_p;
int len, i; // skip;
char *token;
char text[20000];
fileHandle_t f;
sfxWeapTiming_t *weapTiming;
weapTiming = weapon->animationSounds;
// load the file
len = trap_FS_FOpenFile(filename, &f, FS_READ);
if (len <= 0) {
return qfalse;
}
if (len >= sizeof(text) - 1) {
CG_Printf("File %s too long\n", filename);
return qfalse;
}
trap_FS_Read(text, len, f);
text[len] = 0;
trap_FS_FCloseFile(f);
// parse the text
text_p = text;
// Elder: uhh, what was this for?
// skip = 0; // quite the compiler warning
for (i = 0; i < MAX_ANIM_SOUNDS; i++) {
// Grab frame number
token = COM_Parse(&text_p);
if (!token)
break;
// Add it to the array
if (atoi(token)) {
weapTiming->sfxInfo[i].frame = atoi(token);
} else
break;
// Grab sound file path
token = COM_Parse(&text_p);
if (!token)
break;
weapTiming->sfxInfo[i].sound = trap_S_RegisterSound(token, qfalse);
}
// Store total number
weapTiming->numFrames = i;
return qtrue;
}
/*
======================
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;
}
/* [QUARANTINE] - Weapon Animations - CG_ParseWeaponAnimFile
==========================
CG_ParseWeaponAnimFile
==========================
*/
static qboolean CG_ParseWeaponAnimFile(const char *filename, weaponInfo_t * weapon)
{
char *text_p, *token;
int len, i; // skip;
float fps;
char text[20000];
fileHandle_t f;
animation_t *animations;
animations = weapon->animations;
// load the file
len = trap_FS_FOpenFile(filename, &f, FS_READ);
if (len <= 0) {
return qfalse;
}
if (len >= sizeof(text) - 1) {
CG_Printf("File %s too long\n", filename);
return qfalse;
}
trap_FS_Read(text, len, f);
text[len] = 0;
trap_FS_FCloseFile(f);
// parse the text
text_p = text;
// skip = 0; // quite the compiler warning
// read information for each frame
for (i = 0; i < MAX_WEAPON_ANIMATIONS; i++) {
token = COM_Parse(&text_p);
if (!token)
break;
animations[i].firstFrame = atoi(token);
token = COM_Parse(&text_p);
if (!token)
break;
animations[i].numFrames = atoi(token);
token = COM_Parse(&text_p);
if (!token)
break;
animations[i].loopFrames = atoi(token);
token = COM_Parse(&text_p);
if (!token)
break;
fps = atof(token);
if (fps == 0)
fps = 1;
animations[i].frameLerp = 1000 / fps;
animations[i].initialLerp = 1000 / fps;
}
if (i != MAX_WEAPON_ANIMATIONS) {
CG_Printf("Error parsing weapon animation file: %s", filename);
return qfalse;
}
return qtrue;
}
// END
/*
==========================
CG_MachineGunEjectBrass
==========================
*/
localEntity_t *CG_MachineGunEjectBrass(centity_t * cent)
{
localEntity_t *le;
refEntity_t *re;
if (cg_brassTime.integer <= 0) {
return NULL;
}
le = CG_AllocLocalEntity();
re = &le->refEntity;
le->leType = LE_FRAGMENT;
le->startTime = cg.time;
le->endTime = le->startTime + cg_brassTime.integer + (cg_brassTime.integer / 4) * random();
le->pos.trType = TR_GRAVITY;
le->pos.trTime = cg.time - (rand() & 15);
AxisCopy(axisDefault, re->axis);
le->bounceFactor = 0.5;
if (cent->currentState.weapon == WP_M4 || cent->currentState.weapon == WP_SSG3000) {
re->hModel = cgs.media.largeBrassModel;
le->bounceFactor *= 0.75;
} else
re->hModel = cgs.media.machinegunBrassModel;
le->angles.trType = TR_LINEAR;
le->angles.trTime = cg.time;
le->angles.trDelta[0] = rand() % 200 - 100;
le->angles.trDelta[1] = rand() % 200 - 100;
le->angles.trDelta[2] = rand() % 200 - 100;
le->leFlags = LEF_TUMBLE;
le->leBounceSoundType = LEBS_BRASS;
le->leMarkType = LEMT_NONE;
return le;
}
/*
==========================
CG_ShotgunEjectBrass
==========================
*/
localEntity_t *CG_ShotgunEjectBrass(centity_t * cent)
{
localEntity_t *le;
refEntity_t *re;
int isHC;
if (cent->currentState.weapon == WP_HANDCANNON)
isHC = 1;
else
isHC = 0;
if (cg_brassTime.integer <= 0) {
return NULL;
}
le = CG_AllocLocalEntity();
re = &le->refEntity;
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;
AxisCopy(axisDefault, re->axis);
if (isHC)
re->hModel = cgs.media.HCBrassModel;
else
re->hModel = cgs.media.shotgunBrassModel;
le->bounceFactor = 0.3f;
le->angles.trType = TR_LINEAR;
le->angles.trTime = cg.time;
le->angles.trDelta[0] = rand() % 200 - 100;
le->angles.trDelta[1] = rand() % 200 - 100;
le->angles.trDelta[2] = rand() % 200 - 100;
le->leFlags = LEF_TUMBLE;
le->leBounceSoundType = LEBS_BRASS;
le->leMarkType = LEMT_NONE;
return le;
}
/*
==========================
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;
VectorCopy(start, move);
VectorSubtract(end, start, vec);
len = VectorNormalize(vec);
PerpendicularVector(temp, vec);
for (i = 0; i < 36; i++) {
RotatePointAroundVector(axis[i], vec, temp, i * 10); //banshee 2.4 was 10
}
le = CG_AllocLocalEntity();
re = &le->refEntity;
le->leType = LE_FADE_RGB;
le->startTime = cg.time;
le->endTime = cg.time + cg_railTrailTime.value;
le->lifeRate = 1.0 / (le->endTime - le->startTime);
re->shaderTime = cg.time / 1000.0f;
re->reType = RT_RAIL_CORE;
re->customShader = cgs.media.railCoreShader;
VectorCopy(start, re->origin);
VectorCopy(end, re->oldorigin);
re->shaderRGBA[0] = ci->color1[0] * 255;
re->shaderRGBA[1] = ci->color1[1] * 255;
re->shaderRGBA[2] = ci->color1[2] * 255;
re->shaderRGBA[3] = 255;
le->color[0] = ci->color1[0] * 0.75;
le->color[1] = ci->color1[1] * 0.75;
le->color[2] = ci->color1[2] * 0.75;
le->color[3] = 1.0f;
AxisClear(re->axis);
VectorMA(move, 20, vec, move);
// VectorCopy(move, next_move);
VectorScale(vec, SPACING, vec);
if (cg_oldRail.integer != 0) {
// nudge down a bit so it isn't exactly in center
re->origin[2] -= 8;
re->oldorigin[2] -= 8;
return;
}
skip = -1;
j = 18;
for (i = 0; i < len; i += SPACING) {
if (i != skip) {
skip = i + SPACING;
le = CG_AllocLocalEntity();
re = &le->refEntity;
le->leFlags = LEF_PUFF_DONT_SCALE;
le->leType = LE_MOVE_SCALE_FADE;
le->startTime = cg.time;
le->endTime = cg.time + (i >> 1) + 600;
le->lifeRate = 1.0 / (le->endTime - le->startTime);
re->shaderTime = cg.time / 1000.0f;
re->reType = RT_SPRITE;
re->radius = 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 ? j + ROTATION : (j + ROTATION) % 36;
}
}
/*
==========================
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;
CG_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_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], str[MAX_STRING_CHARS];
char *model, *skin;
vec3_t mins, maxs;
// QUARANTINE - Weapon Animations - Added Variable
char filename[MAX_QPATH]; //Used to open animation.cfg files
// END
int i;
qboolean weapAnimLoad = qtrue;
if (weaponNum == 0 || weaponNum >= MAX_WEAPONS) {
return;
}
weaponInfo = &cg_weapons[weaponNum];
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;
}
//Elder: hack for handcannon to use M3 ammo icon
else if (weaponNum == WP_HANDCANNON && ammo->giType == IT_AMMO && ammo->giTag == WP_M3) {
break;
}
//Elder: hack for akimbos to use MK23 ammo icon
else if (weaponNum == WP_AKIMBO && ammo->giType == IT_AMMO && ammo->giTag == WP_PISTOL) {
break;
}
}
if (ammo->classname && ammo->world_model[0]) {
weaponInfo->ammoModel = trap_R_RegisterModel(ammo->world_model[0]);
//Elder: added
weaponInfo->ammoIcon = trap_R_RegisterShader(ammo->icon);
}
strcpy(path, item->world_model[0]);
COM_StripExtensionInPlace(path);
strcat(path, "_flash.md3");
weaponInfo->flashModel = trap_R_RegisterModel(path);
strcpy(path, item->world_model[0]);
COM_StripExtensionInPlace(path);
strcat(path, "_barrel.md3");
weaponInfo->barrelModel = trap_R_RegisterModel(path);
strcpy(path, item->world_model[0]);
COM_StripExtensionInPlace(path);
strcat(path, "_hand.md3");
weaponInfo->handsModel = trap_R_RegisterModel(path);
//Elder: added to cache 1st-person models
strcpy(path, item->world_model[0]);
COM_StripExtensionInPlace(path);
strcat(path, "_1st.md3");
weaponInfo->firstModel = trap_R_RegisterModel(path);
if (!weaponInfo->handsModel) {
weaponInfo->handsModel = trap_R_RegisterModel("models/weapons2/shotgun/shotgun_hand.md3");
}
//Elder: if no _1st model, point to the weaponModel... this may get funky :)
if (!weaponInfo->firstModel) {
// Added warning message
CG_Printf(" ^3Warning: %s first-person model not found; using world model^7\n",
weaponInfo->item->pickup_name);
weaponInfo->firstModel = weaponInfo->weaponModel;
}
// weaponInfo->loopFireSound = qfalse;
memset (str, 0, sizeof(str));
switch (weaponNum) {
//Blaze: Reaction Pistol
case WP_PISTOL:
MAKERGB(weaponInfo->flashDlightColor, 1, 1, 0);
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/mk23/mk23fire.wav", qfalse);
weaponInfo->worldReloadSound[0] = trap_S_RegisterSound("sound/weapons/mk23/mk23reload.wav", qfalse);
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_mk23", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
cgs.media.bulletExplosionShader = trap_R_RegisterShader("bulletExplosion");
// Load the animation information
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
// Load sound information -- ALWAYS DO THIS AFTER THE ANIMATION
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_KNIFE:
MAKERGB(weaponInfo->flashDlightColor, 1, 0.70f, 0);
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/knife/slash.wav", qfalse);
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_knife", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
weaponInfo->missileModel =
trap_R_RegisterModel(va("models/weapons2/%s/knife.md3", model));
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
// Load the animation information
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
break;
case WP_M4:
//Elder: added
MAKERGB(weaponInfo->flashDlightColor, 1, 1, 0);
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/m4/m4fire.wav", qfalse);
weaponInfo->worldReloadSound[0] = trap_S_RegisterSound("sound/weapons/m4/m4reload.wav", qfalse);
weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
cgs.media.bulletExplosionShader = trap_R_RegisterShader("bulletExplosion");
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_m4", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_SSG3000:
//Elder: added
MAKERGB(weaponInfo->flashDlightColor, 1, 0.5f, 0);
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/ssg3000/ssgfire.wav", qfalse);
weaponInfo->worldReloadSound[0] = trap_S_RegisterSound("sound/weapons/ssg3000/ssgbolt.wav", qfalse);
weaponInfo->worldReloadSound[1] = trap_S_RegisterSound("sound/weapons/ssg3000/ssgin.wav", qfalse);
weaponInfo->worldReloadSound[2] = trap_S_RegisterSound("sound/weapons/ssg3000/ssgbolt.wav", qfalse);
weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
cgs.media.bulletExplosionShader = trap_R_RegisterShader("bulletExplosion");
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_ssg3000", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
}
// Load sound information -- ALWAYS DO THIS AFTER THE ANIMATION
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_MP5:
//Elder: added
MAKERGB(weaponInfo->flashDlightColor, 1, 0.75f, 0);
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/mp5/mp5fire.wav", qfalse);
weaponInfo->worldReloadSound[0] = trap_S_RegisterSound("sound/weapons/mp5/mp5reload.wav", qfalse);
weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
cgs.media.bulletExplosionShader = trap_R_RegisterShader("bulletExplosion");
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_mp5", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_HANDCANNON:
//Elder: added
MAKERGB(weaponInfo->flashDlightColor, 1, 1, 0);
//Elder: changed to hcfire from cannon_fire
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/handcannon/hcfire.wav", qfalse);
weaponInfo->worldReloadSound[0] = trap_S_RegisterSound("sound/weapons/handcannon/hcreload.wav", qfalse);
weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass;
cgs.media.bulletExplosionShader = trap_R_RegisterShader("bulletExplosion");
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_handcannon", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_M3:
MAKERGB(weaponInfo->flashDlightColor, 1, 1, 0);
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/m3/m3fire.wav", qfalse);
weaponInfo->worldReloadSound[1] = trap_S_RegisterSound("sound/weapons/m3/m3in.wav", qfalse);
weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass;
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_m3", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_AKIMBO:
//Elder: added
MAKERGB(weaponInfo->flashDlightColor, 1, 1, 0.5f);
// Elder: no more pseudo-dual sound needed :)
weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/mk23/mk23fire.wav", qfalse);
weaponInfo->worldReloadSound[0] = trap_S_RegisterSound("sound/weapons/akimbo/akimboreload.wav", qfalse);
weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
cgs.media.bulletExplosionShader = trap_R_RegisterShader("bulletExplosion");
// JBravo: skin replacements
// Makro - no longer using skin cvars
trap_Cvar_VariableStringBuffer("cg_RQ3_akimbo", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
// Load sound information -- ALWAYS DO THIS AFTER THE ANIMATION
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
case WP_GRENADE:
trap_Cvar_VariableStringBuffer("cg_RQ3_grenade", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
//Use the projectile model
weaponInfo->missileModel = trap_R_RegisterModel(va("models/weapons2/%s/gren_projectile.md3", model));
weaponInfo->wiTrailTime = 700;
weaponInfo->trailRadius = 32;
MAKERGB(weaponInfo->flashDlightColor, 1, 0.70f, 0);
//Blaze: Make sure we load this as we may have exploding crates
//cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" );
// JBravo: skin replacements
// Makro - no longer using skin cvars
if (strcmp(skin, "default")) {
weaponInfo->customSkin = trap_R_RegisterSkin (va("models/weapons2/%s/%s.skin", model, skin));
if (!weaponInfo->customSkin) {
Com_Printf("^1Weapon skin load failure: %s\n", va("models/weapons2/%s/%s.skin", model, skin));
}
}
// Load the animation information
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/animation.cfg", model);
if (!CG_ParseWeaponAnimFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon animation file %s\n", filename);
weapAnimLoad = qfalse;
}
// Load sound information -- ALWAYS DO THIS AFTER THE ANIMATION
if (weapAnimLoad) {
Com_sprintf(filename, sizeof(filename), "models/weapons2/%s/sound.cfg", model);
if (!CG_ParseWeaponSoundFile(filename, weaponInfo)) {
Com_Printf("^1Failed to load weapon sound file %s\n", filename);
}
} else {
Com_Printf("^1Could not load sound.cfg because animation.cfg loading failed\n");
}
break;
default:
break;
}
}
/*
=================
CG_RegisterItemVisuals
The server says this item is used on this level
=================
*/
void CG_RegisterItemVisuals(int itemNum)
{
itemInfo_t *itemInfo;
gitem_t *item;
char *model, *skin, str[MAX_TOKEN_CHARS];
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);
}
// JBravo: replacement skins for items
if (item->giType == IT_HOLDABLE) {
if (!strcmp(item->classname, "item_kevlar")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_kevlar", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/items/%s/%s.skin", model, skin));
if (!itemInfo->customSkin)
Com_Printf("^1Item skin load failure: %s\n", va("models/items/%s/%s.skin", model, skin));
}
}
if (!strcmp(item->classname, "item_silencer")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_silencer", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/items/%s/%s.skin", model, skin));
if (!itemInfo->customSkin)
Com_Printf("^1Item skin load failure: %s\n", va("models/items/%s/%s.skin", model, skin));
}
}
if (!strcmp(item->classname, "item_laser")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_laser", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/items/%s/%s.skin", model, skin));
if (!itemInfo->customSkin)
Com_Printf("^1Item skin load failure: %s\n", va("models/items/%s/%s.skin", model, skin));
}
}
if (!strcmp(item->classname, "item_bandolier")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_bandolier", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/items/%s/%s.skin", model, skin));
if (!itemInfo->customSkin)
Com_Printf("^1Item skin load failure: %s\n", va("models/items/%s/%s.skin", model, skin));
}
}
if (!strcmp(item->classname, "item_slippers")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_slippers", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/items/%s/%s.skin", model, skin));
if (!itemInfo->customSkin)
Com_Printf("^1Item skin load failure: %s\n", va("models/items/%s/%s.skin", model, skin));
}
}
if (!strcmp(item->classname, "item_helmet")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_helmet", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/items/%s/%s.skin", model, skin));
if (!itemInfo->customSkin)
Com_Printf("^1Item skin load failure: %s\n", va("models/items/%s/%s.skin", model, skin));
}
}
}
if (item->giType == IT_AMMO) {
if (!strcmp(item->classname, "ammo_mk23")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_ammo_mk23", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/ammo/%s.skin", skin));
if (!itemInfo->customSkin)
Com_Printf("^1Ammo skin load failure: %s\n", va("models/ammo/%s.skin", skin));
}
}
if (!strcmp(item->classname, "ammo_shells")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_ammo_m3", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/ammo/%s.skin", skin));
if (!itemInfo->customSkin)
Com_Printf("^1Ammo skin load failure: %s\n", va("models/ammo/%s.skin", skin));
}
}
if (!strcmp(item->classname, "ammo_ssg3000")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_ammo_ssg3000", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/ammo/%s.skin", skin));
if (!itemInfo->customSkin)
Com_Printf("^1Ammo skin load failure: %s\n", va("models/ammo/%s.skin", skin));
}
}
if (!strcmp(item->classname, "ammo_mp5")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_ammo_mp5", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/ammo/%s.skin", skin));
if (!itemInfo->customSkin)
Com_Printf("^1Ammo skin load failure: %s\n", va("models/ammo/%s.skin", skin));
}
}
if (!strcmp(item->classname, "ammo_m4")) {
trap_Cvar_VariableStringBuffer("cg_RQ3_ammo_m4", str, sizeof(str));
model = modelFromStr(str);
skin = skinFromStr(str);
if (strcmp(skin, "default")) {
itemInfo->customSkin = trap_R_RegisterSkin (va("models/ammo/%s.skin", skin));
if (!itemInfo->customSkin)
Com_Printf("^1Ammo skin load failure: %s\n", va("models/ammo/%s.skin", skin));
}
}
}
//
// 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_CalculateWeaponPosition
==============
*/
static void CG_CalculateWeaponPosition(vec3_t origin, vec3_t angles)
{
float scale, fracsin;
int delta;
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;
}
// 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_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 )
{
// gun2 is for tag_weapon2. atm, only another pistol for akimbo in thirdperson
refEntity_t gun1, gun2;
refEntity_t flash;
refEntity_t silencer, laser;
vec3_t angles;
weapon_t weaponNum;
weaponInfo_t *weapon;
centity_t *nonPredictedCent;
localEntity_t *shell = NULL;
weaponNum = cent->currentState.weapon;
CG_RegisterWeapon( weaponNum );
weapon = &cg_weapons[ weaponNum ];
// add the weapon
memset( &gun1, 0, sizeof( gun1 ) );
memset( &gun2, 0, sizeof( gun2 ) );
VectorCopy( parent->lightingOrigin, gun1.lightingOrigin );
gun1.shadowPlane = parent->shadowPlane;
gun1.renderfx = parent->renderfx;
gun1.shaderRGBA[ 0 ] = 255;
gun1.shaderRGBA[ 1 ] = 255;
gun1.shaderRGBA[ 2 ] = 255;
gun1.shaderRGBA[ 3 ] = 255;
gun1.nonNormalizedAxes = qtrue;
VectorCopy( parent->lightingOrigin, gun2.lightingOrigin );
gun2.shadowPlane = parent->shadowPlane;
gun2.renderfx = parent->renderfx;
gun2.shaderRGBA[ 0 ] = 255;
gun2.shaderRGBA[ 1 ] = 255;
gun2.shaderRGBA[ 2 ] = 255;
gun2.shaderRGBA[ 3 ] = 255;
//Elder: We are in third person, use the third-person model (DEFAULT)
if (ps == NULL) {
orientation_t tag;
gun1.hModel = weapon->weaponModel;
if ( weaponNum == WP_AKIMBO && trap_R_LerpTag(&tag, parent->hModel, 0, 0, 1, "tag_weapon2") )
gun2.hModel = gun1.hModel;
}
//Elder: we are in first-person, use the first-person (NOT default) model
else {
gun1.hModel = weapon->firstModel;
}
if ( !gun1.hModel ) {
return;
}
// JBravo: activate the custom skin, if any
if (weapon->customSkin) {
gun1.customSkin = weapon->customSkin;
// NiceAss: Use on the second pistol for akimbo
if ( gun2.hModel )
gun2.customSkin = weapon->customSkin;
// 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 );
}
}
if ( !ps ) {
// Position the second pistol for akimbo if the model supports it
if ( gun2.hModel )
CG_PositionEntityOnTag(&gun2, parent, parent->hModel, "tag_weapon2");
CG_PositionEntityOnTag(&gun1, parent, parent->hModel, "tag_weapon");
}
else {
CG_WeaponAnimation( cent, &gun1.oldframe, &gun1.frame, &gun1.backlerp );
CG_PositionWeaponOnTag( &gun1, parent, parent->hModel, "tag_weapon" );
// Elder: Local sound events will sync perfectly here
// However, we must remember to ignore the ones pmove will generate
// (for other clients to hear) or we'll get some nasty echo
// Temp hack because it isn't fully implemented
if (weapon->item->giTag == WP_PISTOL ||
weapon->item->giTag == WP_AKIMBO ||
weapon->item->giTag == WP_M4 ||
weapon->item->giTag == WP_MP5 ||
weapon->item->giTag == WP_M3 ||
weapon->item->giTag == WP_HANDCANNON ||
weapon->item->giTag == WP_SSG3000 || weapon->item->giTag == WP_GRENADE) {
int i = 0;
qboolean noSound = qfalse;
while (gun1.frame != weapon->animationSounds->sfxInfo[i].frame) {
if (++i == weapon->animationSounds->numFrames) {
noSound = qtrue;
break;
}
}
// reset the current sound
if (cg.curSyncSound.played && cg.curSyncSound.frame != gun1.frame)
cg.curSyncSound.played = qfalse;
if (!noSound) {
// copy the struct to reset it
if (cg.curSyncSound.frame != gun1.frame)
cg.curSyncSound = weapon->animationSounds->sfxInfo[i];
if (cg.curSyncSound.played == qfalse) {
cg.curSyncSound.played = qtrue;
// Makro - no longer local in order to have reverb effect applied
trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_WEAPON, cg.curSyncSound.sound);
}
}
}
}
// Elder: break off here so we still have weapon animations on bolt out
// Elder: added ps so we see SSG in third-person zoomed
if ( cg.zoomed && ps )
return;
// JBravo: Adding a fix from NiceAss (cg_drawgun affecting other player models)
if ( !cg_drawGun.integer && ps )
return;
trap_R_AddRefEntityToScene( &gun1 );
if ( gun2.hModel )
trap_R_AddRefEntityToScene( &gun2 );
// 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;
}
if (ps && (cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_SILENCER)) &&
(weaponNum == WP_PISTOL || weaponNum == WP_MP5 || weaponNum == WP_SSG3000)) {
//Makro - wasn't initialized, caused a warning in MSVC
vec3_t silencerEnd;
float scale = 0.0f;
vec3_t angles;
if (weaponNum == WP_PISTOL)
scale = 1.2f;
if (weaponNum == WP_SSG3000)
scale = 2.5f;
if (weaponNum == WP_MP5)
scale = 1.3f;
memset(&silencer, 0, sizeof(silencer));
silencer.hModel = cgs.media.rq3_silencerModel;
silencer.shadowPlane = parent->shadowPlane;
silencer.renderfx = parent->renderfx;
angles[YAW] = 90;
angles[PITCH] = 0;
angles[ROLL] = 0;
AnglesToAxis(angles, silencer.axis);
VectorScale(silencer.axis[0], scale, silencer.axis[0]);
VectorScale(silencer.axis[1], scale, silencer.axis[1]);
VectorScale(silencer.axis[2], scale, silencer.axis[2]);
CG_PositionRotatedOffsetEntityOnTag(&silencer, &gun1, weapon->firstModel, "tag_silencer", vec3_origin);
// Offset weapon
VectorMA(silencer.origin, -7.2f, silencer.axis[1], silencer.origin);
VectorMA(silencer.origin, 10.0f, silencer.axis[1], silencerEnd);
silencer.nonNormalizedAxes = qtrue;
trap_R_AddRefEntityToScene( &silencer );
// NiceAss: Add a puff of smoke at the end of the silencer when fired.
//Makro - changed a bit, might look cooler
if (cent->muzzleFlashTime == -1) {
#define NUM_SILENCER_PUFFS 3
localEntity_t *smoke[NUM_SILENCER_PUFFS];
int i;
vec3_t up;
// Move the puff over to center on silencer end.
VectorMA(silencerEnd, 5, cg.refdef.viewaxis[1], silencerEnd);
VectorMA(silencerEnd, 5, cg.refdef.viewaxis[2], silencerEnd);
/*
VectorSet(up, 0.0f, 0.0f, 15.0f);
smoke = CG_SmokePuff(silencerEnd, up, 0.5f, 1, 1, 1, 0.5f, 300, cg.time, 0, 0,
cgs.media.shotgunSmokePuffShader);
smoke->leType = LE_SCALE_FADE;
*/
//VectorSet(up, 0.0f, 45.0f, 0.0f);
for (i=0; i<NUM_SILENCER_PUFFS; i++)
{
VectorScale(silencer.axis[1], -(20+random()*50), up);
smoke[i] = CG_SmokePuff(silencerEnd, up, 0.2f, 1, 1, 1, 0.25f+crandom()*0.1f, 200+random()*200, cg.time, 0, 0,
cgs.media.shotgunSmokePuffShader);
smoke[i]->leType = LE_MOVE_SCALE_FADE;
smoke[i]->lifeRate = 0.75 / (smoke[i]->endTime - smoke[i]->startTime);
}
cent->muzzleFlashTime = 0;
}
}
if (ps && (cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_LASER)) &&
(weaponNum == WP_PISTOL || weaponNum == WP_MP5 || weaponNum == WP_M4)) {
float scale = 0.0f;
vec3_t angles;
if (weaponNum == WP_PISTOL)
scale = 0.66f;
if (weaponNum == WP_MP5)
scale = 0.675f;
if (weaponNum == WP_M4)
scale = 0.79f;
memset(&laser, 0, sizeof(laser));
laser.hModel = cgs.media.rq3_laserModel;
laser.shadowPlane = parent->shadowPlane;
laser.renderfx = parent->renderfx;
angles[YAW] = 90;
angles[PITCH] = 0;
angles[ROLL] = 0;
AnglesToAxis(angles, laser.axis);
VectorScale(laser.axis[0], scale, laser.axis[0]);
VectorScale(laser.axis[1], scale, laser.axis[1]);
VectorScale(laser.axis[2], scale, laser.axis[2]);
CG_PositionRotatedEntityOnTag(&laser, &gun1, weapon->firstModel, "tag_laser");
laser.nonNormalizedAxes = qtrue;
trap_R_AddRefEntityToScene( &laser );
}
// NiceAss: fix for brass ejecting on LCA
if (cg.lca)
cent->ejectBrassTime = 0;
// NiceAss: Tag locations used for shell ejection
// The handcannon hacks have ruined my beautiful code =(
if (cg.time > cent->ejectBrassTime && cent->ejectBrassTime && weapon->ejectBrassFunc &&
(ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum)) {
orientation_t tag;
shell = weapon->ejectBrassFunc(cent);
if ( shell != NULL && trap_R_LerpTag(&tag, gun1.hModel, 0, 0, 1, "tag_shell") ) {
float speed = 1.0f;
int axis1 = 0, axis2 = 0;
if ( ps ) {
if (weapon->item->giTag == WP_AKIMBO && !ps->stats[STAT_BURST])
CG_PositionRotatedEntityOnTag(&shell->refEntity, &gun1, gun1.hModel, "tag_shell2");
else
CG_PositionRotatedEntityOnTag(&shell->refEntity, &gun1, gun1.hModel, "tag_shell");
}
else {
if (weapon->item->giTag == WP_AKIMBO && !cg.akimboFlash)
if ( gun2.hModel && trap_R_LerpTag(&tag, gun2.hModel, 0, 0, 1, "tag_shell") )
CG_PositionRotatedEntityOnTag(&shell->refEntity, &gun2, gun2.hModel, "tag_shell");
else
CG_PositionRotatedEntityOnTag(&shell->refEntity, &gun1, gun1.hModel, "tag_shell2");
else
CG_PositionRotatedEntityOnTag(&shell->refEntity, &gun1, gun1.hModel, "tag_shell");
}
VectorCopy(shell->refEntity.origin, shell->pos.trBase);
if (trap_CM_PointContents(shell->pos.trBase, 0) == CONTENTS_WATER)
speed = 0.5f;
if (weaponNum == WP_HANDCANNON) {
speed = -speed; // horrible hacks
axis1 = 1;
}
vectoangles(shell->refEntity.axis[axis1], shell->angles.trBase);
VectorScale(shell->refEntity.axis[axis2], 160 * speed, shell->pos.trDelta);
VectorAdd(shell->pos.trDelta, cent->currentState.pos.trDelta, shell->pos.trDelta);
}
// All this code for a SECOND shell on the HC
if (weaponNum == WP_HANDCANNON && trap_R_LerpTag(&tag, gun1.hModel, 0, 0, 1, "tag_shell2") ) {
float speed = -1.0f;
shell = weapon->ejectBrassFunc(cent);
if (shell != NULL) {
CG_PositionRotatedEntityOnTag(&shell->refEntity, &gun1, gun1.hModel, "tag_shell2");
VectorCopy(shell->refEntity.origin, shell->pos.trBase);
if (trap_CM_PointContents(shell->pos.trBase, 0) == CONTENTS_WATER)
speed = -0.5f;
vectoangles(shell->refEntity.axis[1], shell->angles.trBase);
VectorScale(shell->refEntity.axis[0], 160 * speed, shell->pos.trDelta);
VectorAdd(shell->pos.trDelta, cent->currentState.pos.trDelta, shell->pos.trDelta);
}
}
cent->ejectBrassTime = 0;
}
if (cent->currentState.number != cg.predictedPlayerState.clientNum) {
refEntity_t muzzle;
memset(&muzzle, 0, sizeof(muzzle));
CG_PositionEntityOnTag(&muzzle, &gun1, weapon->weaponModel, "tag_flash");
CG_DrawVisibleLaser(muzzle.origin, cent->currentState.clientNum, muzzle.axis[0]);
}
//Elder: re-added to fix loss of muzzle flashes!
// impulse flash
if (cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash) {
return;
}
memset(&flash, 0, sizeof(flash));
VectorCopy(parent->lightingOrigin, flash.lightingOrigin);
flash.shadowPlane = parent->shadowPlane;
flash.renderfx = parent->renderfx;
flash.hModel = weapon->flashModel;
if (!flash.hModel) {
return;
}
angles[YAW] = 0;
angles[PITCH] = 0;
angles[ROLL] = crandom() * 10;
AnglesToAxis(angles, flash.axis);
//Elder: add conditional here so the dlight is still drawn when cg_RQ3_flash is 0
if (cg_RQ3_flash.integer) {
if (ps) {
float scale = 2.0f;
if (ps->weapon == WP_M4)
scale = 1.5f;
// Elder: draw flash based on first-person view
if (ps->weapon == WP_AKIMBO) {
// choose tag for akimbos
if (ps->stats[STAT_BURST])
CG_PositionRotatedEntityOnTag(&flash, &gun1, weapon->firstModel, "tag_flash");
else
CG_PositionRotatedEntityOnTag(&flash, &gun1, weapon->firstModel, "tag_flash2");
} else
CG_PositionRotatedEntityOnTag(&flash, &gun1, weapon->firstModel, "tag_flash");
// Make flash larger to compensate for depth hack
VectorScale(flash.axis[0], scale, flash.axis[0]);
VectorScale(flash.axis[1], scale, flash.axis[1]);
VectorScale(flash.axis[2], scale, flash.axis[2]);
flash.nonNormalizedAxes = qtrue;
}
else {
//Elder: draw flash based on 3rd-person view
if (weapon->item->giTag == WP_AKIMBO) {
// choose tag for akimbos
if (cg.akimboFlash)
CG_PositionRotatedEntityOnTag(&flash, &gun1, weapon->weaponModel, "tag_flash");
else {
if ( gun2.hModel )
CG_PositionRotatedEntityOnTag(&flash, &gun2, weapon->weaponModel, "tag_flash");
else
CG_PositionRotatedEntityOnTag(&flash, &gun1, weapon->weaponModel, "tag_flash2");
}
}
else
CG_PositionRotatedEntityOnTag(&flash, &gun1, weapon->weaponModel, "tag_flash");
}
trap_R_AddRefEntityToScene(&flash);
} else {
//Elder: crappy way to get the dlight working w/o the flash model
if (ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum) {
if (weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2]) {
if (!cg_drawGun.value)
trap_R_AddLightToScene(gun1.origin, 300 + (rand() & 31),
weapon->flashDlightColor[0], weapon->flashDlightColor[1],
weapon->flashDlightColor[2]);
}
}
return;
}
if (ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum) {
// add rail trail
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;
float fovOffset;
vec3_t angles;
weaponInfo_t *weapon;
//Blaze: Reaction vars for gun positions
//float rxn_gunx, rxn_guny, rxn_gunz;
//Slicer - Spect code for follow
if (ps->persistant[PERS_TEAM] == TEAM_SPECTATOR && !(ps->pm_flags & PMF_FOLLOW)) {
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
// Elder: don't draw if throwing
//Blaze: removed cg_drawGun checks and moved into other code.
if ((cg.snap->ps.stats[STAT_RQ3] & RQ3_THROWWEAPON) == RQ3_THROWWEAPON) {
//Blaze: Removed these
if (cg.predictedPlayerState.eFlags & EF_FIRING) {
}
return;
}
// don't draw if testing a gun model
if (cg.testGun) {
return;
}
//Blaze end:
// drop gun lower at higher fov
if (cg_fov.integer > 90) {
fovOffset = -0.2 * (cg_fov.integer - 90);
} else {
fovOffset = 0;
}
// cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum];
CG_RegisterWeapon(ps->weapon);
weapon = &cg_weapons[ps->weapon];
memset(&hand, 0, sizeof(hand));
// set up gun position
CG_CalculateWeaponPosition(hand.origin, angles);
//Blaze: Use above code instead
//Elder: reused so Cloud can adjust the models manually
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);
// development tool
hand.frame = hand.oldframe = cg_gun_frame.integer;
hand.backlerp = 0;
hand.hModel = weapon->handsModel;
hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT;
if (cg_RQ3_leftHanded.integer)
{
VectorNegate(hand.axis[1], hand.axis[1]);
}
// 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, bits, count, 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
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)
{
//Elder: never switch to empty grenades/knives
if (!cg.snap->ps.ammo[i] && (i == WP_GRENADE || i == WP_KNIFE)) {
return qfalse;
}
//Blaze: Check the amount of clips too for the weapon rotate code
//Elder: this won't work plus AQ2 lets you select empty weapons
// JBravo: lets not allow switching to weapons other than pistol or knife if the player has a case
if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_REDFLAG]) {
if (i != WP_PISTOL && i != WP_KNIFE)
return qfalse;
}
if (!(cg.snap->ps.stats[STAT_WEAPONS] & (1 << i))) {
return qfalse;
}
return qtrue;
}
static void CG_TooBusyBandaging(void)
{
CG_AddMessage("You are too busy bandaging...\n");
}
/*
===============
CG_NextWeapon_f
===============
*/
void CG_NextWeapon_f(void)
{
int i, original;
if (!cg.snap) {
return;
}
if (cg.snap->ps.pm_flags & PMF_FOLLOW) {
return;
}
// if we are going into the intermission, don't do anything
if (cg.intermissionStarted) {
return;
}
//Elder: added
if (cg.snap->ps.weaponstate == WEAPON_BANDAGING) {
CG_TooBusyBandaging();
return;
}
//Elder: don't allow weapon switching when in the middle of bursts
if (cg.snap->ps.stats[STAT_BURST] > 0)
return;
//Elder: in the middle of firing, reloading or weapon-switching
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_WeaponSelectable(cg.weaponSelect)) {
break;
}
}
if (i == MAX_WEAPONS) {
cg.weaponSelect = original;
}
//Slicer: Done Server Side
else {
//Slicer lets call unzoom for SSG only
if (original == WP_SSG3000)
trap_SendClientCommand("unzoom");
}
}
/*
===============
CG_PrevWeapon_f
===============
*/
void CG_PrevWeapon_f(void)
{
int i, original;
if (!cg.snap) {
return;
}
if (cg.snap->ps.pm_flags & PMF_FOLLOW) {
return;
}
// if we are going into the intermission, don't do anything
if (cg.intermissionStarted) {
return;
}
//Elder: added
// if ( (cg.snap->ps.stats[STAT_RQ3] & RQ3_BANDAGE_WORK) == RQ3_BANDAGE_WORK) {
if (cg.snap->ps.weaponstate == WEAPON_BANDAGING) {
CG_TooBusyBandaging();
return;
}
//Elder: don't allow weapon switching when in the middle of bursts
if (cg.snap->ps.stats[STAT_BURST] > 0)
return;
//Elder: in the middle of firing, reloading or weapon-switching
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_WeaponSelectable(cg.weaponSelect)) {
break;
}
}
if (i == MAX_WEAPONS) {
cg.weaponSelect = original;
} else {
//Slicer: Done Server Side
//Slicer lets call unzoom for SSG only
if (original == WP_SSG3000)
trap_SendClientCommand("unzoom");
}
}
/*
===============
Added by Elder
CG_SpecialWeapon_f
===============
*/
void CG_SpecialWeapon_f(void)
{
int i, original;
if (!cg.snap) {
//CG_Printf("No snapshot: normally exiting\n");
return;
}
if (cg.snap->ps.pm_flags & PMF_FOLLOW) {
return;
}
// if we are going into the intermission, don't do anything
if (cg.intermissionStarted) {
return;
}
//Elder: added
if (cg.snap->ps.weaponstate == WEAPON_BANDAGING) {
CG_AddMessage("You are too busy bandaging...\n");
return;
}
//Elder: don't allow weapon switching when in the middle of bursts
if (cg.snap->ps.stats[STAT_BURST] > 0)
return;
// JBravo: not in CTF
if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_REDFLAG])
return;
//Elder: in the middle of firing, reloading or weapon-switching
cg.weaponSelectTime = cg.time;
original = cg.weaponSelect;
for (i = 0; i < MAX_WEAPONS; i++) {
cg.weaponSelect++;
if (cg.weaponSelect == MAX_WEAPONS) {
cg.weaponSelect = 0;
}
//Skip normal weapons
switch (cg.weaponSelect) {
case WP_PISTOL:
case WP_AKIMBO:
case WP_KNIFE:
case WP_GRENADE:
continue;
break;
}
if (CG_WeaponSelectable(cg.weaponSelect)) {
break;
}
}
// FIXME: 15 because of the stupid continue I used
if (i >= MAX_WEAPONS - 1) {
cg.weaponSelect = original;
} else {
//Slicer: Done Server Side
//Slicer lets call unzoom for SSG only
if (original == WP_SSG3000)
trap_SendClientCommand("unzoom");
}
}
//Elder: for returning to the zoom state in ps stats
void CG_RQ3_SyncZoom(void)
{
if ((cg.snap->ps.stats[STAT_RQ3] & RQ3_ZOOM_LOW) == RQ3_ZOOM_LOW &&
(cg.snap->ps.stats[STAT_RQ3] & RQ3_ZOOM_MED) == RQ3_ZOOM_MED) {
cg.zoomLevel = RQ3_ZOOM_LOW | RQ3_ZOOM_MED;
} else if ((cg.snap->ps.stats[STAT_RQ3] & RQ3_ZOOM_LOW) == RQ3_ZOOM_LOW) {
cg.zoomLevel = RQ3_ZOOM_LOW;
} else if ((cg.snap->ps.stats[STAT_RQ3] & RQ3_ZOOM_MED) == RQ3_ZOOM_MED) {
cg.zoomLevel = RQ3_ZOOM_MED;
} else {
cg.zoomLevel = 0;
}
}
//Elder: save zoom level and do any other necessary housekeeping
void CG_RQ3_SaveZoomLevel()
{
cg.lastZoomLevel = cg.zoomLevel;
}
//Elder: This should identical to the portion inside
//the weapon function in g_cmds.c
/*void CG_RQ3_Zoom(void)
{
//Elder: reworked SSG zoom
if (cg.snap->ps.weapon == WP_SSG3000) {
CG_RQ3_SaveZoomLevel();
if ((cg.zoomLevel & RQ3_ZOOM_LOW) == RQ3_ZOOM_LOW && (cg.zoomLevel & RQ3_ZOOM_MED) == RQ3_ZOOM_MED) {
//Elder: zoom 1x
cg.zoomLevel &= ~RQ3_ZOOM_LOW;
cg.zoomLevel &= ~RQ3_ZOOM_MED;
} else if ((cg.zoomLevel & RQ3_ZOOM_MED) == RQ3_ZOOM_MED) {
//Elder: zoom 6x
cg.zoomLevel |= RQ3_ZOOM_LOW;
} else if ((cg.zoomLevel & RQ3_ZOOM_LOW) == RQ3_ZOOM_LOW) {
//Elder: zoom 4x
cg.zoomLevel |= RQ3_ZOOM_MED;
cg.zoomLevel &= ~RQ3_ZOOM_LOW;
} else {
//Elder: zoom 2x
cg.zoomLevel |= RQ3_ZOOM_LOW;
}
cg.zoomTime = cg.time;
}
}
*/
/*//Elder: reset locally and send server message
void CG_RQ3_Zoom1x()
{
if (cg_RQ3_ssgZoomAssist.integer) {
cg.lastZoomLevel = cg.zoomLevel;
cg.zoomLevel = 0;
cg.zoomTime = cg.time;
}
}
*/
int CG_RQ3_GetGrenadeMode()
{
int grenMode = 0;
if ((cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENSHORT) == RQ3_GRENSHORT)
grenMode |= RQ3_GRENSHORT;
if ((cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENMED) == RQ3_GRENMED)
grenMode |= RQ3_GRENMED;
return grenMode;
}
//Print grenade mode message
void CG_RQ3_GrenadeMode()
{
//Print a message for the next mode in line
if ((cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENSHORT) == RQ3_GRENSHORT &&
(cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENMED) == RQ3_GRENMED) {
CG_Printf("Prepared to make a short range throw\n");
} else if ((cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENSHORT) == RQ3_GRENSHORT) {
CG_Printf("Prepared to make a medium range throw\n");
} else if ((cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_GRENMED) == RQ3_GRENMED) {
CG_Printf("Prepared to make a long range throw\n");
}
}
/*
===============
CG_Weapon_f
Elder: does local stuff, then proceeds to server
Used with Q3's internal weapon switching "e.g. weapon 10"
===============
*/
void CG_Weapon_f(void)
{
int num;
if (!cg.snap) {
//CG_Printf("No snapshot: normally exiting\n");
return;
}
///Elder: spectator?
if (cg.snap->ps.pm_flags & PMF_FOLLOW) {
return;
}
//JBravo: Not dead
if (cg.snap->ps.stats[STAT_HEALTH] < 0) {
return;
}
// if we are going into the intermission, don't do anything
if (cg.intermissionStarted) {
return;
}
// Hawkins (give 'weapon' dual meaning)
if (trap_Argc() == 1) {
if (cg.snap->ps.weapon == WP_SSG3000) {
//Slicer : had to comment this, it caused a bug on the zooming, and it really didn't make much sense for lagged players
}
trap_SendClientCommand("weapon");
//Elder: added to get out of function at this point
return;
}
//Elder: added to prevent weapon switching while bandaging
if (cg.snap->ps.weaponstate == WEAPON_BANDAGING) {
CG_AddMessage("You are too busy bandaging...\n");
return;
}
//Elder: don't allow weapon switching when in the middle of bursts
if (cg.snap->ps.stats[STAT_BURST] > 0)
return;
num = atoi(CG_Argv(1));
if (num < 1 || num > MAX_WEAPONS - 1) {
return;
}
// JBravo: CTB provisions
if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_REDFLAG]) {
if (num != WP_PISTOL && num != WP_KNIFE)
return;
}
//Elder: this point on is the regular Q3 weapon function - weird
cg.weaponSelectTime = cg.time;
if (!(cg.snap->ps.stats[STAT_WEAPONS] & (1 << num))) {
return; // don't have the weapon
}
//Elder: If re-selecting SSG - leave function
if ((1 << num) == (1 << WP_SSG3000) && cg.snap->ps.weapon == WP_SSG3000) {
return;
}
//Slicer : Done Server Side
//Slicer lets call unzoom for SSG only
if (cg.weaponSelect == WP_SSG3000)
trap_SendClientCommand("unzoom");
cg.weaponSelect = num;
}
/*
===================
CG_OutOfAmmoChange
The current weapon has just run out of ammo
===================
*/
void CG_OutOfAmmoChange(void)
{
if (!cg.snap)
return;
//Select the pistol when we run out of grenades or knives
if (cg.snap->ps.weapon == WP_KNIFE || cg.snap->ps.weapon == WP_GRENADE) {
cg.weaponSelectTime = cg.time;
cg.weaponSelect = WP_PISTOL;
}
}
/*
===================================================================================================
WEAPON EVENTS
===================================================================================================
*/
/*
================
CG_FireWeapon
Caused by an EV_FIRE_WEAPON event
================
*/
void CG_FireWeapon(centity_t * cent, int weapModification)
{
entityState_t *ent;
int c, i;
weaponInfo_t *weap;
ent = &cent->currentState;
if (ent->weapon == WP_NONE) {
return;
}
if (ent->weapon >= WP_NUM_WEAPONS) {
CG_Error("CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS");
return;
}
weap = &cg_weapons[ent->weapon];
//Elder: added to supress burst mode flashes + sounds when 'predicting'
// M4
if (cg.snap->ps.weapon == WP_M4 &&
(cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_M4MODE) == RQ3_M4MODE &&
cg.snap->ps.stats[STAT_BURST] > 2 && cg_RQ3_predictWeapons.integer == 1) {
return;
}
// MP5
if (cg.snap->ps.weapon == WP_MP5 &&
(cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_MP5MODE) == RQ3_MP5MODE &&
cg.snap->ps.stats[STAT_BURST] > 2 && cg_RQ3_predictWeapons.integer == 1) {
return;
}
// MK23
if (cg.snap->ps.weapon == WP_PISTOL &&
(cg.snap->ps.persistant[PERS_WEAPONMODES] & RQ3_MK23MODE) == RQ3_MK23MODE &&
cg.snap->ps.stats[STAT_BURST] > 0 && cg_RQ3_predictWeapons.integer == 1) {
return;
}
// KNIFE
// NiceAss: This is if the knife slash is not the first (2,3,4,5)
if (weapModification == RQ3_WPMOD_KNIFENOMARK) {
return;
}
// NiceAss:
if (ent->weapon == WP_M3)
cent->ejectBrassTime = cg.time + 500;
else if (ent->weapon == WP_SSG3000)
cent->ejectBrassTime = cg.time + 650;
else if (ent->weapon != WP_KNIFE && ent->weapon != WP_GRENADE && ent->weapon != WP_HANDCANNON)
cent->ejectBrassTime = cg.time;
// mark the entity as muzzle flashing, so when it is added it will
// append the flash to the weapon model
if (weapModification != RQ3_WPMOD_SILENCER)
cent->muzzleFlashTime = cg.time;
else
cent->muzzleFlashTime = -1;
// Elder: choose alternate muzzle flashes for 3rd-person views
if (ent->weapon == WP_AKIMBO) {
if (cg.akimboFlash) {
cg.akimboFlash = 0;
} else {
cg.akimboFlash = 1;
}
}
//Elder: silencer stuff
if (weapModification == RQ3_WPMOD_SILENCER) {
trap_S_StartSound(NULL, ent->number, CHAN_WEAPON, cgs.media.silencerSound);
} else // if (cg.snap->ps.weapon != WP_KNIFE)
{
// 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]);
}
}
}
// View kicks -- note this doesn't affect aim which is handled on the server-side
// even though it probably should
if (ent->clientNum == cg.snap->ps.clientNum) {
vec3_t forward;
switch (cg.snap->ps.weapon) {
case WP_KNIFE:
AngleVectors(cg.predictedPlayerState.viewangles, forward, NULL, NULL);
VectorScale(forward, -2, cg.kick_origin);
cg.kick_angles[0] = -2;
cg.kick_time = cg.time;
cg.kick_duration = 200;
break;
case WP_PISTOL:
for (i = 0; i < 3; i++) {
cg.kick_origin[i] = crandom() * 0.35f;
cg.kick_angles[i] = crandom() * 0.7f;
}
cg.kick_origin[0] = crandom() * 0.35f;
cg.kick_time = cg.time;
break;
case WP_M3:
AngleVectors(cg.predictedPlayerState.viewangles, forward, NULL, NULL);
VectorScale(forward, -2, cg.kick_origin);
cg.kick_angles[0] = -2;
cg.kick_time = cg.time;
break;
case WP_M4:
for (i = 0; i < 3; i++) {
cg.kick_origin[i] = crandom() * 0.25f;
cg.kick_angles[i] = crandom() * 0.5f;
}
cg.kick_origin[0] = crandom() * 0.35f;
cg.kick_time = cg.time;
break;
case WP_MP5:
for (i = 0; i < 3; i++) {
cg.kick_origin[i] = crandom() * 0.25f;
cg.kick_angles[i] = crandom() * 0.5f;
}
cg.kick_origin[0] = crandom() * 0.35f;
cg.kick_time = cg.time;
break;
case WP_HANDCANNON:
AngleVectors(cg.predictedPlayerState.viewangles, forward, NULL, NULL);
VectorScale(forward, -2, cg.kick_origin);
cg.kick_angles[0] = -2;
cg.kick_time = cg.time;
break;
case WP_AKIMBO:
for (i = 0; i < 3; i++) {
cg.kick_origin[i] = crandom() * 0.25f;
cg.kick_angles[i] = crandom() * 0.5f;
}
cg.kick_origin[0] = crandom() * 0.35f;
cg.kick_time = cg.time;
break;
}
}
// JBravo: unlagged
//CG_PredictWeaponEffects(cent);
}
/*
=================
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, int weapModification)
{
qhandle_t mod, mark, shader;
sfxHandle_t sfx;
float radius, light;
vec3_t lightColor, puffDir, velocity;
localEntity_t *le, *smokePuff;
int r, duration, angle, contentType, sparkCount, i;
qboolean alphaFade, isSprite;
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) {
// Elder: bullet weapons all fall under MK23 >:(
case WP_M4:
case WP_MP5:
case WP_PISTOL:
case WP_AKIMBO:
case WP_SSG3000:
duration = 160;
mod = cgs.media.hitSparkModel;
shader = cgs.media.hitSparkShader;
radius = 8;
r = rand() & 3;
if (soundType == IMPACTSOUND_METAL) {
mark = cgs.media.metalMarkShader;
if (r < 2)
sfx = cgs.media.sfx_metalric1;
else if (r == 2)
sfx = cgs.media.sfx_metalric2;
else
sfx = cgs.media.sfx_metalric3;
} else if (soundType == IMPACTSOUND_GLASS) {
mark = cgs.media.glassMarkShader;
if (r < 2)
sfx = cgs.media.sfx_glassric1;
else if (r == 2)
sfx = cgs.media.sfx_glassric2;
else
sfx = cgs.media.sfx_glassric3;
}
//Makro - added
else if (soundType == IMPACTSOUND_BRICK) {
mark = cgs.media.bulletMarkShader;
if (r < 2)
sfx = cgs.media.sfx_brickric1;
else if (r == 2)
sfx = cgs.media.sfx_brickric2;
else
sfx = cgs.media.sfx_brickric3;
}
//Makro - added
else if (soundType == IMPACTSOUND_WOOD) {
mark = cgs.media.bulletMarkShader;
if (r < 2)
sfx = cgs.media.sfx_woodric1;
else if (r == 2)
sfx = cgs.media.sfx_woodric2;
else
sfx = cgs.media.sfx_woodric3;
}
//Makro - added
else if (soundType == IMPACTSOUND_CERAMIC) {
mark = cgs.media.bulletMarkShader;
if (r < 2)
sfx = cgs.media.sfx_ceramicric1;
else if (r == 2)
sfx = cgs.media.sfx_ceramicric2;
else
sfx = cgs.media.sfx_ceramicric3;
}
//Makro - added
else if (soundType == IMPACTSOUND_SNOW) {
radius = 3;
mark = cgs.media.snowMarkShader;
sfx = cgs.media.footsteps[FOOTSTEP_SNOW2][rand()%4];
} else {
mark = cgs.media.bulletMarkShader;
if (r == 0)
sfx = cgs.media.sfx_ric1;
else if (r == 1)
sfx = cgs.media.sfx_ric2;
else
sfx = cgs.media.sfx_ric3;
}
break;
case WP_M3:
case WP_HANDCANNON:
duration = 200;
mod = cgs.media.hitSparkModel;
shader = cgs.media.hitSparkShader;
sfx = 0;
radius = 4;
if (soundType == IMPACTSOUND_GLASS)
mark = cgs.media.glassMarkShader;
else if (soundType == IMPACTSOUND_METAL)
mark = cgs.media.metalMarkShader;
//Makro - added
else if (soundType == IMPACTSOUND_SNOW) {
mark = cgs.media.snowMarkShader;
radius = 2;
} else
mark = cgs.media.bulletMarkShader;
break;
case WP_GRENADE:
mod = cgs.media.dishFlashModel;
shader = cgs.media.grenadeExplosionShader;
sfx = cgs.media.sfx_rockexp;
mark = cgs.media.burnMarkShader;
radius = 96; //64
light = 350; //300
isSprite = qtrue;
break;
case WP_KNIFE:
if (weapModification == RQ3_WPMOD_KNIFESLASH) {
duration = 100;
mod = cgs.media.hitSparkModel;
shader = cgs.media.hitSparkShader;
//Makro - added for snow/grass
if (soundType == IMPACTSOUND_GRASS) {
sfx = cgs.media.footsteps[FOOTSTEP_GRASS][rand()%4];
} else if (soundType == IMPACTSOUND_SNOW) {
sfx = cgs.media.footsteps[FOOTSTEP_SNOW][rand()%4];
} else {
mark = cgs.media.slashMarkShader;
sfx = cgs.media.knifeClankSound;
}
radius = rand() % 4 + 6;
} else {
duration = 180;
mod = cgs.media.hitSparkModel;
shader = cgs.media.hitSparkShader;
sfx = cgs.media.knifeClankSound;
}
break;
default:
break;
}
//
// ricochet sound
//
if (weapon == WP_KNIFE || weapon == WP_SSG3000 || weapon == WP_GRENADE || soundType == IMPACTSOUND_METAL)
i = 1;
else
//Elder: 90% of the time render a bullet ricochet sound
i = rand() % 10;
if (sfx && i < 9) {
trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx);
}
//
// create the explosion
//
if (mod) {
if (weapon == WP_GRENADE) {
vec3_t delta;
float severity;
const float MAX_EXPLOSION_DIST = 800.f;
VectorSubtract(origin, cg.refdef.vieworg, delta);
severity = Com_Clamp(0.f, 1.f, 1.f - VectorLength(delta) / MAX_EXPLOSION_DIST);
if (severity > 0.f)
{
trace_t tr;
CG_Trace(&tr, cg.refdef.vieworg, NULL, NULL, origin, cg.predictedPlayerState.clientNum, CONTENTS_SOLID);
if (tr.fraction < 1.f)
severity *= 0.5f;
severity *= severity;
if (cg.explosionTime < cg.time || severity > cg.explosionForce)
cg.explosionForce = severity;
cg.explosionTime = cg.time + EXPLOSION_SHAKE_TIME;
}
le = CG_MakeExplosion(origin, dir, mod, shader, duration, isSprite);
le->light = light;
VectorCopy(lightColor, le->lightColor);
} else if (cg_RQ3_impactEffects.integer) {
vec3_t temp, offsetDir;
byte color[4];
int flashCount;
float scale;
switch (weapon) {
case WP_MP5:
case WP_PISTOL:
case WP_AKIMBO:
flashCount = 3 + rand() % 3;
color[0] = 224;
color[1] = 180;
color[2] = 128;
color[3] = 192;
break;
case WP_M4:
case WP_KNIFE:
flashCount = 3 + rand() % 3;
color[0] = 192;
color[1] = 96;
color[2] = 64;
color[3] = 192;
break;
case WP_M3:
case WP_HANDCANNON:
flashCount = 2 + rand() % 3;
color[0] = 192;
color[1] = 96;
color[2] = 64;
color[3] = 192;
break;
case WP_SSG3000:
default:
flashCount = 4 + rand() % 3;
color[0] = 255;
color[1] = 224;
color[2] = 128;
color[3] = 192;
break;
}
// Elder: should probably dump this into another function
for (i = 0; i < flashCount; i++) {
// introduce variance
VectorCopy(dir, temp);
scale = crandom() + 1.8f;
temp[0] += (crandom() * 0.4f) - 0.2f;
temp[1] += (crandom() * 0.4f) - 0.2f;
temp[2] += (crandom() * 0.4f) - 0.2f;
// save result
VectorCopy(temp, offsetDir);
VectorScale(temp, scale, temp);
le = CG_MakeExplosion(origin, dir, mod, shader, duration, isSprite);
VectorMA(origin, scale + 0.5f, temp, le->refEntity.origin);
VectorCopy(temp, le->refEntity.axis[0]);
// set spark colour
le->refEntity.shaderRGBA[0] = color[0];
le->refEntity.shaderRGBA[1] = color[1];
le->refEntity.shaderRGBA[2] = color[2];
le->refEntity.shaderRGBA[3] = color[3];
// readjust behaviour
le->leType = LE_MOVE_SCALE_FADE;
le->pos.trType = TR_LINEAR;
le->pos.trTime = cg.time;
VectorCopy(le->refEntity.origin, le->pos.trBase);
VectorScale(offsetDir, 30, le->pos.trDelta);
le->light = light;
VectorCopy(lightColor, le->lightColor);
}
}
}
//
// impact mark
//
alphaFade = (mark == cgs.media.energyMarkShader || mark == cgs.media.snowMarkShader ); // plasma fades alpha, all others fade color
// Elder: Our knife slashes are vertical
if (weapon == WP_KNIFE) {
//Makro - changed
//angle = random() * 90;
angle = -45 + random() * 90;
if (angle < 0)
angle +=360;
} else
angle = random() * 360;
if (mark)
CG_ImpactMark(mark, origin, dir, angle, 1, 1, 1, 1, alphaFade, radius, qfalse);
//}
//
// impact visual effects
//
//Elder: 75% of the time render a smoke puff
//Makro - I say we do it all the time
//i = rand() % 4;
//Makro - not for snow or grass surfaces
//if (cg_RQ3_impactEffects.integer && i < 3 && soundType != IMPACTSOUND_SNOW && soundType != IMPACTSOUND_GRASS) {
if (cg_RQ3_impactEffects.integer && soundType != IMPACTSOUND_SNOW && soundType != IMPACTSOUND_GRASS) {
contentType = trap_CM_PointContents(origin, 0);
// no puff in water
if (!(contentType & CONTENTS_WATER)) {
int count = 0, speed = 0;
float size = 0;
switch (weapon) {
case WP_MP5:
count = 3;
size = 2.0f;
speed = 36;
break;
case WP_M4:
count = 5;
size = 4.5f;
speed = 64;
break;
case WP_PISTOL:
case WP_AKIMBO:
count = 4;
size = 3.0f;
speed = 48;
break;
case WP_SSG3000:
count = 5;
size = 6.0f;
speed = 80;
break;
/*
//Makro - old code
puffDir[0] = 0;
puffDir[1] = 0;
puffDir[2] = 16;
VectorCopy(dir, puffOffset);
VectorNormalize(puffOffset);
VectorNegate(puffOffset, puffOffset);
VectorScale(puffOffset, 13, puffOffset);
VectorSubtract(origin, puffOffset, puffOrigin);
smokePuff = CG_SmokePuff(puffOrigin, puffDir,
(int) (random() * 100) % 4 + 13,
1, 1, 1, 0.25f,
650,
cg.time, 0,
LEF_PUFF_DONT_SCALE, cgs.media.smokePuffAnimShader);
*/
default:
break;
}
for (i=0; i<count; i++)
{
VectorScale(dir, speed + crandom() * 8, puffDir);
//VectorScale(silencer.axis[1], -(20+random()*50), up);
smokePuff = CG_SmokePuff(origin, puffDir, size+random()*size, .8f, .8f, .8f, 0.3f+crandom()*0.1f, 300+random()*150, cg.time-random()*100, cg.time+random()*200, 0,
cgs.media.shotgunSmokePuffShader);
smokePuff->leType = LE_MOVE_SCALE_FADE;
smokePuff->lifeRate = 1.0f / (smokePuff->endTime - smokePuff->startTime);
}
}
}
//Makro - particles for default, grass, wood, brick and ceramic materials
if (cg_RQ3_impactEffects.integer && (soundType == IMPACTSOUND_WOOD || soundType == IMPACTSOUND_DEFAULT || soundType == IMPACTSOUND_BRICK || soundType == IMPACTSOUND_CERAMIC || soundType == IMPACTSOUND_GRASS))
{
int count = rand() % 5;
for (i=0; i<count; i++)
{
VectorScale(dir, 96 + crandom() * 16, puffDir);
CG_ParticleHitWall(origin, puffDir, 300 + rand() % 250, 4, 4, -1, 1);
}
}
// Elder: Spark effect for metal surfaces
if (cg_RQ3_impactEffects.integer && soundType == IMPACTSOUND_METAL) {
if (weapon != WP_GRENADE) {
if (weapon == WP_M3 || weapon == WP_HANDCANNON)
sparkCount = 5 + rand() % 5;
else if (weapon == WP_KNIFE)
sparkCount = 10 + rand() % 10;
else if (weapon == WP_SSG3000)
sparkCount = 25 + rand() % 20;
else
sparkCount = 15 + rand() % 15;
// Generate the particles
for (i = 0; i < sparkCount; i++) {
if (weapon == WP_KNIFE)
VectorScale(dir, 50 + rand() % 10, velocity);
else
VectorScale(dir, 150 + rand() % 30, velocity);
//random upwards sparks
if (rand() % 5 < 1)
velocity[2] += 120 + rand() % 30;
velocity[0] += rand() % 50 - 25;
velocity[1] += rand() % 50 - 25;
// NiceAss: Longer life, faster sparks == more eye candy! =)
CG_ParticleSparks(origin, velocity, 150 + rand() % 120, 2, 2, -5, 1);
}
}
}
//Makro: snow surfaces
if (cg_RQ3_impactEffects.integer && soundType == IMPACTSOUND_SNOW) {
if (weapon != WP_GRENADE) {
if (weapon == WP_M3 || weapon == WP_HANDCANNON)
sparkCount = 5 + rand() % 5;
else if (weapon == WP_KNIFE)
sparkCount = 10 + rand() % 10;
else if (weapon == WP_SSG3000)
sparkCount = 25 + rand() % 20;
else
sparkCount = 15 + rand() % 15;
// Generate the particles
for (i = 0; i < sparkCount; i++) {
if (weapon == WP_KNIFE)
VectorScale(dir, 60 + rand() % 20, velocity);
else
VectorScale(dir, 75 + rand() % 25, velocity);
CG_ParticleHitSnow(origin, velocity, 500 + rand() % 150, 2, 2, -5, 1);
}
}
}
//Makro: grass surfaces
if (cg_RQ3_impactEffects.integer && soundType == IMPACTSOUND_GRASS) {
if (weapon != WP_GRENADE) {
if (weapon == WP_M3 || weapon == WP_HANDCANNON)
sparkCount = 5 + rand() % 5;
else if (weapon == WP_KNIFE)
sparkCount = 10 + rand() % 10;
else if (weapon == WP_SSG3000)
sparkCount = 25 + rand() % 20;
else
sparkCount = 15 + rand() % 15;
// Generate the particles
for (i = 0; i < sparkCount; i++) {
if (weapon == WP_KNIFE)
VectorScale(dir, 60 + rand() % 20, velocity);
else
VectorScale(dir, 75 + rand() % 25, velocity);
CG_ParticleHitGrass(origin, velocity, 500 + rand() % 150, 2, 2, -5, 1);
}
}
}
// Elder: grenade explosion
if (cg_RQ3_impactEffects.integer && weapon == WP_GRENADE) {
vec3_t shrapnelDest;
sparkCount = 60 + rand() % 10;
origin[2] += 32;
for (i = 0; i < sparkCount; i++) {
VectorScale(dir, rand() % 200, velocity);
velocity[0] += rand() % 200 - 100;
velocity[1] += rand() % 200 - 100;
if (i % 8 == 7) {
// Add shrapnel trace effect
VectorMA(origin, 0.7f, velocity, shrapnelDest);
CG_ShrapnelSpark(origin, shrapnelDest, 10, 280);
}
// Add sparks
CG_ParticleSparks(origin, velocity, 900 + rand() % 200, 5, 5, -2.5f, 3.5f);
}
// Add smoke puff
puffDir[0] = 0;
puffDir[1] = 0;
puffDir[2] = 20;
origin[2] -= 16;
smokePuff = CG_SmokePuff(origin, puffDir,
rand() % 12 + 48,
1, 1, 1, 0.4f, 1750, cg.time, 0, 0, cgs.media.smokePuffShader);
}
}
/*
=================
CG_MissileHitPlayer
=================
*/
void CG_MissileHitPlayer(int weapon, vec3_t origin, vec3_t dir, int entityNum)
{
CG_Bleed(origin, entityNum);
}
/*
============================================================================
SHOTGUN TRACING
============================================================================
*/
/*
================
CG_ShotgunPellet
Elder: added shellWeapon param
We are not going to show every HC impact
================
*/
static void CG_ShotgunPellet(vec3_t start, vec3_t end, int skipNum, int shellWeapon)
{
trace_t tr;
int sourceContentType, destContentType, l, playerNum;
vec3_t t_start;
//Makro
int Material;
VectorCopy(start, t_start);
playerNum = skipNum;
// NiceAss: Allow M3/HC shots to go through teammates
for (l = 0; l < 10; l++) {
CG_Trace(&tr, t_start, NULL, NULL, end, skipNum, MASK_SHOT);
if (tr.entityNum < MAX_CLIENTS && cgs.gametype >= GT_TEAM &&
cgs.clientinfo[tr.entityNum].team == cgs.clientinfo[playerNum].team) {
VectorCopy(tr.endpos, t_start);
skipNum = tr.entityNum;
continue;
}
break;
}
sourceContentType = trap_CM_PointContents(start, 0);
destContentType = trap_CM_PointContents(tr.endpos, 0);
// FIXME: should probably move this cruft into CG_BubbleTrail
if (sourceContentType == destContentType) {
if (sourceContentType & CONTENTS_WATER) {
CG_BubbleTrail(start, tr.endpos, 32);
}
} else if (sourceContentType & CONTENTS_WATER) {
trace_t trace;
trap_CM_BoxTrace(&trace, end, start, NULL, NULL, 0, CONTENTS_WATER);
CG_BubbleTrail(start, trace.endpos, 32);
} else if (destContentType & CONTENTS_WATER) {
trace_t trace;
trap_CM_BoxTrace(&trace, start, end, NULL, NULL, 0, CONTENTS_WATER);
CG_BubbleTrail(tr.endpos, trace.endpos, 32);
}
if (tr.surfaceFlags & SURF_NOIMPACT) {
return;
}
if (cg_entities[tr.entityNum].currentState.eType == ET_PLAYER) {
//Blaze: Changed WP_SHOTGUN to WP_M3
//Elder: don't display so many blood stains - so we can reduce slow down
cg.shellHits++;
if (cg.shellHits < MAX_SHELL_HITS)
CG_MissileHitPlayer(WP_M3, 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;
}
//Makro - new surfaceparm system
Material = GetMaterialFromFlag(tr.surfaceFlags);
if (IsMetalMat(Material)) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0);
}
}
else if (Material == MAT_GLASS) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_GLASS, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_GLASS, 0);
}
}
//Makro - added
else if (IsWoodMat(Material)) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_WOOD, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_WOOD, 0);
}
}
//Makro - added
else if (Material == MAT_BRICK) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_BRICK, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_BRICK, 0);
}
}
//Makro - added
else if (Material == MAT_CERAMIC) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_CERAMIC, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_CERAMIC, 0);
}
}
//Makro - added
else if (IsSnowMat(Material)) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_SNOW, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_SNOW, 0);
}
}
//Makro - added
else if (Material == MAT_GRASS) {
//Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_GRASS, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_GRASS, 0);
}
} else {
// Elder: By default, the M3 and HC will spark on all surfaces
// Blaze: Changed WP_SHOTGUN to WP_M3
if (shellWeapon == WP_M3)
CG_MissileHitWall(WP_M3, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0);
else if (shellWeapon == WP_HANDCANNON && crandom() > 0.5) {
//Elder: show only approximately every other impact mark
CG_MissileHitWall(WP_HANDCANNON, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0);
}
}
}
}
/*
================
CG_ShotgunPattern
Perform the same traces the server did to locate the
hit splashes
================
*/
void CG_ShotgunPattern(vec3_t origin, vec3_t origin2, int otherEntNum, int shotType, int seed)
{
int i, count;
float r, u;
vec3_t end, forward, right, up;
int hc_multipler = 0;
// 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
//Elder: added
if (shotType == WP_M3)
count = DEFAULT_M3_COUNT;
else if (shotType == WP_HANDCANNON) {
count = DEFAULT_HANDCANNON_COUNT;
hc_multipler = 4;
} else {
count = DEFAULT_HANDCANNON_COUNT;
hc_multipler = 5;
}
cg.shellHits = 0;
for (i = 0; i < count; i++) {
if (shotType == WP_M3) {
r = Q_crandom(&seed) * DEFAULT_M3_HSPREAD * 16;
u = Q_crandom(&seed) * DEFAULT_M3_VSPREAD * 16;
} else {
//Elder: fill in shotType
shotType = WP_HANDCANNON;
r = Q_crandom(&seed) * DEFAULT_SHOTGUN_HSPREAD * 16 * 4;
u = Q_crandom(&seed) * DEFAULT_SHOTGUN_VSPREAD * 16 * hc_multipler;
}
VectorMA(origin, 8192 * 16, forward, end);
VectorMA(end, r, right, end);
VectorMA(end, u, up, end);
CG_ShotgunPellet(origin, end, otherEntNum, shotType);
}
//Reset shellHits once we're finished with it
cg.shellHits = 0;
}
/*
==============
CG_ShotgunFire
==============
*/
void CG_ShotgunFire(entityState_t * es, qboolean ism3)
{
vec3_t v;
int contents;
VectorSubtract(es->origin2, es->pos.trBase, v);
VectorNormalize(v);
VectorScale(v, 32, v);
VectorAdd(es->pos.trBase, v, v);
if (cgs.glconfig.hardwareType != GLHW_RAGEPRO) {
// ragepro can't alpha fade, so don't even bother with smoke
vec3_t up;
contents = trap_CM_PointContents(es->pos.trBase, 0);
if (!(contents & CONTENTS_WATER)) {
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);
}
}
//Elder: note param changes
if (ism3) {
CG_ShotgunPattern(es->pos.trBase, es->origin2, es->otherEntityNum, WP_M3, es->eventParm);
} else {
CG_ShotgunPattern(es->pos.trBase, es->origin2, es->otherEntityNum, WP_HANDCANNON, es->eventParm);
es->angles2[1] += 20;
//es->origin2[1] += 5;
CG_ShotgunPattern(es->pos.trBase, es->origin2, es->otherEntityNum, -1, es->eventParm);
}
}
/*
============================================================================
BULLETS
============================================================================
*/
/*
===============
CG_CreateTracer
- NiceAss
===============
*/
void CG_CreateTracer(int entity, vec3_t start, vec3_t end)
{
localEntity_t *le;
float dist;
vec3_t dir, end2, temp, midpoint;
float tracerSpeed = 5000;
VectorSubtract(end, start, dir);
dist = VectorNormalize(dir);
if (dist < 1.5 * cg_tracerLength.value)
return;
// Stop right before the end
VectorMA(end, -cg_tracerLength.value, dir, end2);
VectorSubtract(end2, start, temp);
dist = VectorLength(temp);
le = CG_AllocLocalEntity();
le->leType = LE_TRACER;
le->startTime = cg.time;
le->endTime = le->startTime + 1000.0 * (dist / tracerSpeed);
le->pos.trType = TR_LINEAR;
le->pos.trTime = le->startTime;
VectorCopy(start, le->pos.trBase);
VectorScale(dir, tracerSpeed, le->pos.trDelta);
midpoint[0] = (start[0] + end2[0]) * 0.5;
midpoint[1] = (start[1] + end2[1]) * 0.5;
midpoint[2] = (start[2] + end2[2]) * 0.5;
trap_S_StartSound(midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound);
}
/*
===============
CG_Tracer
===============
*/
void CG_Tracer(vec3_t source, vec3_t dest)
{
vec3_t forward, right, line, start, finish;
polyVert_t verts[4];
float len, begin, end;
// 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);
}
/*
======================
CG_Bullet
Renders bullet effects.
Elder: added soundType conditional
======================
*/
void CG_Bullet(vec3_t end, int sourceEntityNum, vec3_t normal,
qboolean flesh, int fleshEntityNum, impactSound_t soundType)
{
trace_t trace;
int sourceContentType, destContentType;
vec3_t start;
centity_t *cent;
float chance;
chance = cg_tracerChance.value;
if (chance > 0.4f)
chance = 0.4f;
// if the shooter is currently valid, calc a source point and possibly
// do trail effects
if (sourceEntityNum >= 0 && chance > 0) {
if (CG_CalcMuzzlePoint(sourceEntityNum, start)) {
sourceContentType = trap_CM_PointContents(start, 0);
destContentType = trap_CM_PointContents(end, 0);
// do a complete bubble trail if necessary
if ((sourceContentType == destContentType) && (sourceContentType & CONTENTS_WATER)) {
CG_BubbleTrail(start, end, 32);
}
// bubble trail from water into air
else if ((sourceContentType & CONTENTS_WATER)) {
trap_CM_BoxTrace(&trace, end, start, NULL, NULL, 0, CONTENTS_WATER);
CG_BubbleTrail(start, trace.endpos, 32);
}
// bubble trail from air into water
else if ((destContentType & CONTENTS_WATER)) {
trap_CM_BoxTrace(&trace, start, end, NULL, NULL, 0, CONTENTS_WATER);
CG_BubbleTrail(trace.endpos, end, 32);
// TODO: water splash mark
}
// draw a tracer
// Elder: only if not using SSG, check if this client is the source
if (sourceEntityNum == cg.snap->ps.clientNum) {
if (cg.snap->ps.weapon != WP_SSG3000 &&
(cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_SILENCER))) {
if (random() < chance)
CG_CreateTracer(sourceEntityNum, start, end);
}
} else {
cent = &cg_entities[sourceEntityNum];
if (cent->currentValid && cent->currentState.weapon != WP_SSG3000) {
if (random() < chance && cent->muzzleFlashTime != -1) {
CG_CreateTracer(sourceEntityNum, start, end);
}
}
}
}
}
// impact splash and mark
if (flesh) {
CG_Bleed(end, fleshEntityNum);
} else {
//Blaze: Changed WP_MACHINEGUN to WP_PISTOL
CG_MissileHitWall(WP_PISTOL, 0, end, normal, soundType, 0);
}
}
/*
==================
CG_LocalLaser
Elder:
Local laser dot if it is the client's own laser
==================
*/
static void CG_LocalLaser( void )
{
vec3_t muzzle, forward, end;
refEntity_t *re;
trace_t tr;
//Create the laser entity if it's not there
if (cg.laserSight == qfalse) {
cg.laserSight = qtrue;
cg.laserEnt = CG_AllocLocalEntity();
cg.laserEnt->startTime = cg.time;
cg.laserEnt->color[3] = 1.0;
}
//Setup refEntity stuff
re = &cg.laserEnt->refEntity;
re->radius = 6;
re->reType = RT_SPRITE;
re->rotation = 0;
re->customShader = cgs.media.laserShader;
//Calculate muzzle and endpoint
if (CG_CalcMuzzlePoint(cg.snap->ps.clientNum, muzzle)) {
AngleVectors(cg.snap->ps.viewangles, forward, NULL, NULL);
VectorMA(muzzle, 8192 * 16, forward, end);
} else {
CG_Error("CG_LocalLaser: Could not calculate own muzzle point\n");
}
CG_Trace(&tr, muzzle, NULL, NULL, end, cg.predictedPlayerState.clientNum, CONTENTS_SOLID);
//Set position of laser dot
if (tr.fraction != 1)
VectorMA(tr.endpos, -4, forward, tr.endpos);
VectorCopy(re->origin, re->oldorigin);
VectorCopy(tr.endpos, re->origin);
//Boost the endTime
cg.laserEnt->endTime += 10000;
//Don't render if it hits the sky
//if (!(tr.surfaceFlags & SURF_SKY))
trap_R_AddRefEntityToScene(re);
}
/*
==================
CG_CheckLaser
Elder:
Whether or not to use the local laser
Broken until I find a way to lerp an entity without a cent
==================
*/
void CG_CheckLaser()
{
//Elder: check for local laser
if ((cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_LASER)) &&
cg_RQ3_laserAssist.integer &&
(cg.snap->ps.weapon == WP_PISTOL || cg.snap->ps.weapon == WP_MP5 || cg.snap->ps.weapon == WP_M4)) {
CG_LocalLaser();
}
//Disable laser
else if (cg.laserSight == qtrue) {
//CG_Printf("Destroying Local Laser...\n");
CG_FreeLocalEntity(cg.laserEnt);
cg.laserSight = qfalse;
}
}
/*
==================
CG_ReloadWeapon
Elder: Does local reload effects like
reload shell drops, sounds, etc.
==================
*/
void CG_ReloadWeapon(centity_t * cent, int reloadStage)
{
entityState_t *ent;
weaponInfo_t *weap;
ent = &cent->currentState;
if (ent->weapon == WP_NONE)
return;
if (ent->weapon >= WP_NUM_WEAPONS) {
CG_Error("CG_ReloadWeapon: ent->weapon >= WP_NUM_WEAPONS");
return;
}
weap = &cg_weapons[ent->weapon];
switch (reloadStage) {
case 0:
if (weap->worldReloadSound[0] && ent->clientNum != cg.snap->ps.clientNum) {
trap_S_StartSound(cent->lerpOrigin, ent->number, CHAN_AUTO, weap->worldReloadSound[0]);
}
break;
case 1:
if (weap->worldReloadSound[1] && ent->clientNum != cg.snap->ps.clientNum) {
trap_S_StartSound(cent->lerpOrigin, ent->number, CHAN_AUTO, weap->worldReloadSound[1]);
}
if (ent->weapon == WP_HANDCANNON)
cent->ejectBrassTime = cg.time;
// CG_ShotgunEjectBrass( cent );
break;
case 2:
if (weap->worldReloadSound[2] && ent->clientNum != cg.snap->ps.clientNum) {
trap_S_StartSound(cent->lerpOrigin, ent->number, CHAN_AUTO, weap->worldReloadSound[2]);
}
break;
default:
CG_Error("CG_ReloadWeapon: Reload stage > 2\n");
break;
}
}