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: g_weapon.c
31 *
32 * desc: perform the server side effects of a weapon firing
33 *
34 */
35
36
37 #include "g_local.h"
38
39 static float s_quadFactor;
40 static vec3_t forward, right, up;
41 static vec3_t muzzleEffect;
42 static vec3_t muzzleTrace;
43
44
45 // forward dec
46 void weapon_zombiespit( gentity_t *ent );
47
48 void Bullet_Fire( gentity_t *ent, float spread, int damage );
49 void Bullet_Fire_Extended( gentity_t *source, gentity_t *attacker, vec3_t start, vec3_t end, float spread, int damage, int recursion );
50
51 int G_GetWeaponDamage( int weapon ); // JPW
52
53 #define NUM_NAILSHOTS 10
54
55 /*
56 ======================================================================
57
58 KNIFE/GAUNTLET (NOTE: gauntlet is now the Zombie melee)
59
60 ======================================================================
61 */
62
63 #define KNIFE_DIST 48
64
65 /*
66 ==============
67 Weapon_Knife
68 ==============
69 */
Weapon_Knife(gentity_t * ent)70 void Weapon_Knife( gentity_t *ent ) {
71 trace_t tr;
72 gentity_t *traceEnt, *tent;
73 int damage, mod;
74 // vec3_t pforward, eforward;
75
76 vec3_t end;
77
78 mod = MOD_KNIFE;
79
80 AngleVectors( ent->client->ps.viewangles, forward, right, up );
81 CalcMuzzlePoint( ent, ent->s.weapon, forward, right, up, muzzleTrace );
82 VectorMA( muzzleTrace, KNIFE_DIST, forward, end );
83 trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT );
84
85 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
86 return;
87 }
88
89 // no contact
90 if ( tr.fraction == 1.0f ) {
91 return;
92 }
93
94 if ( tr.entityNum >= MAX_CLIENTS ) { // world brush or non-player entity (no blood)
95 tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
96 } else { // other player
97 tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
98 }
99
100 tent->s.otherEntityNum = tr.entityNum;
101 tent->s.eventParm = DirToByte( tr.plane.normal );
102 tent->s.weapon = ent->s.weapon;
103
104 if ( tr.entityNum == ENTITYNUM_WORLD ) { // don't worry about doing any damage
105 return;
106 }
107
108 traceEnt = &g_entities[ tr.entityNum ];
109
110 if ( !( traceEnt->takedamage ) ) {
111 return;
112 }
113
114 // RF, no knife damage for big guys
115 switch ( traceEnt->aiCharacter ) {
116 case AICHAR_PROTOSOLDIER:
117 case AICHAR_SUPERSOLDIER:
118 case AICHAR_HEINRICH:
119 return;
120 }
121
122 damage = G_GetWeaponDamage( ent->s.weapon ); // JPW // default knife damage for frontal attacks
123
124 if ( traceEnt->client ) {
125 if ( ent->client->ps.serverCursorHint == HINT_KNIFE ) {
126 // AngleVectors (ent->client->ps.viewangles, pforward, NULL, NULL);
127 // AngleVectors (traceEnt->client->ps.viewangles, eforward, NULL, NULL);
128
129 // (SA) TODO: neutralize pitch (so only yaw is considered)
130 // if(DotProduct( eforward, pforward ) > 0.9f) { // from behind
131
132 // if relaxed, the strike is almost assured a kill
133 // if not relaxed, but still from behind, it does 10x damage (50)
134
135 // (SA) commented out right now as the ai's state always checks here as 'combat'
136
137 // if(ent->s.aiState == AISTATE_RELAXED) {
138 damage = 100; // enough to drop a 'normal' (100 health) human with one jab
139 mod = MOD_KNIFE_STEALTH;
140 // } else {
141 // damage *= 10;
142 // }
143 //----(SA) end
144 }
145 }
146
147 G_Damage( traceEnt, ent, ent, vec3_origin, tr.endpos, ( damage + rand() % 5 ) * s_quadFactor, 0, mod );
148 }
149
150 // JPW NERVE
151 /*
152 ======================
153 Weapon_Class_Special
154 class-specific in multiplayer
155 ======================
156 */
157 // JPW NERVE
Weapon_Medic(gentity_t * ent)158 void Weapon_Medic( gentity_t *ent ) {
159 vec3_t velocity, org, offset;
160 vec3_t angles;
161
162 trace_t tr;
163 gentity_t *traceEnt;
164 int healamt, headshot;
165
166 vec3_t end;
167
168 AngleVectors( ent->client->ps.viewangles, forward, right, up );
169 CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace );
170 VectorMA( muzzleTrace, 30, forward, end ); // CH_ACTIVATE_DIST
171 trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT );
172
173 if ( tr.fraction < 1.0 ) {
174 traceEnt = &g_entities[ tr.entityNum ];
175 if ( traceEnt->client != NULL ) {
176 if ( ( traceEnt->client->ps.pm_type == PM_DEAD ) && ( traceEnt->client->sess.sessionTeam == ent->client->sess.sessionTeam ) ) {
177 if ( level.time - ent->client->ps.classWeaponTime > g_medicChargeTime.integer ) {
178 ent->client->ps.classWeaponTime = level.time - g_medicChargeTime.integer;
179 }
180 ent->client->ps.classWeaponTime += 125;
181 traceEnt->client->medicHealAmt++;
182 if ( ent->client->ps.classWeaponTime > level.time ) { // heal the dude
183 // copy some stuff out that we'll wanna restore
184 VectorCopy( traceEnt->client->ps.origin, org );
185 healamt = traceEnt->client->medicHealAmt;
186 headshot = traceEnt->client->ps.eFlags & EF_HEADSHOT;
187
188 ClientSpawn( traceEnt );
189 if ( healamt > 80 ) {
190 healamt = 80;
191 }
192 if ( healamt < 10 ) {
193 healamt = 10;
194 }
195 if ( headshot ) {
196 traceEnt->client->ps.eFlags |= EF_HEADSHOT;
197 }
198 traceEnt->health = healamt;
199 VectorCopy( org,traceEnt->s.origin );
200 VectorCopy( org,traceEnt->r.currentOrigin );
201 VectorCopy( org,traceEnt->client->ps.origin );
202 }
203 }
204 }
205 } else { // throw out health pack
206 if ( level.time - ent->client->ps.classWeaponTime >= g_medicChargeTime.integer * 0.25f ) {
207 if ( level.time - ent->client->ps.classWeaponTime > g_medicChargeTime.integer ) {
208 ent->client->ps.classWeaponTime = level.time - g_medicChargeTime.integer;
209 }
210 ent->client->ps.classWeaponTime += g_medicChargeTime.integer * 0.25;
211
212 VectorCopy( ent->client->ps.viewangles, angles );
213 angles[PITCH] = 0; // always forward
214 AngleVectors( angles, velocity, NULL, NULL );
215 VectorScale( velocity, 75, offset );
216 VectorScale( velocity, 50, velocity );
217 velocity[2] += 50 + crandom() * 50;
218
219 VectorAdd( ent->client->ps.origin,offset,org );
220 }
221 }
222 }
223 // jpw
224
225 // DHM - Nerve
Weapon_Engineer(gentity_t * ent)226 void Weapon_Engineer( gentity_t *ent ) {
227 trace_t tr;
228 gentity_t *traceEnt;
229 // int mod = MOD_KNIFE;
230
231 vec3_t end;
232
233 AngleVectors( ent->client->ps.viewangles, forward, right, up );
234 CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace );
235 VectorMA( muzzleTrace, 96, forward, end ); // CH_ACTIVATE_DIST
236 trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT | CONTENTS_TRIGGER );
237
238 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
239 return;
240 }
241
242 // no contact
243 if ( tr.fraction == 1.0f ) {
244 return;
245 }
246
247 if ( tr.entityNum == ENTITYNUM_NONE || tr.entityNum == ENTITYNUM_WORLD ) {
248 return;
249 }
250
251 traceEnt = &g_entities[ tr.entityNum ];
252 if ( traceEnt->methodOfDeath == MOD_DYNAMITE ) {
253
254 traceEnt->health += 3;
255 if ( traceEnt->health >= 248 ) {
256 traceEnt->health = 255;
257 // Need some kind of event/announcement here
258
259 Add_Ammo( ent, WP_DYNAMITE, 1, qtrue );
260
261 traceEnt->think = G_FreeEntity;
262 traceEnt->nextthink = level.time + FRAMETIME;
263 // JPW NERVE
264 if ( ent->client->sess.sessionTeam == TEAM_RED ) {
265 trap_SendServerCommand( -1, "cp \"Axis engineer disarmed a det charge!\n\"" );
266 } else {
267 trap_SendServerCommand( -1, "cp \"Allied engineer disarmed a det charge!\n\"" );
268 }
269 // jpw
270 }
271 } else if ( !traceEnt->takedamage && !Q_stricmp( traceEnt->classname, "misc_mg42" ) ) {
272 // "Ammo" for this weapon is time based
273 if ( ent->client->ps.classWeaponTime + g_engineerChargeTime.integer < level.time ) {
274 ent->client->ps.classWeaponTime = level.time - g_engineerChargeTime.integer;
275 }
276 ent->client->ps.classWeaponTime += 150;
277
278 if ( ent->client->ps.classWeaponTime > level.time ) {
279 ent->client->ps.classWeaponTime = level.time;
280 return; // Out of "ammo"
281 }
282
283 if ( traceEnt->health >= 255 ) {
284 traceEnt->s.frame = 0;
285
286 if ( traceEnt->mg42BaseEnt > 0 ) {
287 g_entities[ traceEnt->mg42BaseEnt ].health = 100;
288 g_entities[ traceEnt->mg42BaseEnt ].takedamage = qtrue;
289 traceEnt->health = 0;
290 } else {
291 traceEnt->health = 100;
292 }
293
294 traceEnt->takedamage = qtrue;
295
296 trap_SendServerCommand( ent - g_entities, "cp \"You have repaired the MG42!\n\"" );
297 } else {
298 traceEnt->health += 3;
299 }
300 }
301 }
302
303
304 // JPW NERVE -- launch airstrike as line of bombs mostly-perpendicular to line of grenade travel
305 // (close air support should *always* drop parallel to friendly lines, tho accidents do happen)
306 void G_ExplodeMissile( gentity_t *ent );
307 #define NUMBOMBS 10
308 #define BOMBSPREAD 150
309 extern void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message );
weapon_callAirStrike(gentity_t * ent)310 void weapon_callAirStrike( gentity_t *ent ) {
311 int i;
312 vec3_t bombaxis, lookaxis, pos, bomboffset, fallaxis;
313 gentity_t *bomb;
314 trace_t tr;
315 float traceheight, bottomtraceheight;
316
317 VectorCopy( ent->s.pos.trBase,bomboffset );
318 bomboffset[2] += 4096;
319
320 // turn off smoke grenade
321 ent->think = G_ExplodeMissile;
322 ent->nextthink = level.time + 1000 + NUMBOMBS * 100 + crandom() * 50; // 3000 offset is for aircraft flyby
323
324 trap_Trace( &tr, ent->s.pos.trBase, NULL, NULL, bomboffset, ent->s.number, MASK_SHOT );
325 if ( ( tr.fraction < 1.0 ) && ( !( tr.surfaceFlags & SURF_SKY ) ) ) {
326 G_SayTo( ent->parent, ent->parent, 2, COLOR_YELLOW, "Pilot: ", "Can't see target, aborting bomb run" );
327 return;
328 }
329
330 VectorCopy( tr.endpos, bomboffset );
331 traceheight = bomboffset[2];
332 bottomtraceheight = traceheight - 8192;
333
334 VectorSubtract( ent->s.pos.trBase,ent->parent->client->ps.origin,lookaxis );
335 lookaxis[2] = 0;
336 VectorNormalize( lookaxis );
337 pos[0] = 0;
338 pos[1] = 0;
339 pos[2] = crandom(); // generate either up or down vector,
340 VectorNormalize( pos ); // which adds randomness to pass direction below
341 RotatePointAroundVector( bombaxis,pos,lookaxis,90 + crandom() * 30 ); // munge the axis line a bit so it's not totally perpendicular
342 VectorNormalize( bombaxis );
343
344 VectorCopy( bombaxis,pos );
345 VectorScale( pos,(float)( -0.5f * BOMBSPREAD * NUMBOMBS ),pos );
346 VectorAdd( ent->s.pos.trBase, pos, pos ); // first bomb position
347 VectorScale( bombaxis,BOMBSPREAD,bombaxis ); // bomb drop direction offset
348
349 // add an aircraft (looks suspiciously like a rocket right now) (but doesn't work)
350 /*
351 bomb = G_Spawn();
352 bomb->nextthink = level.time + 26000;
353 bomb->think = G_ExplodeMissile;
354 bomb->s.eType = ET_MISSILE;
355 bomb->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST;
356 bomb->s.weapon = WP_GRENADE_LAUNCHER; // might wanna change this
357 bomb->r.ownerNum = ent->s.number;
358 bomb->parent = ent->parent;
359 bomb->damage = 400; // maybe should un-hard-code these?
360 bomb->splashDamage = 400;
361 bomb->classname = "fighterbomber";
362 bomb->splashRadius = 400;
363 bomb->methodOfDeath = MOD_DYNAMITE; // FIXME add MOD for air strike
364 bomb->splashMethodOfDeath = MOD_DYNAMITE_SPLASH;
365 bomb->clipmask = MASK_MISSILESHOT;
366 bomb->s.pos.trType = TR_STATIONARY; // TR_LINEAR;
367 bomb->s.pos.trTime = level.time;
368 VectorCopy(ent->s.pos.trBase, bomb->s.pos.trBase);
369 bomb->s.pos.trBase[2] += 200;
370 bomb->s.modelindex = G_ModelIndex( "models/mapobjects/vehicles/m109.md3" );
371 */
372 for ( i = 0; i < NUMBOMBS; i++ ) {
373 bomb = G_Spawn();
374 bomb->nextthink = level.time + i * 100 + crandom() * 50 + 1000; // 1000 for aircraft flyby, other term for tumble stagger
375 bomb->think = G_ExplodeMissile;
376 bomb->s.eType = ET_MISSILE;
377 bomb->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST;
378 bomb->s.weapon = WP_GRENADE_LAUNCHER; // might wanna change this
379 bomb->r.ownerNum = ent->s.number;
380 bomb->parent = ent->parent;
381 bomb->damage = 400; // maybe should un-hard-code these?
382 bomb->splashDamage = 400;
383 bomb->classname = "air strike";
384 bomb->splashRadius = 400;
385 bomb->methodOfDeath = MOD_AIRSTRIKE;
386 bomb->splashMethodOfDeath = MOD_AIRSTRIKE;
387 bomb->clipmask = MASK_MISSILESHOT;
388 bomb->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height
389 bomb->s.pos.trTime = level.time; // move a bit on the very first frame
390 bomboffset[0] = crandom() * 0.5 * BOMBSPREAD;
391 bomboffset[1] = crandom() * 0.5 * BOMBSPREAD;
392 bomboffset[2] = 0;
393 VectorAdd( pos,bomboffset,bomb->s.pos.trBase );
394
395 VectorCopy( bomb->s.pos.trBase,bomboffset ); // make sure bombs fall "on top of" nonuniform scenery
396 bomboffset[2] = traceheight;
397
398 VectorCopy( bomboffset, fallaxis );
399 fallaxis[2] = bottomtraceheight;
400
401 trap_Trace( &tr, bomboffset, NULL, NULL, fallaxis, ent->s.number, MASK_SHOT );
402 if ( tr.fraction != 1.0 ) {
403 VectorCopy( tr.endpos,bomb->s.pos.trBase );
404 }
405
406 bomb->s.pos.trDelta[0] = 0; // might need to change this
407 bomb->s.pos.trDelta[1] = 0;
408 bomb->s.pos.trDelta[2] = 0;
409 SnapVector( bomb->s.pos.trDelta ); // save net bandwidth
410 VectorCopy( bomb->s.pos.trBase, bomb->r.currentOrigin );
411
412 // move pos for next bomb
413 VectorAdd( pos,bombaxis,pos );
414 }
415 }
416
417 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity );
Weapon_Class_Special(gentity_t * ent)418 void Weapon_Class_Special( gentity_t *ent ) {
419
420 switch ( ent->client->ps.stats[STAT_PLAYER_CLASS] ) {
421 case PC_SOLDIER:
422 G_Printf( "shooting soldier\n" );
423 break;
424 case PC_MEDIC:
425 Weapon_Medic( ent );
426 break;
427 case PC_ENGINEER:
428 //G_Printf("shooting engineer\n");
429 //ent->client->ps.classWeaponTime = level.time;
430 Weapon_Engineer( ent );
431 break;
432 case PC_LT:
433 if ( level.time - ent->client->ps.classWeaponTime > g_LTChargeTime.integer ) {
434 weapon_grenadelauncher_fire( ent,WP_GRENADE_SMOKE );
435 ent->client->ps.classWeaponTime = level.time;
436 }
437 break;
438 }
439 }
440 // jpw
441
442 /*
443 ==============
444 Weapon_Gauntlet
445 ==============
446 */
Weapon_Gauntlet(gentity_t * ent)447 void Weapon_Gauntlet( gentity_t *ent ) {
448 trace_t *tr;
449 tr = CheckMeleeAttack( ent, 32, qfalse );
450 if ( tr ) {
451 G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos,
452 ( 10 + rand() % 5 ) * s_quadFactor, 0, MOD_GAUNTLET );
453 }
454 }
455
456 /*
457 ===============
458 CheckMeleeAttack
459 using 'isTest' to return hits to world surfaces
460 ===============
461 */
CheckMeleeAttack(gentity_t * ent,float dist,qboolean isTest)462 trace_t *CheckMeleeAttack( gentity_t *ent, float dist, qboolean isTest ) {
463 static trace_t tr;
464 vec3_t end;
465 gentity_t *tent;
466 gentity_t *traceEnt;
467
468 // set aiming directions
469 AngleVectors( ent->client->ps.viewangles, forward, right, up );
470
471 CalcMuzzlePoint( ent, WP_GAUNTLET, forward, right, up, muzzleTrace );
472
473 VectorMA( muzzleTrace, dist, forward, end );
474
475 trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT );
476 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
477 return NULL;
478 }
479
480 // no contact
481 if ( tr.fraction == 1.0f ) {
482 return NULL;
483 }
484
485 if ( ent->client->noclip ) {
486 return NULL;
487 }
488
489 traceEnt = &g_entities[ tr.entityNum ];
490
491 // send blood impact
492 if ( traceEnt->takedamage && traceEnt->client ) {
493 tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
494 tent->s.otherEntityNum = traceEnt->s.number;
495 tent->s.eventParm = DirToByte( tr.plane.normal );
496 tent->s.weapon = ent->s.weapon;
497 }
498
499 //----(SA) added
500 if ( isTest ) {
501 return &tr;
502 }
503 //----(SA)
504
505 if ( !traceEnt->takedamage ) {
506 return NULL;
507 }
508
509 if ( ent->client->ps.powerups[PW_QUAD] ) {
510 G_AddEvent( ent, EV_POWERUP_QUAD, 0 );
511 s_quadFactor = g_quadfactor.value;
512 } else {
513 s_quadFactor = 1;
514 }
515
516 return &tr;
517 }
518
519
520 /*
521 ======================================================================
522
523 MACHINEGUN
524
525 ======================================================================
526 */
527
528 /*
529 ======================
530 SnapVectorTowards
531
532 Round a vector to integers for more efficient network
533 transmission, but make sure that it rounds towards a given point
534 rather than blindly truncating. This prevents it from truncating
535 into a wall.
536 ======================
537 */
538
539 // (SA) modified so it doesn't have trouble with negative locations (quadrant problems)
540 // (this was causing some problems with bullet marks appearing since snapping
541 // too far off the target surface causes the the distance between the transmitted impact
542 // point and the actual hit surface larger than the mark radius. (so nothing shows) )
543
SnapVectorTowards(vec3_t v,vec3_t to)544 void SnapVectorTowards( vec3_t v, vec3_t to ) {
545 int i;
546
547 for ( i = 0 ; i < 3 ; i++ ) {
548 if ( to[i] <= v[i] ) {
549 v[i] = floor( v[i] );
550 } else {
551 v[i] = ceil( v[i] );
552 }
553 }
554 }
555
556 // JPW
557 // mechanism allows different weapon damage for single/multiplayer; we want "balanced" weapons
558 // in multiplayer but don't want to alter the existing single-player damage items that have already
559 // been changed
560 //
561 // KLUDGE/FIXME: also modded #defines below to become macros that call this fn for minimal impact elsewhere
562 //
G_GetWeaponDamage(int weapon)563 int G_GetWeaponDamage( int weapon ) {
564 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
565 switch ( weapon ) {
566 case WP_LUGER:
567 case WP_SILENCER: return 6;
568 case WP_COLT: return 8;
569 case WP_AKIMBO: return 8; //----(SA) added
570 case WP_VENOM: return 12; // 15 ----(SA) slight modify for DM
571 case WP_MP40: return 6;
572 case WP_THOMPSON: return 8;
573 case WP_STEN: return 10;
574 case WP_FG42SCOPE:
575 case WP_FG42: return 15;
576 case WP_MAUSER: return 20;
577 case WP_GARAND: return 25;
578 case WP_SNIPERRIFLE: return 55;
579 case WP_SNOOPERSCOPE: return 25;
580 case WP_NONE: return 0;
581 case WP_KNIFE: return 5;
582 case WP_GRENADE_LAUNCHER: return 100;
583 case WP_GRENADE_PINEAPPLE: return 80;
584 case WP_DYNAMITE: return 400;
585 case WP_PANZERFAUST: return 200; // (SA) was 100
586 case WP_MORTAR: return 100;
587 case WP_FLAMETHROWER: // FIXME -- not used in single player yet
588 case WP_TESLA:
589 case WP_GAUNTLET:
590 case WP_SNIPER:
591 default: return 1;
592 }
593 } else { // multiplayer damage
594 switch ( weapon ) {
595 case WP_LUGER:
596 case WP_SILENCER: return 14;
597 case WP_COLT: return 18;
598 case WP_AKIMBO: return 18; //----(SA) added
599 case WP_VENOM: return 20;
600 case WP_MP40: return 14;
601 case WP_THOMPSON: return 18;
602 case WP_STEN: return 14;
603 case WP_FG42SCOPE:
604 case WP_FG42: return 15;
605 case WP_MAUSER: return 25;
606 case WP_GARAND: return 25;
607 case WP_SNIPERRIFLE: return 80;
608 case WP_SNOOPERSCOPE: return 75;
609 case WP_NONE: return 0;
610 case WP_KNIFE: return 10;
611 case WP_GRENADE_SMOKE: return 100;
612 case WP_GRENADE_LAUNCHER: return 200;
613 case WP_GRENADE_PINEAPPLE: return 200;
614 case WP_DYNAMITE: return 600;
615 case WP_PANZERFAUST: return 400;
616 case WP_MORTAR: return 100;
617 case WP_FLAMETHROWER: return 1;
618 case WP_TESLA:
619 case WP_GAUNTLET:
620 case WP_SNIPER:
621 default: return 1;
622 }
623 }
624 }
625 // JPW - this chunk appears to not be used, right?
626 /*
627 #define MACHINEGUN_SPREAD 200
628 #define MACHINEGUN_DAMAGE G_GetWeaponDamage(WP_MACHINEGUN) // JPW
629 #define MACHINEGUN_TEAM_DAMAGE G_GetWeaponDamage(WP_MACHINEGUN) // JPW // wimpier MG in teamplay
630 */
631 // jpw
632
633 // RF, wrote this so we can dynamically switch between old and new values while testing g_userAim
G_GetWeaponSpread(int weapon)634 float G_GetWeaponSpread( int weapon ) {
635 if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE -- don't affect SP game
636 if ( g_userAim.integer ) {
637 // these should be higher since they become erratic if aiming is out
638 switch ( weapon ) {
639 case WP_LUGER: return 600;
640 case WP_SILENCER: return 900;
641 case WP_COLT: return 700;
642 case WP_AKIMBO: return 700; //----(SA) added
643 case WP_VENOM: return 1000;
644 case WP_MP40: return 1000;
645 case WP_FG42SCOPE: return 300;
646 case WP_FG42: return 800;
647 case WP_THOMPSON: return 1200;
648 case WP_STEN: return 1200;
649 case WP_MAUSER: return 400;
650 case WP_GARAND: return 500;
651 case WP_SNIPERRIFLE: return 300;
652 case WP_SNOOPERSCOPE: return 300;
653 }
654 } else { // old values
655 switch ( weapon ) {
656 case WP_LUGER: return 25;
657 case WP_SILENCER: return 150;
658 case WP_COLT: return 30;
659 case WP_AKIMBO: return 30; //----(SA) added
660 case WP_VENOM: return 200;
661 case WP_MP40: return 200;
662 case WP_FG42SCOPE: return 10;
663 case WP_FG42: return 150;
664 case WP_THOMPSON: return 250;
665 case WP_STEN: return 300;
666 case WP_MAUSER: return 15;
667 case WP_GARAND: return 25;
668 case WP_SNIPERRIFLE: return 10;
669 case WP_SNOOPERSCOPE: return 10;
670 }
671 }
672 } else { // JPW NERVE but in multiplayer... new spreads and don't look at g_userAim
673 switch ( weapon ) {
674 case WP_LUGER: return 600;
675 case WP_SILENCER: return 900;
676 case WP_COLT: return 800;
677 case WP_AKIMBO: return 800; //----(SA)added
678 case WP_VENOM: return 600;
679 case WP_MP40: return 400;
680 case WP_FG42SCOPE:
681 case WP_FG42: return 500;
682 case WP_THOMPSON: return 600;
683 case WP_STEN: return 200;
684 case WP_MAUSER: return 700;
685 case WP_GARAND: return 600;
686 case WP_SNIPERRIFLE: return 700; // was 300
687 case WP_SNOOPERSCOPE: return 700;
688 }
689 }
690 G_Printf( "shouldn't ever get here (weapon %d)\n",weapon );
691 // jpw
692 return 0; // shouldn't get here
693 }
694
695 #define LUGER_SPREAD G_GetWeaponSpread( WP_LUGER )
696 #define LUGER_DAMAGE G_GetWeaponDamage( WP_LUGER ) // JPW
697 #define SILENCER_SPREAD G_GetWeaponSpread( WP_SILENCER )
698 #define COLT_SPREAD G_GetWeaponSpread( WP_COLT )
699 #define COLT_DAMAGE G_GetWeaponDamage( WP_COLT ) // JPW
700
701 #define VENOM_SPREAD G_GetWeaponSpread( WP_VENOM )
702 #define VENOM_DAMAGE G_GetWeaponDamage( WP_VENOM ) // JPW
703
704 #define MP40_SPREAD G_GetWeaponSpread( WP_MP40 )
705 #define MP40_DAMAGE G_GetWeaponDamage( WP_MP40 ) // JPW
706 #define THOMPSON_SPREAD G_GetWeaponSpread( WP_THOMPSON )
707 #define THOMPSON_DAMAGE G_GetWeaponDamage( WP_THOMPSON ) // JPW
708 #define STEN_SPREAD G_GetWeaponSpread( WP_STEN )
709 #define STEN_DAMAGE G_GetWeaponDamage( WP_STEN ) // JPW
710 #define FG42_SPREAD G_GetWeaponSpread( WP_FG42 )
711 #define FG42_DAMAGE G_GetWeaponDamage( WP_FG42 ) // JPW
712
713 #define MAUSER_SPREAD G_GetWeaponSpread( WP_MAUSER )
714 #define MAUSER_DAMAGE G_GetWeaponDamage( WP_MAUSER ) // JPW
715 #define GARAND_SPREAD G_GetWeaponSpread( WP_GARAND )
716 #define GARAND_DAMAGE G_GetWeaponDamage( WP_GARAND ) // JPW
717
718 #define SNIPER_SPREAD G_GetWeaponSpread( WP_SNIPERRIFLE )
719 #define SNIPER_DAMAGE G_GetWeaponDamage( WP_SNIPERRIFLE ) // JPW
720
721 #define SNOOPER_SPREAD G_GetWeaponSpread( WP_SNOOPERSCOPE )
722 #define SNOOPER_DAMAGE G_GetWeaponDamage( WP_SNOOPERSCOPE ) // JPW
723
724 /*
725 ==============
726 SP5_Fire
727
728 dead code
729 ==============
730 */
731
732
733 /*
734 ==============
735 Cross_Fire
736 ==============
737 */
Cross_Fire(gentity_t * ent)738 void Cross_Fire( gentity_t *ent ) {
739 // (SA) temporarily use the zombie spit effect to check working state
740 weapon_zombiespit( ent );
741 }
742
743
744
745 /*
746 ==============
747 Tesla_Fire
748 ==============
749 */
Tesla_Fire(gentity_t * ent)750 void Tesla_Fire( gentity_t *ent ) {
751 // TODO: Find all targets in the client's view frame, and lock onto them all, applying damage
752 // and telling all clients to draw the appropriate effects.
753
754 //G_Printf("TODO: Tesla damage/effects\n" );
755 }
756
757
758
RubbleFlagCheck(gentity_t * ent,trace_t tr)759 void RubbleFlagCheck( gentity_t *ent, trace_t tr ) {
760 #if 0 // (SA) moving client-side
761 qboolean is_valid = qfalse;
762 int type = 0;
763
764 if ( tr.surfaceFlags & SURF_RUBBLE || tr.surfaceFlags & SURF_GRAVEL ) {
765 is_valid = qtrue;
766 type = 4;
767 } else if ( tr.surfaceFlags & SURF_METAL ) {
768 //----(SA) removed
769 // is_valid = qtrue;
770 // type = 2;
771 } else if ( tr.surfaceFlags & SURF_WOOD ) {
772 is_valid = qtrue;
773 type = 1;
774 }
775
776 if ( is_valid && ent->client && ( ent->s.weapon == WP_VENOM
777 || ent->client->ps.persistant[PERS_HWEAPON_USE] ) ) {
778 if ( rand() % 100 > 75 ) {
779 gentity_t *sfx;
780 vec3_t start;
781 vec3_t dir;
782
783 sfx = G_Spawn();
784
785 sfx->s.density = type;
786
787 VectorCopy( tr.endpos, start );
788
789 VectorCopy( muzzleTrace, dir );
790 VectorNegate( dir, dir );
791
792 G_SetOrigin( sfx, start );
793 G_SetAngle( sfx, dir );
794
795 G_AddEvent( sfx, EV_SHARD, DirToByte( dir ) );
796
797 sfx->think = G_FreeEntity;
798 sfx->nextthink = level.time + 1000;
799
800 sfx->s.frame = 3 + ( rand() % 3 ) ;
801
802 trap_LinkEntity( sfx );
803
804 }
805 }
806 #endif
807 }
808
809 /*
810 ==============
811 EmitterCheck
812 see if a new particle emitter should be created at the bullet impact point
813 ==============
814 */
EmitterCheck(gentity_t * ent,gentity_t * attacker,trace_t * tr)815 void EmitterCheck( gentity_t *ent, gentity_t *attacker, trace_t *tr ) {
816 gentity_t *tent;
817 vec3_t origin;
818
819 if ( !ent->emitNum ) { // no emitters left for this entity.
820 return;
821 }
822
823 VectorCopy( tr->endpos, origin );
824 SnapVectorTowards( tr->endpos, attacker->s.origin ); // make sure it's out of the wall
825
826
827 // why were these stricmp's?...
828 if ( ent->s.eType == ET_EXPLOSIVE ) {
829 } else if ( ent->s.eType == ET_LEAKY ) {
830
831 tent = G_TempEntity( origin, EV_EMITTER );
832 VectorCopy( origin, tent->s.origin );
833 tent->s.time = ent->emitTime;
834 tent->s.density = ent->emitPressure; // 'pressure'
835 tent->s.teamNum = ent->emitID; // 'type'
836 VectorCopy( tr->plane.normal, tent->s.origin2 );
837 }
838
839 ent->emitNum--;
840 }
841
842
SniperSoundEFX(vec3_t pos)843 void SniperSoundEFX( vec3_t pos ) {
844 G_TempEntity( pos, EV_SNIPER_SOUND );
845 }
846
847
848 /*
849 ==============
850 Bullet_Endpos
851 find target end position for bullet trace based on entities weapon and accuracy
852 ==============
853 */
Bullet_Endpos(gentity_t * ent,float spread,vec3_t * end)854 void Bullet_Endpos( gentity_t *ent, float spread, vec3_t *end ) {
855 float r, u;
856 qboolean randSpread = qtrue;
857 int dist = 8192;
858
859 r = crandom() * spread;
860 u = crandom() * spread;
861
862 // Ridah, if this is an AI shooting, apply their accuracy
863 if ( ent->r.svFlags & SVF_CASTAI ) {
864 float accuracy;
865 accuracy = ( 1.0 - AICast_GetAccuracy( ent->s.number ) ) * AICAST_AIM_SPREAD;
866 r += crandom() * accuracy;
867 u += crandom() * ( accuracy * 1.25 );
868 } else {
869 if ( ent->s.weapon == WP_SNOOPERSCOPE || ent->s.weapon == WP_SNIPERRIFLE || ent->s.weapon == WP_FG42SCOPE ) {
870 // if(ent->s.weapon == WP_SNOOPERSCOPE || ent->s.weapon == WP_SNIPERRIFLE) {
871 // aim dir already accounted for sway of scoped weapons in CalcMuzzlePoints()
872 dist *= 2;
873 randSpread = qfalse;
874 }
875 }
876
877 VectorMA( muzzleTrace, dist, forward, *end );
878
879 if ( randSpread ) {
880 VectorMA( *end, r, right, *end );
881 VectorMA( *end, u, up, *end );
882 }
883 }
884
885 /*
886 ==============
887 Bullet_Fire
888 ==============
889 */
Bullet_Fire(gentity_t * ent,float spread,int damage)890 void Bullet_Fire( gentity_t *ent, float spread, int damage ) {
891 vec3_t end;
892
893 Bullet_Endpos( ent, spread, &end );
894 Bullet_Fire_Extended( ent, ent, muzzleTrace, end, spread, damage, 0 );
895 }
896
897
898 /*
899 ==============
900 Bullet_Fire_Extended
901 A modified Bullet_Fire with more parameters.
902 The original Bullet_Fire still passes through here and functions as it always has.
903
904 uses for this include shooting through entities (windows, doors, other players, etc.) and reflecting bullets
905 ==============
906 */
Bullet_Fire_Extended(gentity_t * source,gentity_t * attacker,vec3_t start,vec3_t end,float spread,int damage,int recursion)907 void Bullet_Fire_Extended( gentity_t *source, gentity_t *attacker, vec3_t start, vec3_t end, float spread, int damage, int recursion ) {
908 trace_t tr;
909 gentity_t *tent;
910 gentity_t *traceEnt;
911 int dflags = 0; // flag if source==attacker, meaning it wasn't shot directly, but was reflected went through an entity that allows bullets to pass through
912 qboolean reflectBullet = qfalse;
913
914 // RF, abort if too many recursions.. there must be a real solution for this, but for now this is the safest
915 // fix I can find
916 if ( recursion > 12 ) {
917 return;
918 }
919
920 damage *= s_quadFactor;
921
922 if ( source != attacker ) {
923 dflags = DAMAGE_PASSTHRU;
924 }
925
926 // (SA) changed so player could shoot his own dynamite.
927 // (SA) whoops, but that broke bullets going through explosives...
928 trap_Trace( &tr, start, NULL, NULL, end, source->s.number, MASK_SHOT );
929 // trap_Trace (&tr, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT);
930
931 // DHM - Nerve :: only in single player
932 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
933 AICast_ProcessBullet( attacker, start, tr.endpos );
934 }
935
936 // bullet debugging using Q3A's railtrail
937 if ( g_debugBullets.integer & 1 ) {
938 tent = G_TempEntity( start, EV_RAILTRAIL );
939 VectorCopy( tr.endpos, tent->s.origin2 );
940 tent->s.otherEntityNum2 = attacker->s.number;
941 }
942
943
944 RubbleFlagCheck( attacker, tr );
945
946 traceEnt = &g_entities[ tr.entityNum ];
947
948 EmitterCheck( traceEnt, attacker, &tr );
949
950 // snap the endpos to integers, but nudged towards the line
951 SnapVectorTowards( tr.endpos, start );
952
953 // should we reflect this bullet?
954 if ( traceEnt->flags & FL_DEFENSE_GUARD ) {
955 reflectBullet = qtrue;
956 } else if ( traceEnt->flags & FL_DEFENSE_CROUCH ) {
957 if ( rand() % 3 < 2 ) {
958 reflectBullet = qtrue;
959 }
960 }
961
962 // send bullet impact
963 if ( traceEnt->takedamage && traceEnt->client && !reflectBullet ) {
964 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
965 tent->s.eventParm = traceEnt->s.number;
966 if ( LogAccuracyHit( traceEnt, attacker ) ) {
967 attacker->client->ps.persistant[PERS_ACCURACY_HITS]++;
968 }
969
970 //----(SA) added
971 if ( g_debugBullets.integer >= 2 ) { // show hit player bb
972 gentity_t *bboxEnt;
973 vec3_t b1, b2;
974 VectorCopy( traceEnt->r.currentOrigin, b1 );
975 VectorCopy( traceEnt->r.currentOrigin, b2 );
976 VectorAdd( b1, traceEnt->r.mins, b1 );
977 VectorAdd( b2, traceEnt->r.maxs, b2 );
978 bboxEnt = G_TempEntity( b1, EV_RAILTRAIL );
979 VectorCopy( b2, bboxEnt->s.origin2 );
980 bboxEnt->s.dmgFlags = 1; // ("type")
981 }
982 //----(SA) end
983
984 } else if ( traceEnt->takedamage && traceEnt->s.eType == ET_BAT ) {
985 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
986 tent->s.eventParm = traceEnt->s.number;
987 } else {
988 // Ridah, bullet impact should reflect off surface
989 vec3_t reflect;
990 float dot;
991
992 if ( g_debugBullets.integer <= -2 ) { // show hit thing bb
993 gentity_t *bboxEnt;
994 vec3_t b1, b2;
995 VectorCopy( traceEnt->r.currentOrigin, b1 );
996 VectorCopy( traceEnt->r.currentOrigin, b2 );
997 VectorAdd( b1, traceEnt->r.mins, b1 );
998 VectorAdd( b2, traceEnt->r.maxs, b2 );
999 bboxEnt = G_TempEntity( b1, EV_RAILTRAIL );
1000 VectorCopy( b2, bboxEnt->s.origin2 );
1001 bboxEnt->s.dmgFlags = 1; // ("type")
1002 }
1003
1004 if ( reflectBullet ) {
1005 // reflect off sheild
1006 VectorSubtract( tr.endpos, traceEnt->r.currentOrigin, reflect );
1007 VectorNormalize( reflect );
1008 VectorMA( traceEnt->r.currentOrigin, 15, reflect, reflect );
1009 tent = G_TempEntity( reflect, EV_BULLET_HIT_WALL );
1010 } else {
1011 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
1012 }
1013
1014 dot = DotProduct( forward, tr.plane.normal );
1015 VectorMA( forward, -2 * dot, tr.plane.normal, reflect );
1016 VectorNormalize( reflect );
1017
1018 tent->s.eventParm = DirToByte( reflect );
1019
1020 if ( reflectBullet ) {
1021 tent->s.otherEntityNum2 = traceEnt->s.number; // force sparks
1022 } else {
1023 tent->s.otherEntityNum2 = ENTITYNUM_NONE;
1024 }
1025 // done.
1026 }
1027 tent->s.otherEntityNum = attacker->s.number;
1028
1029 if ( traceEnt->takedamage ) {
1030 qboolean reflectBool = qfalse;
1031 vec3_t trDir;
1032
1033 if ( reflectBullet ) {
1034 // if we are facing the direction the bullet came from, then reflect it
1035 AngleVectors( traceEnt->s.apos.trBase, trDir, NULL, NULL );
1036 if ( DotProduct( forward, trDir ) < 0.6 ) {
1037 reflectBool = qtrue;
1038 }
1039 }
1040
1041 if ( reflectBool ) {
1042 vec3_t reflect_end;
1043 // reflect this bullet
1044 G_AddEvent( traceEnt, EV_GENERAL_SOUND, level.bulletRicochetSound );
1045 CalcMuzzlePoints( traceEnt, traceEnt->s.weapon );
1046
1047 //----(SA) modified to use extended version so attacker would pass through
1048 // Bullet_Fire( traceEnt, 1000, damage );
1049 Bullet_Endpos( traceEnt, 2800, &reflect_end ); // make it inaccurate
1050 Bullet_Fire_Extended( traceEnt, attacker, muzzleTrace, reflect_end, spread, damage, recursion + 1 );
1051 //----(SA) end
1052
1053 } else {
1054
1055 // Ridah, don't hurt team-mates
1056 // DHM - Nerve :: Only in single player
1057 if ( attacker->client && traceEnt->client && g_gametype.integer == GT_SINGLE_PLAYER && ( traceEnt->r.svFlags & SVF_CASTAI ) && ( attacker->r.svFlags & SVF_CASTAI ) && AICast_SameTeam( AICast_GetCastState( attacker->s.number ), traceEnt->s.number ) ) {
1058 // AI's don't hurt members of their own team
1059 return;
1060 }
1061 // done.
1062
1063 G_Damage( traceEnt, attacker, attacker, forward, tr.endpos, damage, dflags, ammoTable[attacker->s.weapon].mod );
1064
1065 // allow bullets to "pass through" func_explosives if they break by taking another simultanious shot
1066 // start new bullet at position this hit and continue to the end position (ignoring shot-through ent in next trace)
1067 // spread = 0 as this is an extension of an already spread shot (so just go straight through)
1068 if ( Q_stricmp( traceEnt->classname, "func_explosive" ) == 0 ) {
1069 if ( traceEnt->health <= 0 ) {
1070 Bullet_Fire_Extended( traceEnt, attacker, tr.endpos, end, 0, damage, recursion + 1 );
1071 }
1072 } else if ( traceEnt->client ) {
1073 if ( traceEnt->health <= 0 ) {
1074 Bullet_Fire_Extended( traceEnt, attacker, tr.endpos, end, 0, damage / 2, recursion + 1 ); // halve the damage each player it goes through
1075 }
1076 }
1077 }
1078 }
1079 }
1080
1081
1082
1083 /*
1084 ======================================================================
1085
1086 GRENADE LAUNCHER
1087
1088 700 has been the standard direction multiplier in fire_grenade()
1089
1090 ======================================================================
1091 */
1092 extern void G_ExplodeMissilePoisonGas( gentity_t *ent );
1093
weapon_crowbar_throw(gentity_t * ent)1094 gentity_t *weapon_crowbar_throw( gentity_t *ent ) {
1095 gentity_t *m;
1096
1097 m = fire_crowbar( ent, muzzleEffect, forward );
1098 m->damage *= s_quadFactor;
1099 m->splashDamage *= s_quadFactor;
1100
1101 return m;
1102 }
1103
weapon_grenadelauncher_fire(gentity_t * ent,int grenType)1104 gentity_t *weapon_grenadelauncher_fire( gentity_t *ent, int grenType ) {
1105 gentity_t *m, *te; // JPW NERVE
1106 float upangle = 0; // start with level throwing and adjust based on angle
1107 vec3_t tosspos;
1108 qboolean underhand = 0;
1109
1110 if ( ( ent->s.apos.trBase[0] > 0 ) && ( grenType != WP_GRENADE_SMOKE ) ) { // JPW NERVE -- smoke grenades always overhand
1111 underhand = qtrue;
1112 }
1113
1114 if ( underhand ) {
1115 forward[2] = 0; // start the toss level for underhand
1116 } else {
1117 forward[2] += 0.2; // extra vertical velocity for overhand
1118
1119 }
1120 VectorNormalize( forward ); // make sure forward is normalized
1121
1122 upangle = -( ent->s.apos.trBase[0] ); // this will give between -90 / 90
1123 upangle = min( upangle, 50 );
1124 upangle = max( upangle, -50 ); // now clamped to -50 / 50 (don't allow firing straight up/down)
1125 upangle = upangle / 100.0f; // -0.5 / 0.5
1126 upangle += 0.5f; // 0.0 / 1.0
1127
1128 if ( upangle < .1 ) {
1129 upangle = .1;
1130 }
1131
1132 // pineapples are not thrown as far as mashers
1133 if ( grenType == WP_GRENADE_LAUNCHER ) {
1134 upangle *= 800; // 0.0 / 800.0
1135 } else if ( grenType == WP_GRENADE_PINEAPPLE ) {
1136 // JPW NERVE
1137 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1138 upangle *= 800;
1139 } else {
1140 // jpw
1141 upangle *= 600; // 0.0 / 600.0
1142 }
1143 }
1144 // JPW NERVE
1145 else if ( grenType == WP_GRENADE_SMOKE ) { // smoke grenades *really* get chucked
1146 upangle *= 800;
1147 }
1148 // jpw
1149 else { // WP_DYNAMITE
1150 upangle *= 400; // 0.0 / 100.0
1151
1152 }
1153 /*
1154 if(ent->aiCharacter)
1155 {
1156 VectorScale(forward, 700, forward); //----(SA) 700 is the default grenade throw they are already used to
1157 m = fire_grenade (ent, muzzleTrace, forward); //----(SA) temp to make AI's throw grenades at their actual target
1158 }
1159 else
1160 */
1161
1162
1163
1164 {
1165 VectorCopy( muzzleEffect, tosspos );
1166 if ( underhand ) {
1167 VectorMA( muzzleEffect, 15, forward, tosspos ); // move a little bit more away from the player (so underhand tosses don't get caught on nearby lips)
1168 tosspos[2] -= 24; // lower origin for the underhand throw
1169 upangle *= 1.3; // a little more force to counter the lower position / lack of additional lift
1170 }
1171
1172 VectorScale( forward, upangle, forward );
1173
1174
1175 {
1176 // check for valid start spot (so you don't throw through or get stuck in a wall)
1177 trace_t tr;
1178 vec3_t viewpos;
1179
1180 VectorCopy( ent->s.pos.trBase, viewpos );
1181 viewpos[2] += ent->client->ps.viewheight;
1182
1183 trap_Trace( &tr, viewpos, NULL, NULL, tosspos, ent->s.number, MASK_SHOT );
1184 if ( tr.fraction < 1 ) { // oops, bad launch spot
1185 VectorCopy( tr.endpos, tosspos );
1186 }
1187 }
1188
1189
1190 m = fire_grenade( ent, tosspos, forward, grenType );
1191 }
1192
1193
1194 //m->damage *= s_quadFactor;
1195 m->damage = 0; // Ridah, grenade's don't explode on contact
1196 m->splashDamage *= s_quadFactor;
1197
1198 if ( ent->aiCharacter == AICHAR_VENOM ) { // poison gas grenade
1199 m->think = G_ExplodeMissilePoisonGas;
1200 m->s.density = 1;
1201 }
1202
1203 // JPW NERVE
1204 if ( grenType == WP_GRENADE_SMOKE ) {
1205 if ( ent->client->sess.sessionTeam == TEAM_RED ) { // store team so we can generate red or blue smoke
1206 m->s.otherEntityNum2 = 1;
1207 } else {
1208 m->s.otherEntityNum2 = 0;
1209 }
1210 m->nextthink = level.time + 4000;
1211 m->think = weapon_callAirStrike;
1212
1213 te = G_TempEntity( m->s.pos.trBase, EV_GLOBAL_SOUND );
1214 te->s.eventParm = G_SoundIndex( "sound/scenaric/forest/me109_flight.wav" );
1215 // te->r.svFlags |= SVF_BROADCAST | SVF_USE_CURRENT_ORIGIN;
1216 }
1217 // jpw
1218
1219 //----(SA) adjust for movement of character. TODO: Probably comment in later, but only for forward/back not strafing
1220 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
1221
1222 // let the AI know which grenade it has fired
1223 ent->grenadeFired = m->s.number;
1224
1225 // Ridah, return the grenade so we can do some prediction before deciding if we really want to throw it or not
1226 return m;
1227 }
1228
1229 /*
1230 =====================
1231 Zombie spit
1232 =====================
1233 */
weapon_zombiespit(gentity_t * ent)1234 void weapon_zombiespit( gentity_t *ent ) {
1235 return;
1236 #if 0 //RF, HARD disable
1237 gentity_t *m;
1238
1239 m = fire_zombiespit( ent, muzzleTrace, forward );
1240 m->damage *= s_quadFactor;
1241 m->splashDamage *= s_quadFactor;
1242
1243 if ( m ) {
1244 G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/Loogie/spit.wav" ) );
1245 }
1246 #endif
1247 }
1248
1249 /*
1250 =====================
1251 Zombie spirit
1252 =====================
1253 */
weapon_zombiespirit(gentity_t * ent,gentity_t * missile)1254 void weapon_zombiespirit( gentity_t *ent, gentity_t *missile ) {
1255 gentity_t *m;
1256
1257 m = fire_zombiespirit( ent, missile, muzzleTrace, forward );
1258 m->damage *= s_quadFactor;
1259 m->splashDamage *= s_quadFactor;
1260
1261 if ( m ) {
1262 G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "zombieAttackPlayer" ) );
1263 }
1264
1265 }
1266
1267 //----(SA) modified this entire "venom" section
1268 /*
1269 ============================================================================
1270
1271 VENOM GUN TRACING
1272
1273 ============================================================================
1274 */
1275 #define DEFAULT_VENOM_COUNT 10
1276 #define DEFAULT_VENOM_SPREAD 20
1277 #define DEFAULT_VENOM_DAMAGE 15
1278
VenomPellet(vec3_t start,vec3_t end,gentity_t * ent)1279 qboolean VenomPellet( vec3_t start, vec3_t end, gentity_t *ent ) {
1280 trace_t tr;
1281 int damage;
1282 gentity_t *traceEnt;
1283
1284 trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT );
1285 traceEnt = &g_entities[ tr.entityNum ];
1286
1287 // send bullet impact
1288 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
1289 return qfalse;
1290 }
1291
1292 if ( traceEnt->takedamage ) {
1293 damage = DEFAULT_VENOM_DAMAGE * s_quadFactor;
1294
1295 G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_VENOM );
1296 if ( LogAccuracyHit( traceEnt, ent ) ) {
1297 return qtrue;
1298 }
1299 }
1300 return qfalse;
1301 }
1302
1303 // this should match CG_VenomPattern
VenomPattern(vec3_t origin,vec3_t origin2,int seed,gentity_t * ent)1304 void VenomPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) {
1305 int i;
1306 float r, u;
1307 vec3_t end;
1308 vec3_t forward, right, up;
1309 qboolean hitClient = qfalse;
1310
1311 // derive the right and up vectors from the forward vector, because
1312 // the client won't have any other information
1313 VectorNormalize2( origin2, forward );
1314 PerpendicularVector( right, forward );
1315 CrossProduct( forward, right, up );
1316
1317 // generate the "random" spread pattern
1318 for ( i = 0 ; i < DEFAULT_VENOM_COUNT ; i++ ) {
1319 r = Q_crandom( &seed ) * DEFAULT_VENOM_SPREAD;
1320 u = Q_crandom( &seed ) * DEFAULT_VENOM_SPREAD;
1321 VectorMA( origin, 8192, forward, end );
1322 VectorMA( end, r, right, end );
1323 VectorMA( end, u, up, end );
1324 if ( VenomPellet( origin, end, ent ) && !hitClient ) {
1325 hitClient = qtrue;
1326 ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
1327 }
1328 }
1329 }
1330
1331
1332
1333 /*
1334 ==============
1335 weapon_venom_fire
1336 ==============
1337 */
weapon_venom_fire(gentity_t * ent,qboolean fullmode,float aimSpreadScale)1338 void weapon_venom_fire( gentity_t *ent, qboolean fullmode, float aimSpreadScale ) {
1339 gentity_t *tent;
1340
1341 if ( fullmode ) {
1342 tent = G_TempEntity( muzzleTrace, EV_VENOMFULL );
1343 } else {
1344 tent = G_TempEntity( muzzleTrace, EV_VENOM );
1345 }
1346
1347 VectorScale( forward, 4096, tent->s.origin2 );
1348 SnapVector( tent->s.origin2 );
1349 tent->s.eventParm = rand() & 255; // seed for spread pattern
1350 tent->s.otherEntityNum = ent->s.number;
1351
1352 if ( fullmode ) {
1353 VenomPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
1354 } else
1355 {
1356 int dam;
1357 dam = VENOM_DAMAGE;
1358 if ( ent->aiCharacter ) { // venom guys are /vicious/
1359 dam *= 0.5f;
1360 }
1361 Bullet_Fire( ent, VENOM_SPREAD * aimSpreadScale, dam );
1362 }
1363 }
1364
1365
1366
1367
1368
1369 /*
1370 ======================================================================
1371
1372 ROCKET
1373
1374 ======================================================================
1375 */
1376
Weapon_RocketLauncher_Fire(gentity_t * ent,float aimSpreadScale)1377 void Weapon_RocketLauncher_Fire( gentity_t *ent, float aimSpreadScale ) {
1378 // trace_t tr;
1379 float r, u;
1380 vec3_t dir, launchpos; //, viewpos, wallDir;
1381 gentity_t *m;
1382
1383 // get a little bit of randomness and apply it back to the direction
1384 if ( !ent->aiCharacter ) {
1385 r = crandom() * aimSpreadScale;
1386 u = crandom() * aimSpreadScale;
1387
1388 VectorScale( forward, 16, dir );
1389 VectorMA( dir, r, right, dir );
1390 VectorMA( dir, u, up, dir );
1391 VectorNormalize( dir );
1392
1393 VectorCopy( muzzleEffect, launchpos );
1394
1395 // check for valid start spot (so you don't lose it in a wall)
1396 // (doesn't ever happen)
1397 // VectorCopy( ent->s.pos.trBase, viewpos );
1398 // viewpos[2] += ent->client->ps.viewheight;
1399 // trap_Trace (&tr, viewpos, NULL, NULL, muzzleEffect, ent->s.number, MASK_SHOT);
1400 // if(tr.fraction < 1) { // oops, bad launch spot
1401 /// VectorCopy(tr.endpos, launchpos);
1402 // VectorSubtract(tr.endpos, viewpos, wallDir);
1403 // VectorNormalize(wallDir);
1404 // VectorMA(tr.endpos, -5, wallDir, launchpos);
1405 // }
1406
1407 m = fire_rocket( ent, launchpos, dir );
1408
1409 // add kick-back
1410 VectorMA( ent->client->ps.velocity, -64, forward, ent->client->ps.velocity );
1411
1412 } else {
1413 m = fire_rocket( ent, muzzleEffect, forward );
1414 }
1415
1416 m->damage *= s_quadFactor;
1417 m->splashDamage *= s_quadFactor;
1418
1419 // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
1420 }
1421
1422
1423
1424
1425 /*
1426 ======================================================================
1427
1428 LIGHTNING GUN
1429
1430 ======================================================================
1431 */
1432
1433 // RF, not used anymore for Flamethrower (still need it for tesla?)
Weapon_LightningFire(gentity_t * ent)1434 void Weapon_LightningFire( gentity_t *ent ) {
1435 trace_t tr;
1436 vec3_t end;
1437 gentity_t *traceEnt;
1438 int damage;
1439
1440 damage = 5 * s_quadFactor;
1441
1442 VectorMA( muzzleTrace, LIGHTNING_RANGE, forward, end );
1443
1444 trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT );
1445
1446 if ( tr.entityNum == ENTITYNUM_NONE ) {
1447 return;
1448 }
1449
1450 traceEnt = &g_entities[ tr.entityNum ];
1451 /*
1452 if ( traceEnt->takedamage && traceEnt->client ) {
1453 tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
1454 tent->s.otherEntityNum = traceEnt->s.number;
1455 tent->s.eventParm = DirToByte( tr.plane.normal );
1456 tent->s.weapon = ent->s.weapon;
1457 if( LogAccuracyHit( traceEnt, ent ) ) {
1458 ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
1459 }
1460 } else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
1461 tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
1462 tent->s.eventParm = DirToByte( tr.plane.normal );
1463 }
1464 */
1465 if ( traceEnt->takedamage && !AICast_NoFlameDamage( traceEnt->s.number ) ) {
1466 #define FLAME_THRESHOLD 50
1467
1468 // RF, only do damage once they start burning
1469 //if (traceEnt->health > 0) // don't explode from flamethrower
1470 // G_Damage( traceEnt, ent, ent, forward, tr.endpos, 1, 0, MOD_LIGHTNING);
1471
1472 // now check the damageQuota to see if we should play a pain animation
1473 // first reduce the current damageQuota with time
1474 if ( traceEnt->flameQuotaTime && traceEnt->flameQuota > 0 ) {
1475 traceEnt->flameQuota -= (int)( ( (float)( level.time - traceEnt->flameQuotaTime ) / 1000 ) * (float)damage / 2.0 );
1476 if ( traceEnt->flameQuota < 0 ) {
1477 traceEnt->flameQuota = 0;
1478 }
1479 }
1480
1481 // add the new damage
1482 traceEnt->flameQuota += damage;
1483 traceEnt->flameQuotaTime = level.time;
1484
1485 // Ridah, make em burn
1486 if ( traceEnt->client && ( traceEnt->health <= 0 || traceEnt->flameQuota > FLAME_THRESHOLD ) ) {
1487 if ( traceEnt->s.onFireEnd < level.time ) {
1488 traceEnt->s.onFireStart = level.time;
1489 }
1490 if ( traceEnt->health <= 0 || !( traceEnt->r.svFlags & SVF_CASTAI ) || ( g_gametype.integer != GT_SINGLE_PLAYER ) ) {
1491 if ( traceEnt->r.svFlags & SVF_CASTAI ) {
1492 traceEnt->s.onFireEnd = level.time + 6000;
1493 } else {
1494 traceEnt->s.onFireEnd = level.time + FIRE_FLASH_TIME;
1495 }
1496 } else {
1497 traceEnt->s.onFireEnd = level.time + 99999; // make sure it goes for longer than they need to die
1498 }
1499 traceEnt->flameBurnEnt = ent->s.number;
1500 // add to playerState for client-side effect
1501 traceEnt->client->ps.onFireStart = level.time;
1502 }
1503 }
1504 }
1505
1506 //======================================================================
1507
1508
1509 /*
1510 ==============
1511 AddLean
1512 add leaning offset
1513 ==============
1514 */
AddLean(gentity_t * ent,vec3_t point)1515 void AddLean( gentity_t *ent, vec3_t point ) {
1516 if ( ent->client ) {
1517 if ( ent->client->ps.leanf ) {
1518 vec3_t right;
1519 AngleVectors( ent->client->ps.viewangles, NULL, right, NULL );
1520 VectorMA( point, ent->client->ps.leanf, right, point );
1521 }
1522 }
1523 }
1524
1525 /*
1526 ===============
1527 LogAccuracyHit
1528 ===============
1529 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)1530 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
1531 if ( !target->takedamage ) {
1532 return qfalse;
1533 }
1534
1535 if ( target == attacker ) {
1536 return qfalse;
1537 }
1538
1539 if ( !target->client ) {
1540 return qfalse;
1541 }
1542
1543 if ( !attacker->client ) {
1544 return qfalse;
1545 }
1546
1547 if ( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
1548 return qfalse;
1549 }
1550
1551 if ( OnSameTeam( target, attacker ) ) {
1552 return qfalse;
1553 }
1554
1555 return qtrue;
1556 }
1557
1558
1559 /*
1560 ===============
1561 CalcMuzzlePoint
1562
1563 set muzzle location relative to pivoting eye
1564 ===============
1565 */
CalcMuzzlePoint(gentity_t * ent,int weapon,vec3_t forward,vec3_t right,vec3_t up,vec3_t muzzlePoint)1566 void CalcMuzzlePoint( gentity_t *ent, int weapon, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
1567 VectorCopy( ent->r.currentOrigin, muzzlePoint );
1568 muzzlePoint[2] += ent->client->ps.viewheight;
1569 // Ridah, this puts the start point outside the bounding box, isn't necessary
1570 // VectorMA( muzzlePoint, 14, forward, muzzlePoint );
1571 // done.
1572
1573 // Ridah, offset for more realistic firing from actual gun position
1574 //----(SA) modified
1575 switch ( weapon ) // Ridah, changed this so I can predict weapons
1576 {
1577 case WP_PANZERFAUST:
1578 // VectorMA( muzzlePoint, 14, right, muzzlePoint ); //----(SA) new first person rl position
1579 VectorMA( muzzlePoint, 10, right, muzzlePoint ); //----(SA) new first person rl position
1580 VectorMA( muzzlePoint, -10, up, muzzlePoint );
1581 break;
1582 // case WP_ROCKET_LAUNCHER:
1583 // VectorMA( muzzlePoint, 14, right, muzzlePoint ); //----(SA) new first person rl position
1584 // break;
1585 case WP_DYNAMITE:
1586 case WP_GRENADE_PINEAPPLE:
1587 case WP_GRENADE_LAUNCHER:
1588 VectorMA( muzzlePoint, 20, right, muzzlePoint );
1589 break;
1590 case WP_AKIMBO: // left side rather than right
1591 VectorMA( muzzlePoint, -6, right, muzzlePoint );
1592 VectorMA( muzzlePoint, -4, up, muzzlePoint );
1593 default:
1594 VectorMA( muzzlePoint, 6, right, muzzlePoint );
1595 VectorMA( muzzlePoint, -4, up, muzzlePoint );
1596 break;
1597 }
1598
1599 // done.
1600
1601 // (SA) actually, this is sort of moot right now since
1602 // you're not allowed to fire when leaning. Leave in
1603 // in case we decide to enable some lean-firing.
1604 AddLean( ent, muzzlePoint );
1605
1606 // snap to integer coordinates for more efficient network bandwidth usage
1607 SnapVector( muzzlePoint );
1608 }
1609
1610 // Rafael - for activate
CalcMuzzlePointForActivate(gentity_t * ent,vec3_t forward,vec3_t right,vec3_t up,vec3_t muzzlePoint)1611 void CalcMuzzlePointForActivate( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
1612
1613 VectorCopy( ent->s.pos.trBase, muzzlePoint );
1614 muzzlePoint[2] += ent->client->ps.viewheight;
1615
1616 AddLean( ent, muzzlePoint );
1617
1618 // snap to integer coordinates for more efficient network bandwidth usage
1619 // SnapVector( muzzlePoint );
1620 // (SA) /\ only used server-side, so leaving the accuracy in is fine (and means things that show a cursorhint will be hit when activated)
1621 // (there were differing views of activatable stuff between cursorhint and activatable)
1622 }
1623 // done.
1624
1625 // Ridah
CalcMuzzlePoints(gentity_t * ent,int weapon)1626 void CalcMuzzlePoints( gentity_t *ent, int weapon ) {
1627 vec3_t viewang;
1628
1629 VectorCopy( ent->client->ps.viewangles, viewang );
1630
1631 if ( !( ent->r.svFlags & SVF_CASTAI ) ) { // non ai's take into account scoped weapon 'sway' (just another way aimspread is visualized/utilized)
1632 float spreadfrac, phase;
1633
1634 if ( weapon == WP_SNIPERRIFLE || weapon == WP_SNOOPERSCOPE || weapon == WP_FG42SCOPE ) {
1635 spreadfrac = ent->client->currentAimSpreadScale;
1636
1637 // rotate 'forward' vector by the sway
1638 phase = level.time / 1000.0 * ZOOM_PITCH_FREQUENCY * M_PI * 2;
1639 viewang[PITCH] += ZOOM_PITCH_AMPLITUDE * sin( phase ) * ( spreadfrac + ZOOM_PITCH_MIN_AMPLITUDE );
1640
1641 phase = level.time / 1000.0 * ZOOM_YAW_FREQUENCY * M_PI * 2;
1642 viewang[YAW] += ZOOM_YAW_AMPLITUDE * sin( phase ) * ( spreadfrac + ZOOM_YAW_MIN_AMPLITUDE );
1643 }
1644 }
1645
1646
1647 // set aiming directions
1648 AngleVectors( viewang, forward, right, up );
1649
1650 //----(SA) modified the muzzle stuff so that weapons that need to fire down a perfect trace
1651 // straight out of the camera (SP5, Mauser right now) can have that accuracy, but
1652 // weapons that need an offset effect (bazooka/grenade/etc.) can still look like
1653 // they came out of the weap.
1654 CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace );
1655 CalcMuzzlePoint( ent, weapon, forward, right, up, muzzleEffect );
1656 }
1657
1658 /*
1659 ===============
1660 FireWeapon
1661 ===============
1662 */
FireWeapon(gentity_t * ent)1663 void FireWeapon( gentity_t *ent ) {
1664 float aimSpreadScale;
1665 vec3_t viewang; // JPW NERVE
1666
1667 // Rafael mg42
1668 //if (ent->active)
1669 // return;
1670 if ( ent->client->ps.persistant[PERS_HWEAPON_USE] && ent->active ) {
1671 return;
1672 }
1673
1674 if ( ent->client->ps.powerups[PW_QUAD] ) {
1675 s_quadFactor = g_quadfactor.value;
1676 } else {
1677 s_quadFactor = 1;
1678 }
1679
1680 // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked
1681 //----(SA) removing old weapon references
1682 // if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) {
1683 // ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
1684 // }
1685
1686 // Ridah, need to call this for AI prediction also
1687 CalcMuzzlePoints( ent, ent->s.weapon );
1688
1689 if ( g_userAim.integer ) {
1690 aimSpreadScale = ent->client->currentAimSpreadScale;
1691 // Ridah, add accuracy factor for AI
1692 if ( ent->aiCharacter ) {
1693 float aim_accuracy;
1694 aim_accuracy = AICast_GetAccuracy( ent->s.number );
1695 if ( aim_accuracy <= 0 ) {
1696 aim_accuracy = 0.0001;
1697 }
1698 aimSpreadScale = ( 1.0 - aim_accuracy ) * 2.0;
1699 } else {
1700 // /maximum/ accuracy for player for a given weapon
1701 switch ( ent->s.weapon ) {
1702 case WP_LUGER:
1703 case WP_SILENCER:
1704 case WP_COLT:
1705 case WP_AKIMBO:
1706 aimSpreadScale += 0.4f;
1707 break;
1708
1709 case WP_PANZERFAUST:
1710 aimSpreadScale += 0.3f; // it's calculated a different way, so this keeps the accuracy never perfect, but never rediculously wild either
1711 break;
1712
1713 default:
1714 aimSpreadScale += 0.15f;
1715 break;
1716 }
1717
1718 if ( aimSpreadScale > 1 ) {
1719 aimSpreadScale = 1.0f; // still cap at 1.0
1720 }
1721 }
1722 } else {
1723 aimSpreadScale = 1.0;
1724 }
1725
1726 // fire the specific weapon
1727 switch ( ent->s.weapon ) {
1728 case WP_KNIFE:
1729 Weapon_Knife( ent );
1730 break;
1731 // JPW NERVE
1732 case WP_CLASS_SPECIAL:
1733 Weapon_Class_Special( ent );
1734 break;
1735 // jpw
1736 break;
1737 case WP_LUGER:
1738 Bullet_Fire( ent, LUGER_SPREAD * aimSpreadScale, LUGER_DAMAGE );
1739 break;
1740 case WP_SILENCER:
1741 Bullet_Fire( ent, SILENCER_SPREAD * aimSpreadScale, LUGER_DAMAGE );
1742 break;
1743 case WP_AKIMBO: //----(SA) added
1744 case WP_COLT:
1745 Bullet_Fire( ent, COLT_SPREAD * aimSpreadScale, COLT_DAMAGE );
1746 break;
1747 case WP_VENOM:
1748 weapon_venom_fire( ent, qfalse, aimSpreadScale );
1749 break;
1750 case WP_SNIPERRIFLE:
1751 Bullet_Fire( ent, SNIPER_SPREAD * aimSpreadScale, SNIPER_DAMAGE );
1752 // JPW NERVE -- added muzzle flip in multiplayer
1753 if ( !ent->aiCharacter ) {
1754 // if (g_gametype.integer != GT_SINGLE_PLAYER) {
1755 VectorCopy( ent->client->ps.viewangles,viewang );
1756 // viewang[PITCH] -= 6; // handled in clientthink instead
1757 ent->client->sniperRifleMuzzleYaw = crandom() * 0.5; // used in clientthink
1758 ent->client->sniperRifleMuzzlePitch = 0.8f;
1759 ent->client->sniperRifleFiredTime = level.time;
1760 SetClientViewAngle( ent,viewang );
1761 }
1762 // jpw
1763 break;
1764 case WP_SNOOPERSCOPE:
1765 Bullet_Fire( ent, SNOOPER_SPREAD * aimSpreadScale, SNOOPER_DAMAGE );
1766 // JPW NERVE -- added muzzle flip in multiplayer
1767 if ( !ent->aiCharacter ) {
1768 // if (g_gametype.integer != GT_SINGLE_PLAYER) {
1769 VectorCopy( ent->client->ps.viewangles,viewang );
1770 ent->client->sniperRifleMuzzleYaw = crandom() * 0.5; // used in clientthink
1771 ent->client->sniperRifleMuzzlePitch = 0.9f;
1772 ent->client->sniperRifleFiredTime = level.time;
1773 SetClientViewAngle( ent,viewang );
1774 }
1775 // jpw
1776 break;
1777 case WP_MAUSER:
1778 Bullet_Fire( ent, MAUSER_SPREAD * aimSpreadScale, MAUSER_DAMAGE );
1779 break;
1780 case WP_GARAND:
1781 Bullet_Fire( ent, GARAND_SPREAD * aimSpreadScale, GARAND_DAMAGE );
1782 break;
1783 //----(SA) added
1784 case WP_FG42SCOPE:
1785 if ( !ent->aiCharacter ) {
1786 // if (g_gametype.integer != GT_SINGLE_PLAYER) {
1787 VectorCopy( ent->client->ps.viewangles,viewang );
1788 // ent->client->sniperRifleMuzzleYaw = crandom()*0.04; // used in clientthink
1789 ent->client->sniperRifleMuzzleYaw = 0;
1790 ent->client->sniperRifleMuzzlePitch = 0.07f;
1791 ent->client->sniperRifleFiredTime = level.time;
1792 SetClientViewAngle( ent,viewang );
1793 }
1794 case WP_FG42:
1795 Bullet_Fire( ent, FG42_SPREAD * aimSpreadScale, FG42_DAMAGE );
1796 break;
1797 //----(SA) end
1798 case WP_STEN:
1799 Bullet_Fire( ent, STEN_SPREAD * aimSpreadScale, STEN_DAMAGE );
1800 break;
1801 case WP_MP40:
1802 Bullet_Fire( ent, MP40_SPREAD * aimSpreadScale, MP40_DAMAGE );
1803 break;
1804 case WP_THOMPSON:
1805 Bullet_Fire( ent, THOMPSON_SPREAD * aimSpreadScale, THOMPSON_DAMAGE );
1806 break;
1807 case WP_PANZERFAUST:
1808 ent->client->ps.classWeaponTime = level.time; // JPW NERVE
1809 Weapon_RocketLauncher_Fire( ent, aimSpreadScale );
1810 break;
1811 case WP_GRENADE_LAUNCHER:
1812 case WP_GRENADE_PINEAPPLE:
1813 case WP_DYNAMITE:
1814 // weapon_grenadelauncher_fire( ent, ent->s.weapon );
1815 //RF- disabled this since it's broken (do we still want it?)
1816 //if (ent->aiName && !Q_strncmp(ent->aiName, "mechanic", 8) && !AICast_HasFiredWeapon(ent->s.number, ent->s.weapon))
1817 // weapon_crowbar_throw (ent);
1818 //else
1819 if ( ent->s.weapon == WP_DYNAMITE ) {
1820 ent->client->ps.classWeaponTime = level.time; // JPW NERVE
1821 }
1822 weapon_grenadelauncher_fire( ent, ent->s.weapon );
1823 break;
1824 case WP_FLAMETHROWER:
1825 // RF, this is done client-side only now
1826 //Weapon_LightningFire( ent );
1827 break;
1828 case WP_TESLA:
1829 if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE
1830 Tesla_Fire( ent );
1831 }
1832
1833 // push the player back a bit
1834 if ( !ent->aiCharacter ) {
1835 vec3_t forward, vangle;
1836 VectorCopy( ent->client->ps.viewangles, vangle );
1837 vangle[PITCH] = 0; // nullify pitch so you can't lightning jump
1838 AngleVectors( vangle, forward, NULL, NULL );
1839 // make it less if in the air
1840 if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) {
1841 VectorMA( ent->client->ps.velocity, -32, forward, ent->client->ps.velocity );
1842 } else {
1843 VectorMA( ent->client->ps.velocity, -100, forward, ent->client->ps.velocity );
1844 }
1845 }
1846 break;
1847 case WP_GAUNTLET:
1848 Weapon_Gauntlet( ent );
1849 break;
1850
1851 case WP_MONSTER_ATTACK1:
1852 switch ( ent->aiCharacter ) {
1853 case AICHAR_WARZOMBIE:
1854 break;
1855 case AICHAR_ZOMBIE:
1856 // temp just to show it works
1857 // G_Printf("ptoo\n");
1858 weapon_zombiespit( ent );
1859 break;
1860 default:
1861 //G_Printf( "FireWeapon: unknown ai weapon: %s attack1\n", ent->classname );
1862 // ??? bug ???
1863 break;
1864 }
1865
1866 case WP_MORTAR:
1867 break;
1868
1869 default:
1870 // FIXME G_Error( "Bad ent->s.weapon" );
1871 break;
1872 }
1873
1874 // Ridah
1875 // DHM - Nerve :: Only in single player
1876 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1877 AICast_RecordWeaponFire( ent );
1878 }
1879 }
1880