1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 /*
30 * name: cg_weapons.c
31 *
32 * desc: events and effects dealing with weapons
33 *
34 */
35
36 #include "cg_local.h"
37
38 int wolfkickModel;
39 int hWeaponSnd;
40 int hflakWeaponSnd;
41 int notebookModel;
42 int propellerModel;
43
44 vec3_t ejectBrassCasingOrigin;
45
46 //----(SA)
47 // forward decs
48 static int getAltWeapon( int weapnum );
49 int getEquivWeapon( int weapnum );
50 int CG_WeaponIndex( int weapnum, int *bank, int *cycle );
51 static qboolean CG_WeaponHasAmmo( int i );
52 static int maxWeapBanks = MAX_WEAP_BANKS, maxWeapsInBank = MAX_WEAPS_IN_BANK; // JPW NERVE
53
54 int weapBanks[MAX_WEAP_BANKS][MAX_WEAPS_IN_BANK] = {
55 // bank
56 {0, 0, 0 }, // 0 (empty)
57
58 {WP_KNIFE, 0, 0 }, // 1
59 {WP_LUGER, WP_COLT, 0 }, // 2 // WP_AKIMBO
60 {WP_MP40, WP_THOMPSON, WP_STEN }, // 3
61 {WP_MAUSER, WP_GARAND, 0 }, // 4
62 {WP_FG42, 0, 0 }, // 5
63 {WP_GRENADE_LAUNCHER, WP_GRENADE_PINEAPPLE, WP_DYNAMITE }, // 6
64 {WP_PANZERFAUST, 0, 0 }, // 7
65 {WP_VENOM, 0, 0 }, // 8
66 {WP_FLAMETHROWER, 0, 0 }, // 9
67 {WP_TESLA, 0, 0 } // 10
68 };
69
70 // JPW NERVE -- in mutiplayer, characters get knife/special on button 1, pistols on 2, 2-handed on 3
71 int weapBanksMultiPlayer[MAX_WEAP_BANKS_MP][MAX_WEAPS_IN_BANK_MP] = {
72 {0, 0, 0, 0, 0, 0, 0, 0 }, // empty bank '0'
73 {WP_KNIFE, 0, 0, 0, 0, 0, 0, 0 },
74 {WP_LUGER, WP_COLT, 0, 0, 0, 0, 0, 0 },
75 {WP_MP40, WP_THOMPSON, WP_STEN, WP_MAUSER, WP_GARAND, WP_PANZERFAUST, WP_VENOM, WP_FLAMETHROWER },
76 {WP_GRENADE_LAUNCHER, WP_GRENADE_PINEAPPLE, 0, 0, 0, 0, 0, 0, },
77 {WP_CLASS_SPECIAL, 0, 0, 0, 0, 0, 0, 0, },
78 {WP_DYNAMITE, 0, 0, 0, 0, 0, 0, 0 }
79 };
80 // jpw
81
82 //----(SA) end
83
84
85 /*
86 ==============
87 CG_MachineGunEjectBrassNew
88 ==============
89 */
CG_MachineGunEjectBrassNew(centity_t * cent)90 static void CG_MachineGunEjectBrassNew( centity_t *cent ) {
91 localEntity_t *le;
92 refEntity_t *re;
93 vec3_t velocity, xvelocity;
94 float waterScale = 1.0f;
95 vec3_t v[3];
96
97 if ( cg_brassTime.integer <= 0 ) {
98 return;
99 }
100
101 le = CG_AllocLocalEntity();
102 re = &le->refEntity;
103
104 velocity[0] = 16;
105 velocity[1] = -50 + 40 * crandom();
106 velocity[2] = 100 + 50 * crandom();
107
108 le->leType = LE_FRAGMENT;
109 le->startTime = cg.time;
110 le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random();
111
112 le->pos.trType = TR_GRAVITY;
113 le->pos.trTime = cg.time - ( rand() & 15 );
114
115 AnglesToAxis( cent->lerpAngles, v );
116
117 VectorCopy( ejectBrassCasingOrigin, re->origin );
118
119 VectorCopy( re->origin, le->pos.trBase );
120
121 if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { //----(SA) modified since slime is no longer deadly
122 // if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) {
123 waterScale = 0.10;
124 }
125
126 xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
127 xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
128 xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
129 VectorScale( xvelocity, waterScale, le->pos.trDelta );
130
131 AxisCopy( axisDefault, re->axis );
132 re->hModel = cgs.media.smallgunBrassModel;
133
134 le->bounceFactor = 0.4 * waterScale;
135
136 le->angles.trType = TR_LINEAR;
137 le->angles.trTime = cg.time;
138 le->angles.trBase[0] = rand() & 31;
139 le->angles.trBase[1] = rand() & 31;
140 le->angles.trBase[2] = rand() & 31;
141 le->angles.trDelta[0] = 2;
142 le->angles.trDelta[1] = 1;
143 le->angles.trDelta[2] = 0;
144
145 le->leFlags = LEF_TUMBLE;
146
147
148 {
149 int contents;
150 vec3_t end;
151 VectorCopy( cent->lerpOrigin, end );
152 end[2] -= 24;
153 contents = trap_CM_PointContents( end, 0 );
154 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
155 le->leBounceSoundType = LEBS_NONE;
156 } else {
157 le->leBounceSoundType = LEBS_BRASS;
158 }
159 }
160
161 le->leMarkType = LEMT_NONE;
162 }
163
164 /*
165 ==========================
166 CG_MachineGunEjectBrass
167 ==========================
168 */
169
CG_MachineGunEjectBrass(centity_t * cent)170 static void CG_MachineGunEjectBrass( centity_t *cent ) {
171 localEntity_t *le;
172 refEntity_t *re;
173 vec3_t velocity, xvelocity;
174 vec3_t offset, xoffset;
175 float waterScale = 1.0f;
176 vec3_t v[3];
177
178 if ( cg_brassTime.integer <= 0 ) {
179 return;
180 }
181
182 if ( !( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) && ( cent->currentState.clientNum == cg.snap->ps.clientNum ) ) {
183 CG_MachineGunEjectBrassNew( cent );
184 return;
185 }
186
187 le = CG_AllocLocalEntity();
188 re = &le->refEntity;
189
190 // velocity[0] = 0;
191 velocity[0] = 16; // Maxx Kaufman offset value
192 velocity[1] = -50 + 40 * crandom();
193 velocity[2] = 100 + 50 * crandom();
194
195 le->leType = LE_FRAGMENT;
196 le->startTime = cg.time;
197 le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random();
198
199 le->pos.trType = TR_GRAVITY;
200 le->pos.trTime = cg.time - ( rand() & 15 );
201
202 AnglesToAxis( cent->lerpAngles, v );
203
204 if ( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) {
205 offset[0] = 32;
206 offset[1] = -4;
207 offset[2] = 0;
208 } else if ( cg.predictedPlayerState.weapon == WP_MP40 || cg.predictedPlayerState.weapon == WP_THOMPSON ) {
209 offset[0] = 20; // Maxx Kaufman offset value
210 offset[1] = -4;
211 offset[2] = 24;
212 } else if ( cg.predictedPlayerState.weapon == WP_VENOM ) {
213 offset[0] = 12;
214 offset[1] = -4;
215 offset[2] = 24;
216 } else {
217 VectorClear( offset );
218 }
219
220 xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
221 xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
222 xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
223 VectorAdd( cent->lerpOrigin, xoffset, re->origin );
224
225 VectorCopy( re->origin, le->pos.trBase );
226
227 if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { //----(SA) modified since slime is no longer deadly
228 // if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) {
229 waterScale = 0.10;
230 }
231
232 xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
233 xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
234 xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
235 VectorScale( xvelocity, waterScale, le->pos.trDelta );
236
237 AxisCopy( axisDefault, re->axis );
238 re->hModel = cgs.media.machinegunBrassModel;
239
240 le->bounceFactor = 0.4 * waterScale;
241
242 le->angles.trType = TR_LINEAR;
243 le->angles.trTime = cg.time;
244 le->angles.trBase[0] = rand() & 31;
245 le->angles.trBase[1] = rand() & 31;
246 le->angles.trBase[2] = rand() & 31;
247 le->angles.trDelta[0] = 2;
248 le->angles.trDelta[1] = 1;
249 le->angles.trDelta[2] = 0;
250
251 le->leFlags = LEF_TUMBLE;
252
253 {
254 int contents;
255 vec3_t end;
256 VectorCopy( cent->lerpOrigin, end );
257 end[2] -= 24;
258 contents = trap_CM_PointContents( end, 0 );
259 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
260 le->leBounceSoundType = LEBS_NONE;
261 } else {
262 le->leBounceSoundType = LEBS_BRASS;
263 }
264 }
265
266 le->leMarkType = LEMT_NONE;
267 }
268
269
270 //----(SA) added
271 /*
272 ==============
273 CG_PanzerFaustEjectBrass
274 toss the 'used' panzerfaust casing (unit is one-shot, disposable)
275 ==============
276 */
CG_PanzerFaustEjectBrass(centity_t * cent)277 static void CG_PanzerFaustEjectBrass( centity_t *cent ) {
278 localEntity_t *le;
279 refEntity_t *re;
280 vec3_t velocity, xvelocity;
281 vec3_t offset, xoffset;
282 float waterScale = 1.0f;
283 vec3_t v[3];
284
285 le = CG_AllocLocalEntity();
286 re = &le->refEntity;
287
288 // velocity[0] = 16;
289 // velocity[1] = -50 + 40 * crandom();
290 // velocity[2] = 100 + 50 * crandom();
291
292 velocity[0] = 16;
293 velocity[1] = -200;
294 velocity[2] = 0;
295
296 le->leType = LE_FRAGMENT;
297 le->startTime = cg.time;
298 // le->startTime = cg.time + 2000;
299 le->endTime = le->startTime + ( cg_brassTime.integer * 8 ) + ( cg_brassTime.integer * random() );
300
301 le->pos.trType = TR_GRAVITY;
302 le->pos.trTime = cg.time - ( rand() & 15 );
303 // le->pos.trTime = cg.time - 2000;
304
305 AnglesToAxis( cent->lerpAngles, v );
306
307 // offset[0] = 12;
308 // offset[1] = -4;
309 // offset[2] = 24;
310
311 offset[0] = -24; // forward
312 offset[1] = -4; // left
313 offset[2] = 24; // up
314
315 xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
316 xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
317 xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
318 VectorAdd( cent->lerpOrigin, xoffset, re->origin );
319
320 VectorCopy( re->origin, le->pos.trBase );
321
322 if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) {
323 waterScale = 0.10;
324 }
325
326 xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0];
327 xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1];
328 xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2];
329 VectorScale( xvelocity, waterScale, le->pos.trDelta );
330
331 AxisCopy( axisDefault, re->axis );
332
333 // (SA) make it bigger
334 le->sizeScale = 3.0f;
335
336 re->hModel = cgs.media.panzerfaustBrassModel;
337
338 le->bounceFactor = 0.4 * waterScale;
339
340 le->angles.trType = TR_LINEAR;
341 le->angles.trTime = cg.time;
342 // le->angles.trBase[0] = rand()&31;
343 // le->angles.trBase[1] = rand()&31;
344 // le->angles.trBase[2] = rand()&31;
345 le->angles.trBase[0] = 0;
346 le->angles.trBase[1] = cent->currentState.apos.trBase[1]; // rotate to match the player
347 le->angles.trBase[2] = 0;
348 // le->angles.trDelta[0] = 2;
349 // le->angles.trDelta[1] = 1;
350 // le->angles.trDelta[2] = 0;
351 le->angles.trDelta[0] = 0;
352 le->angles.trDelta[1] = 0;
353 le->angles.trDelta[2] = 0;
354
355 le->leFlags = LEF_TUMBLE | LEF_SMOKING; // (SA) probably doesn't need to be 'tumble' since it doesn't really rotate much when flying
356
357 le->leBounceSoundType = LEBS_NONE;
358
359 le->leMarkType = LEMT_NONE;
360 }
361 /*
362 ==============
363 CG_SpearTrail
364 simple bubble trail behind a missile
365 ==============
366 */
CG_SpearTrail(centity_t * ent,const weaponInfo_t * wi)367 void CG_SpearTrail( centity_t *ent, const weaponInfo_t *wi ) {
368 int contents, lastContents;
369 vec3_t origin, lastPos;
370 entityState_t *es;
371
372 es = &ent->currentState;
373 BG_EvaluateTrajectory( &es->pos, cg.time, origin );
374 contents = CG_PointContents( origin, -1 );
375
376 BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
377 lastContents = CG_PointContents( lastPos, -1 );
378
379 ent->trailTime = cg.time;
380
381 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
382 if ( contents & lastContents & CONTENTS_WATER ) {
383 CG_BubbleTrail( lastPos, origin, 1, 8 );
384 }
385 }
386 }
387
388
389 // JPW NERVE -- LT pyro for marking air strikes
390 /*
391 ==========================
392 CG_PyroSmokeTrail
393 ==========================
394 */
CG_PyroSmokeTrail(centity_t * ent,const weaponInfo_t * wi)395 void CG_PyroSmokeTrail( centity_t *ent, const weaponInfo_t *wi ) {
396 int step;
397 vec3_t origin, lastPos, dir;
398 int startTime;
399 entityState_t *es;
400 int t;
401 float rnd;
402 static float grounddir = 99;
403
404 if ( grounddir == 99 ) { // pick a wind direction -- cheap trick because it can be different
405 grounddir = crandom(); // on different clients, but it's all smoke and mirrors anyway
406 }
407 step = 30;
408 es = &ent->currentState;
409 startTime = ent->trailTime;
410 t = step * ( ( startTime + step ) / step );
411
412 BG_EvaluateTrajectory( &es->pos, cg.time, origin );
413 BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
414
415 ent->trailTime = cg.time;
416
417 // drop fire trail sprites
418 for ( ; t <= ent->trailTime ; t += step ) {
419
420 BG_EvaluateTrajectory( &es->pos, t, lastPos );
421 // VectorCopy ( ent->lerpOrigin, lastPos );
422
423 if ( ent->currentState.density ) { // corkscrew effect
424 vec3_t right;
425 vec3_t angles;
426 VectorCopy( ent->currentState.apos.trBase, angles );
427 angles[ROLL] += cg.time % 360;
428 AngleVectors( angles, NULL, right, NULL );
429 VectorMA( lastPos, ent->currentState.density, right, lastPos );
430 }
431
432 dir[0] = crandom() * 5; // compute offset from flare base
433 dir[1] = crandom() * 5;
434 dir[2] = 0;
435 VectorAdd( lastPos,dir,origin ); // store in origin
436
437 rnd = random();
438
439 dir[0] = random() * 0.25;
440 dir[1] = grounddir; // simulate a little wind so it looks natural
441 dir[2] = random(); // one direction (so smoke goes side-like)
442 VectorNormalize( dir );
443 VectorScale( dir,45,dir ); // was 75
444
445 if ( !ent->currentState.otherEntityNum2 ) { // axis team, generate red smoke
446 CG_SmokePuff( origin, dir,
447 25 + rnd * 110, // width
448 rnd * 0.5 + 0.5, rnd * 0.5 + 0.5, 1, 0.5,
449 4800 + ( rand() % 2800 ), // duration was 2800+
450 t,
451 0,
452 0,
453 cgs.media.smokePuffShader );
454 } else {
455 CG_SmokePuff( origin, dir,
456 25 + rnd * 110, // width
457 1.0, rnd * 0.5 + 0.5, rnd * 0.5 + 0.5, 0.5,
458 4800 + ( rand() % 2800 ), // duration was 2800+
459 t,
460 0,
461 0,
462 cgs.media.smokePuffShader );
463 }
464 }
465 }
466 // jpw
467
468
469 // Ridah, new trail effects
470 /*
471 ==========================
472 CG_RocketTrail
473 ==========================
474 */
CG_RocketTrail(centity_t * ent,const weaponInfo_t * wi)475 void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) {
476 int step;
477 vec3_t origin, lastPos;
478 int contents;
479 int lastContents, startTime;
480 entityState_t *es;
481 int t;
482 // localEntity_t *le;
483
484 if ( ent->currentState.eType == ET_FLAMEBARREL ) {
485 step = 30;
486 } else if ( ent->currentState.eType == ET_FP_PARTS ) {
487 step = 50;
488 } else if ( ent->currentState.eType == ET_RAMJET ) {
489 step = 10;
490 } else {
491 step = 10;
492 }
493
494 es = &ent->currentState;
495 startTime = ent->trailTime;
496 t = step * ( ( startTime + step ) / step );
497
498 BG_EvaluateTrajectory( &es->pos, cg.time, origin );
499 contents = CG_PointContents( origin, -1 );
500
501 // if object (e.g. grenade) is stationary, don't toss up smoke
502 if ( ( ent->currentState.eType != ET_RAMJET ) && es->pos.trType == TR_STATIONARY ) {
503 ent->trailTime = cg.time;
504 return;
505 }
506
507 BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
508 lastContents = CG_PointContents( lastPos, -1 );
509
510 ent->trailTime = cg.time;
511
512 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
513 if ( contents & lastContents & CONTENTS_WATER ) {
514 CG_BubbleTrail( lastPos, origin, 3, 8 );
515 }
516 return;
517 }
518
519 // drop fire trail sprites
520 for ( ; t <= ent->trailTime ; t += step ) {
521 float rnd;
522
523 BG_EvaluateTrajectory( &es->pos, t, lastPos );
524 /*
525 le = CG_SmokePuff( lastPos, vec3_origin,
526 5, // width
527 1, 1, 1, 0.33,
528 150 + rand()%350, // duration
529 t,
530 0,
531 cgs.media.flameThrowerhitShader );
532
533 // use the optimized local entity add
534 le->leType = LE_SCALE_FADE;
535 */
536 rnd = random();
537 if ( ent->currentState.eType == ET_FLAMEBARREL ) {
538 if ( ( rand() % 100 ) > 50 ) {
539 CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, 100 + (int)( rnd * 400 ), 5, 7 + (int)( rnd * 10 ) ); // fire
540
541 }
542 CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke
543 } else if ( ent->currentState.eType == ET_FP_PARTS ) {
544 if ( ( rand() % 100 ) > 50 ) {
545 CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, 100 + (int)( rnd * 400 ), 5, 7 + (int)( rnd * 10 ) ); // fire
546
547 }
548 CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke
549 } else if ( ent->currentState.eType == ET_RAMJET ) {
550 int duration;
551
552 VectorCopy( ent->lerpOrigin, lastPos );
553 duration = 100;
554 CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, duration + (int)( rnd * 100 ), 5, 5 + (int)( rnd * 10 ) ); // fire
555 CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 400 + (int)( rnd * 750 ), 12, 24 + (int)( rnd * 30 ) ); // smoke
556 } else if ( ent->currentState.eType == ET_FIRE_COLUMN || ent->currentState.eType == ET_FIRE_COLUMN_SMOKE ) {
557 int duration;
558 int sizeStart;
559 int sizeEnd;
560
561 //VectorCopy (ent->lerpOrigin, lastPos);
562
563 if ( ent->currentState.density ) { // corkscrew effect
564 vec3_t right;
565 vec3_t angles;
566 VectorCopy( ent->currentState.apos.trBase, angles );
567 angles[ROLL] += cg.time % 360;
568 AngleVectors( angles, NULL, right, NULL );
569 VectorMA( lastPos, ent->currentState.density, right, lastPos );
570 }
571
572 duration = ent->currentState.angles[0];
573 sizeStart = ent->currentState.angles[1];
574 sizeEnd = ent->currentState.angles[2];
575
576 if ( !duration ) {
577 duration = 100;
578 }
579
580 if ( !sizeStart ) {
581 sizeStart = 5;
582 }
583
584 if ( !sizeEnd ) {
585 sizeEnd = 7;
586 }
587
588 CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, duration + (int)( rnd * 400 ), sizeStart, sizeEnd + (int)( rnd * 10 ) ); // fire
589
590 if ( ent->currentState.eType == ET_FIRE_COLUMN_SMOKE && ( rand() % 100 ) > 50 ) {
591 CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke
592 }
593 } else
594 {
595 //CG_ParticleExplosion( "twiltb", lastPos, vec3_origin, 300+(int)(rnd*100), 4, 14+(int)(rnd*8) ); // fire
596 CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke
597 }
598 }
599 /*
600 // spawn a smoke junction
601 if ((cg.time - ent->lastTrailTime) >= 50 + rand()%50) {
602 ent->headJuncIndex = CG_AddSmokeJunc( ent->headJuncIndex,
603 cgs.media.smokeTrailShader,
604 origin,
605 4500, 0.4, 20, 80 );
606 ent->lastTrailTime = cg.time;
607 }
608 */
609 // done.
610 }
611
612 // Ridah
613 /*
614 ==========================
615 CG_GrenadeTrail
616 ==========================
617 */
CG_GrenadeTrail(centity_t * ent,const weaponInfo_t * wi)618 static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) {
619 int step;
620 vec3_t origin, lastPos;
621 int contents;
622 int lastContents, startTime;
623 entityState_t *es;
624 int t;
625
626 step = 15; // nice and smooth curves
627
628 es = &ent->currentState;
629 startTime = ent->trailTime;
630 t = step * ( ( startTime + step ) / step );
631
632 BG_EvaluateTrajectory( &es->pos, cg.time, origin );
633 contents = CG_PointContents( origin, -1 );
634
635 // if object (e.g. grenade) is stationary, don't toss up smoke
636 if ( es->pos.trType == TR_STATIONARY ) {
637 ent->trailTime = cg.time;
638 return;
639 }
640
641 BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
642 lastContents = CG_PointContents( lastPos, -1 );
643
644 ent->trailTime = cg.time;
645
646 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
647 if ( contents & lastContents & CONTENTS_WATER ) {
648 CG_BubbleTrail( lastPos, origin, 2, 8 );
649 }
650 return;
651 }
652
653 //----(SA) trying this back on for DM
654
655 // spawn smoke junctions
656 for ( ; t <= ent->trailTime ; t += step ) {
657 BG_EvaluateTrajectory( &es->pos, t, origin );
658 ent->headJuncIndex = CG_AddSmokeJunc( ent->headJuncIndex,
659 cgs.media.smokeTrailShader,
660 origin,
661 // 1500, 0.3, 10, 50 );
662 1000, 0.3, 2, 20 );
663 ent->lastTrailTime = cg.time;
664 }
665 //----(SA) end
666 }
667 // done.
668
669
670
671
672 /*
673 ==========================
674 CG_NailgunEjectBrass
675 ==========================
676 */
677 /*
678 static void CG_NailgunEjectBrass( centity_t *cent ) {
679 localEntity_t *smoke;
680 vec3_t origin;
681 vec3_t v[3];
682 vec3_t offset;
683 vec3_t xoffset;
684 vec3_t up;
685
686 AnglesToAxis( cent->lerpAngles, v );
687
688 offset[0] = 0;
689 offset[1] = -12;
690 offset[2] = 24;
691
692 xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
693 xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
694 xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
695 VectorAdd( cent->lerpOrigin, xoffset, origin );
696
697 VectorSet( up, 0, 0, 64 );
698
699 smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader );
700 // use the optimized local entity add
701 smoke->leType = LE_SCALE_FADE;
702 }
703
704 static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) {
705 int step;
706 vec3_t origin, lastPos;
707 int t;
708 int startTime, contents;
709 int lastContents;
710 entityState_t *es;
711 vec3_t up;
712 localEntity_t *smoke;
713
714 up[0] = 0;
715 up[1] = 0;
716 up[2] = 0;
717
718 step = 50;
719
720 es = &ent->currentState;
721 startTime = ent->trailTime;
722 t = step * ( (startTime + step) / step );
723
724 BG_EvaluateTrajectory( &es->pos, cg.time, origin );
725 contents = CG_PointContents( origin, -1 );
726
727 // if object (e.g. grenade) is stationary, don't toss up smoke
728 if ( es->pos.trType == TR_STATIONARY ) {
729 ent->trailTime = cg.time;
730 return;
731 }
732
733 BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos );
734 lastContents = CG_PointContents( lastPos, -1 );
735
736 ent->trailTime = cg.time;
737
738 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
739 if ( contents & lastContents & CONTENTS_WATER ) {
740 CG_BubbleTrail( lastPos, origin, 1, 8 );
741 }
742 return;
743 }
744
745 for ( ; t <= ent->trailTime ; t += step ) {
746 BG_EvaluateTrajectory( &es->pos, t, lastPos );
747
748 smoke = CG_SmokePuff( lastPos, up,
749 wi->trailRadius,
750 1, 1, 1, 0.33f,
751 wi->wiTrailTime,
752 t,
753 0,
754 0,
755 cgs.media.nailPuffShader );
756 // use the optimized local entity add
757 smoke->leType = LE_SCALE_FADE;
758 }
759
760 }
761 */
762
763 /*
764 ==========================
765 CG_RailTrail
766 SA: re-inserted this as a debug mechanism for bullets
767 ==========================
768 */
CG_RailTrail2(clientInfo_t * ci,vec3_t start,vec3_t end)769 void CG_RailTrail2( clientInfo_t *ci, vec3_t start, vec3_t end ) {
770 localEntity_t *le;
771 refEntity_t *re;
772
773 le = CG_AllocLocalEntity();
774 re = &le->refEntity;
775
776 le->leType = LE_FADE_RGB;
777 le->startTime = cg.time;
778 le->endTime = cg.time + cg_railTrailTime.value;
779 le->lifeRate = 1.0 / ( le->endTime - le->startTime );
780
781 re->shaderTime = cg.time / 1000.0f;
782 re->reType = RT_RAIL_CORE;
783 re->customShader = cgs.media.railCoreShader;
784
785 VectorCopy( start, re->origin );
786 VectorCopy( end, re->oldorigin );
787
788 // // still allow different colors so we can tell AI shots from player shots, etc.
789 if ( ci ) {
790 le->color[0] = ci->color[0] * 0.75;
791 le->color[1] = ci->color[1] * 0.75;
792 le->color[2] = ci->color[2] * 0.75;
793 } else {
794 le->color[0] = 1;
795 le->color[1] = 0;
796 le->color[2] = 0;
797 }
798 le->color[3] = 1.0f;
799
800 AxisClear( re->axis );
801 }
802
803 //void CG_RailTrailBox( clientInfo_t *ci, vec3_t start, vec3_t end) {
804 /*
805 ==============
806 CG_RailTrail
807 modified so we could draw boxes for debugging as well
808 ==============
809 */
CG_RailTrail(clientInfo_t * ci,vec3_t start,vec3_t end,int type)810 void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end, int type ) { //----(SA) added 'type'
811 vec3_t diff, v1, v2, v3, v4, v5, v6;
812
813 if ( !type ) { // just a line
814 CG_RailTrail2( ci, start, end );
815 return;
816 }
817
818 // type '1' (box)
819
820 VectorSubtract( start, end, diff );
821
822 VectorCopy( start, v1 );
823 VectorCopy( start, v2 );
824 VectorCopy( start, v3 );
825 v1[0] -= diff[0];
826 v2[1] -= diff[1];
827 v3[2] -= diff[2];
828 CG_RailTrail2( ci, start, v1 );
829 CG_RailTrail2( ci, start, v2 );
830 CG_RailTrail2( ci, start, v3 );
831
832 VectorCopy( end, v4 );
833 VectorCopy( end, v5 );
834 VectorCopy( end, v6 );
835 v4[0] += diff[0];
836 v5[1] += diff[1];
837 v6[2] += diff[2];
838 CG_RailTrail2( ci, end, v4 );
839 CG_RailTrail2( ci, end, v5 );
840 CG_RailTrail2( ci, end, v6 );
841
842 CG_RailTrail2( ci, v2, v6 );
843 CG_RailTrail2( ci, v6, v1 );
844 CG_RailTrail2( ci, v1, v5 );
845
846 CG_RailTrail2( ci, v2, v4 );
847 CG_RailTrail2( ci, v4, v3 );
848 CG_RailTrail2( ci, v3, v5 );
849
850 }
851
852
853
854
855
856 /*
857 ======================
858 CG_ParseWeaponConfig
859 read information for weapon animations (first/length/fps)
860 ======================
861 */
CG_ParseWeaponConfig(const char * filename,weaponInfo_t * wi,int weaponNum)862 static qboolean CG_ParseWeaponConfig( const char *filename, weaponInfo_t *wi, int weaponNum ) {
863 char *text_p, *prev;
864 int len;
865 int i;
866 float fps;
867 char *token;
868 qboolean newfmt = qfalse; //----(SA)
869 char text[20000];
870 fileHandle_t f;
871
872 // load the file
873 len = trap_FS_FOpenFile( filename, &f, FS_READ );
874 if ( len <= 0 ) {
875 return qfalse;
876 }
877
878 if ( len >= sizeof( text ) - 1 ) {
879 CG_Printf( "File %s too long\n", filename );
880 return qfalse;
881 }
882
883 trap_FS_Read( text, len, f );
884 text[len] = 0;
885 trap_FS_FCloseFile( f );
886
887 // parse the text
888 text_p = text;
889
890 // read optional parameters
891 while ( 1 ) {
892 prev = text_p; // so we can unget
893 token = COM_Parse( &text_p );
894 if ( !token[0] ) { // get the variable
895 break;
896 }
897 if ( !Q_stricmp( token, "whatever_variable" ) ) {
898 token = COM_Parse( &text_p ); // get the value
899 if ( !token[0] ) {
900 break;
901 }
902 continue;
903 }
904
905 if ( !Q_stricmp( token, "newfmt" ) ) {
906 newfmt = qtrue;
907 continue;
908 }
909
910 // if it is a number, start parsing animations
911 if ( Q_isnumeric( token[0] ) ) {
912 text_p = prev; // unget the token
913 break;
914 }
915 Com_Printf( "unknown token in weapon cfg '%s' is %s\n", token, filename );
916 }
917
918
919 for ( i = 0 ; i < MAX_WP_ANIMATIONS ; i++ ) {
920
921 token = COM_Parse( &text_p ); // first frame
922 if ( !token[0] ) {
923 // don't show warning for weapon cfg without altswitch that does not require it.
924 if ( i == WEAP_ALTSWITCHFROM && weapAlts[weaponNum] == WP_NONE ) {
925 for ( ; i < MAX_WP_ANIMATIONS ; i++ ) {
926 Com_Memcpy( &wi->weapAnimations[i], &wi->weapAnimations[WEAP_IDLE1], sizeof( wi->weapAnimations[0] ) );
927 }
928 }
929 break;
930 }
931 wi->weapAnimations[i].firstFrame = atoi( token );
932
933 token = COM_Parse( &text_p ); // length
934 if ( !token[0] ) {
935 break;
936 }
937 wi->weapAnimations[i].numFrames = atoi( token );
938
939 token = COM_Parse( &text_p ); // fps
940 if ( !token[0] ) {
941 break;
942 }
943 fps = atof( token );
944 if ( fps == 0 ) {
945 fps = 1;
946 }
947
948 wi->weapAnimations[i].frameLerp = 1000 / fps;
949 wi->weapAnimations[i].initialLerp = 1000 / fps;
950
951 token = COM_Parse( &text_p ); // looping frames
952 if ( !token[0] ) {
953 break;
954 }
955 wi->weapAnimations[i].loopFrames = atoi( token );
956 if ( wi->weapAnimations[i].loopFrames > wi->weapAnimations[i].numFrames ) {
957 wi->weapAnimations[i].loopFrames = wi->weapAnimations[i].numFrames;
958 } else if ( wi->weapAnimations[i].loopFrames < 0 ) {
959 wi->weapAnimations[i].loopFrames = 0;
960 }
961
962
963 // store animation/draw bits in '.moveSpeed'
964
965 wi->weapAnimations[i].moveSpeed = 0;
966
967 if ( newfmt ) {
968 token = COM_Parse( &text_p ); // barrel anim bits
969 if ( !token[0] ) {
970 break;
971 }
972 wi->weapAnimations[i].moveSpeed = atoi( token );
973
974 token = COM_Parse( &text_p ); // animated weapon
975 if ( !token[0] ) {
976 break;
977 }
978 if ( atoi( token ) ) {
979 wi->weapAnimations[i].moveSpeed |= ( 1 << W_MAX_PARTS ); // set the bit one higher than can be set by the barrel bits
980
981 }
982 token = COM_Parse( &text_p ); // barrel hide bits (so objects can be flagged to not be drawn during all sequences (a reloading hand that comes in from off screen for that one animation for example)
983 if ( !token[0] ) {
984 break;
985 }
986 wi->weapAnimations[i].moveSpeed |= ( ( atoi( token ) ) << 8 ); // use 2nd byte for draw bits
987 }
988
989 }
990
991 if ( i != MAX_WP_ANIMATIONS ) {
992 CG_Printf( "Error parsing weapon animation file: %s\n", filename );
993 return qfalse;
994 }
995
996
997 return qtrue;
998 }
999
1000
1001 /*
1002 =================
1003 CG_RegisterWeapon
1004
1005 The server says this item is used on this level
1006 =================
1007 */
CG_RegisterWeapon(int weaponNum)1008 void CG_RegisterWeapon( int weaponNum ) {
1009 weaponInfo_t *weaponInfo;
1010 gitem_t *item, *ammo;
1011 char path[MAX_QPATH], comppath[MAX_QPATH];
1012 vec3_t mins, maxs;
1013 int i;
1014
1015 weaponInfo = &cg_weapons[weaponNum];
1016
1017 // don't bother trying
1018 switch ( weaponNum ) {
1019 case WP_NONE:
1020 case WP_CLASS_SPECIAL:
1021 case WP_MONSTER_ATTACK1:
1022 case WP_MONSTER_ATTACK2:
1023 case WP_MONSTER_ATTACK3:
1024 case WP_GAUNTLET:
1025 case WP_SNIPER:
1026 case WP_MORTAR:
1027
1028 // (SA) i don't know about these, but we don't have models for 'em
1029 case WP_GRENADE_SMOKE:
1030 case WP_MEDIC_HEAL:
1031 return;
1032 default:
1033 break;
1034 }
1035
1036 if ( weaponInfo->registered ) {
1037 return;
1038 }
1039
1040 memset( weaponInfo, 0, sizeof( *weaponInfo ) );
1041 weaponInfo->registered = qtrue;
1042
1043 for ( item = bg_itemlist + 1 ; item->classname ; item++ ) {
1044 if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) {
1045 weaponInfo->item = item;
1046 break;
1047 }
1048 }
1049 if ( !item->classname ) {
1050 CG_Error( "Couldn't find weapon %i", weaponNum );
1051 }
1052
1053 CG_RegisterItemVisuals( item - bg_itemlist );
1054
1055 // load cmodel before model so filecache works
1056
1057 // alternate view weapon
1058 weaponInfo->weaponModel[W_TP_MODEL] = trap_R_RegisterModel( item->world_model[W_TP_MODEL] );
1059 weaponInfo->weaponModel[W_FP_MODEL] = trap_R_RegisterModel( item->world_model[W_FP_MODEL] );
1060 weaponInfo->weaponModel[W_SKTP_MODEL] = trap_R_RegisterModel( item->world_model[W_SKTP_MODEL] );
1061
1062 if ( !weaponInfo->weaponModel[W_FP_MODEL] || !cg_drawFPGun.integer ) {
1063 weaponInfo->weaponModel[W_FP_MODEL] = weaponInfo->weaponModel[W_TP_MODEL];
1064 }
1065
1066 if ( !weaponInfo->weaponModel[W_TP_MODEL] ) {
1067 // left commented out since we have level-loading optimization issues to still resolve.
1068 // ie. every weapon and it's associated effects/parts/sounds etc. are loaded for every level.
1069 // This was turned off when we started (the "only load what the level calls for" thing) because when
1070 // DM does a "give all" and fires, he doesn't want to wait for everything to load. So perhaps a "cacheallweaps" or something.
1071 // CG_Printf( "Couldn't register weapon model %i (unable to load view model)\n", weaponNum );
1072 // RF, I need to be able to run the game, I dont have the silencer weapon (19)
1073 #ifndef _DEBUG
1074 // CG_Error( "Couldn't register weapon model %i (unable to load view model)", weaponNum );
1075 #endif
1076 return;
1077 }
1078
1079
1080 // load weapon config
1081 //----(SA) modified. use first person model for finding weapon config name, not third
1082 if ( item->world_model[W_FP_MODEL] ) {
1083 COM_StripFilename( item->world_model[W_FP_MODEL], path );
1084 if ( !CG_ParseWeaponConfig( va( "%sweapon.cfg", path ), weaponInfo, weaponNum ) ) {
1085 CG_Error( "Couldn't register weapon %i (%s) (failed to parse weapon.cfg)", weaponNum, path );
1086 }
1087 }
1088 //----(SA) end
1089
1090 // calc midpoint for rotation
1091 trap_R_ModelBounds( weaponInfo->weaponModel[W_TP_MODEL], mins, maxs );
1092
1093 for ( i = 0 ; i < 3 ; i++ ) {
1094 weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] );
1095 }
1096
1097 weaponInfo->weaponIcon[0] = trap_R_RegisterShader( item->icon );
1098 weaponInfo->weaponIcon[1] = trap_R_RegisterShader( va( "%s_select", item->icon ) ); // get the 'selected' icon as well
1099
1100 // JOSEPH 4-17-00
1101 weaponInfo->ammoIcon = trap_R_RegisterShader( item->ammoicon );
1102 // END JOSEPH
1103
1104 for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) {
1105 if ( ( ammo->giType == IT_AMMO && ammo->giTag == BG_FindAmmoForWeapon( weaponNum ) ) ) {
1106 break;
1107 }
1108 }
1109 if ( ammo->classname && ammo->world_model[0] ) {
1110 weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] );
1111 }
1112
1113 if ( item->world_model[W_FP_MODEL] ) {
1114 Q_strncpyz( comppath, item->world_model[W_FP_MODEL], sizeof(comppath) ); // first try the fp view weap
1115 } else if ( item->world_model[W_TP_MODEL] ) {
1116 Q_strncpyz( comppath, item->world_model[W_TP_MODEL], sizeof(comppath) ); // not there, use the standard view hand
1117 }
1118
1119 if ( !cg_drawFPGun.integer && item->world_model[W_TP_MODEL] ) { // then if it didn't find the 1st person one or you are set to not use one
1120 Q_strncpyz( comppath, item->world_model[W_TP_MODEL], sizeof(comppath) ); // use the standard view hand
1121 }
1122
1123 for ( i = W_TP_MODEL; i < W_NUM_TYPES; i++ )
1124 {
1125 int j;
1126
1127 if ( !item->world_model[i] ) {
1128 Q_strncpyz( path, comppath, sizeof(path) );
1129 } else {
1130 Q_strncpyz( path, item->world_model[i], sizeof(path) );
1131 }
1132
1133 COM_StripExtension( path, path, sizeof(path) );
1134 Q_strcat( path, sizeof(path), "_flash.md3" );
1135 weaponInfo->flashModel[i] = trap_R_RegisterModel( path );
1136
1137
1138 for ( j = 0; j < W_MAX_PARTS; j++ ) {
1139 if ( !item->world_model[i] ) {
1140 Q_strncpyz( path, comppath, sizeof(path) );
1141 } else {
1142 Q_strncpyz( path, item->world_model[i], sizeof(path) );
1143 }
1144 COM_StripExtension( path, path, sizeof(path) );
1145 if ( j == W_PART_1 ) {
1146 Q_strcat( path, sizeof(path), "_barrel.md3" );
1147 } else {
1148 Q_strcat( path, sizeof(path), va( "_barrel%d.md3", j + 1 ) );
1149 }
1150 weaponInfo->wpPartModels[i][j] = trap_R_RegisterModel( path );
1151 }
1152
1153 // used for spinning belt on venom
1154 if ( i == W_FP_MODEL ) {
1155 if ( !item->world_model[2] ) {
1156 Q_strncpyz( path, comppath, sizeof(path) );
1157 } else {
1158 Q_strncpyz( path, item->world_model[2], sizeof(path) );
1159 }
1160 COM_StripExtension( path, path, sizeof(path) );
1161 Q_strcat( path, sizeof(path), "_barrel6b.md3" );
1162 weaponInfo->wpPartModels[i][W_PART_7] = trap_R_RegisterModel( path );
1163 }
1164 }
1165
1166
1167 // sniper scope model
1168 if ( weaponNum == WP_MAUSER || weaponNum == WP_GARAND ) {
1169
1170 if ( !item->world_model[W_FP_MODEL] ) {
1171 Q_strncpyz( path, comppath, sizeof(path) );
1172 } else {
1173 Q_strncpyz( path, item->world_model[W_FP_MODEL], sizeof(path) );
1174 }
1175 COM_StripExtension( path, path, sizeof(path) );
1176 Q_strcat( path, sizeof(path), "_scope.md3" );
1177 weaponInfo->modModel[0] = trap_R_RegisterModel( path );
1178 }
1179
1180 if ( !item->world_model[W_FP_MODEL] ) {
1181 Q_strncpyz( path, comppath, sizeof(path) );
1182 } else {
1183 Q_strncpyz( path, item->world_model[W_FP_MODEL], sizeof(path) );
1184 }
1185 COM_StripExtension( path, path, sizeof(path) );
1186 Q_strcat( path, sizeof(path), "_hand.md3" );
1187 weaponInfo->handsModel = trap_R_RegisterModel( path );
1188
1189 if ( !weaponInfo->handsModel ) {
1190 weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );
1191 }
1192
1193 //----(SA) weapon pickup 'stand'
1194 if ( !item->world_model[W_TP_MODEL] ) {
1195 Q_strncpyz( path, comppath, sizeof(path) );
1196 } else {
1197 Q_strncpyz( path, item->world_model[W_TP_MODEL], sizeof(path) );
1198 }
1199 COM_StripExtension( path, path, sizeof(path) );
1200 Q_strcat( path, sizeof(path), "_stand.md3" );
1201 weaponInfo->standModel = trap_R_RegisterModel( path );
1202 //----(SA) end
1203
1204 switch ( weaponNum ) {
1205 case WP_MONSTER_ATTACK1:
1206 case WP_MONSTER_ATTACK2:
1207 case WP_MONSTER_ATTACK3:
1208 break;
1209
1210
1211 case WP_AKIMBO: //----(SA) added
1212 // same as colt
1213 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1214 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/colt/coltf1.wav" );
1215 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); // use same as mp40
1216 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1217
1218 // unique
1219 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/colt/colt_reload2.wav" );
1220 break;
1221
1222 case WP_COLT:
1223 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1224 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/colt/coltf1.wav" );
1225 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); // use same as mp40
1226 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/colt/colt_reload.wav" );
1227 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1228 break;
1229
1230
1231 case WP_KNIFE:
1232 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/knife/knife_slash1.wav" );
1233 weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/knife/knife_slash2.wav" );
1234 break;
1235
1236 case WP_LUGER:
1237 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1238
1239 weaponInfo->switchSound[0] = trap_S_RegisterSound( "sound/weapons/luger/silencerremove.wav" ); //----(SA) added
1240
1241 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/luger/lugerf1.wav" );
1242 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); // use same as mp40
1243 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/luger/luger_reload.wav" );
1244 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1245 break;
1246
1247 case WP_SILENCER: // luger mod
1248 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1249
1250 weaponInfo->switchSound[0] = trap_S_RegisterSound( "sound/weapons/luger/silencerattatch.wav" ); //----(SA) added
1251
1252 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/luger/silencerf1.wav" );
1253 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/luger/luger_reload.wav" );
1254 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1255 break;
1256
1257 case WP_MAUSER:
1258 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1259 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mauserf1.wav" );
1260 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mausere1.wav" );
1261 weaponInfo->lastShotSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mauserf1_last.wav" );
1262 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/mauser/mauser_reload.wav" );
1263 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1264 break;
1265 case WP_SNIPERRIFLE:
1266 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1267 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/sniperf1.wav" );
1268 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mausere1.wav" );
1269 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/mauser/sniper_reload.wav" );
1270 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1271 break;
1272
1273 case WP_GARAND:
1274 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1275 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/garand/garandf1.wav" );
1276 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/garand/garand_reload.wav" );
1277 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1278 break;
1279 case WP_SNOOPERSCOPE:
1280 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1281 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/garand/snooperf1.wav" );
1282 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/garand/snooper_reload.wav" );
1283 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1284 break;
1285
1286 case WP_THOMPSON:
1287 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1288 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/thompson/thompson.wav" );
1289 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); // use same as mp40
1290 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/thompson/thompson_reload.wav" );
1291 weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/thompson/thompson_overheat.wav" );
1292 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1293 break;
1294
1295 case WP_MP40:
1296 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1297 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40f1.wav" );
1298 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" );
1299 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/mp40/mp40_reload.wav" );
1300 weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/mp40/mp40_overheat.wav" );
1301 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1302 break;
1303
1304 case WP_STEN:
1305 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1306 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/sten/stenf1.wav" );
1307 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/sten/sten_reload.wav" );
1308 weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/sten/sten_overheat.wav" );
1309 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1310 break;
1311
1312 case WP_FG42:
1313 case WP_FG42SCOPE:
1314 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1315 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/fg42/fg42f1.wav" );
1316 weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/fg42/fg42e1.wav" );
1317 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/fg42/fg42_reload.wav" );
1318 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1319 break;
1320 //----(SA) end
1321
1322
1323 case WP_PANZERFAUST:
1324 weaponInfo->ejectBrassFunc = CG_PanzerFaustEjectBrass;
1325 weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" );
1326 weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav" );
1327 weaponInfo->missileTrailFunc = CG_RocketTrail;
1328 weaponInfo->missileDlight = 200;
1329 MAKERGB( weaponInfo->flashDlightColor, 0.75, 0.3, 0.0 );
1330 MAKERGB( weaponInfo->missileDlightColor, 0.75, 0.3, 0.0 );
1331 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav" );
1332 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/rocket/rocklf_reload.wav" );
1333 cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" );
1334 break;
1335
1336 case WP_MORTAR:
1337 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mortar/mortarf1.wav" );
1338 weaponInfo->missileTrailFunc = CG_GrenadeTrail;
1339 weaponInfo->missileDlight = 400;
1340 weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav" );
1341 weaponInfo->wiTrailTime = 300;
1342 weaponInfo->trailRadius = 32;
1343 MAKERGB( weaponInfo->flashDlightColor, 1, 0.7, 0.5 );
1344 break;
1345 // JPW NERVE
1346 case WP_GRENADE_SMOKE:
1347 weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/grenade/pineapple.md3" );
1348 weaponInfo->missileTrailFunc = CG_PyroSmokeTrail;
1349 weaponInfo->missileDlight = 200;
1350 weaponInfo->wiTrailTime = 4000;
1351 weaponInfo->trailRadius = 256;
1352 break;
1353 // jpw
1354 // DHM - Nerve - temp effects
1355 case WP_CLASS_SPECIAL:
1356 case WP_MEDIC_HEAL:
1357 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/knife/knife_slash1.wav" );
1358 weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/knife/knife_slash2.wav" );
1359 break;
1360 // dhm
1361 case WP_GRENADE_LAUNCHER:
1362 case WP_GRENADE_PINEAPPLE:
1363 if ( weaponNum == WP_GRENADE_LAUNCHER ) {
1364 weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" );
1365 } else {
1366 weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/grenade/pineapple.md3" );
1367 }
1368 weaponInfo->missileTrailFunc = CG_GrenadeTrail;
1369 weaponInfo->wiTrailTime = 700;
1370 // weaponInfo->wiTrailTime = 2000;
1371 weaponInfo->wiTrailTime = 1000;
1372 weaponInfo->trailRadius = 32;
1373 MAKERGB( weaponInfo->flashDlightColor, 1, 0.7, 0.5 );
1374 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav" );
1375 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/grenade/grenlf_reload.wav" );
1376 cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" );
1377 break;
1378
1379 case WP_DYNAMITE:
1380 weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/dynamite.md3" );
1381 // weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav" );
1382 // weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/grenade/grenlf_reload.wav" );
1383 cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" );
1384 break;
1385
1386 case WP_VENOM:
1387 MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 );
1388 weaponInfo->spinupSound = trap_S_RegisterSound( "sound/weapons/venom/venomsu1.wav" ); //----(SA) added
1389 weaponInfo->spindownSound = trap_S_RegisterSound( "sound/weapons/venom/venomsd1.wav" ); //----(SA) added
1390 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/venom/venomf1.wav" );
1391 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/venom/venom_reload.wav" );
1392 weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/venom/venom_overheat.wav" );
1393 weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass;
1394 break;
1395
1396 case WP_FLAMETHROWER:
1397 //MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.7, 0.4 );
1398 break;
1399
1400 case WP_TESLA:
1401 MAKERGB( weaponInfo->flashDlightColor, 0.2, 0.6, 1 );
1402 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/tesla/teslaf1.wav" );
1403 weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/tesla/tesla_reload.wav" );
1404 weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/tesla/tesla_overheat.wav" );
1405 break;
1406
1407
1408 case WP_GAUNTLET:
1409 MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 );
1410 //weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav" );
1411 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav" );
1412 break;
1413
1414 default:
1415 MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 );
1416 weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav" );
1417 break;
1418 }
1419 }
1420
1421 /*
1422 =================
1423 CG_RegisterItemVisuals
1424
1425 The server says this item is used on this level
1426 =================
1427 */
CG_RegisterItemVisuals(int itemNum)1428 void CG_RegisterItemVisuals( int itemNum ) {
1429 itemInfo_t *itemInfo;
1430 gitem_t *item;
1431 int i;
1432
1433 itemInfo = &cg_items[ itemNum ];
1434 if ( itemInfo->registered ) {
1435 return;
1436 }
1437
1438 item = &bg_itemlist[ itemNum ];
1439
1440 memset( itemInfo, 0, sizeof( *itemInfo ) );
1441
1442 for ( i = 0; i < MAX_ITEM_MODELS; i++ )
1443 itemInfo->models[i] = trap_R_RegisterModel( item->world_model[i] );
1444
1445
1446 itemInfo->icons[0] = trap_R_RegisterShader( item->icon );
1447 if ( item->giType == IT_HOLDABLE ) {
1448 // (SA) register alternate icons (since holdables can have multiple uses, they might have different icons to represent how many uses are left)
1449 for ( i = 1; i < MAX_ITEM_ICONS; i++ )
1450 itemInfo->icons[i] = trap_R_RegisterShader( va( "%s%i", item->icon, i + 1 ) );
1451 }
1452
1453 if ( item->giType == IT_WEAPON ) {
1454 CG_RegisterWeapon( item->giTag );
1455 }
1456
1457 itemInfo->registered = qtrue; //----(SA) moved this down after the registerweapon()
1458
1459 wolfkickModel = trap_R_RegisterModel( "models/weapons2/foot/v_wolfoot_10f.md3" );
1460 hWeaponSnd = trap_S_RegisterSound( "sound/weapons/mg42/37mm.wav" );
1461
1462 hflakWeaponSnd = trap_S_RegisterSound( "sound/weapons/flak/flak.wav" );
1463 notebookModel = trap_R_RegisterModel( "models/mapobjects/book/book.md3" );
1464 propellerModel = trap_R_RegisterModel( "models/mapobjects/vehicles/m109_prop.md3" );
1465
1466 // JPW NERVE had to put this somewhere, this seems OK
1467 if ( cg_gameType.integer != GT_WOLF ) {
1468 maxWeapBanks = MAX_WEAP_BANKS;
1469 maxWeapsInBank = MAX_WEAPS_IN_BANK;
1470 } else {
1471 trap_R_RegisterModel( "models/mapobjects/vehicles/m109.md3" );
1472 CG_RegisterWeapon( WP_GRENADE_SMOKE ); // register WP_CLASS_SPECIAL visuals here
1473 CG_RegisterWeapon( WP_MEDIC_HEAL );
1474 maxWeapBanks = MAX_WEAP_BANKS_MP;
1475 maxWeapsInBank = MAX_WEAPS_IN_BANK_MP;
1476 }
1477 // if player runs out of SMG ammunition, it shouldn't *also* deplete pistol ammunition. If you change this, change
1478 // g_spawn.c as well
1479 if ( cg_gameType.integer != GT_SINGLE_PLAYER ) {
1480 item = BG_FindItem( "Thompson" );
1481 item->giAmmoIndex = WP_THOMPSON;
1482 item = BG_FindItem( "Sten" );
1483 item->giAmmoIndex = WP_STEN;
1484 item = BG_FindItem( "MP40" );
1485 item->giAmmoIndex = WP_MP40;
1486 }
1487 // jpw
1488 }
1489
1490
1491 /*
1492 ========================================================================================
1493
1494 VIEW WEAPON
1495
1496 ========================================================================================
1497 */
1498
1499
1500 //
1501 // weapon animations
1502 //
1503
1504 /*
1505 ==============
1506 CG_GetPartFramesFromWeap
1507 get animation info from the parent if necessary
1508 ==============
1509 */
CG_GetPartFramesFromWeap(centity_t * cent,refEntity_t * part,refEntity_t * parent,int partid,weaponInfo_t * wi)1510 qboolean CG_GetPartFramesFromWeap( centity_t *cent, refEntity_t *part, refEntity_t *parent, int partid, weaponInfo_t *wi ) {
1511 int i;
1512 int frameoffset = 0;
1513 animation_t *anim;
1514
1515 anim = cent->pe.weap.animation;
1516
1517 if ( partid == W_MAX_PARTS ) {
1518 return qtrue; // primary weap model drawn for all frames right now
1519 }
1520
1521 // check draw bit
1522 if ( anim->moveSpeed & ( 1 << ( partid + 8 ) ) ) { // hide bits are in high byte
1523 return qfalse; // not drawn for current sequence
1524 }
1525
1526 // find part's start frame for this animation sequence
1527 for ( i = 0; i < cent->pe.weap.animationNumber; i++ ) {
1528 if ( wi->weapAnimations[i].moveSpeed & ( 1 << partid ) ) { // this part has animation for this sequence
1529 frameoffset += wi->weapAnimations[i].numFrames;
1530 }
1531 }
1532
1533 // now set the correct frame into the part
1534 if ( anim->moveSpeed & ( 1 << partid ) ) {
1535 part->backlerp = parent->backlerp;
1536 part->oldframe = frameoffset + ( parent->oldframe - anim->firstFrame );
1537 part->frame = frameoffset + ( parent->frame - anim->firstFrame );
1538 }
1539
1540 return qtrue;
1541 }
1542
1543
1544 /*
1545 ===============
1546 CG_SetWeapLerpFrameAnimation
1547
1548 may include ANIM_TOGGLEBIT
1549 ===============
1550 */
CG_SetWeapLerpFrameAnimation(weaponInfo_t * wi,lerpFrame_t * lf,int newAnimation)1551 static void CG_SetWeapLerpFrameAnimation( weaponInfo_t *wi, lerpFrame_t *lf, int newAnimation ) {
1552 animation_t *anim;
1553
1554 lf->animationNumber = newAnimation;
1555 newAnimation &= ~ANIM_TOGGLEBIT;
1556
1557 if ( newAnimation < 0 || newAnimation >= MAX_WP_ANIMATIONS ) {
1558 CG_Error( "Bad animation number (CG_SWLFA): %i", newAnimation );
1559 }
1560
1561 anim = &wi->weapAnimations[ newAnimation ];
1562
1563 lf->animation = anim;
1564 lf->animationTime = lf->frameTime + anim->initialLerp;
1565
1566 if ( cg_debugAnim.integer & 2 ) {
1567 CG_Printf( "Weap Anim: %d\n", newAnimation );
1568 }
1569 }
1570
1571
1572 /*
1573 ===============
1574 CG_ClearWeapLerpFrame
1575 ===============
1576 */
CG_ClearWeapLerpFrame(weaponInfo_t * wi,lerpFrame_t * lf,int animationNumber)1577 void CG_ClearWeapLerpFrame( weaponInfo_t *wi, lerpFrame_t *lf, int animationNumber ) {
1578 lf->frameTime = lf->oldFrameTime = cg.time;
1579 CG_SetWeapLerpFrameAnimation( wi, lf, animationNumber );
1580 lf->oldFrame = lf->frame = lf->animation->firstFrame;
1581
1582 }
1583
1584
1585 /*
1586 ===============
1587 CG_RunWeapLerpFrame
1588
1589 Sets cg.snap, cg.oldFrame, and cg.backlerp
1590 cg.time should be between oldFrameTime and frameTime after exit
1591 ===============
1592 */
CG_RunWeapLerpFrame(clientInfo_t * ci,weaponInfo_t * wi,lerpFrame_t * lf,int newAnimation,float speedScale)1593 static void CG_RunWeapLerpFrame( clientInfo_t *ci, weaponInfo_t *wi, lerpFrame_t *lf, int newAnimation, float speedScale ) {
1594 int f;
1595 animation_t *anim;
1596
1597 // debugging tool to get no animations
1598 if ( cg_animSpeed.integer == 0 ) {
1599 lf->oldFrame = lf->frame = lf->backlerp = 0;
1600 return;
1601 }
1602
1603 // see if the animation sequence is switching
1604 if ( !lf->animation ) {
1605 CG_ClearWeapLerpFrame( wi, lf, newAnimation );
1606 } else if ( newAnimation != lf->animationNumber ) {
1607 if ( ( newAnimation & ~ANIM_TOGGLEBIT ) == WEAP_RAISE ||
1608 ( newAnimation & ~ANIM_TOGGLEBIT ) == WEAP_ALTSWITCHFROM ||
1609 ( newAnimation & ~ANIM_TOGGLEBIT ) == WEAP_ALTSWITCHTO ) {
1610 CG_ClearWeapLerpFrame( wi, lf, newAnimation ); // clear when switching to raise (since it should be out of view anyway)
1611 } else {
1612 CG_SetWeapLerpFrameAnimation( wi, lf, newAnimation );
1613 }
1614 }
1615
1616 // if we have passed the current frame, move it to
1617 // oldFrame and calculate a new frame
1618 if ( cg.time >= lf->frameTime ) {
1619 lf->oldFrame = lf->frame;
1620 lf->oldFrameTime = lf->frameTime;
1621
1622 // get the next frame based on the animation
1623 anim = lf->animation;
1624 if ( !anim->frameLerp ) {
1625 return; // shouldn't happen
1626 }
1627 if ( cg.time < lf->animationTime ) {
1628 lf->frameTime = lf->animationTime; // initial lerp
1629 } else {
1630 lf->frameTime = lf->oldFrameTime + anim->frameLerp;
1631 }
1632 f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
1633 f *= speedScale; // adjust for haste, etc
1634 if ( f >= anim->numFrames ) {
1635 f -= anim->numFrames;
1636 if ( anim->loopFrames ) {
1637 f %= anim->loopFrames;
1638 f += anim->numFrames - anim->loopFrames;
1639 } else {
1640 f = anim->numFrames - 1;
1641 // the animation is stuck at the end, so it
1642 // can immediately transition to another sequence
1643 lf->frameTime = cg.time;
1644 }
1645 }
1646 lf->frame = anim->firstFrame + f;
1647 if ( cg.time > lf->frameTime ) {
1648 lf->frameTime = cg.time;
1649 if ( cg_debugAnim.integer ) {
1650 // CG_Printf( "Clamp lf->frameTime\n" );
1651 }
1652 }
1653 }
1654
1655 if ( lf->frameTime > cg.time + 200 ) {
1656 lf->frameTime = cg.time;
1657 }
1658
1659 if ( lf->oldFrameTime > cg.time ) {
1660 lf->oldFrameTime = cg.time;
1661 }
1662 // calculate current lerp value
1663 if ( lf->frameTime == lf->oldFrameTime ) {
1664 lf->backlerp = 0;
1665 } else {
1666 lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
1667 }
1668 }
1669
1670
1671
1672 /*
1673 ==============
1674 CG_WeaponAnimation
1675 ==============
1676 */
1677
1678 //----(SA) modified. this is now client-side only (server does not dictate weapon animation info)
CG_WeaponAnimation(playerState_t * ps,weaponInfo_t * weapon,int * weapOld,int * weap,float * weapBackLerp)1679 static void CG_WeaponAnimation( playerState_t *ps, weaponInfo_t *weapon, int *weapOld, int *weap, float *weapBackLerp ) {
1680
1681 centity_t *cent = &cg.predictedPlayerEntity;
1682 clientInfo_t *ci = &cgs.clientinfo[ ps->clientNum ];
1683
1684 if ( cg_noPlayerAnims.integer ) {
1685 *weapOld = *weap = 0;
1686 return;
1687 }
1688
1689 CG_RunWeapLerpFrame( ci, weapon, ¢->pe.weap, ps->weapAnim, 1 );
1690
1691 *weapOld = cent->pe.weap.oldFrame;
1692 *weap = cent->pe.weap.frame;
1693 *weapBackLerp = cent->pe.weap.backlerp;
1694
1695 if ( cg_debugAnim.integer == 3 ) {
1696 CG_Printf( "oldframe: %d frame: %d backlerp: %f\n", cent->pe.weap.oldFrame, cent->pe.weap.frame, cent->pe.weap.backlerp );
1697 }
1698 }
1699
1700 ////////////////////////////////////////////////////////////////////////
1701 ////////////////////////////////////////////////////////////////////////
1702
1703
1704 // (SA) it wasn't used anyway
1705
1706
1707 /*
1708 ==============
1709 CG_CalculateWeaponPosition
1710 ==============
1711 */
CG_CalculateWeaponPosition(vec3_t origin,vec3_t angles)1712 static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) {
1713 float scale;
1714 int delta;
1715 float fracsin, leanscale;
1716
1717 VectorCopy( cg.refdef.vieworg, origin );
1718 VectorCopy( cg.refdefViewAngles, angles );
1719
1720 // adjust 'lean' into weapon
1721 if ( cg.predictedPlayerState.leanf != 0 ) {
1722 vec3_t right, up;
1723
1724 leanscale = 1.0f;
1725
1726 switch ( cg.predictedPlayerState.weapon ) {
1727 case WP_GARAND:
1728 leanscale = 3.0f;
1729 break;
1730 case WP_FLAMETHROWER:
1731 case WP_TESLA:
1732 case WP_MAUSER:
1733 leanscale = 2.0f;
1734 break;
1735
1736 // never adjust
1737 case WP_KNIFE:
1738 case WP_GRENADE_LAUNCHER:
1739 case WP_GRENADE_PINEAPPLE:
1740 break;
1741
1742 // adjust when leaning right (in case of reload)
1743 default:
1744 if ( cg.predictedPlayerState.leanf > 0 ) {
1745 leanscale = 1.3f;
1746 }
1747 break;
1748 }
1749
1750 // reverse the roll on the weapon so it stays relatively level
1751 angles[ROLL] -= cg.predictedPlayerState.leanf / ( leanscale * 2.0f );
1752 AngleVectors( angles, NULL, right, up );
1753 VectorMA( origin, angles[ROLL], right, origin );
1754
1755 // pitch the gun down a bit to show that firing is not allowed when leaning
1756 angles[PITCH] += ( fabs( cg.predictedPlayerState.leanf ) / 2.0f );
1757
1758 // this gives you some impression that the weapon stays in relatively the same
1759 // position while you lean, so you appear to 'peek' over the weapon
1760 AngleVectors( cg.refdefViewAngles, NULL, right, NULL );
1761 VectorMA( origin, -cg.predictedPlayerState.leanf / 4.0f, right, origin );
1762 }
1763
1764
1765 // on odd legs, invert some angles
1766 if ( cg.bobcycle & 1 ) {
1767 scale = -cg.xyspeed;
1768 } else {
1769 scale = cg.xyspeed;
1770 }
1771
1772 // gun angles from bobbing
1773
1774 angles[ROLL] += scale * cg.bobfracsin * 0.005;
1775 angles[YAW] += scale * cg.bobfracsin * 0.01;
1776 angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005;
1777
1778 // drop the weapon when landing
1779 delta = cg.time - cg.landTime;
1780 if ( delta < LAND_DEFLECT_TIME ) {
1781 origin[2] += cg.landChange * 0.25 * delta / LAND_DEFLECT_TIME;
1782 } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
1783 origin[2] += cg.landChange * 0.25 *
1784 ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME;
1785 }
1786
1787 #if 0
1788 // drop the weapon when stair climbing
1789 delta = cg.time - cg.stepTime;
1790 if ( delta < STEP_TIME / 2 ) {
1791 origin[2] -= cg.stepChange * 0.25 * delta / ( STEP_TIME / 2 );
1792 } else if ( delta < STEP_TIME ) {
1793 origin[2] -= cg.stepChange * 0.25 * ( STEP_TIME - delta ) / ( STEP_TIME / 2 );
1794 }
1795 #endif
1796
1797 // idle drift
1798 //----(SA) adjustment for MAX KAUFMAN
1799 // scale = cg.xyspeed + 40;
1800 scale = 80;
1801 //----(SA) end
1802 fracsin = sin( cg.time * 0.001 );
1803 angles[ROLL] += scale * fracsin * 0.01;
1804 angles[YAW] += scale * fracsin * 0.01;
1805 angles[PITCH] += scale * fracsin * 0.01;
1806
1807 // RF, subtract the kickAngles
1808 VectorMA( angles, -1.0, cg.kickAngles, angles );
1809
1810 }
1811
1812
1813 // Ridah
1814 /*
1815 ===============
1816 CG_FlamethrowerFlame
1817 ===============
1818 */
CG_FlamethrowerFlame(centity_t * cent,vec3_t origin)1819 static void CG_FlamethrowerFlame( centity_t *cent, vec3_t origin ) {
1820
1821 if ( cent->currentState.weapon != WP_FLAMETHROWER ) {
1822 return;
1823 }
1824
1825 if ( cent->currentState.number == cg.snap->ps.clientNum ) {
1826 if ( cg.snap->ps.weapon != WP_FLAMETHROWER || ( cg.snap->ps.weaponstate != WEAPON_FIRING && cg.snap->ps.weaponstate != WEAPON_READY ) ) {
1827 return;
1828 }
1829 }
1830
1831 CG_FireFlameChunks( cent, origin, cent->lerpAngles, 1.0, qtrue, 0 );
1832
1833 return;
1834 }
1835 // done.
1836
1837 /*
1838 ======================
1839 CG_MachinegunSpinAngle
1840 ======================
1841 */
1842 /*
1843 //#define SPIN_SPEED 0.9
1844 //#define COAST_TIME 1000
1845 #define SPIN_SPEED 1
1846 #define COAST_TIME 2000
1847
1848 static float CG_MachinegunSpinAngle( centity_t *cent ) {
1849 int delta;
1850 float angle;
1851 float speed;
1852
1853 delta = cg.time - cent->pe.barrelTime;
1854 if ( cent->pe.barrelSpinning ) {
1855 angle = cent->pe.barrelAngle + delta * SPIN_SPEED;
1856 } else {
1857 if ( delta > COAST_TIME ) {
1858 delta = COAST_TIME;
1859 }
1860
1861 speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
1862 angle = cent->pe.barrelAngle + delta * speed;
1863 }
1864
1865 if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) {
1866 cent->pe.barrelTime = cg.time;
1867 cent->pe.barrelAngle = AngleMod( angle );
1868 cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING);
1869 }
1870
1871 return angle;
1872 }
1873 */
1874
1875 /*
1876 ==============
1877 CG_TeslaSpinAngle
1878 ==============
1879 */
1880 //#define TESLA_SPINSPEED .2
1881 //#define TESLA_COASTTIME 2000
1882 #define TESLA_SPINSPEED .05
1883 #define TESLA_IDLESPEED .15
1884 #define TESLA_COASTTIME 1000
1885
CG_TeslaSpinAngle(centity_t * cent)1886 static float CG_TeslaSpinAngle( centity_t *cent ) {
1887 int delta;
1888 float angle;
1889 // float speed;
1890
1891 delta = cg.time - cent->pe.barrelTime;
1892
1893 angle = cent->pe.barrelAngle;
1894
1895 if ( cent->currentState.eFlags & EF_FIRING ) {
1896 angle += delta * TESLA_SPINSPEED;
1897 } else {
1898 angle += delta * TESLA_IDLESPEED;
1899 }
1900
1901 cent->pe.barrelAngle = AngleMod( angle );
1902
1903 cent->pe.barrelTime = cg.time;
1904
1905 return AngleMod( angle );
1906
1907 //----(SA) trying new tesla effect scheme for MK
1908 // angle = -(cent->pe.barrelAngle + delta * TESLA_SPINSPEED);
1909 // cent->pe.barrelAngle = AngleMod( angle );
1910
1911 // if(cent->currentState.eFlags & EF_FIRING)
1912 // cent->pe.barrelAngle += delta * TESLA_SPINSPEED;
1913 // else
1914 // cent->pe.barrelAngle += delta * TESLA_IDLESPEED;
1915
1916 #if 0
1917 return AngleMod( cent->pe.barrelAngle );
1918
1919 return angle;
1920
1921
1922 if ( cent->pe.barrelSpinning ) {
1923 angle = -( cent->pe.barrelAngle + delta * TESLA_SPINSPEED );
1924 } else {
1925 if ( delta > TESLA_COASTTIME ) {
1926 delta = TESLA_COASTTIME;
1927 }
1928
1929 speed = 0.5 * ( TESLA_SPINSPEED + (float)( TESLA_COASTTIME - delta ) / TESLA_COASTTIME );
1930 angle = -( cent->pe.barrelAngle + delta * speed );
1931 }
1932
1933 if ( cent->pe.barrelSpinning == !( cent->currentState.eFlags & EF_FIRING ) ) {
1934 cent->pe.barrelTime = cg.time;
1935 cent->pe.barrelAngle = AngleMod( angle );
1936 cent->pe.barrelSpinning = !!( cent->currentState.eFlags & EF_FIRING );
1937 }
1938
1939 return angle;
1940 #endif
1941 }
1942
1943 //----(SA) added
1944
1945 /*
1946 ======================
1947 CG_VenomSpinAngle
1948 ======================
1949 */
1950
1951 #define VENOM_LOADTIME 2000
1952 #define VENOM_DELTATIME ( VENOM_LOADTIME / 10 ) // as there are 10 shots to be loaded
1953
1954 #define SPIN_SPEED 1
1955 #define COAST_TIME 2000
1956
CG_VenomSpinAngle(centity_t * cent)1957 static float CG_VenomSpinAngle( centity_t *cent ) {
1958 int delta;
1959 float angle;
1960 float speed;
1961 qboolean firing;
1962
1963 firing = (qboolean)( cent->currentState.eFlags & EF_FIRING );
1964
1965 if ( cg.snap->ps.weaponstate != WEAPON_FIRING ) { // (SA) this seems better
1966 firing = qfalse;
1967 }
1968
1969 delta = cg.time - cent->pe.barrelTime;
1970 if ( cent->pe.barrelSpinning ) {
1971 angle = cent->pe.barrelAngle + delta * SPIN_SPEED;
1972 } else {
1973 if ( delta > COAST_TIME ) {
1974 delta = COAST_TIME;
1975 }
1976
1977 speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
1978 angle = cent->pe.barrelAngle + delta * speed;
1979 }
1980
1981 if ( cent->pe.barrelSpinning == !firing ) {
1982 cent->pe.barrelTime = cg.time;
1983 cent->pe.barrelAngle = AngleMod( angle );
1984 cent->pe.barrelSpinning = !!firing;
1985
1986 // just switching between not spinning and spinning, play the appropriate weapon sound
1987 if ( cent->pe.barrelSpinning ) {
1988 if ( cg_weapons[WP_VENOM].spinupSound ) {
1989 trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cg_weapons[WP_VENOM].spinupSound );
1990 }
1991 } else {
1992 if ( cg_weapons[WP_VENOM].spindownSound ) {
1993 trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cg_weapons[WP_VENOM].spindownSound );
1994 }
1995 }
1996
1997 }
1998
1999 return angle;
2000 }
2001
2002
2003
2004
2005 /*
2006 ==============
2007 CG_DrawRealWeapons
2008 ==============
2009 */
CG_DrawRealWeapons(centity_t * cent)2010 qboolean CG_DrawRealWeapons( centity_t *cent ) {
2011
2012 switch ( cent->currentState.aiChar ) {
2013 case AICHAR_LOPER:
2014 case AICHAR_SUPERSOLDIER: //----(SA) added
2015 case AICHAR_PROTOSOLDIER:
2016 case AICHAR_ZOMBIE:
2017 case AICHAR_HELGA: //----(SA) added // boss1 is now helga-blob
2018 case AICHAR_WARZOMBIE:
2019 return qfalse;
2020 }
2021
2022 return qtrue;
2023 }
2024
2025
2026 /*
2027 ========================
2028 CG_AddWeaponWithPowerups
2029 ========================
2030 */
CG_AddWeaponWithPowerups(refEntity_t * gun,int powerups,playerState_t * ps,centity_t * cent)2031 static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups, playerState_t *ps, centity_t *cent ) {
2032
2033 // add powerup effects
2034 if ( powerups & ( 1 << PW_INVIS ) ) {
2035 gun->customShader = cgs.media.invisShader;
2036 trap_R_AddRefEntityToScene( gun );
2037 } else {
2038 trap_R_AddRefEntityToScene( gun );
2039
2040 if ( powerups & ( 1 << PW_BATTLESUIT ) ) {
2041 gun->customShader = cgs.media.battleWeaponShader;
2042 trap_R_AddRefEntityToScene( gun );
2043 }
2044 if ( powerups & ( 1 << PW_QUAD ) ) {
2045 gun->customShader = cgs.media.quadWeaponShader;
2046 trap_R_AddRefEntityToScene( gun );
2047 }
2048 }
2049 /*
2050 if (ps && ps->clientNum == cg.snap->ps.clientNum) {
2051 float alpha, adjust;
2052 weaponInfo_t *weapon;
2053
2054 weapon = &cg_weapons[ps->weapon];
2055 //if (gun->hModel == weapon->handsModel)
2056 // if (cg.snap->ps.onFireStart)
2057 {
2058
2059 // add the flames if on fire
2060 // alpha = 2.0 * (float)(FIRE_FLASH_TIME - (cg.time - cg.snap->ps.onFireStart))/FIRE_FLASH_TIME;
2061 alpha = 1;
2062 if (alpha > 0) {
2063 if (alpha >= 1.0) {
2064 alpha = 1.0;
2065 }
2066 gun->shaderRGBA[3] = (unsigned char)(255.0*alpha);
2067 // calc the fireRiseDir from the velocity
2068 VectorNegate( cg.snap->ps.velocity, gun->fireRiseDir );
2069 VectorNormalize( gun->fireRiseDir );
2070 gun->fireRiseDir[2] += 1;
2071 if (VectorNormalize( gun->fireRiseDir ) < 1) {
2072 VectorClear( gun->fireRiseDir );
2073 gun->fireRiseDir[2] = 1;
2074 }
2075 // now move towards the newDir
2076 adjust = 5.0*(0.001*cg.frametime);
2077 VectorMA( cg.v_fireRiseDir, adjust, gun->fireRiseDir, cg.v_fireRiseDir );
2078 if (VectorNormalize( cg.v_fireRiseDir ) <= 0.1) {
2079 VectorCopy( gun->fireRiseDir, cg.v_fireRiseDir );
2080 }
2081 VectorCopy( cg.v_fireRiseDir, gun->fireRiseDir );
2082
2083 // gun->reFlags |= REFLAG_ONLYHAND;
2084 gun->customShader = cgs.media.dripWetShader2;
2085 // gun->customShader = cgs.media.onFireShader;
2086 trap_R_AddRefEntityToScene( gun );
2087 // gun->shaderTime = 500;
2088 // trap_R_AddRefEntityToScene( gun );
2089 gun->customShader = cgs.media.dripWetShader;
2090 // gun->customShader = cgs.media.onFireShader2;
2091 trap_R_AddRefEntityToScene( gun );
2092 // gun->reFlags &= ~REFLAG_ONLYHAND;
2093 }
2094 }
2095 }
2096 */
2097 }
2098
2099 /*
2100 ==============
2101 CG_PlayerTeslaCoilFire
2102
2103 TODO: this needs to be fixed for multiplay. entities being hurt need to be sent
2104 by server to all clients, so they draw the correct effects.
2105 ==============
2106 */
CG_PlayerTeslaCoilFire(centity_t * cent,vec3_t flashorigin)2107 void CG_PlayerTeslaCoilFire( centity_t *cent, vec3_t flashorigin ) {
2108
2109 #define TESLA_LIGHTNING_POINT_TIMEOUT 3000
2110 #define TESLA_LIGHTNING_MAX_DIST ( cent->currentState.aiChar == AICHAR_SUPERSOLDIER ? TESLA_SUPERSOLDIER_RANGE : TESLA_RANGE ) // use these to perhaps vary the distance according to aiming
2111 #define TESLA_LIGHTNING_NORMAL_DIST ( TESLA_RANGE / 2.0 )
2112 #define TESLA_MAX_POINT_TESTS 10
2113 #define TESLA_MAX_POINT_TESTS_PERFRAME 20
2114
2115 int i, j, pointTests = 0;
2116 vec3_t testPos, tagPos, vec;
2117 trace_t tr;
2118 float maxDist;
2119 int numPoints;
2120 vec3_t viewAngles, viewDir;
2121 int visEnemies[16];
2122 float visDists[16];
2123 int visEnemiesSorted[MAX_TESLA_BOLTS];
2124 int numEnemies, numSorted = 0, best;
2125 float bestDist;
2126 centity_t *ctrav;
2127 vec3_t traceOrg;
2128 int playerTeam;
2129
2130 if ( cent->currentState.weapon != WP_TESLA ) {
2131 return;
2132 }
2133
2134 // JPW NERVE no tesla in multiplayer
2135 if ( cg_gameType.integer != GT_SINGLE_PLAYER ) {
2136 return;
2137 }
2138
2139 //if (cent->currentState.number == cg.snap->ps.clientNum)
2140 // VectorCopy( cg.snap->ps.viewangles, viewAngles );
2141 //else
2142 VectorCopy( cent->lerpAngles, viewAngles );
2143
2144 AngleVectors( viewAngles, viewDir, NULL, NULL );
2145
2146 if ( cent->currentState.number == cg.snap->ps.clientNum ) {
2147 VectorCopy( cg.snap->ps.origin, traceOrg );
2148 playerTeam = cg.snap->ps.teamNum;
2149 } else {
2150 VectorCopy( cent->lerpOrigin, traceOrg );
2151 playerTeam = cent->currentState.teamNum;
2152 }
2153
2154 maxDist = TESLA_LIGHTNING_MAX_DIST;
2155 numPoints = MAX_TESLA_BOLTS;
2156
2157 VectorCopy( flashorigin, tagPos );
2158
2159 // first, build a list of visible enemies that can be hurt by this tesla, then filter by distance
2160 if ( !cent->pe.teslaDamageApplyTime || cent->pe.teslaDamageApplyTime < cg.time - 200 ) {
2161 numEnemies = 0;
2162 // check the local playing client
2163 VectorSubtract( cg.snap->ps.origin, traceOrg, vec );
2164 VectorNormalize( vec );
2165 if ( ( cent != &cg_entities[cg.snap->ps.clientNum] ) &&
2166 ( cg.snap->ps.teamNum != playerTeam ) &&
2167 ( Distance( tagPos, cg.snap->ps.origin ) < TESLA_LIGHTNING_MAX_DIST ) &&
2168 ( DotProduct( viewDir, vec ) > 0.8 ) ) {
2169 CG_Trace( &tr, traceOrg, NULL, NULL, cg.snap->ps.origin, cg.snap->ps.clientNum, MASK_SHOT & ~( CONTENTS_BODY ) );
2170 if ( tr.fraction == 1 || tr.entityNum == cg.snap->ps.clientNum ) {
2171 visDists[numEnemies] = Distance( tagPos, cg.snap->ps.origin );
2172 visEnemies[numEnemies++] = cg.snap->ps.clientNum;
2173 } else { // try head
2174 VectorCopy( cg.snap->ps.origin, vec );
2175 vec[2] += cg.snap->ps.viewheight;
2176 CG_Trace( &tr, tagPos, NULL, NULL, vec, cg.snap->ps.clientNum, MASK_SHOT & ~( CONTENTS_BODY ) );
2177 if ( tr.fraction == 1 || tr.entityNum == cg.snap->ps.clientNum ) {
2178 visDists[numEnemies] = Distance( tagPos, cg.snap->ps.origin );
2179 visEnemies[numEnemies++] = cg.snap->ps.clientNum;
2180 } else { // try body, from tag
2181 VectorCopy( cg.snap->ps.origin, vec );
2182 CG_Trace( &tr, tagPos, NULL, NULL, vec, cg.snap->ps.clientNum, MASK_SHOT & ~( CONTENTS_BODY ) );
2183 if ( tr.fraction == 1 || tr.entityNum == cg.snap->ps.clientNum ) {
2184 visDists[numEnemies] = Distance( tagPos, cg.snap->ps.origin );
2185 visEnemies[numEnemies++] = cg.snap->ps.clientNum;
2186 }
2187 }
2188 }
2189 }
2190
2191 if ( cgs.localServer && cgs.gametype == GT_SINGLE_PLAYER ) {
2192 // check for AI's getting hurt (TODO: bot support?)
2193 for ( ctrav = cg_entities, i = 0; i < cgs.maxclients && numEnemies < 16; ctrav++, i++ ) {
2194 // RF, proto and supersoldier are invulnerable to tesla
2195 /* switch ( ctrav->currentState.aiChar ) {
2196 case AICHAR_SUPERSOLDIER:
2197 case AICHAR_PROTOSOLDIER:
2198 continue;
2199 }
2200 */ //
2201 if ( ctrav->currentState.aiChar &&
2202 ( ctrav != cent ) &&
2203 ( ctrav->currentState.teamNum != playerTeam ) &&
2204 !( ctrav->currentState.eFlags & EF_DEAD ) &&
2205 ctrav->currentValid && // is in the visible frame
2206 ( Distance( tagPos, ctrav->lerpOrigin ) < TESLA_LIGHTNING_MAX_DIST ) ) {
2207 VectorSubtract( ctrav->lerpOrigin, traceOrg, vec );
2208 VectorNormalize( vec );
2209
2210 if ( DotProduct( viewDir, vec ) > 0.8 ) {
2211 CG_Trace( &tr, traceOrg, NULL, NULL, ctrav->lerpOrigin, ctrav->currentState.number, MASK_SHOT & ~CONTENTS_BODY );
2212 if ( tr.fraction == 1 || tr.entityNum == ctrav->currentState.number ) {
2213 visDists[numEnemies] = Distance( tagPos, ctrav->lerpOrigin );
2214 visEnemies[numEnemies++] = ctrav->currentState.number;
2215 }
2216 }
2217 }
2218 }
2219 }
2220
2221 // now sort by distance
2222 for ( j = 0; j < MAX_TESLA_BOLTS; j++ ) {
2223 visEnemiesSorted[j] = -1;
2224
2225 bestDist = 99999;
2226 best = -1;
2227 for ( i = 0; i < numEnemies; i++ ) {
2228 if ( visEnemies[i] < 0 ) {
2229 continue;
2230 }
2231 if ( visDists[i] < bestDist ) {
2232 bestDist = visDists[i];
2233 visEnemiesSorted[j] = visEnemies[i];
2234 best = i;
2235 }
2236 }
2237
2238 if ( best >= 0 ) {
2239 visEnemies[best] = -1;
2240 numSorted = j + 1;
2241 }
2242 }
2243
2244 // now fill in the teslaEnemy[]'s
2245 for ( i = 0; i < MAX_TESLA_BOLTS; i++ ) {
2246 if ( numSorted && i / numSorted < 1 /*( MAX_TESLA_BOLTS / 3 )*/ ) { // bolts per enemy
2247 j = i % numSorted;
2248 cent->pe.teslaEnemy[i] = visEnemiesSorted[j];
2249 // apply damage
2250 CG_ClientDamage( visEnemiesSorted[j], cent->currentState.number, CLDMG_TESLA );
2251 // show the effect
2252 cg_entities[ visEnemiesSorted[j] ].pe.teslaDamagedTime = cg.time;
2253 } else {
2254 if ( cent->pe.teslaEnemy[i] >= 0 ) {
2255 cent->pe.teslaEndPointTimes[i] = 0; // make sure we find a new spot
2256 }
2257 cent->pe.teslaEnemy[i] = -1;
2258 }
2259 }
2260 cent->pe.teslaDamageApplyTime = cg.time;
2261 }
2262
2263 for ( i = 0; i < numPoints; i++ ) {
2264
2265 //if (!(rand()%3))
2266 // continue;
2267
2268 VectorSubtract( cent->pe.teslaEndPoints[i], tagPos, vec );
2269 VectorNormalize( vec );
2270
2271 // if this point has timed out, find a new spot
2272 if ( cent->pe.teslaEnemy[i] >= 0 ) {
2273 // attacking the player
2274 VectorSet( testPos, 6 * crandom(),
2275 6 * crandom(),
2276 20 * crandom() - 8 );
2277 //VectorClear( testPos );
2278 if ( cent->pe.teslaEnemy[i] != cg.snap->ps.clientNum ) {
2279 VectorAdd( testPos, cg_entities[cent->pe.teslaEnemy[i]].lerpOrigin, testPos );
2280 } else {
2281 VectorAdd( testPos, cg.snap->ps.origin, testPos );
2282 }
2283 cent->pe.teslaEndPointTimes[i] = cg.time; // - rand()%(TESLA_LIGHTNING_POINT_TIMEOUT/2);
2284 VectorCopy( testPos, cent->pe.teslaEndPoints[i] );
2285 } else if ( ( !cent->pe.teslaEndPointTimes[i] ) ||
2286 ( cent->pe.teslaEndPointTimes[i] > cg.time ) ||
2287 ( cent->pe.teslaEndPointTimes[i] < cg.time - TESLA_LIGHTNING_POINT_TIMEOUT ) ||
2288 ( VectorDistance( tagPos, cent->pe.teslaEndPoints[i] ) > maxDist ) ||
2289 ( DotProduct( viewDir, vec ) < 0.7 ) ) {
2290
2291 //if (cent->currentState.groundEntityNum == ENTITYNUM_NONE)
2292 // continue; // must be on the ground
2293
2294 // find a new spot
2295 for ( j = 0; j < TESLA_MAX_POINT_TESTS; j++ ) {
2296 VectorSet( testPos, cg.refdef.fov_y * crandom() * 0.5,
2297 cg.refdef.fov_x * crandom() * 0.5,
2298 0 );
2299 VectorAdd( viewAngles, testPos, testPos );
2300 AngleVectors( testPos, vec, NULL, NULL );
2301 VectorMA( tagPos, TESLA_LIGHTNING_NORMAL_DIST, vec, testPos );
2302 // try a trace to find a world collision
2303 CG_Trace( &tr, tagPos, NULL, NULL, testPos, cent->currentState.number, MASK_SHOT & ~CONTENTS_BODY );
2304 if ( tr.fraction < 1 && tr.entityNum == ENTITYNUM_WORLD && !( tr.surfaceFlags & ( SURF_NOIMPACT | SURF_SKY ) ) ) {
2305 // found a valid spot!
2306 cent->pe.teslaEndPointTimes[i] = cg.time - rand() % ( TESLA_LIGHTNING_POINT_TIMEOUT / 2 );
2307 VectorCopy( tr.endpos, cent->pe.teslaEndPoints[i] );
2308 break;
2309 }
2310 if ( pointTests++ > TESLA_MAX_POINT_TESTS_PERFRAME ) {
2311 j = TESLA_MAX_POINT_TESTS;
2312 continue;
2313 }
2314 }
2315 if ( j == TESLA_MAX_POINT_TESTS ) {
2316 continue; // just don't draw this point
2317 }
2318
2319 // add an impact mark on the wall
2320 VectorSubtract( cent->pe.teslaEndPoints[i], tagPos, vec );
2321 VectorNormalize( vec );
2322 VectorInverse( vec );
2323 CG_ImpactMark( cgs.media.lightningHitWallShader, cent->pe.teslaEndPoints[i], vec, random() * 360, 0.2, 0.2, 0.2, 1.0, qtrue, 4, qfalse, 300 );
2324 }
2325 //
2326 // we have a valid lightning point, so draw it
2327 // sanity check though to make sure it's valid
2328 if ( VectorDistance( tagPos, cent->pe.teslaEndPoints[i] ) <= maxDist ) {
2329 CG_DynamicLightningBolt( cgs.media.lightningBoltShader, tagPos, cent->pe.teslaEndPoints[i], 1 + ( ( cg.time % ( ( i + 2 ) * ( i + 3 ) ) ) + i ) % 2, 20 + (float)( i % 3 ) * 5 + 6.0 * random(), ( cent->pe.teslaEnemy[i] < 0 ), 1.0, 0, i * i * 3 );
2330
2331 // play a zap sound
2332 if ( cent->pe.lightningSoundTime < cg.time - 200 ) {
2333 CG_SoundPlayIndexedScript( cgs.media.teslaZapScript, cent->pe.teslaEndPoints[i], ENTITYNUM_WORLD );
2334 CG_SoundPlayIndexedScript( cgs.media.teslaZapScript, cent->lerpOrigin, ENTITYNUM_WORLD );
2335 //trap_S_StartSound( cent->pe.teslaEndPoints[i], ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.lightningSounds[rand()%3] );
2336 cent->pe.lightningSoundTime = cg.time + rand() % 200;
2337 }
2338 }
2339 }
2340
2341 if ( cg.time % 3 ) { // break it up a bit
2342 // add the looping sound
2343 CG_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.teslaLoopSound, 255 );
2344 }
2345
2346 // drop a dynamic light out infront of us
2347 AngleVectors( viewAngles, vec, NULL, NULL );
2348 VectorMA( tagPos, 300, vec, testPos );
2349 // try a trace to find a world collision
2350 CG_Trace( &tr, tagPos, NULL, NULL, testPos, cent->currentState.number, MASK_SOLID );
2351
2352 if ( ( cg.time / 50 ) % ( 4 + ( cg.time % 4 ) ) == 0 ) {
2353 // alt light
2354 trap_R_AddLightToScene( tr.endpos, 256 + 600 * tr.fraction, 0.2, 0.6, 1, 1 );
2355 } else if ( ( cg.time / 50 ) % ( 4 + ( cg.time % 4 ) ) == 1 ) {
2356 // no light
2357 //trap_R_AddLightToScene( tr.endpos, 128 + 500*tr.fraction, 1, 1, 1, 10 );
2358 } else {
2359 // blue light
2360 trap_R_AddLightToScene( tr.endpos, 256 + 600 * tr.fraction, 0.2, 0.6, 1, 0 );
2361 }
2362
2363 // shake the camera a bit
2364 CG_StartShakeCamera( 0.05, 200, cent->lerpOrigin, 100 );
2365 }
2366
2367 //----(SA)
2368 /*
2369 ==============
2370 CG_AddProtoWeapons
2371 ==============
2372 */
CG_AddProtoWeapons(refEntity_t * parent,playerState_t * ps,centity_t * cent)2373 void CG_AddProtoWeapons( refEntity_t *parent, playerState_t *ps, centity_t *cent ) {
2374 #if 0
2375 refEntity_t gun;
2376
2377 if ( !( cent->currentState.aiChar == AICHAR_PROTOSOLDIER ) ) {
2378 return;
2379 }
2380 memset( &gun, 0, sizeof( gun ) );
2381 VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
2382 // gun.hModel = cgs.media.protoWeapon;
2383 gun.shadowPlane = parent->shadowPlane;
2384 gun.renderfx = parent->renderfx;
2385 CG_PositionEntityOnTag( &gun, parent, "tag_armright", 0, NULL );
2386 CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ps, cent );
2387 #endif
2388 }
2389 //----(SA) end
2390
2391
2392
2393 // Ridah
2394 /*
2395 ==============
2396 CG_MonsterUsingWeapon
2397 ==============
2398 */
CG_MonsterUsingWeapon(centity_t * cent,int aiChar,int weaponNum)2399 qboolean CG_MonsterUsingWeapon( centity_t *cent, int aiChar, int weaponNum ) {
2400 return ( cent->currentState.aiChar == aiChar ) && ( cent->currentState.weapon == weaponNum );
2401 }
2402
2403 /*
2404 =============
2405 CG_AddPlayerWeapon
2406
2407 Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
2408 The main player will have this called for BOTH cases, so effects like light and
2409 sound should only be done on the world model case.
2410 =============
2411 */
2412 static qboolean debuggingweapon = qfalse;
2413
CG_AddPlayerWeapon(refEntity_t * parent,playerState_t * ps,centity_t * cent)2414 void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) {
2415
2416 refEntity_t gun;
2417 refEntity_t barrel;
2418 refEntity_t flash;
2419 vec3_t angles;
2420 weapon_t weaponNum, weapSelect;
2421 weaponInfo_t *weapon;
2422 centity_t *nonPredictedCent;
2423 qboolean firing; // Ridah
2424
2425 qboolean akimboFire = qfalse; //----(SA) added
2426
2427 qboolean playerScaled;
2428 qboolean drawpart, drawrealweap;
2429 int i;
2430 qboolean isPlayer;
2431
2432 // (SA) might as well have this check consistant throughout the routine
2433 isPlayer = (qboolean)( cent->currentState.clientNum == cg.snap->ps.clientNum );
2434
2435 weaponNum = cent->currentState.weapon;
2436 weapSelect = cg.weaponSelect;
2437
2438 if ( ps && cg.cameraMode ) {
2439 return;
2440 }
2441
2442 // don't draw any weapons when the binocs are up
2443 if ( cent->currentState.eFlags & EF_ZOOMING ) {
2444 if ( isPlayer ) {
2445 if ( !cg.renderingThirdPerson ) {
2446 return;
2447 }
2448 } else {
2449 return;
2450 }
2451 }
2452
2453 // don't draw weapon stuff when looking through a scope
2454 if ( weaponNum == WP_SNOOPERSCOPE || weaponNum == WP_SNIPERRIFLE || weaponNum == WP_FG42SCOPE ||
2455 weapSelect == WP_SNOOPERSCOPE || weapSelect == WP_SNIPERRIFLE || weapSelect == WP_FG42SCOPE ) {
2456 if ( isPlayer && !cg.renderingThirdPerson ) {
2457 return;
2458 }
2459 }
2460
2461 // no weapon when on mg_42
2462 if ( cent->currentState.eFlags & EF_MG42_ACTIVE ) {
2463 return;
2464 }
2465
2466 // DHM - Nerve :: Special case for WP_CLASS_SPECIAL
2467 if ( cgs.gametype == GT_WOLF && weaponNum == WP_CLASS_SPECIAL ) {
2468 switch ( cent->currentState.teamNum ) {
2469 case PC_ENGINEER:
2470 CG_RegisterWeapon( WP_CLASS_SPECIAL );
2471 weapon = &cg_weapons[WP_CLASS_SPECIAL];
2472 break;
2473 case PC_MEDIC:
2474 CG_RegisterWeapon( WP_MEDIC_HEAL );
2475 weapon = &cg_weapons[WP_MEDIC_HEAL];
2476 break;
2477 case PC_LT:
2478 CG_RegisterWeapon( WP_GRENADE_SMOKE );
2479 weapon = &cg_weapons[WP_GRENADE_SMOKE];
2480 break;
2481 default:
2482 CG_RegisterWeapon( weaponNum );
2483 weapon = &cg_weapons[weaponNum];
2484 break;
2485 }
2486 } else {
2487 CG_RegisterWeapon( weaponNum );
2488 weapon = &cg_weapons[weaponNum];
2489 }
2490 // dhm - end
2491
2492
2493 if ( isPlayer ) {
2494 akimboFire = BG_AkimboFireSequence( weaponNum, cg.predictedPlayerState.ammoclip[WP_AKIMBO], cg.predictedPlayerState.ammoclip[WP_COLT] );
2495 } else if ( ps ) {
2496 akimboFire = BG_AkimboFireSequence( weaponNum, ps->ammoclip[WP_AKIMBO], ps->ammoclip[WP_AKIMBO] );
2497 }
2498
2499 // add the weapon
2500 memset( &gun, 0, sizeof( gun ) );
2501 VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
2502 gun.shadowPlane = parent->shadowPlane;
2503 gun.renderfx = parent->renderfx;
2504
2505 // set custom shading for railgun refire rate
2506 if ( ps ) {
2507 gun.shaderRGBA[0] = 255;
2508 gun.shaderRGBA[1] = 255;
2509 gun.shaderRGBA[2] = 255;
2510 gun.shaderRGBA[3] = 255;
2511 }
2512
2513 if ( ps ) {
2514 gun.hModel = weapon->weaponModel[W_FP_MODEL];
2515 } else {
2516 CG_AddProtoWeapons( parent, ps, cent );
2517 // skeletal guys use a different third person weapon (for different tag business)
2518 if ( cgs.clientinfo[ cent->currentState.clientNum ].isSkeletal && weapon->weaponModel[W_SKTP_MODEL] ) {
2519 gun.hModel = weapon->weaponModel[W_SKTP_MODEL];
2520 } else {
2521 gun.hModel = weapon->weaponModel[W_TP_MODEL];
2522 }
2523 }
2524
2525 if ( !gun.hModel ) {
2526 if ( debuggingweapon ) {
2527 CG_Printf( "returning due to: !gun.hModel\n" );
2528 }
2529 return;
2530 }
2531
2532 if ( weaponNum == WP_GAUNTLET ) { // (SA) this is the 'knife'. no model yet, so we can give it to the zombie and have him visually 'unarmed'
2533 if ( debuggingweapon ) {
2534 CG_Printf( "returning due to: weaponNum == WP_GAUNTLET\n" );
2535 }
2536 return;
2537 }
2538
2539 if ( !ps && cg.snap->ps.pm_flags & PMF_LADDER && isPlayer ) { //----(SA) player on ladder
2540 if ( debuggingweapon ) {
2541 CG_Printf( "returning due to: !ps && cg.snap->ps.pm_flags & PMF_LADDER\n" );
2542 }
2543 return;
2544 }
2545
2546 if ( !ps ) {
2547 // add weapon ready sound
2548 cent->pe.lightningFiring = qfalse;
2549 if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) {
2550 // lightning gun and guantlet make a different sound when fire is held down
2551 CG_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound, 255 );
2552 cent->pe.lightningFiring = qtrue;
2553 } else if ( weapon->readySound ) {
2554 CG_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound, 255 );
2555 }
2556 }
2557
2558
2559 // Ridah
2560 firing = ( ( cent->currentState.eFlags & EF_FIRING ) != 0 );
2561
2562 CG_PositionEntityOnTag( &gun, parent, "tag_weapon", 0, NULL );
2563
2564 playerScaled = (qboolean)( cgs.clientinfo[ cent->currentState.clientNum ].playermodelScale[0] != 0 );
2565 if ( !ps && playerScaled ) { // don't "un-scale" weap up in 1st person
2566 for ( i = 0; i < 3; i++ ) { // scale weapon back up so it doesn't pick up the adjusted scale of the character models.
2567 // this will affect any parts attached to the gun as well (barrel/bolt/flash/brass/etc.)
2568 VectorScale( gun.axis[i], 1.0 / ( cgs.clientinfo[ cent->currentState.clientNum ].playermodelScale[i] ), gun.axis[i] );
2569 }
2570 }
2571
2572 // characters that draw their own special weapon model will not draw the standard ones
2573 if ( CG_DrawRealWeapons( cent ) ) {
2574 drawrealweap = qtrue;
2575 } else {
2576 drawrealweap = qfalse;
2577 }
2578
2579 if ( ps ) {
2580 drawpart = CG_GetPartFramesFromWeap( cent, &gun, parent, W_MAX_PARTS, weapon ); // W_MAX_PARTS specifies this as the primary view model
2581 } else {
2582 drawpart = qtrue;
2583 }
2584
2585 if ( drawpart && drawrealweap ) {
2586 CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ps, cent );
2587 }
2588
2589 if ( isPlayer ) {
2590 refEntity_t brass;
2591
2592 // opposite tag in akimbo, since at this point the weapon
2593 // has fired and the fire seq has switched over
2594 if ( weaponNum == WP_AKIMBO && akimboFire ) {
2595 CG_PositionRotatedEntityOnTag( &brass, &gun, "tag_brass2" );
2596 } else {
2597 CG_PositionRotatedEntityOnTag( &brass, &gun, "tag_brass" );
2598 }
2599
2600 VectorCopy( brass.origin, ejectBrassCasingOrigin );
2601 }
2602
2603 memset( &barrel, 0, sizeof( barrel ) );
2604 VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
2605 barrel.shadowPlane = parent->shadowPlane;
2606 barrel.renderfx = parent->renderfx;
2607
2608 // add barrels
2609 // attach generic weapon parts to the first person weapon.
2610 // if a barrel should be attached for third person, add it in the (!ps) section below
2611 angles[YAW] = angles[PITCH] = 0;
2612
2613 if ( ps ) {
2614 qboolean spunpart;
2615
2616 for ( i = W_PART_1; i < W_MAX_PARTS; i++ ) {
2617
2618 spunpart = qfalse;
2619 barrel.hModel = weapon->wpPartModels[W_FP_MODEL][i];
2620
2621 // check for spinning
2622 if ( weaponNum == WP_VENOM ) {
2623 if ( i == W_PART_1 ) {
2624 angles[ROLL] = CG_VenomSpinAngle( cent );
2625 spunpart = qtrue;
2626 } else if ( i == W_PART_2 ) {
2627 angles[ROLL] = -CG_VenomSpinAngle( cent );
2628 spunpart = qtrue;
2629 }
2630 // 'blurry' barel when firing
2631 // (SA) not right now. at the moment, just spin the belt when firing, no swapout
2632 else if ( i == W_PART_3 ) {
2633 if ( ( cent->pe.weap.animationNumber & ~ANIM_TOGGLEBIT ) == WEAP_ATTACK1 ) {
2634 barrel.hModel = weapon->wpPartModels[W_FP_MODEL][i];
2635 angles[ROLL] = -CG_VenomSpinAngle( cent );
2636 angles[ROLL] = -( angles[ROLL] / 8.0f );
2637 } else {
2638 angles[ROLL] = 0;
2639 }
2640 spunpart = qtrue;
2641 }
2642 } else if ( weaponNum == WP_TESLA ) {
2643 if ( i == W_PART_1 || i == W_PART_2 ) {
2644 angles[ROLL] = CG_TeslaSpinAngle( cent );
2645 spunpart = qtrue;
2646 }
2647 }
2648
2649 if ( spunpart ) {
2650 AnglesToAxis( angles, barrel.axis );
2651 }
2652 // end spinning
2653
2654
2655 if ( barrel.hModel ) {
2656 if ( i == W_PART_1 ) {
2657 if ( spunpart ) {
2658 CG_PositionRotatedEntityOnTag( &barrel, parent, "tag_barrel" );
2659 } else { CG_PositionEntityOnTag( &barrel, parent, "tag_barrel", 0, NULL );}
2660 } else {
2661 if ( spunpart ) {
2662 CG_PositionRotatedEntityOnTag( &barrel, parent, va( "tag_barrel%d", i + 1 ) );
2663 } else { CG_PositionEntityOnTag( &barrel, parent, va( "tag_barrel%d", i + 1 ), 0, NULL );}
2664 }
2665
2666 drawpart = CG_GetPartFramesFromWeap( cent, &barrel, parent, i, weapon );
2667
2668 if ( drawpart && drawrealweap ) {
2669 CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent );
2670 }
2671 }
2672 }
2673 } else { // weapons with barrels drawn in third person
2674 if ( drawrealweap ) {
2675 if ( weaponNum == WP_VENOM ) {
2676
2677 angles[ROLL] = CG_VenomSpinAngle( cent );
2678 AnglesToAxis( angles, barrel.axis );
2679
2680 barrel.hModel = weapon->wpPartModels[W_TP_MODEL][W_PART_1];
2681 CG_PositionRotatedEntityOnTag( &barrel, &gun, "tag_barrel" );
2682 CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent );
2683 }
2684 }
2685 }
2686
2687 // add the scope model to the rifle if you've got it
2688 if ( isPlayer && !cg.renderingThirdPerson ) { // (SA) for now just do it on the first person weapons
2689 if ( weaponNum == WP_MAUSER ) {
2690 if ( COM_BitCheck( cg.predictedPlayerState.weapons, WP_SNIPERRIFLE ) ) {
2691 barrel.hModel = weapon->modModel[0];
2692 if ( barrel.hModel ) {
2693 CG_PositionEntityOnTag( &barrel, &gun, "tag_scope", 0, NULL );
2694 CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent );
2695 }
2696 }
2697 }
2698 }
2699
2700
2701 // make sure we aren't looking at cg.predictedPlayerEntity for LG
2702 nonPredictedCent = &cg_entities[cent->currentState.clientNum];
2703
2704 // if the index of the nonPredictedCent is not the same as the clientNum
2705 // then this is a fake player (like on the single player podiums), so
2706 // go ahead and use the cent
2707 if ( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) {
2708 nonPredictedCent = cent;
2709 }
2710
2711
2712 // add the flash
2713 memset( &flash, 0, sizeof( flash ) );
2714 VectorCopy( parent->lightingOrigin, flash.lightingOrigin );
2715 flash.shadowPlane = parent->shadowPlane;
2716 flash.renderfx = parent->renderfx;
2717
2718 if ( ps ) {
2719 flash.hModel = weapon->flashModel[W_FP_MODEL];
2720 } else {
2721 flash.hModel = weapon->flashModel[W_TP_MODEL];
2722 }
2723
2724 angles[YAW] = 0;
2725 angles[PITCH] = 0;
2726 angles[ROLL] = crandom() * 10;
2727 AnglesToAxis( angles, flash.axis );
2728
2729 CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash" );
2730
2731 // store this position for other cgame elements to access
2732 cent->pe.gunRefEnt = gun;
2733 cent->pe.gunRefEntFrame = cg.clientFrame;
2734
2735 if ( ( weaponNum == WP_FLAMETHROWER || weaponNum == WP_TESLA ) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) {
2736 // continuous flash
2737
2738 } else {
2739
2740 // continuous smoke after firing
2741 #define BARREL_SMOKE_TIME 1000
2742
2743 if ( ps || cg.renderingThirdPerson || !isPlayer ) {
2744 if ( weaponNum == WP_VENOM || weaponNum == WP_STEN ) {
2745 if ( !cg_paused.integer ) { // don't add while paused
2746 // hot smoking gun
2747 if ( cg.time - cent->overheatTime < 3000 ) {
2748 if ( !( rand() % 3 ) ) {
2749 float alpha;
2750 alpha = 1.0f - ( (float)( cg.time - cent->overheatTime ) / 3000.0f );
2751 alpha *= 0.25f; // .25 max alpha
2752 if ( weaponNum == WP_VENOM ) { // silly thing that makes the smoke off the venom swirlier since it's spinning real fast
2753 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, tv( 0,0,1 ), 8, 1000, 8, 20, 70, alpha );
2754 } else {
2755 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, tv( 0,0,1 ), 8, 1000, 8, 20, 30, alpha );
2756 }
2757 }
2758 }
2759 }
2760
2761 } else if ( weaponNum == WP_PANZERFAUST ) {
2762 if ( !cg_paused.integer ) { // don't add while paused
2763 if ( cg.time - cent->muzzleFlashTime < BARREL_SMOKE_TIME ) {
2764 if ( !( rand() % 5 ) ) {
2765 float alpha;
2766 alpha = 1.0f - ( (float)( cg.time - cent->muzzleFlashTime ) / (float)BARREL_SMOKE_TIME ); // what fraction of BARREL_SMOKE_TIME are we at
2767 alpha *= 0.25f; // .25 max alpha
2768 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, tv( 0,0,1 ), 8, 1000, 8, 20, 30, alpha );
2769 }
2770 }
2771 }
2772 }
2773 }
2774
2775 // impulse flash
2776 if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) {
2777 // Ridah, blue ignition flame if not firing flamer
2778 if ( weaponNum != WP_FLAMETHROWER && weaponNum != WP_TESLA ) {
2779 return;
2780 }
2781 }
2782
2783 }
2784
2785 // weapons that don't need to go any further as they have no flash or light
2786 if ( weaponNum == WP_GRENADE_LAUNCHER ||
2787 weaponNum == WP_GRENADE_PINEAPPLE ||
2788 weaponNum == WP_KNIFE ||
2789 weaponNum == WP_DYNAMITE ) {
2790 return;
2791 }
2792
2793 if ( weaponNum == WP_STEN ) { // sten has no muzzleflash
2794 flash.hModel = 0;
2795 }
2796
2797 // weaps with barrel smoke
2798 if ( ps || cg.renderingThirdPerson || !isPlayer ) {
2799 if ( !cg_paused.integer ) { // don't add while paused
2800 if ( weaponNum == WP_STEN || weaponNum == WP_VENOM ) {
2801 if ( cg.time - cent->muzzleFlashTime < 100 ) {
2802 // CG_ParticleImpactSmokePuff (cgs.media.smokeParticleShader, flash.origin);
2803 CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, tv( 0,0,1 ), 8, 500, 8, 20, 30, 0.25f );
2804 }
2805 }
2806 }
2807 }
2808
2809 if ( isPlayer ) {
2810 if ( weaponNum == WP_AKIMBO ) {
2811 if ( !cent->akimboFire ) {
2812 CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash2" );
2813 }
2814 }
2815 }
2816
2817
2818 if ( flash.hModel ) {
2819 if ( weaponNum != WP_FLAMETHROWER && weaponNum != WP_TESLA ) { //Ridah, hide the flash also for now
2820 // RF, changed this so the muzzle flash stays onscreen for long enough to be seen
2821 if ( cg.time - cent->muzzleFlashTime < MUZZLE_FLASH_TIME ) {
2822 // if ( firing ) // Ridah
2823 trap_R_AddRefEntityToScene( &flash );
2824 }
2825 }
2826 }
2827
2828 // Ridah, zombie fires from his head
2829 //if (CG_MonsterUsingWeapon( cent, AICHAR_ZOMBIE, WP_MONSTER_ATTACK1 )) {
2830 // CG_PositionEntityOnTag( &flash, parent, parent->hModel, "tag_head", NULL);
2831 //}
2832
2833 if ( ps || cg.renderingThirdPerson || !isPlayer ) {
2834
2835 if ( firing ) {
2836 // Ridah, Flamethrower effect
2837 CG_FlamethrowerFlame( cent, flash.origin );
2838
2839 // RF, Tesla coil
2840 CG_PlayerTeslaCoilFire( cent, flash.origin );
2841
2842 // make a dlight for the flash
2843 if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) {
2844 trap_R_AddLightToScene( flash.origin, 200 + ( rand() & 31 ), weapon->flashDlightColor[0],
2845 weapon->flashDlightColor[1], weapon->flashDlightColor[2], 0 );
2846 }
2847 } else {
2848 if ( weaponNum == WP_FLAMETHROWER ) {
2849 vec3_t angles;
2850 AxisToAngles( flash.axis, angles );
2851 CG_FireFlameChunks( cent, flash.origin, angles, 1.0, qfalse, 0 );
2852 }
2853 }
2854 }
2855 }
2856
CG_AddPlayerFoot(refEntity_t * parent,playerState_t * ps,centity_t * cent)2857 void CG_AddPlayerFoot( refEntity_t *parent, playerState_t *ps, centity_t *cent ) {
2858 refEntity_t wolfkick;
2859 vec3_t kickangle;
2860 int frame;
2861
2862 if ( !( cg.snap->ps.persistant[PERS_WOLFKICK] ) ) {
2863 return;
2864 }
2865
2866 memset( &wolfkick, 0, sizeof( wolfkick ) );
2867
2868 VectorCopy( parent->lightingOrigin, wolfkick.lightingOrigin );
2869 wolfkick.shadowPlane = parent->shadowPlane;
2870
2871 // note to self we want this to lerp and advance frame
2872 wolfkick.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;;
2873 wolfkick.hModel = wolfkickModel;
2874
2875 VectorCopy( cg.refdef.vieworg, wolfkick.origin );
2876 //----(SA) allow offsets for testing boot model
2877 if ( cg_gun_x.value ) {
2878 VectorMA( wolfkick.origin, cg_gun_x.value, cg.refdef.viewaxis[0], wolfkick.origin );
2879 }
2880 if ( cg_gun_y.value ) {
2881 VectorMA( wolfkick.origin, cg_gun_y.value, cg.refdef.viewaxis[1], wolfkick.origin );
2882 }
2883 if ( cg_gun_z.value ) {
2884 VectorMA( wolfkick.origin, cg_gun_z.value, cg.refdef.viewaxis[2], wolfkick.origin );
2885 }
2886 //----(SA) end
2887
2888
2889 VectorCopy( cg.refdefViewAngles, kickangle );
2890 if ( kickangle[0] < 0 ) {
2891 kickangle[0] = 0; //----(SA) avoid "Rockette" syndrome :)
2892 }
2893 AnglesToAxis( kickangle, wolfkick.axis );
2894
2895
2896 frame = cg.snap->ps.persistant[PERS_WOLFKICK];
2897
2898 // CG_Printf("frame: %d\n", frame);
2899
2900 wolfkick.frame = frame;
2901 wolfkick.oldframe = frame - 1;
2902 wolfkick.backlerp = 1 - cg.frameInterpolation;
2903 trap_R_AddRefEntityToScene( &wolfkick );
2904
2905 }
2906
2907 /*
2908 ==============
2909 CG_AddViewWeapon
2910
2911 Add the weapon, and flash for the player's view
2912 ==============
2913 */
CG_AddViewWeapon(playerState_t * ps)2914 void CG_AddViewWeapon( playerState_t *ps ) {
2915 refEntity_t hand;
2916 vec3_t fovOffset;
2917 vec3_t angles;
2918 vec3_t gunoff;
2919 weaponInfo_t *weapon;
2920
2921 if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
2922 return;
2923 }
2924
2925 if ( ps->pm_type == PM_INTERMISSION ) {
2926 return;
2927 }
2928
2929 // no gun if in third person view
2930 if ( cg.renderingThirdPerson ) {
2931 return;
2932 }
2933
2934 // allow the gun to be completely removed
2935 if ( !cg_drawGun.integer ) {
2936 /*
2937 vec3_t origin;
2938
2939 if ( cg.predictedPlayerState.eFlags & EF_FIRING ) {
2940 // special hack for lightning gun...
2941 VectorCopy( cg.refdef.vieworg, origin );
2942 VectorMA( origin, -8, cg.refdef.viewaxis[2], origin );
2943 CG_LightningBolt( &cg_entities[ps->clientNum], origin );
2944 }
2945 */
2946 return;
2947 }
2948
2949 // don't draw if testing a gun model
2950 if ( cg.testGun ) {
2951 return;
2952 }
2953
2954 if ( ps->eFlags & EF_MG42_ACTIVE ) {
2955 return;
2956 }
2957
2958 VectorClear(fovOffset);
2959
2960 if ( cg_fixedAspect.integer ) {
2961 fovOffset[2] = 0;
2962 } else if ( cg.fov > 90 ) {
2963 // drop gun lower at higher fov
2964 fovOffset[2] = -0.2 * ( cg.fov - 90 ) * cg.refdef.fov_x / cg.fov;
2965 } else if ( cg.fov < 90 ) {
2966 // move gun forward at lower fov
2967 fovOffset[0] = -0.2 * ( cg.fov - 90 ) * cg.refdef.fov_x / cg.fov;
2968 } else if ( cg_fov.integer > 90 ) {
2969 // old auto adjust
2970 fovOffset[2] = -0.2 * ( cg_fov.integer - 90 );
2971 }
2972
2973 memset( &hand, 0, sizeof( hand ) );
2974
2975 if ( ps->weapon > WP_NONE ) {
2976 // DHM - Nerve :: handle WP_CLASS_SPECIAL for different classes
2977 if ( cgs.gametype == GT_WOLF && ps->weapon == WP_CLASS_SPECIAL ) {
2978 switch ( ps->stats[ STAT_PLAYER_CLASS ] ) {
2979 case PC_ENGINEER:
2980 CG_RegisterWeapon( WP_CLASS_SPECIAL );
2981 weapon = &cg_weapons[ WP_CLASS_SPECIAL ];
2982 break;
2983 case PC_MEDIC:
2984 CG_RegisterWeapon( WP_MEDIC_HEAL );
2985 weapon = &cg_weapons[ WP_MEDIC_HEAL ];
2986 break;
2987 case PC_LT:
2988 CG_RegisterWeapon( WP_GRENADE_SMOKE );
2989 weapon = &cg_weapons[ WP_GRENADE_SMOKE ];
2990 break;
2991 default:
2992 CG_RegisterWeapon( ps->weapon );
2993 weapon = &cg_weapons[ ps->weapon ];
2994 break;
2995 }
2996 } else {
2997 CG_RegisterWeapon( ps->weapon );
2998 weapon = &cg_weapons[ ps->weapon ];
2999 }
3000 // dhm - end
3001
3002 // set up gun position
3003 CG_CalculateWeaponPosition( hand.origin, angles );
3004
3005 gunoff[0] = cg_gun_x.value;
3006 gunoff[1] = cg_gun_y.value;
3007 gunoff[2] = cg_gun_z.value;
3008
3009 //----(SA) removed
3010
3011 VectorMA( hand.origin, ( gunoff[0] + fovOffset[0] ), cg.refdef.viewaxis[0], hand.origin );
3012 VectorMA( hand.origin, ( gunoff[1] + fovOffset[1] ), cg.refdef.viewaxis[1], hand.origin );
3013 VectorMA( hand.origin, ( gunoff[2] + fovOffset[2] ), cg.refdef.viewaxis[2], hand.origin );
3014
3015 AnglesToAxis( angles, hand.axis );
3016
3017 if ( cg_gun_frame.integer ) {
3018 hand.frame = hand.oldframe = cg_gun_frame.integer;
3019 hand.backlerp = 0;
3020 } else { // get the animation state
3021 CG_WeaponAnimation( ps, weapon, &hand.oldframe, &hand.frame, &hand.backlerp ); //----(SA) changed
3022 }
3023
3024 VectorCopy( hand.origin, hand.lightingOrigin );
3025
3026 hand.hModel = weapon->handsModel;
3027 hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; //----(SA)
3028
3029 // add everything onto the hand
3030 CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity );
3031 // Ridah
3032
3033 } // end "if ( ps->weapon > WP_NONE)"
3034
3035 // Rafael
3036 // add the foot
3037 CG_AddPlayerFoot( &hand, ps, &cg.predictedPlayerEntity );
3038
3039 cg.predictedPlayerEntity.lastWeaponClientFrame = cg.clientFrame;
3040 }
3041
3042 /*
3043 ==============================================================================
3044
3045 WEAPON SELECTION
3046
3047 ==============================================================================
3048 */
3049
3050 #define WP_ICON_X 38 // new sizes per MK
3051 #define WP_ICON_X_WIDE 72 // new sizes per MK
3052 #define WP_ICON_Y 38
3053 #define WP_ICON_SPACE_Y 10
3054 #define WP_DRAW_X 640 - WP_ICON_X - 4 // 4 is 'selected' border width
3055 #define WP_DRAW_X_WIDE 640 - WP_ICON_X_WIDE - 4
3056 #define WP_DRAW_Y 4
3057
3058 // secondary fire icons
3059 #define WP_ICON_SEC_X 18 // new sizes per MK
3060 #define WP_ICON_SEC_Y 18
3061
3062
3063 /*
3064 ===================
3065 CG_DrawWeaponSelect
3066 ===================
3067 */
CG_DrawWeaponSelect(void)3068 void CG_DrawWeaponSelect( void ) {
3069 int i;
3070 int x, y;
3071 int curweap, curweapbank = 0, curweapcycle = 0, drawweap;
3072 int realweap; // DHM - Nerve
3073 int bits[MAX_WEAPONS / ( sizeof( int ) * 8 )];
3074 float *color;
3075
3076 // don't display if dead
3077 if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) {
3078 return;
3079 }
3080
3081 if ( !cg.weaponSelect ) {
3082 return;
3083 }
3084
3085 color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME );
3086 if ( !color ) {
3087 return;
3088 }
3089 trap_R_SetColor( color );
3090
3091 if ( cg_fixedAspect.integer == 2 ) {
3092 CG_SetScreenPlacement(PLACE_RIGHT, PLACE_TOP);
3093 }
3094
3095 //----(SA) neither of these overlap the weapon selection area anymore, so let them stay
3096 // showing weapon select clears pickup item display, but not the blend blob
3097 // cg.itemPickupTime = 0;
3098
3099 // also clear holdable list
3100 // cg.holdableSelectTime = 0;
3101 //----(SA) end
3102
3103 // count the number of weapons owned
3104 memcpy( bits, cg.snap->ps.weapons, sizeof( bits ) );
3105
3106 curweap = cg.weaponSelect;
3107
3108 // get bank/cycle of current weapon
3109 if ( !CG_WeaponIndex( curweap, &curweapbank, &curweapcycle ) ) {
3110
3111 // weapon selected isn't a primary weapon, so draw the alternates bank
3112 CG_WeaponIndex( getAltWeapon( curweap ), &curweapbank, &curweapcycle );
3113
3114 }
3115
3116 y = WP_DRAW_Y;
3117
3118 for ( i = 0; i < maxWeapsInBank; i++ ) {
3119
3120 qboolean wideweap; // is the icon one of the double width ones
3121
3122 // primary fire
3123 // JPW NERVE
3124 if ( cg_gameType.integer == GT_WOLF ) {
3125 drawweap = weapBanksMultiPlayer[curweapbank][i];
3126 } else {
3127 // jpw
3128 drawweap = weapBanks[curweapbank][i];
3129 }
3130
3131 realweap = drawweap; // DHM - Nerve
3132
3133 switch ( drawweap ) {
3134 case WP_THOMPSON:
3135 case WP_MP40:
3136 case WP_STEN:
3137 case WP_MAUSER:
3138 case WP_GARAND:
3139 case WP_VENOM:
3140 case WP_TESLA:
3141 case WP_PANZERFAUST:
3142 case WP_FLAMETHROWER:
3143 case WP_FG42:
3144 case WP_FG42SCOPE:
3145 wideweap = qtrue;
3146 break;
3147 default:
3148 wideweap = qfalse;
3149 break;
3150 }
3151
3152 if ( wideweap ) {
3153 x = WP_DRAW_X_WIDE;
3154 } else {
3155 x = WP_DRAW_X;
3156 }
3157
3158 if ( drawweap && ( bits[0] & ( 1 << drawweap ) ) ) {
3159 // you've got it, draw it
3160
3161 // DHM - Nerve :: Special case for WP_CLASS_SPECIAL
3162 if ( cgs.gametype == GT_WOLF && drawweap == WP_CLASS_SPECIAL ) {
3163 switch ( cg.predictedPlayerState.stats[ STAT_PLAYER_CLASS ] ) {
3164 case PC_ENGINEER:
3165 drawweap = WP_CLASS_SPECIAL;
3166 break;
3167 case PC_MEDIC:
3168 drawweap = WP_MEDIC_HEAL;
3169 break;
3170 case PC_LT:
3171 drawweap = WP_GRENADE_SMOKE;
3172 break;
3173 default:
3174 break;
3175 }
3176 }
3177 // dhm - end
3178
3179 CG_RegisterWeapon( drawweap );
3180
3181 if ( wideweap ) {
3182 // weapon icon
3183 if ( realweap == curweap ) {
3184 CG_DrawPic( x, y, WP_ICON_X_WIDE, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[1] );
3185 } else {
3186 CG_DrawPic( x, y, WP_ICON_X_WIDE, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[0] );
3187 }
3188
3189 // no ammo cross
3190 if ( !CG_WeaponHasAmmo( realweap ) ) { // DHM - Nerve
3191 CG_DrawPic( x, y, WP_ICON_X_WIDE, WP_ICON_Y, cgs.media.noammoShader );
3192 }
3193 } else {
3194 // weapon icon
3195 if ( realweap == curweap ) {
3196 CG_DrawPic( x, y, WP_ICON_X, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[1] );
3197 } else {
3198 CG_DrawPic( x, y, WP_ICON_X, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[0] );
3199 }
3200
3201 // no ammo cross
3202 if ( !CG_WeaponHasAmmo( realweap ) ) { // DHM - Nerve
3203 CG_DrawPic( x, y, WP_ICON_X, WP_ICON_Y, cgs.media.noammoShader );
3204 }
3205 }
3206
3207 } else {
3208 continue;
3209 }
3210
3211 // secondary fire
3212 if ( wideweap ) {
3213 x = WP_DRAW_X_WIDE - WP_ICON_SEC_X - 4;
3214 } else {
3215 x = WP_DRAW_X - WP_ICON_SEC_X - 4;
3216 }
3217
3218 // JPW NERVE
3219 if ( cg_gameType.integer == GT_WOLF ) {
3220 drawweap = getAltWeapon( weapBanksMultiPlayer[curweapbank][i] );
3221 } else {
3222 // jpw
3223 drawweap = getAltWeapon( weapBanks[curweapbank][i] );
3224 }
3225
3226 // clear drawweap if getaltweap() returns the same weap as passed in. (no secondary available)
3227 // JPW NERVE
3228 if ( cg_gameType.integer == GT_WOLF ) {
3229 if ( drawweap == weapBanksMultiPlayer[curweapbank][i] ) {
3230 drawweap = 0;
3231 }
3232 } else {
3233 // jpw
3234 if ( drawweap == weapBanks[curweapbank][i] ) {
3235 drawweap = 0;
3236 }
3237 }
3238
3239 realweap = drawweap; // DHM - Nerve
3240
3241 if ( drawweap && ( bits[0] & ( 1 << drawweap ) ) ) {
3242 // you've got it, draw it
3243 // DHM - Nerve :: Special case for WP_CLASS_SPECIAL
3244 if ( cgs.gametype == GT_WOLF && drawweap == WP_CLASS_SPECIAL ) {
3245 switch ( cg.predictedPlayerState.stats[ STAT_PLAYER_CLASS ] ) {
3246 case PC_ENGINEER:
3247 drawweap = WP_CLASS_SPECIAL;
3248 break;
3249 case PC_MEDIC:
3250 drawweap = WP_MEDIC_HEAL;
3251 break;
3252 case PC_LT:
3253 drawweap = WP_GRENADE_SMOKE;
3254 break;
3255 default:
3256 break;
3257 }
3258 }
3259 // dhm - end
3260
3261 CG_RegisterWeapon( drawweap );
3262
3263 // weapon icon
3264 if ( realweap == cg.weaponSelect ) {
3265 CG_DrawPic( x, y, WP_ICON_SEC_X, WP_ICON_SEC_Y, cg_weapons[drawweap].weaponIcon[1] );
3266 } else {
3267 CG_DrawPic( x, y, WP_ICON_SEC_X, WP_ICON_SEC_Y, cg_weapons[drawweap].weaponIcon[0] );
3268 }
3269
3270 // no ammo cross
3271 if ( !CG_WeaponHasAmmo( realweap ) ) {
3272 CG_DrawPic( x, y, WP_ICON_SEC_X, WP_ICON_SEC_Y, cgs.media.noammoShader );
3273 }
3274 }
3275
3276
3277 y += ( WP_ICON_Y + WP_ICON_SPACE_Y );
3278 }
3279 }
3280
3281
3282
3283
3284 /*
3285 ==============
3286 CG_WeaponHasAmmo
3287 check for ammo
3288 ==============
3289 */
CG_WeaponHasAmmo(int i)3290 static qboolean CG_WeaponHasAmmo( int i ) {
3291 if ( !( cg.predictedPlayerState.ammo[BG_FindAmmoForWeapon( i )] ) &&
3292 !( cg.predictedPlayerState.ammoclip[BG_FindClipForWeapon( i )] ) ) {
3293 return qfalse;
3294 }
3295
3296 return qtrue;
3297 }
3298
3299
3300 /*
3301 ===============
3302 CG_WeaponSelectable
3303 ===============
3304 */
CG_WeaponSelectable(int i)3305 static qboolean CG_WeaponSelectable( int i ) {
3306
3307 // allow the player to unselect all weapons
3308 // if(i == WP_NONE)
3309 // return qtrue;
3310
3311 // if holding a melee weapon (chair/shield/etc.) only allow single-handed weapons
3312 if ( cg.snap->ps.eFlags & EF_MELEE_ACTIVE ) {
3313 if ( !( WEAPS_ONE_HANDED & ( 1 << i ) ) ) {
3314 return qfalse;
3315 }
3316 }
3317
3318 // allow switch out of scope for weapons where you fired the last shot while scoped
3319 // and we left you in that view to see the result of the shot
3320 switch ( cg.weaponSelect ) {
3321 case WP_SNOOPERSCOPE:
3322 if ( i == WP_GARAND ) {
3323 return qtrue;
3324 }
3325 break;
3326 case WP_SNIPERRIFLE:
3327 if ( i == WP_MAUSER ) {
3328 return qtrue;
3329 }
3330 break;
3331 case WP_FG42SCOPE:
3332 if ( i == WP_FG42 ) {
3333 return qtrue;
3334 }
3335 break;
3336 default:
3337 break;
3338 }
3339
3340
3341 // check for weapon
3342 if ( !( COM_BitCheck( cg.predictedPlayerState.weapons, i ) ) ) {
3343 return qfalse;
3344 }
3345
3346 if ( !CG_WeaponHasAmmo( i ) ) {
3347 return qfalse;
3348 }
3349
3350 return qtrue;
3351 }
3352
3353
3354
3355
3356 /*
3357 ==============
3358 CG_WeaponIndex
3359 ==============
3360 */
CG_WeaponIndex(int weapnum,int * bank,int * cycle)3361 int CG_WeaponIndex( int weapnum, int *bank, int *cycle ) {
3362 static int bnk, cyc;
3363
3364 if ( weapnum <= 0 || weapnum >= WP_NUM_WEAPONS ) {
3365 if ( bank ) {
3366 *bank = 0;
3367 }
3368 if ( cycle ) {
3369 *cycle = 0;
3370 }
3371 return 0;
3372 }
3373
3374 for ( bnk = 0; bnk < maxWeapBanks; bnk++ ) {
3375 for ( cyc = 0; cyc < maxWeapsInBank; cyc++ ) {
3376
3377 // end of cycle, go to next bank
3378 if ( cg_gameType.integer != GT_WOLF ) { // JPW NERVE
3379 if ( !weapBanks[bnk][cyc] ) {
3380 break;
3381 }
3382
3383 // found the current weapon
3384 if ( weapBanks[bnk][cyc] == weapnum ) {
3385 if ( bank ) {
3386 *bank = bnk;
3387 }
3388 if ( cycle ) {
3389 *cycle = cyc;
3390 }
3391 return 1;
3392 }
3393 }
3394 // JPW NERVE
3395 else {
3396 if ( !weapBanksMultiPlayer[bnk][cyc] ) {
3397 break;
3398 }
3399
3400 // found the current weapon
3401 if ( weapBanksMultiPlayer[bnk][cyc] == weapnum ) {
3402 if ( bank ) {
3403 *bank = bnk;
3404 }
3405 if ( cycle ) {
3406 *cycle = cyc;
3407 }
3408 return 1;
3409 }
3410 }
3411 // jpw
3412 }
3413 }
3414
3415 // failed to find the weapon in the table
3416 // probably an alternate
3417
3418 return 0;
3419 }
3420
3421
3422
3423 /*
3424 ==============
3425 getNextWeapInBank
3426 Pass in a bank and cycle and this will return the next valid weapon higher in the cycle.
3427 if the weap passed in is above highest in a cycle (maxWeapsInBank), this will safely loop around
3428 ==============
3429 */
getNextWeapInBank(int bank,int cycle)3430 static int getNextWeapInBank( int bank, int cycle ) {
3431
3432 cycle++;
3433
3434 cycle = cycle % maxWeapsInBank;
3435
3436 if ( cg_gameType.integer != GT_WOLF ) { // JPW NERVE
3437 if ( weapBanks[bank][cycle] ) { // return next weapon in bank if there is one
3438 return weapBanks[bank][cycle];
3439 } else { // return first in bank
3440 return weapBanks[bank][0];
3441 }
3442 }
3443 // JPW NERVE
3444 else {
3445 if ( weapBanksMultiPlayer[bank][cycle] ) { // return next weapon in bank if there is one
3446 return weapBanksMultiPlayer[bank][cycle];
3447 } else { // return first in bank
3448 return weapBanksMultiPlayer[bank][0];
3449 }
3450 }
3451 // jpw
3452 }
3453
getNextWeapInBankBynum(int weapnum)3454 static int getNextWeapInBankBynum( int weapnum ) {
3455 int bank, cycle;
3456
3457 if ( !CG_WeaponIndex( weapnum, &bank, &cycle ) ) {
3458 return weapnum;
3459 }
3460
3461 return getNextWeapInBank( bank, cycle );
3462 }
3463
3464
3465 /*
3466 ==============
3467 getPrevWeapInBank
3468 Pass in a bank and cycle and this will return the next valid weapon lower in the cycle.
3469 if the weap passed in is the lowest in a cycle (0), this will loop around to the
3470 top (maxWeapsInBank-1) and start down from there looking for a valid weapon position
3471 ==============
3472 */
getPrevWeapInBank(int bank,int cycle)3473 static int getPrevWeapInBank( int bank, int cycle ) {
3474 cycle--;
3475 if ( cycle < 0 ) {
3476 cycle = maxWeapsInBank - 1;
3477 }
3478
3479
3480 if ( cg_gameType.integer != GT_WOLF ) {
3481 while ( !weapBanks[bank][cycle] ) {
3482 cycle--;
3483
3484 if ( cycle < 0 ) {
3485 cycle = maxWeapsInBank - 1;
3486 }
3487 }
3488 return weapBanks[bank][cycle];
3489 } else {
3490 while ( !weapBanksMultiPlayer[bank][cycle] ) {
3491 cycle--;
3492
3493 if ( cycle < 0 ) {
3494 cycle = maxWeapsInBank - 1;
3495 }
3496 }
3497 return weapBanksMultiPlayer[bank][cycle];
3498 }
3499 }
3500
3501
getPrevWeapInBankBynum(int weapnum)3502 static int getPrevWeapInBankBynum( int weapnum ) {
3503 int bank, cycle;
3504
3505 if ( !CG_WeaponIndex( weapnum, &bank, &cycle ) ) {
3506 return weapnum;
3507 }
3508
3509 return getPrevWeapInBank( bank, cycle );
3510 }
3511
3512
3513
3514 /*
3515 ==============
3516 getNextBankWeap
3517 Pass in a bank and cycle and this will return the next valid weapon in a higher bank.
3518 sameBankPosition: if there's a weapon in the next bank at the same cycle,
3519 return that (colt returns thompson for example) rather than the lowest weapon
3520 ==============
3521 */
getNextBankWeap(int bank,int cycle,qboolean sameBankPosition)3522 static int getNextBankWeap( int bank, int cycle, qboolean sameBankPosition ) {
3523 bank++;
3524
3525 bank = bank % maxWeapBanks;
3526
3527 if ( cg_gameType.integer != GT_WOLF ) { // JPW NERVE
3528 if ( sameBankPosition && weapBanks[bank][cycle] ) {
3529 return weapBanks[bank][cycle];
3530 } else {
3531 return weapBanks[bank][0];
3532 }
3533 }
3534 // JPW NERVE
3535 else {
3536 if ( sameBankPosition && weapBanksMultiPlayer[bank][cycle] ) {
3537 return weapBanksMultiPlayer[bank][cycle];
3538 } else {
3539 return weapBanksMultiPlayer[bank][0];
3540 }
3541 }
3542 // jpw
3543 }
3544
3545 /*
3546 ==============
3547 getPrevBankWeap
3548 Pass in a bank and cycle and this will return the next valid weapon in a lower bank.
3549 sameBankPosition: if there's a weapon in the prev bank at the same cycle,
3550 return that (thompson returns colt for example) rather than the highest weapon
3551 ==============
3552 */
getPrevBankWeap(int bank,int cycle,qboolean sameBankPosition)3553 static int getPrevBankWeap( int bank, int cycle, qboolean sameBankPosition ) {
3554 int i;
3555
3556 bank--;
3557
3558 if ( bank < 0 ) { // don't go below 0, cycle up to top
3559 bank += maxWeapBanks;
3560 }
3561 bank = bank % maxWeapBanks;
3562
3563 if ( cg_gameType.integer != GT_WOLF ) { // JPW NERVE
3564 if ( sameBankPosition && weapBanks[bank][cycle] ) {
3565 return weapBanks[bank][cycle];
3566 } else
3567 { // find highest weap in bank
3568 for ( i = maxWeapsInBank - 1; i >= 0; i-- ) {
3569 if ( weapBanks[bank][i] ) {
3570 return weapBanks[bank][i];
3571 }
3572 }
3573
3574 // if it gets to here, no valid weaps in this bank, go down another bank
3575 return getPrevBankWeap( bank, cycle, sameBankPosition );
3576 }
3577 }
3578 // JPW NERVE
3579 else {
3580 if ( sameBankPosition && weapBanksMultiPlayer[bank][cycle] ) {
3581 return weapBanksMultiPlayer[bank][cycle];
3582 } else
3583 { // find highest weap in bank
3584 for ( i = maxWeapsInBank - 1; i >= 0; i-- ) {
3585 if ( weapBanksMultiPlayer[bank][i] ) {
3586 return weapBanksMultiPlayer[bank][i];
3587 }
3588 }
3589
3590 // if it gets to here, no valid weaps in this bank, go down another bank
3591 return getPrevBankWeap( bank, cycle, sameBankPosition );
3592 }
3593 }
3594 // jpw
3595 }
3596
3597 /*
3598 ==============
3599 getAltWeapon
3600 ==============
3601 */
getAltWeapon(int weapnum)3602 static int getAltWeapon( int weapnum ) {
3603 if ( weapnum > MAX_WEAP_ALTS ) {
3604 return weapnum;
3605 }
3606
3607 if ( weapAlts[weapnum] ) {
3608 return weapAlts[weapnum];
3609 }
3610
3611 return weapnum;
3612 }
3613
3614 /*
3615 ==============
3616 getEquivWeapon
3617 return the id of the opposite team's weapon.
3618 Passing the weapnum of the mp40 returns the id of the thompson, and likewise
3619 passing the weapnum of the thompson returns the id of the mp40.
3620 No equivalent available will return the weapnum passed in.
3621 ==============
3622 */
getEquivWeapon(int weapnum)3623 int getEquivWeapon( int weapnum ) {
3624 int num = weapnum;
3625
3626 switch ( weapnum ) {
3627 // going from german to american
3628 case WP_LUGER: num = WP_COLT; break;
3629 case WP_MAUSER: num = WP_GARAND; break;
3630 case WP_MP40: num = WP_THOMPSON; break;
3631 case WP_GRENADE_LAUNCHER: num = WP_GRENADE_PINEAPPLE; break;
3632
3633 // going from american to german
3634 case WP_COLT: num = WP_LUGER; break;
3635 case WP_GARAND: num = WP_MAUSER; break;
3636 case WP_THOMPSON: num = WP_MP40; break;
3637 case WP_GRENADE_PINEAPPLE: num = WP_GRENADE_LAUNCHER; break;
3638 }
3639 return num;
3640 }
3641
3642
3643
3644 /*
3645 ==============
3646 CG_WeaponSuggest
3647 ==============
3648 */
CG_WeaponSuggest(int weap)3649 void CG_WeaponSuggest( int weap ) {
3650 #if 0 // not currently supported
3651 int bank, cycle;
3652
3653 if ( !cg_useSuggestedWeapons.integer ) {
3654 return;
3655 }
3656
3657 cg.weaponSelectTime = cg.time;
3658
3659 CG_WeaponIndex( weap, &bank, &cycle ); // get location of this weap
3660
3661 cg.lastWeapSelInBank[bank] = weap; // make this weap first priority in that bank
3662 #endif
3663 }
3664
3665
3666 /*
3667 ==============
3668 CG_SetSniperZoom
3669 ==============
3670 */
3671
CG_SetSniperZoom(int lastweap,int newweap)3672 void CG_SetSniperZoom( int lastweap, int newweap ) {
3673 int zoomindex;
3674
3675 if ( lastweap == newweap ) {
3676 return;
3677 }
3678
3679 cg.zoomval = 0;
3680 cg.zoomedScope = 0;
3681
3682 // check for fade-outs
3683 switch ( lastweap ) {
3684 case WP_SNIPERRIFLE:
3685 // cg.zoomedScope = 500; // TODO: add to zoomTable
3686 // cg.zoomTime = cg.time;
3687 break;
3688 case WP_SNOOPERSCOPE:
3689 // cg.zoomedScope = 500; // TODO: add to zoomTable
3690 // cg.zoomTime = cg.time;
3691 break;
3692 case WP_FG42SCOPE:
3693 // cg.zoomedScope = 1; // TODO: add to zoomTable
3694 // cg.zoomTime = cg.time;
3695 break;
3696 }
3697
3698 switch ( newweap ) {
3699
3700 default:
3701 return; // no sniper zoom, get out.
3702
3703 case WP_SNIPERRIFLE:
3704 cg.zoomval = cg_zoomDefaultSniper.value;
3705 cg.zoomedScope = 900; // TODO: add to zoomTable
3706 zoomindex = ZOOM_SNIPER;
3707 break;
3708 case WP_SNOOPERSCOPE:
3709 cg.zoomval = cg_zoomDefaultSnooper.value;
3710 cg.zoomedScope = 800; // TODO: add to zoomTable
3711 zoomindex = ZOOM_SNOOPER;
3712 break;
3713 case WP_FG42SCOPE:
3714 cg.zoomval = cg_zoomDefaultFG.value;
3715 cg.zoomedScope = 1; // TODO: add to zoomTable
3716 zoomindex = ZOOM_FG42SCOPE;
3717 break;
3718 }
3719
3720 // constrain user preferred fov to weapon limitations
3721 if ( cg.zoomval > zoomTable[zoomindex][ZOOM_OUT] ) {
3722 cg.zoomval = zoomTable[zoomindex][ZOOM_OUT];
3723 }
3724 if ( cg.zoomval < zoomTable[zoomindex][ZOOM_IN] ) {
3725 cg.zoomval = zoomTable[zoomindex][ZOOM_IN];
3726 }
3727
3728 cg.zoomTime = cg.time;
3729 }
3730
3731
3732 /*
3733 ==============
3734 CG_PlaySwitchSound
3735 Get special switching sounds if they're there
3736 ==============
3737 */
CG_PlaySwitchSound(int lastweap,int newweap)3738 void CG_PlaySwitchSound( int lastweap, int newweap ) {
3739 // weaponInfo_t *weap;
3740 // weap = &cg_weapons[ ent->weapon ];
3741 sfxHandle_t switchsound;
3742
3743 switchsound = cgs.media.selectSound;
3744
3745 if ( getAltWeapon( lastweap ) == newweap ) { // alt switch
3746 switch ( newweap ) {
3747 case WP_SILENCER:
3748 case WP_LUGER:
3749 switchsound = cg_weapons[newweap].switchSound[0];
3750 break;
3751 default:
3752 break;
3753 }
3754 }
3755
3756 trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_WEAPON, switchsound );
3757 }
3758
3759
3760 /*
3761 ==============
3762 CG_FinishWeaponChange
3763 ==============
3764 */
CG_FinishWeaponChange(int lastweap,int newweap)3765 void CG_FinishWeaponChange( int lastweap, int newweap ) {
3766 int newbank;
3767
3768 cg.weaponSelectTime = cg.time; // flash the weapon icon
3769
3770 // remember which weapon in this bank was last selected so when cycling back
3771 // to this bank, that weap will be highlighted first
3772 if ( CG_WeaponIndex( newweap, &newbank, NULL ) ) {
3773 cg.lastWeapSelInBank[newbank] = newweap;
3774 }
3775
3776 if ( lastweap == newweap ) { // no need to do any more than flash the icon
3777 return;
3778 }
3779
3780 CG_PlaySwitchSound( lastweap, newweap ); //----(SA) added
3781
3782 CG_SetSniperZoom( lastweap, newweap );
3783
3784 // setup for a user call to CG_LastWeaponUsed_f()
3785 if ( lastweap == cg.lastFiredWeapon ) {
3786 // don't set switchback for some weaps...
3787 switch ( lastweap ) {
3788 case WP_SNIPERRIFLE:
3789 case WP_SNOOPERSCOPE:
3790 case WP_FG42SCOPE:
3791 break;
3792 default:
3793 cg.switchbackWeapon = lastweap;
3794 break;
3795 }
3796 } else {
3797 // if this ended up having the switchback be the same
3798 // as the new weapon, set the switchback to the prev
3799 // selected weapon will become the switchback
3800 if ( cg.switchbackWeapon == newweap ) {
3801 cg.switchbackWeapon = lastweap;
3802 }
3803 }
3804
3805 cg.weaponSelect = newweap;
3806 }
3807
3808 /*
3809 ==============
3810 CG_AltfireWeapon_f
3811 for example, switching between WP_MAUSER and WP_SNIPERRIFLE
3812 ==============
3813 */
CG_AltWeapon_f(void)3814 void CG_AltWeapon_f( void ) {
3815 int original, num;
3816
3817 if ( !cg.snap ) {
3818 return;
3819 }
3820 if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
3821 return;
3822 }
3823
3824 if ( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) { // no alt-switching when on mg42
3825 return;
3826 }
3827
3828 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
3829 return; // force pause so holding it down won't go too fast
3830
3831 }
3832 // Don't try to switch when in the middle of reloading.
3833 if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) {
3834 return;
3835 }
3836
3837 original = cg.weaponSelect;
3838
3839 num = getAltWeapon( original );
3840
3841 if ( CG_WeaponSelectable( num ) ) { // new weapon is valid
3842
3843 //----(SA) testing mod functionality for the silencer on the luger
3844 // (SA) this way, if you switch away from the silenced luger,
3845 // the silencer will still be attached when you switch back
3846 // (until you remove it)
3847 // TODO: will need to make sure the table gets initialized properly on restart/death/whatever.
3848 // I still think I'm going to make the weapon banks stored in the config, so this will
3849 // just be a matter of resetting the banks to what's in the config.
3850 switch ( original ) {
3851 case WP_LUGER:
3852 if ( cg.snap->ps.eFlags & EF_MELEE_ACTIVE ) { // if you're holding a chair, you can't screw on the silencer
3853 return;
3854 }
3855 weapBanks[2][0] = WP_SILENCER;
3856 break;
3857 case WP_SILENCER:
3858 if ( cg.snap->ps.eFlags & EF_MELEE_ACTIVE ) { // if you're holding a chair, you can't remove the silencer
3859 return;
3860 }
3861 weapBanks[2][0] = WP_LUGER;
3862 break;
3863
3864 case WP_AKIMBO:
3865 weapBanks[2][1] = WP_COLT;
3866 break;
3867 case WP_COLT:
3868 weapBanks[2][1] = WP_AKIMBO;
3869 break;
3870 }
3871
3872 //----(SA) end
3873 CG_FinishWeaponChange( original, num );
3874 }
3875 }
3876
3877
3878 /*
3879 ==============
3880 CG_NextWeap
3881
3882 switchBanks - curweap is the last in a bank, 'qtrue' means go to the next available bank, 'qfalse' means loop to the head of the bank
3883 ==============
3884 */
CG_NextWeap(qboolean switchBanks)3885 void CG_NextWeap( qboolean switchBanks ) {
3886 int bank = 0, cycle = 0, newbank = 0, newcycle = 0;
3887 int num, curweap;
3888 qboolean nextbank = qfalse; // need to switch to the next bank of weapons?
3889 int i, j;
3890
3891 num = curweap = cg.weaponSelect;
3892
3893 CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon
3894
3895 // if you're using an alt mode weapon, try switching back to the parent first
3896 if ( curweap >= WP_BEGINSECONDARY && curweap <= WP_LASTSECONDARY ) {
3897 num = getAltWeapon( curweap ); // base any further changes on the parent
3898 if ( CG_WeaponSelectable( num ) ) { // the parent was selectable, drop back to that
3899 CG_FinishWeaponChange( curweap, num );
3900 return;
3901 }
3902 }
3903
3904
3905 // if ( cg_cycleAllWeaps.integer || !switchBanks ) {
3906 if ( 1 ) {
3907 for ( i = 0; i < maxWeapsInBank; i++ ) {
3908 num = getNextWeapInBankBynum( num );
3909
3910 CG_WeaponIndex( num, NULL, &newcycle ); // get cycle of new weapon. if it's lower than the original, then it cycled around
3911
3912 if ( switchBanks ) {
3913 if ( newcycle <= cycle ) {
3914 nextbank = qtrue;
3915 break;
3916 }
3917 } else { // don't switch banks if you get to the end
3918
3919 if ( num == curweap ) { // back to start, just leave it where it is
3920 return;
3921 }
3922 }
3923
3924 if ( CG_WeaponSelectable( num ) ) {
3925 break;
3926 }
3927 }
3928 } else {
3929 nextbank = qtrue;
3930 }
3931
3932 if ( nextbank ) {
3933 for ( i = 0; i < maxWeapBanks; i++ ) {
3934 // if ( cg_cycleAllWeaps.integer ) {
3935 if ( 1 ) {
3936 num = getNextBankWeap( bank + i, cycle, qfalse ); // cycling all weaps always starts the next bank at the bottom
3937 } else {
3938 if ( cg.lastWeapSelInBank[bank + i + 1] ) {
3939 num = cg.lastWeapSelInBank[bank + i + 1];
3940 } else {
3941 num = getNextBankWeap( bank + i, cycle, qtrue );
3942 }
3943 }
3944
3945 if ( num == 0 ) {
3946 continue;
3947 }
3948
3949 if ( CG_WeaponSelectable( num ) ) { // first entry in bank was selectable, no need to scan the bank
3950 break;
3951 }
3952
3953 CG_WeaponIndex( num, &newbank, &newcycle ); // get the bank of the new weap
3954
3955 for ( j = newcycle; j < maxWeapsInBank; j++ ) {
3956 num = getNextWeapInBank( newbank, j );
3957
3958 if ( CG_WeaponSelectable( num ) ) { // found selectable weapon
3959 break;
3960 }
3961
3962 num = 0;
3963 }
3964
3965 if ( num ) { // a selectable weapon was found in the current bank
3966 break;
3967 }
3968 }
3969 }
3970
3971 CG_FinishWeaponChange( curweap, num ); //----(SA)
3972 }
3973
3974 /*
3975 ==============
3976 CG_PrevWeap
3977
3978 switchBanks - curweap is the last in a bank
3979 'qtrue' - go to the next available bank
3980 'qfalse' - loop to the head of the bank
3981 ==============
3982 */
CG_PrevWeap(qboolean switchBanks)3983 void CG_PrevWeap( qboolean switchBanks ) {
3984 int bank = 0, cycle = 0, newbank = 0, newcycle = 0;
3985 int num, curweap;
3986 qboolean prevbank = qfalse; // need to switch to the next bank of weapons?
3987 int i, j;
3988
3989 num = curweap = cg.weaponSelect;
3990
3991 CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon
3992
3993 // if you're using an alt mode weapon, try switching back to the parent first
3994 if ( curweap >= WP_BEGINSECONDARY && curweap <= WP_LASTSECONDARY ) {
3995 num = getAltWeapon( curweap ); // base any further changes on the parent
3996 if ( CG_WeaponSelectable( num ) ) { // the parent was selectable, drop back to that
3997 CG_FinishWeaponChange( curweap, num );
3998 return;
3999 }
4000 }
4001
4002 // initially, just try to find a lower weapon in the current bank
4003 // if ( cg_cycleAllWeaps.integer || !switchBanks ) {
4004 if ( 1 ) {
4005
4006 // if(cycle == 0) { // already at bottom of list
4007 // prevbank = qtrue;
4008 // } else {
4009 for ( i = cycle; i >= 0; i-- ) {
4010 // num = getPrevWeapInBank(bank, i);
4011 num = getPrevWeapInBankBynum( num );
4012
4013 CG_WeaponIndex( num, NULL, &newcycle ); // get cycle of new weapon. if it's greater than the original, then it cycled around
4014
4015 if ( switchBanks ) {
4016 if ( newcycle > ( cycle - 1 ) ) {
4017 prevbank = qtrue;
4018 break;
4019 }
4020 } else { // don't switch banks if you get to the end
4021 if ( num == curweap ) { // back to start, just leave it where it is
4022 return;
4023 }
4024 }
4025
4026 if ( CG_WeaponSelectable( num ) ) {
4027 break;
4028 }
4029 }
4030 // }
4031 } else {
4032 prevbank = qtrue;
4033 }
4034
4035 // cycle to previous bank.
4036 // if cycleAllWeaps: find highest weapon in bank
4037 // else: try to find weap in bank that matches cycle position
4038 // else: use base weap in bank
4039
4040 if ( prevbank ) {
4041 for ( i = 0; i < maxWeapBanks; i++ ) {
4042 // if ( cg_cycleAllWeaps.integer ) {
4043 if ( 1 ) {
4044 num = getPrevBankWeap( bank - i, cycle, qfalse ); // cycling all weaps always starts the next bank at the bottom
4045 } else {
4046 num = getPrevBankWeap( bank - i, cycle, qtrue );
4047 }
4048
4049 if ( num == 0 ) {
4050 continue;
4051 }
4052
4053 if ( CG_WeaponSelectable( num ) ) { // first entry in bank was selectable, no need to scan the bank
4054 break;
4055 }
4056
4057 CG_WeaponIndex( num, &newbank, &newcycle ); // get the bank of the new weap
4058
4059 for ( j = maxWeapsInBank; j > 0; j-- ) {
4060 num = getPrevWeapInBank( newbank, j );
4061
4062 if ( CG_WeaponSelectable( num ) ) { // found selectable weapon
4063 break;
4064 }
4065
4066 num = 0;
4067 }
4068
4069 if ( num ) { // a selectable weapon was found in the current bank
4070 break;
4071 }
4072 }
4073 }
4074
4075 CG_FinishWeaponChange( curweap, num ); //----(SA)
4076 }
4077
4078
4079 /*
4080 ==============
4081 CG_LastWeaponUsed_f
4082 ==============
4083 */
CG_LastWeaponUsed_f(void)4084 void CG_LastWeaponUsed_f( void ) {
4085
4086 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
4087 return; // force pause so holding it down won't go too fast
4088
4089 }
4090 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4091
4092 // don't switchback if reloading (it nullifies the reload)
4093 if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) {
4094 return;
4095 }
4096
4097 if ( !cg.switchbackWeapon ) {
4098 cg.switchbackWeapon = cg.weaponSelect;
4099 return;
4100 }
4101
4102 if ( CG_WeaponSelectable( cg.switchbackWeapon ) ) {
4103 CG_FinishWeaponChange( cg.weaponSelect, cg.switchbackWeapon );
4104 } else { // switchback no longer selectable, reset cycle
4105 cg.switchbackWeapon = 0;
4106 }
4107
4108 }
4109
4110 /*
4111 ==============
4112 CG_NextWeaponInBank_f
4113 ==============
4114 */
CG_NextWeaponInBank_f(void)4115 void CG_NextWeaponInBank_f( void ) {
4116
4117 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
4118 return; // force pause so holding it down won't go too fast
4119
4120 }
4121 // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel)
4122 // for zooming (binocs/snooper/sniper/etc.)
4123 if ( cg.zoomval ) {
4124 if ( cg_useWeapsForZoom.integer == 1 ) {
4125 CG_ZoomIn_f();
4126 return;
4127 } else if ( cg_useWeapsForZoom.integer == 2 ) {
4128 CG_ZoomOut_f();
4129 return;
4130 }
4131 }
4132
4133 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4134
4135 CG_NextWeap( qfalse );
4136 }
4137
4138 /*
4139 ==============
4140 CG_PrevWeaponInBank_f
4141 ==============
4142 */
CG_PrevWeaponInBank_f(void)4143 void CG_PrevWeaponInBank_f( void ) {
4144
4145 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
4146 return; // force pause so holding it down won't go too fast
4147
4148 }
4149 // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel)
4150 // for zooming (binocs/snooper/sniper/etc.)
4151 if ( cg.zoomval ) {
4152 if ( cg_useWeapsForZoom.integer == 2 ) {
4153 CG_ZoomIn_f();
4154 return;
4155 } else if ( cg_useWeapsForZoom.integer == 1 ) {
4156 CG_ZoomOut_f();
4157 return;
4158 }
4159 }
4160
4161 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4162
4163 CG_PrevWeap( qfalse );
4164 }
4165
4166
4167 /*
4168 ==============
4169 CG_NextWeapon_f
4170 ==============
4171 */
CG_NextWeapon_f(void)4172 void CG_NextWeapon_f( void ) {
4173
4174 if ( !cg.snap ) {
4175 return;
4176 }
4177 if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
4178 return;
4179 }
4180
4181 // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel)
4182 // for zooming (binocs/snooper/sniper/etc.)
4183 if ( cg.zoomval ) {
4184 if ( cg_useWeapsForZoom.integer == 1 ) {
4185 CG_ZoomIn_f();
4186 return;
4187 } else if ( cg_useWeapsForZoom.integer == 2 ) {
4188 CG_ZoomOut_f();
4189 return;
4190 }
4191 }
4192
4193 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
4194 return; // force pause so holding it down won't go too fast
4195
4196 }
4197 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4198
4199 // Don't try to switch when in the middle of reloading.
4200 // cheatinfo: The server actually would let you switch if this check were not
4201 // present, but would discard the reload. So the when you switched
4202 // back you'd have to start the reload over. This seems bad, however
4203 // the delay for the current reload is already in effect, so you'd lose
4204 // the reload time twice. (the first pause for the current weapon reload,
4205 // and the pause when you have to reload again 'cause you canceled this one)
4206
4207 if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) {
4208 return;
4209 }
4210
4211 CG_NextWeap( qtrue );
4212 }
4213
4214
4215 /*
4216 ==============
4217 CG_PrevWeapon_f
4218 ==============
4219 */
CG_PrevWeapon_f(void)4220 void CG_PrevWeapon_f( void ) {
4221 if ( !cg.snap ) {
4222 return;
4223 }
4224 if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
4225 return;
4226 }
4227
4228 // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel)
4229 // for zooming (binocs/snooper/sniper/etc.)
4230 if ( cg.zoomval ) {
4231 if ( cg_useWeapsForZoom.integer == 1 ) {
4232 CG_ZoomOut_f();
4233 return;
4234 } else if ( cg_useWeapsForZoom.integer == 2 ) {
4235 CG_ZoomIn_f();
4236 return;
4237 }
4238 }
4239
4240 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
4241 return; // force pause so holding it down won't go too fast
4242
4243 }
4244 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4245
4246 // Don't try to switch when in the middle of reloading.
4247 if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) {
4248 return;
4249 }
4250
4251 CG_PrevWeap( qtrue );
4252 }
4253
4254
4255 /*
4256 ==============
4257 CG_WeaponBank_f
4258 weapon keys are not generally bound directly('bind 1 weapon 1'),
4259 rather the key is bound to a given bank ('bind 1 weaponbank 1')
4260 ==============
4261 */
CG_WeaponBank_f(void)4262 void CG_WeaponBank_f( void ) {
4263 int num, i, curweap;
4264 int curbank = 0, curcycle = 0, bank = 0, cycle = 0;
4265
4266 if ( !cg.snap ) {
4267 return;
4268 }
4269
4270 if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
4271 return;
4272 }
4273
4274 if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) {
4275 return; // force pause so holding it down won't go too fast
4276
4277 }
4278 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4279
4280 // Don't try to switch when in the middle of reloading.
4281 if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) {
4282 return;
4283 }
4284
4285 bank = atoi( CG_Argv( 1 ) );
4286
4287 if ( bank <= 0 || bank > maxWeapBanks ) {
4288 return;
4289 }
4290
4291 curweap = cg.weaponSelect;
4292 CG_WeaponIndex( curweap, &curbank, &curcycle ); // get bank/cycle of current weapon
4293
4294 if ( !cg.lastWeapSelInBank[bank] ) {
4295 if ( cg_gameType.integer != GT_WOLF ) { // JPW NERVE
4296 num = weapBanks[bank][0];
4297 }
4298 // JPW NERVE
4299 else {
4300 num = weapBanksMultiPlayer[bank][0];
4301 }
4302 // jpw
4303 cycle -= 1; // cycle up to first weap
4304 } else {
4305 num = cg.lastWeapSelInBank[bank];
4306 CG_WeaponIndex( num, &bank, &cycle );
4307 if ( bank != curbank ) {
4308 cycle -= 1;
4309 }
4310
4311 }
4312
4313 for ( i = 0; i < maxWeapsInBank; i++ ) {
4314 num = getNextWeapInBank( bank, cycle + i );
4315
4316 if ( CG_WeaponSelectable( num ) ) {
4317 break;
4318 }
4319 }
4320
4321 if ( i == maxWeapsInBank ) {
4322 return;
4323 }
4324
4325 CG_FinishWeaponChange( curweap, num );
4326
4327 }
4328
4329 /*
4330 ===============
4331 CG_Weapon_f
4332 ===============
4333 */
CG_Weapon_f(void)4334 void CG_Weapon_f( void ) {
4335 int num, i, curweap;
4336 int bank = 0, cycle = 0, newbank = 0, newcycle = 0;
4337 qboolean banked = qfalse;
4338
4339 if ( !cg.snap ) {
4340 return;
4341 }
4342
4343 if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) {
4344 return;
4345 }
4346
4347 num = atoi( CG_Argv( 1 ) );
4348
4349 // JPW NERVE
4350 // weapon bind should execute weaponbank instead -- for splitting out class weapons, per Id request
4351 if ( cg_gameType.integer == GT_WOLF ) {
4352 if ( num < maxWeapBanks ) {
4353 CG_WeaponBank_f();
4354 }
4355 return;
4356 }
4357 // jpw
4358
4359 cg.weaponSelectTime = cg.time; // flash the current weapon icon
4360
4361 // Don't try to switch when in the middle of reloading.
4362 if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) {
4363 return;
4364 }
4365
4366
4367 if ( num <= WP_NONE || num > WP_NUM_WEAPONS ) {
4368 return;
4369 }
4370
4371 curweap = cg.weaponSelect;
4372
4373 CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon
4374 banked = CG_WeaponIndex( num, &newbank, &newcycle ); // get bank/cycle of requested weapon
4375
4376 // the new weapon was not found in the reglar banks
4377 // assume the player want's to go directly to it if possible
4378 if ( !banked ) {
4379 if ( CG_WeaponSelectable( num ) ) {
4380 CG_FinishWeaponChange( curweap, num );
4381 return;
4382 }
4383 }
4384
4385 if ( bank != newbank ) {
4386 cycle = newcycle - 1; // drop down one from the requested weap's cycle so it will
4387 }
4388 // try to initially cycle up to the requested weapon
4389
4390 for ( i = 0; i < maxWeapsInBank; i++ ) {
4391 num = getNextWeapInBank( newbank, cycle + i );
4392
4393 if ( num == curweap ) { // no other weapons in bank
4394 return;
4395 }
4396
4397 if ( CG_WeaponSelectable( num ) ) {
4398 break;
4399 }
4400 }
4401
4402 if ( i == maxWeapsInBank ) {
4403 return;
4404 }
4405
4406 CG_FinishWeaponChange( curweap, num );
4407 }
4408
4409 /*
4410 ===================
4411 CG_OutOfAmmoChange
4412
4413 The current weapon has just run out of ammo
4414 ===================
4415 */
CG_OutOfAmmoChange(void)4416 void CG_OutOfAmmoChange( void ) {
4417 int i;
4418 int bank = 0, cycle = 0;
4419 int equiv = WP_NONE;
4420
4421 //
4422 // trivial switching
4423 //
4424
4425 // if you're using an alt mode weapon, try switching back to the parent
4426 // otherwise, switch to the equivalent if you've got it
4427 if ( cg.weaponSelect >= WP_BEGINSECONDARY && cg.weaponSelect <= WP_LASTSECONDARY ) {
4428 cg.weaponSelect = equiv = getAltWeapon( cg.weaponSelect ); // base any further changes on the parent
4429 if ( CG_WeaponSelectable( equiv ) ) { // the parent was selectable, drop back to that
4430 CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA)
4431 return;
4432 }
4433 }
4434
4435
4436 // now try the opposite team's equivalent weap
4437 equiv = getEquivWeapon( cg.weaponSelect );
4438
4439 if ( equiv != cg.weaponSelect && CG_WeaponSelectable( equiv ) ) {
4440 cg.weaponSelect = equiv;
4441 CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA)
4442 return;
4443 }
4444
4445 //
4446 // more complicated selection
4447 //
4448
4449 // didn't have available alternative or equivalent, try another weap in the bank
4450 CG_WeaponIndex( cg.weaponSelect, &bank, &cycle ); // get bank/cycle of current weapon
4451
4452 for ( i = cycle; i < maxWeapsInBank; i++ ) {
4453 equiv = getNextWeapInBank( bank, i );
4454 if ( CG_WeaponSelectable( equiv ) ) { // found a reasonable replacement
4455 cg.weaponSelect = equiv;
4456 CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA)
4457 return;
4458 }
4459 }
4460
4461
4462 // still nothing available, just go to the next
4463 // available weap using the regular selection scheme
4464 CG_NextWeap( qtrue );
4465
4466 }
4467
4468 /*
4469 ===================================================================================================
4470
4471 WEAPON EVENTS
4472
4473 ===================================================================================================
4474 */
4475
4476 // Note to self this is dead code
CG_MG42EFX(centity_t * cent)4477 void CG_MG42EFX( centity_t *cent ) {
4478 vec3_t forward;
4479 vec3_t point;
4480 refEntity_t flash;
4481
4482 // trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, hWeaponSnd );
4483
4484 VectorCopy( cent->currentState.origin, point );
4485 AngleVectors( cent->currentState.angles, forward, NULL, NULL );
4486 VectorMA( point, 40, forward, point );
4487 trap_R_AddLightToScene( point, 200 + ( rand() & 31 ),1.0, 0.6, 0.23, 0 );
4488
4489 memset( &flash, 0, sizeof( flash ) );
4490 flash.renderfx = RF_LIGHTING_ORIGIN;
4491 flash.hModel = cgs.media.mg42muzzleflash;
4492
4493 VectorCopy( point, flash.origin );
4494 AnglesToAxis( cg.refdefViewAngles, flash.axis );
4495
4496 trap_R_AddRefEntityToScene( &flash );
4497 }
4498
CG_FLAKEFX(centity_t * cent,int whichgun)4499 void CG_FLAKEFX( centity_t *cent, int whichgun ) {
4500 entityState_t *ent;
4501 vec3_t forward, right, up;
4502 vec3_t point;
4503 refEntity_t flash;
4504
4505 ent = ¢->currentState;
4506
4507 VectorCopy( cent->currentState.pos.trBase, point );
4508 AngleVectors( cent->currentState.apos.trBase, forward, right, up );
4509
4510 // gun 1 and 2 were switched
4511 if ( whichgun == 2 ) {
4512 VectorMA( point, 136, forward, point );
4513 VectorMA( point, 31, up, point );
4514 VectorMA( point, 22, right, point );
4515 } else if ( whichgun == 1 ) {
4516 VectorMA( point, 136, forward, point );
4517 VectorMA( point, 31, up, point );
4518 VectorMA( point, -22, right, point );
4519 } else if ( whichgun == 3 ) {
4520 VectorMA( point, 136, forward, point );
4521 VectorMA( point, 10, up, point );
4522 VectorMA( point, 22, right, point );
4523 } else if ( whichgun == 4 ) {
4524 VectorMA( point, 136, forward, point );
4525 VectorMA( point, 10, up, point );
4526 VectorMA( point, -22, right, point );
4527 }
4528
4529 trap_R_AddLightToScene( point, 200 + ( rand() & 31 ),1.0, 0.6, 0.23, 0 );
4530
4531 memset( &flash, 0, sizeof( flash ) );
4532 flash.renderfx = RF_LIGHTING_ORIGIN;
4533 flash.hModel = cgs.media.mg42muzzleflash;
4534
4535 VectorCopy( point, flash.origin );
4536 AnglesToAxis( cg.refdefViewAngles, flash.axis );
4537
4538 trap_R_AddRefEntityToScene( &flash );
4539
4540 trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, hflakWeaponSnd );
4541 }
4542
4543
4544 //----(SA)
4545 /*
4546 ==============
4547 CG_MortarEFX
4548 Right now mostly copied directly from Raf's MG42 FX, but with the optional addtion of smoke
4549 ==============
4550 */
CG_MortarEFX(centity_t * cent)4551 void CG_MortarEFX( centity_t *cent ) {
4552 refEntity_t flash;
4553
4554 if ( cent->currentState.density & 1 ) {
4555 // smoke
4556 CG_ParticleImpactSmokePuff( cgs.media.smokePuffShader, cent->currentState.origin );
4557 }
4558
4559 if ( cent->currentState.density & 2 ) {
4560 // light
4561 trap_R_AddLightToScene( cent->currentState.origin, 200 + ( rand() & 31 ), 1.0, 1.0, 1.0, 0 );
4562
4563 // muzzle flash
4564 memset( &flash, 0, sizeof( flash ) );
4565 flash.renderfx = RF_LIGHTING_ORIGIN;
4566 flash.hModel = cgs.media.mg42muzzleflash;
4567 VectorCopy( cent->currentState.origin, flash.origin );
4568 AnglesToAxis( cg.refdefViewAngles, flash.axis );
4569 trap_R_AddRefEntityToScene( &flash );
4570 }
4571 }
4572
4573 //----(SA) end
4574
4575
4576 // RF
4577 /*
4578 ==============
4579 CG_WeaponFireRecoil
4580 ==============
4581 */
CG_WeaponFireRecoil(int weapon)4582 void CG_WeaponFireRecoil( int weapon ) {
4583 // const vec3_t maxKickAngles = {25, 30, 25};
4584 float pitchRecoilAdd, pitchAdd;
4585 float yawRandom;
4586 vec3_t recoil;
4587 //
4588 pitchRecoilAdd = 0;
4589 pitchAdd = 0;
4590 yawRandom = 0;
4591 //
4592 switch ( weapon ) {
4593 case WP_LUGER:
4594 case WP_SILENCER:
4595 case WP_COLT:
4596 case WP_AKIMBO: //----(SA) added
4597 //pitchAdd = 2+rand()%3;
4598 //yawRandom = 2;
4599 break;
4600 case WP_MAUSER:
4601 case WP_GARAND:
4602 //pitchAdd = 4+rand()%3;
4603 //yawRandom = 4;
4604 pitchAdd = 2; //----(SA) for DM
4605 yawRandom = 1; //----(SA) for DM
4606 break;
4607 case WP_SNIPERRIFLE:
4608 case WP_SNOOPERSCOPE:
4609 pitchAdd = 0.6;
4610 break;
4611 case WP_FG42SCOPE:
4612 case WP_FG42:
4613 case WP_MP40:
4614 case WP_THOMPSON:
4615 case WP_STEN:
4616 //pitchRecoilAdd = 1;
4617 pitchAdd = 1 + rand() % 3;
4618 yawRandom = 2;
4619
4620
4621 pitchAdd *= 0.3;
4622 yawRandom *= 0.3;
4623 break;
4624 case WP_PANZERFAUST:
4625 //pitchAdd = 12+rand()%3;
4626 //yawRandom = 6;
4627
4628 CG_StartShakeCamera( 0.05, 700, cg.snap->ps.origin, 100 );
4629
4630 // push the player back instead
4631 break;
4632 case WP_VENOM:
4633 pitchRecoilAdd = pow( random(),8 ) * ( 10 + VectorLength( cg.snap->ps.velocity ) / 5 );
4634 pitchAdd = ( rand() % 5 ) - 2;
4635 yawRandom = 2;
4636
4637
4638 pitchRecoilAdd *= 0.5;
4639 pitchAdd *= 0.5;
4640 yawRandom *= 0.5;
4641 break;
4642 default:
4643 return;
4644 }
4645 // calc the recoil
4646 recoil[YAW] = crandom() * yawRandom;
4647 recoil[ROLL] = -recoil[YAW]; // why not
4648 recoil[PITCH] = -pitchAdd;
4649 // scale it up a bit (easier to modify this while tweaking)
4650 VectorScale( recoil, 30, recoil );
4651 // set the recoil
4652 VectorCopy( recoil, cg.kickAVel );
4653 // set the recoil
4654 cg.recoilPitch -= pitchRecoilAdd;
4655 }
4656
4657
4658 /*
4659 ================
4660 CG_FireWeapon
4661
4662 Caused by an EV_FIRE_WEAPON event
4663
4664 ================
4665 */
CG_FireWeapon(centity_t * cent)4666 void CG_FireWeapon( centity_t *cent ) {
4667 entityState_t *ent;
4668 int c;
4669 weaponInfo_t *weap;
4670 sfxHandle_t *firesound;
4671 sfxHandle_t *fireEchosound;
4672
4673 ent = ¢->currentState;
4674
4675 // Rafael - mg42
4676 if ( ( cent->currentState.clientNum == cg.snap->ps.clientNum && cg.snap->ps.persistant[PERS_HWEAPON_USE] ) ||
4677 ( cent->currentState.clientNum != cg.snap->ps.clientNum && ( cent->currentState.eFlags & EF_MG42_ACTIVE ) ) ) {
4678 if ( cg.snap->ps.gunfx ) {
4679 return;
4680 }
4681
4682 trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, hWeaponSnd );
4683 //trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, hWeaponSnd );
4684 if ( cg_brassTime.integer > 0 ) {
4685 CG_MachineGunEjectBrass( cent );
4686 }
4687
4688 // CG_MG42EFX (cent);
4689
4690 return;
4691 }
4692
4693 if ( ent->weapon == WP_NONE ) {
4694 return;
4695 }
4696 if ( ent->weapon >= WP_NUM_WEAPONS ) {
4697 CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
4698 return;
4699 }
4700 weap = &cg_weapons[ ent->weapon ];
4701
4702 cg.lastFiredWeapon = ent->weapon; //----(SA) added
4703
4704 // mark the entity as muzzle flashing, so when it is added it will
4705 // append the flash to the weapon model
4706 cent->muzzleFlashTime = cg.time;
4707
4708 // RF, kick angles
4709 if ( ent->number == cg.snap->ps.clientNum ) {
4710 CG_WeaponFireRecoil( ent->weapon );
4711 }
4712
4713 // lightning gun only does this this on initial press
4714 if ( ent->weapon == WP_FLAMETHROWER ) {
4715 if ( cent->pe.lightningFiring ) {
4716 return;
4717 }
4718 } else if ( ent->weapon == WP_GRENADE_LAUNCHER ||
4719 ent->weapon == WP_GRENADE_PINEAPPLE ||
4720 ent->weapon == WP_DYNAMITE ||
4721 ent->weapon == WP_GRENADE_SMOKE ) { // JPW NERVE
4722 if ( ent->weapon == WP_GRENADE_SMOKE ) {
4723 CG_Printf( "smoke grenade!\n" );
4724 }
4725 if ( ent->apos.trBase[0] > 0 ) { // underhand
4726 return;
4727 }
4728 }
4729
4730 // play quad sound if needed
4731 if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) {
4732 trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound );
4733 }
4734
4735 if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == EV_FIRE_WEAPON_LASTSHOT ) {
4736 firesound = &weap->lastShotSound[0];
4737 fireEchosound = &weap->flashEchoSound[0];
4738
4739 // try to use the lastShotSound, but don't assume it's there.
4740 // if a weapon without the sound calls it, drop back to regular fire sound
4741
4742 for ( c = 0; c < 4; c++ ) {
4743 if ( !firesound[c] ) {
4744 break;
4745 }
4746 }
4747 if ( !c ) {
4748 firesound = &weap->flashSound[0];
4749 fireEchosound = &weap->flashEchoSound[0];
4750 }
4751 } else {
4752 firesound = &weap->flashSound[0];
4753 fireEchosound = &weap->flashEchoSound[0];
4754 }
4755
4756
4757 // play a sound
4758 for ( c = 0 ; c < 4 ; c++ ) {
4759 if ( !firesound[c] ) {
4760 break;
4761 }
4762 }
4763 if ( c > 0 ) {
4764 c = rand() % c;
4765 if ( firesound[c] ) {
4766 trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, firesound[c] );
4767
4768 if ( fireEchosound && fireEchosound[c] ) { // check for echo
4769 centity_t *cent;
4770 vec3_t porg, gorg, norm; // player/gun origin
4771 float gdist;
4772
4773 cent = &cg_entities[ent->number];
4774 VectorCopy( cent->currentState.pos.trBase, gorg );
4775 VectorCopy( cg.refdef.vieworg, porg );
4776 VectorSubtract( gorg, porg, norm );
4777 gdist = VectorNormalize( norm );
4778 if ( gdist > 512 && gdist < 4096 ) { // temp dist. TODO: use numbers that are weapon specific
4779 // use gorg as the new sound origin
4780 VectorMA( cg.refdef.vieworg, 64, norm, gorg ); // sound-on-a-stick
4781 trap_S_StartSound( gorg, ent->number, CHAN_WEAPON, fireEchosound[c] );
4782 }
4783 }
4784 }
4785 }
4786
4787 // do brass ejection
4788 if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) {
4789 weap->ejectBrassFunc( cent );
4790 }
4791 }
4792
4793
4794 // Ridah
4795 /*
4796 =================
4797 CG_AddSparks
4798 =================
4799 */
CG_AddSparks(vec3_t origin,vec3_t dir,int speed,int duration,int count,float randScale)4800 void CG_AddSparks( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) {
4801 localEntity_t *le;
4802 refEntity_t *re;
4803 vec3_t velocity;
4804 int i;
4805
4806 for ( i = 0; i < count; i++ ) {
4807 le = CG_AllocLocalEntity();
4808 re = &le->refEntity;
4809
4810 VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale );
4811 VectorScale( velocity, (float)speed, velocity );
4812
4813 le->leType = LE_SPARK;
4814 le->startTime = cg.time;
4815 le->endTime = le->startTime + duration - (int)( 0.5 * random() * duration );
4816 le->lastTrailTime = cg.time;
4817
4818 VectorCopy( origin, re->origin );
4819 AxisCopy( axisDefault, re->axis );
4820
4821 le->pos.trType = TR_GRAVITY_LOW;
4822 VectorCopy( origin, le->pos.trBase );
4823 VectorMA( le->pos.trBase, 2 + random() * 4, dir, le->pos.trBase );
4824 VectorCopy( velocity, le->pos.trDelta );
4825 le->pos.trTime = cg.time;
4826
4827 le->refEntity.customShader = cgs.media.sparkParticleShader;
4828
4829 le->bounceFactor = 0.9;
4830
4831 // le->leBounceSoundType = LEBS_BLOOD;
4832 // le->leMarkType = LEMT_BLOOD;
4833 }
4834 }
4835 /*
4836 =================
4837 CG_AddBulletParticles
4838 =================
4839 */
CG_AddBulletParticles(vec3_t origin,vec3_t dir,int speed,int duration,int count,float randScale)4840 void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) {
4841 // localEntity_t *le;
4842 // refEntity_t *re;
4843 vec3_t velocity, pos;
4844 int i;
4845 /*
4846 // add the falling streaks
4847 for (i=0; i<count/3; i++) {
4848 le = CG_AllocLocalEntity();
4849 re = &le->refEntity;
4850
4851 VectorSet( velocity, dir[0] + crandom()*randScale, dir[1] + crandom()*randScale, dir[2] + crandom()*randScale );
4852 VectorScale( velocity, (float)speed*3, velocity );
4853
4854 le->leType = LE_SPARK;
4855 le->startTime = cg.time;
4856 le->endTime = le->startTime + duration - (int)(0.5 * random() * duration);
4857 le->lastTrailTime = cg.time;
4858
4859 VectorCopy( origin, re->origin );
4860 AxisCopy( axisDefault, re->axis );
4861
4862 le->pos.trType = TR_GRAVITY;
4863 VectorCopy( origin, le->pos.trBase );
4864 VectorMA( le->pos.trBase, 2 + random()*4, dir, le->pos.trBase );
4865 VectorCopy( velocity, le->pos.trDelta );
4866 le->pos.trTime = cg.time;
4867
4868 le->refEntity.customShader = cgs.media.bulletParticleTrailShader;
4869 // le->refEntity.customShader = cgs.media.sparkParticleShader;
4870
4871 le->bounceFactor = 0.9;
4872
4873 // le->leBounceSoundType = LEBS_BLOOD;
4874 // le->leMarkType = LEMT_BLOOD;
4875 }
4876 */
4877 // add the falling particles
4878 for ( i = 0; i < count; i++ ) {
4879
4880 VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale );
4881 VectorScale( velocity, (float)speed, velocity );
4882
4883 VectorCopy( origin, pos );
4884 VectorMA( pos, 2 + random() * 4, dir, pos );
4885
4886 CG_ParticleBulletDebris( pos, velocity, 300 + rand() % 300 );
4887
4888 }
4889 }
4890
4891 //----(SA) from MP
4892 /*
4893 =================
4894 CG_AddDirtBulletParticles
4895 =================
4896 */
CG_AddDirtBulletParticles(vec3_t origin,vec3_t dir,int speed,int duration,int count,float randScale,float width,float height,float alpha,char * shadername)4897 void CG_AddDirtBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale,
4898 float width, float height, float alpha, char *shadername ) { // JPW NERVE
4899 vec3_t velocity, pos;
4900 int i;
4901
4902 // add the big falling particle
4903 VectorSet( velocity, 0, 0, (float)speed );
4904
4905 VectorCopy( origin, pos );
4906
4907 // JPW NERVE
4908 CG_ParticleDirtBulletDebris_Core( pos, velocity, duration, width,height, alpha, shadername ); //600 + rand()%300 ); // keep central one
4909 for ( i = 0; i < count; i++ ) {
4910 VectorSet( velocity, dir[0] * crandom() * speed * randScale,
4911 dir[1] * crandom() * speed * randScale, dir[2] * random() * speed );
4912 CG_ParticleDirtBulletDebris_Core( pos, velocity, duration + ( rand() % ( duration >> 1 ) ), // dur * 0.5, but int
4913 width,height, alpha, shadername );
4914 }
4915 }
4916 //----(SA) end
4917
4918 /*
4919 =================
4920 CG_AddDebris
4921 =================
4922 */
CG_AddDebris(vec3_t origin,vec3_t dir,int speed,int duration,int count)4923 void CG_AddDebris( vec3_t origin, vec3_t dir, int speed, int duration, int count ) {
4924 localEntity_t *le;
4925 refEntity_t *re;
4926 vec3_t velocity, unitvel;
4927 float timeAdd;
4928 int i;
4929
4930 for ( i = 0; i < count; i++ ) {
4931 le = CG_AllocLocalEntity();
4932 re = &le->refEntity;
4933
4934 VectorSet( unitvel, dir[0] + crandom() * 0.9, dir[1] + crandom() * 0.9, fabs( dir[2] ) > 0.5 ? dir[2] * ( 0.2 + 0.8 * random() ) : random() * 0.6 );
4935 VectorScale( unitvel, (float)speed + (float)speed * 0.5 * crandom(), velocity );
4936
4937 le->leType = LE_DEBRIS;
4938 le->startTime = cg.time;
4939 le->endTime = le->startTime + duration + (int)( (float)duration * 0.8 * crandom() );
4940 le->lastTrailTime = cg.time;
4941
4942 VectorCopy( origin, re->origin );
4943 AxisCopy( axisDefault, re->axis );
4944
4945 le->pos.trType = TR_GRAVITY_LOW;
4946 VectorCopy( origin, le->pos.trBase );
4947 VectorCopy( velocity, le->pos.trDelta );
4948 le->pos.trTime = cg.time;
4949
4950 timeAdd = 10.0 + random() * 40.0;
4951 BG_EvaluateTrajectory( &le->pos, cg.time + (int)timeAdd, le->pos.trBase );
4952
4953 le->bounceFactor = 0.5;
4954
4955 // if (!rand()%2)
4956 // le->effectWidth = 0; // no flame
4957 // else
4958 le->effectWidth = 5 + random() * 5;
4959
4960 // if (rand()%3)
4961 le->effectFlags |= 1; // smoke trail
4962
4963
4964 // le->leBounceSoundType = LEBS_BLOOD;
4965 // le->leMarkType = LEMT_BLOOD;
4966 }
4967 }
4968 // done.
4969
4970
4971 /*
4972 ==============
4973 CG_WaterRipple
4974 ==============
4975 */
CG_WaterRipple(qhandle_t shader,vec3_t loc,vec3_t dir,int size,int lifetime)4976 void CG_WaterRipple( qhandle_t shader, vec3_t loc, vec3_t dir, int size, int lifetime ) {
4977 localEntity_t *le;
4978 refEntity_t *re;
4979
4980 le = CG_AllocLocalEntity();
4981 le->leType = LE_SCALE_FADE;
4982 le->leFlags = LEF_PUFF_DONT_SCALE;
4983
4984 le->startTime = cg.time;
4985 le->endTime = cg.time + lifetime;
4986 le->lifeRate = 1.0 / ( le->endTime - le->startTime );
4987
4988 re = &le->refEntity;
4989 VectorCopy( loc, re->origin );
4990 re->shaderTime = cg.time / 1000.0f;
4991 re->reType = RT_SPLASH;
4992 re->radius = size;
4993 re->customShader = shader;
4994 re->shaderRGBA[0] = 0xff;
4995 re->shaderRGBA[1] = 0xff;
4996 re->shaderRGBA[2] = 0xff;
4997 re->shaderRGBA[3] = 0xff;
4998 le->color[3] = 1.0;
4999 }
5000
5001 /*
5002 =================
5003 CG_MissileHitWall
5004
5005 Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
5006
5007 ClientNum is a dummy field used to define what sort of effect to spawn
5008 =================
5009 */
CG_MissileHitWall(int weapon,int clientNum,vec3_t origin,vec3_t dir,int surfFlags)5010 void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, int surfFlags ) { // (SA) modified to send missilehitwall surface parameters
5011 qhandle_t mod;
5012 qhandle_t mark;
5013 qhandle_t shader;
5014 sfxHandle_t sfx, sfx2;
5015 float radius;
5016 float light;
5017 vec3_t lightColor;
5018 localEntity_t *le;
5019 int r;
5020 qboolean alphaFade = qfalse;
5021 qboolean isSprite;
5022 int duration;
5023 // Ridah
5024 int lightOverdraw;
5025 vec3_t sprOrg;
5026 vec3_t sprVel = { 0 };
5027 int i,j;
5028
5029 //----(SA) added
5030 float shakeAmt;
5031 int shakeDur, shakeRad;
5032 shakeAmt = 0;
5033 shakeDur = shakeRad = 0;
5034 //----(SA) end
5035
5036 mark = 0;
5037 radius = 32;
5038 sfx = 0;
5039 sfx2 = 0;
5040 mod = 0;
5041 shader = 0;
5042 light = 0;
5043 lightColor[0] = 1;
5044 lightColor[1] = 1;
5045 lightColor[2] = 0;
5046 // Ridah
5047 lightOverdraw = 0;
5048
5049 // set defaults
5050 isSprite = qfalse;
5051 duration = 600;
5052
5053 if ( surfFlags & SURF_SKY ) {
5054 return;
5055 }
5056
5057 switch ( weapon ) {
5058 case WP_KNIFE:
5059 sfx = cgs.media.sfx_knifehit[4]; // different values for different types (stone/metal/wood/etc.)
5060 mark = cgs.media.bulletMarkShader;
5061 radius = 1 + rand() % 2;
5062
5063 CG_AddBulletParticles( origin, dir,
5064 20, // speed
5065 800, // duration
5066 3 + rand() % 6, // count
5067 1.0 ); // rand scale
5068 break;
5069
5070 case WP_LUGER:
5071 case WP_AKIMBO: //----(SA) added
5072 case WP_COLT:
5073 case WP_MAUSER:
5074 case WP_GARAND:
5075 case WP_SNIPERRIFLE:
5076 case WP_SNOOPERSCOPE:
5077 case WP_MP40:
5078 case WP_FG42:
5079 case WP_FG42SCOPE:
5080 case WP_THOMPSON:
5081 case WP_STEN:
5082 case WP_SILENCER:
5083 case WP_VENOM:
5084
5085 // actually yeah. meant that. very rare.
5086 if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { // JPW NERVE
5087 r = rand() & 31;
5088 } else {
5089 r = ( rand() & 3 ) + 1; // JPW NERVE increased spark frequency so players can tell where rounds are coming from in MP
5090
5091 }
5092 if ( r == 3 ) {
5093 sfx = cgs.media.sfx_ric1;
5094 } else if ( r == 2 ) {
5095 sfx = cgs.media.sfx_ric2;
5096 } else if ( r == 1 ) {
5097 sfx = cgs.media.sfx_ric3;
5098 }
5099
5100 // clientNum is a dummy field used to define what sort of effect to spawn
5101
5102 if ( !clientNum ) {
5103 // RF, why is this here? we need sparks if clientNum = 0, used for warzombie
5104 // if ( sfx )
5105 CG_AddSparks( origin, dir,
5106 350, // speed
5107 200, // duration
5108 15 + rand() % 7, // count
5109 0.2 ); // rand scale
5110 } else if ( clientNum == 1 ) { // just do a little smoke puff
5111 vec3_t d, o;
5112 VectorMA( origin, 12, dir, o );
5113 VectorScale( dir, 7, d );
5114 d[2] += 16;
5115
5116 // just snow
5117 if ( surfFlags & SURF_SNOW ) {
5118 CG_AddDirtBulletParticles( origin, dir,
5119 120, // speed
5120 900, // duration
5121 3, // count
5122 0.6f,
5123 20,
5124 4,
5125 0.3f,
5126 "water_splash" ); // rand scale
5127 }
5128
5129 // grass/gravel
5130 if ( surfFlags & ( SURF_GRASS | SURF_GRAVEL ) ) {
5131 CG_AddDirtBulletParticles( origin, dir,
5132 190, // speed
5133 800, // duration
5134 3, // count
5135 0.1,
5136 60,
5137 10,
5138 0.5,
5139 "dirt_splash" ); // rand scale
5140 } else {
5141 CG_ParticleImpactSmokePuff( cgs.media.smokeParticleShader, o );
5142
5143 // some debris particles
5144 CG_AddBulletParticles( origin, dir,
5145 20, // speed
5146 800, // duration
5147 3 + rand() % 6, // count
5148 1.0 ); // rand scale
5149
5150 // just do a little one
5151 if ( sfx && ( rand() % 3 == 0 ) ) {
5152 CG_AddSparks( origin, dir,
5153 450, // speed
5154 300, // duration
5155 3 + rand() % 3, // count
5156 0.5 ); // rand scale
5157 }
5158 }
5159 } else if ( clientNum == 2 ) {
5160 sfx = 0;
5161 mark = 0;
5162
5163 // (SA) needed to do the CG_WaterRipple using a localent since I needed the timer reset on the shader for each shot
5164 CG_WaterRipple( cgs.media.wakeMarkShaderAnim, origin, tv( 0, 0, 1 ), 32, 1000 );
5165
5166 CG_AddDirtBulletParticles( origin, dir,
5167 190, // speed
5168 900, // duration
5169 5, // count
5170 0.5, // rand scale
5171 50, // w
5172 12, // h
5173 0.125, // alpha
5174 "water_splash" );
5175
5176 break; // (SA) testing
5177
5178 #if 0
5179 // play a water splash
5180 mod = cgs.media.waterSplashModel;
5181 shader = cgs.media.waterSplashShader;
5182 duration = 250;
5183 #endif
5184 }
5185
5186 // Ridah, optimization, only spawn the bullet hole if we are close
5187 // enough to see it, this way we can leave other marks around a lot
5188 // longer, since most of the time we can't actually see the bullet holes
5189 // (SA) small modification. only do this for non-rifles (so you can see your shots hitting when you're zooming with a rifle scope)
5190 if ( weapon == WP_FG42SCOPE || weapon == WP_SNIPERRIFLE || weapon == WP_SNOOPERSCOPE || ( Distance( cg.refdef.vieworg, origin ) < 384 ) ) {
5191
5192 if ( clientNum ) {
5193
5194 // mark and sound can potentially use the surface for override values
5195
5196 mark = cgs.media.bulletMarkShader; // default
5197 alphaFade = qtrue; // max made the bullet mark alpha (he'll make everything in the game out of 1024 textures, all with alpha blend funcs yet...)
5198 radius = 1.5f + rand() % 2; // slightly larger for DM
5199
5200 if ( surfFlags & SURF_METAL ) {
5201 sfx = cgs.media.sfx_bullet_metalhit[rand() % 3];
5202 mark = cgs.media.bulletMarkShaderMetal;
5203 alphaFade = qtrue;
5204 } else if ( surfFlags & SURF_WOOD ) {
5205 sfx = cgs.media.sfx_bullet_woodhit[rand() % 3];
5206 mark = cgs.media.bulletMarkShaderWood;
5207 alphaFade = qtrue;
5208 radius += 1; // experimenting with different mark sizes per surface
5209
5210 /*
5211 if (rand()%100 > 75)
5212 {
5213 gentity_t *sfx;
5214 vec3_t start;
5215 vec3_t dir;
5216
5217 sfx = G_Spawn ();
5218
5219 sfx->s.density = type;
5220
5221 VectorCopy (tr.endpos, start);
5222
5223 VectorCopy (muzzleTrace, dir);
5224 VectorNegate (dir, dir);
5225
5226 G_SetOrigin (sfx, start);
5227 G_SetAngle (sfx, dir);
5228
5229 G_AddEvent( sfx, EV_SHARD, DirToByte( dir ));
5230
5231 sfx->think = G_FreeEntity;
5232 sfx->nextthink = level.time + 1000;
5233
5234 sfx->s.frame = 3 + (rand()%3) ;
5235
5236 trap_LinkEntity (sfx);
5237
5238
5239 void CG_Shard(centity_t *cent, vec3_t origin, vec3_t dir)
5240 CG_Shard
5241
5242 }
5243
5244 */
5245
5246
5247 } else if ( surfFlags & SURF_CERAMIC ) {
5248 sfx = cgs.media.sfx_bullet_ceramichit[rand() % 3];
5249 mark = cgs.media.bulletMarkShaderCeramic;
5250 alphaFade = qtrue;
5251 radius += 2; // experimenting with different mark sizes per surface
5252
5253 } else if ( surfFlags & SURF_GLASS ) {
5254 sfx = cgs.media.sfx_bullet_glasshit[rand() % 3];
5255 mark = cgs.media.bulletMarkShaderGlass;
5256 alphaFade = qtrue;
5257 } else if ( surfFlags & SURF_GRASS ) {
5258
5259 } else if ( surfFlags & SURF_GRAVEL ) {
5260
5261 } else if ( surfFlags & SURF_SNOW ) {
5262
5263 } else if ( surfFlags & SURF_ROOF ) {
5264
5265 } else if ( surfFlags & SURF_CARPET ) {
5266
5267 }
5268
5269 }
5270 }
5271 break;
5272
5273
5274 case WP_MORTAR:
5275 sfx = cgs.media.sfx_rockexp;
5276 mark = cgs.media.burnMarkShader;
5277 radius = 64;
5278 light = 300;
5279 isSprite = qtrue;
5280 duration = 1000;
5281 lightColor[0] = 0.75;
5282 lightColor[1] = 0.5;
5283 lightColor[2] = 0.1;
5284
5285 shakeAmt = 0.15f;
5286 shakeDur = 600;
5287 shakeRad = 700;
5288
5289 VectorScale( dir, 16, sprVel );
5290 for ( i = 0; i < 5; i++ ) {
5291 for ( j = 0; j < 3; j++ )
5292 sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom();
5293 sprVel[2] += rand() % 50;
5294 // CG_ParticleExplosion( 2, sprOrg, sprVel, 1000+rand()%250, 20, 40+rand()%60 );
5295 CG_ParticleExplosion( "blacksmokeanimb", sprOrg, sprVel, 3500 + rand() % 250, 10, 250 + rand() % 60 );
5296 }
5297
5298 VectorMA( origin, 24, dir, sprOrg );
5299 VectorScale( dir, 64, sprVel );
5300 // RF, I like this new animation, feel free to revert
5301 CG_ParticleExplosion( "expblue", sprOrg, sprVel, 1000, 20, 300 );
5302 // CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1200, 9, 300 );
5303 break;
5304
5305 case WP_DYNAMITE:
5306 shader = cgs.media.rocketExplosionShader;
5307 sfx = cgs.media.sfx_dynamiteexp;
5308 sfx2 = cgs.media.sfx_dynamiteexpDist;
5309 mark = cgs.media.burnMarkShader;
5310 radius = 64;
5311 light = 300;
5312 isSprite = qtrue;
5313 duration = 1000;
5314 lightColor[0] = 0.75;
5315 lightColor[1] = 0.5;
5316 lightColor[2] = 0.1;
5317
5318 shakeAmt = 0.25f;
5319 shakeDur = 2800;
5320 shakeRad = 8192;
5321
5322 if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { // JPW NERVE
5323 for ( i = 0; i < 5; i++ ) {
5324 for ( j = 0; j < 3; j++ )
5325 sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom();
5326 sprVel[2] += rand() % 50;
5327 CG_ParticleExplosion( "blacksmokeanimb", sprOrg, sprVel,
5328 3500 + rand() % 250, // duration
5329 10, // startsize
5330 250 + rand() % 60 ); // endsize
5331 }
5332
5333 VectorMA( origin, 16, dir, sprOrg );
5334 VectorScale( dir, 100, sprVel );
5335
5336 // trying this one just for now just for variety
5337 CG_ParticleExplosion( "explode1", sprOrg, sprVel,
5338 1200, // duration
5339 9, // startsize
5340 300 ); // endsize
5341
5342 CG_AddDebris( origin, dir,
5343 280, // speed
5344 1400, // duration
5345 7 + rand() % 2 ); // count
5346 }
5347 // JPW NERVE
5348 else {
5349 for ( i = 0; i < 5; i++ ) {
5350 for ( j = 0; j < 2; j++ )
5351 sprOrg[j] = origin[j] + 96 * crandom();
5352 sprOrg[2] = origin[2] + 32 * random();
5353 sprVel[2] += rand() % 75;
5354 CG_ParticleExplosion( "blacksmokeanimb", sprOrg, sprVel,
5355 3500 + rand() % 250, // duration
5356 10, // startsize
5357 250 + rand() % 400 ); // endsize
5358 }
5359
5360 VectorMA( origin, 16, dir, sprOrg );
5361 VectorScale( dir, 100, sprVel );
5362
5363 // trying this one just for now just for variety
5364 CG_ParticleExplosion( "explode1", sprOrg, sprVel,
5365 1200, // duration
5366 9, // startsize
5367 250 + rand() % 400 ); // endsize
5368
5369 CG_AddDebris( origin, dir,
5370 400 + random() * 300, // speed
5371 1400, // duration
5372 7 + rand() % 12 ); // count
5373 }
5374 // jpw
5375 break;
5376
5377 case WP_GRENADE_SMOKE: // JPW NERVE
5378 case WP_GRENADE_LAUNCHER:
5379 case WP_GRENADE_PINEAPPLE:
5380 // mod = cgs.media.dishFlashModel;
5381 // shader = cgs.media.grenadeExplosionShader;
5382 shader = cgs.media.rocketExplosionShader; // copied from RL
5383 sfx = cgs.media.sfx_rockexp;
5384 mark = cgs.media.burnMarkShader;
5385 radius = 64;
5386 light = 300;
5387 isSprite = qtrue;
5388 duration = 1000;
5389 lightColor[0] = 0.75;
5390 lightColor[1] = 0.5;
5391 lightColor[2] = 0.1;
5392
5393 if ( weapon != WP_GRENADE_SMOKE ) {
5394 shakeAmt = 0.15f;
5395 shakeDur = 1000;
5396 shakeRad = 600;
5397 }
5398
5399 // Ridah, explosion sprite animation
5400 VectorMA( origin, 16, dir, sprOrg );
5401 VectorScale( dir, 100, sprVel );
5402
5403 // RF, testing new explosion animation
5404 CG_ParticleExplosion( "expblue", sprOrg, sprVel, 700, 20, 160 );
5405 //CG_ParticleExplosion( "twiltb", sprOrg, sprVel, 600, 9, 100 );
5406 //CG_ParticleExplosion( 3, sprOrg, sprVel, 900, 9, 250 );
5407 /*
5408 r = 2 + rand()%3;
5409 for (i=0; i<3; i++) {
5410 for (j=0;j<3;j++) sprOrg[j] = origin[j] + 14*dir[j] + 14*crandom();
5411 CG_ParticleExplosion( 3, sprOrg, sprVel, 800+rand()%250, 9, 60+rand()%200 );
5412 }
5413 */
5414 // Ridah, throw some debris
5415 CG_AddDebris( origin, dir,
5416 280, // speed
5417 1400, // duration
5418 // 15 + rand()%5 ); // count
5419 7 + rand() % 2 ); // count
5420
5421 break;
5422 case VERYBIGEXPLOSION:
5423 case WP_PANZERFAUST:
5424 // mod = cgs.media.dishFlashModel;
5425 // shader = cgs.media.rocketExplosionShader;
5426 sfx = cgs.media.sfx_rockexp;
5427 mark = cgs.media.burnMarkShader;
5428 radius = 64;
5429 light = 600;
5430 isSprite = qtrue;
5431 duration = 1000;
5432 // Ridah, changed to flamethrower colors
5433 // lightColor[0] = 1;
5434 // lightColor[1] = 1;//0.75;
5435 // lightColor[2] = 0.6;//0.0;
5436 lightColor[0] = 0.75;
5437 lightColor[1] = 0.5;
5438 lightColor[2] = 0.1;
5439
5440 // explosion sprite animation
5441 VectorMA( origin, 24, dir, sprOrg );
5442 VectorScale( dir, 64, sprVel );
5443
5444 // cam shake
5445 if ( weapon == VERYBIGEXPLOSION ) {
5446 shakeAmt = 0.2f;
5447 shakeDur = 1000;
5448 shakeRad = 1200;
5449 } else {
5450 shakeAmt = 0.15f;
5451 shakeDur = 800;
5452 shakeRad = 1000;
5453 }
5454
5455 // JPW NERVE
5456 if ( cg_gameType.integer == GT_SINGLE_PLAYER ) {
5457 if ( weapon == VERYBIGEXPLOSION ) {
5458 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1200, 20, 300 );
5459 } else {
5460 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 40, 70 );
5461 }
5462
5463 // NOTE: these must all have the same duration, so that we are less likely to use a wider range of images per scene
5464 for ( i = 0; i < 4; i++ ) {
5465 if ( weapon == VERYBIGEXPLOSION ) {
5466 for ( j = 0; j < 3; j++ ) sprOrg[j] = origin[j] + 32 * dir[j] + 32 * crandom();
5467 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1200, 40, 160 + rand() % 120 );
5468 } else if ( i < 2 ) {
5469 for ( j = 0; j < 3; j++ ) sprOrg[j] = origin[j] + 24 * dir[j] + 16 * crandom();
5470 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 15, 40 + rand() % 30 );
5471 }
5472 }
5473
5474 // Ridah, throw some debris
5475 CG_AddDebris( origin, dir,
5476 120, // speed
5477 2000, //350, // duration
5478 // 15 + rand()%5 ); // count
5479 7 + rand() % 2 ); // count
5480 }
5481 // JPW NERVE -- multiplayer explosions over the top due to large damage radiusesesizes
5482 else {
5483 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1600, 20, 200 + random() * 400 );
5484
5485 // NOTE: these must all have the same duration, so that we are less likely to use a wider range of images per scene
5486 for ( i = 0; i < 4; i++ ) {
5487 for ( j = 0; j < 3; j++ ) sprOrg[j] = origin[j] + 160 * crandom();
5488 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1600, 40, 260 + rand() % 120 );
5489 }
5490
5491 CG_AddDebris( origin, dir,
5492 400 + random() * 200, // speed
5493 rand() % 2000 + 1000, //350, // duration
5494 // 15 + rand()%5 ); // count
5495 5 + rand() % 5 ); // count
5496
5497 }
5498 // jpw
5499 /*
5500 // some sparks
5501 CG_AddSparks( origin, dir,
5502 200, // speed
5503 800, // duration
5504 5 + rand()%10, // count
5505 0.8 ); // rand scale
5506 */
5507 // done.
5508
5509 break;
5510
5511 default:
5512 case WP_FLAMETHROWER:
5513 // no explosion at LG impact, it is added with the beam
5514 return;
5515
5516 break;
5517
5518 }
5519 // done.
5520
5521 if ( sfx ) {
5522 trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
5523 }
5524
5525 //----(SA) added
5526 if ( sfx2 ) { // distant sounds for weapons with a broadcast fire sound (so you /always/ hear dynamite explosions)
5527 trap_S_StartLocalSound( sfx2, CHAN_AUTO );
5528 }
5529 //----(SA) end
5530
5531
5532 //
5533 // camera shake
5534 //
5535 if ( shakeAmt ) {
5536 CG_StartShakeCamera( shakeAmt, shakeDur, origin, shakeRad );
5537 }
5538
5539
5540 //
5541 // create the explosion
5542 //
5543 if ( mod ) {
5544 le = CG_MakeExplosion( origin, dir,
5545 mod, shader,
5546 duration, isSprite );
5547 le->light = light;
5548 // Ridah
5549 le->lightOverdraw = lightOverdraw;
5550 VectorCopy( lightColor, le->lightColor );
5551 }
5552
5553 //
5554 // impact mark
5555 //
5556 if ( mark ) {
5557 CG_ImpactMark( mark, origin, dir, random() * 360, 1,1,1,1, alphaFade, radius, qfalse, -1 );
5558 }
5559 }
5560
5561 /*
5562 ==============
5563 CG_MissileHitWallSmall
5564 ==============
5565 */
CG_MissileHitWallSmall(int weapon,int clientNum,vec3_t origin,vec3_t dir)5566 void CG_MissileHitWallSmall( int weapon, int clientNum, vec3_t origin, vec3_t dir ) {
5567 qhandle_t mod;
5568 qhandle_t mark;
5569 qhandle_t shader;
5570 sfxHandle_t sfx;
5571 float radius;
5572 float light;
5573 vec3_t lightColor;
5574 localEntity_t *le;
5575 // int r;
5576 qboolean alphaFade;
5577 qboolean isSprite;
5578 int duration;
5579 // Ridah
5580 int lightOverdraw;
5581 vec3_t sprOrg;
5582 vec3_t sprVel;
5583 // int i,j;
5584
5585 mod = 0;
5586 lightColor[0] = 1;
5587 lightColor[1] = 1;
5588 lightColor[2] = 0;
5589 // Ridah
5590 lightOverdraw = 0;
5591
5592 // set defaults
5593 shader = cgs.media.rocketExplosionShader; // copied from RL
5594 sfx = cgs.media.sfx_rockexp;
5595 mark = cgs.media.burnMarkShader;
5596 radius = 64;
5597 light = 300;
5598 isSprite = qtrue;
5599 duration = 1000;
5600 lightColor[0] = 0.75;
5601 lightColor[1] = 0.5;
5602 lightColor[2] = 0.1;
5603
5604 // Ridah, explosion sprite animation
5605 VectorMA( origin, 16, dir, sprOrg );
5606 VectorScale( dir, 64, sprVel );
5607
5608 CG_ParticleExplosion( "explode1", sprOrg, sprVel, 600, 6, 50 );
5609
5610 // Ridah, throw some debris
5611 CG_AddDebris( origin, dir,
5612 280, // speed
5613 1400, // duration
5614 // 15 + rand()%5 ); // count
5615 7 + rand() % 2 ); // count
5616
5617 if ( sfx ) {
5618 trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
5619 }
5620
5621 //
5622 // create the explosion
5623 //
5624 if ( mod ) {
5625 le = CG_MakeExplosion( origin, dir,
5626 mod, shader,
5627 duration, isSprite );
5628 le->light = light;
5629 // Ridah
5630 le->lightOverdraw = lightOverdraw;
5631 VectorCopy( lightColor, le->lightColor );
5632 }
5633
5634 //
5635 // impact mark
5636 //
5637 alphaFade = ( mark == cgs.media.energyMarkShader ); // plasma fades alpha, all others fade color
5638 // CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse, 60000 );
5639 CG_ImpactMark( mark, origin, dir, random() * 360, 1,1,1,1, alphaFade, radius, qfalse, INT_MAX );
5640
5641 CG_StartShakeCamera( 0.05, 300, origin, 300 );
5642 }
5643
5644 /*
5645 =================
5646 CG_MissileHitPlayer
5647 =================
5648 */
CG_MissileHitPlayer(centity_t * cent,int weapon,vec3_t origin,vec3_t dir,int entityNum)5649 void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir, int entityNum ) {
5650 int i;
5651
5652 CG_Bleed( origin, entityNum );
5653
5654 // some weapons will make an explosion with the blood, while
5655 // others will just make the blood
5656 switch ( weapon ) {
5657 // knives just make the flesh hit sound. no other effects
5658 case WP_KNIFE:
5659 i = rand() % 4;
5660 if ( cgs.media.sfx_knifehit[i] ) {
5661 trap_S_StartSound( origin, cent->currentState.number, CHAN_WEAPON, cgs.media.sfx_knifehit[i] );
5662 }
5663
5664 if ( cent->currentState.number == cg.snap->ps.clientNum ) {
5665 CG_StartShakeCamera( 0.03, 500, origin, 100 );
5666 }
5667 break;
5668
5669 case WP_GRENADE_LAUNCHER:
5670 case WP_PANZERFAUST:
5671 // this shake is /on top/ of the shake from the impact (done in CG_MissileHitWall)
5672 CG_StartShakeCamera( 0.1, 500, origin, 100 );
5673 CG_MissileHitWall( weapon, 0, origin, dir, 0 ); // (SA) modified to send missilehitwall surface parameters
5674 break;
5675 default:
5676 break;
5677 }
5678 }
5679
5680
5681
5682 /*
5683 ============================================================================
5684
5685 VENOM GUN TRACING
5686
5687 ============================================================================
5688 */
5689
5690 /*
5691 ================
5692 CG_VenomPellet
5693 ================
5694 */
CG_VenomPellet(vec3_t start,vec3_t end,int skipNum)5695 static void CG_VenomPellet( vec3_t start, vec3_t end, int skipNum ) {
5696 }
5697
5698
5699 //----(SA) all changes to venom below should be mine
5700 #define DEFAULT_VENOM_COUNT 10
5701 //#define DEFAULT_VENOM_SPREAD 20
5702 //#define DEFAULT_VENOM_SPREAD 400
5703 #define DEFAULT_VENOM_SPREAD 700
5704
5705 /*
5706 ================
5707 CG_VenomPattern
5708
5709 Perform the same traces the server did to locate the
5710 hit splashes (FIXME: random seed isn't synced anymore)
5711
5712 (SA) right now this is random like a shotgun. I want to make it more
5713 organized so that the pattern is more of a circle (with some degree of randomness)
5714 ================
5715 */
CG_VenomPattern(vec3_t origin,vec3_t origin2,int otherEntNum)5716 static void CG_VenomPattern( vec3_t origin, vec3_t origin2, int otherEntNum ) {
5717 int i;
5718 float r, u;
5719 vec3_t end;
5720 vec3_t forward, right, up;
5721
5722 // derive the right and up vectors from the forward vector, because
5723 // the client won't have any other information
5724 VectorNormalize2( origin2, forward );
5725 PerpendicularVector( right, forward );
5726 CrossProduct( forward, right, up );
5727
5728 // generate the "random" spread pattern
5729 for ( i = 0 ; i < DEFAULT_VENOM_COUNT ; i++ ) {
5730 r = crandom() * DEFAULT_VENOM_SPREAD;
5731 u = crandom() * DEFAULT_VENOM_SPREAD;
5732 VectorMA( origin, 8192, forward, end );
5733 VectorMA( end, r, right, end );
5734 VectorMA( end, u, up, end );
5735
5736 CG_VenomPellet( origin, end, otherEntNum );
5737 }
5738 }
5739
5740 /*
5741 ==============
5742 CG_VenomFire
5743 ==============
5744 */
CG_VenomFire(entityState_t * es,qboolean fullmode)5745 void CG_VenomFire( entityState_t *es, qboolean fullmode ) {
5746 vec3_t v;
5747 int contents;
5748
5749 VectorSubtract( es->origin2, es->pos.trBase, v );
5750 VectorNormalize( v );
5751 VectorScale( v, 32, v );
5752 VectorAdd( es->pos.trBase, v, v );
5753 if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) {
5754 // ragepro can't alpha fade, so don't even bother with smoke
5755 vec3_t up;
5756
5757 contents = CG_PointContents( es->pos.trBase, 0 );
5758 if ( !( contents & CONTENTS_WATER ) ) {
5759 VectorSet( up, 0, 0, 32 );
5760 if ( fullmode ) {
5761 CG_SmokePuff( v, up, 24, 1, 1, 1, 0.33, 1200, cg.time, 0, 0, cgs.media.shotgunSmokePuffShader ); // LEF_PUFF_DONT_SCALE
5762 }
5763 //----(SA) for the time being don't do the single shot smoke as it's position is funky
5764 // else
5765 // CG_SmokePuff( v, up, 4, 1, 1, 1, 0.33, 700, cg.time, 0, cgs.media.shotgunSmokePuffShader );
5766 }
5767 }
5768 if ( fullmode ) {
5769 CG_VenomPattern( es->pos.trBase, es->origin2, es->otherEntityNum );
5770 }
5771 }
5772
5773 /*
5774 ============================================================================
5775
5776 BULLETS
5777
5778 ============================================================================
5779 */
5780
5781 /*
5782 ===============
5783 CG_SpawnTracer
5784 ===============
5785 */
CG_SpawnTracer(int sourceEnt,vec3_t pstart,vec3_t pend)5786 void CG_SpawnTracer( int sourceEnt, vec3_t pstart, vec3_t pend ) {
5787 localEntity_t *le;
5788 float dist;
5789 vec3_t dir, ofs;
5790 orientation_t or;
5791 vec3_t start, end;
5792
5793 VectorCopy( pstart, start );
5794 VectorCopy( pend, end );
5795
5796 VectorSubtract( end, start, dir );
5797 dist = VectorNormalize( dir );
5798
5799 if ( dist < 2.0 * cg_tracerLength.value ) {
5800 return; // segment isnt long enough, dont bother
5801
5802 }
5803 if ( sourceEnt < cgs.maxclients ) {
5804 // for visual purposes, find the actual tag_weapon for this client
5805 // and offset the start and end accordingly
5806 if ( cg_entities[sourceEnt].currentState.eFlags & EF_MG42_ACTIVE ) { // mounted
5807 start[2] -= 32; // (SA) hack to get the tracer down below the barrel FIXME: do properly
5808 } else {
5809 if ( CG_GetWeaponTag( sourceEnt, "tag_flash", &or ) ) {
5810 VectorSubtract( or.origin, start, ofs );
5811 if ( VectorLength( ofs ) < 64 ) {
5812 VectorAdd( start, ofs, start );
5813 }
5814 }
5815 }
5816 }
5817
5818 // subtract the length of the tracer from the end point, so we dont go through the end point
5819 VectorMA( end, -cg_tracerLength.value, dir, end );
5820 dist = VectorDistance( start, end );
5821
5822 le = CG_AllocLocalEntity();
5823 le->leType = LE_MOVING_TRACER;
5824 le->startTime = cg.time - ( cg.frametime ? ( rand() % cg.frametime ) / 2 : 0 );
5825 le->endTime = le->startTime + 1000.0 * dist / cg_tracerSpeed.value;
5826
5827 le->pos.trType = TR_LINEAR;
5828 le->pos.trTime = le->startTime;
5829 VectorCopy( start, le->pos.trBase );
5830 VectorScale( dir, cg_tracerSpeed.value, le->pos.trDelta );
5831 }
5832
5833 /*
5834 ===============
5835 CG_DrawTracer
5836 ===============
5837 */
CG_DrawTracer(vec3_t start,vec3_t finish)5838 void CG_DrawTracer( vec3_t start, vec3_t finish ) {
5839 vec3_t forward, right;
5840 polyVert_t verts[4];
5841 vec3_t line;
5842
5843 VectorSubtract( finish, start, forward );
5844
5845 line[0] = DotProduct( forward, cg.refdef.viewaxis[1] );
5846 line[1] = DotProduct( forward, cg.refdef.viewaxis[2] );
5847
5848 VectorScale( cg.refdef.viewaxis[1], line[1], right );
5849 VectorMA( right, -line[0], cg.refdef.viewaxis[2], right );
5850 VectorNormalize( right );
5851
5852 VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz );
5853 verts[0].st[0] = 1;
5854 verts[0].st[1] = 1;
5855 verts[0].modulate[0] = 255;
5856 verts[0].modulate[1] = 255;
5857 verts[0].modulate[2] = 255;
5858 verts[0].modulate[3] = 255;
5859
5860 VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz );
5861 verts[1].st[0] = 1;
5862 verts[1].st[1] = 0;
5863 verts[1].modulate[0] = 255;
5864 verts[1].modulate[1] = 255;
5865 verts[1].modulate[2] = 255;
5866 verts[1].modulate[3] = 255;
5867
5868 VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz );
5869 verts[2].st[0] = 0;
5870 verts[2].st[1] = 0;
5871 verts[2].modulate[0] = 255;
5872 verts[2].modulate[1] = 255;
5873 verts[2].modulate[2] = 255;
5874 verts[2].modulate[3] = 255;
5875
5876 VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz );
5877 verts[3].st[0] = 0;
5878 verts[3].st[1] = 1;
5879 verts[3].modulate[0] = 255;
5880 verts[3].modulate[1] = 255;
5881 verts[3].modulate[2] = 255;
5882 verts[3].modulate[3] = 255;
5883
5884 trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts );
5885 }
5886
5887 /*
5888 ===============
5889 CG_Tracer
5890 ===============
5891 */
CG_Tracer(vec3_t source,vec3_t dest,int sparks)5892 void CG_Tracer( vec3_t source, vec3_t dest, int sparks ) {
5893 float len, begin, end;
5894 vec3_t start, finish;
5895 vec3_t forward;
5896
5897 // tracer
5898 VectorSubtract( dest, source, forward );
5899 len = VectorNormalize( forward );
5900
5901 // start at least a little ways from the muzzle
5902 if ( len < 100 && !sparks ) {
5903 return;
5904 }
5905 begin = 50 + random() * ( len - 60 );
5906 end = begin + cg_tracerLength.value;
5907 if ( end > len ) {
5908 end = len;
5909 }
5910 VectorMA( source, begin, forward, start );
5911 VectorMA( source, end, forward, finish );
5912
5913 CG_DrawTracer( start, finish );
5914 }
5915
5916
5917 /*
5918 ======================
5919 CG_CalcMuzzlePoint
5920 ======================
5921 */
CG_CalcMuzzlePoint(int entityNum,vec3_t muzzle)5922 static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) {
5923 vec3_t forward, right, up;
5924 centity_t *cent;
5925
5926 if ( entityNum == cg.snap->ps.clientNum ) {
5927 VectorCopy( cg.snap->ps.origin, muzzle );
5928 muzzle[2] += cg.snap->ps.viewheight;
5929 AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL );
5930 VectorMA( muzzle, 14, forward, muzzle );
5931 return qtrue;
5932 }
5933
5934 cent = &cg_entities[entityNum];
5935
5936 VectorCopy( cent->currentState.pos.trBase, muzzle );
5937
5938 AngleVectors( cent->currentState.apos.trBase, forward, right, up );
5939 muzzle[2] += DEFAULT_VIEWHEIGHT;
5940
5941 VectorMA( muzzle, 14, forward, muzzle );
5942
5943 return qtrue;
5944 }
5945
5946 /*
5947 ======================
5948 CG_Bullet
5949
5950 Renders bullet effects.
5951 ======================
5952 */
CG_Bullet(vec3_t end,int sourceEntityNum,vec3_t normal,qboolean flesh,int fleshEntityNum,qboolean wolfkick,int otherEntNum2)5953 void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, qboolean wolfkick, int otherEntNum2 ) {
5954 trace_t trace;
5955 int sourceContentType = 0, destContentType = 0; // TTimo: init
5956 vec3_t dir;
5957 vec3_t start, trend, tmp; // JPW
5958 static int lastBloodSpat;
5959
5960 // if the shooter is currently valid, calc a source point and possibly
5961 // do trail effects
5962 if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) {
5963 if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) {
5964 sourceContentType = CG_PointContents( start, 0 );
5965 destContentType = CG_PointContents( end, 0 );
5966
5967 // do a complete bubble trail if necessary
5968 if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) {
5969 CG_BubbleTrail( start, end, .5, 8 );
5970 }
5971 // bubble trail from water into air
5972 else if ( ( sourceContentType & CONTENTS_WATER ) ) {
5973 trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER );
5974 CG_BubbleTrail( start, trace.endpos, .5, 8 );
5975 }
5976 // bubble trail from air into water
5977 else if ( ( destContentType & CONTENTS_WATER ) ) {
5978 // only add bubbles if effect is close to viewer
5979 if ( Distance( cg.snap->ps.origin, end ) < 1024 ) {
5980 trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER );
5981 CG_BubbleTrail( end, trace.endpos, .5, 8 );
5982 }
5983 }
5984
5985 // if not flesh, then do a moving tracer
5986 if ( flesh ) {
5987 // draw a tracer
5988 if ( !wolfkick && random() < cg_tracerChance.value ) {
5989 CG_Tracer( start, end, 0 );
5990 }
5991 } else { // (not flesh)
5992 if ( otherEntNum2 >= 0 && otherEntNum2 != ENTITYNUM_NONE ) {
5993 CG_SpawnTracer( otherEntNum2, start, end );
5994 } else {
5995 CG_SpawnTracer( sourceEntityNum, start, end );
5996 }
5997 }
5998 }
5999 }
6000
6001 // impact splash and mark
6002 if ( flesh ) {
6003 vec3_t origin;
6004 int aiType;
6005
6006 aiType = cg_entities[fleshEntityNum].currentState.aiChar;
6007
6008 if ( fleshEntityNum < MAX_CLIENTS ) {
6009 CG_Bleed( end, fleshEntityNum );
6010 }
6011
6012 // play the bullet hit flesh sound
6013 // HACK, if this is not us getting hit, make it quieter
6014 if ( fleshEntityNum == cg.snap->ps.clientNum ) {
6015
6016 // (SA) TODO: for metal guys, make metal a flag rather than an aitype check?
6017 if ( aiType == AICHAR_PROTOSOLDIER ||
6018 aiType == AICHAR_SUPERSOLDIER ) {
6019 CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshMetalScript, NULL, fleshEntityNum );
6020 } else {
6021 CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshScript, NULL, fleshEntityNum );
6022 }
6023 } else {
6024 VectorSubtract( cg_entities[fleshEntityNum].lerpOrigin, cg.snap->ps.origin, origin );
6025 VectorMA( cg.snap->ps.origin, 3, origin, origin );
6026 if ( aiType == AICHAR_PROTOSOLDIER ||
6027 aiType == AICHAR_SUPERSOLDIER ) {
6028 CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshMetalScript, origin, ENTITYNUM_WORLD );
6029 } else {
6030 CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshScript, origin, ENTITYNUM_WORLD );
6031 }
6032 }
6033
6034 // if we haven't dropped a blood spat in a while, check if this is a good scenario
6035 if ( lastBloodSpat > cg.time || lastBloodSpat < cg.time - 500 ) {
6036 if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) {
6037 VectorSubtract( end, start, dir );
6038 VectorNormalize( dir );
6039 VectorMA( end, 128, dir, trend );
6040 trap_CM_BoxTrace( &trace, end, trend, NULL, NULL, 0, MASK_SHOT & ~CONTENTS_BODY );
6041
6042 if ( trace.fraction < 1 ) {
6043 CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace.endpos, trace.plane.normal, random() * 360,
6044 1,1,1,1, qtrue, 15 + random() * 20, qfalse, cg_bloodTime.integer * 1000 );
6045 lastBloodSpat = cg.time;
6046 } else if ( lastBloodSpat < cg.time - 1000 ) {
6047 // drop one on the ground?
6048 VectorCopy( end, trend );
6049 trend[2] -= 64;
6050 trap_CM_BoxTrace( &trace, end, trend, NULL, NULL, 0, MASK_SHOT & ~CONTENTS_BODY );
6051
6052 if ( trace.fraction < 1 ) {
6053 CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace.endpos, trace.plane.normal, random() * 360,
6054 1,1,1,1, qtrue, 15 + random() * 10, qfalse, cg_bloodTime.integer * 1000 );
6055 lastBloodSpat = cg.time;
6056 }
6057 }
6058 }
6059 }
6060
6061 } else { // (not flesh)
6062 int fromweap;
6063 fromweap = cg_entities[sourceEntityNum].currentState.weapon;
6064
6065 if ( !fromweap || cg_entities[sourceEntityNum].currentState.eFlags & EF_MG42_ACTIVE ) { // mounted
6066 fromweap = WP_MP40;
6067 }
6068
6069 // TODO: not sure what kind of effect were going to do
6070 if ( wolfkick ) {
6071 return;
6072 }
6073
6074 if ( CG_CalcMuzzlePoint( sourceEntityNum, start )
6075 || cg.snap->ps.persistant[PERS_HWEAPON_USE] ) {
6076 vec3_t start2;
6077 VectorSubtract( end, start, dir );
6078 VectorNormalize( dir );
6079 VectorMA( end, -4, dir, start2 ); // back off a little so it doesn't start in solid
6080 VectorMA( end, 64, dir, dir );
6081 trap_CM_BoxTrace( &trace, start2, dir, NULL, NULL, 0, MASK_SHOT );
6082
6083 if ( ( trace.surfaceFlags & SURF_METAL ) || !( rand() % 10 ) || ( otherEntNum2 != ENTITYNUM_NONE ) ) {
6084 // JPW NERVE compute new spark direction from normal & dir (rotate -dir 180 degrees about normal)
6085
6086 // (SA) NOTE: isn't this done by the server and sent along in the 'normal' that's passed into this routine? (1107 g_weapon.c)
6087
6088 VectorScale( dir,-1.0f,tmp );
6089 RotatePointAroundVector( tmp,normal,tmp,180.0f );
6090 CG_MissileHitWall( fromweap, 0, end, tmp, trace.surfaceFlags ); // sparks // (SA) modified to send missilehitwall surface parameters
6091 // jpw
6092 // CG_MissileHitWall( fromweap, 0, end, normal, trace.surfaceFlags ); // sparks // (SA) modified to send missilehitwall surface parameters
6093 }
6094 if ( !( sourceContentType & CONTENTS_WATER ) && ( destContentType & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { // only when shooting /into/ water
6095 trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, MASK_WATER );
6096 // CG_Trace(&trace, start, NULL, NULL, end, -1, MASK_WATER);
6097 // if (!(trace.surfaceFlags & SURF_NOMARKS)) { // check to see if the surface should draw splashes
6098 CG_MissileHitWall( fromweap, 2, trace.endpos, trace.plane.normal, trace.surfaceFlags );
6099 // }
6100 } else {
6101 CG_MissileHitWall( fromweap, 1, end, normal, trace.surfaceFlags ); // smoke puff // (SA) modified to send missilehitwall surface parameters
6102
6103 if ( 0 ) {
6104 localEntity_t *le;
6105 le = CG_AllocLocalEntity();
6106 le->leType = LE_EMITTER;
6107 le->startTime = cg.time;
6108 le->endTime = le->startTime + 20000;
6109 le->pos.trType = TR_STATIONARY;
6110 VectorCopy( end, le->pos.trBase );
6111 VectorCopy( normal, le->angles.trBase );
6112 le->ownerNum = 0;
6113 }
6114 }
6115 }
6116 }
6117 }
6118
6119 /*
6120 ============
6121 CG_ClientDamage
6122 ============
6123 */
CG_ClientDamage(int entnum,int enemynum,int id)6124 void CG_ClientDamage( int entnum, int enemynum, int id ) {
6125 if ( id > CLDMG_MAX ) {
6126 CG_Error( "CG_ClientDamage: unknown damage type: %i\n", id );
6127 }
6128
6129 // NERVE - SMF - clientDamage commands are now sent through usercmds for multiplayer
6130 if ( cgs.gametype == GT_WOLF ) {
6131 if ( entnum == cg.snap->ps.clientNum ) {
6132 // NOTE: MAX_CLIENTS currently only needs 7 bits, the rest is for id tag
6133 cg.cld = id << 7;
6134 cg.cld |= enemynum;
6135 }
6136 }
6137 // -NERVE - SMF
6138 else {
6139 trap_SendClientCommand( va( "cld %i %i %i", entnum, enemynum, id ) );
6140 }
6141 }
6142