diff options
author | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 |
---|---|---|
committer | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 |
commit | 6a777afc079c2a8d3af3ecd2145fe8dd50567a39 (patch) | |
tree | 520f4489cebf8564ef6cb27064ceea45cbc005b3 /src/game/g_buildable.c |
Diffstat (limited to 'src/game/g_buildable.c')
-rw-r--r-- | src/game/g_buildable.c | 4748 |
1 files changed, 4748 insertions, 0 deletions
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c new file mode 100644 index 0000000..8643109 --- /dev/null +++ b/src/game/g_buildable.c @@ -0,0 +1,4748 @@ +/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous 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. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+// from g_combat.c
+extern char *modNames[ ];
+
+extern vmCvar_t g_rotaxJumppadPower;//ROTAXfun
+
+/*
+================
+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;
+
+ // don't toggle the togglebit more than once per frame
+ if( ent->animTime != level.time )
+ {
+ localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT );
+ ent->animTime = level.time;
+ }
+ else
+ localAnim |= ent->s.legsAnim & 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_PLAYERSOLID );
+
+ 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_PLAYERSOLID );
+
+ 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
+
+/*
+================
+G_FindPower
+
+attempt to find power for self, return qtrue if successful
+================
+*/
+static qboolean G_FindPower( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ gentity_t *closestPower = NULL;
+ int distance = 0;
+ int minDistance = 10000;
+ vec3_t temp_v;
+
+ 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 &&
+ ( ( ent->s.modelindex == BA_H_REACTOR &&
+ distance <= REACTOR_BASESIZE ) ||
+ ( ent->s.modelindex == BA_H_REPEATER &&
+ distance <= REPEATER_BASESIZE ) ) ) {
+
+ closestPower = ent;
+ minDistance = distance;
+ }
+ }
+ }
+
+
+ //if there were no power items nearby give up
+ if( closestPower ) {
+ self->parentNode = closestPower;
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+================
+G_IsPowered
+
+Simple wrapper to G_FindPower to check if a location has power
+================
+*/
+qboolean G_IsPowered( vec3_t origin )
+{
+ gentity_t dummy;
+
+ dummy.parentNode = NULL;
+ dummy.biteam = BIT_HUMANS;
+ dummy.s.modelindex = BA_NONE;
+ VectorCopy( origin, dummy.s.origin );
+
+ return G_FindPower( &dummy );
+}
+
+/*
+================
+G_FindDCC
+
+attempt to find a controlling DCC for self, return qtrue if successful
+================
+*/
+static qboolean G_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 was no nearby DCC give up
+ if( !foundDCC )
+ return qfalse;
+
+ self->dccNode = closestDCC;
+
+ return qtrue;
+}
+
+/*
+================
+G_IsDCCBuilt
+
+simple wrapper to G_FindDCC to check for a dcc
+================
+*/
+qboolean G_IsDCCBuilt( void )
+{
+ gentity_t dummy;
+
+ memset( &dummy, 0, sizeof( gentity_t ) );
+
+ dummy.dccNode = NULL;
+ dummy.biteam = BIT_HUMANS;
+
+ return G_FindDCC( &dummy );
+}
+
+/*
+================
+G_FindOvermind
+
+Attempt to find an overmind for self
+================
+*/
+static qboolean G_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_IsOvermindBuilt
+
+Simple wrapper to G_FindOvermind to check if a location has an overmind
+================
+*/
+qboolean G_IsOvermindBuilt( void )
+{
+ gentity_t dummy;
+
+ memset( &dummy, 0, sizeof( gentity_t ) );
+
+ dummy.overmindNode = NULL;
+ dummy.biteam = BIT_ALIENS;
+
+ return G_FindOvermind( &dummy );
+}
+
+/*
+================
+G_FindCreep
+
+attempt to find creep for self, return qtrue if successful
+================
+*/
+static qboolean G_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;
+}
+
+
+/*
+================
+G_IsCreepHere
+
+simple wrapper to G_FindCreep to check if a location has creep
+================
+*/
+static qboolean G_IsCreepHere( vec3_t origin )
+{
+ gentity_t dummy;
+
+ memset( &dummy, 0, sizeof( gentity_t ) );
+
+ dummy.parentNode = NULL;
+ dummy.s.modelindex = BA_NONE;
+ VectorCopy( origin, dummy.s.origin );
+
+ return G_FindCreep( &dummy );
+}
+
+/*
+================
+G_CreepSlow
+
+Set any nearby humans' SS_CREEPSLOWED flag
+================
+*/
+static void G_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->flags & FL_NOTARGET )
+ continue;
+
+ 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 )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ 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 )
+ {
+ if( 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 );
+
+ //ROTAX
+ if (g_suddenDeath.integer == 1 && self->s.modelindex == BA_A_SPAWN)
+ {
+ G_TeamCommand( PTE_HUMANS,
+ va( "tchat -1 \"^1->EGG DOWN^7 by %s^7\"",
+ attacker->client->pers.netname ) );//rotaxfun bubliny pridan parametr -1
+ }
+
+ //ROTAXfun
+ AddScore( attacker, 20 );
+ }
+ else
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+
+ //ROTAXfun
+ AddScore( attacker, -5 );
+ }
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+================
+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 the thing blocking the spawn is a buildable, kill it.
+ // If it's part of the map, kill self.
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue );
+ }
+ else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+ else if( g_antiSpawnBlock.integer && ent->client &&
+ ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ //spawnblock protection
+ if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 )
+ {
+ //five seconds of countermeasures and we're still blocked
+ //time for something more drastic
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT );
+ self->spawnBlockTime += 2000;
+ //inappropriate MOD but prints an apt obituary
+ }
+ else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 )
+ //five seconds of blocked by client and...
+ {
+ //random direction
+ vec3_t velocity;
+ velocity[0] = crandom() * g_antiSpawnBlock.integer;
+ velocity[1] = crandom() * g_antiSpawnBlock.integer;
+ velocity[2] = g_antiSpawnBlock.integer;
+
+ VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity );
+ trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" );
+ }
+ else if( !self->spawnBlockTime )
+ self->spawnBlockTime = level.time;
+ }
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
+ else
+ self->spawnBlockTime = 0;
+ }
+ }
+
+ G_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 );
+
+ //rotax
+ if (attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && self->s.modelindex == BA_A_OVERMIND)
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"Overmind ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", attacker->client->pers.netname ));
+ }
+}
+
+
+
+
+
+//==================================================================================
+
+
+
+
+
+#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->flags & FL_NOTARGET )
+ continue;
+
+ 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 );
+ }
+ }
+
+ // just in case an egg finishes building after we tell overmind to stfu
+ if( level.numAlienSpawns > 0 )
+ level.overmindMuted = qfalse;
+
+ //low on spawns
+ if( !level.overmindMuted && level.numAlienSpawns <= 0 &&
+ level.time > self->overmindSpawnsTimer )
+ {
+ qboolean haveBuilder = qfalse;
+ gentity_t *builder;
+
+ self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD;
+ G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 );
+
+ for( i = 0; i < level.numConnectedClients; i++ )
+ {
+ builder = &g_entities[ level.sortedClients[ i ] ];
+ if( builder->health > 0 &&
+ ( builder->client->pers.classSelection == PCL_ALIEN_BUILDER0 ||
+ builder->client->pers.classSelection == PCL_ALIEN_BUILDER0_UPG ) )
+ {
+ haveBuilder = qtrue;
+ break;
+ }
+ }
+ // aliens now know they have no eggs, but they're screwed, so stfu
+ if( !haveBuilder || G_TimeTilSuddenDeath( ) <= 0 )
+ level.overmindMuted = qtrue;
+ }
+
+ //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;
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+}
+
+//rotax
+void Reactor_ShowTeamDamage( gentity_t *self, gentity_t *attacker, int damage)
+{
+ if (attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS)
+ {
+ G_TeamCommand( PTE_HUMANS,
+ va( "print \"Reactor ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", attacker->client->pers.netname ));
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+
+/*
+================
+ABarricade_Pain
+
+pain function for Alien Spawn
+================
+*/
+void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( rand( ) % 2 )
+ 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 )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ 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
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+
+ //ROTAXfun
+ AddScore( attacker, -5 );
+ }
+ else//ROTAXfun
+ {
+ if (self->s.modelindex == BA_A_ACIDTUBE)
+ AddScore( attacker, 5 );
+ else if (self->s.modelindex == BA_A_HIVE)
+ AddScore( attacker, 5 );
+ else if (self->s.modelindex == BA_A_TRAPPER)
+ AddScore( attacker, 3 );
+ else if (self->s.modelindex == BA_A_OVERMIND)
+ AddScore( attacker, 60 );
+ else if (self->s.modelindex == BA_A_BOOSTER)
+ AddScore( attacker, 5 );
+ else
+ AddScore( attacker, 1 );
+ }
+
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+================
+ABarricade_Think
+
+Think function for Alien Barricade
+================
+*/
+void ABarricade_Think( gentity_t *self )
+{
+
+ self->powered = G_IsOvermindBuilt( );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ G_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 );
+ }
+
+ G_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;
+
+ self->powered = G_IsOvermindBuilt( );
+
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->spawned && G_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->flags & FL_NOTARGET )
+ continue;
+
+ 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;
+ }
+ }
+ }
+
+ G_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->powered = G_IsOvermindBuilt( );
+
+ 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( !G_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 && G_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->flags & FL_NOTARGET )
+ continue;
+
+ 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;
+ }
+ }
+ }
+
+ G_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 ) +
+ VectorMaxComponent( hovelMaxs ) + 1.0f;
+
+ VectorMA( hovel->s.origin, displacement, forward, origin );
+
+ VectorCopy( hovel->s.origin, start );
+ VectorCopy( origin, end );
+
+ // see if there's something between the hovel and its exit
+ // (eg built right up against a wall)
+ trap_Trace( &tr, start, NULL, NULL, end, player->s.number, MASK_PLAYERSOLID );
+ if( tr.fraction < 1.0f )
+ return qtrue;
+
+ 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 );
+
+ 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 );
+ // nudge
+ VectorMA( normal, 200.0f, forward, player->client->ps.velocity );
+ G_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 && G_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;
+ G_UnlaggedClear( activator );
+
+ activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING;
+ activator->client->hovel = self;
+ self->builder = activator;
+
+ // Cancel pending suicides
+ activator->suicideTime = 0;
+
+ 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 );
+ G_SetClientViewAngle( activator, hovelAngles );
+ }
+ }
+}
+
+
+/*
+================
+AHovel_Think
+
+Think for alien hovel
+================
+*/
+void AHovel_Think( gentity_t *self )
+{
+ self->powered = G_IsOvermindBuilt( );
+ if( self->spawned )
+ {
+ if( self->active )
+ G_SetIdleBuildableAnim( self, BANIM_IDLE2 );
+ else
+ G_SetIdleBuildableAnim( self, BANIM_IDLE1 );
+ }
+
+ G_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;
+
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ 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
+ self->die = nullDieFunction;
+
+ //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_UnlaggedClear( builder );
+
+ G_SetOrigin( builder, newOrigin );
+ VectorCopy( newOrigin, builder->client->ps.origin );
+ G_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
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+
+ //ROTAXfun
+ AddScore( attacker, -5 );
+ }
+ else
+ AddScore( attacker, 1 );
+
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+ROTAX
+================
+ANone_Think
+
+Think for alien passive structures
+================
+*/
+void ANone_Think( gentity_t *self )
+{
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + 200;
+}
+
+
+/*
+ROTAX
+================
+ABush_Die
+
+Die for alien bush
+================
+*/
+void ABush_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ vec3_t dir;
+ vec3_t range = { 200.0f, 200.0f, 200.0f };
+ int entityList[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ int i, total_entities;
+ gentity_t *target;
+
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ VectorCopy( self->s.origin2, dir );
+
+
+
+
+ if ( self->spawned )
+ {
+ // Grab all entities around us
+ VectorAdd(self->s.origin, range, maxs);
+ VectorSubtract(self->s.origin, range, mins);
+
+ total_entities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES);
+
+ // Loop entities looking for an enemy body
+ for(i=0; i<total_entities; i++) {
+ target = &g_entities[entityList[i]];
+ if(target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS) {
+ if (G_Visible( self, target ))
+ {
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, target->client->ps.stats ) )
+ continue;
+
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, target->client->ps.stats ) )
+ continue;
+
+ if( !( target->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) )
+ {
+ target->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
+ target->client->lastPoisonCloudedTime = level.time;
+ target->client->lastPoisonCloudedClient = self;
+ trap_SendServerCommand( target->client->ps.clientNum, "poisoncloud" );
+ }
+ }
+ }
+ }
+ }
+
+
+
+
+ //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
+ self->die = nullDieFunction;
+
+ self->r.contents = 0; //stop collisions...
+ trap_LinkEntity( self ); //...requires a relink
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+
+ //ROTAXfun
+ AddScore( attacker, -5 );
+ }
+ else
+ {
+ //ROTAXfun
+ AddScore( attacker, 3 );
+ }
+
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+
+/*ROTAX
+================
+ATree_Die
+
+Called when an alien tree dies
+================
+*/
+void ATree_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->think = ASpawn_Melt;
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( self->spawned )
+ {
+ self->nextthink = level.time + 15000;
+ self->r.maxs[ 2 ] = 0;
+ //self->r.maxs[ 0 ] = 0;
+ //self->r.maxs[ 1 ] = 0;
+ trap_LinkEntity( self );
+ }
+ else
+ self->nextthink = level.time; //blast immediately
+
+ if( attacker && attacker->client )
+ {
+ if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+
+ //ROTAXfun
+ AddScore( attacker, -5 );
+ }
+ else
+ AddScore( attacker, 1 );
+
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+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( other->flags & FL_NOTARGET )
+ return; // notarget cancels even beneficial effects?
+
+ if( !self->spawned || self->health <= 0 )
+ return;
+
+ if( !G_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;
+ }
+}
+
+//ROTAXfun
+/*
+================
+AJumppad_Touch
+
+Called when an alien touches a jumppad
+================
+*/
+void AJumppad_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ gclient_t *client = other->client;
+ vec3_t vel;
+
+ if( other->flags & FL_NOTARGET )
+ return; // notarget cancels even beneficial effects?
+
+ if( !self->spawned || self->health <= 0 )
+ return;
+
+ if( !G_FindOvermind( self ) )
+ return;
+
+ if( !client )
+ return;
+
+ //Works for humans too :)
+ //if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ // return;
+
+ //Melo by se dit jen pokud na tom stojim, ne kdyz se jen dotykam (stojim na tom, nebo saham hlavou)
+// if (client->ps.groundEntityNum != self->s.number && self->s.groundEntityNum != client->ps.clientNum)
+ if (client->ps.groundEntityNum != self->s.number)
+ return;
+
+ vel[0] = 0;
+ vel[1] = 0;
+
+// if (client->ps.groundEntityNum == self->s.number)
+ vel[2] = g_rotaxJumppadPower.integer; //Melo by to strilet smerem kam je natocenej model, ne jen nahoru (ale bacha na nenataceci bboxy, strilelo by to dolu, ale kdyz bych na tom stal zeshora tak by to semnou praskalo do jumppadu)
+
+/* ze by to neumelo zapornou rychlost?
+ if (self->s.groundEntityNum == client->ps.clientNum)
+ vel[2] = vel[2] - g_rotaxJumppadPower.integer;*/
+
+ VectorAdd( client->ps.velocity, vel, client->ps.velocity );
+
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue );
+}
+
+
+//==================================================================================
+
+#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->flags & FL_NOTARGET ) // is the target cheating?
+ 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 );
+
+ self->powered = G_IsOvermindBuilt( );
+
+ G_CreepSlow( self );
+
+ self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+
+ //if there is no creep nearby die
+ if( !G_FindCreep( self ) )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+
+ if( self->spawned && G_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 );
+}
+
+/*
+ROTAX
+================
+HNone_Think
+
+Think function for passive structures
+================
+*/
+void HNone_Think( gentity_t *self )
+{
+ //self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ self->nextthink = level.time + 200;
+}
+
+#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->flags & FL_NOTARGET )
+ continue;
+
+ 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_IsDCCBuilt( ) )
+ {
+ 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 = G_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 = G_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 = G_FindPower( self ) ) )
+ {
+ if( self->active )
+ {
+ G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_IDLE1 );
+ self->active = qfalse;
+ self->enemy = NULL;
+ }
+
+ 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->flags & FL_NOTARGET )
+ continue; // notarget cancels even beneficial effects?
+
+ 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->flags & FL_NOTARGET )
+ 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 = G_FindPower( self ) ) )
+ {
+ self->nextthink = level.time + POWER_REFRESH_TIME;
+ return;
+ }
+
+ if( self->spawned )
+ {
+ //find a dcc for self
+ self->dcced = G_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 = G_FindPower( self ) ) || !( self->dcced = G_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->flags & FL_NOTARGET )
+ continue;
+
+ if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ enemy->health > 0 &&
+ Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE )
+ {
+ 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 )
+{
+ buildHistory_t *new;
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = ( attacker && attacker->client ) ? attacker : NULL;
+ if( new->ent )
+ new->name[ 0 ] = 0;
+ else
+ Q_strncpyz( new->name, "<world>", 8 );
+ new->buildable = self->s.modelindex;
+ VectorCopy( self->s.pos.trBase, new->origin );
+ VectorCopy( self->s.angles, new->angles );
+ VectorCopy( self->s.origin2, new->origin2 );
+ VectorCopy( self->s.angles2, new->angles2 );
+ new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ? BF_TEAMKILLED : BF_DESTROYED;
+ new->next = NULL;
+ G_LogBuild( new );
+
+ //pretty events and cleanup
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->powered = qfalse; //free up power
+ //prevent any firing effects and cancel structure protection
+ self->s.eFlags &= ~( EF_FIRING | EF_DBUILDER );
+
+ 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 )
+ {
+ if( 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 );
+
+ //ROTAX
+ if (g_suddenDeath.integer == 1 && self->s.modelindex == BA_H_SPAWN)
+ {
+ G_TeamCommand( PTE_ALIENS,
+ va( "tchat -1 \"^4->NODE DOWN^7 by %s^7\"",
+ attacker->client->pers.netname ) );//rotaxfun bubliny pridan parametr -1
+ }
+
+ //ROTAXfun
+ if (self->s.modelindex == BA_H_MGTURRET)
+ AddScore( attacker, 5 );
+ else if (self->s.modelindex == BA_H_TESLAGEN)
+ AddScore( attacker, 5 );
+ else if (self->s.modelindex == BA_H_REACTOR)
+ AddScore( attacker, 60 );
+ else if (self->s.modelindex == BA_H_SPAWN)
+ AddScore( attacker, 20 );
+ else if (self->s.modelindex == BA_H_DCC)
+ AddScore( attacker, 3 );
+ else if (self->s.modelindex == BA_H_ARMOURY)
+ AddScore( attacker, 3 );
+ else if (self->s.modelindex == BA_H_MEDISTAT)
+ AddScore( attacker, 5 );
+ else
+ AddScore( attacker, 1 );
+ }
+ else
+ {
+ G_TeamCommand( PTE_HUMANS,
+ va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname ) );
+ G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n",
+ BG_FindHumanNameForBuildable( self->s.modelindex ),
+ attacker->client->pers.netname );
+
+ //ROTAXfun
+ AddScore( attacker, -5 );
+ }
+ G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n",
+ attacker->client->ps.clientNum, self->s.modelindex, mod,
+ attacker->client->pers.netname,
+ BG_FindNameForBuildable( self->s.modelindex ),
+ modNames[ mod ] );
+ }
+}
+
+/*
+================
+HSpawn_Think
+
+Think for human spawn
+================
+*/
+void HSpawn_Think( gentity_t *self )
+{
+ gentity_t *ent;
+
+ // spawns work without power
+ self->powered = qtrue;
+
+ 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 the thing blocking the spawn is a buildable, kill it.
+ // If it's part of the map, kill self.
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue );
+ }
+ else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER )
+ {
+ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ return;
+ }
+ else if( g_antiSpawnBlock.integer && ent->client &&
+ ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //spawnblock protection
+ if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 )
+ {
+ //five seconds of countermeasures and we're still blocked
+ //time for something more drastic
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT );
+ self->spawnBlockTime += 2000;
+ //inappropriate MOD but prints an apt obituary
+ }
+ else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 )
+ //five seconds of blocked by client and...
+ {
+ //random direction
+ vec3_t velocity;
+ velocity[0] = crandom() * g_antiSpawnBlock.integer;
+ velocity[1] = crandom() * g_antiSpawnBlock.integer;
+ velocity[2] = g_antiSpawnBlock.integer;
+
+ VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity );
+ trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" );
+ }
+ else if( !self->spawnBlockTime )
+ self->spawnBlockTime = level.time;
+ }
+
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
+ else
+ self->spawnBlockTime = 0;
+ }
+
+ //spawn under attack
+ if( self->health < self->lastHealth &&
+ level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) )
+ {
+ 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 && ent->health > 0 && !level.pausedTime )
+ {
+ if( ent->buildTime + bTime < level.time )
+ ent->spawned = qtrue;
+ }
+
+ ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK );
+
+ 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;
+
+ if( ent->deconstruct )
+ ent->s.generic1 |= B_MARKED_TOGGLEBIT;
+
+ ent->time1000 += msec;
+
+ if( ent->time1000 >= 1000 )
+ {
+ ent->time1000 -= 1000;
+
+ if( !ent->spawned && ent->health > 0 )
+ 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_CompareBuildablesForRemoval
+
+qsort comparison function for a buildable removal list
+===============
+*/
+static int G_CompareBuildablesForRemoval( const void *a, const void *b )
+{
+ int precedence[ ] =
+ {
+ BA_NONE,
+
+ BA_A_BARRICADE,
+ BA_A_ACIDTUBE,
+ BA_A_TRAPPER,
+ BA_A_HIVE,
+ BA_A_BOOSTER,
+ BA_A_HOVEL,
+ BA_A_SPAWN,
+ BA_A_OVERMIND,
+ BA_A_FLAGA,//ROTAX
+ BA_A_BAMBOO,//ROTAX
+ BA_A_BRICKA,//ROTAX
+ BA_A_BRIDGE,//ROTAX
+ BA_A_TREE,//ROTAX
+ BA_A_ROCK,//ROTAX
+ BA_A_BUSH,//ROTAX
+ BA_A_FLOWER,//ROTAX
+ BA_A_GRASS,//ROTAX
+ BA_A_JUMPPAD,//ROTAX
+
+ BA_H_MGTURRET,
+ BA_H_TESLAGEN,
+ BA_H_DCC,
+ BA_H_MEDISTAT,
+ BA_H_ARMOURY,
+ BA_H_SPAWN,
+ BA_H_REPEATER,
+ BA_H_PLANK,//ROTAX
+ BA_H_CRATE,//ROTAX
+ BA_H_PILLAR,//ROTAX
+ BA_H_BRICKH,//ROTAX
+ BA_H_FLAGH,//ROTAX
+ BA_H_CONTAINER,//ROTAX
+ BA_H_REACTOR
+ };
+
+ gentity_t *buildableA, *buildableB;
+ int i;
+ int aPrecedence = 0, bPrecedence = 0;
+
+ buildableA = *(gentity_t **)a;
+ buildableB = *(gentity_t **)b;
+
+ // If they're the same type then pick the one marked earliest
+ if( buildableA->s.modelindex == buildableB->s.modelindex )
+ return buildableA->deconstructTime - buildableB->deconstructTime;
+
+ for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ )
+ {
+ if( buildableA->s.modelindex == precedence[ i ] )
+ aPrecedence = i;
+
+ if( buildableB->s.modelindex == precedence[ i ] )
+ bPrecedence = i;
+ }
+
+ return aPrecedence - bPrecedence;
+}
+
+/*
+===============
+G_FreeMarkedBuildables
+
+Free up build points for a team by deconstructing marked buildables
+===============
+*/
+void G_FreeMarkedBuildables( void )
+{
+ int i;
+ gentity_t *ent;
+ buildHistory_t *new, *last;
+ last = level.buildHistory;
+
+ if( !g_markDeconstruct.integer )
+ return; // Not enabled, can't deconstruct anything
+
+ for( i = 0; i < level.numBuildablesForRemoval; i++ )
+ {
+ ent = level.markedBuildables[ i ];
+
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ new->ID = -1;
+ new->ent = NULL;
+ Q_strncpyz( new->name, "<markdecon>", 12 );
+ new->buildable = ent->s.modelindex;
+ VectorCopy( ent->s.pos.trBase, new->origin );
+ VectorCopy( ent->s.angles, new->angles );
+ VectorCopy( ent->s.origin2, new->origin2 );
+ VectorCopy( ent->s.angles2, new->angles2 );
+ new->fate = BF_DECONNED;
+ new->next = NULL;
+ new->marked = NULL;
+
+ last = last->marked = new;
+
+ G_FreeEntity( ent );
+ }
+}
+
+/*
+===============
+G_SufficientBPAvailable
+
+Determine if enough build points can be released for the buildable
+and list the buildables that much be destroyed if this is the case
+===============
+*/
+static qboolean G_SufficientBPAvailable( buildableTeam_t team,
+ int buildPoints,
+ buildable_t buildable )
+{
+ int i;
+ int numBuildables = 0;
+ int pointsYielded = 0;
+ gentity_t *ent;
+ qboolean unique = BG_FindUniqueTestForBuildable( buildable );
+ int remainingBP, remainingSpawns;
+
+ if( team == BIT_ALIENS )
+ {
+ remainingBP = level.alienBuildPoints;
+ remainingSpawns = level.numAlienSpawns;
+ }
+ else if( team == BIT_HUMANS )
+ {
+ remainingBP = level.humanBuildPoints;
+ remainingSpawns = level.numHumanSpawns;
+ }
+ else
+ return qfalse;
+
+ // Simple non-marking case
+ if( !g_markDeconstruct.integer )
+ {
+ if( remainingBP - buildPoints < 0 )
+ return qfalse;
+ else
+ return qtrue;
+ }
+
+ // Set buildPoints to the number extra that are required
+ buildPoints -= remainingBP;
+
+ level.numBuildablesForRemoval = 0;
+
+ // Build a list of buildable entities
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( ent->health <= 0 )
+ continue;
+
+ // Don't allow destruction of hovel with granger inside
+ if( ent->s.modelindex == BA_A_HOVEL && ent->active )
+ continue;
+
+ if( ent->biteam != team )
+ continue;
+
+ // Prevent destruction of the last spawn
+ if( remainingSpawns <= 1 )
+ {
+ if( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_H_SPAWN )
+ continue;
+ }
+
+ // If it's a unique buildable, it can only be replaced by the same type
+ if( unique && ent->s.modelindex != buildable )
+ continue;
+
+ if( ent->deconstruct )
+ level.markedBuildables[ numBuildables++ ] = ent;
+ }
+
+ // We still need build points, but have no candidates for removal
+ if( buildPoints > 0 && numBuildables == 0 )
+ return qfalse;
+
+ // Sort the list
+ qsort( level.markedBuildables, numBuildables, sizeof( level.markedBuildables[ 0 ] ),
+ G_CompareBuildablesForRemoval );
+
+ // Do a pass looking for a buildable of the same type that we're
+ // building and mark it (and only it) for destruction if found
+ for( i = 0; i < numBuildables; i++ )
+ {
+ ent = level.markedBuildables[ i ];
+
+ if( ent->s.modelindex == buildable )
+ {
+ // If we're removing what we're building this will always work
+ level.markedBuildables[ 0 ] = ent;
+ level.numBuildablesForRemoval = 1;
+
+ return qtrue;
+ }
+ }
+
+ // Determine if there are enough markees to yield the required BP
+ for( ; pointsYielded < buildPoints && level.numBuildablesForRemoval < numBuildables;
+ level.numBuildablesForRemoval++ )
+ {
+ ent = level.markedBuildables[ level.numBuildablesForRemoval ];
+ pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ }
+
+ // Not enough points yielded
+ if( pointsYielded < buildPoints )
+ {
+ level.numBuildablesForRemoval = 0;
+ return qfalse;
+ }
+ else
+ {
+ return qtrue;
+ }
+}
+
+/*
+================
+G_CanBuild
+
+Checks to see if a buildable can be built
+================
+*/
+itemBuildError_t G_CanBuild( 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;
+ int buildPoints;
+
+ 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 );
+
+ 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 ) ) )
+ reason = IBE_NORMAL;
+
+ //ROTAXfun
+ //if( tr1.entityNum != ENTITYNUM_WORLD )
+ // reason = IBE_NORMAL;
+
+ //check there is enough room to spawn from (presuming this is a spawn)
+ if( G_CheckSpawnPoint( -1, origin, normal, buildable, NULL ) != NULL )
+ reason = IBE_NORMAL;
+
+ contents = trap_PointContents( entity_origin, -1 );
+ buildPoints = BG_FindBuildPointsForBuildable( buildable );
+
+ 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( origin, angles, normal, ent ) )
+ reason = IBE_HOVELEXIT;
+ }
+
+ //check there is creep near by for building on
+ if( BG_FindCreepTestForBuildable( buildable ) )
+ {
+ if( !G_IsCreepHere( 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 an Overmind
+ 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 &&
+ tempent->health > 0 )
+ 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 && !tempent->deconstruct )
+ {
+ switch( buildable )
+ {
+ case BA_A_OVERMIND:
+ //ROTAXfun - vraceno if (g_suddenDeath.integer == 1)
+ //reason = IBE_OVERMIND_AFTER_SD;
+ reason = IBE_OVERMIND;
+ break;
+
+ case BA_A_HOVEL:
+ if (g_suddenDeath.integer == 1)//ROTAXfun
+ reason = IBE_HOVEL_AFTER_SD;
+ //reason = IBE_HOVEL;
+ break;
+
+ default:
+ Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable );
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+
+ if( !G_SufficientBPAvailable( BIT_ALIENS, buildPoints, buildable ) )
+ reason = IBE_NOASSERT;
+ }
+ else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //human criteria
+ if( !G_IsPowered( 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_IsDCCBuilt( ) )
+ 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_IsPowered( 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 && !tempent->deconstruct )
+ {
+ //ROTAXfun - vraceno if (g_suddenDeath.integer == 1)
+ //reason = IBE_RC_AFTER_SD;
+ reason = IBE_REACTOR;
+ //ROTAXfun - vraceno break;
+ }
+ }
+ }
+
+ if( !G_SufficientBPAvailable( BIT_HUMANS, buildPoints, buildable ) )
+ reason = IBE_NOPOWER;
+ }
+
+ //this item does not fit here
+ if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) )
+ return IBE_NOROOM;
+
+ return reason;
+}
+
+/*
+==============
+G_BuildingExists
+==============
+*/
+qboolean G_BuildingExists( int bclass )
+{
+ int i;
+ gentity_t *tempent;
+ //look for an Armoury
+ for (i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ {
+ if( tempent->s.eType != ET_BUILDABLE )
+ continue;
+ if( tempent->s.modelindex == bclass && tempent->health > 0 )
+ {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+
+/*
+================
+G_Build
+
+Spawns a buildable
+================
+*/
+static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles )
+{
+ gentity_t *built;
+ buildHistory_t *new;
+ vec3_t normal;
+
+ // initialise the buildhistory so other functions can use it
+ if( builder && builder->client )
+ {
+ new = G_Alloc( sizeof( buildHistory_t ) );
+ G_LogBuild( new );
+ }
+
+ // Free existing buildables
+ G_FreeMarkedBuildables( );
+
+ //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 );
+
+ // detect the buildable's normal vector
+ if( !builder->client )
+ {
+ // initial base layout created by server
+
+ if( builder->s.origin2[ 0 ]
+ || builder->s.origin2[ 1 ]
+ || builder->s.origin2[ 2 ] )
+ {
+ VectorCopy( builder->s.origin2, normal );
+ }
+ else if( BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+ }
+ else
+ {
+ // in-game building by a player
+
+ if( 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 );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+ }
+
+ // when building the initial layout, spawn the entity slightly off its
+ // target surface so that it can be "dropped" onto it
+ if( !builder->client )
+ VectorMA( origin, 1.0f, normal, origin );
+
+ 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;
+ built->spawnBlockTime = 0;
+
+ // build instantly in cheat mode
+ if( builder->client && g_cheats.integer )
+ {
+ built->health = BG_FindHealthForBuildable( buildable );
+ built->buildTime = built->s.time =
+ level.time - BG_FindBuildTimeForBuildable( buildable );
+ }
+
+ //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;
+
+ //ROTAX
+ case BA_A_BUSH:
+ built->die = ABush_Die;
+ built->think = ANone_Think;
+ built->pain = ASpawn_Pain;
+ break;
+ case BA_A_TREE:
+ built->die = ATree_Die;
+ built->think = ANone_Think;
+ built->pain = ASpawn_Pain;
+ break;
+ case BA_A_JUMPPAD:
+ built->die = AHovel_Die;
+ built->think = ANone_Think;
+ built->pain = ASpawn_Pain;
+ built->touch = AJumppad_Touch;
+ break;
+ case BA_A_FLAGA:
+ case BA_A_BAMBOO:
+ case BA_A_BRICKA:
+ case BA_A_BRIDGE:
+ case BA_A_ROCK:
+ case BA_A_FLOWER:
+ case BA_A_GRASS:
+ built->die = AHovel_Die;
+ built->think = ANone_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->pain = Reactor_ShowTeamDamage;//rotax
+ 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;
+
+ case BA_H_PLANK://ROTAX
+ case BA_H_CRATE://ROTAX
+ case BA_H_PILLAR://ROTAX
+ case BA_H_BRICKH://ROTAX
+ case BA_H_FLAGH://ROTAX
+ case BA_H_CONTAINER://ROTAX
+ built->think = HNone_Think;
+ built->die = HSpawn_Die;
+ 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;
+
+ if( builder->client->pers.designatedBuilder )
+ {
+ built->s.eFlags |= EF_DBUILDER; // designated builder protection
+ }
+ }
+ else
+ built->builtBy = -1;
+
+ G_SetOrigin( built, origin );
+
+ // gently nudge the buildable onto the surface :)
+ VectorScale( normal, -50.0f, built->s.pos.trDelta );
+
+ // set turret angles
+ VectorCopy( builder->s.angles2, built->s.angles2 );
+
+ VectorCopy( angles, built->s.angles );
+ built->s.angles[ PITCH ] = angles[ PITCH ];//ROTAXfun 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;
+
+ built->s.generic1 = (int)( ( (float)built->health /
+ (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK );
+
+ if( built->s.generic1 < 0 )
+ built->s.generic1 = 0;
+
+ if( BG_FindTeamForBuildable( built->s.modelindex ) == PTE_ALIENS )
+ {
+ built->powered = qtrue;
+ built->s.generic1 |= B_POWERED_TOGGLEBIT;
+ }
+ else if( ( built->powered = G_FindPower( built ) ) )
+ built->s.generic1 |= B_POWERED_TOGGLEBIT;
+
+ if( ( built->dcced = G_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 );
+
+
+ if( builder->client )
+ {
+ builder->client->pers.statscounters.structsbuilt++;
+ if( builder->client->pers.teamSelection == PTE_ALIENS )
+ {
+ level.alienStatsCounters.structsbuilt++;
+ }
+ else if( builder->client->pers.teamSelection == PTE_HUMANS )
+ {
+ level.humanStatsCounters.structsbuilt++;
+ }
+ }
+
+ if( builder->client ) {
+ G_TeamCommand( builder->client->pers.teamSelection,
+ va( "print \"%s is ^2being built^7 by %s^7\n\"",
+ BG_FindHumanNameForBuildable( built->s.modelindex ),
+ builder->client->pers.netname ) );
+ G_LogPrintf("Build: %i %i 0: %s^7 is ^2building^7 %s\n",
+ builder->client->ps.clientNum,
+ built->s.modelindex,
+ builder->client->pers.netname,
+ BG_FindNameForBuildable( built->s.modelindex ) );
+
+ //ROTAXfun
+ if (built->s.modelindex != BA_H_BRICKH && built->s.modelindex != BA_A_BRICKA && built->s.modelindex != BA_A_FLOWER)
+ AddScore( builder, 2 );
+ }
+
+ // ok we're all done building, so what we log here should be the final values
+ if( builder && builder->client ) // log ingame building only
+ {
+ new = level.buildHistory;
+ new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID;
+ new->ent = builder;
+ new->name[ 0 ] = 0;
+ new->buildable = buildable;
+ VectorCopy( built->s.pos.trBase, new->origin );
+ VectorCopy( built->s.angles, new->angles );
+ VectorCopy( built->s.origin2, new->origin2 );
+ VectorCopy( built->s.angles2, new->angles2 );
+ new->fate = BF_BUILT;
+ }
+
+ if( builder && builder->client )
+ built->bdnumb = new->ID;
+ else
+ built->bdnumb = -1;
+
+ return built;
+}
+
+/*
+=================
+G_BuildIfValid
+=================
+*/
+qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable )
+{
+ float dist;
+ vec3_t origin;
+
+ dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+
+ switch( G_CanBuild( ent, buildable, dist, origin ) )
+ {
+ case IBE_NONE:
+ //rotax pokud nema jmeno, nemuze stavet
+ if (g_newbieNamePrefix.string[ 0 ] && !Q_stricmpn( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string)))
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Take a nick before trying to build. Type: /name your_nick\n\"" );
+ return qfalse;
+ }
+ else
+ {
+ G_Build( 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;
+
+ /*ROTAXfun
+ case IBE_HOVEL:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL );
+ return qfalse;
+
+ case IBE_OVERMIND_AFTER_SD://ROTAXfun
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You can build only one Overmind in sudden death\n\"" );
+ return qfalse;
+ */
+
+ case IBE_HOVEL_AFTER_SD://ROTAXfun
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You can build only one Hovel in sudden death\n\"" );
+ 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;
+
+ /*ROTAXfun - vraceno
+ case IBE_RC_AFTER_SD://ROTAXfun
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You can build only one Reactor in sudden death\n\"" );
+ 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_Build( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_TNODEWARN:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN );
+ G_Build( ent, buildable, origin, ent->s.apos.trBase );
+ return qtrue;
+
+ case IBE_RPTWARN:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN );
+ G_Build( 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;
+}
+
+/*
+================
+G_FinishSpawningBuildable
+
+Traces down to find where an item should rest, instead of letting them
+free fall from their spawn points
+================
+*/
+static void G_FinishSpawningBuildable( gentity_t *ent )
+{
+ trace_t tr;
+ vec3_t dest;
+ gentity_t *built;
+ buildable_t buildable = ent->s.modelindex;
+
+ built = G_Build( 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 towards normal surface
+ VectorScale( built->s.origin2, -4096.0f, dest );
+ VectorAdd( dest, built->s.origin, dest );
+
+ 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 "G_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 = G_FinishSpawningBuildable;
+}
+
+ /*
+ ============
+ G_CheckDBProtection
+
+ Count how many designated builders are in both teams and
+ if none found in some team, cancel protection for all
+ structures of that team
+ ============
+ */
+
+ void G_CheckDBProtection( void )
+ {
+ int alienDBs = 0, humanDBs = 0, i;
+ gentity_t *ent;
+
+ // count designated builders
+ for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++)
+ {
+ if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) )
+ continue;
+
+ if( ent->client->pers.designatedBuilder)
+ {
+ if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ humanDBs++;
+ }
+ else if( ent->client->pers.teamSelection == PTE_ALIENS )
+ {
+ alienDBs++;
+ }
+ }
+ }
+
+ // both teams have designate builders, we're done
+ if( alienDBs > 0 && humanDBs > 0 )
+ return;
+
+ // cancel protection if needed
+ for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++)
+ {
+ if( ent->s.eType != ET_BUILDABLE)
+ continue;
+
+ if( ( !alienDBs && ent->biteam == BIT_ALIENS ) ||
+ ( !humanDBs && ent->biteam == BIT_HUMANS ) )
+ {
+ ent->s.eFlags &= ~EF_DBUILDER;
+ }
+ }
+ }
+
+/*
+============
+G_LayoutSave
+
+============
+*/
+void G_LayoutSave( char *name )
+{
+ char map[ MAX_QPATH ];
+ char fileName[ MAX_OSPATH ];
+ fileHandle_t f;
+ int len;
+ int i;
+ gentity_t *ent;
+ char *s;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ if( !map[ 0 ] )
+ {
+ G_Printf( "LayoutSave( ): no map is loaded\n" );
+ return;
+ }
+ Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name );
+
+ len = trap_FS_FOpenFile( fileName, &f, FS_WRITE );
+ if( len < 0 )
+ {
+ G_Printf( "layoutsave: could not open %s\n", fileName );
+ return;
+ }
+
+ G_Printf("layoutsave: saving layout to %s\n", fileName );
+
+ for( i = MAX_CLIENTS; i < level.num_entities; i++ )
+ {
+ ent = &level.gentities[ i ];
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ s = va( "%i %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ ent->s.modelindex,
+ ent->s.pos.trBase[ 0 ],
+ ent->s.pos.trBase[ 1 ],
+ ent->s.pos.trBase[ 2 ],
+ ent->s.angles[ 0 ],
+ ent->s.angles[ 1 ],
+ ent->s.angles[ 2 ],
+ ent->s.origin2[ 0 ],
+ ent->s.origin2[ 1 ],
+ ent->s.origin2[ 2 ],
+ ent->s.angles2[ 0 ],
+ ent->s.angles2[ 1 ],
+ ent->s.angles2[ 2 ] );
+ trap_FS_Write( s, strlen( s ), f );
+ }
+ trap_FS_FCloseFile( f );
+}
+
+int G_LayoutList( const char *map, char *list, int len )
+{
+ // up to 128 single character layout names could fit in layouts
+ char fileList[ ( MAX_CVAR_VALUE_STRING / 2 ) * 5 ] = {""};
+ char layouts[ MAX_CVAR_VALUE_STRING ] = {""};
+ int numFiles, i, fileLen = 0, listLen;
+ int count = 0;
+ char *filePtr;
+
+ Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " );
+ numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat",
+ fileList, sizeof( fileList ) );
+ filePtr = fileList;
+ for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ listLen = strlen( layouts );
+ if( fileLen < 5 )
+ continue;
+
+ // list is full, stop trying to add to it
+ if( ( listLen + fileLen ) >= sizeof( layouts ) )
+ break;
+
+ Q_strcat( layouts, sizeof( layouts ), filePtr );
+ listLen = strlen( layouts );
+
+ // strip extension and add space delimiter
+ layouts[ listLen - 4 ] = ' ';
+ layouts[ listLen - 3 ] = '\0';
+ count++;
+ }
+ if( count != numFiles )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: layout list was truncated to %d "
+ "layouts, but %d layout files exist in layouts/%s/.\n",
+ count, numFiles, map );
+ }
+ Q_strncpyz( list, layouts, len );
+ return count + 1;
+}
+
+/*
+============
+G_LayoutSelect
+
+set level.layout based on g_layouts or g_layoutAuto
+============
+*/
+void G_LayoutSelect( void )
+{
+ char fileName[ MAX_OSPATH ];
+ char layouts[ MAX_CVAR_VALUE_STRING ];
+ char layouts2[ MAX_CVAR_VALUE_STRING ];
+ char *l;
+ char map[ MAX_QPATH ];
+ char *s;
+ int cnt = 0;
+ int layoutNum;
+
+ Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) );
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ // one time use cvar
+ trap_Cvar_Set( "g_layouts", "" );
+
+ // pick an included layout at random if no list has been provided
+ if( !layouts[ 0 ] && g_layoutAuto.integer )
+ {
+ G_LayoutList( map, layouts, sizeof( layouts ) );
+ }
+
+ if( !layouts[ 0 ] )
+ return;
+
+ Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );
+ l = &layouts2[ 0 ];
+ layouts[ 0 ] = '\0';
+ s = COM_ParseExt( &l, qfalse );
+ while( *s )
+ {
+ if( !Q_stricmp( s, "*BUILTIN*" ) )
+ {
+ Q_strcat( layouts, sizeof( layouts ), s );
+ Q_strcat( layouts, sizeof( layouts ), " " );
+ cnt++;
+ s = COM_ParseExt( &l, qfalse );
+ continue;
+ }
+
+ Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s );
+ if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 )
+ {
+ Q_strcat( layouts, sizeof( layouts ), s );
+ Q_strcat( layouts, sizeof( layouts ), " " );
+ cnt++;
+ }
+ else
+ G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s );
+ s = COM_ParseExt( &l, qfalse );
+ }
+ if( !cnt )
+ {
+ G_Printf( S_COLOR_RED "ERROR: none of the specified layouts could be "
+ "found, using map default\n" );
+ return;
+ }
+ layoutNum = ( rand( ) % cnt ) + 1;
+ cnt = 0;
+
+ Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );
+ l = &layouts2[ 0 ];
+ s = COM_ParseExt( &l, qfalse );
+ while( *s )
+ {
+ Q_strncpyz( level.layout, s, sizeof( level.layout ) );
+ cnt++;
+ if( cnt >= layoutNum )
+ break;
+ s = COM_ParseExt( &l, qfalse );
+ }
+ G_Printf("using layout \"%s\" from list ( %s)\n", level.layout, layouts );
+}
+
+static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin,
+ vec3_t angles, vec3_t origin2, vec3_t angles2 )
+{
+ gentity_t *builder;
+
+ builder = G_Spawn( );
+ builder->client = 0;
+ VectorCopy( origin, builder->s.pos.trBase );
+ VectorCopy( angles, builder->s.angles );
+ VectorCopy( origin2, builder->s.origin2 );
+ VectorCopy( angles2, builder->s.angles2 );
+ G_SpawnBuildable( builder, buildable );
+}
+
+/*
+============
+G_InstantBuild
+
+This function is extremely similar to the few functions that place a
+buildable on map load. It exists because G_LayoutBuildItem takes a couple
+of frames to finish spawning it, so it's not truly instant
+Do not call this function immediately after the map loads - that's what
+G_LayoutBuildItem is for.
+============
+*/
+gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 )
+{
+ gentity_t *builder, *built;
+ trace_t tr;
+ vec3_t dest;
+
+ builder = G_Spawn( );
+ builder->client = 0;
+ VectorCopy( origin, builder->s.pos.trBase );
+ VectorCopy( angles, builder->s.angles );
+ VectorCopy( origin2, builder->s.origin2 );
+ VectorCopy( angles2, builder->s.angles2 );
+//old method didn't quite work out
+//builder->s.modelindex = buildable;
+//G_FinishSpawningBuildable( builder );
+
+ built = G_Build( builder, buildable, builder->s.pos.trBase, builder->s.angles );
+ G_FreeEntity( builder );
+
+ built->takedamage = qtrue;
+ built->spawned = qtrue; //map entities are already spawned
+ built->health = BG_FindHealthForBuildable( buildable );
+ built->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+
+ // drop towards normal surface
+ VectorScale( built->s.origin2, -4096.0f, dest );
+ VectorAdd( dest, built->s.origin, dest );
+
+ 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 "G_FinishSpawningBuildable: %s startsolid at %s\n",
+ built->classname, vtos( built->s.origin ) );
+ G_FreeEntity( built );
+ return NULL;
+ }
+
+ //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 );
+ return built;
+}
+
+/*
+============
+G_SpawnRevertedBuildable
+
+Given a buildhistory, try to replace the lost buildable
+============
+*/
+void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark )
+{
+ vec3_t mins, maxs;
+ int i, j, blockCount, blockers[ MAX_GENTITIES ];
+ gentity_t *targ, *built, *toRecontent[ MAX_GENTITIES ];
+
+ BG_FindBBoxForBuildable( bh->buildable, mins, maxs );
+ VectorAdd( bh->origin, mins, mins );
+ VectorAdd( bh->origin, maxs, maxs );
+ blockCount = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES );
+ for( i = j = 0; i < blockCount; i++ )
+ {
+ targ = g_entities + blockers[ i ];
+ if( targ->s.eType == ET_BUILDABLE )
+ G_FreeEntity( targ );
+ else if( targ->s.eType == ET_PLAYER )
+ {
+ targ->r.contents = 0; // make it intangible
+ toRecontent[ j++ ] = targ; // and remember it
+ }
+ }
+ level.numBuildablesForRemoval = 0;
+ built = G_InstantBuild( bh->buildable, bh->origin, bh->angles, bh->origin2, bh->angles2 );
+ if( built )
+ {
+ built->r.contents = 0;
+ built->think = G_CommitRevertedBuildable;
+ built->nextthink = level.time;
+ built->deconstruct = mark;
+ }
+ for( i = 0; i < j; i++ )
+ toRecontent[ i ]->r.contents = CONTENTS_BODY;
+}
+
+/*
+============
+G_CommitRevertedBuildable
+
+Check if there's anyone occupying me, and if not, become solid and operate as
+normal. Else, try to get rid of them.
+============
+*/
+void G_CommitRevertedBuildable( gentity_t *ent )
+{
+ gentity_t *targ;
+ int i, n, occupants[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ int victims = 0;
+
+ VectorAdd( ent->s.origin, ent->r.mins, mins );
+ VectorAdd( ent->s.origin, ent->r.maxs, maxs );
+ trap_UnlinkEntity( ent );
+ n = trap_EntitiesInBox( mins, maxs, occupants, MAX_GENTITIES );
+ trap_LinkEntity( ent );
+
+ for( i = 0; i < n; i++ )
+ {
+ vec3_t gtfo;
+ targ = g_entities + occupants[ i ];
+ if( targ->client )
+ {
+ VectorSet( gtfo, crandom() * 150, crandom() * 150, random() * 150 );
+ VectorAdd( targ->client->ps.velocity, gtfo, targ->client->ps.velocity );
+ victims++;
+ }
+ }
+ if( !victims )
+ { // we're in the clear!
+ ent->r.contents = MASK_PLAYERSOLID;
+ trap_LinkEntity( ent ); // relink
+ // oh dear, manual think set
+ switch( ent->s.modelindex )
+ {
+ case BA_A_SPAWN:
+ ent->think = ASpawn_Think;
+ break;
+ case BA_A_BARRICADE:
+ case BA_A_BOOSTER:
+ ent->think = ABarricade_Think;
+ break;
+ case BA_A_ACIDTUBE:
+ ent->think = AAcidTube_Think;
+ break;
+ case BA_A_HIVE:
+ ent->think = AHive_Think;
+ break;
+ case BA_A_TRAPPER:
+ ent->think = ATrapper_Think;
+ break;
+ case BA_A_OVERMIND:
+ ent->think = AOvermind_Think;
+ break;
+ case BA_A_HOVEL:
+ ent->think = AHovel_Think;
+ break;
+
+ //ROTAX
+ case BA_A_FLAGA:
+ case BA_A_BAMBOO:
+ case BA_A_BRICKA:
+ case BA_A_BRIDGE:
+ case BA_A_TREE:
+ case BA_A_ROCK:
+ case BA_A_BUSH:
+ case BA_A_FLOWER:
+ case BA_A_GRASS:
+ case BA_A_JUMPPAD:
+ ent->think = ANone_Think;
+ break;
+
+ case BA_H_SPAWN:
+ ent->think = HSpawn_Think;
+ break;
+ case BA_H_MGTURRET:
+ ent->think = HMGTurret_Think;
+ break;
+ case BA_H_TESLAGEN:
+ ent->think = HTeslaGen_Think;
+ break;
+ case BA_H_ARMOURY:
+ ent->think = HArmoury_Think;
+ break;
+ case BA_H_DCC:
+ ent->think = HDCC_Think;
+ break;
+ case BA_H_MEDISTAT:
+ ent->think = HMedistat_Think;
+ break;
+ case BA_H_REACTOR:
+ ent->think = HReactor_Think;
+ break;
+ case BA_H_REPEATER:
+ ent->think = HRepeater_Think;
+ break;
+ case BA_H_PLANK://ROTAX
+ case BA_H_CRATE://ROTAX
+ case BA_H_PILLAR://ROTAX
+ case BA_H_BRICKH://ROTAX
+ case BA_H_FLAGH://ROTAX
+ case BA_H_CONTAINER://ROTAX
+ ent->think = HNone_Think;
+ break;
+ }
+ ent->nextthink = level.time + BG_FindNextThinkForBuildable( ent->s.modelindex );
+ // oh if only everything was that simple
+ return;
+ }
+#define REVERT_THINK_INTERVAL 50
+ ent->nextthink = level.time + REVERT_THINK_INTERVAL;
+}
+
+/*
+============
+G_RevertCanFit
+
+take a bhist and make sure you're not overwriting anything by placing it
+============
+*/
+qboolean G_RevertCanFit( buildHistory_t *bh )
+{
+ int i, num, blockers[ MAX_GENTITIES ];
+ vec3_t mins, maxs;
+ gentity_t *targ;
+ vec3_t dist;
+
+ BG_FindBBoxForBuildable( bh->buildable, mins, maxs );
+ VectorAdd( bh->origin, mins, mins );
+ VectorAdd( bh->origin, maxs, maxs );
+ num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ targ = g_entities + blockers[ i ];
+ if( targ->s.eType == ET_BUILDABLE )
+ {
+ VectorSubtract( bh->origin, targ->s.pos.trBase, dist );
+ if( targ->s.modelindex == bh->buildable && VectorLength( dist ) < 10 && targ->health <= 0 )
+ continue; // it's the same buildable, hasn't blown up yet
+ else
+ return qfalse; // can't get rid of this one
+ }
+ else
+ continue;
+ }
+ return qtrue;
+}
+
+/*
+============
+G_LayoutLoad
+
+load the layout .dat file indicated by level.layout and spawn buildables
+as if a builder was creating them
+============
+*/
+void G_LayoutLoad( void )
+{
+ fileHandle_t f;
+ int len;
+ char *layout;
+ char map[ MAX_QPATH ];
+ int buildable = BA_NONE;
+ vec3_t origin = { 0.0f, 0.0f, 0.0f };
+ vec3_t angles = { 0.0f, 0.0f, 0.0f };
+ vec3_t origin2 = { 0.0f, 0.0f, 0.0f };
+ vec3_t angles2 = { 0.0f, 0.0f, 0.0f };
+ char line[ MAX_STRING_CHARS ];
+ int i = 0;
+
+ if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) )
+ return;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ),
+ &f, FS_READ );
+ if( len < 0 )
+ {
+ G_Printf( "ERROR: layout %s could not be opened\n", level.layout );
+ return;
+ }
+ layout = G_Alloc( len + 1 );
+ trap_FS_Read( layout, len, f );
+ *( layout + len ) = '\0';
+ trap_FS_FCloseFile( f );
+ while( *layout )
+ {
+ if( i >= sizeof( line ) - 1 )
+ {
+ G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n",
+ va( "layouts/%s/%s.dat", map, level.layout ), line );
+ return;
+ }
+ line[ i++ ] = *layout;
+ line[ i ] = '\0';
+ if( *layout == '\n' )
+ {
+ i = 0;
+ sscanf( line, "%d %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ &buildable,
+ &origin[ 0 ], &origin[ 1 ], &origin[ 2 ],
+ &angles[ 0 ], &angles[ 1 ], &angles[ 2 ],
+ &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ],
+ &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] );
+
+ if( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES )
+ G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 );
+ else
+ G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in "
+ " layout. skipping\n", buildable );
+ }
+ layout++;
+ }
+}
+
+void G_BaseSelfDestruct( pTeam_t team )
+{
+ int i;
+ gentity_t *ent;
+
+ for( i = MAX_CLIENTS; i < level.num_entities; i++ )
+ {
+ ent = &level.gentities[ i ];
+ if( ent->health <= 0 )
+ continue;
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+ if( team == PTE_HUMANS && ent->biteam != BIT_HUMANS )
+ continue;
+ if( team == PTE_ALIENS && ent->biteam != BIT_ALIENS )
+ continue;
+ G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ }
+}
+
+ int G_LogBuild( buildHistory_t *new )
+ {
+ new->next = level.buildHistory;
+ level.buildHistory = new;
+ return G_CountBuildLog();
+ }
+
+ int G_CountBuildLog( void )
+ {
+ buildHistory_t *ptr, *mark;
+ int i = 0, overflow;
+ for( ptr = level.buildHistory; ptr; ptr = ptr->next, i++ );
+ if( i > g_buildLogMaxLength.integer )
+ {
+ for( overflow = i - g_buildLogMaxLength.integer; overflow > 0; overflow-- )
+ {
+ ptr = level.buildHistory;
+ while( ptr->next )
+ {
+ if( ptr->next->next )
+ ptr = ptr->next;
+ else
+ {
+ while( ( mark = ptr->next ) )
+ {
+ ptr->next = ptr->next->marked;
+ G_Free( mark );
+ }
+ }
+ }
+ }
+ return g_buildLogMaxLength.integer;
+ }
+ return i;
+ }
+
+ char *G_FindBuildLogName( int id )
+ {
+ buildHistory_t *ptr;
+
+ for( ptr = level.buildHistory; ptr && ptr->ID != id; ptr = ptr->next );
+ if( ptr )
+ {
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ return ptr->ent->client->pers.netname;
+ }
+ else if( ptr->name[ 0 ] )
+ {
+ return ptr->name;
+ }
+ }
+
+ return "<buildlog entry expired>";
+ }
+
\ No newline at end of file |