1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5 
6 This file is part of Tremulous.
7 
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12 
13 Tremulous is distributed in the hope that it will be
14 useful, 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 Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 
26 /*
27 ================
28 G_setBuildableAnim
29 
30 Triggers an animation client side
31 ================
32 */
G_setBuildableAnim(gentity_t * ent,buildableAnimNumber_t anim,qboolean force)33 void G_setBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force )
34 {
35   int localAnim = anim;
36 
37   if( force )
38     localAnim |= ANIM_FORCEBIT;
39 
40   localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT );
41 
42   ent->s.legsAnim = localAnim;
43 }
44 
45 /*
46 ================
47 G_setIdleBuildableAnim
48 
49 Set the animation to use whilst no other animations are running
50 ================
51 */
G_setIdleBuildableAnim(gentity_t * ent,buildableAnimNumber_t anim)52 void G_setIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim )
53 {
54   ent->s.torsoAnim = anim;
55 }
56 
57 /*
58 ===============
59 G_CheckSpawnPoint
60 
61 Check if a spawn at a specified point is valid
62 ===============
63 */
G_CheckSpawnPoint(int spawnNum,vec3_t origin,vec3_t normal,buildable_t spawn,vec3_t spawnOrigin)64 gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal,
65     buildable_t spawn, vec3_t spawnOrigin )
66 {
67   float   displacement;
68   vec3_t  mins, maxs;
69   vec3_t  cmins, cmaxs;
70   vec3_t  localOrigin;
71   trace_t tr;
72 
73   BG_FindBBoxForBuildable( spawn, mins, maxs );
74 
75   if( spawn == BA_A_SPAWN )
76   {
77     VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX );
78     VectorSet( cmaxs,  MAX_ALIEN_BBOX,  MAX_ALIEN_BBOX,  MAX_ALIEN_BBOX );
79 
80     displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3;
81     VectorMA( origin, displacement, normal, localOrigin );
82 
83     trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
84 
85     if( tr.entityNum != ENTITYNUM_NONE )
86       return &g_entities[ tr.entityNum ];
87 
88     trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_SHOT );
89 
90     if( tr.entityNum == ENTITYNUM_NONE )
91     {
92       if( spawnOrigin != NULL )
93         VectorCopy( localOrigin, spawnOrigin );
94 
95       return NULL;
96     }
97     else
98       return &g_entities[ tr.entityNum ];
99   }
100   else if( spawn == BA_H_SPAWN )
101   {
102     BG_FindBBoxForClass( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL );
103 
104     VectorCopy( origin, localOrigin );
105     localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f;
106 
107     trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
108 
109     if( tr.entityNum != ENTITYNUM_NONE )
110       return &g_entities[ tr.entityNum ];
111 
112     trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_SHOT );
113 
114     if( tr.entityNum == ENTITYNUM_NONE )
115     {
116       if( spawnOrigin != NULL )
117         VectorCopy( localOrigin, spawnOrigin );
118 
119       return NULL;
120     }
121     else
122       return &g_entities[ tr.entityNum ];
123   }
124 
125   return NULL;
126 }
127 
128 /*
129 ================
130 G_NumberOfDependants
131 
132 Return number of entities that depend on this one
133 ================
134 */
G_NumberOfDependants(gentity_t * self)135 static int G_NumberOfDependants( gentity_t *self )
136 {
137   int       i, n = 0;
138   gentity_t *ent;
139 
140   for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
141   {
142     if( ent->s.eType != ET_BUILDABLE )
143       continue;
144 
145     if( ent->parentNode == self )
146       n++;
147   }
148 
149   return n;
150 }
151 
152 #define POWER_REFRESH_TIME  2000
153 
154 /*
155 ================
156 findPower
157 
158 attempt to find power for self, return qtrue if successful
159 ================
160 */
findPower(gentity_t * self)161 static qboolean findPower( gentity_t *self )
162 {
163   int       i;
164   gentity_t *ent;
165   gentity_t *closestPower = NULL;
166   int       distance = 0;
167   int       minDistance = 10000;
168   vec3_t    temp_v;
169   qboolean  foundPower = qfalse;
170 
171   if( self->biteam != BIT_HUMANS )
172     return qfalse;
173 
174   //reactor is always powered
175   if( self->s.modelindex == BA_H_REACTOR )
176     return qtrue;
177 
178   //if this already has power then stop now
179   if( self->parentNode && self->parentNode->powered )
180     return qtrue;
181 
182   //reset parent
183   self->parentNode = NULL;
184 
185   //iterate through entities
186   for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
187   {
188     if( ent->s.eType != ET_BUILDABLE )
189       continue;
190 
191     //if entity is a power item calculate the distance to it
192     if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) &&
193         ent->spawned )
194     {
195       VectorSubtract( self->s.origin, ent->s.origin, temp_v );
196       distance = VectorLength( temp_v );
197       if( distance < minDistance && ent->powered )
198       {
199         closestPower = ent;
200         minDistance = distance;
201         foundPower = qtrue;
202       }
203     }
204   }
205 
206   //if there were no power items nearby give up
207   if( !foundPower )
208     return qfalse;
209 
210   //bleh
211   if( ( closestPower->s.modelindex == BA_H_REACTOR && ( minDistance <= REACTOR_BASESIZE ) ) ||
212       ( closestPower->s.modelindex == BA_H_REPEATER && ( minDistance <= REPEATER_BASESIZE ) &&
213         closestPower->powered ) )
214   {
215     self->parentNode = closestPower;
216 
217     return qtrue;
218   }
219   else
220     return qfalse;
221 }
222 
223 /*
224 ================
225 G_isPower
226 
227 Simple wrapper to findPower to check if a location has power
228 ================
229 */
G_isPower(vec3_t origin)230 qboolean G_isPower( vec3_t origin )
231 {
232   gentity_t dummy;
233 
234   dummy.parentNode = NULL;
235   dummy.biteam = BIT_HUMANS;
236   VectorCopy( origin, dummy.s.origin );
237 
238   return findPower( &dummy );
239 }
240 
241 /*
242 ================
243 findDCC
244 
245 attempt to find a controlling DCC for self, return qtrue if successful
246 ================
247 */
findDCC(gentity_t * self)248 static qboolean findDCC( gentity_t *self )
249 {
250   int       i;
251   gentity_t *ent;
252   gentity_t *closestDCC = NULL;
253   int       distance = 0;
254   int       minDistance = 10000;
255   vec3_t    temp_v;
256   qboolean  foundDCC = qfalse;
257 
258   if( self->biteam != BIT_HUMANS )
259     return qfalse;
260 
261   //if this already has dcc then stop now
262   if( self->dccNode && self->dccNode->powered )
263     return qtrue;
264 
265   //reset parent
266   self->dccNode = NULL;
267 
268   //iterate through entities
269   for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
270   {
271     if( ent->s.eType != ET_BUILDABLE )
272       continue;
273 
274     //if entity is a dcc calculate the distance to it
275     if( ent->s.modelindex == BA_H_DCC && ent->spawned )
276     {
277       VectorSubtract( self->s.origin, ent->s.origin, temp_v );
278       distance = VectorLength( temp_v );
279       if( distance < minDistance && ent->powered )
280       {
281         closestDCC = ent;
282         minDistance = distance;
283         foundDCC = qtrue;
284       }
285     }
286   }
287 
288   //if there were no power items nearby give up
289   if( !foundDCC )
290     return qfalse;
291 
292   self->dccNode = closestDCC;
293 
294   return qtrue;
295 }
296 
297 /*
298 ================
299 G_isDCC
300 
301 simple wrapper to findDCC to check for a dcc
302 ================
303 */
G_isDCC(void)304 qboolean G_isDCC( void )
305 {
306   gentity_t dummy;
307 
308   memset( &dummy, 0, sizeof( gentity_t ) );
309 
310   dummy.dccNode = NULL;
311   dummy.biteam = BIT_HUMANS;
312 
313   return findDCC( &dummy );
314 }
315 
316 /*
317 ================
318 findOvermind
319 
320 Attempt to find an overmind for self
321 ================
322 */
findOvermind(gentity_t * self)323 static qboolean findOvermind( gentity_t *self )
324 {
325   int       i;
326   gentity_t *ent;
327 
328   if( self->biteam != BIT_ALIENS )
329     return qfalse;
330 
331   //if this already has overmind then stop now
332   if( self->overmindNode && self->overmindNode->health > 0 )
333     return qtrue;
334 
335   //reset parent
336   self->overmindNode = NULL;
337 
338   //iterate through entities
339   for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
340   {
341     if( ent->s.eType != ET_BUILDABLE )
342       continue;
343 
344     //if entity is an overmind calculate the distance to it
345     if( ent->s.modelindex == BA_A_OVERMIND && ent->spawned && ent->health > 0 )
346     {
347       self->overmindNode = ent;
348       return qtrue;
349     }
350   }
351 
352   return qfalse;
353 }
354 
355 /*
356 ================
357 G_isOvermind
358 
359 Simple wrapper to findOvermind to check if a location has an overmind
360 ================
361 */
G_isOvermind(void)362 qboolean G_isOvermind( void )
363 {
364   gentity_t dummy;
365 
366   memset( &dummy, 0, sizeof( gentity_t ) );
367 
368   dummy.overmindNode = NULL;
369   dummy.biteam = BIT_ALIENS;
370 
371   return findOvermind( &dummy );
372 }
373 
374 /*
375 ================
376 findCreep
377 
378 attempt to find creep for self, return qtrue if successful
379 ================
380 */
findCreep(gentity_t * self)381 static qboolean findCreep( gentity_t *self )
382 {
383   int       i;
384   gentity_t *ent;
385   gentity_t *closestSpawn = NULL;
386   int       distance = 0;
387   int       minDistance = 10000;
388   vec3_t    temp_v;
389 
390   //don't check for creep if flying through the air
391   if( self->s.groundEntityNum == -1 )
392     return qtrue;
393 
394   //if self does not have a parentNode or it's parentNode is invalid find a new one
395   if( ( self->parentNode == NULL ) || !self->parentNode->inuse )
396   {
397     for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
398     {
399       if( ent->s.eType != ET_BUILDABLE )
400         continue;
401 
402       if( ( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_A_OVERMIND ) &&
403           ent->spawned )
404       {
405         VectorSubtract( self->s.origin, ent->s.origin, temp_v );
406         distance = VectorLength( temp_v );
407         if( distance < minDistance )
408         {
409           closestSpawn = ent;
410           minDistance = distance;
411         }
412       }
413     }
414 
415     if( minDistance <= CREEP_BASESIZE )
416     {
417       self->parentNode = closestSpawn;
418       return qtrue;
419     }
420     else
421       return qfalse;
422   }
423 
424   //if we haven't returned by now then we must already have a valid parent
425   return qtrue;
426 }
427 
428 /*
429 ================
430 isCreep
431 
432 simple wrapper to findCreep to check if a location has creep
433 ================
434 */
isCreep(vec3_t origin)435 static qboolean isCreep( vec3_t origin )
436 {
437   gentity_t dummy;
438 
439   memset( &dummy, 0, sizeof( gentity_t ) );
440 
441   dummy.parentNode = NULL;
442   VectorCopy( origin, dummy.s.origin );
443 
444   return findCreep( &dummy );
445 }
446 
447 /*
448 ================
449 creepSlow
450 
451 Set any nearby humans' SS_CREEPSLOWED flag
452 ================
453 */
creepSlow(gentity_t * self)454 static void creepSlow( gentity_t *self )
455 {
456   int         entityList[ MAX_GENTITIES ];
457   vec3_t      range;
458   vec3_t      mins, maxs;
459   int         i, num;
460   gentity_t   *enemy;
461   buildable_t buildable = self->s.modelindex;
462   float       creepSize = (float)BG_FindCreepSizeForBuildable( buildable );
463 
464   VectorSet( range, creepSize, creepSize, creepSize );
465 
466   VectorAdd( self->s.origin, range, maxs );
467   VectorSubtract( self->s.origin, range, mins );
468 
469   //find humans
470   num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
471   for( i = 0; i < num; i++ )
472   {
473     enemy = &g_entities[ entityList[ i ] ];
474 
475     if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
476         enemy->client->ps.groundEntityNum != ENTITYNUM_NONE &&
477         G_Visible( self, enemy ) )
478     {
479       enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED;
480       enemy->client->lastCreepSlowTime = level.time;
481     }
482   }
483 }
484 
485 /*
486 ================
487 nullDieFunction
488 
489 hack to prevent compilers complaining about function pointer -> NULL conversion
490 ================
491 */
nullDieFunction(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)492 static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
493 {
494 }
495 
496 /*
497 ================
498 freeBuildable
499 ================
500 */
freeBuildable(gentity_t * self)501 static void freeBuildable( gentity_t *self )
502 {
503   G_FreeEntity( self );
504 }
505 
506 
507 //==================================================================================
508 
509 
510 
511 /*
512 ================
513 A_CreepRecede
514 
515 Called when an alien spawn dies
516 ================
517 */
A_CreepRecede(gentity_t * self)518 void A_CreepRecede( gentity_t *self )
519 {
520   //if the creep just died begin the recession
521   if( !( self->s.eFlags & EF_DEAD ) )
522   {
523     self->s.eFlags |= EF_DEAD;
524     G_AddEvent( self, EV_BUILD_DESTROY, 0 );
525 
526     if( self->spawned )
527       self->s.time = -level.time;
528     else
529       self->s.time = -( level.time -
530           (int)( (float)CREEP_SCALEDOWN_TIME *
531                  ( 1.0f - ( (float)( level.time - self->buildTime ) /
532                             (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
533   }
534 
535   //creep is still receeding
536   if( ( self->timestamp + 10000 ) > level.time )
537     self->nextthink = level.time + 500;
538   else //creep has died
539     G_FreeEntity( self );
540 }
541 
542 
543 
544 
545 //==================================================================================
546 
547 
548 
549 
550 /*
551 ================
552 ASpawn_Melt
553 
554 Called when an alien spawn dies
555 ================
556 */
ASpawn_Melt(gentity_t * self)557 void ASpawn_Melt( gentity_t *self )
558 {
559   G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
560     self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
561 
562   //start creep recession
563   if( !( self->s.eFlags & EF_DEAD ) )
564   {
565     self->s.eFlags |= EF_DEAD;
566     G_AddEvent( self, EV_BUILD_DESTROY, 0 );
567 
568     if( self->spawned )
569       self->s.time = -level.time;
570     else
571       self->s.time = -( level.time -
572           (int)( (float)CREEP_SCALEDOWN_TIME *
573                  ( 1.0f - ( (float)( level.time - self->buildTime ) /
574                             (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
575   }
576 
577   //not dead yet
578   if( ( self->timestamp + 10000 ) > level.time )
579     self->nextthink = level.time + 500;
580   else //dead now
581     G_FreeEntity( self );
582 }
583 
584 /*
585 ================
586 ASpawn_Blast
587 
588 Called when an alien spawn dies
589 ================
590 */
ASpawn_Blast(gentity_t * self)591 void ASpawn_Blast( gentity_t *self )
592 {
593   vec3_t  dir;
594 
595   VectorCopy( self->s.origin2, dir );
596 
597   //do a bit of radius damage
598   G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
599     self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
600 
601   //pretty events and item cleanup
602   self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed
603   G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
604   self->timestamp = level.time;
605   self->think = ASpawn_Melt;
606   self->nextthink = level.time + 500; //wait .5 seconds before damaging others
607 
608   self->r.contents = 0;    //stop collisions...
609   trap_LinkEntity( self ); //...requires a relink
610 }
611 
612 /*
613 ================
614 ASpawn_Die
615 
616 Called when an alien spawn dies
617 ================
618 */
ASpawn_Die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)619 void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
620 {
621   G_setBuildableAnim( self, BANIM_DESTROY1, qtrue );
622   G_setIdleBuildableAnim( self, BANIM_DESTROYED );
623 
624   self->die = nullDieFunction;
625   self->think = ASpawn_Blast;
626 
627   if( self->spawned )
628     self->nextthink = level.time + 5000;
629   else
630     self->nextthink = level.time; //blast immediately
631 
632   self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
633 
634   if( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
635   {
636     if( self->s.modelindex == BA_A_OVERMIND )
637       G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue );
638     else if( self->s.modelindex == BA_A_SPAWN )
639       G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue );
640   }
641 }
642 
643 /*
644 ================
645 ASpawn_Think
646 
647 think function for Alien Spawn
648 ================
649 */
ASpawn_Think(gentity_t * self)650 void ASpawn_Think( gentity_t *self )
651 {
652   gentity_t *ent;
653 
654   if( self->spawned )
655   {
656     //only suicide if at rest
657     if( self->s.groundEntityNum )
658     {
659       if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
660               self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL )
661       {
662         if( ent->s.eType == ET_BUILDABLE || ent->s.number == ENTITYNUM_WORLD ||
663             ent->s.eType == ET_MOVER )
664         {
665           G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
666           return;
667         }
668 
669         if( ent->s.eType == ET_CORPSE )
670           G_FreeEntity( ent ); //quietly remove
671       }
672     }
673   }
674 
675   creepSlow( self );
676 
677   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
678 }
679 
680 /*
681 ================
682 ASpawn_Pain
683 
684 pain function for Alien Spawn
685 ================
686 */
ASpawn_Pain(gentity_t * self,gentity_t * attacker,int damage)687 void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage )
688 {
689   G_setBuildableAnim( self, BANIM_PAIN1, qfalse );
690 }
691 
692 
693 
694 
695 
696 //==================================================================================
697 
698 
699 
700 
701 
702 #define OVERMIND_ATTACK_PERIOD 10000
703 #define OVERMIND_DYING_PERIOD  5000
704 #define OVERMIND_SPAWNS_PERIOD 30000
705 
706 /*
707 ================
708 AOvermind_Think
709 
710 Think function for Alien Overmind
711 ================
712 */
AOvermind_Think(gentity_t * self)713 void AOvermind_Think( gentity_t *self )
714 {
715   int       entityList[ MAX_GENTITIES ];
716   vec3_t    range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE };
717   vec3_t    mins, maxs;
718   int       i, num;
719   gentity_t *enemy;
720 
721   VectorAdd( self->s.origin, range, maxs );
722   VectorSubtract( self->s.origin, range, mins );
723 
724   if( self->spawned && ( self->health > 0 ) )
725   {
726     //do some damage
727     num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
728     for( i = 0; i < num; i++ )
729     {
730       enemy = &g_entities[ entityList[ i ] ];
731 
732       if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
733       {
734         self->timestamp = level.time;
735         G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
736           self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS );
737         G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
738       }
739     }
740 
741     //low on spawns
742     if( level.numAlienSpawns <= 0 && level.time > self->overmindSpawnsTimer )
743     {
744       self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
745       G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 );
746     }
747 
748     //overmind dying
749     if( self->health < ( OVERMIND_HEALTH / 10.0f ) && level.time > self->overmindDyingTimer )
750     {
751       self->overmindDyingTimer = level.time + OVERMIND_DYING_PERIOD;
752       G_BroadcastEvent( EV_OVERMIND_DYING, 0 );
753     }
754 
755     //overmind under attack
756     if( self->health < self->lastHealth && level.time > self->overmindAttackTimer )
757     {
758       self->overmindAttackTimer = level.time + OVERMIND_ATTACK_PERIOD;
759       G_BroadcastEvent( EV_OVERMIND_ATTACK, 0 );
760     }
761 
762     self->lastHealth = self->health;
763   }
764   else
765     self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
766 
767   creepSlow( self );
768 
769   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
770 }
771 
772 
773 
774 
775 
776 
777 //==================================================================================
778 
779 
780 
781 
782 
783 /*
784 ================
785 ABarricade_Pain
786 
787 pain function for Alien Spawn
788 ================
789 */
ABarricade_Pain(gentity_t * self,gentity_t * attacker,int damage)790 void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
791 {
792   if( rand( ) % 1 )
793     G_setBuildableAnim( self, BANIM_PAIN1, qfalse );
794   else
795     G_setBuildableAnim( self, BANIM_PAIN2, qfalse );
796 }
797 
798 /*
799 ================
800 ABarricade_Blast
801 
802 Called when an alien spawn dies
803 ================
804 */
ABarricade_Blast(gentity_t * self)805 void ABarricade_Blast( gentity_t *self )
806 {
807   vec3_t  dir;
808 
809   VectorCopy( self->s.origin2, dir );
810 
811   //do a bit of radius damage
812   G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
813     self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
814 
815   //pretty events and item cleanup
816   self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
817   G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
818   self->timestamp = level.time;
819   self->think = A_CreepRecede;
820   self->nextthink = level.time + 500; //wait .5 seconds before damaging others
821 
822   self->r.contents = 0;    //stop collisions...
823   trap_LinkEntity( self ); //...requires a relink
824 }
825 
826 /*
827 ================
828 ABarricade_Die
829 
830 Called when an alien spawn dies
831 ================
832 */
ABarricade_Die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)833 void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
834 {
835   G_setBuildableAnim( self, BANIM_DESTROY1, qtrue );
836   G_setIdleBuildableAnim( self, BANIM_DESTROYED );
837 
838   self->die = nullDieFunction;
839   self->think = ABarricade_Blast;
840   self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
841 
842   if( self->spawned )
843     self->nextthink = level.time + 5000;
844   else
845     self->nextthink = level.time; //blast immediately
846 }
847 
848 /*
849 ================
850 ABarricade_Think
851 
852 Think function for Alien Barricade
853 ================
854 */
ABarricade_Think(gentity_t * self)855 void ABarricade_Think( gentity_t *self )
856 {
857   //if there is no creep nearby die
858   if( !findCreep( self ) )
859   {
860     G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
861     return;
862   }
863 
864   creepSlow( self );
865 
866   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
867 }
868 
869 
870 
871 
872 //==================================================================================
873 
874 
875 
876 
877 void AAcidTube_Think( gentity_t *self );
878 
879 /*
880 ================
881 AAcidTube_Damage
882 
883 Damage function for Alien Acid Tube
884 ================
885 */
AAcidTube_Damage(gentity_t * self)886 void AAcidTube_Damage( gentity_t *self )
887 {
888   if( self->spawned )
889   {
890     if( !( self->s.eFlags & EF_FIRING ) )
891     {
892       self->s.eFlags |= EF_FIRING;
893       G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) );
894     }
895 
896     if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time )
897       self->think = AAcidTube_Damage;
898     else
899     {
900       self->think = AAcidTube_Think;
901       self->s.eFlags &= ~EF_FIRING;
902     }
903 
904     //do some damage
905     G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
906       self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
907   }
908 
909   creepSlow( self );
910 
911   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
912 }
913 
914 /*
915 ================
916 AAcidTube_Think
917 
918 Think function for Alien Acid Tube
919 ================
920 */
AAcidTube_Think(gentity_t * self)921 void AAcidTube_Think( gentity_t *self )
922 {
923   int       entityList[ MAX_GENTITIES ];
924   vec3_t    range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
925   vec3_t    mins, maxs;
926   int       i, num;
927   gentity_t *enemy;
928 
929   VectorAdd( self->s.origin, range, maxs );
930   VectorSubtract( self->s.origin, range, mins );
931 
932   //if there is no creep nearby die
933   if( !findCreep( self ) )
934   {
935     G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
936     return;
937   }
938 
939   if( self->spawned && findOvermind( self ) )
940   {
941     //do some damage
942     num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
943     for( i = 0; i < num; i++ )
944     {
945       enemy = &g_entities[ entityList[ i ] ];
946 
947       if( !G_Visible( self, enemy ) )
948         continue;
949 
950       if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
951       {
952         self->timestamp = level.time;
953         self->think = AAcidTube_Damage;
954         self->nextthink = level.time + 100;
955         G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
956         return;
957       }
958     }
959   }
960 
961   creepSlow( self );
962 
963   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
964 }
965 
966 
967 
968 
969 //==================================================================================
970 
971 
972 
973 
974 /*
975 ================
976 AHive_Think
977 
978 Think function for Alien Hive
979 ================
980 */
AHive_Think(gentity_t * self)981 void AHive_Think( gentity_t *self )
982 {
983   int       entityList[ MAX_GENTITIES ];
984   vec3_t    range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
985   vec3_t    mins, maxs;
986   int       i, num;
987   gentity_t *enemy;
988   vec3_t    dirToTarget;
989 
990   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
991 
992   VectorAdd( self->s.origin, range, maxs );
993   VectorSubtract( self->s.origin, range, mins );
994 
995   //if there is no creep nearby die
996   if( !findCreep( self ) )
997   {
998     G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
999     return;
1000   }
1001 
1002   if( self->timestamp < level.time )
1003     self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it
1004 
1005   if( self->spawned && !self->active && findOvermind( self ) )
1006   {
1007     //do some damage
1008     num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1009     for( i = 0; i < num; i++ )
1010     {
1011       enemy = &g_entities[ entityList[ i ] ];
1012 
1013       if( enemy->health <= 0 )
1014         continue;
1015 
1016       if( !G_Visible( self, enemy ) )
1017         continue;
1018 
1019       if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1020       {
1021         self->active = qtrue;
1022         self->target_ent = enemy;
1023         self->timestamp = level.time + HIVE_REPEAT;
1024 
1025         VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
1026         VectorNormalize( dirToTarget );
1027         vectoangles( dirToTarget, self->turretAim );
1028 
1029         //fire at target
1030         FireWeapon( self );
1031         G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
1032         return;
1033       }
1034     }
1035   }
1036 
1037   creepSlow( self );
1038 }
1039 
1040 
1041 
1042 
1043 //==================================================================================
1044 
1045 
1046 
1047 
1048 #define HOVEL_TRACE_DEPTH 128.0f
1049 
1050 /*
1051 ================
1052 AHovel_Blocked
1053 
1054 Is this hovel entrance blocked?
1055 ================
1056 */
AHovel_Blocked(gentity_t * hovel,gentity_t * player,qboolean provideExit)1057 qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit )
1058 {
1059   vec3_t    forward, normal, origin, start, end, angles, hovelMaxs;
1060   vec3_t    mins, maxs;
1061   float     displacement;
1062   trace_t   tr;
1063 
1064   BG_FindBBoxForBuildable( BA_A_HOVEL, NULL, hovelMaxs );
1065   BG_FindBBoxForClass( player->client->ps.stats[ STAT_PCLASS ],
1066                        mins, maxs, NULL, NULL, NULL );
1067 
1068   VectorCopy( hovel->s.origin2, normal );
1069   AngleVectors( hovel->s.angles, forward, NULL, NULL );
1070   VectorInverse( forward );
1071 
1072   displacement = VectorMaxComponent( maxs ) * M_ROOT3 +
1073                  VectorMaxComponent( hovelMaxs ) * M_ROOT3 + 1.0f;
1074 
1075   VectorMA( hovel->s.origin, displacement, forward, origin );
1076   vectoangles( forward, angles );
1077 
1078   VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start );
1079 
1080   //compute a place up in the air to start the real trace
1081   trap_Trace( &tr, origin, mins, maxs, start, player->s.number, MASK_PLAYERSOLID );
1082   VectorMA( origin, ( HOVEL_TRACE_DEPTH * tr.fraction ) - 1.0f, normal, start );
1083   VectorMA( origin, -HOVEL_TRACE_DEPTH, normal, end );
1084 
1085   trap_Trace( &tr, start, mins, maxs, end, player->s.number, MASK_PLAYERSOLID );
1086 
1087   if( tr.startsolid )
1088     return qtrue;
1089 
1090   VectorCopy( tr.endpos, origin );
1091 
1092   trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID );
1093 
1094   if( provideExit )
1095   {
1096     G_SetOrigin( player, origin );
1097     VectorCopy( origin, player->client->ps.origin );
1098     VectorCopy( vec3_origin, player->client->ps.velocity );
1099     SetClientViewAngle( player, angles );
1100   }
1101 
1102   if( tr.fraction < 1.0f )
1103     return qtrue;
1104   else
1105     return qfalse;
1106 }
1107 
1108 /*
1109 ================
1110 APropHovel_Blocked
1111 
1112 Wrapper to test a hovel placement for validity
1113 ================
1114 */
APropHovel_Blocked(vec3_t origin,vec3_t angles,vec3_t normal,gentity_t * player)1115 static qboolean APropHovel_Blocked( vec3_t origin, vec3_t angles, vec3_t normal,
1116                                     gentity_t *player )
1117 {
1118   gentity_t hovel;
1119 
1120   VectorCopy( origin, hovel.s.origin );
1121   VectorCopy( angles, hovel.s.angles );
1122   VectorCopy( normal, hovel.s.origin2 );
1123 
1124   return AHovel_Blocked( &hovel, player, qfalse );
1125 }
1126 
1127 /*
1128 ================
1129 AHovel_Use
1130 
1131 Called when an alien uses a hovel
1132 ================
1133 */
AHovel_Use(gentity_t * self,gentity_t * other,gentity_t * activator)1134 void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
1135 {
1136   vec3_t  hovelOrigin, hovelAngles, inverseNormal;
1137 
1138   if( self->spawned && findOvermind( self ) )
1139   {
1140     if( self->active )
1141     {
1142       //this hovel is in use
1143       G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_OCCUPIED );
1144     }
1145     else if( ( ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ) ||
1146                ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) &&
1147              activator->health > 0 && self->health > 0 )
1148     {
1149       if( AHovel_Blocked( self, activator, qfalse ) )
1150       {
1151         //you can get in, but you can't get out
1152         G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_BLOCKED );
1153         return;
1154       }
1155 
1156       self->active = qtrue;
1157       G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
1158 
1159       //prevent lerping
1160       activator->client->ps.eFlags ^= EF_TELEPORT_BIT;
1161       activator->client->ps.eFlags |= EF_NODRAW;
1162 
1163       activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING;
1164       activator->client->hovel = self;
1165       self->builder = activator;
1166 
1167       VectorCopy( self->s.pos.trBase, hovelOrigin );
1168       VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin );
1169 
1170       VectorCopy( self->s.origin2, inverseNormal );
1171       VectorInverse( inverseNormal );
1172       vectoangles( inverseNormal, hovelAngles );
1173 
1174       VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin );
1175 
1176       G_SetOrigin( activator, hovelOrigin );
1177       VectorCopy( hovelOrigin, activator->client->ps.origin );
1178       SetClientViewAngle( activator, hovelAngles );
1179     }
1180   }
1181 }
1182 
1183 
1184 /*
1185 ================
1186 AHovel_Think
1187 
1188 Think for alien hovel
1189 ================
1190 */
AHovel_Think(gentity_t * self)1191 void AHovel_Think( gentity_t *self )
1192 {
1193   if( self->spawned )
1194   {
1195     if( self->active )
1196       G_setIdleBuildableAnim( self, BANIM_IDLE2 );
1197     else
1198       G_setIdleBuildableAnim( self, BANIM_IDLE1 );
1199   }
1200 
1201   creepSlow( self );
1202 
1203   self->nextthink = level.time + 200;
1204 }
1205 
1206 /*
1207 ================
1208 AHovel_Die
1209 
1210 Die for alien hovel
1211 ================
1212 */
AHovel_Die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1213 void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
1214 {
1215   vec3_t  dir;
1216 
1217   VectorCopy( self->s.origin2, dir );
1218 
1219   //do a bit of radius damage
1220   G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
1221     self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
1222 
1223   //pretty events and item cleanup
1224   self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
1225   G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
1226   self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
1227   self->timestamp = level.time;
1228   self->think = ASpawn_Melt;
1229   self->nextthink = level.time + 500; //wait .5 seconds before damaging others
1230 
1231   //if the hovel is occupied free the occupant
1232   if( self->active )
1233   {
1234     gentity_t *builder = self->builder;
1235     vec3_t    newOrigin;
1236     vec3_t    newAngles;
1237 
1238     VectorCopy( self->s.angles, newAngles );
1239     newAngles[ ROLL ] = 0;
1240 
1241     VectorCopy( self->s.origin, newOrigin );
1242     VectorMA( newOrigin, 1.0f, self->s.origin2, newOrigin );
1243 
1244     //prevent lerping
1245     builder->client->ps.eFlags ^= EF_TELEPORT_BIT;
1246     builder->client->ps.eFlags &= ~EF_NODRAW;
1247 
1248     G_SetOrigin( builder, newOrigin );
1249     VectorCopy( newOrigin, builder->client->ps.origin );
1250     SetClientViewAngle( builder, newAngles );
1251 
1252     //client leaves hovel
1253     builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
1254   }
1255 
1256   self->r.contents = 0;    //stop collisions...
1257   trap_LinkEntity( self ); //...requires a relink
1258 }
1259 
1260 
1261 
1262 
1263 
1264 //==================================================================================
1265 
1266 
1267 
1268 
1269 /*
1270 ================
1271 ABooster_Touch
1272 
1273 Called when an alien touches a booster
1274 ================
1275 */
ABooster_Touch(gentity_t * self,gentity_t * other,trace_t * trace)1276 void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
1277 {
1278   gclient_t *client = other->client;
1279 
1280   if( !self->spawned )
1281     return;
1282 
1283   if( !findOvermind( self ) )
1284     return;
1285 
1286   if( !client )
1287     return;
1288 
1289   if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1290     return;
1291 
1292   //only allow boostage once every 30 seconds
1293   if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time )
1294     return;
1295 
1296   if( !( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) )
1297   {
1298     client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
1299     client->lastBoostedTime = level.time;
1300   }
1301 }
1302 
1303 
1304 
1305 
1306 //==================================================================================
1307 
1308 #define TRAPPER_ACCURACY 10 // lower is better
1309 
1310 /*
1311 ================
1312 ATrapper_FireOnEnemy
1313 
1314 Used by ATrapper_Think to fire at enemy
1315 ================
1316 */
ATrapper_FireOnEnemy(gentity_t * self,int firespeed,float range)1317 void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range )
1318 {
1319   gentity_t *enemy = self->enemy;
1320   vec3_t    dirToTarget;
1321   vec3_t    halfAcceleration, thirdJerk;
1322   float     distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex );
1323   int       lowMsec = 0;
1324   int       highMsec = (int)( (
1325     ( ( distanceToTarget * LOCKBLOB_SPEED ) +
1326       ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) /
1327     ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f );
1328 
1329   VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration );
1330   VectorScale( enemy->jerk, 1.0f / 3.0f, thirdJerk );
1331 
1332   // highMsec and lowMsec can only move toward
1333   // one another, so the loop must terminate
1334   while( highMsec - lowMsec > TRAPPER_ACCURACY )
1335   {
1336     int   partitionMsec = ( highMsec + lowMsec ) / 2;
1337     float time = (float)partitionMsec / 1000.0f;
1338     float projectileDistance = LOCKBLOB_SPEED * time;
1339 
1340     VectorMA( enemy->s.pos.trBase, time, enemy->s.pos.trDelta, dirToTarget );
1341     VectorMA( dirToTarget, time * time, halfAcceleration, dirToTarget );
1342     VectorMA( dirToTarget, time * time * time, thirdJerk, dirToTarget );
1343     VectorSubtract( dirToTarget, self->s.pos.trBase, dirToTarget );
1344     distanceToTarget = VectorLength( dirToTarget );
1345 
1346     if( projectileDistance < distanceToTarget )
1347       lowMsec = partitionMsec;
1348     else if( projectileDistance > distanceToTarget )
1349       highMsec = partitionMsec;
1350     else if( projectileDistance == distanceToTarget )
1351       break; // unlikely to happen
1352   }
1353 
1354   VectorNormalize( dirToTarget );
1355   vectoangles( dirToTarget, self->turretAim );
1356 
1357   //fire at target
1358   FireWeapon( self );
1359   G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
1360   self->count = level.time + firespeed;
1361 }
1362 
1363 /*
1364 ================
1365 ATrapper_CheckTarget
1366 
1367 Used by ATrapper_Think to check enemies for validity
1368 ================
1369 */
ATrapper_CheckTarget(gentity_t * self,gentity_t * target,int range)1370 qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range )
1371 {
1372   vec3_t    distance;
1373   trace_t   trace;
1374 
1375   if( !target ) // Do we have a target?
1376     return qfalse;
1377   if( !target->inuse ) // Does the target still exist?
1378     return qfalse;
1379   if( target == self ) // is the target us?
1380     return qfalse;
1381   if( !target->client ) // is the target a bot or player?
1382     return qfalse;
1383   if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us?
1384     return qfalse;
1385   if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive?
1386     return qfalse;
1387   if( target->health <= 0 ) // is the target still alive?
1388     return qfalse;
1389   if( target->client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) // locked?
1390     return qfalse;
1391 
1392   VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, distance );
1393   if( VectorLength( distance ) > range ) // is the target within range?
1394     return qfalse;
1395 
1396   //only allow a narrow field of "vision"
1397   VectorNormalize( distance ); //is now direction of target
1398   if( DotProduct( distance, self->s.origin2 ) < LOCKBLOB_DOT )
1399     return qfalse;
1400 
1401   trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
1402   if ( trace.contents & CONTENTS_SOLID ) // can we see the target?
1403     return qfalse;
1404 
1405   return qtrue;
1406 }
1407 
1408 /*
1409 ================
1410 ATrapper_FindEnemy
1411 
1412 Used by ATrapper_Think to locate enemy gentities
1413 ================
1414 */
ATrapper_FindEnemy(gentity_t * ent,int range)1415 void ATrapper_FindEnemy( gentity_t *ent, int range )
1416 {
1417   gentity_t *target;
1418 
1419   //iterate through entities
1420   for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ )
1421   {
1422     //if target is not valid keep searching
1423     if( !ATrapper_CheckTarget( ent, target, range ) )
1424       continue;
1425 
1426     //we found a target
1427     ent->enemy = target;
1428     return;
1429   }
1430 
1431   //couldn't find a target
1432   ent->enemy = NULL;
1433 }
1434 
1435 /*
1436 ================
1437 ATrapper_Think
1438 
1439 think function for Alien Defense
1440 ================
1441 */
ATrapper_Think(gentity_t * self)1442 void ATrapper_Think( gentity_t *self )
1443 {
1444   int range =     BG_FindRangeForBuildable( self->s.modelindex );
1445   int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
1446 
1447   creepSlow( self );
1448 
1449   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
1450 
1451   //if there is no creep nearby die
1452   if( !findCreep( self ) )
1453   {
1454     G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
1455     return;
1456   }
1457 
1458   if( self->spawned && findOvermind( self ) )
1459   {
1460     //if the current target is not valid find a new one
1461     if( !ATrapper_CheckTarget( self, self->enemy, range ) )
1462       ATrapper_FindEnemy( self, range );
1463 
1464     //if a new target cannot be found don't do anything
1465     if( !self->enemy )
1466       return;
1467 
1468     //if we are pointing at our target and we can fire shoot it
1469     if( self->count < level.time )
1470       ATrapper_FireOnEnemy( self, firespeed, range );
1471   }
1472 }
1473 
1474 
1475 
1476 //==================================================================================
1477 
1478 
1479 
1480 /*
1481 ================
1482 HRepeater_Think
1483 
1484 Think for human power repeater
1485 ================
1486 */
HRepeater_Think(gentity_t * self)1487 void HRepeater_Think( gentity_t *self )
1488 {
1489   int       i;
1490   qboolean  reactor = qfalse;
1491   gentity_t *ent;
1492 
1493   if( self->spawned )
1494   {
1495     //iterate through entities
1496     for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
1497     {
1498       if( ent->s.eType != ET_BUILDABLE )
1499         continue;
1500 
1501       if( ent->s.modelindex == BA_H_REACTOR && ent->spawned )
1502         reactor = qtrue;
1503     }
1504   }
1505 
1506   if( G_NumberOfDependants( self ) == 0 )
1507   {
1508     //if no dependants for x seconds then disappear
1509     if( self->count < 0 )
1510       self->count = level.time;
1511     else if( self->count > 0 && ( ( level.time - self->count ) > REPEATER_INACTIVE_TIME ) )
1512       G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
1513   }
1514   else
1515     self->count = -1;
1516 
1517   self->powered = reactor;
1518 
1519   self->nextthink = level.time + POWER_REFRESH_TIME;
1520 }
1521 
1522 /*
1523 ================
1524 HRepeater_Use
1525 
1526 Use for human power repeater
1527 ================
1528 */
HRepeater_Use(gentity_t * self,gentity_t * other,gentity_t * activator)1529 void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
1530 {
1531   if( self->health <= 0 )
1532     return;
1533 
1534   if( !self->spawned )
1535     return;
1536 
1537   if( other )
1538     G_GiveClientMaxAmmo( other, qtrue );
1539 }
1540 
1541 
1542 #define DCC_ATTACK_PERIOD 10000
1543 
1544 /*
1545 ================
1546 HReactor_Think
1547 
1548 Think function for Human Reactor
1549 ================
1550 */
HReactor_Think(gentity_t * self)1551 void HReactor_Think( gentity_t *self )
1552 {
1553   int       entityList[ MAX_GENTITIES ];
1554   vec3_t    range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE };
1555   vec3_t    mins, maxs;
1556   int       i, num;
1557   gentity_t *enemy, *tent;
1558 
1559   VectorAdd( self->s.origin, range, maxs );
1560   VectorSubtract( self->s.origin, range, mins );
1561 
1562   if( self->spawned && ( self->health > 0 ) )
1563   {
1564     //do some damage
1565     num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1566     for( i = 0; i < num; i++ )
1567     {
1568       enemy = &g_entities[ entityList[ i ] ];
1569 
1570       if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1571       {
1572         self->timestamp = level.time;
1573         G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE,
1574           REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS );
1575 
1576         tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL );
1577 
1578         VectorCopy( self->s.pos.trBase, tent->s.origin2 );
1579 
1580         tent->s.generic1 = self->s.number; //src
1581         tent->s.clientNum = enemy->s.number; //dest
1582       }
1583     }
1584 
1585     //reactor under attack
1586     if( self->health < self->lastHealth &&
1587         level.time > level.humanBaseAttackTimer && G_isDCC( ) )
1588     {
1589       level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
1590       G_BroadcastEvent( EV_DCC_ATTACK, 0 );
1591     }
1592 
1593     self->lastHealth = self->health;
1594   }
1595 
1596   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
1597 }
1598 
1599 //==================================================================================
1600 
1601 
1602 
1603 /*
1604 ================
1605 HArmoury_Activate
1606 
1607 Called when a human activates an Armoury
1608 ================
1609 */
HArmoury_Activate(gentity_t * self,gentity_t * other,gentity_t * activator)1610 void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator )
1611 {
1612   if( self->spawned )
1613   {
1614     //only humans can activate this
1615     if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
1616       return;
1617 
1618     //if this is powered then call the armoury menu
1619     if( self->powered )
1620       G_TriggerMenu( activator->client->ps.clientNum, MN_H_ARMOURY );
1621     else
1622       G_TriggerMenu( activator->client->ps.clientNum, MN_H_NOTPOWERED );
1623   }
1624 }
1625 
1626 /*
1627 ================
1628 HArmoury_Think
1629 
1630 Think for armoury
1631 ================
1632 */
HArmoury_Think(gentity_t * self)1633 void HArmoury_Think( gentity_t *self )
1634 {
1635   //make sure we have power
1636   self->nextthink = level.time + POWER_REFRESH_TIME;
1637 
1638   self->powered = findPower( self );
1639 }
1640 
1641 
1642 
1643 
1644 //==================================================================================
1645 
1646 
1647 
1648 
1649 
1650 /*
1651 ================
1652 HDCC_Think
1653 
1654 Think for dcc
1655 ================
1656 */
HDCC_Think(gentity_t * self)1657 void HDCC_Think( gentity_t *self )
1658 {
1659   //make sure we have power
1660   self->nextthink = level.time + POWER_REFRESH_TIME;
1661 
1662   self->powered = findPower( self );
1663 }
1664 
1665 
1666 
1667 
1668 //==================================================================================
1669 
1670 /*
1671 ================
1672 HMedistat_Think
1673 
1674 think function for Human Medistation
1675 ================
1676 */
HMedistat_Think(gentity_t * self)1677 void HMedistat_Think( gentity_t *self )
1678 {
1679   int       entityList[ MAX_GENTITIES ];
1680   vec3_t    mins, maxs;
1681   int       i, num;
1682   gentity_t *player;
1683   qboolean  occupied = qfalse;
1684 
1685   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
1686 
1687   //make sure we have power
1688   if( !( self->powered = findPower( self ) ) )
1689   {
1690     self->nextthink = level.time + POWER_REFRESH_TIME;
1691     return;
1692   }
1693 
1694   if( self->spawned )
1695   {
1696     VectorAdd( self->s.origin, self->r.maxs, maxs );
1697     VectorAdd( self->s.origin, self->r.mins, mins );
1698 
1699     mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ];
1700     maxs[ 2 ] += 60; //player height
1701 
1702     //if active use the healing idle
1703     if( self->active )
1704       G_setIdleBuildableAnim( self, BANIM_IDLE2 );
1705 
1706     //check if a previous occupier is still here
1707     num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1708     for( i = 0; i < num; i++ )
1709     {
1710       player = &g_entities[ entityList[ i ] ];
1711 
1712       if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1713       {
1714         if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
1715             player->client->ps.pm_type != PM_DEAD &&
1716             self->enemy == player )
1717           occupied = qtrue;
1718       }
1719     }
1720 
1721     if( !occupied )
1722     {
1723       self->enemy = NULL;
1724 
1725       //look for something to heal
1726       for( i = 0; i < num; i++ )
1727       {
1728         player = &g_entities[ entityList[ i ] ];
1729 
1730         if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1731         {
1732           if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
1733               player->client->ps.pm_type != PM_DEAD )
1734           {
1735             self->enemy = player;
1736 
1737             //start the heal anim
1738             if( !self->active )
1739             {
1740               G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
1741               self->active = qtrue;
1742             }
1743           }
1744           else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) )
1745             BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats );
1746         }
1747       }
1748     }
1749 
1750     //nothing left to heal so go back to idling
1751     if( !self->enemy && self->active )
1752     {
1753       G_setBuildableAnim( self, BANIM_CONSTRUCT2, qtrue );
1754       G_setIdleBuildableAnim( self, BANIM_IDLE1 );
1755 
1756       self->active = qfalse;
1757     }
1758     else if( self->enemy ) //heal!
1759     {
1760       if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED )
1761         self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
1762 
1763       if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
1764         self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
1765 
1766       self->enemy->health++;
1767 
1768       //if they're completely healed, give them a medkit
1769       if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] &&
1770           !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) )
1771         BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );
1772     }
1773   }
1774 }
1775 
1776 
1777 
1778 
1779 //==================================================================================
1780 
1781 
1782 
1783 
1784 /*
1785 ================
1786 HMGTurret_TrackEnemy
1787 
1788 Used by HMGTurret_Think to track enemy location
1789 ================
1790 */
HMGTurret_TrackEnemy(gentity_t * self)1791 qboolean HMGTurret_TrackEnemy( gentity_t *self )
1792 {
1793   vec3_t  dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal;
1794   vec3_t  refNormal = { 0.0f, 0.0f, 1.0f };
1795   float   temp, rotAngle;
1796   float   accuracyTolerance, angularSpeed;
1797 
1798   if( self->lev1Grabbed )
1799   {
1800     //can't turn fast if grabbed
1801     accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE;
1802     angularSpeed = MGTURRET_GRAB_ANGULARSPEED;
1803   }
1804   else if( self->dcced )
1805   {
1806     accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE;
1807     angularSpeed = MGTURRET_DCC_ANGULARSPEED;
1808   }
1809   else
1810   {
1811     accuracyTolerance = MGTURRET_ACCURACYTOLERANCE;
1812     angularSpeed = MGTURRET_ANGULARSPEED;
1813   }
1814 
1815   VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
1816 
1817   VectorNormalize( dirToTarget );
1818 
1819   CrossProduct( self->s.origin2, refNormal, xNormal );
1820   VectorNormalize( xNormal );
1821   rotAngle = RAD2DEG( acos( DotProduct( self->s.origin2, refNormal ) ) );
1822   RotatePointAroundVector( dttAdjusted, xNormal, dirToTarget, rotAngle );
1823 
1824   vectoangles( dttAdjusted, angleToTarget );
1825 
1826   angularDiff[ PITCH ] = AngleSubtract( self->s.angles2[ PITCH ], angleToTarget[ PITCH ] );
1827   angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] );
1828 
1829   //if not pointing at our target then move accordingly
1830   if( angularDiff[ PITCH ] < (-accuracyTolerance) )
1831     self->s.angles2[ PITCH ] += angularSpeed;
1832   else if( angularDiff[ PITCH ] > accuracyTolerance )
1833     self->s.angles2[ PITCH ] -= angularSpeed;
1834   else
1835     self->s.angles2[ PITCH ] = angleToTarget[ PITCH ];
1836 
1837   //disallow vertical movement past a certain limit
1838   temp = fabs( self->s.angles2[ PITCH ] );
1839   if( temp > 180 )
1840     temp -= 360;
1841 
1842   if( temp < -MGTURRET_VERTICALCAP )
1843     self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP;
1844 
1845   //if not pointing at our target then move accordingly
1846   if( angularDiff[ YAW ] < (-accuracyTolerance) )
1847     self->s.angles2[ YAW ] += angularSpeed;
1848   else if( angularDiff[ YAW ] > accuracyTolerance )
1849     self->s.angles2[ YAW ] -= angularSpeed;
1850   else
1851     self->s.angles2[ YAW ] = angleToTarget[ YAW ];
1852 
1853   AngleVectors( self->s.angles2, dttAdjusted, NULL, NULL );
1854   RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle );
1855   vectoangles( dirToTarget, self->turretAim );
1856 
1857   //if pointing at our target return true
1858   if( abs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance &&
1859       abs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance )
1860     return qtrue;
1861 
1862   return qfalse;
1863 }
1864 
1865 
1866 /*
1867 ================
1868 HMGTurret_CheckTarget
1869 
1870 Used by HMGTurret_Think to check enemies for validity
1871 ================
1872 */
HMGTurret_CheckTarget(gentity_t * self,gentity_t * target,qboolean ignorePainted)1873 qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted )
1874 {
1875   trace_t   trace;
1876   gentity_t *traceEnt;
1877 
1878   if( !target )
1879     return qfalse;
1880 
1881   if( !target->client )
1882     return qfalse;
1883 
1884   if( target->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
1885     return qfalse;
1886 
1887   if( target->health <= 0 )
1888     return qfalse;
1889 
1890   if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE )
1891     return qfalse;
1892 
1893   //some turret has already selected this target
1894   if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted )
1895     return qfalse;
1896 
1897   trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
1898 
1899   traceEnt = &g_entities[ trace.entityNum ];
1900 
1901   if( !traceEnt->client )
1902     return qfalse;
1903 
1904   if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
1905     return qfalse;
1906 
1907   return qtrue;
1908 }
1909 
1910 
1911 /*
1912 ================
1913 HMGTurret_FindEnemy
1914 
1915 Used by HMGTurret_Think to locate enemy gentities
1916 ================
1917 */
HMGTurret_FindEnemy(gentity_t * self)1918 void HMGTurret_FindEnemy( gentity_t *self )
1919 {
1920   int       entityList[ MAX_GENTITIES ];
1921   vec3_t    range;
1922   vec3_t    mins, maxs;
1923   int       i, num;
1924   gentity_t *target;
1925 
1926   VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE );
1927   VectorAdd( self->s.origin, range, maxs );
1928   VectorSubtract( self->s.origin, range, mins );
1929 
1930   //find aliens
1931   num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1932   for( i = 0; i < num; i++ )
1933   {
1934     target = &g_entities[ entityList[ i ] ];
1935 
1936     if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1937     {
1938       //if target is not valid keep searching
1939       if( !HMGTurret_CheckTarget( self, target, qfalse ) )
1940         continue;
1941 
1942       //we found a target
1943       self->enemy = target;
1944       return;
1945     }
1946   }
1947 
1948   if( self->dcced )
1949   {
1950     //check again, this time ignoring painted targets
1951     for( i = 0; i < num; i++ )
1952     {
1953       target = &g_entities[ entityList[ i ] ];
1954 
1955       if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1956       {
1957         //if target is not valid keep searching
1958         if( !HMGTurret_CheckTarget( self, target, qtrue ) )
1959           continue;
1960 
1961         //we found a target
1962         self->enemy = target;
1963         return;
1964       }
1965     }
1966   }
1967 
1968   //couldn't find a target
1969   self->enemy = NULL;
1970 }
1971 
1972 
1973 /*
1974 ================
1975 HMGTurret_Think
1976 
1977 Think function for MG turret
1978 ================
1979 */
HMGTurret_Think(gentity_t * self)1980 void HMGTurret_Think( gentity_t *self )
1981 {
1982   int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
1983 
1984   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
1985 
1986   //used for client side muzzle flashes
1987   self->s.eFlags &= ~EF_FIRING;
1988 
1989   //if not powered don't do anything and check again for power next think
1990   if( !( self->powered = findPower( self ) ) )
1991   {
1992     self->nextthink = level.time + POWER_REFRESH_TIME;
1993     return;
1994   }
1995 
1996   if( self->spawned )
1997   {
1998     //find a dcc for self
1999     self->dcced = findDCC( self );
2000 
2001     //if the current target is not valid find a new one
2002     if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) )
2003     {
2004       if( self->enemy )
2005         self->enemy->targeted = NULL;
2006 
2007       HMGTurret_FindEnemy( self );
2008     }
2009 
2010     //if a new target cannot be found don't do anything
2011     if( !self->enemy )
2012       return;
2013 
2014     self->enemy->targeted = self;
2015 
2016     //if we are pointing at our target and we can fire shoot it
2017     if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) )
2018     {
2019       //fire at target
2020       FireWeapon( self );
2021 
2022       self->s.eFlags |= EF_FIRING;
2023       G_AddEvent( self, EV_FIRE_WEAPON, 0 );
2024       G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
2025 
2026       self->count = level.time + firespeed;
2027     }
2028   }
2029 }
2030 
2031 
2032 
2033 
2034 //==================================================================================
2035 
2036 
2037 
2038 
2039 /*
2040 ================
2041 HTeslaGen_Think
2042 
2043 Think function for Tesla Generator
2044 ================
2045 */
HTeslaGen_Think(gentity_t * self)2046 void HTeslaGen_Think( gentity_t *self )
2047 {
2048   int       entityList[ MAX_GENTITIES ];
2049   vec3_t    range;
2050   vec3_t    mins, maxs;
2051   vec3_t    dir;
2052   int       i, num;
2053   gentity_t *enemy;
2054 
2055   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
2056 
2057   //if not powered don't do anything and check again for power next think
2058   if( !( self->powered = findPower( self ) ) || !( self->dcced = findDCC( self ) ) )
2059   {
2060     self->s.eFlags &= ~EF_FIRING;
2061     self->nextthink = level.time + POWER_REFRESH_TIME;
2062     return;
2063   }
2064 
2065   if( self->spawned && self->count < level.time )
2066   {
2067     //used to mark client side effects
2068     self->s.eFlags &= ~EF_FIRING;
2069 
2070     VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE );
2071     VectorAdd( self->s.origin, range, maxs );
2072     VectorSubtract( self->s.origin, range, mins );
2073 
2074     //find aliens
2075     num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
2076     for( i = 0; i < num; i++ )
2077     {
2078       enemy = &g_entities[ entityList[ i ] ];
2079 
2080       if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
2081           enemy->health > 0 )
2082       {
2083         VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir );
2084         VectorNormalize( dir );
2085         vectoangles( dir, self->turretAim );
2086 
2087         //fire at target
2088         FireWeapon( self );
2089       }
2090     }
2091 
2092     if( self->s.eFlags & EF_FIRING )
2093     {
2094       G_AddEvent( self, EV_FIRE_WEAPON, 0 );
2095 
2096       //doesn't really need an anim
2097       //G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
2098 
2099       self->count = level.time + TESLAGEN_REPEAT;
2100     }
2101   }
2102 }
2103 
2104 
2105 
2106 
2107 //==================================================================================
2108 
2109 
2110 
2111 
2112 /*
2113 ================
2114 HSpawn_Disappear
2115 
2116 Called when a human spawn is destroyed before it is spawned
2117 think function
2118 ================
2119 */
HSpawn_Disappear(gentity_t * self)2120 void HSpawn_Disappear( gentity_t *self )
2121 {
2122   vec3_t  dir;
2123 
2124   // we don't have a valid direction, so just point straight up
2125   dir[ 0 ] = dir[ 1 ] = 0;
2126   dir[ 2 ] = 1;
2127 
2128   self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
2129   self->timestamp = level.time;
2130 
2131   self->think = freeBuildable;
2132   self->nextthink = level.time + 100;
2133 
2134   self->r.contents = 0;    //stop collisions...
2135   trap_LinkEntity( self ); //...requires a relink
2136 }
2137 
2138 
2139 /*
2140 ================
2141 HSpawn_blast
2142 
2143 Called when a human spawn explodes
2144 think function
2145 ================
2146 */
HSpawn_Blast(gentity_t * self)2147 void HSpawn_Blast( gentity_t *self )
2148 {
2149   vec3_t  dir;
2150 
2151   // we don't have a valid direction, so just point straight up
2152   dir[ 0 ] = dir[ 1 ] = 0;
2153   dir[ 2 ] = 1;
2154 
2155   self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
2156   G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
2157   self->timestamp = level.time;
2158 
2159   //do some radius damage
2160   G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage,
2161     self->splashRadius, self, self->splashMethodOfDeath );
2162 
2163   self->think = freeBuildable;
2164   self->nextthink = level.time + 100;
2165 
2166   self->r.contents = 0;    //stop collisions...
2167   trap_LinkEntity( self ); //...requires a relink
2168 }
2169 
2170 
2171 /*
2172 ================
2173 HSpawn_die
2174 
2175 Called when a human spawn dies
2176 ================
2177 */
HSpawn_Die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2178 void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
2179 {
2180   //pretty events and cleanup
2181   G_setBuildableAnim( self, BANIM_DESTROY1, qtrue );
2182   G_setIdleBuildableAnim( self, BANIM_DESTROYED );
2183 
2184   self->die = nullDieFunction;
2185   self->powered = qfalse; //free up power
2186   self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
2187 
2188   if( self->spawned )
2189   {
2190     self->think = HSpawn_Blast;
2191     self->nextthink = level.time + HUMAN_DETONATION_DELAY;
2192   }
2193   else
2194   {
2195     self->think = HSpawn_Disappear;
2196     self->nextthink = level.time; //blast immediately
2197   }
2198 
2199   if( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
2200   {
2201     if( self->s.modelindex == BA_H_REACTOR )
2202       G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue );
2203     else if( self->s.modelindex == BA_H_SPAWN )
2204       G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue );
2205   }
2206 }
2207 
2208 /*
2209 ================
2210 HSpawn_Think
2211 
2212 Think for human spawn
2213 ================
2214 */
HSpawn_Think(gentity_t * self)2215 void HSpawn_Think( gentity_t *self )
2216 {
2217   gentity_t *ent;
2218 
2219   //make sure we have power
2220   self->powered = findPower( self );
2221 
2222   if( self->spawned )
2223   {
2224     //only suicide if at rest
2225     if( self->s.groundEntityNum )
2226     {
2227       if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
2228               self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL )
2229       {
2230         if( ent->s.eType == ET_BUILDABLE || ent->s.number == ENTITYNUM_WORLD ||
2231             ent->s.eType == ET_MOVER )
2232         {
2233           G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
2234           return;
2235         }
2236 
2237         if( ent->s.eType == ET_CORPSE )
2238           G_FreeEntity( ent ); //quietly remove
2239       }
2240     }
2241 
2242     //spawn under attack
2243     if( self->health < self->lastHealth &&
2244         level.time > level.humanBaseAttackTimer && G_isDCC( ) )
2245     {
2246       level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
2247       G_BroadcastEvent( EV_DCC_ATTACK, 0 );
2248     }
2249 
2250     self->lastHealth = self->health;
2251   }
2252 
2253   self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
2254 }
2255 
2256 
2257 
2258 
2259 //==================================================================================
2260 
2261 
2262 /*
2263 ============
2264 G_BuildableTouchTriggers
2265 
2266 Find all trigger entities that a buildable touches.
2267 ============
2268 */
G_BuildableTouchTriggers(gentity_t * ent)2269 void G_BuildableTouchTriggers( gentity_t *ent )
2270 {
2271   int       i, num;
2272   int       touch[ MAX_GENTITIES ];
2273   gentity_t *hit;
2274   trace_t   trace;
2275   vec3_t    mins, maxs;
2276   vec3_t    bmins, bmaxs;
2277   static    vec3_t range = { 10, 10, 10 };
2278 
2279   // dead buildables don't activate triggers!
2280   if( ent->health <= 0 )
2281     return;
2282 
2283   BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs );
2284 
2285   VectorAdd( ent->s.origin, bmins, mins );
2286   VectorAdd( ent->s.origin, bmaxs, maxs );
2287 
2288   VectorSubtract( mins, range, mins );
2289   VectorAdd( maxs, range, maxs );
2290 
2291   num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
2292 
2293   VectorAdd( ent->s.origin, bmins, mins );
2294   VectorAdd( ent->s.origin, bmaxs, maxs );
2295 
2296   for( i = 0; i < num; i++ )
2297   {
2298     hit = &g_entities[ touch[ i ] ];
2299 
2300     if( !hit->touch )
2301       continue;
2302 
2303     if( !( hit->r.contents & CONTENTS_TRIGGER ) )
2304       continue;
2305 
2306     //ignore buildables not yet spawned
2307     if( !ent->spawned )
2308       continue;
2309 
2310     if( !trap_EntityContact( mins, maxs, hit ) )
2311       continue;
2312 
2313     memset( &trace, 0, sizeof( trace ) );
2314 
2315     if( hit->touch )
2316       hit->touch( hit, ent, &trace );
2317   }
2318 }
2319 
2320 
2321 /*
2322 ===============
2323 G_BuildableThink
2324 
2325 General think function for buildables
2326 ===============
2327 */
G_BuildableThink(gentity_t * ent,int msec)2328 void G_BuildableThink( gentity_t *ent, int msec )
2329 {
2330   int bHealth = BG_FindHealthForBuildable( ent->s.modelindex );
2331   int bRegen = BG_FindRegenRateForBuildable( ent->s.modelindex );
2332   int bTime = BG_FindBuildTimeForBuildable( ent->s.modelindex );
2333 
2334   //pack health, power and dcc
2335 
2336   //toggle spawned flag for buildables
2337   if( !ent->spawned )
2338   {
2339     if( ent->buildTime + bTime < level.time )
2340       ent->spawned = qtrue;
2341   }
2342 
2343   ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_SCALE );
2344 
2345   if( ent->s.generic1 < 0 )
2346     ent->s.generic1 = 0;
2347 
2348   if( ent->powered )
2349     ent->s.generic1 |= B_POWERED_TOGGLEBIT;
2350 
2351   if( ent->dcced )
2352     ent->s.generic1 |= B_DCCED_TOGGLEBIT;
2353 
2354   if( ent->spawned )
2355     ent->s.generic1 |= B_SPAWNED_TOGGLEBIT;
2356 
2357   ent->time1000 += msec;
2358 
2359   if( ent->time1000 >= 1000 )
2360   {
2361     ent->time1000 -= 1000;
2362 
2363     if( !ent->spawned )
2364       ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) );
2365     else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth &&
2366         bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
2367       ent->health += bRegen;
2368 
2369     if( ent->health > bHealth )
2370       ent->health = bHealth;
2371   }
2372 
2373   if( ent->lev1Grabbed && ent->lev1GrabTime + LEVEL1_GRAB_TIME < level.time )
2374     ent->lev1Grabbed = qfalse;
2375 
2376   if( ent->clientSpawnTime > 0 )
2377     ent->clientSpawnTime -= msec;
2378 
2379   if( ent->clientSpawnTime < 0 )
2380     ent->clientSpawnTime = 0;
2381 
2382   //check if this buildable is touching any triggers
2383   G_BuildableTouchTriggers( ent );
2384 
2385   //fall back on normal physics routines
2386   G_Physics( ent, msec );
2387 }
2388 
2389 
2390 /*
2391 ===============
2392 G_BuildableRange
2393 
2394 Check whether a point is within some range of a type of buildable
2395 ===============
2396 */
G_BuildableRange(vec3_t origin,float r,buildable_t buildable)2397 qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )
2398 {
2399   int       entityList[ MAX_GENTITIES ];
2400   vec3_t    range;
2401   vec3_t    mins, maxs;
2402   int       i, num;
2403   gentity_t *ent;
2404 
2405   VectorSet( range, r, r, r );
2406   VectorAdd( origin, range, maxs );
2407   VectorSubtract( origin, range, mins );
2408 
2409   num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
2410   for( i = 0; i < num; i++ )
2411   {
2412     ent = &g_entities[ entityList[ i ] ];
2413 
2414     if( ent->s.eType != ET_BUILDABLE )
2415       continue;
2416 
2417     if( ent->biteam == BIT_HUMANS && !ent->powered )
2418       continue;
2419 
2420     if( ent->s.modelindex == buildable && ent->spawned )
2421       return qtrue;
2422   }
2423 
2424   return qfalse;
2425 }
2426 
2427 
2428 /*
2429 ================
2430 G_itemFits
2431 
2432 Checks to see if an item fits in a specific area
2433 ================
2434 */
G_itemFits(gentity_t * ent,buildable_t buildable,int distance,vec3_t origin)2435 itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin )
2436 {
2437   vec3_t            angles;
2438   vec3_t            entity_origin, normal;
2439   vec3_t            mins, maxs;
2440   trace_t           tr1, tr2, tr3;
2441   int               i;
2442   itemBuildError_t  reason = IBE_NONE;
2443   gentity_t         *tempent;
2444   float             minNormal;
2445   qboolean          invert;
2446   int               contents;
2447   playerState_t     *ps = &ent->client->ps;
2448 
2449   BG_FindBBoxForBuildable( buildable, mins, maxs );
2450 
2451   BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 );
2452 
2453   trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, ent->s.number, MASK_PLAYERSOLID );
2454   trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID );
2455 
2456   VectorCopy( entity_origin, origin );
2457 
2458   VectorCopy( tr1.plane.normal, normal );
2459   minNormal = BG_FindMinNormalForBuildable( buildable );
2460   invert = BG_FindInvertNormalForBuildable( buildable );
2461 
2462   //can we build at this angle?
2463   if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) )
2464     return IBE_NORMAL;
2465 
2466   if( tr1.entityNum != ENTITYNUM_WORLD )
2467     return IBE_NORMAL;
2468 
2469   //check there is enough room to spawn from (presuming this is a spawn)
2470   if( G_CheckSpawnPoint( -1, origin, normal, buildable, NULL ) != NULL )
2471     return IBE_NORMAL;
2472 
2473   contents = trap_PointContents( entity_origin, -1 );
2474 
2475   if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
2476   {
2477     //alien criteria
2478 
2479     if( buildable == BA_A_HOVEL )
2480     {
2481       vec3_t    builderMins, builderMaxs;
2482 
2483       //this assumes the adv builder is the biggest thing that'll use the hovel
2484       BG_FindBBoxForClass( PCL_ALIEN_BUILDER0_UPG, builderMins, builderMaxs, NULL, NULL, NULL );
2485 
2486       if( APropHovel_Blocked( angles, origin, normal, ent ) )
2487         reason = IBE_HOVELEXIT;
2488     }
2489 
2490     //check there is creep near by for building on
2491     if( BG_FindCreepTestForBuildable( buildable ) )
2492     {
2493       if( !isCreep( entity_origin ) )
2494         reason = IBE_NOCREEP;
2495     }
2496 
2497     //check permission to build here
2498     if( tr1.surfaceFlags & SURF_NOALIENBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
2499         contents & CONTENTS_NOALIENBUILD || contents & CONTENTS_NOBUILD )
2500       reason = IBE_PERMISSION;
2501 
2502     //look for a hivemind
2503     for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
2504     {
2505       if( tempent->s.eType != ET_BUILDABLE )
2506         continue;
2507       if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned )
2508         break;
2509     }
2510 
2511     //if none found...
2512     if( i >= level.num_entities && buildable != BA_A_OVERMIND )
2513       reason = IBE_NOOVERMIND;
2514 
2515     //can we only have one of these?
2516     if( BG_FindUniqueTestForBuildable( buildable ) )
2517     {
2518       for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
2519       {
2520         if( tempent->s.eType != ET_BUILDABLE )
2521           continue;
2522 
2523         if( tempent->s.modelindex == buildable )
2524         {
2525           switch( buildable )
2526           {
2527             case BA_A_OVERMIND:
2528               reason = IBE_OVERMIND;
2529               break;
2530 
2531             case BA_A_HOVEL:
2532               reason = IBE_HOVEL;
2533               break;
2534 
2535             default:
2536               Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable );
2537               break;
2538           }
2539 
2540           break;
2541         }
2542       }
2543     }
2544 
2545     if( level.alienBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 )
2546       reason = IBE_NOASSERT;
2547   }
2548   else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
2549   {
2550     //human criteria
2551     if( !G_isPower( entity_origin ) )
2552     {
2553       //tell player to build a repeater to provide power
2554       if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER )
2555         reason = IBE_REPEATER;
2556     }
2557 
2558     //this buildable requires a DCC
2559     if( BG_FindDCCTestForBuildable( buildable ) && !G_isDCC( ) )
2560       reason = IBE_NODCC;
2561 
2562     //check that there is a parent reactor when building a repeater
2563     if( buildable == BA_H_REPEATER )
2564     {
2565       for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
2566       {
2567         if( tempent->s.eType != ET_BUILDABLE )
2568           continue;
2569 
2570         if( tempent->s.modelindex == BA_H_REACTOR )
2571           break;
2572       }
2573 
2574       if( i >= level.num_entities )
2575       {
2576         //no reactor present
2577 
2578         //check for other nearby repeaters
2579         for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
2580         {
2581           if( tempent->s.eType != ET_BUILDABLE )
2582             continue;
2583 
2584           if( tempent->s.modelindex == BA_H_REPEATER &&
2585               Distance( tempent->s.origin, entity_origin ) < REPEATER_BASESIZE )
2586           {
2587             reason = IBE_RPTWARN2;
2588             break;
2589           }
2590         }
2591 
2592         if( reason == IBE_NONE )
2593           reason = IBE_RPTWARN;
2594       }
2595       else if( G_isPower( entity_origin ) )
2596         reason = IBE_RPTWARN2;
2597     }
2598 
2599     //check permission to build here
2600     if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
2601         contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD )
2602       reason = IBE_PERMISSION;
2603 
2604     //can we only build one of these?
2605     if( BG_FindUniqueTestForBuildable( buildable ) )
2606     {
2607       for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
2608       {
2609         if( tempent->s.eType != ET_BUILDABLE )
2610           continue;
2611 
2612         if( tempent->s.modelindex == BA_H_REACTOR )
2613         {
2614           reason = IBE_REACTOR;
2615           break;
2616         }
2617       }
2618     }
2619 
2620     if( level.humanBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 )
2621       reason = IBE_NOPOWER;
2622   }
2623 
2624   //this item does not fit here
2625   if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) )
2626     return IBE_NOROOM;
2627 
2628   return reason;
2629 }
2630 
2631 
2632 /*
2633 ================
2634 G_buildItem
2635 
2636 Spawns a buildable
2637 ================
2638 */
G_buildItem(gentity_t * builder,buildable_t buildable,vec3_t origin,vec3_t angles)2639 gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles )
2640 {
2641   gentity_t *built;
2642   vec3_t    normal;
2643 
2644   //spawn the buildable
2645   built = G_Spawn();
2646 
2647   built->s.eType = ET_BUILDABLE;
2648 
2649   built->classname = BG_FindEntityNameForBuildable( buildable );
2650 
2651   built->s.modelindex = buildable; //so we can tell what this is on the client side
2652   built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable );
2653 
2654   BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs );
2655   built->health = 1;
2656 
2657   built->splashDamage = BG_FindSplashDamageForBuildable( buildable );
2658   built->splashRadius = BG_FindSplashRadiusForBuildable( buildable );
2659   built->splashMethodOfDeath = BG_FindMODForBuildable( buildable );
2660 
2661   built->nextthink = BG_FindNextThinkForBuildable( buildable );
2662 
2663   built->takedamage = qtrue;
2664   built->spawned = qfalse;
2665   built->buildTime = built->s.time = level.time;
2666 
2667   //things that vary for each buildable that aren't in the dbase
2668   switch( buildable )
2669   {
2670     case BA_A_SPAWN:
2671       built->die = ASpawn_Die;
2672       built->think = ASpawn_Think;
2673       built->pain = ASpawn_Pain;
2674       break;
2675 
2676     case BA_A_BARRICADE:
2677       built->die = ABarricade_Die;
2678       built->think = ABarricade_Think;
2679       built->pain = ABarricade_Pain;
2680       break;
2681 
2682     case BA_A_BOOSTER:
2683       built->die = ABarricade_Die;
2684       built->think = ABarricade_Think;
2685       built->pain = ABarricade_Pain;
2686       built->touch = ABooster_Touch;
2687       break;
2688 
2689     case BA_A_ACIDTUBE:
2690       built->die = ABarricade_Die;
2691       built->think = AAcidTube_Think;
2692       built->pain = ASpawn_Pain;
2693       break;
2694 
2695     case BA_A_HIVE:
2696       built->die = ABarricade_Die;
2697       built->think = AHive_Think;
2698       built->pain = ASpawn_Pain;
2699       break;
2700 
2701     case BA_A_TRAPPER:
2702       built->die = ABarricade_Die;
2703       built->think = ATrapper_Think;
2704       built->pain = ASpawn_Pain;
2705       break;
2706 
2707     case BA_A_OVERMIND:
2708       built->die = ASpawn_Die;
2709       built->think = AOvermind_Think;
2710       built->pain = ASpawn_Pain;
2711       break;
2712 
2713     case BA_A_HOVEL:
2714       built->die = AHovel_Die;
2715       built->use = AHovel_Use;
2716       built->think = AHovel_Think;
2717       built->pain = ASpawn_Pain;
2718       break;
2719 
2720     case BA_H_SPAWN:
2721       built->die = HSpawn_Die;
2722       built->think = HSpawn_Think;
2723       break;
2724 
2725     case BA_H_MGTURRET:
2726       built->die = HSpawn_Die;
2727       built->think = HMGTurret_Think;
2728       break;
2729 
2730     case BA_H_TESLAGEN:
2731       built->die = HSpawn_Die;
2732       built->think = HTeslaGen_Think;
2733       break;
2734 
2735     case BA_H_ARMOURY:
2736       built->think = HArmoury_Think;
2737       built->die = HSpawn_Die;
2738       built->use = HArmoury_Activate;
2739       break;
2740 
2741     case BA_H_DCC:
2742       built->think = HDCC_Think;
2743       built->die = HSpawn_Die;
2744       break;
2745 
2746     case BA_H_MEDISTAT:
2747       built->think = HMedistat_Think;
2748       built->die = HSpawn_Die;
2749       break;
2750 
2751     case BA_H_REACTOR:
2752       built->think = HReactor_Think;
2753       built->die = HSpawn_Die;
2754       built->use = HRepeater_Use;
2755       built->powered = built->active = qtrue;
2756       break;
2757 
2758     case BA_H_REPEATER:
2759       built->think = HRepeater_Think;
2760       built->die = HSpawn_Die;
2761       built->use = HRepeater_Use;
2762       built->count = -1;
2763       break;
2764 
2765     default:
2766       //erk
2767       break;
2768   }
2769 
2770   built->s.number = built - g_entities;
2771   built->r.contents = CONTENTS_BODY;
2772   built->clipmask = MASK_PLAYERSOLID;
2773   built->enemy = NULL;
2774   built->s.weapon = BG_FindProjTypeForBuildable( buildable );
2775 
2776   if( builder->client )
2777     built->builtBy = builder->client->ps.clientNum;
2778   else
2779     built->builtBy = -1;
2780 
2781   G_SetOrigin( built, origin );
2782   VectorCopy( angles, built->s.angles );
2783   built->s.angles[ PITCH ] = 0.0f;
2784   built->s.angles2[ YAW ] = angles[ YAW ];
2785   built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable );
2786   built->s.pos.trTime = level.time;
2787   built->physicsBounce = BG_FindBounceForBuildable( buildable );
2788   built->s.groundEntityNum = -1;
2789 
2790   if( builder->client && builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
2791   {
2792     if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
2793       VectorSet( normal, 0.0f, 0.0f, -1.0f );
2794     else
2795       VectorCopy( builder->client->ps.grapplePoint, normal );
2796 
2797     //gently nudge the buildable onto the surface :)
2798     VectorScale( normal, -50.0f, built->s.pos.trDelta );
2799   }
2800   else
2801     VectorSet( normal, 0.0f, 0.0f, 1.0f );
2802 
2803   built->s.generic1 = (int)( ( (float)built->health /
2804         (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_SCALE );
2805 
2806   if( built->s.generic1 < 0 )
2807     built->s.generic1 = 0;
2808 
2809   if( ( built->powered = findPower( built ) ) )
2810     built->s.generic1 |= B_POWERED_TOGGLEBIT;
2811 
2812   if( ( built->dcced = findDCC( built ) ) )
2813     built->s.generic1 |= B_DCCED_TOGGLEBIT;
2814 
2815   built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT;
2816 
2817   VectorCopy( normal, built->s.origin2 );
2818 
2819   G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 );
2820 
2821   G_setIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) );
2822 
2823   if( built->builtBy >= 0 )
2824     G_setBuildableAnim( built, BANIM_CONSTRUCT1, qtrue );
2825 
2826   trap_LinkEntity( built );
2827 
2828   return built;
2829 }
2830 
2831 /*
2832 =================
2833 G_ValidateBuild
2834 =================
2835 */
G_ValidateBuild(gentity_t * ent,buildable_t buildable)2836 qboolean G_ValidateBuild( gentity_t *ent, buildable_t buildable )
2837 {
2838   float         dist;
2839   vec3_t        origin;
2840 
2841   dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
2842 
2843   switch( G_itemFits( ent, buildable, dist, origin ) )
2844   {
2845     case IBE_NONE:
2846       G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
2847       return qtrue;
2848 
2849     case IBE_NOASSERT:
2850       G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT );
2851       return qfalse;
2852 
2853     case IBE_NOOVERMIND:
2854       G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND );
2855       return qfalse;
2856 
2857     case IBE_NOCREEP:
2858       G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
2859       return qfalse;
2860 
2861     case IBE_OVERMIND:
2862       G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND );
2863       return qfalse;
2864 
2865     case IBE_HOVEL:
2866       G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL );
2867       return qfalse;
2868 
2869     case IBE_HOVELEXIT:
2870       G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT );
2871       return qfalse;
2872 
2873     case IBE_NORMAL:
2874       if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
2875         G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL );
2876       else
2877         G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL );
2878       return qfalse;
2879 
2880     case IBE_PERMISSION:
2881       if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
2882         G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL );
2883       else
2884         G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL );
2885       return qfalse;
2886 
2887     case IBE_REACTOR:
2888       G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
2889       return qfalse;
2890 
2891     case IBE_REPEATER:
2892       G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
2893       return qfalse;
2894 
2895     case IBE_NOROOM:
2896       if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
2897         G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOM );
2898       else
2899         G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOROOM );
2900       return qfalse;
2901 
2902     case IBE_NOPOWER:
2903       G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
2904       return qfalse;
2905 
2906     case IBE_NODCC:
2907       G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
2908       return qfalse;
2909 
2910     case IBE_SPWNWARN:
2911       G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN );
2912       G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
2913       return qtrue;
2914 
2915     case IBE_TNODEWARN:
2916       G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN );
2917       G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
2918       return qtrue;
2919 
2920     case IBE_RPTWARN:
2921       G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN );
2922       G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
2923       return qtrue;
2924 
2925     case IBE_RPTWARN2:
2926       G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 );
2927       return qfalse;
2928 
2929     default:
2930       break;
2931   }
2932 
2933   return qfalse;
2934 }
2935 
2936 /*
2937 ================
2938 FinishSpawningBuildable
2939 
2940 Traces down to find where an item should rest, instead of letting them
2941 free fall from their spawn points
2942 ================
2943 */
FinishSpawningBuildable(gentity_t * ent)2944 void FinishSpawningBuildable( gentity_t *ent )
2945 {
2946   trace_t     tr;
2947   vec3_t      dest;
2948   gentity_t   *built;
2949   buildable_t buildable = ent->s.modelindex;
2950 
2951   built = G_buildItem( ent, buildable, ent->s.pos.trBase, ent->s.angles );
2952   G_FreeEntity( ent );
2953 
2954   built->takedamage = qtrue;
2955   built->spawned = qtrue; //map entities are already spawned
2956   built->health = BG_FindHealthForBuildable( buildable );
2957   built->s.generic1 |= B_SPAWNED_TOGGLEBIT;
2958 
2959   // drop to floor
2960   if( buildable != BA_NONE && BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY )
2961     VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] + 4096 );
2962   else
2963     VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] - 4096 );
2964 
2965   trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask );
2966 
2967   if( tr.startsolid )
2968   {
2969     G_Printf( S_COLOR_YELLOW "FinishSpawningBuildable: %s startsolid at %s\n", built->classname, vtos( built->s.origin ) );
2970     G_FreeEntity( built );
2971     return;
2972   }
2973 
2974   //point items in the correct direction
2975   VectorCopy( tr.plane.normal, built->s.origin2 );
2976 
2977   // allow to ride movers
2978   built->s.groundEntityNum = tr.entityNum;
2979 
2980   G_SetOrigin( built, tr.endpos );
2981 
2982   trap_LinkEntity( built );
2983 }
2984 
2985 /*
2986 ============
2987 G_SpawnBuildable
2988 
2989 Sets the clipping size and plants the object on the floor.
2990 
2991 Items can't be immediately dropped to floor, because they might
2992 be on an entity that hasn't spawned yet.
2993 ============
2994 */
G_SpawnBuildable(gentity_t * ent,buildable_t buildable)2995 void G_SpawnBuildable( gentity_t *ent, buildable_t buildable )
2996 {
2997   ent->s.modelindex = buildable;
2998 
2999   // some movers spawn on the second frame, so delay item
3000   // spawns until the third frame so they can ride trains
3001   ent->nextthink = level.time + FRAMETIME * 2;
3002   ent->think = FinishSpawningBuildable;
3003 }
3004