void TacticalSpotsDetector::SelectCandidateAreas(const OriginParams &originParams, const int *areas, int numAreas, CandidateAreas &result) { const aas_area_t *worldAreas = AiAasWorld::Instance()->Areas(); const aas_areasettings_t *worldAreaSettings = AiAasWorld::Instance()->AreaSettings(); int badAreaContents = 0; badAreaContents |= AREACONTENTS_LAVA | AREACONTENTS_SLIME | AREACONTENTS_DONOTENTER; badAreaContents |= AREACONTENTS_JUMPPAD | AREACONTENTS_TELEPORTER | AREACONTENTS_MOVER | AREACONTENTS_WATER; for (int i = 0; i < numAreas && result.size() < result.capacity(); ++i) { const aas_areasettings_t &areaSettings = worldAreaSettings[areas[i]]; if (!(areaSettings.areaflags & AREA_GROUNDED)) continue; if (areaSettings.areaflags & (AREA_DISABLED|AREA_JUNK)) continue; if (areaSettings.contents & badAreaContents) continue; if (!areaSettings.numreachableareas) continue; const aas_area_t &area = worldAreas[areas[i]]; float height = area.mins[2] - originParams.origin[2]; if (height < minHeightAdvantage) continue; float dx = area.maxs[0] - area.mins[0]; float dy = area.maxs[1] - area.mins[1]; // Skip small areas. If an area did not qualify as "junk" its not enough. if (dx < 24.0f || dy < 24.0f) continue; float score = 1.0f; // Increase score for higher areas float heightFactor = BoundedFraction(height - minHeightAdvantage, originParams.searchRadius); score = ApplyFactor(score, heightFactor, heightInfluence); // Increase scores for larger areas score *= 1.0f + 2.0f * BoundedFraction(dx - 24.0f, 96.0f); score *= 1.0f + 2.0f * BoundedFraction(dy - 24.0f, 96.0f); if ((areaSettings.areaflags & AREA_LEDGE)) score *= ledgePenalty; if ((areaSettings.areaflags & AREA_WALL)) score *= wallPenalty; result.push_back(AreaAndScore(areas[i], score)); } // Sort result so best score areas are first std::sort(result.begin(), result.end()); }
void TacticalSpotsDetector::SelectAreasForCover(const OriginParams &originParams, const CoverProblemParams &problemParams, ReachCheckedAreas &candidateAreas, TraceCheckedAreas &result) { const aas_area_t *worldAreas = AiAasWorld::Instance()->Areas(); // Do not more result.capacity() iterations for (unsigned i = 0, end = std::min(candidateAreas.size(), result.capacity()); i < end; ++i) { const aas_area_t &area = worldAreas[candidateAreas[i].areaNum]; if (!LooksLikeACoverArea(area, originParams, problemParams)) continue; // Prefer larger areas float dimensionFactor = 1.0f; dimensionFactor *= BoundedFraction(area.maxs[0] - area.mins[0], 64); dimensionFactor *= BoundedFraction(area.maxs[1] - area.mins[1], 64); result.push_back(AreaAndScore(candidateAreas[i].areaNum, candidateAreas[i].score * dimensionFactor)); }; // Sort result so best score areas are first std::sort(result.begin(), result.end()); }
void TacticalSpotsDetector::SortByVisAndOtherFactors(const OriginParams ¶ms, TraceCheckedAreas &areas) { const aas_area_t *worldAreas = AiAasWorld::Instance()->Areas(); const aas_areasettings_t *worldAreaSettings = AiAasWorld::Instance()->AreaSettings(); const float originZ = params.origin[2]; const float searchRadius = params.searchRadius; // A matrix of trace results: // -1 means that trace has not been computed // 0 means that trace.fraction < 1.0f // 1 means that trace.fraction == 1.0f signed char traceResultCache[areas.capacity() * areas.capacity()]; std::fill(traceResultCache, traceResultCache + areas.capacity() * areas.capacity(), -1); // Compute area points to avoid doing it in the trace loop. vec3_t areaPoints[areas.capacity()]; for (unsigned i = 0; i < areas.size(); ++i) { const aas_area_t &area = worldAreas[areas[i].areaNum]; VectorCopy(area.center, areaPoints[i]); areaPoints[i][2] = area.mins[2] + PLAYER_VIEW_GROUND_OFFSET; } trace_t trace; for (unsigned i = 0; i < areas.size(); ++i) { // Avoid fp <-> int conversions in a loop int numVisAreas = 0; for (unsigned j = 0; j < i; ++j) { int cachedTraceResult = traceResultCache[areas.capacity() * i + j]; if (cachedTraceResult >= 0) { numVisAreas += cachedTraceResult; continue; } G_Trace(&trace, areaPoints[i], nullptr, nullptr, areaPoints[j], nullptr, MASK_AISOLID); // Omit fractional part by intention signed char visibility = (signed char)trace.fraction; traceResultCache[areas.capacity() * i + j] = visibility; traceResultCache[areas.capacity() * j + i] = visibility; numVisAreas += visibility; } // Skip a trace from an area to itself for (unsigned j = i + 1; j < areas.size(); ++j) { int cachedTraceResult = traceResultCache[areas.capacity() * i + j]; if (cachedTraceResult >= 0) { numVisAreas += cachedTraceResult; continue; } G_Trace(&trace, areaPoints[i], nullptr, nullptr, areaPoints[j], nullptr, MASK_AISOLID); // Omit fractional part by intention signed char visibility = (signed char)trace.fraction; traceResultCache[areas.capacity() * i + j] = visibility; traceResultCache[areas.capacity() * j + i] = visibility; numVisAreas += visibility; } float visFactor = numVisAreas / (float)areas.size(); visFactor = 1.0f / Q_RSqrt(visFactor); areas[i].score *= 0.1f + 0.9f * visFactor; // We should modify the final score by following factors. // These factors should be checked in earlier calls but mainly for early rejection of non-suitable areas. const int areaNum = areas[i].areaNum; float height = (worldAreas[areaNum].mins[2] - originZ - minHeightAdvantage); float heightFactor = BoundedFraction(height, searchRadius - minHeightAdvantage); areas[i].score = ApplyFactor(areas[i].score, heightFactor, heightInfluence); const aas_areasettings_t &areaSettings = worldAreaSettings[areaNum]; if (areaSettings.areaflags & AREA_WALL) areas[i].score *= wallPenalty; if (areaSettings.areaflags & AREA_LEDGE) areas[i].score *= ledgePenalty; } // Sort results so best score areas are first std::sort(areas.begin(), areas.end()); }
void BotTacticalSpotsCache::FindReachableClassEntities( const Vec3 &origin, float radius, const char *classname, BotTacticalSpotsCache::ReachableEntities &result ) { int *triggerEntities; int numEntities = FindNearbyEntities( origin, radius, &triggerEntities ); ReachableEntities candidateEntities; // Copy to locals for faster access (a compiler might be paranoid about aliasing) edict_t *gameEdicts = game.edicts; if( numEntities > (int)candidateEntities.capacity() ) { for( int i = 0; i < numEntities; ++i ) { edict_t *ent = gameEdicts + triggerEntities[i]; // Specify expected strcmp() result explicitly to avoid misinterpreting the condition // (Strings are equal if an strcmp() result is zero) if( strcmp( ent->classname, classname ) != 0 ) { continue; } float distance = DistanceFast( origin.Data(), ent->s.origin ); candidateEntities.push_back( EntAndScore( triggerEntities[i], radius - distance ) ); if( candidateEntities.size() == candidateEntities.capacity() ) { break; } } } else { for( int i = 0; i < numEntities; ++i ) { edict_t *ent = gameEdicts + triggerEntities[i]; if( strcmp( ent->classname, classname ) != 0 ) { continue; } float distance = DistanceFast( origin.Data(), ent->s.origin ); candidateEntities.push_back( EntAndScore( triggerEntities[i], radius - distance ) ); } } const AiAasWorld *aasWorld = AiAasWorld::Instance(); AiAasRouteCache *routeCache = self->ai->botRef->routeCache; bool testTwoCurrAreas = false; int fromAreaNum = 0; // If an origin matches actual bot origin if( ( origin - self->s.origin ).SquaredLength() < WorldState::OriginVar::MAX_ROUNDING_SQUARE_DISTANCE_ERROR ) { // Try testing both areas if( self->ai->botRef->CurrAreaNum() != self->ai->botRef->DroppedToFloorAreaNum() ) { testTwoCurrAreas = true; } else { fromAreaNum = self->ai->botRef->CurrAreaNum(); } } else { fromAreaNum = aasWorld->FindAreaNum( origin ); } if( testTwoCurrAreas ) { int fromAreaNums[2] = { self->ai->botRef->CurrAreaNum(), self->ai->botRef->DroppedToFloorAreaNum() }; for( EntAndScore &candidate: candidateEntities ) { edict_t *ent = gameEdicts + candidate.entNum; int toAreaNum = FindMostFeasibleEntityAasArea( ent, aasWorld ); if( !toAreaNum ) { continue; } int travelTime = 0; for( int i = 0; i < 2; ++i ) { travelTime = routeCache->TravelTimeToGoalArea( fromAreaNums[i], toAreaNum, Bot::ALLOWED_TRAVEL_FLAGS ); if( travelTime ) { break; } } if( !travelTime ) { continue; } // AAS travel time is in seconds^-2 float factor = 1.0f / Q_RSqrt( 1.0001f - BoundedFraction( travelTime, 200 ) ); result.push_back( EntAndScore( candidate.entNum, candidate.score * factor ) ); } } else { for( EntAndScore &candidate: candidateEntities ) { edict_t *ent = gameEdicts + candidate.entNum; int toAreaNum = FindMostFeasibleEntityAasArea( ent, aasWorld ); if( !toAreaNum ) { continue; } int travelTime = routeCache->TravelTimeToGoalArea( fromAreaNum, toAreaNum, Bot::ALLOWED_TRAVEL_FLAGS ); if( !travelTime ) { continue; } float factor = 1.0f / Q_RSqrt( 1.0001f - BoundedFraction( travelTime, 200 ) ); result.push_back( EntAndScore( candidate.entNum, candidate.score * factor ) ); } } // Sort entities so best entities are first std::sort( result.begin(), result.end() ); }