diff options
Diffstat (limited to 'src/game/g_buildable.c')
-rw-r--r-- | src/game/g_buildable.c | 4821 |
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 ); } |