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_headers.h"
28 
29 #include "g_local.h"
30 #include "g_functions.h"
31 #include "anims.h"
32 #include "b_local.h"
33 #include "w_local.h"
34 
35 vec3_t	wpFwd, wpVright, wpUp;
36 vec3_t	wpMuzzle;
37 
38 gentity_t *ent_list[MAX_GENTITIES];
39 
40 // some naughty little things that are used cg side
41 int g_rocketLockEntNum = ENTITYNUM_NONE;
42 int g_rocketLockTime = 0;
43 int	g_rocketSlackTime = 0;
44 
45 // Weapon Helper Functions
46 
47 //-----------------------------------------------------------------------------
WP_TraceSetStart(const gentity_t * ent,vec3_t start,const vec3_t mins,const vec3_t maxs)48 void WP_TraceSetStart( const gentity_t *ent, vec3_t start, const vec3_t mins, const vec3_t maxs )
49 //-----------------------------------------------------------------------------
50 {
51 	//make sure our start point isn't on the other side of a wall
52 	trace_t	tr;
53 	vec3_t	entMins, newstart;
54 	vec3_t	entMaxs;
55 
56 	VectorSet( entMaxs, 5, 5, 5 );
57 	VectorScale( entMaxs, -1, entMins );
58 
59 	if ( !ent->client )
60 	{
61 		return;
62 	}
63 
64 	VectorCopy( ent->currentOrigin, newstart );
65 	newstart[2] = start[2]; // force newstart to be on the same plane as the wpMuzzle ( start )
66 
67 	gi.trace( &tr, newstart, entMins, entMaxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, G2_NOCOLLIDE, 0 );
68 
69 	if ( tr.startsolid || tr.allsolid )
70 	{
71 		// there is a problem here..
72 		return;
73 	}
74 
75 	if ( tr.fraction < 1.0f )
76 	{
77 		VectorCopy( tr.endpos, start );
78 	}
79 }
80 
81 //-----------------------------------------------------------------------------
CreateMissile(vec3_t org,vec3_t dir,float vel,int life,gentity_t * owner,qboolean altFire)82 gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire )
83 //-----------------------------------------------------------------------------
84 {
85 	gentity_t	*missile;
86 
87 	missile = G_Spawn();
88 
89 	missile->nextthink = level.time + life;
90 	missile->e_ThinkFunc = thinkF_G_FreeEntity;
91 	missile->s.eType = ET_MISSILE;
92 	missile->owner = owner;
93 
94 	missile->alt_fire = altFire;
95 
96 	missile->s.pos.trType = TR_LINEAR;
97 	missile->s.pos.trTime = level.time;// - 10;	// move a bit on the very first frame
98 	VectorCopy( org, missile->s.pos.trBase );
99 	VectorScale( dir, vel, missile->s.pos.trDelta );
100 	VectorCopy( org, missile->currentOrigin);
101 	gi.linkentity( missile );
102 
103 	return missile;
104 }
105 
106 
107 //-----------------------------------------------------------------------------
WP_Stick(gentity_t * missile,trace_t * trace,float fudge_distance)108 void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance )
109 //-----------------------------------------------------------------------------
110 {
111 	vec3_t org, ang;
112 
113 	// not moving or rotating
114 	missile->s.pos.trType = TR_STATIONARY;
115 	VectorClear( missile->s.pos.trDelta );
116 	VectorClear( missile->s.apos.trDelta );
117 
118 	// so we don't stick into the wall
119 	VectorMA( trace->endpos, fudge_distance, trace->plane.normal, org );
120 	G_SetOrigin( missile, org );
121 
122 	vectoangles( trace->plane.normal, ang );
123 	G_SetAngles( missile, ang );
124 
125 	// I guess explode death wants me as the normal?
126 //	VectorCopy( trace->plane.normal, missile->pos1 );
127 	gi.linkentity( missile );
128 }
129 
130 // This version shares is in the thinkFunc format
131 //-----------------------------------------------------------------------------
WP_Explode(gentity_t * self)132 void WP_Explode( gentity_t *self )
133 //-----------------------------------------------------------------------------
134 {
135 	gentity_t	*attacker = self;
136 	vec3_t		wpFwd;
137 
138 	// stop chain reaction runaway loops
139 	self->takedamage = qfalse;
140 
141 	self->s.loopSound = 0;
142 
143 //	VectorCopy( self->currentOrigin, self->s.pos.trBase );
144 	AngleVectors( self->s.angles, wpFwd, NULL, NULL );
145 
146 	if ( self->fxID > 0 )
147 	{
148 		G_PlayEffect( self->fxID, self->currentOrigin, wpFwd );
149 	}
150 
151 	if ( self->owner )
152 	{
153 		attacker = self->owner;
154 	}
155 	else if ( self->activator )
156 	{
157 		attacker = self->activator;
158 	}
159 
160 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
161 	{
162 		G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, attacker, MOD_EXPLOSIVE_SPLASH );
163 	}
164 
165 	if ( self->target )
166 	{
167 		G_UseTargets( self, attacker );
168 	}
169 
170 	G_SetOrigin( self, self->currentOrigin );
171 
172 	self->nextthink = level.time + 50;
173 	self->e_ThinkFunc = thinkF_G_FreeEntity;
174 }
175 
176 // 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
177 //-----------------------------------------------------------------------------
WP_ExplosiveDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)178 void WP_ExplosiveDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
179 //-----------------------------------------------------------------------------
180 {
181 	self->enemy = attacker;
182 
183 	if ( attacker && !attacker->s.number )
184 	{
185 		// less damage when shot by player
186 		self->splashDamage /= 3;
187 		self->splashRadius /= 3;
188 	}
189 
190 	self->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead
191 
192 	WP_Explode( self );
193 }
194 
195 //---------------------------------------------------------
AddLeanOfs(const gentity_t * const ent,vec3_t point)196 void AddLeanOfs(const gentity_t *const ent, vec3_t point)
197 //---------------------------------------------------------
198 {
199 	if(ent->client)
200 	{
201 		if(ent->client->ps.leanofs)
202 		{
203 			vec3_t	right;
204 			//add leaning offset
205 			AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
206 			VectorMA(point, (float)ent->client->ps.leanofs, right, point);
207 		}
208 	}
209 }
210 
211 //---------------------------------------------------------
SubtractLeanOfs(const gentity_t * const ent,vec3_t point)212 void SubtractLeanOfs(const gentity_t *const ent, vec3_t point)
213 //---------------------------------------------------------
214 {
215 	if(ent->client)
216 	{
217 		if(ent->client->ps.leanofs)
218 		{
219 			vec3_t	right;
220 			//add leaning offset
221 			AngleVectors( ent->client->ps.viewangles, NULL, right, NULL );
222 			VectorMA( point, ent->client->ps.leanofs*-1, right, point );
223 		}
224 	}
225 }
226 
227 //---------------------------------------------------------
ViewHeightFix(const gentity_t * const ent)228 void ViewHeightFix(const gentity_t *const ent)
229 //---------------------------------------------------------
230 {
231 	//FIXME: this is hacky and doesn't need to be here.  Was only put here to make up
232 	//for the times a crouch anim would be used but not actually crouching.
233 	//When we start calcing eyepos (SPOT_HEAD) from the tag_eyes, we won't need
234 	//this (or viewheight at all?)
235 	if ( !ent )
236 		return;
237 
238 	if ( !ent->client || !ent->NPC )
239 		return;
240 
241 	if ( ent->client->ps.stats[STAT_HEALTH] <= 0 )
242 		return;//dead
243 
244 	if ( ent->client->ps.legsAnim == BOTH_CROUCH1IDLE || ent->client->ps.legsAnim == BOTH_CROUCH1 || ent->client->ps.legsAnim == BOTH_CROUCH1WALK )
245 	{
246 		if ( ent->client->ps.viewheight!=ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET )
247 			ent->client->ps.viewheight = ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET;
248 	}
249 	else
250 	{
251 		if ( ent->client->ps.viewheight!=ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET )
252 			ent->client->ps.viewheight = ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET;
253 	}
254 }
255 
W_AccuracyLoggableWeapon(int weapon,qboolean alt_fire,int mod)256 qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod )
257 {
258 	if ( mod != MOD_UNKNOWN )
259 	{
260 		switch( mod )
261 		{
262 		//standard weapons
263 		case MOD_BRYAR:
264 		case MOD_BRYAR_ALT:
265 		case MOD_BLASTER:
266 		case MOD_BLASTER_ALT:
267 		case MOD_DISRUPTOR:
268 		case MOD_SNIPER:
269 		case MOD_BOWCASTER:
270 		case MOD_BOWCASTER_ALT:
271 		case MOD_ROCKET:
272 		case MOD_ROCKET_ALT:
273 			return qtrue;
274 			break;
275 		//non-alt standard
276 		case MOD_REPEATER:
277 		case MOD_DEMP2:
278 		case MOD_FLECHETTE:
279 			return qtrue;
280 			break;
281 		//emplaced gun
282 		case MOD_EMPLACED:
283 			return qtrue;
284 			break;
285 		//atst
286 		case MOD_ENERGY:
287 		case MOD_EXPLOSIVE:
288 			if ( weapon == WP_ATST_MAIN || weapon == WP_ATST_SIDE )
289 			{
290 				return qtrue;
291 			}
292 			break;
293 		}
294 	}
295 	else if ( weapon != WP_NONE )
296 	{
297 		switch( weapon )
298 		{
299 		case WP_BRYAR_PISTOL:
300 		case WP_BLASTER:
301 		case WP_DISRUPTOR:
302 		case WP_BOWCASTER:
303 		case WP_ROCKET_LAUNCHER:
304 			return qtrue;
305 			break;
306 		//non-alt standard
307 		case WP_REPEATER:
308 		case WP_DEMP2:
309 		case WP_FLECHETTE:
310 			if ( !alt_fire )
311 			{
312 				return qtrue;
313 			}
314 			break;
315 		//emplaced gun
316 		case WP_EMPLACED_GUN:
317 			return qtrue;
318 			break;
319 		//atst
320 		case WP_ATST_MAIN:
321 		case WP_ATST_SIDE:
322 			return qtrue;
323 			break;
324 		}
325 	}
326 	return qfalse;
327 }
328 
329 /*
330 ===============
331 LogAccuracyHit
332 ===============
333 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)334 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
335 	if( !target->takedamage ) {
336 		return qfalse;
337 	}
338 
339 	if ( target == attacker ) {
340 		return qfalse;
341 	}
342 
343 	if( !target->client ) {
344 		return qfalse;
345 	}
346 
347 	if( !attacker->client ) {
348 		return qfalse;
349 	}
350 
351 	if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
352 		return qfalse;
353 	}
354 
355 	if ( OnSameTeam( target, attacker ) ) {
356 		return qfalse;
357 	}
358 
359 	return qtrue;
360 }
361 
362 //---------------------------------------------------------
CalcMuzzlePoint(gentity_t * const ent,vec3_t wpFwd,vec3_t right,vec3_t wpUp,vec3_t muzzlePoint,float lead_in)363 void CalcMuzzlePoint( gentity_t *const ent, vec3_t wpFwd, vec3_t right, vec3_t wpUp, vec3_t muzzlePoint, float lead_in )
364 //---------------------------------------------------------
365 {
366 	vec3_t		org;
367 	mdxaBone_t	boltMatrix;
368 
369 	if( !lead_in ) //&& ent->s.number != 0
370 	{//Not players or melee
371 		if( ent->client )
372 		{
373 			if ( ent->client->renderInfo.mPCalcTime >= level.time - FRAMETIME*2 )
374 			{//Our muzz point was calced no more than 2 frames ago
375 				VectorCopy(ent->client->renderInfo.muzzlePoint, muzzlePoint);
376 				return;
377 			}
378 		}
379 	}
380 
381 	VectorCopy( ent->currentOrigin, muzzlePoint );
382 
383 	switch( ent->s.weapon )
384 	{
385 	case WP_BRYAR_PISTOL:
386 		ViewHeightFix(ent);
387 		muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
388 		muzzlePoint[2] -= 16;
389 		VectorMA( muzzlePoint, 28, wpFwd, muzzlePoint );
390 		VectorMA( muzzlePoint, 6, wpVright, muzzlePoint );
391 		break;
392 
393 	case WP_ROCKET_LAUNCHER:
394 	case WP_THERMAL:
395 		ViewHeightFix(ent);
396 		muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
397 		muzzlePoint[2] -= 2;
398 		break;
399 
400 	case WP_BLASTER:
401 		ViewHeightFix(ent);
402 		muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
403 		muzzlePoint[2] -= 1;
404 		if ( ent->s.number == 0 )
405 			VectorMA( muzzlePoint, 12, wpFwd, muzzlePoint ); // player, don't set this any lower otherwise the projectile will impact immediately when your back is to a wall
406 		else
407 			VectorMA( muzzlePoint, 2, wpFwd, muzzlePoint ); // NPC, don't set too far wpFwd otherwise the projectile can go through doors
408 
409 		VectorMA( muzzlePoint, 1, wpVright, muzzlePoint );
410 		break;
411 
412 	case WP_SABER:
413 		if(ent->NPC!=NULL &&
414 			(ent->client->ps.torsoAnim == TORSO_WEAPONREADY2 ||
415 			ent->client->ps.torsoAnim == BOTH_ATTACK2))//Sniper pose
416 		{
417 			ViewHeightFix(ent);
418 			wpMuzzle[2] += ent->client->ps.viewheight;//By eyes
419 		}
420 		else
421 		{
422 			muzzlePoint[2] += 16;
423 		}
424 		VectorMA( muzzlePoint, 8, wpFwd, muzzlePoint );
425 		VectorMA( muzzlePoint, 16, wpVright, muzzlePoint );
426 		break;
427 
428 	case WP_BOT_LASER:
429 		muzzlePoint[2] -= 16;	//
430 		break;
431 	case WP_ATST_MAIN:
432 
433 		if (ent->count > 0)
434 		{
435 			ent->count = 0;
436 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
437 						ent->handLBolt,
438 						&boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
439 						NULL, ent->s.modelScale );
440 		}
441 		else
442 		{
443 			ent->count = 1;
444 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
445 						ent->handRBolt,
446 						&boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
447 						NULL, ent->s.modelScale );
448 		}
449 
450 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
451 
452 		VectorCopy(org,muzzlePoint);
453 
454 		break;
455 	}
456 
457 	AddLeanOfs(ent, muzzlePoint);
458 }
459 
460 //---------------------------------------------------------
FireWeapon(gentity_t * ent,qboolean alt_fire)461 void FireWeapon( gentity_t *ent, qboolean alt_fire )
462 //---------------------------------------------------------
463 {
464 	float alert = 256;
465 
466 	// track shots taken for accuracy tracking.
467 	ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
468 
469 	// set aiming directions
470 	if ( ent->s.weapon == WP_DISRUPTOR && alt_fire )
471 	{
472 		if ( ent->NPC )
473 		{
474 			//snipers must use the angles they actually did their shot trace with
475 			AngleVectors( ent->lastAngles, wpFwd, wpVright, wpUp );
476 		}
477 	}
478 	else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN )
479 	{
480 		vec3_t	delta1, enemy_org1, muzzle1;
481 		vec3_t	angleToEnemy1;
482 
483 		VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 );
484 
485 		if ( !ent->s.number )
486 		{//player driving an AT-ST
487 			//SIGH... because we can't anticipate alt-fire, must calc muzzle here and now
488 			mdxaBone_t		boltMatrix;
489 			int				bolt;
490 
491 			if ( ent->client->ps.weapon == WP_ATST_MAIN )
492 			{//FIXME: alt_fire should fire both barrels, but slower?
493 				if ( ent->alt_fire )
494 				{
495 					bolt = ent->handRBolt;
496 				}
497 				else
498 				{
499 					bolt = ent->handLBolt;
500 				}
501 			}
502 			else
503 			{// ATST SIDE weapons
504 				if ( ent->alt_fire )
505 				{
506 					if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) )
507 					{//don't have it!
508 						return;
509 					}
510 					bolt = ent->genericBolt2;
511 				}
512 				else
513 				{
514 					if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) )
515 					{//don't have it!
516 						return;
517 					}
518 					bolt = ent->genericBolt1;
519 				}
520 			}
521 
522 			vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0};
523 			if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw )
524 			{
525 				yawOnlyAngles[YAW] = ent->client->ps.legsYaw;
526 			}
527 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale );
528 
529 			// work the matrix axis stuff into the original axis and origins used.
530 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint );
531 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir );
532 			ent->client->renderInfo.mPCalcTime = level.time;
533 
534 			AngleVectors( ent->client->ps.viewangles, wpFwd, wpVright, wpUp );
535 			//CalcMuzzlePoint( ent, wpFwd, vright, wpUp, wpMuzzle, 0 );
536 		}
537 		else if ( !ent->enemy )
538 		{//an NPC with no enemy to auto-aim at
539 			VectorCopy( ent->client->renderInfo.muzzleDir, wpFwd );
540 		}
541 		else
542 		{//NPC, auto-aim at enemy
543 			CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
544 
545 			VectorSubtract (enemy_org1, muzzle1, delta1);
546 
547 			vectoangles ( delta1, angleToEnemy1 );
548 			AngleVectors (angleToEnemy1, wpFwd, wpVright, wpUp);
549 		}
550 	}
551 	else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy )
552 	{
553 		vec3_t	delta1, enemy_org1, muzzle1;
554 		vec3_t	angleToEnemy1;
555 
556 		CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
557 		CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 );
558 
559 		VectorSubtract (enemy_org1, muzzle1, delta1);
560 
561 		vectoangles ( delta1, angleToEnemy1 );
562 		AngleVectors (angleToEnemy1, wpFwd, wpVright, wpUp);
563 	}
564 	else
565 	{
566 		AngleVectors( ent->client->ps.viewangles, wpFwd, wpVright, wpUp );
567 	}
568 
569 	ent->alt_fire = alt_fire;
570 	CalcMuzzlePoint ( ent, wpFwd, wpVright, wpUp, wpMuzzle , 0);
571 
572 	// fire the specific weapon
573 	switch( ent->s.weapon )
574 	{
575 	// Player weapons
576 	//-----------------
577 	case WP_SABER:
578 		return;
579 		break;
580 
581 	case WP_BRYAR_PISTOL:
582 		WP_FireBryarPistol( ent, alt_fire );
583 		break;
584 
585 	case WP_BLASTER:
586 		WP_FireBlaster( ent, alt_fire );
587 		break;
588 
589 	case WP_DISRUPTOR:
590 		alert = 50; // if you want it to alert enemies, remove this
591 		WP_FireDisruptor( ent, alt_fire );
592 		break;
593 
594 	case WP_BOWCASTER:
595 		WP_FireBowcaster( ent, alt_fire );
596 		break;
597 
598 	case WP_REPEATER:
599 		WP_FireRepeater( ent, alt_fire );
600 		break;
601 
602 	case WP_DEMP2:
603 		WP_FireDEMP2( ent, alt_fire );
604 		break;
605 
606 	case WP_FLECHETTE:
607 		WP_FireFlechette( ent, alt_fire );
608 		break;
609 
610 	case WP_ROCKET_LAUNCHER:
611 		WP_FireRocket( ent, alt_fire );
612 		break;
613 
614 	case WP_THERMAL:
615 		WP_FireThermalDetonator( ent, alt_fire );
616 		break;
617 
618 	case WP_TRIP_MINE:
619 		alert = 0; // if you want it to alert enemies, remove this
620 		WP_PlaceLaserTrap( ent, alt_fire );
621 		break;
622 
623 	case WP_DET_PACK:
624 		alert = 0; // if you want it to alert enemies, remove this
625 		WP_FireDetPack( ent, alt_fire );
626 		break;
627 
628 	case WP_BOT_LASER:
629 		WP_BotLaser( ent );
630 		break;
631 
632 	case WP_EMPLACED_GUN:
633 		// doesn't care about whether it's alt-fire or not.  We can do an alt-fire if needed
634 		WP_EmplacedFire( ent );
635 		break;
636 
637 	case WP_MELEE:
638 		alert = 0; // if you want it to alert enemies, remove this
639 		WP_Melee( ent );
640 		break;
641 
642 	case WP_ATST_MAIN:
643 		WP_ATSTMainFire( ent );
644 		break;
645 
646 	case WP_ATST_SIDE:
647 
648 		// TEMP
649 		if ( alt_fire )
650 		{
651 //			WP_FireRocket( ent, qfalse );
652 			WP_ATSTSideAltFire(ent);
653 		}
654 		else
655 		{
656 			if ( ent->s.number == 0 && ent->client->ps.vehicleModel )
657 			{
658 				WP_ATSTMainFire( ent );
659 			}
660 			else
661 			{
662 				WP_ATSTSideFire(ent);
663 			}
664 		}
665 		break;
666 
667 	case WP_TIE_FIGHTER:
668 		// TEMP
669 		WP_EmplacedFire( ent );
670 		break;
671 
672 	case WP_RAPID_FIRE_CONC:
673 		// TEMP
674 		if ( alt_fire )
675 		{
676 			WP_FireRepeater( ent, alt_fire );
677 		}
678 		else
679 		{
680 			WP_EmplacedFire( ent );
681 		}
682 		break;
683 
684 	case WP_STUN_BATON:
685 		WP_FireStunBaton( ent, alt_fire );
686 		break;
687 
688 	case WP_BLASTER_PISTOL: // enemy version
689 		WP_FireBryarPistol( ent, qfalse ); // never an alt-fire?
690 		break;
691 
692 	default:
693 		return;
694 		break;
695 	}
696 
697 	if ( !ent->s.number )
698 	{
699 		if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) )
700 		{//these can fire multiple shots, count them individually within the firing functions
701 		}
702 		else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) )
703 		{
704 			ent->client->sess.missionStats.shotsFired++;
705 		}
706 	}
707 	// We should probably just use this as a default behavior, in special cases, just set alert to false.
708 	if ( ent->s.number == 0 && alert > 0 )
709 	{
710 		AddSoundEvent( ent, wpMuzzle, alert, AEL_DISCOVERED );
711 		AddSightEvent( ent, wpMuzzle, alert*2, AEL_DISCOVERED, 20 );
712 	}
713 }
714 
715 // spawnflag
716 #define	EMPLACED_INACTIVE	1
717 #define EMPLACED_FACING		2
718 #define EMPLACED_VULNERABLE	4
719 
720 //----------------------------------------------------------
721 
722 /*QUAKED emplaced_gun (0 0 1) (-24 -24 0) (24 24 64) INACTIVE FACING VULNERABLE
723 
724  INACTIVE cannot be used until used by a target_activate
725  FACING - player must be facing relatively in the same direction as the gun in order to use it
726  VULNERABLE - allow the gun to take damage
727 
728  count - how much ammo to give this gun ( default 999 )
729  health - how much damage the gun can take before it blows ( default 250 )
730  delay - ONLY AFFECTS NPCs - time between shots ( default 200 on hardest setting )
731  wait - ONLY AFFECTS NPCs - time between bursts ( default 800 on hardest setting )
732  splashdamage - how much damage a blowing up gun deals ( default 80 )
733  splashradius - radius for exploding damage ( default 128 )
734 */
735 
736 //----------------------------------------------------------
emplaced_gun_use(gentity_t * self,gentity_t * other,gentity_t * activator)737 void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator )
738 {
739 	vec3_t fwd1, fwd2;
740 
741 	if ( self->health <= 0 )
742 	{
743 		// can't use a dead gun.
744 		return;
745 	}
746 
747 	if ( self->svFlags & SVF_INACTIVE )
748 	{
749 		return; // can't use inactive gun
750 	}
751 
752 	if ( !activator->client )
753 	{
754 		return; // only a client can use it.
755 	}
756 
757 	if ( self->activator )
758 	{
759 		// someone is already in the gun.
760 		return;
761 	}
762 
763 	// We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing.
764 	if ( self->spawnflags & EMPLACED_FACING )
765 	{
766 		// Let's get some direction vectors for the users
767 		AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL );
768 
769 		// Get the guns direction vector
770 		AngleVectors( self->pos1, fwd2, NULL, NULL );
771 
772 		float dot = DotProduct( fwd1, fwd2 );
773 
774 		// Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it.
775 		if ( dot < 0.0f )
776 		{
777 			return;
778 		}
779 	}
780 
781 	// don't allow using it again for half a second
782 	if ( self->delay + 500 < level.time )
783 	{
784 		int	oldWeapon = activator->s.weapon;
785 
786 		if ( oldWeapon == WP_SABER )
787 		{
788 			self->alt_fire = activator->client->ps.saberActive;
789 		}
790 
791 		// swap the users weapon with the emplaced gun and add the ammo the gun has to the player
792 		activator->client->ps.weapon = self->s.weapon;
793 		Add_Ammo( activator, WP_EMPLACED_GUN, self->count );
794 		activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN );
795 
796 		// Allow us to point from one to the other
797 		activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it.
798 		self->activator = activator;
799 
800 		if ( activator->weaponModel >= 0 )
801 		{
802 			// rip that gun out of their hands....
803 			gi.G2API_RemoveGhoul2Model( activator->ghoul2, activator->weaponModel );
804 			activator->weaponModel = -1;
805 		}
806 
807 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
808 		if ( activator->NPC )
809 		{
810 			if ( activator->weaponModel >= 0 )
811 			{
812 				// rip that gun out of their hands....
813 				gi.G2API_RemoveGhoul2Model( activator->ghoul2, activator->weaponModel );
814 				activator->weaponModel = -1;
815 
816 // Doesn't work?
817 //				activator->maxs[2] += 35; // make it so you can potentially shoot their head
818 //				activator->s.radius += 10; // increase ghoul radius so we can collide with the enemy more accurately
819 //				gi.linkentity( activator );
820 			}
821 
822 			ChangeWeapon( activator, WP_EMPLACED_GUN );
823 		}
824 		else if ( activator->s.number == 0 )
825 		{
826 			// we don't want for it to draw the weapon select stuff
827 			cg.weaponSelect = WP_EMPLACED_GUN;
828 			CG_CenterPrint( "@INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 );
829 		}
830 		// Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid
831 		if ( self->nextTrain )
832 		{//you never know
833 			G_FreeEntity( self->nextTrain );
834 		}
835 		self->nextTrain = G_Spawn();
836 		//self->nextTrain->classname = "emp_placeholder";
837 		self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs?
838 		G_SetOrigin( self->nextTrain, activator->client->ps.origin );
839 		VectorCopy( activator->mins, self->nextTrain->mins );
840 		VectorCopy( activator->maxs, self->nextTrain->maxs );
841 		gi.linkentity( self->nextTrain );
842 
843 		//need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox
844 		VectorSet( activator->mins, -24, -24, -24 );
845 		VectorSet( activator->maxs, 24, 24, 40 );
846 
847 		// Move the activator into the center of the gun.  For NPC's the only way the can get out of the gun is to die.
848 		VectorCopy( self->s.origin, activator->client->ps.origin );
849 		activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor
850 		gi.linkentity( activator );
851 
852 		// the gun will track which weapon we used to have
853 		self->s.weapon = oldWeapon;
854 
855 		// Lock the player
856 		activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON;
857 		activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it.
858 		self->activator = activator;
859 		self->delay = level.time; // can't disconnect from the thing for half a second
860 
861 		// Let the gun be considered an enemy
862 		self->svFlags |= SVF_NONNPC_ENEMY;
863 		self->noDamageTeam = activator->client->playerTeam;
864 
865 		// FIXME: don't do this, we'll try and actually put the player in this beast
866 		// move the player to the center of the gun
867 //		activator->contents = 0;
868 //		VectorCopy( self->currentOrigin, activator->client->ps.origin );
869 
870 		SetClientViewAngle( activator, self->pos1 );
871 
872 		//FIXME: should really wait a bit after spawn and get this just once?
873 		self->waypoint = NAV_FindClosestWaypointForEnt( self, WAYPOINT_NONE );
874 #ifdef _DEBUG
875 		if ( self->waypoint == -1 )
876 		{
877 			gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) );
878 		}
879 #endif
880 
881 		G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" ));
882 	}
883 }
884 
885 //----------------------------------------------------------
emplaced_gun_pain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,vec3_t point,int damage,int mod,int hitLoc)886 void emplaced_gun_pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc )
887 {
888 	if ( self->health <= 0 )
889 	{
890 		// play pain effect?
891 	}
892 	else
893 	{
894 		if ( self->paintarget )
895 		{
896 			G_UseTargets2( self, self->activator, self->paintarget );
897 		}
898 
899 		// Don't do script if dead
900 		G_ActivateBehavior( self, BSET_PAIN );
901 	}
902 }
903 
904 //----------------------------------------------------------
emplaced_blow(gentity_t * ent)905 void emplaced_blow( gentity_t *ent )
906 {
907 	ent->e_DieFunc = dieF_NULL;
908 	emplaced_gun_die( ent, ent->lastEnemy, ent->lastEnemy, 0, MOD_UNKNOWN );
909 }
910 
911 //----------------------------------------------------------
emplaced_gun_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)912 void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
913 {
914 	vec3_t org;
915 
916 	// turn off any firing animations it may have been doing
917 	self->s.frame = self->startFrame = self->endFrame = 0;
918 	self->svFlags &= ~SVF_ANIMATING;
919 
920 	self->health = 0;
921 //	self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon
922 
923 	self->takedamage = qfalse;
924 	self->lastEnemy = attacker;
925 
926 	// we defer explosion so the player has time to get out
927 	if ( self->e_DieFunc )
928 	{
929 		self->e_ThinkFunc = thinkF_emplaced_blow;
930 		self->nextthink = level.time + 3000; // don't blow for a couple of seconds
931 		return;
932 	}
933 
934 	if ( self->activator && self->activator->client )
935 	{
936 		if ( self->activator->NPC )
937 		{
938 			vec3_t right;
939 
940 			// radius damage seems to throw them, but add an extra bit to throw them away from the weapon
941 			AngleVectors( self->currentAngles, NULL, right, NULL );
942 			VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity );
943 			self->activator->client->ps.velocity[2] = -100;
944 
945 			// kill them
946 			self->activator->health = 0;
947 			self->activator->client->ps.stats[STAT_HEALTH] = 0;
948 		}
949 
950 		// kill the players emplaced ammo, cheesy way to keep the gun from firing
951 		self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0;
952 	}
953 
954 	self->e_PainFunc = painF_NULL;
955 	self->e_ThinkFunc = thinkF_NULL;
956 
957 	if ( self->target )
958 	{
959 		G_UseTargets( self, attacker );
960 	}
961 
962 	G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
963 
964 	// when the gun is dead, add some ugliness to it.
965 	vec3_t ugly;
966 
967 	ugly[YAW] = 4;
968 	ugly[PITCH] = self->lastAngles[PITCH] * 0.8f + Q_flrand(-1.0f, 1.0f) * 6;
969 	ugly[ROLL] = Q_flrand(-1.0f, 1.0f) * 7;
970 	gi.G2API_SetBoneAnglesIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, ugly, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
971 
972 	VectorCopy( self->currentOrigin,  org );
973 	org[2] += 20;
974 
975 	G_PlayEffect( "emplaced/explode", org );
976 
977 	// create some persistent smoke by using a dynamically created fx runner
978 	gentity_t *ent = G_Spawn();
979 
980 	if ( ent )
981 	{
982 		ent->delay = 200;
983 		ent->random = 100;
984 
985 		ent->fxID = G_EffectIndex( "emplaced/dead_smoke" );
986 
987 		ent->e_ThinkFunc = thinkF_fx_runner_think;
988 		ent->nextthink = level.time + 50;
989 
990 		// move up above the gun origin
991 		VectorCopy( self->currentOrigin, org );
992 		org[2] += 35;
993 		G_SetOrigin( ent, org );
994 		VectorCopy( org, ent->s.origin );
995 
996 		VectorSet( ent->s.angles, -90, 0, 0 ); // up
997 		G_SetAngles( ent, ent->s.angles );
998 
999 		gi.linkentity( ent );
1000 	}
1001 
1002 	G_ActivateBehavior( self, BSET_DEATH );
1003 }
1004 
1005 //----------------------------------------------------------
SP_emplaced_gun(gentity_t * ent)1006 void SP_emplaced_gun( gentity_t *ent )
1007 {
1008 	char name[] = "models/map_objects/imp_mine/turret_chair.glm";
1009 
1010 	ent->svFlags |= SVF_PLAYER_USABLE;
1011 	ent->contents = CONTENTS_BODY;//CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP;//CONTENTS_SOLID;
1012 
1013 	if ( ent->spawnflags & EMPLACED_INACTIVE )
1014 	{
1015 		ent->svFlags |= SVF_INACTIVE;
1016 	}
1017 
1018 	VectorSet( ent->mins, -30, -30, -5 );
1019 	VectorSet( ent->maxs, 30, 30, 60 );
1020 
1021 	ent->takedamage = qtrue;
1022 
1023 	if ( !( ent->spawnflags & EMPLACED_VULNERABLE ))
1024 	{
1025 		ent->flags |= FL_GODMODE;
1026 	}
1027 
1028 	ent->s.radius = 110;
1029 	ent->spawnflags |= 4; // deadsolid
1030 
1031 	ent->e_ThinkFunc = thinkF_NULL;
1032 	ent->e_PainFunc = painF_emplaced_gun_pain;
1033 	ent->e_DieFunc  = dieF_emplaced_gun_die;
1034 
1035 	G_EffectIndex( "emplaced/explode" );
1036 	G_EffectIndex( "emplaced/dead_smoke" );
1037 
1038 	G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" );
1039 	G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" );
1040 	G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" );
1041 
1042 	// Set up our defaults and override with custom amounts as necessary
1043 	G_SpawnInt( "count", "999", &ent->count );
1044 	G_SpawnInt( "health", "250", &ent->health );
1045 	G_SpawnInt( "splashDamage", "80", &ent->splashDamage );
1046 	G_SpawnInt( "splashRadius", "128", &ent->splashRadius );
1047 	G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!!
1048 	G_SpawnFloat( "wait", "800", &ent->wait );
1049 
1050 	ent->max_health = ent->health;
1051 	ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud
1052 
1053 	ent->s.modelindex = G_ModelIndex( name );
1054 	ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
1055 
1056 	// Activate our tags and bones
1057 	ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[0], "*seat" );
1058 	ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[0], "*flash01" );
1059 	ent->handRBolt = gi.G2API_AddBolt( &ent->ghoul2[0], "*flash02" );
1060 	ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "base_bone", qtrue );
1061 	ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[0], "swivel_bone", qtrue );
1062 	gi.G2API_SetBoneAngles( &ent->ghoul2[0], "swivel_bone", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0);
1063 
1064 	RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));
1065 	ent->s.weapon = WP_EMPLACED_GUN;
1066 
1067 	G_SetOrigin( ent, ent->s.origin );
1068 	G_SetAngles( ent, ent->s.angles );
1069 	VectorCopy( ent->s.angles, ent->lastAngles );
1070 
1071 	// store base angles for later
1072 	VectorCopy( ent->s.angles, ent->pos1 );
1073 
1074 	ent->e_UseFunc = useF_emplaced_gun_use;
1075 
1076 	gi.linkentity (ent);
1077 }
1078