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