1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6 
7 This file is part of the OpenJK source code.
8 
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 
23 #include "g_local.h"
24 #include "g_functions.h"
25 #include "b_local.h"
26 #include "../cgame/cg_local.h"
27 
28 extern	cvar_t	*g_spskill;
29 
30 void G_SetEnemy( gentity_t *self, gentity_t *enemy );
31 void finish_spawning_turret( gentity_t *base );
32 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
33 //special routine for tracking angles between client and server -rww
34 void turret_SetBoneAngles(gentity_t *ent, const char *bone, const vec3_t angles);
35 
36 #define	ARM_ANGLE_RANGE		60
37 #define	HEAD_ANGLE_RANGE	90
38 
39 #define SPF_TURRETG2_TURBO		4
40 #define SPF_TURRETG2_LEAD_ENEMY	8
41 
42 #define name "models/map_objects/imp_mine/turret_canon.glm"
43 #define name2 "models/map_objects/imp_mine/turret_damage.md3"
44 #define name3 "models/map_objects/wedge/laser_cannon_model.glm"
45 
46 //------------------------------------------------------------------------------------------------------------
TurretPain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)47 void TurretPain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
48 //------------------------------------------------------------------------------------------------------------
49 {
50 	vec3_t dir;
51 
52 	VectorSubtract( point, self->currentOrigin, dir );
53 	VectorNormalize( dir );
54 
55 	if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
56 	{
57 		// DEMP2 makes the turret stop shooting for a bit..and does extra feedback
58 		self->attackDebounceTime = level.time + 800 + Q_flrand(0.0f, 1.0f) * 500;
59 		G_PlayEffect( "sparks/spark_exp_nosnd", point, dir );
60 	}
61 
62 	if ( !self->enemy )
63 	{//react to being hit
64 		G_SetEnemy( self, attacker );
65 	}
66 
67 	G_PlayEffect( "sparks/spark_exp_nosnd", point, dir );
68 }
69 
70 //------------------------------------------------------------------------------------------------------------
turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)71 void turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
72 //------------------------------------------------------------------------------------------------------------
73 {
74 	vec3_t	forward = { 0,0,-1 }, pos;
75 
76 	// Turn off the thinking of the base & use it's targets
77 	self->e_ThinkFunc = thinkF_NULL;
78 	self->e_UseFunc = useF_NULL;
79 
80 	// clear my data
81 	self->e_DieFunc  = dieF_NULL;
82 	self->takedamage = qfalse;
83 	self->health = 0;
84 	self->s.loopSound = 0;
85 
86 	// hack the effect angle so that explode death can orient the effect properly
87 	if ( self->spawnflags & 2 )
88 	{
89 		VectorSet( forward, 0, 0, 1 );
90 	}
91 
92 //	VectorCopy( self->currentOrigin, self->s.pos.trBase );
93 
94 	if ( self->spawnflags & SPF_TURRETG2_TURBO )
95 	{
96 		G_PlayEffect( G_EffectIndex( "explosions/fighter_explosion2" ), self->currentOrigin, self->currentAngles );
97 	}
98 	else
99 	{
100 		if ( self->fxID > 0 )
101 		{
102 			VectorMA( self->currentOrigin, 12, forward, pos );
103 			G_PlayEffect( self->fxID, pos, forward );
104 		}
105 	}
106 
107 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
108 	{
109 		G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, attacker, MOD_UNKNOWN );
110 	}
111 
112 	if ( self->s.eFlags & EF_SHADER_ANIM )
113 	{
114 		self->s.frame = 1; // black
115 	}
116 
117 	self->s.weapon = 0; // crosshair code uses this to mark crosshair red
118 
119 	if ( self->s.modelindex2 )
120 	{
121 		// switch to damage model if we should
122 		self->s.modelindex = self->s.modelindex2;
123 
124 		VectorCopy( self->currentAngles, self->s.apos.trBase );
125 		VectorClear( self->s.apos.trDelta );
126 
127 		if ( self->target )
128 		{
129 			G_UseTargets( self, attacker );
130 		}
131 	}
132 	else
133 	{
134 		ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
135 	}
136 }
137 
138 //start an animation on model_root both server side and client side
TurboLaser_SetBoneAnim(gentity_t * eweb,int startFrame,int endFrame)139 void TurboLaser_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame)
140 {
141 	//set info on the entity so it knows to start the anim on the client next snapshot.
142 	//eweb->s.eFlags |= EF_G2ANIMATING;
143 
144 	if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame)
145 	{ //already playing this anim, let's flag it to restart
146 		//eweb->s.torsoFlip = !eweb->s.torsoFlip;
147 	}
148 	else
149 	{
150 		eweb->s.torsoAnim = startFrame;
151 		eweb->s.legsAnim = endFrame;
152 	}
153 
154 	//now set the animation on the server ghoul2 instance.
155 	assert(&eweb->ghoul2[0]);
156 	gi.G2API_SetBoneAnim(&eweb->ghoul2[0], "model_root", startFrame, endFrame,
157 		(BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100);
158 }
159 
160 #define START_DIS 15
161 
162 extern void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir );
163 
164 //----------------------------------------------------------------
turret_fire(gentity_t * ent,vec3_t start,vec3_t dir)165 static void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
166 //----------------------------------------------------------------
167 {
168 	vec3_t		org, ang;
169 	gentity_t	*bolt;
170 
171 	if ( (gi.pointcontents( start, ent->s.number )&MASK_SHOT) )
172 	{
173 		return;
174 	}
175 
176 	VectorMA( start, -START_DIS, dir, org ); // dumb....
177 
178 	if ( ent->random )
179 	{
180 		vectoangles( dir, ang );
181 		ang[PITCH] += Q_flrand( -ent->random, ent->random );
182 		ang[YAW] += Q_flrand( -ent->random, ent->random );
183 		AngleVectors( ang, dir, NULL, NULL );
184 	}
185 
186 	vectoangles(dir, ang);
187 
188 	if ( (ent->spawnflags&SPF_TURRETG2_TURBO) )
189 	{
190 		//muzzle flash
191 		G_PlayEffect( G_EffectIndex( "turret/turb_muzzle_flash" ), org, ang );
192 		G_SoundOnEnt( ent, CHAN_LESS_ATTEN, "sound/vehicles/weapons/turbolaser/fire1" );
193 
194 		WP_FireTurboLaserMissile( ent, start, dir );
195 		if ( ent->alt_fire )
196 		{
197 			TurboLaser_SetBoneAnim( ent, 2, 3 );
198 		}
199 		else
200 		{
201 			TurboLaser_SetBoneAnim( ent, 0, 1 );
202 		}
203 	}
204 	else
205 	{
206 		G_PlayEffect( "blaster/muzzle_flash", org, dir );
207 
208 		bolt = G_Spawn();
209 
210 		bolt->classname = "turret_proj";
211 		bolt->nextthink = level.time + 10000;
212 		bolt->e_ThinkFunc = thinkF_G_FreeEntity;
213 		bolt->s.eType = ET_MISSILE;
214 		bolt->s.weapon = WP_BLASTER;
215 		bolt->owner = ent;
216 		bolt->damage = ent->damage;
217 		bolt->dflags = DAMAGE_NO_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS;		// Don't push them around, or else we are constantly re-aiming
218 		bolt->splashDamage = 0;
219 		bolt->splashRadius = 0;
220 		bolt->methodOfDeath = MOD_ENERGY;
221 		bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
222 		bolt->trigger_formation = qfalse;		// don't draw tail on first frame
223 
224 		VectorSet( bolt->maxs, 1.5, 1.5, 1.5 );
225 		VectorScale( bolt->maxs, -1, bolt->mins );
226 		bolt->s.pos.trType = TR_LINEAR;
227 		bolt->s.pos.trTime = level.time;
228 		VectorCopy( start, bolt->s.pos.trBase );
229 		VectorScale( dir, 1100, bolt->s.pos.trDelta );
230 		SnapVector( bolt->s.pos.trDelta );		// save net bandwidth
231 		VectorCopy( start, bolt->currentOrigin);
232 	}
233 }
234 
235 //-----------------------------------------------------
turret_head_think(gentity_t * self)236 void turret_head_think( gentity_t *self )
237 //-----------------------------------------------------
238 {
239 	// if it's time to fire and we have an enemy, then gun 'em down!  pushDebounce time controls next fire time
240 	if ( self->enemy && self->pushDebounceTime < level.time && self->attackDebounceTime < level.time )
241 	{
242 		// set up our next fire time
243 		self->pushDebounceTime = level.time + self->wait;
244 
245 		vec3_t		fwd, org;
246 		mdxaBone_t	boltMatrix;
247 
248 		// Getting the flash bolt here
249 		gi.G2API_GetBoltMatrix( self->ghoul2,
250 					0,
251 					(self->spawnflags&SPF_TURRETG2_TURBO) ? ( (self->alt_fire ? gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle2" ) : gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle1" )) ) : gi.G2API_AddBolt( &self->ghoul2[0], "*flash03" ),
252 					&boltMatrix,
253 					self->currentAngles,
254 					self->currentOrigin,
255 					level.time,
256 					NULL,
257 					self->modelScale );
258 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
259 		{
260 			self->alt_fire = (qboolean)!self->alt_fire;
261 		}
262 
263 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
264 
265 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
266 		{
267 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, fwd );
268 		}
269 		else
270 		{
271 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
272 		}
273 
274 		VectorMA( org, START_DIS, fwd, org );
275 
276 		turret_fire( self, org, fwd );
277 		self->fly_sound_debounce_time = level.time;//used as lastShotTime
278 	}
279 }
280 
281 //-----------------------------------------------------
turret_aim(gentity_t * self)282 static void turret_aim( gentity_t *self )
283 //-----------------------------------------------------
284 {
285 	vec3_t	enemyDir, org, org2;
286 	vec3_t	desiredAngles, setAngle;
287 	float	diffYaw = 0.0f, diffPitch = 0.0f;
288 	float	maxYawSpeed		= ( self->spawnflags & SPF_TURRETG2_TURBO ) ? 30.0f : 14.0f;
289 	float	maxPitchSpeed	= ( self->spawnflags & SPF_TURRETG2_TURBO ) ? 15.0f : 3.0f;
290 
291 	// move our gun base yaw to where we should be at this time....
292 	EvaluateTrajectory( &self->s.apos, level.time, self->currentAngles );
293 	self->currentAngles[YAW] = AngleNormalize360( self->currentAngles[YAW] );
294 	self->speed = AngleNormalize360( self->speed );
295 
296 	if ( self->enemy )
297 	{
298 		// ...then we'll calculate what new aim adjustments we should attempt to make this frame
299 		// Aim at enemy
300 		if ( self->enemy->client )
301 		{
302 			VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
303 		}
304 		else
305 		{
306 			VectorCopy( self->enemy->currentOrigin, org );
307 		}
308 		if ( self->spawnflags & 2 )
309 		{
310 			org[2] -= 15;
311 		}
312 		else
313 		{
314 			org[2] -= 5;
315 		}
316 		mdxaBone_t	boltMatrix;
317 
318 		// Getting the "eye" here
319 		gi.G2API_GetBoltMatrix( self->ghoul2,
320 					0,
321 					(self->spawnflags&SPF_TURRETG2_TURBO) ? ( (self->alt_fire ? gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle2" ) : gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle1" )) ) : gi.G2API_AddBolt( &self->ghoul2[0], "*flash03" ),
322 					&boltMatrix,
323 					self->currentAngles,
324 					self->s.origin,
325 					level.time,
326 					NULL,
327 					self->modelScale );
328 
329 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
330 
331 		VectorSubtract( org, org2, enemyDir );
332 		vectoangles( enemyDir, desiredAngles );
333 
334 		diffYaw = AngleSubtract( self->currentAngles[YAW], desiredAngles[YAW] );
335 		diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] );
336 	}
337 	else
338 	{
339 		// no enemy, so make us slowly sweep back and forth as if searching for a new one
340 //		diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f;	// don't do this for now since it can make it go into walls.
341 	}
342 
343 	if ( diffYaw )
344 	{
345 		// cap max speed....
346 		if ( fabs(diffYaw) > maxYawSpeed )
347 		{
348 			diffYaw = ( diffYaw >= 0 ? maxYawSpeed : -maxYawSpeed );
349 		}
350 
351 		// ...then set up our desired yaw
352 		VectorSet( setAngle, 0.0f, diffYaw, 0.0f );
353 
354 		VectorCopy( self->currentAngles, self->s.apos.trBase );
355 		VectorScale( setAngle,- 5, self->s.apos.trDelta );
356 		self->s.apos.trTime = level.time;
357 		self->s.apos.trType = TR_LINEAR;
358 	}
359 
360 	if ( diffPitch )
361 	{
362 		if ( fabs(diffPitch) > maxPitchSpeed )
363 		{
364 			// cap max speed
365 			self->speed += (diffPitch > 0.0f) ? -maxPitchSpeed : maxPitchSpeed;
366 		}
367 		else
368 		{
369 			// small enough, so just add half the diff so we smooth out the stopping
370 			self->speed -= ( diffPitch );//desiredAngles[PITCH];
371 		}
372 
373 		// Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit?
374 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
375 		{
376 			if ( self->spawnflags & 2 )
377 			{
378 				VectorSet( desiredAngles, 0.0f, 0.0f, -self->speed );
379 			}
380 			else
381 			{
382 				VectorSet( desiredAngles, 0.0f, 0.0f, self->speed );
383 			}
384 			turret_SetBoneAngles(self, "pitch", desiredAngles);
385 		}
386 		else
387 		{
388 			// Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit?
389 			if ( self->spawnflags & 2 )
390 			{
391 				VectorSet( desiredAngles, self->speed, 0.0f, 0.0f );
392 			}
393 			else
394 			{
395 				VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f );
396 			}
397 			gi.G2API_SetBoneAngles( &self->ghoul2[0], "Bone_body", desiredAngles,
398 							BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time );
399 		}
400 	}
401 
402 	if ( diffYaw || diffPitch )
403 	{
404 		self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
405 	}
406 	else
407 	{
408 		self->s.loopSound = 0;
409 	}
410 }
411 
412 //-----------------------------------------------------
turret_turnoff(gentity_t * self)413 static void turret_turnoff( gentity_t *self )
414 //-----------------------------------------------------
415 {
416 	if ( self->enemy == NULL )
417 	{
418 		// we don't need to turnoff
419 		return;
420 	}
421 
422 	if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
423 	{
424 		TurboLaser_SetBoneAnim( self, 4, 5 );
425 	}
426 
427 	// shut-down sound
428 	G_Sound( self, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
429 
430 	// make turret play ping sound for 5 seconds
431 	self->aimDebounceTime = level.time + 5000;
432 
433 	// Clear enemy
434 	self->enemy = NULL;
435 }
436 
437 //-----------------------------------------------------
turret_find_enemies(gentity_t * self)438 static qboolean turret_find_enemies( gentity_t *self )
439 //-----------------------------------------------------
440 {
441 	// HACK for t2_wedge!!!
442 	if ( self->spawnflags & SPF_TURRETG2_TURBO )
443 		return qfalse;
444 
445 	qboolean	found = qfalse;
446 	int			count;
447 	float		bestDist = self->radius * self->radius;
448 	float		enemyDist;
449 	vec3_t		enemyDir, org, org2;
450 	gentity_t	*entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
451 
452 	if ( self->aimDebounceTime > level.time ) // time since we've been shut off
453 	{
454 		// We were active and alert, i.e. had an enemy in the last 3 secs
455 		if ( self->painDebounceTime < level.time )
456 		{
457 			G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" ));
458 			self->painDebounceTime = level.time + 1000;
459 		}
460 	}
461 
462 	VectorCopy( self->currentOrigin, org2 );
463 	if ( self->spawnflags & 2 )
464 	{
465 		org2[2] += 20;
466 	}
467 	else
468 	{
469 		org2[2] -= 20;
470 	}
471 
472 	count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
473 
474 	for ( int i = 0; i < count; i++ )
475 	{
476 		target = entity_list[i];
477 
478 		if ( !target->client )
479 		{
480 			// only attack clients
481 			continue;
482 		}
483 		if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
484 		{
485 			continue;
486 		}
487 		if ( target->client->playerTeam == self->noDamageTeam )
488 		{
489 			// A bot we don't want to shoot
490 			continue;
491 		}
492 		if ( !gi.inPVS( org2, target->currentOrigin ))
493 		{
494 			continue;
495 		}
496 
497 		VectorCopy( target->client->renderInfo.eyePoint, org );
498 
499 		if ( self->spawnflags & 2 )
500 		{
501 			org[2] -= 15;
502 		}
503 		else
504 		{
505 			org[2] += 5;
506 		}
507 
508 		trace_t	tr;
509 		gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
510 
511 		if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
512 		{
513 			// Only acquire if have a clear shot, Is it in range and closer than our best?
514 			VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir );
515 			enemyDist = VectorLengthSquared( enemyDir );
516 
517 			if ( enemyDist < bestDist )// all things equal, keep current
518 			{
519 				if ( self->attackDebounceTime < level.time )
520 				{
521 					// We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
522 					G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" ));
523 
524 					// Wind up turrets for a bit
525 					self->attackDebounceTime = level.time + 1400;
526 				}
527 
528 				bestTarget = target;
529 				bestDist = enemyDist;
530 				found = qtrue;
531 			}
532 		}
533 	}
534 
535 	if ( found )
536 	{
537 		if ( !self->enemy )
538 		{//just aquired one
539 			AddSoundEvent( bestTarget, self->currentOrigin, 256, AEL_DISCOVERED );
540 			AddSightEvent( bestTarget, self->currentOrigin, 512, AEL_DISCOVERED, 20 );
541 		}
542 		G_SetEnemy( self, bestTarget );
543 		if ( VALIDSTRING( self->target2 ))
544 		{
545 			G_UseTargets2( self, self, self->target2 );
546 		}
547 	}
548 
549 	return found;
550 }
551 
552 //-----------------------------------------------------
turret_base_think(gentity_t * self)553 void turret_base_think( gentity_t *self )
554 //-----------------------------------------------------
555 {
556 	qboolean	turnOff = qtrue;
557 	float		enemyDist;
558 	vec3_t		enemyDir, org, org2;
559 
560 	self->nextthink = level.time + FRAMETIME;
561 
562 	if ( self->spawnflags & 1 )
563 	{
564 		// not turned on
565 		turret_turnoff( self );
566 		turret_aim( self );
567 
568 		// No target
569 		self->flags |= FL_NOTARGET;
570 		return;
571 	}
572 	else
573 	{
574 		// I'm all hot and bothered
575 		self->flags &= ~FL_NOTARGET;
576 	}
577 
578 	if ( !self->enemy )
579 	{
580 		if ( turret_find_enemies( self ))
581 		{
582 			turnOff = qfalse;
583 		}
584 	}
585 	else
586 	{
587 		if ( self->enemy->health > 0 )
588 		{
589 			// enemy is alive
590 			VectorSubtract( self->enemy->currentOrigin, self->currentOrigin, enemyDir );
591 			enemyDist = VectorLengthSquared( enemyDir );
592 
593 			if ( enemyDist < self->radius * self->radius )
594 			{
595 				// was in valid radius
596 				if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
597 				{
598 					// Every now and again, check to see if we can even trace to the enemy
599 					trace_t tr;
600 
601 					if ( self->enemy->client )
602 					{
603 						VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
604 					}
605 					else
606 					{
607 						VectorCopy( self->enemy->currentOrigin, org );
608 					}
609 					VectorCopy( self->currentOrigin, org2 );
610 					if ( self->spawnflags & 2 )
611 					{
612 						org2[2] += 10;
613 					}
614 					else
615 					{
616 						org2[2] -= 10;
617 					}
618 					gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
619 
620 					if ( self->spawnflags & SPF_TURRETG2_TURBO || ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number ) )
621 					{
622 						turnOff = qfalse;	// Can see our enemy
623 					}
624 				}
625 			}
626 		}
627 
628 		turret_head_think( self );
629 	}
630 
631 	if ( turnOff )
632 	{
633 		if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
634 		{
635 			turret_turnoff( self );
636 		}
637 	}
638 	else
639 	{
640 		// keep our enemy for a minimum of 2 seconds from now
641 		self->bounceCount = level.time + 2000 + Q_flrand(0.0f, 1.0f) * 150;
642 	}
643 
644 	turret_aim( self );
645 }
646 
647 //-----------------------------------------------------------------------------
turret_base_use(gentity_t * self,gentity_t * other,gentity_t * activator)648 void turret_base_use( gentity_t *self, gentity_t *other, gentity_t *activator )
649 //-----------------------------------------------------------------------------
650 {
651 	// Toggle on and off
652 	self->spawnflags = (self->spawnflags ^ 1);
653 
654 	if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
655 	{
656 		self->s.frame = 1; // black
657 	}
658 	else
659 	{
660 		self->s.frame = 0; // glow
661 	}
662 }
663 
664 //special routine for tracking angles between client and server -rww
turret_SetBoneAngles(gentity_t * ent,const char * bone,const vec3_t angles)665 void turret_SetBoneAngles(gentity_t *ent, const char *bone, const vec3_t angles)
666 {
667 	/*
668 	int *thebone = &ent->s.boneIndex1;
669 	int *firstFree = NULL;
670 	int i = 0;
671 	int boneIndex = G_BoneIndex(bone);
672 	int flags;
673 	Eorientations up, right, forward;
674 	vec3_t *boneVector = &ent->s.boneAngles1;
675 	vec3_t *freeBoneVec = NULL;
676 
677 	while (thebone)
678 	{
679 		if (!*thebone && !firstFree)
680 		{ //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing.
681 			firstFree = thebone;
682 			freeBoneVec = boneVector;
683 		}
684 		else if (*thebone)
685 		{
686 			if (*thebone == boneIndex)
687 			{ //this is it
688 				break;
689 			}
690 		}
691 
692 		switch (i)
693 		{
694 		case 0:
695 			thebone = &ent->s.boneIndex2;
696 			boneVector = &ent->s.boneAngles2;
697 			break;
698 		case 1:
699 			thebone = &ent->s.boneIndex3;
700 			boneVector = &ent->s.boneAngles3;
701 			break;
702 		case 2:
703 			thebone = &ent->s.boneIndex4;
704 			boneVector = &ent->s.boneAngles4;
705 			break;
706 		default:
707 			thebone = NULL;
708 			boneVector = NULL;
709 			break;
710 		}
711 
712 		i++;
713 	}
714 
715 	if (!thebone)
716 	{ //didn't find it, create it
717 		if (!firstFree)
718 		{ //no free bones.. can't do a thing then.
719 			Com_Printf("WARNING: NPC has no free bone indexes\n");
720 			return;
721 		}
722 
723 		thebone = firstFree;
724 
725 		*thebone = boneIndex;
726 		boneVector = freeBoneVec;
727 	}
728 
729 	//If we got here then we have a vector and an index.
730 
731 	//Copy the angles over the vector in the entitystate, so we can use the corresponding index
732 	//to set the bone angles on the client.
733 	VectorCopy(angles, *boneVector);
734 */
735 	//Now set the angles on our server instance if we have one.
736 
737 	if ( !ent->ghoul2.size() )
738 	{
739 		return;
740 	}
741 
742 	int flags = BONE_ANGLES_POSTMULT;
743 	Eorientations up, right, forward;
744 	up = POSITIVE_Y;
745 	right = NEGATIVE_Z;
746 	forward = NEGATIVE_X;
747 
748 	//first 3 bits is forward, second 3 bits is right, third 3 bits is up
749 	//ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
750 
751 	gi.G2API_SetBoneAngles( &ent->ghoul2[0], bone, angles, flags, up,
752 							right, forward, NULL, 100, level.time );
753 }
754 
turret_set_models(gentity_t * self,qboolean dying)755 void turret_set_models( gentity_t *self, qboolean dying )
756 {
757 	if ( dying )
758 	{
759 		if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
760 		{
761 			self->s.modelindex = G_ModelIndex( name2 );
762 			self->s.modelindex2 = G_ModelIndex( name );
763 		}
764 
765 		gi.G2API_RemoveGhoul2Model( self->ghoul2, 0 );
766 		/*G_KillG2Queue( self->s.number );
767 		self->s.modelGhoul2 = 0;
768 
769 		gi.G2API_InitGhoul2Model( &self->ghoul2,
770 									name2,
771 									0, //base->s.modelindex,
772 									//note, this is not the same kind of index - this one's referring to the actual
773 									//index of the model in the g2 instance, whereas modelindex is the index of a
774 									//configstring -rww
775 									0,
776 									0,
777 									0,
778 									0);
779 		*/
780 	}
781 	else
782 	{
783 		if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
784 		{
785 			self->s.modelindex = G_ModelIndex( name );
786 			self->s.modelindex2 = G_ModelIndex( name2 );
787 			//set the new onw
788 			gi.G2API_InitGhoul2Model( self->ghoul2,
789 										name,
790 										0, //base->s.modelindex,
791 										//note, this is not the same kind of index - this one's referring to the actual
792 										//index of the model in the g2 instance, whereas modelindex is the index of a
793 										//configstring -rww
794 										0,
795 										0,
796 										0,
797 										0);
798 		}
799 		else
800 		{
801 			self->s.modelindex = G_ModelIndex( name3 );
802 			//set the new onw
803 			gi.G2API_InitGhoul2Model( self->ghoul2,
804 										name3,
805 										0, //base->s.modelindex,
806 										//note, this is not the same kind of index - this one's referring to the actual
807 										//index of the model in the g2 instance, whereas modelindex is the index of a
808 										//configstring -rww
809 										0,
810 										0,
811 										0,
812 										0);
813 		}
814 
815 		/*self->s.modelGhoul2 = 1;
816 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
817 		{//larger
818 			self->s.g2radius = 128;
819 		}
820 		else
821 		{
822 			self->s.g2radius = 80;
823 		}*/
824 
825 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
826 		{//different pitch bone and muzzle flash points
827 			turret_SetBoneAngles(self, "pitch", vec3_origin);
828 			//self->genericValue11 = gi.G2API_AddBolt( self->ghoul2, 0, "*muzzle1" );
829 			//self->genericValue12 = gi.G2API_AddBolt( self->ghoul2, 0, "*muzzle2" );
830 		}
831 		else
832 		{
833 			turret_SetBoneAngles(self, "Bone_body", vec3_origin);
834 			//self->genericValue11 = gi.G2API_AddBolt( self->ghoul2, 0, "*flash03" );
835 		}
836 	}
837 }
838 
839 /*QUAKED misc_turret (1 0 0) (-8 -8 -22) (8 8 0) START_OFF UPSIDE_DOWN TURBO
840 Turret that hangs from the ceiling, will aim and shoot at enemies
841 
842   START_OFF - Starts off
843   UPSIDE_DOWN - make it rest on a surface/floor instead of hanging from the ceiling
844   TURBO - Big-ass, Boxy Death Star Turbo Laser version
845 
846   radius - How far away an enemy can be for it to pick it up (default 512)
847   wait	- Time between shots (default 150 ms)
848   dmg	- How much damage each shot does (default 5)
849   health - How much damage it can take before exploding (default 100)
850 
851   splashDamage - How much damage the explosion does
852   splashRadius - The radius of the explosion
853   NOTE: If either of the above two are 0, it will not make an explosion
854 
855   targetname - Toggles it on/off
856   target - What to use when destroyed
857   target2 - What to use when it decides to start shooting at an enemy
858 
859   team - team that is not targeted by and does not take damage from this turret
860 	"player",
861 	"enemy",	(default)
862 	"neutral"
863 */
864 //-----------------------------------------------------
SP_misc_turret(gentity_t * base)865 void SP_misc_turret( gentity_t *base )
866 //-----------------------------------------------------
867 {
868 	/*base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/turret_canon.glm" );
869 	base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" );
870 	base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/turret_canon.glm", base->s.modelindex );
871 	base->s.radius = 80.0f;*/
872 	turret_set_models( base, qfalse );
873 
874 	gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
875 	base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash03" );
876 
877 	finish_spawning_turret( base );
878 
879 	if (( base->spawnflags & 1 )) // Start_Off
880 	{
881 		base->s.frame = 1; // black
882 	}
883 	else
884 	{
885 		base->s.frame = 0; // glow
886 	}
887 	base->s.eFlags |= EF_SHADER_ANIM;
888 }
889 
890 //-----------------------------------------------------
finish_spawning_turret(gentity_t * base)891 void finish_spawning_turret( gentity_t *base )
892 {
893 	vec3_t		fwd;
894 
895 	if ( base->spawnflags & 2 )
896 	{
897 		base->s.angles[ROLL] += 180;
898 		base->s.origin[2] -= 22.0f;
899 	}
900 
901 	G_SetAngles( base, base->s.angles );
902 	AngleVectors( base->currentAngles, fwd, NULL, NULL );
903 
904 	G_SetOrigin(base, base->s.origin);
905 
906 	base->noDamageTeam = TEAM_ENEMY;
907 
908 	base->s.eType = ET_GENERAL;
909 
910 	if ( base->team && base->team[0] )
911 	{
912 		base->noDamageTeam = (team_t)GetIDForString( TeamTable, base->team );
913 		base->team = NULL;
914 	}
915 
916 	// Set up our explosion effect for the ExplodeDeath code....
917 	base->fxID = G_EffectIndex( "turret/explode" );
918 	G_EffectIndex( "sparks/spark_exp_nosnd" );
919 
920 	base->e_UseFunc = useF_turret_base_use;
921 	base->e_PainFunc = painF_TurretPain;
922 
923 	// don't start working right away
924 	base->e_ThinkFunc = thinkF_turret_base_think;
925 	base->nextthink = level.time + FRAMETIME * 5;
926 
927 	// this is really the pitch angle.....
928 	base->speed = 0;
929 
930 	G_SpawnFloat( "shotspeed", "0", &base->mass );
931 	if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
932 	{
933 		if ( !base->random )
934 		{//error worked into projectile direction
935 			base->random = 2.0f;
936 		}
937 
938 		if ( !base->mass )
939 		{//misnomer: speed of projectile
940 			base->mass = 4000;
941 		}
942 
943 		if ( !base->health )
944 		{
945 			base->health = 2000;
946 		}
947 
948 		// search radius
949 		if ( !base->radius )
950 		{
951 			base->radius = 32768;
952 		}
953 
954 		// How quickly to fire
955 		if ( !base->wait )
956 		{
957 			base->wait = 500;// + Q_flrand(0.0f, 1.0f) * 500;
958 		}
959 
960 		if ( !base->splashDamage )
961 		{
962 			base->splashDamage = 200;
963 		}
964 
965 		if ( !base->splashRadius )
966 		{
967 			base->splashRadius = 500;
968 		}
969 
970 		// how much damage each shot does
971 		if ( !base->damage )
972 		{
973 			base->damage = 10;
974 		}
975 
976 		VectorSet( base->s.modelScale, 2.0f, 2.0f, 2.0f );
977 		VectorSet( base->maxs, 128.0f, 128.0f, 120.0f );
978 		VectorSet( base->mins, -128.0f, -128.0f, -120.0f );
979 
980 		// Cull Radius.
981 		base->s.radius = 256;
982 
983 		//start in "off" anim
984 		TurboLaser_SetBoneAnim( base, 4, 5 );
985 
986 		// Make sure it doesn't do sparks and such when saber contacts with it.
987 		base->flags = FL_DMG_BY_HEAVY_WEAP_ONLY;
988 		base->takedamage = qfalse;
989 		base->contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP;
990 
991 		base->noDamageTeam = TEAM_NEUTRAL;
992 		base->team = NULL;
993 	}
994 	else
995 	{
996 		// this is a random time offset for the no-enemy-search-around-mode
997 		base->count = Q_flrand(0.0f, 1.0f) * 9000;
998 
999 		if ( !base->health )
1000 		{
1001 			base->health = 100;
1002 		}
1003 
1004 		// search radius
1005 		if ( !base->radius )
1006 		{
1007 			base->radius = 512;
1008 		}
1009 
1010 		// How quickly to fire
1011 		if ( !base->wait )
1012 		{
1013 			base->wait = 150 + Q_flrand(0.0f, 1.0f) * 55;
1014 		}
1015 
1016 		if ( !base->splashDamage )
1017 		{
1018 			base->splashDamage = 10;
1019 		}
1020 
1021 		if ( !base->splashRadius )
1022 		{
1023 			base->splashRadius = 25;
1024 		}
1025 
1026 		// how much damage each shot does
1027 		if ( !base->damage )
1028 		{
1029 			base->damage = 5;
1030 		}
1031 
1032 		if ( base->spawnflags & 2 )
1033 		{//upside-down, invert mins and maxe
1034 			VectorSet( base->maxs, 10.0f, 10.0f, 30.0f );
1035 			VectorSet( base->mins, -10.0f, -10.0f, 0.0f );
1036 		}
1037 		else
1038 		{
1039 			VectorSet( base->maxs, 10.0f, 10.0f, 0.0f );
1040 			VectorSet( base->mins, -10.0f, -10.0f, -30.0f );
1041 		}
1042 
1043 		base->takedamage = qtrue;
1044 		base->contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP;
1045 	}
1046 
1047 	// Precache special FX and moving sounds
1048 	if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
1049 	{
1050 		G_EffectIndex( "turret/turb_muzzle_flash" );
1051 		G_EffectIndex( "turret/turb_shot" );
1052 		G_EffectIndex( "turret/turb_impact" );
1053 		//FIXME: Turbo Laser Cannon sounds!
1054 		G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" );
1055 		G_EffectIndex( "explosions/fighter_explosion2" );
1056 		RegisterItem( FindItemForWeapon( WP_TIE_FIGHTER ));
1057 	}
1058 	else
1059 	{
1060 		// Precache moving sounds
1061 		G_SoundIndex( "sound/chars/turret/startup.wav" );
1062 		G_SoundIndex( "sound/chars/turret/shutdown.wav" );
1063 		G_SoundIndex( "sound/chars/turret/ping.wav" );
1064 		G_SoundIndex( "sound/chars/turret/move.wav" );
1065 	}
1066 
1067 	base->max_health = base->health;
1068 	base->e_DieFunc  = dieF_turret_die;
1069 
1070 	base->material = MAT_METAL;
1071 
1072 	if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
1073 	{
1074 		RegisterItem( FindItemForWeapon( WP_TURRET ));
1075 
1076 		base->svFlags |= SVF_NO_TELEPORT|SVF_SELF_ANIMATING;
1077 	}
1078 	else
1079 	{
1080 		// Register this so that we can use it for the missile effect
1081 		RegisterItem( FindItemForWeapon( WP_BLASTER ));
1082 
1083 		base->svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
1084 	}
1085 
1086 	// But set us as a turret so that we can be identified as a turret
1087 	base->s.weapon = WP_TURRET;
1088 
1089 	gi.linkentity( base );
1090 }
1091 
1092 /*QUAKED misc_ns_turret (1 0 0) (-8 -8 -32) (8 8 29) START_OFF
1093 NS turret that only hangs from the ceiling, will aim and shoot at enemies
1094 
1095   START_OFF - Starts off
1096 
1097   radius - How far away an enemy can be for it to pick it up (default 512)
1098   wait	- Time between shots (default 150 ms)
1099   dmg	- How much damage each shot does (default 5)
1100   health - How much damage it can take before exploding (default 100)
1101 
1102   splashDamage - How much damage the explosion does
1103   splashRadius - The radius of the explosion
1104   NOTE: If either of the above two are 0, it will not make an explosion
1105 
1106   targetname - Toggles it on/off
1107   target - What to use when destroyed
1108 
1109   team - team that is not targeted by and does not take damage from this turret
1110 	"player",
1111 	"enemy",	(default)
1112 	"neutral"
1113 */
1114 //-----------------------------------------------------
SP_misc_ns_turret(gentity_t * base)1115 void SP_misc_ns_turret( gentity_t *base )
1116 //-----------------------------------------------------
1117 {
1118 	base->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/turret/turret.glm" );
1119 	base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" ); // FIXME!
1120 	base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/nar_shaddar/turret/turret.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
1121 	base->s.radius = 80.0f;
1122 
1123 	gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1124 	base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
1125 
1126 	finish_spawning_turret( base );
1127 }
1128 
1129 //--------------------------------------
1130 
laser_arm_fire(gentity_t * ent)1131 void laser_arm_fire (gentity_t *ent)
1132 {
1133 	vec3_t	start, end, fwd, rt, up;
1134 	trace_t	trace;
1135 
1136 	if ( ent->attackDebounceTime < level.time && ent->alt_fire )
1137 	{
1138 		// If I'm firing the laser and it's time to quit....then quit!
1139 		ent->alt_fire = qfalse;
1140 //		ent->e_ThinkFunc = thinkF_NULL;
1141 //		return;
1142 	}
1143 
1144 	ent->nextthink = level.time + FRAMETIME;
1145 
1146 	// If a fool gets in the laser path, fry 'em
1147 	AngleVectors( ent->currentAngles, fwd, rt, up );
1148 
1149 	VectorMA( ent->currentOrigin, 20, fwd, start );
1150 	//VectorMA( start, -6, rt, start );
1151 	//VectorMA( start, -3, up, start );
1152 	VectorMA( start, 4096, fwd, end );
1153 
1154 	gi.trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT, (EG2_Collision)0, 0 );//ignore
1155 	ent->fly_sound_debounce_time = level.time;//used as lastShotTime
1156 
1157 	// Only deal damage when in alt-fire mode
1158 	if ( trace.fraction < 1.0 && ent->alt_fire )
1159 	{
1160 		if ( trace.entityNum < ENTITYNUM_WORLD )
1161 		{
1162 			gentity_t *hapless_victim = &g_entities[trace.entityNum];
1163 			if ( hapless_victim && hapless_victim->takedamage && ent->damage )
1164 			{
1165 				G_Damage( hapless_victim, ent, ent->nextTrain->activator, fwd, trace.endpos, ent->damage, DAMAGE_IGNORE_TEAM, MOD_UNKNOWN );
1166 			}
1167 		}
1168 	}
1169 
1170 	if ( ent->alt_fire )
1171 	{
1172 //		CG_FireLaser( start, trace.endpos, trace.plane.normal, ent->nextTrain->startRGBA, qfalse );
1173 	}
1174 	else
1175 	{
1176 //		CG_AimLaser( start, trace.endpos, trace.plane.normal );
1177 	}
1178 }
1179 
laser_arm_use(gentity_t * self,gentity_t * other,gentity_t * activator)1180 void laser_arm_use (gentity_t *self, gentity_t *other, gentity_t *activator)
1181 {
1182 	vec3_t	newAngles;
1183 
1184 	self->activator = activator;
1185 	switch( self->count )
1186 	{
1187 	case 0:
1188 	default:
1189 		//Fire
1190 		//gi.Printf("FIRE!\n");
1191 //		self->lastEnemy->lastEnemy->e_ThinkFunc = thinkF_laser_arm_fire;
1192 //		self->lastEnemy->lastEnemy->nextthink = level.time + FRAMETIME;
1193 		//For 3 seconds
1194 		self->lastEnemy->lastEnemy->alt_fire = qtrue; // Let 'er rip!
1195 		self->lastEnemy->lastEnemy->attackDebounceTime = level.time + self->lastEnemy->lastEnemy->wait;
1196 		G_Sound(self->lastEnemy->lastEnemy, G_SoundIndex("sound/chars/l_arm/fire.wav"));
1197 		break;
1198 	case 1:
1199 		//Yaw left
1200 		//gi.Printf("LEFT...\n");
1201 		VectorCopy( self->lastEnemy->currentAngles, newAngles );
1202 		newAngles[1] += self->speed;
1203 		G_SetAngles( self->lastEnemy, newAngles );
1204 //		bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS );
1205 		G_Sound( self->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
1206 		break;
1207 	case 2:
1208 		//Yaw right
1209 		//gi.Printf("RIGHT...\n");
1210 		VectorCopy( self->lastEnemy->currentAngles, newAngles );
1211 		newAngles[1] -= self->speed;
1212 		G_SetAngles( self->lastEnemy, newAngles );
1213 //		bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS );
1214 		G_Sound( self->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
1215 		break;
1216 	case 3:
1217 		//pitch up
1218 		//gi.Printf("UP...\n");
1219 		//FIXME: Clamp
1220 		VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles );
1221 		newAngles[0] -= self->speed;
1222 		if ( newAngles[0] < -45 )
1223 		{
1224 			newAngles[0] = -45;
1225 		}
1226 		G_SetAngles( self->lastEnemy->lastEnemy, newAngles );
1227 		G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
1228 		break;
1229 	case 4:
1230 		//pitch down
1231 		//gi.Printf("DOWN...\n");
1232 		//FIXME: Clamp
1233 		VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles );
1234 		newAngles[0] += self->speed;
1235 		if ( newAngles[0] > 90 )
1236 		{
1237 			newAngles[0] = 90;
1238 		}
1239 		G_SetAngles( self->lastEnemy->lastEnemy, newAngles );
1240 		G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
1241 		break;
1242 	}
1243 }
1244 /*QUAKED misc_laser_arm (1 0 0) (-8 -8 -8) (8 8 8)
1245 
1246 What it does when used depends on it's "count" (can be set by a script)
1247 	count:
1248 		0 (default) - Fire in direction facing
1249 		1 turn left
1250 		2 turn right
1251 		3 aim up
1252 		4 aim down
1253 
1254   speed - How fast it turns (degrees per second, default 30)
1255   dmg	- How much damage the laser does 10 times a second (default 5 = 50 points per second)
1256   wait  - How long the beam lasts, in seconds (default is 3)
1257 
1258   targetname - to use it
1259   target - What thing for it to be pointing at to start with
1260 
1261   "startRGBA" - laser color, Red Green Blue Alpha, range 0 to 1 (default  1.0 0.85 0.15 0.75 = Yellow-Orange)
1262 */
laser_arm_start(gentity_t * base)1263 void laser_arm_start (gentity_t *base)
1264 {
1265 	vec3_t	armAngles;
1266 	vec3_t	headAngles;
1267 
1268 	base->e_ThinkFunc = thinkF_NULL;
1269 	//We're the base, spawn the arm and head
1270 	gentity_t *arm = G_Spawn();
1271 	gentity_t *head = G_Spawn();
1272 
1273 	VectorCopy( base->s.angles, armAngles );
1274 	VectorCopy( base->s.angles, headAngles );
1275 	if ( base->target && base->target[0] )
1276 	{//Start out pointing at something
1277 		gentity_t *targ = G_Find( NULL, FOFS(targetname), base->target );
1278 		if ( !targ )
1279 		{//couldn't find it!
1280 			Com_Printf(S_COLOR_RED "ERROR : laser_arm can't find target %s!\n", base->target);
1281 		}
1282 		else
1283 		{//point at it
1284 			vec3_t	dir, angles;
1285 
1286 			VectorSubtract(targ->currentOrigin, base->s.origin, dir );
1287 			vectoangles( dir, angles );
1288 			armAngles[1] = angles[1];
1289 			headAngles[0] = angles[0];
1290 			headAngles[1] = angles[1];
1291 		}
1292 	}
1293 
1294 	//Base
1295 	//Base does the looking for enemies and pointing the arm and head
1296 	G_SetAngles( base, base->s.angles );
1297 	//base->s.origin[2] += 4;
1298 	G_SetOrigin(base, base->s.origin);
1299 	gi.linkentity(base);
1300 	//FIXME: need an actual model
1301 	base->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_base.md3");
1302 	base->s.eType = ET_GENERAL;
1303 	G_SpawnVector4( "startRGBA", "1.0 0.85 0.15 0.75", (float *)&base->startRGBA );
1304 	//anglespeed - how fast it can track the player, entered in degrees per second, so we divide by FRAMETIME/1000
1305 	if ( !base->speed )
1306 	{
1307 		base->speed = 3.0f;
1308 	}
1309 	else
1310 	{
1311 		base->speed *= FRAMETIME/1000.0f;
1312 	}
1313 	base->e_UseFunc = useF_laser_arm_use;
1314 	base->nextthink = level.time + FRAMETIME;
1315 
1316 	//Arm
1317 	//Does nothing, not solid, gets removed when head explodes
1318 	G_SetOrigin( arm, base->s.origin );
1319 	gi.linkentity(arm);
1320 	G_SetAngles( arm, armAngles );
1321 //	bolt_head_to_arm( arm, head, LARM_FOFS, LARM_ROFS, LARM_UOFS );
1322 	arm->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_arm.md3");
1323 
1324 	//Head
1325 	//Fires when enemy detected, animates, can be blown up
1326 	//Need to normalize the headAngles pitch for the clamping later
1327 	if ( headAngles[0] < -180 )
1328 	{
1329 		headAngles[0] += 360;
1330 	}
1331 	else if ( headAngles[0] > 180 )
1332 	{
1333 		headAngles[0] -= 360;
1334 	}
1335 	G_SetAngles( head, headAngles );
1336 	head->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_head.md3");
1337 	head->s.eType = ET_GENERAL;
1338 //	head->svFlags |= SVF_BROADCAST;// Broadcast to all clients
1339 	VectorSet( head->mins, -8, -8, -8 );
1340 	VectorSet( head->maxs, 8, 8, 8 );
1341 	head->contents = CONTENTS_BODY;
1342 	gi.linkentity(head);
1343 
1344 	//dmg
1345 	if ( !base->damage )
1346 	{
1347 		head->damage = 5;
1348 	}
1349 	else
1350 	{
1351 		head->damage = base->damage;
1352 	}
1353 	base->damage = 0;
1354 	//lifespan of beam
1355 	if ( !base->wait )
1356 	{
1357 		head->wait = 3000;
1358 	}
1359 	else
1360 	{
1361 		head->wait = base->wait * 1000;
1362 	}
1363 	base->wait = 0;
1364 
1365 	//Precache firing and explode sounds
1366 	G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
1367 	G_SoundIndex("sound/chars/l_arm/fire.wav");
1368 	G_SoundIndex("sound/chars/l_arm/move.wav");
1369 
1370 	//Link them up
1371 	base->lastEnemy = arm;
1372 	arm->lastEnemy = head;
1373 	head->owner = arm;
1374 	arm->nextTrain = head->nextTrain = base;
1375 
1376 	// The head should always think, since it will be either firing a damage laser or just a target laser
1377 	head->e_ThinkFunc = thinkF_laser_arm_fire;
1378 	head->nextthink = level.time + FRAMETIME;
1379 	head->alt_fire = qfalse; // Don't do damage until told to
1380 }
1381 
SP_laser_arm(gentity_t * base)1382 void SP_laser_arm (gentity_t *base)
1383 {
1384 	base->e_ThinkFunc = thinkF_laser_arm_start;
1385 	base->nextthink = level.time + START_TIME_LINK_ENTS;
1386 }
1387 
1388 //--------------------------
1389 // PERSONAL ASSAULT SENTRY
1390 //--------------------------
1391 
1392 #define PAS_DAMAGE	2
1393 
1394 //-----------------------------------------------------------------------------
pas_use(gentity_t * self,gentity_t * other,gentity_t * activator)1395 void pas_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1396 //-----------------------------------------------------------------------------
1397 {
1398 	// Toggle on and off
1399 	self->spawnflags = (self->spawnflags ^ 1);
1400 
1401 	if ( self->spawnflags & 1 )
1402 	{
1403 		self->nextthink = 0; // turn off and do nothing
1404 		self->e_ThinkFunc = thinkF_NULL;
1405 	}
1406 	else
1407 	{
1408 		self->nextthink = level.time + 50;
1409 		self->e_ThinkFunc = thinkF_pas_think;
1410 	}
1411 }
1412 
1413 //----------------------------------------------------------------
pas_fire(gentity_t * ent)1414 void pas_fire( gentity_t *ent )
1415 //----------------------------------------------------------------
1416 {
1417 	vec3_t		fwd, org;
1418 	mdxaBone_t	boltMatrix;
1419 
1420 	// Getting the flash bolt here
1421 	gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
1422 				ent->torsoBolt,
1423 				&boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time),
1424 				NULL, ent->s.modelScale );
1425 
1426 	gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
1427 	gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
1428 
1429 	G_PlayEffect( "turret/muzzle_flash", org, fwd );
1430 
1431 	gentity_t	*bolt;
1432 
1433 	bolt = G_Spawn();
1434 
1435 	bolt->classname = "turret_proj";
1436 	bolt->nextthink = level.time + 10000;
1437 	bolt->e_ThinkFunc = thinkF_G_FreeEntity;
1438 	bolt->s.eType = ET_MISSILE;
1439 	bolt->s.weapon = WP_TURRET;
1440 	bolt->owner = ent;
1441 	bolt->damage = PAS_DAMAGE;
1442 	bolt->dflags = DAMAGE_NO_KNOCKBACK;		// Don't push them around, or else we are constantly re-aiming
1443 	bolt->splashDamage = 0;
1444 	bolt->splashRadius = 0;
1445 	bolt->methodOfDeath = MOD_ENERGY;
1446 	bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1447 
1448 	VectorSet( bolt->maxs, 1, 1, 1 );
1449 	VectorScale( bolt->maxs, -1, bolt->mins );
1450 
1451 	bolt->s.pos.trType = TR_LINEAR;
1452 	bolt->s.pos.trTime = level.time;		// move a bit on the very first frame
1453 	VectorCopy( org, bolt->s.pos.trBase );
1454 	VectorScale( fwd, 900, bolt->s.pos.trDelta );
1455 	SnapVector( bolt->s.pos.trDelta );		// save net bandwidth
1456 	VectorCopy( org, bolt->currentOrigin);
1457 }
1458 
1459 //-----------------------------------------------------
pas_find_enemies(gentity_t * self)1460 static qboolean pas_find_enemies( gentity_t *self )
1461 //-----------------------------------------------------
1462 {
1463 	qboolean	found = qfalse;
1464 	int			count;
1465 	float		bestDist = self->radius * self->radius;
1466 	float		enemyDist;
1467 	vec3_t		enemyDir, org, org2;
1468 	gentity_t	*entity_list[MAX_GENTITIES], *target;
1469 
1470 	if ( self->aimDebounceTime > level.time ) // time since we've been shut off
1471 	{
1472 		// We were active and alert, i.e. had an enemy in the last 3 secs
1473 		if ( self->painDebounceTime < level.time )
1474 		{
1475 			G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" ));
1476 			self->painDebounceTime = level.time + 1000;
1477 		}
1478 	}
1479 
1480 	mdxaBone_t	boltMatrix;
1481 
1482 	// Getting the "eye" here
1483 	gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
1484 				self->torsoBolt,
1485 				&boltMatrix, self->currentAngles, self->s.origin, (cg.time?cg.time:level.time),
1486 				NULL, self->s.modelScale );
1487 
1488 	gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
1489 
1490 	count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
1491 
1492 	for ( int i = 0; i < count; i++ )
1493 	{
1494 		target = entity_list[i];
1495 
1496 		if ( !target->client )
1497 		{
1498 			continue;
1499 		}
1500 		if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
1501 		{
1502 			continue;
1503 		}
1504 		if ( target->client->playerTeam == self->noDamageTeam )
1505 		{
1506 			// A bot we don't want to shoot
1507 			continue;
1508 		}
1509 		if ( !gi.inPVS( org2, target->currentOrigin ))
1510 		{
1511 			continue;
1512 		}
1513 
1514 		if ( target->client )
1515 		{
1516 			VectorCopy( target->client->renderInfo.eyePoint, org );
1517 			org[2] -= 15;
1518 		}
1519 		else
1520 		{
1521 			VectorCopy( target->currentOrigin, org );
1522 		}
1523 
1524 		trace_t	tr;
1525 		gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1526 
1527 		if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
1528 		{
1529 			// Only acquire if have a clear shot, Is it in range and closer than our best?
1530 			VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir );
1531 			enemyDist = VectorLengthSquared( enemyDir );
1532 
1533 			if ( target->s.number ) // don't do this for the player
1534 			{
1535 				G_StartFlee( target, self, self->currentOrigin, AEL_DANGER, 3000, 5000 );
1536 			}
1537 
1538 			if ( enemyDist < bestDist )// all things equal, keep current
1539 			{
1540 				if ( self->attackDebounceTime + 2000 < level.time )
1541 				{
1542 					// We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
1543 					G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" ));
1544 
1545 					// Wind up turrets for a bit
1546 					self->attackDebounceTime = level.time + 900 + Q_flrand(0.0f, 1.0f) * 200;
1547 				}
1548 
1549 				G_SetEnemy( self, target );
1550 				bestDist = enemyDist;
1551 				found = qtrue;
1552 			}
1553 		}
1554 	}
1555 
1556 	if ( found && VALIDSTRING( self->target2 ))
1557 	{
1558 		G_UseTargets2( self, self, self->target2 );
1559 	}
1560 
1561 	return found;
1562 }
1563 
1564 //---------------------------------
pas_adjust_enemy(gentity_t * ent)1565 void pas_adjust_enemy( gentity_t *ent )
1566 //---------------------------------
1567 {
1568 	qboolean keep = qtrue;
1569 
1570 	if ( ent->enemy->health <= 0 )
1571 	{
1572 		keep = qfalse;
1573 	}
1574 	else// if ( Q_flrand(0.0f, 1.0f) > 0.5f )
1575 	{
1576 		// do a trace every now and then.
1577 		mdxaBone_t	boltMatrix;
1578 		vec3_t		org, org2;
1579 
1580 		// Getting the "eye" here
1581 		gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
1582 					ent->torsoBolt,
1583 					&boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time),
1584 					NULL, ent->s.modelScale );
1585 
1586 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
1587 
1588 		if ( ent->enemy->client )
1589 		{
1590 			VectorCopy( ent->enemy->client->renderInfo.eyePoint, org );
1591 			org[2] -= 15;
1592 		}
1593 		else
1594 		{
1595 			VectorCopy( ent->enemy->currentOrigin, org );
1596 		}
1597 
1598 		trace_t	tr;
1599 		gi.trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1600 
1601 		if ( tr.allsolid || tr.startsolid || tr.entityNum != ent->enemy->s.number )
1602 		{
1603 			// trace failed
1604 			keep = qfalse;
1605 		}
1606 	}
1607 
1608 	if ( keep )
1609 	{
1610 		ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
1611 	}
1612 	else if ( ent->bounceCount < level.time ) // don't ping pong on and off
1613 	{
1614 		ent->enemy = NULL;
1615 		// shut-down sound
1616 		G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
1617 
1618 		// make turret play ping sound for 5 seconds
1619 		ent->aimDebounceTime = level.time + 5000;
1620 	}
1621 }
1622 
1623 //---------------------------------
pas_think(gentity_t * ent)1624 void pas_think( gentity_t *ent )
1625 //---------------------------------
1626 {
1627 	if ( !ent->damage )
1628 	{
1629 		// let us do our animation, then we are good to go in terms of pounding the crap out of enemies.
1630 		ent->damage = 1;
1631 		gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 11, BONE_ANIM_OVERRIDE_FREEZE, 0.8f, cg.time, -1, -1 );
1632 		ent->nextthink = level.time + 1200;
1633 		return;
1634 	}
1635 
1636 	if ( !ent->count )
1637 	{
1638 		// turrets that have no ammo may as well do nothing
1639 		return;
1640 	}
1641 
1642 	ent->nextthink = level.time + FRAMETIME;
1643 
1644 	if ( ent->enemy )
1645 	{
1646 		// make sure that the enemy is still valid
1647 		pas_adjust_enemy( ent );
1648 	}
1649 
1650 	if ( !ent->enemy )
1651 	{
1652 		pas_find_enemies( ent );
1653 	}
1654 
1655 	qboolean	moved = qfalse;
1656 	float		diffYaw = 0.0f, diffPitch = 0.0f;
1657 	vec3_t		enemyDir, org;
1658 	vec3_t		frontAngles, backAngles;
1659 	vec3_t		desiredAngles;
1660 
1661 	ent->speed = AngleNormalize360( ent->speed );
1662 	ent->random = AngleNormalize360( ent->random );
1663 
1664 	if ( ent->enemy )
1665 	{
1666 		// ...then we'll calculate what new aim adjustments we should attempt to make this frame
1667 		// Aim at enemy
1668 		if ( ent->enemy->client )
1669 		{
1670 			VectorCopy( ent->enemy->client->renderInfo.eyePoint, org );
1671 			org[2] -= 40;
1672 		}
1673 		else
1674 		{
1675 			VectorCopy( ent->enemy->currentOrigin, org );
1676 		}
1677 
1678 		VectorSubtract( org, ent->currentOrigin, enemyDir );
1679 		vectoangles( enemyDir, desiredAngles );
1680 
1681 		diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] );
1682 		diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] );
1683 	}
1684 	else
1685 	{
1686 		// no enemy, so make us slowly sweep back and forth as if searching for a new one
1687 		diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f;
1688 	}
1689 
1690 	if ( fabs(diffYaw) > 0.25f )
1691 	{
1692 		moved = qtrue;
1693 
1694 		if ( fabs(diffYaw) > 10.0f )
1695 		{
1696 			// cap max speed
1697 			ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f;
1698 		}
1699 		else
1700 		{
1701 			// small enough
1702 			ent->speed -= diffYaw;
1703 		}
1704 	}
1705 
1706 
1707 	if ( fabs(diffPitch) > 0.25f )
1708 	{
1709 		moved = qtrue;
1710 
1711 		if ( fabs(diffPitch) > 4.0f )
1712 		{
1713 			// cap max speed
1714 			ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f;
1715 		}
1716 		else
1717 		{
1718 			// small enough
1719 			ent->random -= diffPitch;
1720 		}
1721 	}
1722 
1723 	// the bone axes are messed up, so hence some dumbness here
1724 	VectorSet( frontAngles, -ent->random, 0.0f, 0.0f );
1725 	VectorSet( backAngles, 0.0f, 0.0f, ent->speed - ent->s.angles[YAW] );
1726 
1727 	gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_barrel", frontAngles,
1728 						BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time);
1729 	gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_gback", frontAngles,
1730 						BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time);
1731 	gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_hinge", backAngles,
1732 						BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL,100,cg.time);
1733 
1734 	if ( moved )
1735 	{
1736 	//ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
1737 	}
1738 	else
1739 	{
1740 		ent->s.loopSound = 0;
1741 	}
1742 
1743 	if ( ent->enemy && ent->attackDebounceTime < level.time && Q_flrand(0.0f, 1.0f) > 0.3f )
1744 	{
1745 		ent->count--;
1746 
1747 		if ( ent->count )
1748 		{
1749 			pas_fire( ent );
1750 			ent->fly_sound_debounce_time = level.time;//used as lastShotTime
1751 		}
1752 		else
1753 		{
1754 			ent->nextthink = 0;
1755 			G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
1756 		}
1757 	}
1758 }
1759 
1760 /*QUAKED misc_sentry_turret (1 0 0) (-16 -16 0) (16 16 24) START_OFF RESERVED
1761 personal assault sentry, like the ones you can carry in your inventory
1762 
1763   RESERVED - do no use this flag for anything, does nothing..etc.
1764 
1765   radius - How far away an enemy can be for it to pick it up (default 512)
1766   count - number of shots before thing deactivates. -1 = infinite, default 150
1767   health - How much damage it can take before exploding (default 50)
1768 
1769   splashDamage - How much damage the explosion does
1770   splashRadius - The radius of the explosion
1771   NOTE: If either of the above two are 0, it will not make an explosion
1772 
1773   target - What to use when destroyed
1774   target2 - What to use when it decides to fire at an enemy
1775 
1776   team - team that does not take damage from this item
1777 	"player",
1778 	"enemy",
1779 	"neutral"
1780 
1781 */
1782 
1783 //---------------------------------
SP_PAS(gentity_t * base)1784 void SP_PAS( gentity_t *base )
1785 //---------------------------------
1786 {
1787 	base->classname = "PAS";
1788 	G_SetOrigin( base, base->s.origin );
1789 	G_SetAngles( base, base->s.angles );
1790 
1791 	base->speed = base->s.angles[YAW];
1792 
1793 	base->s.modelindex = G_ModelIndex( "models/items/psgun.glm" );
1794 	base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/items/psgun.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
1795 	base->s.radius = 30.0f;
1796 	VectorSet( base->s.modelScale, 1.0f, 1.0f, 1.0f );
1797 
1798 	base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue );
1799 	gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_hinge", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1800 	gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_gback", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1801 	gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_barrel", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1802 
1803 	base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
1804 
1805 	base->s.eType = ET_GENERAL;
1806 
1807 	if ( !base->radius )
1808 	{
1809 		base->radius = 512;
1810 	}
1811 
1812 	if ( base->count == 0 )
1813 	{
1814 		// give ammo
1815 		base->count = 150;
1816 	}
1817 
1818 	base->e_UseFunc = useF_pas_use;
1819 
1820 	base->damage = 0; // start animation flag
1821 
1822 	base->contents = CONTENTS_SHOTCLIP|CONTENTS_CORPSE;//for certain traces
1823 	VectorSet( base->mins, -8, -8, 0 );
1824 	VectorSet( base->maxs, 8, 8, 18 );
1825 
1826 	if ( !(base->spawnflags & 1 )) // START_OFF
1827 	{
1828 		base->nextthink = level.time + 1000; // we aren't starting off, so start working right away
1829 		base->e_ThinkFunc = thinkF_pas_think;
1830 	}
1831 
1832 	// Set up our explosion effect for the ExplodeDeath code....
1833 	base->fxID = G_EffectIndex( "turret/explode" );
1834 	G_EffectIndex( "sparks/spark_exp_nosnd" );
1835 
1836 	if ( !base->health )
1837 	{
1838 		base->health = 50;
1839 	}
1840 	base->max_health = base->health;
1841 
1842 	base->takedamage = qtrue;
1843 	base->e_PainFunc = painF_TurretPain;
1844 	base->e_DieFunc  = dieF_turret_die;
1845 
1846 	// hack this flag on so that when it calls the turret die code, it will orient the effect up
1847 	// HACK
1848 	//--------------------------------------
1849 	base->spawnflags |= 2;
1850 
1851 	// Use this for our missile effect
1852 	RegisterItem( FindItemForWeapon( WP_TURRET ));
1853 	base->s.weapon = WP_TURRET;
1854 
1855 	base->svFlags |= SVF_NONNPC_ENEMY;
1856 
1857 	base->noDamageTeam = TEAM_NEUTRAL;
1858 	if ( base->team && base->team[0] )
1859 	{
1860 		base->noDamageTeam = (team_t)GetIDForString( TeamTable, base->team );
1861 		base->team = NULL;
1862 	}
1863 
1864 	gi.linkentity( base );
1865 }
1866 
1867 //------------------------------------------------------------------------
place_portable_assault_sentry(gentity_t * self,vec3_t origin,vec3_t angs)1868 qboolean place_portable_assault_sentry( gentity_t *self, vec3_t origin, vec3_t angs )
1869 //------------------------------------------------------------------------
1870 {
1871 	vec3_t		fwd, pos;
1872 	vec3_t		mins, maxs;
1873 	trace_t		tr;
1874 	gentity_t	*pas;
1875 
1876 	VectorSet( maxs, 9, 9, 0 );
1877 	VectorScale( maxs, -1, mins );
1878 
1879 	angs[PITCH] = 0;
1880 	angs[ROLL] = 0;
1881 	AngleVectors( angs, fwd, NULL, NULL );
1882 
1883 	// and move a consistent distance away from us so we don't have the dumb thing spawning inside of us.
1884 	VectorMA( origin, 30, fwd, pos );
1885 	gi.trace( &tr, origin, NULL, NULL, pos, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1886 
1887 	// find the ground
1888 	tr.endpos[2] += 20;
1889 	VectorCopy( tr.endpos, pos );
1890 	pos[2] -= 64;
1891 
1892 	gi.trace( &tr, tr.endpos, mins, maxs, pos, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1893 
1894 	// check for a decent surface, meaning mostly flat...should probably also check surface parms so we don't set us down on lava or something.
1895 	if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1.0f && tr.plane.normal[2] > 0.9f && tr.entityNum >= ENTITYNUM_WORLD )
1896 	{
1897 		// Then spawn us if it seems cool.
1898 		pas = G_Spawn();
1899 
1900 		if ( pas )
1901 		{
1902 			VectorCopy( tr.endpos, pas->s.origin );
1903 			SP_PAS( pas );
1904 
1905 			pas->contents |= CONTENTS_PLAYERCLIP; // player placed ones can block players but not npcs
1906 
1907 			pas->e_UseFunc = useF_NULL; // placeable ones never need to be used
1908 
1909 			// we don't hurt us or anyone who belongs to the same team as us.
1910 			if ( self->client )
1911 			{
1912 				pas->noDamageTeam = self->client->playerTeam;
1913 			}
1914 
1915 			G_Sound( self, G_SoundIndex( "sound/player/use_sentry" ));
1916 			pas->activator = self;
1917 			return qtrue;
1918 		}
1919 	}
1920 	return qfalse;
1921 }
1922 
1923 
1924 //-------------
1925 // ION CANNON
1926 //-------------
1927 
1928 
1929 //----------------------------------------
ion_cannon_think(gentity_t * self)1930 void ion_cannon_think( gentity_t *self )
1931 //----------------------------------------
1932 {
1933 	if ( self->spawnflags & 2 )
1934 	{
1935 		if ( self->count )
1936 		{
1937 			// still have bursts left, so keep going
1938 			self->count--;
1939 		}
1940 		else
1941 		{
1942 			// done with burst, so wait delay amount, plus a random bit
1943 			self->nextthink = level.time + ( self->delay + Q_flrand(-1.0f, 1.0f) * self->random );
1944 			self->count = Q_irand(0,5); // 0-5 bursts
1945 
1946 			// Not firing this time
1947 			return;
1948 		}
1949 	}
1950 
1951 	if ( self->fxID )
1952 	{
1953 		vec3_t		fwd, org;
1954 		mdxaBone_t	boltMatrix;
1955 
1956 		// Getting the flash bolt here
1957 		gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
1958 					self->torsoBolt,
1959 					&boltMatrix, self->s.angles, self->s.origin, (cg.time?cg.time:level.time),
1960 					NULL, self->s.modelScale );
1961 
1962 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
1963 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
1964 
1965 		G_PlayEffect( self->fxID, org, fwd );
1966 	}
1967 
1968 	if ( self->target2 )
1969 	{
1970 		// If we have a target2 fire it off in sync with our gun firing
1971 		G_UseTargets2( self, self, self->target2 );
1972 	}
1973 
1974 	gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 8, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
1975 	self->nextthink = level.time + self->wait + Q_flrand(-1.0f, 1.0f) * self->random;
1976 }
1977 
1978 //----------------------------------------------------------------------------------------------
ion_cannon_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)1979 void ion_cannon_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
1980 //----------------------------------------------------------------------------------------------
1981 {
1982 	vec3_t org;
1983 
1984 	// dead, so nuke the ghoul model and put in the damage md3 version
1985 	if ( self->playerModel >= 0 )
1986 	{
1987 		gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel );
1988 	}
1989 	self->s.modelindex = self->s.modelindex2;
1990 	self->s.modelindex2 = 0;
1991 
1992 		// Turn off the thinking of the base & use it's targets
1993 	self->e_ThinkFunc = thinkF_NULL;
1994 	self->e_UseFunc = useF_NULL;
1995 
1996 	if ( self->target )
1997 	{
1998 		G_UseTargets( self, attacker );
1999 	}
2000 
2001 	// clear my data
2002 	self->e_DieFunc  = dieF_NULL;
2003 	self->takedamage = qfalse;
2004 	self->health = 0;
2005 
2006 	self->takedamage = qfalse;//stop chain reaction runaway loops
2007 	self->s.loopSound = 0;
2008 
2009 	// not solid anymore
2010 	self->contents = 0;
2011 
2012 	VectorCopy( self->currentOrigin, self->s.pos.trBase );
2013 
2014 	VectorCopy( self->currentOrigin, org );
2015 	org[2] += 20;
2016 	G_PlayEffect( "env/ion_cannon_explosion", org );
2017 
2018 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
2019 	{
2020 		G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius,
2021 				attacker, MOD_UNKNOWN );
2022 	}
2023 
2024 	gi.linkentity( self );
2025 }
2026 
2027 //----------------------------------------------------------------------------
ion_cannon_use(gentity_t * self,gentity_t * other,gentity_t * activator)2028 void ion_cannon_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2029 //----------------------------------------------------------------------------
2030 {
2031 	// toggle
2032 	if ( self->e_ThinkFunc == thinkF_NULL )
2033 	{
2034 		// start thinking now
2035 		self->e_ThinkFunc = thinkF_ion_cannon_think;
2036 		self->nextthink = level.time + FRAMETIME; // fires right on being used
2037 	}
2038 	else
2039 	{
2040 		self->e_ThinkFunc = thinkF_NULL;
2041 	}
2042 }
2043 
2044 /*QUAKED misc_ion_cannon (1 0 0) (-280 -280 0) (280 280 640) START_OFF BURSTS SHIELDED
2045 Huge ion cannon, like the ones at the rebel base on Hoth.
2046 
2047   START_OFF - Starts off
2048   BURSTS - adds more variation, shots come out in bursts
2049   SHIELDED - cannon is shielded, any kind of shot bounces off.
2050 
2051   wait	- How fast it shoots (default 1500 ms between shots, can't be less than 500 ms)
2052   random - milliseconds wait variation (default 400 ms...up to plus or minus .4 seconds)
2053   delay	- Number of milliseconds between bursts (default 6000 ms, can't be less than 1000 ms, only works when BURSTS checked)
2054 
2055   health - default 2000
2056   splashDamage - how much damage to do when it dies, must be greater than 0 to actually work
2057   splashRadius - damage radius, must be greater than 0 to actually work
2058 
2059   targetname - Toggles it on/off
2060   target - What to use when destroyed
2061   target2 - What to use when it fires a shot.
2062 */
2063 //-----------------------------------------------------
SP_misc_ion_cannon(gentity_t * base)2064 void SP_misc_ion_cannon( gentity_t *base )
2065 //-----------------------------------------------------
2066 {
2067 	G_SetAngles( base, base->s.angles );
2068 
2069 	G_SetOrigin(base, base->s.origin);
2070 
2071 	base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon.glm" );
2072 	base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/ion_cannon.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
2073 	base->s.radius = 320.0f;
2074 	VectorSet( base->s.modelScale, 2.0f, 2.0f, 2.0f );
2075 
2076 	base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue );
2077 	base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
2078 
2079 	// register damage model
2080 	base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon_damage.md3" );
2081 
2082 	base->e_UseFunc = useF_ion_cannon_use;
2083 
2084 	// How quickly to fire
2085 	if ( base->wait == 0.0f )
2086 	{
2087 		base->wait = 1500.0f;
2088 	}
2089 	else if ( base->wait < 500.0f )
2090 	{
2091 		base->wait = 500.0f;
2092 	}
2093 
2094 	if ( base->random == 0.0f )
2095 	{
2096 		base->random = 400.0f;
2097 	}
2098 
2099 	if ( base->delay == 0 )
2100 	{
2101 		base->delay = 6000;
2102 	}
2103 	else if ( base->delay < 1000 )
2104 	{
2105 		base->delay = 1000;
2106 	}
2107 
2108 	// we only take damage from a heavy weapon class missile
2109 	base->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
2110 
2111 	if ( base->spawnflags & 4 )//shielded
2112 	{
2113 		base->flags |= FL_SHIELDED; //technically, this would only take damage from a lightsaber, but the other flag just above would cancel that out too.
2114 	}
2115 
2116 	G_SpawnInt( "health", "2000", &base->health );
2117 	base->e_DieFunc = dieF_ion_cannon_die;
2118 	base->takedamage = qtrue;
2119 
2120 	// Start Off?
2121 	if ( base->spawnflags & 1 )
2122 	{
2123 		base->e_ThinkFunc = thinkF_NULL;
2124 	}
2125 	else
2126 	{
2127 		// start thinking now, otherwise, we'll wait until we are used
2128 		base->e_ThinkFunc = thinkF_ion_cannon_think;
2129 		base->nextthink = level.time + base->wait + Q_flrand(-1.0f, 1.0f) * base->random;
2130 	}
2131 
2132 	// Bursts?
2133 	if ( base->spawnflags & 2 )
2134 	{
2135 		base->count = Q_irand(0,5); // 0-5 bursts
2136 	}
2137 
2138 	// precache
2139 	base->fxID = G_EffectIndex( "env/ion_cannon" );
2140 
2141 	// Set up our explosion effect for the ExplodeDeath code....
2142 	G_EffectIndex( "env/ion_cannon_explosion" );
2143 
2144 	base->contents = CONTENTS_BODY;
2145 
2146 	VectorSet( base->mins, -141.0f, -148.0f, 0.0f );
2147 	VectorSet( base->maxs, 142.0f, 135.0f, 245.0f );
2148 
2149 	gi.linkentity( base );
2150 }
2151 
2152 
2153 //-----------------------------------------------------
spotlight_use(gentity_t * self,gentity_t * other,gentity_t * activator)2154 void spotlight_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2155 {
2156 	if ( self->e_ThinkFunc == thinkF_NULL )
2157 	{
2158 		// start thinking now, otherwise, we'll wait until we are used
2159 		self->e_ThinkFunc = thinkF_spotlight_think;
2160 		self->nextthink = level.time + FRAMETIME;
2161 	}
2162 	else
2163 	{
2164 		self->e_ThinkFunc = thinkF_NULL;
2165 		self->s.eFlags &= ~EF_ALT_FIRING;
2166 	}
2167 }
2168 
2169 //-----------------------------------------------------
spotlight_think(gentity_t * ent)2170 void spotlight_think( gentity_t *ent )
2171 {
2172 	vec3_t		dir, end;
2173 	trace_t		tr;
2174 
2175 	// dumb hack flag so that we can draw an interpolated light cone cgame side.
2176 	ent->s.eFlags |= EF_ALT_FIRING;
2177 
2178 	VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, dir );
2179 	VectorNormalize( dir );
2180 	vectoangles( dir, ent->s.apos.trBase );
2181 	ent->s.apos.trType = TR_INTERPOLATE;
2182 
2183 	VectorMA( ent->currentOrigin, 2048, dir, end ); // just pick some max trace distance
2184 	gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID, (EG2_Collision)0, 0 );
2185 
2186 	ent->radius = tr.fraction * 2048.0f;
2187 
2188 	if ( tr.fraction < 1 )
2189 	{
2190 		if ( DistanceSquared( tr.endpos, g_entities[0].currentOrigin ) < 140 * 140 )
2191 		{
2192 			// hit player--use target2
2193 			G_UseTargets2( ent, &g_entities[0], ent->target2 );
2194 
2195 #ifndef FINAL_BUILD
2196 			if ( g_developer->integer == PRINT_DEVELOPER )
2197 			{
2198 				Com_Printf( S_COLOR_MAGENTA "Spotlight hit player at time: %d!!!\n", level.time );
2199 			}
2200 #endif
2201 		}
2202 	}
2203 
2204 	ent->nextthink = level.time + 50;
2205 }
2206 
2207 //-----------------------------------------------------
spotlight_link(gentity_t * ent)2208 void spotlight_link( gentity_t *ent )
2209 {
2210 	gentity_t *target = 0;
2211 
2212 	target = G_Find( target, FOFS(targetname), ent->target );
2213 
2214 	if ( !target )
2215 	{
2216 		Com_Printf( S_COLOR_RED "ERROR: spotlight_link: bogus target %s\n", ent->target );
2217 		G_FreeEntity( ent );
2218 		return;
2219 	}
2220 
2221 	ent->enemy = target;
2222 
2223 	// Start Off?
2224 	if ( ent->spawnflags & 1 )
2225 	{
2226 		ent->e_ThinkFunc = thinkF_NULL;
2227 		ent->s.eFlags &= ~EF_ALT_FIRING;
2228 	}
2229 	else
2230 	{
2231 		// start thinking now, otherwise, we'll wait until we are used
2232 		ent->e_ThinkFunc = thinkF_spotlight_think;
2233 		ent->nextthink = level.time + FRAMETIME;
2234 	}
2235 }
2236 
2237 /*QUAKED misc_spotlight (1 0 0) (-10 -10 0) (10 10 10) START_OFF
2238 model="models/map_objects/imp_mine/spotlight.md3"
2239 Search spotlight that must be targeted at a func_train or other entity
2240 Uses its target2 when it detects the player
2241 
2242   START_OFF - Starts off
2243 
2244   targetname - Toggles it on/off
2245   target - What to point at
2246   target2 - What to use when detects player
2247 */
2248 
2249 //-----------------------------------------------------
SP_misc_spotlight(gentity_t * base)2250 void SP_misc_spotlight( gentity_t *base )
2251 //-----------------------------------------------------
2252 {
2253 	if ( !base->target )
2254 	{
2255 		Com_Printf( S_COLOR_RED "ERROR: misc_spotlight must have a target\n" );
2256 		G_FreeEntity( base );
2257 		return;
2258 	}
2259 
2260 	G_SetAngles( base, base->s.angles );
2261 	G_SetOrigin( base, base->s.origin );
2262 
2263 	base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/spotlight.md3" );
2264 
2265 	G_SpawnInt( "health", "300", &base->health );
2266 
2267 	// Set up our lightcone effect, though we will have it draw cgame side so it looks better
2268 	G_EffectIndex( "env/light_cone" );
2269 
2270 	base->contents = CONTENTS_BODY;
2271 	base->e_UseFunc = useF_spotlight_use;
2272 
2273 	// the thing we need to target may not have spawned yet, so try back in a bit
2274 	base->e_ThinkFunc = thinkF_spotlight_link;
2275 	base->nextthink = level.time + 100;
2276 
2277 	gi.linkentity( base );
2278 }
2279 
2280 /*QUAKED misc_panel_turret (0 0 1) (-8 -8 -12) (8 8 16) HEALTH
2281 Creates a turret that, when the player uses a panel, takes control of this turret and adopts the turret view
2282 
2283   HEALTH - gun turret has health and displays a hud with its current health
2284 
2285 "target" - thing to use when player enters the turret view
2286 "target2" - thing to use when player leaves the turret view
2287 "target3" - thing to use when it dies.
2288 
2289   radius - the max yaw range in degrees, (default 90) which means you can move 90 degrees on either side of the start angles.
2290   random - the max pitch range in degrees, (default 60) which means you can move 60 degrees above or below the start angles.
2291   delay - time between shots, in milliseconds (default 200).
2292   damage - amount of damage shots do, (default 50).
2293   speed - missile speed, (default 3000)
2294 
2295   heatlh - how much heatlh the thing has, (default 200) only works if HEALTH is checked, otherwise it can't be destroyed.
2296 */
2297 
2298 extern gentity_t	*player;
2299 extern qboolean		G_ClearViewEntity( gentity_t *ent );
2300 extern void			G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
2301 extern gentity_t	*CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
2302 
panel_turret_shoot(gentity_t * self,vec3_t org,vec3_t dir)2303 void panel_turret_shoot( gentity_t *self, vec3_t org, vec3_t dir)
2304 {
2305 	gentity_t *missile = CreateMissile( org, dir, self->speed, 10000, self );
2306 
2307 	missile->classname = "b_proj";
2308 	missile->s.weapon = WP_TIE_FIGHTER;
2309 
2310 	VectorSet( missile->maxs, 9, 9, 9 );
2311 	VectorScale( missile->maxs, -1, missile->mins );
2312 
2313 	missile->bounceCount = 0;
2314 
2315 	missile->damage = self->damage;
2316 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
2317 	missile->methodOfDeath = MOD_ENERGY;
2318 	missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
2319 
2320 	G_SoundOnEnt( self, CHAN_AUTO, "sound/movers/objects/ladygun_fire" );
2321 
2322 	VectorMA( org, 32, dir, org );
2323 	org[2] -= 4;
2324 	G_PlayEffect( "ships/imp_blastermuzzleflash", org, dir );
2325 }
2326 
2327 //-----------------------------------------
misc_panel_turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)2328 void misc_panel_turret_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
2329 {
2330 	if ( self->target3 )
2331 	{
2332 		G_UseTargets2( self, player, self->target3 );
2333 	}
2334 
2335 	// FIXME: might need some other kind of logic or functionality in here??
2336 	G_UseTargets2( self, player, self->target2 );
2337 	G_ClearViewEntity( player );
2338 	cg.overrides.active &= ~CG_OVERRIDE_FOV;
2339 	cg.overrides.fov = 0;
2340 }
2341 
2342 //-----------------------------------------
panel_turret_think(gentity_t * self)2343 void panel_turret_think( gentity_t *self )
2344 {
2345 	// Ensure that I am the viewEntity
2346 	if ( player && player->client && player->client->ps.viewEntity == self->s.number )
2347 	{
2348 		usercmd_t *ucmd = &player->client->usercmd;
2349 
2350 		// We are the viewEnt so update our new viewangles based on the sum of our start angles and the ucmd angles
2351 		for ( int i = 0; i < 3; i++ )
2352 		{
2353 			// convert our base angle to a short, add with the usercmd.angle ( a short ), then switch use back to a real angle
2354 			self->s.apos.trBase[i] = AngleNormalize180( SHORT2ANGLE( ucmd->angles[i] + ANGLE2SHORT( self->s.angles[i] ) + self->pos3[i] ));
2355 		}
2356 
2357 		// Only clamp if we have a PITCH clamp
2358 		if ( self->random != 0.0f )
2359 		// Angle clamping -- PITCH
2360 		{
2361 			if ( self->s.apos.trBase[PITCH] > self->random ) // random is PITCH
2362 			{
2363 				self->pos3[PITCH] += ANGLE2SHORT( AngleNormalize180( self->random - self->s.apos.trBase[PITCH]));
2364 				self->s.apos.trBase[PITCH] = self->random;
2365 			}
2366 			else if ( self->s.apos.trBase[PITCH] < -self->random )
2367 			{
2368 				self->pos3[PITCH] -= ANGLE2SHORT( AngleNormalize180( self->random + self->s.apos.trBase[PITCH]));
2369 				self->s.apos.trBase[PITCH] = -self->random;
2370 			}
2371 		}
2372 
2373 		// Only clamp if we have a YAW clamp
2374 		if ( self->radius != 0.0f )
2375 		{
2376 			float yawDif = AngleSubtract( self->s.apos.trBase[YAW], self->s.angles[YAW] );
2377 
2378 			// Angle clamping -- YAW
2379 			if (  yawDif > self->radius ) // radius is YAW
2380 			{
2381 				self->pos3[YAW] += ANGLE2SHORT( self->radius - yawDif );
2382 				self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] + self->radius );
2383 			}
2384 			else if ( yawDif < -self->radius ) // radius is YAW
2385 			{
2386 				self->pos3[YAW] -= ANGLE2SHORT( self->radius + yawDif );
2387 				self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] - self->radius );
2388 			}
2389 		}
2390 
2391 		// Let cgame interpolation smooth out the angle changes
2392 		self->s.apos.trType = TR_INTERPOLATE;
2393 		self->s.pos.trType	= TR_INTERPOLATE; // not really moving, but this fixes an interpolation bug in cg_ents.
2394 
2395 		// Check for backing out of turret
2396 		if ( ( self->useDebounceTime < level.time ) && ((ucmd->buttons & BUTTON_USE) || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove) )
2397 		{
2398 			self->useDebounceTime = level.time + 200;
2399 
2400 			G_UseTargets2( self, player, self->target2 );
2401 			G_ClearViewEntity( player );
2402 			G_Sound( player, self->soundPos2 );
2403 
2404 			cg.overrides.active &= ~CG_OVERRIDE_FOV;
2405 			cg.overrides.fov = 0;
2406 			if ( ucmd->upmove > 0 )
2407 			{//stop player from doing anything for a half second after
2408 				player->aimDebounceTime = level.time + 500;
2409 			}
2410 
2411 			// can be drawn
2412 //			self->s.eFlags &= ~EF_NODRAW;
2413 		}
2414 		else
2415 		{
2416 			// don't draw me when being looked through
2417 //			self->s.eFlags |= EF_NODRAW;
2418 //			self->s.modelindex = 0;
2419 
2420 			// we only need to think when we are being used
2421 			self->nextthink = level.time + 50;
2422 
2423 			cg.overrides.active |= CG_OVERRIDE_FOV;
2424 			cg.overrides.fov = 90;
2425 		}
2426 
2427 		if ( ucmd->buttons & BUTTON_ATTACK || ucmd->buttons & BUTTON_ALT_ATTACK )
2428 		{
2429 			if ( self->attackDebounceTime < level.time )
2430 			{
2431 				vec3_t dir, pt;
2432 
2433 				AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
2434 
2435 				VectorCopy( self->currentOrigin, pt );
2436 				pt[2] -= 4;
2437 				panel_turret_shoot( self, pt, dir );
2438 
2439 				self->attackDebounceTime = level.time + self->delay;
2440 			}
2441 		}
2442 	}
2443 }
2444 
2445 //-----------------------------------------
panel_turret_use(gentity_t * self,gentity_t * other,gentity_t * activator)2446 void panel_turret_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2447 {
2448 	// really only usable by the player
2449 	if ( !activator || !activator->client || activator->s.number )
2450 	{
2451 		return;
2452 	}
2453 
2454 	if ( self->useDebounceTime > level.time )
2455 	{
2456 		// can't use it again right away.
2457 		return;
2458 	}
2459 
2460 	if ( self->spawnflags & 1 ) // health...presumably the lady luck gun
2461 	{
2462 		G_Sound( self, G_SoundIndex( "sound/movers/objects/ladygun_on" ));
2463 	}
2464 
2465 	self->useDebounceTime = level.time + 200;
2466 
2467 	// Compensating for the difference between the players view at the time of use and the start angles that the gun object has
2468 	self->pos3[PITCH]	= -activator->client->usercmd.angles[PITCH];
2469 	self->pos3[YAW]		= -activator->client->usercmd.angles[YAW];
2470 	self->pos3[ROLL]	= 0;
2471 
2472 	// set me as view entity
2473 	G_UseTargets2( self, activator, self->target );
2474 	G_SetViewEntity( activator, self );
2475 
2476 	G_Sound( activator, self->soundPos1 );
2477 
2478 	self->e_ThinkFunc = thinkF_panel_turret_think;
2479 //	panel_turret_think( self );
2480 	self->nextthink = level.time + 150;
2481 }
2482 
2483 //-----------------------------------------
SP_misc_panel_turret(gentity_t * self)2484 void SP_misc_panel_turret( gentity_t *self )
2485 {
2486 	G_SpawnFloat( "radius", "90", &self->radius );	// yaw
2487 	G_SpawnFloat( "random", "60", &self->random );	// pitch
2488 	G_SpawnFloat( "speed" , "3000", &self->speed );
2489 	G_SpawnInt( "delay", "200", &self->delay );
2490 	G_SpawnInt( "damage", "50", &self->damage );
2491 
2492 	VectorSet( self->pos3, 0.0f, 0.0f, 0.0f );
2493 
2494 	if ( self->spawnflags & 1 ) // heatlh
2495 	{
2496 		self->takedamage = qtrue;
2497 		self->contents = CONTENTS_SHOTCLIP;
2498 		G_SpawnInt( "health", "200", &self->health );
2499 
2500 		self->max_health = self->health;
2501 		self->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud
2502 		G_SoundIndex( "sound/movers/objects/ladygun_on" );
2503 	}
2504 
2505 	self->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ladyluck_gun.md3" );
2506 
2507 	self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" );
2508 	self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" );
2509 
2510 	G_SoundIndex( "sound/movers/objects/ladygun_fire" );
2511 	G_EffectIndex("ships/imp_blastermuzzleflash");
2512 
2513 	G_SetOrigin( self, self->s.origin );
2514 	G_SetAngles( self, self->s.angles );
2515 
2516 	VectorSet( self->mins, -8, -8, -12 );
2517 	VectorSet( self->maxs, 8, 8, 0 );
2518 	self->contents = CONTENTS_SOLID;
2519 
2520 	self->s.weapon = WP_TURRET;
2521 
2522 	RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));
2523 	gi.linkentity( self );
2524 
2525 	self->e_UseFunc = useF_panel_turret_use;
2526 	self->e_DieFunc = dieF_misc_panel_turret_die;
2527 }
2528 
2529 #undef name
2530 #undef name2
2531 #undef name3
2532