//rww - cheap check to see if an armed client is looking in our general direction qboolean NPC_SomeoneLookingAtMe(gentity_t *ent) { int i = 0; gentity_t *pEnt; while (i < MAX_CLIENTS) { pEnt = &g_entities[i]; if (pEnt && pEnt->inuse && pEnt->client && pEnt->client->sess.sessionTeam != TEAM_SPECTATOR && !(pEnt->client->ps.pm_flags & PMF_FOLLOW) && pEnt->s.weapon != WP_NONE) { if (trap_InPVS(ent->r.currentOrigin, pEnt->r.currentOrigin)) { if (InFOV( ent, pEnt, 30, 30 )) { //I'm in a 30 fov or so cone from this player.. that's enough I guess. return qtrue; } } } i++; } return qfalse; }
/* =========== Team_GetLocation Report a location for the player. Uses placed nearby target_location entities ============ */ gentity_t *Team_GetLocation(gentity_t *ent) { gentity_t *eloc, *best; float bestlen, len; vec3_t origin; best = NULL; bestlen = 3*8192.0*8192.0; VectorCopy( ent->r.currentOrigin, origin ); for (eloc = level.locationHead; eloc; eloc = eloc->nextTrain) { len = ( origin[0] - eloc->r.currentOrigin[0] ) * ( origin[0] - eloc->r.currentOrigin[0] ) + ( origin[1] - eloc->r.currentOrigin[1] ) * ( origin[1] - eloc->r.currentOrigin[1] ) + ( origin[2] - eloc->r.currentOrigin[2] ) * ( origin[2] - eloc->r.currentOrigin[2] ); if ( len > bestlen ) { continue; } if ( !trap_InPVS( origin, eloc->r.currentOrigin ) ) { continue; } bestlen = len; best = eloc; } return best; }
bool TurretComponent::TargetValid(Entity& target, bool newTarget) { if (!target.Get<ClientComponent>() || target.Get<SpectatorComponent>() || Entities::IsDead(target) || (target.oldEnt->flags & FL_NOTARGET) || !Entities::OnOpposingTeams(entity, target) || G_Distance(entity.oldEnt, target.oldEnt) > range || !trap_InPVS(entity.oldEnt->s.origin, target.oldEnt->s.origin)) { if (!newTarget) { turretLogger.Verbose("Target lost: Out of range or eliminated."); } return false; } // New targets require a line of sight. if (G_LineOfFire(entity.oldEnt, target.oldEnt)) { lastLineOfSightToTarget = level.time; } else if (newTarget) { return false; } // Give up on an existing target if there was no line of sight for a while. if (lastLineOfSightToTarget + GIVEUP_TARGET_TIME <= level.time) { turretLogger.Verbose("Giving up on target: No line of sight for %d ms.", level.time - lastLineOfSightToTarget ); return false; } return true; }
/** * @todo Move out of sg_team.c as it is not team-specific. */ gentity_t *GetCloseLocationEntity( gentity_t *ent ) { gentity_t *eloc, *best; float bestlen, len; best = nullptr; bestlen = 3.0f * 8192.0f * 8192.0f; for ( eloc = level.locationHead; eloc; eloc = eloc->nextPathSegment ) { len = DistanceSquared( ent->r.currentOrigin, eloc->r.currentOrigin ); if ( len > bestlen ) { continue; } if ( !trap_InPVS( ent->r.currentOrigin, eloc->r.currentOrigin ) ) { continue; } bestlen = len; best = eloc; } return best; }
/* =========== Team_GetLocation Report a location for the player. Uses placed nearby target_location entities ============ */ gentity_t *Team_GetLocation( gentity_t *ent ) { gentity_t *eloc, *best; float bestlen, len; best = NULL; bestlen = 3.0f * 8192.0f * 8192.0f; for ( eloc = level.locationHead; eloc; eloc = eloc->nextTrain ) { len = DistanceSquared( ent->r.currentOrigin, eloc->r.currentOrigin ); if ( len > bestlen ) { continue; } if ( !trap_InPVS( ent->r.currentOrigin, eloc->r.currentOrigin ) ) { continue; } bestlen = len; best = eloc; } return best; }
qboolean G_VisibleFromBinoculars ( gentity_t* viewer, gentity_t* ent, vec3_t origin ) { vec3_t vieworg; trace_t trace; VectorCopy(viewer->client->ps.origin, vieworg); vieworg[2] += viewer->client->ps.viewheight; if(!G_CullPointAndRadius( origin, 0 )) { return qfalse; } if(!trap_InPVS( vieworg, origin )) { return qfalse; } trap_Trace( &trace, vieworg, NULL, NULL, origin, viewer->s.number, MASK_SHOT ); /* if( ent && trace.entityNum != ent-g_entities ) { return qfalse; }*/ if( trace.fraction != 1.f ) { if( ent ) { if( trace.entityNum != ent->s.number ) { return qfalse; } else { return qtrue; } } else { return qfalse; } } return qtrue; }
/* ------------------------- NAVNEW_ClearPathBetweenPoints ------------------------- */ int NAVNEW_ClearPathBetweenPoints(vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int ignore, int clipmask) { trace_t trace; //Test if they're even conceivably close to one another if ( !trap_InPVS( start, end ) ) { return ENTITYNUM_WORLD; } trap_Trace( &trace, start, mins, maxs, end, ignore, clipmask ); return trace.entityNum; }
/** * @brief Find a beacon matching a pattern. * @return An ET_BEACON entity or nullptr. */ gentity_t *FindSimilar( const vec3_t origin, beaconType_t type, int data, int team, int owner, float radius, int eFlags, int eFlagsRelevant ) { int flags = BG_Beacon( type )->flags; for ( gentity_t *ent = nullptr; (ent = G_IterateEntities(ent)); ) { if ( ent->s.eType != ET_BEACON ) continue; if ( ent->s.bc_type != type ) continue; if ( ( ent->s.eFlags & eFlagsRelevant ) != ( eFlags & eFlagsRelevant ) ) continue; if( ent->s.bc_team != team ) continue; if ( ( flags & BCF_DATA_UNIQUE ) && ent->s.bc_data != data ) continue; if ( ent->s.eFlags & EF_BC_DYING ) continue; if ( flags & BCF_PER_TEAM ) {} else if( flags & BCF_PER_PLAYER ) { if( ent->s.bc_owner != owner ) continue; } else { if ( Distance( ent->s.origin, origin ) > radius ) continue; if ( !trap_InPVS( ent->s.origin, origin ) ) continue; } return ent; } return nullptr; }
void NPC_ShowDebugInfo (void) { if ( showBBoxes ) { gentity_t *found = NULL; vec3_t mins, maxs; while( (found = G_Find( found, FOFS(classname), "NPC" ) ) != NULL ) { if ( trap_InPVS( found->r.currentOrigin, g_entities[0].r.currentOrigin ) ) { VectorAdd( found->r.currentOrigin, found->r.mins, mins ); VectorAdd( found->r.currentOrigin, found->r.maxs, maxs ); G_Cube( mins, maxs, NPCDEBUG_RED, 0.25 ); } } } }
/* IsVisible: * Is player #1 visible by player #2 ? */ qboolean IsVisible(gentity_t * player1, gentity_t * player2, float maxrange) { vec3_t length; float distance; trace_t trace; // check for looking through non-transparent water if (!trap_InPVS(player1->client->ps.origin, player2->client->ps.origin)) return qfalse; trap_Trace(&trace, player1->client->ps.origin, NULL, NULL, player2->client->ps.origin, player1->s.clientNum, MASK_SOLID); VectorSubtract(player1->client->ps.origin, player2->client->ps.origin, length); distance = VectorLength(length); return ((maxrange == 0 || distance < maxrange) && trace.fraction == 1.0f); }
/** * @brief G_VisibleFromBinoculars_Box * @param[in] viewer * @param[in] ent * @param[in,out] origin * @param[in] mins * @param[in] maxs * @return */ qboolean G_VisibleFromBinoculars_Box(gentity_t *viewer, gentity_t *ent, vec3_t origin, vec3_t mins, vec3_t maxs) { vec3_t vieworg; trace_t trace; VectorCopy(viewer->client->ps.origin, vieworg); vieworg[2] += viewer->client->ps.viewheight; // check if head is visible if (ent->methodOfDeath != MOD_LANDMINE) { origin[2] += ent->client->ps.viewheight; } if (!G_CullPointAndRadius(origin, 0)) { return qfalse; } if (!trap_InPVS(vieworg, origin)) { return qfalse; } trap_Trace(&trace, vieworg, mins, maxs, origin, viewer->s.number, MASK_SHOT); if (trace.fraction != 1.f) { if (trace.entityNum != ent->s.number) { return qfalse; } else { return qtrue; } } return qtrue; }
//[CoOp] qboolean InPlayersPVS(vec3_t point) {//checks to see if this point is visible to all the players in the game. int Counter = 0; gentity_t *checkEnt = NULL; for(; Counter < level.maxclients ; Counter++ ) { checkEnt = &g_entities[Counter]; if ( !checkEnt->inuse || !checkEnt->client || checkEnt->client->pers.connected == CON_DISCONNECTED || checkEnt->client->sess.sessionTeam == TEAM_SPECTATOR ) {//this entity isn't going to be seeing anything continue; } if(trap_InPVS(point, checkEnt->client->ps.origin)) {//can be seen return qtrue; } } return qfalse; }
int G_FindLocalInterestPoint( gentity_t *self ) { int i, bestPoint = ENTITYNUM_NONE; float dist, bestDist = Q3_INFINITE; vec3_t diffVec, eyes; CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes ); for ( i = 0; i < level.numInterestPoints; i++ ) { //Don't ignore portals? If through a portal, need to look at portal! if ( trap_InPVS( level.interestPoints[i].origin, eyes ) ) { VectorSubtract( level.interestPoints[i].origin, eyes, diffVec ); if ( (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 && fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 ) {//Too close to look so far up or down continue; } dist = VectorLengthSquared( diffVec ); //Some priority to more interesting points //dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5); if ( dist < MAX_INTEREST_DIST && dist < bestDist ) { if ( G_ClearLineOfSight( eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE ) ) { bestDist = dist; bestPoint = i; } } } } if ( bestPoint != ENTITYNUM_NONE && level.interestPoints[bestPoint].target ) { G_UseTargets2( self, self, level.interestPoints[bestPoint].target ); } return bestPoint; }
/* * Team_GetLocation * * Report a location for the player. Uses placed nearby target_location entities */ Gentity * Team_GetLocation(Gentity *ent) { Gentity *eloc, *best; float bestlen, len; Vec3 origin; best = NULL; bestlen = 3*8192.0*8192.0; copyv3(ent->r.currentOrigin, origin); for(eloc = level.locationHead; eloc; eloc = eloc->nextTrain){ len = (origin[0] - eloc->r.currentOrigin[0]) * (origin[0] - eloc->r.currentOrigin[0]) + (origin[1] - eloc->r.currentOrigin[1]) * (origin[1] - eloc->r.currentOrigin[1]) + (origin[2] - eloc->r.currentOrigin[2]) * (origin[2] - eloc->r.currentOrigin[2]); if(len > bestlen) continue; if(!trap_InPVS(origin, eloc->r.currentOrigin)) continue; bestlen = len; best = eloc; } return best; }
//ported from SP void G_DynamicMusicUpdate( void ) { int battle = 0; vec3_t center; qboolean clearLOS = qfalse; int distSq, radius = 2048; int i, e, x; gentity_t *ent; int entityList[MAX_GENTITIES]; int entTeam; vec3_t mins, maxs; int numListedEntities; gentity_t *player; if( DMSData.dmDebounceTime >= 0 && DMSData.dmDebounceTime < level.time ) {//debounce over, reset to default music DMSData.dmDebounceTime = -1; DMSData.dmState = DM_AUTO; DMSData.olddmState = DM_AUTO; } if ( DMSData.dmState == DM_DEATH) {//Play the death music if(DMSData.olddmState != DM_DEATH) {//haven't set the state yet trap_SetConfigstring( CS_MUSIC, DMS_DEATH_MUSIC ); DMSData.olddmState = DM_DEATH; DMSData.dmDebounceTime = level.time + DMS_DEATH_MUSIC_TIME; } return; } if ( DMSData.dmState == DM_BOSS ) { if(DMSData.olddmState != DM_BOSS) { trap_SetConfigstring( CS_MUSIC, DMSData.bossMusic.fileName ); DMSData.olddmState = DM_BOSS; } return; } if ( DMSData.dmState == DM_SILENCE ) {//turn off the music if(DMSData.olddmState != DM_SILENCE) { trap_SetConfigstring( CS_MUSIC, "" ); DMSData.olddmState = DM_SILENCE; } return; } if ( DMSData.dmBeatTime > level.time ) {//not on a beat return; } DMSData.dmBeatTime = level.time + 1000;//1 second beats for(i = 0; i < MAX_CLIENTS; i++) { player = &g_entities[i]; //check to make sure this player is valid if(!player || !player->inuse || player->client->pers.connected == CON_DISCONNECTED || player->client->sess.sessionTeam == TEAM_SPECTATOR) { continue; } //enemy-based VectorCopy( player->r.currentOrigin, center ); for ( x = 0 ; x < 3 ; x++ ) { mins[x] = center[x] - radius; maxs[x] = center[x] + radius; } numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { ent = &g_entities[entityList[e]]; if ( !ent || !ent->inuse ) { continue; } if ( !ent->client || !ent->NPC ) { if ( ent->classname && (!Q_stricmp( "PAS", ent->classname )||!Q_stricmp( "misc_turret", ent->classname )) ) {//a turret entTeam = ent->teamnodmg; //entTeam = ent->noDamageTeam; } else { continue; } } else {//an NPC entTeam = ent->client->playerTeam; } if ( entTeam == player->client->playerTeam ) {//ally continue; } if ( entTeam == TEAM_FREE && (!ent->enemy || !ent->enemy->client || ent->enemy->client->playerTeam != player->client->playerTeam) ) {//a droid that is not mad at me or my allies continue; } if ( !trap_InPVS( player->r.currentOrigin, ent->r.currentOrigin ) ) {//not potentially visible continue; } if ( ent->client && ent->s.weapon == WP_NONE ) {//they don't have a weapon... FIXME: only do this for droids? continue; } clearLOS = qfalse; if ( (ent->enemy==player&&(!ent->NPC||ent->NPC->confusionTime<level.time)) || (ent->client&&ent->client->ps.weaponTime) || (!ent->client&&ent->attackDebounceTime>level.time)) {//mad if ( ent->health > 0 ) {//alive //FIXME: do I really need this check? if ( ent->s.weapon == WP_SABER && ent->client && ent->client->ps.saberHolstered == 2 && ent->enemy != player ) {//a Jedi who has not yet gotten mad at me continue; } if ( ent->NPC && ent->NPC->behaviorState == BS_CINEMATIC ) {//they're not actually going to do anything about being mad at me... continue; } //okay, they're in my PVS, but how close are they? Are they actively attacking me? if ( !ent->client && ent->s.weapon == WP_TURRET && ent->fly_sound_debounce_time && ent->fly_sound_debounce_time - level.time < 10000 ) {//a turret that shot at me less than ten seconds ago } else if( ent->NPC && level.time < ent->NPC->shotTime ) {//npc that fired recently } /* changed from SP else if ( ent->client && ent->client->ps.lastShotTime && ent->client->ps.lastShotTime - level.time < 10000 ) {//an NPC that shot at me less than ten seconds ago } */ else {//not actively attacking me lately, see how far away they are distSq = DistanceSquared( ent->r.currentOrigin, player->r.currentOrigin ); if ( distSq > 4194304 ) {//> 2048 away continue; } else if ( distSq > 1048576 ) {//> 1024 away clearLOS = G_ClearLOS3( player, player->client->renderInfo.eyePoint, ent ); if ( clearLOS == qfalse ) {//No LOS continue; } } } battle++; } } } if ( !battle ) {//no active enemies, but look for missiles, shot impacts, etc... //[CoOp] int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS, qfalse ); //int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS ); //[/CoOp] if ( alert != -1 ) {//FIXME: maybe tripwires and other FIXED things need their own sound, some kind of danger/caution theme if ( G_CheckForDanger( player, alert ) ) {//found danger near by battle = 1; } } } } if ( battle ) { SetDMSState(DM_ACTION); } else {//switch to explore SetDMSState(DM_EXPLORE); } if(DMSData.dmState != DMSData.olddmState) {//switching between action and explore modes TransitionBetweenState(); } }
//----------------------------------------------------- void turret_base_think( gentity_t *self ) //----------------------------------------------------- { qboolean turnOff = qtrue; float enemyDist; vec3_t enemyDir, org, org2; if ( self->spawnflags & 1 ) { // not turned on turret_turnoff( self ); // No target self->flags |= FL_NOTARGET; self->nextthink = -1;//never think again return; } else { // I'm all hot and bothered self->flags &= ~FL_NOTARGET; //remember to keep thinking! self->nextthink = level.time + FRAMETIME; } if ( !self->enemy ) { if ( turret_find_enemies( self )) { turnOff = qfalse; } } else if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR ) {//don't keep going after spectators self->enemy = NULL; } else {//FIXME: remain single-minded or look for a new enemy every now and then? if ( self->enemy->health > 0 ) { // enemy is alive VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < (self->radius * self->radius) ) { // was in valid radius if ( trap_InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) ) { // Every now and again, check to see if we can even trace to the enemy trace_t tr; if ( self->enemy->client ) { VectorCopy( self->enemy->client->renderInfo.eyePoint, org ); } else { VectorCopy( self->enemy->r.currentOrigin, org ); } VectorCopy( self->r.currentOrigin, org2 ); if ( self->spawnflags & 2 ) { org2[2] += 10; } else { org2[2] -= 10; } trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number ) { turnOff = qfalse; // Can see our enemy } } } } turret_head_think( self ); } if ( turnOff ) { if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off { turret_sleep( self ); } } else { // keep our enemy for a minimum of 2 seconds from now self->bounceCount = level.time + 2000 + random() * 150; } turret_aim( self ); }
//----------------------------------------------------- static qboolean turret_find_enemies( gentity_t *self ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = self->radius * self->radius; float enemyDist; vec3_t enemyDir, org, org2; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; trace_t tr; gentity_t *top = &g_entities[self->r.ownerNum]; if ( !top ) { return qfalse; } if ( self->aimDebounceTime > level.time ) // time since we've been shut off { // We were active and alert, i.e. had an enemy in the last 3 secs if ( self->timestamp < level.time ) { //G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" )); self->timestamp = level.time + 1000; } } VectorCopy( top->r.currentOrigin, org2 ); count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { target = entity_list[i]; if ( !target->client ) { // only attack clients continue; } if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( self->alliedTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == self->alliedTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == self->alliedTeam ) { // An ent we don't want to shoot continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } VectorCopy( target->r.currentOrigin, org ); org[2] += target->r.maxs[2]*0.5f; trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, top->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist // all things equal, keep current || (!Q_stricmp( "atst_vehicle", target->NPC_type ) && bestTarget && Q_stricmp( "atst_vehicle", bestTarget->NPC_type ) ) )//target AT-STs over non-AT-STs... FIXME: must be a better, easier way to tell this, no? { if ( self->attackDebounceTime < level.time ) { // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound //G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" )); // Wind up turrets for a bit self->attackDebounceTime = level.time + 1400; } bestTarget = target; bestDist = enemyDist; found = qtrue; } } } if ( found ) { G_SetEnemy( self, bestTarget ); if ( VALIDSTRING( self->target2 )) { G_UseTargets2( self, self, self->target2 ); } } return found; }
visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ) { // flags should never be 0 if ( !flags ) { return VIS_NOT; } // check PVS if ( flags & CHECK_PVS ) { if ( !trap_InPVS ( ent->r.currentOrigin, NPC->r.currentOrigin ) ) { return VIS_NOT; } } if ( !(flags & (CHECK_360|CHECK_FOV|CHECK_SHOOT)) ) { return VIS_PVS; } // check within visrange if (flags & CHECK_VISRANGE) { if( !InVisrange ( ent ) ) { return VIS_PVS; } } // check 360 degree visibility //Meaning has to be a direct line of site if ( flags & CHECK_360 ) { if ( !CanSee ( ent ) ) { return VIS_PVS; } } if ( !(flags & (CHECK_FOV|CHECK_SHOOT)) ) { return VIS_360; } // check FOV if ( flags & CHECK_FOV ) { if ( !InFOV ( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov) ) { return VIS_360; } } if ( !(flags & CHECK_SHOOT) ) { return VIS_FOV; } // check shootability if ( flags & CHECK_SHOOT ) { if ( !CanShoot ( ent, NPC ) ) { return VIS_FOV; } } return VIS_SHOOT; }
/* ============ AICast_StartServerFrame Do movements, sighting, etc ============ */ void AICast_StartServerFrame( int time ) { int i, elapsed, count, clCount; cast_state_t *cs; int castcount; static int lasttime; static vmCvar_t aicast_disable; gentity_t *ent; cast_state_t *pcs; // int oldLegsTimer; if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { return; } if ( g_gametype.integer != GT_SINGLE_PLAYER ) { return; } if ( saveGamePending ) { return; } // if waiting at intermission, don't think if ( strlen( g_missionStats.string ) > 1 ) { return; } if ( !aicast_disable.handle ) { trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT ); } else { trap_Cvar_Update( &aicast_disable ); if ( aicast_disable.integer ) { return; } } trap_Cvar_Update( &aicast_debug ); // no need to think during the intermission if ( level.intermissiontime ) { return; } // // make sure the AAS gets updated trap_BotLibStartFrame( (float) time / 1000 ); // // elapsed = time - lasttime; if ( elapsed == 0 ) { return; // no time has elapsed } pcs = AICast_GetCastState( 0 ); //G_Printf( "AI startserverframe: %i\n", time ); if ( elapsed < 0 ) { elapsed = 0; lasttime = time; } // don't let the framerate drop below 10 if ( elapsed > 100 ) { elapsed = 100; } // // process player's current script if it exists AICast_ScriptRun( AICast_GetCastState( 0 ), qfalse ); // AICast_SightUpdate( (int)( (float)SIGHT_PER_SEC * ( (float)elapsed / 1000 ) ) ); // count = 0; castcount = 0; clCount = 0; ent = g_entities; // //update the AI characters // TTimo gcc: left-hand operand of comma expression has no effect // initial line: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++) for ( i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++ ) { if ( ent->client ) { clCount++; } // cs = AICast_GetCastState( i ); // is this a cast AI? if ( cs->bs ) { if ( ent->aiInactive == qfalse && ent->inuse ) { // elapsed = level.time - cs->lastMoveThink; // // optimization, if they're not in the player's PVS, and they aren't trying to move, then don't bother thinking if ( ( ( ent->health > 0 ) && ( elapsed > 300 ) ) || ( g_entities[0].client && g_entities[0].client->cameraPortal ) || ( cs->vislist[0].visible_timestamp == cs->vislist[0].lastcheck_timestamp ) || ( pcs->vislist[cs->entityNum].visible_timestamp == pcs->vislist[cs->entityNum].lastcheck_timestamp ) || ( VectorLength( ent->client->ps.velocity ) > 0 ) || ( cs->bs->lastucmd.forwardmove || cs->bs->lastucmd.rightmove || cs->bs->lastucmd.upmove > 0 || cs->bs->lastucmd.buttons || cs->bs->lastucmd.wbuttons ) || ( trap_InPVS( cs->bs->origin, g_entities[0].s.pos.trBase ) ) ) { // do pvs check last, since it's the most expensive to call // oldLegsTimer = ent->client->ps.legsTimer; // // send it's movement commands // serverTime = time; AICast_UpdateInput( cs, elapsed ); trap_BotUserCommand( cs->bs->client, &( cs->bs->lastucmd ) ); cs->lastMoveThink = level.time; // // check for anim changes that may require us to stay still // /* if (oldLegsTimer != ent->client->ps.legsTimer) { // dont move until they are finished if (cs->castScriptStatus.scriptNoMoveTime < level.time + ent->client->ps.legsTimer) { cs->castScriptStatus.scriptNoMoveTime = level.time + ent->client->ps.legsTimer; } } */ } } else { trap_UnlinkEntity( ent ); } // // see if we've checked all cast AI's if ( ++castcount >= numcast ) { break; } } } // lasttime = time; }
//----------------------------------------------------- void turretG2_base_think( gentity_t *self ) //----------------------------------------------------- { qboolean turnOff = qtrue; float enemyDist; vec3_t enemyDir, org, org2; self->nextthink = level.time + FRAMETIME; if ( self->health <= 0 ) { //dead if (self->spawnflags & SPF_TURRETG2_CANRESPAWN) { //can respawn if ( self->genericValue5 && self->genericValue5 < level.time ) { //we are dead, see if it's time to respawn turretG2_respawn( self ); } } return; } else if ( self->spawnflags & 1 ) { // not turned on turretG2_turnoff( self ); turretG2_aim( self ); // No target self->flags |= FL_NOTARGET; return; } else { // I'm all hot and bothered self->flags &= ~FL_NOTARGET; } if ( self->enemy ) { if ( self->enemy->health < 0 || !self->enemy->inuse ) { self->enemy = NULL; } } if ( self->last_move_time < level.time ) { //MISNOMER: used a enemy recalcing debouncer if ( turretG2_find_enemies( self ) ) { //found one turnOff = qfalse; if ( self->enemy->client ) { //hold on to clients for a min of 3 seconds self->last_move_time = level.time + 3000; } else { //hold less self->last_move_time = level.time + 500; } } } if ( self->enemy != NULL ) { if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR ) { //don't keep going after spectators self->enemy = NULL; } else { //FIXME: remain single-minded or look for a new enemy every now and then? // enemy is alive VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < self->radius * self->radius ) { // was in valid radius if ( trap_InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) ) { // Every now and again, check to see if we can even trace to the enemy trace_t tr; if ( self->enemy->client ) { VectorCopy( self->enemy->client->renderInfo.eyePoint, org ); } else { VectorCopy( self->enemy->r.currentOrigin, org ); } VectorCopy( self->r.currentOrigin, org2 ); if ( self->spawnflags & 2 ) { org2[2] += 10; } else { org2[2] -= 10; } trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number ) { turnOff = qfalse; // Can see our enemy } } } } } if ( turnOff ) { if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off { turretG2_turnoff( self ); } } else { // keep our enemy for a minimum of 2 seconds from now self->bounceCount = level.time + 2000 + random() * 150; } turretG2_aim( self ); if ( !turnOff ) { turretG2_head_think( self ); } }
void props_me109_think( gentity_t *self ) { qboolean in_PVS = qfalse; { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( player ) { in_PVS = trap_InPVS( player->r.currentOrigin, self->s.pos.trBase ); if ( in_PVS ) { self->melee->s.eType = ET_GENERAL; { float len; vec3_t vec; vec3_t forward; vec3_t dir; vec3_t point; VectorCopy( player->r.currentOrigin, point ); VectorSubtract( player->r.currentOrigin, self->r.currentOrigin, vec ); len = VectorLength( vec ); vectoangles( vec, dir ); AngleVectors( dir, forward, NULL, NULL ); VectorMA( point, len * 0.1, forward, point ); G_SetOrigin( self->melee, point ); } } else { self->melee->s.eType = ET_GENERAL; } trap_LinkEntity( self->melee ); } } Plane_Attack( self, in_PVS ); Calc_Roll( self ); if ( self->health < 250 ) { gentity_t *tent; vec3_t point; VectorCopy( self->r.currentOrigin, point ); tent = G_TempEntity( point, EV_SMOKE ); VectorCopy( point, tent->s.origin ); tent->s.time = 2000; tent->s.time2 = 1000; tent->s.density = 4; tent->s.angles2[0] = 16; tent->s.angles2[1] = 48; tent->s.angles2[2] = 10; self->props_frame_state = plane_choke; self->health--; } if ( self->health > 0 ) { self->nextthink = level.time + 50; if ( self->props_frame_state == plane_choke ) { self->melee->s.loopSound = self->melee->noise_index = fpchoke_snd; } else if ( self->props_frame_state == plane_startup ) { self->melee->s.loopSound = self->melee->noise_index = fpstartup_snd; } else if ( self->props_frame_state == plane_idle ) { self->melee->s.loopSound = self->melee->noise_index = fpidle_snd; } else if ( self->props_frame_state == plane_flyby1 ) { self->melee->s.loopSound = self->melee->noise_index = fpflyby1_snd; } else if ( self->props_frame_state == plane_flyby2 ) { self->melee->s.loopSound = self->melee->noise_index = fpflyby2_snd; } } else { propExplosionLarge( self ); self->melee->s.loopSound = self->melee->noise_index = 0; ExplodePlaneSndFx( self ); G_FreeEntity( self->melee ); G_FreeEntity( self ); } }
/* ================ Team_FragBonuses Calculate the bonuses for flag defense, flag carrier defense, etc. Note that bonuses are not cumulative. You get one, they are in importance order. ================ */ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker) { int i; gentity_t *ent; int flag_pw, enemy_flag_pw; int otherteam; int tokens; gentity_t *flag, *carrier = NULL; char *c; vec3_t v1, v2; int team; // no bonus for fragging yourself or team mates if (!targ->client || !attacker->client || targ == attacker || OnSameTeam(targ, attacker)) return; team = targ->client->sess.sessionTeam; otherteam = OtherTeam(targ->client->sess.sessionTeam); if (otherteam < 0) return; // whoever died isn't on a team // same team, if the flag at base, check to he has the enemy flag if (team == TEAM_RED) { flag_pw = PW_REDFLAG; enemy_flag_pw = PW_BLUEFLAG; } else { flag_pw = PW_BLUEFLAG; enemy_flag_pw = PW_REDFLAG; } // did the attacker frag the flag carrier? tokens = 0; if (targ->client->ps.powerups[enemy_flag_pw]) { attacker->client->pers.teamState.lastfraggedcarrier = level.time; AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS); attacker->client->pers.teamState.fragcarrier++; //PrintMsg(NULL, "%s" S_COLOR_WHITE " fragged %s's flag carrier!\n", // attacker->client->pers.netname, TeamName(team)); PrintCTFMessage(attacker->s.number, team, CTFMESSAGE_FRAGGED_FLAG_CARRIER); // the target had the flag, clear the hurt carrier // field on the other team for (i = 0; i < sv_maxclients.integer; i++) { ent = g_entities + i; if (ent->inuse && ent->client->sess.sessionTeam == otherteam) ent->client->pers.teamState.lasthurtcarrier = 0; } return; } // did the attacker frag a head carrier? other->client->ps.generic1 if (tokens) { attacker->client->pers.teamState.lastfraggedcarrier = level.time; AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS * tokens * tokens); attacker->client->pers.teamState.fragcarrier++; //PrintMsg(NULL, "%s" S_COLOR_WHITE " fragged %s's skull carrier!\n", // attacker->client->pers.netname, TeamName(team)); // the target had the flag, clear the hurt carrier // field on the other team for (i = 0; i < sv_maxclients.integer; i++) { ent = g_entities + i; if (ent->inuse && ent->client->sess.sessionTeam == otherteam) ent->client->pers.teamState.lasthurtcarrier = 0; } return; } if (targ->client->pers.teamState.lasthurtcarrier && level.time - targ->client->pers.teamState.lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && !attacker->client->ps.powerups[flag_pw]) { // attacker is on the same team as the flag carrier and // fragged a guy who hurt our flag carrier AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS); attacker->client->pers.teamState.carrierdefense++; targ->client->pers.teamState.lasthurtcarrier = 0; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; team = attacker->client->sess.sessionTeam; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } if (targ->client->pers.teamState.lasthurtcarrier && level.time - targ->client->pers.teamState.lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT) { // attacker is on the same team as the skull carrier and AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS); attacker->client->pers.teamState.carrierdefense++; targ->client->pers.teamState.lasthurtcarrier = 0; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; team = attacker->client->sess.sessionTeam; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } // flag and flag carrier area defense bonuses // we have to find the flag and carrier entities // find the flag switch (attacker->client->sess.sessionTeam) { case TEAM_RED: c = "team_CTF_redflag"; break; case TEAM_BLUE: c = "team_CTF_blueflag"; break; default: return; } // find attacker's team's flag carrier for (i = 0; i < sv_maxclients.integer; i++) { carrier = g_entities + i; if (carrier->inuse && carrier->client->ps.powerups[flag_pw]) break; carrier = NULL; } flag = NULL; while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) { if (!(flag->flags & FL_DROPPED_ITEM)) break; } if (!flag) return; // can't find attacker's flag // ok we have the attackers flag and a pointer to the carrier // check to see if we are defending the base's flag VectorSubtract(targ->r.currentOrigin, flag->r.currentOrigin, v1); VectorSubtract(attacker->r.currentOrigin, flag->r.currentOrigin, v2); if ( ( ( VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS && trap_InPVS(flag->r.currentOrigin, targ->r.currentOrigin ) ) || ( VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS && trap_InPVS(flag->r.currentOrigin, attacker->r.currentOrigin ) ) ) && attacker->client->sess.sessionTeam != targ->client->sess.sessionTeam) { // we defended the base flag AddScore(attacker, targ->r.currentOrigin, CTF_FLAG_DEFENSE_BONUS); attacker->client->pers.teamState.basedefense++; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } if (carrier && carrier != attacker) { VectorSubtract(targ->r.currentOrigin, carrier->r.currentOrigin, v1); VectorSubtract(attacker->r.currentOrigin, carrier->r.currentOrigin, v1); if ( ( ( VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS && trap_InPVS(carrier->r.currentOrigin, targ->r.currentOrigin ) ) || ( VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS && trap_InPVS(carrier->r.currentOrigin, attacker->r.currentOrigin ) ) ) && attacker->client->sess.sessionTeam != targ->client->sess.sessionTeam) { AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_PROTECT_BONUS); attacker->client->pers.teamState.carrierdefense++; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } } }
/* ============== AICast_VisibleFromPos ============== */ qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum, vec3_t destpos, int destnum, qboolean updateVisPos ) { int i, contents_mask, passent, hitent; trace_t trace; vec3_t start, end, middle, eye; cast_state_t *cs = NULL; int srcviewheight; vec3_t destmins, destmaxs; vec3_t right, vec; qboolean inPVS; if ( g_entities[destnum].flags & FL_NOTARGET ) { return qfalse; } if ( srcnum < aicast_maxclients ) { cs = AICast_GetCastState( srcnum ); } // if ( cs && cs->bs ) { srcviewheight = cs->bs->cur_ps.viewheight; } else if ( g_entities[srcnum].client ) { srcviewheight = g_entities[srcnum].client->ps.viewheight; } else { srcviewheight = 0; } // VectorCopy( g_entities[destnum].r.mins, destmins ); VectorCopy( g_entities[destnum].r.maxs, destmaxs ); // //calculate middle of bounding box VectorAdd( destmins, destmaxs, middle ); VectorScale( middle, 0.5, middle ); VectorAdd( destpos, middle, middle ); // calculate eye position VectorCopy( srcpos, eye ); eye[2] += srcviewheight; // // set the right vector VectorSubtract( middle, eye, vec ); VectorNormalize( vec ); right[0] = vec[1]; right[1] = vec[0]; right[2] = 0; // inPVS = qfalse; // for ( i = 0; i < 5; i++ ) { if ( cs && updateVisPos ) { // if it's a grenade or something, PVS checks don't work very well //if the point is not in potential visible sight if ( i < 3 ) { // don't do PVS check for left/right checks if ( !trap_InPVS( eye, middle ) ) { continue; } else { inPVS = qtrue; } } else if ( !inPVS ) { break; // wasn't in potential view in either of the previous tests } // so don't bother doing left/right } // contents_mask = MASK_AISIGHT; //(MASK_SHOT | CONTENTS_AI_NOSIGHT) & ~(CONTENTS_BODY); // we can see anything that a bullet can pass through passent = srcnum; hitent = destnum; VectorCopy( eye, start ); VectorCopy( middle, end ); //if the entity is in water, lava or slime if ( trap_PointContents( middle, destnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); } //end if //if eye is in water, lava or slime if ( trap_PointContents( eye, srcnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { if ( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { passent = destnum; hitent = srcnum; VectorCopy( middle, start ); VectorCopy( eye, end ); } //end if contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); } //end if //trace from start to end trap_Trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE /*passent*/, contents_mask ); //if water was hit if ( trace.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { //if the water surface is translucent // if (trace.surface.flags & (SURF_TRANS33|SURF_TRANS66)) { //trace through the water contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); trap_Trace( &trace, trace.endpos, NULL, NULL, end, passent, contents_mask ); } //end if } //end if //if a full trace or the hitent was hit if ( trace.fraction >= 1 || trace.entityNum == hitent ) { return qtrue; } //check bottom and top of bounding box as well if ( i == 0 ) { middle[2] -= ( destmaxs[2] - destmins[2] ) * 0.5; } else if ( i == 1 ) { middle[2] += destmaxs[2] - destmins[2]; } else if ( i == 2 ) { // right side middle[2] -= ( destmaxs[2] - destmins[2] ) / 2.0; VectorMA( eye, destmaxs[0] - 0.5, right, eye ); } else if ( i == 3 ) { // left side VectorMA( eye, -2.0 * ( destmaxs[0] - 0.5 ), right, eye ); } } //end for return qfalse; }
//perform pvs check based on rmg or not qboolean BotPVSCheck( const vec3_t p1, const vec3_t p2 ) { return trap_InPVS(p1, p2); }
void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt(qfalse) == qfalse || !NPC->enemy ) { NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS4 = enemyCS4 = qfalse; move4 = qtrue; faceEnemy4 = qfalse; shoot4 = qfalse; hitAlly4 = qfalse; VectorClear( impactPos4 ); enemyDist4 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); //if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || // NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) if (0) { shoot4 = qfalse; if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist4 and InFront if ( enemyDist4 < MELEE_DIST_SQUARED && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPC->enemy, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( BG_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCInfo->blockedDebounceTime = 1; } } } else if ( NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot4 = qfalse; if ( NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; //if ( Q_irand( 0, 1 ) ) if (1) { laserAnim = BOTH_ATTACK2; } /* else { laserAnim = BOTH_ATTACK7; } */ NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPC->lockCount = 2; G_PlayEffectID( G_EffectIndex("galak/trace_beam"), NPC->r.currentOrigin, vec3_origin ); NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCInfo->coverTarg = G_Spawn(); if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); NPCInfo->coverTarg->r.svFlags |= SVF_BROADCAST; NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( NPC->client->ps.torsoTimer <= 0 ) {//attack done! NPC->lockCount = 0; G_FreeEntity( NPCInfo->coverTarg ); NPC->s.loopSound = 0; TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); trap_Trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_UNKNOWN ); } } if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist4 < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if (// !NPC->client->ps.powerups[PW_GALAK_SHIELD] 1 //rwwFIXMEFIXME: just act like the shield is down til the effects and stuff are done && enemyDist4 < MELEE_DIST_SQUARED && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) && NPC->enemy->localAnimIndex <= 1 )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPC, "attackDelay" ) ) { //animate me int swingAnim = BOTH_ATTACK1; //FIXME: swing sound NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPC, "smackTime", 600 ); NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPC, "attackDelay" ) && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_spskill.integer))&& enemyDist4 > MIN_LOB_DIST_SQUARED&& enemyDist4 < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) && NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot4 = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist4 < MIN_LOB_DIST_SQUARED && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) && TIMER_Done( NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist4 > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) && TIMER_Done( NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS4( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS4 = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS4 = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist4 < MIN_LOB_DIST_SQUARED )//256 { enemyCS4 = qfalse;//not true, but should stop us from firing hitAlly4 = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPC->enemy, impactPos4 ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS4 = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly4 = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) { int hit; gentity_t *hitEnt; if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; break; } NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCInfo->enemyLastSeenTime = level.time; hit = NPC_ShotEntity( NPC->enemy, impactPos4 ); hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS4 = qtrue; } else { faceEnemy4 = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS4 ) { faceEnemy4 = qtrue; } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy move4 = qtrue; } } if ( enemyCS4 ) { shoot4 = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy move4 = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot4 && TIMER_Done( NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; qboolean clearshot; CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPC->enemy->r.currentOrigin, target ); target[0] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[1] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[2] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); //Find the desired angles clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPC->s.number, NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS4&&enemyCS4) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS4 && enemyCS4 && TIMER_Done( NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot4 = qfalse; } } else { vectoangles( velocity, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPC->client->hiddenDir ); NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); } } else if ( faceEnemy4 ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPC, "standTime" ) ) { move4 = qfalse; } if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCInfo->goalEntity == NPC->enemy ) {//goal is my entity, so don't move move4 = qfalse; } } if ( move4 && !NPC->lockCount ) {//move toward goal if ( NPCInfo->goalEntity /*&& NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7*/ ) { move4 = GM_Move(); } else { move4 = qfalse; } } if ( !TIMER_Done( NPC, "flee" ) ) {//running away faceEnemy4 = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy4 ) {//we want to face in the dir we're running if ( !move4 ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); } if ( move4 ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot4 = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot4 = qfalse; } if ( NPC->enemy && NPC->enemy->enemy ) { if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot4 = qfalse; } } //FIXME: don't shoot right away! if ( shoot4 ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPC->r.absmin, NPC->r.absmax, NPC->enemy->r.absmin, NPC->enemy->r.absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) if (0) { NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); } else { G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) {//touched enemy //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) if (0) {//zap him! vec3_t smackDir; //animate me TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer ); TIMER_Set( NPC, "standTime", NPC->client->ps.legsTimer ); //FIXME: debounce this? NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); //throw them G_Throw( NPC->enemy, smackDir, 100 ); //NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPC->enemy->client ) { // NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; NPC->enemy->client->ps.electrifyTime = level.time + 1000; } //stop any attacks ucmd.buttons = 0; } } if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) { if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 3; } else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 2; } else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 1; } } } }
//----------------------------------------------------- static qboolean VEH_TurretFindEnemies( Vehicle_t *pVeh, gentity_t *parent, turretStats_t *turretStats, int turretNum, int curMuzzle ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = turretStats->fAIRange * turretStats->fAIRange; float enemyDist; vec3_t enemyDir, org, org2; qboolean foundClient = qfalse; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; WP_CalcVehMuzzle( parent, curMuzzle ); VectorCopy( pVeh->m_vMuzzlePos[curMuzzle], org2 ); count = G_RadiusList( org2, turretStats->fAIRange, parent, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { trace_t tr; target = entity_list[i]; if ( target == parent || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( !target->client ) {// only attack clients if ( !(target->flags&FL_BBRUSH)//not a breakable brush || !target->takedamage//is a bbrush, but invincible || (target->NPC_targetname&&parent->targetname&&Q_stricmp(target->NPC_targetname,parent->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me { if ( target->s.weapon == WP_TURRET && target->classname && Q_strncmp( "misc_turret", target->classname, 11 ) == 0 ) {//these guys we want to shoot at } else { continue; } } //else: we will shoot at bbrushes! } else if ( target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } else if ( target->client->tempSpectate >= level.time ) { continue; } if ( target == ((gentity_t*)pVeh->m_pPilot) || target->r.ownerNum == parent->s.number ) {//don't get angry at my pilot or passengers? continue; } if ( parent->client && parent->client->sess.sessionTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == parent->client->sess.sessionTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == parent->client->sess.sessionTeam ) {//some other entity that's allied with us continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } VectorCopy( target->r.currentOrigin, org ); trap_Trace( &tr, org2, NULL, NULL, org, parent->s.number, MASK_SHOT ); if ( tr.entityNum == target->s.number || (!tr.allsolid && !tr.startsolid && tr.fraction == 1.0 ) ) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, org2, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current { bestTarget = target; bestDist = enemyDist; found = qtrue; if ( target->client ) {//prefer clients over non-clients foundClient = qtrue; } } } } if ( found ) { pVeh->turretStatus[turretNum].enemyEntNum = bestTarget->s.number; } return found; }
void VEH_TurretThink( Vehicle_t *pVeh, gentity_t *parent, int turretNum ) //----------------------------------------------------- { qboolean doAim = qfalse; float enemyDist, rangeSq; vec3_t enemyDir; turretStats_t *turretStats = &pVeh->m_pVehicleInfo->turret[turretNum]; vehWeaponInfo_t *vehWeapon = NULL; gentity_t *turretEnemy = NULL; int curMuzzle = 0;//? if ( !turretStats || !turretStats->iAmmoMax ) {//not a valid turret return; } if ( turretStats->passengerNum && pVeh->m_iNumPassengers >= turretStats->passengerNum ) {//the passenger that has control of this turret is on the ship VEH_TurretObeyPassengerControl( pVeh, parent, turretNum ); return; } else if ( !turretStats->bAI )//try AI {//this turret does not think on its own. return; } vehWeapon = &g_vehWeaponInfo[turretStats->iWeapon]; rangeSq = (turretStats->fAIRange*turretStats->fAIRange); curMuzzle = pVeh->turretStatus[turretNum].nextMuzzle; if ( pVeh->turretStatus[turretNum].enemyEntNum < ENTITYNUM_WORLD ) { turretEnemy = &g_entities[pVeh->turretStatus[turretNum].enemyEntNum]; if ( turretEnemy->health < 0 || !turretEnemy->inuse || turretEnemy == ((gentity_t*)pVeh->m_pPilot)//enemy became my pilot///? || turretEnemy == parent || turretEnemy->r.ownerNum == parent->s.number // a passenger? || ( turretEnemy->client && turretEnemy->client->sess.sessionTeam == TEAM_SPECTATOR ) || ( turretEnemy->client && turretEnemy->client->tempSpectate >= level.time ) ) {//don't keep going after spectators, pilot, self, dead people, etc. turretEnemy = NULL; pVeh->turretStatus[turretNum].enemyEntNum = ENTITYNUM_NONE; } } if ( pVeh->turretStatus[turretNum].enemyHoldTime < level.time ) { if ( VEH_TurretFindEnemies( pVeh, parent, turretStats, turretNum, curMuzzle ) ) { turretEnemy = &g_entities[pVeh->turretStatus[turretNum].enemyEntNum]; doAim = qtrue; } else if ( parent->enemy && parent->enemy->s.number < ENTITYNUM_WORLD ) { turretEnemy = parent->enemy; doAim = qtrue; } if ( turretEnemy ) {//found one if ( turretEnemy->client ) {//hold on to clients for a min of 3 seconds pVeh->turretStatus[turretNum].enemyHoldTime = level.time + 3000; } else {//hold less pVeh->turretStatus[turretNum].enemyHoldTime = level.time + 500; } } } if ( turretEnemy != NULL ) { if ( turretEnemy->health > 0 ) { // enemy is alive WP_CalcVehMuzzle( parent, curMuzzle ); VectorSubtract( turretEnemy->r.currentOrigin, pVeh->m_vMuzzlePos[curMuzzle], enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < rangeSq ) { // was in valid radius if ( trap_InPVS( pVeh->m_vMuzzlePos[curMuzzle], turretEnemy->r.currentOrigin ) ) { // Every now and again, check to see if we can even trace to the enemy trace_t tr; vec3_t start, end; VectorCopy( pVeh->m_vMuzzlePos[curMuzzle], start ); VectorCopy( turretEnemy->r.currentOrigin, end ); trap_Trace( &tr, start, NULL, NULL, end, parent->s.number, MASK_SHOT ); if ( tr.entityNum == turretEnemy->s.number || (!tr.allsolid && !tr.startsolid ) ) { doAim = qtrue; // Can see our enemy } } } } } if ( doAim ) { vec3_t aimAngles; if ( VEH_TurretAim( pVeh, parent, turretEnemy, turretStats, vehWeapon, turretNum, curMuzzle, aimAngles ) ) { VEH_TurretCheckFire( pVeh, parent, /*turretEnemy,*/ turretStats, vehWeapon, turretNum, curMuzzle ); } } }
//----------------------------------------------------- static qboolean turretG2_find_enemies( gentity_t *self ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = self->radius * self->radius; float enemyDist; vec3_t enemyDir, org, org2; qboolean foundClient = qfalse; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; if ( self->aimDebounceTime > level.time ) // time since we've been shut off { // We were active and alert, i.e. had an enemy in the last 3 secs if ( self->painDebounceTime < level.time ) { if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) { G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" )); } self->painDebounceTime = level.time + 1000; } } VectorCopy( self->r.currentOrigin, org2 ); if ( self->spawnflags & 2 ) { org2[2] += 20; } else { org2[2] -= 20; } count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { trace_t tr; target = entity_list[i]; if ( !target->client ) { // only attack clients if ( !(target->flags&FL_BBRUSH)//not a breakable brush || !target->takedamage//is a bbrush, but invincible || (target->NPC_targetname&&self->targetname&&Q_stricmp(target->NPC_targetname,self->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me { continue; } //else: we will shoot at bbrushes! } if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( target->client && target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( self->alliedTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == self->alliedTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == self->alliedTeam ) { // An ent we don't want to shoot continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } if ( target->client ) { VectorCopy( target->client->renderInfo.eyePoint, org ); } else { VectorCopy( target->r.currentOrigin, org ); } if ( self->spawnflags & 2 ) { org[2] -= 15; } else { org[2] += 5; } trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current { if ( self->attackDebounceTime < level.time ) { // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) { G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" )); } // Wind up turrets for a bit self->attackDebounceTime = level.time + 1400; } bestTarget = target; bestDist = enemyDist; found = qtrue; if ( target->client ) { //prefer clients over non-clients foundClient = qtrue; } } } } if ( found ) { /* if ( !self->enemy ) {//just aquired one AddSoundEvent( bestTarget, self->r.currentOrigin, 256, AEL_DISCOVERED ); AddSightEvent( bestTarget, self->r.currentOrigin, 512, AEL_DISCOVERED, 20 ); } */ G_SetEnemy( self, bestTarget ); if ( VALIDSTRING( self->target2 )) { G_UseTargets2( self, self, self->target2 ); } } return found; }
/* * Team_FragBonuses * * Calculate the bonuses for flag defense, flag carrier defense, etc. * Note that bonuses are not cumulative. You get one, they are in importance * order. */ void Team_FragBonuses(Gentity *targ, Gentity *inflictor, Gentity *attacker) { int i; Gentity *ent; int flag_pw, enemy_flag_pw; int otherteam; int tokens; Gentity *flag, *carrier = NULL; char *c; Vec3 v1, v2; int team; /* no bonus for fragging yourself or team mates */ if(!targ->client || !attacker->client || targ == attacker || OnSameTeam(targ, attacker)) return; team = targ->client->sess.team; otherteam = OtherTeam(targ->client->sess.team); if(otherteam < 0) return; /* whoever died isn't on a team */ /* same team, if the flag at base, check to he has the enemy flag */ if(team == TEAM_RED){ flag_pw = PW_REDFLAG; enemy_flag_pw = PW_BLUEFLAG; }else{ flag_pw = PW_BLUEFLAG; enemy_flag_pw = PW_REDFLAG; } if(g_gametype.integer == GT_1FCTF) enemy_flag_pw = PW_NEUTRALFLAG; /* did the attacker frag the flag carrier? */ tokens = 0; if(targ->client->ps.powerups[enemy_flag_pw]){ attacker->client->pers.teamState.lastfraggedcarrier = level.time; AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS); attacker->client->pers.teamState.fragcarrier++; PrintMsg(NULL, "%s" S_COLOR_WHITE " fragged %s's flag carrier!\n", attacker->client->pers.netname, TeamName( team)); /* the target had the flag, clear the hurt carrier * field on the other team */ for(i = 0; i < g_maxclients.integer; i++){ ent = g_entities + i; if(ent->inuse && ent->client->sess.team == otherteam) ent->client->pers.teamState.lasthurtcarrier = 0; } return; } /* did the attacker frag a head carrier? other->client->ps.generic1 */ if(tokens){ attacker->client->pers.teamState.lastfraggedcarrier = level.time; AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS * tokens * tokens); attacker->client->pers.teamState.fragcarrier++; PrintMsg(NULL, "%s" S_COLOR_WHITE " fragged %s's skull carrier!\n", attacker->client->pers.netname, TeamName( team)); /* the target had the flag, clear the hurt carrier * field on the other team */ for(i = 0; i < g_maxclients.integer; i++){ ent = g_entities + i; if(ent->inuse && ent->client->sess.team == otherteam) ent->client->pers.teamState.lasthurtcarrier = 0; } return; } if(targ->client->pers.teamState.lasthurtcarrier && level.time - targ->client->pers.teamState.lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && !attacker->client->ps.powerups[flag_pw]){ /* attacker is on the same team as the flag carrier and * fragged a guy who hurt our flag carrier */ AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS); attacker->client->pers.teamState.carrierdefense++; targ->client->pers.teamState.lasthurtcarrier = 0; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; /* add the sprite over the player's head */ attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); attacker->client->ps.eFlags |= EF_AWARD_DEFEND; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } if(targ->client->pers.teamState.lasthurtcarrier && level.time - targ->client->pers.teamState.lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT){ /* attacker is on the same team as the skull carrier and */ AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS); attacker->client->pers.teamState.carrierdefense++; targ->client->pers.teamState.lasthurtcarrier = 0; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; /* add the sprite over the player's head */ attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); attacker->client->ps.eFlags |= EF_AWARD_DEFEND; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } /* flag and flag carrier area defense bonuses */ /* we have to find the flag and carrier entities */ /* find the flag */ switch(attacker->client->sess.team){ case TEAM_RED: c = "team_CTF_redflag"; break; case TEAM_BLUE: c = "team_CTF_blueflag"; break; default: return; } /* find attacker's team's flag carrier */ for(i = 0; i < g_maxclients.integer; i++){ carrier = g_entities + i; if(carrier->inuse && carrier->client->ps.powerups[flag_pw]) break; carrier = NULL; } flag = NULL; while((flag = G_Find (flag, FOFS(classname), c)) != NULL) if(!(flag->flags & FL_DROPPED_ITEM)) break; if(!flag) return; /* can't find attacker's flag */ /* ok we have the attackers flag and a pointer to the carrier */ /* check to see if we are defending the base's flag */ subv3(targ->r.currentOrigin, flag->r.currentOrigin, v1); subv3(attacker->r.currentOrigin, flag->r.currentOrigin, v2); if(((lenv3(v1) < CTF_TARGET_PROTECT_RADIUS && trap_InPVS(flag->r.currentOrigin, targ->r.currentOrigin)) || (lenv3(v2) < CTF_TARGET_PROTECT_RADIUS && trap_InPVS(flag->r.currentOrigin, attacker->r.currentOrigin))) && attacker->client->sess.team != targ->client->sess.team){ /* we defended the base flag */ AddScore(attacker, targ->r.currentOrigin, CTF_FLAG_DEFENSE_BONUS); attacker->client->pers.teamState.basedefense++; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; /* add the sprite over the player's head */ attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); attacker->client->ps.eFlags |= EF_AWARD_DEFEND; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } if(carrier && carrier != attacker){ subv3(targ->r.currentOrigin, carrier->r.currentOrigin, v1); subv3(attacker->r.currentOrigin, carrier->r.currentOrigin, v1); if(((lenv3(v1) < CTF_ATTACKER_PROTECT_RADIUS && trap_InPVS(carrier->r.currentOrigin, targ->r.currentOrigin)) || (lenv3(v2) < CTF_ATTACKER_PROTECT_RADIUS && trap_InPVS(carrier->r.currentOrigin, attacker->r.currentOrigin))) && attacker->client->sess.team != targ->client->sess.team){ AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_PROTECT_BONUS); attacker->client->pers.teamState.carrierdefense++; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; /* add the sprite over the player's head */ attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); attacker->client->ps.eFlags |= EF_AWARD_DEFEND; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; return; } } }
/** * @brief Perform an approximate trace to find a taggable entity. * @param team Team the caller belongs to. * @param refreshTagged Refresh all already tagged entities's tags and exclude these entities from further consideration. */ gentity_t *TagTrace( const vec3_t begin, const vec3_t end, int skip, int mask, team_t team, bool refreshTagged ) { tagtrace_ent_t list[ MAX_GENTITIES ]; int i, count = 0; gentity_t *ent, *reticleEnt = nullptr; vec3_t seg, delta; float dot; VectorSubtract( end, begin, seg ); // Do a trace for bounding boxes under the reticle first, they are prefered { trace_t tr; trap_Trace( &tr, begin, nullptr, nullptr, end, skip, mask, 0 ); if ( EntityTaggable( tr.entityNum, team, true ) ) { reticleEnt = g_entities + tr.entityNum; if ( !refreshTagged || !CheckRefreshTag( reticleEnt, team ) ) return reticleEnt; } } for( i = 0; i < level.num_entities; i++ ) { ent = g_entities + i; if( ent == reticleEnt ) continue; if( !ent->inuse ) continue; if( !EntityTaggable( i, team, true ) ) continue; VectorSubtract( ent->r.currentOrigin, begin, delta ); dot = DotProduct( seg, delta ) / VectorLength( seg ) / VectorLength( delta ); if( dot < 0.9 ) continue; if( !trap_InPVS( ent->r.currentOrigin, begin ) ) continue; // LOS { trace_t tr; trap_Trace( &tr, begin, nullptr, nullptr, ent->r.currentOrigin, skip, mask, 0 ); if( tr.entityNum != i ) continue; } if( refreshTagged && CheckRefreshTag( ent, team ) ) continue; list[ count ].ent = ent; list[ count++ ].dot = dot; } if( !count ) return nullptr; qsort( list, count, sizeof( tagtrace_ent_t ), TagTrace_EntCmp ); return list[ 0 ].ent; }