1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 //
23 // g_weapon.c
24 // perform the server side effects of a weapon firing
25 
26 #include "g_local.h"
27 
28 static	float	s_quadFactor;
29 static	vec3_t	forward, right, up;
30 static	vec3_t	muzzle;
31 
32 #define NUM_NAILSHOTS 15
33 
34 /*
35 ================
36 G_BounceProjectile
37 ================
38 */
G_BounceProjectile(vec3_t start,vec3_t impact,vec3_t dir,vec3_t endout)39 void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) {
40 	vec3_t v, newv;
41 	float dot;
42 
43 	VectorSubtract( impact, start, v );
44 	dot = DotProduct( v, dir );
45 	VectorMA( v, -2*dot, dir, newv );
46 
47 	VectorNormalize(newv);
48 	VectorMA(impact, 8192, newv, endout);
49 }
50 
51 
52 /*
53 ======================================================================
54 
55 GAUNTLET
56 
57 ======================================================================
58 */
59 
Weapon_Gauntlet(gentity_t * ent)60 void Weapon_Gauntlet( gentity_t *ent ) {
61 
62 }
63 
64 /*
65 ===============
66 CheckGauntletAttack
67 ===============
68 */
CheckGauntletAttack(gentity_t * ent)69 qboolean CheckGauntletAttack( gentity_t *ent ) {
70 	trace_t		tr;
71 	vec3_t		end;
72 	gentity_t	*tent;
73 	gentity_t	*traceEnt;
74 	int			damage;
75 
76 	// set aiming directions
77 	AngleVectors (ent->client->ps.viewangles, forward, right, up);
78 
79 	CalcMuzzlePoint ( ent, forward, right, up, muzzle );
80 
81 	VectorMA (muzzle, 32, forward, end);
82 
83 	trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
84 	if ( tr.surfaceFlags & SURF_NOIMPACT ) {
85 		return qfalse;
86 	}
87 
88 	traceEnt = &g_entities[ tr.entityNum ];
89 
90 	// send blood impact
91 	if ( traceEnt->takedamage && traceEnt->client ) {
92 		tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
93 		tent->s.otherEntityNum = traceEnt->s.number;
94 		tent->s.eventParm = DirToByte( tr.plane.normal );
95 		tent->s.weapon = ent->s.weapon;
96 	}
97 
98 	if ( !traceEnt->takedamage) {
99 		return qfalse;
100 	}
101 
102 	if (ent->client->ps.powerups[PW_QUAD] ) {
103 		G_AddEvent( ent, EV_POWERUP_QUAD, 0 );
104 		s_quadFactor = g_quadfactor.value;
105 	} else {
106 		s_quadFactor = 1;
107 	}
108 #ifdef MISSIONPACK
109 	if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) {
110 		s_quadFactor *= 2;
111 	}
112 #endif
113 
114 	damage = 50 * s_quadFactor;
115 	G_Damage( traceEnt, ent, ent, forward, tr.endpos,
116 		damage, 0, MOD_GAUNTLET );
117 
118 	return qtrue;
119 }
120 
121 
122 /*
123 ======================================================================
124 
125 MACHINEGUN
126 
127 ======================================================================
128 */
129 
130 /*
131 ======================
132 SnapVectorTowards
133 
134 Round a vector to integers for more efficient network
135 transmission, but make sure that it rounds towards a given point
136 rather than blindly truncating.  This prevents it from truncating
137 into a wall.
138 ======================
139 */
SnapVectorTowards(vec3_t v,vec3_t to)140 void SnapVectorTowards( vec3_t v, vec3_t to ) {
141 	int		i;
142 
143 	for ( i = 0 ; i < 3 ; i++ ) {
144 		if ( to[i] <= v[i] ) {
145 			v[i] = (int)v[i];
146 		} else {
147 			v[i] = (int)v[i] + 1;
148 		}
149 	}
150 }
151 
152 #ifdef MISSIONPACK
153 #define CHAINGUN_SPREAD		600
154 #endif
155 #define MACHINEGUN_SPREAD	200
156 #define	MACHINEGUN_DAMAGE	7
157 #define	MACHINEGUN_TEAM_DAMAGE	5		// wimpier MG in teamplay
158 
Bullet_Fire(gentity_t * ent,float spread,int damage)159 void Bullet_Fire (gentity_t *ent, float spread, int damage ) {
160 	trace_t		tr;
161 	vec3_t		end;
162 #ifdef MISSIONPACK
163 	vec3_t		impactpoint, bouncedir;
164 #endif
165 	float		r;
166 	float		u;
167 	gentity_t	*tent;
168 	gentity_t	*traceEnt;
169 	int			i, passent;
170 
171 	damage *= s_quadFactor;
172 
173 	r = random() * M_PI * 2.0f;
174 	u = sin(r) * crandom() * spread * 16;
175 	r = cos(r) * crandom() * spread * 16;
176 	VectorMA (muzzle, 8192*16, forward, end);
177 	VectorMA (end, r, right, end);
178 	VectorMA (end, u, up, end);
179 
180 	passent = ent->s.number;
181 	for (i = 0; i < 10; i++) {
182 
183 		trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
184 		if ( tr.surfaceFlags & SURF_NOIMPACT ) {
185 			return;
186 		}
187 
188 		traceEnt = &g_entities[ tr.entityNum ];
189 
190 		// snap the endpos to integers, but nudged towards the line
191 		SnapVectorTowards( tr.endpos, muzzle );
192 
193 		// send bullet impact
194 		if ( traceEnt->takedamage && traceEnt->client ) {
195 			tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
196 			tent->s.eventParm = traceEnt->s.number;
197 			if( LogAccuracyHit( traceEnt, ent ) ) {
198 				ent->client->accuracy_hits++;
199 			}
200 		} else {
201 			tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
202 			tent->s.eventParm = DirToByte( tr.plane.normal );
203 		}
204 		tent->s.otherEntityNum = ent->s.number;
205 
206 		if ( traceEnt->takedamage) {
207 #ifdef MISSIONPACK
208 			if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
209 				if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
210 					G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
211 					VectorCopy( impactpoint, muzzle );
212 					// the player can hit him/herself with the bounced rail
213 					passent = ENTITYNUM_NONE;
214 				}
215 				else {
216 					VectorCopy( tr.endpos, muzzle );
217 					passent = traceEnt->s.number;
218 				}
219 				continue;
220 			}
221 			else {
222 #endif
223 				G_Damage( traceEnt, ent, ent, forward, tr.endpos,
224 					damage, 0, MOD_MACHINEGUN);
225 #ifdef MISSIONPACK
226 			}
227 #endif
228 		}
229 		break;
230 	}
231 }
232 
233 
234 /*
235 ======================================================================
236 
237 BFG
238 
239 ======================================================================
240 */
241 
BFG_Fire(gentity_t * ent)242 void BFG_Fire ( gentity_t *ent ) {
243 	gentity_t	*m;
244 
245 	m = fire_bfg (ent, muzzle, forward);
246 	m->damage *= s_quadFactor;
247 	m->splashDamage *= s_quadFactor;
248 
249 //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
250 }
251 
252 
253 /*
254 ======================================================================
255 
256 SHOTGUN
257 
258 ======================================================================
259 */
260 
261 // DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT	are in bg_public.h, because
262 // client predicts same spreads
263 #define	DEFAULT_SHOTGUN_DAMAGE	10
264 
ShotgunPellet(vec3_t start,vec3_t end,gentity_t * ent)265 qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) {
266 	trace_t		tr;
267 	int			damage, i, passent;
268 	gentity_t	*traceEnt;
269 #ifdef MISSIONPACK
270 	vec3_t		impactpoint, bouncedir;
271 #endif
272 	vec3_t		tr_start, tr_end;
273 
274 	passent = ent->s.number;
275 	VectorCopy( start, tr_start );
276 	VectorCopy( end, tr_end );
277 	for (i = 0; i < 10; i++) {
278 		trap_Trace (&tr, tr_start, NULL, NULL, tr_end, passent, MASK_SHOT);
279 		traceEnt = &g_entities[ tr.entityNum ];
280 
281 		// send bullet impact
282 		if (  tr.surfaceFlags & SURF_NOIMPACT ) {
283 			return qfalse;
284 		}
285 
286 		if ( traceEnt->takedamage) {
287 			damage = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor;
288 #ifdef MISSIONPACK
289 			if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
290 				if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
291 					G_BounceProjectile( tr_start, impactpoint, bouncedir, tr_end );
292 					VectorCopy( impactpoint, tr_start );
293 					// the player can hit him/herself with the bounced rail
294 					passent = ENTITYNUM_NONE;
295 				}
296 				else {
297 					VectorCopy( tr.endpos, tr_start );
298 					passent = traceEnt->s.number;
299 				}
300 				continue;
301 			}
302 			else {
303 				G_Damage( traceEnt, ent, ent, forward, tr.endpos,
304 					damage, 0, MOD_SHOTGUN);
305 				if( LogAccuracyHit( traceEnt, ent ) ) {
306 					return qtrue;
307 				}
308 			}
309 #else
310 			G_Damage( traceEnt, ent, ent, forward, tr.endpos,	damage, 0, MOD_SHOTGUN);
311 				if( LogAccuracyHit( traceEnt, ent ) ) {
312 					return qtrue;
313 				}
314 #endif
315 		}
316 		return qfalse;
317 	}
318 	return qfalse;
319 }
320 
321 // this should match CG_ShotgunPattern
ShotgunPattern(vec3_t origin,vec3_t origin2,int seed,gentity_t * ent)322 void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) {
323 	int			i;
324 	float		r, u;
325 	vec3_t		end;
326 	vec3_t		forward, right, up;
327 	int			oldScore;
328 	qboolean	hitClient = qfalse;
329 
330 	// derive the right and up vectors from the forward vector, because
331 	// the client won't have any other information
332 	VectorNormalize2( origin2, forward );
333 	PerpendicularVector( right, forward );
334 	CrossProduct( forward, right, up );
335 
336 	oldScore = ent->client->ps.persistant[PERS_SCORE];
337 
338 	// generate the "random" spread pattern
339 	for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) {
340 		r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
341 		u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
342 		VectorMA( origin, 8192 * 16, forward, end);
343 		VectorMA (end, r, right, end);
344 		VectorMA (end, u, up, end);
345 		if( ShotgunPellet( origin, end, ent ) && !hitClient ) {
346 			hitClient = qtrue;
347 			ent->client->accuracy_hits++;
348 		}
349 	}
350 }
351 
352 
weapon_supershotgun_fire(gentity_t * ent)353 void weapon_supershotgun_fire (gentity_t *ent) {
354 	gentity_t		*tent;
355 
356 	// send shotgun blast
357 	tent = G_TempEntity( muzzle, EV_SHOTGUN );
358 	VectorScale( forward, 4096, tent->s.origin2 );
359 	SnapVector( tent->s.origin2 );
360 	tent->s.eventParm = rand() & 255;		// seed for spread pattern
361 	tent->s.otherEntityNum = ent->s.number;
362 
363 	ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
364 }
365 
366 
367 /*
368 ======================================================================
369 
370 GRENADE LAUNCHER
371 
372 ======================================================================
373 */
374 
weapon_grenadelauncher_fire(gentity_t * ent)375 void weapon_grenadelauncher_fire (gentity_t *ent) {
376 	gentity_t	*m;
377 
378 	// extra vertical velocity
379 	forward[2] += 0.2f;
380 	VectorNormalize( forward );
381 
382 	m = fire_grenade (ent, muzzle, forward);
383 	m->damage *= s_quadFactor;
384 	m->splashDamage *= s_quadFactor;
385 
386 //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
387 }
388 
389 /*
390 ======================================================================
391 
392 ROCKET
393 
394 ======================================================================
395 */
396 
Weapon_RocketLauncher_Fire(gentity_t * ent)397 void Weapon_RocketLauncher_Fire (gentity_t *ent) {
398 	gentity_t	*m;
399 
400 	m = fire_rocket (ent, muzzle, forward);
401 	m->damage *= s_quadFactor;
402 	m->splashDamage *= s_quadFactor;
403 
404 //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
405 }
406 
407 
408 /*
409 ======================================================================
410 
411 PLASMA GUN
412 
413 ======================================================================
414 */
415 
Weapon_Plasmagun_Fire(gentity_t * ent)416 void Weapon_Plasmagun_Fire (gentity_t *ent) {
417 	gentity_t	*m;
418 
419 	m = fire_plasma (ent, muzzle, forward);
420 	m->damage *= s_quadFactor;
421 	m->splashDamage *= s_quadFactor;
422 
423 //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
424 }
425 
426 /*
427 ======================================================================
428 
429 RAILGUN
430 
431 ======================================================================
432 */
433 
434 
435 /*
436 =================
437 weapon_railgun_fire
438 =================
439 */
440 #define	MAX_RAIL_HITS	4
weapon_railgun_fire(gentity_t * ent)441 void weapon_railgun_fire (gentity_t *ent) {
442 	vec3_t		end;
443 #ifdef MISSIONPACK
444 	vec3_t impactpoint, bouncedir;
445 #endif
446 	trace_t		trace;
447 	gentity_t	*tent;
448 	gentity_t	*traceEnt;
449 	int			damage;
450 	int			i;
451 	int			hits;
452 	int			unlinked;
453 	int			passent;
454 	gentity_t	*unlinkedEntities[MAX_RAIL_HITS];
455 
456 	damage = 100 * s_quadFactor;
457 
458 	VectorMA (muzzle, 8192, forward, end);
459 
460 	// trace only against the solids, so the railgun will go through people
461 	unlinked = 0;
462 	hits = 0;
463 	passent = ent->s.number;
464 	do {
465 		trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT );
466 		if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
467 			break;
468 		}
469 		traceEnt = &g_entities[ trace.entityNum ];
470 		if ( traceEnt->takedamage ) {
471 #ifdef MISSIONPACK
472 			if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
473 				if ( G_InvulnerabilityEffect( traceEnt, forward, trace.endpos, impactpoint, bouncedir ) ) {
474 					G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
475 					// snap the endpos to integers to save net bandwidth, but nudged towards the line
476 					SnapVectorTowards( trace.endpos, muzzle );
477 					// send railgun beam effect
478 					tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );
479 					// set player number for custom colors on the railtrail
480 					tent->s.clientNum = ent->s.clientNum;
481 					VectorCopy( muzzle, tent->s.origin2 );
482 					// move origin a bit to come closer to the drawn gun muzzle
483 					VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
484 					VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );
485 					tent->s.eventParm = 255;	// don't make the explosion at the end
486 					//
487 					VectorCopy( impactpoint, muzzle );
488 					// the player can hit him/herself with the bounced rail
489 					passent = ENTITYNUM_NONE;
490 				}
491 			}
492 			else {
493 				if( LogAccuracyHit( traceEnt, ent ) ) {
494 					hits++;
495 				}
496 				G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN);
497 			}
498 #else
499 				if( LogAccuracyHit( traceEnt, ent ) ) {
500 					hits++;
501 				}
502 				G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN);
503 #endif
504 		}
505 		if ( trace.contents & CONTENTS_SOLID ) {
506 			break;		// we hit something solid enough to stop the beam
507 		}
508 		// unlink this entity, so the next trace will go past it
509 		trap_UnlinkEntity( traceEnt );
510 		unlinkedEntities[unlinked] = traceEnt;
511 		unlinked++;
512 	} while ( unlinked < MAX_RAIL_HITS );
513 
514 	// link back in any entities we unlinked
515 	for ( i = 0 ; i < unlinked ; i++ ) {
516 		trap_LinkEntity( unlinkedEntities[i] );
517 	}
518 
519 	// the final trace endpos will be the terminal point of the rail trail
520 
521 	// snap the endpos to integers to save net bandwidth, but nudged towards the line
522 	SnapVectorTowards( trace.endpos, muzzle );
523 
524 	// send railgun beam effect
525 	tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );
526 
527 	// set player number for custom colors on the railtrail
528 	tent->s.clientNum = ent->s.clientNum;
529 
530 	VectorCopy( muzzle, tent->s.origin2 );
531 	// move origin a bit to come closer to the drawn gun muzzle
532 	VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
533 	VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );
534 
535 	// no explosion at end if SURF_NOIMPACT, but still make the trail
536 	if ( trace.surfaceFlags & SURF_NOIMPACT ) {
537 		tent->s.eventParm = 255;	// don't make the explosion at the end
538 	} else {
539 		tent->s.eventParm = DirToByte( trace.plane.normal );
540 	}
541 	tent->s.clientNum = ent->s.clientNum;
542 
543 	// give the shooter a reward sound if they have made two railgun hits in a row
544 	if ( hits == 0 ) {
545 		// complete miss
546 		ent->client->accurateCount = 0;
547 	} else {
548 		// check for "impressive" reward sound
549 		ent->client->accurateCount += hits;
550 		if ( ent->client->accurateCount >= 2 ) {
551 			ent->client->accurateCount -= 2;
552 			ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
553 			// add the sprite over the player's head
554 			ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
555 			ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE;
556 			ent->client->rewardTime = level.time + REWARD_SPRITE_TIME;
557 		}
558 		ent->client->accuracy_hits++;
559 	}
560 
561 }
562 
563 
564 /*
565 ======================================================================
566 
567 GRAPPLING HOOK
568 
569 ======================================================================
570 */
571 
Weapon_GrapplingHook_Fire(gentity_t * ent)572 void Weapon_GrapplingHook_Fire (gentity_t *ent)
573 {
574 	if (!ent->client->fireHeld && !ent->client->hook)
575 		fire_grapple (ent, muzzle, forward);
576 
577 	ent->client->fireHeld = qtrue;
578 }
579 
Weapon_HookFree(gentity_t * ent)580 void Weapon_HookFree (gentity_t *ent)
581 {
582 	ent->parent->client->hook = NULL;
583 	ent->parent->client->ps.pm_flags &= ~PMF_GRAPPLE_PULL;
584 	G_FreeEntity( ent );
585 }
586 
Weapon_HookThink(gentity_t * ent)587 void Weapon_HookThink (gentity_t *ent)
588 {
589 	if (ent->enemy) {
590 		vec3_t v, oldorigin;
591 
592 		VectorCopy(ent->r.currentOrigin, oldorigin);
593 		v[0] = ent->enemy->r.currentOrigin[0] + (ent->enemy->r.mins[0] + ent->enemy->r.maxs[0]) * 0.5;
594 		v[1] = ent->enemy->r.currentOrigin[1] + (ent->enemy->r.mins[1] + ent->enemy->r.maxs[1]) * 0.5;
595 		v[2] = ent->enemy->r.currentOrigin[2] + (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5;
596 		SnapVectorTowards( v, oldorigin );	// save net bandwidth
597 
598 		G_SetOrigin( ent, v );
599 	}
600 
601 	VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);
602 }
603 
604 /*
605 ======================================================================
606 
607 LIGHTNING GUN
608 
609 ======================================================================
610 */
611 
Weapon_LightningFire(gentity_t * ent)612 void Weapon_LightningFire( gentity_t *ent ) {
613 	trace_t		tr;
614 	vec3_t		end;
615 #ifdef MISSIONPACK
616 	vec3_t impactpoint, bouncedir;
617 #endif
618 	gentity_t	*traceEnt, *tent;
619 	int			damage, i, passent;
620 
621 	damage = 8 * s_quadFactor;
622 
623 	passent = ent->s.number;
624 	for (i = 0; i < 10; i++) {
625 		VectorMA( muzzle, LIGHTNING_RANGE, forward, end );
626 
627 		trap_Trace( &tr, muzzle, NULL, NULL, end, passent, MASK_SHOT );
628 
629 #ifdef MISSIONPACK
630 		// if not the first trace (the lightning bounced of an invulnerability sphere)
631 		if (i) {
632 			// add bounced off lightning bolt temp entity
633 			// the first lightning bolt is a cgame only visual
634 			//
635 			tent = G_TempEntity( muzzle, EV_LIGHTNINGBOLT );
636 			VectorCopy( tr.endpos, end );
637 			SnapVector( end );
638 			VectorCopy( end, tent->s.origin2 );
639 		}
640 #endif
641 		if ( tr.entityNum == ENTITYNUM_NONE ) {
642 			return;
643 		}
644 
645 		traceEnt = &g_entities[ tr.entityNum ];
646 
647 		if ( traceEnt->takedamage) {
648 #ifdef MISSIONPACK
649 			if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) {
650 				if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) {
651 					G_BounceProjectile( muzzle, impactpoint, bouncedir, end );
652 					VectorCopy( impactpoint, muzzle );
653 					VectorSubtract( end, impactpoint, forward );
654 					VectorNormalize(forward);
655 					// the player can hit him/herself with the bounced lightning
656 					passent = ENTITYNUM_NONE;
657 				}
658 				else {
659 					VectorCopy( tr.endpos, muzzle );
660 					passent = traceEnt->s.number;
661 				}
662 				continue;
663 			}
664 			else {
665 				G_Damage( traceEnt, ent, ent, forward, tr.endpos,
666 					damage, 0, MOD_LIGHTNING);
667 			}
668 #else
669 				G_Damage( traceEnt, ent, ent, forward, tr.endpos,
670 					damage, 0, MOD_LIGHTNING);
671 #endif
672 		}
673 
674 		if ( traceEnt->takedamage && traceEnt->client ) {
675 			tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
676 			tent->s.otherEntityNum = traceEnt->s.number;
677 			tent->s.eventParm = DirToByte( tr.plane.normal );
678 			tent->s.weapon = ent->s.weapon;
679 			if( LogAccuracyHit( traceEnt, ent ) ) {
680 				ent->client->accuracy_hits++;
681 			}
682 		} else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
683 			tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
684 			tent->s.eventParm = DirToByte( tr.plane.normal );
685 		}
686 
687 		break;
688 	}
689 }
690 
691 #ifdef MISSIONPACK
692 /*
693 ======================================================================
694 
695 NAILGUN
696 
697 ======================================================================
698 */
699 
Weapon_Nailgun_Fire(gentity_t * ent)700 void Weapon_Nailgun_Fire (gentity_t *ent) {
701 	gentity_t	*m;
702 	int			count;
703 
704 	for( count = 0; count < NUM_NAILSHOTS; count++ ) {
705 		m = fire_nail (ent, muzzle, forward, right, up );
706 		m->damage *= s_quadFactor;
707 		m->splashDamage *= s_quadFactor;
708 	}
709 
710 //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
711 }
712 
713 
714 /*
715 ======================================================================
716 
717 PROXIMITY MINE LAUNCHER
718 
719 ======================================================================
720 */
721 
weapon_proxlauncher_fire(gentity_t * ent)722 void weapon_proxlauncher_fire (gentity_t *ent) {
723 	gentity_t	*m;
724 
725 	// extra vertical velocity
726 	forward[2] += 0.2f;
727 	VectorNormalize( forward );
728 
729 	m = fire_prox (ent, muzzle, forward);
730 	m->damage *= s_quadFactor;
731 	m->splashDamage *= s_quadFactor;
732 
733 //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
734 }
735 
736 #endif
737 
738 //======================================================================
739 
740 
741 /*
742 ===============
743 LogAccuracyHit
744 ===============
745 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)746 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
747 	if( !target->takedamage ) {
748 		return qfalse;
749 	}
750 
751 	if ( target == attacker ) {
752 		return qfalse;
753 	}
754 
755 	if( !target->client ) {
756 		return qfalse;
757 	}
758 
759 	if( !attacker->client ) {
760 		return qfalse;
761 	}
762 
763 	if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
764 		return qfalse;
765 	}
766 
767 	if ( OnSameTeam( target, attacker ) ) {
768 		return qfalse;
769 	}
770 
771 	return qtrue;
772 }
773 
774 
775 /*
776 ===============
777 CalcMuzzlePoint
778 
779 set muzzle location relative to pivoting eye
780 ===============
781 */
CalcMuzzlePoint(gentity_t * ent,vec3_t forward,vec3_t right,vec3_t up,vec3_t muzzlePoint)782 void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
783 	VectorCopy( ent->s.pos.trBase, muzzlePoint );
784 	muzzlePoint[2] += ent->client->ps.viewheight;
785 	VectorMA( muzzlePoint, 14, forward, muzzlePoint );
786 	// snap to integer coordinates for more efficient network bandwidth usage
787 	SnapVector( muzzlePoint );
788 }
789 
790 /*
791 ===============
792 CalcMuzzlePointOrigin
793 
794 set muzzle location relative to pivoting eye
795 ===============
796 */
CalcMuzzlePointOrigin(gentity_t * ent,vec3_t origin,vec3_t forward,vec3_t right,vec3_t up,vec3_t muzzlePoint)797 void CalcMuzzlePointOrigin ( gentity_t *ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
798 	VectorCopy( ent->s.pos.trBase, muzzlePoint );
799 	muzzlePoint[2] += ent->client->ps.viewheight;
800 	VectorMA( muzzlePoint, 14, forward, muzzlePoint );
801 	// snap to integer coordinates for more efficient network bandwidth usage
802 	SnapVector( muzzlePoint );
803 }
804 
805 
806 
807 /*
808 ===============
809 FireWeapon
810 ===============
811 */
FireWeapon(gentity_t * ent)812 void FireWeapon( gentity_t *ent ) {
813 	if (ent->client->ps.powerups[PW_QUAD] ) {
814 		s_quadFactor = g_quadfactor.value;
815 	} else {
816 		s_quadFactor = 1;
817 	}
818 #ifdef MISSIONPACK
819 	if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) {
820 		s_quadFactor *= 2;
821 	}
822 #endif
823 
824 	// track shots taken for accuracy tracking.  Grapple is not a weapon and gauntet is just not tracked
825 	if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) {
826 #ifdef MISSIONPACK
827 		if( ent->s.weapon == WP_NAILGUN ) {
828 			ent->client->accuracy_shots += NUM_NAILSHOTS;
829 		} else {
830 			ent->client->accuracy_shots++;
831 		}
832 #else
833 		ent->client->accuracy_shots++;
834 #endif
835 	}
836 
837 	// set aiming directions
838 	AngleVectors (ent->client->ps.viewangles, forward, right, up);
839 
840 	CalcMuzzlePointOrigin ( ent, ent->client->oldOrigin, forward, right, up, muzzle );
841 
842 	// fire the specific weapon
843 	switch( ent->s.weapon ) {
844 	case WP_GAUNTLET:
845 		Weapon_Gauntlet( ent );
846 		break;
847 	case WP_LIGHTNING:
848 		Weapon_LightningFire( ent );
849 		break;
850 	case WP_SHOTGUN:
851 		weapon_supershotgun_fire( ent );
852 		break;
853 	case WP_MACHINEGUN:
854 		if ( g_gametype.integer != GT_TEAM ) {
855 			Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE );
856 		} else {
857 			Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE );
858 		}
859 		break;
860 	case WP_GRENADE_LAUNCHER:
861 		weapon_grenadelauncher_fire( ent );
862 		break;
863 	case WP_ROCKET_LAUNCHER:
864 		Weapon_RocketLauncher_Fire( ent );
865 		break;
866 	case WP_PLASMAGUN:
867 		Weapon_Plasmagun_Fire( ent );
868 		break;
869 	case WP_RAILGUN:
870 		weapon_railgun_fire( ent );
871 		break;
872 	case WP_BFG:
873 		BFG_Fire( ent );
874 		break;
875 	case WP_GRAPPLING_HOOK:
876 		Weapon_GrapplingHook_Fire( ent );
877 		break;
878 #ifdef MISSIONPACK
879 	case WP_NAILGUN:
880 		Weapon_Nailgun_Fire( ent );
881 		break;
882 	case WP_PROX_LAUNCHER:
883 		weapon_proxlauncher_fire( ent );
884 		break;
885 	case WP_CHAINGUN:
886 		Bullet_Fire( ent, CHAINGUN_SPREAD, MACHINEGUN_DAMAGE );
887 		break;
888 #endif
889 	default:
890 // FIXME		G_Error( "Bad ent->s.weapon" );
891 		break;
892 	}
893 }
894 
895 
896 #ifdef MISSIONPACK
897 
898 /*
899 ===============
900 KamikazeRadiusDamage
901 ===============
902 */
KamikazeRadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius)903 static void KamikazeRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius ) {
904 	float		dist;
905 	gentity_t	*ent;
906 	int			entityList[MAX_GENTITIES];
907 	int			numListedEntities;
908 	vec3_t		mins, maxs;
909 	vec3_t		v;
910 	vec3_t		dir;
911 	int			i, e;
912 
913 	if ( radius < 1 ) {
914 		radius = 1;
915 	}
916 
917 	for ( i = 0 ; i < 3 ; i++ ) {
918 		mins[i] = origin[i] - radius;
919 		maxs[i] = origin[i] + radius;
920 	}
921 
922 	numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
923 
924 	for ( e = 0 ; e < numListedEntities ; e++ ) {
925 		ent = &g_entities[entityList[ e ]];
926 
927 		if (!ent->takedamage) {
928 			continue;
929 		}
930 
931 		// dont hit things we have already hit
932 		if( ent->kamikazeTime > level.time ) {
933 			continue;
934 		}
935 
936 		// find the distance from the edge of the bounding box
937 		for ( i = 0 ; i < 3 ; i++ ) {
938 			if ( origin[i] < ent->r.absmin[i] ) {
939 				v[i] = ent->r.absmin[i] - origin[i];
940 			} else if ( origin[i] > ent->r.absmax[i] ) {
941 				v[i] = origin[i] - ent->r.absmax[i];
942 			} else {
943 				v[i] = 0;
944 			}
945 		}
946 
947 		dist = VectorLength( v );
948 		if ( dist >= radius ) {
949 			continue;
950 		}
951 
952 //		if( CanDamage (ent, origin) ) {
953 			VectorSubtract (ent->r.currentOrigin, origin, dir);
954 			// push the center of mass higher than the origin so players
955 			// get knocked into the air more
956 			dir[2] += 24;
957 			G_Damage( ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS|DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE );
958 			ent->kamikazeTime = level.time + 3000;
959 //		}
960 	}
961 }
962 
963 /*
964 ===============
965 KamikazeShockWave
966 ===============
967 */
KamikazeShockWave(vec3_t origin,gentity_t * attacker,float damage,float push,float radius)968 static void KamikazeShockWave( vec3_t origin, gentity_t *attacker, float damage, float push, float radius ) {
969 	float		dist;
970 	gentity_t	*ent;
971 	int			entityList[MAX_GENTITIES];
972 	int			numListedEntities;
973 	vec3_t		mins, maxs;
974 	vec3_t		v;
975 	vec3_t		dir;
976 	int			i, e;
977 
978 	if ( radius < 1 )
979 		radius = 1;
980 
981 	for ( i = 0 ; i < 3 ; i++ ) {
982 		mins[i] = origin[i] - radius;
983 		maxs[i] = origin[i] + radius;
984 	}
985 
986 	numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
987 
988 	for ( e = 0 ; e < numListedEntities ; e++ ) {
989 		ent = &g_entities[entityList[ e ]];
990 
991 		// dont hit things we have already hit
992 		if( ent->kamikazeShockTime > level.time ) {
993 			continue;
994 		}
995 
996 		// find the distance from the edge of the bounding box
997 		for ( i = 0 ; i < 3 ; i++ ) {
998 			if ( origin[i] < ent->r.absmin[i] ) {
999 				v[i] = ent->r.absmin[i] - origin[i];
1000 			} else if ( origin[i] > ent->r.absmax[i] ) {
1001 				v[i] = origin[i] - ent->r.absmax[i];
1002 			} else {
1003 				v[i] = 0;
1004 			}
1005 		}
1006 
1007 		dist = VectorLength( v );
1008 		if ( dist >= radius ) {
1009 			continue;
1010 		}
1011 
1012 //		if( CanDamage (ent, origin) ) {
1013 			VectorSubtract (ent->r.currentOrigin, origin, dir);
1014 			dir[2] += 24;
1015 			G_Damage( ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS|DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE );
1016 			//
1017 			dir[2] = 0;
1018 			VectorNormalize(dir);
1019 			if ( ent->client ) {
1020 				ent->client->ps.velocity[0] = dir[0] * push;
1021 				ent->client->ps.velocity[1] = dir[1] * push;
1022 				ent->client->ps.velocity[2] = 100;
1023 			}
1024 			ent->kamikazeShockTime = level.time + 3000;
1025 //		}
1026 	}
1027 }
1028 
1029 /*
1030 ===============
1031 KamikazeDamage
1032 ===============
1033 */
KamikazeDamage(gentity_t * self)1034 static void KamikazeDamage( gentity_t *self ) {
1035 	int i;
1036 	float t;
1037 	gentity_t *ent;
1038 	vec3_t newangles;
1039 
1040 	self->count += 100;
1041 
1042 	if (self->count >= KAMI_SHOCKWAVE_STARTTIME) {
1043 		// shockwave push back
1044 		t = self->count - KAMI_SHOCKWAVE_STARTTIME;
1045 		KamikazeShockWave(self->s.pos.trBase, self->activator, 25, 400,	(int) (float) t * KAMI_SHOCKWAVE_MAXRADIUS / (KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME) );
1046 	}
1047 	//
1048 	if (self->count >= KAMI_EXPLODE_STARTTIME) {
1049 		// do our damage
1050 		t = self->count - KAMI_EXPLODE_STARTTIME;
1051 		KamikazeRadiusDamage( self->s.pos.trBase, self->activator, 400,	(int) (float) t * KAMI_BOOMSPHERE_MAXRADIUS / (KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME) );
1052 	}
1053 
1054 	// either cycle or kill self
1055 	if( self->count >= KAMI_SHOCKWAVE_ENDTIME ) {
1056 		G_FreeEntity( self );
1057 		return;
1058 	}
1059 	self->nextthink = level.time + 100;
1060 
1061 	// add earth quake effect
1062 	newangles[0] = crandom() * 2;
1063 	newangles[1] = crandom() * 2;
1064 	newangles[2] = 0;
1065 	for (i = 0; i < MAX_CLIENTS; i++)
1066 	{
1067 		ent = &g_entities[i];
1068 		if (!ent->inuse)
1069 			continue;
1070 		if (!ent->client)
1071 			continue;
1072 
1073 		if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE) {
1074 			ent->client->ps.velocity[0] += crandom() * 120;
1075 			ent->client->ps.velocity[1] += crandom() * 120;
1076 			ent->client->ps.velocity[2] = 30 + random() * 25;
1077 		}
1078 
1079 		ent->client->ps.delta_angles[0] += ANGLE2SHORT(newangles[0] - self->movedir[0]);
1080 		ent->client->ps.delta_angles[1] += ANGLE2SHORT(newangles[1] - self->movedir[1]);
1081 		ent->client->ps.delta_angles[2] += ANGLE2SHORT(newangles[2] - self->movedir[2]);
1082 	}
1083 	VectorCopy(newangles, self->movedir);
1084 }
1085 
1086 /*
1087 ===============
1088 G_StartKamikaze
1089 ===============
1090 */
G_StartKamikaze(gentity_t * ent)1091 void G_StartKamikaze( gentity_t *ent ) {
1092 	gentity_t	*explosion;
1093 	gentity_t	*te;
1094 	vec3_t		snapped;
1095 
1096 	// start up the explosion logic
1097 	explosion = G_Spawn();
1098 
1099 	explosion->s.eType = ET_EVENTS + EV_KAMIKAZE;
1100 	explosion->eventTime = level.time;
1101 
1102 	if ( ent->client ) {
1103 		VectorCopy( ent->s.pos.trBase, snapped );
1104 	}
1105 	else {
1106 		VectorCopy( ent->activator->s.pos.trBase, snapped );
1107 	}
1108 	SnapVector( snapped );		// save network bandwidth
1109 	G_SetOrigin( explosion, snapped );
1110 
1111 	explosion->classname = "kamikaze";
1112 	explosion->s.pos.trType = TR_STATIONARY;
1113 
1114 	explosion->kamikazeTime = level.time;
1115 
1116 	explosion->think = KamikazeDamage;
1117 	explosion->nextthink = level.time + 100;
1118 	explosion->count = 0;
1119 	VectorClear(explosion->movedir);
1120 
1121 	trap_LinkEntity( explosion );
1122 
1123 	if (ent->client) {
1124 		//
1125 		explosion->activator = ent;
1126 		//
1127 		ent->s.eFlags &= ~EF_KAMIKAZE;
1128 		// nuke the guy that used it
1129 		G_Damage( ent, ent, ent, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_KAMIKAZE );
1130 	}
1131 	else {
1132 		if ( !strcmp(ent->activator->classname, "bodyque") ) {
1133 			explosion->activator = &g_entities[ent->activator->r.ownerNum];
1134 		}
1135 		else {
1136 			explosion->activator = ent->activator;
1137 		}
1138 	}
1139 
1140 	// play global sound at all clients
1141 	te = G_TempEntity(snapped, EV_GLOBAL_TEAM_SOUND );
1142 	te->r.svFlags |= SVF_BROADCAST;
1143 	te->s.eventParm = GTS_KAMIKAZE;
1144 }
1145 #endif
1146