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