void TacticalSpotsDetector::CheckAreasReachFromOrigin(const OriginParams &originParams,
                                                      const CandidateAreas &candidateAreas, ReachCheckedAreas &result)
{
    AiAasRouteCache *routeCache = originParams.routeCache;
    const aas_area_t *areas = AiAasWorld::Instance()->Areas();

    int originAreaNum = originParams.originAreaNum;
    const float *origin = originParams.origin;
    float searchRadius = originParams.searchRadius;

    // Do not more than result.capacity() iterations.
    // Some feasible areas in candidateAreas tai that pass test may be skipped,
    // but this is intended to reduce performance drops (do not more than result.capacity() pathfinder calls).
    for (unsigned i = 0, end = std::min(candidateAreas.size(), result.capacity()); i < end; ++i)
    {
        int areaNum = candidateAreas[i].areaNum;
        int travelTime = routeCache->TravelTimeToGoalArea(originAreaNum, areaNum, Bot::ALLOWED_TRAVEL_FLAGS);
        if (!travelTime)
            continue;

        // AAS time is in seconds^-2
        float travelTimeFactor = 1.0f - ComputeTravelTimeFactor(travelTime * 10);
        float distanceFactor = ComputeDistanceFactor(areas[areaNum].center, origin, searchRadius);
        float newScore = candidateAreas[i].score;
        newScore = ApplyFactor(newScore, distanceFactor, distanceInfluence);
        newScore = ApplyFactor(newScore, travelTimeFactor, travelTimeInfluence);
        result.push_back(AreaAndScore(areaNum, newScore));
    }

    // Sort result so best score areas are first
    std::sort(result.begin(), result.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() );
}