summaryrefslogtreecommitdiff
path: root/src/game/g_buildable.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/g_buildable.c')
-rw-r--r--src/game/g_buildable.c4821
1 files changed, 2386 insertions, 2435 deletions
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c
index 461c2d0..05135c4 100644
--- a/src/game/g_buildable.c
+++ b/src/game/g_buildable.c
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,16 +17,13 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
#include "g_local.h"
-// from g_combat.c
-extern char *modNames[ ];
-
/*
================
G_SetBuildableAnim
@@ -35,19 +33,17 @@ Triggers an animation client side
*/
void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force )
{
- int localAnim = anim;
+ int localAnim = anim | ( ent->s.legsAnim & ANIM_TOGGLEBIT );
if( force )
localAnim |= ANIM_FORCEBIT;
- // don't toggle the togglebit more than once per frame
+ // don't flip the togglebit more than once per frame
if( ent->animTime != level.time )
{
- localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT );
ent->animTime = level.time;
+ localAnim ^= ANIM_TOGGLEBIT;
}
- else
- localAnim |= ent->s.legsAnim & ANIM_TOGGLEBIT;
ent->s.legsAnim = localAnim;
}
@@ -71,8 +67,8 @@ 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 )
+gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin,
+ const vec3_t normal, buildable_t spawn, vec3_t spawnOrigin )
{
float displacement;
vec3_t mins, maxs;
@@ -80,83 +76,40 @@ gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal,
vec3_t localOrigin;
trace_t tr;
- BG_FindBBoxForBuildable( spawn, mins, maxs );
+ BG_BuildableBoundingBox( 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;
+ displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3 + 1.0f;
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 );
+ BG_ClassBoundingBox( 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 ];
}
+ else
+ return NULL;
- return NULL;
-}
+ trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT );
-/*
-================
-G_NumberOfDependants
+ if( tr.entityNum != ENTITYNUM_NONE )
+ return &g_entities[ tr.entityNum ];
-Return number of entities that depend on this one
-================
-*/
-static int G_NumberOfDependants( gentity_t *self )
-{
- int i, n = 0;
- gentity_t *ent;
+ trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, ENTITYNUM_NONE, MASK_PLAYERSOLID );
- for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
- {
- if( ent->s.eType != ET_BUILDABLE )
- continue;
+ if( tr.entityNum != ENTITYNUM_NONE )
+ return &g_entities[ tr.entityNum ];
- if( ent->parentNode == self )
- n++;
- }
+ if( spawnOrigin != NULL )
+ VectorCopy( localOrigin, spawnOrigin );
- return n;
+ return NULL;
}
#define POWER_REFRESH_TIME 2000
@@ -168,191 +121,403 @@ G_FindPower
attempt to find power for self, return qtrue if successful
================
*/
-static qboolean G_FindPower( gentity_t *self )
+qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned )
{
- int i;
- gentity_t *ent;
+ int i, j;
+ gentity_t *ent, *ent2;
gentity_t *closestPower = NULL;
int distance = 0;
- int minDistance = 10000;
+ int minDistance = REPEATER_BASESIZE + 1;
vec3_t temp_v;
- if( self->biteam != BIT_HUMANS )
+ if( self->buildableTeam != TEAM_HUMANS )
return qfalse;
- //reactor is always powered
+ // Reactor is always powered
if( self->s.modelindex == BA_H_REACTOR )
- return qtrue;
+ {
+ self->parentNode = self;
- //if this already has power then stop now
- if( self->parentNode && self->parentNode->powered )
return qtrue;
+ }
- //reset parent
- self->parentNode = NULL;
+ // Handle repeaters
+ if( self->s.modelindex == BA_H_REPEATER )
+ {
+ self->parentNode = G_Reactor( );
- //iterate through entities
- for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ return self->parentNode != NULL;
+ }
+
+ // Iterate through entities
+ for( i = MAX_CLIENTS, 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 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 )
+ ( searchUnspawned || ent->spawned ) && ent->powered && ent->health > 0 )
{
- VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, 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 ) ) ) {
+ // Always prefer a reactor if there is one in range
+ if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE )
+ {
+ // Only power as much BP as the reactor can hold
+ if( self->s.modelindex != BA_NONE )
+ {
+ int buildPoints = g_humanBuildPoints.integer;
+
+ // Scan the buildables in the reactor zone
+ for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ )
+ {
+ if( ent2->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent2 == self )
+ continue;
+
+ if( ent2->parentNode == ent )
+ {
+ buildPoints -= BG_Buildable( ent2->s.modelindex )->buildPoints;
+ }
+ }
+
+ buildPoints -= level.humanBuildPointQueue;
+
+ buildPoints -= BG_Buildable( self->s.modelindex )->buildPoints;
+ if( buildPoints >= 0 )
+ {
+ self->parentNode = ent;
+ return qtrue;
+ }
+ else
+ {
+ // a buildable can still be built if it shares BP from two zones
+
+ // TODO: handle combined power zones here
+ }
+ }
+
+ // Dummy buildables don't need to look for zones
+ else
+ {
+ self->parentNode = ent;
+ return qtrue;
+ }
+ }
+ else if( distance < minDistance )
+ {
+ // It's a repeater, so check that enough BP will be available to power
+ // the buildable but only if self is a real buildable
+
+ if( self->s.modelindex != BA_NONE )
+ {
+ int buildPoints = g_humanRepeaterBuildPoints.integer;
+
+ // Scan the buildables in the repeater zone
+ for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ )
+ {
+ if( ent2->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent2 == self )
+ continue;
+
+ if( ent2->parentNode == ent )
+ buildPoints -= BG_Buildable( ent2->s.modelindex )->buildPoints;
+ }
+
+ if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active )
+ buildPoints -= level.buildPointZones[ ent->buildPointZone ].queuedBuildPoints;
+
+ buildPoints -= BG_Buildable( self->s.modelindex )->buildPoints;
+
+ if( buildPoints >= 0 )
+ {
+ closestPower = ent;
+ minDistance = distance;
+ }
+ else
+ {
+ // a buildable can still be built if it shares BP from two zones
+
+ // TODO: handle combined power zones here
+ }
+ }
+ else
+ {
+ // Dummy buildables don't need to look for zones
closestPower = ent;
minDistance = distance;
+ }
}
}
}
- //if there were no power items nearby give up
- if( closestPower ) {
- self->parentNode = closestPower;
- return qtrue;
- }
- else
- return qfalse;
+ self->parentNode = closestPower;
+ return self->parentNode != NULL;
}
/*
================
-G_IsPowered
+G_PowerEntityForPoint
-Simple wrapper to G_FindPower to check if a location has power
+Simple wrapper to G_FindPower to find the entity providing
+power for the specified point
================
*/
-qboolean G_IsPowered( vec3_t origin )
+gentity_t *G_PowerEntityForPoint( const vec3_t origin )
{
gentity_t dummy;
dummy.parentNode = NULL;
- dummy.biteam = BIT_HUMANS;
+ dummy.buildableTeam = TEAM_HUMANS;
dummy.s.modelindex = BA_NONE;
- VectorCopy( origin, dummy.s.origin );
+ VectorCopy( origin, dummy.r.currentOrigin );
- return G_FindPower( &dummy );
+ if( G_FindPower( &dummy, qfalse ) )
+ return dummy.parentNode;
+ else
+ return NULL;
}
/*
================
-G_FindDCC
+G_PowerEntityForEntity
-attempt to find a controlling DCC for self, return qtrue if successful
+Simple wrapper to G_FindPower to find the entity providing
+power for the specified entity
================
*/
-static qboolean G_FindDCC( gentity_t *self )
+gentity_t *G_PowerEntityForEntity( gentity_t *ent )
+{
+ if( G_FindPower( ent, qfalse ) )
+ return ent->parentNode;
+ return NULL;
+}
+
+/*
+================
+G_IsPowered
+
+Check if a location has power, returning the entity type
+that is providing it
+================
+*/
+buildable_t G_IsPowered( vec3_t origin )
+{
+ gentity_t *ent = G_PowerEntityForPoint( origin );
+
+ if( ent )
+ return ent->s.modelindex;
+ else
+ return BA_NONE;
+}
+
+
+/*
+==================
+G_GetBuildPoints
+
+Get the number of build points from a position
+==================
+*/
+int G_GetBuildPoints( const vec3_t pos, team_t team )
+{
+ if( G_TimeTilSuddenDeath( ) <= 0 )
+ {
+ return 0;
+ }
+ else if( team == TEAM_ALIENS )
+ {
+ return level.alienBuildPoints;
+ }
+ else if( team == TEAM_HUMANS )
+ {
+ gentity_t *powerPoint = G_PowerEntityForPoint( pos );
+
+ if( powerPoint && powerPoint->s.modelindex == BA_H_REACTOR )
+ return level.humanBuildPoints;
+
+ if( powerPoint && powerPoint->s.modelindex == BA_H_REPEATER &&
+ powerPoint->usesBuildPointZone && level.buildPointZones[ powerPoint->buildPointZone ].active )
+ {
+ return level.buildPointZones[ powerPoint->buildPointZone ].totalBuildPoints -
+ level.buildPointZones[ powerPoint->buildPointZone ].queuedBuildPoints;
+ }
+
+ // Return the BP of the main zone by default
+ return level.humanBuildPoints;
+ }
+
+ return 0;
+}
+
+/*
+==================
+G_GetMarkedBuildPoints
+
+Get the number of marked build points from a position
+==================
+*/
+int G_GetMarkedBuildPoints( const vec3_t pos, team_t team )
{
- int i;
gentity_t *ent;
- gentity_t *closestDCC = NULL;
- int distance = 0;
- int minDistance = 10000;
- vec3_t temp_v;
- qboolean foundDCC = qfalse;
+ int i;
+ int sum = 0;
- if( self->biteam != BIT_HUMANS )
- return qfalse;
+ if( G_TimeTilSuddenDeath( ) <= 0 )
+ return 0;
- //if this already has dcc then stop now
- if( self->dccNode && self->dccNode->powered )
- return qtrue;
+ if( !g_markDeconstruct.integer )
+ return 0;
- //reset parent
- self->dccNode = NULL;
+ for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
- //iterate through entities
- for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ if( team == TEAM_HUMANS &&
+ ent->s.modelindex != BA_H_REACTOR &&
+ ent->s.modelindex != BA_H_REPEATER &&
+ ent->parentNode != G_PowerEntityForPoint( pos ) )
+ continue;
+
+ if( !ent->inuse )
+ continue;
+
+ if( ent->health <= 0 )
+ continue;
+
+ if( ent->buildableTeam != team )
+ continue;
+
+ if( ent->deconstruct )
+ sum += BG_Buildable( ent->s.modelindex )->buildPoints;
+ }
+
+ return sum;
+}
+
+/*
+==================
+G_InPowerZone
+
+See if a buildable is inside of another power zone.
+Return pointer to provider if so.
+It's different from G_FindPower because FindPower for
+providers will find themselves.
+(This doesn't check if power zones overlap)
+==================
+*/
+gentity_t *G_InPowerZone( gentity_t *self )
+{
+ int i;
+ gentity_t *ent;
+ int distance;
+ vec3_t temp_v;
+
+ for( i = MAX_CLIENTS, 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 )
+ if( ent == self )
+ continue;
+
+ if( !ent->spawned )
+ continue;
+
+ if( ent->health <= 0 )
+ 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 && ent->powered )
{
- VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v );
distance = VectorLength( temp_v );
- if( ( !foundDCC || distance < minDistance ) && ent->powered )
- {
- closestDCC = ent;
- minDistance = distance;
- foundDCC = qtrue;
- }
+
+ if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE )
+ return ent;
+ else if( ent->s.modelindex == BA_H_REPEATER && distance <= REPEATER_BASESIZE )
+ return ent;
}
}
- //if there was no nearby DCC give up
- if( !foundDCC )
- return qfalse;
-
- self->dccNode = closestDCC;
-
- return qtrue;
+ return NULL;
}
/*
================
-G_IsDCCBuilt
+G_FindDCC
-simple wrapper to G_FindDCC to check for a dcc
+attempt to find a controlling DCC for self, return number found
================
*/
-qboolean G_IsDCCBuilt( void )
+int G_FindDCC( gentity_t *self )
{
- gentity_t dummy;
+ int i;
+ gentity_t *ent;
+ int distance = 0;
+ vec3_t temp_v;
+ int foundDCC = 0;
- memset( &dummy, 0, sizeof( gentity_t ) );
+ if( self->buildableTeam != TEAM_HUMANS )
+ return 0;
- dummy.dccNode = NULL;
- dummy.biteam = BIT_HUMANS;
+ //iterate through entities
+ for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
- return G_FindDCC( &dummy );
+ //if entity is a dcc calculate the distance to it
+ if( ent->s.modelindex == BA_H_DCC && ent->spawned )
+ {
+ VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v );
+ distance = VectorLength( temp_v );
+ if( distance < DC_RANGE && ent->powered )
+ {
+ foundDCC++;
+ }
+ }
+ }
+
+ return foundDCC;
}
/*
================
-G_FindOvermind
+G_IsDCCBuilt
-Attempt to find an overmind for self
+See if any powered DCC exists
================
*/
-static qboolean G_FindOvermind( gentity_t *self )
+qboolean G_IsDCCBuilt( void )
{
int i;
gentity_t *ent;
- if( self->biteam != BIT_ALIENS )
- return qfalse;
+ for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ {
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
- //if this already has overmind then stop now
- if( self->overmindNode && self->overmindNode->health > 0 )
- return qtrue;
+ if( ent->s.modelindex != BA_H_DCC )
+ continue;
- //reset parent
- self->overmindNode = NULL;
+ if( !ent->spawned )
+ continue;
- //iterate through entities
- for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
- {
- if( ent->s.eType != ET_BUILDABLE )
+ if( ent->health <= 0 )
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 qtrue;
}
return qfalse;
@@ -360,21 +525,46 @@ static qboolean G_FindOvermind( gentity_t *self )
/*
================
-G_IsOvermindBuilt
+G_Reactor
+G_Overmind
+
+Since there's only one of these and we quite often want to find them, cache the
+results, but check them for validity each time
-Simple wrapper to G_FindOvermind to check if a location has an overmind
+The code here will break if more than one reactor or overmind is allowed, even
+if one of them is dead/unspawned
================
*/
-qboolean G_IsOvermindBuilt( void )
+static gentity_t *G_FindBuildable( buildable_t buildable );
+
+gentity_t *G_Reactor( void )
{
- gentity_t dummy;
+ static gentity_t *rc;
- memset( &dummy, 0, sizeof( gentity_t ) );
+ // If cache becomes invalid renew it
+ if( !rc || rc->s.eType != ET_BUILDABLE || rc->s.modelindex != BA_H_REACTOR )
+ rc = G_FindBuildable( BA_H_REACTOR );
- dummy.overmindNode = NULL;
- dummy.biteam = BIT_ALIENS;
+ // If we found it and it's alive, return it
+ if( rc && rc->spawned && rc->health > 0 )
+ return rc;
- return G_FindOvermind( &dummy );
+ return NULL;
+}
+
+gentity_t *G_Overmind( void )
+{
+ static gentity_t *om;
+
+ // If cache becomes invalid renew it
+ if( !om || om->s.eType != ET_BUILDABLE || om->s.modelindex != BA_A_OVERMIND )
+ om = G_FindBuildable( BA_A_OVERMIND );
+
+ // If we found it and it's alive, return it
+ if( om && om->spawned && om->health > 0 )
+ return om;
+
+ return NULL;
}
/*
@@ -384,7 +574,7 @@ G_FindCreep
attempt to find creep for self, return qtrue if successful
================
*/
-static qboolean G_FindCreep( gentity_t *self )
+qboolean G_FindCreep( gentity_t *self )
{
int i;
gentity_t *ent;
@@ -394,21 +584,23 @@ static qboolean G_FindCreep( gentity_t *self )
vec3_t temp_v;
//don't check for creep if flying through the air
- if( self->s.groundEntityNum == -1 )
+ if( !self->client && self->s.groundEntityNum == ENTITYNUM_NONE )
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 )
+ //if self does not have a parentNode or its parentNode is invalid, then find a new one
+ if( self->client || self->parentNode == NULL || !self->parentNode->inuse ||
+ self->parentNode->health <= 0 )
{
- for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ for( i = MAX_CLIENTS, 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 )
+ if( ( ent->s.modelindex == BA_A_SPAWN ||
+ ent->s.modelindex == BA_A_OVERMIND ) &&
+ ent->spawned && ent->health > 0 )
{
- VectorSubtract( self->s.origin, ent->s.origin, temp_v );
+ VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v );
distance = VectorLength( temp_v );
if( distance < minDistance )
{
@@ -420,13 +612,17 @@ static qboolean G_FindCreep( gentity_t *self )
if( minDistance <= CREEP_BASESIZE )
{
- self->parentNode = closestSpawn;
+ if( !self->client )
+ self->parentNode = closestSpawn;
return qtrue;
}
else
return qfalse;
}
+ if( self->client )
+ return qfalse;
+
//if we haven't returned by now then we must already have a valid parent
return qtrue;
}
@@ -446,7 +642,7 @@ static qboolean G_IsCreepHere( vec3_t origin )
dummy.parentNode = NULL;
dummy.s.modelindex = BA_NONE;
- VectorCopy( origin, dummy.s.origin );
+ VectorCopy( origin, dummy.r.currentOrigin );
return G_FindCreep( &dummy );
}
@@ -466,12 +662,12 @@ static void G_CreepSlow( gentity_t *self )
int i, num;
gentity_t *enemy;
buildable_t buildable = self->s.modelindex;
- float creepSize = (float)BG_FindCreepSizeForBuildable( buildable );
+ float creepSize = (float)BG_Buildable( buildable )->creepSize;
VectorSet( range, creepSize, creepSize, creepSize );
- VectorAdd( self->s.origin, range, maxs );
- VectorSubtract( self->s.origin, range, mins );
+ VectorAdd( self->r.currentOrigin, range, maxs );
+ VectorSubtract( self->r.currentOrigin, range, mins );
//find humans
num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
@@ -479,12 +675,11 @@ static void G_CreepSlow( gentity_t *self )
{
enemy = &g_entities[ entityList[ i ] ];
- if( enemy->flags & FL_NOTARGET )
- continue;
+ 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 ) )
+ if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS &&
+ enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
{
enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED;
enemy->client->lastCreepSlowTime = level.time;
@@ -503,41 +698,27 @@ static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *a
{
}
-static void G_BuildableDeathSound( gentity_t *self )
-{
- gentity_t *snd = G_TempEntity( self->r.currentOrigin, EV_GENERAL_SOUND );
- snd->s.eventParm = G_SoundIndex( BG_FindTeamForBuildable( self->s.modelindex ) == PTE_HUMANS ?
- "sound/buildables/human/destroyed" : "sound/buildables/alien/construct2" );
-}
-
-/*
-================
-freeBuildable
-================
-*/
-static void freeBuildable( gentity_t *self )
-{
- G_FreeEntity( self );
-}
-
-
//==================================================================================
/*
================
-A_CreepRecede
+AGeneric_CreepRecede
-Called when an alien spawn dies
+Called when an alien buildable dies
================
*/
-void A_CreepRecede( gentity_t *self )
+void AGeneric_CreepRecede( gentity_t *self )
{
//if the creep just died begin the recession
if( !( self->s.eFlags & EF_DEAD ) )
{
self->s.eFlags |= EF_DEAD;
+ G_QueueBuildPoints( self );
+
+ G_RewardAttackers( self );
+
G_AddEvent( self, EV_BUILD_DESTROY, 0 );
if( self->spawned )
@@ -546,7 +727,7 @@ void A_CreepRecede( gentity_t *self )
self->s.time = -( level.time -
(int)( (float)CREEP_SCALEDOWN_TIME *
( 1.0f - ( (float)( level.time - self->buildTime ) /
- (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) );
+ (float)BG_Buildable( self->s.modelindex )->buildTime ) ) ) );
}
//creep is still receeding
@@ -556,71 +737,30 @@ void A_CreepRecede( gentity_t *self )
G_FreeEntity( self );
}
-
-
-
-//==================================================================================
-
-
-
-
/*
================
-ASpawn_Melt
+AGeneric_Blast
-Called when an alien spawn dies
+Called when an Alien buildable explodes after dead state
================
*/
-void ASpawn_Melt( gentity_t *self )
+void AGeneric_Blast( 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;
+ 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 );
+ G_SelectiveRadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath,
+ TEAM_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->think = AGeneric_CreepRecede;
+ self->nextthink = level.time + 500;
self->r.contents = 0; //stop collisions...
trap_LinkEntity( self ); //...requires a relink
@@ -628,74 +768,93 @@ void ASpawn_Blast( gentity_t *self )
/*
================
-ASpawn_Die
+AGeneric_Die
-Called when an alien spawn dies
+Called when an Alien buildable is killed and enters a brief dead state prior to
+exploding.
================
*/
-void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+void AGeneric_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_BuildableDeathSound( self );
-
G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
self->die = nullDieFunction;
- self->think = ASpawn_Blast;
+ self->killedBy = attacker - g_entities;
+ self->think = AGeneric_Blast;
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+ self->powered = qfalse;
if( self->spawned )
self->nextthink = level.time + 5000;
else
self->nextthink = level.time; //blast immediately
- self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+ G_RemoveRangeMarkerFrom( self );
+ G_LogDestruction( self, attacker, mod );
+}
- if( attacker && attacker->client )
+/*
+================
+AGeneric_CreepCheck
+
+Tests for creep and kills the buildable if there is none
+================
+*/
+void AGeneric_CreepCheck( gentity_t *self )
+{
+ gentity_t *spawn;
+
+ spawn = self->parentNode;
+ if( !G_FindCreep( self ) )
{
- 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 );
- }
+ if( spawn )
+ G_Damage( self, NULL, g_entities + spawn->killedBy, NULL, NULL,
+ self->health, 0, MOD_NOCREEP );
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 );
- }
- 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 ] );
+ G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP );
+ return;
}
+ G_CreepSlow( self );
+}
+
+/*
+================
+AGeneric_Think
+
+A generic think function for Alien buildables
+================
+*/
+void AGeneric_Think( gentity_t *self )
+{
+ self->powered = G_Overmind( ) != NULL;
+ self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;
+ AGeneric_CreepCheck( self );
}
/*
================
+AGeneric_Pain
+
+A generic pain function for Alien buildables
+================
+*/
+void AGeneric_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( self->health <= 0 )
+ return;
+
+ // Alien buildables only have the first pain animation defined
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
+}
+
+
+
+
+//==================================================================================
+
+/*
+================
ASpawn_Think
think function for Alien Spawn
@@ -708,16 +867,21 @@ void ASpawn_Think( gentity_t *self )
if( self->spawned )
{
//only suicide if at rest
- if( self->s.groundEntityNum )
+ if( self->s.groundEntityNum != ENTITYNUM_NONE )
{
- if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin,
+ if( ( ent = G_CheckSpawnPoint( self->s.number, self->r.currentOrigin,
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 );
+ // don't queue the bp from this
+ if( ent->builtBy && ent->builtBy->slot >= 0 )
+ G_Damage( ent, NULL, g_entities + ent->builtBy->slot, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ else
+ 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 )
@@ -725,61 +889,16 @@ void ASpawn_Think( gentity_t *self )
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
+
+ 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 );
-
- if ( self->s.modelindex == BA_A_OVERMIND && self->health > 0 &&
- attacker && attacker->client && attacker->client->pers.teamSelection == PTE_ALIENS )
- G_TeamCommand( PTE_ALIENS, va( "print \"Overmind ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"",
- attacker->client->pers.netname ));
+ self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;
}
@@ -805,39 +924,26 @@ 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 );
+ int i;
if( self->spawned && ( self->health > 0 ) )
{
//do some damage
- num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- for( i = 0; i < num; i++ )
+ if( G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
+ self->splashRadius, self, MOD_OVERMIND, TEAM_ALIENS ) )
{
- 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 );
- }
+ self->timestamp = level.time;
+ 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;
+ // shut up during intermission
+ if( level.intermissiontime )
+ level.overmindMuted = qtrue;
+
//low on spawns
if( !level.overmindMuted && level.numAlienSpawns <= 0 &&
level.time > self->overmindSpawnsTimer )
@@ -885,14 +991,13 @@ void AOvermind_Think( gentity_t *self )
G_CreepSlow( self );
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;
}
-
//==================================================================================
@@ -903,12 +1008,15 @@ void AOvermind_Think( gentity_t *self )
================
ABarricade_Pain
-pain function for Alien Spawn
+Barricade pain animation depends on shrunk state
================
*/
void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
{
- if( rand( ) % 2 )
+ if( self->health <= 0 )
+ return;
+
+ if( !self->shrunkTime )
G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
else
G_SetBuildableAnim( self, BANIM_PAIN2, qfalse );
@@ -916,223 +1024,134 @@ void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
/*
================
-ABarricade_Blast
+ABarricade_Shrink
-Called when an alien spawn dies
+Set shrink state for a barricade. When unshrinking, checks to make sure there
+is enough room.
================
*/
-void ABarricade_Blast( gentity_t *self )
+void ABarricade_Shrink( gentity_t *self, qboolean shrink )
{
- vec3_t dir;
-
- VectorCopy( self->s.origin2, dir );
+ if ( !self->spawned || self->health <= 0 )
+ shrink = qtrue;
+ if ( shrink && self->shrunkTime )
+ {
+ int anim;
- //do a bit of radius damage
- G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage,
- self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS );
+ // We need to make sure that the animation has been set to shrunk mode
+ // because we start out shrunk but with the construct animation when built
+ self->shrunkTime = level.time;
+ anim = self->s.torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT );
+ if ( self->spawned && self->health > 0 && anim != BANIM_DESTROYED )
+ {
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue );
+ }
+ return;
+ }
- //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
+ if ( !shrink && ( !self->shrunkTime ||
+ level.time < self->shrunkTime + BARRICADE_SHRINKTIMEOUT ) )
+ return;
- self->r.contents = 0; //stop collisions...
- trap_LinkEntity( self ); //...requires a relink
-}
+ BG_BuildableBoundingBox( BA_A_BARRICADE, self->r.mins, self->r.maxs );
-/*
-================
-ABarricade_Die
+ if ( shrink )
+ {
+ self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP );
+ self->shrunkTime = level.time;
-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;
+ // shrink animation, the destroy animation is used
+ if ( self->spawned && self->health > 0 )
+ {
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+ }
+ }
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_BuildableDeathSound( self );
-
- 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
+ {
+ trace_t tr;
+ int anim;
- if( self->spawned )
- self->nextthink = level.time + 5000;
- else
- self->nextthink = level.time; //blast immediately
+ trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
+ self->r.currentOrigin, self->s.number, MASK_PLAYERSOLID );
+ if ( tr.startsolid || tr.fraction < 1.0f )
+ {
+ self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP );
+ return;
+ }
+ self->shrunkTime = 0;
- if( attacker && attacker->client )
- {
- if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ // unshrink animation, IDLE2 has been hijacked for this
+ anim = self->s.legsAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT );
+ if ( self->spawned && self->health > 0 &&
+ anim != BANIM_CONSTRUCT1 && anim != BANIM_CONSTRUCT2 )
{
- 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 );
+ G_SetIdleBuildableAnim( self, BG_Buildable( BA_A_BARRICADE )->idleAnim );
+ G_SetBuildableAnim( self, BANIM_ATTACK2, qtrue );
}
- 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 ] );
}
+
+ // a change in size requires a relink
+ if ( self->spawned )
+ trap_LinkEntity( self );
}
/*
================
-ABarricade_Think
+ABarricade_Die
-Think function for Alien Barricade
+Called when an alien barricade dies
================
*/
-void ABarricade_Think( gentity_t *self )
+void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
{
-
- 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 );
+ AGeneric_Die( self, inflictor, attacker, damage, mod );
+ ABarricade_Shrink( self, qtrue );
}
-
-
-
-//==================================================================================
-
-
-
-
-void AAcidTube_Think( gentity_t *self );
-
/*
================
-AAcidTube_Damage
+ABarricade_Think
-Damage function for Alien Acid Tube
+Think function for Alien Barricade
================
*/
-void AAcidTube_Damage( gentity_t *self )
+void ABarricade_Think( 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 );
+ AGeneric_Think( self );
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ // Shrink if unpowered
+ ABarricade_Shrink( self, !self->powered );
}
/*
================
-AAcidTube_Think
+ABarricade_Touch
-Think function for Alien Acid Tube
+Barricades shrink when they are come into contact with an Alien that can
+pass through
================
*/
-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 );
+void ABarricade_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ gclient_t *client = other->client;
+ int client_z, min_z;
- //if there is no creep nearby die
- if( !G_FindCreep( self ) )
- {
- G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+ if( !client || client->pers.teamSelection != TEAM_ALIENS )
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 )
- {
- if( level.paused || enemy->client->pers.paused )
- continue;
- 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 );
+ // Client must be high enough to pass over. Note that STEPSIZE (18) is
+ // hardcoded here because we don't include bg_local.h!
+ client_z = other->r.currentOrigin[ 2 ] + other->r.mins[ 2 ];
+ min_z = self->r.currentOrigin[ 2 ] - 18 +
+ (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP );
+ if( client_z < min_z )
+ return;
+ ABarricade_Shrink( self, qtrue );
}
-
-
-
//==================================================================================
@@ -1140,40 +1159,27 @@ void AAcidTube_Think( gentity_t *self )
/*
================
-AHive_Think
+AAcidTube_Think
-Think function for Alien Hive
+Think function for Alien Acid Tube
================
*/
-void AHive_Think( gentity_t *self )
+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;
- vec3_t dirToTarget;
-
- self->powered = G_IsOvermindBuilt( );
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ AGeneric_Think( self );
- VectorAdd( self->s.origin, range, maxs );
- VectorSubtract( self->s.origin, range, mins );
+ VectorAdd( self->r.currentOrigin, range, maxs );
+ VectorSubtract( self->r.currentOrigin, range, mins );
- //if there is no creep nearby die
- if( !G_FindCreep( self ) )
+ // attack nearby humans
+ if( self->spawned && self->health > 0 && self->powered )
{
- 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++ )
{
@@ -1182,33 +1188,26 @@ void AHive_Think( gentity_t *self )
if( enemy->flags & FL_NOTARGET )
continue;
- if( enemy->health <= 0 )
- continue;
-
- if( !G_Visible( self, enemy ) )
+ if( !G_Visible( self, enemy, CONTENTS_SOLID ) )
continue;
- if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
{
- if( level.paused || enemy->client->pers.paused )
- continue;
- 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 );
+ // start the attack animation
+ if( level.time >= self->timestamp + ACIDTUBE_REPEAT_ANIM )
+ {
+ self->timestamp = level.time;
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) );
+ }
+
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self, ACIDTUBE_DAMAGE,
+ ACIDTUBE_RANGE, self, MOD_ATUBE, TEAM_ALIENS );
+ self->nextthink = level.time + ACIDTUBE_REPEAT;
return;
}
}
}
-
- G_CreepSlow( self );
}
@@ -1216,284 +1215,111 @@ void AHive_Think( gentity_t *self )
//==================================================================================
-
-
-
-#define HOVEL_TRACE_DEPTH 128.0f
-
/*
================
-AHovel_Blocked
+AHive_CheckTarget
-Is this hovel entrance blocked?
+Returns true and fires the hive missile if the target is valid
================
*/
-qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit )
+static qboolean AHive_CheckTarget( gentity_t *self, gentity_t *enemy )
{
- 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 );
+ trace_t trace;
+ vec3_t tip_origin, dirToTarget;
- //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 );
+ // Check if this is a valid target
+ if( enemy->health <= 0 || !enemy->client ||
+ enemy->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )
+ return qfalse;
- 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( enemy->flags & FL_NOTARGET )
+ return qfalse;
- if( tr.fraction < 1.0f )
- return qtrue;
- else
+ // Check if the tip of the hive can see the target
+ VectorMA( self->s.pos.trBase, self->r.maxs[ 2 ], self->s.origin2,
+ tip_origin );
+ if( Distance( tip_origin, enemy->r.currentOrigin ) > HIVE_SENSE_RANGE )
return qfalse;
-}
-/*
-================
-APropHovel_Blocked
+ trap_Trace( &trace, tip_origin, NULL, NULL, enemy->s.pos.trBase,
+ self->s.number, MASK_SHOT );
+ if( trace.fraction < 1.0f && trace.entityNum != enemy->s.number )
+ return qfalse;
-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;
+ self->active = qtrue;
+ self->target_ent = enemy;
+ self->timestamp = level.time + HIVE_REPEAT;
- VectorCopy( origin, hovel.s.origin );
- VectorCopy( angles, hovel.s.angles );
- VectorCopy( normal, hovel.s.origin2 );
+ VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
- return AHovel_Blocked( &hovel, player, qfalse );
+ // Fire at target
+ FireWeapon( self );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ return qtrue;
}
/*
================
-AHovel_Use
+AHive_Think
-Called when an alien uses a hovel
+Think function for Alien Hive
================
*/
-void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
+void AHive_Think( gentity_t *self )
{
- vec3_t hovelOrigin, hovelAngles, inverseNormal;
+ int start;
- 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;
- }
+ AGeneric_Think( self );
- 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 );
+ // Hive missile hasn't returned in HIVE_REPEAT seconds, forget about it
+ if( self->timestamp < level.time )
+ self->active = qfalse;
- activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING;
- activator->client->hovel = self;
- self->builder = activator;
-
- // Cancel pending suicides
- activator->suicideTime = 0;
+ // Find a target to attack
+ if( self->spawned && !self->active && self->powered )
+ {
+ int i, num, entityList[ MAX_GENTITIES ];
+ vec3_t mins, maxs,
+ range = { HIVE_SENSE_RANGE, HIVE_SENSE_RANGE, HIVE_SENSE_RANGE };
- VectorCopy( self->s.pos.trBase, hovelOrigin );
- VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin );
+ VectorAdd( self->r.currentOrigin, range, maxs );
+ VectorSubtract( self->r.currentOrigin, range, mins );
- VectorCopy( self->s.origin2, inverseNormal );
- VectorInverse( inverseNormal );
- vectoangles( inverseNormal, hovelAngles );
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin );
+ if( num == 0 )
+ return;
- G_SetOrigin( activator, hovelOrigin );
- VectorCopy( hovelOrigin, activator->client->ps.origin );
- G_SetClientViewAngle( activator, hovelAngles );
+ start = rand( ) / ( RAND_MAX / num + 1 );
+ for( i = start; i < num + start; i++ )
+ {
+ if( AHive_CheckTarget( self, g_entities + entityList[ i % num ] ) )
+ return;
}
}
}
-
/*
================
-AHovel_Think
+AHive_Pain
-Think for alien hovel
+pain function for Alien Hive
================
*/
-void AHovel_Think( gentity_t *self )
+void AHive_Pain( gentity_t *self, gentity_t *attacker, int damage )
{
- 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 );
-
- G_BuildableDeathSound( self );
-
- 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 );
+ if( self->spawned && self->powered && !self->active )
+ AHive_CheckTarget( self, attacker );
- //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 );
- }
- 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 ] );
- }
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
}
-
-
-
//==================================================================================
-
-
/*
================
ABooster_Touch
@@ -1505,20 +1331,20 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
gclient_t *client = other->client;
- if( !self->spawned || self->health <= 0 )
- return;
-
- if( !G_FindOvermind( self ) )
+ if( !self->spawned || !self->powered || self->health <= 0 )
return;
if( !client )
return;
- if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
return;
+ if( other->flags & FL_NOTARGET )
+ return; // notarget cancels even beneficial effects?
+
client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
- client->lastBoostedTime = level.time;
+ client->boostedTime = level.time;
}
@@ -1540,11 +1366,11 @@ 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 );
+ float distanceToTarget = BG_Buildable( self->s.modelindex )->turretRange;
int lowMsec = 0;
int highMsec = (int)( (
( ( distanceToTarget * LOCKBLOB_SPEED ) +
- ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) /
+ ( distanceToTarget * BG_Class( enemy->client->ps.stats[ STAT_CLASS ] )->speed ) ) /
( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f );
VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration );
@@ -1603,9 +1429,9 @@ qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range )
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?
+ if( target->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) // one of us?
return qfalse;
- if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive?
+ if( target->client->sess.spectatorState != SPECTATOR_NOT ) // is the target alive?
return qfalse;
if( target->health <= 0 ) // is the target still alive?
return qfalse;
@@ -1638,10 +1464,14 @@ Used by ATrapper_Think to locate enemy gentities
void ATrapper_FindEnemy( gentity_t *ent, int range )
{
gentity_t *target;
+ int i;
+ int start;
- //iterate through entities
- for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ )
+ // iterate through entities
+ start = rand( ) / ( RAND_MAX / level.num_entities + 1 );
+ for( i = start; i < level.num_entities + start; i++ )
{
+ target = g_entities + ( i % level.num_entities );
//if target is not valid keep searching
if( !ATrapper_CheckTarget( ent, target, range ) )
continue;
@@ -1664,23 +1494,12 @@ 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 );
+ int range = BG_Buildable( self->s.modelindex )->turretRange;
+ int firespeed = BG_Buildable( self->s.modelindex )->turretFireSpeed;
- self->powered = G_IsOvermindBuilt( );
+ AGeneric_Think( self );
- 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( self->spawned && self->powered )
{
//if the current target is not valid find a new one
if( !ATrapper_CheckTarget( self, self->enemy, range ) )
@@ -1698,48 +1517,304 @@ void ATrapper_Think( gentity_t *self )
+
//==================================================================================
+
/*
================
-HRepeater_Think
+G_SuicideIfNoPower
-Think for human power repeater
+Destroy human structures that have been unpowered too long
================
*/
-void HRepeater_Think( gentity_t *self )
+static qboolean G_SuicideIfNoPower( gentity_t *self )
+{
+ if( self->buildableTeam != TEAM_HUMANS )
+ return qfalse;
+
+ if( !self->powered )
+ {
+ // if the power hasn't reached this buildable for some time, then destroy the buildable
+ if( self->count == 0 )
+ self->count = level.time;
+ else if( ( level.time - self->count ) >= HUMAN_BUILDABLE_INACTIVE_TIME )
+ {
+ if( self->parentNode )
+ G_Damage( self, NULL, g_entities + self->parentNode->killedBy,
+ NULL, NULL, self->health, 0, MOD_NOCREEP );
+ else
+ G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP );
+ return qtrue;
+ }
+ }
+ else
+ self->count = 0;
+
+ return qfalse;
+}
+
+/*
+================
+G_IdlePowerState
+
+Set buildable idle animation to match power state
+================
+*/
+static void G_IdlePowerState( gentity_t *self )
+{
+ if( self->powered )
+ {
+ if( self->s.torsoAnim == BANIM_IDLE3 )
+ G_SetIdleBuildableAnim( self, BG_Buildable( self->s.modelindex )->idleAnim );
+ }
+ else
+ {
+ if( self->s.torsoAnim != BANIM_IDLE3 )
+ G_SetIdleBuildableAnim( self, BANIM_IDLE3 );
+ }
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HSpawn_Disappear
+
+Called when a human spawn is destroyed before it is spawned
+think function
+================
+*/
+void HSpawn_Disappear( gentity_t *self )
+{
+ self->timestamp = level.time;
+ G_QueueBuildPoints( self );
+ G_RewardAttackers( self );
+
+ G_FreeEntity( self );
+}
+
+
+/*
+================
+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->timestamp = level.time;
+
+ //do some radius damage
+ G_RadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage,
+ self->splashRadius, self, self->splashMethodOfDeath );
+
+ // begin freeing build points
+ G_QueueBuildPoints( self );
+ G_RewardAttackers( self );
+ // turn into an explosion
+ self->s.eType = ET_EVENTS + EV_HUMAN_BUILDABLE_EXPLOSION;
+ self->freeAfterEvent = qtrue;
+ G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) );
+}
+
+
+/*
+================
+HSpawn_die
+
+Called when a human spawn dies
+================
+*/
+void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->killedBy = attacker - g_entities;
+ self->powered = qfalse; //free up power
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( self->spawned )
+ {
+ self->think = HSpawn_Blast;
+ self->nextthink = level.time + HUMAN_DETONATION_DELAY;
+ }
+ else
+ {
+ self->think = HSpawn_Disappear;
+ self->nextthink = level.time; //blast immediately
+ }
+
+ G_RemoveRangeMarkerFrom( self );
+ G_LogDestruction( self, attacker, mod );
+}
+
+/*
+================
+HSpawn_Think
+
+Think for human spawn
+================
+*/
+void HSpawn_Think( gentity_t *self )
{
- int i;
- qboolean reactor = qfalse;
gentity_t *ent;
+ // set parentNode
+ self->powered = G_FindPower( self, qfalse );
+
+ if( G_SuicideIfNoPower( self ) )
+ return;
+
if( self->spawned )
{
- //iterate through entities
- for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ //only suicide if at rest
+ if( self->s.groundEntityNum != ENTITYNUM_NONE )
{
- if( ent->s.eType != ET_BUILDABLE )
- continue;
+ if( ( ent = G_CheckSpawnPoint( self->s.number, self->r.currentOrigin,
+ 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, self->health, 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, self->health, 0, MOD_SUICIDE );
+ return;
+ }
- if( ent->s.modelindex == BA_H_REACTOR && ent->spawned )
- reactor = qtrue;
+ if( ent->s.eType == ET_CORPSE )
+ G_FreeEntity( ent ); //quietly remove
+ }
}
}
- if( G_NumberOfDependants( self ) == 0 )
+ self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;
+}
+
+
+
+
+//==================================================================================
+
+
+
+
+/*
+================
+HRepeater_Die
+
+Called when a repeater dies
+================
+*/
+static void HRepeater_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+
+ self->die = nullDieFunction;
+ self->killedBy = attacker - g_entities;
+ self->powered = qfalse; //free up power
+ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
+
+ if( self->spawned )
{
- //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 );
+ self->think = HSpawn_Blast;
+ self->nextthink = level.time + HUMAN_DETONATION_DELAY;
}
else
- self->count = -1;
+ {
+ self->think = HSpawn_Disappear;
+ self->nextthink = level.time; //blast immediately
+ }
+
+ G_RemoveRangeMarkerFrom( self );
+ G_LogDestruction( self, attacker, mod );
+
+ if( self->usesBuildPointZone )
+ {
+ buildPointZone_t *zone = &level.buildPointZones[self->buildPointZone];
+
+ zone->active = qfalse;
+ self->usesBuildPointZone = qfalse;
+ }
+}
+
+/*
+================
+HRepeater_Think
+
+Think for human power repeater
+================
+*/
+void HRepeater_Think( gentity_t *self )
+{
+ int i;
+ gentity_t *powerEnt;
+ buildPointZone_t *zone;
+
+ self->powered = G_FindPower( self, qfalse );
+
+ powerEnt = G_InPowerZone( self );
+ if( powerEnt != NULL )
+ {
+ // If the repeater is inside of another power zone then suicide
+ // Attribute death to whoever built the reactor if that's a human,
+ // which will ensure that it does not queue the BP
+ if( powerEnt->builtBy && powerEnt->builtBy->slot >= 0 )
+ G_Damage( self, NULL, g_entities + powerEnt->builtBy->slot, NULL, NULL, self->health, 0, MOD_SUICIDE );
+ else
+ G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE );
+ return;
+ }
+
+ G_IdlePowerState( self );
- self->powered = reactor;
+ // Initialise the zone once the repeater has spawned
+ if( self->spawned && ( !self->usesBuildPointZone || !level.buildPointZones[ self->buildPointZone ].active ) )
+ {
+ // See if a free zone exists
+ for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ )
+ {
+ zone = &level.buildPointZones[ i ];
+
+ if( !zone->active )
+ {
+ // Initialise the BP queue with no BP queued
+ zone->queuedBuildPoints = 0;
+ zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer;
+ zone->nextQueueTime = level.time;
+ zone->active = qtrue;
+
+ self->buildPointZone = zone - level.buildPointZones;
+ self->usesBuildPointZone = qtrue;
+
+ break;
+ }
+ }
+ }
self->nextthink = level.time + POWER_REFRESH_TIME;
}
@@ -1753,19 +1828,13 @@ Use for human power repeater
*/
void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
- if( self->health <= 0 )
+ if( self->health <= 0 || !self->spawned )
return;
- if( !self->spawned )
- return;
-
- if( other )
+ if( other && other->client )
G_GiveClientMaxAmmo( other, qtrue );
}
-
-#define DCC_ATTACK_PERIOD 10000
-
/*
================
HReactor_Think
@@ -1776,75 +1845,70 @@ 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 range = { REACTOR_ATTACK_RANGE,
+ REACTOR_ATTACK_RANGE,
+ REACTOR_ATTACK_RANGE };
+ vec3_t dccrange = { REACTOR_ATTACK_DCC_RANGE,
+ REACTOR_ATTACK_DCC_RANGE,
+ REACTOR_ATTACK_DCC_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->dcc )
+ {
+ VectorAdd( self->r.currentOrigin, dccrange, maxs );
+ VectorSubtract( self->r.currentOrigin, dccrange, mins );
+ }
+ else
+ {
+ VectorAdd( self->r.currentOrigin, range, maxs );
+ VectorSubtract( self->r.currentOrigin, range, mins );
+ }
if( self->spawned && ( self->health > 0 ) )
{
- //do some damage
+ qboolean fired = qfalse;
+
+ // Creates a tesla trail for every target
num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
for( i = 0; i < num; i++ )
{
enemy = &g_entities[ entityList[ i ] ];
-
+ if( !enemy->client ||
+ enemy->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )
+ continue;
if( enemy->flags & FL_NOTARGET )
continue;
- if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- {
- if( level.paused || enemy->client->pers.paused )
- continue;
- 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
- }
+ tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL );
+ tent->s.misc = self->s.number; //src
+ tent->s.clientNum = enemy->s.number; //dest
+ VectorCopy( self->s.pos.trBase, tent->s.origin2 );
+ fired = qtrue;
}
- //reactor under attack
- if( self->health < self->lastHealth &&
- level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) )
+ // Actual damage is done by radius
+ if( fired )
{
- level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
- G_BroadcastEvent( EV_DCC_ATTACK, 0 );
+ self->timestamp = level.time;
+ if( self->dcc )
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self,
+ REACTOR_ATTACK_DCC_DAMAGE,
+ REACTOR_ATTACK_DCC_RANGE, self,
+ MOD_REACTOR, TEAM_HUMANS );
+ else
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self,
+ REACTOR_ATTACK_DAMAGE,
+ REACTOR_ATTACK_RANGE, self,
+ MOD_REACTOR, TEAM_HUMANS );
}
-
- self->lastHealth = self->health;
}
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
-}
-
-/*
-================
-HReactor_Pain
-================
-*/
-
-void HReactor_Pain( gentity_t *self, gentity_t *attacker, int damage)
-{
- if (self->health <= 0)
- return;
-
- if (!attacker || !attacker->client)
- return;
-
- if (attacker->client->pers.teamSelection != PTE_HUMANS)
- return;
-
- G_TeamCommand(PTE_HUMANS, va( "print \"Reactor ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"",
- attacker->client->pers.netname));
+ if( self->dcc )
+ self->nextthink = level.time + REACTOR_ATTACK_DCC_REPEAT;
+ else
+ self->nextthink = level.time + REACTOR_ATTACK_REPEAT;
}
//==================================================================================
@@ -1863,7 +1927,7 @@ 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 )
+ if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )
return;
//if this is powered then call the armoury menu
@@ -1886,7 +1950,9 @@ void HArmoury_Think( gentity_t *self )
//make sure we have power
self->nextthink = level.time + POWER_REFRESH_TIME;
- self->powered = G_FindPower( self );
+ self->powered = G_FindPower( self, qfalse );
+
+ G_SuicideIfNoPower( self );
}
@@ -1910,7 +1976,9 @@ void HDCC_Think( gentity_t *self )
//make sure we have power
self->nextthink = level.time + POWER_REFRESH_TIME;
- self->powered = G_FindPower( self );
+ self->powered = G_FindPower( self, qfalse );
+
+ G_SuicideIfNoPower( self );
}
@@ -1918,6 +1986,26 @@ void HDCC_Think( gentity_t *self )
//==================================================================================
+
+
+
+/*
+================
+HMedistat_Die
+
+Die function for Human Medistation
+================
+*/
+void HMedistat_Die( gentity_t *self, gentity_t *inflictor,
+ gentity_t *attacker, int damage, int mod )
+{
+ //clear target's healing flag
+ if( self->enemy && self->enemy->client )
+ self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE;
+
+ HSpawn_Die( self, inflictor, attacker, damage, mod );
+}
+
/*
================
HMedistat_Think
@@ -1933,15 +2021,22 @@ void HMedistat_Think( gentity_t *self )
gentity_t *player;
qboolean occupied = qfalse;
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;
+
+ self->powered = G_FindPower( self, qfalse );
+ if( G_SuicideIfNoPower( self ) )
+ return;
+ G_IdlePowerState( self );
+
+ //clear target's healing flag
+ if( self->enemy && self->enemy->client )
+ self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE;
//make sure we have power
- if( !( self->powered = G_FindPower( self ) ) )
+ if( !self->powered )
{
if( self->active )
{
- G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue );
- G_SetIdleBuildableAnim( self, BANIM_IDLE1 );
self->active = qfalse;
self->enemy = NULL;
}
@@ -1952,8 +2047,8 @@ void HMedistat_Think( gentity_t *self )
if( self->spawned )
{
- VectorAdd( self->s.origin, self->r.maxs, maxs );
- VectorAdd( self->s.origin, self->r.mins, mins );
+ VectorAdd( self->r.currentOrigin, self->r.maxs, maxs );
+ VectorAdd( self->r.currentOrigin, self->r.mins, mins );
mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ];
maxs[ 2 ] += 60; //player height
@@ -1961,19 +2056,27 @@ void HMedistat_Think( gentity_t *self )
//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->flags & FL_NOTARGET )
+ continue; // notarget cancels even beneficial effects?
+
+ //remove poison from everyone, not just the healed player
+ if( player->client && player->client->ps.stats[ STAT_STATE ] & SS_POISONED )
+ player->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+
+ if( self->enemy == player && player->client &&
+ player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS &&
+ player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ PM_Alive( player->client->ps.pm_type ) )
{
- if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
- player->client->ps.pm_type != PM_DEAD &&
- self->enemy == player )
- occupied = qtrue;
+ occupied = qtrue;
+ player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE;
}
}
@@ -1986,13 +2089,14 @@ void HMedistat_Think( gentity_t *self )
{
player = &g_entities[ entityList[ i ] ];
- if( player->flags & FL_NOTARGET )
- continue; // notarget cancels even beneficial effects?
+ if( player->flags & FL_NOTARGET )
+ continue; // notarget cancels even beneficial effects?
- if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ if( player->client && player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
{
- if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
- player->client->ps.pm_type != PM_DEAD )
+ if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] ||
+ player->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) &&
+ PM_Alive( player->client->ps.pm_type ) )
{
self->enemy = player;
@@ -2001,6 +2105,7 @@ void HMedistat_Think( gentity_t *self )
{
G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
self->active = qtrue;
+ player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE;
}
}
else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) )
@@ -2017,26 +2122,25 @@ void HMedistat_Think( gentity_t *self )
self->active = qfalse;
}
- else if( self->enemy ) //heal!
+ else if( self->enemy && self->enemy->client ) //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->ps.stats[ STAT_STAMINA ] < STAMINA_MAX )
+ self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE;
- if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
- self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
+ if( self->enemy->client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX )
+ self->enemy->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX;
- self->enemy->health++;
+ if( self->enemy->health < self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] )
+ {
+ self->enemy->health++;
+ self->enemy->client->ps.stats[ STAT_HEALTH ] = 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 );
-
- // if completely healed, cancel retribution
if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] )
{
- for( i = 0; i < MAX_CLIENTS; i++ )
- self->enemy->client->tkcredits[ i ] = 0;
+ if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) )
+ BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );
}
}
}
@@ -2052,6 +2156,39 @@ void HMedistat_Think( gentity_t *self )
/*
================
+HMGTurret_CheckTarget
+
+Used by HMGTurret_Think to check enemies for validity
+================
+*/
+qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target,
+ qboolean los_check )
+{
+ trace_t tr;
+ vec3_t dir, end;
+
+ if( !target || target->health <= 0 || !target->client ||
+ target->client->pers.teamSelection != TEAM_ALIENS )
+ return qfalse;
+
+ if( target->flags & FL_NOTARGET )
+ return qfalse;
+
+ if( !los_check )
+ return qtrue;
+
+ // Accept target if we can line-trace to it
+ VectorSubtract( target->s.pos.trBase, self->s.pos.trBase, dir );
+ VectorNormalize( dir );
+ VectorMA( self->s.pos.trBase, MGTURRET_RANGE, dir, end );
+ trap_Trace( &tr, self->s.pos.trBase, NULL, NULL, end,
+ self->s.number, MASK_SHOT );
+ return tr.entityNum == target - g_entities;
+}
+
+
+/*
+================
HMGTurret_TrackEnemy
Used by HMGTurret_Think to track enemy location
@@ -2062,27 +2199,8 @@ 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 );
@@ -2096,10 +2214,10 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )
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;
+ if( angularDiff[ PITCH ] < 0 && angularDiff[ PITCH ] < (-MGTURRET_ANGULARSPEED) )
+ self->s.angles2[ PITCH ] += MGTURRET_ANGULARSPEED;
+ else if( angularDiff[ PITCH ] > 0 && angularDiff[ PITCH ] > MGTURRET_ANGULARSPEED )
+ self->s.angles2[ PITCH ] -= MGTURRET_ANGULARSPEED;
else
self->s.angles2[ PITCH ] = angleToTarget[ PITCH ];
@@ -2112,10 +2230,10 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )
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;
+ if( angularDiff[ YAW ] < 0 && angularDiff[ YAW ] < ( -MGTURRET_ANGULARSPEED ) )
+ self->s.angles2[ YAW ] += MGTURRET_ANGULARSPEED;
+ else if( angularDiff[ YAW ] > 0 && angularDiff[ YAW ] > MGTURRET_ANGULARSPEED )
+ self->s.angles2[ YAW ] -= MGTURRET_ANGULARSPEED;
else
self->s.angles2[ YAW ] = angleToTarget[ YAW ];
@@ -2123,132 +2241,110 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )
RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle );
vectoangles( dirToTarget, self->turretAim );
- //if pointing at our target return true
- if( fabs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance &&
- fabs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance )
- return qtrue;
-
- return qfalse;
+ //fire if target is within accuracy
+ return ( fabs( angularDiff[ YAW ] ) - MGTURRET_ANGULARSPEED <=
+ MGTURRET_ACCURACY_TO_FIRE ) &&
+ ( fabs( angularDiff[ PITCH ] ) - MGTURRET_ANGULARSPEED <=
+ MGTURRET_ACCURACY_TO_FIRE );
}
/*
================
-HMGTurret_CheckTarget
+HMGTurret_FindEnemy
-Used by HMGTurret_Think to check enemies for validity
+Used by HMGTurret_Think to locate enemy gentities
================
*/
-qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted )
+void HMGTurret_FindEnemy( gentity_t *self )
{
- trace_t trace;
- gentity_t *traceEnt;
-
- if( !target )
- return qfalse;
-
- if( target->flags & FL_NOTARGET )
- return qfalse;
-
- if( !target->client )
- return qfalse;
-
- if( level.paused || target->client->pers.paused )
- 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 );
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range;
+ vec3_t mins, maxs;
+ int i, num;
+ gentity_t *target;
+ int start;
- traceEnt = &g_entities[ trace.entityNum ];
+ self->enemy = NULL;
+
+ // Look for targets in a box around the turret
+ VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE );
+ VectorAdd( self->r.currentOrigin, range, maxs );
+ VectorSubtract( self->r.currentOrigin, range, mins );
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- if( !traceEnt->client )
- return qfalse;
+ if( num == 0 )
+ return;
- if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
- return qfalse;
+ start = rand( ) / ( RAND_MAX / num + 1 );
+ for( i = start; i < num + start ; i++ )
+ {
+ target = &g_entities[ entityList[ i % num ] ];
+ if( !HMGTurret_CheckTarget( self, target, qtrue ) )
+ continue;
- return qtrue;
+ self->enemy = target;
+ return;
+ }
}
-
/*
================
-HMGTurret_FindEnemy
+HMGTurret_State
-Used by HMGTurret_Think to locate enemy gentities
+Raise or lower MG turret towards desired state
================
*/
-void HMGTurret_FindEnemy( gentity_t *self )
+enum {
+ MGT_STATE_INACTIVE,
+ MGT_STATE_DROP,
+ MGT_STATE_RISE,
+ MGT_STATE_ACTIVE
+};
+
+static qboolean HMGTurret_State( gentity_t *self, int state )
{
- int entityList[ MAX_GENTITIES ];
- vec3_t range;
- vec3_t mins, maxs;
- int i, num;
- gentity_t *target;
+ float angle;
- VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE );
- VectorAdd( self->s.origin, range, maxs );
- VectorSubtract( self->s.origin, range, mins );
+ if( self->waterlevel == state )
+ return qfalse;
- //find aliens
- num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
- for( i = 0; i < num; i++ )
- {
- target = &g_entities[ entityList[ i ] ];
+ angle = AngleNormalize180( self->s.angles2[ PITCH ] );
- if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ if( state == MGT_STATE_INACTIVE )
+ {
+ if( angle < MGTURRET_VERTICALCAP )
{
- //if target is not valid keep searching
- if( !HMGTurret_CheckTarget( self, target, qfalse ) )
- continue;
+ if( self->waterlevel != MGT_STATE_DROP )
+ {
+ self->speed = 0.25f;
+ self->waterlevel = MGT_STATE_DROP;
+ }
+ else
+ self->speed *= 1.25f;
- //we found a target
- self->enemy = target;
- return;
+ self->s.angles2[ PITCH ] =
+ MIN( MGTURRET_VERTICALCAP, angle + self->speed );
+ return qtrue;
}
+ else
+ self->waterlevel = MGT_STATE_INACTIVE;
}
-
- if( self->dcced )
+ else if( state == MGT_STATE_ACTIVE )
{
- //check again, this time ignoring painted targets
- for( i = 0; i < num; i++ )
+ if( !self->enemy && angle > 0.0f )
{
- 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;
- }
+ self->waterlevel = MGT_STATE_RISE;
+ self->s.angles2[ PITCH ] =
+ MAX( 0.0f, angle - MGTURRET_ANGULARSPEED * 0.5f );
}
+ else
+ self->waterlevel = MGT_STATE_ACTIVE;
}
- //couldn't find a target
- self->enemy = NULL;
+ return qfalse;
}
-#define MGTURRET_DROOPSCALE 0.5f
-#define TURRET_REST_TIME 5000
-#define TURRET_REST_SPEED 3.0f
-#define TURRET_REST_TOLERANCE 4.0f
-
/*
================
HMGTurret_Think
@@ -2258,69 +2354,73 @@ Think function for MG turret
*/
void HMGTurret_Think( gentity_t *self )
{
- int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex );
+ self->nextthink = level.time +
+ BG_Buildable( self->s.modelindex )->nextthink;
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
-
- //used for client side muzzle flashes
+ // Turn off 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 ) ) )
- {
- if( self->spawned )
- {
- // unpowered turret barrel falls to bottom of range
- float droop;
+ self->powered = G_FindPower( self, qfalse );
+ if( G_SuicideIfNoPower( self ) )
+ return;
+ G_IdlePowerState( self );
- droop = AngleNormalize180( self->s.angles2[ PITCH ] );
- if( droop < MGTURRET_VERTICALCAP )
- {
- droop += MGTURRET_DROOPSCALE;
- if( droop > MGTURRET_VERTICALCAP )
- droop = MGTURRET_VERTICALCAP;
- self->s.angles2[ PITCH ] = droop;
- return;
- }
- }
+ // If not powered or spawned don't do anything
+ if( !self->powered )
+ {
+ // if power loss drop turret
+ if( self->spawned &&
+ HMGTurret_State( self, MGT_STATE_INACTIVE ) )
+ return;
self->nextthink = level.time + POWER_REFRESH_TIME;
return;
}
-
- if( self->spawned )
+ if( !self->spawned )
+ return;
+
+ // If the current target is not valid find a new enemy
+ if( !HMGTurret_CheckTarget( self, self->enemy, qtrue ) )
{
- //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 );
- }
+ self->active = qfalse;
+ self->turretSpinupTime = -1;
+ HMGTurret_FindEnemy( self );
+ }
+ // if newly powered raise turret
+ HMGTurret_State( self, MGT_STATE_ACTIVE );
+ if( !self->enemy )
+ return;
- //if a new target cannot be found don't do anything
- if( !self->enemy )
- return;
+ // Track until we can hit the target
+ if( !HMGTurret_TrackEnemy( self ) )
+ {
+ self->active = qfalse;
+ self->turretSpinupTime = -1;
+ return;
+ }
- self->enemy->targeted = self;
+ // Update spin state
+ if( !self->active && self->timestamp < level.time )
+ {
+ self->active = qtrue;
- //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->turretSpinupTime = level.time + MGTURRET_SPINUP_TIME;
+ G_AddEvent( self, EV_MGTURRET_SPINUP, 0 );
+ }
- self->s.eFlags |= EF_FIRING;
- G_AddEvent( self, EV_FIRE_WEAPON, 0 );
- G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ // Not firing or haven't spun up yet
+ if( !self->active || self->turretSpinupTime > level.time )
+ return;
+
+ // Fire repeat delay
+ if( self->timestamp > level.time )
+ return;
- self->count = level.time + firespeed;
- }
- }
+ FireWeapon( self );
+ self->s.eFlags |= EF_FIRING;
+ self->timestamp = level.time + BG_Buildable( self->s.modelindex )->turretFireSpeed;
+ G_AddEvent( self, EV_FIRE_WEAPON, 0 );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
}
@@ -2340,54 +2440,51 @@ 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_Buildable( self->s.modelindex )->nextthink;
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ self->powered = G_FindPower( self, qfalse );
+ if( G_SuicideIfNoPower( self ) )
+ return;
+ G_IdlePowerState( self );
//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 ) ) )
+ if( !self->powered )
{
self->s.eFlags &= ~EF_FIRING;
self->nextthink = level.time + POWER_REFRESH_TIME;
return;
}
- if( self->spawned && self->count < level.time )
+ if( self->spawned && self->timestamp < level.time )
{
- //used to mark client side effects
+ vec3_t origin, range, mins, maxs;
+ int entityList[ MAX_GENTITIES ], i, num;
+
+ // Communicates firing state to client
self->s.eFlags &= ~EF_FIRING;
+ // Move the muzzle from the entity origin up a bit to fire over turrets
+ VectorMA( self->r.currentOrigin, self->r.maxs[ 2 ], self->s.origin2, origin );
+
VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE );
- VectorAdd( self->s.origin, range, maxs );
- VectorSubtract( self->s.origin, range, mins );
+ VectorAdd( origin, range, maxs );
+ VectorSubtract( origin, range, mins );
- //find aliens
+ // Attack nearby Aliens
num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
for( i = 0; i < num; i++ )
{
- enemy = &g_entities[ entityList[ i ] ];
+ self->enemy = &g_entities[ entityList[ i ] ];
- if( enemy->flags & FL_NOTARGET )
+ if( self->enemy->flags & FL_NOTARGET )
continue;
- if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
- enemy->health > 0 &&
- !level.paused && !enemy->client->pers.paused &&
- 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
+ if( self->enemy->client && self->enemy->health > 0 &&
+ self->enemy->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS &&
+ Distance( origin, self->enemy->s.pos.trBase ) <= TESLAGEN_RANGE )
FireWeapon( self );
- }
}
+ self->enemy = NULL;
if( self->s.eFlags & EF_FIRING )
{
@@ -2396,7 +2493,7 @@ void HTeslaGen_Think( gentity_t *self )
//doesn't really need an anim
//G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
- self->count = level.time + TESLAGEN_REPEAT;
+ self->timestamp = level.time + TESLAGEN_REPEAT;
}
}
}
@@ -2410,228 +2507,125 @@ void HTeslaGen_Think( gentity_t *self )
/*
-================
-HSpawn_Disappear
-
-Called when a human spawn is destroyed before it is spawned
-think function
-================
+============
+G_QueueValue
+============
*/
-void HSpawn_Disappear( gentity_t *self )
+
+static int G_QueueValue( gentity_t *self )
{
- vec3_t dir;
+ int i;
+ int damageTotal = 0;
+ int queuePoints;
+ double queueFraction = 0;
- // we don't have a valid direction, so just point straight up
- dir[ 0 ] = dir[ 1 ] = 0;
- dir[ 2 ] = 1;
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ gentity_t *player = g_entities + i;
- self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed
- self->timestamp = level.time;
+ damageTotal += self->credits[ i ];
+
+ if( self->buildableTeam != player->client->pers.teamSelection )
+ queueFraction += (double) self->credits[ i ];
+ }
- self->think = freeBuildable;
- self->nextthink = level.time + 100;
+ if( damageTotal > 0 )
+ queueFraction = queueFraction / (double) damageTotal;
+ else // all damage was done by nonclients, so queue everything
+ queueFraction = 1.0;
- self->r.contents = 0; //stop collisions...
- trap_LinkEntity( self ); //...requires a relink
+ queuePoints = (int) ( queueFraction * (double) BG_Buildable( self->s.modelindex )->buildPoints );
+ return queuePoints;
}
-
/*
-================
-HSpawn_blast
-
-Called when a human spawn explodes
-think function
-================
+============
+G_QueueBuildPoints
+============
*/
-void HSpawn_Blast( gentity_t *self )
+void G_QueueBuildPoints( gentity_t *self )
{
- vec3_t dir;
+ gentity_t *powerEntity;
+ int queuePoints;
- // we don't have a valid direction, so just point straight up
- dir[ 0 ] = dir[ 1 ] = 0;
- dir[ 2 ] = 1;
+ queuePoints = G_QueueValue( self );
- 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;
+ if( !queuePoints )
+ return;
+
+ switch( self->buildableTeam )
+ {
+ default:
+ case TEAM_NONE:
+ return;
- //do some radius damage
- G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage,
- self->splashRadius, self, 0, self->splashMethodOfDeath );
+ case TEAM_ALIENS:
+ if( !level.alienBuildPointQueue )
+ level.alienNextQueueTime = level.time + g_alienBuildQueueTime.integer;
- self->think = freeBuildable;
- self->nextthink = level.time + 100;
+ level.alienBuildPointQueue += queuePoints;
+ break;
- self->r.contents = 0; //stop collisions...
- trap_LinkEntity( self ); //...requires a relink
-}
+ case TEAM_HUMANS:
+ powerEntity = G_PowerEntityForEntity( self );
+ if( powerEntity )
+ {
+ int nqt;
+ switch( powerEntity->s.modelindex )
+ {
+ case BA_H_REACTOR:
+ nqt = G_NextQueueTime( level.humanBuildPointQueue,
+ g_humanBuildPoints.integer,
+ g_humanBuildQueueTime.integer );
+ if( !level.humanBuildPointQueue ||
+ level.time + nqt < level.humanNextQueueTime )
+ level.humanNextQueueTime = level.time + nqt;
+
+ level.humanBuildPointQueue += queuePoints;
+ break;
-/*
-================
-HSpawn_die
+ case BA_H_REPEATER:
+ if( powerEntity->usesBuildPointZone &&
+ level.buildPointZones[ powerEntity->buildPointZone ].active )
+ {
+ buildPointZone_t *zone = &level.buildPointZones[ powerEntity->buildPointZone ];
-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 );
-
- G_BuildableDeathSound( self );
-
- //pretty events and cleanup
- G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );
- G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+ nqt = G_NextQueueTime( zone->queuedBuildPoints,
+ zone->totalBuildPoints,
+ g_humanRepeaterBuildQueueTime.integer );
- 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( !zone->queuedBuildPoints ||
+ level.time + nqt < zone->nextQueueTime )
+ zone->nextQueueTime = level.time + nqt;
- 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
- }
+ zone->queuedBuildPoints += queuePoints;
+ }
+ break;
- 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 );
- }
- 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 );
- }
- 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 ] );
+ default:
+ break;
+ }
+ }
}
}
/*
-================
-HSpawn_Think
-
-Think for human spawn
-================
+============
+G_NextQueueTime
+============
*/
-void HSpawn_Think( gentity_t *self )
+int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate )
{
- 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;
- }
+ float fractionQueued;
- if( ent->s.eType == ET_CORPSE )
- G_FreeEntity( ent ); //quietly remove
- }
- else
- self->spawnBlockTime = 0;
- }
+ if( totalBP == 0 )
+ return 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 );
+ fractionQueued = queuedBP / (float)totalBP;
+ return ( 1.0f - fractionQueued ) * queueBaseRate;
}
-
-
-
-//==================================================================================
-
-
/*
============
G_BuildableTouchTriggers
@@ -2653,18 +2647,18 @@ void G_BuildableTouchTriggers( gentity_t *ent )
if( ent->health <= 0 )
return;
- BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs );
+ BG_BuildableBoundingBox( ent->s.modelindex, bmins, bmaxs );
- VectorAdd( ent->s.origin, bmins, mins );
- VectorAdd( ent->s.origin, bmaxs, maxs );
+ VectorAdd( ent->r.currentOrigin, bmins, mins );
+ VectorAdd( ent->r.currentOrigin, 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 );
+ VectorAdd( ent->r.currentOrigin, bmins, mins );
+ VectorAdd( ent->r.currentOrigin, bmaxs, maxs );
for( i = 0; i < num; i++ )
{
@@ -2700,66 +2694,85 @@ 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
+ int maxHealth = BG_Buildable( ent->s.modelindex )->health;
+ int regenRate = BG_Buildable( ent->s.modelindex )->regenRate;
+ int buildTime = BG_Buildable( ent->s.modelindex )->buildTime;
//toggle spawned flag for buildables
- if( !ent->spawned && ent->health > 0 )
+ if( !ent->spawned && ent->health > 0 && !level.pausedTime )
{
- if( ent->buildTime + bTime < level.time )
+ if( ent->buildTime + buildTime < level.time )
+ {
ent->spawned = qtrue;
+ if( ent->s.modelindex == BA_A_OVERMIND )
+ {
+ G_TeamCommand( TEAM_ALIENS, "cp \"The Overmind has awakened!\"" );
+ }
+ }
}
- 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;
-
+ // Timer actions
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 > 0 && ent->health < maxHealth )
+ {
+ if( !ent->spawned )
+ ent->health += (int)( ceil( (float)maxHealth / (float)( buildTime * 0.001f ) ) );
+ else
+ {
+ if( ent->buildableTeam == TEAM_ALIENS && regenRate &&
+ ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
+ {
+ ent->health += regenRate;
+ }
+ else if( ent->buildableTeam == TEAM_HUMANS && ent->dcc &&
+ ( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME ) < level.time )
+ {
+ ent->health += DC_HEALRATE * ent->dcc;
+ }
+ }
- if( ent->health > bHealth )
- ent->health = bHealth;
+ if( ent->health >= maxHealth )
+ {
+ int i;
+ ent->health = maxHealth;
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ ent->credits[ i ] = 0;
+ }
+ }
}
- 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
+ ent->dcc = ( ent->buildableTeam != TEAM_HUMANS ) ? 0 : G_FindDCC( ent );
+
+ // Set health
+ ent->s.misc = MAX( ent->health, 0 );
+
+ // Set flags
+ ent->s.eFlags &= ~( EF_B_POWERED | EF_B_SPAWNED | EF_B_MARKED );
+ if( ent->powered )
+ ent->s.eFlags |= EF_B_POWERED;
+
+ if( ent->spawned )
+ ent->s.eFlags |= EF_B_SPAWNED;
+
+ if( ent->deconstruct )
+ ent->s.eFlags |= EF_B_MARKED;
+
+ // Check if this buildable is touching any triggers
G_BuildableTouchTriggers( ent );
- //fall back on normal physics routines
- G_Physics( ent, msec );
+ // Fall back on normal physics routines
+ if( msec != 0 )
+ G_Physics( ent, msec );
}
@@ -2790,7 +2803,7 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )
if( ent->s.eType != ET_BUILDABLE )
continue;
- if( ent->biteam == BIT_HUMANS && !ent->powered )
+ if( ent->buildableTeam == TEAM_HUMANS && !ent->powered )
continue;
if( ent->s.modelindex == buildable && ent->spawned )
@@ -2800,20 +2813,29 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )
return qfalse;
}
-static qboolean G_BoundsIntersect(const vec3_t mins, const vec3_t maxs,
- const vec3_t mins2, const vec3_t maxs2)
+/*
+================
+G_FindBuildable
+
+Finds a buildable of the specified type
+================
+*/
+static gentity_t *G_FindBuildable( buildable_t buildable )
{
- if ( maxs[0] < mins2[0] ||
- maxs[1] < mins2[1] ||
- maxs[2] < mins2[2] ||
- mins[0] > maxs2[0] ||
- mins[1] > maxs2[1] ||
- mins[2] > maxs2[2])
+ int i;
+ gentity_t *ent;
+
+ for( i = MAX_CLIENTS, ent = g_entities + i;
+ i < level.num_entities; i++, ent++ )
{
- return qfalse;
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ if( ent->s.modelindex == buildable && !( ent->s.eFlags & EF_DEAD ) )
+ return ent;
}
- return qtrue;
+ return NULL;
}
/*
@@ -2829,15 +2851,15 @@ static qboolean G_BuildablesIntersect( buildable_t a, vec3_t originA,
vec3_t minsA, maxsA;
vec3_t minsB, maxsB;
- BG_FindBBoxForBuildable( a, minsA, maxsA );
+ BG_BuildableBoundingBox( a, minsA, maxsA );
VectorAdd( minsA, originA, minsA );
VectorAdd( maxsA, originA, maxsA );
- BG_FindBBoxForBuildable( b, minsB, maxsB );
+ BG_BuildableBoundingBox( b, minsB, maxsB );
VectorAdd( minsB, originB, minsB );
VectorAdd( maxsB, originB, maxsB );
- return G_BoundsIntersect( minsA, maxsA, minsB, maxsB );
+ return BoundsIntersect( minsA, maxsA, minsB, maxsB );
}
/*
@@ -2860,7 +2882,6 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b )
BA_A_TRAPPER,
BA_A_HIVE,
BA_A_BOOSTER,
- BA_A_HOVEL,
BA_A_SPAWN,
BA_A_OVERMIND,
@@ -2882,30 +2903,55 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b )
buildableA = *(gentity_t **)a;
buildableB = *(gentity_t **)b;
-
// Prefer the one that collides with the thing we're building
aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin,
- buildableA->s.modelindex, buildableA->s.origin );
+ buildableA->s.modelindex, buildableA->r.currentOrigin );
bMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin,
- buildableB->s.modelindex, buildableB->s.origin );
+ buildableB->s.modelindex, buildableB->r.currentOrigin );
if( aMatches && !bMatches )
return -1;
- if( !aMatches && bMatches )
+ else if( !aMatches && bMatches )
return 1;
+ // If the only spawn is marked, prefer it last
+ if( cmpBuildable == BA_A_SPAWN || cmpBuildable == BA_H_SPAWN )
+ {
+ if( ( buildableA->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) ||
+ ( buildableA->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) )
+ return 1;
+
+ if( ( buildableB->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) ||
+ ( buildableB->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) )
+ return -1;
+ }
+
// If one matches the thing we're building, prefer it
aMatches = ( buildableA->s.modelindex == cmpBuildable );
bMatches = ( buildableB->s.modelindex == cmpBuildable );
if( aMatches && !bMatches )
return -1;
- if( !aMatches && bMatches )
+ else if( !aMatches && bMatches )
return 1;
- // If they're the same type then pick the one marked earliest
+ // They're the same type
if( buildableA->s.modelindex == buildableB->s.modelindex )
+ {
+ gentity_t *powerEntity = G_PowerEntityForPoint( cmpOrigin );
+
+ // Prefer the entity that is providing power for this point
+ aMatches = ( powerEntity == buildableA );
+ bMatches = ( powerEntity == buildableB );
+ if( aMatches && !bMatches )
+ return -1;
+ else if( !aMatches && bMatches )
+ return 1;
+
+ // Pick the one marked earliest
return buildableA->deconstructTime - buildableB->deconstructTime;
+ }
- for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ )
+ // Resort to preference list
+ for( i = 0; i < ARRAY_LEN( precedence ); i++ )
{
if( buildableA->s.modelindex == precedence[ i ] )
aPrecedence = i;
@@ -2919,17 +2965,49 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b )
/*
===============
+G_ClearDeconMarks
+
+Remove decon mark from all buildables
+===============
+*/
+void G_ClearDeconMarks( void )
+{
+ int i;
+ gentity_t *ent;
+
+ for( i = MAX_CLIENTS, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
+ {
+ if( !ent->inuse )
+ continue;
+
+ if( ent->s.eType != ET_BUILDABLE )
+ continue;
+
+ ent->deconstruct = qfalse;
+ }
+}
+
+/*
+===============
G_FreeMarkedBuildables
Free up build points for a team by deconstructing marked buildables
===============
*/
-void G_FreeMarkedBuildables( void )
+void G_FreeMarkedBuildables( gentity_t *deconner, char *readable, int rsize,
+ char *nums, int nsize )
{
int i;
+ int bNum;
+ int listItems = 0;
+ int totalListItems = 0;
gentity_t *ent;
- buildHistory_t *new, *last;
- last = level.buildHistory;
+ int removalCounts[ BA_NUM_BUILDABLES ] = {0};
+
+ if( readable && rsize )
+ readable[ 0 ] = '\0';
+ if( nums && nsize )
+ nums[ 0 ] = '\0';
if( !g_markDeconstruct.integer )
return; // Not enabled, can't deconstruct anything
@@ -2937,24 +3015,43 @@ void G_FreeMarkedBuildables( void )
for( i = 0; i < level.numBuildablesForRemoval; i++ )
{
ent = level.markedBuildables[ i ];
+ bNum = BG_Buildable( ent->s.modelindex )->number;
+
+ if( removalCounts[ bNum ] == 0 )
+ totalListItems++;
- 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_Damage( ent, NULL, deconner, NULL, NULL, ent->health, 0, MOD_REPLACE );
+ removalCounts[ bNum ]++;
+
+ if( nums )
+ Q_strcat( nums, nsize, va( " %d", (int)( ent - g_entities ) ) );
+
+ G_RemoveRangeMarkerFrom( ent );
G_FreeEntity( ent );
}
+
+ if( !readable )
+ return;
+
+ for( i = 0; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( removalCounts[ i ] )
+ {
+ if( listItems )
+ {
+ if( listItems == ( totalListItems - 1 ) )
+ Q_strcat( readable, rsize, va( "%s and ",
+ ( totalListItems > 2 ) ? "," : "" ) );
+ else
+ Q_strcat( readable, rsize, ", " );
+ }
+ Q_strcat( readable, rsize, va( "%s", BG_Buildable( i )->humanName ) );
+ if( removalCounts[ i ] > 1 )
+ Q_strcat( readable, rsize, va( " (%dx)", removalCounts[ i ] ) );
+ listItems++;
+ }
+ }
}
/*
@@ -2962,20 +3059,20 @@ void G_FreeMarkedBuildables( void )
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
+and list the buildables that must be destroyed if this is the case
===============
*/
static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
vec3_t origin )
{
- int i;
- int numBuildables = 0;
- int pointsYielded = 0;
- gentity_t *ent;
- qboolean unique = BG_FindUniqueTestForBuildable( buildable );
- int remainingBP, remainingSpawns;
- int team;
- int buildPoints, buildpointsneeded;
+ int i;
+ int numBuildables = 0;
+ int numRequired = 0;
+ int pointsYielded = 0;
+ gentity_t *ent;
+ team_t team = BG_Buildable( buildable )->team;
+ int buildPoints = BG_Buildable( buildable )->buildPoints;
+ int remainingBP, remainingSpawns;
qboolean collision = qfalse;
int collisionCount = 0;
qboolean repeaterInRange = qfalse;
@@ -2984,29 +3081,35 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
buildable_t spawn;
buildable_t core;
int spawnCount = 0;
+ qboolean changed = qtrue;
level.numBuildablesForRemoval = 0;
- buildPoints = buildpointsneeded = BG_FindBuildPointsForBuildable( buildable );
- team = BG_FindTeamForBuildable( buildable );
- if( team == BIT_ALIENS )
+ if( team == TEAM_ALIENS )
{
- remainingBP = level.alienBuildPoints;
+ remainingBP = G_GetBuildPoints( origin, team );
remainingSpawns = level.numAlienSpawns;
- bpError = IBE_NOASSERT;
+ bpError = IBE_NOALIENBP;
spawn = BA_A_SPAWN;
core = BA_A_OVERMIND;
}
- else if( team == BIT_HUMANS )
+ else if( team == TEAM_HUMANS )
{
- remainingBP = level.humanBuildPoints;
+ if( buildable == BA_H_REACTOR || buildable == BA_H_REPEATER )
+ remainingBP = level.humanBuildPoints;
+ else
+ remainingBP = G_GetBuildPoints( origin, team );
+
remainingSpawns = level.numHumanSpawns;
- bpError = IBE_NOPOWER;
+ bpError = IBE_NOHUMANBP;
spawn = BA_H_SPAWN;
core = BA_H_REACTOR;
}
else
+ {
+ Com_Error( ERR_FATAL, "team is %d", team );
return IBE_NONE;
+ }
// Simple non-marking case
if( !g_markDeconstruct.integer )
@@ -3020,18 +3123,15 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
if( ent->s.eType != ET_BUILDABLE )
continue;
- if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin ) )
+ if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->r.currentOrigin ) )
return IBE_NOROOM;
}
return IBE_NONE;
}
- buildpointsneeded -= remainingBP;
-
// Set buildPoints to the number extra that are required
- if( !g_markDeconstructMode.integer )
- buildPoints -= remainingBP;
+ buildPoints -= remainingBP;
// Build a list of buildable entities
for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
@@ -3039,15 +3139,27 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
if( ent->s.eType != ET_BUILDABLE )
continue;
- collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin );
+ collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->r.currentOrigin );
if( collision )
+ {
+ // Don't allow replacements at all
+ if( g_markDeconstruct.integer == 1 )
+ return IBE_NOROOM;
+
+ // Only allow replacements of the same type
+ if( g_markDeconstruct.integer == 2 && ent->s.modelindex != buildable )
+ return IBE_NOROOM;
+
+ // Any other setting means anything goes
+
collisionCount++;
+ }
// Check if this is a repeater and it's in range
if( buildable == BA_H_REPEATER &&
buildable == ent->s.modelindex &&
- Distance( ent->s.origin, origin ) < REPEATER_BASESIZE )
+ Distance( ent->r.currentOrigin, origin ) < REPEATER_BASESIZE )
{
repeaterInRange = qtrue;
repeaterInRangeCount++;
@@ -3055,55 +3167,76 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
else
repeaterInRange = qfalse;
+ // Don't allow marked buildables to be replaced in another zone,
+ // unless the marked buildable isn't in a zone (and thus unpowered)
+ if( team == TEAM_HUMANS &&
+ buildable != BA_H_REACTOR &&
+ buildable != BA_H_REPEATER &&
+ ent->parentNode != G_PowerEntityForPoint( origin ) )
+ continue;
+
if( !ent->inuse )
continue;
if( ent->health <= 0 )
continue;
- if( ent->biteam != team )
+ if( ent->buildableTeam != team )
continue;
- // Don't allow destruction of hovel with granger inside
- if( ent->s.modelindex == BA_A_HOVEL && ent->active )
- {
- if( buildable == BA_A_HOVEL )
- return IBE_HOVEL;
- else
- continue;
- }
-
// Explicitly disallow replacement of the core buildable with anything
// other than the core buildable
if( ent->s.modelindex == core && buildable != core )
continue;
+ // Don't allow a power source to be replaced by a dependant
+ if( team == TEAM_HUMANS &&
+ G_PowerEntityForPoint( origin ) == ent &&
+ buildable != BA_H_REPEATER &&
+ buildable != core )
+ continue;
+
+ // Don't include unpowered buildables
+ if( !collision && !ent->powered )
+ continue;
+
if( ent->deconstruct )
{
level.markedBuildables[ numBuildables++ ] = ent;
+ // Buildables that are marked here will always end up at the front of the
+ // removal list, so just incrementing numBuildablesForRemoval is sufficient
if( collision || repeaterInRange )
{
+ // Collided with something, so we definitely have to remove it or
+ // it's a repeater that intersects the new repeater's power area,
+ // so it must be removed
+
if( collision )
collisionCount--;
if( repeaterInRange )
repeaterInRangeCount--;
- pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ if( ent->powered )
+ pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints;
level.numBuildablesForRemoval++;
}
- else if( unique && ent->s.modelindex == buildable )
+ else if( BG_Buildable( ent->s.modelindex )->uniqueTest &&
+ ent->s.modelindex == buildable )
{
// If it's a unique buildable, it must be replaced by the same type
- pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ if( ent->powered )
+ pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints;
level.numBuildablesForRemoval++;
}
}
}
+ numRequired = level.numBuildablesForRemoval;
+
// We still need build points, but have no candidates for removal
- if( buildpointsneeded > 0 && numBuildables == 0 )
+ if( buildPoints > 0 && numBuildables == 0 )
return bpError;
// Collided with something we can't remove
@@ -3112,7 +3245,7 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
// There are one or more repeaters we can't remove
if( repeaterInRangeCount > 0 )
- return IBE_RPTWARN2;
+ return IBE_RPTPOWERHERE;
// Sort the list
cmpBuildable = buildable;
@@ -3125,23 +3258,50 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable,
level.numBuildablesForRemoval++ )
{
ent = level.markedBuildables[ level.numBuildablesForRemoval ];
- pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex );
+ if( ent->powered )
+ pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints;
+ }
+
+ // Do another pass to see if we can meet quota with fewer buildables
+ // than we have now due to mismatches between priority and BP amounts
+ // by repeatedly testing if we can chop off the first thing that isn't
+ // required by rules of collision/uniqueness, which are always at the head
+ while( changed && level.numBuildablesForRemoval > 1 &&
+ level.numBuildablesForRemoval > numRequired )
+ {
+ int pointsUnYielded = 0;
+ changed = qfalse;
+ ent = level.markedBuildables[ numRequired ];
+ if( ent->powered )
+ pointsUnYielded = BG_Buildable( ent->s.modelindex )->buildPoints;
+
+ if( pointsYielded - pointsUnYielded >= buildPoints )
+ {
+ pointsYielded -= pointsUnYielded;
+ memmove( &level.markedBuildables[ numRequired ],
+ &level.markedBuildables[ numRequired + 1 ],
+ ( level.numBuildablesForRemoval - numRequired )
+ * sizeof( gentity_t * ) );
+ level.numBuildablesForRemoval--;
+ changed = qtrue;
+ }
}
- // Make sure we're not removing the last spawn
for( i = 0; i < level.numBuildablesForRemoval; i++ )
{
if( level.markedBuildables[ i ]->s.modelindex == spawn )
spawnCount++;
}
+
+ // Make sure we're not removing the last spawn
if( !g_cheats.integer && remainingSpawns > 0 && ( remainingSpawns - spawnCount ) < 1 )
- return IBE_NORMAL;
+ return IBE_LASTSPAWN;
// Not enough points yielded
- if( pointsYielded < buildpointsneeded )
+ if( pointsYielded < buildPoints )
return bpError;
-
- return IBE_NONE;
+ else
+ return IBE_NONE;
}
/*
@@ -3190,39 +3350,34 @@ 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 )
+itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance,
+ vec3_t origin, vec3_t normal, int *groundEntNum )
{
vec3_t angles;
- vec3_t entity_origin, normal;
- vec3_t mins, maxs, nbmins, nbmaxs;
- vec3_t nbVect;
+ vec3_t entity_origin;
+ vec3_t mins, maxs;
trace_t tr1, tr2, tr3;
- int i;
- itemBuildError_t reason = IBE_NONE;
+ itemBuildError_t reason = IBE_NONE, tempReason;
gentity_t *tempent;
float minNormal;
qboolean invert;
int contents;
playerState_t *ps = &ent->client->ps;
- int buildPoints;
- gentity_t *tmp;
- itemBuildError_t tempReason;
// Stop all buildables from interacting with traces
G_SetBuildableLinkState( qfalse );
- BG_FindBBoxForBuildable( buildable, mins, maxs );
+ BG_BuildableBoundingBox( 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 );
-
+ *groundEntNum = tr1.entityNum;
VectorCopy( tr1.plane.normal, normal );
- minNormal = BG_FindMinNormalForBuildable( buildable );
- invert = BG_FindInvertNormalForBuildable( buildable );
+ minNormal = BG_Buildable( buildable )->minNormal;
+ invert = BG_Buildable( buildable )->invertNormal;
//can we build at this angle?
if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) )
@@ -3232,174 +3387,93 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance
reason = IBE_NORMAL;
contents = trap_PointContents( entity_origin, -1 );
- buildPoints = BG_FindBuildPointsForBuildable( buildable );
-
- //check if we are near a nobuild marker, if so, can't build here...
- for( i = 0; i < MAX_GENTITIES; i++ )
- {
- tmp = &g_entities[ i ];
-
- if( !tmp->noBuild.isNB )
- continue;
-
- nbVect[0] = tmp->noBuild.Area;
- nbVect[1] = tmp->noBuild.Area;
- nbVect[2] = tmp->noBuild.Height;
-
- VectorSubtract( origin, nbVect, nbmins );
- VectorAdd( origin, nbVect, nbmaxs );
-
- if( trap_EntityContact( nbmins, nbmaxs, tmp ) )
- reason = IBE_PERMISSION;
-
- }
- if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+
+ if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE )
+ reason = tempReason;
+
+ if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )
{
//alien criteria
- if( buildable == BA_A_HOVEL )
+ // Check there is an Overmind
+ if( buildable != BA_A_OVERMIND )
{
- 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;
+ if( !G_Overmind( ) )
+ reason = IBE_NOOVERMIND;
}
//check there is creep near by for building on
- if( BG_FindCreepTestForBuildable( buildable ) )
+ if( BG_Buildable( buildable )->creepTest )
{
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 )
+ // Check permission to build here
+ if( tr1.surfaceFlags & SURF_NOALIENBUILD || contents & CONTENTS_NOALIENBUILD )
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:
- reason = IBE_OVERMIND;
- break;
-
- case BA_A_HOVEL:
- reason = IBE_HOVEL;
- break;
-
- default:
- Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable );
- break;
- }
-
- break;
- }
- }
- }
}
- else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ else if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
{
//human criteria
- if( !G_IsPowered( entity_origin ) )
+
+ // Check for power
+ if( G_IsPowered( entity_origin ) == BA_NONE )
{
//tell player to build a repeater to provide power
if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER )
- reason = IBE_REPEATER;
+ reason = IBE_NOPOWERHERE;
}
//this buildable requires a DCC
- if( BG_FindDCCTestForBuildable( buildable ) && !G_IsDCCBuilt( ) )
+ if( BG_Buildable( buildable )->dccTest && !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;
+ tempent = G_Reactor( );
+
+ if( tempent == NULL ) // No reactor
+ reason = IBE_RPTNOREAC;
+ else if( g_markDeconstruct.integer && G_IsPowered( entity_origin ) == BA_H_REACTOR )
+ reason = IBE_RPTPOWERHERE;
+ else if( !g_markDeconstruct.integer && G_IsPowered( entity_origin ) )
+ reason = IBE_RPTPOWERHERE;
}
- //check permission to build here
- if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD ||
- contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD )
+ // Check permission to build here
+ if( tr1.surfaceFlags & SURF_NOHUMANBUILD || contents & CONTENTS_NOHUMANBUILD )
reason = IBE_PERMISSION;
+ }
+
+ // Check permission to build here
+ if( tr1.surfaceFlags & SURF_NOBUILD || contents & CONTENTS_NOBUILD )
+ reason = IBE_PERMISSION;
- //can we only build one of these?
- if( BG_FindUniqueTestForBuildable( buildable ) )
+ // Can we only have one of these?
+ if( BG_Buildable( buildable )->uniqueTest )
+ {
+ tempent = G_FindBuildable( buildable );
+ if( tempent && !tempent->deconstruct )
{
- for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ switch( buildable )
{
- if( tempent->s.eType != ET_BUILDABLE )
- continue;
+ case BA_A_OVERMIND:
+ reason = IBE_ONEOVERMIND;
+ break;
- if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct )
- {
- reason = IBE_REACTOR;
+ case BA_H_REACTOR:
+ reason = IBE_ONEREACTOR;
+ break;
+
+ default:
+ Com_Error( ERR_FATAL, "No reason for denying build of %d", buildable );
break;
- }
}
}
}
- if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE )
- reason = tempReason;
-
// Relink buildables
G_SetBuildableLinkState( qtrue );
@@ -3413,8 +3487,8 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance
}
//this item does not fit here
- if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) )
- return IBE_NOROOM;
+ if( reason == IBE_NONE && ( tr2.fraction < 1.0f || tr3.fraction < 1.0f ) )
+ reason = IBE_NOROOM;
if( reason != IBE_NONE )
level.numBuildablesForRemoval = 0;
@@ -3422,28 +3496,55 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance
return reason;
}
+
/*
-==============
-G_BuildingExists
-==============
+================
+G_AddRangeMarkerForBuildable
+================
*/
-qboolean G_BuildingExists( int bclass )
+static void G_AddRangeMarkerForBuildable( gentity_t *self )
{
- int i;
- gentity_t *tempent;
- //look for an Armoury
- for (i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ )
+ gentity_t *rm;
+
+ switch( self->s.modelindex )
{
- if( tempent->s.eType != ET_BUILDABLE )
- continue;
- if( tempent->s.modelindex == bclass && tempent->health > 0 )
- {
- return qtrue;
- }
+ case BA_A_SPAWN:
+ case BA_A_OVERMIND:
+ case BA_A_ACIDTUBE:
+ case BA_A_TRAPPER:
+ case BA_A_HIVE:
+ case BA_H_MGTURRET:
+ case BA_H_TESLAGEN:
+ case BA_H_DCC:
+ case BA_H_REACTOR:
+ case BA_H_REPEATER:
+ break;
+ default:
+ return;
}
- return qfalse;
+
+ rm = G_Spawn();
+ rm->classname = "buildablerangemarker";
+ rm->r.svFlags = SVF_BROADCAST | SVF_CLIENTMASK;
+ rm->s.eType = ET_RANGE_MARKER;
+ rm->s.modelindex = self->s.modelindex;
+
+ self->rangeMarker = rm;
}
+/*
+================
+G_RemoveRangeMarkerFrom
+================
+*/
+void G_RemoveRangeMarkerFrom( gentity_t *self )
+{
+ if( self->rangeMarker )
+ {
+ G_FreeEntity( self->rangeMarker );
+ self->rangeMarker = NULL;
+ }
+}
/*
================
@@ -3452,142 +3553,105 @@ G_Build
Spawns a buildable
================
*/
-static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles )
+static gentity_t *G_Build( gentity_t *builder, buildable_t buildable,
+ const vec3_t origin, const vec3_t normal, const vec3_t angles, int groundEntNum )
{
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 );
+ char readable[ MAX_STRING_CHARS ];
+ char buildnums[ MAX_STRING_CHARS ];
+ buildLog_t *log;
- 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 );
+ if( builder->client )
+ log = G_BuildLogNew( builder, BF_CONSTRUCT );
+ else
+ log = NULL;
- // detect the buildable's normal vector
- if( !builder->client )
- {
- // initial base layout created by server
+ // Free existing buildables
+ G_FreeMarkedBuildables( builder, readable, sizeof( readable ),
+ buildnums, sizeof( buildnums ) );
- 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
+ if( builder->client )
{
- // 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 );
+ // Spawn the buildable
+ built = G_Spawn();
}
+ else
+ built = builder;
- // 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->s.eType = ET_BUILDABLE;
+ built->killedBy = ENTITYNUM_NONE;
+ built->classname = BG_Buildable( buildable )->entityName;
+ built->s.modelindex = buildable;
+ built->buildableTeam = built->s.modelindex2 = BG_Buildable( buildable )->team;
+ BG_BuildableBoundingBox( buildable, built->r.mins, built->r.maxs );
built->health = 1;
- built->splashDamage = BG_FindSplashDamageForBuildable( buildable );
- built->splashRadius = BG_FindSplashRadiusForBuildable( buildable );
- built->splashMethodOfDeath = BG_FindMODForBuildable( buildable );
+ built->splashDamage = BG_Buildable( buildable )->splashDamage;
+ built->splashRadius = BG_Buildable( buildable )->splashRadius;
+ built->splashMethodOfDeath = BG_Buildable( buildable )->meansOfDeath;
- built->nextthink = BG_FindNextThinkForBuildable( buildable );
+ built->nextthink = BG_Buildable( buildable )->nextthink;
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->health = BG_Buildable( buildable )->health;
built->buildTime = built->s.time =
- level.time - BG_FindBuildTimeForBuildable( buildable );
+ level.time - BG_Buildable( buildable )->buildTime;
}
//things that vary for each buildable that aren't in the dbase
switch( buildable )
{
case BA_A_SPAWN:
- built->die = ASpawn_Die;
+ built->die = AGeneric_Die;
built->think = ASpawn_Think;
- built->pain = ASpawn_Pain;
+ built->pain = AGeneric_Pain;
break;
case BA_A_BARRICADE:
built->die = ABarricade_Die;
built->think = ABarricade_Think;
built->pain = ABarricade_Pain;
+ built->touch = ABarricade_Touch;
+ built->shrunkTime = 0;
+ ABarricade_Shrink( built, qtrue );
break;
case BA_A_BOOSTER:
- built->die = ABarricade_Die;
- built->think = ABarricade_Think;
- built->pain = ABarricade_Pain;
+ built->die = AGeneric_Die;
+ built->think = AGeneric_Think;
+ built->pain = AGeneric_Pain;
built->touch = ABooster_Touch;
break;
case BA_A_ACIDTUBE:
- built->die = ABarricade_Die;
+ built->die = AGeneric_Die;
built->think = AAcidTube_Think;
- built->pain = ASpawn_Pain;
+ built->pain = AGeneric_Pain;
break;
case BA_A_HIVE:
- built->die = ABarricade_Die;
+ built->die = AGeneric_Die;
built->think = AHive_Think;
- built->pain = ASpawn_Pain;
+ built->pain = AHive_Pain;
break;
case BA_A_TRAPPER:
- built->die = ABarricade_Die;
+ built->die = AGeneric_Die;
built->think = ATrapper_Think;
- built->pain = ASpawn_Pain;
+ built->pain = AGeneric_Pain;
break;
case BA_A_OVERMIND:
- built->die = ASpawn_Die;
+ built->die = AGeneric_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;
+ built->pain = AGeneric_Pain;
break;
case BA_H_SPAWN:
@@ -3618,7 +3682,7 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori
case BA_H_MEDISTAT:
built->think = HMedistat_Think;
- built->die = HSpawn_Die;
+ built->die = HMedistat_Die;
break;
case BA_H_REACTOR:
@@ -3626,12 +3690,11 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori
built->die = HSpawn_Die;
built->use = HRepeater_Use;
built->powered = built->active = qtrue;
- built->pain = HReactor_Pain;
break;
case BA_H_REPEATER:
built->think = HRepeater_Think;
- built->die = HSpawn_Die;
+ built->die = HRepeater_Die;
built->use = HRepeater_Use;
built->count = -1;
break;
@@ -3641,152 +3704,91 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori
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 );
+ built->s.weapon = BG_Buildable( buildable )->turretProjType;
if( builder->client )
- {
- built->builtBy = builder->client->ps.clientNum;
-
- if( builder->client->pers.designatedBuilder )
- {
- built->s.eFlags |= EF_DBUILDER; // designated builder protection
- }
- }
+ built->builtBy = builder->client->pers.namelog;
+ else if( builder->builtBy )
+ built->builtBy = builder->builtBy;
else
- built->builtBy = -1;
+ built->builtBy = NULL;
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 ] = 0.0f;
+ VectorCopy( angles, built->s.apos.trBase );
+ VectorCopy( angles, built->r.currentAngles );
+ built->s.apos.trBase[ PITCH ] = 0.0f;
+ built->r.currentAngles[ PITCH ] = 0.0f;
built->s.angles2[ YAW ] = angles[ YAW ];
- built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable );
- built->s.pos.trTime = level.time;
- built->physicsBounce = BG_FindBounceForBuildable( buildable );
- built->s.groundEntityNum = -1;
+ built->s.angles2[ PITCH ] = MGTURRET_VERTICALCAP;
+ built->physicsBounce = BG_Buildable( buildable )->bounce;
- built->s.generic1 = (int)( ( (float)built->health /
- (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK );
+ built->s.groundEntityNum = groundEntNum;
+ if( groundEntNum == ENTITYNUM_NONE )
+ {
+ built->s.pos.trType = BG_Buildable( buildable )->traj;
+ built->s.pos.trTime = level.time;
+ // gently nudge the buildable onto the surface :)
+ VectorScale( normal, -50.0f, built->s.pos.trDelta );
+ }
- if( built->s.generic1 < 0 )
- built->s.generic1 = 0;
+ built->s.misc = MAX( built->health, 0 );
- if( BG_FindTeamForBuildable( built->s.modelindex ) == PTE_ALIENS )
+ if( BG_Buildable( buildable )->team == TEAM_ALIENS )
{
built->powered = qtrue;
- built->s.generic1 |= B_POWERED_TOGGLEBIT;
+ built->s.eFlags |= EF_B_POWERED;
}
- else if( ( built->powered = G_FindPower( built ) ) )
- built->s.generic1 |= B_POWERED_TOGGLEBIT;
+ else if( ( built->powered = G_FindPower( built, qfalse ) ) )
+ built->s.eFlags |= EF_B_POWERED;
- if( ( built->dcced = G_FindDCC( built ) ) )
- built->s.generic1 |= B_DCCED_TOGGLEBIT;
-
- built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT;
+ built->s.eFlags &= ~EF_B_SPAWNED;
VectorCopy( normal, built->s.origin2 );
G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 );
- G_SetIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) );
+ G_SetIdleBuildableAnim( built, BG_Buildable( buildable )->idleAnim );
- if( built->builtBy >= 0 )
+ if( built->builtBy )
G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue );
trap_LinkEntity( built );
-
-
- if( builder->client )
+
+ if( builder && 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++;
- }
+ G_TeamCommand( builder->client->ps.stats[ STAT_TEAM ],
+ va( "print \"%s ^2built^7 by %s%s%s\n\"",
+ BG_Buildable( built->s.modelindex )->humanName,
+ builder->client->pers.netname,
+ ( readable[ 0 ] ) ? "^7, ^3replacing^7 " : "",
+ readable ) );
+ G_LogPrintf( "Construct: %d %d %s%s: %s" S_COLOR_WHITE " is building "
+ "%s%s%s\n",
+ (int)( builder - g_entities ),
+ (int)( built - g_entities ),
+ BG_Buildable( built->s.modelindex )->name,
+ buildnums,
+ builder->client->pers.netname,
+ BG_Buildable( built->s.modelindex )->humanName,
+ readable[ 0 ] ? ", replacing " : "",
+ readable );
}
- 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 ) );
- }
+ if( log )
+ G_BuildLogSet( log, built );
- // 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;
+ G_AddRangeMarkerForBuildable( built );
return built;
}
-static void G_SpawnMarker( vec3_t origin )
-{
- gentity_t *nb;
- int i;
-
- // Make the marker...
- nb = G_Spawn( );
- nb->s.modelindex = 0; //Coder humor is win
- VectorCopy( origin, nb->s.pos.trBase );
- VectorCopy( origin, nb->r.currentOrigin );
- nb->noBuild.isNB = qtrue;
- nb->noBuild.Area = level.nbArea;
- nb->noBuild.Height = level.nbHeight;
- trap_LinkEntity( nb );
-
- // Log markers made...
- for( i = 0; i < MAX_GENTITIES; i++ )
- {
- if( level.nbMarkers[ i ].Marker != NULL )
- continue;
-
- level.nbMarkers[ i ].Marker = nb;
- VectorCopy( origin, level.nbMarkers[ i ].Origin );
- SnapVector( level.nbMarkers[ i ].Origin );
- break;
- }
-
- // End nobuild mode...
- level.noBuilding = qfalse;
- level.nbArea = 0.0f;
- level.nbHeight = 0.0f;
-}
-
/*
=================
G_BuildIfValid
@@ -3795,27 +3797,19 @@ G_BuildIfValid
qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable )
{
float dist;
- vec3_t origin;
+ vec3_t origin, normal;
+ int groundEntNum;
- dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+ dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist;
- switch( G_CanBuild( ent, buildable, dist, origin ) )
+ switch( G_CanBuild( ent, buildable, dist, origin, normal, &groundEntNum ) )
{
case IBE_NONE:
- if( level.noBuilding )
- {
- vec3_t mins;
- BG_FindBBoxForBuildable( buildable, mins, NULL );
- origin[2] += mins[2];
-
- G_SpawnMarker( origin );
- return qtrue;
- }
- G_Build( ent, buildable, origin, ent->s.apos.trBase );
+ G_Build( ent, buildable, origin, normal, ent->s.apos.trBase, groundEntNum );
return qtrue;
- case IBE_NOASSERT:
- G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT );
+ case IBE_NOALIENBP:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOBP );
return qfalse;
case IBE_NOOVERMIND:
@@ -3826,96 +3820,44 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable )
G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
return qfalse;
- case IBE_OVERMIND:
- G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND );
- return qfalse;
-
- case IBE_HOVEL:
- G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL );
- return qfalse;
-
- case IBE_HOVELEXIT:
- G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT );
+ case IBE_ONEOVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_ONEOVERMIND );
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 );
+ G_TriggerMenu( ent->client->ps.clientNum, MN_B_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 );
+ G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL );
return qfalse;
- case IBE_REACTOR:
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
+ case IBE_ONEREACTOR:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_ONEREACTOR );
return qfalse;
- case IBE_REPEATER:
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
+ case IBE_NOPOWERHERE:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWERHERE );
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 );
+ G_TriggerMenu( ent->client->ps.clientNum, MN_B_NOROOM );
return qfalse;
- case IBE_NOPOWER:
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
+ case IBE_NOHUMANBP:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOBP);
return qfalse;
case IBE_NODCC:
G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
return qfalse;
- case IBE_SPWNWARN:
- if( level.noBuilding )
- {
- vec3_t mins;
- BG_FindBBoxForBuildable( buildable, mins, NULL );
- origin[2] += mins[2];
- G_SpawnMarker( origin );
- return qtrue;
- }
- G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN );
- G_Build( ent, buildable, origin, ent->s.apos.trBase );
- return qtrue;
-
- case IBE_TNODEWARN:
- if( level.noBuilding )
- {
- vec3_t mins;
- BG_FindBBoxForBuildable( buildable, mins, NULL );
- origin[2] += mins[2];
- G_SpawnMarker( origin );
- return qtrue;
- }
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN );
- G_Build( ent, buildable, origin, ent->s.apos.trBase );
- return qtrue;
-
- case IBE_RPTWARN:
- if( level.noBuilding )
- {
- vec3_t mins;
- BG_FindBBoxForBuildable( buildable, mins, NULL );
- origin[2] += mins[2];
- G_SpawnMarker( origin );
- return qtrue;
- }
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN );
- G_Build( ent, buildable, origin, ent->s.apos.trBase );
- return qtrue;
+ case IBE_RPTPOWERHERE:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTPOWERHERE );
+ return qfalse;
- case IBE_RPTWARN2:
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 );
+ case IBE_LASTSPAWN:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN );
return qfalse;
default:
@@ -3933,33 +3875,41 @@ 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 )
+static gentity_t *G_FinishSpawningBuildable( gentity_t *ent, qboolean force )
{
trace_t tr;
- vec3_t dest;
+ vec3_t normal, 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 );
+ if( ent->s.origin2[ 0 ] || ent->s.origin2[ 1 ] || ent->s.origin2[ 2 ] )
+ VectorCopy( ent->s.origin2, normal );
+ else if( BG_Buildable( buildable )->traj == TR_BUOYANCY )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ built = G_Build( ent, buildable, ent->r.currentOrigin,
+ normal, ent->r.currentAngles, ENTITYNUM_NONE );
built->takedamage = qtrue;
built->spawned = qtrue; //map entities are already spawned
- built->health = BG_FindHealthForBuildable( buildable );
- built->s.generic1 |= B_SPAWNED_TOGGLEBIT;
+ built->health = BG_Buildable( buildable )->health;
+ built->s.eFlags |= EF_B_SPAWNED;
// drop towards normal surface
VectorScale( built->s.origin2, -4096.0f, dest );
- VectorAdd( dest, built->s.origin, dest );
+ VectorAdd( dest, built->r.currentOrigin, dest );
- trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask );
+ trap_Trace( &tr, built->r.currentOrigin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask );
- if( tr.startsolid )
+ if( tr.startsolid && !force )
{
G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n",
- built->classname, vtos( built->s.origin ) );
+ built->classname, vtos( built->r.currentOrigin ) );
+ G_RemoveRangeMarkerFrom( built );
G_FreeEntity( built );
- return;
+ return NULL;
}
//point items in the correct direction
@@ -3971,6 +3921,20 @@ static void G_FinishSpawningBuildable( gentity_t *ent )
G_SetOrigin( built, tr.endpos );
trap_LinkEntity( built );
+ return built;
+}
+
+/*
+============
+G_SpawnBuildableThink
+
+Complete spawning a buildable using its placeholder
+============
+*/
+static void G_SpawnBuildableThink( gentity_t *ent )
+{
+ G_FinishSpawningBuildable( ent, qfalse );
+ G_BuildableThink( ent, 0 );
}
/*
@@ -3990,76 +3954,96 @@ void G_SpawnBuildable( gentity_t *ent, buildable_t 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;
+ ent->think = G_SpawnBuildableThink;
+}
+
+void G_ParseCSVBuildablePlusList( const char *string, int *buildables, int buildablesSize )
+{
+ char buffer[ MAX_STRING_CHARS ];
+ int i = 0;
+ char *p, *q;
+ qboolean EOS = qfalse;
+
+ Q_strncpyz( buffer, string, MAX_STRING_CHARS );
+
+ p = q = buffer;
+
+ while( *p != '\0' && i < buildablesSize - 1 )
+ {
+ //skip to first , or EOS
+ while( *p != ',' && *p != '\0' )
+ p++;
+
+ if( *p == '\0' )
+ EOS = qtrue;
+
+ *p = '\0';
+
+ //strip leading whitespace
+ while( *q == ' ' )
+ q++;
+
+ if( !Q_stricmp( q, "alien" ) )
+ {
+ buildable_t b;
+ for( b = BA_A_SPAWN; b <= BA_A_HIVE && i < buildablesSize - 1; ++b )
+ buildables[ i++ ] = b;
+
+ if( i < buildablesSize - 1 )
+ buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_ALIENS;
+ }
+ else if( !Q_stricmp( q, "human" ) )
+ {
+ buildable_t b;
+ for( b = BA_H_SPAWN; b <= BA_H_REPEATER && i < buildablesSize - 1; ++b )
+ buildables[ i++ ] = b;
+
+ if( i < buildablesSize - 1 )
+ buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_HUMANS;
+ }
+ else if( !Q_stricmp( q, "ivo_spectator" ) )
+ buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_NONE;
+ else if( !Q_stricmp( q, "ivo_alien" ) )
+ buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_ALIENS;
+ else if( !Q_stricmp( q, "ivo_human" ) )
+ buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_HUMANS;
+ else
+ {
+ buildables[ i ] = BG_BuildableByName( q )->number;
+ if( buildables[ i ] == BA_NONE )
+ Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable or special identifier %s\n", q );
+ else
+ i++;
+ }
+
+ if( !EOS )
+ {
+ p++;
+ q = p;
+ }
+ else
+ break;
+ }
+
+ buildables[ i ] = BA_NONE;
}
- /*
- ============
- 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 )
+void G_LayoutSave( char *lstr )
{
+ char *lstrPipePtr;
+ qboolean bAllowed[ BA_NUM_BUILDABLES + NUM_TEAMS ];
char map[ MAX_QPATH ];
char fileName[ MAX_OSPATH ];
fileHandle_t f;
int len;
int i;
gentity_t *ent;
- char *s;
+ const char *s;
trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
if( !map[ 0 ] )
@@ -4067,7 +4051,22 @@ void G_LayoutSave( char *name )
G_Printf( "LayoutSave( ): no map is loaded\n" );
return;
}
- Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name );
+
+ if( ( lstrPipePtr = strchr( lstr, '|' ) ) )
+ {
+ int bList[ BA_NUM_BUILDABLES + NUM_TEAMS ];
+ *lstrPipePtr = '\0';
+ G_ParseCSVBuildablePlusList( lstr, &bList[ 0 ], sizeof( bList ) / sizeof( bList[ 0 ] ) );
+ memset( bAllowed, 0, sizeof( bAllowed ) );
+ for( i = 0; bList[ i ] != BA_NONE; i++ )
+ bAllowed[ bList[ i ] ] = qtrue;
+ *lstrPipePtr = '|';
+ lstr = lstrPipePtr + 1;
+ }
+ else
+ bAllowed[ BA_NONE ] = qtrue; // allow all
+
+ Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, lstr );
len = trap_FS_FOpenFile( fileName, &f, FS_WRITE );
if( len < 0 )
@@ -4076,22 +4075,48 @@ void G_LayoutSave( char *name )
return;
}
- G_Printf("layoutsave: saving layout to %s\n", fileName );
+ G_Printf( "layoutsave: saving layout to %s\n", fileName );
for( i = MAX_CLIENTS; i < level.num_entities; i++ )
{
+ const char *name;
+
ent = &level.gentities[ i ];
- if( ent->s.eType != ET_BUILDABLE )
+ if( ent->s.eType == ET_BUILDABLE )
+ {
+ if( !bAllowed[ BA_NONE ] && !bAllowed[ ent->s.modelindex ] )
+ continue;
+ name = BG_Buildable( ent->s.modelindex )->name;
+ }
+ else if( ent->count == 1 && !strcmp( ent->classname, "info_player_intermission" ) )
+ {
+ if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_NONE ] )
+ continue;
+ name = "ivo_spectator";
+ }
+ else if( ent->count == 1 && !strcmp( ent->classname, "info_alien_intermission" ) )
+ {
+ if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_ALIENS ] )
+ continue;
+ name = "ivo_alien";
+ }
+ else if( ent->count == 1 && !strcmp( ent->classname, "info_human_intermission" ) )
+ {
+ if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_HUMANS ] )
+ continue;
+ name = "ivo_human";
+ }
+ else
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 ],
+ s = va( "%s %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ name,
+ ent->r.currentOrigin[ 0 ],
+ ent->r.currentOrigin[ 1 ],
+ ent->r.currentOrigin[ 2 ],
+ ent->r.currentAngles[ 0 ],
+ ent->r.currentAngles[ 1 ],
+ ent->r.currentAngles[ 2 ],
ent->s.origin2[ 0 ],
ent->s.origin2[ 1 ],
ent->s.origin2[ 2 ],
@@ -4103,6 +4128,11 @@ void G_LayoutSave( char *name )
trap_FS_FCloseFile( f );
}
+/*
+============
+G_LayoutList
+============
+*/
int G_LayoutList( const char *map, char *list, int len )
{
// up to 128 single character layout names could fit in layouts
@@ -4112,7 +4142,7 @@ int G_LayoutList( const char *map, char *list, int len )
int count = 0;
char *filePtr;
- Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " );
+ Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " );
numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat",
fileList, sizeof( fileList ) );
filePtr = fileList;
@@ -4121,7 +4151,7 @@ int G_LayoutList( const char *map, char *list, int len )
fileLen = strlen( filePtr );
listLen = strlen( layouts );
if( fileLen < 5 )
- continue;
+ continue;
// list is full, stop trying to add to it
if( ( listLen + fileLen ) >= sizeof( layouts ) )
@@ -4149,27 +4179,31 @@ int G_LayoutList( const char *map, char *list, int len )
============
G_LayoutSelect
-set level.layout based on g_layouts or g_layoutAuto
+set level.layout based on g_nextLayout, 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 layouts[ ( MAX_CVAR_VALUE_STRING - 1 ) * 9 + 1 ];
+ char layouts2[ ( MAX_CVAR_VALUE_STRING - 1 ) * 9 + 1 ];
+ char *l;
char map[ MAX_QPATH ];
char *s;
int cnt = 0;
int layoutNum;
- Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) );
+ Q_strncpyz( layouts, g_nextLayout.string, sizeof( layouts ) );
+ if( !layouts[ 0 ] )
+ {
+ for( layoutNum = 0; layoutNum < 9; ++layoutNum )
+ Q_strcat( layouts, sizeof( layouts ), g_layouts[ layoutNum ].string );
+ }
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
+ // one time use cvar
+ trap_Cvar_Set( "g_nextLayout", "" );
+
+ // pick an included layout at random if no list has been provided
if( !layouts[ 0 ] && g_layoutAuto.integer )
{
G_LayoutList( map, layouts, sizeof( layouts ) );
@@ -4181,20 +4215,13 @@ void G_LayoutSelect( void )
Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );
l = &layouts2[ 0 ];
layouts[ 0 ] = '\0';
- s = COM_ParseExt( &l, qfalse );
- while( *s )
+ while( 1 )
{
- 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 )
+ s = COM_ParseExt( &l, qfalse );
+ if( !*s )
+ break;
+
+ if( strchr( s, '+' ) || strchr( s, '|' ) || G_LayoutExists( map, s ) )
{
Q_strcat( layouts, sizeof( layouts ), s );
Q_strcat( layouts, sizeof( layouts ), " " );
@@ -4202,7 +4229,6 @@ void G_LayoutSelect( void )
}
else
G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s );
- s = COM_ParseExt( &l, qfalse );
}
if( !cnt )
{
@@ -4210,335 +4236,178 @@ void G_LayoutSelect( void )
"found, using map default\n" );
return;
}
- layoutNum = ( rand( ) % cnt ) + 1;
+ layoutNum = rand( ) / ( RAND_MAX / cnt + 1 ) + 1;
cnt = 0;
Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );
l = &layouts2[ 0 ];
- s = COM_ParseExt( &l, qfalse );
- while( *s )
+ while( 1 )
{
+ s = COM_ParseExt( &l, qfalse );
+ if( !*s )
+ break;
+
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_Printf( "using layout \"%s\" from list (%s)\n", level.layout, layouts );
}
/*
============
-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.
+G_LayoutBuildItem
============
*/
-gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 )
+static void G_LayoutBuildItem( 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;
-
+ gentity_t *builder;
+
builder = G_Spawn( );
- builder->client = 0;
- VectorCopy( origin, builder->s.pos.trBase );
- VectorCopy( angles, builder->s.angles );
+ builder->classname = "builder";
+ VectorCopy( origin, builder->r.currentOrigin );
+ VectorCopy( angles, builder->r.currentAngles );
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_SpawnBuildable( builder, buildable );
}
-/*
-============
-G_SpawnRevertedBuildable
-
-Given a buildhistory, try to replace the lost buildable
-============
-*/
-void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark )
+static void G_SpawnIntermissionViewOverride( char *cn, vec3_t origin, vec3_t angles )
{
- 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 )
+ gentity_t *spot = G_Find( NULL, FOFS( classname ), cn );
+ if( !spot )
{
- built->r.contents = 0;
- built->think = G_CommitRevertedBuildable;
- built->nextthink = level.time;
- built->deconstruct = mark;
+ spot = G_Spawn();
+ spot->classname = cn;
}
- for( i = 0; i < j; i++ )
- toRecontent[ i ]->r.contents = CONTENTS_BODY;
-}
-
-/*
-============
-G_CommitRevertedBuildable
+ spot->count = 1;
-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;
- 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;
- }
- 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;
+ VectorCopy( origin, spot->r.currentOrigin );
+ VectorCopy( angles, spot->r.currentAngles );
}
/*
============
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 )
+void G_LayoutLoad( char *lstr )
{
+ char *lstrPlusPtr, *lstrPipePtr;
+ qboolean bAllowed[ BA_NUM_BUILDABLES + NUM_TEAMS ];
fileHandle_t f;
int len;
- char *layout;
+ char *layout, *layoutHead;
char map[ MAX_QPATH ];
- int buildable = BA_NONE;
+ char buildName[ MAX_TOKEN_CHARS ];
+ int buildable;
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;
+ int i;
- if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) )
+ if( !lstr[ 0 ] || !Q_stricmp( lstr, "*BUILTIN*" ) )
return;
-
+
+ loadAnotherLayout:
+ lstrPlusPtr = strchr( lstr, '+' );
+ if( lstrPlusPtr )
+ *lstrPlusPtr = '\0';
+
+ if( ( lstrPipePtr = strchr( lstr, '|' ) ) )
+ {
+ int bList[ BA_NUM_BUILDABLES + NUM_TEAMS ];
+ *lstrPipePtr = '\0';
+ G_ParseCSVBuildablePlusList( lstr, &bList[ 0 ], sizeof( bList ) / sizeof( bList[ 0 ] ) );
+ memset( bAllowed, 0, sizeof( bAllowed ) );
+ for( i = 0; bList[ i ] != BA_NONE; i++ )
+ bAllowed[ bList[ i ] ] = qtrue;
+ *lstrPipePtr = '|';
+ lstr = lstrPipePtr + 1;
+ }
+ else
+ bAllowed[ BA_NONE ] = qtrue; // allow all
+
trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
- len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ),
+ len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, lstr ),
&f, FS_READ );
if( len < 0 )
{
- G_Printf( "ERROR: layout %s could not be opened\n", level.layout );
+ G_Printf( "ERROR: layout %s could not be opened\n", lstr );
return;
}
- layout = G_Alloc( len + 1 );
+ layoutHead = layout = BG_Alloc( len + 1 );
trap_FS_Read( layout, len, f );
- *( layout + len ) = '\0';
+ layout[ len ] = '\0';
trap_FS_FCloseFile( f );
+ i = 0;
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;
+ va( "layouts/%s/%s.dat", map, lstr ), line );
+ break;
}
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,
+ i = 0;
+ sscanf( line, "%s %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ buildName,
&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 );
+ buildable = BG_BuildableByName( buildName )->number;
+ if( !Q_stricmp( buildName, "ivo_spectator" ) )
+ {
+ if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_NONE ] )
+ G_SpawnIntermissionViewOverride( "info_player_intermission", origin, angles );
+ }
+ else if( !Q_stricmp( buildName, "ivo_alien" ) )
+ {
+ if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_ALIENS ] )
+ G_SpawnIntermissionViewOverride( "info_alien_intermission", origin, angles );
+ }
+ else if( !Q_stricmp( buildName, "ivo_human" ) )
+ {
+ if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_HUMANS ] )
+ G_SpawnIntermissionViewOverride( "info_human_intermission", origin, angles );
+ }
+ else if( buildable <= BA_NONE || buildable >= BA_NUM_BUILDABLES )
+ G_Printf( S_COLOR_YELLOW "WARNING: bad buildable name (%s) in layout."
+ " skipping\n", buildName );
else
- G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in "
- " layout. skipping\n", buildable );
+ {
+ if( bAllowed[ BA_NONE ] || bAllowed[ buildable ] )
+ G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 );
+ }
}
layout++;
}
+ BG_Free( layoutHead );
+
+ if( lstrPlusPtr )
+ {
+ *lstrPlusPtr = '+';
+ lstr = lstrPlusPtr + 1;
+ goto loadAnotherLayout;
+ }
}
-void G_BaseSelfDestruct( pTeam_t team )
+/*
+============
+G_BaseSelfDestruct
+============
+*/
+void G_BaseSelfDestruct( team_t team )
{
int i;
gentity_t *ent;
@@ -4550,192 +4419,274 @@ void G_BaseSelfDestruct( pTeam_t team )
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 )
+ if( ent->buildableTeam != team )
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>";
- }
-
/*
============
-G_NobuildLoad
-
-load the nobuild markers that were previously saved (if there are any).
+build log
============
*/
-void G_NobuildLoad( void )
+buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate )
{
- fileHandle_t f;
- int len;
- char *nobuild;
- char map[ MAX_QPATH ];
- vec3_t origin = { 0.0f, 0.0f, 0.0f };
- char line[ MAX_STRING_CHARS ];
- int i = 0;
- gentity_t *nb;
- float area;
- float height;
+ buildLog_t *log = &level.buildLog[ level.buildId++ % MAX_BUILDLOG ];
+
+ if( level.numBuildLogs < MAX_BUILDLOG )
+ level.numBuildLogs++;
+ log->time = level.time;
+ log->fate = fate;
+ log->actor = actor && actor->client ? actor->client->pers.namelog : NULL;
+ return log;
+}
- trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
- len = trap_FS_FOpenFile( va( "nobuild/%s.dat", map ),
- &f, FS_READ );
- if( len < 0 )
+void G_BuildLogSet( buildLog_t *log, gentity_t *ent )
+{
+ log->modelindex = ent->s.modelindex;
+ log->deconstruct = ent->deconstruct;
+ log->deconstructTime = ent->deconstructTime;
+ log->builtBy = ent->builtBy;
+ VectorCopy( ent->r.currentOrigin, log->origin );
+ VectorCopy( ent->r.currentAngles, log->angles );
+ VectorCopy( ent->s.origin2, log->origin2 );
+ VectorCopy( ent->s.angles2, log->angles2 );
+ log->powerSource = ent->parentNode ? ent->parentNode->s.modelindex : BA_NONE;
+ log->powerValue = G_QueueValue( ent );
+}
+
+void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate )
+{
+ G_BuildLogSet( G_BuildLogNew( actor, fate ), buildable );
+}
+
+void G_BuildLogRevertThink( gentity_t *ent )
+{
+ gentity_t *built;
+ vec3_t mins, maxs;
+ int blockers[ MAX_GENTITIES ];
+ int num;
+ int victims = 0;
+ int i;
+
+ if( ent->suicideTime > 0 )
{
- // This isn't needed since nobuild is pretty much optional...
- //G_Printf( "ERROR: nobuild for %s could not be opened\n", map );
- return;
+ BG_BuildableBoundingBox( ent->s.modelindex, mins, maxs );
+ VectorAdd( ent->s.pos.trBase, mins, mins );
+ VectorAdd( ent->s.pos.trBase, maxs, maxs );
+ num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ gentity_t *targ;
+ vec3_t push;
+
+ targ = g_entities + blockers[ i ];
+ if( targ->client )
+ {
+ float val = ( targ->client->ps.eFlags & EF_WALLCLIMB) ? 300.0 : 150.0;
+
+ VectorSet( push, crandom() * val, crandom() * val, random() * val );
+ VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity );
+ victims++;
+ }
+ }
+
+ ent->suicideTime--;
+
+ if( victims )
+ {
+ // still a blocker
+ ent->nextthink = level.time + FRAMETIME;
+ return;
+ }
}
- nobuild = G_Alloc( len + 1 );
- trap_FS_Read( nobuild, len, f );
- *( nobuild + len ) = '\0';
- trap_FS_FCloseFile( f );
- while( *nobuild )
+
+ built = G_FinishSpawningBuildable( ent, qtrue );
+ built->buildTime = built->s.time = 0;
+ G_KillBox( built );
+
+ G_LogPrintf( "revert: restore %d %s\n",
+ (int)( built - g_entities ), BG_Buildable( built->s.modelindex )->name );
+
+ G_BuildableThink( built, 0 );
+}
+
+void G_BuildLogRevert( int id )
+{
+ buildLog_t *log;
+ gentity_t *ent;
+ int i;
+ vec3_t dist;
+
+ level.numBuildablesForRemoval = 0;
+
+ level.numBuildLogs -= level.buildId - id;
+ while( level.buildId > id )
{
- if( i >= sizeof( line ) - 1 )
+ log = &level.buildLog[ --level.buildId % MAX_BUILDLOG ];
+ if( log->fate == BF_CONSTRUCT )
{
- return;
+ for( i = MAX_CLIENTS; i < level.num_entities; i++ )
+ {
+ ent = &g_entities[ i ];
+ if( ( ( ent->s.eType == ET_BUILDABLE &&
+ ent->health > 0 ) ||
+ ( ent->s.eType == ET_GENERAL &&
+ ent->think == G_BuildLogRevertThink ) ) &&
+ ent->s.modelindex == log->modelindex )
+ {
+ VectorSubtract( ent->s.pos.trBase, log->origin, dist );
+ if( VectorLengthSquared( dist ) <= 2.0f )
+ {
+ if( ent->s.eType == ET_BUILDABLE )
+ G_LogPrintf( "revert: remove %d %s\n",
+ (int)( ent - g_entities ), BG_Buildable( ent->s.modelindex )->name );
+ G_RemoveRangeMarkerFrom( ent );
+ G_FreeEntity( ent );
+ break;
+ }
+ }
+ }
}
-
- line[ i++ ] = *nobuild;
- line[ i ] = '\0';
- if( *nobuild == '\n' )
+ else
{
- i = 0;
- sscanf( line, "%f %f %f %f %f\n",
- &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &area, &height );
-
- // Make the marker...
- nb = G_Spawn( );
- nb->s.modelindex = 0;
- VectorCopy( origin, nb->s.pos.trBase );
- VectorCopy( origin, nb->r.currentOrigin );
- nb->noBuild.isNB = qtrue;
- nb->noBuild.Area = area;
- nb->noBuild.Height = height;
- trap_LinkEntity( nb );
-
- // Log markers made...
- for( i = 0; i < MAX_GENTITIES; i++ )
- {
- if( level.nbMarkers[ i ].Marker != NULL )
- continue;
-
- level.nbMarkers[ i ].Marker = nb;
- VectorCopy( origin, level.nbMarkers[ i ].Origin );
- SnapVector( level.nbMarkers[ i ].Origin );
- break;
- }
-
+ gentity_t *builder = G_Spawn();
+ builder->classname = "builder";
+
+ VectorCopy( log->origin, builder->r.currentOrigin );
+ VectorCopy( log->angles, builder->r.currentAngles );
+ VectorCopy( log->origin2, builder->s.origin2 );
+ VectorCopy( log->angles2, builder->s.angles2 );
+ builder->s.modelindex = log->modelindex;
+ builder->deconstruct = log->deconstruct;
+ builder->deconstructTime = log->deconstructTime;
+ builder->builtBy = log->builtBy;
+
+ builder->think = G_BuildLogRevertThink;
+ builder->nextthink = level.time + FRAMETIME;
+
+ // Number of thinks before giving up and killing players in the way
+ builder->suicideTime = 30;
+
+ if( log->fate == BF_DESTROY || log->fate == BF_TEAMKILL )
+ {
+ int value = log->powerValue;
+
+ if( BG_Buildable( log->modelindex )->team == TEAM_ALIENS )
+ {
+ level.alienBuildPointQueue =
+ MAX( 0, level.alienBuildPointQueue - value );
+ }
+ else
+ {
+ if( log->powerSource == BA_H_REACTOR )
+ {
+ level.humanBuildPointQueue =
+ MAX( 0, level.humanBuildPointQueue - value );
+ }
+ else if( log->powerSource == BA_H_REPEATER )
+ {
+ gentity_t *source;
+ buildPointZone_t *zone;
+
+ source = G_PowerEntityForPoint( log->origin );
+ if( source && source->usesBuildPointZone )
+ {
+ zone = &level.buildPointZones[ source->buildPointZone ];
+ zone->queuedBuildPoints =
+ MAX( 0, zone->queuedBuildPoints - value );
+ }
+ }
+ }
+ }
}
- nobuild++;
}
}
/*
-============
-G_NobuildSave
-Save all currently placed nobuild markers into the "nobuild" folder
-============
+================
+G_UpdateBuildableRangeMarkers
+================
*/
-void G_NobuildSave( void )
+void G_UpdateBuildableRangeMarkers( void )
{
- char map[ MAX_QPATH ];
- char fileName[ MAX_OSPATH ];
- fileHandle_t f;
- int len;
- int i;
- gentity_t *ent;
- char *s;
+ // is the entity 64-bit client-masking extension available?
+ qboolean maskingExtension = ( trap_Cvar_VariableIntegerValue( "sv_gppExtension" ) >= 1 );
- trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
- if( !map[ 0 ] )
+ gentity_t *e;
+ for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e )
{
- G_Printf( "NobuildSave( ): no map is loaded\n" );
- return;
- }
- Com_sprintf( fileName, sizeof( fileName ), "nobuild/%s.dat", map );
+ buildable_t bType;
+ team_t bTeam;
+ int i;
- len = trap_FS_FOpenFile( fileName, &f, FS_WRITE );
- if( len < 0 )
- {
- G_Printf( "nobuildsave: could not open %s\n", fileName );
- return;
- }
+ if( e->s.eType != ET_BUILDABLE || !e->rangeMarker )
+ continue;
- G_Printf("nobuildsave: saving nobuild to %s\n", fileName );
+ bType = e->s.modelindex;
+ bTeam = BG_Buildable( bType )->team;
- for( i = 0; i < MAX_GENTITIES; i++ )
- {
- ent = &level.gentities[ i ];
- if( ent->noBuild.isNB != qtrue )
- continue;
+ e->rangeMarker->s.pos = e->s.pos;
+ if( bType == BA_A_HIVE || bType == BA_H_TESLAGEN )
+ VectorMA( e->s.pos.trBase, e->r.maxs[ 2 ], e->s.origin2, e->rangeMarker->s.pos.trBase );
+ else if( bType == BA_A_TRAPPER || bType == BA_H_MGTURRET )
+ vectoangles( e->s.origin2, e->rangeMarker->s.apos.trBase );
- s = va( "%f %f %f %f %f\n",
- ent->r.currentOrigin[ 0 ],
- ent->r.currentOrigin[ 1 ],
- ent->r.currentOrigin[ 2 ],
- ent->noBuild.Area,
- ent->noBuild.Height );
- trap_FS_Write( s, strlen( s ), f );
+ e->rangeMarker->r.singleClient = 0;
+ e->rangeMarker->r.hack.generic1 = 0;
+
+ // remove any previously added NOCLIENT flags from the hack below
+ e->rangeMarker->r.svFlags &= ~SVF_NOCLIENT;
+
+ for( i = 0; i < level.maxclients; ++i )
+ {
+ gclient_t *client;
+ team_t team;
+ qboolean weaponDisplays, wantsToSee;
+
+ client = &level.clients[ i ];
+ if( client->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( i >= 32 && !maskingExtension )
+ {
+ // resort to not sending range markers at all
+ if( !trap_Cvar_VariableIntegerValue( "g_rangeMarkerWarningGiven" ) )
+ {
+ trap_SendServerCommand( -1, "print \"" S_COLOR_YELLOW "WARNING: There is no "
+ "support for entity 64-bit client-masking on this server. Please update "
+ "your server executable. Until then, range markers will not be displayed "
+ "while there are clients with client numbers above 31 in the game.\n\"" );
+ trap_Cvar_Set( "g_rangeMarkerWarningGiven", "1" );
+ }
+
+ for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e )
+ {
+ if( e->s.eType == ET_BUILDABLE && e->rangeMarker )
+ e->rangeMarker->r.svFlags |= SVF_NOCLIENT;
+ }
+
+ return;
+ }
+
+ team = client->pers.teamSelection;
+ if( team != TEAM_NONE )
+ {
+ weaponDisplays = ( BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) ||
+ client->ps.weapon == WP_ABUILD || client->ps.weapon == WP_ABUILD2 );
+ }
+ wantsToSee = !!( client->pers.buildableRangeMarkerMask & ( 1 << bType ) );
+
+ if( ( team == TEAM_NONE || ( team == bTeam && weaponDisplays ) ) && wantsToSee )
+ {
+ if( i >= 32 )
+ e->rangeMarker->r.hack.generic1 |= 1 << ( i - 32 );
+ else
+ e->rangeMarker->r.singleClient |= 1 << i;
+ }
+ }
+
+ trap_LinkEntity( e->rangeMarker );
}
- trap_FS_FCloseFile( f );
}