/* ================ G_ProcessMine If an enemy is close to the entity, go boom! ================ */ void G_ProcessMine(gentity_t *ent) { int i, total_entities, entityList[MAX_GENTITIES]; vec3_t range, mins, maxs; gentity_t *target; // Set the next time to run this check (can be overwritten below) ent->nextthink = level.time + MINE_CHECK_FREQUENCY; // Grab all entities around us VectorSet(range, MINE_DETECT, MINE_DETECT, MINE_DETECT); VectorAdd(ent->s.origin, range, maxs); VectorSubtract(ent->s.origin, range, mins); total_entities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES); // Loop entities looking for an enemy body for(i=0; i<total_entities; i++) { target = &g_entities[entityList[i]]; if(target->client && target->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS) { if (G_Visible( ent, target, MASK_SHOT )) { // Found an enemy, boom time! ent->nextthink = level.time + MINE_BOOM_TIME; ent->think = G_ExplodeMissile; return; } } } }
/* ================ G_ProcessFlare If an player is close to the entity, hurt! ================ */ void G_ProcessFlare(gentity_t *ent) { int i, total_entities, entityList[MAX_GENTITIES]; vec3_t range, mins, maxs; gentity_t *target; if (level.time > ent->s.time + 30000) { ent->nextthink = level.time + 100; ent->think = G_ExplodeMissile; return; } // Set the next time to run this check (can be overwritten below) ent->nextthink = level.time + FLARE_CHECK_FREQUENCY; // Grab all entities around us VectorSet(range, FLARE_DETECT, FLARE_DETECT, FLARE_DETECT); VectorAdd(ent->s.origin, range, maxs); VectorSubtract(ent->s.origin, range, mins); total_entities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES); // Loop entities looking for an enemy body for(i=0; i<total_entities; i++) { target = &g_entities[entityList[i]]; if(target->client) { if (G_Visible( ent, target, MASK_SHOT )) { // Found an enemy, hurt time! G_Damage( target, ent, ent->parent, NULL, ent->s.origin, ent->damage, 0, ent->methodOfDeath ); //ent->nextthink = level.time + MINE_BOOM_TIME; //ent->think = G_ExplodeMissile; return; } } } }
//========================================== // BOT_DMclass_FindEnemy // Scan for enemy (simplifed for now to just pick any visible enemy) //========================================== void BOT_DMclass_FindEnemy( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 15000 nav_ents_t *goalEnt; edict_t *bestTarget = NULL; float dist, weight, bestWeight = 9999999; vec3_t forward, vec; int i; if( G_ISGHOSTING( self ) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled() ) { self->ai->enemyReactionDelay = 0; self->enemy = self->ai->latched_enemy = NULL; return; } // we also latch NULL enemies, so the bot can loose them if( self->ai->enemyReactionDelay > 0 ) { self->ai->enemyReactionDelay -= game.frametime; return; } self->enemy = self->ai->latched_enemy; FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent || !goalEnt->ent->r.inuse ) continue; if( !goalEnt->ent->r.client ) // this may be changed, there could be enemies which aren't clients continue; if( G_ISGHOSTING( goalEnt->ent ) ) continue; if( self->ai->status.entityWeights[i] <= 0 || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) continue; if( GS_TeamBasedGametype() && goalEnt->ent->s.team == self->s.team ) continue; dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); // ignore very soft weighted enemies unless they are in your face if( dist > 500 && self->ai->status.entityWeights[i] <= 0.1f ) continue; //if( dist > 700 && dist > WEIGHT_MAXDISTANCE_FACTOR * self->ai->status.entityWeights[i] ) // continue; weight = dist / self->ai->status.entityWeights[i]; if( weight < bestWeight ) { if( trap_inPVS( self->s.origin, goalEnt->ent->s.origin ) && G_Visible( self, goalEnt->ent ) ) { bool close = dist < 2000 || goalEnt->ent == self->ai->last_attacker; if( !close ) { VectorSubtract( goalEnt->ent->s.origin, self->s.origin, vec ); VectorNormalize( vec ); close = DotProduct( vec, forward ) > 0.3; } if( close ) { bestWeight = weight; bestTarget = goalEnt->ent; } } } } AI_NewEnemyInView( self, bestTarget ); #undef WEIGHT_MAXDISTANCE_FACTOR }
void explodedretch(gentity_t *ent) { int i; int entityList[ MAX_GENTITIES ]; vec3_t range; vec3_t mins, maxs; int num; gentity_t *enemy; float creepSize = (float)650; int freezedcounter = 0; //gentity_t *gren; if(ent->client->ps.persistant[ PERS_CREDIT ] < 1) //Neeeddd fix <3 { trap_SendServerCommand( ent-g_entities, "print \"^5Overmind: ^7You need 1 evos to explode.\n\"" ); } else { ent->client->ps.persistant[ PERS_CREDIT ] -= 1; G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, 0 ); G_RadiusDamage( ent->r.currentOrigin, ent, 200, 200, ent, MOD_GRENADE); G_Damage( ent, ent, ent, NULL, NULL, 10000, 0, MOD_UNKNOWN ); //Slow down enemy around. VectorSet( range, creepSize, creepSize, creepSize ); VectorAdd( ent->s.origin, range, maxs ); VectorSubtract( ent->s.origin, range, mins ); //find enemys around me 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 ] != ent->client->ps.stats[ STAT_PTEAM ] ) && G_Visible( ent, enemy ) ) { //enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; enemy->client->ps.stats[ STAT_STATE ] |= SS_ICEWAVED; enemy->client->lastCreepSlowTime = level.time + 5000; freezedcounter++; //G_SelectiveRadiusDamage( ent->s.pos.trBase, ent, (float)dmg, (float)500, ent, MOD_SLOWBLOB, ent->client->ps.stats[ STAT_PTEAM ] ); } } if(ent->client){ //29 Freezing Field Icewave over 5 enemies in 1 icewave if(ent->client->pers.badges[ 29 ] != 1 && freezedcounter >= 5) { ent->client->pers.badgeupdate[29] = 1; ent->client->pers.badges[29] = 1; G_WinBadge( ent, 29 ); } } } /* if(ent->client->pers.teamSelection != PTE_HUMANS && ent->client->pers.teamSelection != PTE_ALIENS ) { trap_SendServerCommand( ent-g_entities, va( "print \"^1You must be on a team\n\"" ) ); return qfalse; } if(ent->client->pers.classSelection == PCL_NONE) { trap_SendServerCommand( ent-g_entities, va( "print \"^1You must be alive\n\"" ) ); return qfalse; } if(ent->client->pers.energy >= 500) { if(ent->client->pers.teamSelection == PTE_HUMANS) { dmg = 3; } if(ent->client->pers.teamSelection == PTE_ALIENS) { dmg = 1; } ent->client->pers.energy-= 500; G_AddEvent( ent, EV_ALIEN_BUILDABLE_EXPLOSION, 0 ); VectorSet( range, creepSize, creepSize, creepSize ); VectorAdd( ent->s.origin, range, maxs ); VectorSubtract( ent->s.origin, range, mins ); //find enemys around me 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 ] != ent->client->ps.stats[ STAT_PTEAM ] ) && G_Visible( ent, enemy ) ) { //enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; enemy->client->ps.stats[ STAT_STATE ] |= SS_ICEWAVED; enemy->client->lastCreepSlowTime = level.time + 5000; //G_SelectiveRadiusDamage( ent->s.pos.trBase, ent, (float)dmg, (float)500, ent, MOD_SLOWBLOB, ent->client->ps.stats[ STAT_PTEAM ] ); } } return qtrue; }*/ }
void Bot::RegisterVisibleEnemies() { if(G_ISGHOSTING(self) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled()) return; CheckIsInThinkFrame(__FUNCTION__); // Compute look dir before loop vec3_t lookDir; AngleVectors(self->s.angles, lookDir, nullptr, nullptr); float fov = 110.0f + 69.0f * Skill(); float dotFactor = cosf((float)DEG2RAD(fov / 2)); struct EntAndDistance { int entNum; float distance; EntAndDistance(int entNum_, float distance_): entNum(entNum_), distance(distance_) {} bool operator<(const EntAndDistance &that) const { return distance < that.distance; } }; // Do not call inPVS() and G_Visible() for potential targets inside a loop for all clients. // In worst case when all bots may see each other we get N^2 traces and PVS tests // First, select all candidate targets along with distance to a bot. // Then choose not more than BotBrain::maxTrackedEnemies nearest enemies for calling OnEnemyViewed() // It may cause data loss (far enemies may have higher logical priority), // but in a common good case (when there are few visible enemies) it preserves data, // and in the worst case mentioned above it does not act weird from player POV and prevents server hang up. // Note: non-client entities also may be candidate targets. StaticVector<EntAndDistance, MAX_EDICTS> candidateTargets; for (int i = 1; i < game.numentities; ++i) { edict_t *ent = game.edicts + i; if (botBrain.MayNotBeFeasibleEnemy(ent)) continue; // Reject targets quickly by fov Vec3 toTarget(ent->s.origin); toTarget -= self->s.origin; float squareDistance = toTarget.SquaredLength(); if (squareDistance < 1) continue; float invDistance = Q_RSqrt(squareDistance); toTarget *= invDistance; if (toTarget.Dot(lookDir) < dotFactor) continue; // It seams to be more instruction cache-friendly to just add an entity to a plain array // and sort it once after the loop instead of pushing an entity in a heap on each iteration candidateTargets.emplace_back(EntAndDistance(ENTNUM(ent), 1.0f / invDistance)); } std::sort(candidateTargets.begin(), candidateTargets.end()); // Select inPVS/visible targets first to aid instruction cache, do not call callbacks in loop StaticVector<edict_t *, MAX_CLIENTS> targetsInPVS; StaticVector<edict_t *, MAX_CLIENTS> visibleTargets; static_assert(AiBaseEnemyPool::MAX_TRACKED_ENEMIES <= MAX_CLIENTS, "targetsInPVS capacity may be exceeded"); for (int i = 0, end = std::min(candidateTargets.size(), botBrain.MaxTrackedEnemies()); i < end; ++i) { edict_t *ent = game.edicts + candidateTargets[i].entNum; if (trap_inPVS(self->s.origin, ent->s.origin)) targetsInPVS.push_back(ent); } for (auto ent: targetsInPVS) if (G_Visible(self, ent)) visibleTargets.push_back(ent); // Call bot brain callbacks on visible targets for (auto ent: visibleTargets) botBrain.OnEnemyViewed(ent); botBrain.AfterAllEnemiesViewed(); CheckAlertSpots(visibleTargets); }
//========================================== // BOT_DMclass_FindEnemy // Scan for enemy (simplifed for now to just pick any visible enemy) //========================================== void BOT_DMclass_FindEnemy( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 15000 nav_ents_t *goalEnt; edict_t *bestTarget = NULL; float dist, weight, bestWeight = 9999999; int i; if( G_ISGHOSTING( self ) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled() ) { self->ai.enemyReactionDelay = 0; self->enemy = self->ai.latched_enemy = NULL; return; } // we also latch NULL enemies, so the bot can loose them if( self->ai.enemyReactionDelay > 0 ) { self->ai.enemyReactionDelay -= game.frametime; return; } self->enemy = self->ai.latched_enemy; for( i = 0; i < nav.num_goalEnts; i++ ) { goalEnt = &nav.goalEnts[i]; if( !goalEnt->ent || !goalEnt->ent->r.inuse ) continue; if( !goalEnt->ent->r.client ) // this may be changed, there could be enemies which aren't clients continue; if( G_ISGHOSTING( goalEnt->ent ) ) continue; if( self->ai.status.entityWeights[i] <= 0 || goalEnt->ent->ai.notarget ) continue; if( GS_TeamBasedGametype() && goalEnt->ent->s.team == self->s.team ) continue; dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); // ignore very soft weighted enemies unless they are in your face if( dist > 500 && self->ai.status.entityWeights[i] <= 0.1f ) continue; if( dist > 700 && dist > WEIGHT_MAXDISTANCE_FACTOR * self->ai.status.entityWeights[i] ) continue; if( trap_inPVS( self->s.origin, goalEnt->ent->s.origin ) && G_Visible( self, goalEnt->ent ) ) { weight = dist / self->ai.status.entityWeights[i]; if( ( dist < 350 ) || G_InFront( self, goalEnt->ent ) ) { if( weight < bestWeight ) { bestWeight = weight; bestTarget = goalEnt->ent; } } } } AI_NewEnemyInView( self, bestTarget ); #undef WEIGHT_MAXDISTANCE_FACTOR }
/* ============= ai_checkattack Decides if we're going to attack or do something else used by ai_run and ai_stand ============= */ bool ai_checkattack (edict_t *self, float dist) { vec3_t temp; bool hesDeadJim; // this causes monsters to run blindly to the combat point w/o firing if (self->goalentity) { if (self->monsterinfo.aiflags & AI_COMBAT_POINT) return false; if (self->monsterinfo.aiflags & AI_SOUND_TARGET) { if (level.time - self->enemy->teleport_time > 5000) { if (self->goalentity == self->enemy) { if (self->movetarget) self->goalentity = self->movetarget; else self->goalentity = NULL; } self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); } else { self->show_hostile = level.time + 1000; return false; } } } enemy_vis = false; // see if the enemy is dead hesDeadJim = false; if ((!self->enemy) || (!self->enemy->r.inuse)) { hesDeadJim = true; } else if (self->monsterinfo.aiflags & AI_MEDIC) { if (self->enemy->health > 0) { hesDeadJim = true; self->monsterinfo.aiflags &= ~AI_MEDIC; } } else { if (self->monsterinfo.aiflags & AI_BRUTAL) { if (self->enemy->health <= -80) hesDeadJim = true; } else { if (self->enemy->health <= 0) hesDeadJim = true; } } if (hesDeadJim) { self->enemy = NULL; // FIXME: look all around for other targets if (self->oldenemy && self->oldenemy->health > 0) { self->enemy = self->oldenemy; self->oldenemy = NULL; HuntTarget (self); } else { if (self->movetarget) { self->goalentity = self->movetarget; self->monsterinfo.walk (self); } else { // we need the pausetime otherwise the stand code // will just revert to walking with no target and // the monsters will wonder around aimlessly trying // to hunt the world entity self->monsterinfo.pausetime = level.time + 100000000; self->monsterinfo.stand (self); } return true; } } self->show_hostile = level.time + 1000; // wake up other monsters // check knowledge of enemy enemy_vis = G_Visible(self, self->enemy); if (enemy_vis) { self->monsterinfo.search_time = level.time + 5000; VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); } // look for other coop players here // if (coop && self->monsterinfo.search_time < level.time) // { // if (FindTarget (self)) // return true; // } enemy_infront = G_InFront(self, self->enemy); enemy_range = range(self, self->enemy); VectorSubtract (self->enemy->s.origin, self->s.origin, temp); enemy_yaw = vectoyaw(temp); // JDC self->ideal_yaw = enemy_yaw; if (self->monsterinfo.attack_state == AS_MISSILE) { ai_run_missile (self); return true; } if (self->monsterinfo.attack_state == AS_MELEE) { ai_run_melee (self); return true; } // if enemy is not currently visible, we will never attack if (!enemy_vis) return false; return self->monsterinfo.checkattack (self); }
/* =========== FindTarget Self is currently not attacking anything, so try to find a target Returns TRUE if an enemy was sighted When a player fires a missile, the point of impact becomes a fakeplayer so that monsters that see the impact will respond as if they had seen the player. To avoid spending too much time, only a single client (or fakeclient) is checked each frame. This means multi player games will have slightly slower noticing monsters. ============ */ bool FindTarget (edict_t *self) { edict_t *client; bool heardit; bool noise; int r; if (self->monsterinfo.aiflags & AI_GOOD_GUY) { if (self->goalentity && self->goalentity->r.inuse && self->goalentity->classname) { if (strcmp(self->goalentity->classname, "target_actor") == 0) return false; } //FIXME look for monsters? return false; } // if we're going to a combat point, just proceed if (self->monsterinfo.aiflags & AI_COMBAT_POINT) return false; // if the first spawnflag bit is set, the monster will only wake up on // really seeing the player, not another monster getting angry or hearing // something // revised behavior so they will wake up if they "see" a player make a noise // but not weapon impact/explosion noises heardit = false; if ((level.sight_entity_framenum+1 >= level.framenum) && !(self->spawnflags & 1) ) { client = level.sight_entity; if (client->enemy == self->enemy) { return false; } } else if (level.sound_entity_framenum+1 >= level.framenum) { client = level.sound_entity; heardit = true; } else if (!(self->enemy) && (level.sound2_entity_framenum+1 >= level.framenum) && !(self->spawnflags & 1) ) { client = level.sound2_entity; heardit = true; } else { client = level.sight_client; if (!client) return false; // no clients to get mad at } // if the entity went away, forget it if (!client->r.inuse) return false; if (client == self->enemy) return true; // JDC false; noise = strcmp(client->classname, "player_noise") == 0; if (!noise && (client->r.svflags & SVF_NOCLIENT)) return false; if (client->r.client) { if (client->flags & FL_NOTARGET) return false; } else if (client->r.svflags & SVF_MONSTER) { if (!client->enemy) return false; if (client->enemy->flags & FL_NOTARGET) return false; } else if (heardit) { if (client->r.owner->flags & FL_NOTARGET) return false; } else return false; if (!heardit) { r = range (self, client); if (r == RANGE_FAR) return false; // this is where we would check invisibility // is client in an spot too dark to be seen? // if (client->light_level <= 5) // return false; if (!G_Visible (self, client)) { return false; } if (r == RANGE_NEAR) { if (client->show_hostile < level.time && !G_InFront (self, client)) { return false; } } else if (r == RANGE_MID) { if (!G_InFront (self, client)) { return false; } } self->enemy = client; if (!noise) { self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; if (!self->enemy->r.client) { self->enemy = self->enemy->enemy; if (!self->enemy->r.client) { self->enemy = NULL; return false; } } } } else // heardit { vec3_t temp; if (self->spawnflags & 1) { if (!G_Visible (self, client)) return false; } VectorSubtract (client->s.origin, self->s.origin, temp); if (VectorLength(temp) > 1000) // too far to hear { return false; } // check area portals - if they are different and not connected then we can't hear it if (client->r.areanum != self->r.areanum) if (!trap_CM_AreasConnected(self->r.areanum, client->r.areanum)) return false; self->ideal_yaw = vectoyaw(temp); M_ChangeYaw (self); // hunt the sound for a bit; hopefully find the real player self->monsterinfo.aiflags |= AI_SOUND_TARGET; self->enemy = client; } // // got one // FoundTarget (self); if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) self->monsterinfo.sight (self, self->enemy); return true; }