1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 #include "qcommon/q_shared.h"
26 
27 void G_SetEnemy( gentity_t *self, gentity_t *enemy );
28 qboolean turret_base_spawn_top( gentity_t *base );
29 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
30 
31 //------------------------------------------------------------------------------------------------------------
TurretPain(gentity_t * self,gentity_t * attacker,int damage)32 void TurretPain( gentity_t *self, gentity_t *attacker, int damage )
33 //------------------------------------------------------------------------------------------------------------
34 {
35 	if (self->target_ent)
36 	{
37 		self->target_ent->health = self->health;
38 		if (self->target_ent->maxHealth)
39 		{
40 			G_ScaleNetHealth(self->target_ent);
41 		}
42 	}
43 
44 	if ( attacker->client && attacker->client->ps.weapon == WP_DEMP2 )
45 	{
46 		self->attackDebounceTime = level.time + 800 + Q_flrand(0.0f, 1.0f) * 500;
47 		self->painDebounceTime = self->attackDebounceTime;
48 	}
49 	if ( !self->enemy )
50 	{//react to being hit
51 		G_SetEnemy( self, attacker );
52 	}
53 }
54 
55 //------------------------------------------------------------------------------------------------------------
TurretBasePain(gentity_t * self,gentity_t * attacker,int damage)56 void TurretBasePain( gentity_t *self, gentity_t *attacker, int damage )
57 //------------------------------------------------------------------------------------------------------------
58 {
59 	if (self->target_ent)
60 	{
61 		self->target_ent->health = self->health;
62 		if (self->target_ent->maxHealth)
63 		{
64 			G_ScaleNetHealth(self->target_ent);
65 		}
66 
67 		TurretPain(self->target_ent, attacker, damage);
68 	}
69 }
70 
71 //------------------------------------------------------------------------------------------------------------
auto_turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)72 void auto_turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
73 //------------------------------------------------------------------------------------------------------------
74 {
75 	vec3_t	forward = { 0,0, 1 }, pos;
76 
77 	// Turn off the thinking of the base & use it's targets
78 	g_entities[self->r.ownerNum].think = NULL;
79 	g_entities[self->r.ownerNum].use = NULL;
80 
81 	// clear my data
82 	self->die = NULL;
83 	self->takedamage = qfalse;
84 	self->s.health = self->health = 0;
85 	self->s.loopSound = 0;
86 	self->s.shouldtarget = qfalse;
87 	//self->s.owner = MAX_CLIENTS; //not owned by any client
88 
89 	VectorCopy( self->r.currentOrigin, pos );
90 	pos[2] += self->r.maxs[2]*0.5f;
91 	G_PlayEffect( EFFECT_EXPLOSION_TURRET, pos, forward );
92 	G_PlayEffectID( G_EffectIndex( "turret/explode" ), pos, forward );
93 
94 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
95 	{
96 		G_RadiusDamage( self->r.currentOrigin,
97 						attacker,
98 						self->splashDamage,
99 						self->splashRadius,
100 						attacker,
101 						NULL,
102 						MOD_UNKNOWN );
103 	}
104 
105 	self->s.weapon = 0; // crosshair code uses this to mark crosshair red
106 
107 
108 	if ( self->s.modelindex2 )
109 	{
110 		// switch to damage model if we should
111 		self->s.modelindex = self->s.modelindex2;
112 
113 		if (self->target_ent && self->target_ent->s.modelindex2)
114 		{
115 			self->target_ent->s.modelindex = self->target_ent->s.modelindex2;
116 		}
117 
118 		VectorCopy( self->r.currentAngles, self->s.apos.trBase );
119 		VectorClear( self->s.apos.trDelta );
120 
121 		if ( self->target )
122 		{
123 			G_UseTargets( self, attacker );
124 		}
125 	}
126 	else
127 	{
128 		ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
129 	}
130 }
131 
132 //------------------------------------------------------------------------------------------------------------
bottom_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)133 void bottom_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
134 //------------------------------------------------------------------------------------------------------------
135 {
136 	if (self->target_ent && self->target_ent->health > 0)
137 	{
138 		self->target_ent->health = self->health;
139 		if (self->target_ent->maxHealth)
140 		{
141 			G_ScaleNetHealth(self->target_ent);
142 		}
143 		auto_turret_die(self->target_ent, inflictor, attacker, damage, meansOfDeath);
144 	}
145 }
146 
147 #define START_DIS 15
148 
149 //----------------------------------------------------------------
turret_fire(gentity_t * ent,vec3_t start,vec3_t dir)150 static void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
151 //----------------------------------------------------------------
152 {
153 	vec3_t		org;
154 	gentity_t	*bolt;
155 
156 	if ( (trap->PointContents( start, ent->s.number )&MASK_SHOT) )
157 	{
158 		return;
159 	}
160 
161 	VectorMA( start, -START_DIS, dir, org ); // dumb....
162 	G_PlayEffectID( ent->genericValue13, org, dir );
163 
164 	bolt = G_Spawn();
165 
166 	//use a custom shot effect
167 	bolt->s.otherEntityNum2 = ent->genericValue14;
168 	//use a custom impact effect
169 	bolt->s.emplacedOwner = ent->genericValue15;
170 
171 	bolt->classname = "turret_proj";
172 	bolt->nextthink = level.time + 10000;
173 	bolt->think = G_FreeEntity;
174 	bolt->s.eType = ET_MISSILE;
175 	bolt->s.weapon = WP_EMPLACED_GUN;
176 	bolt->r.ownerNum = ent->s.number;
177 	bolt->damage = ent->damage;
178 	bolt->alliedTeam = ent->alliedTeam;
179 	bolt->teamnodmg = ent->teamnodmg;
180 	//bolt->dflags = DAMAGE_NO_KNOCKBACK;// | DAMAGE_HEAVY_WEAP_CLASS;		// Don't push them around, or else we are constantly re-aiming
181 	bolt->splashDamage = ent->damage;
182 	bolt->splashRadius = 100;
183 	bolt->methodOfDeath = MOD_TARGET_LASER;
184 	bolt->splashMethodOfDeath = MOD_TARGET_LASER;
185 	bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
186 	//bolt->trigger_formation = qfalse;		// don't draw tail on first frame
187 
188 	VectorSet( bolt->r.maxs, 1.5, 1.5, 1.5 );
189 	VectorScale( bolt->r.maxs, -1, bolt->r.mins );
190 	bolt->s.pos.trType = TR_LINEAR;
191 	bolt->s.pos.trTime = level.time;
192 	VectorCopy( start, bolt->s.pos.trBase );
193 	VectorScale( dir, ent->mass, bolt->s.pos.trDelta );
194 	SnapVector( bolt->s.pos.trDelta );		// save net bandwidth
195 	VectorCopy( start, bolt->r.currentOrigin);
196 
197 	bolt->parent = ent;
198 }
199 
200 //-----------------------------------------------------
turret_head_think(gentity_t * self)201 void turret_head_think( gentity_t *self )
202 //-----------------------------------------------------
203 {
204 	gentity_t *top = &g_entities[self->r.ownerNum];
205 	if ( !top )
206 	{
207 		return;
208 	}
209 	if ( self->painDebounceTime > level.time )
210 	{
211 		vec3_t	v_up;
212 		VectorSet( v_up, 0, 0, 1 );
213 		G_PlayEffect( EFFECT_SPARKS, self->r.currentOrigin, v_up );
214 		if ( Q_irand( 0, 3) )
215 		{//25% chance of still firing
216 			return;
217 		}
218 	}
219 	// if it's time to fire and we have an enemy, then gun 'em down!  pushDebounce time controls next fire time
220 	if ( self->enemy && self->setTime < level.time && self->attackDebounceTime < level.time )
221 	{
222 		vec3_t		fwd, org;
223 		// set up our next fire time
224 		self->setTime = level.time + self->wait;
225 
226 		/*
227 		mdxaBone_t	boltMatrix;
228 
229 		// Getting the flash bolt here
230 		trap->G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
231 					self->torsoBolt,
232 					&boltMatrix, self->r.currentAngles, self->r.currentOrigin, (cg.time?cg.time:level.time),
233 					NULL, self->s.modelScale );
234 
235 		trap->G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
236 		trap->G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
237 		*/
238 		VectorCopy( top->r.currentOrigin, org );
239 		org[2] += top->r.maxs[2]-8;
240 		AngleVectors( top->r.currentAngles, fwd, NULL, NULL );
241 
242 		VectorMA( org, START_DIS, fwd, org );
243 
244 		turret_fire( top, org, fwd );
245 		self->fly_sound_debounce_time = level.time;//used as lastShotTime
246 	}
247 }
248 
249 //-----------------------------------------------------
turret_aim(gentity_t * self)250 static void turret_aim( gentity_t *self )
251 //-----------------------------------------------------
252 {
253 	vec3_t	enemyDir, org, org2;
254 	vec3_t	desiredAngles, setAngle;
255 	float	diffYaw = 0.0f, diffPitch = 0.0f, turnSpeed;
256 	const float pitchCap = 40.0f;
257 	gentity_t *top = &g_entities[self->r.ownerNum];
258 	if ( !top )
259 	{
260 		return;
261 	}
262 
263 	// move our gun base yaw to where we should be at this time....
264 	BG_EvaluateTrajectory( &top->s.apos, level.time, top->r.currentAngles );
265 	top->r.currentAngles[YAW] = AngleNormalize180( top->r.currentAngles[YAW] );
266 	top->r.currentAngles[PITCH] = AngleNormalize180( top->r.currentAngles[PITCH] );
267 	turnSpeed = top->speed;
268 
269 	if ( self->painDebounceTime > level.time )
270 	{
271 		desiredAngles[YAW] = top->r.currentAngles[YAW]+flrand(-45,45);
272 		desiredAngles[PITCH] = top->r.currentAngles[PITCH]+flrand(-10,10);
273 
274 		if (desiredAngles[PITCH] < -pitchCap)
275 		{
276 			desiredAngles[PITCH] = -pitchCap;
277 		}
278 		else if (desiredAngles[PITCH] > pitchCap)
279 		{
280 			desiredAngles[PITCH] = pitchCap;
281 		}
282 
283 		diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] );
284 		diffPitch = AngleSubtract( desiredAngles[PITCH], top->r.currentAngles[PITCH] );
285 		turnSpeed = flrand( -5, 5 );
286 	}
287 	else if ( self->enemy )
288 	{
289 		// ...then we'll calculate what new aim adjustments we should attempt to make this frame
290 		// Aim at enemy
291 		VectorCopy( self->enemy->r.currentOrigin, org );
292 		org[2]+=self->enemy->r.maxs[2]*0.5f;
293 		if (self->enemy->s.eType == ET_NPC &&
294 			self->enemy->s.NPC_class == CLASS_VEHICLE &&
295 			self->enemy->m_pVehicle &&
296 			self->enemy->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
297 		{ //hack!
298 			org[2] += 32.0f;
299 		}
300 		/*
301 		mdxaBone_t	boltMatrix;
302 
303 		// Getting the "eye" here
304 		trap->G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
305 					self->torsoBolt,
306 					&boltMatrix, self->r.currentAngles, self->s.origin, (cg.time?cg.time:level.time),
307 					NULL, self->s.modelScale );
308 
309 		trap->G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
310 		*/
311 		VectorCopy( top->r.currentOrigin, org2 );
312 
313 		VectorSubtract( org, org2, enemyDir );
314 		vectoangles( enemyDir, desiredAngles );
315 		desiredAngles[PITCH] = AngleNormalize180(desiredAngles[PITCH]);
316 
317 		if (desiredAngles[PITCH] < -pitchCap)
318 		{
319 			desiredAngles[PITCH] = -pitchCap;
320 		}
321 		else if (desiredAngles[PITCH] > pitchCap)
322 		{
323 			desiredAngles[PITCH] = pitchCap;
324 		}
325 
326 		diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] );
327 		diffPitch = AngleSubtract( desiredAngles[PITCH], top->r.currentAngles[PITCH] );
328 	}
329 	else
330 	{//FIXME: Pan back and forth in original facing
331 		// no enemy, so make us slowly sweep back and forth as if searching for a new one
332 		desiredAngles[YAW] = sin( level.time * 0.0001f + top->count );
333 		desiredAngles[YAW] *=  60.0f;
334 		desiredAngles[YAW] += self->s.angles[YAW];
335 		desiredAngles[YAW] = AngleNormalize180( desiredAngles[YAW] );
336 		diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] );
337 		diffPitch = AngleSubtract( 0, top->r.currentAngles[PITCH] );
338 		turnSpeed = 1.0f;
339 	}
340 
341 	if ( diffYaw )
342 	{
343 		// cap max speed....
344 		if ( fabs(diffYaw) > turnSpeed )
345 		{
346 			diffYaw = ( diffYaw >= 0 ? turnSpeed : -turnSpeed );
347 		}
348 	}
349 	if ( diffPitch )
350 	{
351 		if ( fabs(diffPitch) > turnSpeed )
352 		{
353 			// cap max speed
354 			diffPitch = (diffPitch > 0.0f ? turnSpeed : -turnSpeed );
355 		}
356 	}
357 	// ...then set up our desired yaw
358 	VectorSet( setAngle, diffPitch, diffYaw, 0 );
359 
360 	VectorCopy( top->r.currentAngles, top->s.apos.trBase );
361 	VectorScale( setAngle, (1000/FRAMETIME), top->s.apos.trDelta );
362 	top->s.apos.trTime = level.time;
363 	top->s.apos.trType = TR_LINEAR_STOP;
364 	top->s.apos.trDuration = FRAMETIME;
365 
366 	if ( diffYaw || diffPitch )
367 	{
368 		top->s.loopSound = G_SoundIndex( "sound/vehicles/weapons/hoth_turret/turn.wav" );
369 	}
370 	else
371 	{
372 		top->s.loopSound = 0;
373 	}
374 }
375 
376 //-----------------------------------------------------
turret_turnoff(gentity_t * self)377 static void turret_turnoff( gentity_t *self )
378 //-----------------------------------------------------
379 {
380 	gentity_t *top = &g_entities[self->r.ownerNum];
381 	if ( top != NULL )
382 	{//still have a top
383 		//stop it from rotating
384 		VectorCopy( top->r.currentAngles, top->s.apos.trBase );
385 		VectorClear( top->s.apos.trDelta );
386 		top->s.apos.trTime = level.time;
387 		top->s.apos.trType = TR_STATIONARY;
388 	}
389 
390 	self->s.loopSound = 0;
391 	// shut-down sound
392 	//G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
393 
394 	// Clear enemy
395 	self->enemy = NULL;
396 }
397 
398 //-----------------------------------------------------
turret_sleep(gentity_t * self)399 static void turret_sleep( gentity_t *self )
400 //-----------------------------------------------------
401 {
402 	if ( self->enemy == NULL )
403 	{
404 		// we don't need to play sound
405 		return;
406 	}
407 
408 	// make turret play ping sound for 5 seconds
409 	self->aimDebounceTime = level.time + 5000;
410 
411 	// Clear enemy
412 	self->enemy = NULL;
413 }
414 
415 //-----------------------------------------------------
turret_find_enemies(gentity_t * self)416 static qboolean turret_find_enemies( gentity_t *self )
417 //-----------------------------------------------------
418 {
419 	qboolean	found = qfalse;
420 	int			i, count;
421 	float		bestDist = self->radius * self->radius;
422 	float		enemyDist;
423 	vec3_t		enemyDir, org, org2;
424 	gentity_t	*entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
425 	trace_t		tr;
426 	gentity_t *top = &g_entities[self->r.ownerNum];
427 	if ( !top )
428 	{
429 		return qfalse;
430 	}
431 
432 	if ( self->aimDebounceTime > level.time ) // time since we've been shut off
433 	{
434 		// We were active and alert, i.e. had an enemy in the last 3 secs
435 		if ( self->timestamp < level.time )
436 		{
437 			//G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
438 			self->timestamp = level.time + 1000;
439 		}
440 	}
441 
442 	VectorCopy( top->r.currentOrigin, org2 );
443 
444 	count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
445 
446 	for ( i = 0; i < count; i++ )
447 	{
448 		target = entity_list[i];
449 
450 		if ( !target->client )
451 		{
452 			// only attack clients
453 			continue;
454 		}
455 		if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
456 		{
457 			continue;
458 		}
459 		if ( target->client->sess.sessionTeam == TEAM_SPECTATOR )
460 		{
461 			continue;
462 		}
463 		if ( target->client->tempSpectate >= level.time )
464 		{
465 			continue;
466 		}
467 		if ( self->alliedTeam )
468 		{
469 			if ( target->client )
470 			{
471 				if ( target->client->sess.sessionTeam == self->alliedTeam )
472 				{
473 					// A bot/client/NPC we don't want to shoot
474 					continue;
475 				}
476 			}
477 			else if ( target->teamnodmg == self->alliedTeam )
478 			{
479 				// An ent we don't want to shoot
480 				continue;
481 			}
482 		}
483 		if ( !trap->InPVS( org2, target->r.currentOrigin ))
484 		{
485 			continue;
486 		}
487 
488 		VectorCopy( target->r.currentOrigin, org );
489 		org[2] += target->r.maxs[2]*0.5f;
490 
491 		trap->Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, qfalse, 0, 0 );
492 
493 		if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
494 		{
495 			// Only acquire if have a clear shot, Is it in range and closer than our best?
496 			VectorSubtract( target->r.currentOrigin, top->r.currentOrigin, enemyDir );
497 			enemyDist = VectorLengthSquared( enemyDir );
498 
499 			if ( enemyDist < bestDist // all things equal, keep current
500 				|| (!Q_stricmp( "atst_vehicle", target->NPC_type ) && bestTarget && Q_stricmp( "atst_vehicle", bestTarget->NPC_type ) ) )//target AT-STs over non-AT-STs... FIXME: must be a better, easier way to tell this, no?
501 			{
502 				if ( self->attackDebounceTime < level.time )
503 				{
504 					// We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
505 					//G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
506 
507 					// Wind up turrets for a bit
508 					self->attackDebounceTime = level.time + 1400;
509 				}
510 
511 				bestTarget = target;
512 				bestDist = enemyDist;
513 				found = qtrue;
514 			}
515 		}
516 	}
517 
518 	if ( found )
519 	{
520 		G_SetEnemy( self, bestTarget );
521 		if ( VALIDSTRING( self->target2 ))
522 		{
523 			G_UseTargets2( self, self, self->target2 );
524 		}
525 	}
526 
527 	return found;
528 }
529 
530 //-----------------------------------------------------
turret_base_think(gentity_t * self)531 void turret_base_think( gentity_t *self )
532 //-----------------------------------------------------
533 {
534 	qboolean	turnOff = qtrue;
535 	float		enemyDist;
536 	vec3_t		enemyDir, org, org2;
537 
538 	if ( self->spawnflags & 1 )
539 	{
540 		// not turned on
541 		turret_turnoff( self );
542 
543 		// No target
544 		self->flags |= FL_NOTARGET;
545 		self->nextthink = -1;//never think again
546 		return;
547 	}
548 	else
549 	{
550 		// I'm all hot and bothered
551 		self->flags &= ~FL_NOTARGET;
552 		//remember to keep thinking!
553 		self->nextthink = level.time + FRAMETIME;
554 	}
555 
556 	if ( !self->enemy )
557 	{
558 		if ( turret_find_enemies( self ))
559 		{
560 			turnOff = qfalse;
561 		}
562 	}
563 	else if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR )
564 	{//don't keep going after spectators
565 		self->enemy = NULL;
566 	}
567 	else if ( self->enemy->client && self->enemy->client->tempSpectate >= level.time )
568 	{//don't keep going after spectators
569 		self->enemy = NULL;
570 	}
571 	else
572 	{//FIXME: remain single-minded or look for a new enemy every now and then?
573 		if ( self->enemy->health > 0 )
574 		{
575 			// enemy is alive
576 			VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir );
577 			enemyDist = VectorLengthSquared( enemyDir );
578 
579 			if ( enemyDist < (self->radius * self->radius) )
580 			{
581 				// was in valid radius
582 				if ( trap->InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) )
583 				{
584 					// Every now and again, check to see if we can even trace to the enemy
585 					trace_t tr;
586 
587 					if ( self->enemy->client )
588 					{
589 						VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
590 					}
591 					else
592 					{
593 						VectorCopy( self->enemy->r.currentOrigin, org );
594 					}
595 					VectorCopy( self->r.currentOrigin, org2 );
596 					if ( self->spawnflags & 2 )
597 					{
598 						org2[2] += 10;
599 					}
600 					else
601 					{
602 						org2[2] -= 10;
603 					}
604 					trap->Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, qfalse, 0, 0 );
605 
606 					if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number )
607 					{
608 						turnOff = qfalse;	// Can see our enemy
609 					}
610 				}
611 			}
612 		}
613 
614 		turret_head_think( self );
615 	}
616 
617 	if ( turnOff )
618 	{
619 		if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
620 		{
621 			turret_sleep( self );
622 		}
623 	}
624 	else
625 	{
626 		// keep our enemy for a minimum of 2 seconds from now
627 		self->bounceCount = level.time + 2000 + Q_flrand(0.0f, 1.0f) * 150;
628 	}
629 
630 	turret_aim( self );
631 }
632 
633 //-----------------------------------------------------------------------------
turret_base_use(gentity_t * self,gentity_t * other,gentity_t * activator)634 void turret_base_use( gentity_t *self, gentity_t *other, gentity_t *activator )
635 //-----------------------------------------------------------------------------
636 {
637 	// Toggle on and off
638 	self->spawnflags = (self->spawnflags ^ 1);
639 
640 	/*
641 	if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
642 	{
643 		self->s.frame = 1; // black
644 	}
645 	else
646 	{
647 		self->s.frame = 0; // glow
648 	}
649 	*/
650 }
651 
652 
653 /*QUAKED misc_turret (1 0 0) (-48 -48 0) (48 48 144) START_OFF
654 Large 2-piece turbolaser turret
655 
656   START_OFF - Starts off
657 
658   radius - How far away an enemy can be for it to pick it up (default 1024)
659   wait	- Time between shots (default 300 ms)
660   dmg	- How much damage each shot does (default 100)
661   health - How much damage it can take before exploding (default 3000)
662   speed - how fast it turns (default 10)
663 
664   splashDamage - How much damage the explosion does (300)
665   splashRadius - The radius of the explosion (128)
666 
667   shotspeed - speed at which projectiles will move
668 
669   targetname - Toggles it on/off
670   target - What to use when destroyed
671   target2 - What to use when it decides to start shooting at an enemy
672 
673   showhealth - set to 1 to show health bar on this entity when crosshair is over it
674 
675   teamowner - crosshair shows green for this team, red for opposite team
676 	0 - none
677 	1 - red
678 	2 - blue
679 
680   alliedTeam - team that this turret won't target
681 	0 - none
682 	1 - red
683 	2 - blue
684 
685   teamnodmg - team that turret does not take damage from
686 	0 - none
687 	1 - red
688 	2 - blue
689 
690 "icon" - icon that represents the objective on the radar
691 */
692 //-----------------------------------------------------
SP_misc_turret(gentity_t * base)693 void SP_misc_turret( gentity_t *base )
694 //-----------------------------------------------------
695 {
696 	char* s;
697 
698 	base->s.modelindex2 = G_ModelIndex( "models/map_objects/hoth/turret_bottom.md3" );
699 	base->s.modelindex = G_ModelIndex( "models/map_objects/hoth/turret_base.md3" );
700 	//base->playerModel = trap->G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/turret_canon.glm", base->s.modelindex );
701 	//base->s.radius = 80.0f;
702 
703 	//trap->G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL );
704 	//base->torsoBolt = trap->G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash03" );
705 
706 	G_SpawnString( "icon", "", &s );
707 	if (s && s[0])
708 	{
709 		// We have an icon, so index it now.  We are reusing the genericenemyindex
710 		// variable rather than adding a new one to the entity state.
711 		base->s.genericenemyindex = G_IconIndex(s);
712 	}
713 
714 	G_SetAngles( base, base->s.angles );
715 	G_SetOrigin( base, base->s.origin );
716 
717 	base->r.contents = CONTENTS_BODY;
718 
719 	VectorSet( base->r.maxs, 32.0f, 32.0f, 128.0f );
720 	VectorSet( base->r.mins, -32.0f, -32.0f, 0.0f );
721 
722 	base->use = turret_base_use;
723 	base->think = turret_base_think;
724 	// don't start working right away
725 	base->nextthink = level.time + FRAMETIME * 5;
726 
727 	trap->LinkEntity( (sharedEntity_t *)base );
728 
729 	if ( !turret_base_spawn_top( base ) )
730 	{
731 		G_FreeEntity( base );
732 	}
733 }
734 
735 //-----------------------------------------------------
turret_base_spawn_top(gentity_t * base)736 qboolean turret_base_spawn_top( gentity_t *base )
737 {
738 	vec3_t		org;
739 	int			t;
740 
741 	gentity_t *top = G_Spawn();
742 	if ( !top )
743 	{
744 		return qfalse;
745 	}
746 
747 	top->s.modelindex = G_ModelIndex( "models/map_objects/hoth/turret_top_new.md3" );
748 	top->s.modelindex2 = G_ModelIndex( "models/map_objects/hoth/turret_top.md3" );
749 	G_SetAngles( top, base->s.angles );
750 	VectorCopy( base->s.origin, org );
751 	org[2] += 128;
752 	G_SetOrigin( top, org );
753 
754 	base->r.ownerNum = top->s.number;
755 	top->r.ownerNum = base->s.number;
756 
757 	if ( base->team && base->team[0] && //level.gametype == GT_SIEGE &&
758 		!base->teamnodmg)
759 	{
760 		base->teamnodmg = atoi(base->team);
761 	}
762 	base->team = NULL;
763 	top->teamnodmg = base->teamnodmg;
764 	top->alliedTeam = base->alliedTeam;
765 
766 	base->s.eType = ET_GENERAL;
767 
768 	// Set up our explosion effect for the ExplodeDeath code....
769 	G_EffectIndex( "turret/explode" );
770 	G_EffectIndex( "sparks/spark_exp_nosnd" );
771 	G_EffectIndex( "turret/hoth_muzzle_flash" );
772 
773 	// this is really the pitch angle.....
774 	top->speed = 0;
775 
776 	// this is a random time offset for the no-enemy-search-around-mode
777 	top->count = Q_flrand(0.0f, 1.0f) * 9000;
778 
779 	if ( !base->health )
780 	{
781 		base->health = 3000;
782 	}
783 	top->health = base->health;
784 
785 	G_SpawnInt( "showhealth", "0", &t );
786 
787 	if (t)
788 	{ //a non-0 maxhealth value will mean we want to show the health on the hud
789 		top->maxHealth = base->health; //acts as "maxhealth"
790 		G_ScaleNetHealth(top);
791 
792 		base->maxHealth = base->health;
793 		G_ScaleNetHealth(base);
794 	}
795 
796 	base->takedamage = qtrue;
797 	base->pain = TurretBasePain;
798 	base->die = bottom_die;
799 
800 	//design specified shot speed
801 	G_SpawnFloat( "shotspeed", "1100", &base->mass );
802 	top->mass = base->mass;
803 
804 	//even if we don't want to show health, let's at least light the crosshair up properly over ourself
805 	if ( !top->s.teamowner )
806 	{
807 		top->s.teamowner = top->alliedTeam;
808 	}
809 
810 	base->alliedTeam = top->alliedTeam;
811 	base->s.teamowner = top->s.teamowner;
812 
813 	base->s.shouldtarget = qtrue;
814 	top->s.shouldtarget = qtrue;
815 
816 	//link them to each other
817 	base->target_ent = top;
818 	top->target_ent = base;
819 
820 	//top->s.owner = MAX_CLIENTS; //not owned by any client
821 
822 	// search radius
823 	if ( !base->radius )
824 	{
825 		base->radius = 1024;
826 	}
827 	top->radius = base->radius;
828 
829 	// How quickly to fire
830 	if ( !base->wait )
831 	{
832 		base->wait = 300 + Q_flrand(0.0f, 1.0f) * 55;
833 	}
834 	top->wait = base->wait;
835 
836 	if ( !base->splashDamage )
837 	{
838 		base->splashDamage = 300;
839 	}
840 	top->splashDamage = base->splashDamage;
841 
842 	if ( !base->splashRadius )
843 	{
844 		base->splashRadius = 128;
845 	}
846 	top->splashRadius = base->splashRadius;
847 
848 	// how much damage each shot does
849 	if ( !base->damage )
850 	{
851 		base->damage = 100;
852 	}
853 	top->damage = base->damage;
854 
855 	// how fast it turns
856 	if ( !base->speed )
857 	{
858 		base->speed = 20;
859 	}
860 	top->speed = base->speed;
861 
862 	VectorSet( top->r.maxs, 48.0f, 48.0f, 16.0f );
863 	VectorSet( top->r.mins, -48.0f, -48.0f, 0.0f );
864 	// Precache moving sounds
865 	//G_SoundIndex( "sound/chars/turret/startup.wav" );
866 	//G_SoundIndex( "sound/chars/turret/shutdown.wav" );
867 	//G_SoundIndex( "sound/chars/turret/ping.wav" );
868 	G_SoundIndex( "sound/vehicles/weapons/hoth_turret/turn.wav" );
869 	top->genericValue13 = G_EffectIndex( "turret/hoth_muzzle_flash" );
870 	top->genericValue14 = G_EffectIndex( "turret/hoth_shot" );
871 	top->genericValue15 = G_EffectIndex( "turret/hoth_impact" );
872 
873 	top->r.contents = CONTENTS_BODY;
874 
875 	//base->max_health = base->health;
876 	top->takedamage = qtrue;
877 	top->pain = TurretPain;
878 	top->die  = auto_turret_die;
879 
880 	top->material = MAT_METAL;
881 	//base->r.svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
882 
883 	// Register this so that we can use it for the missile effect
884 	RegisterItem( BG_FindItemForWeapon( WP_EMPLACED_GUN ));
885 
886 	// But set us as a turret so that we can be identified as a turret
887 	top->s.weapon = WP_EMPLACED_GUN;
888 
889 	trap->LinkEntity( (sharedEntity_t *)top );
890 	return qtrue;
891 }
892