1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6 
7 This file is part of the OpenJK source code.
8 
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 
23 #include "g_local.h"
24 #include "ghoul2/G2.h"
25 #include "qcommon/q_shared.h"
26 
27 void G_SetEnemy( gentity_t *self, gentity_t *enemy );
28 void finish_spawning_turretG2( gentity_t *base );
29 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
30 void turretG2_base_use( gentity_t *self, gentity_t *other, gentity_t *activator );
31 
32 
33 #define SPF_TURRETG2_CANRESPAWN	4
34 #define SPF_TURRETG2_TURBO		8
35 #define SPF_TURRETG2_LEAD_ENEMY	16
36 #define SPF_SHOWONRADAR			32
37 
38 #define	ARM_ANGLE_RANGE		60
39 #define	HEAD_ANGLE_RANGE	90
40 
41 #define name "models/map_objects/imp_mine/turret_canon.glm"
42 #define name2 "models/map_objects/imp_mine/turret_damage.md3"
43 #define name3 "models/map_objects/wedge/laser_cannon_model.glm"
44 
45 //special routine for tracking angles between client and server -rww
G2Tur_SetBoneAngles(gentity_t * ent,char * bone,vec3_t angles)46 void G2Tur_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles)
47 {
48 	int *thebone = &ent->s.boneIndex1;
49 	int *firstFree = NULL;
50 	int i = 0;
51 	int boneIndex = G_BoneIndex(bone);
52 	int flags, up, right, forward;
53 	vec3_t *boneVector = &ent->s.boneAngles1;
54 	vec3_t *freeBoneVec = NULL;
55 
56 	while (thebone)
57 	{
58 		if (!*thebone && !firstFree)
59 		{ //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing.
60 			firstFree = thebone;
61 			freeBoneVec = boneVector;
62 		}
63 		else if (*thebone)
64 		{
65 			if (*thebone == boneIndex)
66 			{ //this is it
67 				break;
68 			}
69 		}
70 
71 		switch (i)
72 		{
73 		case 0:
74 			thebone = &ent->s.boneIndex2;
75 			boneVector = &ent->s.boneAngles2;
76 			break;
77 		case 1:
78 			thebone = &ent->s.boneIndex3;
79 			boneVector = &ent->s.boneAngles3;
80 			break;
81 		case 2:
82 			thebone = &ent->s.boneIndex4;
83 			boneVector = &ent->s.boneAngles4;
84 			break;
85 		default:
86 			thebone = NULL;
87 			boneVector = NULL;
88 			break;
89 		}
90 
91 		i++;
92 	}
93 
94 	if (!thebone)
95 	{ //didn't find it, create it
96 		if (!firstFree)
97 		{ //no free bones.. can't do a thing then.
98 			Com_Printf("WARNING: NPC has no free bone indexes\n");
99 			return;
100 		}
101 
102 		thebone = firstFree;
103 
104 		*thebone = boneIndex;
105 		boneVector = freeBoneVec;
106 	}
107 
108 	//If we got here then we have a vector and an index.
109 
110 	//Copy the angles over the vector in the entitystate, so we can use the corresponding index
111 	//to set the bone angles on the client.
112 	VectorCopy(angles, *boneVector);
113 
114 	//Now set the angles on our server instance if we have one.
115 
116 	if (!ent->ghoul2)
117 	{
118 		return;
119 	}
120 
121 	flags = BONE_ANGLES_POSTMULT;
122 	up = POSITIVE_Y;
123 	right = NEGATIVE_Z;
124 	forward = NEGATIVE_X;
125 
126 	//first 3 bits is forward, second 3 bits is right, third 3 bits is up
127 	ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
128 
129 	trap->G2API_SetBoneAngles( ent->ghoul2,
130 					0,
131 					bone,
132 					angles,
133 					flags,
134 					up,
135 					right,
136 					forward,
137 					NULL,
138 					100,
139 					level.time );
140 }
141 
turretG2_set_models(gentity_t * self,qboolean dying)142 void turretG2_set_models( gentity_t *self, qboolean dying )
143 {
144 	if ( dying )
145 	{
146 		if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
147 		{
148 			self->s.modelindex = G_ModelIndex( name2 );
149 			self->s.modelindex2 = G_ModelIndex( name );
150 		}
151 
152 		trap->G2API_RemoveGhoul2Model( &self->ghoul2, 0 );
153 		G_KillG2Queue( self->s.number );
154 		self->s.modelGhoul2 = 0;
155 		/*
156 		trap->G2API_InitGhoul2Model( &self->ghoul2,
157 									name2,
158 									0, //base->s.modelindex,
159 									//note, this is not the same kind of index - this one's referring to the actual
160 									//index of the model in the g2 instance, whereas modelindex is the index of a
161 									//configstring -rww
162 									0,
163 									0,
164 									0,
165 									0);
166 		*/
167 	}
168 	else
169 	{
170 		if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
171 		{
172 			self->s.modelindex = G_ModelIndex( name );
173 			self->s.modelindex2 = G_ModelIndex( name2 );
174 			//set the new onw
175 			trap->G2API_InitGhoul2Model( &self->ghoul2,
176 										name,
177 										0, //base->s.modelindex,
178 										//note, this is not the same kind of index - this one's referring to the actual
179 										//index of the model in the g2 instance, whereas modelindex is the index of a
180 										//configstring -rww
181 										0,
182 										0,
183 										0,
184 										0);
185 		}
186 		else
187 		{
188 			self->s.modelindex = G_ModelIndex( name3 );
189 			//set the new onw
190 			trap->G2API_InitGhoul2Model( &self->ghoul2,
191 										name3,
192 										0, //base->s.modelindex,
193 										//note, this is not the same kind of index - this one's referring to the actual
194 										//index of the model in the g2 instance, whereas modelindex is the index of a
195 										//configstring -rww
196 										0,
197 										0,
198 										0,
199 										0);
200 		}
201 
202 		self->s.modelGhoul2 = 1;
203 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
204 		{//larger
205 			self->s.g2radius = 128;
206 		}
207 		else
208 		{
209 			self->s.g2radius = 80;
210 		}
211 
212 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
213 		{//different pitch bone and muzzle flash points
214 			G2Tur_SetBoneAngles(self, "pitch", vec3_origin);
215 			self->genericValue11 = trap->G2API_AddBolt( self->ghoul2, 0, "*muzzle1" );
216 			self->genericValue12 = trap->G2API_AddBolt( self->ghoul2, 0, "*muzzle2" );
217 		}
218 		else
219 		{
220 			G2Tur_SetBoneAngles(self, "Bone_body", vec3_origin);
221 			self->genericValue11 = trap->G2API_AddBolt( self->ghoul2, 0, "*flash03" );
222 		}
223 	}
224 }
225 
226 //------------------------------------------------------------------------------------------------------------
TurretG2Pain(gentity_t * self,gentity_t * attacker,int damage)227 void TurretG2Pain( gentity_t *self, gentity_t *attacker, int damage )
228 //------------------------------------------------------------------------------------------------------------
229 {
230 	if (self->paintarget && self->paintarget[0])
231 	{
232 		if (self->genericValue8 < level.time)
233 		{
234 			G_UseTargets2(self, self, self->paintarget);
235 			self->genericValue8 = level.time + self->genericValue4;
236 		}
237 	}
238 
239 	if ( attacker->client && attacker->client->ps.weapon == WP_DEMP2 )
240 	{
241 		self->attackDebounceTime = level.time + 2000 + Q_flrand(0.0f, 1.0f) * 500;
242 		self->painDebounceTime = self->attackDebounceTime;
243 	}
244 	if ( !self->enemy )
245 	{//react to being hit
246 		G_SetEnemy( self, attacker );
247 	}
248 	//self->s.health = self->health;
249 	//mmm..yes..bad.
250 }
251 
252 //------------------------------------------------------------------------------------------------------------
turretG2_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)253 void turretG2_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
254 //------------------------------------------------------------------------------------------------------------
255 {
256 	vec3_t	forward = { 0,0,-1 }, pos;
257 
258 	// Turn off the thinking of the base & use it's targets
259 	//self->think = NULL;
260 	self->use = NULL;
261 
262 	// clear my data
263 	self->die  = NULL;
264 	self->pain = NULL;
265 	self->takedamage = qfalse;
266 	self->s.health = self->health = 0;
267 	self->s.loopSound = 0;
268 	self->s.shouldtarget = qfalse;
269 	//self->s.owner = MAX_CLIENTS; //not owned by any client
270 
271 	// hack the effect angle so that explode death can orient the effect properly
272 	if ( self->spawnflags & 2 )
273 	{
274 		VectorSet( forward, 0, 0, 1 );
275 	}
276 
277 //	VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
278 
279 	VectorMA( self->r.currentOrigin, 12, forward, pos );
280 	G_PlayEffect( EFFECT_EXPLOSION_TURRET, pos, forward );
281 
282 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
283 	{
284 		G_RadiusDamage( self->r.currentOrigin,
285 						attacker,
286 						self->splashDamage,
287 						self->splashRadius,
288 						attacker,
289 						NULL,
290 						MOD_UNKNOWN );
291 	}
292 
293 	if ( self->s.eFlags & EF_SHADER_ANIM )
294 	{
295 		self->s.frame = 1; // black
296 	}
297 
298 	self->s.weapon = 0; // crosshair code uses this to mark crosshair red
299 
300 	if ( self->s.modelindex2 )
301 	{
302 		// switch to damage model if we should
303 		turretG2_set_models( self, qtrue );
304 
305 		VectorCopy( self->r.currentAngles, self->s.apos.trBase );
306 		VectorClear( self->s.apos.trDelta );
307 
308 		if ( self->target )
309 		{
310 			G_UseTargets( self, attacker );
311 		}
312 
313 		if (self->spawnflags & SPF_TURRETG2_CANRESPAWN)
314 		{//respawn
315 			if (self->health < 1 && !self->genericValue5)
316 			{ //we are dead, set our respawn delay if we have one
317 				self->genericValue5 = level.time + self->count;
318 			}
319 		}
320 	}
321 	else
322 	{
323 		ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
324 	}
325 }
326 
327 #define START_DIS 15
328 
329 //start an animation on model_root both server side and client side
TurboLaser_SetBoneAnim(gentity_t * eweb,int startFrame,int endFrame)330 void TurboLaser_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame)
331 {
332 	//set info on the entity so it knows to start the anim on the client next snapshot.
333 	eweb->s.eFlags |= EF_G2ANIMATING;
334 
335 	if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame)
336 	{ //already playing this anim, let's flag it to restart
337 		eweb->s.torsoFlip = !eweb->s.torsoFlip;
338 	}
339 	else
340 	{
341 		eweb->s.torsoAnim = startFrame;
342 		eweb->s.legsAnim = endFrame;
343 	}
344 
345 	//now set the animation on the server ghoul2 instance.
346 	assert(eweb->ghoul2);
347 	trap->G2API_SetBoneAnim(eweb->ghoul2, 0, "model_root", startFrame, endFrame,
348 		(BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100);
349 }
350 
351 extern void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir );
352 //----------------------------------------------------------------
turretG2_fire(gentity_t * ent,vec3_t start,vec3_t dir)353 static void turretG2_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
354 //----------------------------------------------------------------
355 {
356 	vec3_t		org, ang;
357 	gentity_t	*bolt;
358 
359 	if ( (trap->PointContents( start, ent->s.number )&MASK_SHOT) )
360 	{
361 		return;
362 	}
363 
364 	VectorMA( start, -START_DIS, dir, org ); // dumb....
365 
366 	if ( ent->random )
367 	{
368 		vectoangles( dir, ang );
369 		ang[PITCH] += flrand( -ent->random, ent->random );
370 		ang[YAW] += flrand( -ent->random, ent->random );
371 		AngleVectors( ang, dir, NULL, NULL );
372 	}
373 
374 	vectoangles(dir, ang);
375 
376 	if ( (ent->spawnflags&SPF_TURRETG2_TURBO) )
377 	{
378 		//muzzle flash
379 		G_PlayEffectID( ent->genericValue13, org, ang );
380 		WP_FireTurboLaserMissile( ent, start, dir );
381 		if ( ent->alt_fire )
382 		{
383 			TurboLaser_SetBoneAnim( ent, 2, 3 );
384 		}
385 		else
386 		{
387 			TurboLaser_SetBoneAnim( ent, 0, 1 );
388 		}
389 	}
390 	else
391 	{
392 		G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), org, ang );
393 		bolt = G_Spawn();
394 
395 		bolt->classname = "turret_proj";
396 		bolt->nextthink = level.time + 10000;
397 		bolt->think = G_FreeEntity;
398 		bolt->s.eType = ET_MISSILE;
399 		bolt->s.weapon = WP_BLASTER;
400 		bolt->r.ownerNum = ent->s.number;
401 		bolt->damage = ent->damage;
402 		bolt->alliedTeam = ent->alliedTeam;
403 		bolt->teamnodmg = ent->teamnodmg;
404 		bolt->dflags = (DAMAGE_NO_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS);		// Don't push them around, or else we are constantly re-aiming
405 		bolt->splashDamage = ent->splashDamage;
406 		bolt->splashRadius = ent->splashDamage;
407 		bolt->methodOfDeath = MOD_TARGET_LASER;//MOD_ENERGY;
408 		bolt->splashMethodOfDeath = MOD_TARGET_LASER;//MOD_ENERGY;
409 		bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
410 		//bolt->trigger_formation = qfalse;		// don't draw tail on first frame
411 
412 		VectorSet( bolt->r.maxs, 1.5, 1.5, 1.5 );
413 		VectorScale( bolt->r.maxs, -1, bolt->r.mins );
414 		bolt->s.pos.trType = TR_LINEAR;
415 		bolt->s.pos.trTime = level.time;
416 		VectorCopy( start, bolt->s.pos.trBase );
417 		VectorScale( dir, ent->mass, bolt->s.pos.trDelta );
418 		SnapVector( bolt->s.pos.trDelta );		// save net bandwidth
419 		VectorCopy( start, bolt->r.currentOrigin);
420 	}
421 }
422 
turretG2_respawn(gentity_t * self)423 void turretG2_respawn( gentity_t *self )
424 {
425 	self->use = turretG2_base_use;
426 	self->pain = TurretG2Pain;
427 	self->die  = turretG2_die;
428 	self->takedamage = qtrue;
429 	self->s.shouldtarget = qtrue;
430 	//self->s.owner = MAX_CLIENTS; //not owned by any client
431 	if ( self->s.eFlags & EF_SHADER_ANIM )
432 	{
433 		self->s.frame = 0; // normal
434 	}
435 	self->s.weapon = WP_TURRET; // crosshair code uses this to mark crosshair red
436 
437 	turretG2_set_models( self, qfalse );
438 	self->s.health = self->health = self->genericValue6;
439 	if (self->maxHealth) {
440 		G_ScaleNetHealth(self);
441 	}
442 	self->genericValue5 = 0;//clear this now
443 }
444 
445 //-----------------------------------------------------
turretG2_head_think(gentity_t * self)446 void turretG2_head_think( gentity_t *self )
447 //-----------------------------------------------------
448 {
449 	// if it's time to fire and we have an enemy, then gun 'em down!  pushDebounce time controls next fire time
450 	if ( self->enemy
451 		&& self->setTime < level.time
452 		&& self->attackDebounceTime < level.time )
453 	{
454 		vec3_t		fwd, org;
455 		mdxaBone_t	boltMatrix;
456 
457 		// set up our next fire time
458 		self->setTime = level.time + self->wait;
459 
460 		// Getting the flash bolt here
461 		trap->G2API_GetBoltMatrix( self->ghoul2,
462 					0,
463 					(self->alt_fire?self->genericValue12:self->genericValue11),
464 					&boltMatrix,
465 					self->r.currentAngles,
466 					self->r.currentOrigin,
467 					level.time,
468 					NULL,
469 					self->modelScale );
470 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
471 		{
472 			self->alt_fire = !self->alt_fire;
473 		}
474 
475 		BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org );
476 		//BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Y, fwd );
477 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
478 		{
479 			BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, fwd );
480 		}
481 		else
482 		{
483 			BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, fwd );
484 		}
485 
486 		VectorMA( org, START_DIS, fwd, org );
487 
488 		turretG2_fire( self, org, fwd );
489 		self->fly_sound_debounce_time = level.time;//used as lastShotTime
490 	}
491 }
492 
493 //-----------------------------------------------------
turretG2_aim(gentity_t * self)494 static void turretG2_aim( gentity_t *self )
495 //-----------------------------------------------------
496 {
497 	vec3_t	enemyDir, org, org2;
498 	vec3_t	desiredAngles, setAngle;
499 	float	diffYaw = 0.0f, diffPitch = 0.0f;
500 	float	maxYawSpeed = (self->spawnflags&SPF_TURRETG2_TURBO)?30.0f:14.0f;
501 	float	maxPitchSpeed = (self->spawnflags&SPF_TURRETG2_TURBO)?15.0f:3.0f;
502 
503 	// move our gun base yaw to where we should be at this time....
504 	BG_EvaluateTrajectory( &self->s.apos, level.time, self->r.currentAngles );
505 	self->r.currentAngles[YAW] = AngleNormalize360( self->r.currentAngles[YAW] );
506 	self->speed = AngleNormalize360( self->speed );
507 
508 	if ( self->enemy )
509 	{
510 		mdxaBone_t	boltMatrix;
511 		// ...then we'll calculate what new aim adjustments we should attempt to make this frame
512 		// Aim at enemy
513 		if ( self->enemy->client )
514 		{
515 			VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
516 		}
517 		else
518 		{
519 			VectorCopy( self->enemy->r.currentOrigin, org );
520 		}
521 		if ( self->spawnflags & 2 )
522 		{
523 			org[2] -= 15;
524 		}
525 		else
526 		{
527 			org[2] -= 5;
528 		}
529 
530 		if ( (self->spawnflags&SPF_TURRETG2_LEAD_ENEMY) )
531 		{//we want to lead them a bit
532 			vec3_t diff, velocity;
533 			float dist;
534 			VectorSubtract( org, self->s.origin, diff );
535 			dist = VectorNormalize( diff );
536 			if ( self->enemy->client )
537 			{
538 				VectorCopy( self->enemy->client->ps.velocity, velocity );
539 			}
540 			else
541 			{
542 				VectorCopy( self->enemy->s.pos.trDelta, velocity );
543 			}
544 			VectorMA( org, (dist/self->mass), velocity, org );
545 		}
546 
547 		// Getting the "eye" here
548 		trap->G2API_GetBoltMatrix( self->ghoul2,
549 					0,
550 					(self->alt_fire?self->genericValue12:self->genericValue11),
551 					&boltMatrix,
552 					self->r.currentAngles,
553 					self->s.origin,
554 					level.time,
555 					NULL,
556 					self->modelScale );
557 
558 		BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org2 );
559 
560 		VectorSubtract( org, org2, enemyDir );
561 		vectoangles( enemyDir, desiredAngles );
562 
563 		diffYaw = AngleSubtract( self->r.currentAngles[YAW], desiredAngles[YAW] );
564 		diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] );
565 	}
566 	else
567 	{
568 		// no enemy, so make us slowly sweep back and forth as if searching for a new one
569 //		diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f;	// don't do this for now since it can make it go into walls.
570 	}
571 
572 	if ( diffYaw )
573 	{
574 		// cap max speed....
575 		if ( fabs(diffYaw) > maxYawSpeed )
576 		{
577 			diffYaw = ( diffYaw >= 0 ? maxYawSpeed : -maxYawSpeed );
578 		}
579 
580 		// ...then set up our desired yaw
581 		VectorSet( setAngle, 0.0f, diffYaw, 0.0f );
582 
583 		VectorCopy( self->r.currentAngles, self->s.apos.trBase );
584 		VectorScale( setAngle,- 5, self->s.apos.trDelta );
585 		self->s.apos.trTime = level.time;
586 		self->s.apos.trType = TR_LINEAR;
587 	}
588 
589 	if ( diffPitch )
590 	{
591 		if ( fabs(diffPitch) > maxPitchSpeed )
592 		{
593 			// cap max speed
594 			self->speed += (diffPitch > 0.0f) ? -maxPitchSpeed : maxPitchSpeed;
595 		}
596 		else
597 		{
598 			// small enough, so just add half the diff so we smooth out the stopping
599 			self->speed -= ( diffPitch );//desiredAngles[PITCH];
600 		}
601 
602 		// 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?
603 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
604 		{
605 			if ( self->spawnflags & 2 )
606 			{
607 				VectorSet( desiredAngles, 0.0f, 0.0f, -self->speed );
608 			}
609 			else
610 			{
611 				VectorSet( desiredAngles, 0.0f, 0.0f, self->speed );
612 			}
613 			G2Tur_SetBoneAngles(self, "pitch", desiredAngles);
614 		}
615 		else
616 		{
617 			if ( self->spawnflags & 2 )
618 			{
619 				VectorSet( desiredAngles, self->speed, 0.0f, 0.0f );
620 			}
621 			else
622 			{
623 				VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f );
624 			}
625 			G2Tur_SetBoneAngles(self, "Bone_body", desiredAngles);
626 		}
627 		/*
628 		trap->G2API_SetBoneAngles( self->ghoul2,
629 						0,
630 						"Bone_body",
631 						desiredAngles,
632 						BONE_ANGLES_POSTMULT,
633 						POSITIVE_Y,
634 						POSITIVE_Z,
635 						POSITIVE_X,
636 						NULL,
637 						100,
638 						level.time );
639 						*/
640 	}
641 
642 	if ( diffYaw || diffPitch )
643 	{//FIXME: turbolaser sounds
644 		if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
645 		{
646 			self->s.loopSound = G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" );
647 		}
648 		else
649 		{
650 			self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
651 		}
652 	}
653 	else
654 	{
655 		self->s.loopSound = 0;
656 	}
657 }
658 
659 //-----------------------------------------------------
turretG2_turnoff(gentity_t * self)660 static void turretG2_turnoff( gentity_t *self )
661 //-----------------------------------------------------
662 {
663 	if ( self->enemy == NULL )
664 	{
665 		// we don't need to turnoff
666 		return;
667 	}
668 	if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
669 	{
670 		TurboLaser_SetBoneAnim( self, 4, 5 );
671 	}
672 	// shut-down sound
673 	if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
674 	{
675 		G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
676 	}
677 
678 	// make turret play ping sound for 5 seconds
679 	self->aimDebounceTime = level.time + 5000;
680 
681 	// Clear enemy
682 	self->enemy = NULL;
683 }
684 
685 //-----------------------------------------------------
turretG2_find_enemies(gentity_t * self)686 static qboolean turretG2_find_enemies( gentity_t *self )
687 //-----------------------------------------------------
688 {
689 	qboolean	found = qfalse;
690 	int			i, count;
691 	float		bestDist = self->radius * self->radius;
692 	float		enemyDist;
693 	vec3_t		enemyDir, org, org2;
694 	qboolean	foundClient = qfalse;
695 	gentity_t	*entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
696 
697 	if ( self->aimDebounceTime > level.time ) // time since we've been shut off
698 	{
699 		// We were active and alert, i.e. had an enemy in the last 3 secs
700 		if ( self->painDebounceTime < level.time )
701 		{
702 			if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
703 			{
704 				G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
705 			}
706 			self->painDebounceTime = level.time + 1000;
707 		}
708 	}
709 
710 	VectorCopy( self->r.currentOrigin, org2 );
711 	if ( self->spawnflags & 2 )
712 	{
713 		org2[2] += 20;
714 	}
715 	else
716 	{
717 		org2[2] -= 20;
718 	}
719 
720 	count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
721 
722 	for ( i = 0; i < count; i++ )
723 	{
724 		trace_t	tr;
725 		target = entity_list[i];
726 
727 		if ( !target->client )
728 		{
729 			// only attack clients
730 			if ( !(target->flags&FL_BBRUSH)//not a breakable brush
731 				|| !target->takedamage//is a bbrush, but invincible
732 				|| (target->NPC_targetname&&self->targetname&&Q_stricmp(target->NPC_targetname,self->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me
733 			{
734 				continue;
735 			}
736 			//else: we will shoot at bbrushes!
737 		}
738 		if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
739 		{
740 			continue;
741 		}
742 		if ( target->client && target->client->sess.sessionTeam == TEAM_SPECTATOR )
743 		{
744 			continue;
745 		}
746 		if ( target->client && target->client->tempSpectate >= level.time )
747 		{
748 			continue;
749 		}
750 		if ( self->alliedTeam )
751 		{
752 			if ( target->client )
753 			{
754 				if ( target->client->sess.sessionTeam == self->alliedTeam )
755 				{
756 					// A bot/client/NPC we don't want to shoot
757 					continue;
758 				}
759 			}
760 			else if ( target->teamnodmg == self->alliedTeam )
761 			{
762 				// An ent we don't want to shoot
763 				continue;
764 			}
765 		}
766 		if ( !trap->InPVS( org2, target->r.currentOrigin ))
767 		{
768 			continue;
769 		}
770 
771 		if ( target->client )
772 		{
773 			VectorCopy( target->client->renderInfo.eyePoint, org );
774 		}
775 		else
776 		{
777 			VectorCopy( target->r.currentOrigin, org );
778 		}
779 
780 		if ( self->spawnflags & 2 )
781 		{
782 			org[2] -= 15;
783 		}
784 		else
785 		{
786 			org[2] += 5;
787 		}
788 
789 		trap->Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, qfalse, 0, 0 );
790 
791 		if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
792 		{
793 			// Only acquire if have a clear shot, Is it in range and closer than our best?
794 			VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir );
795 			enemyDist = VectorLengthSquared( enemyDir );
796 
797 			if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current
798 			{
799 				if ( self->attackDebounceTime < level.time )
800 				{
801 					// We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
802 					if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
803 					{
804 						G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
805 					}
806 
807 					// Wind up turrets for a bit
808 					self->attackDebounceTime = level.time + 1400;
809 				}
810 
811 				bestTarget = target;
812 				bestDist = enemyDist;
813 				found = qtrue;
814 				if ( target->client )
815 				{//prefer clients over non-clients
816 					foundClient = qtrue;
817 				}
818 			}
819 		}
820 	}
821 
822 	if ( found )
823 	{
824 		/*
825 		if ( !self->enemy )
826 		{//just aquired one
827 			AddSoundEvent( bestTarget, self->r.currentOrigin, 256, AEL_DISCOVERED );
828 			AddSightEvent( bestTarget, self->r.currentOrigin, 512, AEL_DISCOVERED, 20 );
829 		}
830 		*/
831 		G_SetEnemy( self, bestTarget );
832 		if ( VALIDSTRING( self->target2 ))
833 		{
834 			G_UseTargets2( self, self, self->target2 );
835 		}
836 	}
837 
838 	return found;
839 }
840 
841 //-----------------------------------------------------
turretG2_base_think(gentity_t * self)842 void turretG2_base_think( gentity_t *self )
843 //-----------------------------------------------------
844 {
845 	qboolean	turnOff = qtrue;
846 	float		enemyDist;
847 	vec3_t		enemyDir, org, org2;
848 
849 	self->nextthink = level.time + FRAMETIME;
850 
851 	if ( self->health <= 0 )
852 	{//dead
853 		if (self->spawnflags & SPF_TURRETG2_CANRESPAWN)
854 		{//can respawn
855 			if ( self->genericValue5 && self->genericValue5 < level.time )
856 			{ //we are dead, see if it's time to respawn
857 				turretG2_respawn( self );
858 			}
859 		}
860 		return;
861 	}
862 	else if ( self->spawnflags & 1 )
863 	{// not turned on
864 		turretG2_turnoff( self );
865 		turretG2_aim( self );
866 
867 		// No target
868 		self->flags |= FL_NOTARGET;
869 		return;
870 	}
871 	else
872 	{
873 		// I'm all hot and bothered
874 		self->flags &= ~FL_NOTARGET;
875 	}
876 
877 	if ( self->enemy )
878 	{
879 		if ( self->enemy->health < 0
880 			|| !self->enemy->inuse )
881 		{
882 			self->enemy = NULL;
883 		}
884 	}
885 
886 	if ( self->last_move_time < level.time )
887 	{//MISNOMER: used a enemy recalcing debouncer
888 		if ( turretG2_find_enemies( self ) )
889 		{//found one
890 			turnOff = qfalse;
891 			if ( self->enemy && self->enemy->client )
892 			{//hold on to clients for a min of 3 seconds
893 				self->last_move_time = level.time + 3000;
894 			}
895 			else
896 			{//hold less
897 				self->last_move_time = level.time + 500;
898 			}
899 		}
900 	}
901 
902 	if ( self->enemy != NULL )
903 	{
904 		if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR )
905 		{//don't keep going after spectators
906 			self->enemy = NULL;
907 		}
908 		else if ( self->enemy->client && self->enemy->client->tempSpectate >= level.time )
909 		{//don't keep going after spectators
910 			self->enemy = NULL;
911 		}
912 		else
913 		{//FIXME: remain single-minded or look for a new enemy every now and then?
914 			// enemy is alive
915 			VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir );
916 			enemyDist = VectorLengthSquared( enemyDir );
917 
918 			if ( enemyDist < self->radius * self->radius )
919 			{
920 				// was in valid radius
921 				if ( trap->InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) )
922 				{
923 					// Every now and again, check to see if we can even trace to the enemy
924 					trace_t tr;
925 
926 					if ( self->enemy->client )
927 					{
928 						VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
929 					}
930 					else
931 					{
932 						VectorCopy( self->enemy->r.currentOrigin, org );
933 					}
934 					VectorCopy( self->r.currentOrigin, org2 );
935 					if ( self->spawnflags & 2 )
936 					{
937 						org2[2] += 10;
938 					}
939 					else
940 					{
941 						org2[2] -= 10;
942 					}
943 					trap->Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, qfalse, 0, 0 );
944 
945 					if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number )
946 					{
947 						turnOff = qfalse;	// Can see our enemy
948 					}
949 				}
950 			}
951 
952 		}
953 	}
954 
955 	if ( turnOff )
956 	{
957 		if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
958 		{
959 			turretG2_turnoff( self );
960 		}
961 	}
962 	else
963 	{
964 		// keep our enemy for a minimum of 2 seconds from now
965 		self->bounceCount = level.time + 2000 + Q_flrand(0.0f, 1.0f) * 150;
966 	}
967 
968 	turretG2_aim( self );
969 	if ( !turnOff )
970 	{
971 		turretG2_head_think( self );
972 	}
973 }
974 
975 //-----------------------------------------------------------------------------
turretG2_base_use(gentity_t * self,gentity_t * other,gentity_t * activator)976 void turretG2_base_use( gentity_t *self, gentity_t *other, gentity_t *activator )
977 //-----------------------------------------------------------------------------
978 {
979 	// Toggle on and off
980 	self->spawnflags = (self->spawnflags ^ 1);
981 
982 	if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
983 	{
984 		self->s.frame = 1; // black
985 	}
986 	else
987 	{
988 		self->s.frame = 0; // glow
989 	}
990 }
991 
992 
993 /*QUAKED misc_turretG2 (1 0 0) (-8 -8 -22) (8 8 0) START_OFF UPSIDE_DOWN CANRESPAWN TURBO LEAD SHOWRADAR
994 
995 Turret that hangs from the ceiling, will aim and shoot at enemies
996 
997   START_OFF - Starts off
998   UPSIDE_DOWN - make it rest on a surface/floor instead of hanging from the ceiling
999   CANRESPAWN - will respawn after being killed (use count)
1000   TURBO - Big-ass, Boxy Death Star Turbo Laser version
1001   LEAD - Turret will aim ahead of moving targets ("lead" them)
1002   SHOWRADAR - show on radar
1003 
1004   radius - How far away an enemy can be for it to pick it up (default 512)
1005   wait	- Time between shots (default 150 ms)
1006   dmg	- How much damage each shot does (default 5)
1007   health - How much damage it can take before exploding (default 100)
1008   count - if CANRESPAWN spawnflag, decides how long it is before gun respawns (in ms) - defaults to 20000 (20 seconds)
1009 
1010   paintarget - target to fire off upon being hurt
1011   painwait - ms to wait between firing off pain targets
1012 
1013   random - random error (in degrees) of projectile direction when it comes out of the muzzle (default is 2)
1014 
1015   shotspeed - the speed of the missile it fires travels at (default is 1100 for regular turrets, 20000 for TURBOLASERS)
1016 
1017   splashDamage - How much damage the explosion does
1018   splashRadius - The radius of the explosion
1019 
1020   targetname - Toggles it on/off
1021   target - What to use when destroyed
1022   target2 - What to use when it decides to start shooting at an enemy
1023 
1024   showhealth - set to 1 to show health bar on this entity when crosshair is over it
1025 
1026   teamowner - crosshair shows green for this team, red for opposite team
1027 	0 - none
1028 	1 - red
1029 	2 - blue
1030 
1031   alliedTeam - team that this turret won't target
1032 	0 - none
1033 	1 - red
1034 	2 - blue
1035 
1036   teamnodmg - team that turret does not take damage from
1037 	0 - none
1038 	1 - red
1039 	2 - blue
1040 
1041   customscale - custom scaling size. 100 is normal size, 1024 is the max scaling. this will change the bounding box size, so be careful of starting in solid!
1042 
1043 "icon" - icon that represents the objective on the radar
1044 */
1045 //-----------------------------------------------------
SP_misc_turretG2(gentity_t * base)1046 void SP_misc_turretG2( gentity_t *base )
1047 //-----------------------------------------------------
1048 {
1049 	int customscaleVal;
1050 	char* s;
1051 
1052 	turretG2_set_models( base, qfalse );
1053 
1054 	G_SpawnInt("painwait", "0", &base->genericValue4);
1055 	base->genericValue8 = 0;
1056 
1057 	G_SpawnInt("customscale", "0", &customscaleVal);
1058 	base->s.iModelScale = customscaleVal;
1059 	if (base->s.iModelScale)
1060 	{
1061 		if (base->s.iModelScale > 1023)
1062 		{
1063 			base->s.iModelScale = 1023;
1064 		}
1065 		base->modelScale[0] = base->modelScale[1] = base->modelScale[2] = base->s.iModelScale/100.0f;
1066 	}
1067 
1068 	G_SpawnString( "icon", "", &s );
1069 	if (s && s[0])
1070 	{
1071 		// We have an icon, so index it now.  We are reusing the genericenemyindex
1072 		// variable rather than adding a new one to the entity state.
1073 		base->s.genericenemyindex = G_IconIndex(s);
1074 	}
1075 
1076 	finish_spawning_turretG2( base );
1077 
1078 	if (( base->spawnflags & 1 )) // Start_Off
1079 	{
1080 		base->s.frame = 1; // black
1081 	}
1082 	else
1083 	{
1084 		base->s.frame = 0; // glow
1085 	}
1086 	if ( !(base->spawnflags&SPF_TURRETG2_TURBO) )
1087 	{
1088 		base->s.eFlags |= EF_SHADER_ANIM;
1089 	}
1090 
1091 	if (base->spawnflags & SPF_SHOWONRADAR)
1092 	{
1093 		base->s.eFlags |= EF_RADAROBJECT;
1094 	}
1095 #undef name
1096 #undef name2
1097 #undef name3
1098 }
1099 
1100 //-----------------------------------------------------
finish_spawning_turretG2(gentity_t * base)1101 void finish_spawning_turretG2( gentity_t *base )
1102 {
1103 	vec3_t		fwd;
1104 	int			t;
1105 
1106 	if ( (base->spawnflags&2) )
1107 	{
1108 		base->s.angles[ROLL] += 180;
1109 		base->s.origin[2] -= 22.0f;
1110 	}
1111 
1112 	G_SetAngles( base, base->s.angles );
1113 	AngleVectors( base->r.currentAngles, fwd, NULL, NULL );
1114 
1115 	G_SetOrigin(base, base->s.origin);
1116 
1117 	base->s.eType = ET_GENERAL;
1118 
1119 	if ( base->team && base->team[0] && //level.gametype == GT_SIEGE &&
1120 		!base->teamnodmg)
1121 	{
1122 		base->teamnodmg = atoi(base->team);
1123 	}
1124 	base->team = NULL;
1125 
1126 	// Set up our explosion effect for the ExplodeDeath code....
1127 	G_EffectIndex( "turret/explode" );
1128 	G_EffectIndex( "sparks/spark_exp_nosnd" );
1129 
1130 	base->use = turretG2_base_use;
1131 	base->pain = TurretG2Pain;
1132 
1133 	// don't start working right away
1134 	base->think = turretG2_base_think;
1135 	base->nextthink = level.time + FRAMETIME * 5;
1136 
1137 	// this is really the pitch angle.....
1138 	base->speed = 0;
1139 
1140 	// respawn time defaults to 20 seconds
1141 	if ( (base->spawnflags&SPF_TURRETG2_CANRESPAWN) && !base->count )
1142 	{
1143 		base->count = 20000;
1144 	}
1145 
1146 	G_SpawnFloat( "shotspeed", "0", &base->mass );
1147 	if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
1148 	{
1149 		if ( !base->random )
1150 		{//error worked into projectile direction
1151 			base->random = 2.0f;
1152 		}
1153 
1154 		if ( !base->mass )
1155 		{//misnomer: speed of projectile
1156 			base->mass = 20000;
1157 		}
1158 
1159 		if ( !base->health )
1160 		{
1161 			base->health = 2000;
1162 		}
1163 
1164 		// search radius
1165 		if ( !base->radius )
1166 		{
1167 			base->radius = 32768;
1168 		}
1169 
1170 		// How quickly to fire
1171 		if ( !base->wait )
1172 		{
1173 			base->wait = 1000;// + Q_flrand(0.0f, 1.0f) * 500;
1174 		}
1175 
1176 		if ( !base->splashDamage )
1177 		{
1178 			base->splashDamage = 200;
1179 		}
1180 
1181 		if ( !base->splashRadius )
1182 		{
1183 			base->splashRadius = 500;
1184 		}
1185 
1186 		// how much damage each shot does
1187 		if ( !base->damage )
1188 		{
1189 			base->damage = 500;
1190 		}
1191 
1192 		if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
1193 		{
1194 			VectorSet( base->r.maxs, 64.0f, 64.0f, 30.0f );
1195 			VectorSet( base->r.mins, -64.0f, -64.0f, -30.0f );
1196 		}
1197 		//start in "off" anim
1198 		TurboLaser_SetBoneAnim( base, 4, 5 );
1199 		if ( level.gametype == GT_SIEGE )
1200 		{//FIXME: designer-specified?
1201 			//FIXME: put on other entities, too, particularly siege objectives and bbrushes...
1202 			base->s.eFlags2 |= EF2_BRACKET_ENTITY;
1203 		}
1204 	}
1205 	else
1206 	{
1207 		if ( !base->random )
1208 		{//error worked into projectile direction
1209 			base->random = 2.0f;
1210 		}
1211 
1212 		if ( !base->mass )
1213 		{//misnomer: speed of projectile
1214 			base->mass = 1100;
1215 		}
1216 
1217 		if ( !base->health )
1218 		{
1219 			base->health = 100;
1220 		}
1221 
1222 		// search radius
1223 		if ( !base->radius )
1224 		{
1225 			base->radius = 512;
1226 		}
1227 
1228 		// How quickly to fire
1229 		if ( !base->wait )
1230 		{
1231 			base->wait = 150 + Q_flrand(0.0f, 1.0f) * 55;
1232 		}
1233 
1234 		if ( !base->splashDamage )
1235 		{
1236 			base->splashDamage = 10;
1237 		}
1238 
1239 		if ( !base->splashRadius )
1240 		{
1241 			base->splashRadius = 25;
1242 		}
1243 
1244 		// how much damage each shot does
1245 		if ( !base->damage )
1246 		{
1247 			base->damage = 5;
1248 		}
1249 
1250 		if ( base->spawnflags & 2 )
1251 		{//upside-down, invert r.mins and maxe
1252 			VectorSet( base->r.maxs, 10.0f, 10.0f, 30.0f );
1253 			VectorSet( base->r.mins, -10.0f, -10.0f, 0.0f );
1254 		}
1255 		else
1256 		{
1257 			VectorSet( base->r.maxs, 10.0f, 10.0f, 0.0f );
1258 			VectorSet( base->r.mins, -10.0f, -10.0f, -30.0f );
1259 		}
1260 	}
1261 
1262 	//stash health off for respawn.  NOTE: cannot use maxhealth because that might not be set if not showing the health bar
1263 	base->genericValue6 = base->health;
1264 
1265 	G_SpawnInt( "showhealth", "0", &t );
1266 	if (t)
1267 	{ //a non-0 maxhealth value will mean we want to show the health on the hud
1268 		base->maxHealth = base->health;
1269 		G_ScaleNetHealth(base);
1270 		base->s.shouldtarget = qtrue;
1271 		//base->s.owner = MAX_CLIENTS; //not owned by any client
1272 	}
1273 
1274 	if (base->s.iModelScale)
1275 	{ //let's scale the bbox too...
1276 		float fScale = base->s.iModelScale/100.0f;
1277 		VectorScale(base->r.mins, fScale, base->r.mins);
1278 		VectorScale(base->r.maxs, fScale, base->r.maxs);
1279 	}
1280 
1281 	// Precache special FX and moving sounds
1282 	if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
1283 	{
1284 		base->genericValue13 = G_EffectIndex( "turret/turb_muzzle_flash" );
1285 		base->genericValue14 = G_EffectIndex( "turret/turb_shot" );
1286 		base->genericValue15 = G_EffectIndex( "turret/turb_impact" );
1287 		//FIXME: Turbo Laser Cannon sounds!
1288 		G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" );
1289 	}
1290 	else
1291 	{
1292 		G_SoundIndex( "sound/chars/turret/startup.wav" );
1293 		G_SoundIndex( "sound/chars/turret/shutdown.wav" );
1294 		G_SoundIndex( "sound/chars/turret/ping.wav" );
1295 		G_SoundIndex( "sound/chars/turret/move.wav" );
1296 	}
1297 
1298 	base->r.contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP;
1299 
1300 	//base->max_health = base->health;
1301 	base->takedamage = qtrue;
1302 	base->die  = turretG2_die;
1303 
1304 	base->material = MAT_METAL;
1305 	//base->r.svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
1306 
1307 	// Register this so that we can use it for the missile effect
1308 	RegisterItem( BG_FindItemForWeapon( WP_BLASTER ));
1309 
1310 	// But set us as a turret so that we can be identified as a turret
1311 	base->s.weapon = WP_TURRET;
1312 
1313 	trap->LinkEntity( (sharedEntity_t *)base );
1314 }
1315