2882 lines
83 KiB
C++
2882 lines
83 KiB
C++
//
|
|
// task/Scenario/ScenarioClustering.cpp
|
|
//
|
|
// Copyright (C) 1999-2011 Rockstar Games. All Rights Reserved.
|
|
//
|
|
|
|
// Rage headers
|
|
|
|
// Framework headers
|
|
#include "fwscene/stores/staticboundsstore.h"
|
|
|
|
// Game headers
|
|
#include "ai/BlockingBounds.h"
|
|
#include "Network/Objects/NetworkObjectPopulationMgr.h"
|
|
#include "pathserver/PathServer.h"
|
|
#include "Peds/ped.h"
|
|
#include "Peds/PedFactory.h"
|
|
#include "Peds/PedIntelligence.h"
|
|
#include "Peds/pedpopulation.h"
|
|
#include "scene/FocusEntity.h"
|
|
#include "scene/world/GameWorld.h"
|
|
#include "streaming/populationstreaming.h"
|
|
#include "task/Default/TaskUnalerted.h"
|
|
#include "task/Scenario/ScenarioChaining.h"
|
|
#include "task/Scenario/ScenarioClustering.h"
|
|
#include "task/Scenario/ScenarioManager.h"
|
|
#include "task/Scenario/ScenarioPoint.h"
|
|
#include "task/Scenario/ScenarioPointManager.h"
|
|
#include "task/Scenario/info/ScenarioInfoManager.h"
|
|
#include "task/Scenario/Types/TaskUseScenario.h"
|
|
#include "task/Service/Army/TaskArmy.h"
|
|
#include "task/Service/Fire/TaskFirePatrol.h"
|
|
#include "task/Service/Medic/TaskAmbulancePatrol.h"
|
|
#include "task/Service/Police/TaskPolicePatrol.h"
|
|
#include "Vehicles/cargen.h"
|
|
#include "Vehicles/VehicleFactory.h"
|
|
#include "Vehicles/vehiclepopulation.h"
|
|
|
|
#if SCENARIO_DEBUG
|
|
#include "task/Scenario/ScenarioDebug.h"
|
|
#endif // SCENARIO_DEBUG
|
|
|
|
AI_OPTIMISATIONS()
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CScenarioPointCluster
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
CScenarioPointCluster::CScenarioPointCluster()
|
|
: m_fNextSpawnAttemptDelay(30.0f)
|
|
, m_bAllPointRequiredForSpawn(false)
|
|
{
|
|
|
|
}
|
|
|
|
CScenarioPointCluster::~CScenarioPointCluster()
|
|
{
|
|
CleanUp();
|
|
}
|
|
|
|
u32 CScenarioPointCluster::GetNumPoints() const
|
|
{
|
|
return m_Points.GetNumPoints();
|
|
}
|
|
|
|
const CScenarioPoint& CScenarioPointCluster::GetPoint(const u32 index) const
|
|
{
|
|
return m_Points.GetPoint(index);
|
|
}
|
|
|
|
CScenarioPoint& CScenarioPointCluster::GetPoint(const u32 index)
|
|
{
|
|
return m_Points.GetPoint(index);
|
|
}
|
|
|
|
void CScenarioPointCluster::PostLoad(const CScenarioPointLoadLookUps& lookups)
|
|
{
|
|
m_Points.PostLoad(lookups);
|
|
|
|
//set the points as being in a cluster
|
|
const u32 count = GetNumPoints();
|
|
for (u32 p = 0; p < count; p++)
|
|
{
|
|
CScenarioPoint& point = GetPoint(p);
|
|
point.SetRunTimeFlag(CScenarioPointRuntimeFlags::IsInCluster, true);
|
|
}
|
|
|
|
SCENARIOCLUSTERING.AddCluster(*this);
|
|
}
|
|
|
|
void CScenarioPointCluster::CleanUp()
|
|
{
|
|
m_Points.CleanUp();
|
|
SCENARIOCLUSTERING.RemoveCluster(*this);
|
|
}
|
|
|
|
const spdSphere& CScenarioPointCluster::GetSphere() const
|
|
{
|
|
return m_ClusterSphere;
|
|
}
|
|
|
|
float CScenarioPointCluster::GetSpawnDelay() const
|
|
{
|
|
return m_fNextSpawnAttemptDelay;
|
|
}
|
|
|
|
bool CScenarioPointCluster::GetAllPointRequiredForSpawn() const
|
|
{
|
|
return m_bAllPointRequiredForSpawn;
|
|
}
|
|
|
|
#if SCENARIO_DEBUG
|
|
void CScenarioPointCluster::AddWidgets(bkGroup* parentGroup)
|
|
{
|
|
//TODO: need callback info here so editor knows that region edits are happening ...
|
|
parentGroup->AddSlider("Failed Spawn Delay (sec)", &m_fNextSpawnAttemptDelay, 0.0f, 500.0f, 1.0f, CScenarioEditor::RegionEditedCallback);
|
|
parentGroup->AddToggle("All Point Required for Spawn", &m_bAllPointRequiredForSpawn, CScenarioEditor::RegionEditedCallback);
|
|
}
|
|
|
|
void CScenarioPointCluster::PrepSaveObject(const CScenarioPointCluster& prepFrom, CScenarioPointLookUps& out_LookUps)
|
|
{
|
|
m_Points.PrepSaveObject(prepFrom.m_Points, out_LookUps);
|
|
|
|
m_ClusterSphere = prepFrom.m_ClusterSphere;
|
|
m_fNextSpawnAttemptDelay = prepFrom.m_fNextSpawnAttemptDelay;
|
|
m_bAllPointRequiredForSpawn = prepFrom.m_bAllPointRequiredForSpawn;
|
|
}
|
|
|
|
int CScenarioPointCluster::AddPoint(CScenarioPoint& point)
|
|
{
|
|
const int index = m_Points.AddPoint(point);
|
|
point.SetRunTimeFlag(CScenarioPointRuntimeFlags::IsInCluster, true);
|
|
UpdateSphere();
|
|
return index;
|
|
}
|
|
|
|
CScenarioPoint* CScenarioPointCluster::RemovePoint(const int pointIndex)
|
|
{
|
|
CScenarioPoint* removed = m_Points.RemovePoint(pointIndex);
|
|
removed->SetRunTimeFlag(CScenarioPointRuntimeFlags::IsInCluster, false);
|
|
UpdateSphere();
|
|
return removed;
|
|
}
|
|
|
|
void CScenarioPointCluster::UpdateSphere()
|
|
{
|
|
m_ClusterSphere.SetV4(Vec4V(V_ZERO));
|
|
const u32 pcount = m_Points.GetNumPoints();
|
|
for(u32 p = 0; p < pcount; p++)
|
|
{
|
|
CScenarioPoint& point = m_Points.GetPoint(p);
|
|
|
|
Vec3V position = point.GetPosition();
|
|
ScalarV radius = FindPointSpawnRadius(point);
|
|
|
|
if (!p)
|
|
{
|
|
m_ClusterSphere.Set(position, radius);
|
|
}
|
|
else
|
|
{
|
|
spdSphere sphere(position, radius);
|
|
m_ClusterSphere.GrowSphere(sphere);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScalarV_Out CScenarioPointCluster::FindPointSpawnRadius(const CScenarioPoint& /*point*/) const
|
|
{
|
|
//TODO: find true value for this ... it is a guess...
|
|
// if (point.HasExtendedRange())
|
|
// {
|
|
// return ScalarV(V_TEN);//ScalarV(CPedPopulation::ms_addRangeExtendedScenario);
|
|
// }
|
|
|
|
return ScalarV(V_HALF);
|
|
}
|
|
|
|
#endif // SCENARIO_DEBUG
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CScenarioClusterSpawnedTrackingData
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
FW_INSTANTIATE_CLASS_POOL_SPILLOVER(CScenarioClusterSpawnedTrackingData, CONFIGURED_FROM_FILE, 0.72f, atHashString("CScenarioClusterSpawnedTrackingData",0x348c3e40));
|
|
|
|
void CScenarioClusterSpawnedTrackingData::RefCountModel(u32 modelId)
|
|
{
|
|
// Do nothing if this model is invalid, or if it's already counted.
|
|
if(modelId == fwModelId::MI_INVALID || modelId == m_RefCountedModelId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Clear out any previous ref counts.
|
|
ResetRefCountedModel();
|
|
|
|
CBaseModelInfo* pBaseModelInfo = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(modelId)));
|
|
if(Verifyf(pBaseModelInfo, "Expected model info for model %08x", modelId))
|
|
{
|
|
pBaseModelInfo->AddRef();
|
|
m_RefCountedModelId = modelId;
|
|
}
|
|
}
|
|
|
|
void CScenarioClusterSpawnedTrackingData::ResetRefCountedModel()
|
|
{
|
|
if(m_RefCountedModelId == fwModelId::MI_INVALID)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CBaseModelInfo* pBaseModelInfo = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(m_RefCountedModelId)));
|
|
if(Verifyf(pBaseModelInfo, "Expected model info for model %08x", m_RefCountedModelId))
|
|
{
|
|
pBaseModelInfo->RemoveRef();
|
|
}
|
|
m_RefCountedModelId = fwModelId::MI_INVALID;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CSPClusterFSMWrapper
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// STATIC ---------------------------------------------------------------------
|
|
|
|
//stop tracking if the entities are greater than 10 meters from the spawn point.
|
|
float CSPClusterFSMWrapper::ms_UntrackDist = 10.0f;
|
|
float CSPClusterFSMWrapper::ms_fNextDespawnCheckDelay = 5.0f;
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CSPClusterFSMWrapper::CSPClusterFSMWrapper()
|
|
: m_bContainsExteriorPoints(false)
|
|
, m_bContainsInteriorPoints(false)
|
|
, m_bContainsPeds(false)
|
|
, m_bContainsVehicles(false)
|
|
, m_bDespawnImmediately(false)
|
|
, m_bExtendedRange(false)
|
|
, m_bMaxCarGenSpawnAttempsExceeded(false)
|
|
, m_MyCluster(NULL)
|
|
, m_fNextSpawnAttemptDelay(0.0f)
|
|
, m_SpawnPedsPass(SpawnPedsPassInvalid)
|
|
, m_SpawnPedsProgress(0)
|
|
#if __ASSERT
|
|
, m_bWasInMPWhenEnteringSpawnState(false)
|
|
#endif
|
|
#if SCENARIO_DEBUG
|
|
, m_DebugSpawn(false)
|
|
, m_uFailCode(FC_NONE)
|
|
#endif
|
|
{
|
|
m_RandomDensityThreshold = (u8)g_ReplayRand.GetRanged(0, 255);
|
|
}
|
|
|
|
CSPClusterFSMWrapper::~CSPClusterFSMWrapper()
|
|
{
|
|
// Don't allow despawning on screen, but make sure we release all the tracked data.
|
|
DespawnAndClearTrackedData(false, true);
|
|
|
|
SCENARIOCLUSTERING.RemoveCluster(*this);
|
|
m_MyCluster = NULL;
|
|
}
|
|
|
|
FW_INSTANTIATE_CLASS_POOL_SPILLOVER(CSPClusterFSMWrapper, CONFIGURED_FROM_FILE, 0.52f, atHashString("CSPClusterFSMWrapper",0x5a09f5f9));
|
|
|
|
void CSPClusterFSMWrapper::Reset()
|
|
{
|
|
// If tracking any entities release them.
|
|
// Note: this will destroy on screen, but should only be called now during SHUTDOWN_SESSION.
|
|
DespawnAndClearTrackedData(true, true);
|
|
m_TrackedData.Reset();
|
|
|
|
// We probably can't safely do this, because there may be some asserts etc
|
|
// that it's not valid to do a state transition ("Changing state again before previous transition has been completed").
|
|
// SetState(State_Start);
|
|
// I think this should work, as long as we don't rely on any OnExit type functions to run, etc.
|
|
fwFsm::Reset();
|
|
|
|
m_bDespawnImmediately = false;
|
|
|
|
#if SCENARIO_DEBUG
|
|
SetFailCode(FC_NONE);
|
|
#endif
|
|
}
|
|
|
|
|
|
void CSPClusterFSMWrapper::ResetForMpSpTransition()
|
|
{
|
|
if(!m_MyCluster) // Not expected to happen, but anyway.
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the state is such that we don't have to worry about what's
|
|
// in the cluster already.
|
|
const int state = GetState();
|
|
if(state == State_Start || state == State_ReadyForSpawnAttempt || state == State_Despawn || state == State_Delay)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If already planned to despawn right away for some reason, no need to do anything else.
|
|
if(m_bDespawnImmediately)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool inMp = NetworkInterface::IsGameInProgress();
|
|
|
|
// Loop over what we have in m_TrackedData[], to see if we need to despawn and restart.
|
|
bool needRestart = false;
|
|
const int numTracked = m_TrackedData.GetCount();
|
|
for(int i = 0; i < numTracked; i++)
|
|
{
|
|
const CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[i];
|
|
|
|
// Check if we have a point that's marked as SP-only or MP-only.
|
|
const CScenarioPoint* pt = tracked->m_Point;
|
|
if(pt && !pt->IsAvailability(CScenarioPoint::kBoth))
|
|
{
|
|
Assert(pt->IsAvailability(CScenarioPoint::kOnlySp) || pt->IsAvailability(CScenarioPoint::kOnlyMp));
|
|
bool needToBeInMp = pt->IsAvailability(CScenarioPoint::kOnlyMp);
|
|
if(inMp != needToBeInMp)
|
|
{
|
|
needRestart = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if we have a scenario type which is marked with the
|
|
// NotAvailableInMultiplayer flag, and we are in MP.
|
|
if(inMp)
|
|
{
|
|
const CScenarioInfo* pScenarioInfo = CScenarioManager::GetScenarioInfo(tracked->m_ScenarioTypeReal);
|
|
if(pScenarioInfo && pScenarioInfo->GetIsFlagSet(CScenarioInfoFlags::NotAvailableInMultiplayer))
|
|
{
|
|
needRestart = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!needRestart)
|
|
{
|
|
// No need to do anything, everything that we have spawned or have planned to spawn
|
|
// can be used in the new mode.
|
|
return;
|
|
}
|
|
|
|
// Set this flag so the state machine can despawn and reset itself during the next update.
|
|
m_bDespawnImmediately = true;
|
|
}
|
|
|
|
|
|
void CSPClusterFSMWrapper::ResetModelRefCounts()
|
|
{
|
|
// Clear out any model references in our tracked data.
|
|
const int count = m_TrackedData.GetCount();
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[i];
|
|
if(tracked)
|
|
{
|
|
tracked->ResetRefCountedModel();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool CSPClusterFSMWrapper::RegisterCarGenVehicleData(const CScenarioPoint& point, CVehicle& vehicle)
|
|
{
|
|
// The only time when we would want to register newly spawned vehicles with
|
|
// the cluster is when we are in State_SpawnVehicles. If we are not, it could
|
|
// be either because the point isn't actually in the cluster, or because we decided
|
|
// we didn't want to spawn after all, and switched to a different state. Either way,
|
|
// we return false and the vehicle will be destroyed if not claimed by another cluster.
|
|
if(GetState() != State_SpawnVehicles)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const int tcount = m_TrackedData.GetCount();
|
|
for (int t = 0; t < tcount; t++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[t];
|
|
Assert(tracked);
|
|
|
|
if (tracked->m_Point.Get() == &point)
|
|
{
|
|
//Track this vehicle ...
|
|
vehicle.m_nVehicleFlags.bIsInCluster = true;
|
|
vehicle.m_nVehicleFlags.bIsInClusterBeingSpawned = true;
|
|
tracked->m_Vehicle = &vehicle;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if __ASSERT
|
|
|
|
bool CSPClusterFSMWrapper::IsTrackingPed(const CPed& ped) const
|
|
{
|
|
const int tcount = m_TrackedData.GetCount();
|
|
for(int t = 0; t < tcount; t++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[t];
|
|
if(tracked->m_Ped == &ped)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CSPClusterFSMWrapper::IsTrackingVehicle(const CVehicle& veh) const
|
|
{
|
|
const int tcount = m_TrackedData.GetCount();
|
|
for(int t = 0; t < tcount; t++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[t];
|
|
if(tracked->m_Vehicle == &veh)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif // __ASSERT
|
|
|
|
#if SCENARIO_DEBUG
|
|
void CSPClusterFSMWrapper::AddWidgets(bkGroup* parentGroup)
|
|
{
|
|
Assert(parentGroup);
|
|
Assert(m_MyCluster);
|
|
m_MyCluster->AddWidgets(parentGroup);
|
|
parentGroup->AddToggle("Render Cluster Info", &CScenarioDebug::ms_bRenderClusterInfo);
|
|
}
|
|
|
|
void CSPClusterFSMWrapper::TriggerSpawn()
|
|
{
|
|
if (!m_MyCluster)
|
|
return;
|
|
|
|
m_DebugSpawn = true;
|
|
}
|
|
|
|
void CSPClusterFSMWrapper::RenderInfo() const
|
|
{
|
|
if (!m_MyCluster)
|
|
return;
|
|
|
|
Vec3V ccenter = m_MyCluster->GetSphere().GetCenter();
|
|
ccenter = ccenter + Vec3V(0.0f,0.0f,0.6f); //Add a little bit to the Z
|
|
Color32 sphColor = Color_orange;
|
|
grcDebugDraw::Sphere(ccenter, 0.05f, sphColor);
|
|
|
|
//Draw the spider web ...
|
|
Color32 lnColor = Color_orange;
|
|
const u32 count = m_MyCluster->GetNumPoints();
|
|
for (u32 p = 0; p < count; p++)
|
|
{
|
|
const CScenarioPoint& point = m_MyCluster->GetPoint(p);
|
|
Vec3V ppos = point.GetPosition();
|
|
grcDebugDraw::Line(ccenter, ppos, lnColor, 1);
|
|
grcDebugDraw::Sphere(ppos, 0.05f, sphColor);
|
|
}
|
|
|
|
//Draw the status text info.
|
|
char debugText[128];
|
|
u32 line = 1;
|
|
|
|
formatf(debugText, "Cluster 0x%p State: %s ", this, GetStaticStateName(GetState()));
|
|
grcDebugDraw::Text(ccenter, Color_DarkOrchid, 0, grcDebugDraw::GetScreenSpaceTextHeight()*(line++), debugText);
|
|
|
|
if (GetState() == State_Delay)
|
|
{
|
|
float secs = m_fNextSpawnAttemptDelay - GetTimeInState();
|
|
formatf(debugText, "Fail Status: %s retry in %.02f secs", GetStaticFailCodeName(m_uFailCode), secs);
|
|
}
|
|
else
|
|
{
|
|
formatf(debugText, "Fail Status: %s", GetStaticFailCodeName(m_uFailCode));
|
|
}
|
|
grcDebugDraw::Text(ccenter, Color_DarkOrchid, 0, grcDebugDraw::GetScreenSpaceTextHeight()*(line++), debugText);
|
|
|
|
Color32 lnColor2 = Color_red3;
|
|
const int tcount = m_TrackedData.GetCount();
|
|
for (int t = 0; t < tcount; t++)
|
|
{
|
|
const CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[t];
|
|
Assert(tracked);
|
|
const char* pedName = (tracked->m_Ped)? tracked->m_Ped->GetModelName() : "";
|
|
const char* vehName = (tracked->m_Vehicle)? tracked->m_Vehicle->GetModelName() : "";
|
|
formatf(debugText, "Tracked (P) %s {%x}, (V) %s {%x}", pedName, tracked->m_Ped.Get(), vehName, tracked->m_Vehicle.Get());
|
|
grcDebugDraw::Text(ccenter, Color_DarkOrchid, 0, grcDebugDraw::GetScreenSpaceTextHeight()*(line++), debugText);
|
|
|
|
//draw a line from the cluster to the tracked ped/vehicle
|
|
if (tracked->m_Ped)
|
|
{
|
|
Vec3V ppos = tracked->m_Ped->GetTransform().GetPosition();
|
|
grcDebugDraw::Line(ccenter, ppos, lnColor2, 1);
|
|
}
|
|
|
|
if (tracked->m_Vehicle)
|
|
{
|
|
Vec3V vpos = tracked->m_Vehicle->GetTransform().GetPosition();
|
|
grcDebugDraw::Line(ccenter, vpos, lnColor2, 1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#if DR_ENABLED
|
|
void CSPClusterFSMWrapper::DebugReport(debugPlayback::TextOutputVisitor& output) const
|
|
{
|
|
char buffer[RAGE_MAX_PATH];
|
|
|
|
char delayBuf[16];
|
|
if (GetState() == State_Delay)
|
|
{
|
|
float secs = m_fNextSpawnAttemptDelay - GetTimeInState();
|
|
formatf(delayBuf, "%.02f", secs);
|
|
}
|
|
else
|
|
{
|
|
formatf(delayBuf, "Active");
|
|
}
|
|
|
|
formatf(buffer, RAGE_MAX_PATH, "0x%-10p%-32s%-24s%-16s%4d", this, GetStaticStateName(GetState()), GetStaticFailCodeName(m_uFailCode), delayBuf, m_TrackedData.GetCount());
|
|
|
|
output.AddLine(buffer);
|
|
}
|
|
#endif //DR_ENABLED
|
|
|
|
#endif // SCENARIO_DEBUG
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::UpdateState(const s32 state, const s32 /*iMessage*/, const fwFsm::Event event)
|
|
{
|
|
fwFsmBegin
|
|
|
|
fwFsmState(State_Start)
|
|
fwFsmOnUpdate
|
|
return Start_OnUpdate();
|
|
|
|
fwFsmState(State_ReadyForSpawnAttempt)
|
|
fwFsmOnEnter
|
|
return ReadyForSpawnAttempt_OnEnter();
|
|
fwFsmOnUpdate
|
|
return ReadyForSpawnAttempt_OnUpdate();
|
|
|
|
fwFsmState(State_Stream)
|
|
fwFsmOnEnter
|
|
return Stream_OnEnter();
|
|
fwFsmOnUpdate
|
|
return Stream_OnUpdate();
|
|
|
|
fwFsmState(State_SpawnVehicles)
|
|
fwFsmOnEnter
|
|
return SpawnVehicles_OnEnter();
|
|
fwFsmOnUpdate
|
|
return SpawnVehicles_OnUpdate();
|
|
|
|
fwFsmState(State_SpawnPeds)
|
|
fwFsmOnEnter
|
|
return SpawnPeds_OnEnter();
|
|
fwFsmOnUpdate
|
|
return SpawnPeds_OnUpdate();
|
|
fwFsmOnExit
|
|
return SpawnPeds_OnExit();
|
|
|
|
fwFsmState(State_PostSpawnUpdating)
|
|
fwFsmOnUpdate
|
|
return PostSpawnUpdating_OnUpdate();
|
|
|
|
fwFsmState(State_Despawn)
|
|
fwFsmOnUpdate
|
|
return Despawn_OnUpdate();
|
|
|
|
fwFsmState(State_Delay)
|
|
fwFsmOnUpdate
|
|
return Delay_OnUpdate();
|
|
fwFsmEnd
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::Start_OnUpdate()
|
|
{
|
|
// Start with a random delay. This is done so we don't get all clusters in a region
|
|
// perfectly synchronized, which wouldn't be ideal for performance.
|
|
m_fNextSpawnAttemptDelay = fwRandom::GetRandomNumberInRange(0.0f, 1.0f)*Min(m_MyCluster->GetSpawnDelay(), SCENARIOCLUSTERING.GetTuning().m_MaxTimeBetweenSpawnAttempts);
|
|
|
|
SetState(State_Delay);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::ReadyForSpawnAttempt_OnEnter()
|
|
{
|
|
//reset fail code ...
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_NONE));
|
|
|
|
// Check if any of the points have been set to have an extended range. If so,
|
|
// the designers would probably expect the cluster to spawn further away.
|
|
const u32 pcount = m_MyCluster->GetNumPoints();
|
|
bool containsVehicles = false, extendedRange = false;
|
|
bool containsPeds = false;
|
|
bool containsExteriorPoints = false, containsInteriorPoints = false;
|
|
for(u32 p = 0; p < pcount; p++)
|
|
{
|
|
const CScenarioPoint& point = m_MyCluster->GetPoint(p);
|
|
|
|
if(point.IsFlagSet(CScenarioPointFlags::NoSpawn))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!extendedRange && point.HasExtendedRange())
|
|
{
|
|
extendedRange = true;
|
|
}
|
|
|
|
if(point.GetInterior() != CScenarioPointManager::kNoInterior)
|
|
{
|
|
containsInteriorPoints = true;
|
|
}
|
|
else
|
|
{
|
|
containsExteriorPoints = true;
|
|
}
|
|
|
|
if(!containsVehicles || !containsPeds)
|
|
{
|
|
const int virtualType = point.GetScenarioTypeVirtualOrReal();
|
|
if(CScenarioManager::IsVehicleScenarioType(virtualType))
|
|
{
|
|
containsVehicles = true;
|
|
}
|
|
else
|
|
{
|
|
containsPeds = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_bContainsExteriorPoints = containsExteriorPoints;
|
|
m_bContainsInteriorPoints = containsInteriorPoints;
|
|
m_bContainsPeds = containsPeds;
|
|
m_bContainsVehicles = containsVehicles;
|
|
m_bExtendedRange = extendedRange;
|
|
|
|
// Reset the spawn attempt delay, which will be used after our next potential failure,
|
|
// if we don't set it to something else.
|
|
m_fNextSpawnAttemptDelay = Min(m_MyCluster->GetSpawnDelay(), SCENARIOCLUSTERING.GetTuning().m_MaxTimeBetweenSpawnAttempts);
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::ReadyForSpawnAttempt_OnUpdate()
|
|
{
|
|
Assert(m_MyCluster);
|
|
|
|
#if SCENARIO_DEBUG
|
|
if (m_DebugSpawn)
|
|
{
|
|
SetState(State_Stream);
|
|
return fwFsm::Continue;
|
|
}
|
|
#endif
|
|
|
|
//NOTE: the player ped is used to determine the we would like to spawn around.
|
|
CPed* player = FindPlayerPed();
|
|
if (player && IsAllowedByDensity())
|
|
{
|
|
ScalarV playerInfluenceRadius;
|
|
GetPlayerInfluenceRadius(playerInfluenceRadius);
|
|
bool bSwitchToSpawn = IsClusterWithinRadius(player, playerInfluenceRadius);
|
|
|
|
if (bSwitchToSpawn)
|
|
{
|
|
bool boundsNotLoaded = false;
|
|
|
|
// If this cluster may spawn vehicles, check if the bounds are streamed in before
|
|
// going any further. Otherwise, the car generators will probably fail, and we may end
|
|
// up having to wait for a long time until we make another attempt, and we may have streamed
|
|
// in some ped/vehicle models unnecessarily.
|
|
if(m_bContainsVehicles)
|
|
{
|
|
ScalarV testRadiusV(V_FIFTEEN); // 15 m radius, should be enough that we don't have to do multiple tests unless it's a spread out cluster.
|
|
ScalarV testRadiusSqV = Scale(testRadiusV, testRadiusV);
|
|
|
|
// We might have to do more than one test if the cluster is spread out, but
|
|
// we keep track of the tests we have done since we will commonly have multiple points
|
|
// close together.
|
|
static const int kMaxTests = 4;
|
|
Vec3V testedPositions[kMaxTests];
|
|
int numTests = 0;
|
|
|
|
const Vec3V streamPosV = player->GetMatrixRef().GetCol3();
|
|
const CScenarioClustering::Tuning& tuning = SCENARIOCLUSTERING.GetTuning();
|
|
const ScalarV assumeLoadedDistV = LoadScalar32IntoScalarV(tuning.m_StaticBoundsAssumeLoadedDist);
|
|
const ScalarV assumeLoadedDistSqV = Scale(assumeLoadedDistV, assumeLoadedDistV);
|
|
|
|
const int numPoints = m_MyCluster->GetNumPoints();
|
|
for(int i = 0; i < numPoints; i++)
|
|
{
|
|
const CScenarioPoint& point = m_MyCluster->GetPoint(i);
|
|
|
|
// Note: we could potentially check and only do this for the vehicle points, since
|
|
// they are the ones that seem to be struggling.
|
|
if(CouldSpawnAt(point))
|
|
{
|
|
bool needsTest = true;
|
|
const Vec3V worldPosV = point.GetWorldPosition();
|
|
const ScalarV distSqV = DistSquared(streamPosV, worldPosV);
|
|
|
|
// First, check if the point is close enough to the player that we should
|
|
// assume that the bounds are there.
|
|
if(IsLessThanAll(distSqV, assumeLoadedDistSqV))
|
|
{
|
|
needsTest = false;
|
|
}
|
|
else
|
|
{
|
|
// Check if this point is already covered by a previous test.
|
|
for(int j = 0; j < numTests; j++)
|
|
{
|
|
if(IsLessThanAll(DistSquared(worldPosV, testedPositions[j]), testRadiusSqV))
|
|
{
|
|
needsTest = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not, do the test.
|
|
if(needsTest)
|
|
{
|
|
spdAABB testBox;
|
|
testBox.Set(Subtract(worldPosV, Vec3V(testRadiusV)), Add(worldPosV, Vec3V(testRadiusV)));
|
|
if(!g_StaticBoundsStore.GetBoxStreamer().HasLoadedWithinAABB(testBox, fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER))
|
|
{
|
|
// Bounds not loaded, don't try to spawn yet.
|
|
boundsNotLoaded = true;
|
|
break;
|
|
}
|
|
|
|
// Store the result. If we run out, break out silently.
|
|
Assert(numTests < kMaxTests);
|
|
testedPositions[numTests++] = worldPosV;
|
|
if(numTests >= kMaxTests)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't have bounds problem, go ahead and start.
|
|
if(!boundsNotLoaded)
|
|
{
|
|
SetState(State_Stream);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MAGIC! Check the distance again in 1 second. We don't want to wait for too long
|
|
// since we may lose our opportunity to spawn before the player gets too close.
|
|
m_fNextSpawnAttemptDelay = Min(1.0f, m_fNextSpawnAttemptDelay);
|
|
|
|
// wait for a bit before we attempt again.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_RANGECHECK));
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::Stream_OnEnter()
|
|
{
|
|
Assert(m_MyCluster);
|
|
|
|
//Find the points that are valid to spawn at.
|
|
int possibleCount = 0;
|
|
const u32 pcount = m_MyCluster->GetNumPoints();
|
|
for(u32 p = 0; p < pcount; p++)
|
|
{
|
|
const CScenarioPoint& point = m_MyCluster->GetPoint(p);
|
|
if (CouldSpawnAt(point))
|
|
{
|
|
#if SCENARIO_DEBUG
|
|
//if this is a debug spawn we should clean out the spawn history logging so we dont get asserts
|
|
if (m_DebugSpawn)
|
|
{
|
|
CScenarioManager::sm_SpawnHistory.ClearHistoryForPoint(point);
|
|
|
|
// This is probably needed to clear up the old scenario chain users:
|
|
CScenarioManager::sm_SpawnHistory.Update();
|
|
CScenarioChainingGraph::UpdatePointChainUserInfo();
|
|
|
|
Assert(!point.HasHistory()); // Shouldn't have any history for the point now.
|
|
}
|
|
#endif
|
|
GatherStreamingDependencies(point);
|
|
possibleCount++;
|
|
}
|
|
}
|
|
|
|
// For car generators on chains, we need to create the chain user objects. We do this outside
|
|
// of the GatherStreamingDependencies() calls, because there are some subtle issues where
|
|
// we want the peds to register first, in case we have peds and empty vehicles on the same
|
|
// chains.
|
|
for(int i = 0; i < m_TrackedData.GetCount(); i++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[i];
|
|
if(!tracked->m_UsesCarGen)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const CScenarioPoint& pt = *tracked->m_Point;
|
|
if(!pt.IsChained())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool allowOverflow = CScenarioManager::AllowExtraChainedUser(pt);
|
|
if(!allowOverflow && pt.IsUsedChainFull())
|
|
{
|
|
// If we hit this case, one of the chains is in use. In that case, we would probably
|
|
// not want to continue even if !GetAllPointRequiredForSpawn(), so just clear up everything
|
|
// and try again later. Nothing should have been spawned or streamed yet.
|
|
DespawnAndClearTrackedData(true, true); //clean up ...
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_ALL_POINTS_NOT_VALID));
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
|
|
// If we wanted to just remove from the array rather than switching to the despawn state
|
|
// as above, this would be the way to do it:
|
|
// m_TrackedData[i] = NULL;
|
|
// m_TrackedData.Delete(i);
|
|
// delete tracked;
|
|
// i--;
|
|
// continue;
|
|
}
|
|
|
|
Assert(!tracked->m_ChainUser);
|
|
tracked->m_ChainUser = CScenarioChainingGraph::RegisterChainUserDummy(pt, allowOverflow);
|
|
}
|
|
|
|
if (m_MyCluster->GetAllPointRequiredForSpawn())
|
|
{
|
|
if (possibleCount != m_TrackedData.GetCount())
|
|
{
|
|
// Shouldn't have spawned anything at this point, I think.
|
|
DespawnAndClearTrackedData(true, true); //clean up ...
|
|
|
|
// If this is an extended range cluster, and we are trying to extend the range of those further,
|
|
// we probably shouldn't wait for the whole 30 second delay. So far, we probably haven't done any
|
|
// actual streaming requests or anything else terribly expensive.
|
|
if(m_bExtendedRange && CScenarioManager::ShouldExtendExtendedRangeFurther())
|
|
{
|
|
m_fNextSpawnAttemptDelay = Min(m_fNextSpawnAttemptDelay, 5.0f); // MAGIC!
|
|
}
|
|
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_ALL_POINTS_NOT_VALID));
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
|
|
#if __ASSERT
|
|
m_bWasInMPWhenEnteringSpawnState = NetworkInterface::IsGameInProgress();
|
|
#endif
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::Stream_OnUpdate()
|
|
{
|
|
|
|
//Nothing to spawn at currently
|
|
if (!m_TrackedData.GetCount() || m_bDespawnImmediately)
|
|
{
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_NO_POINTS_FOUND));
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
//Somehow we have not been able to stream in ... so leave it for now.
|
|
if (GetTimeInState() >= GetStreamingTimeOut())
|
|
{
|
|
// wait for a bit before we attempt again.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_STREAMING_TIMEOUT));
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
//Despawn to get rid of anything ...
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
//Request our dependencies. This requests ped and vehicle models, but doesn't spawn anything.
|
|
if(RequestStreamingDependencies(false))
|
|
{
|
|
// Before we decide to go to the spawn state, check if we still think we
|
|
// should spawn anything. The player may have moved so that what was previously
|
|
// valid no longer is.
|
|
if(IsStillValidToSpawn())
|
|
{
|
|
SetState(State_SpawnVehicles);
|
|
return fwFsm::Continue;
|
|
}
|
|
else
|
|
{
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_NOT_VALID_TO_SPAWN));
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
//Despawn to get rid of anything ...
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnVehicles_OnEnter()
|
|
{
|
|
// Reset this, it will get set to true by RequestStreamingDependencies() if we
|
|
// any car generator has been scheduled too many times, and failed to spawn.
|
|
m_bMaxCarGenSpawnAttempsExceeded = false;
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnVehicles_OnUpdate()
|
|
{
|
|
//Somehow we have not been able to stream in ... so leave it for now.
|
|
if (m_bMaxCarGenSpawnAttempsExceeded || GetTimeInState() >= SCENARIOCLUSTERING.GetTuning().m_SpawnStateTimeout || m_bDespawnImmediately)
|
|
{
|
|
// wait for a bit before we attempt again.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_SPAWNING_TIMEOUT));
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
//Despawn to get rid of anything ...
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
// Request our dependencies. Unlike during the Stream state, when we pass in true here,
|
|
// the car generators become scheduled to spawn, and the function will return true when
|
|
// they have spawned, and everything else is resident (but there are still no peds).
|
|
if(RequestStreamingDependencies(true))
|
|
{
|
|
// At this point, we have spawned any vehicles (still invisible and out of the game world).
|
|
// We do a final check to decide if we should go ahead and do the rest, in case the cluster would
|
|
// now be too visible to the player.
|
|
if(!IsStillValidToSpawn())
|
|
{
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_NOT_VALID_TO_SPAWN));
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
//Despawn to get rid of anything ...
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
// By RequestStreamingDependencies() returning true, we know that all ped models we need
|
|
// are currently loaded. But, since we don't spawn them all on the same frame now, we need
|
|
// to make sure they stay that way, by adding to the reference count.
|
|
const int numTracked = m_TrackedData.GetCount();
|
|
for(int i = 0; i < numTracked; i++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[i];
|
|
if(!tracked->m_UsesCarGen)
|
|
{
|
|
tracked->RefCountModel(tracked->m_PedType.m_PedModelIndex);
|
|
}
|
|
}
|
|
|
|
SetState(State_SpawnPeds);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnPeds_OnEnter()
|
|
{
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false;) //reset incase we were debug spawning...
|
|
|
|
// We will do three passes over m_TrackedData. Start at the beginning of the first...
|
|
m_SpawnPedsPass = SpawnPedsPass1;
|
|
m_SpawnPedsProgress = 0;
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnPeds_OnUpdate()
|
|
{
|
|
if(m_bDespawnImmediately)
|
|
{
|
|
SCENARIO_DEBUG_ONLY(m_DebugSpawn = false);//reset this so we dont get into loop where we constantly try to spawn.
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_NO_POINTS_FOUND));
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
// Call different update functions for the different passes.
|
|
switch(m_SpawnPedsPass)
|
|
{
|
|
case SpawnPedsPass1:
|
|
return SpawnPeds_OnUpdatePass1();
|
|
case SpawnPedsPass2:
|
|
return SpawnPeds_OnUpdatePass2();
|
|
case SpawnPedsPass3:
|
|
return SpawnPeds_OnUpdatePass3();
|
|
default:
|
|
Assert(0);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnPeds_OnUpdatePass1()
|
|
{
|
|
Assert(m_TrackedData.GetCount() < 255); // m_SpawnPedsProgress uses a u8
|
|
|
|
// Spawn all the peds at regular on-foot scenario points.
|
|
const u32 tdcount = m_TrackedData.GetCount();
|
|
for (u32 td = m_SpawnPedsProgress; td < tdcount; td++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[td];
|
|
if (!tracked->m_UsesCarGen)
|
|
{
|
|
SpawnAtPoint(*tracked);
|
|
Assert(!tracked->m_NeedsAddToWorld);
|
|
|
|
// If we were holding a reference on a ped model, release it now.
|
|
tracked->ResetRefCountedModel();
|
|
|
|
// In this case, we spawned a ped, or possibly failed to do so. We don't
|
|
// want to do too much work each frame, so we will stop now and continue
|
|
// with the next element on the next frame.
|
|
m_SpawnPedsProgress = (u8)(td + 1);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
// If we get here, all regular peds have had a chance to spawn.
|
|
|
|
// validate that all the points are valid if we are required too
|
|
if (m_MyCluster->GetAllPointRequiredForSpawn())
|
|
{
|
|
//Count the number of valid spawns ...
|
|
u32 vcount = 0;
|
|
const u32 tdcount = m_TrackedData.GetCount();
|
|
for (u32 td = 0; td < tdcount; td++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[td];
|
|
if (!IsInvalid(*tracked))
|
|
{
|
|
vcount++;
|
|
}
|
|
}
|
|
|
|
if (tdcount != vcount)
|
|
{
|
|
// State_Despawn would despawn anyway, but we probably want to do it
|
|
// forcefully in this case, since nothing should have actually been
|
|
// visible on screen up to this point.
|
|
DespawnAndClearTrackedData(true, false);
|
|
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
|
|
// Continue with the next pass. If we got here, we didn't actually spawn any
|
|
// peds this frame, so we call the update function for the next pass immediately.
|
|
m_SpawnPedsPass = SpawnPedsPass2;
|
|
m_SpawnPedsProgress = 0;
|
|
return SpawnPeds_OnUpdatePass2();
|
|
}
|
|
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnPeds_OnUpdatePass2()
|
|
{
|
|
// Do a pass over the points, and for any vehicle scenarios with ped layouts, spawn the peds, and track them.
|
|
const u32 tdcount = m_TrackedData.GetCount();
|
|
for(unsigned int td = m_SpawnPedsProgress; td < tdcount; td++)
|
|
{
|
|
if(m_TrackedData[td]->m_UsesCarGen)
|
|
{
|
|
CScenarioPoint* point = m_TrackedData[td]->m_Point;
|
|
CVehicle* veh = m_TrackedData[td]->m_Vehicle;
|
|
if(point && veh)
|
|
{
|
|
const int scenarioType = point->GetScenarioTypeVirtualOrReal();
|
|
CPed* ppSpawnedPeds[16];
|
|
int numSpawnedPeds = 0;
|
|
if(CScenarioManager::SpawnPedsNearVehicleUsingLayout(*veh, scenarioType, &numSpawnedPeds, ppSpawnedPeds, NELEM(ppSpawnedPeds)))
|
|
{
|
|
for(int i = 0; i < numSpawnedPeds; i++)
|
|
{
|
|
CPed* pPed = ppSpawnedPeds[i];
|
|
|
|
CScenarioClusterSpawnedTrackingData* tracked = rage_new CScenarioClusterSpawnedTrackingData;
|
|
tracked->m_ScenarioTypeReal = (u32)scenarioType; // Not sure, it's a bit awkward to have the vehicle scenario type on the ped.
|
|
tracked->m_Point = point;
|
|
tracked->m_Ped = pPed;
|
|
|
|
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_IsInCluster, true);
|
|
m_TrackedData.PushAndGrow(tracked);
|
|
}
|
|
|
|
if(numSpawnedPeds)
|
|
{
|
|
// If we spawned anything using the vehicle layout (which would be rare),
|
|
// continue with the next point on the next frame.
|
|
m_SpawnPedsProgress = (u8)(td + 1);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// State_Despawn would despawn anyway, but we probably want to do it
|
|
// forcefully in this case, since nothing should have actually been
|
|
// visible on screen up to this point.
|
|
DespawnAndClearTrackedData(true, false);
|
|
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Continue with the next pass. If we got here, we didn't actually spawn any
|
|
// peds this frame, so we call the update function for the next pass immediately.
|
|
m_SpawnPedsPass = SpawnPedsPass3;
|
|
m_SpawnPedsProgress = 0;
|
|
return SpawnPeds_OnUpdatePass3();
|
|
}
|
|
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnPeds_OnUpdatePass3()
|
|
{
|
|
const u32 tdcount = m_TrackedData.GetCount();
|
|
for (u32 td = m_SpawnPedsProgress; td < tdcount; td++)
|
|
{
|
|
if(m_TrackedData[td]->m_UsesCarGen)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[td];
|
|
CScenarioPoint* point = tracked->m_Point;
|
|
CVehicle* veh = tracked->m_Vehicle;
|
|
if(point && veh)
|
|
{
|
|
CCarGenerator* cargen = point->GetCarGen();
|
|
if(Verifyf(cargen, "Expected car generator."))
|
|
{
|
|
bool ignoreLocalPopulationLimits = false;
|
|
if(NetworkInterface::IsGameInProgress() && point->IsHighPriority())
|
|
{
|
|
ignoreLocalPopulationLimits = true;
|
|
CNetworkObjectPopulationMgr::SetProcessLocalPopulationLimits(false);
|
|
}
|
|
bool success = CScenarioManager::PopulateCarGenVehicle(*veh, *cargen, true);
|
|
if(ignoreLocalPopulationLimits)
|
|
{
|
|
CNetworkObjectPopulationMgr::SetProcessLocalPopulationLimits(true);
|
|
}
|
|
if(!success)
|
|
{
|
|
// State_Despawn would despawn anyway, but we probably want to do it
|
|
// forcefully in this case, since nothing should have actually been
|
|
// visible on screen up to this point.
|
|
DespawnAndClearTrackedData(true, false);
|
|
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
|
|
CPed* pDriver = veh->GetDriver();
|
|
CScenarioPointChainUseInfo* pUserInfo = tracked->m_ChainUser;
|
|
if(pUserInfo)
|
|
{
|
|
if(pDriver)
|
|
{
|
|
//Need to transfer ownership of this to the real user.
|
|
//get the task we were setup with ... "this should be task_unalerted"
|
|
CTaskUnalerted* pTaskUnalerted = FindTaskUnalertedForDriver(*pDriver);
|
|
|
|
if(pTaskUnalerted)
|
|
{
|
|
pTaskUnalerted->SetScenarioChainUserInfo(pUserInfo);
|
|
}
|
|
}
|
|
|
|
CScenarioChainingGraph::UpdateDummyChainUser(*pUserInfo, pDriver, veh);
|
|
}
|
|
tracked->m_ChainUser = NULL;
|
|
|
|
// If the car generator didn't already add us to the world (which is the usual
|
|
// and desired case), add us now. This includes fading up, etc.
|
|
if(tracked->m_NeedsAddToWorld)
|
|
{
|
|
if(!CGameWorld::HasEntityBeenAdded(veh))
|
|
{
|
|
// The car generator currently adds to the world if we were not outside,
|
|
// for simplicity, so if we get here, we are supposed to be outside.
|
|
fwInteriorLocation loc = CGameWorld::OUTSIDE;
|
|
CTheCarGenerators::AddToGameWorld(*veh, NULL, loc);
|
|
}
|
|
tracked->m_NeedsAddToWorld = false;
|
|
}
|
|
|
|
veh->m_nVehicleFlags.bIsInClusterBeingSpawned = false;
|
|
|
|
// Add to the history.
|
|
Assert(!point->HasHistory());
|
|
const int scenarioType = point->GetScenarioTypeVirtualOrReal();
|
|
taskAssert(!SCENARIOINFOMGR.IsVirtualIndex(scenarioType));
|
|
CScenarioManager::GetSpawnHistory().Add(*veh, *point, scenarioType, pUserInfo);
|
|
|
|
// If we spawned a driver, stop here and continue on the next frame at the next
|
|
// element. Note that it's intentional that we just check the driver here: passengers
|
|
// are already put in a queue so we don't have to worry about spawning too many,
|
|
// at this level.
|
|
if(pDriver)
|
|
{
|
|
m_SpawnPedsProgress = (u8)(td + 1);
|
|
return fwFsm::Continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Done with all spawn passes, switch to the update state.
|
|
m_SpawnPedsPass = SpawnPedsPassInvalid;
|
|
m_SpawnPedsProgress = 0;
|
|
SetState(State_PostSpawnUpdating);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::SpawnPeds_OnExit()
|
|
{
|
|
const u32 tdcount = m_TrackedData.GetCount();
|
|
|
|
// Check for a friend.
|
|
CPed* pAmbientFriend = NULL;
|
|
CPed* pFirstNonAmbientFriend = NULL;
|
|
for (u32 td = 0; td < tdcount; td++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* pTrackData = m_TrackedData[td];
|
|
|
|
if (pTrackData && pTrackData->m_Ped)
|
|
{
|
|
CPed* pPed = pTrackData->m_Ped;
|
|
if (pPed->GetTaskData().GetIsFlagSet(CTaskFlags::DependentAmbientFriend))
|
|
{
|
|
pAmbientFriend = pPed;
|
|
}
|
|
else if (!pFirstNonAmbientFriend)
|
|
{
|
|
pFirstNonAmbientFriend = pPed;
|
|
}
|
|
}
|
|
|
|
// Can early out once both of these are set.
|
|
if (pAmbientFriend && pFirstNonAmbientFriend)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pAmbientFriend)
|
|
{
|
|
// Set the ambient friend on all peds in the cluster, except for on the ambient friend, who gets the first regular ped as his buddy.
|
|
for(u32 td = 0; td < tdcount; td++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* pTrackData = m_TrackedData[td];
|
|
|
|
if (pTrackData && pTrackData->m_Ped)
|
|
{
|
|
CPed* pPed = pTrackData->m_Ped;
|
|
|
|
if (pPed == pAmbientFriend)
|
|
{
|
|
pPed->GetPedIntelligence()->SetAmbientFriend(pFirstNonAmbientFriend);
|
|
}
|
|
else
|
|
{
|
|
pPed->GetPedIntelligence()->SetAmbientFriend(pAmbientFriend);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::PostSpawnUpdating_OnUpdate()
|
|
{
|
|
#if SCENARIO_DEBUG
|
|
if (m_DebugSpawn)
|
|
{
|
|
//Despawn if we were spawned in... The despawn will reset us to state State_ReadyForSpawnAttempt
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
#endif
|
|
|
|
if(m_bDespawnImmediately)
|
|
{
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
//update all the tracked data ..
|
|
u32 tdcount = m_TrackedData.GetCount();
|
|
for (u32 td = 0; td < tdcount; td++)
|
|
{
|
|
//UpdateTracked(*m_TrackedData[td]);
|
|
CScenarioClusterSpawnedTrackingData& tracked = *m_TrackedData[td];
|
|
if (IsInvalid(tracked))
|
|
{
|
|
CPed* pPed = tracked.m_Ped;
|
|
CVehicle* pVeh = tracked.m_Vehicle;
|
|
if(pPed)
|
|
{
|
|
ClearPedTracking(*pPed);
|
|
}
|
|
if(pVeh)
|
|
{
|
|
ClearVehicleTracking(*pVeh);
|
|
}
|
|
tracked.m_Ped = NULL;
|
|
tracked.m_Vehicle = NULL;
|
|
|
|
delete m_TrackedData[td];
|
|
m_TrackedData[td] = NULL;
|
|
m_TrackedData.Delete(td);
|
|
tdcount--; //step back
|
|
td--;
|
|
}
|
|
}
|
|
|
|
if (GetTimeInState() > CSPClusterFSMWrapper::ms_fNextDespawnCheckDelay)
|
|
{
|
|
SetTimeInState(0.f);
|
|
|
|
bool bAreAllMembersReadyForRemoval = true;
|
|
CPed* player = FindPlayerPed();
|
|
if (player && tdcount)
|
|
{
|
|
// We used to do this, to see if the cluster was far away before trying to check visibility/distance.
|
|
// This didn't work too well since many of our clusters involve driving away.
|
|
//
|
|
// // If all members are ready to be removed, make sure the player isn't close enough that we'd just spawn again
|
|
// ScalarV playerInfluenceRadius;
|
|
// const bool bAddExtraDistance = true;
|
|
// GetPlayerInfluenceRadius(playerInfluenceRadius, bAddExtraDistance); // Add a little extra distance to ensure we're outside the spawn radius
|
|
// if (!IsAllowedByDensity() || !IsClusterWithinRadius(player, playerInfluenceRadius))
|
|
// {
|
|
// u32 tdcount = m_TrackedData.GetCount();
|
|
// for (u32 td = 0; td < tdcount; td++)
|
|
// {
|
|
// if (!IsTrackedMemberReadyToBeRemoved(*m_TrackedData[td]))
|
|
// {
|
|
// bAreAllMembersReadyForRemoval = false;
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
// bAreAllMembersReadyForRemoval = false;
|
|
// }
|
|
|
|
u32 tdcount = m_TrackedData.GetCount();
|
|
for (u32 td = 0; td < tdcount; td++)
|
|
{
|
|
if (!IsTrackedMemberReadyToBeRemoved(*m_TrackedData[td]))
|
|
{
|
|
bAreAllMembersReadyForRemoval = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAreAllMembersReadyForRemoval)
|
|
{
|
|
//all the data we were tracking is no longer valid so go into the delay then attempt to spawn again...
|
|
SetState(State_Despawn);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
}
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::Despawn_OnUpdate()
|
|
{
|
|
// We are now in the despawn state, clear this flag.
|
|
m_bDespawnImmediately = false;
|
|
|
|
#if SCENARIO_DEBUG
|
|
if (m_DebugSpawn)
|
|
{
|
|
//if we are debug spawning just jump back to attempt spawn state.
|
|
DespawnAndClearTrackedData(true, true);
|
|
SetState(State_ReadyForSpawnAttempt);
|
|
return fwFsm::Continue;
|
|
}
|
|
#endif
|
|
|
|
// Destroy gracefully and untrack. We'll stay in this state until everything has been destroyed.
|
|
DespawnAndClearTrackedData(false, false);
|
|
|
|
if(!m_TrackedData.GetCount())
|
|
{
|
|
//Once we have killed everything we should wait a bit before we attempt to spawn again
|
|
SetState(State_Delay);
|
|
return fwFsm::Continue;
|
|
}
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
fwFsm::Return CSPClusterFSMWrapper::Delay_OnUpdate()
|
|
{
|
|
Assert(m_MyCluster);
|
|
if (SCENARIO_DEBUG_ONLY(m_DebugSpawn || ) GetTimeInState() >= m_fNextSpawnAttemptDelay)
|
|
{
|
|
SetState(State_ReadyForSpawnAttempt);
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
return fwFsm::Continue;
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::ValidatePointAndSelectRealType(const CScenarioPoint& point, int& scenarioTypeReal_OUT)
|
|
{
|
|
scenarioTypeReal_OUT = -1;
|
|
|
|
// points attached to entities are not valid for clusters
|
|
if (point.GetEntity())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool doChecks = true; //perform the validity checks ...
|
|
SCENARIO_DEBUG_ONLY(doChecks = !m_DebugSpawn);
|
|
|
|
if (doChecks)
|
|
{
|
|
if (!CPedPopulation::SP_SpawnValidityCheck_General(point, true /* allow clustered points */))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!CPedPopulation::SP_SpawnValidityCheck_Interior(point, 0/*iSpawnOptions*/))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Always need to do this step ...
|
|
int unused = -1;
|
|
scenarioTypeReal_OUT = CPedPopulation::SelectPointsRealType(point, 0/*iSpawnOptions*/, unused);
|
|
if (scenarioTypeReal_OUT == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Always do this because some types can not be spawned at or are spawned in a different way
|
|
if (!CPedPopulation::SP_SpawnValidityCheck_ScenarioType(point, scenarioTypeReal_OUT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (doChecks)
|
|
{
|
|
if (!CPedPopulation::SP_SpawnValidityCheck_ScenarioInfo(point, scenarioTypeReal_OUT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!CheckVisibilityForPed(point, scenarioTypeReal_OUT))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::ValidateCarGenPoint(const CScenarioPoint& point)
|
|
{
|
|
CCarGenerator* cargen = point.GetCarGen();
|
|
if (!cargen)
|
|
return false;
|
|
|
|
Assert(cargen->IsScenarioCarGen());
|
|
|
|
if (!cargen->CheckHasSpaceToSchedule())
|
|
{
|
|
//failure registered in function.
|
|
return false;
|
|
}
|
|
|
|
if (!point.IsHighPriority())
|
|
{
|
|
if (!cargen->CheckPopulationHasSpace())
|
|
{
|
|
//failure registered in function.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool doChecks = true; //perform the validity checks ...
|
|
SCENARIO_DEBUG_ONLY(doChecks = !m_DebugSpawn);
|
|
|
|
if (doChecks)
|
|
{
|
|
if (!cargen->CheckIsActiveForGen())
|
|
{
|
|
//failure registered in function.
|
|
return false;
|
|
}
|
|
|
|
//Early check so we dont schedule this cargen to spawn when it will fail due to this
|
|
// another check will happen at the point of spawning as well because the culling ped
|
|
// could have moved while the cargen was scheduled.
|
|
|
|
if(!CheckVisibilityForVehicle(point))
|
|
{
|
|
SCENARIO_DEBUG_ONLY(cargen->RegisterSpawnFailure(CPedPopulation::PedPopulationFailureData::FR_spawnPointOutsideCreationZone));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSPClusterFSMWrapper::GatherStreamingDependencies(const CScenarioPoint& point)
|
|
{
|
|
int scenarioTypeReal = -1;
|
|
if (point.GetCarGen())
|
|
{
|
|
if (ValidateCarGenPoint(point))
|
|
{
|
|
//for car generation points we want to just create tracking data ...
|
|
// it will be filled in later if the cargen code is able too create a vehicle
|
|
CScenarioClusterSpawnedTrackingData* tracked = rage_new CScenarioClusterSpawnedTrackingData();
|
|
tracked->m_UsesCarGen = true;
|
|
tracked->m_Point = const_cast<CScenarioPoint*>(&point);
|
|
m_TrackedData.PushAndGrow(tracked);
|
|
}
|
|
}
|
|
else if (ValidatePointAndSelectRealType(point, scenarioTypeReal))
|
|
{
|
|
Assert(scenarioTypeReal != -1);
|
|
|
|
CScenarioClusterSpawnedTrackingData* tracked = rage_new CScenarioClusterSpawnedTrackingData();
|
|
tracked->m_ScenarioTypeReal = scenarioTypeReal;
|
|
|
|
// if chained we need to reserve the chain so others dont attempt to use it
|
|
if (point.IsChained())
|
|
{
|
|
tracked->m_ChainUser = CScenarioChainingGraph::RegisterChainUserDummy(point);
|
|
|
|
aiAssertf(tracked->m_ChainUser, "RegisterChainUserDummy() call failed, possibly pool may be full "
|
|
"(check for previous assert), or the chain may be broken somehow. "
|
|
"The point is at %.1f, %.1f, %.1f.", point.GetWorldPosition().GetXf(), point.GetWorldPosition().GetYf(), point.GetWorldPosition().GetZf());
|
|
}
|
|
|
|
tracked->m_Point = const_cast<CScenarioPoint*>(&point);
|
|
m_TrackedData.PushAndGrow(tracked);
|
|
|
|
Assertf(!point.IsAvailability(CScenarioPoint::kOnlySp) || !NetworkInterface::IsGameInProgress(),
|
|
"Cluster point marked as Only in SP scheduled to spawn in MP, at %.1f, %.1f, %.1f",
|
|
point.GetWorldPosition().GetXf(), point.GetWorldPosition().GetYf(), point.GetWorldPosition().GetZf());
|
|
}
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::RequestStreamingDependencies(bool scheduleCarGens)
|
|
{
|
|
//be optimistic here
|
|
bool bAllStreamed = true;
|
|
|
|
const u32 tdcount = m_TrackedData.GetCount();
|
|
for (u32 td = 0; td < tdcount; td++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[td];
|
|
if (tracked->m_UsesCarGen)
|
|
{
|
|
if(scheduleCarGens)
|
|
{
|
|
//these are the cargen points so we check to see if we have a tracked vehicle yet
|
|
if (!tracked->m_Vehicle)
|
|
{
|
|
bAllStreamed = false;//no vehicle yet ...
|
|
bool streamingReady = false;
|
|
StreamAndScheduleCarGenPoint(*tracked, true, streamingReady);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool streamingReady = false;
|
|
StreamAndScheduleCarGenPoint(*tracked, false, streamingReady);
|
|
if(!streamingReady)
|
|
{
|
|
bAllStreamed = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool hiPri = tracked->m_Point->IsHighPriority();
|
|
|
|
#if SCENARIO_DEBUG
|
|
if (m_DebugSpawn)
|
|
{
|
|
hiPri = true;
|
|
}
|
|
#endif
|
|
|
|
Vector3 scenarioPos = VEC3V_TO_VECTOR3(tracked->m_Point->GetPosition());
|
|
if(CScenarioManager::SelectPedTypeForScenario(tracked->m_PedType, tracked->m_ScenarioTypeReal, *tracked->m_Point, scenarioPos, 0, 0, fwModelId(), true))
|
|
{
|
|
if(tracked->m_PedType.m_NeedsToBeStreamed)
|
|
{
|
|
CScenarioManager::StreamScenarioPeds(fwModelId(strLocalIndex(tracked->m_PedType.m_PedModelIndex)), hiPri, tracked->m_ScenarioTypeReal);
|
|
bAllStreamed = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if we did not select a type then we are not streamed in.
|
|
bAllStreamed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bAllStreamed;
|
|
}
|
|
|
|
void CSPClusterFSMWrapper::SpawnAtPoint(CScenarioClusterSpawnedTrackingData& pntDep)
|
|
{
|
|
Assert(pntDep.m_Point);
|
|
CScenarioPoint& point = *pntDep.m_Point;
|
|
|
|
Assert(!pntDep.m_Ped);
|
|
|
|
// Set up the scenario information
|
|
Vector3 vSpawnPos;
|
|
float fHeading = 0.0f;
|
|
|
|
const CScenarioInfo* pScenarioInfo = CScenarioManager::GetScenarioInfo(pntDep.m_ScenarioTypeReal);
|
|
if (aiVerifyf(pScenarioInfo,"No scenario info for scenario point"))
|
|
{
|
|
CTask* pScenarioTask = CScenarioManager::SetupScenarioAndTask( pntDep.m_ScenarioTypeReal, vSpawnPos, fHeading, point, 0, false, true, true);
|
|
|
|
// Create the ped
|
|
if( pScenarioTask )
|
|
{
|
|
Assertf(!point.IsAvailability(CScenarioPoint::kOnlySp) || !NetworkInterface::IsGameInProgress(),
|
|
"Cluster point marked as Only in SP is about to spawn in MP, at %.1f, %.1f, %.1f. m_bWasInMPWhenEnteringSpawnState = %d",
|
|
point.GetWorldPosition().GetXf(), point.GetWorldPosition().GetYf(), point.GetWorldPosition().GetZf(), (int)m_bWasInMPWhenEnteringSpawnState);
|
|
|
|
//TODO: Not sure here about these params ...
|
|
const float maxFrustumDistToCheck = 80.0f;
|
|
const bool allowDeepInteriors = false;
|
|
|
|
Assert(point.IsChained() || !pntDep.m_ChainUser);
|
|
|
|
// Protect against streamed out models. This shouldn't happen since we use a ref count now,
|
|
// but before that was added, we would have gotten a hard crash inside CreateScenarioPed(),
|
|
// so better to be safe at this point in case there are still some cases that are not covered properly.
|
|
bool tryToSpawn = true;
|
|
if(pntDep.m_PedType.m_PedModelIndex != fwModelId::MI_INVALID)
|
|
{
|
|
if(!Verifyf(CModelInfo::HaveAssetsLoaded(fwModelId(strLocalIndex(pntDep.m_PedType.m_PedModelIndex))),
|
|
"Model %08x was expected to be loaded, but is not.", pntDep.m_PedType.m_PedModelIndex))
|
|
{
|
|
tryToSpawn = false;
|
|
}
|
|
}
|
|
|
|
if(point.HasHistory())
|
|
{
|
|
aiDebugf1("Didn't spawn ped at scenario cluster point at %.1f, %.1f, %.1f, already has history (probably due to a clone added there since we last checked.",
|
|
point.GetWorldPosition().GetXf(), point.GetWorldPosition().GetYf(), point.GetWorldPosition().GetZf());
|
|
tryToSpawn = false;
|
|
}
|
|
|
|
CPed* pPed = NULL;
|
|
if(tryToSpawn)
|
|
{
|
|
const bool bDoCollisionChecksForClusterPoints = pScenarioInfo->GetIsFlagSet(CScenarioInfoFlags::CheckForObstructionEvenInClusters);
|
|
// Set up the parameters we would need in order to do a collision check.
|
|
CScenarioManager::CollisionCheckParams collisionCheckParams;
|
|
if (bDoCollisionChecksForClusterPoints)
|
|
{
|
|
collisionCheckParams.m_pScenarioPoint = &point;
|
|
collisionCheckParams.m_ScenarioTypeReal = pntDep.m_ScenarioTypeReal;
|
|
collisionCheckParams.m_ScenarioPos = VECTOR3_TO_VEC3V(vSpawnPos);
|
|
collisionCheckParams.m_pAttachedEntity = point.GetEntity();
|
|
collisionCheckParams.m_pAttachedTrain = NULL;
|
|
collisionCheckParams.m_pPropInEnvironment = NULL;
|
|
|
|
if (collisionCheckParams.m_pAttachedEntity && collisionCheckParams.m_pAttachedEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pAttachedVehicle = static_cast<CVehicle *>(collisionCheckParams.m_pAttachedEntity);
|
|
if (pAttachedVehicle->InheritsFromTrain())
|
|
{
|
|
collisionCheckParams.m_pAttachedTrain = (CTrain*)pAttachedVehicle;
|
|
}
|
|
}
|
|
|
|
CObject* pPropInEnvironment = NULL;
|
|
if (CScenarioManager::FindPropsInEnvironmentForScenario(pPropInEnvironment, point, pntDep.m_ScenarioTypeReal, false))
|
|
{
|
|
collisionCheckParams.m_pPropInEnvironment = pPropInEnvironment;
|
|
|
|
}
|
|
}
|
|
|
|
pPed = CScenarioManager::CreateScenarioPed( pntDep.m_ScenarioTypeReal, point, pScenarioTask, vSpawnPos, maxFrustumDistToCheck, allowDeepInteriors, fHeading, 0, 0, false, bDoCollisionChecksForClusterPoints ? &collisionCheckParams : NULL, &pntDep.m_PedType, false, pntDep.m_ChainUser);
|
|
}
|
|
else
|
|
{
|
|
// If we are not going to call CreateScenarioPed(), we need to delete the task manually so it doesn't leak.
|
|
delete pScenarioTask;
|
|
pScenarioTask = NULL;
|
|
}
|
|
|
|
if (point.IsChained() && Verifyf(pntDep.m_ChainUser, "Missing expected dummy chain user pointer"))
|
|
{
|
|
bool keepUser = false;
|
|
|
|
if (pPed)
|
|
{
|
|
//get the task we were setup with ... "this should be task_unalerted"
|
|
CTask* task = pPed->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_UNALERTED);
|
|
if (task)
|
|
{
|
|
CTaskUnalerted* tua = (CTaskUnalerted*)task;
|
|
CScenarioChainingGraph::UpdateDummyChainUser(*pntDep.m_ChainUser, pPed, NULL);
|
|
tua->SetScenarioChainUserInfo(pntDep.m_ChainUser);
|
|
|
|
keepUser = true;
|
|
}
|
|
}
|
|
|
|
if(!keepUser)
|
|
{
|
|
// Clean up.
|
|
CScenarioPointChainUseInfo* pUser = pntDep.m_ChainUser;
|
|
pntDep.m_ChainUser = NULL;
|
|
|
|
if(pUser->GetNumRefsInSpawnHistory())
|
|
{
|
|
// If we are in the history, we can't just call UnregisterPointChainUser(). Instead,
|
|
// we turn it into a non-dummy user with the ped and vehicle pointers as NULL.
|
|
// It will get cleaned up once there is no longer a reference from the history.
|
|
// Note that in this case, if it got added to the history the ped would probably
|
|
// actually exist, so I don't think it's the case that we need to remove it from the
|
|
// history or anything.
|
|
CScenarioChainingGraph::UpdateDummyChainUser(*pUser, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
CScenarioChainingGraph::UnregisterPointChainUser(*pUser);
|
|
}
|
|
}
|
|
pntDep.m_ChainUser = NULL;
|
|
}
|
|
|
|
//Success so track some data ...
|
|
if (pPed)
|
|
{
|
|
pntDep.m_Ped = pPed;
|
|
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_IsInCluster, true);
|
|
}
|
|
else
|
|
{
|
|
SCENARIO_DEBUG_ONLY(SetFailCode(FC_PED_CREATION));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSPClusterFSMWrapper::StreamAndScheduleCarGenPoint(CScenarioClusterSpawnedTrackingData& pntDep, bool allowSchedule, bool& readyToScheduleOut)
|
|
{
|
|
Assert(pntDep.m_UsesCarGen);
|
|
|
|
readyToScheduleOut = false;
|
|
|
|
if(!pntDep.m_Point)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CCarGenerator* cargen = pntDep.m_Point->GetCarGen();
|
|
if (!cargen)
|
|
{
|
|
//it is possible that the point is valid and cluster is valid, but the cargen object has not been added yet
|
|
// due to the sweep across the world has not finished yet....
|
|
return;
|
|
}
|
|
|
|
//Already scheduled ...
|
|
if(cargen->IsScheduled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(allowSchedule)
|
|
{
|
|
const CScenarioClustering::Tuning& tuning = SCENARIOCLUSTERING.GetTuning();
|
|
if(pntDep.m_NumCarGenSchedules >= tuning.m_MaxCarGenScheduleAttempts)
|
|
{
|
|
m_bMaxCarGenSpawnAttempsExceeded = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Check these again as the queue or pools may have filled up since we started this process.
|
|
if (!cargen->CheckHasSpaceToSchedule())
|
|
{
|
|
//failure registered in function.
|
|
return;
|
|
}
|
|
|
|
if (!pntDep.m_Point->IsHighPriority())
|
|
{
|
|
if (!cargen->CheckPopulationHasSpace())
|
|
{
|
|
//failure registered in function.
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool doChecks = true; //perform the validity checks ...
|
|
BANK_ONLY(doChecks = !m_DebugSpawn);
|
|
|
|
if (doChecks)
|
|
{
|
|
if(!cargen->CheckScenarioConditionsA())
|
|
{
|
|
//failure registered in function.
|
|
return;
|
|
}
|
|
}
|
|
|
|
fwModelId carToUse;
|
|
fwModelId trailerToUse;
|
|
bool carModelShouldBeRequested = false;
|
|
if (!cargen->PickValidModels(carToUse, trailerToUse, carModelShouldBeRequested))
|
|
{
|
|
//failure registered in function.
|
|
return;
|
|
}
|
|
|
|
// If this is set, we have already determined that the correct model is not available and would potentially have
|
|
// to be streamed in.
|
|
if(carModelShouldBeRequested)
|
|
{
|
|
CTheCarGenerators::RequestVehicleModelForScenario(carToUse, pntDep.m_Point->IsHighPriority());
|
|
return;//if the car is not present in memory we can get out here ...
|
|
}
|
|
|
|
if (doChecks)
|
|
{
|
|
if(!cargen->CheckScenarioConditionsB(carToUse, trailerToUse))
|
|
{
|
|
//failure registered in function.
|
|
return;
|
|
}
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
if(!cargen->CheckNetworkObjectLimits(carToUse))
|
|
{
|
|
//failure registered in function.
|
|
return;
|
|
}
|
|
|
|
if(!NetworkInterface::IsPosLocallyControlled( VEC3V_TO_VECTOR3(cargen->GetCentre()) ) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!allowSchedule)
|
|
{
|
|
readyToScheduleOut = true;
|
|
return;
|
|
}
|
|
|
|
Matrix34 CreationMatrix;
|
|
cargen->BuildCreationMatrix(&CreationMatrix, carToUse.GetModelIndex());
|
|
|
|
if (doChecks)
|
|
{
|
|
// do bbox test with dynamic objects to make sure we're not creating on top of another car
|
|
if (!cargen->DoSyncProbeForBlockingObjects(carToUse, CreationMatrix, NULL))
|
|
{
|
|
//failure registered in function.
|
|
|
|
// If we failed this test, still consider it a schedule attempt, since we have done
|
|
// some expensive tests that we don't want to just keep repeating until we hit the
|
|
// whole cluster spawn timeout.
|
|
pntDep.m_NumCarGenSchedules++;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
const bool bAllowedToSwitchOffCarGenIfSomethingOnTop = !pntDep.m_Point->IsHighPriority() SCENARIO_DEBUG_ONLY(&& !m_DebugSpawn);
|
|
bool forceSpawn = false;
|
|
#if SCENARIO_DEBUG
|
|
forceSpawn = m_DebugSpawn;
|
|
#endif
|
|
|
|
ScheduledVehicle* schedVeh = cargen->InsertVehForGen(carToUse, trailerToUse, CreationMatrix, bAllowedToSwitchOffCarGenIfSomethingOnTop, forceSpawn);
|
|
if(schedVeh)
|
|
{
|
|
schedVeh->needsStaticBoundsStreamingTest = false;
|
|
|
|
// We don't want the car generator to add us to the game world. We want to stay invisible
|
|
// and without collisions until we know that we can spawn the whole cluster.
|
|
schedVeh->addToGameWorld = false;
|
|
pntDep.m_NeedsAddToWorld = true;
|
|
|
|
pntDep.m_NumCarGenSchedules++;
|
|
}
|
|
}
|
|
|
|
void CSPClusterFSMWrapper::DespawnAndClearTrackedData(bool forceDespawn, bool mustClearAllTracked)
|
|
{
|
|
int tcount = m_TrackedData.GetCount();
|
|
for(int t = 0; t < tcount; t++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[t];
|
|
Assert(tracked);
|
|
if(tracked->m_Ped)
|
|
{
|
|
CPed* pPed = tracked->m_Ped;
|
|
|
|
// If the ped is inside a vehicle, try to remove the vehicle.
|
|
CVehicle* pVehPedInside = pPed->GetVehiclePedInside();
|
|
if(pVehPedInside)
|
|
{
|
|
// Mark the vehicle to be removed aggressively.
|
|
pVehPedInside->m_nVehicleFlags.bTryToRemoveAggressively = true;
|
|
if(forceDespawn || IsVehicleReadyToBeRemoved(*pVehPedInside))
|
|
{
|
|
// Clear the ped's InCluster flag, as we're about to remove it.
|
|
ClearPedTracking(*pPed);
|
|
if(pVehPedInside != tracked->m_Vehicle && pVehPedInside->CanBeDeleted())
|
|
{
|
|
//The vehicle delete is expected to clean up the ped....
|
|
CVehicleFactory::GetFactory()->Destroy(pVehPedInside);
|
|
}
|
|
tracked->m_Ped = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Mark the ped to be removed aggressively.
|
|
pPed->SetRemoveAsSoonAsPossible(true);
|
|
// Clear the ped's InCluster flag, as we're about to remove it.
|
|
// Note: we intentionally do this even if we don't actually remove it here,
|
|
// otherwise the SetRemoveAsSoonAsPossible() call wouldn't really work.
|
|
ClearPedTracking(*pPed);
|
|
|
|
// Destroy the ped if we can.
|
|
if(forceDespawn || IsPedReadyToBeRemoved(*pPed))
|
|
{
|
|
if(pPed->CanBeDeleted())
|
|
{
|
|
CPedFactory::GetFactory()->DestroyPed(pPed);
|
|
}
|
|
tracked->m_Ped = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tracked->m_Vehicle)
|
|
{
|
|
CVehicle* pVehicle = tracked->m_Vehicle;
|
|
|
|
// Mark the vehicle to be removed aggressively.
|
|
pVehicle->m_nVehicleFlags.bTryToRemoveAggressively = true;
|
|
// Note: we intentionally do this even if we don't actually remove it here,
|
|
// otherwise bTryToRemoveAggressively probably wouldn't really work.
|
|
ClearVehicleTracking(*pVehicle);
|
|
|
|
// Remove the vehicle right now if it's not too visible.
|
|
if(forceDespawn || IsVehicleReadyToBeRemoved(*pVehicle))
|
|
{
|
|
if(pVehicle->CanBeDeleted())
|
|
{
|
|
CVehicleFactory::GetFactory()->Destroy(pVehicle);
|
|
}
|
|
tracked->m_Vehicle = NULL;
|
|
}
|
|
}
|
|
|
|
bool removeTracked = mustClearAllTracked;
|
|
|
|
// If we don't need to clear everything we are tracking, check to see if this is an invalid
|
|
// one that we may want to remove.
|
|
if(!removeTracked && IsInvalid(*tracked))
|
|
{
|
|
removeTracked = true;
|
|
|
|
// As an exception, if this point is a car generator that's still scheduled to spawn,
|
|
// don't remove it. This way, we will remain in State_Despawn until the car generators
|
|
// are all done processing our previous spawn attempt, so we can't make another.
|
|
if(tracked->m_Point)
|
|
{
|
|
CCarGenerator* cargen = tracked->m_Point->GetCarGen();
|
|
if(cargen && cargen->IsScheduled())
|
|
{
|
|
removeTracked = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are meant to clear all the tracking data, or if we have destroyed the ped/vehicle,
|
|
// or if they have become clones, remove from the cluster.
|
|
if(removeTracked)
|
|
{
|
|
// Clear out the ped pointer and the matching in cluster flag, if it still exists.
|
|
if(tracked->m_Ped)
|
|
{
|
|
ClearPedTracking(*tracked->m_Ped);
|
|
tracked->m_Ped = NULL;
|
|
}
|
|
// Clear out the vehicle pointer and the matching in cluster flag, if it still exists.
|
|
if(tracked->m_Vehicle)
|
|
{
|
|
ClearVehicleTracking(*tracked->m_Vehicle);
|
|
tracked->m_Vehicle = NULL;
|
|
}
|
|
|
|
// Clear the point and chain user, if there is anything left.
|
|
tracked->m_Point = NULL;
|
|
if (tracked->m_ChainUser)
|
|
{
|
|
CScenarioPointChainUseInfo* pUser = tracked->m_ChainUser;
|
|
tracked->m_ChainUser = NULL;
|
|
|
|
Assert(pUser->IsDummy());
|
|
CScenarioChainingGraph::UnregisterPointChainUser(*pUser);
|
|
}
|
|
|
|
// Delete the tracking data and remove it from the array.
|
|
delete tracked;
|
|
m_TrackedData.DeleteFast(t);
|
|
t--;
|
|
tcount--;
|
|
}
|
|
}
|
|
|
|
// At this point, we should be empty if mustClearAllTracked is true.
|
|
Assert(!tcount || !mustClearAllTracked);
|
|
}
|
|
|
|
// None of this is working too well right now, since clustered peds often need to leave their positions
|
|
// to walk along chains, etc. We should perhaps measure the distance between cluster-spawned
|
|
// entities instead, to see if they got separated. They are also not expected to always
|
|
// run TASK_USE_SCENARIO when on foot.
|
|
#if 0
|
|
|
|
void CSPClusterFSMWrapper::UpdateTracked(CScenarioClusterSpawnedTrackingData& tracked)
|
|
{
|
|
//if we no longer have a point this means it is invalid
|
|
if (!tracked.m_Point)
|
|
return;
|
|
|
|
//If we dont have a ped or a vehicle it is invalid
|
|
CPed* pPed = tracked.m_Ped;
|
|
CVehicle* pVehicle = tracked.m_Vehicle;
|
|
if (!tracked.m_Ped && !pVehicle)
|
|
return;
|
|
|
|
if (pPed)
|
|
{
|
|
bool untrack = false;
|
|
|
|
CTask* task = pPed->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_USE_SCENARIO);
|
|
if (!task)
|
|
{
|
|
//if the ped no longer has task use scenario then we should no longer track the ped
|
|
untrack = true;
|
|
}
|
|
else
|
|
{
|
|
// Put the square of the radius in a vector register.
|
|
const ScalarV thresholdDistSqV(CSPClusterFSMWrapper::ms_UntrackDist*CSPClusterFSMWrapper::ms_UntrackDist);
|
|
|
|
// Compute the squared distance and check if it's outside the range.
|
|
const Vec3V pedPosV = pPed->GetTransform().GetPosition();
|
|
const ScalarV distSqV = DistSquared(pedPosV, tracked.m_Point->GetPosition());
|
|
|
|
//if the ped left the scenario point.
|
|
if(IsGreaterThanOrEqualAll(distSqV, thresholdDistSqV))
|
|
{
|
|
untrack = true; //dont track it ...
|
|
}
|
|
}
|
|
|
|
if(untrack)
|
|
{
|
|
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_IsInCluster, false);
|
|
tracked.m_Ped = NULL;
|
|
}
|
|
}
|
|
|
|
if (pVehicle)
|
|
{
|
|
// Put the square of the radius in a vector register.
|
|
const ScalarV thresholdDistSqV(CSPClusterFSMWrapper::ms_UntrackDist*CSPClusterFSMWrapper::ms_UntrackDist);
|
|
|
|
// Compute the squared distance and check if it's outside the range.
|
|
const Vec3V pedPosV = pVehicle->GetTransform().GetPosition();
|
|
const ScalarV distSqV = DistSquared(pedPosV, tracked.m_Point->GetPosition());
|
|
|
|
//if the ped left the scenario point.
|
|
if(IsGreaterThanOrEqualAll(distSqV, thresholdDistSqV))
|
|
{
|
|
pVehicle->m_nVehicleFlags.bIsInCluster = false;
|
|
tracked.m_Vehicle = NULL; //dont track it ...
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // 0
|
|
|
|
// Returns whether this tracked member is ready to be removed from the game
|
|
bool CSPClusterFSMWrapper::IsTrackedMemberReadyToBeRemoved(CScenarioClusterSpawnedTrackingData& tracked)
|
|
{
|
|
if (IsInvalid(tracked))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (tracked.m_Ped)
|
|
{
|
|
// Check if this ped is now inside of a vehicle. If so, our regular call
|
|
// to CPedPopulation::ShouldRemovePed() would actually always return false.
|
|
// Instead, we check if the vehicle can be removed.
|
|
CVehicle* pVehPedInside = tracked.m_Ped->GetVehiclePedInside();
|
|
if(pVehPedInside)
|
|
{
|
|
// If this vehicle belongs to a cluster, we always consider the ped removable,
|
|
// as far as this function goes. The vehicle could possibly belong to this cluster,
|
|
// in which case its removability will be checked separately anyway, or another cluster.
|
|
// In that case, it still won't be forcefully destroyed while driving the other vehicle,
|
|
// DespawnAndClearTrackedData() doesn't allow for that.
|
|
if(pVehPedInside->m_nVehicleFlags.bIsInCluster)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Check visibility to the vehicle we happen to be in.
|
|
return IsVehicleReadyToBeRemoved(*pVehPedInside);
|
|
}
|
|
}
|
|
|
|
return IsPedReadyToBeRemoved(*tracked.m_Ped);
|
|
}
|
|
|
|
if (tracked.m_Vehicle)
|
|
{
|
|
return IsVehicleReadyToBeRemoved(*tracked.m_Vehicle);
|
|
}
|
|
|
|
aiAssert(false); //Should never reach here. IsInvalid returns true if there's neither a vehicle or a ped.
|
|
return true;
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::IsPedReadyToBeRemoved(CPed& ped) const
|
|
{
|
|
float fCullRangeInView = 0.f;
|
|
const CPedPopulation::SP_SpawnValidityCheck_PositionParams& positionParams = CPedPopulation::SP_SpawnValidityCheck_GetCurrentParams();
|
|
if (CPedPopulation::ShouldRemovePed(&ped, positionParams.popCtrlCentre, positionParams.fRemovalRangeScale, positionParams.bInAnInterior, positionParams.fRemovalRateScale, fCullRangeInView))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ped.DelayedRemovalTimeReset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::IsVehicleReadyToBeRemoved(CVehicle& veh) const
|
|
{
|
|
//const CPedPopulation::SP_SpawnValidityCheck_PositionParams& positionParams = CPedPopulation::SP_SpawnValidityCheck_GetCurrentParams();
|
|
CPopCtrl popCtrl = CPedPopulation::GetPopulationControlPointInfo();
|
|
|
|
const float st0 = rage::Sinf(popCtrl.m_baseHeading);
|
|
const float ct0 = rage::Cosf(popCtrl.m_baseHeading);
|
|
Vector4 vViewPlane(-st0, ct0, 0.0f, 0.0f);
|
|
vViewPlane.w = - vViewPlane.Dot3(popCtrl.m_centre);
|
|
|
|
CVehicle *pPlayerVehicle = FindPlayerVehicle();
|
|
bool bAircraftPopCentre = pPlayerVehicle && pPlayerVehicle->GetIsAircraft();
|
|
CVehiclePopulation::EVehLastRemovalReason removalReason = CVehiclePopulation::Removal_None;
|
|
s32 newDelayedRemovalTime = 0;
|
|
|
|
if (CVehiclePopulation::ShouldRemoveVehicle(&veh, popCtrl.m_centre, vViewPlane, CVehiclePopulation::GetPopulationRangeScale(), CVehiclePopulation::GetRemovalTimeScale(), bAircraftPopCentre, removalReason, newDelayedRemovalTime))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
veh.DelayedRemovalTimeReset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool CSPClusterFSMWrapper::IsInvalid(const CScenarioClusterSpawnedTrackingData& tracked) const
|
|
{
|
|
//if we no longer have a point this means it is invalid
|
|
if (!tracked.m_Point)
|
|
return true;
|
|
|
|
//If we dont have a ped or a vehicle it is invalid
|
|
if (!tracked.m_Ped && !tracked.m_Vehicle)
|
|
return true;
|
|
|
|
if (tracked.m_Ped && tracked.m_Ped->IsNetworkClone())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (tracked.m_Vehicle && tracked.m_Vehicle->IsNetworkClone())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float CSPClusterFSMWrapper::GetStreamingTimeOut() const
|
|
{
|
|
const float fStreamingTimeout = 3.0f;
|
|
float retval = fStreamingTimeout;
|
|
|
|
#if SCENARIO_DEBUG
|
|
//Allow a bit more time for debug spawning ...
|
|
if (m_DebugSpawn)
|
|
{
|
|
retval = 10.0f;
|
|
}
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::CouldSpawnAt(const CScenarioPoint& point) const
|
|
{
|
|
if(point.IsFlagSet(CScenarioPointFlags::NoSpawn))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const int virtualType = point.GetScenarioTypeVirtualOrReal();
|
|
|
|
// If this is a vehicle scenario then check to see if it has a cargenerator as
|
|
// that is the only way this point could be spawned at.
|
|
if(CScenarioManager::IsVehicleScenarioType(virtualType))
|
|
{
|
|
if (!point.GetCarGen()) //no cargen means we cant spawn things here ...
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CSPClusterFSMWrapper::IsStillValidToSpawn() const
|
|
{
|
|
#if SCENARIO_DEBUG
|
|
if(m_DebugSpawn)
|
|
{
|
|
// When debug spawning, the checks below should not apply.
|
|
return true;
|
|
}
|
|
#endif // SCENARIO_DEBUG
|
|
|
|
const int trackedCount = m_TrackedData.GetCount();
|
|
for(int i = 0; i < trackedCount; i++)
|
|
{
|
|
CScenarioClusterSpawnedTrackingData* tracked = m_TrackedData[i];
|
|
const CScenarioPoint* pt = tracked->m_Point;
|
|
if(pt)
|
|
{
|
|
if(tracked->m_UsesCarGen)
|
|
{
|
|
if(!CheckVisibilityForVehicle(*pt))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!CheckVisibilityForPed(*pt, tracked->m_ScenarioTypeReal))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CSPClusterFSMWrapper::CheckVisibilityForPed(const CScenarioPoint& point, int scenarioTypeReal) const
|
|
{
|
|
//Adjust the params to allow for a greater max creation radius for clustered points
|
|
CPedPopulation::SP_SpawnValidityCheck_PositionParams curParams = CPedPopulation::SP_SpawnValidityCheck_GetCurrentParams();
|
|
curParams.fAddRangeInViewMax = LARGE_FLOAT;
|
|
curParams.fAddRangeOutOfViewMax = LARGE_FLOAT;
|
|
curParams.faddRangeExtendedScenario = LARGE_FLOAT;
|
|
|
|
// Probably don't want short range points to invalidate us - if anything, we should use it to reduce the spawn distance of the whole cluster.
|
|
curParams.fMaxRangeShortScenario = LARGE_FLOAT;
|
|
curParams.fMaxRangeShortAndHighPriorityScenario = LARGE_FLOAT;
|
|
|
|
if(!CPedPopulation::SP_SpawnValidityCheck_Position(point, scenarioTypeReal, curParams))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CSPClusterFSMWrapper::CheckVisibilityForVehicle(const CScenarioPoint& point) const
|
|
{
|
|
bool noMaxLimit = true;
|
|
if(!CVehiclePopulation::HasPopGenShapeBeenInitialized()
|
|
|| !CScenarioManager::CheckPosAgainstVehiclePopulationKeyhole(point.GetPosition(), ScalarV(V_ONE) /* TODO: Think more about distance scaling in the clustered case. */, point.HasExtendedRange(), true, noMaxLimit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The car generator code does this internally, so if we are not going to pass this test,
|
|
// we may as well not schedule the car generators.
|
|
CCarGenerator* cargen = point.GetCarGen();
|
|
if(cargen)
|
|
{
|
|
bool bTooClose = false;
|
|
if(!cargen->CheckIfWithinRangeOfAnyPlayers(bTooClose))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CSPClusterFSMWrapper::GetPlayerInfluenceRadius(ScalarV_InOut playerInfluenceRadius, bool addExtraDistance) const
|
|
{
|
|
if(m_bExtendedRange)
|
|
{
|
|
playerInfluenceRadius.Setf(200.0f);//todo: data drive this ... also this is a guess ...
|
|
if(CScenarioManager::ShouldExtendExtendedRangeFurther())
|
|
{
|
|
playerInfluenceRadius = Max(ScalarV(CPedPopulation::GetAddRangeExtendedScenarioExtended()), playerInfluenceRadius);
|
|
if(m_bContainsVehicles)
|
|
{
|
|
playerInfluenceRadius = Max(ScalarV(CScenarioManager::GetExtendedExtendedRangeForVehicleSpawn()), playerInfluenceRadius);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
playerInfluenceRadius = ScalarV(100.0f);//todo: data drive this ... also this is a guess ...
|
|
}
|
|
|
|
// If it contains vehicles, make sure it's trying to spawn some distance away, wouldn't make
|
|
// sense to get a spawn distance smaller than a regular vehicle scenario.
|
|
if(m_bContainsVehicles)
|
|
{
|
|
playerInfluenceRadius = Max(playerInfluenceRadius, ScalarV(CScenarioManager::GetClusterRangeForVehicleSpawn()));
|
|
}
|
|
|
|
if (addExtraDistance)
|
|
{
|
|
playerInfluenceRadius.Setf(playerInfluenceRadius.Getf() * 1.1f); // Add ten percent
|
|
}
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::IsClusterWithinRadius( const CPed* player, ScalarV_In playerInfluenceRadius ) const
|
|
{
|
|
const spdSphere& cSphere = m_MyCluster->GetSphere();
|
|
Vec3V playerPosn = player->GetTransform().GetPosition();
|
|
spdSphere playerSphere(playerPosn, playerInfluenceRadius);
|
|
if (CPedPopulation::ShouldUse2dDistCheck(player))
|
|
{
|
|
if (playerSphere.ContainsSphereFlat(cSphere) || playerSphere.IntersectsSphereFlat(cSphere))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (playerSphere.ContainsSphere(cSphere) || playerSphere.IntersectsSphere(cSphere))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CSPClusterFSMWrapper::IsAllowedByDensity() const
|
|
{
|
|
bool bAllowByDensity = true;
|
|
if(m_bContainsPeds)
|
|
{
|
|
// Note: perhaps we wouldn't need to do this if we were simply
|
|
// respecting the regular number of desired scenario peds. On the other
|
|
// hand, maybe even then it would be a good thing to get a more even reduction
|
|
// between clustered scenarios and non-clustered scenarios?
|
|
|
|
// Get the total density multipliers for interior and exterior scenario peds.
|
|
float fInteriorMult = 1.0f;
|
|
float fExteriorMult = 1.0f;
|
|
CPedPopulation::GetTotalScenarioPedDensityMultipliers(fInteriorMult, fExteriorMult);
|
|
|
|
// Respect the interior and exterior multipliers, depending on what types of points are in this cluster.
|
|
float fDensityMul = 1.0f;
|
|
if(m_bContainsExteriorPoints)
|
|
{
|
|
fDensityMul = Min(fDensityMul, fExteriorMult);
|
|
}
|
|
if(m_bContainsInteriorPoints)
|
|
{
|
|
fDensityMul = Min(fDensityMul, fInteriorMult);
|
|
}
|
|
|
|
// If the density is desired density is less than 1, do a check against the
|
|
// random value associated with this cluster, to determine if it should
|
|
// spawn or not. We wouldn't want to do a full random roll here, since then
|
|
// all clusters may spawn given enough time.
|
|
if(fDensityMul < 1.0f)
|
|
{
|
|
unsigned int densityThreshold = (unsigned int)(fDensityMul*255.0f);
|
|
if(m_RandomDensityThreshold >= densityThreshold)
|
|
{
|
|
bAllowByDensity = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bAllowByDensity;
|
|
}
|
|
|
|
CTaskUnalerted* CSPClusterFSMWrapper::FindTaskUnalertedForDriver(CPed& ped) const
|
|
{
|
|
CTaskUnalerted* pTaskUnalerted = static_cast<CTaskUnalerted*>(ped.GetPedIntelligence()->FindTaskDefaultByType(CTaskTypes::TASK_UNALERTED));
|
|
|
|
if (pTaskUnalerted == NULL)
|
|
{
|
|
CTaskPolice* pTaskPolice = static_cast<CTaskPolice*>(ped.GetPedIntelligence()->FindTaskDefaultByType(CTaskTypes::TASK_POLICE));
|
|
if (pTaskPolice)
|
|
{
|
|
pTaskUnalerted = pTaskPolice->GetTaskUnalerted();
|
|
}
|
|
}
|
|
|
|
if (pTaskUnalerted == NULL)
|
|
{
|
|
CTaskArmy* pTaskArmy = static_cast<CTaskArmy*>(ped.GetPedIntelligence()->FindTaskDefaultByType(CTaskTypes::TASK_ARMY));
|
|
if (pTaskArmy)
|
|
{
|
|
pTaskUnalerted = pTaskArmy->GetTaskUnalerted();
|
|
}
|
|
}
|
|
|
|
if (pTaskUnalerted == NULL)
|
|
{
|
|
CTaskFirePatrol* pTaskFirePatrol = static_cast<CTaskFirePatrol*>(ped.GetPedIntelligence()->FindTaskDefaultByType(CTaskTypes::TASK_FIRE_PATROL));
|
|
if (pTaskFirePatrol)
|
|
{
|
|
pTaskUnalerted = pTaskFirePatrol->GetTaskUnalerted();
|
|
}
|
|
}
|
|
|
|
if (pTaskUnalerted == NULL)
|
|
{
|
|
CTaskAmbulancePatrol* pTaskAmbulancePatrol = static_cast<CTaskAmbulancePatrol*>(ped.GetPedIntelligence()->FindTaskDefaultByType(CTaskTypes::TASK_AMBULANCE_PATROL));
|
|
if (pTaskAmbulancePatrol)
|
|
{
|
|
pTaskUnalerted = pTaskAmbulancePatrol->GetTaskUnalerted();
|
|
}
|
|
}
|
|
|
|
return pTaskUnalerted;
|
|
}
|
|
|
|
|
|
void CSPClusterFSMWrapper::ClearPedTracking(CPed& ped)
|
|
{
|
|
ped.SetPedConfigFlag(CPED_CONFIG_FLAG_IsInCluster, false);
|
|
}
|
|
|
|
|
|
void CSPClusterFSMWrapper::ClearVehicleTracking(CVehicle& veh)
|
|
{
|
|
veh.m_nVehicleFlags.bIsInCluster = false;
|
|
veh.m_nVehicleFlags.bIsInClusterBeingSpawned = false;
|
|
}
|
|
|
|
#if SCENARIO_DEBUG
|
|
// STATIC ---------------------------------------------------------------------
|
|
|
|
const char * CSPClusterFSMWrapper::GetStaticStateName( s16 iState )
|
|
{
|
|
switch (iState)
|
|
{
|
|
case State_Start: return "State_Start";
|
|
case State_ReadyForSpawnAttempt: return "State_ReadyForSpawnAttempt";
|
|
case State_Stream: return "State_Stream";
|
|
case State_SpawnVehicles: return "State_SpawnVehicles";
|
|
case State_SpawnPeds: return "State_SpawnPeds";
|
|
case State_PostSpawnUpdating: return "State_PostSpawnUpdating";
|
|
case State_Despawn: return "State_Despawn";
|
|
case State_Delay: return "State_Delay";
|
|
default: Assert(0);
|
|
}
|
|
|
|
return "State_Invalid";
|
|
}
|
|
|
|
const char * CSPClusterFSMWrapper::GetStaticFailCodeName( u8 failCode )
|
|
{
|
|
switch (failCode)
|
|
{
|
|
case FC_NONE: return "FC_None";
|
|
case FC_RANGECHECK: return "FC_RangeCheck";
|
|
case FC_STREAMING_TIMEOUT: return "FC_StreamingTimeout";
|
|
case FC_SPAWNING_TIMEOUT: return "FC_SpawningTimeout";
|
|
case FC_PED_CREATION: return "FC_PedCreation";
|
|
case FC_NO_POINTS_FOUND: return "FC_NoValidPointsFound";
|
|
case FC_ALL_POINTS_NOT_VALID: return "FC_AllPointsNotValid";
|
|
case FC_NOT_VALID_TO_SPAWN: return "FC_NotValidToSpawn";
|
|
default: Assert(0);
|
|
}
|
|
|
|
return "FC_Invalid";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CSPClusterFSMWrapper::SetFailCode(u8 failCode)
|
|
{
|
|
m_uFailCode = failCode;
|
|
}
|
|
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CScenarioClustering
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
CScenarioClustering::Tuning::Tuning()
|
|
: m_MaxTimeBetweenSpawnAttempts(10.0f)
|
|
, m_StaticBoundsAssumeLoadedDist(160.0f)
|
|
, m_SpawnStateTimeout(3.0f)
|
|
, m_MaxCarGenScheduleAttempts(2)
|
|
{
|
|
}
|
|
|
|
CScenarioClustering::CScenarioClustering()
|
|
: m_MpGameInProgress(false)
|
|
{
|
|
}
|
|
|
|
void CScenarioClustering::AddCluster(CScenarioPointCluster& cluster)
|
|
{
|
|
if (Verifyf(FindIndex(cluster) == -1, "Cluster already seems to be in the tracked list. Why add it twice?"))
|
|
{
|
|
CSPClusterFSMWrapper* wrapper = GetNewWrapper();
|
|
if (wrapper)
|
|
{
|
|
wrapper->m_MyCluster = &cluster;
|
|
ms_ActiveClusters.PushAndGrow(wrapper);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CScenarioClustering::RemoveCluster(CScenarioPointCluster& cluster)
|
|
{
|
|
const int foundId = FindIndex(cluster);
|
|
if (foundId != -1)
|
|
{
|
|
CSPClusterFSMWrapper* toDelete = ms_ActiveClusters[foundId];
|
|
ms_ActiveClusters.DeleteFast(foundId);
|
|
delete toDelete;
|
|
}
|
|
}
|
|
|
|
void CScenarioClustering::RemoveCluster(CSPClusterFSMWrapper& cwrapper)
|
|
{
|
|
Assert(cwrapper.m_MyCluster);
|
|
RemoveCluster(*cwrapper.m_MyCluster);
|
|
}
|
|
|
|
void CScenarioClustering::ResetAllClusters()
|
|
{
|
|
const int numClusters = ms_ActiveClusters.GetCount();
|
|
for(int i = 0; i < numClusters; i++)
|
|
{
|
|
ms_ActiveClusters[i]->Reset();
|
|
}
|
|
}
|
|
|
|
const CSPClusterFSMWrapper* CScenarioClustering::FindClusterWrapper(const CScenarioPointCluster& cluster) const
|
|
{
|
|
int index = FindIndex(cluster);
|
|
if(index >= 0)
|
|
{
|
|
return ms_ActiveClusters[index];
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void CScenarioClustering::Update()
|
|
{
|
|
if(fwTimer::IsGamePaused())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if we have switched between SP and MP since the last update.
|
|
bool mpGameInProgress = NetworkInterface::IsGameInProgress();
|
|
if(mpGameInProgress != m_MpGameInProgress)
|
|
{
|
|
ResetForMpSpTransition();
|
|
m_MpGameInProgress = mpGameInProgress;
|
|
}
|
|
|
|
const u32 count = ms_ActiveClusters.GetCount();
|
|
for (u32 ac = 0; ac < count; ac++)
|
|
{
|
|
//FSM update ...
|
|
ms_ActiveClusters[ac]->Update();
|
|
}
|
|
|
|
#if __ASSERT
|
|
static u32 s_LastCheck;
|
|
u32 timeMs = fwTimer::GetTimeInMilliseconds();
|
|
if(timeMs > s_LastCheck + 2000)
|
|
{
|
|
ValidateInClusterFlags();
|
|
s_LastCheck = timeMs;
|
|
}
|
|
#endif // __ASSERT
|
|
}
|
|
|
|
bool CScenarioClustering::RegisterCarGenVehicleData(const CScenarioPoint& point, CVehicle& vehicle)
|
|
{
|
|
bool found = false;
|
|
|
|
const u32 count = ms_ActiveClusters.GetCount();
|
|
for (u32 ac = 0; ac < count; ac++)
|
|
{
|
|
if (ms_ActiveClusters[ac]->RegisterCarGenVehicleData(point, vehicle))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
void CScenarioClustering::ResetForMpSpTransition()
|
|
{
|
|
const int numClusters = ms_ActiveClusters.GetCount();
|
|
for(int i = 0; i < numClusters; i++)
|
|
{
|
|
ms_ActiveClusters[i]->ResetForMpSpTransition();
|
|
}
|
|
}
|
|
|
|
void CScenarioClustering::ResetModelRefCounts()
|
|
{
|
|
// Loop over all the clusters and let each one clear out any model references.
|
|
const int numClusters = ms_ActiveClusters.GetCount();
|
|
for(int i = 0; i < numClusters; i++)
|
|
{
|
|
ms_ActiveClusters[i]->ResetModelRefCounts();
|
|
}
|
|
|
|
}
|
|
|
|
#if __ASSERT
|
|
|
|
void CScenarioClustering::ValidateInClusterFlags()
|
|
{
|
|
const int clusterCount = ms_ActiveClusters.GetCount();
|
|
|
|
CPed::Pool* pPedPool = CPed::GetPool();
|
|
const int maxPeds = pPedPool->GetSize();
|
|
for(int i = 0; i < maxPeds; i++)
|
|
{
|
|
const CPed* pPed = pPedPool->GetSlot(i);
|
|
if(!pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsInCluster))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool found = false;
|
|
for(int j = 0; j < clusterCount; j++)
|
|
{
|
|
const CSPClusterFSMWrapper& w = *ms_ActiveClusters[j];
|
|
if(w.IsTrackingPed(*pPed))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const Vec3V posV = pPed->GetMatrixRef().GetCol3();
|
|
Assertf(found, "Ped %p has become orphaned from its scenario cluster (at %.1f, %.1f, %.1f).", (void*)pPed, posV.GetXf(), posV.GetYf(), posV.GetZf());
|
|
}
|
|
|
|
CVehicle::Pool* pVehiclePool = CVehicle::GetPool();
|
|
const int maxVehicles = (int) pVehiclePool->GetSize();
|
|
for(int i = 0; i < maxVehicles; i++)
|
|
{
|
|
const CVehicle* pVehicle = pVehiclePool->GetSlot(i);
|
|
if(!pVehicle)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pVehicle->m_nVehicleFlags.bIsInCluster)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool found = false;
|
|
for(int j = 0; j < clusterCount; j++)
|
|
{
|
|
const CSPClusterFSMWrapper& w = *ms_ActiveClusters[j];
|
|
if(w.IsTrackingVehicle(*pVehicle))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
const Vec3V posV = pVehicle->GetMatrixRef().GetCol3();
|
|
Assertf(found, "Vehicle %p has become orphaned from its scenario cluster (at %.1f, %.1f, %.1f).", (void*)pVehicle, posV.GetXf(), posV.GetYf(), posV.GetZf());
|
|
}
|
|
}
|
|
|
|
#endif // __ASSERT
|
|
|
|
#if SCENARIO_DEBUG
|
|
|
|
void CScenarioClustering::AddStaticWidgets(bkBank& bank)
|
|
{
|
|
bank.AddSlider("Max Time Between Spawn Attempts", &m_Tuning.m_MaxTimeBetweenSpawnAttempts, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Static Bounds Assume Loaded Dist", &m_Tuning.m_StaticBoundsAssumeLoadedDist, 0.0f, 2000.0f, 5.0f);
|
|
bank.AddSlider("Spawn State Timeout", &m_Tuning.m_SpawnStateTimeout, 0.0f, 120.0f, 0.1f);
|
|
bank.AddSlider("Max Car Gen Schedule Attempts", &m_Tuning.m_MaxCarGenScheduleAttempts, 0, 255, 1);
|
|
}
|
|
|
|
void CScenarioClustering::AddWidgets(CScenarioPointCluster& cluster, bkGroup* parentGroup)
|
|
{
|
|
const int foundId = FindIndex(cluster);
|
|
if (foundId != -1)
|
|
{
|
|
ms_ActiveClusters[foundId]->AddWidgets(parentGroup);
|
|
}
|
|
}
|
|
|
|
void CScenarioClustering::TriggerSpawn(CScenarioPointCluster& cluster)
|
|
{
|
|
const int foundId = FindIndex(cluster);
|
|
if (foundId != -1)
|
|
{
|
|
ms_ActiveClusters[foundId]->TriggerSpawn();
|
|
}
|
|
}
|
|
|
|
void CScenarioClustering::DebugRender() const
|
|
{
|
|
const u32 count = ms_ActiveClusters.GetCount();
|
|
for (u32 ac = 0; ac < count; ac++)
|
|
{
|
|
ms_ActiveClusters[ac]->RenderInfo();
|
|
}
|
|
}
|
|
|
|
#if DR_ENABLED
|
|
void CScenarioClustering::DebugReport(debugPlayback::TextOutputVisitor& output) const
|
|
{
|
|
const u32 count = ms_ActiveClusters.GetCount();
|
|
for (u32 ac = 0; ac < count; ac++)
|
|
{
|
|
ms_ActiveClusters[ac]->DebugReport(output);
|
|
}
|
|
}
|
|
#endif //DR_ENABLED
|
|
|
|
#endif
|
|
|
|
int CScenarioClustering::FindIndex(const CScenarioPointCluster& cluster) const
|
|
{
|
|
const u32 count = ms_ActiveClusters.GetCount();
|
|
for (u32 ac = 0; ac < count; ac++)
|
|
{
|
|
if (ms_ActiveClusters[ac]->m_MyCluster == &cluster)
|
|
{
|
|
return ac;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
CSPClusterFSMWrapper* CScenarioClustering::GetNewWrapper()
|
|
{
|
|
const s32 curSpace = CSPClusterFSMWrapper::_ms_pPool->GetNoOfFreeSpaces();
|
|
|
|
// In order to prevent crashes with likely data loss if exceeding the pool limits,
|
|
// we check before trying to use the pools. If there is not enough space, we notify
|
|
// the user and return NULL, which the calling code needs to handle.
|
|
if(!curSpace)
|
|
{
|
|
#if __ASSERT
|
|
Assertf(0, "CSPClusterFSMWrapper pool is full. Size %d", CSPClusterFSMWrapper::_ms_pPool->GetSize());
|
|
#else
|
|
Errorf( "CSPClusterFSMWrapper pool is full. Size %d", CSPClusterFSMWrapper::_ms_pPool->GetSize());
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
const s32 warningLimit = 5;
|
|
if(curSpace <= warningLimit)
|
|
{
|
|
#if __ASSERT
|
|
Assertf(0, "CSPClusterFSMWrapper pool has %d of %d possible slots left.", CSPClusterFSMWrapper::_ms_pPool->GetNoOfFreeSpaces(), CSPClusterFSMWrapper::_ms_pPool->GetSize());
|
|
#else
|
|
Errorf( "CSPClusterFSMWrapper pool has %d of %d possible slots left.", CSPClusterFSMWrapper::_ms_pPool->GetNoOfFreeSpaces(), CSPClusterFSMWrapper::_ms_pPool->GetSize());
|
|
#endif
|
|
}
|
|
|
|
CSPClusterFSMWrapper* newPoint = rage_new CSPClusterFSMWrapper();
|
|
return newPoint;
|
|
}
|