summaryrefslogtreecommitdiff
path: root/src/game/g_buildable.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/g_buildable.c')
-rw-r--r--src/game/g_buildable.c2987
1 files changed, 2987 insertions, 0 deletions
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c
new file mode 100644
index 00000000..083e0662
--- /dev/null
+++ b/src/game/g_buildable.c
@@ -0,0 +1,2987 @@
+/*
+ * Portions Copyright (C) 2000-2001 Tim Angus
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the OSML - Open Source Modification License v1.0 as
+ * described in the file COPYING which is distributed with this source
+ * code.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "g_local.h"
+
+/*
+================
+G_setBuildableAnim
+
+Triggers an animation client side
+================
+*/
+void G_setBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force )
+{
+ int localAnim = anim;
+
+ if( force )
+ localAnim |= ANIM_FORCEBIT;
+
+ localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT );
+
+ ent->s.legsAnim = localAnim;
+}
+
+/*
+================
+G_setIdleBuildableAnim
+
+Set the animation to use whilst no other animations are running
+================
+*/
+void G_setIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim )
+{
+ ent->s.torsoAnim = anim;
+}
+
+/*
+===============
+G_CheckSpawnPoint
+
+Check if a spawn at a specified point is valid
+===============
+*/
+gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal,
+ buildable_t spawn, vec3_t spawnOrigin )
+{
+ float displacement;
+ vec3_t mins, maxs;
+ vec3_t cmins, cmaxs;
+ vec3_t localOrigin;
+ trace_t tr;
+
+ BG_FindBBoxForBuildable( spawn, mins, maxs );
+
+ if( spawn == BA_A_SPAWN )
+ {
+ VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX );
+ VectorSet( cmaxs, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX );
+
+ displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3;
+ VectorMA( origin, displacement, normal, localOrigin );
+
+ trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
+
+ if( tr.entityNum != ENTITYNUM_NONE )
+ return &g_entities[ tr.entityNum ];
+
+ trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_SHOT );
+
+ if( tr.entityNum == ENTITYNUM_NONE )
+ {
+ if( spawnOrigin != NULL )
+ VectorCopy( localOrigin, spawnOrigin );
+
+ return NULL;
+ }
+ else
+ return &g_entities[ tr.entityNum ];
+ }
+ else if( spawn == BA_H_SPAWN )
+ {
+ BG_FindBBoxForClass( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL );
+
+ VectorCopy( origin, localOrigin );
+ localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f;
+
+ trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
+
+ if( tr.entityNum != ENTITYNUM_NONE )
+ return &g_entities[ tr.entityNum ];
+
+ trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_SHOT );
+
+ if( tr.entityNum == ENTITYNUM_NONE )
+ {
+ if( spawnOrigin != NULL )
+ VectorCopy( localOrigin, spawnOrigin );
+
+ return NULL;
+ }
+ else
+ return &g_entities[ tr.entityNum ];
+ }
+
+ return NULL;
+}
+
+/*
+================
+G_NumberOfDependants
+
+Return number of entities that depend on this one
+================
+*/
+static int G_NumberOfDependants( gentity_t *self )
+{
+ int i, n = 0;
+ gentity_t *ent;
+
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->parentNode == self )
+ n++;
+ }
+
+ return n;
+}
+
+#define POWER_REFRESH_TIME 2000
+
+/*
+================
+findPower
+
+attempt to find power for self, return qtrue if successful
+================
+*/
+static qboolean findPower( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestPower = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+ qboolean foundPower = qfalse;
+
+ if( self->biteam != BIT_HUMANS )
+ return qfalse;
+
+ //reactor is always powered
+ if( self->s.modelindex == BA_H_REACTOR )
+ return qtrue;
+
+ //if this already has power then stop now
+ if( self->parentNode && self->parentNode->powered )
+ return qtrue;
+
+ //reset parent
+ self->parentNode = NULL;
+
+ //iterate through entities
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ //if entity is a power item calculate the distance to it
+ if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) &&
+ ent->spawned )
+ {
+ VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ distance = VectorLength( temp_v );
+ if( distance < minDistance && ent->powered )
+ {
+ closestPower = ent;
+ minDistance = distance;
+ foundPower = qtrue;
+ }
+ }
+ }
+
+ //if there were no power items nearby give up
+ if( !foundPower )
+ return qfalse;
+
+ //bleh
+ if( ( closestPower->s.modelindex == BA_H_REACTOR && ( minDistance <= REACTOR_BASESIZE ) ) ||
+ ( closestPower->s.modelindex == BA_H_REPEATER && ( minDistance <= REPEATER_BASESIZE ) &&
+ closestPower->powered ) )
+ {
+ self->parentNode = closestPower;
+
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+================
+G_isPower
+
+Simple wrapper to findPower to check if a location has power
+================
+*/
+qboolean G_isPower( vec3_t origin )
+{
+ gentity_t dummy;
+
+ dummy.parentNode = NULL;
+ dummy.biteam = BIT_HUMANS;
+ VectorCopy( origin, dummy.s.origin );
+
+ return findPower( &dummy );
+}
+
+/*
+================
+findDCC
+
+attempt to find a controlling DCC for self, return qtrue if successful
+================
+*/
+static qboolean findDCC( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestDCC = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+ qboolean foundDCC = qfalse;
+
+ if( self->biteam != BIT_HUMANS )
+ return qfalse;
+
+ //if this already has dcc then stop now
+ if( self->dccNode && self->dccNode->powered )
+ return qtrue;
+
+ //reset parent
+ self->dccNode = NULL;
+
+ //iterate through entities
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ //if entity is a dcc calculate the distance to it
+ if( ent->s.modelindex == BA_H_DCC && ent->spawned )
+ {
+ VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ distance = VectorLength( temp_v );
+ if( distance < minDistance && ent->powered )
+ {
+ closestDCC = ent;
+ minDistance = distance;
+ foundDCC = qtrue;
+ }
+ }
+ }
+
+ //if there were no power items nearby give up
+ if( !foundDCC )
+ return qfalse;
+
+ self->dccNode = closestDCC;
+
+ return qtrue;
+}
+
+/*
+================
+G_isDCC
+
+simple wrapper to findDCC to check for a dcc
+================
+*/
+qboolean G_isDCC( void )
+{
+ gentity_t dummy;
+
+ dummy.dccNode = NULL;
+ dummy.biteam = BIT_HUMANS;
+
+ return findDCC( &dummy );
+}
+
+/*
+================
+findOvermind
+
+Attempt to find an overmind for self
+================
+*/
+static qboolean findOvermind( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+
+ if( self->biteam != BIT_ALIENS )
+ return qfalse;
+
+ //if this already has overmind then stop now
+ if( self->overmindNode && self->overmindNode->health > 0 )
+ return qtrue;
+
+ //reset parent
+ self->overmindNode = NULL;
+
+ //iterate through entities
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ //if entity is an overmind calculate the distance to it
+ if( ent->s.modelindex == BA_A_OVERMIND && ent->spawned && ent->health > 0 )
+ {
+ self->overmindNode = ent;
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+================
+G_isOvermind
+
+Simple wrapper to findOvermind to check if a location has an overmind
+================
+*/
+qboolean G_isOvermind( void )
+{
+ gentity_t dummy;
+
+ dummy.overmindNode = NULL;
+ dummy.biteam = BIT_ALIENS;
+
+ return findOvermind( &dummy );
+}
+
+/*
+================
+findCreep
+
+attempt to find creep for self, return qtrue if successful
+================
+*/
+static qboolean findCreep( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestSpawn = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+
+ //don't check for creep if flying through the air
+ if( self->s.groundEntityNum == -1 )
+ return qtrue;
+
+ //if self does not have a parentNode or it's parentNode is invalid find a new one
+ if( ( self->parentNode == NULL ) || !self->parentNode->inuse )
+ {
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_A_OVERMIND ) &&
+ ent->spawned )
+ {
+ VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ distance = VectorLength( temp_v );
+ if( distance < minDistance )
+ {
+ closestSpawn = ent;
+ minDistance = distance;
+ }
+ }
+ }
+
+ if( minDistance <= CREEP_BASESIZE )
+ {
+ self->parentNode = closestSpawn;
+ return qtrue;
+ }
+ else
+ return qfalse;
+ }
+
+ //if we haven't returned by now then we must already have a valid parent
+ return qtrue;
+}
+
+/*
+================
+isCreep
+
+simple wrapper to findCreep to check if a location has creep
+================
+*/
+static qboolean isCreep( vec3_t origin )
+{
+ gentity_t dummy;
+
+ dummy.parentNode = NULL;
+ VectorCopy( origin, dummy.s.origin );
+
+ return findCreep( &dummy );
+}
+
+/*
+================
+creepSlow
+
+Set any nearby humans' SS_CREEPSLOWED flag
+================
+*/
+static void creepSlow( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+ buildable_t buildable = self->s.modelindex;
+ float creepSize = (float)BG_FindCreepSizeForBuildable( buildable );
+
+ VectorSet( range, creepSize, creepSize, creepSize );
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //find humans
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ enemy->client->ps.groundEntityNum != ENTITYNUM_NONE &&
+ G_Visible( self, enemy ) )
+ {
+ enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED;
+ enemy->client->lastCreepSlowTime = level.time;
+ }
+ }
+}
+
+/*
+================
+nullDieFunction
+
+hack to prevent compilers complaining about function pointer -> NULL conversion
+================
+*/
+static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+}
+
+/*
+================
+freeBuildable
+================
+*/
+static void freeBuildable( gentity_t *self )
+{
+ G_FreeEntity( self );
+}
+
+
+//==================================================================================
+
+
+
+/*
+================
+A_CreepRecede
+
+Called when an alien spawn dies
+================
+*/
+void A_CreepRecede( gentity_t *self )
+{
+ //if the creep just died begin the recession
+ if( !( self->s.eFlags & EF_DEAD ) )
+ {
+ self->s.eFlags |= EF_DEAD;
+ G_AddEvent( self, EV_BUILD_DESTROY, 0 );
+
+ if( self->spawned )
+ self->s.time = -level.time;
+ else
+ self->s.time = -( level.time -
+ (int)( (float)CREEP_SCALEDOWN_TIME *
+ ( 1.0f - ( (float)( level.time - self->buildTime ) /
+ (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
+ }
+
+ //creep is still receeding
+ if( ( self->timestamp + 10000 ) > level.time )
+ self->nextthink = level.time + 500;
+ else //creep has died
+ G_FreeEntity( self );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+ASpawn_Melt
+
+Called when an alien spawn dies
+================
+*/
+void ASpawn_Melt( gentity_t *self )
+{
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //start creep recession
+ if( !( self->s.eFlags & EF_DEAD ) )
+ {
+ self->s.eFlags |= EF_DEAD;
+ G_AddEvent( self, EV_BUILD_DESTROY, 0 );
+
+ if( self->spawned )
+ self->s.time = -level.time;
+ else
+ self->s.time = -( level.time -
+ (int)( (float)CREEP_SCALEDOWN_TIME *
+ ( 1.0f - ( (float)( level.time - self->buildTime ) /
+ (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
+ }
+
+ //not dead yet
+ if( ( self->timestamp + 10000 ) > level.time )
+ self->nextthink = level.time + 500;
+ else //dead now
+ G_FreeEntity( self );
+}
+
+/*
+================
+ASpawn_Blast
+
+Called when an alien spawn dies
+================
+*/
+void ASpawn_Blast( gentity_t *self )
+{
+ vec3_t dir;
+
+ VectorCopy( self->s.origin2, dir );
+
+ //do a bit of radius damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //pretty events and item cleanup
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed
+ G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->timestamp = level.time;
+ self->think = ASpawn_Melt;
+ self->nextthink = level.time + 500; //wait .5 seconds before damaging others
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+/*
+================
+ASpawn_Die
+
+Called when an alien spawn dies
+================
+*/
+void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ G_setBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_setIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->think = ASpawn_Blast;
+
+ if( self->spawned )
+ self->nextthink = level.time + 5000;
+ else
+ self->nextthink = level.time; //blast immediately
+
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( self->s.modelindex == BA_A_OVERMIND )
+ G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue );
+ else if( self->s.modelindex == BA_A_SPAWN )
+ G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue );
+ }
+}
+
+/*
+================
+ASpawn_Think
+
+think function for Alien Spawn
+================
+*/
+void ASpawn_Think( gentity_t *self )
+{
+ gentity_t *ent;
+
+ if( self->spawned )
+ {
+ //only suicide if at rest
+ if( self->s.groundEntityNum )
+ {
+ if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
+ self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL )
+ {
+ if( ent->s.eType == ET_BUILDABLE || ent->s.number == ENTITYNUM_WORLD ||
+ ent->s.eType == ET_MOVER )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
+ }
+ }
+
+ creepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+/*
+================
+ASpawn_Pain
+
+pain function for Alien Spawn
+================
+*/
+void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ G_setBuildableAnim( self, BANIM_PAIN1, qfalse );
+}
+
+
+
+
+
+//==================================================================================
+
+
+
+
+
+#define OVERMIND_ATTACK_PERIOD 10000
+#define OVERMIND_DYING_PERIOD 5000
+#define OVERMIND_SPAWNS_PERIOD 30000
+
+/*
+================
+AOvermind_Think
+
+Think function for Alien Overmind
+================
+*/
+void AOvermind_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ if( self->spawned && ( self->health > 0 ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ self->timestamp = level.time;
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS );
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ }
+ }
+
+ //low on spawns
+ if( level.numAlienSpawns <= 0 && level.time > self->overmindSpawnsTimer )
+ {
+ self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 );
+ }
+
+ //overmind dying
+ if( self->health < ( OVERMIND_HEALTH / 10.0f ) && level.time > self->overmindDyingTimer )
+ {
+ self->overmindDyingTimer = level.time + OVERMIND_DYING_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_DYING, 0 );
+ }
+
+ //overmind under attack
+ if( self->health < self->lastHealth && level.time > self->overmindAttackTimer )
+ {
+ self->overmindAttackTimer = level.time + OVERMIND_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_ATTACK, 0 );
+ }
+
+ self->lastHealth = self->health;
+ }
+ else
+ self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
+
+ creepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+
+
+//==================================================================================
+
+
+
+
+
+/*
+================
+ABarricade_Pain
+
+pain function for Alien Spawn
+================
+*/
+void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( rand( ) % 1 )
+ G_setBuildableAnim( self, BANIM_PAIN1, qfalse );
+ else
+ G_setBuildableAnim( self, BANIM_PAIN2, qfalse );
+}
+
+/*
+================
+ABarricade_Blast
+
+Called when an alien spawn dies
+================
+*/
+void ABarricade_Blast( gentity_t *self )
+{
+ vec3_t dir;
+
+ VectorCopy( self->s.origin2, dir );
+
+ //do a bit of radius damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //pretty events and item cleanup
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->timestamp = level.time;
+ self->think = A_CreepRecede;
+ self->nextthink = level.time + 500; //wait .5 seconds before damaging others
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+/*
+================
+ABarricade_Die
+
+Called when an alien spawn dies
+================
+*/
+void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ G_setBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_setIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->think = ABarricade_Blast;
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( self->spawned )
+ self->nextthink = level.time + 5000;
+ else
+ self->nextthink = level.time; //blast immediately
+}
+
+/*
+================
+ABarricade_Think
+
+Think function for Alien Barricade
+================
+*/
+void ABarricade_Think( gentity_t *self )
+{
+ //if there is no creep nearby die
+ if( !findCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ creepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+void AAcidTube_Think( gentity_t *self );
+
+/*
+================
+AAcidTube_Damage
+
+Damage function for Alien Acid Tube
+================
+*/
+void AAcidTube_Damage( gentity_t *self )
+{
+ if( self->spawned )
+ {
+ if( !( self->s.eFlags & EF_FIRING ) )
+ {
+ self->s.eFlags |= EF_FIRING;
+ G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) );
+ }
+
+ if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time )
+ self->think = AAcidTube_Damage;
+ else
+ {
+ self->think = AAcidTube_Think;
+ self->s.eFlags &= ~EF_FIRING;
+ }
+
+ //do some damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+ }
+
+ creepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+/*
+================
+AAcidTube_Think
+
+Think function for Alien Acid Tube
+================
+*/
+void AAcidTube_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //if there is no creep nearby die
+ if( !findCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->spawned && findOvermind( self ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( !G_Visible( self, enemy ) )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ self->timestamp = level.time;
+ self->think = AAcidTube_Damage;
+ self->nextthink = level.time + 100;
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ return;
+ }
+ }
+ }
+
+ creepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+AHive_Think
+
+Think function for Alien Hive
+================
+*/
+void AHive_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy;
+ vec3_t dirToTarget;
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //if there is no creep nearby die
+ if( !findCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->timestamp < level.time )
+ self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it
+
+ if( self->spawned && !self->active && findOvermind( self ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->health <= 0 )
+ continue;
+
+ if( !G_Visible( self, enemy ) )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ self->active = qtrue;
+ self->target_ent = enemy;
+ self->timestamp = level.time + HIVE_REPEAT;
+
+ VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ return;
+ }
+ }
+ }
+
+ creepSlow( self );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+#define HOVEL_TRACE_DEPTH 128.0f
+
+/*
+================
+AHovel_Blocked
+
+Is this hovel entrance blocked?
+================
+*/
+qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit )
+{
+ vec3_t forward, normal, origin, start, end, angles, hovelMaxs;
+ vec3_t mins, maxs;
+ float displacement;
+ trace_t tr;
+
+ BG_FindBBoxForBuildable( BA_A_HOVEL, NULL, hovelMaxs );
+ BG_FindBBoxForClass( player->client->ps.stats[ STAT_PCLASS ],
+ mins, maxs, NULL, NULL, NULL );
+
+ VectorCopy( hovel->s.origin2, normal );
+ AngleVectors( hovel->s.angles, forward, NULL, NULL );
+ VectorInverse( forward );
+
+ displacement = VectorMaxComponent( maxs ) * M_ROOT3 +
+ VectorMaxComponent( hovelMaxs ) * M_ROOT3 + 1.0f;
+
+ VectorMA( hovel->s.origin, displacement, forward, origin );
+ vectoangles( forward, angles );
+
+ VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start );
+
+ //compute a place up in the air to start the real trace
+ trap_Trace( &tr, origin, mins, maxs, start, player->s.number, MASK_PLAYERSOLID );
+ VectorMA( origin, ( HOVEL_TRACE_DEPTH * tr.fraction ) - 1.0f, normal, start );
+ VectorMA( origin, -HOVEL_TRACE_DEPTH, normal, end );
+
+ trap_Trace( &tr, start, mins, maxs, end, player->s.number, MASK_PLAYERSOLID );
+
+ if( tr.startsolid )
+ return qtrue;
+
+ VectorCopy( tr.endpos, origin );
+
+ trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID );
+
+ if( provideExit )
+ {
+ G_SetOrigin( player, origin );
+ VectorCopy( origin, player->client->ps.origin );
+ VectorCopy( vec3_origin, player->client->ps.velocity );
+ SetClientViewAngle( player, angles );
+ }
+
+ if( tr.fraction < 1.0f )
+ return qtrue;
+ else
+ return qfalse;
+}
+
+/*
+================
+APropHovel_Blocked
+
+Wrapper to test a hovel placement for validity
+================
+*/
+static qboolean APropHovel_Blocked( vec3_t origin, vec3_t angles, vec3_t normal,
+ gentity_t *player )
+{
+ gentity_t hovel;
+
+ VectorCopy( origin, hovel.s.origin );
+ VectorCopy( angles, hovel.s.angles );
+ VectorCopy( normal, hovel.s.origin2 );
+
+ return AHovel_Blocked( &hovel, player, qfalse );
+}
+
+/*
+================
+AHovel_Use
+
+Called when an alien uses a hovel
+================
+*/
+void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ vec3_t hovelOrigin, hovelAngles, inverseNormal;
+
+ if( self->spawned && findOvermind( self ) )
+ {
+ if( self->active )
+ {
+ //this hovel is in use
+ G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_OCCUPIED );
+ }
+ else if( ( ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ) ||
+ ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) &&
+ activator->health > 0 && self->health > 0 )
+ {
+ if( AHovel_Blocked( self, activator, qfalse ) )
+ {
+ //you can get in, but you can't get out
+ G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_BLOCKED );
+ return;
+ }
+
+ self->active = qtrue;
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+
+ //prevent lerping
+ activator->client->ps.eFlags ^= EF_TELEPORT_BIT;
+ activator->client->ps.eFlags |= EF_NODRAW;
+
+ activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING;
+ activator->client->hovel = self;
+ self->builder = activator;
+
+ VectorCopy( self->s.pos.trBase, hovelOrigin );
+ VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin );
+
+ VectorCopy( self->s.origin2, inverseNormal );
+ VectorInverse( inverseNormal );
+ vectoangles( inverseNormal, hovelAngles );
+
+ VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin );
+
+ G_SetOrigin( activator, hovelOrigin );
+ VectorCopy( hovelOrigin, activator->client->ps.origin );
+ SetClientViewAngle( activator, hovelAngles );
+ }
+ }
+}
+
+
+/*
+================
+AHovel_Think
+
+Think for alien hovel
+================
+*/
+void AHovel_Think( gentity_t *self )
+{
+ if( self->spawned )
+ {
+ if( self->active )
+ G_setIdleBuildableAnim( self, BANIM_IDLE2 );
+ else
+ G_setIdleBuildableAnim( self, BANIM_IDLE1 );
+ }
+
+ creepSlow( self );
+
+ self->nextthink = level.time + 200;
+}
+
+/*
+================
+AHovel_Die
+
+Die for alien hovel
+================
+*/
+void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ vec3_t dir;
+
+ VectorCopy( self->s.origin2, dir );
+
+ //do a bit of radius damage
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+
+ //pretty events and item cleanup
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+ self->timestamp = level.time;
+ self->think = ASpawn_Melt;
+ self->nextthink = level.time + 500; //wait .5 seconds before damaging others
+
+ //if the hovel is occupied free the occupant
+ if( self->active )
+ {
+ gentity_t *builder = self->builder;
+ vec3_t newOrigin;
+ vec3_t newAngles;
+
+ VectorCopy( self->s.angles, newAngles );
+ newAngles[ ROLL ] = 0;
+
+ VectorCopy( self->s.origin, newOrigin );
+ VectorMA( newOrigin, 1.0f, self->s.origin2, newOrigin );
+
+ //prevent lerping
+ builder->client->ps.eFlags ^= EF_TELEPORT_BIT;
+ builder->client->ps.eFlags &= ~EF_NODRAW;
+
+ G_SetOrigin( builder, newOrigin );
+ VectorCopy( newOrigin, builder->client->ps.origin );
+ SetClientViewAngle( builder, newAngles );
+
+ //client leaves hovel
+ builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
+ }
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+ABooster_Touch
+
+Called when an alien touches a booster
+================
+*/
+void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ gclient_t *client = other->client;
+
+ if( !self->spawned )
+ return;
+
+ if( !findOvermind( self ) )
+ return;
+
+ if( !client )
+ return;
+
+ if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ return;
+
+ //only allow boostage once every 30 seconds
+ if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time )
+ return;
+
+ if( !( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) )
+ {
+ client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
+ client->lastBoostedTime = level.time;
+ }
+}
+
+
+
+
+//==================================================================================
+
+#define TRAPPER_ACCURACY 10 // lower is better
+
+/*
+================
+ATrapper_FireOnEnemy
+
+Used by ATrapper_Think to fire at enemy
+================
+*/
+void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range )
+{
+ gentity_t *enemy = self->enemy;
+ vec3_t dirToTarget;
+ vec3_t halfAcceleration, thirdJerk;
+ float distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex );
+ int lowMsec = 0;
+ int highMsec = (int)( (
+ ( ( distanceToTarget * LOCKBLOB_SPEED ) +
+ ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) /
+ ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f );
+
+ VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration );
+ VectorScale( enemy->jerk, 1.0f / 3.0f, thirdJerk );
+
+ // highMsec and lowMsec can only move toward
+ // one another, so the loop must terminate
+ while( highMsec - lowMsec > TRAPPER_ACCURACY )
+ {
+ int partitionMsec = ( highMsec + lowMsec ) / 2;
+ float time = (float)partitionMsec / 1000.0f;
+ float projectileDistance = LOCKBLOB_SPEED * time;
+
+ VectorMA( enemy->s.pos.trBase, time, enemy->s.pos.trDelta, dirToTarget );
+ VectorMA( dirToTarget, time * time, halfAcceleration, dirToTarget );
+ VectorMA( dirToTarget, time * time * time, thirdJerk, dirToTarget );
+ VectorSubtract( dirToTarget, self->s.pos.trBase, dirToTarget );
+ distanceToTarget = VectorLength( dirToTarget );
+
+ if( projectileDistance < distanceToTarget )
+ lowMsec = partitionMsec;
+ else if( projectileDistance > distanceToTarget )
+ highMsec = partitionMsec;
+ else if( projectileDistance == distanceToTarget )
+ break; // unlikely to happen
+ }
+
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ self->count = level.time + firespeed;
+}
+
+/*
+================
+ATrapper_CheckTarget
+
+Used by ATrapper_Think to check enemies for validity
+================
+*/
+qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range )
+{
+ vec3_t distance;
+ trace_t trace;
+
+ if( !target ) // Do we have a target?
+ return qfalse;
+ if( !target->inuse ) // Does the target still exist?
+ return qfalse;
+ if( target == self ) // is the target us?
+ return qfalse;
+ if( !target->client ) // is the target a bot or player?
+ return qfalse;
+ if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us?
+ return qfalse;
+ if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive?
+ return qfalse;
+ if( target->health <= 0 ) // is the target still alive?
+ return qfalse;
+ if( target->client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) // locked?
+ return qfalse;
+
+ VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, distance );
+ if( VectorLength( distance ) > range ) // is the target within range?
+ return qfalse;
+
+ //only allow a narrow field of "vision"
+ VectorNormalize( distance ); //is now direction of target
+ if( DotProduct( distance, self->s.origin2 ) < LOCKBLOB_DOT )
+ return qfalse;
+
+ trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
+ if ( trace.contents & CONTENTS_SOLID ) // can we see the target?
+ return qfalse;
+
+ return qtrue;
+}
+
+/*
+================
+ATrapper_FindEnemy
+
+Used by ATrapper_Think to locate enemy gentities
+================
+*/
+void ATrapper_FindEnemy( gentity_t *ent, int range )
+{
+ gentity_t *target;
+
+ //iterate through entities
+ for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ )
+ {
+ //if target is not valid keep searching
+ if( !ATrapper_CheckTarget( ent, target, range ) )
+ continue;
+
+ //we found a target
+ ent->enemy = target;
+ return;
+ }
+
+ //couldn't find a target
+ ent->enemy = NULL;
+}
+
+/*
+================
+ATrapper_Think
+
+think function for Alien Defense
+================
+*/
+void ATrapper_Think( gentity_t *self )
+{
+ int range = BG_FindRangeForBuildable( self->s.modelindex );
+ int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
+
+ creepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //if there is no creep nearby die
+ if( !findCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->spawned && findOvermind( self ) )
+ {
+ //if the current target is not valid find a new one
+ if( !ATrapper_CheckTarget( self, self->enemy, range ) )
+ ATrapper_FindEnemy( self, range );
+
+ //if a new target cannot be found don't do anything
+ if( !self->enemy )
+ return;
+
+ //if we are pointing at our target and we can fire shoot it
+ if( self->count < level.time )
+ ATrapper_FireOnEnemy( self, firespeed, range );
+ }
+}
+
+
+
+//==================================================================================
+
+
+
+/*
+================
+HRepeater_Think
+
+Think for human power repeater
+================
+*/
+void HRepeater_Think( gentity_t *self )
+{
+ int i;
+ qboolean reactor = qfalse;
+ gentity_t *ent;
+
+ if( self->spawned )
+ {
+ //iterate through entities
+ for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->s.modelindex == BA_H_REACTOR && ent->spawned )
+ reactor = qtrue;
+ }
+ }
+
+ if( G_NumberOfDependants( self ) == 0 )
+ {
+ //if no dependants for x seconds then disappear
+ if( self->count < 0 )
+ self->count = level.time;
+ else if( self->count > 0 && ( ( level.time - self->count ) > REPEATER_INACTIVE_TIME ) )
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ }
+ else
+ self->count = -1;
+
+ self->powered = reactor;
+
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+}
+
+/*
+================
+HRepeater_Use
+
+Use for human power repeater
+================
+*/
+void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->health <= 0 )
+ return;
+
+ if( !self->spawned )
+ return;
+
+ if( other )
+ G_GiveClientMaxAmmo( other, qtrue );
+}
+
+
+#define DCC_ATTACK_PERIOD 10000
+
+/*
+================
+HReactor_Think
+
+Think function for Human Reactor
+================
+*/
+void HReactor_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE };
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *enemy, *tent;
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ if( self->spawned && ( self->health > 0 ) )
+ {
+ //do some damage
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ self->timestamp = level.time;
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE,
+ REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS );
+
+ tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL );
+
+ VectorCopy( self->s.pos.trBase, tent->s.origin2 );
+
+ tent->s.generic1 = self->s.number; //src
+ tent->s.clientNum = enemy->s.number; //dest
+ }
+ }
+
+ //reactor under attack
+ if( self->health < self->lastHealth &&
+ level.time > level.humanBaseAttackTimer && G_isDCC( ) )
+ {
+ level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_DCC_ATTACK, 0 );
+ }
+
+ self->lastHealth = self->health;
+ }
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+//==================================================================================
+
+
+
+/*
+================
+HArmoury_Activate
+
+Called when a human activates an Armoury
+================
+*/
+void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator )
+{
+ if( self->spawned )
+ {
+ //only humans can activate this
+ if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ //if this is powered then call the armoury menu
+ if( self->powered )
+ G_TriggerMenu( activator->client->ps.clientNum, MN_H_ARMOURY );
+ else
+ G_TriggerMenu( activator->client->ps.clientNum, MN_H_NOTPOWERED );
+ }
+}
+
+/*
+================
+HArmoury_Think
+
+Think for armoury
+================
+*/
+void HArmoury_Think( gentity_t *self )
+{
+ //make sure we have power
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+
+ self->powered = findPower( self );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+
+/*
+================
+HDCC_Think
+
+Think for dcc
+================
+*/
+void HDCC_Think( gentity_t *self )
+{
+ //make sure we have power
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+
+ self->powered = findPower( self );
+}
+
+
+
+
+//==================================================================================
+
+/*
+================
+HMedistat_Think
+
+think function for Human Medistation
+================
+*/
+void HMedistat_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *player;
+ qboolean occupied = qfalse;
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //make sure we have power
+ if( !( self->powered = findPower( self ) ) )
+ {
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned )
+ {
+ VectorAdd( self->s.origin, self->r.maxs, maxs );
+ VectorAdd( self->s.origin, self->r.mins, mins );
+
+ mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ];
+ maxs[ 2 ] += 60; //player height
+
+ //if active use the healing idle
+ if( self->active )
+ G_setIdleBuildableAnim( self, BANIM_IDLE2 );
+
+ //check if a previous occupier is still here
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ player = &g_entities[ entityList[ i ] ];
+
+ if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ player->client->ps.pm_type != PM_DEAD &&
+ self->enemy == player )
+ occupied = qtrue;
+ }
+ }
+
+ if( !occupied )
+ {
+ self->enemy = NULL;
+
+ //look for something to heal
+ for( i = 0; i < num; i++ )
+ {
+ player = &g_entities[ entityList[ i ] ];
+
+ if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ player->client->ps.pm_type != PM_DEAD )
+ {
+ self->enemy = player;
+
+ //start the heal anim
+ if( !self->active )
+ {
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ self->active = qtrue;
+ }
+ }
+ else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) )
+ BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats );
+ }
+ }
+ }
+
+ //nothing left to heal so go back to idling
+ if( !self->enemy && self->active )
+ {
+ G_setBuildableAnim( self, BANIM_CONSTRUCT2, qtrue );
+ G_setIdleBuildableAnim( self, BANIM_IDLE1 );
+
+ self->active = qfalse;
+ }
+ else if( self->enemy ) //heal!
+ {
+ if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED )
+ self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+
+ if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
+ self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
+
+ self->enemy->health++;
+
+ //if they're completely healed, give them a medkit
+ if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) )
+ BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );
+ }
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HMGTurret_TrackEnemy
+
+Used by HMGTurret_Think to track enemy location
+================
+*/
+qboolean HMGTurret_TrackEnemy( gentity_t *self )
+{
+ vec3_t dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ float temp, rotAngle;
+ float accuracyTolerance, angularSpeed;
+
+ if( self->lev1Grabbed )
+ {
+ //can't turn fast if grabbed
+ accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE;
+ angularSpeed = MGTURRET_GRAB_ANGULARSPEED;
+ }
+ else if( self->dcced )
+ {
+ accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE;
+ angularSpeed = MGTURRET_DCC_ANGULARSPEED;
+ }
+ else
+ {
+ accuracyTolerance = MGTURRET_ACCURACYTOLERANCE;
+ angularSpeed = MGTURRET_ANGULARSPEED;
+ }
+
+ VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+
+ VectorNormalize( dirToTarget );
+
+ CrossProduct( self->s.origin2, refNormal, xNormal );
+ VectorNormalize( xNormal );
+ rotAngle = RAD2DEG( acos( DotProduct( self->s.origin2, refNormal ) ) );
+ RotatePointAroundVector( dttAdjusted, xNormal, dirToTarget, rotAngle );
+
+ vectoangles( dttAdjusted, angleToTarget );
+
+ angularDiff[ PITCH ] = AngleSubtract( self->s.angles2[ PITCH ], angleToTarget[ PITCH ] );
+ angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] );
+
+ //if not pointing at our target then move accordingly
+ if( angularDiff[ PITCH ] < (-accuracyTolerance) )
+ self->s.angles2[ PITCH ] += angularSpeed;
+ else if( angularDiff[ PITCH ] > accuracyTolerance )
+ self->s.angles2[ PITCH ] -= angularSpeed;
+ else
+ self->s.angles2[ PITCH ] = angleToTarget[ PITCH ];
+
+ //disallow vertical movement past a certain limit
+ temp = fabs( self->s.angles2[ PITCH ] );
+ if( temp > 180 )
+ temp -= 360;
+
+ if( temp < -MGTURRET_VERTICALCAP )
+ self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP;
+
+ //if not pointing at our target then move accordingly
+ if( angularDiff[ YAW ] < (-accuracyTolerance) )
+ self->s.angles2[ YAW ] += angularSpeed;
+ else if( angularDiff[ YAW ] > accuracyTolerance )
+ self->s.angles2[ YAW ] -= angularSpeed;
+ else
+ self->s.angles2[ YAW ] = angleToTarget[ YAW ];
+
+ AngleVectors( self->s.angles2, dttAdjusted, NULL, NULL );
+ RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //if pointing at our target return true
+ if( abs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance &&
+ abs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance )
+ return qtrue;
+
+ return qfalse;
+}
+
+
+/*
+================
+HMGTurret_CheckTarget
+
+Used by HMGTurret_Think to check enemies for validity
+================
+*/
+qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted )
+{
+ trace_t trace;
+ gentity_t *traceEnt;
+
+ if( !target )
+ return qfalse;
+
+ if( !target->client )
+ return qfalse;
+
+ if( target->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ return qfalse;
+
+ if( target->health <= 0 )
+ return qfalse;
+
+ if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE )
+ return qfalse;
+
+ //some turret has already selected this target
+ if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted )
+ return qfalse;
+
+ trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
+
+ traceEnt = &g_entities[ trace.entityNum ];
+
+ if( !traceEnt->client )
+ return qfalse;
+
+ if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+/*
+================
+HMGTurret_FindEnemy
+
+Used by HMGTurret_Think to locate enemy gentities
+================
+*/
+void HMGTurret_FindEnemy( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *target;
+
+ VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE );
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //find aliens
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ target = &g_entities[ entityList[ i ] ];
+
+ if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //if target is not valid keep searching
+ if( !HMGTurret_CheckTarget( self, target, qfalse ) )
+ continue;
+
+ //we found a target
+ self->enemy = target;
+ return;
+ }
+ }
+
+ if( self->dcced )
+ {
+ //check again, this time ignoring painted targets
+ for( i = 0; i < num; i++ )
+ {
+ target = &g_entities[ entityList[ i ] ];
+
+ if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //if target is not valid keep searching
+ if( !HMGTurret_CheckTarget( self, target, qtrue ) )
+ continue;
+
+ //we found a target
+ self->enemy = target;
+ return;
+ }
+ }
+ }
+
+ //couldn't find a target
+ self->enemy = NULL;
+}
+
+
+/*
+================
+HMGTurret_Think
+
+Think function for MG turret
+================
+*/
+void HMGTurret_Think( gentity_t *self )
+{
+ int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //used for client side muzzle flashes
+ self->s.eFlags &= ~EF_FIRING;
+
+ //if not powered don't do anything and check again for power next think
+ if( !( self->powered = findPower( self ) ) )
+ {
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned )
+ {
+ //find a dcc for self
+ self->dcced = findDCC( self );
+
+ //if the current target is not valid find a new one
+ if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) )
+ {
+ if( self->enemy )
+ self->enemy->targeted = NULL;
+
+ HMGTurret_FindEnemy( self );
+ }
+
+ //if a new target cannot be found don't do anything
+ if( !self->enemy )
+ return;
+
+ self->enemy->targeted = self;
+
+ //if we are pointing at our target and we can fire shoot it
+ if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) )
+ {
+ //fire at target
+ FireWeapon( self );
+
+ self->s.eFlags |= EF_FIRING;
+ G_AddEvent( self, EV_FIRE_WEAPON, 0 );
+ G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+
+ self->count = level.time + firespeed;
+ }
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HTeslaGen_Think
+
+Think function for Tesla Generator
+================
+*/
+void HTeslaGen_Think( gentity_t *self )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ vec3_t dir;
+ int i, num;
+ gentity_t *enemy;
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //if not powered don't do anything and check again for power next think
+ if( !( self->powered = findPower( self ) ) || !( self->dcced = findDCC( self ) ) )
+ {
+ self->s.eFlags &= ~EF_FIRING;
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned && self->count < level.time )
+ {
+ //used to mark client side effects
+ self->s.eFlags &= ~EF_FIRING;
+
+ VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE );
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //find aliens
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ enemy = &g_entities[ entityList[ i ] ];
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ enemy->health > 0 )
+ {
+ VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir );
+ VectorNormalize( dir );
+ vectoangles( dir, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ }
+ }
+
+ if( self->s.eFlags & EF_FIRING )
+ {
+ G_AddEvent( self, EV_FIRE_WEAPON, 0 );
+
+ //doesn't really need an anim
+ //G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+
+ self->count = level.time + TESLAGEN_REPEAT;
+ }
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HSpawn_Disappear
+
+Called when a human spawn is destroyed before it is spawned
+think function
+================
+*/
+void HSpawn_Disappear( gentity_t *self )
+{
+ vec3_t dir;
+
+ // we don't have a valid direction, so just point straight up
+ dir[ 0 ] = dir[ 1 ] = 0;
+ dir[ 2 ] = 1;
+
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ self->timestamp = level.time;
+
+ self->think = freeBuildable;
+ self->nextthink = level.time + 100;
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+
+/*
+================
+HSpawn_blast
+
+Called when a human spawn explodes
+think function
+================
+*/
+void HSpawn_Blast( gentity_t *self )
+{
+ vec3_t dir;
+
+ // we don't have a valid direction, so just point straight up
+ dir[ 0 ] = dir[ 1 ] = 0;
+ dir[ 2 ] = 1;
+
+ self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
+ G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+ self->timestamp = level.time;
+
+ //do some radius damage
+ G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath );
+
+ self->think = freeBuildable;
+ self->nextthink = level.time + 100;
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+}
+
+
+/*
+================
+HSpawn_die
+
+Called when a human spawn dies
+================
+*/
+void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ //pretty events and cleanup
+ G_setBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_setIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->powered = qfalse; //free up power
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( self->spawned )
+ {
+ self->think = HSpawn_Blast;
+ self->nextthink = level.time + HUMAN_DETONATION_DELAY;
+ }
+ else
+ {
+ self->think = HSpawn_Disappear;
+ self->nextthink = level.time; //blast immediately
+ }
+
+ if( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( self->s.modelindex == BA_H_REACTOR )
+ G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue );
+ else if( self->s.modelindex == BA_H_SPAWN )
+ G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue );
+ }
+}
+
+/*
+================
+HSpawn_Think
+
+Think for human spawn
+================
+*/
+void HSpawn_Think( gentity_t *self )
+{
+ gentity_t *ent;
+
+ //make sure we have power
+ self->powered = findPower( self );
+
+ if( self->spawned )
+ {
+ //only suicide if at rest
+ if( self->s.groundEntityNum )
+ {
+ if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
+ self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL )
+ {
+ if( ent->s.eType == ET_BUILDABLE || ent->s.number == ENTITYNUM_WORLD ||
+ ent->s.eType == ET_MOVER )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
+ }
+
+ //spawn under attack
+ if( self->health < self->lastHealth &&
+ level.time > level.humanBaseAttackTimer && G_isDCC( ) )
+ {
+ level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_DCC_ATTACK, 0 );
+ }
+
+ self->lastHealth = self->health;
+ }
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+
+
+
+//==================================================================================
+
+
+/*
+============
+G_BuildableTouchTriggers
+
+Find all trigger entities that a buildable touches.
+============
+*/
+void G_BuildableTouchTriggers( gentity_t *ent )
+{
+ int i, num;
+ int touch[ MAX_GENTITIES ];
+ gentity_t *hit;
+ trace_t trace;
+ vec3_t mins, maxs;
+ vec3_t bmins, bmaxs;
+ static vec3_t range = { 10, 10, 10 };
+
+ // dead buildables don't activate triggers!
+ if( ent->health <= 0 )
+ return;
+
+ BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs );
+
+ VectorAdd( ent->s.origin, bmins, mins );
+ VectorAdd( ent->s.origin, bmaxs, maxs );
+
+ VectorSubtract( mins, range, mins );
+ VectorAdd( maxs, range, maxs );
+
+ num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
+
+ VectorAdd( ent->s.origin, bmins, mins );
+ VectorAdd( ent->s.origin, bmaxs, maxs );
+
+ for( i = 0; i < num; i++ )
+ {
+ hit = &g_entities[ touch[ i ] ];
+
+ if( !hit->touch )
+ continue;
+
+ if( !( hit->r.contents & CONTENTS_TRIGGER ) )
+ continue;
+
+ //ignore buildables not yet spawned
+ if( !ent->spawned )
+ continue;
+
+ if( !trap_EntityContact( mins, maxs, hit ) )
+ continue;
+
+ memset( &trace, 0, sizeof( trace ) );
+
+ if( hit->touch )
+ hit->touch( hit, ent, &trace );
+ }
+}
+
+
+/*
+===============
+G_BuildableThink
+
+General think function for buildables
+===============
+*/
+void G_BuildableThink( gentity_t *ent, int msec )
+{
+ int bHealth = BG_FindHealthForBuildable( ent->s.modelindex );
+ int bRegen = BG_FindRegenRateForBuildable( ent->s.modelindex );
+ int bTime = BG_FindBuildTimeForBuildable( ent->s.modelindex );
+
+ //pack health, power and dcc
+
+ //toggle spawned flag for buildables
+ if( !ent->spawned )
+ {
+ if( ent->buildTime + bTime < level.time )
+ ent->spawned = qtrue;
+ }
+
+ ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_SCALE );
+
+ if( ent->s.generic1 < 0 )
+ ent->s.generic1 = 0;
+
+ if( ent->powered )
+ ent->s.generic1 |= B_POWERED_TOGGLEBIT;
+
+ if( ent->dcced )
+ ent->s.generic1 |= B_DCCED_TOGGLEBIT;
+
+ if( ent->spawned )
+ ent->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+
+ ent->time1000 += msec;
+
+ if( ent->time1000 >= 1000 )
+ {
+ ent->time1000 -= 1000;
+
+ if( !ent->spawned )
+ ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) );
+ else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth &&
+ bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
+ ent->health += bRegen;
+
+ if( ent->health > bHealth )
+ ent->health = bHealth;
+ }
+
+ if( ent->lev1Grabbed && ent->lev1GrabTime + LEVEL1_GRAB_TIME < level.time )
+ ent->lev1Grabbed = qfalse;
+
+ if( ent->clientSpawnTime > 0 )
+ ent->clientSpawnTime -= msec;
+
+ if( ent->clientSpawnTime < 0 )
+ ent->clientSpawnTime = 0;
+
+ //check if this buildable is touching any triggers
+ G_BuildableTouchTriggers( ent );
+
+ //fall back on normal physics routines
+ G_Physics( ent, msec );
+}
+
+
+/*
+===============
+G_BuildableRange
+
+Check whether a point is within some range of a type of buildable
+===============
+*/
+qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )
+{
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *ent;
+
+ VectorSet( range, r, r, r );
+ VectorAdd( origin, range, maxs );
+ VectorSubtract( origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ ent = &g_entities[ entityList[ i ] ];
+
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->biteam == BIT_HUMANS && !ent->powered )
+ continue;
+
+ if( ent->s.modelindex == buildable && ent->spawned )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+
+/*
+================
+G_itemFits
+
+Checks to see if an item fits in a specific area
+================
+*/
+itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin )
+{
+ vec3_t angles;
+ vec3_t entity_origin, normal;
+ vec3_t mins, maxs;
+ trace_t tr1, tr2, tr3;
+ int i;
+ itemBuildError_t reason = IBE_NONE;
+ gentity_t *tempent;
+ float minNormal;
+ qboolean invert;
+ int contents;
+ playerState_t *ps = &ent->client->ps;
+
+ BG_FindBBoxForBuildable( buildable, mins, maxs );
+
+ BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 );
+
+ trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, ent->s.number, MASK_PLAYERSOLID );
+ trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID );
+
+ VectorCopy( entity_origin, origin );
+
+ //this item does not fit here
+ if( tr2.fraction < 1.0 || tr3.fraction < 1.0 )
+ return IBE_NOROOM; //NO other reason is allowed to override this
+
+ VectorCopy( tr1.plane.normal, normal );
+ minNormal = BG_FindMinNormalForBuildable( buildable );
+ invert = BG_FindInvertNormalForBuildable( buildable );
+
+ //can we build at this angle?
+ if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) )
+ return IBE_NORMAL;
+
+ if( tr1.entityNum != ENTITYNUM_WORLD )
+ return IBE_NORMAL;
+
+ //check there is enough room to spawn from (presuming this is a spawn)
+ if( G_CheckSpawnPoint( -1, origin, normal, buildable, NULL ) != NULL )
+ return IBE_NORMAL;
+
+ contents = trap_PointContents( entity_origin, -1 );
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //alien criteria
+
+ if( buildable == BA_A_HOVEL )
+ {
+ vec3_t builderMins, builderMaxs;
+
+ //this assumes the adv builder is the biggest thing that'll use the hovel
+ BG_FindBBoxForClass( PCL_ALIEN_BUILDER0_UPG, builderMins, builderMaxs, NULL, NULL, NULL );
+
+ if( APropHovel_Blocked( angles, origin, normal, ent ) )
+ reason = IBE_HOVELEXIT;
+ }
+
+ //check there is creep near by for building on
+ if( BG_FindCreepTestForBuildable( buildable ) )
+ {
+ if( !isCreep( entity_origin ) )
+ reason = IBE_NOCREEP;
+ }
+
+ //check permission to build here
+ if( tr1.surfaceFlags & SURF_NOALIENBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
+ contents & CONTENTS_NOALIENBUILD || contents & CONTENTS_NOBUILD )
+ reason = IBE_PERMISSION;
+
+ //look for a hivemind
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+ if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned )
+ break;
+ }
+
+ //if none found...
+ if( i >= level.num_entities && buildable != BA_A_OVERMIND )
+ reason = IBE_NOOVERMIND;
+
+ //can we only have one of these?
+ if( BG_FindUniqueTestForBuildable( buildable ) )
+ {
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == buildable )
+ {
+ switch( buildable )
+ {
+ case BA_A_OVERMIND:
+ reason = IBE_OVERMIND;
+ break;
+
+ case BA_A_HOVEL:
+ reason = IBE_HOVEL;
+ break;
+
+ default:
+ Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable );
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+
+ if( level.alienBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 )
+ reason = IBE_NOASSERT;
+ }
+ else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //human criteria
+ if( !G_isPower( entity_origin ) )
+ {
+ //tell player to build a repeater to provide power
+ if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER )
+ reason = IBE_REPEATER;
+ }
+
+ //this buildable requires a DCC
+ if( BG_FindDCCTestForBuildable( buildable ) && !G_isDCC( ) )
+ reason = IBE_NODCC;
+
+ //check that there is a parent reactor when building a repeater
+ if( buildable == BA_H_REPEATER )
+ {
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == BA_H_REACTOR )
+ break;
+ }
+
+ if( i >= level.num_entities )
+ {
+ //no reactor present
+
+ //check for other nearby repeaters
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == BA_H_REPEATER &&
+ Distance( tempent->s.origin, entity_origin ) < REPEATER_BASESIZE )
+ {
+ reason = IBE_RPTWARN2;
+ break;
+ }
+ }
+
+ if( reason == IBE_NONE )
+ reason = IBE_RPTWARN;
+ }
+ else if( G_isPower( entity_origin ) )
+ reason = IBE_RPTWARN2;
+ }
+
+ //check permission to build here
+ if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
+ contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD )
+ reason = IBE_PERMISSION;
+
+ //can we only build one of these?
+ if( BG_FindUniqueTestForBuildable( buildable ) )
+ {
+ for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( tempent->s.modelindex == BA_H_REACTOR )
+ {
+ reason = IBE_REACTOR;
+ break;
+ }
+ }
+ }
+
+ if( level.humanBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 )
+ reason = IBE_NOPOWER;
+ }
+
+ return reason;
+}
+
+
+/*
+================
+G_buildItem
+
+Spawns a buildable
+================
+*/
+gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles )
+{
+ gentity_t *built;
+ vec3_t normal;
+
+ //spawn the buildable
+ built = G_Spawn();
+
+ built->s.eType = ET_BUILDABLE;
+
+ built->classname = BG_FindEntityNameForBuildable( buildable );
+
+ built->s.modelindex = buildable; //so we can tell what this is on the client side
+ built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable );
+
+ BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs );
+ built->health = 1;
+
+ built->splashDamage = BG_FindSplashDamageForBuildable( buildable );
+ built->splashRadius = BG_FindSplashRadiusForBuildable( buildable );
+ built->splashMethodOfDeath = BG_FindMODForBuildable( buildable );
+
+ built->nextthink = BG_FindNextThinkForBuildable( buildable );
+
+ built->takedamage = qtrue;
+ built->spawned = qfalse;
+ built->buildTime = built->s.time = level.time;
+
+ //things that vary for each buildable that aren't in the dbase
+ switch( buildable )
+ {
+ case BA_A_SPAWN:
+ built->die = ASpawn_Die;
+ built->think = ASpawn_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_BARRICADE:
+ built->die = ABarricade_Die;
+ built->think = ABarricade_Think;
+ built->pain = ABarricade_Pain;
+ break;
+
+ case BA_A_BOOSTER:
+ built->die = ABarricade_Die;
+ built->think = ABarricade_Think;
+ built->pain = ABarricade_Pain;
+ built->touch = ABooster_Touch;
+ break;
+
+ case BA_A_ACIDTUBE:
+ built->die = ABarricade_Die;
+ built->think = AAcidTube_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_HIVE:
+ built->die = ABarricade_Die;
+ built->think = AHive_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_TRAPPER:
+ built->die = ABarricade_Die;
+ built->think = ATrapper_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_OVERMIND:
+ built->die = ASpawn_Die;
+ built->think = AOvermind_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_A_HOVEL:
+ built->die = AHovel_Die;
+ built->use = AHovel_Use;
+ built->think = AHovel_Think;
+ built->pain = ASpawn_Pain;
+ break;
+
+ case BA_H_SPAWN:
+ built->die = HSpawn_Die;
+ built->think = HSpawn_Think;
+ break;
+
+ case BA_H_MGTURRET:
+ built->die = HSpawn_Die;
+ built->think = HMGTurret_Think;
+ break;
+
+ case BA_H_TESLAGEN:
+ built->die = HSpawn_Die;
+ built->think = HTeslaGen_Think;
+ break;
+
+ case BA_H_ARMOURY:
+ built->think = HArmoury_Think;
+ built->die = HSpawn_Die;
+ built->use = HArmoury_Activate;
+ break;
+
+ case BA_H_DCC:
+ built->think = HDCC_Think;
+ built->die = HSpawn_Die;
+ break;
+
+ case BA_H_MEDISTAT:
+ built->think = HMedistat_Think;
+ built->die = HSpawn_Die;
+ break;
+
+ case BA_H_REACTOR:
+ built->think = HReactor_Think;
+ built->die = HSpawn_Die;
+ built->use = HRepeater_Use;
+ built->powered = built->active = qtrue;
+ break;
+
+ case BA_H_REPEATER:
+ built->think = HRepeater_Think;
+ built->die = HSpawn_Die;
+ built->use = HRepeater_Use;
+ built->count = -1;
+ break;
+
+ default:
+ //erk
+ break;
+ }
+
+ built->s.number = built - g_entities;
+ built->r.contents = CONTENTS_BODY;
+ built->clipmask = MASK_PLAYERSOLID;
+ built->enemy = NULL;
+ built->s.weapon = BG_FindProjTypeForBuildable( buildable );
+
+ if( builder->client )
+ built->builtBy = builder->client->ps.clientNum;
+ else
+ built->builtBy = -1;
+
+ G_SetOrigin( built, origin );
+ VectorCopy( angles, built->s.angles );
+ built->s.angles[ PITCH ] = 0.0f;
+ built->s.angles2[ YAW ] = angles[ YAW ];
+ built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable );
+ built->s.pos.trTime = level.time;
+ built->physicsBounce = BG_FindBounceForBuildable( buildable );
+ built->s.groundEntityNum = -1;
+
+ if( builder->client && builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( builder->client->ps.grapplePoint, normal );
+
+ //gently nudge the buildable onto the surface :)
+ VectorScale( normal, -50.0f, built->s.pos.trDelta );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ built->s.generic1 = (int)( ( (float)built->health /
+ (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_SCALE );
+
+ if( built->s.generic1 < 0 )
+ built->s.generic1 = 0;
+
+ if( ( built->powered = findPower( built ) ) )
+ built->s.generic1 |= B_POWERED_TOGGLEBIT;
+
+ if( ( built->dcced = findDCC( built ) ) )
+ built->s.generic1 |= B_DCCED_TOGGLEBIT;
+
+ built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT;
+
+ VectorCopy( normal, built->s.origin2 );
+
+ G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 );
+
+ G_setIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) );
+
+ if( built->builtBy >= 0 )
+ G_setBuildableAnim( built, BANIM_CONSTRUCT1, qtrue );
+
+ trap_LinkEntity( built );
+
+ return built;
+}
+
+/*
+=================
+G_ValidateBuild
+=================
+*/
+qboolean G_ValidateBuild( gentity_t *ent, buildable_t buildable )
+{
+ float dist;
+ vec3_t origin;
+
+ dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+
+ switch( G_itemFits( ent, buildable, dist, origin ) )
+ {
+ case IBE_NONE:
+ G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_NOASSERT:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT );
+ return qfalse;
+
+ case IBE_NOOVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND );
+ return qfalse;
+
+ case IBE_NOCREEP:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
+ return qfalse;
+
+ case IBE_OVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND );
+ return qfalse;
+
+ case IBE_HOVEL:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL );
+ return qfalse;
+
+ case IBE_HOVELEXIT:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT );
+ return qfalse;
+
+ case IBE_NORMAL:
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL );
+ else
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL );
+ return qfalse;
+
+ case IBE_PERMISSION:
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL );
+ else
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL );
+ return qfalse;
+
+ case IBE_REACTOR:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
+ return qfalse;
+
+ case IBE_REPEATER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
+ return qfalse;
+
+ case IBE_NOROOM:
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOM );
+ else
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOROOM );
+ return qfalse;
+
+ case IBE_NOPOWER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
+ return qfalse;
+
+ case IBE_NODCC:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
+ return qfalse;
+
+ case IBE_SPWNWARN:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN );
+ G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_TNODEWARN:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN );
+ G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_RPTWARN:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN );
+ G_buildItem( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_RPTWARN2:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 );
+ return qfalse;
+
+ default:
+ break;
+ }
+
+ return qfalse;
+}
+
+/*
+================
+FinishSpawningBuildable
+
+Traces down to find where an item should rest, instead of letting them
+free fall from their spawn points
+================
+*/
+void FinishSpawningBuildable( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t dest;
+ gentity_t *built;
+ buildable_t buildable = ent->s.modelindex;
+
+ built = G_buildItem( ent, buildable, ent->s.pos.trBase, ent->s.angles );
+ G_FreeEntity( ent );
+
+ built->takedamage = qtrue;
+ built->spawned = qtrue; //map entities are already spawned
+ built->health = BG_FindHealthForBuildable( buildable );
+ built->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+
+ // drop to floor
+ if( buildable != BA_NONE && BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY )
+ VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] + 4096 );
+ else
+ VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] - 4096 );
+
+ trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask );
+
+ if( tr.startsolid )
+ {
+ G_Printf( S_COLOR_YELLOW "FinishSpawningBuildable: %s startsolid at %s\n", built->classname, vtos( built->s.origin ) );
+ G_FreeEntity( built );
+ return;
+ }
+
+ //point items in the correct direction
+ VectorCopy( tr.plane.normal, built->s.origin2 );
+
+ // allow to ride movers
+ built->s.groundEntityNum = tr.entityNum;
+
+ G_SetOrigin( built, tr.endpos );
+
+ trap_LinkEntity( built );
+}
+
+/*
+============
+G_SpawnBuildable
+
+Sets the clipping size and plants the object on the floor.
+
+Items can't be immediately dropped to floor, because they might
+be on an entity that hasn't spawned yet.
+============
+*/
+void G_SpawnBuildable( gentity_t *ent, buildable_t buildable )
+{
+ ent->s.modelindex = buildable;
+
+ // some movers spawn on the second frame, so delay item
+ // spawns until the third frame so they can ride trains
+ ent->nextthink = level.time + FRAMETIME * 2;
+ ent->think = FinishSpawningBuildable;
+}