/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2006 Tim Angus This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "g_local.h" // from g_combat.c extern char *modNames[ ]; extern vmCvar_t g_rotaxJumppadPower;//ROTAXfun /* ================ G_SetBuildableAnim Triggers an animation client side ================ */ void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ) { int localAnim = anim; if( force ) localAnim |= ANIM_FORCEBIT; // don't toggle the togglebit more than once per frame if( ent->animTime != level.time ) { localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ); ent->animTime = level.time; } else localAnim |= ent->s.legsAnim & ANIM_TOGGLEBIT; ent->s.legsAnim = localAnim; } /* ================ G_SetIdleBuildableAnim Set the animation to use whilst no other animations are running ================ */ void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ) { ent->s.torsoAnim = anim; } /* =============== G_CheckSpawnPoint Check if a spawn at a specified point is valid =============== */ gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, buildable_t spawn, vec3_t spawnOrigin ) { float displacement; vec3_t mins, maxs; vec3_t cmins, cmaxs; vec3_t localOrigin; trace_t tr; BG_FindBBoxForBuildable( spawn, mins, maxs ); if( spawn == BA_A_SPAWN ) { VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX ); VectorSet( cmaxs, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX ); displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3; VectorMA( origin, displacement, normal, localOrigin ); trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); if( tr.entityNum != ENTITYNUM_NONE ) return &g_entities[ tr.entityNum ]; trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); if( tr.entityNum == ENTITYNUM_NONE ) { if( spawnOrigin != NULL ) VectorCopy( localOrigin, spawnOrigin ); return NULL; } else return &g_entities[ tr.entityNum ]; } else if( spawn == BA_H_SPAWN ) { BG_FindBBoxForClass( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL ); VectorCopy( origin, localOrigin ); localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f; trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); if( tr.entityNum != ENTITYNUM_NONE ) return &g_entities[ tr.entityNum ]; trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); if( tr.entityNum == ENTITYNUM_NONE ) { if( spawnOrigin != NULL ) VectorCopy( localOrigin, spawnOrigin ); return NULL; } else return &g_entities[ tr.entityNum ]; } return NULL; } /* ================ G_NumberOfDependants Return number of entities that depend on this one ================ */ static int G_NumberOfDependants( gentity_t *self ) { int i, n = 0; gentity_t *ent; for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; if( ent->parentNode == self ) n++; } return n; } #define POWER_REFRESH_TIME 2000 /* ================ G_FindPower attempt to find power for self, return qtrue if successful ================ */ static qboolean G_FindPower( gentity_t *self ) { int i; gentity_t *ent; gentity_t *closestPower = NULL; int distance = 0; int minDistance = 10000; vec3_t temp_v; if( self->biteam != BIT_HUMANS ) return qfalse; //reactor is always powered if( self->s.modelindex == BA_H_REACTOR ) return qtrue; //if this already has power then stop now if( self->parentNode && self->parentNode->powered ) return qtrue; //reset parent self->parentNode = NULL; //iterate through entities for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; //if entity is a power item calculate the distance to it if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && ent->spawned ) { VectorSubtract( self->s.origin, ent->s.origin, temp_v ); distance = VectorLength( temp_v ); if( distance < minDistance && ent->powered && ( ( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) || ( ent->s.modelindex == BA_H_REPEATER && distance <= REPEATER_BASESIZE ) ) ) { closestPower = ent; minDistance = distance; } } } //if there were no power items nearby give up if( closestPower ) { self->parentNode = closestPower; return qtrue; } else return qfalse; } /* ================ G_IsPowered Simple wrapper to G_FindPower to check if a location has power ================ */ qboolean G_IsPowered( vec3_t origin ) { gentity_t dummy; dummy.parentNode = NULL; dummy.biteam = BIT_HUMANS; dummy.s.modelindex = BA_NONE; VectorCopy( origin, dummy.s.origin ); return G_FindPower( &dummy ); } /* ================ G_FindDCC attempt to find a controlling DCC for self, return qtrue if successful ================ */ static qboolean G_FindDCC( gentity_t *self ) { int i; gentity_t *ent; gentity_t *closestDCC = NULL; int distance = 0; int minDistance = 10000; vec3_t temp_v; qboolean foundDCC = qfalse; if( self->biteam != BIT_HUMANS ) return qfalse; //if this already has dcc then stop now if( self->dccNode && self->dccNode->powered ) return qtrue; //reset parent self->dccNode = NULL; //iterate through entities for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; //if entity is a dcc calculate the distance to it if( ent->s.modelindex == BA_H_DCC && ent->spawned ) { VectorSubtract( self->s.origin, ent->s.origin, temp_v ); distance = VectorLength( temp_v ); if( distance < minDistance && ent->powered ) { closestDCC = ent; minDistance = distance; foundDCC = qtrue; } } } //if there was no nearby DCC give up if( !foundDCC ) return qfalse; self->dccNode = closestDCC; return qtrue; } /* ================ G_IsDCCBuilt simple wrapper to G_FindDCC to check for a dcc ================ */ qboolean G_IsDCCBuilt( void ) { gentity_t dummy; memset( &dummy, 0, sizeof( gentity_t ) ); dummy.dccNode = NULL; dummy.biteam = BIT_HUMANS; return G_FindDCC( &dummy ); } /* ================ G_FindOvermind Attempt to find an overmind for self ================ */ static qboolean G_FindOvermind( gentity_t *self ) { int i; gentity_t *ent; if( self->biteam != BIT_ALIENS ) return qfalse; //if this already has overmind then stop now if( self->overmindNode && self->overmindNode->health > 0 ) return qtrue; //reset parent self->overmindNode = NULL; //iterate through entities for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; //if entity is an overmind calculate the distance to it if( ent->s.modelindex == BA_A_OVERMIND && ent->spawned && ent->health > 0 ) { self->overmindNode = ent; return qtrue; } } return qfalse; } /* ================ G_IsOvermindBuilt Simple wrapper to G_FindOvermind to check if a location has an overmind ================ */ qboolean G_IsOvermindBuilt( void ) { gentity_t dummy; memset( &dummy, 0, sizeof( gentity_t ) ); dummy.overmindNode = NULL; dummy.biteam = BIT_ALIENS; return G_FindOvermind( &dummy ); } /* ================ G_FindCreep attempt to find creep for self, return qtrue if successful ================ */ static qboolean G_FindCreep( gentity_t *self ) { int i; gentity_t *ent; gentity_t *closestSpawn = NULL; int distance = 0; int minDistance = 10000; vec3_t temp_v; //don't check for creep if flying through the air if( self->s.groundEntityNum == -1 ) return qtrue; //if self does not have a parentNode or it's parentNode is invalid find a new one if( ( self->parentNode == NULL ) || !self->parentNode->inuse ) { for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; if( ( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_A_OVERMIND ) && ent->spawned ) { VectorSubtract( self->s.origin, ent->s.origin, temp_v ); distance = VectorLength( temp_v ); if( distance < minDistance ) { closestSpawn = ent; minDistance = distance; } } } if( minDistance <= CREEP_BASESIZE ) { self->parentNode = closestSpawn; return qtrue; } else return qfalse; } //if we haven't returned by now then we must already have a valid parent return qtrue; } /* ================ G_IsCreepHere simple wrapper to G_FindCreep to check if a location has creep ================ */ static qboolean G_IsCreepHere( vec3_t origin ) { gentity_t dummy; memset( &dummy, 0, sizeof( gentity_t ) ); dummy.parentNode = NULL; dummy.s.modelindex = BA_NONE; VectorCopy( origin, dummy.s.origin ); return G_FindCreep( &dummy ); } /* ================ G_CreepSlow Set any nearby humans' SS_CREEPSLOWED flag ================ */ static void G_CreepSlow( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range; vec3_t mins, maxs; int i, num; gentity_t *enemy; buildable_t buildable = self->s.modelindex; float creepSize = (float)BG_FindCreepSizeForBuildable( buildable ); VectorSet( range, creepSize, creepSize, creepSize ); VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); //find humans num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; if( enemy->flags & FL_NOTARGET ) continue; if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && G_Visible( self, enemy ) ) { enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; enemy->client->lastCreepSlowTime = level.time; } } } /* ================ nullDieFunction hack to prevent compilers complaining about function pointer -> NULL conversion ================ */ static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { } /* ================ freeBuildable ================ */ static void freeBuildable( gentity_t *self ) { G_FreeEntity( self ); } //================================================================================== /* ================ A_CreepRecede Called when an alien spawn dies ================ */ void A_CreepRecede( gentity_t *self ) { //if the creep just died begin the recession if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) self->s.time = -level.time; else self->s.time = -( level.time - (int)( (float)CREEP_SCALEDOWN_TIME * ( 1.0f - ( (float)( level.time - self->buildTime ) / (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) ); } //creep is still receeding if( ( self->timestamp + 10000 ) > level.time ) self->nextthink = level.time + 500; else //creep has died G_FreeEntity( self ); } //================================================================================== /* ================ ASpawn_Melt Called when an alien spawn dies ================ */ void ASpawn_Melt( gentity_t *self ) { G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //start creep recession if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) self->s.time = -level.time; else self->s.time = -( level.time - (int)( (float)CREEP_SCALEDOWN_TIME * ( 1.0f - ( (float)( level.time - self->buildTime ) / (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) ); } //not dead yet if( ( self->timestamp + 10000 ) > level.time ) self->nextthink = level.time + 500; else //dead now G_FreeEntity( self ); } /* ================ ASpawn_Blast Called when an alien spawn dies ================ */ void ASpawn_Blast( gentity_t *self ) { vec3_t dir; VectorCopy( self->s.origin2, dir ); //do a bit of radius damage G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); self->timestamp = level.time; self->think = ASpawn_Melt; self->nextthink = level.time + 500; //wait .5 seconds before damaging others self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink } /* ================ ASpawn_Die Called when an alien spawn dies ================ */ void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; VectorCopy( self->s.pos.trBase, new->origin ); VectorCopy( self->s.angles, new->angles ); VectorCopy( self->s.origin2, new->origin2 ); VectorCopy( self->s.angles2, new->angles2 ); new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; new->next = NULL; G_LogBuild( new ); G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->think = ASpawn_Blast; if( self->spawned ) self->nextthink = level.time + 5000; else self->nextthink = level.time; //blast immediately self->s.eFlags &= ~EF_FIRING; //prevent any firing effects if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { if( self->s.modelindex == BA_A_OVERMIND ) G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue ); else if( self->s.modelindex == BA_A_SPAWN ) G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue ); //ROTAX if (g_suddenDeath.integer == 1 && self->s.modelindex == BA_A_SPAWN) { G_TeamCommand( PTE_HUMANS, va( "tchat -1 \"^1->EGG DOWN^7 by %s^7\"", attacker->client->pers.netname ) );//rotaxfun bubliny pridan parametr -1 } //ROTAXfun AddScore( attacker, 20 ); } else { G_TeamCommand( PTE_ALIENS, va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ) ); G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ); //ROTAXfun AddScore( attacker, -5 ); } G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", attacker->client->ps.clientNum, self->s.modelindex, mod, attacker->client->pers.netname, BG_FindNameForBuildable( self->s.modelindex ), modNames[ mod ] ); } } /* ================ ASpawn_Think think function for Alien Spawn ================ */ void ASpawn_Think( gentity_t *self ) { gentity_t *ent; if( self->spawned ) { //only suicide if at rest if( self->s.groundEntityNum ) { if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL ) { // If the thing blocking the spawn is a buildable, kill it. // If it's part of the map, kill self. if( ent->s.eType == ET_BUILDABLE ) { G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); } else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } else if( g_antiSpawnBlock.integer && ent->client && ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //spawnblock protection if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 ) { //five seconds of countermeasures and we're still blocked //time for something more drastic G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT ); self->spawnBlockTime += 2000; //inappropriate MOD but prints an apt obituary } else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 ) //five seconds of blocked by client and... { //random direction vec3_t velocity; velocity[0] = crandom() * g_antiSpawnBlock.integer; velocity[1] = crandom() * g_antiSpawnBlock.integer; velocity[2] = g_antiSpawnBlock.integer; VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity ); trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" ); } else if( !self->spawnBlockTime ) self->spawnBlockTime = level.time; } if( ent->s.eType == ET_CORPSE ) G_FreeEntity( ent ); //quietly remove } else self->spawnBlockTime = 0; } } G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } /* ================ ASpawn_Pain pain function for Alien Spawn ================ */ void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage ) { G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); //rotax if (attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && self->s.modelindex == BA_A_OVERMIND) { G_TeamCommand( PTE_ALIENS, va( "print \"Overmind ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", attacker->client->pers.netname )); } } //================================================================================== #define OVERMIND_ATTACK_PERIOD 10000 #define OVERMIND_DYING_PERIOD 5000 #define OVERMIND_SPAWNS_PERIOD 30000 /* ================ AOvermind_Think Think function for Alien Overmind ================ */ void AOvermind_Think( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE }; vec3_t mins, maxs; int i, num; gentity_t *enemy; VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); if( self->spawned && ( self->health > 0 ) ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; if( enemy->flags & FL_NOTARGET ) continue; if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { self->timestamp = level.time; G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS ); G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); } } // just in case an egg finishes building after we tell overmind to stfu if( level.numAlienSpawns > 0 ) level.overmindMuted = qfalse; //low on spawns if( !level.overmindMuted && level.numAlienSpawns <= 0 && level.time > self->overmindSpawnsTimer ) { qboolean haveBuilder = qfalse; gentity_t *builder; self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 ); for( i = 0; i < level.numConnectedClients; i++ ) { builder = &g_entities[ level.sortedClients[ i ] ]; if( builder->health > 0 && ( builder->client->pers.classSelection == PCL_ALIEN_BUILDER0 || builder->client->pers.classSelection == PCL_ALIEN_BUILDER0_UPG ) ) { haveBuilder = qtrue; break; } } // aliens now know they have no eggs, but they're screwed, so stfu if( !haveBuilder || G_TimeTilSuddenDeath( ) <= 0 ) level.overmindMuted = qtrue; } //overmind dying if( self->health < ( OVERMIND_HEALTH / 10.0f ) && level.time > self->overmindDyingTimer ) { self->overmindDyingTimer = level.time + OVERMIND_DYING_PERIOD; G_BroadcastEvent( EV_OVERMIND_DYING, 0 ); } //overmind under attack if( self->health < self->lastHealth && level.time > self->overmindAttackTimer ) { self->overmindAttackTimer = level.time + OVERMIND_ATTACK_PERIOD; G_BroadcastEvent( EV_OVERMIND_ATTACK, 0 ); } self->lastHealth = self->health; } else self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } //rotax void Reactor_ShowTeamDamage( gentity_t *self, gentity_t *attacker, int damage) { if (attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS) { G_TeamCommand( PTE_HUMANS, va( "print \"Reactor ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", attacker->client->pers.netname )); } } //================================================================================== /* ================ ABarricade_Pain pain function for Alien Spawn ================ */ void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage ) { if( rand( ) % 2 ) G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); else G_SetBuildableAnim( self, BANIM_PAIN2, qfalse ); } /* ================ ABarricade_Blast Called when an alien spawn dies ================ */ void ABarricade_Blast( gentity_t *self ) { vec3_t dir; VectorCopy( self->s.origin2, dir ); //do a bit of radius damage G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); self->timestamp = level.time; self->think = A_CreepRecede; self->nextthink = level.time + 500; //wait .5 seconds before damaging others self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink } /* ================ ABarricade_Die Called when an alien spawn dies ================ */ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; VectorCopy( self->s.pos.trBase, new->origin ); VectorCopy( self->s.angles, new->angles ); VectorCopy( self->s.origin2, new->origin2 ); VectorCopy( self->s.angles2, new->angles2 ); new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; new->next = NULL; G_LogBuild( new ); G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->think = ABarricade_Blast; self->s.eFlags &= ~EF_FIRING; //prevent any firing effects if( self->spawned ) self->nextthink = level.time + 5000; else self->nextthink = level.time; //blast immediately if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { G_TeamCommand( PTE_ALIENS, va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ) ); G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ); //ROTAXfun AddScore( attacker, -5 ); } else//ROTAXfun { if (self->s.modelindex == BA_A_ACIDTUBE) AddScore( attacker, 5 ); else if (self->s.modelindex == BA_A_HIVE) AddScore( attacker, 5 ); else if (self->s.modelindex == BA_A_TRAPPER) AddScore( attacker, 3 ); else if (self->s.modelindex == BA_A_OVERMIND) AddScore( attacker, 60 ); else if (self->s.modelindex == BA_A_BOOSTER) AddScore( attacker, 5 ); else AddScore( attacker, 1 ); } G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", attacker->client->ps.clientNum, self->s.modelindex, mod, attacker->client->pers.netname, BG_FindNameForBuildable( self->s.modelindex ), modNames[ mod ] ); } } /* ================ ABarricade_Think Think function for Alien Barricade ================ */ void ABarricade_Think( gentity_t *self ) { self->powered = G_IsOvermindBuilt( ); //if there is no creep nearby die if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } //================================================================================== void AAcidTube_Think( gentity_t *self ); /* ================ AAcidTube_Damage Damage function for Alien Acid Tube ================ */ void AAcidTube_Damage( gentity_t *self ) { if( self->spawned ) { if( !( self->s.eFlags & EF_FIRING ) ) { self->s.eFlags |= EF_FIRING; G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); } if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time ) self->think = AAcidTube_Damage; else { self->think = AAcidTube_Think; self->s.eFlags &= ~EF_FIRING; } //do some damage G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); } G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } /* ================ AAcidTube_Think Think function for Alien Acid Tube ================ */ void AAcidTube_Think( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE }; vec3_t mins, maxs; int i, num; gentity_t *enemy; self->powered = G_IsOvermindBuilt( ); VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); //if there is no creep nearby die if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } if( self->spawned && G_FindOvermind( self ) ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; if( enemy->flags & FL_NOTARGET ) continue; if( !G_Visible( self, enemy ) ) continue; if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { self->timestamp = level.time; self->think = AAcidTube_Damage; self->nextthink = level.time + 100; G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); return; } } } G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } //================================================================================== /* ================ AHive_Think Think function for Alien Hive ================ */ void AHive_Think( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE }; vec3_t mins, maxs; int i, num; gentity_t *enemy; vec3_t dirToTarget; self->powered = G_IsOvermindBuilt( ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); //if there is no creep nearby die if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } if( self->timestamp < level.time ) self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it if( self->spawned && !self->active && G_FindOvermind( self ) ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; if( enemy->flags & FL_NOTARGET ) continue; if( enemy->health <= 0 ) continue; if( !G_Visible( self, enemy ) ) continue; if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { self->active = qtrue; self->target_ent = enemy; self->timestamp = level.time + HIVE_REPEAT; VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); VectorNormalize( dirToTarget ); vectoangles( dirToTarget, self->turretAim ); //fire at target FireWeapon( self ); G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); return; } } } G_CreepSlow( self ); } //================================================================================== #define HOVEL_TRACE_DEPTH 128.0f /* ================ AHovel_Blocked Is this hovel entrance blocked? ================ */ qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit ) { vec3_t forward, normal, origin, start, end, angles, hovelMaxs; vec3_t mins, maxs; float displacement; trace_t tr; BG_FindBBoxForBuildable( BA_A_HOVEL, NULL, hovelMaxs ); BG_FindBBoxForClass( player->client->ps.stats[ STAT_PCLASS ], mins, maxs, NULL, NULL, NULL ); VectorCopy( hovel->s.origin2, normal ); AngleVectors( hovel->s.angles, forward, NULL, NULL ); VectorInverse( forward ); displacement = VectorMaxComponent( maxs ) + VectorMaxComponent( hovelMaxs ) + 1.0f; VectorMA( hovel->s.origin, displacement, forward, origin ); VectorCopy( hovel->s.origin, start ); VectorCopy( origin, end ); // see if there's something between the hovel and its exit // (eg built right up against a wall) trap_Trace( &tr, start, NULL, NULL, end, player->s.number, MASK_PLAYERSOLID ); if( tr.fraction < 1.0f ) return qtrue; vectoangles( forward, angles ); VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start ); //compute a place up in the air to start the real trace trap_Trace( &tr, origin, mins, maxs, start, player->s.number, MASK_PLAYERSOLID ); VectorMA( origin, ( HOVEL_TRACE_DEPTH * tr.fraction ) - 1.0f, normal, start ); VectorMA( origin, -HOVEL_TRACE_DEPTH, normal, end ); trap_Trace( &tr, start, mins, maxs, end, player->s.number, MASK_PLAYERSOLID ); VectorCopy( tr.endpos, origin ); trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID ); if( provideExit ) { G_SetOrigin( player, origin ); VectorCopy( origin, player->client->ps.origin ); // nudge VectorMA( normal, 200.0f, forward, player->client->ps.velocity ); G_SetClientViewAngle( player, angles ); } if( tr.fraction < 1.0f ) return qtrue; else return qfalse; } /* ================ APropHovel_Blocked Wrapper to test a hovel placement for validity ================ */ static qboolean APropHovel_Blocked( vec3_t origin, vec3_t angles, vec3_t normal, gentity_t *player ) { gentity_t hovel; VectorCopy( origin, hovel.s.origin ); VectorCopy( angles, hovel.s.angles ); VectorCopy( normal, hovel.s.origin2 ); return AHovel_Blocked( &hovel, player, qfalse ); } /* ================ AHovel_Use Called when an alien uses a hovel ================ */ void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t hovelOrigin, hovelAngles, inverseNormal; if( self->spawned && G_FindOvermind( self ) ) { if( self->active ) { //this hovel is in use G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_OCCUPIED ); } else if( ( ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ) || ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) && activator->health > 0 && self->health > 0 ) { if( AHovel_Blocked( self, activator, qfalse ) ) { //you can get in, but you can't get out G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); return; } self->active = qtrue; G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); //prevent lerping activator->client->ps.eFlags ^= EF_TELEPORT_BIT; activator->client->ps.eFlags |= EF_NODRAW; G_UnlaggedClear( activator ); activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING; activator->client->hovel = self; self->builder = activator; // Cancel pending suicides activator->suicideTime = 0; VectorCopy( self->s.pos.trBase, hovelOrigin ); VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin ); VectorCopy( self->s.origin2, inverseNormal ); VectorInverse( inverseNormal ); vectoangles( inverseNormal, hovelAngles ); VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin ); G_SetOrigin( activator, hovelOrigin ); VectorCopy( hovelOrigin, activator->client->ps.origin ); G_SetClientViewAngle( activator, hovelAngles ); } } } /* ================ AHovel_Think Think for alien hovel ================ */ void AHovel_Think( gentity_t *self ) { self->powered = G_IsOvermindBuilt( ); if( self->spawned ) { if( self->active ) G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); else G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); } G_CreepSlow( self ); self->nextthink = level.time + 200; } /* ================ AHovel_Die Die for alien hovel ================ */ void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { vec3_t dir; buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; VectorCopy( self->s.pos.trBase, new->origin ); VectorCopy( self->s.angles, new->angles ); VectorCopy( self->s.origin2, new->origin2 ); VectorCopy( self->s.angles2, new->angles2 ); new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; new->next = NULL; G_LogBuild( new ); VectorCopy( self->s.origin2, dir ); //do a bit of radius damage G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); self->s.eFlags &= ~EF_FIRING; //prevent any firing effects self->timestamp = level.time; self->think = ASpawn_Melt; self->nextthink = level.time + 500; //wait .5 seconds before damaging others self->die = nullDieFunction; //if the hovel is occupied free the occupant if( self->active ) { gentity_t *builder = self->builder; vec3_t newOrigin; vec3_t newAngles; VectorCopy( self->s.angles, newAngles ); newAngles[ ROLL ] = 0; VectorCopy( self->s.origin, newOrigin ); VectorMA( newOrigin, 1.0f, self->s.origin2, newOrigin ); //prevent lerping builder->client->ps.eFlags ^= EF_TELEPORT_BIT; builder->client->ps.eFlags &= ~EF_NODRAW; G_UnlaggedClear( builder ); G_SetOrigin( builder, newOrigin ); VectorCopy( newOrigin, builder->client->ps.origin ); G_SetClientViewAngle( builder, newAngles ); //client leaves hovel builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; } self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { G_TeamCommand( PTE_ALIENS, va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ) ); G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ); //ROTAXfun AddScore( attacker, -5 ); } else AddScore( attacker, 1 ); G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", attacker->client->ps.clientNum, self->s.modelindex, mod, attacker->client->pers.netname, BG_FindNameForBuildable( self->s.modelindex ), modNames[ mod ] ); } } /* ROTAX ================ ANone_Think Think for alien passive structures ================ */ void ANone_Think( gentity_t *self ) { G_CreepSlow( self ); self->nextthink = level.time + 200; } /* ROTAX ================ ABush_Die Die for alien bush ================ */ void ABush_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { vec3_t dir; vec3_t range = { 200.0f, 200.0f, 200.0f }; int entityList[ MAX_GENTITIES ]; vec3_t mins, maxs; int i, total_entities; gentity_t *target; buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; VectorCopy( self->s.pos.trBase, new->origin ); VectorCopy( self->s.angles, new->angles ); VectorCopy( self->s.origin2, new->origin2 ); VectorCopy( self->s.angles2, new->angles2 ); new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; new->next = NULL; G_LogBuild( new ); VectorCopy( self->s.origin2, dir ); if ( self->spawned ) { // Grab all entities around us VectorAdd(self->s.origin, range, maxs); VectorSubtract(self->s.origin, range, mins); total_entities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES); // Loop entities looking for an enemy body for(i=0; iclient && target->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS) { if (G_Visible( self, target )) { if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, target->client->ps.stats ) ) continue; if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, target->client->ps.stats ) ) continue; if( !( target->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) { target->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; target->client->lastPoisonCloudedTime = level.time; target->client->lastPoisonCloudedClient = self; trap_SendServerCommand( target->client->ps.clientNum, "poisoncloud" ); } } } } } //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); self->s.eFlags &= ~EF_FIRING; //prevent any firing effects self->timestamp = level.time; self->think = ASpawn_Melt; self->nextthink = level.time + 500; //wait .5 seconds before damaging others self->die = nullDieFunction; self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { G_TeamCommand( PTE_ALIENS, va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ) ); G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ); //ROTAXfun AddScore( attacker, -5 ); } else { //ROTAXfun AddScore( attacker, 3 ); } G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", attacker->client->ps.clientNum, self->s.modelindex, mod, attacker->client->pers.netname, BG_FindNameForBuildable( self->s.modelindex ), modNames[ mod ] ); } } /*ROTAX ================ ATree_Die Called when an alien tree dies ================ */ void ATree_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; VectorCopy( self->s.pos.trBase, new->origin ); VectorCopy( self->s.angles, new->angles ); VectorCopy( self->s.origin2, new->origin2 ); VectorCopy( self->s.angles2, new->angles2 ); new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; new->next = NULL; G_LogBuild( new ); G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->think = ASpawn_Melt; self->s.eFlags &= ~EF_FIRING; //prevent any firing effects if( self->spawned ) { self->nextthink = level.time + 15000; self->r.maxs[ 2 ] = 0; //self->r.maxs[ 0 ] = 0; //self->r.maxs[ 1 ] = 0; trap_LinkEntity( self ); } else self->nextthink = level.time; //blast immediately if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { G_TeamCommand( PTE_ALIENS, va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ) ); G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ); //ROTAXfun AddScore( attacker, -5 ); } else AddScore( attacker, 1 ); G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", attacker->client->ps.clientNum, self->s.modelindex, mod, attacker->client->pers.netname, BG_FindNameForBuildable( self->s.modelindex ), modNames[ mod ] ); } } //================================================================================== /* ================ ABooster_Touch Called when an alien touches a booster ================ */ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { gclient_t *client = other->client; if( other->flags & FL_NOTARGET ) return; // notarget cancels even beneficial effects? if( !self->spawned || self->health <= 0 ) return; if( !G_FindOvermind( self ) ) return; if( !client ) return; if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) return; //only allow boostage once every 30 seconds if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time ) return; if( !( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) ) { client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; client->lastBoostedTime = level.time; } } //ROTAXfun /* ================ AJumppad_Touch Called when an alien touches a jumppad ================ */ void AJumppad_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { gclient_t *client = other->client; vec3_t vel; if( other->flags & FL_NOTARGET ) return; // notarget cancels even beneficial effects? if( !self->spawned || self->health <= 0 ) return; if( !G_FindOvermind( self ) ) return; if( !client ) return; //Works for humans too :) //if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) // return; //Melo by se dit jen pokud na tom stojim, ne kdyz se jen dotykam (stojim na tom, nebo saham hlavou) // if (client->ps.groundEntityNum != self->s.number && self->s.groundEntityNum != client->ps.clientNum) if (client->ps.groundEntityNum != self->s.number) return; vel[0] = 0; vel[1] = 0; // if (client->ps.groundEntityNum == self->s.number) vel[2] = g_rotaxJumppadPower.integer; //Melo by to strilet smerem kam je natocenej model, ne jen nahoru (ale bacha na nenataceci bboxy, strilelo by to dolu, ale kdyz bych na tom stal zeshora tak by to semnou praskalo do jumppadu) /* ze by to neumelo zapornou rychlost? if (self->s.groundEntityNum == client->ps.clientNum) vel[2] = vel[2] - g_rotaxJumppadPower.integer;*/ VectorAdd( client->ps.velocity, vel, client->ps.velocity ); G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); } //================================================================================== #define TRAPPER_ACCURACY 10 // lower is better /* ================ ATrapper_FireOnEnemy Used by ATrapper_Think to fire at enemy ================ */ void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range ) { gentity_t *enemy = self->enemy; vec3_t dirToTarget; vec3_t halfAcceleration, thirdJerk; float distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex ); int lowMsec = 0; int highMsec = (int)( ( ( ( distanceToTarget * LOCKBLOB_SPEED ) + ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) / ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f ); VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration ); VectorScale( enemy->jerk, 1.0f / 3.0f, thirdJerk ); // highMsec and lowMsec can only move toward // one another, so the loop must terminate while( highMsec - lowMsec > TRAPPER_ACCURACY ) { int partitionMsec = ( highMsec + lowMsec ) / 2; float time = (float)partitionMsec / 1000.0f; float projectileDistance = LOCKBLOB_SPEED * time; VectorMA( enemy->s.pos.trBase, time, enemy->s.pos.trDelta, dirToTarget ); VectorMA( dirToTarget, time * time, halfAcceleration, dirToTarget ); VectorMA( dirToTarget, time * time * time, thirdJerk, dirToTarget ); VectorSubtract( dirToTarget, self->s.pos.trBase, dirToTarget ); distanceToTarget = VectorLength( dirToTarget ); if( projectileDistance < distanceToTarget ) lowMsec = partitionMsec; else if( projectileDistance > distanceToTarget ) highMsec = partitionMsec; else if( projectileDistance == distanceToTarget ) break; // unlikely to happen } VectorNormalize( dirToTarget ); vectoangles( dirToTarget, self->turretAim ); //fire at target FireWeapon( self ); G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->count = level.time + firespeed; } /* ================ ATrapper_CheckTarget Used by ATrapper_Think to check enemies for validity ================ */ qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range ) { vec3_t distance; trace_t trace; if( !target ) // Do we have a target? return qfalse; if( !target->inuse ) // Does the target still exist? return qfalse; if( target == self ) // is the target us? return qfalse; if( !target->client ) // is the target a bot or player? return qfalse; if( target->flags & FL_NOTARGET ) // is the target cheating? return qfalse; if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us? return qfalse; if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive? return qfalse; if( target->health <= 0 ) // is the target still alive? return qfalse; if( target->client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) // locked? return qfalse; VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, distance ); if( VectorLength( distance ) > range ) // is the target within range? return qfalse; //only allow a narrow field of "vision" VectorNormalize( distance ); //is now direction of target if( DotProduct( distance, self->s.origin2 ) < LOCKBLOB_DOT ) return qfalse; trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT ); if ( trace.contents & CONTENTS_SOLID ) // can we see the target? return qfalse; return qtrue; } /* ================ ATrapper_FindEnemy Used by ATrapper_Think to locate enemy gentities ================ */ void ATrapper_FindEnemy( gentity_t *ent, int range ) { gentity_t *target; //iterate through entities for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ ) { //if target is not valid keep searching if( !ATrapper_CheckTarget( ent, target, range ) ) continue; //we found a target ent->enemy = target; return; } //couldn't find a target ent->enemy = NULL; } /* ================ ATrapper_Think think function for Alien Defense ================ */ void ATrapper_Think( gentity_t *self ) { int range = BG_FindRangeForBuildable( self->s.modelindex ); int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); self->powered = G_IsOvermindBuilt( ); G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //if there is no creep nearby die if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } if( self->spawned && G_FindOvermind( self ) ) { //if the current target is not valid find a new one if( !ATrapper_CheckTarget( self, self->enemy, range ) ) ATrapper_FindEnemy( self, range ); //if a new target cannot be found don't do anything if( !self->enemy ) return; //if we are pointing at our target and we can fire shoot it if( self->count < level.time ) ATrapper_FireOnEnemy( self, firespeed, range ); } } //================================================================================== /* ================ HRepeater_Think Think for human power repeater ================ */ void HRepeater_Think( gentity_t *self ) { int i; qboolean reactor = qfalse; gentity_t *ent; if( self->spawned ) { //iterate through entities for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; if( ent->s.modelindex == BA_H_REACTOR && ent->spawned ) reactor = qtrue; } } if( G_NumberOfDependants( self ) == 0 ) { //if no dependants for x seconds then disappear if( self->count < 0 ) self->count = level.time; else if( self->count > 0 && ( ( level.time - self->count ) > REPEATER_INACTIVE_TIME ) ) G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); } else self->count = -1; self->powered = reactor; self->nextthink = level.time + POWER_REFRESH_TIME; } /* ================ HRepeater_Use Use for human power repeater ================ */ void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if( self->health <= 0 ) return; if( !self->spawned ) return; if( other ) G_GiveClientMaxAmmo( other, qtrue ); } /* ROTAX ================ HNone_Think Think function for passive structures ================ */ void HNone_Think( gentity_t *self ) { //self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); self->nextthink = level.time + 200; } #define DCC_ATTACK_PERIOD 10000 /* ================ HReactor_Think Think function for Human Reactor ================ */ void HReactor_Think( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE }; vec3_t mins, maxs; int i, num; gentity_t *enemy, *tent; VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); if( self->spawned && ( self->health > 0 ) ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; if( enemy->flags & FL_NOTARGET ) continue; if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { self->timestamp = level.time; G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE, REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS ); tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); VectorCopy( self->s.pos.trBase, tent->s.origin2 ); tent->s.generic1 = self->s.number; //src tent->s.clientNum = enemy->s.number; //dest } } //reactor under attack if( self->health < self->lastHealth && level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) { level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; G_BroadcastEvent( EV_DCC_ATTACK, 0 ); } self->lastHealth = self->health; } self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } //================================================================================== /* ================ HArmoury_Activate Called when a human activates an Armoury ================ */ void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator ) { if( self->spawned ) { //only humans can activate this if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) return; //if this is powered then call the armoury menu if( self->powered ) G_TriggerMenu( activator->client->ps.clientNum, MN_H_ARMOURY ); else G_TriggerMenu( activator->client->ps.clientNum, MN_H_NOTPOWERED ); } } /* ================ HArmoury_Think Think for armoury ================ */ void HArmoury_Think( gentity_t *self ) { //make sure we have power self->nextthink = level.time + POWER_REFRESH_TIME; self->powered = G_FindPower( self ); } //================================================================================== /* ================ HDCC_Think Think for dcc ================ */ void HDCC_Think( gentity_t *self ) { //make sure we have power self->nextthink = level.time + POWER_REFRESH_TIME; self->powered = G_FindPower( self ); } //================================================================================== /* ================ HMedistat_Think think function for Human Medistation ================ */ void HMedistat_Think( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t mins, maxs; int i, num; gentity_t *player; qboolean occupied = qfalse; self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //make sure we have power if( !( self->powered = G_FindPower( self ) ) ) { if( self->active ) { G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); self->active = qfalse; self->enemy = NULL; } self->nextthink = level.time + POWER_REFRESH_TIME; return; } if( self->spawned ) { VectorAdd( self->s.origin, self->r.maxs, maxs ); VectorAdd( self->s.origin, self->r.mins, mins ); mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ]; maxs[ 2 ] += 60; //player height //if active use the healing idle if( self->active ) G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); //check if a previous occupier is still here num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { player = &g_entities[ entityList[ i ] ]; if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && player->client->ps.pm_type != PM_DEAD && self->enemy == player ) occupied = qtrue; } } if( !occupied ) { self->enemy = NULL; //look for something to heal for( i = 0; i < num; i++ ) { player = &g_entities[ entityList[ i ] ]; if( player->flags & FL_NOTARGET ) continue; // notarget cancels even beneficial effects? if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && player->client->ps.pm_type != PM_DEAD ) { self->enemy = player; //start the heal anim if( !self->active ) { G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->active = qtrue; } } else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats ); } } } //nothing left to heal so go back to idling if( !self->enemy && self->active ) { G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); self->active = qfalse; } else if( self->enemy ) //heal! { if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED ) self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; self->enemy->health++; //if they're completely healed, give them a medkit if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] && !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); } } } //================================================================================== /* ================ HMGTurret_TrackEnemy Used by HMGTurret_Think to track enemy location ================ */ qboolean HMGTurret_TrackEnemy( gentity_t *self ) { vec3_t dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal; vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; float temp, rotAngle; float accuracyTolerance, angularSpeed; if( self->lev1Grabbed ) { //can't turn fast if grabbed accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE; angularSpeed = MGTURRET_GRAB_ANGULARSPEED; } else if( self->dcced ) { accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE; angularSpeed = MGTURRET_DCC_ANGULARSPEED; } else { accuracyTolerance = MGTURRET_ACCURACYTOLERANCE; angularSpeed = MGTURRET_ANGULARSPEED; } VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); VectorNormalize( dirToTarget ); CrossProduct( self->s.origin2, refNormal, xNormal ); VectorNormalize( xNormal ); rotAngle = RAD2DEG( acos( DotProduct( self->s.origin2, refNormal ) ) ); RotatePointAroundVector( dttAdjusted, xNormal, dirToTarget, rotAngle ); vectoangles( dttAdjusted, angleToTarget ); angularDiff[ PITCH ] = AngleSubtract( self->s.angles2[ PITCH ], angleToTarget[ PITCH ] ); angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] ); //if not pointing at our target then move accordingly if( angularDiff[ PITCH ] < (-accuracyTolerance) ) self->s.angles2[ PITCH ] += angularSpeed; else if( angularDiff[ PITCH ] > accuracyTolerance ) self->s.angles2[ PITCH ] -= angularSpeed; else self->s.angles2[ PITCH ] = angleToTarget[ PITCH ]; //disallow vertical movement past a certain limit temp = fabs( self->s.angles2[ PITCH ] ); if( temp > 180 ) temp -= 360; if( temp < -MGTURRET_VERTICALCAP ) self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP; //if not pointing at our target then move accordingly if( angularDiff[ YAW ] < (-accuracyTolerance) ) self->s.angles2[ YAW ] += angularSpeed; else if( angularDiff[ YAW ] > accuracyTolerance ) self->s.angles2[ YAW ] -= angularSpeed; else self->s.angles2[ YAW ] = angleToTarget[ YAW ]; AngleVectors( self->s.angles2, dttAdjusted, NULL, NULL ); RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle ); vectoangles( dirToTarget, self->turretAim ); //if pointing at our target return true if( abs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance && abs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance ) return qtrue; return qfalse; } /* ================ HMGTurret_CheckTarget Used by HMGTurret_Think to check enemies for validity ================ */ qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted ) { trace_t trace; gentity_t *traceEnt; if( !target ) return qfalse; if( target->flags & FL_NOTARGET ) return qfalse; if( !target->client ) return qfalse; if( target->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) return qfalse; if( target->health <= 0 ) return qfalse; if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE ) return qfalse; //some turret has already selected this target if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted ) return qfalse; trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT ); traceEnt = &g_entities[ trace.entityNum ]; if( !traceEnt->client ) return qfalse; if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) return qfalse; return qtrue; } /* ================ HMGTurret_FindEnemy Used by HMGTurret_Think to locate enemy gentities ================ */ void HMGTurret_FindEnemy( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range; vec3_t mins, maxs; int i, num; gentity_t *target; VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); //find aliens num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { target = &g_entities[ entityList[ i ] ]; if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //if target is not valid keep searching if( !HMGTurret_CheckTarget( self, target, qfalse ) ) continue; //we found a target self->enemy = target; return; } } if( self->dcced ) { //check again, this time ignoring painted targets for( i = 0; i < num; i++ ) { target = &g_entities[ entityList[ i ] ]; if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //if target is not valid keep searching if( !HMGTurret_CheckTarget( self, target, qtrue ) ) continue; //we found a target self->enemy = target; return; } } } //couldn't find a target self->enemy = NULL; } /* ================ HMGTurret_Think Think function for MG turret ================ */ void HMGTurret_Think( gentity_t *self ) { int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //used for client side muzzle flashes self->s.eFlags &= ~EF_FIRING; //if not powered don't do anything and check again for power next think if( !( self->powered = G_FindPower( self ) ) ) { self->nextthink = level.time + POWER_REFRESH_TIME; return; } if( self->spawned ) { //find a dcc for self self->dcced = G_FindDCC( self ); //if the current target is not valid find a new one if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) ) { if( self->enemy ) self->enemy->targeted = NULL; HMGTurret_FindEnemy( self ); } //if a new target cannot be found don't do anything if( !self->enemy ) return; self->enemy->targeted = self; //if we are pointing at our target and we can fire shoot it if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) ) { //fire at target FireWeapon( self ); self->s.eFlags |= EF_FIRING; G_AddEvent( self, EV_FIRE_WEAPON, 0 ); G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->count = level.time + firespeed; } } } //================================================================================== /* ================ HTeslaGen_Think Think function for Tesla Generator ================ */ void HTeslaGen_Think( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range; vec3_t mins, maxs; vec3_t dir; int i, num; gentity_t *enemy; self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //if not powered don't do anything and check again for power next think if( !( self->powered = G_FindPower( self ) ) || !( self->dcced = G_FindDCC( self ) ) ) { self->s.eFlags &= ~EF_FIRING; self->nextthink = level.time + POWER_REFRESH_TIME; return; } if( self->spawned && self->count < level.time ) { //used to mark client side effects self->s.eFlags &= ~EF_FIRING; VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); //find aliens num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { enemy = &g_entities[ entityList[ i ] ]; if( enemy->flags & FL_NOTARGET ) continue; if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && enemy->health > 0 && Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE ) { VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir ); VectorNormalize( dir ); vectoangles( dir, self->turretAim ); //fire at target FireWeapon( self ); } } if( self->s.eFlags & EF_FIRING ) { G_AddEvent( self, EV_FIRE_WEAPON, 0 ); //doesn't really need an anim //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->count = level.time + TESLAGEN_REPEAT; } } } //================================================================================== /* ================ HSpawn_Disappear Called when a human spawn is destroyed before it is spawned think function ================ */ void HSpawn_Disappear( gentity_t *self ) { vec3_t dir; // we don't have a valid direction, so just point straight up dir[ 0 ] = dir[ 1 ] = 0; dir[ 2 ] = 1; self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed self->timestamp = level.time; self->think = freeBuildable; self->nextthink = level.time + 100; self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink } /* ================ HSpawn_blast Called when a human spawn explodes think function ================ */ void HSpawn_Blast( gentity_t *self ) { vec3_t dir; // we don't have a valid direction, so just point straight up dir[ 0 ] = dir[ 1 ] = 0; dir[ 2 ] = 1; self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); self->timestamp = level.time; //do some radius damage G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, self->splashMethodOfDeath ); self->think = freeBuildable; self->nextthink = level.time + 100; self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink } /* ================ HSpawn_die Called when a human spawn dies ================ */ void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; VectorCopy( self->s.pos.trBase, new->origin ); VectorCopy( self->s.angles, new->angles ); VectorCopy( self->s.origin2, new->origin2 ); VectorCopy( self->s.angles2, new->angles2 ); new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ? BF_TEAMKILLED : BF_DESTROYED; new->next = NULL; G_LogBuild( new ); //pretty events and cleanup G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->powered = qfalse; //free up power //prevent any firing effects and cancel structure protection self->s.eFlags &= ~( EF_FIRING | EF_DBUILDER ); if( self->spawned ) { self->think = HSpawn_Blast; self->nextthink = level.time + HUMAN_DETONATION_DELAY; } else { self->think = HSpawn_Disappear; self->nextthink = level.time; //blast immediately } if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { if( self->s.modelindex == BA_H_REACTOR ) G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue ); else if( self->s.modelindex == BA_H_SPAWN ) G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue ); //ROTAX if (g_suddenDeath.integer == 1 && self->s.modelindex == BA_H_SPAWN) { G_TeamCommand( PTE_ALIENS, va( "tchat -1 \"^4->NODE DOWN^7 by %s^7\"", attacker->client->pers.netname ) );//rotaxfun bubliny pridan parametr -1 } //ROTAXfun if (self->s.modelindex == BA_H_MGTURRET) AddScore( attacker, 5 ); else if (self->s.modelindex == BA_H_TESLAGEN) AddScore( attacker, 5 ); else if (self->s.modelindex == BA_H_REACTOR) AddScore( attacker, 60 ); else if (self->s.modelindex == BA_H_SPAWN) AddScore( attacker, 20 ); else if (self->s.modelindex == BA_H_DCC) AddScore( attacker, 3 ); else if (self->s.modelindex == BA_H_ARMOURY) AddScore( attacker, 3 ); else if (self->s.modelindex == BA_H_MEDISTAT) AddScore( attacker, 5 ); else AddScore( attacker, 1 ); } else { G_TeamCommand( PTE_HUMANS, va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ) ); G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", BG_FindHumanNameForBuildable( self->s.modelindex ), attacker->client->pers.netname ); //ROTAXfun AddScore( attacker, -5 ); } G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", attacker->client->ps.clientNum, self->s.modelindex, mod, attacker->client->pers.netname, BG_FindNameForBuildable( self->s.modelindex ), modNames[ mod ] ); } } /* ================ HSpawn_Think Think for human spawn ================ */ void HSpawn_Think( gentity_t *self ) { gentity_t *ent; // spawns work without power self->powered = qtrue; if( self->spawned ) { //only suicide if at rest if( self->s.groundEntityNum ) { if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) { // If the thing blocking the spawn is a buildable, kill it. // If it's part of the map, kill self. if( ent->s.eType == ET_BUILDABLE ) { G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); } else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } else if( g_antiSpawnBlock.integer && ent->client && ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { //spawnblock protection if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 ) { //five seconds of countermeasures and we're still blocked //time for something more drastic G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT ); self->spawnBlockTime += 2000; //inappropriate MOD but prints an apt obituary } else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 ) //five seconds of blocked by client and... { //random direction vec3_t velocity; velocity[0] = crandom() * g_antiSpawnBlock.integer; velocity[1] = crandom() * g_antiSpawnBlock.integer; velocity[2] = g_antiSpawnBlock.integer; VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity ); trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" ); } else if( !self->spawnBlockTime ) self->spawnBlockTime = level.time; } if( ent->s.eType == ET_CORPSE ) G_FreeEntity( ent ); //quietly remove } else self->spawnBlockTime = 0; } //spawn under attack if( self->health < self->lastHealth && level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) { level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; G_BroadcastEvent( EV_DCC_ATTACK, 0 ); } self->lastHealth = self->health; } self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } //================================================================================== /* ============ G_BuildableTouchTriggers Find all trigger entities that a buildable touches. ============ */ void G_BuildableTouchTriggers( gentity_t *ent ) { int i, num; int touch[ MAX_GENTITIES ]; gentity_t *hit; trace_t trace; vec3_t mins, maxs; vec3_t bmins, bmaxs; static vec3_t range = { 10, 10, 10 }; // dead buildables don't activate triggers! if( ent->health <= 0 ) return; BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs ); VectorAdd( ent->s.origin, bmins, mins ); VectorAdd( ent->s.origin, bmaxs, maxs ); VectorSubtract( mins, range, mins ); VectorAdd( maxs, range, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); VectorAdd( ent->s.origin, bmins, mins ); VectorAdd( ent->s.origin, bmaxs, maxs ); for( i = 0; i < num; i++ ) { hit = &g_entities[ touch[ i ] ]; if( !hit->touch ) continue; if( !( hit->r.contents & CONTENTS_TRIGGER ) ) continue; //ignore buildables not yet spawned if( !ent->spawned ) continue; if( !trap_EntityContact( mins, maxs, hit ) ) continue; memset( &trace, 0, sizeof( trace ) ); if( hit->touch ) hit->touch( hit, ent, &trace ); } } /* =============== G_BuildableThink General think function for buildables =============== */ void G_BuildableThink( gentity_t *ent, int msec ) { int bHealth = BG_FindHealthForBuildable( ent->s.modelindex ); int bRegen = BG_FindRegenRateForBuildable( ent->s.modelindex ); int bTime = BG_FindBuildTimeForBuildable( ent->s.modelindex ); //pack health, power and dcc //toggle spawned flag for buildables if( !ent->spawned && ent->health > 0 && !level.pausedTime ) { if( ent->buildTime + bTime < level.time ) ent->spawned = qtrue; } ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK ); if( ent->s.generic1 < 0 ) ent->s.generic1 = 0; if( ent->powered ) ent->s.generic1 |= B_POWERED_TOGGLEBIT; if( ent->dcced ) ent->s.generic1 |= B_DCCED_TOGGLEBIT; if( ent->spawned ) ent->s.generic1 |= B_SPAWNED_TOGGLEBIT; if( ent->deconstruct ) ent->s.generic1 |= B_MARKED_TOGGLEBIT; ent->time1000 += msec; if( ent->time1000 >= 1000 ) { ent->time1000 -= 1000; if( !ent->spawned && ent->health > 0 ) ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) ); else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth && bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) ent->health += bRegen; if( ent->health > bHealth ) ent->health = bHealth; } if( ent->lev1Grabbed && ent->lev1GrabTime + LEVEL1_GRAB_TIME < level.time ) ent->lev1Grabbed = qfalse; if( ent->clientSpawnTime > 0 ) ent->clientSpawnTime -= msec; if( ent->clientSpawnTime < 0 ) ent->clientSpawnTime = 0; //check if this buildable is touching any triggers G_BuildableTouchTriggers( ent ); //fall back on normal physics routines G_Physics( ent, msec ); } /* =============== G_BuildableRange Check whether a point is within some range of a type of buildable =============== */ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) { int entityList[ MAX_GENTITIES ]; vec3_t range; vec3_t mins, maxs; int i, num; gentity_t *ent; VectorSet( range, r, r, r ); VectorAdd( origin, range, maxs ); VectorSubtract( origin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { ent = &g_entities[ entityList[ i ] ]; if( ent->s.eType != ET_BUILDABLE ) continue; if( ent->biteam == BIT_HUMANS && !ent->powered ) continue; if( ent->s.modelindex == buildable && ent->spawned ) return qtrue; } return qfalse; } /* =============== G_CompareBuildablesForRemoval qsort comparison function for a buildable removal list =============== */ static int G_CompareBuildablesForRemoval( const void *a, const void *b ) { int precedence[ ] = { BA_NONE, BA_A_BARRICADE, BA_A_ACIDTUBE, BA_A_TRAPPER, BA_A_HIVE, BA_A_BOOSTER, BA_A_HOVEL, BA_A_SPAWN, BA_A_OVERMIND, BA_A_FLAGA,//ROTAX BA_A_BAMBOO,//ROTAX BA_A_BRICKA,//ROTAX BA_A_BRIDGE,//ROTAX BA_A_TREE,//ROTAX BA_A_ROCK,//ROTAX BA_A_BUSH,//ROTAX BA_A_FLOWER,//ROTAX BA_A_GRASS,//ROTAX BA_A_JUMPPAD,//ROTAX BA_H_MGTURRET, BA_H_TESLAGEN, BA_H_DCC, BA_H_MEDISTAT, BA_H_ARMOURY, BA_H_SPAWN, BA_H_REPEATER, BA_H_PLANK,//ROTAX BA_H_CRATE,//ROTAX BA_H_PILLAR,//ROTAX BA_H_BRICKH,//ROTAX BA_H_FLAGH,//ROTAX BA_H_CONTAINER,//ROTAX BA_H_REACTOR }; gentity_t *buildableA, *buildableB; int i; int aPrecedence = 0, bPrecedence = 0; buildableA = *(gentity_t **)a; buildableB = *(gentity_t **)b; // If they're the same type then pick the one marked earliest if( buildableA->s.modelindex == buildableB->s.modelindex ) return buildableA->deconstructTime - buildableB->deconstructTime; for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ ) { if( buildableA->s.modelindex == precedence[ i ] ) aPrecedence = i; if( buildableB->s.modelindex == precedence[ i ] ) bPrecedence = i; } return aPrecedence - bPrecedence; } /* =============== G_FreeMarkedBuildables Free up build points for a team by deconstructing marked buildables =============== */ void G_FreeMarkedBuildables( void ) { int i; gentity_t *ent; buildHistory_t *new, *last; last = level.buildHistory; if( !g_markDeconstruct.integer ) return; // Not enabled, can't deconstruct anything for( i = 0; i < level.numBuildablesForRemoval; i++ ) { ent = level.markedBuildables[ i ]; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = -1; new->ent = NULL; Q_strncpyz( new->name, "", 12 ); new->buildable = ent->s.modelindex; VectorCopy( ent->s.pos.trBase, new->origin ); VectorCopy( ent->s.angles, new->angles ); VectorCopy( ent->s.origin2, new->origin2 ); VectorCopy( ent->s.angles2, new->angles2 ); new->fate = BF_DECONNED; new->next = NULL; new->marked = NULL; last = last->marked = new; G_FreeEntity( ent ); } } /* =============== G_SufficientBPAvailable Determine if enough build points can be released for the buildable and list the buildables that much be destroyed if this is the case =============== */ static qboolean G_SufficientBPAvailable( buildableTeam_t team, int buildPoints, buildable_t buildable ) { int i; int numBuildables = 0; int pointsYielded = 0; gentity_t *ent; qboolean unique = BG_FindUniqueTestForBuildable( buildable ); int remainingBP, remainingSpawns; if( team == BIT_ALIENS ) { remainingBP = level.alienBuildPoints; remainingSpawns = level.numAlienSpawns; } else if( team == BIT_HUMANS ) { remainingBP = level.humanBuildPoints; remainingSpawns = level.numHumanSpawns; } else return qfalse; // Simple non-marking case if( !g_markDeconstruct.integer ) { if( remainingBP - buildPoints < 0 ) return qfalse; else return qtrue; } // Set buildPoints to the number extra that are required buildPoints -= remainingBP; level.numBuildablesForRemoval = 0; // Build a list of buildable entities for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( !ent->inuse ) continue; if( ent->health <= 0 ) continue; // Don't allow destruction of hovel with granger inside if( ent->s.modelindex == BA_A_HOVEL && ent->active ) continue; if( ent->biteam != team ) continue; // Prevent destruction of the last spawn if( remainingSpawns <= 1 ) { if( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_H_SPAWN ) continue; } // If it's a unique buildable, it can only be replaced by the same type if( unique && ent->s.modelindex != buildable ) continue; if( ent->deconstruct ) level.markedBuildables[ numBuildables++ ] = ent; } // We still need build points, but have no candidates for removal if( buildPoints > 0 && numBuildables == 0 ) return qfalse; // Sort the list qsort( level.markedBuildables, numBuildables, sizeof( level.markedBuildables[ 0 ] ), G_CompareBuildablesForRemoval ); // Do a pass looking for a buildable of the same type that we're // building and mark it (and only it) for destruction if found for( i = 0; i < numBuildables; i++ ) { ent = level.markedBuildables[ i ]; if( ent->s.modelindex == buildable ) { // If we're removing what we're building this will always work level.markedBuildables[ 0 ] = ent; level.numBuildablesForRemoval = 1; return qtrue; } } // Determine if there are enough markees to yield the required BP for( ; pointsYielded < buildPoints && level.numBuildablesForRemoval < numBuildables; level.numBuildablesForRemoval++ ) { ent = level.markedBuildables[ level.numBuildablesForRemoval ]; pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex ); } // Not enough points yielded if( pointsYielded < buildPoints ) { level.numBuildablesForRemoval = 0; return qfalse; } else { return qtrue; } } /* ================ G_CanBuild Checks to see if a buildable can be built ================ */ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ) { vec3_t angles; vec3_t entity_origin, normal; vec3_t mins, maxs; trace_t tr1, tr2, tr3; int i; itemBuildError_t reason = IBE_NONE; gentity_t *tempent; float minNormal; qboolean invert; int contents; playerState_t *ps = &ent->client->ps; int buildPoints; BG_FindBBoxForBuildable( buildable, mins, maxs ); BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 ); trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, ent->s.number, MASK_PLAYERSOLID ); trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID ); VectorCopy( entity_origin, origin ); VectorCopy( tr1.plane.normal, normal ); minNormal = BG_FindMinNormalForBuildable( buildable ); invert = BG_FindInvertNormalForBuildable( buildable ); //can we build at this angle? if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) ) reason = IBE_NORMAL; //ROTAXfun //if( tr1.entityNum != ENTITYNUM_WORLD ) // reason = IBE_NORMAL; //check there is enough room to spawn from (presuming this is a spawn) if( G_CheckSpawnPoint( -1, origin, normal, buildable, NULL ) != NULL ) reason = IBE_NORMAL; contents = trap_PointContents( entity_origin, -1 ); buildPoints = BG_FindBuildPointsForBuildable( buildable ); if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //alien criteria if( buildable == BA_A_HOVEL ) { vec3_t builderMins, builderMaxs; //this assumes the adv builder is the biggest thing that'll use the hovel BG_FindBBoxForClass( PCL_ALIEN_BUILDER0_UPG, builderMins, builderMaxs, NULL, NULL, NULL ); if( APropHovel_Blocked( origin, angles, normal, ent ) ) reason = IBE_HOVELEXIT; } //check there is creep near by for building on if( BG_FindCreepTestForBuildable( buildable ) ) { if( !G_IsCreepHere( entity_origin ) ) reason = IBE_NOCREEP; } //check permission to build here if( tr1.surfaceFlags & SURF_NOALIENBUILD || tr1.surfaceFlags & SURF_NOBUILD || contents & CONTENTS_NOALIENBUILD || contents & CONTENTS_NOBUILD ) reason = IBE_PERMISSION; //look for an Overmind for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) { if( tempent->s.eType != ET_BUILDABLE ) continue; if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned && tempent->health > 0 ) break; } //if none found... if( i >= level.num_entities && buildable != BA_A_OVERMIND ) reason = IBE_NOOVERMIND; //can we only have one of these? if( BG_FindUniqueTestForBuildable( buildable ) ) { for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) { if( tempent->s.eType != ET_BUILDABLE ) continue; if( tempent->s.modelindex == buildable && !tempent->deconstruct ) { switch( buildable ) { case BA_A_OVERMIND: //ROTAXfun - vraceno if (g_suddenDeath.integer == 1) //reason = IBE_OVERMIND_AFTER_SD; reason = IBE_OVERMIND; break; case BA_A_HOVEL: if (g_suddenDeath.integer == 1)//ROTAXfun reason = IBE_HOVEL_AFTER_SD; //reason = IBE_HOVEL; break; default: Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable ); break; } break; } } } if( !G_SufficientBPAvailable( BIT_ALIENS, buildPoints, buildable ) ) reason = IBE_NOASSERT; } else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { //human criteria if( !G_IsPowered( entity_origin ) ) { //tell player to build a repeater to provide power if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) reason = IBE_REPEATER; } //this buildable requires a DCC if( BG_FindDCCTestForBuildable( buildable ) && !G_IsDCCBuilt( ) ) reason = IBE_NODCC; //check that there is a parent reactor when building a repeater if( buildable == BA_H_REPEATER ) { for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) { if( tempent->s.eType != ET_BUILDABLE ) continue; if( tempent->s.modelindex == BA_H_REACTOR ) break; } if( i >= level.num_entities ) { //no reactor present //check for other nearby repeaters for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) { if( tempent->s.eType != ET_BUILDABLE ) continue; if( tempent->s.modelindex == BA_H_REPEATER && Distance( tempent->s.origin, entity_origin ) < REPEATER_BASESIZE ) { reason = IBE_RPTWARN2; break; } } if( reason == IBE_NONE ) reason = IBE_RPTWARN; } else if( G_IsPowered( entity_origin ) ) reason = IBE_RPTWARN2; } //check permission to build here if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD || contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD ) reason = IBE_PERMISSION; //can we only build one of these? if( BG_FindUniqueTestForBuildable( buildable ) ) { for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) { if( tempent->s.eType != ET_BUILDABLE ) continue; if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) { //ROTAXfun - vraceno if (g_suddenDeath.integer == 1) //reason = IBE_RC_AFTER_SD; reason = IBE_REACTOR; //ROTAXfun - vraceno break; } } } if( !G_SufficientBPAvailable( BIT_HUMANS, buildPoints, buildable ) ) reason = IBE_NOPOWER; } //this item does not fit here if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) ) return IBE_NOROOM; return reason; } /* ============== G_BuildingExists ============== */ qboolean G_BuildingExists( int bclass ) { int i; gentity_t *tempent; //look for an Armoury for (i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) { if( tempent->s.eType != ET_BUILDABLE ) continue; if( tempent->s.modelindex == bclass && tempent->health > 0 ) { return qtrue; } } return qfalse; } /* ================ G_Build Spawns a buildable ================ */ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ) { gentity_t *built; buildHistory_t *new; vec3_t normal; // initialise the buildhistory so other functions can use it if( builder && builder->client ) { new = G_Alloc( sizeof( buildHistory_t ) ); G_LogBuild( new ); } // Free existing buildables G_FreeMarkedBuildables( ); //spawn the buildable built = G_Spawn(); built->s.eType = ET_BUILDABLE; built->classname = BG_FindEntityNameForBuildable( buildable ); built->s.modelindex = buildable; //so we can tell what this is on the client side built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable ); BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs ); // detect the buildable's normal vector if( !builder->client ) { // initial base layout created by server if( builder->s.origin2[ 0 ] || builder->s.origin2[ 1 ] || builder->s.origin2[ 2 ] ) { VectorCopy( builder->s.origin2, normal ); } else if( BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY ) VectorSet( normal, 0.0f, 0.0f, -1.0f ); else VectorSet( normal, 0.0f, 0.0f, 1.0f ); } else { // in-game building by a player if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) { if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) VectorSet( normal, 0.0f, 0.0f, -1.0f ); else VectorCopy( builder->client->ps.grapplePoint, normal ); } else VectorSet( normal, 0.0f, 0.0f, 1.0f ); } // when building the initial layout, spawn the entity slightly off its // target surface so that it can be "dropped" onto it if( !builder->client ) VectorMA( origin, 1.0f, normal, origin ); built->health = 1; built->splashDamage = BG_FindSplashDamageForBuildable( buildable ); built->splashRadius = BG_FindSplashRadiusForBuildable( buildable ); built->splashMethodOfDeath = BG_FindMODForBuildable( buildable ); built->nextthink = BG_FindNextThinkForBuildable( buildable ); built->takedamage = qtrue; built->spawned = qfalse; built->buildTime = built->s.time = level.time; built->spawnBlockTime = 0; // build instantly in cheat mode if( builder->client && g_cheats.integer ) { built->health = BG_FindHealthForBuildable( buildable ); built->buildTime = built->s.time = level.time - BG_FindBuildTimeForBuildable( buildable ); } //things that vary for each buildable that aren't in the dbase switch( buildable ) { case BA_A_SPAWN: built->die = ASpawn_Die; built->think = ASpawn_Think; built->pain = ASpawn_Pain; break; case BA_A_BARRICADE: built->die = ABarricade_Die; built->think = ABarricade_Think; built->pain = ABarricade_Pain; break; case BA_A_BOOSTER: built->die = ABarricade_Die; built->think = ABarricade_Think; built->pain = ABarricade_Pain; built->touch = ABooster_Touch; break; case BA_A_ACIDTUBE: built->die = ABarricade_Die; built->think = AAcidTube_Think; built->pain = ASpawn_Pain; break; case BA_A_HIVE: built->die = ABarricade_Die; built->think = AHive_Think; built->pain = ASpawn_Pain; break; case BA_A_TRAPPER: built->die = ABarricade_Die; built->think = ATrapper_Think; built->pain = ASpawn_Pain; break; case BA_A_OVERMIND: built->die = ASpawn_Die; built->think = AOvermind_Think; built->pain = ASpawn_Pain; break; case BA_A_HOVEL: built->die = AHovel_Die; built->use = AHovel_Use; built->think = AHovel_Think; built->pain = ASpawn_Pain; break; //ROTAX case BA_A_BUSH: built->die = ABush_Die; built->think = ANone_Think; built->pain = ASpawn_Pain; break; case BA_A_TREE: built->die = ATree_Die; built->think = ANone_Think; built->pain = ASpawn_Pain; break; case BA_A_JUMPPAD: built->die = AHovel_Die; built->think = ANone_Think; built->pain = ASpawn_Pain; built->touch = AJumppad_Touch; break; case BA_A_FLAGA: case BA_A_BAMBOO: case BA_A_BRICKA: case BA_A_BRIDGE: case BA_A_ROCK: case BA_A_FLOWER: case BA_A_GRASS: built->die = AHovel_Die; built->think = ANone_Think; built->pain = ASpawn_Pain; break; case BA_H_SPAWN: built->die = HSpawn_Die; built->think = HSpawn_Think; break; case BA_H_MGTURRET: built->die = HSpawn_Die; built->think = HMGTurret_Think; break; case BA_H_TESLAGEN: built->die = HSpawn_Die; built->think = HTeslaGen_Think; break; case BA_H_ARMOURY: built->think = HArmoury_Think; built->die = HSpawn_Die; built->use = HArmoury_Activate; break; case BA_H_DCC: built->think = HDCC_Think; built->die = HSpawn_Die; break; case BA_H_MEDISTAT: built->think = HMedistat_Think; built->die = HSpawn_Die; break; case BA_H_REACTOR: built->think = HReactor_Think; built->pain = Reactor_ShowTeamDamage;//rotax built->die = HSpawn_Die; built->use = HRepeater_Use; built->powered = built->active = qtrue; break; case BA_H_REPEATER: built->think = HRepeater_Think; built->die = HSpawn_Die; built->use = HRepeater_Use; built->count = -1; break; case BA_H_PLANK://ROTAX case BA_H_CRATE://ROTAX case BA_H_PILLAR://ROTAX case BA_H_BRICKH://ROTAX case BA_H_FLAGH://ROTAX case BA_H_CONTAINER://ROTAX built->think = HNone_Think; built->die = HSpawn_Die; break; default: //erk break; } built->s.number = built - g_entities; built->r.contents = CONTENTS_BODY; built->clipmask = MASK_PLAYERSOLID; built->enemy = NULL; built->s.weapon = BG_FindProjTypeForBuildable( buildable ); if( builder->client ) { built->builtBy = builder->client->ps.clientNum; if( builder->client->pers.designatedBuilder ) { built->s.eFlags |= EF_DBUILDER; // designated builder protection } } else built->builtBy = -1; G_SetOrigin( built, origin ); // gently nudge the buildable onto the surface :) VectorScale( normal, -50.0f, built->s.pos.trDelta ); // set turret angles VectorCopy( builder->s.angles2, built->s.angles2 ); VectorCopy( angles, built->s.angles ); built->s.angles[ PITCH ] = angles[ PITCH ];//ROTAXfun 0.0f built->s.angles2[ YAW ] = angles[ YAW ]; built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable ); built->s.pos.trTime = level.time; built->physicsBounce = BG_FindBounceForBuildable( buildable ); built->s.groundEntityNum = -1; built->s.generic1 = (int)( ( (float)built->health / (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK ); if( built->s.generic1 < 0 ) built->s.generic1 = 0; if( BG_FindTeamForBuildable( built->s.modelindex ) == PTE_ALIENS ) { built->powered = qtrue; built->s.generic1 |= B_POWERED_TOGGLEBIT; } else if( ( built->powered = G_FindPower( built ) ) ) built->s.generic1 |= B_POWERED_TOGGLEBIT; if( ( built->dcced = G_FindDCC( built ) ) ) built->s.generic1 |= B_DCCED_TOGGLEBIT; built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT; VectorCopy( normal, built->s.origin2 ); G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 ); G_SetIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) ); if( built->builtBy >= 0 ) G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); trap_LinkEntity( built ); if( builder->client ) { builder->client->pers.statscounters.structsbuilt++; if( builder->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.structsbuilt++; } else if( builder->client->pers.teamSelection == PTE_HUMANS ) { level.humanStatsCounters.structsbuilt++; } } if( builder->client ) { G_TeamCommand( builder->client->pers.teamSelection, va( "print \"%s is ^2being built^7 by %s^7\n\"", BG_FindHumanNameForBuildable( built->s.modelindex ), builder->client->pers.netname ) ); G_LogPrintf("Build: %i %i 0: %s^7 is ^2building^7 %s\n", builder->client->ps.clientNum, built->s.modelindex, builder->client->pers.netname, BG_FindNameForBuildable( built->s.modelindex ) ); //ROTAXfun if (built->s.modelindex != BA_H_BRICKH && built->s.modelindex != BA_A_BRICKA && built->s.modelindex != BA_A_FLOWER) AddScore( builder, 2 ); } // ok we're all done building, so what we log here should be the final values if( builder && builder->client ) // log ingame building only { new = level.buildHistory; new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; new->ent = builder; new->name[ 0 ] = 0; new->buildable = buildable; VectorCopy( built->s.pos.trBase, new->origin ); VectorCopy( built->s.angles, new->angles ); VectorCopy( built->s.origin2, new->origin2 ); VectorCopy( built->s.angles2, new->angles2 ); new->fate = BF_BUILT; } if( builder && builder->client ) built->bdnumb = new->ID; else built->bdnumb = -1; return built; } /* ================= G_BuildIfValid ================= */ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) { float dist; vec3_t origin; dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); switch( G_CanBuild( ent, buildable, dist, origin ) ) { case IBE_NONE: //rotax pokud nema jmeno, nemuze stavet if (g_newbieNamePrefix.string[ 0 ] && !Q_stricmpn( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string))) { trap_SendServerCommand( ent-g_entities, "print \"Take a nick before trying to build. Type: /name your_nick\n\"" ); return qfalse; } else { G_Build( ent, buildable, origin, ent->s.apos.trBase ); } return qtrue; case IBE_NOASSERT: G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT ); return qfalse; case IBE_NOOVERMIND: G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND ); return qfalse; case IBE_NOCREEP: G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP ); return qfalse; case IBE_OVERMIND: G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND ); return qfalse; /*ROTAXfun case IBE_HOVEL: G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL ); return qfalse; case IBE_OVERMIND_AFTER_SD://ROTAXfun trap_SendServerCommand( ent-g_entities, "print \"You can build only one Overmind in sudden death\n\"" ); return qfalse; */ case IBE_HOVEL_AFTER_SD://ROTAXfun trap_SendServerCommand( ent-g_entities, "print \"You can build only one Hovel in sudden death\n\"" ); return qfalse; case IBE_HOVELEXIT: G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT ); return qfalse; case IBE_NORMAL: if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL ); else G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL ); return qfalse; case IBE_PERMISSION: if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL ); else G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL ); return qfalse; case IBE_REACTOR: G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR ); return qfalse; /*ROTAXfun - vraceno case IBE_RC_AFTER_SD://ROTAXfun trap_SendServerCommand( ent-g_entities, "print \"You can build only one Reactor in sudden death\n\"" ); return qfalse; */ case IBE_REPEATER: G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER ); return qfalse; case IBE_NOROOM: if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOM ); else G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOROOM ); return qfalse; case IBE_NOPOWER: G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER ); return qfalse; case IBE_NODCC: G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC ); return qfalse; case IBE_SPWNWARN: G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_TNODEWARN: G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_RPTWARN: G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_RPTWARN2: G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 ); return qfalse; default: break; } return qfalse; } /* ================ G_FinishSpawningBuildable Traces down to find where an item should rest, instead of letting them free fall from their spawn points ================ */ static void G_FinishSpawningBuildable( gentity_t *ent ) { trace_t tr; vec3_t dest; gentity_t *built; buildable_t buildable = ent->s.modelindex; built = G_Build( ent, buildable, ent->s.pos.trBase, ent->s.angles ); G_FreeEntity( ent ); built->takedamage = qtrue; built->spawned = qtrue; //map entities are already spawned built->health = BG_FindHealthForBuildable( buildable ); built->s.generic1 |= B_SPAWNED_TOGGLEBIT; // drop towards normal surface VectorScale( built->s.origin2, -4096.0f, dest ); VectorAdd( dest, built->s.origin, dest ); trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); if( tr.startsolid ) { G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", built->classname, vtos( built->s.origin ) ); G_FreeEntity( built ); return; } //point items in the correct direction VectorCopy( tr.plane.normal, built->s.origin2 ); // allow to ride movers built->s.groundEntityNum = tr.entityNum; G_SetOrigin( built, tr.endpos ); trap_LinkEntity( built ); } /* ============ G_SpawnBuildable Sets the clipping size and plants the object on the floor. Items can't be immediately dropped to floor, because they might be on an entity that hasn't spawned yet. ============ */ void G_SpawnBuildable( gentity_t *ent, buildable_t buildable ) { ent->s.modelindex = buildable; // some movers spawn on the second frame, so delay item // spawns until the third frame so they can ride trains ent->nextthink = level.time + FRAMETIME * 2; ent->think = G_FinishSpawningBuildable; } /* ============ G_CheckDBProtection Count how many designated builders are in both teams and if none found in some team, cancel protection for all structures of that team ============ */ void G_CheckDBProtection( void ) { int alienDBs = 0, humanDBs = 0, i; gentity_t *ent; // count designated builders for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++) { if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) ) continue; if( ent->client->pers.designatedBuilder) { if( ent->client->pers.teamSelection == PTE_HUMANS ) { humanDBs++; } else if( ent->client->pers.teamSelection == PTE_ALIENS ) { alienDBs++; } } } // both teams have designate builders, we're done if( alienDBs > 0 && humanDBs > 0 ) return; // cancel protection if needed for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++) { if( ent->s.eType != ET_BUILDABLE) continue; if( ( !alienDBs && ent->biteam == BIT_ALIENS ) || ( !humanDBs && ent->biteam == BIT_HUMANS ) ) { ent->s.eFlags &= ~EF_DBUILDER; } } } /* ============ G_LayoutSave ============ */ void G_LayoutSave( char *name ) { char map[ MAX_QPATH ]; char fileName[ MAX_OSPATH ]; fileHandle_t f; int len; int i; gentity_t *ent; char *s; trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); if( !map[ 0 ] ) { G_Printf( "LayoutSave( ): no map is loaded\n" ); return; } Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name ); len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); if( len < 0 ) { G_Printf( "layoutsave: could not open %s\n", fileName ); return; } G_Printf("layoutsave: saving layout to %s\n", fileName ); for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { ent = &level.gentities[ i ]; if( ent->s.eType != ET_BUILDABLE ) continue; s = va( "%i %f %f %f %f %f %f %f %f %f %f %f %f\n", ent->s.modelindex, ent->s.pos.trBase[ 0 ], ent->s.pos.trBase[ 1 ], ent->s.pos.trBase[ 2 ], ent->s.angles[ 0 ], ent->s.angles[ 1 ], ent->s.angles[ 2 ], ent->s.origin2[ 0 ], ent->s.origin2[ 1 ], ent->s.origin2[ 2 ], ent->s.angles2[ 0 ], ent->s.angles2[ 1 ], ent->s.angles2[ 2 ] ); trap_FS_Write( s, strlen( s ), f ); } trap_FS_FCloseFile( f ); } int G_LayoutList( const char *map, char *list, int len ) { // up to 128 single character layout names could fit in layouts char fileList[ ( MAX_CVAR_VALUE_STRING / 2 ) * 5 ] = {""}; char layouts[ MAX_CVAR_VALUE_STRING ] = {""}; int numFiles, i, fileLen = 0, listLen; int count = 0; char *filePtr; Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " ); numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat", fileList, sizeof( fileList ) ); filePtr = fileList; for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) { fileLen = strlen( filePtr ); listLen = strlen( layouts ); if( fileLen < 5 ) continue; // list is full, stop trying to add to it if( ( listLen + fileLen ) >= sizeof( layouts ) ) break; Q_strcat( layouts, sizeof( layouts ), filePtr ); listLen = strlen( layouts ); // strip extension and add space delimiter layouts[ listLen - 4 ] = ' '; layouts[ listLen - 3 ] = '\0'; count++; } if( count != numFiles ) { G_Printf( S_COLOR_YELLOW "WARNING: layout list was truncated to %d " "layouts, but %d layout files exist in layouts/%s/.\n", count, numFiles, map ); } Q_strncpyz( list, layouts, len ); return count + 1; } /* ============ G_LayoutSelect set level.layout based on g_layouts or g_layoutAuto ============ */ void G_LayoutSelect( void ) { char fileName[ MAX_OSPATH ]; char layouts[ MAX_CVAR_VALUE_STRING ]; char layouts2[ MAX_CVAR_VALUE_STRING ]; char *l; char map[ MAX_QPATH ]; char *s; int cnt = 0; int layoutNum; Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) ); trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); // one time use cvar trap_Cvar_Set( "g_layouts", "" ); // pick an included layout at random if no list has been provided if( !layouts[ 0 ] && g_layoutAuto.integer ) { G_LayoutList( map, layouts, sizeof( layouts ) ); } if( !layouts[ 0 ] ) return; Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); l = &layouts2[ 0 ]; layouts[ 0 ] = '\0'; s = COM_ParseExt( &l, qfalse ); while( *s ) { if( !Q_stricmp( s, "*BUILTIN*" ) ) { Q_strcat( layouts, sizeof( layouts ), s ); Q_strcat( layouts, sizeof( layouts ), " " ); cnt++; s = COM_ParseExt( &l, qfalse ); continue; } Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s ); if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 ) { Q_strcat( layouts, sizeof( layouts ), s ); Q_strcat( layouts, sizeof( layouts ), " " ); cnt++; } else G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s ); s = COM_ParseExt( &l, qfalse ); } if( !cnt ) { G_Printf( S_COLOR_RED "ERROR: none of the specified layouts could be " "found, using map default\n" ); return; } layoutNum = ( rand( ) % cnt ) + 1; cnt = 0; Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); l = &layouts2[ 0 ]; s = COM_ParseExt( &l, qfalse ); while( *s ) { Q_strncpyz( level.layout, s, sizeof( level.layout ) ); cnt++; if( cnt >= layoutNum ) break; s = COM_ParseExt( &l, qfalse ); } G_Printf("using layout \"%s\" from list ( %s)\n", level.layout, layouts ); } static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ) { gentity_t *builder; builder = G_Spawn( ); builder->client = 0; VectorCopy( origin, builder->s.pos.trBase ); VectorCopy( angles, builder->s.angles ); VectorCopy( origin2, builder->s.origin2 ); VectorCopy( angles2, builder->s.angles2 ); G_SpawnBuildable( builder, buildable ); } /* ============ G_InstantBuild This function is extremely similar to the few functions that place a buildable on map load. It exists because G_LayoutBuildItem takes a couple of frames to finish spawning it, so it's not truly instant Do not call this function immediately after the map loads - that's what G_LayoutBuildItem is for. ============ */ gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ) { gentity_t *builder, *built; trace_t tr; vec3_t dest; builder = G_Spawn( ); builder->client = 0; VectorCopy( origin, builder->s.pos.trBase ); VectorCopy( angles, builder->s.angles ); VectorCopy( origin2, builder->s.origin2 ); VectorCopy( angles2, builder->s.angles2 ); //old method didn't quite work out //builder->s.modelindex = buildable; //G_FinishSpawningBuildable( builder ); built = G_Build( builder, buildable, builder->s.pos.trBase, builder->s.angles ); G_FreeEntity( builder ); built->takedamage = qtrue; built->spawned = qtrue; //map entities are already spawned built->health = BG_FindHealthForBuildable( buildable ); built->s.generic1 |= B_SPAWNED_TOGGLEBIT; // drop towards normal surface VectorScale( built->s.origin2, -4096.0f, dest ); VectorAdd( dest, built->s.origin, dest ); trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); if( tr.startsolid ) { G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", built->classname, vtos( built->s.origin ) ); G_FreeEntity( built ); return NULL; } //point items in the correct direction VectorCopy( tr.plane.normal, built->s.origin2 ); // allow to ride movers built->s.groundEntityNum = tr.entityNum; G_SetOrigin( built, tr.endpos ); trap_LinkEntity( built ); return built; } /* ============ G_SpawnRevertedBuildable Given a buildhistory, try to replace the lost buildable ============ */ void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ) { vec3_t mins, maxs; int i, j, blockCount, blockers[ MAX_GENTITIES ]; gentity_t *targ, *built, *toRecontent[ MAX_GENTITIES ]; BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); VectorAdd( bh->origin, mins, mins ); VectorAdd( bh->origin, maxs, maxs ); blockCount = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); for( i = j = 0; i < blockCount; i++ ) { targ = g_entities + blockers[ i ]; if( targ->s.eType == ET_BUILDABLE ) G_FreeEntity( targ ); else if( targ->s.eType == ET_PLAYER ) { targ->r.contents = 0; // make it intangible toRecontent[ j++ ] = targ; // and remember it } } level.numBuildablesForRemoval = 0; built = G_InstantBuild( bh->buildable, bh->origin, bh->angles, bh->origin2, bh->angles2 ); if( built ) { built->r.contents = 0; built->think = G_CommitRevertedBuildable; built->nextthink = level.time; built->deconstruct = mark; } for( i = 0; i < j; i++ ) toRecontent[ i ]->r.contents = CONTENTS_BODY; } /* ============ G_CommitRevertedBuildable Check if there's anyone occupying me, and if not, become solid and operate as normal. Else, try to get rid of them. ============ */ void G_CommitRevertedBuildable( gentity_t *ent ) { gentity_t *targ; int i, n, occupants[ MAX_GENTITIES ]; vec3_t mins, maxs; int victims = 0; VectorAdd( ent->s.origin, ent->r.mins, mins ); VectorAdd( ent->s.origin, ent->r.maxs, maxs ); trap_UnlinkEntity( ent ); n = trap_EntitiesInBox( mins, maxs, occupants, MAX_GENTITIES ); trap_LinkEntity( ent ); for( i = 0; i < n; i++ ) { vec3_t gtfo; targ = g_entities + occupants[ i ]; if( targ->client ) { VectorSet( gtfo, crandom() * 150, crandom() * 150, random() * 150 ); VectorAdd( targ->client->ps.velocity, gtfo, targ->client->ps.velocity ); victims++; } } if( !victims ) { // we're in the clear! ent->r.contents = MASK_PLAYERSOLID; trap_LinkEntity( ent ); // relink // oh dear, manual think set switch( ent->s.modelindex ) { case BA_A_SPAWN: ent->think = ASpawn_Think; break; case BA_A_BARRICADE: case BA_A_BOOSTER: ent->think = ABarricade_Think; break; case BA_A_ACIDTUBE: ent->think = AAcidTube_Think; break; case BA_A_HIVE: ent->think = AHive_Think; break; case BA_A_TRAPPER: ent->think = ATrapper_Think; break; case BA_A_OVERMIND: ent->think = AOvermind_Think; break; case BA_A_HOVEL: ent->think = AHovel_Think; break; //ROTAX case BA_A_FLAGA: case BA_A_BAMBOO: case BA_A_BRICKA: case BA_A_BRIDGE: case BA_A_TREE: case BA_A_ROCK: case BA_A_BUSH: case BA_A_FLOWER: case BA_A_GRASS: case BA_A_JUMPPAD: ent->think = ANone_Think; break; case BA_H_SPAWN: ent->think = HSpawn_Think; break; case BA_H_MGTURRET: ent->think = HMGTurret_Think; break; case BA_H_TESLAGEN: ent->think = HTeslaGen_Think; break; case BA_H_ARMOURY: ent->think = HArmoury_Think; break; case BA_H_DCC: ent->think = HDCC_Think; break; case BA_H_MEDISTAT: ent->think = HMedistat_Think; break; case BA_H_REACTOR: ent->think = HReactor_Think; break; case BA_H_REPEATER: ent->think = HRepeater_Think; break; case BA_H_PLANK://ROTAX case BA_H_CRATE://ROTAX case BA_H_PILLAR://ROTAX case BA_H_BRICKH://ROTAX case BA_H_FLAGH://ROTAX case BA_H_CONTAINER://ROTAX ent->think = HNone_Think; break; } ent->nextthink = level.time + BG_FindNextThinkForBuildable( ent->s.modelindex ); // oh if only everything was that simple return; } #define REVERT_THINK_INTERVAL 50 ent->nextthink = level.time + REVERT_THINK_INTERVAL; } /* ============ G_RevertCanFit take a bhist and make sure you're not overwriting anything by placing it ============ */ qboolean G_RevertCanFit( buildHistory_t *bh ) { int i, num, blockers[ MAX_GENTITIES ]; vec3_t mins, maxs; gentity_t *targ; vec3_t dist; BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); VectorAdd( bh->origin, mins, mins ); VectorAdd( bh->origin, maxs, maxs ); num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { targ = g_entities + blockers[ i ]; if( targ->s.eType == ET_BUILDABLE ) { VectorSubtract( bh->origin, targ->s.pos.trBase, dist ); if( targ->s.modelindex == bh->buildable && VectorLength( dist ) < 10 && targ->health <= 0 ) continue; // it's the same buildable, hasn't blown up yet else return qfalse; // can't get rid of this one } else continue; } return qtrue; } /* ============ G_LayoutLoad load the layout .dat file indicated by level.layout and spawn buildables as if a builder was creating them ============ */ void G_LayoutLoad( void ) { fileHandle_t f; int len; char *layout; char map[ MAX_QPATH ]; int buildable = BA_NONE; vec3_t origin = { 0.0f, 0.0f, 0.0f }; vec3_t angles = { 0.0f, 0.0f, 0.0f }; vec3_t origin2 = { 0.0f, 0.0f, 0.0f }; vec3_t angles2 = { 0.0f, 0.0f, 0.0f }; char line[ MAX_STRING_CHARS ]; int i = 0; if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) ) return; trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ), &f, FS_READ ); if( len < 0 ) { G_Printf( "ERROR: layout %s could not be opened\n", level.layout ); return; } layout = G_Alloc( len + 1 ); trap_FS_Read( layout, len, f ); *( layout + len ) = '\0'; trap_FS_FCloseFile( f ); while( *layout ) { if( i >= sizeof( line ) - 1 ) { G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", va( "layouts/%s/%s.dat", map, level.layout ), line ); return; } line[ i++ ] = *layout; line[ i ] = '\0'; if( *layout == '\n' ) { i = 0; sscanf( line, "%d %f %f %f %f %f %f %f %f %f %f %f %f\n", &buildable, &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &angles[ 0 ], &angles[ 1 ], &angles[ 2 ], &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ], &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] ); if( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES ) G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); else G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in " " layout. skipping\n", buildable ); } layout++; } } void G_BaseSelfDestruct( pTeam_t team ) { int i; gentity_t *ent; for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { ent = &level.gentities[ i ]; if( ent->health <= 0 ) continue; if( ent->s.eType != ET_BUILDABLE ) continue; if( team == PTE_HUMANS && ent->biteam != BIT_HUMANS ) continue; if( team == PTE_ALIENS && ent->biteam != BIT_ALIENS ) continue; G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); } } int G_LogBuild( buildHistory_t *new ) { new->next = level.buildHistory; level.buildHistory = new; return G_CountBuildLog(); } int G_CountBuildLog( void ) { buildHistory_t *ptr, *mark; int i = 0, overflow; for( ptr = level.buildHistory; ptr; ptr = ptr->next, i++ ); if( i > g_buildLogMaxLength.integer ) { for( overflow = i - g_buildLogMaxLength.integer; overflow > 0; overflow-- ) { ptr = level.buildHistory; while( ptr->next ) { if( ptr->next->next ) ptr = ptr->next; else { while( ( mark = ptr->next ) ) { ptr->next = ptr->next->marked; G_Free( mark ); } } } } return g_buildLogMaxLength.integer; } return i; } char *G_FindBuildLogName( int id ) { buildHistory_t *ptr; for( ptr = level.buildHistory; ptr && ptr->ID != id; ptr = ptr->next ); if( ptr ) { if( ptr->ent ) { if( ptr->ent->client ) return ptr->ent->client->pers.netname; } else if( ptr->name[ 0 ] ) { return ptr->name; } } return ""; }