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 "g_functions.h"
29 #include "anims.h"
30 #include "b_local.h"
31 #include "wp_saber.h"
32 #include "g_vehicles.h"
33 #include "w_local.h"
34 #include "../cgame/cg_local.h"
35 
36 vec3_t	forwardVec, vrightVec, up;
37 vec3_t	muzzle;
38 
39 gentity_t *ent_list[MAX_GENTITIES];
40 extern cvar_t	*g_debugMelee;
41 
42 // some naughty little things that are used cg side
43 int g_rocketLockEntNum = ENTITYNUM_NONE;
44 int g_rocketLockTime = 0;
45 int	g_rocketSlackTime = 0;
46 
47 // Weapon Helper Functions
48 float weaponSpeed[WP_NUM_WEAPONS][2] =
49 {
50 	{ 0,0 },//WP_NONE,
51 	{ 0,0 },//WP_SABER,				 // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste.
52 	{ BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL },//WP_BLASTER_PISTOL,
53 	{ BLASTER_VELOCITY,BLASTER_VELOCITY },//WP_BLASTER,
54 	{ Q3_INFINITE,Q3_INFINITE },//WP_DISRUPTOR,
55 	{ BOWCASTER_VELOCITY,BOWCASTER_VELOCITY },//WP_BOWCASTER,
56 	{ REPEATER_VELOCITY,REPEATER_ALT_VELOCITY },//WP_REPEATER,
57 	{ DEMP2_VELOCITY,DEMP2_ALT_RANGE },//WP_DEMP2,
58 	{ FLECHETTE_VEL,FLECHETTE_MINE_VEL },//WP_FLECHETTE,
59 	{ ROCKET_VELOCITY,ROCKET_ALT_VELOCITY },//WP_ROCKET_LAUNCHER,
60 	{ TD_VELOCITY,TD_ALT_VELOCITY },//WP_THERMAL,
61 	{ 0,0 },//WP_TRIP_MINE,
62 	{ 0,0 },//WP_DET_PACK,
63 	{ CONC_VELOCITY,Q3_INFINITE },//WP_CONCUSSION,
64 	{ 0,0 },//WP_MELEE,			// Any ol' melee attack
65 	{ 0,0 },//WP_STUN_BATON,
66 	{ BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL },//WP_BRYAR_PISTOL,
67 	{ EMPLACED_VEL,EMPLACED_VEL },//WP_EMPLACED_GUN,
68 	{ BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL },//WP_BOT_LASER,		// Probe droid	- Laser blast
69 	{ 0,0 },//WP_TURRET,			// turret guns
70 	{ ATST_MAIN_VEL,ATST_MAIN_VEL },//WP_ATST_MAIN,
71 	{ ATST_SIDE_MAIN_VELOCITY,ATST_SIDE_ALT_NPC_VELOCITY },//WP_ATST_SIDE,
72 	{ EMPLACED_VEL,EMPLACED_VEL },//WP_TIE_FIGHTER,
73 	{ EMPLACED_VEL,REPEATER_ALT_VELOCITY },//WP_RAPID_FIRE_CONC,
74 	{ 0,0 },//WP_JAWA,
75 	{ TUSKEN_RIFLE_VEL,TUSKEN_RIFLE_VEL },//WP_TUSKEN_RIFLE,
76 	{ 0,0 },//WP_TUSKEN_STAFF,
77 	{ 0,0 },//WP_SCEPTER,
78 	{ 0,0 },//WP_NOGHRI_STICK,
79 };
80 
WP_SpeedOfMissileForWeapon(int wp,qboolean alt_fire)81 float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire )
82 {
83 	if ( alt_fire )
84 	{
85 		return weaponSpeed[wp][1];
86 	}
87 	return weaponSpeed[wp][0];
88 }
89 
90 //-----------------------------------------------------------------------------
WP_TraceSetStart(const gentity_t * ent,vec3_t start,const vec3_t mins,const vec3_t maxs)91 void WP_TraceSetStart( const gentity_t *ent, vec3_t start, const vec3_t mins, const vec3_t maxs )
92 //-----------------------------------------------------------------------------
93 {
94 	//make sure our start point isn't on the other side of a wall
95 	trace_t	tr;
96 	vec3_t	entMins, newstart;
97 	vec3_t	entMaxs;
98 
99 	VectorSet( entMaxs, 5, 5, 5 );
100 	VectorScale( entMaxs, -1, entMins );
101 
102 	if ( !ent->client )
103 	{
104 		return;
105 	}
106 
107 	VectorCopy( ent->currentOrigin, newstart );
108 	newstart[2] = start[2]; // force newstart to be on the same plane as the muzzle ( start )
109 
110 	gi.trace( &tr, newstart, entMins, entMaxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, (EG2_Collision)0, 0 );
111 
112 	if ( tr.startsolid || tr.allsolid )
113 	{
114 		// there is a problem here..
115 		return;
116 	}
117 
118 	if ( tr.fraction < 1.0f )
119 	{
120 		VectorCopy( tr.endpos, start );
121 	}
122 }
123 
124 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
125 //-----------------------------------------------------------------------------
CreateMissile(vec3_t org,vec3_t dir,float vel,int life,gentity_t * owner,qboolean altFire)126 gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire )
127 //-----------------------------------------------------------------------------
128 {
129 	gentity_t	*missile;
130 
131 	missile = G_Spawn();
132 
133 	missile->nextthink = level.time + life;
134 	missile->e_ThinkFunc = thinkF_G_FreeEntity;
135 	missile->s.eType = ET_MISSILE;
136 	missile->owner = owner;
137 
138 	Vehicle_t*	pVeh = G_IsRidingVehicle(owner);
139 
140 	missile->alt_fire = altFire;
141 
142 	missile->s.pos.trType = TR_LINEAR;
143 	missile->s.pos.trTime = level.time;// - 10;	// move a bit on the very first frame
144 	VectorCopy( org, missile->s.pos.trBase );
145 	VectorScale( dir, vel, missile->s.pos.trDelta );
146  	if (pVeh)
147 	{
148  		missile->s.eFlags |= EF_USE_ANGLEDELTA;
149 	 	vectoangles(missile->s.pos.trDelta, missile->s.angles);
150 	  	VectorMA(missile->s.pos.trDelta, 2.0f, pVeh->m_pParentEntity->client->ps.velocity, missile->s.pos.trDelta);
151 	}
152 
153 	VectorCopy( org, missile->currentOrigin);
154 	gi.linkentity( missile );
155 
156 	return missile;
157 }
158 
159 
160 //-----------------------------------------------------------------------------
WP_Stick(gentity_t * missile,trace_t * trace,float fudge_distance)161 void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance )
162 //-----------------------------------------------------------------------------
163 {
164 	vec3_t org, ang;
165 
166 	// not moving or rotating
167 	missile->s.pos.trType = TR_STATIONARY;
168 	VectorClear( missile->s.pos.trDelta );
169 	VectorClear( missile->s.apos.trDelta );
170 
171 	// so we don't stick into the wall
172 	VectorMA( trace->endpos, fudge_distance, trace->plane.normal, org );
173 	G_SetOrigin( missile, org );
174 
175 	vectoangles( trace->plane.normal, ang );
176 	G_SetAngles( missile, ang );
177 
178 	// I guess explode death wants me as the normal?
179 //	VectorCopy( trace->plane.normal, missile->pos1 );
180 	gi.linkentity( missile );
181 }
182 
183 // This version shares is in the thinkFunc format
184 //-----------------------------------------------------------------------------
WP_Explode(gentity_t * self)185 void WP_Explode( gentity_t *self )
186 //-----------------------------------------------------------------------------
187 {
188 	gentity_t	*attacker = self;
189 	vec3_t		forwardVec={0,0,1};
190 
191 	// stop chain reaction runaway loops
192 	self->takedamage = qfalse;
193 
194 	self->s.loopSound = 0;
195 
196 //	VectorCopy( self->currentOrigin, self->s.pos.trBase );
197 	if ( !self->client )
198 	{
199 	AngleVectors( self->s.angles, forwardVec, NULL, NULL );
200 	}
201 
202 	if ( self->fxID > 0 )
203 	{
204 		G_PlayEffect( self->fxID, self->currentOrigin, forwardVec );
205 	}
206 
207 	if ( self->owner )
208 	{
209 		attacker = self->owner;
210 	}
211 	else if ( self->activator )
212 	{
213 		attacker = self->activator;
214 	}
215 
216 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
217 	{
218 		G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, 0/*don't ignore attacker*/, MOD_EXPLOSIVE_SPLASH );
219 	}
220 
221 	if ( self->target )
222 	{
223 		G_UseTargets( self, attacker );
224 	}
225 
226 	G_SetOrigin( self, self->currentOrigin );
227 
228 	self->nextthink = level.time + 50;
229 	self->e_ThinkFunc = thinkF_G_FreeEntity;
230 }
231 
232 // We need to have a dieFunc, otherwise G_Damage won't actually make us die.  I could modify G_Damage, but that entails too many changes
233 //-----------------------------------------------------------------------------
WP_ExplosiveDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)234 void WP_ExplosiveDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
235 //-----------------------------------------------------------------------------
236 {
237 	self->enemy = attacker;
238 
239 	if ( attacker && !attacker->s.number )
240 	{
241 		// less damage when shot by player
242 		self->splashDamage /= 3;
243 		self->splashRadius /= 3;
244 	}
245 
246 	self->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead
247 
248 	WP_Explode( self );
249 }
250 
WP_MissileTargetHint(gentity_t * shooter,vec3_t start,vec3_t out)251 bool WP_MissileTargetHint(gentity_t* shooter, vec3_t start, vec3_t out)
252 {
253 	return false;
254 }
255 
G_GetHitLocFromTrace(trace_t * trace,int mod)256 int G_GetHitLocFromTrace( trace_t *trace, int mod )
257 {
258 	int hitLoc = HL_NONE;
259 	for (int i=0; i < MAX_G2_COLLISIONS; i++)
260 	{
261 		if ( trace->G2CollisionMap[i].mEntityNum == -1 )
262 		{
263 			break;
264 		}
265 
266 		CCollisionRecord &coll = trace->G2CollisionMap[i];
267 		if ( (coll.mFlags & G2_FRONTFACE) )
268 		{
269 			G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &hitLoc, coll.mCollisionPosition, NULL, NULL, mod );
270 			//we only want the first "entrance wound", so break
271 			break;
272 		}
273 	}
274 	return hitLoc;
275 }
276 
277 //---------------------------------------------------------
AddLeanOfs(const gentity_t * const ent,vec3_t point)278 void AddLeanOfs(const gentity_t *const ent, vec3_t point)
279 //---------------------------------------------------------
280 {
281 	if(ent->client)
282 	{
283 		if(ent->client->ps.leanofs)
284 		{
285 			vec3_t	right;
286 			//add leaning offset
287 			AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
288 			VectorMA(point, (float)ent->client->ps.leanofs, right, point);
289 		}
290 	}
291 }
292 
293 //---------------------------------------------------------
SubtractLeanOfs(const gentity_t * const ent,vec3_t point)294 void SubtractLeanOfs(const gentity_t *const ent, vec3_t point)
295 //---------------------------------------------------------
296 {
297 	if(ent->client)
298 	{
299 		if(ent->client->ps.leanofs)
300 		{
301 			vec3_t	right;
302 			//add leaning offset
303 			AngleVectors( ent->client->ps.viewangles, NULL, right, NULL );
304 			VectorMA( point, ent->client->ps.leanofs*-1, right, point );
305 		}
306 	}
307 }
308 
309 //---------------------------------------------------------
ViewHeightFix(const gentity_t * const ent)310 void ViewHeightFix(const gentity_t *const ent)
311 //---------------------------------------------------------
312 {
313 	//FIXME: this is hacky and doesn't need to be here.  Was only put here to make up
314 	//for the times a crouch anim would be used but not actually crouching.
315 	//When we start calcing eyepos (SPOT_HEAD) from the tag_eyes, we won't need
316 	//this (or viewheight at all?)
317 	if ( !ent )
318 		return;
319 
320 	if ( !ent->client || !ent->NPC )
321 		return;
322 
323 	if ( ent->client->ps.stats[STAT_HEALTH] <= 0 )
324 		return;//dead
325 
326 	if ( ent->client->ps.legsAnim == BOTH_CROUCH1IDLE || ent->client->ps.legsAnim == BOTH_CROUCH1 || ent->client->ps.legsAnim == BOTH_CROUCH1WALK )
327 	{
328 		if ( ent->client->ps.viewheight!=ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET )
329 			ent->client->ps.viewheight = ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET;
330 	}
331 	else
332 	{
333 		if ( ent->client->ps.viewheight!=ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET )
334 			ent->client->ps.viewheight = ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET;
335 	}
336 }
337 
W_AccuracyLoggableWeapon(int weapon,qboolean alt_fire,int mod)338 qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod )
339 {
340 	if ( mod != MOD_UNKNOWN )
341 	{
342 		switch( mod )
343 		{
344 		//standard weapons
345 		case MOD_BRYAR:
346 		case MOD_BRYAR_ALT:
347 		case MOD_BLASTER:
348 		case MOD_BLASTER_ALT:
349 		case MOD_DISRUPTOR:
350 		case MOD_SNIPER:
351 		case MOD_BOWCASTER:
352 		case MOD_BOWCASTER_ALT:
353 		case MOD_ROCKET:
354 		case MOD_ROCKET_ALT:
355 		case MOD_CONC:
356 		case MOD_CONC_ALT:
357 			return qtrue;
358 			break;
359 		//non-alt standard
360 		case MOD_REPEATER:
361 		case MOD_DEMP2:
362 		case MOD_FLECHETTE:
363 			return qtrue;
364 			break;
365 		//emplaced gun
366 		case MOD_EMPLACED:
367 			return qtrue;
368 			break;
369 		//atst
370 		case MOD_ENERGY:
371 		case MOD_EXPLOSIVE:
372 			if ( weapon == WP_ATST_MAIN || weapon == WP_ATST_SIDE )
373 			{
374 				return qtrue;
375 			}
376 			break;
377 		}
378 	}
379 	else if ( weapon != WP_NONE )
380 	{
381 		switch( weapon )
382 		{
383 		case WP_BRYAR_PISTOL:
384 		case WP_BLASTER_PISTOL:
385 		case WP_BLASTER:
386 		case WP_DISRUPTOR:
387 		case WP_BOWCASTER:
388 		case WP_ROCKET_LAUNCHER:
389 		case WP_CONCUSSION:
390 			return qtrue;
391 			break;
392 		//non-alt standard
393 		case WP_REPEATER:
394 		case WP_DEMP2:
395 		case WP_FLECHETTE:
396 			if ( !alt_fire )
397 			{
398 				return qtrue;
399 			}
400 			break;
401 		//emplaced gun
402 		case WP_EMPLACED_GUN:
403 			return qtrue;
404 			break;
405 		//atst
406 		case WP_ATST_MAIN:
407 		case WP_ATST_SIDE:
408 			return qtrue;
409 			break;
410 		}
411 	}
412 	return qfalse;
413 }
414 
415 /*
416 ===============
417 LogAccuracyHit
418 ===============
419 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)420 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
421 	if( !target->takedamage ) {
422 		return qfalse;
423 	}
424 
425 	if ( target == attacker ) {
426 		return qfalse;
427 	}
428 
429 	if( !target->client ) {
430 		return qfalse;
431 	}
432 
433 	if( !attacker->client ) {
434 		return qfalse;
435 	}
436 
437 	if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
438 		return qfalse;
439 	}
440 
441 	if ( OnSameTeam( target, attacker ) ) {
442 		return qfalse;
443 	}
444 
445 	return qtrue;
446 }
447 
448 //---------------------------------------------------------
CalcMuzzlePoint(gentity_t * const ent,vec3_t forwardVec,vec3_t right,vec3_t up,vec3_t muzzlePoint,float lead_in)449 void CalcMuzzlePoint( gentity_t *const ent, vec3_t forwardVec, vec3_t right, vec3_t up, vec3_t muzzlePoint, float lead_in )
450 //---------------------------------------------------------
451 {
452 	vec3_t		org;
453 	mdxaBone_t	boltMatrix;
454 
455 	if( !lead_in ) //&& ent->s.number != 0
456 	{//Not players or melee
457 		if( ent->client )
458 		{
459 			if ( ent->client->renderInfo.mPCalcTime >= level.time - FRAMETIME*2 )
460 			{//Our muzz point was calced no more than 2 frames ago
461 				VectorCopy(ent->client->renderInfo.muzzlePoint, muzzlePoint);
462 				return;
463 			}
464 		}
465 	}
466 
467 	VectorCopy( ent->currentOrigin, muzzlePoint );
468 
469 	switch( ent->s.weapon )
470 	{
471 	case WP_BRYAR_PISTOL:
472 	case WP_BLASTER_PISTOL:
473 		ViewHeightFix(ent);
474 		muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
475 		muzzlePoint[2] -= 16;
476 		VectorMA( muzzlePoint, 28, forwardVec, muzzlePoint );
477 		VectorMA( muzzlePoint, 6, vrightVec, muzzlePoint );
478 		break;
479 
480 	case WP_ROCKET_LAUNCHER:
481 	case WP_CONCUSSION:
482 	case WP_THERMAL:
483 		ViewHeightFix(ent);
484 		muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
485 		muzzlePoint[2] -= 2;
486 		break;
487 
488 	case WP_BLASTER:
489 		ViewHeightFix(ent);
490 		muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
491 		muzzlePoint[2] -= 1;
492 		if ( ent->s.number == 0 )
493 			VectorMA( muzzlePoint, 12, forwardVec, muzzlePoint ); // player, don't set this any lower otherwise the projectile will impact immediately when your back is to a wall
494 		else
495 			VectorMA( muzzlePoint, 2, forwardVec, muzzlePoint ); // NPC, don't set too far forwardVec otherwise the projectile can go through doors
496 
497 		VectorMA( muzzlePoint, 1, vrightVec, muzzlePoint );
498 		break;
499 
500 	case WP_SABER:
501 		if(ent->NPC!=NULL &&
502 			(ent->client->ps.torsoAnim == TORSO_WEAPONREADY2 ||
503 			ent->client->ps.torsoAnim == BOTH_ATTACK2))//Sniper pose
504 		{
505 			ViewHeightFix(ent);
506 			muzzle[2] += ent->client->ps.viewheight;//By eyes
507 		}
508 		else
509 		{
510 			muzzlePoint[2] += 16;
511 		}
512 		VectorMA( muzzlePoint, 8, forwardVec, muzzlePoint );
513 		VectorMA( muzzlePoint, 16, vrightVec, muzzlePoint );
514 		break;
515 
516 	case WP_BOT_LASER:
517 		muzzlePoint[2] -= 16;	//
518 		break;
519 	case WP_ATST_MAIN:
520 
521 		if (ent->count > 0)
522 		{
523 			ent->count = 0;
524 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
525 						ent->handLBolt,
526 						&boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
527 						NULL, ent->s.modelScale );
528 		}
529 		else
530 		{
531 			ent->count = 1;
532 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
533 						ent->handRBolt,
534 						&boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
535 						NULL, ent->s.modelScale );
536 		}
537 
538 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
539 
540 		VectorCopy(org,muzzlePoint);
541 
542 		break;
543 	}
544 
545 	AddLeanOfs(ent, muzzlePoint);
546 }
547 
548 // Muzzle point table...
549 vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS] =
550 {//	Fwd,	right,	up.
551 	{0,		0,		0	},	// WP_NONE,
552 	{8	,	16,		0	},	// WP_SABER,
553 	{12,	6,		-6	},	// WP_BLASTER_PISTOL,
554 	{12,	6,		-6	},	// WP_BLASTER,
555 	{12,	6,		-6	},	// WP_DISRUPTOR,
556 	{12,	2,		-6	},	// WP_BOWCASTER,
557 	{12,	4.5,	-6	},	// WP_REPEATER,
558 	{12,	6,		-6	},	// WP_DEMP2,
559 	{12,	6,		-6	},	// WP_FLECHETTE,
560 	{12,	8,		-4	},	// WP_ROCKET_LAUNCHER,
561 	{12,	0,		-4	},	// WP_THERMAL,
562 	{12,	0,		-10	},	// WP_TRIP_MINE,
563 	{12,	0,		-4	},	// WP_DET_PACK,
564 	{12,	8,		-4	},	// WP_CONCUSSION,
565 	{0	,	8,		0	},	// WP_MELEE,
566 	{0,		0,		0	},	// WP_ATST_MAIN,
567 	{0,		0,		0	},	// WP_ATST_SIDE,
568 	{0	,	8,		0	},	// WP_STUN_BATON,
569 	{12,	6,		-6	},	// WP_BRYAR_PISTOL,
570 };
571 
WP_RocketLock(gentity_t * ent,float lockDist)572 void WP_RocketLock( gentity_t *ent, float lockDist )
573 {
574 	// Not really a charge weapon, but we still want to delay fire until the button comes up so that we can
575 	//	implement our alt-fire locking stuff
576 	vec3_t		ang;
577 	trace_t		tr;
578 
579 	vec3_t muzzleOffPoint, muzzlePoint, forwardVec, right, up;
580 
581 	AngleVectors( ent->client->ps.viewangles, forwardVec, right, up );
582 
583 	AngleVectors(ent->client->ps.viewangles, ang, NULL, NULL);
584 
585 	VectorCopy( ent->client->ps.origin, muzzlePoint );
586 	VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint);
587 
588 	VectorMA(muzzlePoint, muzzleOffPoint[0], forwardVec, muzzlePoint);
589 	VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint);
590 	muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2];
591 
592 	ang[0] = muzzlePoint[0] + ang[0]*lockDist;
593 	ang[1] = muzzlePoint[1] + ang[1]*lockDist;
594 	ang[2] = muzzlePoint[2] + ang[2]*lockDist;
595 
596 	gi.trace(&tr, muzzlePoint, NULL, NULL, ang, ent->client->ps.clientNum, MASK_PLAYERSOLID, (EG2_Collision)0, 0);
597 
598 	if (tr.fraction != 1 && tr.entityNum < ENTITYNUM_NONE && tr.entityNum != ent->client->ps.clientNum)
599 	{
600 		gentity_t *bgEnt = &g_entities[tr.entityNum];
601 		if ( bgEnt && (bgEnt->s.powerups&PW_CLOAKED) )
602 		{
603 			ent->client->rocketLockIndex = ENTITYNUM_NONE;
604 			ent->client->rocketLockTime = 0;
605 		}
606 		else if (bgEnt && bgEnt->s.eType == ET_PLAYER )
607 		{
608 			if (ent->client->rocketLockIndex == ENTITYNUM_NONE)
609 			{
610 				ent->client->rocketLockIndex = tr.entityNum;
611 				ent->client->rocketLockTime = level.time;
612 			}
613 			else if (ent->client->rocketLockIndex != tr.entityNum && ent->client->rocketTargetTime < level.time)
614 			{
615 				ent->client->rocketLockIndex = tr.entityNum;
616 				ent->client->rocketLockTime = level.time;
617 			}
618 			else if (ent->client->rocketLockIndex == tr.entityNum)
619 			{
620 				if (ent->client->rocketLockTime == -1)
621 				{
622 					ent->client->rocketLockTime = ent->client->rocketLastValidTime;
623 				}
624 			}
625 
626 			if (ent->client->rocketLockIndex == tr.entityNum)
627 			{
628 				ent->client->rocketTargetTime = level.time + 500;
629 			}
630 		}
631 	}
632 	else if (ent->client->rocketTargetTime < level.time)
633 	{
634 		ent->client->rocketLockIndex = ENTITYNUM_NONE;
635 		ent->client->rocketLockTime = 0;
636 	}
637 	else
638 	{
639 		if (ent->client->rocketLockTime != -1)
640 		{
641 			ent->client->rocketLastValidTime = ent->client->rocketLockTime;
642 		}
643 		ent->client->rocketLockTime = -1;
644 	}
645 }
646 
647 #define VEH_HOMING_MISSILE_THINK_TIME		100
WP_FireVehicleWeapon(gentity_t * ent,vec3_t start,vec3_t dir,vehWeaponInfo_t * vehWeapon)648 void WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon )
649 {
650 	if ( !vehWeapon )
651 	{//invalid vehicle weapon
652 		return;
653 	}
654 	else if ( vehWeapon->bIsProjectile )
655 	{//projectile entity
656 		gentity_t	*missile;
657 		vec3_t		mins, maxs;
658 
659 		VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f );
660 		VectorScale( maxs, -1, mins );
661 
662 		//make sure our start point isn't on the other side of a wall
663 		WP_TraceSetStart( ent, start, mins, maxs );
664 
665 		//QUERY: alt_fire true or not?  Does it matter?
666 		missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse );
667 		if ( vehWeapon->bHasGravity )
668 		{//TESTME: is this all we need to do?
669 			missile->s.pos.trType = TR_GRAVITY;
670 		}
671 
672 		missile->classname = "vehicle_proj";
673 
674 		missile->damage = vehWeapon->iDamage;
675 		missile->splashDamage = vehWeapon->iSplashDamage;
676 		missile->splashRadius = vehWeapon->fSplashRadius;
677 
678 		// HUGE HORRIBLE HACK
679 		if (ent->owner && ent->owner->s.number==0)
680 		{
681 			//Should only be for speeders - mainly for t2_trip
682 			if (ent->m_pVehicle->m_pVehicleInfo && ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
683 			{
684 				missile->damage			*= 20.0f;
685 				missile->splashDamage	*= 20.0f;
686 				missile->splashRadius	*= 20.0f;
687 			}
688 		}
689 
690 		//FIXME: externalize some of these properties?
691 		missile->dflags = DAMAGE_DEATH_KNOCKBACK;
692 		missile->clipmask = MASK_SHOT;
693 		//Maybe by checking flags...?
694 		if ( vehWeapon->bSaberBlockable )
695 		{
696 			missile->clipmask |= CONTENTS_LIGHTSABER;
697 		}
698 		/*
699 		if ( (vehWeapon->iFlags&VWF_KNOCKBACK) )
700 		{
701 			missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK;
702 		}
703 		if ( (vehWeapon->iFlags&VWF_DISTORTION_TRAIL) )
704 		{
705 			missile->s.eFlags |= EF_DISTORTION_TRAIL;
706 		}
707 		if ( (vehWeapon->iFlags&VWF_RADAR) )
708 		{
709 			missile->s.eFlags |= EF_RADAROBJECT;
710 		}
711 		*/
712 		missile->s.weapon = WP_BLASTER;//does this really matter?
713 
714 		// Make it easier to hit things
715 		VectorCopy( mins, missile->mins );
716 		VectorCopy( maxs, missile->maxs );
717 		//some slightly different stuff for things with bboxes
718 		if ( vehWeapon->fWidth || vehWeapon->fHeight )
719 		{//we assume it's a rocket-like thing
720 			missile->methodOfDeath = MOD_ROCKET;
721 			missile->splashMethodOfDeath = MOD_ROCKET;// ?SPLASH;
722 
723 			// we don't want it to ever bounce
724 			missile->bounceCount = 0;
725 
726 			missile->mass = 10;
727 		}
728 		else
729 		{//a blaster-laser-like thing
730 			missile->s.weapon = WP_BLASTER;//does this really matter?
731 			missile->methodOfDeath = MOD_EMPLACED;//MOD_TURBLAST; //count as a heavy weap
732 			missile->splashMethodOfDeath = MOD_EMPLACED;//MOD_TURBLAST;// ?SPLASH;
733 			// we don't want it to bounce forever
734 			missile->bounceCount = 8;
735 		}
736 
737 		if ( vehWeapon->iHealth )
738 		{//the missile can take damage
739 			missile->health = vehWeapon->iHealth;
740 			missile->takedamage = qtrue;
741 			missile->contents = MASK_SHOT;
742 			missile->e_DieFunc = dieF_WP_ExplosiveDie;//dieF_RocketDie;
743 		}
744 
745 		//set veh as cgame side owner for purpose of fx overrides
746 		if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot)
747 		{
748 			missile->owner = ent->m_pVehicle->m_pPilot;
749 		}
750 		else
751 		{
752 			missile->owner = ent;
753 		}
754 		missile->s.otherEntityNum = ent->s.number;
755 		missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]);
756 
757 		if ( vehWeapon->iLifeTime )
758 		{//expire after a time
759 			if ( vehWeapon->bExplodeOnExpire )
760 			{//blow up when your lifetime is up
761 				missile->e_ThinkFunc = thinkF_WP_Explode;//FIXME: custom func?
762 			}
763 			else
764 			{//just remove yourself
765 				missile->e_ThinkFunc = thinkF_G_FreeEntity;//FIXME: custom func?
766 			}
767 			missile->nextthink = level.time + vehWeapon->iLifeTime;
768 		}
769 		if ( vehWeapon->fHoming )
770 		{//homing missile
771 			//crap, we need to set up the homing stuff like it is in MP...
772 			WP_RocketLock( ent, 16384 );
773 			if ( ent->client && ent->client->rocketLockIndex != ENTITYNUM_NONE )
774 			{
775 				int dif = 0;
776 				float rTime;
777 				rTime = ent->client->rocketLockTime;
778 
779 				if (rTime == -1)
780 				{
781 					rTime = ent->client->rocketLastValidTime;
782 				}
783 
784 				if ( !vehWeapon->iLockOnTime )
785 				{//no minimum lock-on time
786 					dif = 10;//guaranteed lock-on
787 				}
788 				else
789 				{
790 					float lockTimeInterval = vehWeapon->iLockOnTime/16.0f;
791 					dif = ( level.time - rTime ) / lockTimeInterval;
792 				}
793 
794 				if (dif < 0)
795 				{
796 					dif = 0;
797 				}
798 
799 				//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
800 				if ( dif >= 10 && rTime != -1 )
801 				{
802 					missile->enemy = &g_entities[ent->client->rocketLockIndex];
803 
804 					if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
805 					{ //if enemy became invalid, died, or is on the same team, then don't seek it
806 						missile->spawnflags |= 1;//just to let it know it should be faster... FIXME: EXTERNALIZE
807 						missile->speed = vehWeapon->fSpeed;
808 						missile->angle = vehWeapon->fHoming;
809 						if ( vehWeapon->iLifeTime )
810 						{//expire after a time
811 							missile->disconnectDebounceTime = level.time + vehWeapon->iLifeTime;
812 							missile->lockCount = (int)(vehWeapon->bExplodeOnExpire);
813 						}
814 						missile->e_ThinkFunc = thinkF_rocketThink;
815 						missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME;
816 						//FIXME: implement radar in SP?
817 						//missile->s.eFlags |= EF_RADAROBJECT;
818 					}
819 				}
820 
821 				ent->client->rocketLockIndex = ENTITYNUM_NONE;
822 				ent->client->rocketLockTime = 0;
823 				ent->client->rocketTargetTime = 0;
824 
825 				VectorCopy( dir, missile->movedir );
826 				missile->random = 1.0f;//FIXME: externalize?
827 			}
828 		}
829 	}
830 	else
831 	{//traceline
832 		//FIXME: implement
833 	}
834 }
835 
WP_VehLeadCrosshairVeh(gentity_t * camTraceEnt,vec3_t newEnd,const vec3_t dir,const vec3_t shotStart,vec3_t shotDir)836 void WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir )
837 {
838 	//FIXME: implement from MP?
839 }
840 
WP_VehCheckTraceFromCamPos(gentity_t * ent,const vec3_t shotStart,vec3_t shotDir)841 qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir )
842 {
843 	//FIXME: implement from MP?
844 	return qfalse;
845 }
846 
847 //---------------------------------------------------------
FireVehicleWeapon(gentity_t * ent,qboolean alt_fire)848 void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire )
849 //---------------------------------------------------------
850 {
851 	Vehicle_t *pVeh = ent->m_pVehicle;
852 
853 	if ( !pVeh )
854 	{
855 		return;
856 	}
857 
858 	if (pVeh->m_iRemovedSurfaces)
859 	{ //can't fire when the thing is breaking apart
860 		return;
861 	}
862 
863 
864 	if (ent->owner && ent->owner->client && ent->owner->client->ps.weapon!=WP_NONE)
865 	{
866 		return;
867 	}
868 
869 	// TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle
870 	// so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you
871 	// would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis
872 
873 	// If this is not the alternate fire, fire a normal blaster shot...
874 	if ( pVeh->m_pVehicleInfo &&
875 		(pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched.
876 	{//fighters can only fire when wings are open
877 		int	weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE;
878 		int	delay = 1000;
879 		qboolean aimCorrect = qfalse;
880 		qboolean linkedFiring = qfalse;
881 
882 		if ( !alt_fire )
883 		{
884 			weaponNum = 0;
885 		}
886 		else
887 		{
888 			weaponNum = 1;
889 		}
890 
891 		vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID;
892 
893 		if ( pVeh->weaponStatus[weaponNum].ammo <= 0 )
894 		{//no ammo for this weapon
895 			if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
896 			{// let the client know he's out of ammo
897 				int i;
898 				//but only if one of the vehicle muzzles is actually ready to fire this weapon
899 				for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
900 				{
901 					if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
902 					{//this muzzle doesn't match the weapon we're trying to use
903 						continue;
904 					}
905 					if ( pVeh->m_iMuzzleTag[i] != -1
906 						&& pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
907 					{//this one would have fired, send the no ammo message
908 						G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
909 						break;
910 					}
911 				}
912 			}
913 			return;
914 		}
915 
916 		delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
917 		aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
918 		if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked
919 			|| ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable
920 				&& pVeh->weaponStatus[weaponNum].linked ) )//linked
921 		{//we're linking the primary or alternate weapons, so we'll do *all* the muzzles
922 			linkedFiring = qtrue;
923 		}
924 
925 		if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS )
926 		{//invalid vehicle weapon
927 			return;
928 		}
929 		else
930 		{
931 			int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0;
932 			qboolean sentAmmoWarning = qfalse;
933 
934 			vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
935 
936 			if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 )
937 			{//always linked weapons don't accumulate delay, just use specified delay
938 				cumulativeDelay = delay;
939 			}
940 			//find out how many we've got for this weapon
941 			for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
942 			{
943 				if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
944 				{//this muzzle doesn't match the weapon we're trying to use
945 					continue;
946 				}
947 				if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
948 				{
949 					numMuzzlesReady++;
950 				}
951 				if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex )
952 				{//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time)
953 					//set the next to this one
954 					pVeh->weaponStatus[weaponNum].nextMuzzle = i;
955 				}
956 				if ( linkedFiring )
957 				{
958 					cumulativeAmmo += vehWeapon->iAmmoPerShot;
959 					if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 )
960 					{//always linked weapons don't accumulate delay, just use specified delay
961 						cumulativeDelay += delay;
962 					}
963 				}
964 				numMuzzles++;
965 			}
966 
967 			if ( linkedFiring )
968 			{//firing all muzzles at once
969 				if ( numMuzzlesReady != numMuzzles )
970 				{//can't fire all linked muzzles yet
971 					return;
972 				}
973 				else
974 				{//can fire all linked muzzles, check ammo
975 					if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo )
976 					{//can't fire, not enough ammo
977 						if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
978 						{// let the client know he's out of ammo
979 							G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
980 						}
981 						return;
982 					}
983 				}
984 			}
985 
986 			for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
987 			{
988 				if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
989 				{//this muzzle doesn't match the weapon we're trying to use
990 					continue;
991 				}
992 				if ( !linkedFiring
993 					&& i != pVeh->weaponStatus[weaponNum].nextMuzzle )
994 				{//we're only firing one muzzle and this isn't it
995 					continue;
996 				}
997 
998 				// Fire this muzzle.
999 				if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
1000 				{
1001 					vec3_t	start, dir;
1002 
1003 					if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
1004 					{//out of ammo!
1005 						if ( !sentAmmoWarning )
1006 						{
1007 							sentAmmoWarning = qtrue;
1008 							if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
1009 							{// let the client know he's out of ammo
1010 								G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
1011 							}
1012 						}
1013 					}
1014 					else
1015 					{//have enough ammo to shoot
1016 						//do the firing
1017 						//WP_CalcVehMuzzle(ent, i);
1018 						VectorCopy( pVeh->m_Muzzles[i].m_vMuzzlePos, start );
1019 						VectorCopy( pVeh->m_Muzzles[i].m_vMuzzleDir, dir );
1020 						if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) )
1021 						{//auto-aim at whatever crosshair would be over from camera's point of view (if closer)
1022 						}
1023 						else if ( aimCorrect )
1024 						{//auto-aim the missile at the crosshair if there's anything there
1025 							trace_t trace;
1026 							vec3_t	end;
1027 							vec3_t	ang;
1028 							vec3_t	fixedDir;
1029 
1030 							if (pVeh->m_pVehicleInfo->type == VH_SPEEDER)
1031 							{
1032 								VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f);
1033 							}
1034 							else
1035 							{
1036 								VectorCopy(pVeh->m_vOrientation, ang);
1037 							}
1038 							AngleVectors( ang, fixedDir, NULL, NULL );
1039 							//VectorMA( ent->currentOrigin, 32768, dir, end );
1040 							VectorMA( ent->currentOrigin, 8192, dir, end );
1041 							gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1042 							if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
1043 							{
1044 								vec3_t newEnd;
1045 								VectorCopy( trace.endpos, newEnd );
1046 								WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir );
1047 							}
1048 						}
1049 
1050 						//play the weapon's muzzle effect if we have one
1051 						if ( vehWeapon->iMuzzleFX )
1052 						{
1053 							G_PlayEffect( vehWeapon->iMuzzleFX, pVeh->m_Muzzles[i].m_vMuzzlePos, pVeh->m_Muzzles[i].m_vMuzzleDir );
1054 						}
1055 						WP_FireVehicleWeapon( ent, start, dir, vehWeapon );
1056 					}
1057 
1058 					if ( linkedFiring )
1059 					{//we're linking the weapon, so continue on and fire all appropriate muzzles
1060 						continue;
1061 					}
1062 					//else just firing one
1063 					//take the ammo, set the next muzzle and set the delay on it
1064 					if ( numMuzzles > 1 )
1065 					{//more than one, look for it
1066 						int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle;
1067 						while ( 1 )
1068 						{
1069 							nextMuzzle++;
1070 							if ( nextMuzzle >= MAX_VEHICLE_MUZZLES )
1071 							{
1072 								nextMuzzle = 0;
1073 							}
1074 							if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle )
1075 							{//WTF?  Wrapped without finding another valid one!
1076 								break;
1077 							}
1078 							if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex )
1079 							{//this is the next muzzle for this weapon
1080 								pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle;
1081 								break;
1082 							}
1083 						}
1084 					}//else, just stay on the one we just fired
1085 					//set the delay on the next muzzle
1086 					pVeh->m_Muzzles[pVeh->weaponStatus[weaponNum].nextMuzzle].m_iMuzzleWait = level.time + delay;
1087 					//take away the ammo
1088 					pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
1089 					//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
1090 					if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
1091 					{
1092 						((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
1093 					}
1094 					//done!
1095 					//we'll get in here again next frame and try the next muzzle...
1096 					//return;
1097 					return;
1098 				}
1099 			}
1100 			//we went through all the muzzles, so apply the cumulative delay and ammo cost
1101 			if ( cumulativeAmmo )
1102 			{//taking ammo one shot at a time
1103 				//take the ammo
1104 				pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo;
1105 				//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
1106 				if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
1107 				{
1108 					((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
1109 				}
1110 			}
1111 			if ( cumulativeDelay )
1112 			{//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles
1113 				for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
1114 				{
1115 					if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
1116 					{//this muzzle doesn't match the weapon we're trying to use
1117 						continue;
1118 					}
1119 					//apply the cumulative delay
1120 					pVeh->m_Muzzles[i].m_iMuzzleWait = level.time + cumulativeDelay;
1121 				}
1122 			}
1123 		}
1124 	}
1125 }
1126 
WP_FireScepter(gentity_t * ent,qboolean alt_fire)1127 void WP_FireScepter( gentity_t *ent, qboolean alt_fire )
1128 {//just a straight beam
1129 	int			damage = 1;
1130 	vec3_t		start, end;
1131 	trace_t		tr;
1132 	gentity_t	*traceEnt = NULL, *tent;
1133 	float		shotRange = 8192;
1134 	qboolean	render_impact = qtrue;
1135 
1136 	VectorCopy( muzzle, start );
1137 	WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
1138 
1139 	WP_MissileTargetHint(ent, start, forwardVec);
1140 	VectorMA( start, shotRange, forwardVec, end );
1141 
1142 	gi.trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
1143 	traceEnt = &g_entities[tr.entityNum];
1144 
1145 	if ( tr.surfaceFlags & SURF_NOIMPACT )
1146 	{
1147 		render_impact = qfalse;
1148 	}
1149 
1150 	// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
1151 	tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
1152 	tent->svFlags |= SVF_BROADCAST;
1153 	VectorCopy( muzzle, tent->s.origin2 );
1154 
1155 	if ( render_impact )
1156 	{
1157 		if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
1158 		{
1159 			// Create a simple impact type mark that doesn't last long in the world
1160 			G_PlayEffect( G_EffectIndex( "disruptor/flesh_impact" ), tr.endpos, tr.plane.normal );
1161 
1162 			int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR );
1163 			G_Damage( traceEnt, ent, ent, forwardVec, tr.endpos, damage, DAMAGE_EXTRA_KNOCKBACK, MOD_DISRUPTOR, hitLoc );
1164 		}
1165 		else
1166 		{
1167 			G_PlayEffect( G_EffectIndex( "disruptor/wall_impact" ), tr.endpos, tr.plane.normal );
1168 		}
1169 	}
1170 
1171 	/*
1172 	shotDist = shotRange * tr.fraction;
1173 
1174 	for ( dist = 0; dist < shotDist; dist += 64 )
1175 	{
1176 		//FIXME: on a really long shot, this could make a LOT of alerts in one frame...
1177 		VectorMA( start, dist, forwardVec, spot );
1178 		AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
1179 	}
1180 	VectorMA( start, shotDist-4, forwardVec, spot );
1181 	AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
1182 	*/
1183 }
1184 
1185 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
1186 //---------------------------------------------------------
FireWeapon(gentity_t * ent,qboolean alt_fire)1187 void FireWeapon( gentity_t *ent, qboolean alt_fire )
1188 //---------------------------------------------------------
1189 {
1190 	float alert = 256;
1191 	Vehicle_t *pVeh = NULL;
1192 
1193 	// track shots taken for accuracy tracking.
1194 	ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
1195 
1196 	// If this is a vehicle, fire it's weapon and we're done.
1197 	if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
1198 	{
1199 		FireVehicleWeapon( ent, alt_fire );
1200 		return;
1201 	}
1202 
1203 	// set aiming directions
1204 	if ( ent->s.weapon == WP_DISRUPTOR && alt_fire )
1205 	{
1206 		if ( ent->NPC )
1207 		{
1208 			//snipers must use the angles they actually did their shot trace with
1209 			AngleVectors( ent->lastAngles, forwardVec, vrightVec, up );
1210 		}
1211 	}
1212 	else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN )
1213 	{
1214 		vec3_t	delta1, enemy_org1, muzzle1;
1215 		vec3_t	angleToEnemy1;
1216 
1217 		VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 );
1218 
1219 		if ( !ent->s.number )
1220 		{//player driving an AT-ST
1221 			//SIGH... because we can't anticipate alt-fire, must calc muzzle here and now
1222 			mdxaBone_t		boltMatrix;
1223 			int				bolt;
1224 
1225 			if ( ent->client->ps.weapon == WP_ATST_MAIN )
1226 			{//FIXME: alt_fire should fire both barrels, but slower?
1227 				if ( ent->alt_fire )
1228 				{
1229 					bolt = ent->handRBolt;
1230 				}
1231 				else
1232 				{
1233 					bolt = ent->handLBolt;
1234 				}
1235 			}
1236 			else
1237 			{// ATST SIDE weapons
1238 				if ( ent->alt_fire )
1239 				{
1240 					if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) )
1241 					{//don't have it!
1242 						return;
1243 					}
1244 					bolt = ent->genericBolt2;
1245 				}
1246 				else
1247 				{
1248 					if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) )
1249 					{//don't have it!
1250 						return;
1251 					}
1252 					bolt = ent->genericBolt1;
1253 				}
1254 			}
1255 
1256 			vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0};
1257 			if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw )
1258 			{
1259 				yawOnlyAngles[YAW] = ent->client->ps.legsYaw;
1260 			}
1261 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale );
1262 
1263 			// work the matrix axis stuff into the original axis and origins used.
1264 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint );
1265 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir );
1266 			ent->client->renderInfo.mPCalcTime = level.time;
1267 
1268 			AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, up );
1269 			//CalcMuzzlePoint( ent, forwardVec, vrightVec, up, muzzle, 0 );
1270 		}
1271 		else if ( !ent->enemy )
1272 		{//an NPC with no enemy to auto-aim at
1273 			VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
1274 		}
1275 		else
1276 		{//NPC, auto-aim at enemy
1277 			CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
1278 
1279 			VectorSubtract (enemy_org1, muzzle1, delta1);
1280 
1281 			vectoangles ( delta1, angleToEnemy1 );
1282 			AngleVectors (angleToEnemy1, forwardVec, vrightVec, up);
1283 		}
1284 	}
1285 	else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy )
1286 	{
1287 		vec3_t	delta1, enemy_org1, muzzle1;
1288 		vec3_t	angleToEnemy1;
1289 
1290 		CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
1291 		CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 );
1292 
1293 		VectorSubtract (enemy_org1, muzzle1, delta1);
1294 
1295 		vectoangles ( delta1, angleToEnemy1 );
1296 		AngleVectors (angleToEnemy1, forwardVec, vrightVec, up);
1297 	}
1298 	else
1299 	{
1300   		if ( (pVeh = G_IsRidingVehicle( ent )) != NULL) //riding a vehicle
1301 		{//use our muzzleDir, can't use viewangles or vehicle m_vOrientation because we may be animated to shoot left or right...
1302 			if ((ent->s.eFlags&EF_NODRAW))//we're inside it
1303 			{
1304 				vec3_t	aimAngles;
1305 				VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
1306 				vectoangles( forwardVec, aimAngles );
1307 				//we're only keeping the yaw
1308 				aimAngles[PITCH] = ent->client->ps.viewangles[PITCH];
1309 				aimAngles[ROLL] = 0;
1310 				AngleVectors( aimAngles, forwardVec, vrightVec, up );
1311 			}
1312 			else
1313 			{
1314 				vec3_t	actorRight;
1315 				vec3_t	actorFwd;
1316 
1317 				VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle );
1318 				AngleVectors(ent->currentAngles, actorFwd, actorRight, 0);
1319 
1320 				// Aiming Left
1321 				//-------------
1322 				if (ent->client->ps.torsoAnim==BOTH_VT_ATL_G || ent->client->ps.torsoAnim==BOTH_VS_ATL_G)
1323 				{
1324  					VectorScale(actorRight, -1.0f, forwardVec);
1325 				}
1326 
1327 				// Aiming Right
1328 				//--------------
1329 				else if (ent->client->ps.torsoAnim==BOTH_VT_ATR_G || ent->client->ps.torsoAnim==BOTH_VS_ATR_G)
1330 				{
1331  					VectorCopy(actorRight, forwardVec);
1332 				}
1333 
1334 				// Aiming Forward
1335 				//----------------
1336 				else
1337 				{
1338 	 				VectorCopy(actorFwd, forwardVec);
1339 				}
1340 
1341 				// If We Have An Enemy, Fudge The Aim To Hit The Enemy
1342 				if (ent->enemy)
1343 				{
1344 					vec3_t	toEnemy;
1345 					VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, toEnemy);
1346 					VectorNormalize(toEnemy);
1347 					if (DotProduct(toEnemy, forwardVec)>0.75f &&
1348 						((ent->s.number==0 && !Q_irand(0,2)) ||		// the player has a 1 in 3 chance
1349 						 (ent->s.number!=0 && !Q_irand(0,5))))		// other guys have a 1 in 6 chance
1350 					{
1351 						VectorCopy(toEnemy, forwardVec);
1352 					}
1353 					else
1354 					{
1355 						forwardVec[0] += Q_flrand(-0.1f, 0.1f);
1356 						forwardVec[1] += Q_flrand(-0.1f, 0.1f);
1357 						forwardVec[2] += Q_flrand(-0.1f, 0.1f);
1358 					}
1359 				}
1360 			}
1361 		}
1362 		else
1363 		{
1364 			AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, up );
1365 		}
1366 	}
1367 
1368 	ent->alt_fire = alt_fire;
1369 	if (!pVeh)
1370 	{
1371 		if (ent->NPC && (ent->NPC->scriptFlags&SCF_FIRE_WEAPON_NO_ANIM))
1372 		{
1373 		 	VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle );
1374 			VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
1375 			MakeNormalVectors(forwardVec, vrightVec, up);
1376 		}
1377 		else
1378 		{
1379 			CalcMuzzlePoint ( ent, forwardVec, vrightVec, up, muzzle , 0);
1380 		}
1381 	}
1382 
1383 	// fire the specific weapon
1384 	switch( ent->s.weapon )
1385 	{
1386 	// Player weapons
1387 	//-----------------
1388 	case WP_SABER:
1389 		return;
1390 		break;
1391 
1392 	case WP_BRYAR_PISTOL:
1393 	case WP_BLASTER_PISTOL:
1394 		WP_FireBryarPistol( ent, alt_fire );
1395 		break;
1396 
1397 	case WP_BLASTER:
1398 		WP_FireBlaster( ent, alt_fire );
1399 		break;
1400 
1401 	case WP_TUSKEN_RIFLE:
1402 		if ( alt_fire )
1403 		{
1404 			WP_FireTuskenRifle( ent );
1405 		}
1406 		else
1407 		{
1408 			WP_Melee( ent );
1409 		}
1410 		break;
1411 
1412 	case WP_DISRUPTOR:
1413 		alert = 50; // if you want it to alert enemies, remove this
1414 		WP_FireDisruptor( ent, alt_fire );
1415 		break;
1416 
1417 	case WP_BOWCASTER:
1418 		WP_FireBowcaster( ent, alt_fire );
1419 		break;
1420 
1421 	case WP_REPEATER:
1422 		WP_FireRepeater( ent, alt_fire );
1423 		break;
1424 
1425 	case WP_DEMP2:
1426 		WP_FireDEMP2( ent, alt_fire );
1427 		break;
1428 
1429 	case WP_FLECHETTE:
1430 		WP_FireFlechette( ent, alt_fire );
1431 		break;
1432 
1433 	case WP_ROCKET_LAUNCHER:
1434 		WP_FireRocket( ent, alt_fire );
1435 		break;
1436 
1437 	case WP_CONCUSSION:
1438 		WP_Concussion( ent, alt_fire );
1439 		break;
1440 
1441 	case WP_THERMAL:
1442 		WP_FireThermalDetonator( ent, alt_fire );
1443 		break;
1444 
1445 	case WP_TRIP_MINE:
1446 		alert = 0; // if you want it to alert enemies, remove this
1447 		WP_PlaceLaserTrap( ent, alt_fire );
1448 		break;
1449 
1450 	case WP_DET_PACK:
1451 		alert = 0; // if you want it to alert enemies, remove this
1452 		WP_FireDetPack( ent, alt_fire );
1453 		break;
1454 
1455 	case WP_BOT_LASER:
1456 		WP_BotLaser( ent );
1457 		break;
1458 
1459 	case WP_EMPLACED_GUN:
1460 		// doesn't care about whether it's alt-fire or not.  We can do an alt-fire if needed
1461 		WP_EmplacedFire( ent );
1462 		break;
1463 
1464 	case WP_MELEE:
1465 		alert = 0; // if you want it to alert enemies, remove this
1466 		if ( !alt_fire || !g_debugMelee->integer )
1467 		{
1468 			WP_Melee( ent );
1469 		}
1470 		break;
1471 
1472 	case WP_ATST_MAIN:
1473 		WP_ATSTMainFire( ent );
1474 		break;
1475 
1476 	case WP_ATST_SIDE:
1477 
1478 		// TEMP
1479 		if ( alt_fire )
1480 		{
1481 //			WP_FireRocket( ent, qfalse );
1482 			WP_ATSTSideAltFire(ent);
1483 		}
1484 		else
1485 		{
1486 			// FIXME!
1487 		/*	if ( ent->s.number == 0
1488 				&& ent->client->NPC_class == CLASS_VEHICLE
1489 				&& vehicleData[((CVehicleNPC *)ent->NPC)->m_iVehicleTypeID].type == VH_FIGHTER )
1490 			{
1491 				WP_ATSTMainFire( ent );
1492 			}
1493 			else*/
1494 			{
1495 				WP_ATSTSideFire(ent);
1496 			}
1497 		}
1498 		break;
1499 
1500 	case WP_TIE_FIGHTER:
1501 		// TEMP
1502 		WP_EmplacedFire( ent );
1503 		break;
1504 
1505 	case WP_RAPID_FIRE_CONC:
1506 		// TEMP
1507 		if ( alt_fire )
1508 		{
1509 			WP_FireRepeater( ent, alt_fire );
1510 		}
1511 		else
1512 		{
1513 			WP_EmplacedFire( ent );
1514 		}
1515 		break;
1516 
1517 	case WP_STUN_BATON:
1518 		WP_FireStunBaton( ent, alt_fire );
1519 		break;
1520 
1521 //	case WP_BLASTER_PISTOL:
1522 	case WP_JAWA:
1523 		WP_FireBryarPistol( ent, qfalse ); // never an alt-fire?
1524 		break;
1525 
1526 	case WP_SCEPTER:
1527 		WP_FireScepter( ent, alt_fire );
1528 		break;
1529 
1530 	case WP_NOGHRI_STICK:
1531 		if ( !alt_fire )
1532 		{
1533 			WP_FireNoghriStick( ent );
1534 		}
1535 		//else does melee attack/damage/func
1536 		break;
1537 
1538 	case WP_TUSKEN_STAFF:
1539 	default:
1540 		return;
1541 		break;
1542 	}
1543 
1544 	if ( !ent->s.number )
1545 	{
1546 		if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) )
1547 		{//these can fire multiple shots, count them individually within the firing functions
1548 		}
1549 		else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) )
1550 		{
1551 			ent->client->sess.missionStats.shotsFired++;
1552 		}
1553 	}
1554 	// We should probably just use this as a default behavior, in special cases, just set alert to false.
1555 	if ( ent->s.number == 0 && alert > 0 )
1556 	{
1557 		if ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD//FIXME: check for sand contents type?
1558 			&& ent->s.weapon != WP_STUN_BATON
1559 			&& ent->s.weapon != WP_MELEE
1560 			&& ent->s.weapon != WP_TUSKEN_STAFF
1561 			&& ent->s.weapon != WP_THERMAL
1562 			&& ent->s.weapon != WP_TRIP_MINE
1563 			&& ent->s.weapon != WP_DET_PACK )
1564 		{//the vibration of the shot carries through your feet into the ground
1565 			AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED, qfalse, qtrue );
1566 		}
1567 		else
1568 		{//an in-air alert
1569 			AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED );
1570 		}
1571 		AddSightEvent( ent, muzzle, alert*2, AEL_DISCOVERED, 20 );
1572 	}
1573 }
1574 
1575 //NOTE: Emplaced gun moved to g_emplaced.cpp
1576 
1577 /*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE
1578 ALTFIRE - fire the alt-fire of the chosen weapon
1579 TOGGLE - keep firing until used again (fires at intervals of "wait")
1580 
1581 "wait" - debounce time between refires (defaults to 500)
1582 "delay" - speed of WP_THERMAL (default is 900)
1583 "random" - ranges from 0 to random, added to wait (defaults to 0)
1584 
1585 "target" - what to aim at (will update aim every frame if it's a moving target)
1586 
1587 "weapon" - specify the weapon to use (default is WP_BLASTER)
1588 	WP_BRYAR_PISTOL
1589 	WP_BLASTER
1590 	WP_DISRUPTOR
1591 	WP_BOWCASTER
1592 	WP_REPEATER
1593 	WP_DEMP2
1594 	WP_FLECHETTE
1595 	WP_ROCKET_LAUNCHER
1596 	WP_CONCUSSION
1597 	WP_THERMAL
1598 	WP_TRIP_MINE
1599 	WP_DET_PACK
1600 	WP_STUN_BATON
1601 	WP_EMPLACED_GUN
1602 	WP_BOT_LASER
1603 	WP_TURRET
1604 	WP_ATST_MAIN
1605 	WP_ATST_SIDE
1606 	WP_TIE_FIGHTER
1607 	WP_RAPID_FIRE_CONC
1608 	WP_BLASTER_PISTOL
1609 */
misc_weapon_shooter_fire(gentity_t * self)1610 void misc_weapon_shooter_fire( gentity_t *self )
1611 {
1612 	FireWeapon( self, (qboolean)((self->spawnflags&1) != 0) );
1613 	if ( (self->spawnflags&2) )
1614 	{//repeat
1615 		self->e_ThinkFunc = thinkF_misc_weapon_shooter_fire;
1616 		if (self->random)
1617 		{
1618 			self->nextthink = level.time + self->wait + (int)(Q_flrand(0.0f, 1.0f)*self->random);
1619 		}
1620 		else
1621 		{
1622 			self->nextthink = level.time + self->wait;
1623 		}
1624 	}
1625 }
1626 
misc_weapon_shooter_use(gentity_t * self,gentity_t * other,gentity_t * activator)1627 void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
1628 {
1629 	if ( self->e_ThinkFunc == thinkF_misc_weapon_shooter_fire )
1630 	{//repeating fire, stop
1631 		self->e_ThinkFunc = thinkF_NULL;
1632 		self->nextthink = -1;
1633 		return;
1634 	}
1635 	//otherwise, fire
1636 	misc_weapon_shooter_fire( self );
1637 }
1638 
misc_weapon_shooter_aim(gentity_t * self)1639 void misc_weapon_shooter_aim( gentity_t *self )
1640 {
1641 	//update my aim
1642 	if ( self->target )
1643 	{
1644 		gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
1645 		if ( targ )
1646 		{
1647 			self->enemy = targ;
1648 			VectorSubtract( targ->currentOrigin, self->currentOrigin, self->client->renderInfo.muzzleDir );
1649 			VectorCopy( targ->currentOrigin, self->pos1 );
1650 			vectoangles( self->client->renderInfo.muzzleDir, self->client->ps.viewangles );
1651 			SetClientViewAngle( self, self->client->ps.viewangles );
1652 			//FIXME: don't keep doing this unless target is a moving target?
1653 			self->nextthink = level.time + FRAMETIME;
1654 		}
1655 		else
1656 		{
1657 			self->enemy = NULL;
1658 		}
1659 	}
1660 }
1661 
1662 extern stringID_table_t WPTable[];
SP_misc_weapon_shooter(gentity_t * self)1663 void SP_misc_weapon_shooter( gentity_t *self )
1664 {
1665 	//alloc a client just for the weapon code to use
1666 	self->client = (gclient_t *)gi.Malloc(sizeof(gclient_t), TAG_G_ALLOC, qtrue);
1667 
1668 	//set weapon
1669 	self->s.weapon = self->client->ps.weapon = WP_BLASTER;
1670 	if ( self->paintarget )
1671 	{//use a different weapon
1672 		self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, self->paintarget );
1673 	}
1674 
1675 	//set where our muzzle is
1676 	VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint );
1677 	//permanently updated
1678 	self->client->renderInfo.mPCalcTime = Q3_INFINITE;
1679 
1680 	//set up to link
1681 	if ( self->target )
1682 	{
1683         self->e_ThinkFunc = thinkF_misc_weapon_shooter_aim;
1684 		self->nextthink = level.time + START_TIME_LINK_ENTS;
1685 	}
1686 	else
1687 	{//just set aim angles
1688 		VectorCopy( self->s.angles, self->client->ps.viewangles );
1689 		AngleVectors( self->s.angles, self->client->renderInfo.muzzleDir, NULL, NULL );
1690 	}
1691 
1692 	//set up to fire when used
1693     self->e_UseFunc = useF_misc_weapon_shooter_use;
1694 
1695 	if ( !self->wait )
1696 	{
1697 		self->wait = 500;
1698 	}
1699 }
1700