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