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