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