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