Files
GTASource/game/task/Scenario/ScenarioVehicleManager.cpp
T
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

721 lines
24 KiB
C++

//
// task/Scenario/ScenarioVehicleManager.cpp
//
// Copyright (C) 1999-2012 Rockstar Games. All Rights Reserved.
//
#include "task/Scenario/ScenarioChaining.h"
#include "task/Scenario/ScenarioChainingTests.h"
#include "task/Scenario/ScenarioDebug.h"
#include "task/Scenario/ScenarioVehicleManager.h"
#include "task/Scenario/ScenarioManager.h"
#include "task/Scenario/ScenarioPointManager.h"
#include "task/Scenario/info/ScenarioInfoManager.h"
#include "task/Default/TaskUnalerted.h"
#include "ai/ambient/AmbientModelSet.h"
#include "ai/ambient/AmbientModelSetManager.h"
#include "ai/BlockingBounds.h"
#include "bank/bank.h"
#include "fwscene/stores/staticboundsstore.h"
#include "game/config.h"
#include "Peds/PedIntelligence.h"
#include "vehicles/vehicle.h"
#include "vehicleAi/VehicleIntelligence.h"
#include "task/Scenario/ScenarioVehicleManager_parser.h"
AI_OPTIMISATIONS()
CVehicleScenarioManager::AttractorTuning::Tunables CVehicleScenarioManager::sm_Tuning;
IMPLEMENT_SCENARIO_TASK_TUNABLES(CVehicleScenarioManager::AttractorTuning, 0x6aa140bf);
//-----------------------------------------------------------------------------
void CVehicleScenarioManager::AttractorData::Init()
{
// Initialize to the current time.
m_TimeAllowToAttractNext = fwTimer::GetTimeInMilliseconds();
}
//-----------------------------------------------------------------------------
CVehicleScenarioManager::CVehicleScenarioManager()
: m_NextToUpdate(0)
{
#if __BANK
m_DebugDrawAttractors = false;
#endif // __BANK
// Find out how many attractors we should reserve space for.
const int maxAttractors = fwConfigManager::GetInstance().GetSizeOfPool(ATSTRINGHASH("VehicleScenarioAttractors", 0x6f01d3c1), CONFIGURED_FROM_FILE);
// Reserve space in the two parallel arrays.
m_AttractorScenarios.Reserve(maxAttractors);
m_AttractorData.Reserve(maxAttractors);
}
CVehicleScenarioManager::~CVehicleScenarioManager()
{
// This may not be absolutely necessary, but if it fails, it means that some point
// probably didn't unregister itself, or that things were shut down in the wrong order.
taskAssert(m_AttractorScenarios.GetCount() == 0);
}
void CVehicleScenarioManager::Update()
{
// Determine how many attractors we should update on this frame,
// but never let us update any attractor more than once per frame.
const int numAttractors = m_AttractorScenarios.GetCount();
int numToUpdate = Min(GetTuning().m_NumToUpdatePerFrame, numAttractors);
// Check if scenario attraction is being suppressed by script.
numToUpdate = CScenarioManager::IsScenarioAttractionSuppressed() ? 0 : numToUpdate;
// Loop to update attractors.
int nextToUpdate = m_NextToUpdate;
bool retryNextFrame = false;
while(numToUpdate > 0)
{
// Start over from the beginning if passing the end.
if(nextToUpdate >= numAttractors)
{
nextToUpdate = 0;
}
// Update this attractor, trying to attract a vehicle.
bool retryThisNextFrame = false;
TryToAttractVehicle(nextToUpdate, retryThisNextFrame);
retryNextFrame |= retryThisNextFrame;
// Update counters.
nextToUpdate++;
numToUpdate--;
}
if(!retryNextFrame)
{
// Remember where we should start the updates on the next frame.
m_NextToUpdate = nextToUpdate;
}
}
void CVehicleScenarioManager::AddAttractor(CScenarioPoint& pt)
{
// Fail an assert if we fill up. It may not be terrible to run with a smaller capacity
// than the absolute worst case, so we may want to take this out, but it's best to
// start out knowing if we run out.
if(!taskVerifyf(m_AttractorScenarios.GetCount() < m_AttractorScenarios.GetCapacity(),
"Out of vehicle scenario attractors, increase VehicleScenarioAttractors in 'gameconfig.xml' (currently %d)",
m_AttractorScenarios.GetCapacity()))
{
return;
}
// Extra safety check to make sure it's not already in there,
// hopefully that's not a case we need to deal with.
taskAssert(FindAttractor(pt) < 0);
// Add to both arrays.
m_AttractorScenarios.Append() = &pt;
m_AttractorData.Append().Init();
// Make sure that the arrays appear to be consistent.
taskAssert(m_AttractorData.GetCount() == m_AttractorScenarios.GetCount());
}
void CVehicleScenarioManager::RemoveAttractor(CScenarioPoint& pt)
{
// Search in the array to see if it's been added.
const int indexOfAttractor = FindAttractor(pt);
if(indexOfAttractor < 0)
{
// Not in the array, probably OK to just silently return.
return;
}
// Delete it from both arrays.
m_AttractorScenarios.DeleteFast(indexOfAttractor);
m_AttractorData.DeleteFast(indexOfAttractor);
// Make sure that the arrays appear to be consistent.
taskAssert(m_AttractorData.GetCount() == m_AttractorScenarios.GetCount());
}
#if __BANK
void CVehicleScenarioManager::AddWidgets(bkBank& bank)
{
bank.AddToggle("Draw Attractors", &m_DebugDrawAttractors);
}
void CVehicleScenarioManager::DebugRender() const
{
if(!m_DebugDrawAttractors)
{
return;
}
const int numAttractors = m_AttractorScenarios.GetCount();
for(int i = 0; i < numAttractors; i++)
{
const CScenarioPoint& pt = *m_AttractorScenarios[i];
const AttractorData& attractor = m_AttractorData[i];
const char* status = "Active";
Color32 col(0x80, 0xff, 0x40, 0x80);
if(fwTimer::GetTimeInMilliseconds() < attractor.m_TimeAllowToAttractNext)
{
col.Set(0xff, 0x40, 0x40, 0x80);
status = "Not ready";
}
const Vec3V posV = pt.GetPosition();
grcDebugDraw::Sphere(posV, 0.5f, col);
grcDebugDraw::Text(posV, Color_white, 0, 0, status);
}
}
void CVehicleScenarioManager::ClearAttractionTimers()
{
const int numAttractors = m_AttractorScenarios.GetCount();
for(int i = 0; i < numAttractors; i++)
{
AttractorData& attractor = m_AttractorData[i];
attractor.m_TimeAllowToAttractNext = fwTimer::GetTimeInMilliseconds();
}
}
#endif // __BANK
bool CVehicleScenarioManager::CanAttractorBeUsed(int attractorIndex) const
{
// Here, we check various conditions for whether this attractor can be used
// right now or not. Most of this is standard stuff for scenarios, like
// time of day.
const CScenarioPoint& pt = *m_AttractorScenarios[attractorIndex];
const int indexWithinGroup = 0; // We don't support virtual scenario types as attractors.
if(!CScenarioManager::CheckScenarioPointEnabled(pt, indexWithinGroup))
{
return false;
}
if(!CScenarioManager::CheckScenarioPointTimesAndProbability(pt, false, true, indexWithinGroup))
{
return false;
}
// The attractor points are a bit odd in that WORLD_VEHICLE_ATTRACTOR has SpawnProbability of 0,
// which makes some sense in that we don't want anything to spawn there, but it seems useful to
// be able to set a probability on the point, and respect that.
if(pt.HasProbabilityOverride())
{
if(!CScenarioManager::CheckScenarioProbability(&pt, -1 /* shouldn't be used */))
{
return false;
}
}
if(pt.HasHistory())
{
return false;
}
if(!CScenarioManager::CheckScenarioPointMapData(pt))
{
return false;
}
const Vec3V posV = pt.GetPosition();
if(CScenarioBlockingAreas::IsPointInsideBlockingAreas(VEC3V_TO_VECTOR3(posV), false, true))
{
return false;
}
if (pt.IsChained() && pt.IsUsedChainFull())
{
return false;
}
return true;
}
bool CVehicleScenarioManager::CanVehicleBeAttractedByAnything(const CVehicle& veh) const
{
// Only attract random vehicles.
if(!veh.PopTypeIsRandom())
{
return false;
}
if(!veh.m_nVehicleFlags.bUsingPretendOccupants)
{
// Check to make sure the vehicle has the correct number of passengers.
const AttractorTuning::Tunables& tuning = GetTuning();
s32 iNumberOfPassengers = veh.GetNumberOfPassenger();
if (iNumberOfPassengers < tuning.m_MinPassengersForAttraction || iNumberOfPassengers > tuning.m_MaxPassengersForAttraction)
{
return false;
}
}
// Probably shouldn't try to attract a cloned vehicle - we can't or shouldn't give them tasks, etc,
// so probably no good can come from that. Similarly, it's probably best to avoid a vehicle
// that's migrating.
if(NetworkUtils::IsNetworkCloneOrMigrating(&veh))
{
return false;
}
// Check for occupants - we either want a driver driving around aimlessly,
// or pretend occupants that we can turn into real occupants if we decide
// to take control.
CPed* pDriver = veh.GetDriver();
if(pDriver)
{
// For now, only allow us to take control over somebody running a regular wander drive task.
const CPedIntelligence& intel = *pDriver->GetPedIntelligence();
CTask* pTask = intel.GetTaskActive();
if(!pTask || pTask->GetTaskType() != CTaskTypes::TASK_CAR_DRIVE_WANDER)
{
return false;
}
}
else if(!veh.m_nVehicleFlags.bUsingPretendOccupants)
{
// No driver but not using pretend occupants, may be an abandoned or parked vehicle
// of some sort, probably shouldn't touch it.
return false;
}
//if we are using pretend occupants, but reacting to an event, also don't allow attraction
if (veh.m_nVehicleFlags.bUsingPretendOccupants && veh.GetIntelligence()->GetPretendOccupantEventPriority() > VehicleEventPriority::VEHICLE_EVENT_PRIORITY_NONE)
{
return false;
}
// can't use attractors if you have a trailer
// no idea why see bug 861861
if (veh.HasTrailer())
{
return false;
}
// If we are not using pretend occupants, we might have some passengers scheduled to
// spawn in as passengers. Even if we are using pretend occupants, if for some reason
// we still have someone scheduled, we also probably wouldn't want to be attracted.
if(veh.HasScheduledOccupants())
{
return false;
}
return true;
}
bool CVehicleScenarioManager::CanVehicleBeAttractedByPoint(const CVehicle& veh, const CScenarioPoint& pt,
const CAmbientModelSet* pAllowedModelSet, const CAmbientModelSetFilter * pModelsFilter) const
{
// First, check if the vehicle could be attracted by anything at all.
if(!CanVehicleBeAttractedByAnything(veh))
{
return false;
}
// Grab the model info.
CVehicleModelInfo* pModelInfo = veh.GetVehicleModelInfo();
Assert(pModelInfo);
// If we have a model set restriction, check if this vehicle is allowed.
if(pAllowedModelSet && !pAllowedModelSet->GetContainsModel(veh.GetBaseModelInfo()->GetModelNameHash()))
{
return false;
}
// Check if the vehicle is restricted inside of vehicles.meta.
else if (!pAllowedModelSet && pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_BLOCK_FROM_ATTRACTOR_SCENARIO))
{
return false;
}
else if (!pAllowedModelSet && (veh.InheritsFromBike() || veh.InheritsFromQuadBike() || veh.InheritsFromAmphibiousQuadBike()) )
{
// Disallow vehicle attractors for things that use helmets unless specifically requested in the model info, B* 1349827 [5/15/2013 mdawe]
return false;
}
else if(!pAllowedModelSet && veh.IsLawEnforcementVehicle())
{
// Unless directly specified, we probably wouldn't want law enforcement vehicles to become
// attracted to scenarios. One reason for this is that they have special rules for population
// with passengers, which we don't want, but also, just potential of not looking right or
// not reacting properly.
return false;
}
if (pModelsFilter)
{
// If any of the models don't pass the filter it is blocked
for (s32 i = 0; i < veh.GetSeatManager()->GetMaxSeats(); ++i)
{
const CPed * pPed = veh.GetSeatManager()->GetPedInSeat(i);
if (pPed && !pModelsFilter->Match(pPed->GetPedModelInfo()->GetModelNameHash()))
{
return false;
}
}
}
// Get the route helper. If we don't have one, we don't allow attraction
// right now.
const CVehicleIntelligence* pIntel = veh.GetIntelligence();
if(!pIntel)
{
return false;
}
const CVehicleFollowRouteHelper* pRouteHelper = pIntel->GetFollowRouteHelper();
if(!pRouteHelper)
{
return false;
}
// Get the scenario position.
const Vec3V scenarioPosV = pt.GetPosition();
// Get the vehicle position and forward direction.
const fwTransform& transform = veh.GetTransform();
const Vec3V vehPosV = transform.GetPosition();
const Vec3V vehDirV = transform.GetForward();
// Compute a vector from the vehicle to the scenario.
const Vec3V vehToScenarioV = Subtract(scenarioPosV, vehPosV);
// Compute the dot product between the forward direction (which should be a unit vector)
// and the vector to the scenario.
const ScalarV dotDirV = Dot(vehDirV, vehToScenarioV);
const ScalarV zeroV(V_ZERO);
const AttractorTuning::Tunables& tuning = GetTuning();
// We will square the dot product to avoid a square root, but for that to be
// valid, it must not be negative.
if(IsGreaterThanAll(dotDirV, zeroV))
{
// Compute the square of the minimum distance (could be precomputed).
const ScalarV minDistV = LoadScalar32IntoScalarV(tuning.m_MinDistToVehicle);
const ScalarV minDistSqV = Scale(minDistV, minDistV);
// Compute the squared distance to the scenario.
const ScalarV distSqV = MagSquared(vehToScenarioV);
// Check if the vehicle is too close to the scenario.
if(IsLessThanAll(distSqV, minDistSqV))
{
return false;
}
// Compute the square of the dot product. This will be proportional to the
// square of the cosine of the angle and the square of the distance.
const ScalarV dotDirSqV = Scale(dotDirV, dotDirV);
// Multiply the squared cosine threshold by the square of the distance.
const ScalarV thresholdSqV(tuning.m_ForwardDirectionThresholdCosSquared);
const ScalarV minDotDirSqV = Scale(distSqV, thresholdSqV);
// Check if the scenario is within the desired angle of the forward direction.
if(!IsGreaterThanAll(dotDirSqV, minDotDirSqV))
{
return false;
}
}
else
{
// The point is behind the vehicle, don't allow the attraction.
return false;
}
// Find the maximum allowed distance between the scenario and the closest point on the path.
// This uses a default value, or is taken from the point's radius parameter, if it's set.
float maxDistToPath = GetTuning().m_MaxDistToPathDefault;
const float radiusFromPt = pt.GetRadius();
maxDistToPath = Selectf(-radiusFromPt, maxDistToPath, radiusFromPt);
// Let the route helper do the check.
if(!pRouteHelper->DoesPathPassNearPoint(scenarioPosV, vehPosV, maxDistToPath))
{
// The route doesn't get close enough to the scenario.
return false;
}
// Success!
return true;
}
int CVehicleScenarioManager::FindAttractor(CScenarioPoint& pt)
{
// TODO: Consider a more efficient data structure (perhaps with the point
// keeping track of the index), if this ends up being called a lot.
const int numAttractors = m_AttractorScenarios.GetCount();
for(int i = 0; i < numAttractors; i++)
{
if(m_AttractorScenarios[i] == &pt)
{
return i;
}
}
return -1;
}
void CVehicleScenarioManager::PreventAttractionForTime(int attractorIndex, u32 preventedForTimeMs)
{
AttractorData& a = m_AttractorData[attractorIndex];
// Note: this probably doesn't work properly if time wraps around.
u32 newTimeMs = fwTimer::GetTimeInMilliseconds() + preventedForTimeMs;
a.m_TimeAllowToAttractNext = Max(newTimeMs, a.m_TimeAllowToAttractNext);
}
bool CVehicleScenarioManager::TakeControlOverVehicle(CVehicle& veh, int attractorIndex, const CAmbientModelSetFilter * pModelsFilter)
{
CScenarioPoint& pt = *m_AttractorScenarios[attractorIndex];
taskAssertf(!veh.GetNumberOfPassenger(), "Scenario controlled vehicles should not have passengers: vehicle '%s' has %d (pretend occ: %d).",
veh.GetDebugName(), veh.GetNumberOfPassenger(), (int)veh.m_nVehicleFlags.bUsingPretendOccupants);
// When we add occupants through SetUpDriver(), apparently we pass in 0 as the seat index when calling
// AddPedInCar(), even though theoretically, another seat might be specified as the driver seat. If so,
// the driver might be counted as a passenger and cause other asserts to fail.
taskAssertf(veh.GetDriverSeat() == 0, "Vehicle '%s' has driver seat set to %d, the driver might get counted as a passenger.",
veh.GetDebugName(), veh.GetDriverSeat());
// Try to make sure we've got actual peds as occupants in the vehicle.
if(veh.m_nVehicleFlags.bUsingPretendOccupants)
{
if(!veh.TryToMakePedsFromPretendOccupants(true, NULL, pModelsFilter))
{
return false;
}
taskAssertf(!veh.GetNumberOfPassenger(),
"Scenario controlled vehicle '%s' has %d passengers after TryToMakePedsFromPretendOccupants().",
veh.GetDebugName(), veh.GetNumberOfPassenger());
}
// Make sure we have a driver.
CPed* pDriver = veh.GetDriver();
if(!pDriver)
{
return false;
}
// Prevent the vehicle from returning to pretend occupants.
veh.m_nVehicleFlags.bDisablePretendOccupants = true;
// Give the driver a task, which knows how to drive to the scenario point,
// and use chained scenarios from there.
CPedIntelligence& intel = *pDriver->GetPedIntelligence();
const int scenarioType = CScenarioManager::ChooseRealScenario(pt.GetScenarioTypeVirtualOrReal(), pDriver);
CTaskUnalerted* pTask = rage_new CTaskUnalerted(NULL, &pt, scenarioType);
// Use road nodes when navigating towards an attractor point.
pTask->SetScenarioNavMode(CScenarioChainingEdge::kRoads);
// Use the speed of the first link in the chain when navigating towards an attractor point.
CScenarioChainingEdge::eNavSpeed iNavSpeed = CScenarioChainingEdge::kNavSpeedDefault;
CScenarioPointChainUseInfo* pChainUseInfo = NULL;
if (pt.IsChained())
{
int numPts = 0;
static const int kMaxPts = 16;
CScenarioChainSearchResults searchResultsArray[kMaxPts];
numPts = SCENARIOPOINTMGR.FindChainedScenarioPoints(pt, searchResultsArray, NELEM(searchResultsArray));
// We could check the conditions on each of the return results and pick only valid points (similar to CScenarioFinder::SearchForNextScenarioInChain_OnUpdate()),
// but that seems unnecessary as whether or not the edge meets this vehicle's conditions could change by the time the vehicle actually gets to the chain.
// Also, we're not really expecting attractor scenario points to be connected to more than one chain, so just pick the first result in the array.
if (numPts > 0)
{
iNavSpeed = (CScenarioChainingEdge::eNavSpeed)(searchResultsArray[0].m_NavSpeed);
}
// Register a chain user. CTaskUnalerted would have done that anyway once it updates,
// but there would potentially be a window in time where another vehicle could get
// attracted to this chain, or a ped could spawn on it, etc. Registering here
// will immediately mark the chain as full (unless it can support more users),
// and we can also pass in the user info to the history.
pChainUseInfo = CScenarioChainingGraph::RegisterPointChainUser(pt, pDriver, &veh);
pTask->SetScenarioChainUserInfo(pChainUseInfo);
}
pTask->SetScenarioNavSpeed((u8)iNavSpeed);
intel.AddTaskDefault(pTask);
// If the point doesn't already have history, add it.
if(!pt.HasHistory())
{
CScenarioManager::GetSpawnHistory().Add(veh, pt, scenarioType, pChainUseInfo);
}
// TODO: Perhaps we should also register this with CScenarioManager::sm_SpawnedVehicles
// (perhaps renaming that array sm_ScenarioVehicles, and moving it to this manager).
// However, that probably wouldn't do too much good in the present form, since it looks
// like it just stores the scenario type of the first scenario and uses that, and in this
// case, the first scenario is always an attractor.
// Success!
return true;
}
void CVehicleScenarioManager::TryToAttractVehicle(int attractorIndex, bool& retryNextFrameOut)
{
CScenarioPoint& pt = *m_AttractorScenarios[attractorIndex];
retryNextFrameOut = false;
// Allow only the selected point to attract vehicles for debug purposes.
#if SCENARIO_DEBUG
if (CScenarioDebug::ms_bRestrictVehicleAttractionToSelectedPoint && !CScenarioDebug::ms_Editor.IsPointSelected(pt))
{
return;
}
#endif // SCENARIO_DEBUG
// Make sure the point isn't already in use.
if(pt.GetUses() > 0)
{
return;
}
// Check if the attractor is ready.
AttractorData& attractor = m_AttractorData[attractorIndex];
if(fwTimer::GetTimeInMilliseconds() < attractor.m_TimeAllowToAttractNext)
{
// Not ready.
return;
}
// Get the scenario info.
const CScenarioInfo* pInfo = SCENARIOINFOMGR.GetScenarioInfoByIndex(pt.GetScenarioTypeVirtualOrReal());
if(!pInfo || !pInfo->GetIsClass<CScenarioVehicleInfo>())
{
return;
}
const CScenarioVehicleInfo* pVehInfo = static_cast<const CScenarioVehicleInfo*>(pInfo);
const AttractorTuning::Tunables& tuning = GetTuning();
// Set up a sphere to use for the vehicle search.
const Vec3V sphereCenterV = pt.GetPosition();
const ScalarV sphereRadiusV(tuning.m_MaxDistToVehicle);
// Check if physics bounds are loaded near the point.
// TODO: Perhaps we should extend this to check within some proximity.
if(!g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(sphereCenterV, fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER))
{
PreventAttractionForTime(attractorIndex, tuning.m_TimeAfterNoBoundsMs);
return;
}
// Check other conditions on the attractor, before spending time to look for vehicles.
if(!CanAttractorBeUsed(attractorIndex))
{
PreventAttractionForTime(attractorIndex, GetTuning().m_TimeAfterFailedConditionsMs);
return;
}
// Set up a temporary array when searching for vehicles.
static const int kMaxFound = 128;
CSpatialArray::FindResult vehiclesFound[kMaxFound];
const CSpatialArray& spatialArray = *CVehicle::ms_spatialArray;
// Look for vehicles.
int numFound = spatialArray.FindInSphere(sphereCenterV, sphereRadiusV, vehiclesFound, kMaxFound);
if(numFound <= 0)
{
// Early out, may be some stuff below we don't need to do.
return;
}
// Get the model set from the point, if any has been set.
const CAmbientModelSet* pModelSet = CScenarioManager::GetVehicleModelSetFromIndex(*pVehInfo, pt.GetModelSetIndex());
const int kMaxBlockedModelSets = 24;
const CAmbientPedModelSet * pBlockedModelSets[kMaxBlockedModelSets] = { NULL };
int iNumBlockedModelSets = 0;
Assert(pInfo);
bool bBlockBulkyItems = false;
CScenarioManager::GatherBlockedModelSetsFromChainPt(pt, *pInfo, pBlockedModelSets, iNumBlockedModelSets, bBlockBulkyItems);
CAmbientModelSetFilterForScenario modelsFilter;
modelsFilter.m_BlockedModels = pBlockedModelSets;
modelsFilter.m_NumBlockedModelSets = iNumBlockedModelSets;
modelsFilter.m_bProhibitBulkyItems = bBlockBulkyItems;
// Loop over the vehicles we found.
while(numFound > 0)
{
// Pick one randomly.
const int index = g_ReplayRand.GetRanged(0, numFound - 1);
CVehicle& veh = *CVehicle::GetVehicleFromSpatialArrayNode(vehiclesFound[index].m_Node);
// Check if this vehicle can be attracted.
if(CanVehicleBeAttractedByPoint(veh, pt, pModelSet, &modelsFilter))
{
// Request a test to see if this chain is blocked. We won't necessarily get a response
// back this frame.
bool requestDone = false;
bool usable = false;
CParkingSpaceFreeTestVehInfo vehInfo;
vehInfo.m_ModelInfo = veh.GetBaseModelInfo();
CScenarioManager::GetChainTests().RequestTest(pt, vehInfo, requestDone, usable);
// If we didn't get a result back yet, try again on the next frame.
if(!requestDone)
{
retryNextFrameOut = true;
break; // No need to test other vehicles.
}
// If it wasn't usable, prevent us from trying again for some time. These tests
// can be somewhat expensive and there is no great urgency in attracting immediately.
if(!usable)
{
PreventAttractionForTime(attractorIndex, sm_Tuning.m_TimeAfterChainTestFailedMs);
break; // No need to test other vehicles.
}
// Attempt to take control over it.
if(TakeControlOverVehicle(veh, attractorIndex, &modelsFilter))
{
// Success: mark the attractor as having been used, and break out.
// TODO: It would be good to allow the point to override m_TimeAfterAttractionMs,
// probably by repurposing CScenarioPoint::m_iTimeTillPedLeaves.
PreventAttractionForTime(attractorIndex, sm_Tuning.m_TimeAfterAttractionMs);
break;
}
}
// Remove the vehicle we just tested from the array.
vehiclesFound[index] = vehiclesFound[--numFound];
}
}
//-----------------------------------------------------------------------------
// End of file 'task/Scenario/ScenarioVehicleManager.cpp'