1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 // g_weapon.c
25 // perform the server side effects of a weapon firing
26 
27 #include "g_local.h"
28 #include "botlib/be_aas.h"
29 #include "bg_saga.h"
30 #include "ghoul2/G2.h"
31 #include "qcommon/q_shared.h"
32 
33 static vec3_t forward, vright, up;
34 static vec3_t muzzle;
35 
36 // Bryar Pistol
37 //--------
38 #define BRYAR_PISTOL_VEL			1600
39 #define BRYAR_PISTOL_DAMAGE			10
40 #define BRYAR_CHARGE_UNIT			200.0f	// bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove
41 #define BRYAR_ALT_SIZE				1.0f
42 
43 // E11 Blaster
44 //---------
45 #define BLASTER_SPREAD				1.6f//1.2f
46 #define BLASTER_VELOCITY			2300
47 #define BLASTER_DAMAGE				20
48 
49 // Tenloss Disruptor
50 //----------
51 #define DISRUPTOR_MAIN_DAMAGE			30 //40
52 #define DISRUPTOR_MAIN_DAMAGE_SIEGE		50
53 #define DISRUPTOR_NPC_MAIN_DAMAGE_CUT	0.25f
54 
55 #define DISRUPTOR_ALT_DAMAGE			100 //125
56 #define DISRUPTOR_NPC_ALT_DAMAGE_CUT	0.2f
57 #define DISRUPTOR_ALT_TRACES			3		// can go through a max of 3 damageable(sp?) entities
58 #define DISRUPTOR_CHARGE_UNIT			50.0f	// distruptor charging gives us one more unit every 50ms--if you change this, you'll have to do the same in bg_pmove
59 
60 // Wookiee Bowcaster
61 //----------
62 #define	BOWCASTER_DAMAGE			50
63 #define	BOWCASTER_VELOCITY			1300
64 #define BOWCASTER_SPLASH_DAMAGE		0
65 #define BOWCASTER_SPLASH_RADIUS		0
66 #define BOWCASTER_SIZE				2
67 
68 #define BOWCASTER_ALT_SPREAD		5.0f
69 #define BOWCASTER_VEL_RANGE			0.3f
70 #define BOWCASTER_CHARGE_UNIT		200.0f	// bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove
71 
72 // Heavy Repeater
73 //----------
74 #define REPEATER_SPREAD				1.4f
75 #define	REPEATER_DAMAGE				14
76 #define	REPEATER_VELOCITY			1600
77 
78 #define REPEATER_ALT_SIZE				3	// half of bbox size
79 #define	REPEATER_ALT_DAMAGE				60
80 #define REPEATER_ALT_SPLASH_DAMAGE		60
81 #define REPEATER_ALT_SPLASH_RADIUS		128
82 #define REPEATER_ALT_SPLASH_RAD_SIEGE	80
83 #define	REPEATER_ALT_VELOCITY			1100
84 
85 // DEMP2
86 //----------
87 #define	DEMP2_DAMAGE				35
88 #define	DEMP2_VELOCITY				1800
89 #define	DEMP2_SIZE					2		// half of bbox size
90 
91 #define DEMP2_ALT_DAMAGE			8 //12		// does 12, 36, 84 at each of the 3 charge levels.
92 #define DEMP2_CHARGE_UNIT			700.0f	// demp2 charging gives us one more unit every 700ms--if you change this, you'll have to do the same in bg_weapons
93 #define DEMP2_ALT_RANGE				4096
94 #define DEMP2_ALT_SPLASHRADIUS		256
95 
96 // Golan Arms Flechette
97 //---------
98 #define FLECHETTE_SHOTS				5
99 #define FLECHETTE_SPREAD			4.0f
100 #define FLECHETTE_DAMAGE			12//15
101 #define FLECHETTE_VEL				3500
102 #define FLECHETTE_SIZE				1
103 #define FLECHETTE_MINE_RADIUS_CHECK	256
104 #define FLECHETTE_ALT_DAMAGE		60
105 #define FLECHETTE_ALT_SPLASH_DAM	60
106 #define FLECHETTE_ALT_SPLASH_RAD	128
107 
108 // Personal Rocket Launcher
109 //---------
110 #define	ROCKET_VELOCITY				900
111 #define	ROCKET_DAMAGE				100
112 #define	ROCKET_SPLASH_DAMAGE		100
113 #define	ROCKET_SPLASH_RADIUS		160
114 #define ROCKET_SIZE					3
115 #define ROCKET_ALT_THINK_TIME		100
116 
117 // Concussion Rifle
118 //---------
119 //primary
120 //man, this thing is too absurdly powerful. having to
121 //slash the values way down from sp.
122 #define	CONC_VELOCITY				3000
123 #define	CONC_DAMAGE					75 //150
124 #define	CONC_NPC_DAMAGE_EASY		40
125 #define	CONC_NPC_DAMAGE_NORMAL		80
126 #define	CONC_NPC_DAMAGE_HARD		100
127 #define	CONC_SPLASH_DAMAGE			40 //50
128 #define	CONC_SPLASH_RADIUS			200 //300
129 //alt
130 #define CONC_ALT_DAMAGE				25 //100
131 #define CONC_ALT_NPC_DAMAGE_EASY	20
132 #define CONC_ALT_NPC_DAMAGE_MEDIUM	35
133 #define CONC_ALT_NPC_DAMAGE_HARD	50
134 
135 // Stun Baton
136 //--------------
137 #define STUN_BATON_DAMAGE			20
138 #define STUN_BATON_ALT_DAMAGE		20
139 #define STUN_BATON_RANGE			8
140 
141 // Melee
142 //--------------
143 #define MELEE_SWING1_DAMAGE			10
144 #define MELEE_SWING2_DAMAGE			12
145 #define MELEE_RANGE					8
146 
147 // ATST Main Gun
148 //--------------
149 #define ATST_MAIN_VEL				4000	//
150 #define ATST_MAIN_DAMAGE			25		//
151 #define ATST_MAIN_SIZE				3		// make it easier to hit things
152 
153 // ATST Side Gun
154 //---------------
155 #define ATST_SIDE_MAIN_DAMAGE				75
156 #define ATST_SIDE_MAIN_VELOCITY				1300
157 #define ATST_SIDE_MAIN_NPC_DAMAGE_EASY		30
158 #define ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL	40
159 #define ATST_SIDE_MAIN_NPC_DAMAGE_HARD		50
160 #define ATST_SIDE_MAIN_SIZE					4
161 #define ATST_SIDE_MAIN_SPLASH_DAMAGE		10	// yeah, pretty small, either zero out or make it worth having?
162 #define ATST_SIDE_MAIN_SPLASH_RADIUS		16	// yeah, pretty small, either zero out or make it worth having?
163 
164 #define ATST_SIDE_ALT_VELOCITY				1100
165 #define ATST_SIDE_ALT_NPC_VELOCITY			600
166 #define ATST_SIDE_ALT_DAMAGE				130
167 
168 #define ATST_SIDE_ROCKET_NPC_DAMAGE_EASY	30
169 #define ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL	50
170 #define ATST_SIDE_ROCKET_NPC_DAMAGE_HARD	90
171 
172 #define	ATST_SIDE_ALT_SPLASH_DAMAGE			130
173 #define	ATST_SIDE_ALT_SPLASH_RADIUS			200
174 #define ATST_SIDE_ALT_ROCKET_SIZE			5
175 #define ATST_SIDE_ALT_ROCKET_SPLASH_SCALE	0.5f	// scales splash for NPC's
176 
177 extern qboolean G_BoxInBounds( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t boundsMins, vec3_t boundsMaxs );
178 extern qboolean G_HeavyMelee( gentity_t *attacker );
179 extern void Jedi_Decloak( gentity_t *self );
180 
181 static void WP_FireEmplaced( gentity_t *ent, qboolean altFire );
182 
183 void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal );
184 
touch_NULL(gentity_t * ent,gentity_t * other,trace_t * trace)185 void touch_NULL( gentity_t *ent, gentity_t *other, trace_t *trace )
186 {
187 
188 }
189 
190 void laserTrapExplode( gentity_t *self );
191 void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);
192 
193 //We should really organize weapon data into tables or parse from the ext data so we have accurate info for this,
WP_SpeedOfMissileForWeapon(int wp,qboolean alt_fire)194 float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire )
195 {
196 	return 500;
197 }
198 
199 //-----------------------------------------------------------------------------
W_TraceSetStart(gentity_t * ent,vec3_t start,vec3_t mins,vec3_t maxs)200 void W_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs )
201 //-----------------------------------------------------------------------------
202 {
203 	//make sure our start point isn't on the other side of a wall
204 	trace_t	tr;
205 	vec3_t	entMins;
206 	vec3_t	entMaxs;
207 	vec3_t	eyePoint;
208 
209 	VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins );
210 	VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs );
211 
212 	if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) )
213 	{
214 		return;
215 	}
216 
217 	if ( !ent->client )
218 	{
219 		return;
220 	}
221 
222 	VectorCopy( ent->s.pos.trBase, eyePoint);
223 	eyePoint[2] += ent->client->ps.viewheight;
224 
225 	trap->Trace( &tr, eyePoint, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, qfalse, 0, 0 );
226 
227 	if ( tr.startsolid || tr.allsolid )
228 	{
229 		return;
230 	}
231 
232 	if ( tr.fraction < 1.0f )
233 	{
234 		VectorCopy( tr.endpos, start );
235 	}
236 }
237 
238 
239 /*
240 ----------------------------------------------
241 	PLAYER WEAPONS
242 ----------------------------------------------
243 */
244 
245 /*
246 ======================================================================
247 
248 BRYAR PISTOL
249 
250 ======================================================================
251 */
252 
253 //----------------------------------------------
WP_FireBryarPistol(gentity_t * ent,qboolean altFire)254 static void WP_FireBryarPistol( gentity_t *ent, qboolean altFire )
255 //---------------------------------------------------------
256 {
257 	int damage = BRYAR_PISTOL_DAMAGE;
258 	int count;
259 
260 	gentity_t	*missile = CreateMissile( muzzle, forward, BRYAR_PISTOL_VEL, 10000, ent, altFire );
261 
262 	missile->classname = "bryar_proj";
263 	missile->s.weapon = WP_BRYAR_PISTOL;
264 
265 	if ( altFire )
266 	{
267 		float boxSize = 0;
268 
269 		count = ( level.time - ent->client->ps.weaponChargeTime ) / BRYAR_CHARGE_UNIT;
270 
271 		if ( count < 1 )
272 		{
273 			count = 1;
274 		}
275 		else if ( count > 5 )
276 		{
277 			count = 5;
278 		}
279 
280 		if (count > 1)
281 		{
282 			damage *= (count*1.7);
283 		}
284 		else
285 		{
286 			damage *= (count*1.5);
287 		}
288 
289 		missile->s.generic1 = count; // The missile will then render according to the charge level.
290 
291 		boxSize = BRYAR_ALT_SIZE*(count*0.5);
292 
293 		VectorSet( missile->r.maxs, boxSize, boxSize, boxSize );
294 		VectorSet( missile->r.mins, -boxSize, -boxSize, -boxSize );
295 	}
296 
297 	missile->damage = damage;
298 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
299 	if (altFire)
300 	{
301 		missile->methodOfDeath = MOD_BRYAR_PISTOL_ALT;
302 	}
303 	else
304 	{
305 		missile->methodOfDeath = MOD_BRYAR_PISTOL;
306 	}
307 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
308 
309 	// we don't want it to bounce forever
310 	missile->bounceCount = 8;
311 }
312 
313 /*
314 ======================================================================
315 
316 GENERIC
317 
318 ======================================================================
319 */
320 
321 //---------------------------------------------------------
WP_FireTurretMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire,int damage,int velocity,int mod,gentity_t * ignore)322 void WP_FireTurretMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod, gentity_t *ignore )
323 //---------------------------------------------------------
324 {
325 	gentity_t *missile;
326 
327 	missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
328 
329 	missile->classname = "generic_proj";
330 	missile->s.weapon = WP_TURRET;
331 
332 	missile->damage = damage;
333 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
334 	missile->methodOfDeath = mod;
335 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
336 
337 	if (ignore)
338 	{
339 		missile->passThroughNum = ignore->s.number+1;
340 	}
341 
342 	// we don't want it to bounce forever
343 	missile->bounceCount = 8;
344 }
345 
346 //-----------------------------------------------------------------------------
WP_Explode(gentity_t * self)347 void WP_Explode( gentity_t *self )
348 //-----------------------------------------------------------------------------
349 {
350 	gentity_t	*attacker = self;
351 	vec3_t		forwardVec={0,0,1};
352 
353 	// stop chain reaction runaway loops
354 	self->takedamage = qfalse;
355 
356 	self->s.loopSound = 0;
357 
358 //	VectorCopy( self->currentOrigin, self->s.pos.trBase );
359 	if ( !self->client )
360 	{
361 	AngleVectors( self->s.angles, forwardVec, NULL, NULL );
362 	}
363 
364 	// FIXME
365 	/*if ( self->e > 0 )
366 	{
367 		G_PlayEffect( self->fxID, self->r.currentOrigin, forwardVec );
368 	}*/
369 
370 	if ( self->s.owner && self->s.owner != 1023 )
371 	{
372 		attacker = &g_entities[self->s.owner];
373 	}
374 	else if ( self->activator )
375 	{
376 		attacker = self->activator;
377 	}
378 	else if ( self->client )
379 	{
380 		attacker = self;
381 	}
382 
383 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
384 	{
385 		G_RadiusDamage( self->r.currentOrigin, attacker, self->splashDamage, self->splashRadius, 0/*don't ignore attacker*/, self, MOD_UNKNOWN );
386 	}
387 
388 	if ( self->target )
389 	{
390 		G_UseTargets( self, attacker );
391 	}
392 
393 	G_SetOrigin( self, self->r.currentOrigin );
394 
395 	self->nextthink = level.time + 50;
396 	self->think = G_FreeEntity;
397 }
398 
399 //Currently only the seeker drone uses this, but it might be useful for other things as well.
400 
401 //---------------------------------------------------------
WP_FireGenericBlasterMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire,int damage,int velocity,int mod)402 void WP_FireGenericBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod )
403 //---------------------------------------------------------
404 {
405 	gentity_t *missile;
406 
407 	missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
408 
409 	missile->classname = "generic_proj";
410 	missile->s.weapon = WP_BRYAR_PISTOL;
411 
412 	missile->damage = damage;
413 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
414 	missile->methodOfDeath = mod;
415 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
416 
417 	// we don't want it to bounce forever
418 	missile->bounceCount = 8;
419 }
420 
421 /*
422 ======================================================================
423 
424 BLASTER
425 
426 ======================================================================
427 */
428 
429 //---------------------------------------------------------
WP_FireBlasterMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire)430 void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire )
431 //---------------------------------------------------------
432 {
433 	int velocity	= BLASTER_VELOCITY;
434 	int	damage		= BLASTER_DAMAGE;
435 	gentity_t *missile;
436 
437 	if (ent->s.eType == ET_NPC)
438 	{ //animent
439 		damage = 10;
440 	}
441 
442 	missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
443 
444 	missile->classname = "blaster_proj";
445 	missile->s.weapon = WP_BLASTER;
446 
447 	missile->damage = damage;
448 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
449 	missile->methodOfDeath = MOD_BLASTER;
450 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
451 
452 	// we don't want it to bounce forever
453 	missile->bounceCount = 8;
454 }
455 
456 //---------------------------------------------------------
WP_FireTurboLaserMissile(gentity_t * ent,vec3_t start,vec3_t dir)457 void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir )
458 //---------------------------------------------------------
459 {
460 	int velocity	= ent->mass; //FIXME: externalize
461 	gentity_t *missile;
462 
463 	missile = CreateMissile( start, dir, velocity, 10000, ent, qfalse );
464 
465 	//use a custom shot effect
466 	missile->s.otherEntityNum2 = ent->genericValue14;
467 	//use a custom impact effect
468 	missile->s.emplacedOwner = ent->genericValue15;
469 
470 	missile->classname = "turbo_proj";
471 	missile->s.weapon = WP_TURRET;
472 
473 	missile->damage = ent->damage;		//FIXME: externalize
474 	missile->splashDamage = ent->splashDamage;	//FIXME: externalize
475 	missile->splashRadius = ent->splashRadius;	//FIXME: externalize
476 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
477 	missile->methodOfDeath = MOD_TURBLAST; //count as a heavy weap
478 	missile->splashMethodOfDeath = MOD_TURBLAST;// ?SPLASH;
479 	missile->clipmask = MASK_SHOT;
480 
481 	// we don't want it to bounce forever
482 	missile->bounceCount = 8;
483 
484 	//set veh as cgame side owner for purpose of fx overrides
485 	missile->s.owner = ent->s.number;
486 
487 	//don't let them last forever
488 	missile->think = G_FreeEntity;
489 	missile->nextthink = level.time + 5000;//at 20000 speed, that should be more than enough
490 }
491 
492 //---------------------------------------------------------
WP_FireEmplacedMissile(gentity_t * ent,vec3_t start,vec3_t dir,qboolean altFire,gentity_t * ignore)493 void WP_FireEmplacedMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, gentity_t *ignore )
494 //---------------------------------------------------------
495 {
496 	int velocity	= BLASTER_VELOCITY;
497 	int	damage		= BLASTER_DAMAGE;
498 	gentity_t *missile;
499 
500 	missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
501 
502 	missile->classname = "emplaced_gun_proj";
503 	missile->s.weapon = WP_TURRET;//WP_EMPLACED_GUN;
504 
505 	missile->activator = ignore;
506 
507 	missile->damage = damage;
508 	missile->dflags = (DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS);
509 	missile->methodOfDeath = MOD_VEHICLE;
510 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
511 
512 	if (ignore)
513 	{
514 		missile->passThroughNum = ignore->s.number+1;
515 	}
516 
517 	// we don't want it to bounce forever
518 	missile->bounceCount = 8;
519 }
520 
521 //---------------------------------------------------------
WP_FireBlaster(gentity_t * ent,qboolean altFire)522 static void WP_FireBlaster( gentity_t *ent, qboolean altFire )
523 //---------------------------------------------------------
524 {
525 	vec3_t  dir, angs;
526 
527 	vectoangles( forward, angs );
528 
529 	if ( altFire )
530 	{
531 		// add some slop to the alt-fire direction
532 		angs[PITCH] += Q_flrand(-1.0f, 1.0f) * BLASTER_SPREAD;
533 		angs[YAW]       += Q_flrand(-1.0f, 1.0f) * BLASTER_SPREAD;
534 	}
535 
536 	AngleVectors( angs, dir, NULL, NULL );
537 
538 	// FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot!
539 	WP_FireBlasterMissile( ent, muzzle, dir, altFire );
540 }
541 
542 
543 int G_GetHitLocation(gentity_t *target, vec3_t ppoint);
544 
545 /*
546 ======================================================================
547 
548 DISRUPTOR
549 
550 ======================================================================
551 */
552 //---------------------------------------------------------
WP_DisruptorMainFire(gentity_t * ent)553 static void WP_DisruptorMainFire( gentity_t *ent )
554 //---------------------------------------------------------
555 {
556 	int			damage = DISRUPTOR_MAIN_DAMAGE;
557 	qboolean	render_impact = qtrue;
558 	vec3_t		start, end;
559 	trace_t		tr;
560 	gentity_t	*traceEnt, *tent;
561 	float		shotRange = 8192;
562 	int			ignore, traces;
563 
564 	if ( level.gametype == GT_SIEGE )
565 	{
566 		damage = DISRUPTOR_MAIN_DAMAGE_SIEGE;
567 	}
568 
569 	memset(&tr, 0, sizeof(tr)); //to shut the compiler up
570 
571 	VectorCopy( ent->client->ps.origin, start );
572 	start[2] += ent->client->ps.viewheight;//By eyes
573 
574 	VectorMA( start, shotRange, forward, end );
575 
576 	ignore = ent->s.number;
577 	traces = 0;
578 	while ( traces < 10 )
579 	{//need to loop this in case we hit a Jedi who dodges the shot
580 		if (d_projectileGhoul2Collision.integer)
581 		{
582 			trap->Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
583 		}
584 		else
585 		{
586 			trap->Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, qfalse, 0, 0 );
587 		}
588 
589 		traceEnt = &g_entities[tr.entityNum];
590 
591 		if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
592 		{ //g2 collision checks -rww
593 			if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
594 			{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
595 				traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
596 				traceEnt->client->g2LastSurfaceTime = level.time;
597 			}
598 
599 			if (traceEnt->ghoul2)
600 			{
601 				tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
602 			}
603 		}
604 
605 		if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
606 			traceEnt->client->ps.duelIndex != ent->s.number)
607 		{
608 			VectorCopy( tr.endpos, start );
609 			ignore = tr.entityNum;
610 			traces++;
611 			continue;
612 		}
613 
614 		if ( Jedi_DodgeEvasion( traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos) ) )
615 		{//act like we didn't even hit him
616 			VectorCopy( tr.endpos, start );
617 			ignore = tr.entityNum;
618 			traces++;
619 			continue;
620 		}
621 		else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
622 		{
623 			if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR, qtrue, 0))
624 			{ //broadcast and stop the shot because it was blocked
625 				gentity_t *te = NULL;
626 
627 				tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
628 				VectorCopy( muzzle, tent->s.origin2 );
629 				tent->s.eventParm = ent->s.number;
630 
631 				te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
632 				VectorCopy(tr.endpos, te->s.origin);
633 				VectorCopy(tr.plane.normal, te->s.angles);
634 				if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
635 				{
636 					te->s.angles[1] = 1;
637 				}
638 				te->s.eventParm = 0;
639 				te->s.weapon = 0;//saberNum
640 				te->s.legsAnim = 0;//bladeNum
641 
642 				return;
643 			}
644 		}
645 		else if ( (traceEnt->flags&FL_SHIELDED) )
646 		{//stopped cold
647 			return;
648 		}
649 		//a Jedi is not dodging this shot
650 		break;
651 	}
652 
653 	if ( tr.surfaceFlags & SURF_NOIMPACT )
654 	{
655 		render_impact = qfalse;
656 	}
657 
658 	// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
659 	tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
660 	VectorCopy( muzzle, tent->s.origin2 );
661 	tent->s.eventParm = ent->s.number;
662 
663 	traceEnt = &g_entities[tr.entityNum];
664 
665 	if ( render_impact )
666 	{
667 		if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
668 		{
669 			if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
670 			{
671 				ent->client->accuracy_hits++;
672 			}
673 
674 			G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NORMAL, MOD_DISRUPTOR );
675 
676 			tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
677 			tent->s.eventParm = DirToByte( tr.plane.normal );
678 			if (traceEnt->client)
679 			{
680 				tent->s.weapon = 1;
681 			}
682 		}
683 		else
684 		{
685 			 // Hmmm, maybe don't make any marks on things that could break
686 			tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
687 			tent->s.eventParm = DirToByte( tr.plane.normal );
688 			tent->s.weapon = 1;
689 		}
690 	}
691 }
692 
693 
G_CanDisruptify(gentity_t * ent)694 qboolean G_CanDisruptify(gentity_t *ent)
695 {
696 	if (!ent || !ent->inuse || !ent->client || ent->s.eType != ET_NPC ||
697 		ent->s.NPC_class != CLASS_VEHICLE || !ent->m_pVehicle)
698 	{ //not vehicle
699 		return qtrue;
700 	}
701 
702 	if (ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
703 	{ //animal is only type that can be disintigeiteigerated
704 		return qtrue;
705 	}
706 
707 	//don't do it to any other veh
708 	return qfalse;
709 }
710 
711 //---------------------------------------------------------
WP_DisruptorAltFire(gentity_t * ent)712 void WP_DisruptorAltFire( gentity_t *ent )
713 //---------------------------------------------------------
714 {
715 	int			damage = 0, skip;
716 	qboolean	render_impact = qtrue;
717 	vec3_t		start, end;
718 	//vec3_t		muzzle2;
719 	trace_t		tr;
720 	gentity_t	*traceEnt, *tent;
721 	float		shotRange = 8192.0f;
722 	int			i;
723 	int			count, maxCount = 60;
724 	int			traces = DISRUPTOR_ALT_TRACES;
725 	qboolean	fullCharge = qfalse;
726 
727 	damage = DISRUPTOR_ALT_DAMAGE-30;
728 
729 	//VectorCopy( muzzle, muzzle2 ); // making a backup copy
730 
731 	if (ent->client)
732 	{
733 		VectorCopy( ent->client->ps.origin, start );
734 		start[2] += ent->client->ps.viewheight;//By eyes
735 
736 		count = ( level.time - ent->client->ps.weaponChargeTime ) / DISRUPTOR_CHARGE_UNIT;
737 		if ( level.gametype == GT_SIEGE )
738 		{//maybe a full alt-charge should be a *bit* more dangerous in Siege mode?
739 			//maxCount = ceil((200.0f-(float)damage)/2.0f);//cap at 200 damage total
740 			maxCount = 200;//the previous line ALWAYS evaluated to 135 - was that on purpose?
741 		}
742 	}
743 	else
744 	{
745 		VectorCopy( ent->r.currentOrigin, start );
746 		start[2] += 24;
747 
748 		count = ( 100 ) / DISRUPTOR_CHARGE_UNIT;
749 	}
750 
751 	count *= 2;
752 
753 	if ( count < 1 )
754 	{
755 		count = 1;
756 	}
757 	else if ( count >= maxCount )
758 	{
759 		count = maxCount;
760 		fullCharge = qtrue;
761 	}
762 
763 	// more powerful charges go through more things
764 	if ( count < 10 )
765 	{
766 		traces = 1;
767 	}
768 	else if ( count < 20 )
769 	{
770 		traces = 2;
771 	}
772 
773 	damage += count;
774 
775 	skip = ent->s.number;
776 
777 	for (i = 0; i < traces; i++ )
778 	{
779 		VectorMA( start, shotRange, forward, end );
780 
781 		if (d_projectileGhoul2Collision.integer)
782 		{
783 			trap->Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
784 		}
785 		else
786 		{
787 			trap->Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, qfalse, 0, 0 );
788 		}
789 
790 		if ( tr.entityNum == ent->s.number )
791 		{
792 			// should never happen, but basically we don't want to consider a hit to ourselves?
793 			// Get ready for an attempt to trace through another person
794 			//VectorCopy( tr.endpos, muzzle2 );
795 			VectorCopy( tr.endpos, start );
796 			skip = tr.entityNum;
797 #ifdef _DEBUG
798 			trap->Print( "BAD! Disruptor gun shot somehow traced back and hit the owner!\n" );
799 #endif
800 			continue;
801 		}
802 
803 		traceEnt = &g_entities[tr.entityNum];
804 
805 		if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
806 		{ //g2 collision checks -rww
807 			if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
808 			{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
809 				traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
810 				traceEnt->client->g2LastSurfaceTime = level.time;
811 			}
812 
813 			if (traceEnt->ghoul2)
814 			{
815 				tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
816 			}
817 		}
818 
819 		if ( tr.surfaceFlags & SURF_NOIMPACT )
820 		{
821 			render_impact = qfalse;
822 		}
823 
824 		if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
825 			traceEnt->client->ps.duelIndex != ent->s.number)
826 		{
827 			skip = tr.entityNum;
828 			VectorCopy(tr.endpos, start);
829 			continue;
830 		}
831 
832 		if (Jedi_DodgeEvasion(traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos)))
833 		{
834 			skip = tr.entityNum;
835 			VectorCopy(tr.endpos, start);
836 			continue;
837 		}
838 		else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
839 		{
840 			if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR_SNIPER, qtrue, 0))
841 			{ //broadcast and stop the shot because it was blocked
842 				gentity_t *te = NULL;
843 
844 				tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
845 				VectorCopy( muzzle, tent->s.origin2 );
846 				tent->s.shouldtarget = fullCharge;
847 				tent->s.eventParm = ent->s.number;
848 
849 				te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
850 				VectorCopy(tr.endpos, te->s.origin);
851 				VectorCopy(tr.plane.normal, te->s.angles);
852 				if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
853 				{
854 					te->s.angles[1] = 1;
855 				}
856 				te->s.eventParm = 0;
857 				te->s.weapon = 0;//saberNum
858 				te->s.legsAnim = 0;//bladeNum
859 
860 				return;
861 			}
862 		}
863 
864 		// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
865 		tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
866 		VectorCopy( muzzle, tent->s.origin2 );
867 		tent->s.shouldtarget = fullCharge;
868 		tent->s.eventParm = ent->s.number;
869 
870 		// If the beam hits a skybox, etc. it would look foolish to add impact effects
871 		if ( render_impact )
872 		{
873 			if ( traceEnt->takedamage && traceEnt->client )
874 			{
875 				tent->s.otherEntityNum = traceEnt->s.number;
876 
877 				// Create a simple impact type mark
878 				tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
879 				tent->s.eventParm = DirToByte(tr.plane.normal);
880 				tent->s.eFlags |= EF_ALT_FIRING;
881 
882 				if ( LogAccuracyHit( traceEnt, ent ))
883 				{
884 					if (ent->client)
885 					{
886 						ent->client->accuracy_hits++;
887 					}
888 				}
889 			}
890 			else
891 			{
892 				 if ( traceEnt->r.svFlags & SVF_GLASS_BRUSH
893 						|| traceEnt->takedamage
894 						|| traceEnt->s.eType == ET_MOVER )
895 				 {
896 					if ( traceEnt->takedamage )
897 					{
898 						G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
899 								DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER );
900 
901 						tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
902 						tent->s.eventParm = DirToByte( tr.plane.normal );
903 					}
904 				 }
905 				 else
906 				 {
907 					 // Hmmm, maybe don't make any marks on things that could break
908 					tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
909 					tent->s.eventParm = DirToByte( tr.plane.normal );
910 				 }
911 				break; // and don't try any more traces
912 			}
913 
914 			if ( (traceEnt->flags&FL_SHIELDED) )
915 			{//stops us cold
916 				break;
917 			}
918 
919 			if ( traceEnt->takedamage )
920 			{
921 				vec3_t preAng;
922 				int preHealth = traceEnt->health;
923 				int preLegs = 0;
924 				int preTorso = 0;
925 
926 				if (traceEnt->client)
927 				{
928 					preLegs = traceEnt->client->ps.legsAnim;
929 					preTorso = traceEnt->client->ps.torsoAnim;
930 					VectorCopy(traceEnt->client->ps.viewangles, preAng);
931 				}
932 
933 				G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER );
934 
935 				if (traceEnt->client && preHealth > 0 && traceEnt->health <= 0 && fullCharge &&
936 					G_CanDisruptify(traceEnt))
937 				{ //was killed by a fully charged sniper shot, so disintegrate
938 					VectorCopy(preAng, traceEnt->client->ps.viewangles);
939 
940 					traceEnt->client->ps.eFlags |= EF_DISINTEGRATION;
941 					VectorCopy(tr.endpos, traceEnt->client->ps.lastHitLoc);
942 
943 					traceEnt->client->ps.legsAnim = preLegs;
944 					traceEnt->client->ps.torsoAnim = preTorso;
945 
946 					traceEnt->r.contents = 0;
947 
948 					VectorClear(traceEnt->client->ps.velocity);
949 				}
950 
951 				tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
952 				tent->s.eventParm = DirToByte( tr.plane.normal );
953 				if (traceEnt->client)
954 				{
955 					tent->s.weapon = 1;
956 				}
957 			}
958 		}
959 		else // not rendering impact, must be a skybox or other similar thing?
960 		{
961 			break; // don't try anymore traces
962 		}
963 
964 		// Get ready for an attempt to trace through another person
965 		VectorCopy( tr.endpos, muzzle );
966 		VectorCopy( tr.endpos, start );
967 		skip = tr.entityNum;
968 	}
969 }
970 
971 
972 //---------------------------------------------------------
WP_FireDisruptor(gentity_t * ent,qboolean altFire)973 static void WP_FireDisruptor( gentity_t *ent, qboolean altFire )
974 //---------------------------------------------------------
975 {
976 	if (!ent || !ent->client || ent->client->ps.zoomMode != 1)
977 	{ //do not ever let it do the alt fire when not zoomed
978 		altFire = qfalse;
979 	}
980 
981 	if (ent && ent->s.eType == ET_NPC && !ent->client)
982 	{ //special case for animents
983 		WP_DisruptorAltFire( ent );
984 		return;
985 	}
986 
987 	if ( altFire )
988 	{
989 		WP_DisruptorAltFire( ent );
990 	}
991 	else
992 	{
993 		WP_DisruptorMainFire( ent );
994 	}
995 }
996 
997 
998 /*
999 ======================================================================
1000 
1001 BOWCASTER
1002 
1003 ======================================================================
1004 */
1005 
WP_BowcasterAltFire(gentity_t * ent)1006 static void WP_BowcasterAltFire( gentity_t *ent )
1007 {
1008 	int	damage	= BOWCASTER_DAMAGE;
1009 
1010 	gentity_t *missile = CreateMissile( muzzle, forward, BOWCASTER_VELOCITY, 10000, ent, qfalse);
1011 
1012 	missile->classname = "bowcaster_proj";
1013 	missile->s.weapon = WP_BOWCASTER;
1014 
1015 	VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
1016 	VectorScale( missile->r.maxs, -1, missile->r.mins );
1017 
1018 	missile->damage = damage;
1019 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1020 	missile->methodOfDeath = MOD_BOWCASTER;
1021 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1022 
1023 	missile->flags |= FL_BOUNCE;
1024 	missile->bounceCount = 3;
1025 }
1026 
1027 //---------------------------------------------------------
WP_BowcasterMainFire(gentity_t * ent)1028 static void WP_BowcasterMainFire( gentity_t *ent )
1029 //---------------------------------------------------------
1030 {
1031 	int			damage	= BOWCASTER_DAMAGE, count;
1032 	float		vel;
1033 	vec3_t		angs, dir;
1034 	gentity_t	*missile;
1035 	int i;
1036 
1037 	if (!ent->client)
1038 	{
1039 		count = 1;
1040 	}
1041 	else
1042 	{
1043 		count = ( level.time - ent->client->ps.weaponChargeTime ) / BOWCASTER_CHARGE_UNIT;
1044 	}
1045 
1046 	if ( count < 1 )
1047 	{
1048 		count = 1;
1049 	}
1050 	else if ( count > 5 )
1051 	{
1052 		count = 5;
1053 	}
1054 
1055 	if ( !(count & 1 ))
1056 	{
1057 		// if we aren't odd, knock us down a level
1058 		count--;
1059 	}
1060 
1061 	//scale the damage down based on how many are about to be fired
1062 	if (count <= 1)
1063 	{
1064 		damage = 50;
1065 	}
1066 	else if (count == 2)
1067 	{
1068 		damage = 45;
1069 	}
1070 	else if (count == 3)
1071 	{
1072 		damage = 40;
1073 	}
1074 	else if (count == 4)
1075 	{
1076 		damage = 35;
1077 	}
1078 	else
1079 	{
1080 		damage = 30;
1081 	}
1082 
1083 	for (i = 0; i < count; i++ )
1084 	{
1085 		// create a range of different velocities
1086 		vel = BOWCASTER_VELOCITY * ( Q_flrand(-1.0f, 1.0f) * BOWCASTER_VEL_RANGE + 1.0f );
1087 
1088 		vectoangles( forward, angs );
1089 
1090 		// add some slop to the alt-fire direction
1091 		angs[PITCH] += Q_flrand(-1.0f, 1.0f) * BOWCASTER_ALT_SPREAD * 0.2f;
1092 		angs[YAW]	+= ((i+0.5f) * BOWCASTER_ALT_SPREAD - count * 0.5f * BOWCASTER_ALT_SPREAD );
1093 
1094 		AngleVectors( angs, dir, NULL, NULL );
1095 
1096 		missile = CreateMissile( muzzle, dir, vel, 10000, ent, qtrue );
1097 
1098 		missile->classname = "bowcaster_alt_proj";
1099 		missile->s.weapon = WP_BOWCASTER;
1100 
1101 		VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
1102 		VectorScale( missile->r.maxs, -1, missile->r.mins );
1103 
1104 		missile->damage = damage;
1105 		missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1106 		missile->methodOfDeath = MOD_BOWCASTER;
1107 		missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1108 
1109 		// we don't want it to bounce
1110 		missile->bounceCount = 0;
1111 	}
1112 }
1113 
1114 //---------------------------------------------------------
WP_FireBowcaster(gentity_t * ent,qboolean altFire)1115 static void WP_FireBowcaster( gentity_t *ent, qboolean altFire )
1116 //---------------------------------------------------------
1117 {
1118 	if ( altFire )
1119 	{
1120 		WP_BowcasterAltFire( ent );
1121 	}
1122 	else
1123 	{
1124 		WP_BowcasterMainFire( ent );
1125 	}
1126 }
1127 
1128 
1129 
1130 /*
1131 ======================================================================
1132 
1133 REPEATER
1134 
1135 ======================================================================
1136 */
1137 
1138 //---------------------------------------------------------
WP_RepeaterMainFire(gentity_t * ent,vec3_t dir)1139 static void WP_RepeaterMainFire( gentity_t *ent, vec3_t dir )
1140 //---------------------------------------------------------
1141 {
1142 	int	damage	= REPEATER_DAMAGE;
1143 
1144 	gentity_t *missile = CreateMissile( muzzle, dir, REPEATER_VELOCITY, 10000, ent, qfalse );
1145 
1146 	missile->classname = "repeater_proj";
1147 	missile->s.weapon = WP_REPEATER;
1148 
1149 	missile->damage = damage;
1150 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1151 	missile->methodOfDeath = MOD_REPEATER;
1152 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1153 
1154 	// we don't want it to bounce forever
1155 	missile->bounceCount = 8;
1156 }
1157 
1158 //---------------------------------------------------------
WP_RepeaterAltFire(gentity_t * ent)1159 static void WP_RepeaterAltFire( gentity_t *ent )
1160 //---------------------------------------------------------
1161 {
1162 	int	damage	= REPEATER_ALT_DAMAGE;
1163 
1164 	gentity_t *missile = CreateMissile( muzzle, forward, REPEATER_ALT_VELOCITY, 10000, ent, qtrue );
1165 
1166 	missile->classname = "repeater_alt_proj";
1167 	missile->s.weapon = WP_REPEATER;
1168 
1169 	VectorSet( missile->r.maxs, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE );
1170 	VectorScale( missile->r.maxs, -1, missile->r.mins );
1171 	missile->s.pos.trType = TR_GRAVITY;
1172 	missile->s.pos.trDelta[2] += 40.0f; //give a slight boost in the upward direction
1173 	missile->damage = damage;
1174 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1175 	missile->methodOfDeath = MOD_REPEATER_ALT;
1176 	missile->splashMethodOfDeath = MOD_REPEATER_ALT_SPLASH;
1177 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1178 	missile->splashDamage = REPEATER_ALT_SPLASH_DAMAGE;
1179 	if ( level.gametype == GT_SIEGE )	// we've been having problems with this being too hyper-potent because of it's radius
1180 	{
1181 		missile->splashRadius = REPEATER_ALT_SPLASH_RAD_SIEGE;
1182 	}
1183 	else
1184 	{
1185 		missile->splashRadius = REPEATER_ALT_SPLASH_RADIUS;
1186 	}
1187 
1188 	// we don't want it to bounce forever
1189 	missile->bounceCount = 8;
1190 }
1191 
1192 //---------------------------------------------------------
WP_FireRepeater(gentity_t * ent,qboolean altFire)1193 static void WP_FireRepeater( gentity_t *ent, qboolean altFire )
1194 //---------------------------------------------------------
1195 {
1196 	vec3_t	dir, angs;
1197 
1198 	vectoangles( forward, angs );
1199 
1200 	if ( altFire )
1201 	{
1202 		WP_RepeaterAltFire( ent );
1203 	}
1204 	else
1205 	{
1206 		// add some slop to the alt-fire direction
1207 		angs[PITCH] += Q_flrand(-1.0f, 1.0f) * REPEATER_SPREAD;
1208 		angs[YAW]	+= Q_flrand(-1.0f, 1.0f) * REPEATER_SPREAD;
1209 
1210 		AngleVectors( angs, dir, NULL, NULL );
1211 
1212 		WP_RepeaterMainFire( ent, dir );
1213 	}
1214 }
1215 
1216 
1217 /*
1218 ======================================================================
1219 
1220 DEMP2
1221 
1222 ======================================================================
1223 */
1224 
WP_DEMP2_MainFire(gentity_t * ent)1225 static void WP_DEMP2_MainFire( gentity_t *ent )
1226 {
1227 	int	damage	= DEMP2_DAMAGE;
1228 
1229 	gentity_t *missile = CreateMissile( muzzle, forward, DEMP2_VELOCITY, 10000, ent, qfalse);
1230 
1231 	missile->classname = "demp2_proj";
1232 	missile->s.weapon = WP_DEMP2;
1233 
1234 	VectorSet( missile->r.maxs, DEMP2_SIZE, DEMP2_SIZE, DEMP2_SIZE );
1235 	VectorScale( missile->r.maxs, -1, missile->r.mins );
1236 	missile->damage = damage;
1237 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1238 	missile->methodOfDeath = MOD_DEMP2;
1239 	missile->clipmask = MASK_SHOT;
1240 
1241 	// we don't want it to ever bounce
1242 	missile->bounceCount = 0;
1243 }
1244 
1245 static gentity_t *ent_list[MAX_GENTITIES];
1246 
DEMP2_AltRadiusDamage(gentity_t * ent)1247 void DEMP2_AltRadiusDamage( gentity_t *ent )
1248 {
1249 	float		frac = ( level.time - ent->genericValue5 ) / 800.0f; // / 1600.0f; // synchronize with demp2 effect
1250 	float		dist, radius, fact;
1251 	gentity_t	*gent;
1252 	int			iEntityList[MAX_GENTITIES];
1253 	gentity_t	*entityList[MAX_GENTITIES];
1254 	gentity_t	*myOwner = NULL;
1255 	int			numListedEntities, i, e;
1256 	vec3_t		mins, maxs;
1257 	vec3_t		v, dir;
1258 
1259 	if (ent->r.ownerNum >= 0 &&
1260 		ent->r.ownerNum < /*MAX_CLIENTS ... let npc's/shooters use it*/MAX_GENTITIES)
1261 	{
1262 		myOwner = &g_entities[ent->r.ownerNum];
1263 	}
1264 
1265 	if (!myOwner || !myOwner->inuse || !myOwner->client)
1266 	{
1267 		ent->think = G_FreeEntity;
1268 		ent->nextthink = level.time;
1269 		return;
1270 	}
1271 
1272 	frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end
1273 
1274 	radius = frac * 200.0f; // 200 is max radius...the model is aprox. 100 units tall...the fx draw code mults. this by 2.
1275 
1276 	fact = ent->count*0.6;
1277 
1278 	if (fact < 1)
1279 	{
1280 		fact = 1;
1281 	}
1282 
1283 	radius *= fact;
1284 
1285 	for ( i = 0 ; i < 3 ; i++ )
1286 	{
1287 		mins[i] = ent->r.currentOrigin[i] - radius;
1288 		maxs[i] = ent->r.currentOrigin[i] + radius;
1289 	}
1290 
1291 	numListedEntities = trap->EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES );
1292 
1293 	i = 0;
1294 	while (i < numListedEntities)
1295 	{
1296 		entityList[i] = &g_entities[iEntityList[i]];
1297 		i++;
1298 	}
1299 
1300 	for ( e = 0 ; e < numListedEntities ; e++ )
1301 	{
1302 		gent = entityList[ e ];
1303 
1304 		if ( !gent || !gent->takedamage || !gent->r.contents )
1305 		{
1306 			continue;
1307 		}
1308 
1309 		// find the distance from the edge of the bounding box
1310 		for ( i = 0 ; i < 3 ; i++ )
1311 		{
1312 			if ( ent->r.currentOrigin[i] < gent->r.absmin[i] )
1313 			{
1314 				v[i] = gent->r.absmin[i] - ent->r.currentOrigin[i];
1315 			}
1316 			else if ( ent->r.currentOrigin[i] > gent->r.absmax[i] )
1317 			{
1318 				v[i] = ent->r.currentOrigin[i] - gent->r.absmax[i];
1319 			}
1320 			else
1321 			{
1322 				v[i] = 0;
1323 			}
1324 		}
1325 
1326 		// shape is an ellipsoid, so cut vertical distance in half`
1327 		v[2] *= 0.5f;
1328 
1329 		dist = VectorLength( v );
1330 
1331 		if ( dist >= radius )
1332 		{
1333 			// shockwave hasn't hit them yet
1334 			continue;
1335 		}
1336 
1337 		if (dist+(16*ent->count) < ent->genericValue6)
1338 		{
1339 			// shockwave has already hit this thing...
1340 			continue;
1341 		}
1342 
1343 		VectorCopy( gent->r.currentOrigin, v );
1344 		VectorSubtract( v, ent->r.currentOrigin, dir);
1345 
1346 		// push the center of mass higher than the origin so players get knocked into the air more
1347 		dir[2] += 12;
1348 
1349 		if (gent != myOwner)
1350 		{
1351 			G_Damage( gent, myOwner, myOwner, dir, ent->r.currentOrigin, ent->damage, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath );
1352 			if ( gent->takedamage
1353 				&& gent->client )
1354 			{
1355 				if ( gent->client->ps.electrifyTime < level.time )
1356 				{//electrocution effect
1357 					if (gent->s.eType == ET_NPC && gent->s.NPC_class == CLASS_VEHICLE &&
1358 						gent->m_pVehicle && (gent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || gent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER))
1359 					{ //do some extra stuff to speeders/walkers
1360 						gent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
1361 					}
1362 					else if ( gent->s.NPC_class != CLASS_VEHICLE
1363 						|| (gent->m_pVehicle && gent->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) )
1364 					{//don't do this to fighters
1365 						gent->client->ps.electrifyTime = level.time + Q_irand( 300, 800 );
1366 					}
1367 				}
1368 				if ( gent->client->ps.powerups[PW_CLOAKED] )
1369 				{//disable cloak temporarily
1370 					Jedi_Decloak( gent );
1371 					gent->client->cloakToggleTime = level.time + Q_irand( 3000, 10000 );
1372 				}
1373 			}
1374 		}
1375 	}
1376 
1377 	// store the last fraction so that next time around we can test against those things that fall between that last point and where the current shockwave edge is
1378 	ent->genericValue6 = radius;
1379 
1380 	if ( frac < 1.0f )
1381 	{
1382 		// shock is still happening so continue letting it expand
1383 		ent->nextthink = level.time + 50;
1384 	}
1385 	else
1386 	{ //don't just leave the entity around
1387 		ent->think = G_FreeEntity;
1388 		ent->nextthink = level.time;
1389 	}
1390 }
1391 
1392 //---------------------------------------------------------
DEMP2_AltDetonate(gentity_t * ent)1393 void DEMP2_AltDetonate( gentity_t *ent )
1394 //---------------------------------------------------------
1395 {
1396 	gentity_t *efEnt;
1397 
1398 	G_SetOrigin( ent, ent->r.currentOrigin );
1399 	if (!ent->pos1[0] && !ent->pos1[1] && !ent->pos1[2])
1400 	{ //don't play effect with a 0'd out directional vector
1401 		ent->pos1[1] = 1;
1402 	}
1403 	//Let's just save ourself some bandwidth and play both the effect and sphere spawn in 1 event
1404 	efEnt = G_PlayEffect( EFFECT_EXPLOSION_DEMP2ALT, ent->r.currentOrigin, ent->pos1 );
1405 
1406 	if (efEnt)
1407 	{
1408 		efEnt->s.weapon = ent->count*2;
1409 	}
1410 
1411 	ent->genericValue5 = level.time;
1412 	ent->genericValue6 = 0;
1413 	ent->nextthink = level.time + 50;
1414 	ent->think = DEMP2_AltRadiusDamage;
1415 	ent->s.eType = ET_GENERAL; // make us a missile no longer
1416 }
1417 
1418 //---------------------------------------------------------
WP_DEMP2_AltFire(gentity_t * ent)1419 static void WP_DEMP2_AltFire( gentity_t *ent )
1420 //---------------------------------------------------------
1421 {
1422 	int		damage	= DEMP2_ALT_DAMAGE;
1423 	int		count, origcount;
1424 	float	fact;
1425 	vec3_t	start, end;
1426 	trace_t	tr;
1427 	gentity_t *missile;
1428 
1429 	VectorCopy( muzzle, start );
1430 
1431 	VectorMA( start, DEMP2_ALT_RANGE, forward, end );
1432 
1433 	count = ( level.time - ent->client->ps.weaponChargeTime ) / DEMP2_CHARGE_UNIT;
1434 
1435 	origcount = count;
1436 
1437 	if ( count < 1 )
1438 	{
1439 		count = 1;
1440 	}
1441 	else if ( count > 3 )
1442 	{
1443 		count = 3;
1444 	}
1445 
1446 	fact = count*0.8;
1447 	if (fact < 1)
1448 	{
1449 		fact = 1;
1450 	}
1451 	damage *= fact;
1452 
1453 	if (!origcount)
1454 	{ //this was just a tap-fire
1455 		damage = 1;
1456 	}
1457 
1458 	trap->Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT, qfalse, 0, 0);
1459 
1460 	missile = G_Spawn();
1461 	G_SetOrigin(missile, tr.endpos);
1462 	//In SP the impact actually travels as a missile based on the trace fraction, but we're
1463 	//just going to be instant. -rww
1464 
1465 	VectorCopy( tr.plane.normal, missile->pos1 );
1466 
1467 	missile->count = count;
1468 
1469 	missile->classname = "demp2_alt_proj";
1470 	missile->s.weapon = WP_DEMP2;
1471 
1472 	missile->think = DEMP2_AltDetonate;
1473 	missile->nextthink = level.time;
1474 
1475 	missile->splashDamage = missile->damage = damage;
1476 	missile->splashMethodOfDeath = missile->methodOfDeath = MOD_DEMP2;
1477 	missile->splashRadius = DEMP2_ALT_SPLASHRADIUS;
1478 
1479 	missile->r.ownerNum = ent->s.number;
1480 
1481 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1482 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1483 
1484 	// we don't want it to ever bounce
1485 	missile->bounceCount = 0;
1486 }
1487 
1488 //---------------------------------------------------------
WP_FireDEMP2(gentity_t * ent,qboolean altFire)1489 static void WP_FireDEMP2( gentity_t *ent, qboolean altFire )
1490 //---------------------------------------------------------
1491 {
1492 	if ( altFire )
1493 	{
1494 		WP_DEMP2_AltFire( ent );
1495 	}
1496 	else
1497 	{
1498 		WP_DEMP2_MainFire( ent );
1499 	}
1500 }
1501 
1502 
1503 
1504 /*
1505 ======================================================================
1506 
1507 FLECHETTE
1508 
1509 ======================================================================
1510 */
1511 
1512 //---------------------------------------------------------
WP_FlechetteMainFire(gentity_t * ent)1513 static void WP_FlechetteMainFire( gentity_t *ent )
1514 //---------------------------------------------------------
1515 {
1516 	vec3_t		fwd, angs;
1517 	gentity_t	*missile;
1518 	int i;
1519 
1520 	for (i = 0; i < FLECHETTE_SHOTS; i++ )
1521 	{
1522 		vectoangles( forward, angs );
1523 
1524 		if (i != 0)
1525 		{ //do nothing on the first shot, it will hit the crosshairs
1526 			angs[PITCH] += Q_flrand(-1.0f, 1.0f) * FLECHETTE_SPREAD;
1527 			angs[YAW]	+= Q_flrand(-1.0f, 1.0f) * FLECHETTE_SPREAD;
1528 		}
1529 
1530 		AngleVectors( angs, fwd, NULL, NULL );
1531 
1532 		missile = CreateMissile( muzzle, fwd, FLECHETTE_VEL, 10000, ent, qfalse);
1533 
1534 		missile->classname = "flech_proj";
1535 		missile->s.weapon = WP_FLECHETTE;
1536 
1537 		VectorSet( missile->r.maxs, FLECHETTE_SIZE, FLECHETTE_SIZE, FLECHETTE_SIZE );
1538 		VectorScale( missile->r.maxs, -1, missile->r.mins );
1539 
1540 		missile->damage = FLECHETTE_DAMAGE;
1541 		missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1542 		missile->methodOfDeath = MOD_FLECHETTE;
1543 		missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1544 
1545 		// we don't want it to bounce forever
1546 		missile->bounceCount = Q_irand(5,8);
1547 
1548 		missile->flags |= FL_BOUNCE_SHRAPNEL;
1549 	}
1550 }
1551 
1552 //---------------------------------------------------------
prox_mine_think(gentity_t * ent)1553 void prox_mine_think( gentity_t *ent )
1554 //---------------------------------------------------------
1555 {
1556 	int			count, i;
1557 	qboolean	blow = qfalse;
1558 
1559 	// if it isn't time to auto-explode, do a small proximity check
1560 	if ( ent->delay > level.time )
1561 	{
1562 		count = G_RadiusList( ent->r.currentOrigin, FLECHETTE_MINE_RADIUS_CHECK, ent, qtrue, ent_list );
1563 
1564 		for ( i = 0; i < count; i++ )
1565 		{
1566 			if ( ent_list[i]->client && ent_list[i]->health > 0 && ent->activator && ent_list[i]->s.number != ent->activator->s.number )
1567 			{
1568 				blow = qtrue;
1569 				break;
1570 			}
1571 		}
1572 	}
1573 	else
1574 	{
1575 		// well, we must die now
1576 		blow = qtrue;
1577 	}
1578 
1579 	if ( blow )
1580 	{
1581 		ent->think = laserTrapExplode;
1582 		ent->nextthink = level.time + 200;
1583 	}
1584 	else
1585 	{
1586 		// we probably don't need to do this thinking logic very often...maybe this is fast enough?
1587 		ent->nextthink = level.time + 500;
1588 	}
1589 }
1590 
1591 //-----------------------------------------------------------------------------
WP_TraceSetStart(gentity_t * ent,vec3_t start,vec3_t mins,vec3_t maxs)1592 static void WP_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs )
1593 //-----------------------------------------------------------------------------
1594 {
1595 	//make sure our start point isn't on the other side of a wall
1596 	trace_t	tr;
1597 	vec3_t	entMins;
1598 	vec3_t	entMaxs;
1599 
1600 	VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins );
1601 	VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs );
1602 
1603 	if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) )
1604 	{
1605 		return;
1606 	}
1607 
1608 	if ( !ent->client )
1609 	{
1610 		return;
1611 	}
1612 
1613 	trap->Trace( &tr, ent->client->ps.origin, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, qfalse, 0, 0 );
1614 
1615 	if ( tr.startsolid || tr.allsolid )
1616 	{
1617 		return;
1618 	}
1619 
1620 	if ( tr.fraction < 1.0f )
1621 	{
1622 		VectorCopy( tr.endpos, start );
1623 	}
1624 }
1625 
WP_ExplosiveDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1626 void WP_ExplosiveDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
1627 {
1628 	laserTrapExplode(self);
1629 }
1630 
1631 //----------------------------------------------
WP_flechette_alt_blow(gentity_t * ent)1632 void WP_flechette_alt_blow( gentity_t *ent )
1633 //----------------------------------------------
1634 {
1635 	ent->s.pos.trDelta[0] = 1;
1636 	ent->s.pos.trDelta[1] = 0;
1637 	ent->s.pos.trDelta[2] = 0;
1638 
1639 	laserTrapExplode(ent);
1640 }
1641 
1642 //------------------------------------------------------------------------------
WP_CreateFlechetteBouncyThing(vec3_t start,vec3_t fwd,gentity_t * self)1643 static void WP_CreateFlechetteBouncyThing( vec3_t start, vec3_t fwd, gentity_t *self )
1644 //------------------------------------------------------------------------------
1645 {
1646 	gentity_t	*missile = CreateMissile( start, fwd, 700 + Q_flrand(0.0f, 1.0f) * 700, 1500 + Q_flrand(0.0f, 1.0f) * 2000, self, qtrue );
1647 
1648 	missile->think = WP_flechette_alt_blow;
1649 
1650 	missile->activator = self;
1651 
1652 	missile->s.weapon = WP_FLECHETTE;
1653 	missile->classname = "flech_alt";
1654 	missile->mass = 4;
1655 
1656 	// How 'bout we give this thing a size...
1657 	VectorSet( missile->r.mins, -3.0f, -3.0f, -3.0f );
1658 	VectorSet( missile->r.maxs, 3.0f, 3.0f, 3.0f );
1659 	missile->clipmask = MASK_SHOT;
1660 
1661 	missile->touch = touch_NULL;
1662 
1663 	// normal ones bounce, alt ones explode on impact
1664 	missile->s.pos.trType = TR_GRAVITY;
1665 
1666 	missile->flags |= FL_BOUNCE_HALF;
1667 	missile->s.eFlags |= EF_ALT_FIRING;
1668 
1669 	missile->bounceCount = 50;
1670 
1671 	missile->damage = FLECHETTE_ALT_DAMAGE;
1672 	missile->dflags = 0;
1673 	missile->splashDamage = FLECHETTE_ALT_SPLASH_DAM;
1674 	missile->splashRadius = FLECHETTE_ALT_SPLASH_RAD;
1675 
1676 	missile->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1677 
1678 	missile->methodOfDeath = MOD_FLECHETTE_ALT_SPLASH;
1679 	missile->splashMethodOfDeath = MOD_FLECHETTE_ALT_SPLASH;
1680 
1681 	VectorCopy( start, missile->pos2 );
1682 }
1683 
1684 //---------------------------------------------------------
WP_FlechetteAltFire(gentity_t * self)1685 static void WP_FlechetteAltFire( gentity_t *self )
1686 //---------------------------------------------------------
1687 {
1688 	vec3_t 	dir, fwd, start, angs;
1689 	int i;
1690 
1691 	vectoangles( forward, angs );
1692 	VectorCopy( muzzle, start );
1693 
1694 	WP_TraceSetStart( self, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
1695 
1696 	for ( i = 0; i < 2; i++ )
1697 	{
1698 		VectorCopy( angs, dir );
1699 
1700 		dir[PITCH] -= Q_flrand(0.0f, 1.0f) * 4 + 8; // make it fly upwards
1701 		dir[YAW] += Q_flrand(-1.0f, 1.0f) * 2;
1702 		AngleVectors( dir, fwd, NULL, NULL );
1703 
1704 		WP_CreateFlechetteBouncyThing( start, fwd, self );
1705 	}
1706 }
1707 
1708 //---------------------------------------------------------
WP_FireFlechette(gentity_t * ent,qboolean altFire)1709 static void WP_FireFlechette( gentity_t *ent, qboolean altFire )
1710 //---------------------------------------------------------
1711 {
1712 	if ( altFire )
1713 	{
1714 		//WP_FlechetteProxMine( ent );
1715 		WP_FlechetteAltFire(ent);
1716 	}
1717 	else
1718 	{
1719 		WP_FlechetteMainFire( ent );
1720 	}
1721 }
1722 
1723 
1724 
1725 /*
1726 ======================================================================
1727 
1728 ROCKET LAUNCHER
1729 
1730 ======================================================================
1731 */
1732 
1733 //---------------------------------------------------------
rocketThink(gentity_t * ent)1734 void rocketThink( gentity_t *ent )
1735 //---------------------------------------------------------
1736 {
1737 	vec3_t newdir, targetdir,
1738 			rup={0,0,1}, right;
1739 	vec3_t	org;
1740 	float dot, dot2, dis;
1741 	int i;
1742 	float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY;
1743 
1744 	if ( ent->genericValue1 && ent->genericValue1 < level.time )
1745 	{//time's up, we're done, remove us
1746 		if ( ent->genericValue2 )
1747 		{//explode when die
1748 			RocketDie( ent, &g_entities[ent->r.ownerNum], &g_entities[ent->r.ownerNum], 0, MOD_UNKNOWN );
1749 		}
1750 		else
1751 		{//just remove when die
1752 			G_FreeEntity( ent );
1753 		}
1754 		return;
1755 	}
1756 	if ( !ent->enemy
1757 		|| !ent->enemy->client
1758 		|| ent->enemy->health <= 0
1759 		|| ent->enemy->client->ps.powerups[PW_CLOAKED] )
1760 	{//no enemy or enemy not a client or enemy dead or enemy cloaked
1761 		if ( !ent->genericValue1  )
1762 		{//doesn't have its own self-kill time
1763 			ent->nextthink = level.time + 10000;
1764 			ent->think = G_FreeEntity;
1765 		}
1766 		return;
1767 	}
1768 
1769 	if ( (ent->spawnflags&1) )
1770 	{//vehicle rocket
1771 		if ( ent->enemy->client && ent->enemy->client->NPC_class == CLASS_VEHICLE )
1772 		{//tracking another vehicle
1773 			if ( ent->enemy->client->ps.speed+4000 > vel )
1774 			{
1775 				vel = ent->enemy->client->ps.speed+4000;
1776 			}
1777 		}
1778 	}
1779 
1780 	if ( ent->enemy && ent->enemy->inuse )
1781 	{
1782 		float newDirMult = ent->angle?ent->angle*2.0f:1.0f;
1783 		float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f;
1784 
1785 		VectorCopy( ent->enemy->r.currentOrigin, org );
1786 		org[2] += (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5f;
1787 
1788 		VectorSubtract( org, ent->r.currentOrigin, targetdir );
1789 		VectorNormalize( targetdir );
1790 
1791 		// Now the rocket can't do a 180 in space, so we'll limit the turn to about 45 degrees.
1792 		dot = DotProduct( targetdir, ent->movedir );
1793 		if ( (ent->spawnflags&1) )
1794 		{//vehicle rocket
1795 			if ( ent->radius > -1.0f )
1796 			{//can lose the lock if DotProduct drops below this number
1797 				if ( dot < ent->radius )
1798 				{//lost the lock!!!
1799 					//HMM... maybe can re-lock on if they come in front again?
1800 					/*
1801 					//OR: should it stop trying to lock altogether?
1802 					if ( ent->genericValue1 )
1803 					{//have a timelimit, set next think to that
1804 						ent->nextthink = ent->genericValue1;
1805 						if ( ent->genericValue2 )
1806 						{//explode when die
1807 							ent->think = G_ExplodeMissile;
1808 						}
1809 						else
1810 						{
1811 							ent->think = G_FreeEntity;
1812 						}
1813 					}
1814 					else
1815 					{
1816 						ent->think = NULL;
1817 						ent->nextthink = -1;
1818 					}
1819 					*/
1820 					return;
1821 				}
1822 			}
1823 		}
1824 
1825 
1826 		// a dot of 1.0 means right-on-target.
1827 		if ( dot < 0.0f )
1828 		{
1829 			// Go in the direction opposite, start a 180.
1830 			CrossProduct( ent->movedir, rup, right );
1831 			dot2 = DotProduct( targetdir, right );
1832 
1833 			if ( dot2 > 0 )
1834 			{
1835 				// Turn 45 degrees right.
1836 				VectorMA( ent->movedir, 0.4f*newDirMult, right, newdir );
1837 			}
1838 			else
1839 			{
1840 				// Turn 45 degrees left.
1841 				VectorMA( ent->movedir, -0.4f*newDirMult, right, newdir );
1842 			}
1843 
1844 			// Yeah we've adjusted horizontally, but let's split the difference vertically, so we kinda try to move towards it.
1845 			newdir[2] = ( (targetdir[2]*newDirMult) + (ent->movedir[2]*oldDirMult) ) * 0.5;
1846 
1847 			// let's also slow down a lot
1848 			vel *= 0.5f;
1849 		}
1850 		else if ( dot < 0.70f )
1851 		{
1852 			// Still a bit off, so we turn a bit softer
1853 			VectorMA( ent->movedir, 0.5f*newDirMult, targetdir, newdir );
1854 		}
1855 		else
1856 		{
1857 			// getting close, so turn a bit harder
1858 			VectorMA( ent->movedir, 0.9f*newDirMult, targetdir, newdir );
1859 		}
1860 
1861 		// add crazy drunkenness
1862 		for (i = 0; i < 3; i++ )
1863 		{
1864 			newdir[i] += Q_flrand(-1.0f, 1.0f) * ent->random * 0.25f;
1865 		}
1866 
1867 		// decay the randomness
1868 		ent->random *= 0.9f;
1869 
1870 		if ( ent->enemy->client
1871 			&& ent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
1872 		{//tracking a client who's on the ground, aim at the floor...?
1873 			// Try to crash into the ground if we get close enough to do splash damage
1874 			dis = Distance( ent->r.currentOrigin, org );
1875 
1876 			if ( dis < 128 )
1877 			{
1878 				// the closer we get, the more we push the rocket down, heh heh.
1879 				newdir[2] -= (1.0f - (dis / 128.0f)) * 0.6f;
1880 			}
1881 		}
1882 
1883 		VectorNormalize( newdir );
1884 
1885 		VectorScale( newdir, vel * 0.5f, ent->s.pos.trDelta );
1886 		VectorCopy( newdir, ent->movedir );
1887 		SnapVector( ent->s.pos.trDelta );			// save net bandwidth
1888 		VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
1889 		ent->s.pos.trTime = level.time;
1890 	}
1891 
1892 	ent->nextthink = level.time + ROCKET_ALT_THINK_TIME;	// Nothing at all spectacular happened, continue.
1893 	return;
1894 }
1895 
1896 extern void G_ExplodeMissile( gentity_t *ent );
RocketDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1897 void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
1898 {
1899 	self->die = 0;
1900 	self->r.contents = 0;
1901 
1902 	G_ExplodeMissile( self );
1903 
1904 	self->think = G_FreeEntity;
1905 	self->nextthink = level.time;
1906 }
1907 
1908 //---------------------------------------------------------
WP_FireRocket(gentity_t * ent,qboolean altFire)1909 static void WP_FireRocket( gentity_t *ent, qboolean altFire )
1910 //---------------------------------------------------------
1911 {
1912 	int	damage	= ROCKET_DAMAGE;
1913 	int	vel = ROCKET_VELOCITY;
1914 	int dif = 0;
1915 	float rTime;
1916 	gentity_t *missile;
1917 
1918 	if ( altFire )
1919 	{
1920 		vel *= 0.5f;
1921 	}
1922 
1923 	missile = CreateMissile( muzzle, forward, vel, 30000, ent, altFire );
1924 
1925 	if (ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE)
1926 	{
1927 		float lockTimeInterval = ((level.gametype==GT_SIEGE)?2400.0f:1200.0f)/16.0f;
1928 		rTime = ent->client->ps.rocketLockTime;
1929 
1930 		if (rTime == -1)
1931 		{
1932 			rTime = ent->client->ps.rocketLastValidTime;
1933 		}
1934 		dif = ( level.time - rTime ) / lockTimeInterval;
1935 
1936 		if (dif < 0)
1937 		{
1938 			dif = 0;
1939 		}
1940 
1941 		//It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
1942 		if ( dif >= 10 && rTime != -1 )
1943 		{
1944 			missile->enemy = &g_entities[ent->client->ps.rocketLockIndex];
1945 
1946 			if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
1947 			{ //if enemy became invalid, died, or is on the same team, then don't seek it
1948 				missile->angle = 0.5f;
1949 				missile->think = rocketThink;
1950 				missile->nextthink = level.time + ROCKET_ALT_THINK_TIME;
1951 			}
1952 		}
1953 
1954 		ent->client->ps.rocketLockIndex = ENTITYNUM_NONE;
1955 		ent->client->ps.rocketLockTime = 0;
1956 		ent->client->ps.rocketTargetTime = 0;
1957 	}
1958 
1959 	missile->classname = "rocket_proj";
1960 	missile->s.weapon = WP_ROCKET_LAUNCHER;
1961 
1962 	// Make it easier to hit things
1963 	VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
1964 	VectorScale( missile->r.maxs, -1, missile->r.mins );
1965 
1966 	missile->damage = damage;
1967 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1968 	if (altFire)
1969 	{
1970 		missile->methodOfDeath = MOD_ROCKET_HOMING;
1971 		missile->splashMethodOfDeath = MOD_ROCKET_HOMING_SPLASH;
1972 	}
1973 	else
1974 	{
1975 		missile->methodOfDeath = MOD_ROCKET;
1976 		missile->splashMethodOfDeath = MOD_ROCKET_SPLASH;
1977 	}
1978 //===testing being able to shoot rockets out of the air==================================
1979 	missile->health = 10;
1980 	missile->takedamage = qtrue;
1981 	missile->r.contents = MASK_SHOT;
1982 	missile->die = RocketDie;
1983 //===testing being able to shoot rockets out of the air==================================
1984 
1985 	missile->clipmask = MASK_SHOT;
1986 	missile->splashDamage = ROCKET_SPLASH_DAMAGE;
1987 	missile->splashRadius = ROCKET_SPLASH_RADIUS;
1988 
1989 	// we don't want it to ever bounce
1990 	missile->bounceCount = 0;
1991 }
1992 
1993 /*
1994 ======================================================================
1995 
1996 THERMAL DETONATOR
1997 
1998 ======================================================================
1999 */
2000 
2001 #define TD_DAMAGE			70 //only do 70 on a direct impact
2002 #define TD_SPLASH_RAD		128
2003 #define TD_SPLASH_DAM		90
2004 #define TD_VELOCITY			900
2005 #define TD_MIN_CHARGE		0.15f
2006 #define TD_TIME				3000//6000
2007 #define TD_ALT_TIME			3000
2008 
2009 #define TD_ALT_DAMAGE		60//100
2010 #define TD_ALT_SPLASH_RAD	128
2011 #define TD_ALT_SPLASH_DAM	50//90
2012 #define TD_ALT_VELOCITY		600
2013 #define TD_ALT_MIN_CHARGE	0.15f
2014 #define TD_ALT_TIME			3000
2015 
2016 void thermalThinkStandard(gentity_t *ent);
2017 
2018 //---------------------------------------------------------
thermalDetonatorExplode(gentity_t * ent)2019 void thermalDetonatorExplode( gentity_t *ent )
2020 //---------------------------------------------------------
2021 {
2022 	if ( !ent->count )
2023 	{
2024 		G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/thermal/warning.wav" ) );
2025 		ent->count = 1;
2026 		ent->genericValue5 = level.time + 500;
2027 		ent->think = thermalThinkStandard;
2028 		ent->nextthink = level.time;
2029 		ent->r.svFlags |= SVF_BROADCAST;//so everyone hears/sees the explosion?
2030 	}
2031 	else
2032 	{
2033 		vec3_t	origin;
2034 		vec3_t	dir={0,0,1};
2035 
2036 		BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
2037 		origin[2] += 8;
2038 		SnapVector( origin );
2039 		G_SetOrigin( ent, origin );
2040 
2041 		ent->s.eType = ET_GENERAL;
2042 		G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
2043 		ent->freeAfterEvent = qtrue;
2044 
2045 		if (G_RadiusDamage( ent->r.currentOrigin, ent->parent,  ent->splashDamage, ent->splashRadius,
2046 				ent, ent, ent->splashMethodOfDeath))
2047 		{
2048 			g_entities[ent->r.ownerNum].client->accuracy_hits++;
2049 		}
2050 
2051 		trap->LinkEntity( (sharedEntity_t *)ent );
2052 	}
2053 }
2054 
thermalThinkStandard(gentity_t * ent)2055 void thermalThinkStandard(gentity_t *ent)
2056 {
2057 	if (ent->genericValue5 < level.time)
2058 	{
2059 		ent->think = thermalDetonatorExplode;
2060 		ent->nextthink = level.time;
2061 		return;
2062 	}
2063 
2064 	G_RunObject(ent);
2065 	ent->nextthink = level.time;
2066 }
2067 
2068 //---------------------------------------------------------
WP_FireThermalDetonator(gentity_t * ent,qboolean altFire)2069 gentity_t *WP_FireThermalDetonator( gentity_t *ent, qboolean altFire )
2070 //---------------------------------------------------------
2071 {
2072 	gentity_t	*bolt;
2073 	vec3_t		dir, start;
2074 	float chargeAmount = 1.0f; // default of full charge
2075 
2076 	VectorCopy( forward, dir );
2077 	VectorCopy( muzzle, start );
2078 
2079 	bolt = G_Spawn();
2080 
2081 	bolt->physicsObject = qtrue;
2082 
2083 	bolt->classname = "thermal_detonator";
2084 	bolt->think = thermalThinkStandard;
2085 	bolt->nextthink = level.time;
2086 	bolt->touch = touch_NULL;
2087 
2088 	// How 'bout we give this thing a size...
2089 	VectorSet( bolt->r.mins, -3.0f, -3.0f, -3.0f );
2090 	VectorSet( bolt->r.maxs, 3.0f, 3.0f, 3.0f );
2091 	bolt->clipmask = MASK_SHOT;
2092 
2093 	W_TraceSetStart( ent, start, bolt->r.mins, bolt->r.maxs );//make sure our start point isn't on the other side of a wall
2094 
2095 	if ( ent->client )
2096 	{
2097 		chargeAmount = level.time - ent->client->ps.weaponChargeTime;
2098 	}
2099 
2100 	// get charge amount
2101 	chargeAmount = chargeAmount / (float)TD_VELOCITY;
2102 
2103 	if ( chargeAmount > 1.0f )
2104 	{
2105 		chargeAmount = 1.0f;
2106 	}
2107 	else if ( chargeAmount < TD_MIN_CHARGE )
2108 	{
2109 		chargeAmount = TD_MIN_CHARGE;
2110 	}
2111 
2112 	// normal ones bounce, alt ones explode on impact
2113 	bolt->genericValue5 = level.time + TD_TIME; // How long 'til she blows
2114 	bolt->s.pos.trType = TR_GRAVITY;
2115 	bolt->parent = ent;
2116 	bolt->r.ownerNum = ent->s.number;
2117 	VectorScale( dir, TD_VELOCITY * chargeAmount, bolt->s.pos.trDelta );
2118 
2119 	if ( ent->health >= 0 )
2120 	{
2121 		bolt->s.pos.trDelta[2] += 120;
2122 	}
2123 
2124 	if ( !altFire )
2125 	{
2126 		bolt->flags |= FL_BOUNCE_HALF;
2127 	}
2128 
2129 	bolt->s.loopSound = G_SoundIndex( "sound/weapons/thermal/thermloop.wav" );
2130 	bolt->s.loopIsSoundset = qfalse;
2131 
2132 	bolt->damage = TD_DAMAGE;
2133 	bolt->dflags = 0;
2134 	bolt->splashDamage = TD_SPLASH_DAM;
2135 	bolt->splashRadius = TD_SPLASH_RAD;
2136 
2137 	bolt->s.eType = ET_MISSILE;
2138 	bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2139 	bolt->s.weapon = WP_THERMAL;
2140 
2141 	bolt->methodOfDeath = MOD_THERMAL;
2142 	bolt->splashMethodOfDeath = MOD_THERMAL_SPLASH;
2143 
2144 	bolt->s.pos.trTime = level.time;		// move a bit on the very first frame
2145 	VectorCopy( start, bolt->s.pos.trBase );
2146 
2147 	SnapVector( bolt->s.pos.trDelta );			// save net bandwidth
2148 	VectorCopy (start, bolt->r.currentOrigin);
2149 
2150 	VectorCopy( start, bolt->pos2 );
2151 
2152 	bolt->bounceCount = -5;
2153 
2154 	return bolt;
2155 }
2156 
WP_DropThermal(gentity_t * ent)2157 gentity_t *WP_DropThermal( gentity_t *ent )
2158 {
2159 	AngleVectors( ent->client->ps.viewangles, forward, vright, up );
2160 	return (WP_FireThermalDetonator( ent, qfalse ));
2161 }
2162 
2163 
2164 //---------------------------------------------------------
WP_LobFire(gentity_t * self,vec3_t start,vec3_t target,vec3_t mins,vec3_t maxs,int clipmask,vec3_t velocity,qboolean tracePath,int ignoreEntNum,int enemyNum,float minSpeed,float maxSpeed,float idealSpeed,qboolean mustHit)2165 qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask,
2166 				vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum,
2167 				float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit )
2168 //---------------------------------------------------------
2169 { //for the galak mech NPC
2170 	float	targetDist, shotSpeed, speedInc = 100, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed,
2171 	vec3_t	targetDir, shotVel, failCase;
2172 	trace_t	trace;
2173 	trajectory_t	tr;
2174 	qboolean	blocked;
2175 	int		elapsedTime, skipNum, timeStep = 500, hitCount = 0, maxHits = 7;
2176 	vec3_t	lastPos, testPos;
2177 	gentity_t	*traceEnt;
2178 
2179 	if ( !idealSpeed )
2180 	{
2181 		idealSpeed = 300;
2182 	}
2183 	else if ( idealSpeed < speedInc )
2184 	{
2185 		idealSpeed = speedInc;
2186 	}
2187 	shotSpeed = idealSpeed;
2188 	skipNum = (idealSpeed-speedInc)/speedInc;
2189 	if ( !minSpeed )
2190 	{
2191 		minSpeed = 100;
2192 	}
2193 	if ( !maxSpeed )
2194 	{
2195 		maxSpeed = 900;
2196 	}
2197 	while ( hitCount < maxHits )
2198 	{
2199 		VectorSubtract( target, start, targetDir );
2200 		targetDist = VectorNormalize( targetDir );
2201 
2202 		VectorScale( targetDir, shotSpeed, shotVel );
2203 		travelTime = targetDist/shotSpeed;
2204 		shotVel[2] += travelTime * 0.5 * g_gravity.value;
2205 
2206 		if ( !hitCount )
2207 		{//save the first (ideal) one as the failCase (fallback value)
2208 			if ( !mustHit )
2209 			{//default is fine as a return value
2210 				VectorCopy( shotVel, failCase );
2211 			}
2212 		}
2213 
2214 		if ( tracePath )
2215 		{//do a rough trace of the path
2216 			blocked = qfalse;
2217 
2218 			VectorCopy( start, tr.trBase );
2219 			VectorCopy( shotVel, tr.trDelta );
2220 			tr.trType = TR_GRAVITY;
2221 			tr.trTime = level.time;
2222 			travelTime *= 1000.0f;
2223 			VectorCopy( start, lastPos );
2224 
2225 			//This may be kind of wasteful, especially on long throws... use larger steps?  Divide the travelTime into a certain hard number of slices?  Trace just to apex and down?
2226 			for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
2227 			{
2228 				if ( (float)elapsedTime > travelTime )
2229 				{//cap it
2230 					elapsedTime = floor( travelTime );
2231 				}
2232 				BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
2233 				trap->Trace( &trace, lastPos, mins, maxs, testPos, ignoreEntNum, clipmask, qfalse, 0, 0 );
2234 
2235 				if ( trace.allsolid || trace.startsolid )
2236 				{
2237 					blocked = qtrue;
2238 					break;
2239 				}
2240 				if ( trace.fraction < 1.0f )
2241 				{//hit something
2242 					if ( trace.entityNum == enemyNum )
2243 					{//hit the enemy, that's perfect!
2244 						break;
2245 					}
2246 					else if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, target ) < 4096 )//hit within 64 of desired location, should be okay
2247 					{//close enough!
2248 						break;
2249 					}
2250 					else
2251 					{//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
2252 						impactDist = DistanceSquared( trace.endpos, target );
2253 						if ( impactDist < bestImpactDist )
2254 						{
2255 							bestImpactDist = impactDist;
2256 							VectorCopy( shotVel, failCase );
2257 						}
2258 						blocked = qtrue;
2259 						//see if we should store this as the failCase
2260 						if ( trace.entityNum < ENTITYNUM_WORLD )
2261 						{//hit an ent
2262 							traceEnt = &g_entities[trace.entityNum];
2263 							if ( traceEnt && traceEnt->takedamage && !OnSameTeam( self, traceEnt ) )
2264 							{//hit something breakable, so that's okay
2265 								//we haven't found a clear shot yet so use this as the failcase
2266 								VectorCopy( shotVel, failCase );
2267 							}
2268 						}
2269 						break;
2270 					}
2271 				}
2272 				if ( elapsedTime == floor( travelTime ) )
2273 				{//reached end, all clear
2274 					break;
2275 				}
2276 				else
2277 				{
2278 					//all clear, try next slice
2279 					VectorCopy( testPos, lastPos );
2280 				}
2281 			}
2282 			if ( blocked )
2283 			{//hit something, adjust speed (which will change arc)
2284 				hitCount++;
2285 				shotSpeed = idealSpeed + ((hitCount-skipNum) * speedInc);//from min to max (skipping ideal)
2286 				if ( hitCount >= skipNum )
2287 				{//skip ideal since that was the first value we tested
2288 					shotSpeed += speedInc;
2289 				}
2290 			}
2291 			else
2292 			{//made it!
2293 				break;
2294 			}
2295 		}
2296 		else
2297 		{//no need to check the path, go with first calc
2298 			break;
2299 		}
2300 	}
2301 
2302 	if ( hitCount >= maxHits )
2303 	{//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
2304 		VectorCopy( failCase, velocity );
2305 		return qfalse;
2306 	}
2307 	VectorCopy( shotVel, velocity );
2308 	return qtrue;
2309 }
2310 
2311 /*
2312 ======================================================================
2313 
2314 LASER TRAP / TRIP MINE
2315 
2316 ======================================================================
2317 */
2318 #define LT_DAMAGE			100
2319 #define LT_SPLASH_RAD		256.0f
2320 #define LT_SPLASH_DAM		105
2321 #define LT_VELOCITY			900.0f
2322 #define LT_SIZE				1.5f
2323 #define LT_ALT_TIME			2000
2324 #define	LT_ACTIVATION_DELAY	1000
2325 #define	LT_DELAY_TIME		50
2326 
laserTrapExplode(gentity_t * self)2327 void laserTrapExplode( gentity_t *self )
2328 {
2329 	vec3_t v;
2330 	self->takedamage = qfalse;
2331 
2332 	if (self->activator)
2333 	{
2334 		G_RadiusDamage( self->r.currentOrigin, self->activator, self->splashDamage, self->splashRadius, self, self, MOD_TRIP_MINE_SPLASH/*MOD_LT_SPLASH*/ );
2335 	}
2336 
2337 	if (self->s.weapon != WP_FLECHETTE)
2338 	{
2339 		G_AddEvent( self, EV_MISSILE_MISS, 0);
2340 	}
2341 
2342 	VectorCopy(self->s.pos.trDelta, v);
2343 	//Explode outward from the surface
2344 
2345 	if (self->s.time == -2)
2346 	{
2347 		v[0] = 0;
2348 		v[1] = 0;
2349 		v[2] = 0;
2350 	}
2351 
2352 	if (self->s.weapon == WP_FLECHETTE)
2353 	{
2354 		G_PlayEffect(EFFECT_EXPLOSION_FLECHETTE, self->r.currentOrigin, v);
2355 	}
2356 	else
2357 	{
2358 		G_PlayEffect(EFFECT_EXPLOSION_TRIPMINE, self->r.currentOrigin, v);
2359 	}
2360 
2361 	self->think = G_FreeEntity;
2362 	self->nextthink = level.time;
2363 }
2364 
laserTrapDelayedExplode(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)2365 void laserTrapDelayedExplode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
2366 {
2367 	self->enemy = attacker;
2368 	self->think = laserTrapExplode;
2369 	self->nextthink = level.time + FRAMETIME;
2370 	self->takedamage = qfalse;
2371 	if ( attacker && attacker->s.number < MAX_CLIENTS )
2372 	{
2373 		//less damage when shot by player
2374 		self->splashDamage /= 3;
2375 		self->splashRadius /= 3;
2376 	}
2377 }
2378 
touchLaserTrap(gentity_t * ent,gentity_t * other,trace_t * trace)2379 void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace )
2380 {
2381 	if (other && other->s.number < ENTITYNUM_WORLD)
2382 	{ //just explode if we hit any entity. This way we don't have things happening like tripmines floating
2383 	  //in the air after getting stuck to a moving door
2384 		if ( ent->activator != other )
2385 		{
2386 			ent->touch = 0;
2387 			ent->nextthink = level.time + FRAMETIME;
2388 			ent->think = laserTrapExplode;
2389 			VectorCopy(trace->plane.normal, ent->s.pos.trDelta);
2390 		}
2391 	}
2392 	else
2393 	{
2394 		ent->touch = 0;
2395 		if (trace->entityNum != ENTITYNUM_NONE)
2396 		{
2397 			ent->enemy = &g_entities[trace->entityNum];
2398 		}
2399 		laserTrapStick(ent, trace->endpos, trace->plane.normal);
2400 	}
2401 }
2402 
proxMineThink(gentity_t * ent)2403 void proxMineThink(gentity_t *ent)
2404 {
2405 	int i = 0;
2406 	gentity_t *cl;
2407 	gentity_t *owner = NULL;
2408 
2409 	if (ent->r.ownerNum < ENTITYNUM_WORLD)
2410 	{
2411 		owner = &g_entities[ent->r.ownerNum];
2412 	}
2413 
2414 	ent->nextthink = level.time;
2415 
2416 	if (ent->genericValue15 < level.time ||
2417 		!owner ||
2418 		!owner->inuse ||
2419 		!owner->client ||
2420 		owner->client->pers.connected != CON_CONNECTED)
2421 	{ //time to die!
2422 		ent->think = laserTrapExplode;
2423 		return;
2424 	}
2425 
2426 	while (i < MAX_CLIENTS)
2427 	{ //eh, just check for clients, don't care about anyone else...
2428 		cl = &g_entities[i];
2429 
2430 		if (cl->inuse && cl->client && cl->client->pers.connected == CON_CONNECTED &&
2431 			owner != cl && cl->client->sess.sessionTeam != TEAM_SPECTATOR &&
2432 			cl->client->tempSpectate < level.time && cl->health > 0)
2433 		{
2434 			if (!OnSameTeam(owner, cl) || g_friendlyFire.integer)
2435 			{ //not on the same team, or friendly fire is enabled
2436 				vec3_t v;
2437 
2438 				VectorSubtract(ent->r.currentOrigin, cl->client->ps.origin, v);
2439 				if (VectorLength(v) < (ent->splashRadius/2.0f))
2440 				{
2441 					ent->think = laserTrapExplode;
2442 					return;
2443 				}
2444 			}
2445 		}
2446 		i++;
2447 	}
2448 }
2449 
laserTrapThink(gentity_t * ent)2450 void laserTrapThink ( gentity_t *ent )
2451 {
2452 	gentity_t	*traceEnt;
2453 	vec3_t		end;
2454 	trace_t		tr;
2455 
2456 	//just relink it every think
2457 	trap->LinkEntity((sharedEntity_t *)ent);
2458 
2459 	//turn on the beam effect
2460 	if ( !(ent->s.eFlags&EF_FIRING) )
2461 	{//arm me
2462 		G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) );
2463 		ent->s.eFlags |= EF_FIRING;
2464 	}
2465 	ent->think = laserTrapThink;
2466 	ent->nextthink = level.time + FRAMETIME;
2467 
2468 	// Find the main impact point
2469 	VectorMA ( ent->s.pos.trBase, 1024, ent->movedir, end );
2470 	trap->Trace ( &tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT, qfalse, 0, 0);
2471 
2472 	traceEnt = &g_entities[ tr.entityNum ];
2473 
2474 	ent->s.time = -1; //let all clients know to draw a beam from this guy
2475 
2476 	if ( traceEnt->client || tr.startsolid )
2477 	{
2478 		//go boom
2479 		ent->touch = 0;
2480 		ent->nextthink = level.time + LT_DELAY_TIME;
2481 		ent->think = laserTrapExplode;
2482 	}
2483 }
2484 
laserTrapStick(gentity_t * ent,vec3_t endpos,vec3_t normal)2485 void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal )
2486 {
2487 	G_SetOrigin( ent, endpos );
2488 	VectorCopy( normal, ent->pos1 );
2489 
2490 	VectorClear( ent->s.apos.trDelta );
2491 	// This will orient the object to face in the direction of the normal
2492 	VectorCopy( normal, ent->s.pos.trDelta );
2493 	//VectorScale( normal, -1, ent->s.pos.trDelta );
2494 	ent->s.pos.trTime = level.time;
2495 
2496 
2497 	//This does nothing, cg_missile makes assumptions about direction of travel controlling angles
2498 	vectoangles( normal, ent->s.apos.trBase );
2499 	VectorClear( ent->s.apos.trDelta );
2500 	ent->s.apos.trType = TR_STATIONARY;
2501 	VectorCopy( ent->s.apos.trBase, ent->s.angles );
2502 	VectorCopy( ent->s.angles, ent->r.currentAngles );
2503 
2504 
2505 	G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/stick.wav" ) );
2506 	if ( ent->count )
2507 	{//a tripwire
2508 		//add draw line flag
2509 		VectorCopy( normal, ent->movedir );
2510 		ent->think = laserTrapThink;
2511 		ent->nextthink = level.time + LT_ACTIVATION_DELAY;//delay the activation
2512 		ent->touch = touch_NULL;
2513 		//make it shootable
2514 		ent->takedamage = qtrue;
2515 		ent->health = 5;
2516 		ent->die = laserTrapDelayedExplode;
2517 
2518 		//shove the box through the wall
2519 		VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 );
2520 		VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 );
2521 
2522 		//so that the owner can blow it up with projectiles
2523 		ent->r.svFlags |= SVF_OWNERNOTSHARED;
2524 	}
2525 	else
2526 	{
2527 		ent->touch = touchLaserTrap;
2528 		ent->think = proxMineThink;//laserTrapExplode;
2529 		ent->genericValue15 = level.time + 30000; //auto-explode after 30 seconds.
2530 		ent->nextthink = level.time + LT_ALT_TIME; // How long 'til she blows
2531 
2532 		//make it shootable
2533 		ent->takedamage = qtrue;
2534 		ent->health = 5;
2535 		ent->die = laserTrapDelayedExplode;
2536 
2537 		//shove the box through the wall
2538 		VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 );
2539 		VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 );
2540 
2541 		//so that the owner can blow it up with projectiles
2542 		ent->r.svFlags |= SVF_OWNERNOTSHARED;
2543 
2544 		if ( !(ent->s.eFlags&EF_FIRING) )
2545 		{//arm me
2546 			G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) );
2547 			ent->s.eFlags |= EF_FIRING;
2548 			ent->s.time = -1;
2549 			ent->s.bolt2 = 1;
2550 		}
2551 	}
2552 }
2553 
TrapThink(gentity_t * ent)2554 void TrapThink(gentity_t *ent)
2555 { //laser trap think
2556 	ent->nextthink = level.time + 50;
2557 	G_RunObject(ent);
2558 }
2559 
CreateLaserTrap(gentity_t * laserTrap,vec3_t start,gentity_t * owner)2560 void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner )
2561 { //create a laser trap entity
2562 	laserTrap->classname = "laserTrap";
2563 	laserTrap->flags |= FL_BOUNCE_HALF;
2564 	laserTrap->s.eFlags |= EF_MISSILE_STICK;
2565 	laserTrap->splashDamage = LT_SPLASH_DAM;
2566 	laserTrap->splashRadius = LT_SPLASH_RAD;
2567 	laserTrap->damage = LT_DAMAGE;
2568 	laserTrap->methodOfDeath = MOD_TRIP_MINE_SPLASH;
2569 	laserTrap->splashMethodOfDeath = MOD_TRIP_MINE_SPLASH;
2570 	laserTrap->s.eType = ET_GENERAL;
2571 	laserTrap->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2572 	laserTrap->s.weapon = WP_TRIP_MINE;
2573 	laserTrap->s.pos.trType = TR_GRAVITY;
2574 	laserTrap->r.contents = MASK_SHOT;
2575 	laserTrap->parent = owner;
2576 	laserTrap->activator = owner;
2577 	laserTrap->r.ownerNum = owner->s.number;
2578 	VectorSet( laserTrap->r.mins, -LT_SIZE, -LT_SIZE, -LT_SIZE );
2579 	VectorSet( laserTrap->r.maxs, LT_SIZE, LT_SIZE, LT_SIZE );
2580 	laserTrap->clipmask = MASK_SHOT;
2581 	laserTrap->s.solid = 2;
2582 	laserTrap->s.modelindex = G_ModelIndex( "models/weapons2/laser_trap/laser_trap_w.glm" );
2583 	laserTrap->s.modelGhoul2 = 1;
2584 	laserTrap->s.g2radius = 40;
2585 
2586 	laserTrap->s.genericenemyindex = owner->s.number+MAX_GENTITIES;
2587 
2588 	laserTrap->health = 1;
2589 
2590 	laserTrap->s.time = 0;
2591 
2592 	laserTrap->s.pos.trTime = level.time;		// move a bit on the very first frame
2593 	VectorCopy( start, laserTrap->s.pos.trBase );
2594 	SnapVector( laserTrap->s.pos.trBase );			// save net bandwidth
2595 
2596 	SnapVector( laserTrap->s.pos.trDelta );			// save net bandwidth
2597 	VectorCopy (start, laserTrap->r.currentOrigin);
2598 
2599 	laserTrap->s.apos.trType = TR_GRAVITY;
2600 	laserTrap->s.apos.trTime = level.time;
2601 	laserTrap->s.apos.trBase[YAW] = rand()%360;
2602 	laserTrap->s.apos.trBase[PITCH] = rand()%360;
2603 	laserTrap->s.apos.trBase[ROLL] = rand()%360;
2604 
2605 	if (rand()%10 < 5)
2606 	{
2607 		laserTrap->s.apos.trBase[YAW] = -laserTrap->s.apos.trBase[YAW];
2608 	}
2609 
2610 	VectorCopy( start, laserTrap->pos2 );
2611 	laserTrap->touch = touchLaserTrap;
2612 	laserTrap->think = TrapThink;
2613 	laserTrap->nextthink = level.time + 50;
2614 }
2615 
WP_PlaceLaserTrap(gentity_t * ent,qboolean alt_fire)2616 void WP_PlaceLaserTrap( gentity_t *ent, qboolean alt_fire )
2617 {
2618 	gentity_t	*laserTrap;
2619 	gentity_t	*found = NULL;
2620 	vec3_t		dir, start;
2621 	int			trapcount = 0;
2622 	int			foundLaserTraps[MAX_GENTITIES];
2623 	int			trapcount_org;
2624 	int			lowestTimeStamp;
2625 	int			removeMe;
2626 	int			i;
2627 
2628 	foundLaserTraps[0] = ENTITYNUM_NONE;
2629 
2630 	VectorCopy( forward, dir );
2631 	VectorCopy( muzzle, start );
2632 
2633 	laserTrap = G_Spawn();
2634 
2635 	//limit to 10 placed at any one time
2636 	//see how many there are now
2637 	while ( (found = G_Find( found, FOFS(classname), "laserTrap" )) != NULL )
2638 	{
2639 		if ( found->parent != ent )
2640 		{
2641 			continue;
2642 		}
2643 		foundLaserTraps[trapcount++] = found->s.number;
2644 	}
2645 	//now remove first ones we find until there are only 9 left
2646 	found = NULL;
2647 	trapcount_org = trapcount;
2648 	lowestTimeStamp = level.time;
2649 	while ( trapcount > 9 )
2650 	{
2651 		removeMe = -1;
2652 		for ( i = 0; i < trapcount_org; i++ )
2653 		{
2654 			if ( foundLaserTraps[i] == ENTITYNUM_NONE )
2655 			{
2656 				continue;
2657 			}
2658 			found = &g_entities[foundLaserTraps[i]];
2659 			if ( laserTrap && found->setTime < lowestTimeStamp )
2660 			{
2661 				removeMe = i;
2662 				lowestTimeStamp = found->setTime;
2663 			}
2664 		}
2665 		if ( removeMe != -1 )
2666 		{
2667 			//remove it... or blow it?
2668 			if ( &g_entities[foundLaserTraps[removeMe]] == NULL )
2669 			{
2670 				break;
2671 			}
2672 			else
2673 			{
2674 				G_FreeEntity( &g_entities[foundLaserTraps[removeMe]] );
2675 			}
2676 			foundLaserTraps[removeMe] = ENTITYNUM_NONE;
2677 			trapcount--;
2678 		}
2679 		else
2680 		{
2681 			break;
2682 		}
2683 	}
2684 
2685 	//now make the new one
2686 	CreateLaserTrap( laserTrap, start, ent );
2687 
2688 	//set player-created-specific fields
2689 	laserTrap->setTime = level.time;//remember when we placed it
2690 
2691 	if (!alt_fire)
2692 	{//tripwire
2693 		laserTrap->count = 1;
2694 	}
2695 
2696 	//move it
2697 	laserTrap->s.pos.trType = TR_GRAVITY;
2698 
2699 	if (alt_fire)
2700 	{
2701 		VectorScale( dir, 512, laserTrap->s.pos.trDelta );
2702 	}
2703 	else
2704 	{
2705 		VectorScale( dir, 256, laserTrap->s.pos.trDelta );
2706 	}
2707 
2708 	trap->LinkEntity((sharedEntity_t *)laserTrap);
2709 }
2710 
2711 
2712 /*
2713 ======================================================================
2714 
2715 DET PACK
2716 
2717 ======================================================================
2718 */
VectorNPos(vec3_t in,vec3_t out)2719 void VectorNPos(vec3_t in, vec3_t out)
2720 {
2721 	if (in[0] < 0) { out[0] = -in[0]; } else { out[0] = in[0]; }
2722 	if (in[1] < 0) { out[1] = -in[1]; } else { out[1] = in[1]; }
2723 	if (in[2] < 0) { out[2] = -in[2]; } else { out[2] = in[2]; }
2724 }
2725 
2726 void DetPackBlow(gentity_t *self);
2727 
charge_stick(gentity_t * self,gentity_t * other,trace_t * trace)2728 void charge_stick (gentity_t *self, gentity_t *other, trace_t *trace)
2729 {
2730 	gentity_t	*tent;
2731 
2732 	if ( other
2733 		&& (other->flags&FL_BBRUSH)
2734 		&& other->s.pos.trType == TR_STATIONARY
2735 		&& other->s.apos.trType == TR_STATIONARY )
2736 	{//a perfectly still breakable brush, let us attach directly to it!
2737 		self->target_ent = other;//remember them when we blow up
2738 	}
2739 	else if ( other
2740 		&& other->s.number < ENTITYNUM_WORLD
2741 		&& other->s.eType == ET_MOVER
2742 		&& trace->plane.normal[2] > 0 )
2743 	{//stick to it?
2744 		self->s.groundEntityNum = other->s.number;
2745 	}
2746 	else if (other && other->s.number < ENTITYNUM_WORLD &&
2747 		(other->client || !other->s.weapon))
2748 	{ //hit another entity that is not stickable, "bounce" off
2749 		vec3_t vNor, tN;
2750 
2751 		VectorCopy(trace->plane.normal, vNor);
2752 		VectorNormalize(vNor);
2753 		VectorNPos(self->s.pos.trDelta, tN);
2754 		self->s.pos.trDelta[0] += vNor[0]*(tN[0]*(((float)Q_irand(1, 10))*0.1));
2755 		self->s.pos.trDelta[1] += vNor[1]*(tN[1]*(((float)Q_irand(1, 10))*0.1));
2756 		self->s.pos.trDelta[2] += vNor[2]*(tN[2]*(((float)Q_irand(1, 10))*0.1));
2757 
2758 		vectoangles(vNor, self->s.angles);
2759 		vectoangles(vNor, self->s.apos.trBase);
2760 		self->touch = charge_stick;
2761 		return;
2762 	}
2763 	else if (other && other->s.number < ENTITYNUM_WORLD)
2764 	{ //hit an entity that we just want to explode on (probably another projectile or something)
2765 		vec3_t v;
2766 
2767 		self->touch = 0;
2768 		self->think = 0;
2769 		self->nextthink = 0;
2770 
2771 		self->takedamage = qfalse;
2772 
2773 		VectorClear(self->s.apos.trDelta);
2774 		self->s.apos.trType = TR_STATIONARY;
2775 
2776 		G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH );
2777 		VectorCopy(trace->plane.normal, v);
2778 		VectorCopy(v, self->pos2);
2779 		self->count = -1;
2780 		G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v);
2781 
2782 		self->think = G_FreeEntity;
2783 		self->nextthink = level.time;
2784 		return;
2785 	}
2786 
2787 	//if we get here I guess we hit hte world so we can stick to it
2788 
2789 	// This requires a bit of explaining.
2790 	// When you suicide, all of the detpacks you have placed (either on a wall, or still falling in the air) will have
2791 	//	their ent->think() set to DetPackBlow and ent->nextthink will be between 100 <-> 300
2792 	// If your detpacks land on a surface (i.e. charge_stick gets called) within that 100<->300 ms then ent->think()
2793 	//	will be overwritten (set to DetpackBlow) and ent->nextthink will be 30000
2794 	// The end result is your detpacks won't explode, but will be stuck to the wall for 30 seconds without being able to
2795 	//	detonate them (or shoot them)
2796 	// The fix Sil came up with is to check the think() function in charge_stick, and only overwrite it if they haven't
2797 	//	been primed to detonate
2798 
2799 	if ( self->think == G_RunObject ) {
2800 		self->touch = 0;
2801 		self->think = DetPackBlow;
2802 		self->nextthink = level.time + 30000;
2803 	}
2804 
2805 	VectorClear(self->s.apos.trDelta);
2806 	self->s.apos.trType = TR_STATIONARY;
2807 
2808 	self->s.pos.trType = TR_STATIONARY;
2809 	VectorCopy( self->r.currentOrigin, self->s.origin );
2810 	VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
2811 	VectorClear( self->s.pos.trDelta );
2812 
2813 	VectorClear( self->s.apos.trDelta );
2814 
2815 	VectorNormalize(trace->plane.normal);
2816 
2817 	vectoangles(trace->plane.normal, self->s.angles);
2818 	VectorCopy(self->s.angles, self->r.currentAngles );
2819 	VectorCopy(self->s.angles, self->s.apos.trBase);
2820 
2821 	VectorCopy(trace->plane.normal, self->pos2);
2822 	self->count = -1;
2823 
2824 	G_Sound(self, CHAN_WEAPON, G_SoundIndex("sound/weapons/detpack/stick.wav"));
2825 
2826 	tent = G_TempEntity( self->r.currentOrigin, EV_MISSILE_MISS );
2827 	tent->s.weapon = 0;
2828 	tent->parent = self;
2829 	tent->r.ownerNum = self->s.number;
2830 
2831 	//so that the owner can blow it up with projectiles
2832 	self->r.svFlags |= SVF_OWNERNOTSHARED;
2833 }
2834 
DetPackBlow(gentity_t * self)2835 void DetPackBlow(gentity_t *self)
2836 {
2837 	vec3_t v;
2838 
2839 	self->pain = 0;
2840 	self->die = 0;
2841 	self->takedamage = qfalse;
2842 
2843 	if ( self->target_ent )
2844 	{//we were attached to something, do *direct* damage to it!
2845 		G_Damage( self->target_ent, self, &g_entities[self->r.ownerNum], v, self->r.currentOrigin, self->damage, 0, MOD_DET_PACK_SPLASH );
2846 	}
2847 	G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH );
2848 	v[0] = 0;
2849 	v[1] = 0;
2850 	v[2] = 1;
2851 
2852 	if (self->count == -1)
2853 	{
2854 		VectorCopy(self->pos2, v);
2855 	}
2856 
2857 	G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v);
2858 
2859 	self->think = G_FreeEntity;
2860 	self->nextthink = level.time;
2861 }
2862 
DetPackPain(gentity_t * self,gentity_t * attacker,int damage)2863 void DetPackPain(gentity_t *self, gentity_t *attacker, int damage)
2864 {
2865 	self->think = DetPackBlow;
2866 	self->nextthink = level.time + Q_irand(50, 100);
2867 	self->takedamage = qfalse;
2868 }
2869 
DetPackDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2870 void DetPackDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
2871 {
2872 	self->think = DetPackBlow;
2873 	self->nextthink = level.time + Q_irand(50, 100);
2874 	self->takedamage = qfalse;
2875 }
2876 
drop_charge(gentity_t * self,vec3_t start,vec3_t dir)2877 void drop_charge (gentity_t *self, vec3_t start, vec3_t dir)
2878 {
2879 	gentity_t	*bolt;
2880 
2881 	VectorNormalize (dir);
2882 
2883 	bolt = G_Spawn();
2884 	bolt->classname = "detpack";
2885 	bolt->nextthink = level.time + FRAMETIME;
2886 	bolt->think = G_RunObject;
2887 	bolt->s.eType = ET_GENERAL;
2888 	bolt->s.g2radius = 100;
2889 	bolt->s.modelGhoul2 = 1;
2890 	bolt->s.modelindex = G_ModelIndex("models/weapons2/detpack/det_pack_proj.glm");
2891 
2892 	bolt->parent = self;
2893 	bolt->r.ownerNum = self->s.number;
2894 	bolt->damage = 100;
2895 	bolt->splashDamage = 200;
2896 	bolt->splashRadius = 200;
2897 	bolt->methodOfDeath = MOD_DET_PACK_SPLASH;
2898 	bolt->splashMethodOfDeath = MOD_DET_PACK_SPLASH;
2899 	bolt->clipmask = MASK_SHOT;
2900 	bolt->s.solid = 2;
2901 	bolt->r.contents = MASK_SHOT;
2902 	bolt->touch = charge_stick;
2903 
2904 	bolt->physicsObject = qtrue;
2905 
2906 	bolt->s.genericenemyindex = self->s.number+MAX_GENTITIES;
2907 	//rww - so client prediction knows we own this and won't hit it
2908 
2909 	VectorSet( bolt->r.mins, -2, -2, -2 );
2910 	VectorSet( bolt->r.maxs, 2, 2, 2 );
2911 
2912 	bolt->health = 1;
2913 	bolt->takedamage = qtrue;
2914 	bolt->pain = DetPackPain;
2915 	bolt->die = DetPackDie;
2916 
2917 	bolt->s.weapon = WP_DET_PACK;
2918 
2919 	bolt->setTime = level.time;
2920 
2921 	G_SetOrigin(bolt, start);
2922 	bolt->s.pos.trType = TR_GRAVITY;
2923 	VectorCopy( start, bolt->s.pos.trBase );
2924 	VectorScale(dir, 300, bolt->s.pos.trDelta );
2925 	bolt->s.pos.trTime = level.time;
2926 
2927 	bolt->s.apos.trType = TR_GRAVITY;
2928 	bolt->s.apos.trTime = level.time;
2929 	bolt->s.apos.trBase[YAW] = rand()%360;
2930 	bolt->s.apos.trBase[PITCH] = rand()%360;
2931 	bolt->s.apos.trBase[ROLL] = rand()%360;
2932 
2933 	if (rand()%10 < 5)
2934 	{
2935 		bolt->s.apos.trBase[YAW] = -bolt->s.apos.trBase[YAW];
2936 	}
2937 
2938 	vectoangles(dir, bolt->s.angles);
2939 	VectorCopy(bolt->s.angles, bolt->s.apos.trBase);
2940 	VectorSet(bolt->s.apos.trDelta, 300, 0, 0 );
2941 	bolt->s.apos.trTime = level.time;
2942 
2943 	trap->LinkEntity((sharedEntity_t *)bolt);
2944 }
2945 
BlowDetpacks(gentity_t * ent)2946 void BlowDetpacks(gentity_t *ent)
2947 {
2948 	gentity_t *found = NULL;
2949 
2950 	if ( ent->client->ps.hasDetPackPlanted )
2951 	{
2952 		while ( (found = G_Find( found, FOFS(classname), "detpack") ) != NULL )
2953 		{//loop through all ents and blow the crap out of them!
2954 			if ( found->parent == ent )
2955 			{
2956 				VectorCopy( found->r.currentOrigin, found->s.origin );
2957 				found->think = DetPackBlow;
2958 				found->nextthink = level.time + 100 + Q_flrand(0.0f, 1.0f) * 200;
2959 				G_Sound( found, CHAN_BODY, G_SoundIndex("sound/weapons/detpack/warning.wav") );
2960 			}
2961 		}
2962 		ent->client->ps.hasDetPackPlanted = qfalse;
2963 	}
2964 }
RemoveDetpacks(gentity_t * ent)2965 void RemoveDetpacks(gentity_t *ent)
2966 {
2967 	gentity_t *found = NULL;
2968 
2969 	if ( ent->client->ps.hasDetPackPlanted )
2970 	{
2971 		while ( (found = G_Find( found, FOFS(classname), "detpack") ) != NULL )
2972 		{//loop through all ents and blow the crap out of them!
2973 			if ( found->parent == ent )
2974 			{
2975 				VectorCopy( found->r.currentOrigin, found->s.origin );
2976 				found->think = G_FreeEntity;
2977 				found->nextthink = level.time;
2978 			//	G_Sound( found, CHAN_BODY, G_SoundIndex("sound/weapons/detpack/warning.wav") );
2979 			}
2980 		}
2981 		ent->client->ps.hasDetPackPlanted = qfalse;
2982 	}
2983 }
2984 
CheatsOn(void)2985 qboolean CheatsOn(void)
2986 {
2987 	if ( !sv_cheats.integer )
2988 	{
2989 		return qfalse;
2990 	}
2991 	return qtrue;
2992 }
2993 
WP_DropDetPack(gentity_t * ent,qboolean alt_fire)2994 void WP_DropDetPack( gentity_t *ent, qboolean alt_fire )
2995 {
2996 	gentity_t	*found = NULL;
2997 	int			trapcount = 0;
2998 	int			foundDetPacks[MAX_GENTITIES] = {ENTITYNUM_NONE};
2999 	int			trapcount_org;
3000 	int			lowestTimeStamp;
3001 	int			removeMe;
3002 	int			i;
3003 
3004 	if ( !ent || !ent->client )
3005 	{
3006 		return;
3007 	}
3008 
3009 	//limit to 10 placed at any one time
3010 	//see how many there are now
3011 	while ( (found = G_Find( found, FOFS(classname), "detpack" )) != NULL )
3012 	{
3013 		if ( found->parent != ent )
3014 		{
3015 			continue;
3016 		}
3017 		foundDetPacks[trapcount++] = found->s.number;
3018 	}
3019 	//now remove first ones we find until there are only 9 left
3020 	found = NULL;
3021 	trapcount_org = trapcount;
3022 	lowestTimeStamp = level.time;
3023 	while ( trapcount > 9 )
3024 	{
3025 		removeMe = -1;
3026 		for ( i = 0; i < trapcount_org; i++ )
3027 		{
3028 			if ( foundDetPacks[i] == ENTITYNUM_NONE )
3029 			{
3030 				continue;
3031 			}
3032 			found = &g_entities[foundDetPacks[i]];
3033 			if ( found->setTime < lowestTimeStamp )
3034 			{
3035 				removeMe = i;
3036 				lowestTimeStamp = found->setTime;
3037 			}
3038 		}
3039 		if ( removeMe != -1 )
3040 		{
3041 			//remove it... or blow it?
3042 			if ( &g_entities[foundDetPacks[removeMe]] == NULL )
3043 			{
3044 				break;
3045 			}
3046 			else
3047 			{
3048 				if (!CheatsOn())
3049 				{ //Let them have unlimited if cheats are enabled
3050 					G_FreeEntity( &g_entities[foundDetPacks[removeMe]] );
3051 				}
3052 			}
3053 			foundDetPacks[removeMe] = ENTITYNUM_NONE;
3054 			trapcount--;
3055 		}
3056 		else
3057 		{
3058 			break;
3059 		}
3060 	}
3061 
3062 	if ( alt_fire  )
3063 	{
3064 		BlowDetpacks(ent);
3065 	}
3066 	else
3067 	{
3068 		AngleVectors( ent->client->ps.viewangles, forward, vright, up );
3069 
3070 		CalcMuzzlePoint( ent, forward, vright, up, muzzle );
3071 
3072 		VectorNormalize( forward );
3073 		VectorMA( muzzle, -4, forward, muzzle );
3074 		drop_charge( ent, muzzle, forward );
3075 
3076 		ent->client->ps.hasDetPackPlanted = qtrue;
3077 	}
3078 }
3079 
WP_FireConcussionAlt(gentity_t * ent)3080 static void WP_FireConcussionAlt( gentity_t *ent )
3081 {//a rail-gun-like beam
3082 	int			damage = CONC_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES;
3083 	qboolean	render_impact = qtrue;
3084 	vec3_t		start, end;
3085 	vec3_t		/*muzzle2,*/ dir;
3086 	trace_t		tr;
3087 	gentity_t	*traceEnt, *tent;
3088 	float		shotRange = 8192.0f;
3089 	qboolean	hitDodged = qfalse;
3090 	vec3_t shot_mins, shot_maxs;
3091 	int			i;
3092 
3093 	//Shove us backwards for half a second
3094 	VectorMA( ent->client->ps.velocity, -200, forward, ent->client->ps.velocity );
3095 	ent->client->ps.groundEntityNum = ENTITYNUM_NONE;
3096 	if ( (ent->client->ps.pm_flags&PMF_DUCKED) )
3097 	{//hunkered down
3098 		ent->client->ps.pm_time = 100;
3099 	}
3100 	else
3101 	{
3102 		ent->client->ps.pm_time = 250;
3103 	}
3104 //	ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK|PMF_TIME_NOFRICTION;
3105 	//FIXME: only if on ground?  So no "rocket jump"?  Or: (see next FIXME)
3106 	//FIXME: instead, set a forced ucmd backmove instead of this sliding
3107 
3108 	//VectorCopy( muzzle, muzzle2 ); // making a backup copy
3109 
3110 	VectorCopy( muzzle, start );
3111 	WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
3112 
3113 	skip = ent->s.number;
3114 
3115 //	if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
3116 //	{
3117 //		// in overcharge mode, so doing double damage
3118 //		damage *= 2;
3119 //	}
3120 
3121 	//Make it a little easier to hit guys at long range
3122 	VectorSet( shot_mins, -1, -1, -1 );
3123 	VectorSet( shot_maxs, 1, 1, 1 );
3124 
3125 	for ( i = 0; i < traces; i++ )
3126 	{
3127 		VectorMA( start, shotRange, forward, end );
3128 
3129 		//NOTE: if you want to be able to hit guys in emplaced guns, use "G2_COLLIDE, 10" instead of "G2_RETURNONHIT, 0"
3130 		//alternately, if you end up hitting an emplaced_gun that has a sitter, just redo this one trace with the "G2_COLLIDE, 10" to see if we it the sitter
3131 		//trap->trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 );
3132 		if (d_projectileGhoul2Collision.integer)
3133 		{
3134 			trap->Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
3135 		}
3136 		else
3137 		{
3138 			trap->Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, qfalse, 0, 0 );
3139 		}
3140 
3141 		traceEnt = &g_entities[tr.entityNum];
3142 
3143 		if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
3144 		{ //g2 collision checks -rww
3145 			if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
3146 			{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
3147 				traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
3148 				traceEnt->client->g2LastSurfaceTime = level.time;
3149 			}
3150 
3151 			if (traceEnt->ghoul2)
3152 			{
3153 				tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
3154 			}
3155 		}
3156 		if ( tr.surfaceFlags & SURF_NOIMPACT )
3157 		{
3158 			render_impact = qfalse;
3159 		}
3160 
3161 		if ( tr.entityNum == ent->s.number )
3162 		{
3163 			// should never happen, but basically we don't want to consider a hit to ourselves?
3164 			// Get ready for an attempt to trace through another person
3165 			//VectorCopy( tr.endpos, muzzle2 );
3166 			VectorCopy( tr.endpos, start );
3167 			skip = tr.entityNum;
3168 #ifdef _DEBUG
3169 			Com_Printf( "BAD! Concussion gun shot somehow traced back and hit the owner!\n" );
3170 #endif
3171 			continue;
3172 		}
3173 
3174 		// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
3175 		//NOTE: let's just draw one beam at the end
3176 		//tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT );
3177 		//tent->svFlags |= SVF_BROADCAST;
3178 
3179 		//VectorCopy( muzzle2, tent->s.origin2 );
3180 
3181 		if ( tr.fraction >= 1.0f )
3182 		{
3183 			// draw the beam but don't do anything else
3184 			break;
3185 		}
3186 
3187 		if ( traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC
3188 		{//FIXME: need a more reliable way to know we hit a jedi?
3189 			hitDodged = Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE );
3190 			//acts like we didn't even hit him
3191 		}
3192 		if ( !hitDodged )
3193 		{
3194 			if ( render_impact )
3195 			{
3196 				if (( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
3197 					|| !Q_stricmp( traceEnt->classname, "misc_model_breakable" )
3198 					|| traceEnt->s.eType == ET_MOVER )
3199 				{
3200 					qboolean noKnockBack;
3201 
3202 					// Create a simple impact type mark that doesn't last long in the world
3203 					//G_PlayEffectID( G_EffectIndex( "concussion/alt_hit" ), tr.endpos, tr.plane.normal );
3204 					//no no no
3205 
3206 					if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
3207 					{//NOTE: hitting multiple ents can still get you over 100% accuracy
3208 						ent->client->accuracy_hits++;
3209 					}
3210 
3211 					noKnockBack = (traceEnt->flags&FL_NO_KNOCKBACK);//will be set if they die, I want to know if it was on *before* they died
3212 					if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH )
3213 					{//hehe
3214 						G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT );
3215 						break;
3216 					}
3217 					G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT );
3218 
3219 					//do knockback and knockdown manually
3220 					if ( traceEnt->client )
3221 					{//only if we hit a client
3222 						vec3_t pushDir;
3223 						VectorCopy( forward, pushDir );
3224 						if ( pushDir[2] < 0.2f )
3225 						{
3226 							pushDir[2] = 0.2f;
3227 						}//hmm, re-normalize?  nah...
3228 						/*
3229 						if ( !noKnockBack )
3230 						{//knock-backable
3231 							G_Throw( traceEnt, pushDir, 200 );
3232 						}
3233 						*/
3234 						if ( traceEnt->health > 0 )
3235 						{//alive
3236 							//if ( G_HasKnockdownAnims( traceEnt ) )
3237 							if (!noKnockBack && !traceEnt->localAnimIndex && traceEnt->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN &&
3238 								BG_KnockDownable(&traceEnt->client->ps)) //just check for humanoids..
3239 							{//knock-downable
3240 								//G_Knockdown( traceEnt, ent, pushDir, 400, qtrue );
3241 								vec3_t plPDif;
3242 								float pStr;
3243 
3244 								//cap it and stuff, base the strength and whether or not we can knockdown on the distance
3245 								//from the shooter to the target
3246 								VectorSubtract(traceEnt->client->ps.origin, ent->client->ps.origin, plPDif);
3247 								pStr = 500.0f-VectorLength(plPDif);
3248 								if (pStr < 150.0f)
3249 								{
3250 									pStr = 150.0f;
3251 								}
3252 								if (pStr > 200.0f)
3253 								{
3254 									traceEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
3255 									traceEnt->client->ps.forceHandExtendTime = level.time + 1100;
3256 									traceEnt->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
3257 								}
3258 								traceEnt->client->ps.otherKiller = ent->s.number;
3259 								traceEnt->client->ps.otherKillerTime = level.time + 5000;
3260 								traceEnt->client->ps.otherKillerDebounceTime = level.time + 100;
3261 
3262 								traceEnt->client->ps.velocity[0] += pushDir[0]*pStr;
3263 								traceEnt->client->ps.velocity[1] += pushDir[1]*pStr;
3264 								traceEnt->client->ps.velocity[2] = pStr;
3265 							}
3266 						}
3267 					}
3268 
3269 					if ( traceEnt->s.eType == ET_MOVER )
3270 					{//stop the traces on any mover
3271 						break;
3272 					}
3273 				}
3274 				else
3275 				{
3276 					 // we only make this mark on things that can't break or move
3277 				//	tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
3278 				//	tent->s.eventParm = DirToByte(tr.plane.normal);
3279 				//	tent->s.eFlags |= EF_ALT_FIRING;
3280 
3281 					//tent->svFlags |= SVF_BROADCAST;
3282 					//eh? why broadcast?
3283 				//	VectorCopy( tr.plane.normal, tent->pos1 );
3284 
3285 					//mmm..no..don't do this more than once for no reason whatsoever.
3286 					break; // hit solid, but doesn't take damage, so stop the shot...we _could_ allow it to shoot through walls, might be cool?
3287 				}
3288 			}
3289 			else // not rendering impact, must be a skybox or other similar thing?
3290 			{
3291 				break; // don't try anymore traces
3292 			}
3293 		}
3294 		// Get ready for an attempt to trace through another person
3295 		//VectorCopy( tr.endpos, muzzle2 );
3296 		VectorCopy( tr.endpos, start );
3297 		skip = tr.entityNum;
3298 		hitDodged = qfalse;
3299 	}
3300 	//just draw one beam all the way to the end
3301 //	tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT );
3302 //	tent->svFlags |= SVF_BROADCAST;
3303 	//again, why broadcast?
3304 
3305 //	tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
3306 //	tent->s.eventParm = DirToByte(tr.plane.normal);
3307 //	tent->s.eFlags |= EF_ALT_FIRING;
3308 //	VectorCopy( muzzle, tent->s.origin2 );
3309 
3310 	// now go along the trail and make sight events
3311 	VectorSubtract( tr.endpos, muzzle, dir );
3312 
3313 //	shotDist = VectorNormalize( dir );
3314 
3315 	//let's pack all this junk into a single tempent, and send it off.
3316 	tent = G_TempEntity(tr.endpos, EV_CONC_ALT_IMPACT);
3317 	tent->s.eventParm = DirToByte(tr.plane.normal);
3318 	tent->s.owner = ent->s.number;
3319 	VectorCopy(dir, tent->s.angles);
3320 	VectorCopy(muzzle, tent->s.origin2);
3321 	VectorCopy(forward, tent->s.angles2);
3322 
3323 #if 0 //yuck
3324 	//FIXME: if shoot *really* close to someone, the alert could be way out of their FOV
3325 	for ( dist = 0; dist < shotDist; dist += 64 )
3326 	{
3327 		//FIXME: on a really long shot, this could make a LOT of alerts in one frame...
3328 		VectorMA( muzzle, dist, dir, spot );
3329 		AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
3330 		//FIXME: creates *way* too many effects, make it one effect somehow?
3331 		G_PlayEffectID( G_EffectIndex( "concussion/alt_ring" ), spot, actualAngles );
3332 	}
3333 	//FIXME: spawn a temp ent that continuously spawns sight alerts here?  And 1 sound alert to draw their attention?
3334 	VectorMA( start, shotDist-4, forward, spot );
3335 	AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
3336 
3337 	G_PlayEffectID( G_EffectIndex( "concussion/altmuzzle_flash" ), muzzle, forward );
3338 #endif
3339 }
3340 
WP_FireConcussion(gentity_t * ent)3341 static void WP_FireConcussion( gentity_t *ent )
3342 {//a fast rocket-like projectile
3343 	vec3_t	start;
3344 	int		damage	= CONC_DAMAGE;
3345 	float	vel = CONC_VELOCITY;
3346 	gentity_t *missile;
3347 
3348 	//hold us still for a bit
3349 	//ent->client->ps.pm_time = 300;
3350 	//ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3351 	//add viewkick
3352 //	if ( ent->s.number < MAX_CLIENTS//player only
3353 //		&& !cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise
3354 //	{//kick the view back
3355 //		cg.kick_angles[PITCH] = Q_flrand( -10, -15 );
3356 //		cg.kick_time = level.time;
3357 //	}
3358 	//mm..yeah..this needs some reworking for mp
3359 
3360 	VectorCopy( muzzle, start );
3361 	WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
3362 
3363 	missile = CreateMissile( start, forward, vel, 10000, ent, qfalse );
3364 
3365 	missile->classname = "conc_proj";
3366 	missile->s.weapon = WP_CONCUSSION;
3367 	missile->mass = 10;
3368 
3369 	// Make it easier to hit things
3370 	VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
3371 	VectorScale( missile->r.maxs, -1, missile->r.mins );
3372 
3373 	missile->damage = damage;
3374 	missile->dflags = DAMAGE_EXTRA_KNOCKBACK;
3375 
3376 	missile->methodOfDeath = MOD_CONC;
3377 	missile->splashMethodOfDeath = MOD_CONC;
3378 
3379 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
3380 	missile->splashDamage = CONC_SPLASH_DAMAGE;
3381 	missile->splashRadius = CONC_SPLASH_RADIUS;
3382 
3383 	// we don't want it to ever bounce
3384 	missile->bounceCount = 0;
3385 }
3386 
3387 
3388 //---------------------------------------------------------
3389 // FireStunBaton
3390 //---------------------------------------------------------
WP_FireStunBaton(gentity_t * ent,qboolean alt_fire)3391 void WP_FireStunBaton( gentity_t *ent, qboolean alt_fire )
3392 {
3393 	gentity_t	*tr_ent;
3394 	trace_t		tr;
3395 	vec3_t		mins, maxs, end;
3396 	vec3_t		muzzleStun;
3397 
3398 	if (!ent->client)
3399 	{
3400 		VectorCopy(ent->r.currentOrigin, muzzleStun);
3401 		muzzleStun[2] += 8;
3402 	}
3403 	else
3404 	{
3405 		VectorCopy(ent->client->ps.origin, muzzleStun);
3406 		muzzleStun[2] += ent->client->ps.viewheight-6;
3407 	}
3408 
3409 	VectorMA(muzzleStun, 20.0f, forward, muzzleStun);
3410 	VectorMA(muzzleStun, 4.0f, vright, muzzleStun);
3411 
3412 	VectorMA( muzzleStun, STUN_BATON_RANGE, forward, end );
3413 
3414 	VectorSet( maxs, 6, 6, 6 );
3415 	VectorScale( maxs, -1, mins );
3416 
3417 	trap->Trace ( &tr, muzzleStun, mins, maxs, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
3418 
3419 	if ( tr.entityNum >= ENTITYNUM_WORLD )
3420 	{
3421 		return;
3422 	}
3423 
3424 	tr_ent = &g_entities[tr.entityNum];
3425 
3426 	if (tr_ent && tr_ent->takedamage && tr_ent->client)
3427 	{ //see if either party is involved in a duel
3428 		if (tr_ent->client->ps.duelInProgress &&
3429 			tr_ent->client->ps.duelIndex != ent->s.number)
3430 		{
3431 			return;
3432 		}
3433 
3434 		if (ent->client &&
3435 			ent->client->ps.duelInProgress &&
3436 			ent->client->ps.duelIndex != tr_ent->s.number)
3437 		{
3438 			return;
3439 		}
3440 	}
3441 
3442 	if ( tr_ent && tr_ent->takedamage )
3443 	{
3444 		G_PlayEffect( EFFECT_STUNHIT, tr.endpos, tr.plane.normal );
3445 
3446 		G_Sound( tr_ent, CHAN_WEAPON, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
3447 		G_Damage( tr_ent, ent, ent, forward, tr.endpos, STUN_BATON_DAMAGE, (DAMAGE_NO_KNOCKBACK|DAMAGE_HALF_ABSORB), MOD_STUN_BATON );
3448 
3449 		if (tr_ent->client)
3450 		{ //if it's a player then use the shock effect
3451 			if ( tr_ent->client->NPC_class == CLASS_VEHICLE )
3452 			{//not on vehicles
3453 				if ( !tr_ent->m_pVehicle
3454 					|| tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL
3455 					|| tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_FLIER )
3456 				{//can zap animals
3457 					tr_ent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
3458 				}
3459 			}
3460 			else
3461 			{
3462 				tr_ent->client->ps.electrifyTime = level.time + 700;
3463 			}
3464 		}
3465 	}
3466 }
3467 
3468 
3469 //---------------------------------------------------------
3470 // FireMelee
3471 //---------------------------------------------------------
WP_FireMelee(gentity_t * ent,qboolean alt_fire)3472 void WP_FireMelee( gentity_t *ent, qboolean alt_fire )
3473 {
3474 	gentity_t	*tr_ent;
3475 	trace_t		tr;
3476 	vec3_t		mins, maxs, end;
3477 	vec3_t		muzzlePunch;
3478 
3479 	if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2)
3480 	{ //right
3481 		if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM))
3482 		{
3483 			return;
3484 		}
3485 	}
3486 	else
3487 	{ //left
3488 		if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))
3489 		{
3490 			return;
3491 		}
3492 	}
3493 
3494 	if (!ent->client)
3495 	{
3496 		VectorCopy(ent->r.currentOrigin, muzzlePunch);
3497 		muzzlePunch[2] += 8;
3498 	}
3499 	else
3500 	{
3501 		VectorCopy(ent->client->ps.origin, muzzlePunch);
3502 		muzzlePunch[2] += ent->client->ps.viewheight-6;
3503 	}
3504 
3505 	VectorMA(muzzlePunch, 20.0f, forward, muzzlePunch);
3506 	VectorMA(muzzlePunch, 4.0f, vright, muzzlePunch);
3507 
3508 	VectorMA( muzzlePunch, MELEE_RANGE, forward, end );
3509 
3510 	VectorSet( maxs, 6, 6, 6 );
3511 	VectorScale( maxs, -1, mins );
3512 
3513 	trap->Trace ( &tr, muzzlePunch, mins, maxs, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
3514 
3515 	if (tr.entityNum != ENTITYNUM_NONE)
3516 	{ //hit something
3517 		tr_ent = &g_entities[tr.entityNum];
3518 
3519 		G_Sound( ent, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
3520 
3521 		if (tr_ent->takedamage && tr_ent->client)
3522 		{ //special duel checks
3523 			if (tr_ent->client->ps.duelInProgress &&
3524 				tr_ent->client->ps.duelIndex != ent->s.number)
3525 			{
3526 				return;
3527 			}
3528 
3529 			if (ent->client &&
3530 				ent->client->ps.duelInProgress &&
3531 				ent->client->ps.duelIndex != tr_ent->s.number)
3532 			{
3533 				return;
3534 			}
3535 		}
3536 
3537 		if ( tr_ent->takedamage )
3538 		{ //damage them, do more damage if we're in the second right hook
3539 			int dmg = MELEE_SWING1_DAMAGE;
3540 
3541 			if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2)
3542 			{ //do a tad bit more damage on the second swing
3543 				dmg = MELEE_SWING2_DAMAGE;
3544 			}
3545 
3546 			if ( G_HeavyMelee( ent ) )
3547 			{ //2x damage for heavy melee class
3548 				dmg *= 2;
3549 			}
3550 
3551 			G_Damage( tr_ent, ent, ent, forward, tr.endpos, dmg, DAMAGE_NO_ARMOR, MOD_MELEE );
3552 		}
3553 	}
3554 }
3555 
3556 ////////////////////////////////////////////////////////////////////////////
3557 ////////////////////////////////////////////////////////////////////////////
3558 ////////////////////////////////////////////////////////////////////////////
3559 ////////////////////////////////////////////////////////////////////////////
3560 ////////////////////////////////////////////////////////////////////////////
3561 
3562 
3563 /*
3564 ======================
3565 SnapVectorTowards
3566 
3567 Round a vector to integers for more efficient network
3568 transmission, but make sure that it rounds towards a given point
3569 rather than blindly truncating.  This prevents it from truncating
3570 into a wall.
3571 ======================
3572 */
SnapVectorTowards(vec3_t v,vec3_t to)3573 void SnapVectorTowards( vec3_t v, vec3_t to ) {
3574 	int		i;
3575 
3576 	for ( i = 0 ; i < 3 ; i++ ) {
3577 		if ( to[i] <= v[i] ) {
3578 			v[i] = floorf( v[i] );
3579 		} else {
3580 			v[i] = ceilf( v[i] );
3581 		}
3582 	}
3583 }
3584 
3585 
3586 //======================================================================
3587 
3588 
3589 /*
3590 ===============
3591 LogAccuracyHit
3592 ===============
3593 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)3594 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
3595 	if( !target->takedamage ) {
3596 		return qfalse;
3597 	}
3598 
3599 	if ( target == attacker ) {
3600 		return qfalse;
3601 	}
3602 
3603 	if( !target->client ) {
3604 		return qfalse;
3605 	}
3606 
3607 	if (!attacker)
3608 	{
3609 		return qfalse;
3610 	}
3611 
3612 	if( !attacker->client ) {
3613 		return qfalse;
3614 	}
3615 
3616 	if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
3617 		return qfalse;
3618 	}
3619 
3620 	if ( OnSameTeam( target, attacker ) ) {
3621 		return qfalse;
3622 	}
3623 
3624 	return qtrue;
3625 }
3626 
3627 
3628 /*
3629 ===============
3630 CalcMuzzlePoint
3631 
3632 set muzzle location relative to pivoting eye
3633 rwwFIXMEFIXME: Since ghoul2 models are on server and properly updated now,
3634 it may be reasonable to base muzzle point off actual weapon bolt point.
3635 The down side would be that it does not necessarily look alright from a
3636 first person perspective.
3637 ===============
3638 */
CalcMuzzlePoint(gentity_t * ent,const vec3_t inForward,const vec3_t inRight,const vec3_t inUp,vec3_t muzzlePoint)3639 void CalcMuzzlePoint ( gentity_t *ent, const vec3_t inForward, const vec3_t inRight, const vec3_t inUp, vec3_t muzzlePoint )
3640 {
3641 	int weapontype;
3642 	vec3_t muzzleOffPoint;
3643 
3644 	weapontype = ent->s.weapon;
3645 	VectorCopy( ent->s.pos.trBase, muzzlePoint );
3646 
3647 	VectorCopy(WP_MuzzlePoint[weapontype], muzzleOffPoint);
3648 
3649 	if (weapontype > WP_NONE && weapontype < WP_NUM_WEAPONS)
3650 	{	// Use the table to generate the muzzlepoint;
3651 		{	// Crouching.  Use the add-to-Z method to adjust vertically.
3652 			VectorMA(muzzlePoint, muzzleOffPoint[0], inForward, muzzlePoint);
3653 			VectorMA(muzzlePoint, muzzleOffPoint[1], inRight, muzzlePoint);
3654 			muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2];
3655 		}
3656 	}
3657 
3658 	// snap to integer coordinates for more efficient network bandwidth usage
3659 	SnapVector( muzzlePoint );
3660 }
3661 
3662 extern void G_MissileImpact( gentity_t *ent, trace_t *trace );
WP_TouchVehMissile(gentity_t * ent,gentity_t * other,trace_t * trace)3663 void WP_TouchVehMissile( gentity_t *ent, gentity_t *other, trace_t *trace )
3664 {
3665 	trace_t	myTrace;
3666 	memcpy( (void *)&myTrace, (void *)trace, sizeof(myTrace) );
3667 	if ( other )
3668 	{
3669 		myTrace.entityNum = other->s.number;
3670 	}
3671 	G_MissileImpact( ent, &myTrace );
3672 }
3673 
WP_CalcVehMuzzle(gentity_t * ent,int muzzleNum)3674 void WP_CalcVehMuzzle(gentity_t *ent, int muzzleNum)
3675 {
3676 	Vehicle_t *pVeh = ent->m_pVehicle;
3677 	mdxaBone_t boltMatrix;
3678 	vec3_t	vehAngles;
3679 
3680 	assert(pVeh);
3681 
3682 	if (pVeh->m_iMuzzleTime[muzzleNum] == level.time)
3683 	{ //already done for this frame, don't need to do it again
3684 		return;
3685 	}
3686 	//Uh... how about we set this, hunh...?  :)
3687 	pVeh->m_iMuzzleTime[muzzleNum] = level.time;
3688 
3689 	VectorCopy( ent->client->ps.viewangles, vehAngles );
3690 	if ( pVeh->m_pVehicleInfo
3691 		&& (pVeh->m_pVehicleInfo->type == VH_ANIMAL
3692 			 ||pVeh->m_pVehicleInfo->type == VH_WALKER
3693 			 ||pVeh->m_pVehicleInfo->type == VH_SPEEDER) )
3694 	{
3695 		vehAngles[PITCH] = vehAngles[ROLL] = 0;
3696 	}
3697 
3698 	trap->G2API_GetBoltMatrix_NoRecNoRot(ent->ghoul2, 0, pVeh->m_iMuzzleTag[muzzleNum], &boltMatrix, vehAngles,
3699 		ent->client->ps.origin, level.time, NULL, ent->modelScale);
3700 	BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pVeh->m_vMuzzlePos[muzzleNum]);
3701 	BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, pVeh->m_vMuzzleDir[muzzleNum]);
3702 }
3703 
WP_VehWeapSetSolidToOwner(gentity_t * self)3704 void WP_VehWeapSetSolidToOwner( gentity_t *self )
3705 {
3706 	self->r.svFlags |= SVF_OWNERNOTSHARED;
3707 	if ( self->genericValue1 )
3708 	{//expire after a time
3709 		if ( self->genericValue2 )
3710 		{//blow up when your lifetime is up
3711 			self->think = G_ExplodeMissile;//FIXME: custom func?
3712 		}
3713 		else
3714 		{//just remove yourself
3715 			self->think = G_FreeEntity;//FIXME: custom func?
3716 		}
3717 		self->nextthink = level.time + self->genericValue1;
3718 	}
3719 }
3720 
3721 #define VEH_HOMING_MISSILE_THINK_TIME		100
WP_FireVehicleWeapon(gentity_t * ent,vec3_t start,vec3_t dir,vehWeaponInfo_t * vehWeapon,qboolean alt_fire,qboolean isTurretWeap)3722 gentity_t *WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon, qboolean alt_fire, qboolean isTurretWeap )
3723 {
3724 	gentity_t	*missile = NULL;
3725 
3726 	//FIXME: add some randomness...?  Inherent inaccuracy stat of weapon?  Pilot skill?
3727 	if ( !vehWeapon )
3728 	{//invalid vehicle weapon
3729 		return NULL;
3730 	}
3731 	else if ( vehWeapon->bIsProjectile )
3732 	{//projectile entity
3733 		vec3_t		mins, maxs;
3734 
3735 		VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f );
3736 		VectorScale( maxs, -1, mins );
3737 
3738 		//make sure our start point isn't on the other side of a wall
3739 		WP_TraceSetStart( ent, start, mins, maxs );
3740 
3741 		//FIXME: CUSTOM MODEL?
3742 		//QUERY: alt_fire true or not?  Does it matter?
3743 		missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse );
3744 
3745 		missile->classname = "vehicle_proj";
3746 
3747 		missile->s.genericenemyindex = ent->s.number+MAX_GENTITIES;
3748 		missile->damage = vehWeapon->iDamage;
3749 		missile->splashDamage = vehWeapon->iSplashDamage;
3750 		missile->splashRadius = vehWeapon->fSplashRadius;
3751 
3752 		//FIXME: externalize some of these properties?
3753 		missile->dflags = DAMAGE_DEATH_KNOCKBACK;
3754 		missile->clipmask = MASK_SHOT;
3755 		//Maybe by checking flags...?
3756 		if ( vehWeapon->bSaberBlockable )
3757 		{
3758 			missile->clipmask |= CONTENTS_LIGHTSABER;
3759 		}
3760 		/*
3761 		if ( (vehWeapon->iFlags&VWF_KNOCKBACK) )
3762 		{
3763 			missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK;
3764 		}
3765 		if ( (vehWeapon->iFlags&VWF_RADAR) )
3766 		{
3767 			missile->s.eFlags |= EF_RADAROBJECT;
3768 		}
3769 		*/
3770 		// Make it easier to hit things
3771 		VectorCopy( mins, missile->r.mins );
3772 		VectorCopy( maxs, missile->r.maxs );
3773 		//some slightly different stuff for things with bboxes
3774 		if ( vehWeapon->fWidth || vehWeapon->fHeight )
3775 		{//we assume it's a rocket-like thing
3776 			missile->s.weapon = WP_ROCKET_LAUNCHER;//does this really matter?
3777 			missile->methodOfDeath = MOD_VEHICLE;//MOD_ROCKET;
3778 			missile->splashMethodOfDeath = MOD_VEHICLE;//MOD_ROCKET;// ?SPLASH;
3779 
3780 			// we don't want it to ever bounce
3781 			missile->bounceCount = 0;
3782 
3783 			missile->mass = 10;
3784 		}
3785 		else
3786 		{//a blaster-laser-like thing
3787 			missile->s.weapon = WP_BLASTER;//does this really matter?
3788 			missile->methodOfDeath = MOD_VEHICLE; //count as a heavy weap
3789 			missile->splashMethodOfDeath = MOD_VEHICLE;// ?SPLASH;
3790 			// we don't want it to bounce forever
3791 			missile->bounceCount = 8;
3792 		}
3793 
3794 		if ( vehWeapon->bHasGravity )
3795 		{//TESTME: is this all we need to do?
3796 			missile->s.weapon = WP_THERMAL;//does this really matter?
3797 			missile->s.pos.trType = TR_GRAVITY;
3798 		}
3799 
3800 		if ( vehWeapon->bIonWeapon )
3801 		{//so it disables ship shields and sends them out of control
3802 			missile->s.weapon = WP_DEMP2;
3803 		}
3804 
3805 		if ( vehWeapon->iHealth )
3806 		{//the missile can take damage
3807 			missile->health = vehWeapon->iHealth;
3808 			missile->takedamage = qtrue;
3809 			missile->r.contents = MASK_SHOT;
3810 			missile->die = RocketDie;
3811 		}
3812 
3813 		//pilot should own this projectile on server if we have a pilot
3814 		if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot)
3815 		{//owned by vehicle pilot
3816 			missile->r.ownerNum = ent->m_pVehicle->m_pPilot->s.number;
3817 		}
3818 		else
3819 		{//owned by vehicle?
3820 			missile->r.ownerNum = ent->s.number;
3821 		}
3822 
3823 		//set veh as cgame side owner for purpose of fx overrides
3824 		missile->s.owner = ent->s.number;
3825 		if ( alt_fire )
3826 		{//use the second weapon's iShotFX
3827 			missile->s.eFlags |= EF_ALT_FIRING;
3828 		}
3829 		if ( isTurretWeap )
3830 		{//look for the turret weapon info on cgame side, not vehicle weapon info
3831 			missile->s.weapon = WP_TURRET;
3832 		}
3833 		if ( vehWeapon->iLifeTime )
3834 		{//expire after a time
3835 			if ( vehWeapon->bExplodeOnExpire )
3836 			{//blow up when your lifetime is up
3837 				missile->think = G_ExplodeMissile;//FIXME: custom func?
3838 			}
3839 			else
3840 			{//just remove yourself
3841 				missile->think = G_FreeEntity;//FIXME: custom func?
3842 			}
3843 			missile->nextthink = level.time + vehWeapon->iLifeTime;
3844 		}
3845 		missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]);
3846 		missile->s.eFlags |= EF_JETPACK_ACTIVE;
3847 		//homing
3848 		if ( vehWeapon->fHoming )
3849 		{//homing missile
3850 			if ( ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE )
3851 			{
3852 				int dif = 0;
3853 				float rTime;
3854 				rTime = ent->client->ps.rocketLockTime;
3855 
3856 				if (rTime == -1)
3857 				{
3858 					rTime = ent->client->ps.rocketLastValidTime;
3859 				}
3860 
3861 				if ( !vehWeapon->iLockOnTime )
3862 				{//no minimum lock-on time
3863 					dif = 10;//guaranteed lock-on
3864 				}
3865 				else
3866 				{
3867 					float lockTimeInterval = vehWeapon->iLockOnTime/16.0f;
3868 					dif = ( level.time - rTime ) / lockTimeInterval;
3869 				}
3870 
3871 				if (dif < 0)
3872 				{
3873 					dif = 0;
3874 				}
3875 
3876 				//It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
3877 				if ( dif >= 10 && rTime != -1 )
3878 				{
3879 					missile->enemy = &g_entities[ent->client->ps.rocketLockIndex];
3880 
3881 					if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
3882 					{ //if enemy became invalid, died, or is on the same team, then don't seek it
3883 						missile->spawnflags |= 1;//just to let it know it should be faster...
3884 						missile->speed = vehWeapon->fSpeed;
3885 						missile->angle = vehWeapon->fHoming;
3886 						missile->radius = vehWeapon->fHomingFOV;
3887 						//crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame...
3888 						if ( vehWeapon->iLifeTime )
3889 						{//expire after a time
3890 							missile->genericValue1 = level.time + vehWeapon->iLifeTime;
3891 							missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire);
3892 						}
3893 						//now go ahead and use the rocketThink func
3894 						missile->think = rocketThink;//FIXME: custom func?
3895 						missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME;
3896 						missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize
3897 						if ( missile->enemy->s.NPC_class == CLASS_VEHICLE )
3898 						{//let vehicle know we've locked on to them
3899 							missile->s.otherEntityNum = missile->enemy->s.number;
3900 						}
3901 					}
3902 				}
3903 
3904 				VectorCopy( dir, missile->movedir );
3905 				missile->random = 1.0f;//FIXME: externalize?
3906 			}
3907 		}
3908 		if ( !vehWeapon->fSpeed )
3909 		{//a mine or something?
3910 			//only do damage when someone touches us
3911 			missile->s.weapon = WP_THERMAL;//does this really matter?
3912 			G_SetOrigin( missile, start );
3913 			missile->touch = WP_TouchVehMissile;
3914 			missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize
3915 			//crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame...
3916 			if ( vehWeapon->iLifeTime )
3917 			{//expire after a time
3918 				missile->genericValue1 = vehWeapon->iLifeTime;
3919 				missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire);
3920 			}
3921 			//now go ahead and use the setsolidtoowner func
3922 			missile->think = WP_VehWeapSetSolidToOwner;
3923 			missile->nextthink = level.time + 3000;
3924 		}
3925 	}
3926 	else
3927 	{//traceline
3928 		//FIXME: implement
3929 	}
3930 
3931 	return missile;
3932 }
3933 
3934 //custom routine to not waste tempents horribly -rww
G_VehMuzzleFireFX(gentity_t * ent,gentity_t * broadcaster,int muzzlesFired)3935 void G_VehMuzzleFireFX( gentity_t *ent, gentity_t *broadcaster, int muzzlesFired )
3936 {
3937 	Vehicle_t *pVeh = ent->m_pVehicle;
3938 	gentity_t *b;
3939 
3940 	if (!pVeh)
3941 	{
3942 		return;
3943 	}
3944 
3945 	if (!broadcaster)
3946 	{ //oh well. We will WASTE A TEMPENT.
3947 		b = G_TempEntity( ent->client->ps.origin, EV_VEH_FIRE );
3948 	}
3949 	else
3950 	{ //joy
3951 		b = broadcaster;
3952 	}
3953 
3954 	//this guy owns it
3955 	b->s.owner = ent->s.number;
3956 
3957 	//this is the bitfield of all muzzles fired this time
3958 	//NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits
3959 	b->s.trickedentindex = muzzlesFired;
3960 
3961 	if ( broadcaster )
3962 	{ //add the event
3963 		G_AddEvent( b, EV_VEH_FIRE, 0 );
3964 	}
3965 }
3966 
G_EstimateCamPos(vec3_t viewAngles,vec3_t cameraFocusLoc,float viewheight,float thirdPersonRange,float thirdPersonHorzOffset,float vertOffset,float pitchOffset,int ignoreEntNum,vec3_t camPos)3967 void G_EstimateCamPos( vec3_t viewAngles, vec3_t cameraFocusLoc, float viewheight, float thirdPersonRange,
3968 					  float thirdPersonHorzOffset, float vertOffset, float pitchOffset,
3969 					  int ignoreEntNum, vec3_t camPos )
3970 {
3971 	int			MASK_CAMERACLIP = (MASK_SOLID|CONTENTS_PLAYERCLIP);
3972 	float		CAMERA_SIZE = 4;
3973 	vec3_t		cameramins;
3974 	vec3_t		cameramaxs;
3975 	vec3_t		cameraFocusAngles, camerafwd, cameraup;
3976 	vec3_t		cameraIdealTarget, cameraCurTarget;
3977 	vec3_t		cameraIdealLoc, cameraCurLoc;
3978 	vec3_t		diff;
3979 	vec3_t		camAngles;
3980 	matrix3_t	viewaxis;
3981 	trace_t		trace;
3982 
3983 	VectorSet( cameramins, -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE );
3984 	VectorSet( cameramaxs, CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE );
3985 
3986 	VectorCopy( viewAngles, cameraFocusAngles );
3987 	cameraFocusAngles[PITCH] += pitchOffset;
3988 	if ( !bg_fighterAltControl.integer )
3989 	{//clamp view pitch
3990 		cameraFocusAngles[PITCH] = AngleNormalize180( cameraFocusAngles[PITCH] );
3991 		if (cameraFocusAngles[PITCH] > 80.0)
3992 		{
3993 			cameraFocusAngles[PITCH] = 80.0;
3994 		}
3995 		else if (cameraFocusAngles[PITCH] < -80.0)
3996 		{
3997 			cameraFocusAngles[PITCH] = -80.0;
3998 		}
3999 	}
4000 	AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup);
4001 
4002 	cameraFocusLoc[2] += viewheight;
4003 
4004 	VectorCopy( cameraFocusLoc, cameraIdealTarget );
4005 	cameraIdealTarget[2] += vertOffset;
4006 
4007 	//NOTE: on cgame, this uses the thirdpersontargetdamp value, we ignore that here
4008 	VectorCopy( cameraIdealTarget, cameraCurTarget );
4009 	trap->Trace( &trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, ignoreEntNum, MASK_CAMERACLIP, qfalse, 0, 0 );
4010 	if (trace.fraction < 1.0)
4011 	{
4012 		VectorCopy(trace.endpos, cameraCurTarget);
4013 	}
4014 
4015 	VectorMA(cameraIdealTarget, -(thirdPersonRange), camerafwd, cameraIdealLoc);
4016 	//NOTE: on cgame, this uses the thirdpersoncameradamp value, we ignore that here
4017 	VectorCopy( cameraIdealLoc, cameraCurLoc );
4018 	trap->Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, ignoreEntNum, MASK_CAMERACLIP, qfalse, 0, 0);
4019 	if (trace.fraction < 1.0)
4020 	{
4021 		VectorCopy( trace.endpos, cameraCurLoc );
4022 	}
4023 
4024 	VectorSubtract(cameraCurTarget, cameraCurLoc, diff);
4025 	{
4026 		float dist = VectorNormalize(diff);
4027 		//under normal circumstances, should never be 0.00000 and so on.
4028 		if ( !dist || (diff[0] == 0 || diff[1] == 0) )
4029 		{//must be hitting something, need some value to calc angles, so use cam forward
4030 			VectorCopy( camerafwd, diff );
4031 		}
4032 	}
4033 
4034 	vectoangles(diff, camAngles);
4035 
4036 	if ( thirdPersonHorzOffset != 0.0f )
4037 	{
4038 		AnglesToAxis( camAngles, viewaxis );
4039 		VectorMA( cameraCurLoc, thirdPersonHorzOffset, viewaxis[1], cameraCurLoc );
4040 	}
4041 
4042 	VectorCopy(cameraCurLoc, camPos);
4043 }
4044 
WP_GetVehicleCamPos(gentity_t * ent,gentity_t * pilot,vec3_t camPos)4045 void WP_GetVehicleCamPos( gentity_t *ent, gentity_t *pilot, vec3_t camPos )
4046 {
4047 	float thirdPersonHorzOffset = ent->m_pVehicle->m_pVehicleInfo->cameraHorzOffset;
4048 	float thirdPersonRange = ent->m_pVehicle->m_pVehicleInfo->cameraRange;
4049 	float pitchOffset = ent->m_pVehicle->m_pVehicleInfo->cameraPitchOffset;
4050 	float vertOffset = ent->m_pVehicle->m_pVehicleInfo->cameraVertOffset;
4051 
4052 	if ( ent->client->ps.hackingTime )
4053 	{
4054 		thirdPersonHorzOffset += (((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * -80.0f;
4055 		thirdPersonRange += fabs(((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * 100.0f;
4056 	}
4057 
4058 	if ( ent->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset )
4059 	{
4060 		if ( pilot->client->ps.viewangles[PITCH] > 0 )
4061 		{
4062 			vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-10;
4063 			if ( vertOffset < -170 )
4064 			{
4065 				vertOffset = -170;
4066 			}
4067 		}
4068 		else if ( pilot->client->ps.viewangles[PITCH] < 0 )
4069 		{
4070 			vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-5;
4071 			if ( vertOffset > 130 )
4072 			{
4073 				vertOffset = 130;
4074 			}
4075 		}
4076 		else
4077 		{
4078 			vertOffset = 30;
4079 		}
4080 		if ( pilot->client->ps.viewangles[PITCH] > 0 )
4081 		{
4082 			pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75;
4083 		}
4084 		else if ( pilot->client->ps.viewangles[PITCH] < 0 )
4085 		{
4086 			pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75;
4087 		}
4088 		else
4089 		{
4090 			pitchOffset = 0;
4091 		}
4092 	}
4093 
4094 	//Control Scheme 3 Method:
4095 	G_EstimateCamPos( ent->client->ps.viewangles, pilot->client->ps.origin, pilot->client->ps.viewheight, thirdPersonRange,
4096 		thirdPersonHorzOffset, vertOffset, pitchOffset,
4097 		pilot->s.number, camPos );
4098 	/*
4099 	//Control Scheme 2 Method:
4100 	G_EstimateCamPos( ent->m_pVehicle->m_vOrientation, ent->r.currentOrigin, pilot->client->ps.viewheight, thirdPersonRange,
4101 		thirdPersonHorzOffset, vertOffset, pitchOffset,
4102 		pilot->s.number, camPos );
4103 	*/
4104 }
4105 
WP_VehLeadCrosshairVeh(gentity_t * camTraceEnt,vec3_t newEnd,const vec3_t dir,const vec3_t shotStart,vec3_t shotDir)4106 void WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir )
4107 {
4108 	if ( camTraceEnt
4109 		&& camTraceEnt->client
4110 		&& camTraceEnt->client->NPC_class == CLASS_VEHICLE )
4111 	{//if the crosshair is on a vehicle, lead it
4112 		float distAdjust = DotProduct( camTraceEnt->client->ps.velocity, dir );
4113 		VectorMA( newEnd, distAdjust, dir, newEnd );
4114 	}
4115 	VectorSubtract( newEnd, shotStart, shotDir );
4116 	VectorNormalize( shotDir );
4117 }
4118 
4119 #define MAX_XHAIR_DIST_ACCURACY	20000.0f
4120 extern float g_cullDistance;
4121 extern int BG_VehTraceFromCamPos( trace_t *camTrace, bgEntity_t *bgEnt, const vec3_t entOrg, const vec3_t shotStart, const vec3_t end, vec3_t newEnd, vec3_t shotDir, float bestDist );
WP_VehCheckTraceFromCamPos(gentity_t * ent,const vec3_t shotStart,vec3_t shotDir)4122 qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir )
4123 {
4124 	//FIXME: only if dynamicCrosshair and dynamicCrosshairPrecision is on!
4125 	if ( !ent
4126 		|| !ent->m_pVehicle
4127 		|| !ent->m_pVehicle->m_pVehicleInfo
4128 		|| !ent->m_pVehicle->m_pPilot//not being driven
4129 		|| !((gentity_t*)ent->m_pVehicle->m_pPilot)->client//not being driven by a client...?!!!
4130 		|| (ent->m_pVehicle->m_pPilot->s.number >= MAX_CLIENTS) )//being driven, but not by a real client, no need to worry about crosshair
4131 	{
4132 		return qfalse;
4133 	}
4134 	if ( (ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && g_cullDistance > MAX_XHAIR_DIST_ACCURACY )
4135 		|| ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
4136 	{
4137 		//FIRST: simulate the normal crosshair trace from the center of the veh straight forward
4138 		trace_t trace;
4139 		vec3_t	dir, start, end;
4140 		if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
4141 		{//for some reason, the walker always draws the crosshair out from from the first muzzle point
4142 			AngleVectors( ent->client->ps.viewangles, dir, NULL, NULL );
4143 			VectorCopy( ent->r.currentOrigin, start );
4144 			start[2] += ent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48;
4145 		}
4146 		else
4147 		{
4148 			vec3_t ang;
4149 			if (ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
4150 			{
4151 				VectorSet(ang, 0.0f, ent->m_pVehicle->m_vOrientation[1], 0.0f);
4152 			}
4153 			else
4154 			{
4155 				VectorCopy(ent->m_pVehicle->m_vOrientation, ang);
4156 			}
4157 			AngleVectors( ang, dir, NULL, NULL );
4158 			VectorCopy( ent->r.currentOrigin, start );
4159 		}
4160 		VectorMA( start, g_cullDistance, dir, end );
4161 		trap->Trace( &trace, start, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID|CONTENTS_BODY, qfalse, 0, 0 );
4162 
4163 		if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
4164 		{//just use the result of that one trace since walkers don't do the extra trace
4165 			VectorSubtract( trace.endpos, shotStart, shotDir );
4166 			VectorNormalize( shotDir );
4167 			return qtrue;
4168 		}
4169 		else
4170 		{//NOW do the trace from the camPos and compare with above trace
4171 			trace_t	extraTrace;
4172 			vec3_t	newEnd;
4173 			int camTraceEntNum = BG_VehTraceFromCamPos( &extraTrace, (bgEntity_t *)ent, ent->r.currentOrigin, shotStart, end, newEnd, shotDir, (trace.fraction*g_cullDistance) );
4174 			if ( camTraceEntNum )
4175 			{
4176 				WP_VehLeadCrosshairVeh( &g_entities[camTraceEntNum-1], newEnd, dir, shotStart, shotDir );
4177 				return qtrue;
4178 			}
4179 		}
4180 	}
4181 	return qfalse;
4182 }
4183 
4184 //---------------------------------------------------------
FireVehicleWeapon(gentity_t * ent,qboolean alt_fire)4185 void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire )
4186 //---------------------------------------------------------
4187 {
4188 	Vehicle_t *pVeh = ent->m_pVehicle;
4189 	int muzzlesFired = 0;
4190 	gentity_t *missile = NULL;
4191 	vehWeaponInfo_t *vehWeapon = NULL;
4192 	qboolean	clearRocketLockEntity = qfalse;
4193 
4194 	if ( !pVeh )
4195 	{
4196 		return;
4197 	}
4198 
4199 	if (pVeh->m_iRemovedSurfaces)
4200 	{ //can't fire when the thing is breaking apart
4201 		return;
4202 	}
4203 
4204 	if (pVeh->m_pVehicleInfo->type == VH_WALKER &&
4205 		ent->client->ps.electrifyTime > level.time)
4206 	{ //don't fire while being electrocuted
4207 		return;
4208 	}
4209 
4210 	// TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle
4211 	// so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you
4212 	// would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis
4213 
4214 	// If this is not the alternate fire, fire a normal blaster shot...
4215 	if ( pVeh->m_pVehicleInfo &&
4216 		(pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched.
4217 	{//fighters can only fire when wings are open
4218 		int	weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE;
4219 		int	delay = 1000;
4220 		qboolean aimCorrect = qfalse;
4221 		qboolean linkedFiring = qfalse;
4222 
4223 		if ( !alt_fire )
4224 		{
4225 			weaponNum = 0;
4226 		}
4227 		else
4228 		{
4229 			weaponNum = 1;
4230 		}
4231 
4232 		vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID;
4233 
4234 		if ( pVeh->weaponStatus[weaponNum].ammo <= 0 )
4235 		{//no ammo for this weapon
4236 			if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
4237 			{// let the client know he's out of ammo
4238 				int i;
4239 				//but only if one of the vehicle muzzles is actually ready to fire this weapon
4240 				for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4241 				{
4242 					if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4243 					{//this muzzle doesn't match the weapon we're trying to use
4244 						continue;
4245 					}
4246 					if ( pVeh->m_iMuzzleTag[i] != -1
4247 						&& pVeh->m_iMuzzleWait[i] < level.time )
4248 					{//this one would have fired, send the no ammo message
4249 						G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
4250 						break;
4251 					}
4252 				}
4253 			}
4254 			return;
4255 		}
4256 
4257 		delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
4258 		aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
4259 		if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked
4260 			|| ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable
4261 				 && pVeh->weaponStatus[weaponNum].linked ) )//linked
4262 		{//we're linking the primary or alternate weapons, so we'll do *all* the muzzles
4263 			linkedFiring = qtrue;
4264 		}
4265 
4266 		if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS )
4267 		{//invalid vehicle weapon
4268 			return;
4269 		}
4270 		else
4271 		{
4272 			int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0;
4273 			qboolean sentAmmoWarning = qfalse;
4274 
4275 			vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
4276 
4277 			if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 )
4278 			{//always linked weapons don't accumulate delay, just use specified delay
4279 				cumulativeDelay = delay;
4280 			}
4281 			//find out how many we've got for this weapon
4282 			for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4283 			{
4284 				if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4285 				{//this muzzle doesn't match the weapon we're trying to use
4286 					continue;
4287 				}
4288 				if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time )
4289 				{
4290 					numMuzzlesReady++;
4291 				}
4292 				if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex )
4293 				{//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time)
4294 					//set the next to this one
4295 					pVeh->weaponStatus[weaponNum].nextMuzzle = i;
4296 				}
4297 				if ( linkedFiring )
4298 				{
4299 					cumulativeAmmo += vehWeapon->iAmmoPerShot;
4300 					if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 )
4301 					{//always linked weapons don't accumulate delay, just use specified delay
4302 						cumulativeDelay += delay;
4303 					}
4304 				}
4305 				numMuzzles++;
4306 			}
4307 
4308 			if ( linkedFiring )
4309 			{//firing all muzzles at once
4310 				if ( numMuzzlesReady != numMuzzles )
4311 				{//can't fire all linked muzzles yet
4312 					return;
4313 				}
4314 				else
4315 				{//can fire all linked muzzles, check ammo
4316 					if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo )
4317 					{//can't fire, not enough ammo
4318 						if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
4319 						{// let the client know he's out of ammo
4320 							G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
4321 						}
4322 						return;
4323 					}
4324 				}
4325 			}
4326 
4327 			for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4328 			{
4329 				if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4330 				{//this muzzle doesn't match the weapon we're trying to use
4331 					continue;
4332 				}
4333 				if ( !linkedFiring
4334 					&& i != pVeh->weaponStatus[weaponNum].nextMuzzle )
4335 				{//we're only firing one muzzle and this isn't it
4336 					continue;
4337 				}
4338 
4339 				// Fire this muzzle.
4340 				if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time )
4341 				{
4342 					vec3_t	start, dir;
4343 
4344 					if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
4345 					{//out of ammo!
4346 						if ( !sentAmmoWarning )
4347 						{
4348 							sentAmmoWarning = qtrue;
4349 							if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
4350 							{// let the client know he's out of ammo
4351 								G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
4352 							}
4353 						}
4354 					}
4355 					else
4356 					{//have enough ammo to shoot
4357 						//do the firing
4358 						WP_CalcVehMuzzle(ent, i);
4359 						VectorCopy( pVeh->m_vMuzzlePos[i], start );
4360 						VectorCopy( pVeh->m_vMuzzleDir[i], dir );
4361 						if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) )
4362 						{//auto-aim at whatever crosshair would be over from camera's point of view (if closer)
4363 						}
4364 						else if ( aimCorrect )
4365 						{//auto-aim the missile at the crosshair if there's anything there
4366 							trace_t trace;
4367 							vec3_t	end;
4368 							vec3_t	ang;
4369 							vec3_t	fixedDir;
4370 
4371 							if (pVeh->m_pVehicleInfo->type == VH_SPEEDER)
4372 							{
4373 								VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f);
4374 							}
4375 							else
4376 							{
4377 								VectorCopy(pVeh->m_vOrientation, ang);
4378 							}
4379 							AngleVectors( ang, fixedDir, NULL, NULL );
4380 							VectorMA( ent->r.currentOrigin, 32768, fixedDir, end );
4381 							//VectorMA( ent->r.currentOrigin, 8192, dir, end );
4382 							trap->Trace( &trace, ent->r.currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
4383 							if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
4384 							{
4385 								vec3_t newEnd;
4386 								VectorCopy( trace.endpos, newEnd );
4387 								WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir );
4388 							}
4389 						}
4390 
4391 						//play the weapon's muzzle effect if we have one
4392 						//NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits
4393 						muzzlesFired |= (1<<i);
4394 
4395 						missile = WP_FireVehicleWeapon( ent, start, dir, vehWeapon, alt_fire, qfalse );
4396 						if ( vehWeapon->fHoming )
4397 						{//clear the rocket lock entity *after* all muzzles have fired
4398 							clearRocketLockEntity = qtrue;
4399 						}
4400 					}
4401 
4402 					if ( linkedFiring )
4403 					{//we're linking the weapon, so continue on and fire all appropriate muzzles
4404 						continue;
4405 					}
4406 					//else just firing one
4407 					//take the ammo, set the next muzzle and set the delay on it
4408 					if ( numMuzzles > 1 )
4409 					{//more than one, look for it
4410 						int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle;
4411 						while ( 1 )
4412 						{
4413 							nextMuzzle++;
4414 							if ( nextMuzzle >= MAX_VEHICLE_MUZZLES )
4415 							{
4416 								nextMuzzle = 0;
4417 							}
4418 							if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle )
4419 							{//WTF?  Wrapped without finding another valid one!
4420 								break;
4421 							}
4422 							if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex )
4423 							{//this is the next muzzle for this weapon
4424 								pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle;
4425 								break;
4426 							}
4427 						}
4428 					}//else, just stay on the one we just fired
4429 					//set the delay on the next muzzle
4430 					pVeh->m_iMuzzleWait[pVeh->weaponStatus[weaponNum].nextMuzzle] = level.time + delay;
4431 					//take away the ammo
4432 					pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
4433 					//NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
4434 					if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
4435 					{
4436 						((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
4437 					}
4438 					//done!
4439 					//we'll get in here again next frame and try the next muzzle...
4440 					//return;
4441 					goto tryFire;
4442 				}
4443 			}
4444 			//we went through all the muzzles, so apply the cumulative delay and ammo cost
4445 			if ( cumulativeAmmo )
4446 			{//taking ammo one shot at a time
4447 				//take the ammo
4448 				pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo;
4449 				//NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
4450 				if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
4451 				{
4452 					((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
4453 				}
4454 			}
4455 			if ( cumulativeDelay )
4456 			{//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles
4457 				for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
4458 				{
4459 					if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
4460 					{//this muzzle doesn't match the weapon we're trying to use
4461 						continue;
4462 					}
4463 					//apply the cumulative delay
4464 					pVeh->m_iMuzzleWait[i] = level.time + cumulativeDelay;
4465 				}
4466 			}
4467 		}
4468 	}
4469 
4470 tryFire:
4471 	if ( clearRocketLockEntity )
4472 	{//hmm, should probably clear that anytime any weapon fires?
4473 		ent->client->ps.rocketLockIndex = ENTITYNUM_NONE;
4474 		ent->client->ps.rocketLockTime = 0;
4475 		ent->client->ps.rocketTargetTime = 0;
4476 	}
4477 
4478 	if ( vehWeapon && muzzlesFired > 0 )
4479 	{
4480 		G_VehMuzzleFireFX(ent, missile, muzzlesFired );
4481 	}
4482 }
4483 
4484 /*
4485 ===============
4486 FireWeapon
4487 ===============
4488 */
4489 int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint);
4490 
FireWeapon(gentity_t * ent,qboolean altFire)4491 void FireWeapon( gentity_t *ent, qboolean altFire ) {
4492 	// track shots taken for accuracy tracking. melee weapons are not tracked.
4493 	if( ent->s.weapon != WP_SABER && ent->s.weapon != WP_STUN_BATON && ent->s.weapon != WP_MELEE )
4494 	{
4495 		if( ent->s.weapon == WP_FLECHETTE ) {
4496 			ent->client->accuracy_shots += FLECHETTE_SHOTS;
4497 		} else {
4498 			ent->client->accuracy_shots++;
4499 		}
4500 	}
4501 
4502 	if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
4503 	{
4504 		FireVehicleWeapon( ent, altFire );
4505 		return;
4506 	}
4507 	else
4508 	{
4509 		// set aiming directions
4510 		if (ent->s.weapon == WP_EMPLACED_GUN &&
4511 			ent->client->ps.emplacedIndex)
4512 		{ //if using emplaced then base muzzle point off of gun position/angles
4513 			gentity_t *emp = &g_entities[ent->client->ps.emplacedIndex];
4514 
4515 			if (emp->inuse)
4516 			{
4517 				float yaw;
4518 				vec3_t viewAngCap;
4519 				int override;
4520 
4521 				VectorCopy(ent->client->ps.viewangles, viewAngCap);
4522 				if (viewAngCap[PITCH] > 40)
4523 				{
4524 					viewAngCap[PITCH] = 40;
4525 				}
4526 
4527 				override = BG_EmplacedView(ent->client->ps.viewangles, emp->s.angles, &yaw,
4528 					emp->s.origin2[0]);
4529 
4530 				if (override)
4531 				{
4532 					viewAngCap[YAW] = yaw;
4533 				}
4534 
4535 				AngleVectors( viewAngCap, forward, vright, up );
4536 			}
4537 			else
4538 			{
4539 				AngleVectors( ent->client->ps.viewangles, forward, vright, up );
4540 			}
4541 		}
4542 		else if (ent->s.number < MAX_CLIENTS &&
4543 			ent->client->ps.m_iVehicleNum && ent->s.weapon == WP_BLASTER)
4544 		{ //riding a vehicle...with blaster selected
4545 			vec3_t vehTurnAngles;
4546 			gentity_t *vehEnt = &g_entities[ent->client->ps.m_iVehicleNum];
4547 
4548 			if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
4549 			{
4550 				VectorCopy(vehEnt->m_pVehicle->m_vOrientation, vehTurnAngles);
4551 				vehTurnAngles[PITCH] = ent->client->ps.viewangles[PITCH];
4552 			}
4553 			else
4554 			{
4555 				VectorCopy(ent->client->ps.viewangles, vehTurnAngles);
4556 			}
4557 			if (ent->client->pers.cmd.rightmove > 0)
4558 			{ //shooting to right
4559 				vehTurnAngles[YAW] -= 90.0f;
4560 			}
4561 			else if (ent->client->pers.cmd.rightmove < 0)
4562 			{ //shooting to left
4563 				vehTurnAngles[YAW] += 90.0f;
4564 			}
4565 
4566 			AngleVectors( vehTurnAngles, forward, vright, up );
4567 		}
4568 		else
4569 		{
4570 			AngleVectors( ent->client->ps.viewangles, forward, vright, up );
4571 		}
4572 
4573 		CalcMuzzlePoint ( ent, forward, vright, up, muzzle );
4574 
4575 		// fire the specific weapon
4576 		switch( ent->s.weapon ) {
4577 		case WP_STUN_BATON:
4578 			WP_FireStunBaton( ent, altFire );
4579 			break;
4580 
4581 		case WP_MELEE:
4582 			WP_FireMelee(ent, altFire);
4583 			break;
4584 
4585 		case WP_SABER:
4586 			break;
4587 
4588 		case WP_BRYAR_PISTOL:
4589 			WP_FireBryarPistol( ent, altFire );
4590 			break;
4591 
4592 		case WP_CONCUSSION:
4593 			if ( altFire )
4594 				WP_FireConcussionAlt( ent );
4595 			else
4596 				WP_FireConcussion( ent );
4597 			break;
4598 
4599 		case WP_BRYAR_OLD:
4600 			WP_FireBryarPistol( ent, altFire );
4601 			break;
4602 
4603 		case WP_BLASTER:
4604 			WP_FireBlaster( ent, altFire );
4605 			break;
4606 
4607 		case WP_DISRUPTOR:
4608 			WP_FireDisruptor( ent, altFire );
4609 			break;
4610 
4611 		case WP_BOWCASTER:
4612 			WP_FireBowcaster( ent, altFire );
4613 			break;
4614 
4615 		case WP_REPEATER:
4616 			WP_FireRepeater( ent, altFire );
4617 			break;
4618 
4619 		case WP_DEMP2:
4620 			WP_FireDEMP2( ent, altFire );
4621 			break;
4622 
4623 		case WP_FLECHETTE:
4624 			WP_FireFlechette( ent, altFire );
4625 			break;
4626 
4627 		case WP_ROCKET_LAUNCHER:
4628 			WP_FireRocket( ent, altFire );
4629 			break;
4630 
4631 		case WP_THERMAL:
4632 			WP_FireThermalDetonator( ent, altFire );
4633 			break;
4634 
4635 		case WP_TRIP_MINE:
4636 			WP_PlaceLaserTrap( ent, altFire );
4637 			break;
4638 
4639 		case WP_DET_PACK:
4640 			WP_DropDetPack( ent, altFire );
4641 			break;
4642 
4643 		case WP_EMPLACED_GUN:
4644 			if (ent->client && ent->client->ewebIndex)
4645 			{ //specially handled by the e-web itself
4646 				break;
4647 			}
4648 			WP_FireEmplaced( ent, altFire );
4649 			break;
4650 		default:
4651 //			assert(!"unknown weapon fire");
4652 			break;
4653 		}
4654 	}
4655 
4656 	G_LogWeaponFire(ent->s.number, ent->s.weapon);
4657 }
4658 
4659 //---------------------------------------------------------
WP_FireEmplaced(gentity_t * ent,qboolean altFire)4660 static void WP_FireEmplaced( gentity_t *ent, qboolean altFire )
4661 //---------------------------------------------------------
4662 {
4663 	vec3_t	dir, angs, gunpoint;
4664 	vec3_t	right;
4665 	gentity_t *gun;
4666 	int side;
4667 
4668 	if (!ent->client)
4669 	{
4670 		return;
4671 	}
4672 
4673 	if (!ent->client->ps.emplacedIndex)
4674 	{ //shouldn't be using WP_EMPLACED_GUN if we aren't on an emplaced weapon
4675 		return;
4676 	}
4677 
4678 	gun = &g_entities[ent->client->ps.emplacedIndex];
4679 
4680 	if (!gun->inuse || gun->health <= 0)
4681 	{ //gun was removed or killed, although we should never hit this check because we should have been forced off it already
4682 		return;
4683 	}
4684 
4685 	VectorCopy(gun->s.origin, gunpoint);
4686 	gunpoint[2] += 46;
4687 
4688 	AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
4689 
4690 	if (gun->genericValue10)
4691 	{ //fire out of the right cannon side
4692 		VectorMA(gunpoint, 10.0f, right, gunpoint);
4693 		side = 0;
4694 	}
4695 	else
4696 	{ //the left
4697 		VectorMA(gunpoint, -10.0f, right, gunpoint);
4698 		side = 1;
4699 	}
4700 
4701 	gun->genericValue10 = side;
4702 	G_AddEvent(gun, EV_FIRE_WEAPON, side);
4703 
4704 	vectoangles( forward, angs );
4705 
4706 	AngleVectors( angs, dir, NULL, NULL );
4707 
4708 	WP_FireEmplacedMissile( gun, gunpoint, dir, altFire, ent );
4709 }
4710 
4711 #define EMPLACED_CANRESPAWN 1
4712 
4713 //----------------------------------------------------------
4714 
4715 /*QUAKED emplaced_gun (0 0 1) (-30 -20 8) (30 20 60) CANRESPAWN
4716 
4717  count - if CANRESPAWN spawnflag, decides how long it is before gun respawns (in ms)
4718  constraint - number of degrees gun is constrained from base angles on each side (default 60.0)
4719 
4720  showhealth - set to 1 to show health bar on this entity when crosshair is over it
4721 
4722   teamowner - crosshair shows green for this team, red for opposite team
4723 	0 - none
4724 	1 - red
4725 	2 - blue
4726 
4727   alliedTeam - team that can use this
4728 	0 - any
4729 	1 - red
4730 	2 - blue
4731 
4732   teamnodmg - team that turret does not take damage from or do damage to
4733 	0 - none
4734 	1 - red
4735 	2 - blue
4736 */
4737 
4738 //----------------------------------------------------------
4739 extern qboolean TryHeal(gentity_t *ent, gentity_t *target); //g_utils.c
emplaced_gun_use(gentity_t * self,gentity_t * other,trace_t * trace)4740 void emplaced_gun_use( gentity_t *self, gentity_t *other, trace_t *trace )
4741 {
4742 	vec3_t fwd1, fwd2;
4743 	float dot;
4744 	int oldWeapon;
4745 	gentity_t *activator = other;
4746 	float zoffset = 50;
4747 	vec3_t anglesToOwner;
4748 	vec3_t vLen;
4749 	float ownLen;
4750 
4751 	if ( self->health <= 0 )
4752 	{ //gun is destroyed
4753 		return;
4754 	}
4755 
4756 	if (self->activator)
4757 	{ //someone is already using me
4758 		return;
4759 	}
4760 
4761 	if (!activator->client)
4762 	{
4763 		return;
4764 	}
4765 
4766 	if (activator->client->ps.emplacedTime > level.time)
4767 	{ //last use attempt still too recent
4768 		return;
4769 	}
4770 
4771 	if (activator->client->ps.forceHandExtend != HANDEXTEND_NONE)
4772 	{ //don't use if busy doing something else
4773 		return;
4774 	}
4775 
4776 	if (activator->client->ps.origin[2] > self->s.origin[2]+zoffset-8)
4777 	{ //can't use it from the top
4778 		return;
4779 	}
4780 
4781 	if (activator->client->ps.pm_flags & PMF_DUCKED)
4782 	{ //must be standing
4783 		return;
4784 	}
4785 
4786 	if (activator->client->ps.isJediMaster)
4787 	{ //jm can't use weapons
4788 		return;
4789 	}
4790 
4791 	VectorSubtract(self->s.origin, activator->client->ps.origin, vLen);
4792 	ownLen = VectorLength(vLen);
4793 
4794 	if (ownLen > 64.0f)
4795 	{ //must be within 64 units of the gun to use at all
4796 		return;
4797 	}
4798 
4799 	// Let's get some direction vectors for the user
4800 	AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL );
4801 
4802 	// Get the guns direction vector
4803 	AngleVectors( self->pos1, fwd2, NULL, NULL );
4804 
4805 	dot = DotProduct( fwd1, fwd2 );
4806 
4807 	// Must be reasonably facing the way the gun points ( 110 degrees or so ), otherwise we don't allow to use it.
4808 	if ( dot < -0.2f )
4809 	{
4810 		goto tryHeal;
4811 	}
4812 
4813 	VectorSubtract(self->s.origin, activator->client->ps.origin, fwd1);
4814 	VectorNormalize(fwd1);
4815 
4816 	dot = DotProduct( fwd1, fwd2 );
4817 
4818 	//check the positioning in relation to the gun as well
4819 	if ( dot < 0.6f )
4820 	{
4821 		goto tryHeal;
4822 	}
4823 
4824 	self->genericValue1 = 1;
4825 
4826 	oldWeapon = activator->s.weapon;
4827 
4828 	// swap the users weapon with the emplaced gun
4829 	activator->client->ps.weapon = self->s.weapon;
4830 	activator->client->ps.weaponstate = WEAPON_READY;
4831 	activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN );
4832 
4833 	activator->client->ps.emplacedIndex = self->s.number;
4834 
4835 	self->s.emplacedOwner = activator->s.number;
4836 	self->s.activeForcePass = NUM_FORCE_POWERS+1;
4837 
4838 	// the gun will track which weapon we used to have
4839 	self->s.weapon = oldWeapon;
4840 
4841 	//user's new owner becomes the gun ent
4842 	activator->r.ownerNum = self->s.number;
4843 	self->activator = activator;
4844 
4845 	VectorSubtract(self->r.currentOrigin, activator->client->ps.origin, anglesToOwner);
4846 	vectoangles(anglesToOwner, anglesToOwner);
4847 	return;
4848 
4849 tryHeal: //well, not in the right dir, try healing it instead...
4850 	TryHeal(activator, self);
4851 }
4852 
emplaced_gun_realuse(gentity_t * self,gentity_t * other,gentity_t * activator)4853 void emplaced_gun_realuse( gentity_t *self, gentity_t *other, gentity_t *activator )
4854 {
4855 	emplaced_gun_use(self, other, NULL);
4856 }
4857 
4858 //----------------------------------------------------------
emplaced_gun_pain(gentity_t * self,gentity_t * attacker,int damage)4859 void emplaced_gun_pain( gentity_t *self, gentity_t *attacker, int damage )
4860 {
4861 	self->s.health = self->health;
4862 
4863 	if ( self->health <= 0 )
4864 	{
4865 		//death effect.. for now taken care of on cgame
4866 	}
4867 	else
4868 	{
4869 		//if we have a pain behavior set then use it I guess
4870 		G_ActivateBehavior( self, BSET_PAIN );
4871 	}
4872 }
4873 
4874 #define EMPLACED_GUN_HEALTH 800
4875 
4876 //----------------------------------------------------------
emplaced_gun_update(gentity_t * self)4877 void emplaced_gun_update(gentity_t *self)
4878 {
4879 	vec3_t	smokeOrg, puffAngle;
4880 	int oldWeap;
4881 	float ownLen = 0;
4882 
4883 	if (self->health < 1 && !self->genericValue5)
4884 	{ //we are dead, set our respawn delay if we have one
4885 		if (self->spawnflags & EMPLACED_CANRESPAWN)
4886 		{
4887 			self->genericValue5 = level.time + 4000 + self->count;
4888 		}
4889 	}
4890 	else if (self->health < 1 && self->genericValue5 < level.time)
4891 	{ //we are dead, see if it's time to respawn
4892 		self->s.time = 0;
4893 		self->genericValue4 = 0;
4894 		self->genericValue3 = 0;
4895 		self->health = EMPLACED_GUN_HEALTH*0.4;
4896 		self->s.health = self->health;
4897 	}
4898 
4899 	if (self->genericValue4 && self->genericValue4 < 2 && self->s.time < level.time)
4900 	{ //we have finished our warning (red flashing) effect, it's time to finish dying
4901 		vec3_t explOrg;
4902 
4903 		VectorSet( puffAngle, 0, 0, 1 );
4904 
4905 		VectorCopy(self->r.currentOrigin, explOrg);
4906 		explOrg[2] += 16;
4907 
4908 		//just use the detpack explosion effect
4909 		G_PlayEffect(EFFECT_EXPLOSION_DETPACK, explOrg, puffAngle);
4910 
4911 		self->genericValue3 = level.time + Q_irand(2500, 3500);
4912 
4913 		G_RadiusDamage(self->r.currentOrigin, self, self->splashDamage, self->splashRadius, self, NULL, MOD_UNKNOWN);
4914 
4915 		self->s.time = -1;
4916 
4917 		self->genericValue4 = 2;
4918 	}
4919 
4920 	if (self->genericValue3 > level.time)
4921 	{ //see if we are freshly dead and should be smoking
4922 		if (self->genericValue2 < level.time)
4923 		{ //is it time yet to spawn another smoke puff?
4924 			VectorSet( puffAngle, 0, 0, 1 );
4925 			VectorCopy(self->r.currentOrigin, smokeOrg);
4926 
4927 			smokeOrg[2] += 60;
4928 
4929 			G_PlayEffect(EFFECT_SMOKE, smokeOrg, puffAngle);
4930 			self->genericValue2 = level.time + Q_irand(250, 400);
4931 		}
4932 	}
4933 
4934 	if (self->activator && self->activator->client && self->activator->inuse)
4935 	{ //handle updating current user
4936 		vec3_t vLen;
4937 		VectorSubtract(self->s.origin, self->activator->client->ps.origin, vLen);
4938 		ownLen = VectorLength(vLen);
4939 
4940 		if (!(self->activator->client->pers.cmd.buttons & BUTTON_USE) && self->genericValue1)
4941 		{
4942 			self->genericValue1 = 0;
4943 		}
4944 
4945 		if ((self->activator->client->pers.cmd.buttons & BUTTON_USE) && !self->genericValue1)
4946 		{
4947 			self->activator->client->ps.emplacedIndex = 0;
4948 			self->activator->client->ps.saberHolstered = 0;
4949 			self->nextthink = level.time + 50;
4950 			return;
4951 		}
4952 	}
4953 
4954 	if ((self->activator && self->activator->client) &&
4955 		(!self->activator->inuse || self->activator->client->ps.emplacedIndex != self->s.number || self->genericValue4 || ownLen > 64))
4956 	{ //get the user off of me then
4957 		self->activator->client->ps.stats[STAT_WEAPONS] &= ~(1<<WP_EMPLACED_GUN);
4958 
4959 		oldWeap = self->activator->client->ps.weapon;
4960 		self->activator->client->ps.weapon = self->s.weapon;
4961 		self->s.weapon = oldWeap;
4962 		self->activator->r.ownerNum = ENTITYNUM_NONE;
4963 		self->activator->client->ps.emplacedTime = level.time + 1000;
4964 		self->activator->client->ps.emplacedIndex = 0;
4965 		self->activator->client->ps.saberHolstered = 0;
4966 		self->activator = NULL;
4967 
4968 		self->s.activeForcePass = 0;
4969 	}
4970 	else if (self->activator && self->activator->client)
4971 	{ //make sure the user is using the emplaced gun weapon
4972 		self->activator->client->ps.weapon = WP_EMPLACED_GUN;
4973 		self->activator->client->ps.weaponstate = WEAPON_READY;
4974 	}
4975 	self->nextthink = level.time + 50;
4976 }
4977 
4978 //----------------------------------------------------------
emplaced_gun_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)4979 void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
4980 { //set us up to flash and then explode
4981 	if (self->genericValue4)
4982 	{
4983 		return;
4984 	}
4985 
4986 	self->genericValue4 = 1;
4987 
4988 	self->s.time = level.time + 3000;
4989 
4990 	self->genericValue5 = 0;
4991 }
4992 
SP_emplaced_gun(gentity_t * ent)4993 void SP_emplaced_gun( gentity_t *ent )
4994 {
4995 	const char *name = "models/map_objects/mp/turret_chair.glm";
4996 	vec3_t down;
4997 	trace_t tr;
4998 
4999 	//make sure our assets are precached
5000 	RegisterItem( BG_FindItemForWeapon(WP_EMPLACED_GUN) );
5001 
5002 	ent->r.contents = CONTENTS_SOLID;
5003 	ent->s.solid = SOLID_BBOX;
5004 
5005 	ent->genericValue5 = 0;
5006 
5007 	VectorSet( ent->r.mins, -30, -20, 8 );
5008 	VectorSet( ent->r.maxs, 30, 20, 60 );
5009 
5010 	VectorCopy(ent->s.origin, down);
5011 
5012 	down[2] -= 1024;
5013 
5014 	trap->Trace(&tr, ent->s.origin, ent->r.mins, ent->r.maxs, down, ent->s.number, MASK_SOLID, qfalse, 0, 0);
5015 
5016 	if (tr.fraction != 1 && !tr.allsolid && !tr.startsolid)
5017 	{
5018 		VectorCopy(tr.endpos, ent->s.origin);
5019 	}
5020 
5021 	ent->spawnflags |= 4; // deadsolid
5022 
5023 	ent->health = EMPLACED_GUN_HEALTH;
5024 
5025 	if (ent->spawnflags & EMPLACED_CANRESPAWN)
5026 	{ //make it somewhat easier to kill if it can respawn
5027 		ent->health *= 0.4;
5028 	}
5029 
5030 	ent->maxHealth = ent->health;
5031 	G_ScaleNetHealth(ent);
5032 
5033 	ent->genericValue4 = 0;
5034 
5035 	ent->takedamage = qtrue;
5036 	ent->pain = emplaced_gun_pain;
5037 	ent->die = emplaced_gun_die;
5038 
5039 	// being caught in this thing when it blows would be really bad.
5040 	ent->splashDamage = 80;
5041 	ent->splashRadius = 128;
5042 
5043 	// amount of ammo that this little poochie has
5044 	G_SpawnInt( "count", "600", &ent->count );
5045 
5046 	G_SpawnFloat( "constraint", "60", &ent->s.origin2[0] );
5047 
5048 	ent->s.modelindex = G_ModelIndex( (char *)name );
5049 	ent->s.modelGhoul2 = 1;
5050 	ent->s.g2radius = 110;
5051 
5052 	//so the cgame knows for sure that we're an emplaced weapon
5053 	ent->s.weapon = WP_EMPLACED_GUN;
5054 
5055 	G_SetOrigin( ent, ent->s.origin );
5056 
5057 	// store base angles for later
5058 	VectorCopy( ent->s.angles, ent->pos1 );
5059 	VectorCopy( ent->s.angles, ent->r.currentAngles );
5060 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
5061 
5062 	ent->think = emplaced_gun_update;
5063 	ent->nextthink = level.time + 50;
5064 
5065 	ent->use = emplaced_gun_realuse;
5066 
5067 	ent->r.svFlags |= SVF_PLAYER_USABLE;
5068 
5069 	ent->s.pos.trType = TR_STATIONARY;
5070 
5071 	ent->s.owner = MAX_CLIENTS+1;
5072 	ent->s.shouldtarget = qtrue;
5073 	//ent->s.teamowner = 0;
5074 
5075 	trap->LinkEntity((sharedEntity_t *)ent);
5076 }
5077