Files
GTASource/game/vehicleAi/VehicleIntelligence.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

5969 lines
212 KiB
C++

//rage headers
#include "math/vecmath.h"
//game headers
#include "vehicleAi/VehicleIntelligence.h"
#include "vehicleAi/task/TaskVehicleMissionBase.h"
#include "vehicleAi/task/TaskVehiclePark.h"
#include "vehicles/VehicleDefines.h"
#include "peds/ped.h"
#include "peds/pedintelligence.h"
#include "control/trafficlights.h"
#include "control/record.h"
#include "debug/DebugScene.h"
#include "event/EventNetwork.h"
#include "event/EventShocking.h"
#include "event/ShockingEvents.h"
#include "frontend/minimap.h"
#include "scene/world/gameWorld.h"
#include "script/Handlers/GameScriptEntity.h"
#include "ai/Task/Task.h"
#include "ai/debug/types/VehicleControlDebugInfo.h"
#include "ai/debug/types/VehicleDebugInfo.h"
#include "ai/debug/types/VehicleFlagDebugInfo.h"
#include "ai/debug/types/VehicleNodeListDebugInfo.h"
#include "ai/debug/types/VehicleStuckDebugInfo.h"
#include "ai/debug/types/VehicleTaskDebugInfo.h"
#include "task/General/TaskBasic.h"
#include "Task/System/TaskManager.h"
#include "Task/System/TaskTypes.h"
#include "Task/System/TaskTree.h"
#include "Task/Vehicle/TaskCar.h"
#include "Task/Response/TaskFlee.h"
#include "camera/CamInterface.h"
#include "camera/gameplay/GameplayDirector.h"
#include "vehicleAi/roadspeedzone.h"
#include "vehicleAi/task/DeferredTasks.h"
#include "vehicleAi/Task/TaskVehicleApproach.h"
#include "vehicleAi/Task/TaskVehicleGoTo.h"
#include "vehicleAi/Task/TaskVehicleGotoAutomobile.h"
#include "vehicleAi/Task/TaskVehicleGotoBoat.h"
#include "vehicleAi/Task/TaskVehicleGoToHelicopter.h"
#include "vehicleAi/Task/TaskVehicleGoToPlane.h"
#include "vehicleAi/Task/TaskVehicleGoToSubmarine.h"
#include "vehicleAi/Task/TaskVehicleAttack.h"
#include "vehicleAi/Task/TaskVehicleBlock.h"
#include "vehicleAi/Task/TaskVehicleCircle.h"
#include "vehicleAi/Task/TaskVehicleCruise.h"
#include "vehicleAi/Task/TaskVehicleCruiseBoat.h"
#include "vehicleAi/Task/TaskVehicleDeadDriver.h"
#include "vehicleAi/Task/TaskVehicleEscort.h"
#include "vehicleAi/Task/TaskVehicleFlying.h"
#include "vehicleAi/Task/TaskVehicleFleeBoat.h"
#include "vehicleAi/Task/TaskVehicleHeliProtect.h"
#include "vehicleAi/Task/TaskVehicleFollowRecording.h"
#include "vehicleAi/Task/TaskVehicleGoTo.h"
#include "vehicleAi/Task/TaskVehicleGoToLongRange.h"
#include "VehicleAi/task/TaskVehicleLandPlane.h"
#include "vehicleAi/Task/TaskVehiclePark.h"
#include "vehicleAi/Task/TaskVehiclePlayer.h"
#include "vehicleAi/Task/TaskVehiclePoliceBehaviour.h"
#include "vehicleAi/Task/TaskVehiclePullAlongside.h"
#include "vehicleAi/Task/TaskVehiclePursue.h"
#include "vehicleAi/Task/TaskVehicleRam.h"
#include "vehicleAi/Task/TaskVehicleSpinOut.h"
#include "vehicleAi/Task/TaskVehicleTempAction.h"
#include "vehicleAi/Task/TaskVehicleThreePointTurn.h"
#include "vehicleAi/Task/TaskVehiclePlaneChase.h"
#include "vehicles/metadata/AIHandlingInfo.h"
#include "vehicles/Trailer.h"
#include "physics/physics.h"
#include "Vehicles/Bike.h"
#include "Vehicles/Heli.h"
#include "Vehicles/Submarine.h"
#include "vehicles/vehiclepopulation.h"
#include "vehicles/vehiclefactory.h"
#include "ai/debug/system/AIDebugLogManager.h"
AI_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
FW_INSTANTIATE_BASECLASS_POOL_SPILLOVER(CVehicleIntelligence, VEHICLE_POOL_SIZE, 0.22f, atHashString("Vehicle Intelligence",0xb7750119), sizeof(CHeliIntelligence));
#if __BANK
const char *CVehicleIntelligence::MissionStrings[MISSION_LAST] = {
"MISSION_NONE",
"MISSION_CRUISE",
"MISSION_RAM",
"MISSION_BLOCK",
"MISSION_GOTO",
"MISSION_STOP",
"MISSION_ATTACK",
"MISSION_FOLLOW",
"MISSION_FLEE",
"MISSION_CIRCLE",
"MISSION_ESCORT_LEFT",
"MISSION_ESCORT_RIGHT",
"MISSION_ESCORT_REAR",
"MISSION_ESCORT_FRONT",
"MISSION_GOTO_RACING",
"MISSION_FOLLOW_RECORDING",
"MISSION_POLICE_BEHAVIOUR",
"MISSION_PARK_PERPENDICULAR",
"MISSION_PARK_PARALLEL",
"MISSION_LAND",
"MISSION_LAND_AND_WAIT",
"MISSION_CRASH",
"MISSION_PULL_OVER",
"MISSION_PROTECT",
"MISSION_PARK_PERPENDICULAR_1",
"MISSION_PARK_PERPENDICULAR_2",
"MISSION_PARK_PARALLEL_1",
"MISSION_PARK_PARALLEL_2",
"MISSION_GOTO_STRAIGHTLINE",
"MISSION_LEAVE_ALONE",
"MISSION_BLOCK_BACKANDFORTH",
"MISSION_TURN_CLOCKWISE_GOING_FORWARD",
"MISSION_TURN_CLOCKWISE_GOING_BACKWARD",
"MISSION_TURN_COUNTERCLOCKWISE_GOING_FORWARD",
"MISSION_TURN_COUNTERCLOCKWISE_GOING_BACKWARD",
"MISSION_BLOCK_FRONT",
"MISSION_BLOCK_BEHIND"
};
const char *CVehicleIntelligence::TempActStrings[TEMPACT_LAST] = {
"TEMPACT_NONE",
"TEMPACT_WAIT",
"TEMPACT_EMPTYTOBEREUSED",
"TEMPACT_REVERSE",
"TEMPACT_HANDBRAKETURNLEFT",
"TEMPACT_HANDBRAKETURNRIGHT",
"TEMPACT_HANDBRAKESTRAIGHT",
"TEMPACT_TURNLEFT",
"TEMPACT_TURNRIGHT",
"TEMPACT_GOFORWARD",
"TEMPACT_SWIRVELEFT",
"TEMPACT_SWIRVERIGHT",
"TEMPACT_EMPTYTOBEREUSED2",
"TEMPACT_REVERSE_LEFT",
"TEMPACT_REVERSE_RIGHT",
"TEMPACT_PLANE_FLY_UP",
"TEMPACT_PLANE_FLY_STRAIGHT",
"TEMPACT_PLANE_SHARP_LEFT",
"TEMPACT_PLANE_SHARP_RIGHT",
"TEMPACT_HEADON_COLLISION",
"TEMPACT_SWIRVELEFT_STOP",
"TEMPACT_SWIRVERIGHT_STOP",
"TEMPACT_REVERSE_STRAIGHT",
"TEMPACT_BOOST_USE_STEERING_ANGLE",
"TEMPACT_BRAKE",
"TEMPACT_HANDBRAKETURNLEFT_INTELLIGENT",
"TEMPACT_HANDBRAKETURNRIGHT_INTELLIGENT",
"TEMPACT_HANDBRAKESTRAIGHT_INTELLIGENT",
"TEMPACT_REVERSE_STRAIGHT_HARD",
};
const char *CVehicleIntelligence::JunctionCommandStrings[JUNCTION_COMMAND_LAST] = {
"JUNCTION_COMMAND_GO",
"JUNCTION_COMMAND_APPROACHING",
"JUNCTION_COMMAND_WAIT_FOR_LIGHTS",
"JUNCTION_COMMAND_WAIT_FOR_TRAFFIC",
"JUNCTION_COMMAND_NOT_ON_JUNCTION",
"JUNCTION_COMMAND_GIVE_WAY"
};
const char *CVehicleIntelligence::JunctionFilterStrings[JUNCTION_FILTER_LAST] = {
"JUNCTION_FILTER_NONE",
"JUNCTION_FILTER_LEFT",
"JUNCTION_FILTER_MIDDLE",
"JUNCTION_FILTER_RIGHT"
};
const char *CVehicleIntelligence::JunctionCommandStringsShort[JUNCTION_COMMAND_LAST] = {
"GO",
"AP",
"WL",
"WT",
"NJ",
"GW"
};
const char *CVehicleIntelligence::JunctionFilterStringsShort[JUNCTION_FILTER_LAST] = {
"N",
"L",
"M",
"R"
};
#endif
float CVehPIDController::Update(float fInput)
{
Assert(fInput == fInput);
float fIntegral = fInput * fwTimer::GetTimeStep();
Assert(fIntegral == fIntegral);
m_fIntegral += fIntegral;
Assert(m_fIntegral == m_fIntegral);
m_fPreviousDerivative = (fInput - m_fPreviousInput) / fwTimer::GetTimeStep();
m_fPreviousInput = fInput;
m_fPreviousOutput = (m_fKp * fInput) + (m_fKi * m_fIntegral) + (m_fKd * m_fPreviousDerivative);
return m_fPreviousOutput;
}
//---------------------------------------------------------------------------
// Helper function used for some LOD checks since POPTYPE_RANDOM_PARKED doesn't
// seem to be reliable enough to detect a parked vehicle (it doesn't get set again
// if the player enters and then leaves the vehicle, for example).
static bool sConsiderParkedVehicleForLod(CVehicle* pVehicle)
{
ePopType popType = pVehicle->PopTypeGet();
if(popType == POPTYPE_RANDOM_PARKED)
{
return true;
}
else if(popType == POPTYPE_RANDOM_AMBIENT)
{
CVehicleIntelligence* pIntel = pVehicle->GetIntelligence();
if(!pIntel || !pIntel->GetActiveTask())
{
return true;
}
}
else if(popType == POPTYPE_MISSION)
{
// Mission vehicles can get low LOD processing if they are not
// running any primary nor secondary tasks.
CVehicleIntelligence* pVehicleIntelligence = pVehicle->GetIntelligence();
if(pVehicleIntelligence)
{
const CTaskManager* pTaskManager = pVehicleIntelligence->GetTaskManager();
if( pTaskManager && !pTaskManager->GetActiveTask(VEHICLE_TASK_TREE_PRIMARY) && !pTaskManager->GetActiveTask(VEHICLE_TASK_TREE_SECONDARY) )
{
// no primary and no secondary task, treat as parked
return true;
}
}
else // pVehicleIntelligence is NULL
{
// no tasks running if no intelligence exists, treat as parked
return true;
}
}
return false;
}
//---------------------------------------------------------------------------
// Internals
//---------------------------------------------------------------------------
namespace // anonymous namespace
{
#if __BANK
const u32 uDEBUG_MAX_TIME_TO_SHOW_ROUTE_INTERCEPTION_TEST_MS = 10000u;
#endif // __BANK
}
//---------------------------------------------------------------------------
// CVehicleIntelligence
//---------------------------------------------------------------------------
bool CVehicleIntelligence::ms_bReallyPreventSwitchedOffFleeing = false;
CVehicleIntelligence::CVehicleIntelligence(CVehicle *pVehicle) :
m_pVehicle(pVehicle),
m_TaskManager(pVehicle),
m_pedScanner(CExpensiveProcess::VPT_PedScanner),
m_vehicleScanner(CExpensiveProcess::VPT_VehicleScanner),
m_objectScanner(CExpensiveProcess::VPT_ObjectScanner),
m_SteeringDistributer(CExpensiveProcess::VPT_SteeringDistributer),
m_BrakingDistributer(CExpensiveProcess::VPT_BrakingDistributer),
m_LaneChangeDistributer(CExpensiveProcess::VPT_LaneChangeDistributer),
m_DummyAvoidanceDistributer(CExpensiveProcess::VPT_DummyAvoidanceDistributer),
m_TailgateDistributer(CExpensiveProcess::VPT_TailgateDistributer),
m_NavmeshLOSDistributer(CExpensiveProcess::VPT_NavmeshLOSDistributer),
m_UpdateJunctionsDistributer(CExpensiveProcess::VPT_UpdateJunctionsDistributer),
m_SirenReactionDistributer(CExpensiveProcess::VPT_SirenReactionDistributer),
m_eventGroup(pVehicle),
m_BoatAvoidanceHelper(pVehicle && ( pVehicle->InheritsFromBoat() || pVehicle->InheritsFromAmphibiousAutomobile() ) ? rage_new CBoatAvoidanceHelper(*pVehicle) : NULL)
{
m_SteeringDistributer.RegisterSlot();
m_BrakingDistributer.RegisterSlot();
m_LaneChangeDistributer.RegisterSlot();
m_DummyAvoidanceDistributer.RegisterSlot();
m_TailgateDistributer.RegisterSlot();
m_NavmeshLOSDistributer.RegisterSlot();
m_UpdateJunctionsDistributer.RegisterSlot();
m_SirenReactionDistributer.RegisterSlot();
LastTimeNotStuck = fwTimer::GetTimeInMilliseconds();
LastTimeHonkedAt = 0;
m_nLastTimeToldOthersAboutRevEngine = 0;
m_fSmoothedSpeedSq = 0.0f;
m_iNumScenarioRouteHistoryPoints = 0;
m_pScenarioRoutePointHistory = NULL;
// bDontGoAgainstTraffic = false;
SpeedFromNodes = 0;
SpeedMultiplier = 1.0f;
m_RecordingNumber = -1;
// AISwitchToStraightLineDistance = 20;
bCarHasToReverseFirst = false;
// PreviousMission = MISSION_NONE;
MillisecondsNotMoving = 0;
MillisecondsNotMovingForRemoval = 0;
m_lastTimeTriedToPushPlayerPed = 0;
m_nTimeLastTriedToShakePedFromRoof = 0;
m_iNumVehsWeAreBlockingThisFrame = 0;
m_iJunctionTurnDirection = BIT_NOT_SET;
m_iJunctionArrivalTime = UINT_MAX;
m_JunctionCommand = JUNCTION_COMMAND_NOT_ON_JUNCTION;
m_iJunctionFilter = JUNCTION_FILTER_NONE;
m_bHasFixedUpPathForCurrentJunction = false;
m_JunctionNode.SetEmpty();
m_PreviousJunctionNode.SetEmpty();
m_Junction = NULL;
m_PreviousJunction = NULL;
m_JunctionEntrance = NULL;
m_PreviousJunctionEntrance = NULL;
m_JunctionExit = NULL;
m_PreviousJunctionExit = NULL;
m_JunctionEntranceNode.SetEmpty();
m_JunctionExitNode.SetEmpty();
m_vJunctionEntrance.Zero();
m_vJunctionExit.Zero();
m_PreviousJunctionEntranceNode.SetEmpty();
m_PreviousJunctionExitNode.SetEmpty();
m_vPreviousJunctionEntrance.Zero();
m_vPreviousJunctionExit.Zero();
m_JunctionEntranceLane = -1;
m_JunctionExitLane = -1;
m_PreviousJunctionEntranceLane = -1;
m_PreviousJunctionExitLane = -1;
m_TrafficLightCommand = TRAFFICLIGHT_COMMAND_INVALID;
m_pCarThatIsBlockingUs = NULL;
//m_LaneShift = 0.0f;
m_bHumaniseControls = false;
m_bHasCachedNodeList = false;
m_bHasCachedFollowRoute = false;
m_bRemoveAggressivelyForCarjackingMission = false;
m_bSwerveAroundPlayerVehicleForRiotVanMission = false;
m_bStationaryInTank = false;
m_bSetBoatIgnoreLandProbes = false;
m_bSteerForBuildings = true;
m_bDontSwerveForUs = false;
m_bIgnorePlanesSmallPitchChange = false;
m_pCachedNodeList = NULL;
m_pCachedNodeListTask = NULL;
m_pCachedFollowRouteHelper = NULL;
m_pCachedFollowRouteHelperTask = NULL;
m_pCarWeAreBehind = NULL;
m_pCarWeCouldBeSlipStreaming = NULL;
m_pObstructingEntity = NULL;
m_pSteeringAroundEntity = NULL;
m_pFleeEntity = NULL;
m_fDesiredSpeedThisFrame = -1.0f;
m_NumCarsBehindUs = 0;
m_NumCarsBehindContributedToCarWeAreBehind = 0;
m_iImpatienceTimerOverride = -1;
m_nHandlingOverrideHash = 0;
m_nNumPedsThatNeedTaskFromPretendOccupant = 0;
m_fPretendOccupantAbility = -1.0f;
m_fPretendOccupantAggressiveness = -1.0f;
m_fCustomPathNodeStreamingRadius = -1.0f;
m_uTimeWeStartedCollidingWithPlayerVehicle = 0;
m_uTimeLastTouchedRealMissionVehicleWhileDummy = 0;
m_uLastTimeInWater = 0;
m_uLastTimePedEntering = 0;
m_uLastTimePedInCover = 0;
m_uLastTimeRamming = 0;
m_uLastTimePlayerAimedAtOrAround = 0;
m_bBlockWeaponSelection = false;
m_bUsingScriptAutopilot = false;
#if __BANK
lastTimeJoinedWithRoadSystem = 0;
lastTimeActuallyJoinedWithRoadSystem = 0;
#endif // __BANK
#if __DEV
m_iCacheNodeListHitsThisFrame = 0;
m_iCacheFollowRouteHitsThisFrame = 0;
#endif
}
CVehicleIntelligence::~CVehicleIntelligence()
{
SetCarWeAreBehind(NULL);
m_pCarThatIsBlockingUs = NULL;
m_pObstructingEntity = NULL;
m_pFleeEntity = NULL;
m_pSteeringAroundEntity = NULL;
if (m_CurrentEvent)
{
delete m_CurrentEvent;
m_CurrentEvent = NULL;
}
// safe to delete null pointers
delete m_BoatAvoidanceHelper;
DeleteScenarioPointHistory();
}
bool CVehicleIntelligence::HasBeenSlowlyPushingPlayer() const
{
TUNE_GROUP_INT(FOLLOW_PATH_AI_THROTTLE, timeAfterWaitingForPedToContinueSteering, 3000, 0, 10000, 1);
return fwTimer::GetTimeInMilliseconds() < m_lastTimeTriedToPushPlayerPed + timeAfterWaitingForPedToContinueSteering;
}
u32 CVehicleIntelligence::GetTimeSinceLastInWater() const
{
Assert(HasBeenInWater());
return CTimeHelpers::GetTimeSince(m_uLastTimeInWater);
}
void CVehicleIntelligence::AddScenarioHistoryPoint(const CRoutePoint& pt)
{
EnsureHasScenarioPointHistory();
Assert(m_iNumScenarioRouteHistoryPoints == 0 ||
IsGreaterThanAll(DistSquared(pt.GetPosition(), m_pScenarioRoutePointHistory[m_iNumScenarioRouteHistoryPoints-1].GetPosition())
, ScalarV(V_FLT_SMALL_2) * ScalarV(V_FLT_SMALL_2)));
if (m_iNumScenarioRouteHistoryPoints >= ms_iNumScenarioRoutePointsToSave)
{
//if we're full, shift all the points over
for (int i = 0; i < m_iNumScenarioRouteHistoryPoints-1; i++)
{
m_pScenarioRoutePointHistory[i] = m_pScenarioRoutePointHistory[i+1];
}
}
m_iNumScenarioRouteHistoryPoints = rage::Min(m_iNumScenarioRouteHistoryPoints+1, ms_iNumScenarioRoutePointsToSave);
m_pScenarioRoutePointHistory[m_iNumScenarioRouteHistoryPoints-1] = pt;
}
void CVehicleIntelligence::SetJunctionNode(const CNodeAddress& junctionNode, CJunction* junctionPtr)
{
// This stuff should hold up, but we may not want the overhead compiled in even in Beta builds:
#if __ASSERT && 0
if(junctionNode.IsEmpty())
{
Assert(!junctionPtr);
}
else if(junctionPtr)
{
Assert(junctionPtr->ContainsJunctionNode(junctionNode));
}
Assert(CJunctions::FindJunctionFromNode(junctionNode) == junctionPtr);
#endif // __ASSERT
m_JunctionNode = junctionNode;
m_Junction = junctionPtr;
}
void CVehicleIntelligence::CacheJunctionInfo()
{
if (m_Junction)
{
CNodeAddress entranceAddress;
CNodeAddress exitAddress;
s32 iEntranceLane = -1;
s32 iExitLane = -1;
m_Junction->FindEntryAndExitNodes(m_pVehicle, entranceAddress, exitAddress, iEntranceLane, iExitLane);
const CVehicleFollowRouteHelper* pFollowRouteHelper = GetFollowRouteHelper();
const CRoutePoint* pRoutePoints = pFollowRouteHelper->GetRoutePoints();
if (!entranceAddress.IsEmpty())
{
s32 iJunctionEntrance = m_Junction->FindEntranceIndexWithNode(entranceAddress);
CJunctionEntrance* pJunctionEntrance = &m_Junction->GetEntrance(iJunctionEntrance);
SetJunctionEntrance(pJunctionEntrance);
SetJunctionEntranceNode(entranceAddress);
SetJunctionEntranceLane(iEntranceLane);
const CPathNode* pEntranceNode = ThePaths.FindNodePointerSafe(entranceAddress);
if (pEntranceNode)
{
const s16 iEntrancePoint = pFollowRouteHelper->FindClosestPointToPosition(VECTOR3_TO_VEC3V(pEntranceNode->GetPos()));
const CRoutePoint& entrancePoint = pRoutePoints[iEntrancePoint];
const Vector3 vEntrance = VEC3V_TO_VECTOR3(entrancePoint.GetPosition() + entrancePoint.GetLaneCenterOffset());
SetJunctionEntrancePosition(vEntrance);
}
}
if (!exitAddress.IsEmpty())
{
s32 iJunctionExit = m_Junction->FindEntranceIndexWithNode(exitAddress);
CJunctionEntrance* pJunctionExit = &m_Junction->GetEntrance(iJunctionExit);
SetJunctionExit(pJunctionExit);
SetJunctionExitNode(exitAddress);
SetJunctionExitLane(iExitLane);
const CPathNode* pExitNode = ThePaths.FindNodePointerSafe(exitAddress);
if (pExitNode)
{
const s16 iExitPoint = pFollowRouteHelper->FindClosestPointToPosition(VECTOR3_TO_VEC3V(pExitNode->GetPos()));
const CRoutePoint& exitPoint = pRoutePoints[iExitPoint];
const Vector3 vExit = VEC3V_TO_VECTOR3(exitPoint.GetPosition() + exitPoint.GetLaneCenterOffset());
SetJunctionExitPosition(vExit);
}
}
CacheJunctionTurnDirection();
}
}
void CVehicleIntelligence::ResetCachedJunctionInfo()
{
m_PreviousJunctionNode = m_JunctionNode;
m_PreviousJunction = m_Junction;
m_PreviousJunctionEntrance = m_JunctionEntrance;
m_PreviousJunctionExit = m_JunctionExit;
m_PreviousJunctionEntranceNode = m_JunctionEntranceNode;
m_PreviousJunctionExitNode = m_JunctionExitNode;
m_vPreviousJunctionEntrance = m_vJunctionEntrance;
m_vPreviousJunctionExit = m_vJunctionExit;
m_PreviousJunctionEntranceLane = m_JunctionEntranceLane;
m_PreviousJunctionExitLane = m_JunctionExitLane;
m_JunctionNode.SetEmpty();
m_Junction = NULL;
m_JunctionEntrance = NULL;
m_JunctionExit = NULL;
m_JunctionEntranceNode.SetEmpty();
m_JunctionExitNode.SetEmpty();
m_vJunctionEntrance.Zero();
m_vJunctionExit.Zero();
m_JunctionEntranceLane = -1;
m_JunctionExitLane = -1;
m_iJunctionTurnDirection = BIT_NOT_SET;
m_iJunctionArrivalTime = UINT_MAX;
m_JunctionCommand = JUNCTION_COMMAND_GO;
m_iJunctionFilter = JUNCTION_FILTER_NONE;
m_bHasFixedUpPathForCurrentJunction = false;
}
// Create shocking events from properties of the vehicle for other entities in the game to react against.
void CVehicleIntelligence::GenerateEvents()
{
// We basically just want it to happen continuously but at the same time not spam events every frame.
// Could use a timer or something prettier here but I don't think we want to waste a member variable just for this.
static dev_u32 s_nFramesBetweenSirenShockingEvents = 127;
if ((fwTimer::GetFrameCount() & s_nFramesBetweenSirenShockingEvents) == 0)
{
if(m_pVehicle->m_nVehicleFlags.GetIsSirenOn())
{
CEventShockingSiren sirenEvent(*m_pVehicle);
CShockingEventsManager::Add(sirenEvent);
}
if(m_pVehicle->IsAlarmActivated())
{
CEventShockingCarAlarm alarmEvent(*m_pVehicle);
CShockingEventsManager::Add(alarmEvent);
}
}
// Below this line all events created are only when the player is in control of the vehicle.
if (!m_pVehicle->GetDriver() || !m_pVehicle->GetDriver()->IsLocalPlayer() || !m_pVehicle->GetDriver()->GetControlFromPlayer())
{
return;
}
static dev_float s_fRevRatioThreshold = 0.89f;
if (m_pVehicle->m_Transmission.GetRevRatio() > s_fRevRatioThreshold
&& m_pVehicle->GetThrottle() > 0.9f
&& (m_pVehicle->GetHandBrake() || m_pVehicle->GetBrake() > 0.9f)
&& m_pVehicle->GetVehicleUpDirection().GetZf() > 0.0f
&& m_pVehicle->m_nVehicleFlags.bEngineOn
&& !m_pVehicle->GetBurnoutMode()
&& m_pVehicle->GetVelocity().Mag2() < 2.0f)
{
if (m_pVehicle->InheritsFromAutomobile())
{
CAutomobile* pAutomobile = static_cast<CAutomobile*>(m_pVehicle);
pAutomobile->m_nAutomobileFlags.bPlayerIsRevvingEngineThisFrame = true;
}
//only do this occasionally
static dev_u32 s_nTimeBetweenTellOthersAboutRevMs = 600;
if (fwTimer::GetTimeInMilliseconds() > m_nLastTimeToldOthersAboutRevEngine + s_nTimeBetweenTellOthersAboutRevMs)
{
m_nLastTimeToldOthersAboutRevEngine = fwTimer::GetTimeInMilliseconds();
TellOthersAboutRevEngine();
}
}
}
bool CVehicleIntelligence::IsPedAffectedByHonkOrRev(const CPed& rPed, float& fDistSq, Vec3V_In vForwardTestDir, Vec3V_In vRightTestDir) const
{
//Ensure the ped is not in a vehicle.
if(rPed.GetIsInVehicle())
{
return false;
}
//Ensure the ped is in the honk zone.
if(!IsEntityInAffectedZoneForHonkOrRev(rPed, fDistSq, vForwardTestDir, vRightTestDir))
{
return false;
}
return true;
}
bool CVehicleIntelligence::IsVehicleAffectedByHonkOrRev(const CVehicle& rVehicle, float& fDistSq, Vec3V_In vForwardTestDir, Vec3V_In vRightTestDir) const
{
//Ensure we are not honking at ourself.
if(&rVehicle == m_pVehicle)
{
return false;
}
//Ensure the vehicle is in the honk zone.
if(!IsEntityInAffectedZoneForHonkOrRev(rVehicle, fDistSq, vForwardTestDir, vRightTestDir))
{
return false;
}
return true;
}
bool CVehicleIntelligence::IsEntityInAffectedZoneForHonkOrRev(const CEntity& rEntity, float& fDistSq, Vec3V_In vForwardTestDirIn, Vec3V_In vRightTestDirIn) const
{
const ScalarV svZero(V_ZERO);
Vec3V vPosDelta = rEntity.GetTransform().GetPosition() - m_pVehicle->GetVehiclePosition();
//these are passed by reference since passing them by value doesn't align on x86-32 builds,
//but we don't want to actually modify the params so just copy them onto the stack here
//before we flatten them
Vec3V vForwardTestDir = vForwardTestDirIn;
Vec3V vRightTestDir = vRightTestDirIn;
vPosDelta.SetZ(svZero);
vForwardTestDir.SetZ(svZero);
vRightTestDir.SetZ(svZero);
//grcDebugDraw::Arrow(m_pVehicle->GetVehiclePosition(), m_pVehicle->GetVehiclePosition() + (vForwardTestDir * ScalarV(V_FIVE)), 0.5f, Color_yellow);
//grcDebugDraw::Arrow(m_pVehicle->GetVehiclePosition(), m_pVehicle->GetVehiclePosition() + (vRightTestDir * ScalarV(V_FIVE)), 0.5f, Color_green);
//reject anyone behind us
if ((Dot(vPosDelta, vForwardTestDir) < svZero).Getb())
{
return false;
}
//reject anyone too far away
static dev_float s_fMaxDistForHonkingSqr = 40.0f * 40.0f;
const ScalarV svMaxDistForHonkingSqr(s_fMaxDistForHonkingSqr);
const ScalarV svDistSq = MagSquared(vPosDelta);
if ((svDistSq > svMaxDistForHonkingSqr).Getb())
{
return false;
}
//reject anyone too far off to the side
static dev_float s_fMaxLateralDistToReceiveHonk = 6.0f;
const ScalarV svMaxLateralDistToRecieveHonk(s_fMaxLateralDistToReceiveHonk);
if ( (Abs(Dot(vPosDelta, vRightTestDir)) > svMaxLateralDistToRecieveHonk).Getb() )
{
return false;
}
fDistSq = svDistSq.Getf();
return true;
}
void CVehicleIntelligence::TellOthersAboutHonk() const
{
Assert(m_pVehicle);
float fDistSqrToClosestCar = FLT_MAX;
CVehicle* pClosestCarInFront = NULL;
const Vec3V vVehicleForwardDirection = VECTOR3_TO_VEC3V(camInterface::GetGameplayDirector().GetFrame().GetFront());
const Vec3V vVehicleRightDirection = VECTOR3_TO_VEC3V(camInterface::GetGameplayDirector().GetFrame().GetRight());
CEntityScannerIterator vehicleList = m_pVehicle->GetIntelligence()->GetVehicleScanner().GetIterator();
for( CEntity* pEntity = vehicleList.GetFirst(); pEntity; pEntity = vehicleList.GetNext() )
{
CVehicle* pOtherVehicle = (CVehicle*) pEntity;
//Ensure the vehicle cares about the honk.
float fDistSq;
if(!IsVehicleAffectedByHonkOrRev(*pOtherVehicle, fDistSq, vVehicleForwardDirection, vVehicleRightDirection))
{
continue;
}
//Update the closest car.
if(fDistSq < fDistSqrToClosestCar)
{
fDistSqrToClosestCar = fDistSq;
pClosestCarInFront = pOtherVehicle;
}
}
float fDistSqrToClosestPed = FLT_MAX;
CPed* pClosestPedInFront = NULL;
CEntityScannerIterator pedList = m_pVehicle->GetIntelligence()->GetPedScanner().GetIterator();
for( CEntity* pEntity = pedList.GetFirst(); pEntity; pEntity = pedList.GetNext() )
{
CPed* pOtherPed = (CPed*) pEntity;
//Ensure the ped cares about the honk.
float fDistSq;
if(!IsPedAffectedByHonkOrRev(*pOtherPed, fDistSq, vVehicleForwardDirection, vVehicleRightDirection))
{
continue;
}
//Update the closest ped.
if(fDistSq < fDistSqrToClosestPed)
{
fDistSqrToClosestPed = fDistSq;
pClosestPedInFront = pOtherPed;
}
}
if (pClosestCarInFront)
{
pClosestCarInFront->GetIntelligence()->LastTimeHonkedAt = fwTimer::GetTimeInMilliseconds();
//Check if the driver is valid.
CPed* pDriver = pClosestCarInFront->GetDriver();
if(pDriver)
{
//Notify the driver that they were honked at.
pDriver->GetPedIntelligence()->HonkedAt(*m_pVehicle);
}
}
if (pClosestPedInFront)
{
//Notify the ped that they were honked at.
pClosestPedInFront->GetPedIntelligence()->HonkedAt(*m_pVehicle);
}
}
void CVehicleIntelligence::TellOthersAboutRevEngine() const
{
Assert(m_pVehicle);
float fDistSqrToClosestCar = FLT_MAX;
CVehicle* pClosestCarInFront = NULL;
const Vec3V vForwardDir = VECTOR3_TO_VEC3V(camInterface::GetGameplayDirector().GetFrame().GetFront());
const Vec3V vRightDir = VECTOR3_TO_VEC3V(camInterface::GetGameplayDirector().GetFrame().GetRight());
CEntityScannerIterator vehicleList = m_pVehicle->GetIntelligence()->GetVehicleScanner().GetIterator();
for( CEntity* pEntity = vehicleList.GetFirst(); pEntity; pEntity = vehicleList.GetNext() )
{
CVehicle* pOtherVehicle = (CVehicle*) pEntity;
//Ensure the vehicle cares about the honk.
float fDistSq;
if(!IsVehicleAffectedByHonkOrRev(*pOtherVehicle, fDistSq, vForwardDir, vRightDir))
{
continue;
}
//Update the closest car.
if(fDistSq < fDistSqrToClosestCar)
{
fDistSqrToClosestCar = fDistSq;
pClosestCarInFront = pOtherVehicle;
}
}
float fDistSqrToClosestPed = FLT_MAX;
CPed* pClosestPedInFront = NULL;
CEntityScannerIterator pedList = m_pVehicle->GetIntelligence()->GetPedScanner().GetIterator();
for( CEntity* pEntity = pedList.GetFirst(); pEntity; pEntity = pedList.GetNext() )
{
CPed* pOtherPed = (CPed*) pEntity;
//Only gang members are affecting by revving in this way.
if(!pOtherPed->IsGangPed())
{
continue;
}
//Ensure the ped cares about the rev.
float fDistSq;
if(!IsPedAffectedByHonkOrRev(*pOtherPed, fDistSq, vForwardDir, vRightDir))
{
continue;
}
//Update the closest ped.
if(fDistSq < fDistSqrToClosestPed)
{
fDistSqrToClosestPed = fDistSq;
pClosestPedInFront = pOtherPed;
}
}
if (pClosestCarInFront)
{
//Check if the driver is valid.
CPed* pDriver = pClosestCarInFront->GetDriver();
if(pDriver)
{
//Notify the driver that they were revved at.
pDriver->GetPedIntelligence()->RevvedAt(*m_pVehicle);
}
}
if (pClosestPedInFront)
{
//Notify the ped that they were revved at.
pClosestPedInFront->GetPedIntelligence()->RevvedAt(*m_pVehicle);
}
// Throw a shocking event for nearby peds to react to.
CEventShockingEngineRevved shockEvent(*m_pVehicle);
CShockingEventsManager::Add(shockEvent);
}
//
//
//
void CVehicleIntelligence::PreUpdateVehicle()
{
//update vehicle specific stuff
if(m_pVehicle->InheritsFromAutomobile())
{
CAutomobile *pAutomobile = static_cast<CAutomobile*>(m_pVehicle);
// Police cars and such will report crimes immediately
// (No need for peds to use the phone)
if (pAutomobile->GetStatus() != STATUS_ABANDONED && pAutomobile->GetStatus() != STATUS_WRECKED && pAutomobile->GetStatus() != STATUS_PLAYER
/*&& GetStatus() != STATUS_PLAYER_REMOTE*/ && pAutomobile->GetStatus() != STATUS_PLAYER_DISABLED)
{
if (pAutomobile->IsLawEnforcementVehicle())
{
pAutomobile->ScanForCrimes();
}
}
}
if(m_pVehicle->InheritsFromBike())
{
CBike *pBike = static_cast<CBike*>(m_pVehicle);
if(pBike->GetDriver())
{
pBike->SetBikeOnSideStand(false);
pBike->m_nBikeFlags.bGettingPickedUp = false;
}
else if (pBike->GetSeatManager()->CountPedsInSeats() > 0)
{
pBike->SetBikeOnSideStand(true);
}
}
}
void CVehicleIntelligence::NetworkResetMillisecondsNotMovingForRemoval()
{
if( aiVerifyf( m_pVehicle,"NULL m_pVehicle" ) &&
aiVerifyf( NetworkInterface::IsGameInProgress(),"%s Only expect to use this command in network games",m_pVehicle->GetDebugName() ) )
{
MillisecondsNotMovingForRemoval = 0;
}
}
//
//
//
void CVehicleIntelligence::ResetVariables(float fTimeStep)
{
float flatSpeed = m_pVehicle->GetAiVelocity().XYMag2();
//for player vehicle, check has throttle down as otherwise causes AI pushing past player vehicle to stop when they nudge the car
bool bNoDriverOrHasInput = !m_pVehicle->GetDriver() || !m_pVehicle->GetDriver()->IsPlayer() || m_pVehicle->GetThrottle() != 0.0f;
if (flatSpeed > (0.1f * 0.1f) && bNoDriverOrHasInput)
{
LastTimeNotStuck = fwTimer::GetTimeInMilliseconds();
}
if(flatSpeed >=(1.5f*1.5f) || m_pVehicle->m_nVehicleFlags.bIsThisAParkedCar)
{
MillisecondsNotMovingForRemoval = 0;
}
else
{
u32 timeStepInMs = (u32)(fTimeStep*1000.0f);
MillisecondsNotMovingForRemoval += timeStepInMs;
}
//Calculate the smoothed speed.
TUNE_GROUP_FLOAT(CAR_AI, fWeightForSmoothedSpeedPerSecond, 0.75f, 0.0f, 3.0f, 0.01f);
float fWeightCurr = fTimeStep * fWeightForSmoothedSpeedPerSecond;
fWeightCurr = Clamp(fWeightCurr, 0.0f, 1.0f);
float fWeightPrev = 1.0f - fWeightCurr;
m_fSmoothedSpeedSq = (fWeightPrev * m_fSmoothedSpeedSq) + (fWeightCurr * flatSpeed);
//update flags
//m_pVehicle->m_nVehicleFlags.bCopperBlockedCouldLeaveCar = false;
SetHumaniseControls(false);
m_pObstructingEntity = NULL;
m_pFleeEntity = NULL;
m_pSteeringAroundEntity = NULL;
m_fDesiredSpeedThisFrame = -1.0f;
}
void CVehicleIntelligence::SetPretendOccupantEventDataBasedOnCurrentTask()
{
const CTaskVehicleMissionBase* pActiveTask = GetActiveTask();
if (!pActiveTask)
{
return;
}
if (pActiveTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_FLEE)
{
// Assertf(!m_pVehicle->GetPedInSeat(0)
// || m_pVehicle->GetPedInSeat(0)->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_SMART_FLEE)
// , "Encountered ped driving vehicle running TASK_VEHICLE_FLEE, but not running TASK_SMART_FLEE");
m_PretendOccupantEventPriority = VehicleEventPriority::VEHICLE_EVENT_PRIORITY_RESPOND_TO_THREAT;
m_PretendOccupantEventParams = pActiveTask->GetParams();
}
else if (pActiveTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_REACT_TO_COP_SIREN)
{
m_PretendOccupantEventPriority = VehicleEventPriority::VEHICLE_EVENT_PRIORITY_RESPOND_TO_SIREN;
m_PretendOccupantEventParams = pActiveTask->GetParams();
}
else
{
ResetPretendOccupantEventData();
FlushPretendOccupantEventGroup(); //necessary?
}
}
void CVehicleIntelligence::Process_TouchingRealMissionVehicle()
{
Assert(m_pVehicle);
const CCollisionHistory* pCollisionHistory = m_pVehicle->GetFrameCollisionHistory();
if(pCollisionHistory->GetNumCollidedEntities() > 0)
{
const CCollisionRecord* pCurrent = pCollisionHistory->GetFirstVehicleCollisionRecord();
while (pCurrent)
{
const CVehicle* pOtherVeh = (const CVehicle*)pCurrent->m_pRegdCollisionEntity.Get();
if (pOtherVeh && !pOtherVeh->IsDummy()
&& (pOtherVeh->PopTypeIsMission() || (pOtherVeh->GetDriver() && pOtherVeh->GetDriver()->PopTypeIsMission())))
{
m_uTimeLastTouchedRealMissionVehicleWhileDummy = fwTimer::GetTimeInMilliseconds();
break;
}
pCurrent = pCurrent->GetNext();
}
}
}
//
//
//
#if __STATS
EXT_PF_TIMER(VehicleIntelligenceProcessTask_Run);
#endif // __STATS
void CVehicleIntelligence::Process(bool fullUpdate, float fFullUpdateTimeStep)
{
// Immediately propagate the number of cars behind us to avoid painfully slow, multi-pass, propagation
Process_NumCarsBehindUs();
if(!fullUpdate)
{
return;
}
// reset stuff for frame
m_pVehicle->m_nVehicleFlags.bWarnedPeds = false;
m_pVehicle->m_nVehicleFlags.bRestingOnPhysical = false;
m_pVehicle->m_nVehicleFlags.bTasksAllowDummying = false;
m_pVehicle->m_nVehicleFlags.bHasParentVehicle = m_pVehicle->GetAttachParentVehicleDummyOrPhysical() != NULL;
m_pVehicle->m_nVehicleFlags.bLodCanUseTimeslicing = false; // Tasks should set this again during the imminent update, if they allow timeslicing.
m_pVehicle->m_nVehicleFlags.bLodShouldUseTimeslicing = false;
m_pVehicle->m_nVehicleFlags.bLodShouldSkipUpdatesInTimeslicedLod = false;
m_pVehicle->m_nVehicleFlags.bAvoidanceDirtyFlag = m_pVehicle->GetIsBeingTowed();
m_pVehicle->m_nVehicleFlags.bUnbreakableLandingGear = false;
m_pVehicle->m_nVehicleFlags.bIsDoingHandBrakeTurn = false;
m_pVehicle->m_nVehicleFlags.bWasStoppedForDoor = false;
m_pVehicle->m_nVehicleFlags.bIsHesitating = false;
m_pVehicle->m_nVehicleFlags.bDisableAvoidanceProjection = false;
//stop for the tank, or air vehicles the player has owned or our siren is valid
const bool bIsThreatening = m_pVehicle->IsTank() || ((m_pVehicle->InheritsFromHeli() || m_pVehicle->InheritsFromPlane()) && m_pVehicle->m_nVehicleFlags.bHasBeenOwnedByPlayer);
if ((bIsThreatening && !m_pVehicle->GetDriver() && (m_pVehicle->m_CarGenThatCreatedUs == -1 || m_pVehicle->m_LastTimeWeHadADriver > 0)) || ShouldStopForSiren() )
{
m_pVehicle->m_nVehicleFlags.bForceOtherVehiclesToStopForThisVehicle = true;
}
if((m_pVehicle->m_Buoyancy.GetStatus() != NOT_IN_WATER) || (m_pVehicle->GetIsInWater()))
{
m_uLastTimeInWater = fwTimer::GetTimeInMilliseconds();
}
if ( m_BoatAvoidanceHelper )
{
m_BoatAvoidanceHelper->Process(*m_pVehicle, fFullUpdateTimeStep);
}
//update junctions
//this is expensive now - we dont want to do it each frame
const bool bUpdateJunctions = m_UpdateJunctionsDistributer.IsRegistered() && m_UpdateJunctionsDistributer.ShouldBeProcessedThisFrame();
// Don't process controls for vehicles that are controlled by a remote machine
if (m_pVehicle->IsNetworkClone())
{
Process_NearbyEntityLists();
if (bUpdateJunctions)
{
m_pVehicle->UpdateJunctions();
}
return;
}
// Prevent vehicles hit from explosions from going to dummy until they are inactive
if(m_pVehicle->m_nVehicleFlags.bInMotionFromExplosion && m_pVehicle->GetCurrentPhysicsInst() && m_pVehicle->GetCurrentPhysicsInst()->IsInLevel())
{
m_pVehicle->m_nVehicleFlags.bInMotionFromExplosion = CPhysics::GetLevel()->IsActive(m_pVehicle->GetCurrentPhysicsInst()->GetLevelIndex());
m_pVehicle->m_nVehicleFlags.bPreventBeingDummyThisFrame =
m_pVehicle->m_nVehicleFlags.bPreventBeingDummyThisFrame || m_pVehicle->m_nVehicleFlags.bInMotionFromExplosion;
}
//if we were using pretend occupants and our task ended for some reason,
//create a new default task.
//this can happen if we transition from real->pretend occupants while pulling
//over for a cop siren
if (m_pVehicle->m_nVehicleFlags.bUsingPretendOccupants && !GetActiveTask()
&& m_pVehicle->CanSetUpWithPretendOccupants())
{
m_pVehicle->SetUpWithPretendOccupants();
}
//Process the dummy flags.
Process_DummyFlags();
if (GetRecordingNumber() >= 0 && !CVehicleRecordingMgr::GetUseCarAI(GetRecordingNumber()))
{
// Invalidate the cached node list
InvalidateCachedNodeList();
InvalidateCachedFollowRoute();
//vehicles on recordings that are not using ai should be assumed dirty
m_pVehicle->m_nVehicleFlags.bAvoidanceDirtyFlag = true;
//assume vehicle recordings always know what they're doing
UpdateCarHasReasonToBeStopped();
HandleEvents();
//process the tasks even when running a car recording so convertible roofs still work.
m_TaskManager.Process(fFullUpdateTimeStep);
}
else
{
PreUpdateVehicle();
if(bUpdateJunctions)
{
m_pVehicle->UpdateJunctions();
}
//Process the car we are behind.
Process_CarBehind();
ResetVariables(fFullUpdateTimeStep);
Process_NearbyEntityLists();
// Invalidate the cached node list
InvalidateCachedNodeList();
InvalidateCachedFollowRoute();
UpdateTimeNotMoving(fFullUpdateTimeStep);
HandleEvents();
m_TaskManager.Process(fFullUpdateTimeStep);
}
#if !__PS3
TUNE_GROUP_BOOL(AI_DEFERRED_TASKS, USING_ANY_DEFERRED_TASKS, true);
if(USING_ANY_DEFERRED_TASKS)
{
aiDeferredTasks::TaskData taskData(NULL, m_pVehicle, fFullUpdateTimeStep, fullUpdate);
aiDeferredTasks::g_VehicleIntelligenceProcess.Queue(taskData);
}
else
{
#endif
PF_START(VehicleIntelligenceProcessTask_Run);
Process_OnDeferredTaskCompletion();
PF_STOP(VehicleIntelligenceProcessTask_Run);
#if !__PS3
}
#endif
}
void CVehicleIntelligence::Process_OnDeferredTaskCompletion()
{
//cache off the current CVehicleNodeList, to avoid querying the task tree each time
#if __DEV
ResetCachedNodeListHits();
ResetCachedFollowRouteHits();
#endif //__DEV
CacheNodeList();
CacheFollowRoute();
if (GetRecordingNumber() >= 0 && !CVehicleRecordingMgr::GetUseCarAI(GetRecordingNumber()))
{
//Clear the car we are behind.
SetCarWeAreBehind(NULL);
}
else
{
if(sConsiderParkedVehicleForLod(m_pVehicle))
{
m_pVehicle->m_nVehicleFlags.bLodShouldUseTimeslicing = true;
}
}
Process_TouchingRealMissionVehicle();
if (m_pVehicle->IsDummy())
{
//CVehicleNodeList * pNodeList = GetNodeList();
const CVehicleFollowRouteHelper* pFollowRoute = GetFollowRouteHelper();
Assert(m_pVehicle->GetCurrentPhysicsInst());
Assert(!m_pVehicle->GetCurrentPhysicsInst() || m_pVehicle->GetCurrentPhysicsInst()->IsInLevel());
const bool bTaskPreventingDummy = m_pVehicle->m_nVehicleFlags.bPreventBeingDummyThisFrame;
const bool bTaskPreventingSuperDummy = m_pVehicle->IsSuperDummy() && m_pVehicle->m_nVehicleFlags.bPreventBeingSuperDummyThisFrame;
const bool bFollowRouteEmpty = (pFollowRoute &&
m_pVehicle->GetCurrentPhysicsInst() &&
m_pVehicle->GetCurrentPhysicsInst()->IsInLevel() &&
CPhysics::GetLevel()->IsActive(m_pVehicle->GetCurrentPhysicsInst()->GetLevelIndex()) &&
pFollowRoute->GetNumPoints() < 2);
const bool bDummyConstraintsChanged = m_pVehicle->ProcessHaveDummyConstraintsChanged(ScalarV(V_TEN), true);
if (bTaskPreventingDummy || bFollowRouteEmpty || bDummyConstraintsChanged)
{
//only skip intersection test if follow route is empty - for task and constraints it's not the end of the world
//if we can't convert due to intersection but if we don't have a follow route we'll fall through the world
const bool bSkipIntersectionTest = bFollowRouteEmpty;
if(!m_pVehicle->InheritsFromTrailer() && !m_pVehicle->m_nVehicleFlags.bHasParentVehicle && !m_pVehicle->TryToMakeFromDummy(bSkipIntersectionTest))
{
if(bFollowRouteEmpty)
{
//couldn't switch back for some reason, if we don't have follow route then deactivate physics
m_pVehicle->DeActivatePhysics();
}
#if __BANK
if(bFollowRouteEmpty)
{
CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) (0x%p) Unable to convert to real (%s). Follow route empty disabling physics.\n",
AILogging::GetDynamicEntityNameSafe(m_pVehicle), AILogging::GetDynamicEntityIsCloneStringSafe(m_pVehicle), m_pVehicle, m_pVehicle->GetNonConversionReason());
}
else
{
char debugText[32];
if(bTaskPreventingDummy) sprintf(debugText,"Task preventing dummy");
if(bDummyConstraintsChanged) sprintf(debugText,"Dummy constraint changed");
CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) (0x%p) Unable to convert to real (%s). (%s)\n",AILogging::GetDynamicEntityNameSafe(m_pVehicle),
AILogging::GetDynamicEntityIsCloneStringSafe(m_pVehicle), m_pVehicle, m_pVehicle->GetNonConversionReason(), debugText);
}
#endif
}
}
else if (m_pVehicle->ContainsLocalPlayer())
{
//also try to convert from dummy if we have a local player.
//someone in QA hit an assert from the vehicle ai lod manager update
//where they detected a player-controlled vehicle in dummy lod after switching
//characters. I wasn't able to repro this myself but my hunch is now that these
//are batched, each vehicle only attempts conversion every second or so.
//now, if we ever detect a player-controlled vehicle that's in dummy mode,
//just convert it right away
const bool bSkipIntersectionTest = true;
m_pVehicle->TryToMakeFromDummy(bSkipIntersectionTest);
}
else if (bTaskPreventingSuperDummy && !m_pVehicle->InheritsFromTrailer())
{
m_pVehicle->TryToMakeIntoDummy(VDM_DUMMY);
}
}
else if (fwTimer::GetTimeInMilliseconds() != m_pVehicle->m_TimeOfCreation &&
!m_pVehicle->m_nVehicleFlags.bIsThisAParkedCar &&
!m_pVehicle->InheritsFromTrailer() &&
!m_pVehicle->m_nVehicleFlags.bHasParentVehicle &&
!(GetRecordingNumber() >= 0 && !CVehicleRecordingMgr::GetUseCarAI(GetRecordingNumber())))
{
Assert(!m_pVehicle->IsDummy());
//if we're real, and have got a valid route, but no collision loaded around us,
//convert now or we'll either fall or freeze, neither of which looks good
const eVehicleDummyMode currentMode = m_pVehicle->GetVehicleAiLod().GetDummyMode();
eVehicleDummyMode overriddenMode = m_pVehicle->ProcessOverriddenDummyMode(VDM_REAL);
if (overriddenMode != currentMode)
{
m_pVehicle->TryToMakeIntoDummy(overriddenMode);
}
}
// Create shocking events from properties of the vehicle for other entities in the game to react against.
GenerateEvents();
m_eventGroup.FlushExpiredEvents();
if (m_pVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE || m_pVehicle->GetVehicleType() == VEHICLE_TYPE_CAR || m_pVehicle->GetVehicleType() == VEHICLE_TYPE_QUADBIKE)
{
CVehicleNodeList * pNodeList = m_pCachedNodeList;
if(pNodeList)
{
static dev_s32 iRandomNodeOffsetMaxExclusive = 3;
s32 node = rage::Min(rage::Max(pNodeList->GetTargetNodeIndex()-1, 0)
, rage::Max(pNodeList->GetTargetNodeIndex()-iRandomNodeOffsetMaxExclusive, 0) + (m_pVehicle->GetRandomSeed() & iRandomNodeOffsetMaxExclusive));
if (!pNodeList->GetPathNodeAddr(node).IsEmpty() && ThePaths.IsRegionLoaded(pNodeList->GetPathNodeAddr(node)) &&
ThePaths.FindNodePointer(pNodeList->GetPathNodeAddr(node))->m_2.m_inTunnel )
{
m_pVehicle->m_nVehicleFlags.bIsInTunnel = true;
}
else
{
m_pVehicle->m_nVehicleFlags.bIsInTunnel = false;
}
}
//if we don't have a nodelist, don't reset the tunnel flag
}
#if DEBUG_DRAW
TUNE_GROUP_BOOL(VEH_INTELLIGENCE, ShowDummyParents, false);
TUNE_GROUP_BOOL(VEH_INTELLIGENCE, ShowDummyChildren, false);
TUNE_GROUP_BOOL(VEH_INTELLIGENCE, ShowCargoCars, false);
TUNE_GROUP_BOOL(VEH_INTELLIGENCE, ShowPhysicalAttachParent, false);
if (ShowDummyParents)
{
if (m_pVehicle->GetDummyAttachmentParent())
{
CVehicle::ms_debugDraw.AddArrow(m_pVehicle->GetVehiclePosition(), m_pVehicle->GetDummyAttachmentParent()->GetVehiclePosition(), 0.5f, Color_red);
}
}
if (ShowDummyChildren)
{
if (m_pVehicle->HasDummyAttachmentChildren())
{
for (int i = 0; i < m_pVehicle->GetMaxNumDummyAttachmentChildren(); i++)
{
if (m_pVehicle->GetDummyAttachmentChild(i))
{
CVehicle::ms_debugDraw.AddArrow(m_pVehicle->GetVehiclePosition(), m_pVehicle->GetDummyAttachmentChild(i)->GetVehiclePosition(), 0.5f, Color_orange);
}
}
}
}
if (ShowCargoCars)
{
if (m_pVehicle->InheritsFromTrailer())
{
CTrailer* pMeAsTrailer = (CTrailer*)m_pVehicle;
if (pMeAsTrailer->HasCargoVehicles())
{
for (int i = 0; i < MAX_CARGO_VEHICLES; i++)
{
if (pMeAsTrailer->GetCargoVehicle(i))
{
CVehicle::ms_debugDraw.AddArrow(pMeAsTrailer->GetVehiclePosition(), pMeAsTrailer->GetCargoVehicle(i)->GetVehiclePosition(), 0.5f, Color_green);
}
}
}
}
}
if (ShowPhysicalAttachParent)
{
if (m_pVehicle->GetAttachParent())
{
CVehicle::ms_debugDraw.AddArrow(m_pVehicle->GetVehiclePosition(), m_pVehicle->GetAttachParent()->GetTransform().GetPosition(), 0.5f, Color_blue);
}
}
#endif //DEBUG_DRAW
}
// NAME : GetNodeList
// PURPOSE : Obtain the node list to use, by traversing task tree
// NOTE : If this function is changed, please also change CacheNodeList accordingly
CVehicleNodeList* CVehicleIntelligence::GetNodeList() const
{
if(m_bHasCachedNodeList)
{
// If task has been removed, then we must try to cache a new nodelist
if(m_pCachedNodeListTask)
{
#if __DEV
CVehicleIntelligence * pVehInt = const_cast<CVehicleIntelligence*>(this);
pVehInt->m_iCacheNodeListHitsThisFrame++;
#endif
return m_pCachedNodeList;
}
}
CVehicleNodeList * pNodeList = NULL;
if (m_pVehicle && m_pVehicle->IsNetworkClone())
{
pNodeList = &static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetVehicleNodeList();
}
else
{
CTaskVehicleMissionBase * pActiveTask = GetActiveTask();
while(pActiveTask)
{
pNodeList = pActiveTask->GetNodeList();
if(pNodeList)
break;
pActiveTask = (CTaskVehicleMissionBase*)pActiveTask->GetSubTask();
}
}
return pNodeList;
}
// NAME : CacheNodeList
// PURPOSE : Caches a nodelist pointer for use outside of the task update
void CVehicleIntelligence::CacheNodeList(CTaskVehicleMissionBase* pOptionalForceTask/* = NULL*/)
{
Assert(!m_bHasCachedNodeList);
if (pOptionalForceTask)
{
m_pCachedNodeListTask = pOptionalForceTask;
m_pCachedNodeList = pOptionalForceTask->GetNodeList();
m_bHasCachedNodeList = true;
return;
}
m_pCachedNodeList = NULL;
m_pCachedNodeListTask = NULL;
if (m_pVehicle && m_pVehicle->IsNetworkClone())
{
const CTaskVehicleMissionBase* pCloneTask = static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetCloneAITask();
if (pCloneTask)
{
m_pCachedNodeList = const_cast<CTaskVehicleMissionBase*>(pCloneTask)->GetNodeList();
m_pCachedNodeListTask = const_cast<CTaskVehicleMissionBase*>(pCloneTask);
m_bHasCachedNodeList = true;
}
}
else
{
CTaskVehicleMissionBase * pActiveTask = GetActiveTask();
while(pActiveTask)
{
m_pCachedNodeList = pActiveTask->GetNodeList();
if(m_pCachedNodeList)
{
m_pCachedNodeListTask = pActiveTask;
m_bHasCachedNodeList = true;
break;
}
pActiveTask = (CTaskVehicleMissionBase*)pActiveTask->GetSubTask();
}
}
}
void CVehicleIntelligence::InvalidateCachedNodeList()
{
m_bHasCachedNodeList = false;
m_pCachedNodeList = NULL;
m_pCachedNodeListTask = NULL;
}
#if __ASSERT
void CVehicleIntelligence::VerifyCachedNodeList()
{
if (m_bHasCachedNodeList)
{
CTaskVehicleMissionBase *pActiveTask = m_pVehicle->IsNetworkClone() ? const_cast<CTaskVehicleMissionBase*>(static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetCloneAITask()) : GetActiveTask();
Assert(pActiveTask);
Assert(pActiveTask == m_pCachedNodeListTask);
if (pActiveTask)
{
const CVehicleNodeList *pNodeList = pActiveTask->GetNodeList();
Assert(pNodeList == m_pCachedNodeList);
}
}
}
#endif // __ASSERT
void CVehicleIntelligence::InvalidateCachedFollowRoute()
{
m_bHasCachedFollowRoute = false;
m_pCachedFollowRouteHelper = NULL;
m_pCachedFollowRouteHelperTask = NULL;
}
void CVehicleIntelligence::ProcessCollision(const CCollisionRecord* pColRecord)
{
//Get the vehicle we collided with.
CVehicle* pVehicleCollidedWith = (pColRecord && pColRecord->m_pRegdCollisionEntity && pColRecord->m_pRegdCollisionEntity->GetIsTypeVehicle()) ?
static_cast<CVehicle *>(pColRecord->m_pRegdCollisionEntity.Get()) : NULL;
if (pVehicleCollidedWith && m_pVehicle)
{
if (m_pVehicle->GetModelIndex() == MI_CAR_DUNE4 || m_pVehicle->GetModelIndex() == MI_CAR_DUNE5)
{
static const u16 MAX_TIME_IN_AIR_TO_KEEP_REAL = 3000;
if(pVehicleCollidedWith->GetVehicleAiLod().GetDummyMode() == VDM_SUPERDUMMY)
{
NetworkInterface::SetSuperDummyLaunchedIntoAir(pVehicleCollidedWith, MAX_TIME_IN_AIR_TO_KEEP_REAL);
}
NetworkInterface::ForceVeryHighUpdateLevelOnVehicle(pVehicleCollidedWith, MAX_TIME_IN_AIR_TO_KEEP_REAL);
}
if (m_pVehicle->GetModelIndex() == MI_CAR_PHANTOM2)
{
static const u16 MAX_TIME_IN_MAX_UPDATE_RATE = 3000;
NetworkInterface::ForceVeryHighUpdateLevelOnVehicle(pVehicleCollidedWith, MAX_TIME_IN_MAX_UPDATE_RATE);
}
}
//Check if we collided with the player's vehicle.
bool bCollidedWithPlayerVehicle = (pVehicleCollidedWith && pVehicleCollidedWith->ContainsLocalPlayer());
if(bCollidedWithPlayerVehicle)
{
//Check if the player's vehicle is moving, or trying to move.
static dev_float s_fMinSpeed = 0.5f;
bool bIsPlayerVehicleMoving = (pVehicleCollidedWith->GetVelocity().Mag2() > square(s_fMinSpeed));
static dev_float s_fMinThrottle = 0.1f;
bool bIsPlayerVehicleTryingToMove = (Abs(pVehicleCollidedWith->GetThrottle()) > s_fMinThrottle);
if(bIsPlayerVehicleMoving || bIsPlayerVehicleTryingToMove)
{
//Check if we are not colliding with the player vehicle.
if(m_uTimeWeStartedCollidingWithPlayerVehicle == 0)
{
//Set the time.
m_uTimeWeStartedCollidingWithPlayerVehicle = fwTimer::GetTimeInMilliseconds();
}
else
{
//Check if we have been colliding with the player for a while.
static float s_fMinTimeToConsiderContinuouslyColliding = 0.25f;
if(m_uTimeWeStartedCollidingWithPlayerVehicle + (u32)(s_fMinTimeToConsiderContinuouslyColliding * 1000.0f) < fwTimer::GetTimeInMilliseconds())
{
//Send the event.
for(int i = 0; i < m_pVehicle->GetSeatManager()->GetMaxSeats(); ++i)
{
CPed* pPed = m_pVehicle->GetSeatManager()->GetPedInSeat(i);
if(pPed)
{
CEventVehicleDamageWeapon event(m_pVehicle, pColRecord->m_pRegdCollisionEntity,
WEAPONTYPE_RAMMEDBYVEHICLE, 0.0f, pColRecord->m_MyCollisionPosLocal, pColRecord->m_MyCollisionNormal);
event.SetIsContinuouslyColliding(true, pVehicleCollidedWith->GetAiXYSpeed());
pPed->GetPedIntelligence()->AddEvent(event);
}
}
}
}
}
else
{
//Clear the time.
m_uTimeWeStartedCollidingWithPlayerVehicle = 0;
}
}
else
{
//Clear the time.
m_uTimeWeStartedCollidingWithPlayerVehicle = 0;
}
}
//NOTE : If this function is changed, please also change CacheFollowRouteHelper accordingly!!!!!
const CVehicleFollowRouteHelper* CVehicleIntelligence::GetFollowRouteHelper() const
{
const bool bHasGoodBackupRoute = m_BackupFollowRoute.GetNumPoints() >= 2;
if(m_bHasCachedFollowRoute)
{
//sometimes, we can cache a followroute with zero points. we don't want to prevent caching
//this route, as the early return here helps prevent us searching for a new one every time,
//but if it's bad and we have a good backup followroute (most common when following
//vehicle scenario chains in direct mode), we should prefer it here
const bool bPreferBackupRouteToCachedRoute = bHasGoodBackupRoute
&& (!m_pCachedFollowRouteHelper || !m_pCachedFollowRouteHelperTask || (m_pCachedFollowRouteHelper && m_pCachedFollowRouteHelper->GetNumPoints() < 2));
if (bPreferBackupRouteToCachedRoute)
{
return &m_BackupFollowRoute;
}
// If task has been removed, then we must try to cache a new followroute
else if(m_pCachedFollowRouteHelperTask)
{
#if __DEV
CVehicleIntelligence * pVehInt = const_cast<CVehicleIntelligence*>(this);
pVehInt->m_iCacheFollowRouteHitsThisFrame++;
#endif
return m_pCachedFollowRouteHelper;
}
}
const CVehicleFollowRouteHelper* pFollowRouteHelper = NULL;
if (m_pVehicle && m_pVehicle->IsNetworkClone())
{
pFollowRouteHelper = &static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetFollowRouteHelper();
}
else
{
CTaskVehicleMissionBase * pActiveTask = GetActiveTask();
while(pActiveTask)
{
if (pActiveTask->GetFollowRouteHelper() && (!pFollowRouteHelper || pActiveTask->GetFollowRouteHelper()->GetNumPoints() > 0))
{
pFollowRouteHelper = pActiveTask->GetFollowRouteHelper();
}
pActiveTask = (CTaskVehicleMissionBase*)pActiveTask->GetSubTask();
}
}
//if we didn't find anything, and it looks like we might have a good backup route, return it
if (!pFollowRouteHelper || (pFollowRouteHelper && pFollowRouteHelper->GetNumPoints() < 2 && bHasGoodBackupRoute))
{
pFollowRouteHelper = &m_BackupFollowRoute;
}
return pFollowRouteHelper;
}
// NAME : CacheFollowRoute
// PURPOSE : Caches a followroute pointer for use outside of the task update
void CVehicleIntelligence::CacheFollowRoute(CTaskVehicleMissionBase* pOptionalForceTask/* = NULL*/)
{
Assert(!m_bHasCachedFollowRoute);
if (pOptionalForceTask)
{
m_pCachedFollowRouteHelperTask = pOptionalForceTask;
m_pCachedFollowRouteHelper = pOptionalForceTask->GetFollowRouteHelper();
m_bHasCachedFollowRoute = true;
return;
}
m_pCachedFollowRouteHelper = NULL;
m_pCachedFollowRouteHelperTask = NULL;
m_bHasCachedFollowRoute = false;
if (m_pVehicle && m_pVehicle->IsNetworkClone())
{
const CTaskVehicleMissionBase* pCloneTask = static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetCloneAITask();
if (pCloneTask)
{
m_pCachedFollowRouteHelper = const_cast<CTaskVehicleMissionBase*>(pCloneTask)->GetFollowRouteHelper();
m_pCachedFollowRouteHelperTask = const_cast<CTaskVehicleMissionBase*>(pCloneTask);
m_bHasCachedFollowRoute = true;
}
}
else
{
CTaskVehicleMissionBase * pActiveTask = GetActiveTask();
while(pActiveTask)
{
if (pActiveTask->GetFollowRouteHelper() && (!m_pCachedFollowRouteHelper || pActiveTask->GetFollowRouteHelper()->GetNumPoints() > 0))
{
m_pCachedFollowRouteHelper = pActiveTask->GetFollowRouteHelper();
m_pCachedFollowRouteHelperTask = pActiveTask;
m_bHasCachedFollowRoute = true;
}
pActiveTask = (CTaskVehicleMissionBase*)pActiveTask->GetSubTask();
}
}
}
const CPathNodeRouteSearchHelper* CVehicleIntelligence::GetRouteSearchHelper() const
{
const CPathNodeRouteSearchHelper* pRouteSearchHelper = NULL;
if (m_pVehicle && m_pVehicle->IsNetworkClone())
{
const CTaskVehicleMissionBase* pCloneTask = static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetCloneAITask();
if (pCloneTask)
{
pRouteSearchHelper = const_cast<CTaskVehicleMissionBase*>(pCloneTask)->GetRouteSearchHelper();
}
}
else
{
CTaskVehicleMissionBase * pActiveTask = GetActiveTask();
while(pActiveTask)
{
pRouteSearchHelper = pActiveTask->GetRouteSearchHelper();
if(pRouteSearchHelper)
break;
pActiveTask = (CTaskVehicleMissionBase*)pActiveTask->GetSubTask();
}
}
return pRouteSearchHelper;
}
#if __BANK
void CVehicleIntelligence::RenderDebug()
{
char debugText[256];
if(!m_pVehicle || !AllowCarAiDebugDisplayForThisVehicle(m_pVehicle))
{
return;
}
//Visualise a load of stuff (will slowly want to move most/all of below into here)
CVehicleDebugVisualiser* pDebugVisualiser = CVehicleDebugVisualiser::GetInstance();
pDebugVisualiser->Visualise(m_pVehicle);
const Vector3 vVehiclePos = VEC3V_TO_VECTOR3(m_pVehicle->GetTransform().GetPosition());
if(CVehicleIntelligence::m_bDisplayCarAddresses)
{
sprintf(debugText, "0x%p", m_pVehicle);
grcDebugDraw::Text(vVehiclePos, Color_white, 0, 0, debugText);
#if 0 && __DEV
sprintf(debugText, "nodelist hits: %i", m_iCacheNodeListHitsThisFrame);
//sprintf(debugText, "followroute hits: %i", m_iCacheFollowRouteHitsThisFrame);
grcDebugDraw::Text(vVehiclePos, Color_green, 0, 16, debugText);
#endif
}
if (CVehicleIntelligence::m_bDisplayDirtyCarPilons && m_pVehicle->m_nVehicleFlags.bAvoidanceDirtyFlag)
{
// Draw our position.
Color32 col = Color_red;
grcDebugDraw::Line(vVehiclePos, Vector3(vVehiclePos.x, vVehiclePos.y,vVehiclePos.z + 4.0f), col);
}
int iNoOfTexts=2;
#if __BANK//def CAM_DEBUG
{
if( CVehicleIntelligence::m_bAICarHandlingDetails|| CVehicleIntelligence::m_bAICarHandlingCurve )
{
Vector3 WorldCoors = vVehiclePos + Vector3(0,0,1.0f);
const CAIHandlingInfo* pAIHandlingInfo = m_pVehicle->GetAIHandlingInfo();
char debugText[255];
sprintf(debugText, "Name: %s", pAIHandlingInfo->GetName().GetCStr());
grcDebugDraw::Text( WorldCoors, Color_blue, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText );
iNoOfTexts++;
sprintf(debugText, "MinBrakeDistance: %f", pAIHandlingInfo->GetMinBrakeDistance());
grcDebugDraw::Text( WorldCoors, Color_blue, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText );
iNoOfTexts++;
sprintf(debugText, "MaxBrakeDistance: %f", pAIHandlingInfo->GetMaxBrakeDistance());
grcDebugDraw::Text( WorldCoors, Color_blue, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText );
iNoOfTexts++;
sprintf(debugText, "MaxSpeedAtBrakeDistance: %f", pAIHandlingInfo->GetMaxSpeedAtBrakeDistance());
grcDebugDraw::Text( WorldCoors, Color_blue, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText );
iNoOfTexts++;
for( s32 i = 0; i < pAIHandlingInfo->GetCurvePointCount(); i++ )
{
sprintf(debugText, "Curve %d: Angle %.2f Speed %.2f", i, pAIHandlingInfo->GetCurvePoint(i)->GetAngle(), pAIHandlingInfo->GetCurvePoint(i)->GetSpeed());
grcDebugDraw::Text( WorldCoors, Color_blue, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText );
iNoOfTexts++;
}
sprintf(debugText, "AbsoluteMinSpeed: %f", pAIHandlingInfo->GetAbsoluteMinSpeed());
grcDebugDraw::Text( WorldCoors, Color_blue, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText );
iNoOfTexts++;
}
if( CVehicleIntelligence::m_bAICarHandlingCurve && CDebugScene::FocusEntities_IsInGroup(m_pVehicle) )
{
const CAIHandlingInfo* pAIHandlingInfo = m_pVehicle->GetAIHandlingInfo();
// Draw some axes
static Vector2 AXIS_ORIGIN(0.8f, 0.3f);
static Vector2 AXIS_END(0.95f, 0.0733f);
grcDebugDraw::Line(AXIS_ORIGIN, Vector2(AXIS_END.x, AXIS_ORIGIN.y), Color_red);
grcDebugDraw::Text(Vector2(AXIS_END.x-0.05f, AXIS_ORIGIN.y+0.05f), Color_red, "Speed (50.0f)");
grcDebugDraw::Line(AXIS_ORIGIN, Vector2(AXIS_ORIGIN.x, AXIS_END.y), Color_green);
grcDebugDraw::Text(Vector2(AXIS_ORIGIN.x-0.1f, AXIS_END.y), Color_green, "Angle (180.0f)");
Vector2 vLastPoint = AXIS_ORIGIN;
for( s32 i = 0; i < pAIHandlingInfo->GetCurvePointCount(); i++ )
{
Vector2 vNewPoint;
vNewPoint.x = AXIS_ORIGIN.x + (AXIS_END.x - AXIS_ORIGIN.x) * (pAIHandlingInfo->GetCurvePoint(i)->GetAngle()/180.0f);
vNewPoint.y = AXIS_ORIGIN.y + (AXIS_END.y - AXIS_ORIGIN.y) * (pAIHandlingInfo->GetCurvePoint(i)->GetSpeed()/50.0f);
if(i>0)
{
grcDebugDraw::Line(vLastPoint, vNewPoint, Color_blue);
}
grcDebugDraw::Circle(vNewPoint, 0.005f, Color_purple4);
vLastPoint = vNewPoint;
}
}
}
#endif // #if __BANK
if( CVehicleIntelligence::m_bDisplayVehicleRecordingInfo )
{
const Vector3 vVehiclePosition = vVehiclePos + Vector3(0.f,0.f,3.0f);
int iNoOfTexts = 0;
int recording = CVehicleRecordingMgr::GetPlaybackSlot(m_pVehicle);
if( recording != -1 )
{
CVehicleStateEachFrame *pVehState =(CVehicleStateEachFrame *) &((CVehicleRecordingMgr::GetPlaybackBuffer(recording))[CVehicleRecordingMgr::GetPlaybackIndex(recording)]);
sprintf(debugText, "Running vehicle recording R(%s), T(%i)", CVehicleRecordingMgr::GetRecordingName(recording), pVehState->TimeInRecording);
grcDebugDraw::Text(vVehiclePosition, Color_green, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "Playback speed: %f", CVehicleRecordingMgr::GetPlaybackSpeed(recording));
grcDebugDraw::Text(vVehiclePosition, Color_green, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "Using AI: %s", CVehicleRecordingMgr::GetUseCarAI(recording) ? "yes" : "no");
grcDebugDraw::Text(vVehiclePosition, Color_green, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
Vector3 vSpeed( pVehState->GetSpeedX(), pVehState->GetSpeedY(), pVehState->GetSpeedZ() );
sprintf(debugText, "Desired speed (inc playback speed): %.2f (%.2f)", vSpeed.Mag(), vSpeed.Mag()*CVehicleRecordingMgr::GetPlaybackSpeed(recording) );
grcDebugDraw::Text(vVehiclePosition, Color_green, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "Actual speed: %.2f", m_pVehicle->GetVelocity().Mag());
grcDebugDraw::Text(vVehiclePosition, Color_green, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
bool bOn = (CVehicleRecordingMgr::GetVehicleRecordingFlags(recording)&CVehicleRecordingMgr::VRF_ConvertToAIOnImpact_PlayerVehcile)?true:false;
sprintf(debugText, "Flags: VRF_ConvertToAIOnImpact_PlayerVehcile(%s)", bOn?"On":"Off");
grcDebugDraw::Text(vVehiclePosition, bOn?Color_green:Color_orange, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
bOn = (CVehicleRecordingMgr::GetVehicleRecordingFlags(recording)&CVehicleRecordingMgr::VRF_ConvertToAIOnImpact_AnyVehicle)?true:false;
sprintf(debugText, "Flags: VRF_ConvertToAIOnImpact_AnyVehicle(%s)", bOn?"On":"Off");
grcDebugDraw::Text(vVehiclePosition, bOn?Color_green:Color_orange, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
bOn = (CVehicleRecordingMgr::GetVehicleRecordingFlags(recording)&CVehicleRecordingMgr::VRF_StartEngineInstantly)?true:false;
sprintf(debugText, "Flags: VRF_StartEngineInstantly(%s)", bOn?"On":"Off");
grcDebugDraw::Text(vVehiclePosition, bOn?Color_green:Color_orange, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
bOn = (CVehicleRecordingMgr::GetVehicleRecordingFlags(recording)&CVehicleRecordingMgr::VRF_StartEngineWithStartup)?true:false;
sprintf(debugText, "Flags: VRF_StartEngineWithStartup(%s)", bOn?"On":"Off");
grcDebugDraw::Text(vVehiclePosition, bOn?Color_green:Color_orange, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
bOn = (CVehicleRecordingMgr::GetVehicleRecordingFlags(recording)&CVehicleRecordingMgr::VRF_ContinueRecordingIfCarDestroyed)?true:false;
sprintf(debugText, "Flags: VRF_ContinueRecordingIfCarDestroyed(%s)", bOn?"On":"Off");
grcDebugDraw::Text(vVehiclePosition, bOn?Color_green:Color_orange, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
}
else
{
sprintf(debugText, "Not running vehicle recording");
grcDebugDraw::Text(vVehiclePosition, Color_red, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
}
}
if( CVehiclePopulation::ms_bDisplayAbandonedRemovalDebug )
{
// Gather the relevant information to display (see CVehiclePopulation::PossiblyRemoveVehicle )
bool bIsOnScreen = m_pVehicle->GetIsVisibleInSomeViewportThisFrame();
bool bHasPedsInIt = m_pVehicle->HasPedsInIt();
bool bIsAParkedCar = m_pVehicle->m_nVehicleFlags.bIsThisAParkedCar;
// Get a handle to the vehicle seat manager
const CSeatManager* pSeatManager = m_pVehicle->GetSeatManager();
bool bHasEverHadPedInAnySeat = pSeatManager && pSeatManager->HasEverHadPedInAnySeat();
// Check for the former peds to see if any ped is still alive
bool bPedFound = false;
CPed* pLastPedInSeat = NULL;
if( bHasEverHadPedInAnySeat && !bHasPedsInIt )
{
for(int iSeat = 0; iSeat < pSeatManager->GetMaxSeats() && !bPedFound; iSeat++)
{
pLastPedInSeat = pSeatManager->GetLastPedInSeat(iSeat);
if( pLastPedInSeat != NULL && !pLastPedInSeat->IsDead() )
{
bPedFound = true;
}
}
}
bool bShouldUseCullRangeOffScreenPedsRemoved = !bIsOnScreen && !bIsAParkedCar && !bHasPedsInIt && bHasEverHadPedInAnySeat && !bPedFound;
const Vector3 vVehiclePosition = vVehiclePos + Vector3(0.f,0.f,3.0f);
int iNoOfTexts = 0;
sprintf(debugText, "OnScreen: %s", bIsOnScreen ? "true" : "false");
grcDebugDraw::Text(vVehiclePosition, bIsOnScreen ? Color_green : Color_red, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "Parked: %s", bIsAParkedCar ? "true" : "false");
grcDebugDraw::Text(vVehiclePosition, bIsAParkedCar ? Color_green : Color_red, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "HasPedsInIt: %s", bHasPedsInIt ? "true" : "false");
grcDebugDraw::Text(vVehiclePosition, bHasPedsInIt ? Color_green : Color_red, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "HasEverHadPedsInIt: %s", bHasEverHadPedInAnySeat ? "true" : "false");
grcDebugDraw::Text(vVehiclePosition, bHasEverHadPedInAnySeat ? Color_red : Color_green, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
if( bPedFound )
{
sprintf(debugText, "PedFound: true [0x%p]", pLastPedInSeat);
grcDebugDraw::Line(vVehiclePosition, VEC3V_TO_VECTOR3(pLastPedInSeat->GetTransform().GetPosition()), Color_green, Color_green);
}
else
{
sprintf(debugText, "PedFound: false");
}
grcDebugDraw::Text(vVehiclePosition, bPedFound ? Color_green : Color_red, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
sprintf(debugText, "Special cull range applies[%.1f]: %s", CVehiclePopulation::GetCullRangeOffScreenPedsRemoved(), bShouldUseCullRangeOffScreenPedsRemoved ? "true" : "false");
grcDebugDraw::Text(vVehiclePosition, bShouldUseCullRangeOffScreenPedsRemoved ? Color_green : Color_red, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
}
if (CVehicleIntelligence::m_bDisplayInterestingDrivers)
{
const Vector3 vVehiclePosition = vVehiclePos + Vector3(0.f,0.f,3.0f);
char sInterestingDriverType[64];
const CPed* pDriver = m_pVehicle->GetDriver();
bool bDrawAnything = false;
if (m_pVehicle->m_nVehicleFlags.bMadDriver)
{
sprintf(sInterestingDriverType, "Aggressive Driver");
bDrawAnything = true;
}
else if (pDriver && pDriver->GetPedIntelligence()->GetDriverAbilityOverride() < SMALL_FLOAT
&& pDriver->GetPedIntelligence()->GetDriverAggressivenessOverride() < SMALL_FLOAT
&& pDriver->GetPedIntelligence()->GetDriverAbilityOverride() >= 0.0f
&& pDriver->GetPedIntelligence()->GetDriverAggressivenessOverride() >= 0.0f)
{
sprintf(sInterestingDriverType, "Sunday Driver");
bDrawAnything = true;
}
else if (pDriver && pDriver->GetPedIntelligence()->GetDriverAbilityOverride() > 0.99f
&& pDriver->GetPedIntelligence()->GetDriverAggressivenessOverride() > 0.99f )
{
sprintf(sInterestingDriverType, "Pro Driver");
bDrawAnything = true;
}
if (bDrawAnything)
{
sprintf(debugText, "%s", sInterestingDriverType);
grcDebugDraw::Text( vVehiclePosition, Color_purple, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
}
}
if(CVehicleIntelligence::m_bDisplayDebugInfoOkToDropOffPassengers)
{
Vector3 Crs = vVehiclePos;
Crs.z += 3.0f;
if(m_pVehicle->GetIntelligence()->OkToDropOffPassengers())
{
grcDebugDraw::Text(Crs, CRGBA(255, 255, 255, 255), "OkToDropOffPassengers");
}
else
{
grcDebugDraw::Text(Crs, CRGBA(255, 0, 0, 255), "NOTOkToDropOffPassengers");
}
}
if(CVehicleIntelligence::m_bDisplayDebugJoinWithRoadSystem)
{
Vector3 Crs = vVehiclePos;
Crs.z += 2.5f;
u32 timeAgo = fwTimer::GetTimeInMilliseconds() - m_pVehicle->GetIntelligence()->lastTimeJoinedWithRoadSystem;
u32 timeAgo2 = fwTimer::GetTimeInMilliseconds() - m_pVehicle->GetIntelligence()->lastTimeActuallyJoinedWithRoadSystem;
if(timeAgo < 10000)
{
if(timeAgo == timeAgo2)
{
sprintf(debugText, "Actually calculated new nodes %d", timeAgo);
grcDebugDraw::Text(Crs, CRGBA(255, 0, 255, 255), debugText);
}
else
{
sprintf(debugText, "Requested new nodes %d", timeAgo);
grcDebugDraw::Text(Crs, CRGBA(128, 128, 128, 255), debugText);
}
}
}
if(CVehicleIntelligence::m_bDisplayAvoidanceCache)
{
//Calculate the text position.
Vector3 vTextPosition = vVehiclePos;
vTextPosition.z += 3.0f;
//Count the lines.
int iNoOfTexts = 0;
//Display the target.
formatf(debugText, "Target: 0x%p", m_AvoidanceCache.m_pTarget.Get());
grcDebugDraw::Text(vTextPosition, Color_blue, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
//Display the desired offset.
formatf(debugText, "Desired Offset: %.2f", m_AvoidanceCache.m_fDesiredOffset);
grcDebugDraw::Text(vTextPosition, Color_blue, 0, iNoOfTexts++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
}
#if __BANK
if (CVehicleIntelligence::m_bDisplayRouteInterceptionTests)
{
const u32 uNowTS = fwTimer::GetTimeInMilliseconds();
const u32 uNumInterceptionTests = m_aDEBUG_RouteInterceptionTests.GetCount();
if (uNumInterceptionTests > 0)
{
const SDEBUG_RouteInterceptionTest& rLastTest = m_aDEBUG_RouteInterceptionTests[uNumInterceptionTests - 1];
formatf(debugText, "LRIT TS: %.2f", (uNowTS - rLastTest.uTS) / 1000.0f);
Vector3 vTextPosition = VEC3V_TO_VECTOR3(m_pVehicle->GetTransform().GetPosition());
vTextPosition.z += 3.0f;
grcDebugDraw::Text(vTextPosition, Color_blue, 0, grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
}
for (u32 uInterceptionTestIdx = 0; uInterceptionTestIdx < uNumInterceptionTests; ++uInterceptionTestIdx)
{
const SDEBUG_RouteInterceptionTest& rTest = m_aDEBUG_RouteInterceptionTests[uInterceptionTestIdx];
const u32 uNumChecks = rTest.aChecks.GetCount();
const bool bShowTest = !m_bHideFailedRouteInterceptionTests || ((uNumChecks > 0) && (rTest.aChecks[uNumChecks - 1].bInterception));
if (bShowTest && (rTest.uTS != 0) && (rTest.uTS + uDEBUG_MAX_TIME_TO_SHOW_ROUTE_INTERCEPTION_TEST_MS > uNowTS))
{
Color32 colVehicleInfoColor = Color_blue;
Color32 colTargetInfoColor = Color_yellow;
const int iAlpha = (rTest.uTS + uDEBUG_MAX_TIME_TO_SHOW_ROUTE_INTERCEPTION_TEST_MS - uNowTS) * 255 / uDEBUG_MAX_TIME_TO_SHOW_ROUTE_INTERCEPTION_TEST_MS;
colVehicleInfoColor.SetAlpha(iAlpha);
colTargetInfoColor.SetAlpha(iAlpha);
Vec3V vPrevVehicleDrawPos(V_ZERO);
Vec3V vPrevEntityDrawPos(V_ZERO);
for (u32 uCheckIdx = 0; uCheckIdx < uNumChecks; ++uCheckIdx)
{
const SDEBUG_RouteInterceptionTest::SCheck& rCheck = rTest.aChecks[uCheckIdx];
ScalarV scHighestPosZ = Max(rCheck.vVehiclePos.GetZ(), rCheck.vEntityPos.GetZ());
Vec3V vVehicleDrawPos = rCheck.vVehiclePos;
vVehicleDrawPos.SetZ(scHighestPosZ);
static const ScalarV scVELOCITY_DRAW_SCALE(0.1f);
static const float fVELOCITY_ARROW_HEAD_SIZE = 0.5f;
grcDebugDraw::Sphere(vVehicleDrawPos, 1.0f, colVehicleInfoColor, false);
grcDebugDraw::Arrow(vVehicleDrawPos, vVehicleDrawPos + Vec3V(rCheck.vVehicleVelXY, ScalarV(V_ZERO)) * scVELOCITY_DRAW_SCALE, fVELOCITY_ARROW_HEAD_SIZE, colVehicleInfoColor);
Vector3 vTextPosition = VEC3V_TO_VECTOR3(vVehicleDrawPos);
vTextPosition.z += 1.0f;
formatf(debugText, "ST: %.2f", rCheck.fTime);
grcDebugDraw::Text(vTextPosition, colVehicleInfoColor, 0, grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
Vec3V vEntityDrawPos = rCheck.vEntityPos;
vEntityDrawPos.SetZ(scHighestPosZ);
grcDebugDraw::Sphere(vEntityDrawPos, 1.0f, colTargetInfoColor, false);
grcDebugDraw::Arrow(vEntityDrawPos, vEntityDrawPos + Vec3V(rCheck.vEntityVelXY, ScalarV(V_ZERO)) * scVELOCITY_DRAW_SCALE, fVELOCITY_ARROW_HEAD_SIZE, colTargetInfoColor);
vTextPosition = VEC3V_TO_VECTOR3(vEntityDrawPos);
vTextPosition.z += 1.0f;
formatf(debugText, "ST: %.2f", rCheck.fTime);
grcDebugDraw::Text(vTextPosition, colTargetInfoColor, 0, grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
Color32 colCheckColor = rCheck.bInterception ? Color_red : Color_green;
colCheckColor.SetAlpha(iAlpha);
grcDebugDraw::Line(vVehicleDrawPos, vEntityDrawPos, colCheckColor, colCheckColor);
vTextPosition = VEC3V_TO_VECTOR3(vVehicleDrawPos + vEntityDrawPos) * 0.5f;
vTextPosition.z += 1.0f;
if (uCheckIdx == uNumChecks - 1)
{
formatf(debugText, "WD: %.2f, LDX: %.2f, LDY: %.2f, RouteAv: %d, RoutePass: %d", rCheck.scDistanceXY.Getf(), rCheck.scDistanceLocalX.Getf(), rCheck.scDistanceLocalY.Getf(), rTest.bRouteAvailable, rTest.bRoutePassByEntityPos);
}
else
{
formatf(debugText, "Dt: %.2f, LDX: %.2f, LDY: %.2f", rCheck.scDistanceXY.Getf(), rCheck.scDistanceLocalX.Getf(), rCheck.scDistanceLocalY.Getf());
}
grcDebugDraw::Text(vTextPosition, colCheckColor, 0, grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
if (uCheckIdx > 0)
{
grcDebugDraw::Line(vPrevVehicleDrawPos, vVehicleDrawPos, colVehicleInfoColor, colVehicleInfoColor);
grcDebugDraw::Line(vPrevEntityDrawPos, vEntityDrawPos, colTargetInfoColor, colTargetInfoColor);
}
vPrevVehicleDrawPos = vVehicleDrawPos;
vPrevEntityDrawPos = vEntityDrawPos;
}
}
}
}
#endif // __BANK
}
#endif // __BANK
#if __BANK
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetMissionName
// PURPOSE :
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
const char *CVehicleIntelligence::GetMissionName(s32 Mission)
{
Assert(Mission >= 0 && Mission < MISSION_LAST);
return (MissionStrings[Mission]);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetDrivingFlagName
// PURPOSE :
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
const char * CVehicleIntelligence::GetDrivingFlagName( s32 flag )
{
if( flag == DF_StopForCars ) return "DF_StopForCars";
if( flag == DF_StopForPeds ) return "DF_StopForPeds";
if( flag == DF_SwerveAroundAllCars ) return "DF_SwerveAroundAllCars";
if( flag == DF_SteerAroundStationaryCars ) return "DF_SteerAroundStationaryCars";
if( flag == DF_SteerAroundPeds ) return "DF_SteerAroundPeds";
if( flag == DF_SteerAroundObjects ) return "DF_SteerAroundObjects";
if( flag == DF_DontSteerAroundPlayerPed ) return "DF_DontSteerAroundPlayerPed";
if( flag == DF_StopAtLights ) return "DF_StopAtLights";
if( flag == DF_GoOffRoadWhenAvoiding) return "DF_GoOffRoadWhenAvoiding";
if( flag == DF_DriveIntoOncomingTraffic ) return "DF_DriveIntoOncomingTraffic";
if( flag == DF_DriveInReverse ) return "DF_DriveInReverse";
if( flag == DF_UseWanderFallbackInsteadOfStraightLine) return "DF_UseWanderFallbackInsteadOfStraightLine";
if( flag == DF_AvoidRestrictedAreas ) return "DF_AvoidRestrictedAreas";
if( flag == DF_PreventBackgroundPathfinding ) return "DF_PreventBackgroundPathfinding";
if( flag == DF_AdjustCruiseSpeedBasedOnRoadSpeed ) return "DF_AdjustCruiseSpeedBasedOnRoadSpeed";
if( flag == DF_PreventJoinInRoadDirectionWhenMoving ) return "DF_PreventJoinInRoadDirectionWhenMoving";
if( flag == DF_DontAvoidTarget ) return "DF_DontAvoidTarget";
if( flag == DF_TargetPositionOverridesEntity ) return "DF_TargetPositionOverridesEntity";
if( flag == DF_UseShortCutLinks ) return "DF_UseShortCutLinks";
if( flag == DF_ChangeLanesAroundObstructions ) return "DF_ChangeLanesAroundObstructions";
if( flag == DF_AvoidTargetCoors ) return "DF_AvoidTargetCoors";
if( flag == DF_UseSwitchedOffNodes ) return "DF_UseSwitchedOffNodes";
if( flag == DF_PreferNavmeshRoute ) return "DF_PreferNavmeshRoute";
if( flag == DF_PlaneTaxiMode) return "DF_PlaneTaxiMode";
if( flag == DF_ForceStraightLine) return "DF_ForceStraightLine";
if( flag == DF_UseStringPullingAtJunctions) return "DF_UseStringPullingAtJunctions";
if( flag == DF_AvoidAdverseConditions) return "DF_AvoidAdverseConditions";
if( flag == DF_AvoidTurns) return "DF_AvoidTurns";
if( flag == DF_ExtendRouteWithWanderResults) return "DF_ExtendRouteWithWanderResults";
if( flag == DF_AvoidHighways) return "DF_AvoidHighways";
if( flag == DF_ForceJoinInRoadDirection) return "DF_ForceJoinInRoadDirection";
if( flag == DF_DontTerminateTaskWhenAchieved ) return "DF_DontTerminateTaskWhenAchieved";
return "Unknown driving flag!";
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetTempActName
// PURPOSE :
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
const char *CVehicleIntelligence::GetTempActName(s32 TempAct)
{
Assert(TempAct >= 0 && TempAct < TEMPACT_LAST);
return (TempActStrings[TempAct]);
}
const char *CVehicleIntelligence::GetJunctionCommandName(s32 JunctionCommand)
{
Assert(JunctionCommand >= 0 && JunctionCommand < JUNCTION_COMMAND_LAST);
return (JunctionCommandStrings[JunctionCommand]);
}
const char *CVehicleIntelligence::GetJunctionFilterName(s32 JunctionFilter)
{
Assert(JunctionFilter >= 0 && JunctionFilter < JUNCTION_FILTER_LAST);
return (JunctionFilterStrings[JunctionFilter]);
}
const char *CVehicleIntelligence::GetJunctionCommandShortName(s32 JunctionCommand)
{
Assert(JunctionCommand >= 0 && JunctionCommand < JUNCTION_COMMAND_LAST);
return (JunctionCommandStringsShort[JunctionCommand]);
}
const char *CVehicleIntelligence::GetJunctionFilterShortName(s32 JunctionFilter)
{
Assert(JunctionFilter >= 0 && JunctionFilter < JUNCTION_FILTER_LAST);
return (JunctionFilterStringsShort[JunctionFilter]);
}
#endif
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : OkToDropOffPassengers
// PURPOSE : Returns true if the car is in a good place to drop off passengers.
// No junctions, no one way, no higher speed nodes.
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
bool CVehicleIntelligence::OkToDropOffPassengers()
{
CVehicleNodeList * pNodeList = GetNodeList();
Assertf(pNodeList, "CVehicleIntelligence::OkToDropOffPassengers - vehicle has no node list");
if(!pNodeList)
return true;
s32 iOldNode = pNodeList->GetTargetNodeIndex() - 1;
s32 iFutureNode = Clamp(iOldNode + 5, 0, CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED);
for (s32 node = iOldNode; node < iFutureNode; node++)
{
if (pNodeList->GetPathNodeAddr(node).IsEmpty() || !ThePaths.IsRegionLoaded(pNodeList->GetPathNodeAddr(node)))
{
return false;
}
const CPathNode *pNode = ThePaths.FindNodePointer(pNodeList->GetPathNodeAddr(node));
// Only on straight bit of road (no junctions)
if (pNode->NumLinks() != 2)
{
return false;
}
// No special nodes
if (pNode->HasSpecialFunction())
{
return false;
}
// No fast nodes (probably motorway or somesuch)
if (pNode->m_2.m_speed >= 2)
{
return false;
}
CPathNodeLink * pLink = ThePaths.FindLinkPointerSafe(pNodeList->GetPathNodeAddr(node).GetRegion(),pNodeList->GetPathLinkIndex(node));
// Only for roads with two way traffic (off-ramps and motorways are single direction)
if (!pLink || pLink->m_1.m_LanesFromOtherNode == 0)
{
return false;
}
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IsOnHighway
// PURPOSE : Returns true if the car is on a highway or a road where cars drive fast
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
bool CVehicleIntelligence::IsOnHighway() const
{
CVehicleNodeList * pNodeList = GetNodeList();
if(!pNodeList)
return false;
s32 iFutureNode = pNodeList->GetTargetNodeIndex() + 1;
s32 iOldNode = pNodeList->GetTargetNodeIndex() - 1;
s32 iIterStart = rage::Max(iOldNode, 0);
s32 iIterEnd = rage::Min(iFutureNode, CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-1);
for (s32 node = iIterStart; node <= iIterEnd; node++)
{
if (pNodeList->GetPathNodeAddr(node).IsEmpty() || !ThePaths.IsRegionLoaded(pNodeList->GetPathNodeAddr(node)))
{
continue;
}
const CPathNode *pNode = ThePaths.FindNodePointer(pNodeList->GetPathNodeAddr(node));
if (pNode->IsHighway())
{
return true;
}
if (pNode->m_2.m_speed >= 2)
{
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IsOnSingleTrackRoad
// PURPOSE : Returns true if the car is on a single track road--ie both directions of traffic travel down the same lane
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
bool CVehicleIntelligence::IsOnSingleTrackRoad() const
{
CVehicleNodeList * pNodeList = GetNodeList();
if(!pNodeList)
return false;
s32 iFutureNode = pNodeList->GetTargetNodeIndex() + 1;
s32 iOldNode = pNodeList->GetTargetNodeIndex() - 1;
s32 iIterStart = rage::Max(iOldNode, 0);
s32 iIterEnd = rage::Min(iFutureNode, CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-1);
for (s32 node = iIterStart; node < iIterEnd; node++)
{
s32 nextNode = node+1;
if (pNodeList->GetPathNodeAddr(node).IsEmpty() || !ThePaths.IsRegionLoaded(pNodeList->GetPathNodeAddr(node)))
{
continue;
}
s16 iLink = -1;
const bool bLinkFound = ThePaths.FindNodesLinkIndexBetween2Nodes(pNodeList->GetPathNodeAddr(node), pNodeList->GetPathNodeAddr(nextNode), iLink);
if (bLinkFound)
{
const CPathNode *pNode = ThePaths.FindNodePointer(pNodeList->GetPathNodeAddr(node));
const CPathNodeLink *pLink = &ThePaths.GetNodesLink(pNode, iLink);
if (pLink->IsSingleTrack())
{
return true;
}
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IsOnSmallRoad
// PURPOSE : Returns true if the car is on a single track road--ie both directions of traffic travel down the same lane
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
bool CVehicleIntelligence::IsOnSmallRoad() const
{
CVehicleNodeList * pNodeList = GetNodeList();
if(!pNodeList)
return false;
s32 iFutureNode = pNodeList->GetTargetNodeIndex() + 1;
s32 iOldNode = pNodeList->GetTargetNodeIndex() - 1;
s32 iIterStart = rage::Max(iOldNode, 0);
s32 iIterEnd = rage::Min(iFutureNode, CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-1);
for (s32 node = iIterStart; node < iIterEnd; node++)
{
s32 nextNode = node+1;
if (pNodeList->GetPathNodeAddr(node).IsEmpty() || !ThePaths.IsRegionLoaded(pNodeList->GetPathNodeAddr(node)))
{
continue;
}
s16 iLink = -1;
const bool bLinkFound = ThePaths.FindNodesLinkIndexBetween2Nodes(pNodeList->GetPathNodeAddr(node), pNodeList->GetPathNodeAddr(nextNode), iLink);
if (bLinkFound)
{
const CPathNode *pNode = ThePaths.FindNodePointer(pNodeList->GetPathNodeAddr(node));
const CPathNodeLink *pLink = &ThePaths.GetNodesLink(pNode, iLink);
if (pLink->m_1.m_NarrowRoad || pLink->IsSingleTrack())
{
return true;
}
if (pLink->m_1.m_LanesToOtherNode <= 1 && pLink->m_1.m_LanesFromOtherNode <= 1)
{
return true;
}
}
}
return false;
}
bool CVehicleIntelligence::CheckRouteInterception(Vec3V_In vEntityPos, Vec3V_In vEntityVelocity, float fMaxSideDistance, float fMaxForwardDistance, float fMinTime, float fMaxTime) const
{
#if __BANK
SDEBUG_RouteInterceptionTest test;
#endif // __BANK
// Interception check output
bool bInterception = false;
ScalarV scInterceptionT(V_ZERO);
Vec3V vInterceptionVehicleWorldPos(V_ZERO);
Vec3V vInterceptionEntityWorldPos(V_ZERO);
ScalarV scInterceptionDistanceLocalX(V_ZERO);
ScalarV scInterceptionDistanceLocalY(V_ZERO);
// Grab vehicle / entity info.
const Vec2V vVehicleForwardXY = m_pVehicle->GetTransform().GetForward().GetXY();
const Vec3V vVehicleVelocity = VECTOR3_TO_VEC3V(m_pVehicle->GetVelocity());
const Vec2V vVehicleVelocityXY = vVehicleVelocity.GetXY();
const ScalarV scVehicleSpeedXY = Mag(vVehicleVelocityXY);
const Vec2V vEntityVelocityXY = vEntityVelocity.GetXY();
// Generate the squared distance threshold / time threshold.
const ScalarV scMaxSideDistance = ScalarVFromF32(fMaxSideDistance);
const ScalarV scMaxForwardDistance = ScalarVFromF32(fMaxForwardDistance);
const ScalarV scMinTime(fMinTime);
const ScalarV scMaxTime(fMaxTime);
#if __BANK
test.AddCheck(0.0f, false, vEntityPos, vEntityVelocityXY, m_pVehicle->GetTransform().GetPosition(), vVehicleVelocityXY, MagXY(vEntityPos - m_pVehicle->GetTransform().GetPosition()), ScalarV(V_NEGONE), ScalarV(V_NEGONE));
#endif // __BANK
// Check route
CVehicleNodeList* pNodeList = GetNodeList();
if (pNodeList)
{
#if __BANK
test.bRouteAvailable = true;
#endif // __BANK
//Grab the current node.
s32 iCurNode = pNodeList->GetTargetNodeIndex();
Assertf(iCurNode >= 0 && iCurNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED, "Current road node is invalid.");
//Iterate over the road nodes checking intersection to the route in each segment.
ScalarV scSimulatedTime = ScalarV(V_ZERO);
Vec3V vVehiclePredictedPos = m_pVehicle->GetTransform().GetPosition();
Vec3V vEntityPredictedPos = vEntityPos;
static CNodeAddress iNodes[CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED];
static const CPathNode * pStoredNodes[CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED];
for(s32 n=0; n<CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED; n++)
{
iNodes[n] = pNodeList->GetPathNodeAddr(n);
pStoredNodes[n] = ThePaths.FindNodePointerSafe(iNodes[n]);
}
for (s32 iNode = iCurNode; !bInterception && IsLessThanOrEqualAll(scSimulatedTime, scMaxTime) && (iNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED); ++iNode)
{
// Grab the coordinates of the next node
bool bNodeOk = false;
Vector3 vNodePos;
if (iNode < 1)
{
if (pStoredNodes[iNode])
{
pStoredNodes[iNode]->GetCoors(vNodePos);
bNodeOk = true;
}
}
else if (pStoredNodes[iNode] && pStoredNodes[iNode-1])
{
if(iNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED - 1)
{
FindTargetCoorsWithNode(m_pVehicle, iNodes[iNode - 1], iNodes[iNode], iNodes[iNode + 1],
pNodeList->GetPathLinkIndex(iNode - 1), pNodeList->GetPathLinkIndex(iNode),
pNodeList->GetPathLaneIndex(iNode), pNodeList->GetPathLaneIndex(iNode + 1), vNodePos);
}
else
{
FindTargetCoorsWithNode(m_pVehicle, iNodes[iNode - 1], iNodes[iNode], EmptyNodeAddress,
pNodeList->GetPathLinkIndex(iNode - 1), 0,
pNodeList->GetPathLaneIndex(iNode), 0, vNodePos);
}
bNodeOk = true;
}
if (!bNodeOk)
{
break;
}
// Calculate interception point T (closest point between vehicle and entity paths)
ScalarV scSegmentTime(V_ZERO);
Vec2V vSegmentVehicleVelocityXY(V_ZERO);
// If the segment has not zero length...
const Vec2V vSegmentDeltaXY = (VECTOR3_TO_VEC3V(vNodePos) - vVehiclePredictedPos).GetXY();
const ScalarV scSegmentLengthXYSquared = MagSquared(vSegmentDeltaXY);
if (IsGreaterThanAll(scSegmentLengthXYSquared, ScalarV(V_ZERO)))
{
// ...we calculate the max segment time / vehicle velocity dir
const ScalarV scSegmentLengthXY = Sqrt(scSegmentLengthXYSquared);
const ScalarV scMaxSegmentTime = Max(ScalarV(V_ZERO), scMaxTime - scSimulatedTime);
scSegmentTime = Min(scMaxSegmentTime, IsGreaterThanAll(scVehicleSpeedXY, ScalarV(V_ZERO)) ? scSegmentLengthXY / scVehicleSpeedXY : ScalarV(V_FLT_LARGE_8));
const Vec2V vSegmentDirXY = vSegmentDeltaXY / scSegmentLengthXY;
vSegmentVehicleVelocityXY = vSegmentDirXY * scVehicleSpeedXY;
}
bInterception = CheckEntityInterceptionXY(vVehiclePredictedPos, vVehicleForwardXY, vSegmentVehicleVelocityXY, vEntityPredictedPos, vEntityVelocityXY, scMaxSideDistance, scMaxForwardDistance, scSegmentTime,
&scInterceptionT, &vInterceptionVehicleWorldPos, &vInterceptionEntityWorldPos, &scInterceptionDistanceLocalX, &scInterceptionDistanceLocalY);
const ScalarV scTotalInterceptionT = scSimulatedTime + scInterceptionT;
bInterception = bInterception && IsGreaterThanOrEqualAll(scTotalInterceptionT, scMinTime);
if (!bInterception)
{
scSimulatedTime += scSegmentTime;
vEntityPredictedPos += scSegmentTime * Vec3V(vEntityVelocityXY, ScalarV(V_ZERO));
vVehiclePredictedPos += scSegmentTime * Vec3V(vSegmentVehicleVelocityXY, ScalarV(V_ZERO));
#if __BANK
test.AddCheck(scSimulatedTime.Getf(), false, vEntityPredictedPos, vEntityVelocityXY, vVehiclePredictedPos, vSegmentVehicleVelocityXY, MagXY(vEntityPredictedPos - vVehiclePredictedPos), ScalarV(V_NEGONE), ScalarV(V_NEGONE));
#endif // __BANK
}
#if __BANK
else
{
test.AddCheck((scSimulatedTime + scInterceptionT).Getf(), true, vInterceptionEntityWorldPos, vEntityVelocityXY, vInterceptionVehicleWorldPos, vSegmentVehicleVelocityXY, MagXY(vInterceptionEntityWorldPos - vInterceptionVehicleWorldPos), scInterceptionDistanceLocalX, scInterceptionDistanceLocalY);
}
#endif // __BANK
}
}
else
{
// No node list -> we calculate with the current positions and velocities
bInterception = CheckEntityInterceptionXY(m_pVehicle->GetTransform().GetPosition(), vVehicleForwardXY, vVehicleVelocityXY, vEntityPos, vEntityVelocityXY, scMaxSideDistance, scMaxForwardDistance, scMaxTime,
&scInterceptionT, &vInterceptionVehicleWorldPos, &vInterceptionEntityWorldPos, &scInterceptionDistanceLocalX, &scInterceptionDistanceLocalY);
bInterception = bInterception && IsGreaterThanOrEqualAll(scInterceptionT, scMinTime);
#if __BANK
test.bRouteAvailable = false;
if (bInterception)
{
test.AddCheck(scInterceptionT.Getf(), true, vInterceptionEntityWorldPos, vEntityVelocityXY, vInterceptionVehicleWorldPos, vVehicleVelocityXY, MagXY(vInterceptionEntityWorldPos - vInterceptionVehicleWorldPos), scInterceptionDistanceLocalX, scInterceptionDistanceLocalY);
}
else
{
Vec3V vEntityPredictedPos = vEntityPos + scMaxTime * Vec3V(vEntityVelocityXY, ScalarV(V_ZERO));
Vec3V vVehiclePredictedPos = m_pVehicle->GetTransform().GetPosition() + scMaxTime * Vec3V(vVehicleVelocityXY, ScalarV(V_ZERO));
test.AddCheck(scMaxTime.Getf(), false, vEntityPredictedPos, vEntityVelocityXY, vVehiclePredictedPos, vVehicleVelocityXY, MagXY(vEntityPredictedPos - vVehiclePredictedPos), ScalarV(V_NEGONE), ScalarV(V_NEGONE));
}
#endif // __BANK
}
#if __BANK
test.uTS = fwTimer::GetTimeInMilliseconds();
// We save the previous test to compare
test.bRoutePassByEntityPos = DoesRoutePassByPosition(vEntityPos, CTaskSmartFlee::sm_Tunables.m_ExitVehicleRouteMinDistance, CTaskSmartFlee::sm_Tunables.m_ExitVehicleMaxDistance);
m_aDEBUG_RouteInterceptionTests.Append(test);
#endif // __BANK
return bInterception;
}
bool CVehicleIntelligence::CheckEntityInterceptionXY(Vec3V_In vVehiclePos, Vec2V_In vVehicleForwardXY, Vec2V_In vVehicleVelocityXY, Vec3V_In vEntityPos, Vec2V_In vEntityVelocityXY, ScalarV_In scMaxSideDistance, ScalarV_In scMaxForwardDistance, ScalarV_In scMaxTime,
ScalarV_Ptr pscInterceptionT, Vec3V_Ptr pvInterceptionVehPos, Vec3V_Ptr pvInterceptionEntityPos, ScalarV_Ptr pscInterceptionDistanceLocalX, ScalarV_Ptr pscInterceptionDistanceLocalY) const
{
aiAssert(pscInterceptionT && pvInterceptionVehPos && pvInterceptionEntityPos && pscInterceptionDistanceLocalX && pscInterceptionDistanceLocalY);
// No node list -> we calculate with the current positions and velocities
ScalarV scInterceptionT(V_ZERO);
const ScalarV scVehicleSpeedXYSquared(MagSquared(vVehicleVelocityXY));
const Vec2V vVehicleMoveDirXY = IsGreaterThanAll(scVehicleSpeedXYSquared, ScalarV(V_ZERO)) ? vVehicleVelocityXY / Sqrt(scVehicleSpeedXYSquared) : vVehicleForwardXY;
const ScalarV scVehicleHeading(fwAngle::GetATanOfXY( vVehicleMoveDirXY.GetYf(), -vVehicleMoveDirXY.GetXf() ));
const Vec2V vEntityLocalVelXY = Rotate(vEntityVelocityXY - vVehicleVelocityXY, -scVehicleHeading);
const Vec2V vEntityLocalPosXY = Rotate(vEntityPos.GetXY() - vVehiclePos.GetXY(), -scVehicleHeading);
if (IsGreaterThanAll(MagSquared(vEntityLocalVelXY), ScalarV(V_ZERO)))
{
scInterceptionT = Clamp(geomTValues::FindTValueOpenSegToOriginV(Vec3V(vEntityLocalPosXY, ScalarV(V_ZERO)), Vec3V(vEntityLocalVelXY, ScalarV(V_ZERO))), ScalarV(V_ZERO), scMaxTime);
}
const Vec2V vInterceptionEntityLocalPosXY = vEntityLocalPosXY + vEntityLocalVelXY * scInterceptionT;
const ScalarV scInterceptionEntitLocalyDistanceX = vInterceptionEntityLocalPosXY.GetX();
const ScalarV scInterceptionEntityLocalDistanceY = vInterceptionEntityLocalPosXY.GetY();
bool bInterception = IsLessThanAll(Abs(scInterceptionEntitLocalyDistanceX), scMaxSideDistance) && IsLessThanAll(Abs(scInterceptionEntityLocalDistanceY), scMaxForwardDistance);
if (bInterception)
{
const Vec3V vInterceptionVehPos = vVehiclePos + scInterceptionT * Vec3V(vVehicleVelocityXY, ScalarV(V_ZERO));
const Vec3V vInterceptionEntityPos = vEntityPos + scInterceptionT * Vec3V(vEntityVelocityXY, ScalarV(V_ZERO));
// Copy to output params
*pscInterceptionT = scInterceptionT;
*pvInterceptionVehPos = vInterceptionVehPos;
*pvInterceptionEntityPos = vInterceptionEntityPos;
*pscInterceptionDistanceLocalX = scInterceptionEntitLocalyDistanceX;
*pscInterceptionDistanceLocalY = scInterceptionEntityLocalDistanceY;
}
return bInterception;
}
bool CVehicleIntelligence::DoesRoutePassByPosition(Vec3V_ConstRef vPos, const float fThreshold, const float fCareThreshold) const
{
//Grab the vehicle position.
Vec3V vVehiclePosition = m_pVehicle->GetTransform().GetPosition();
//Generate the squared threshold.
float fThresholdSq = square(fThreshold);
ScalarV scThresholdSq = ScalarVFromF32(fThresholdSq);
//Check if the vehicle's immediate position is within the threshold.
ScalarV scDistSq = DistSquared(vPos, vVehiclePosition);
if(IsLessThanAll(scDistSq, scThresholdSq))
{
return true;
}
//Generate the squared care threshold.
ScalarV scCareThresholdSq = ScalarVFromF32(square(fCareThreshold));
//Ensure the vehicle is within the care threshold.
if(IsGreaterThanAll(scDistSq, scCareThresholdSq))
{
return false;
}
//The rest of algorithm works as follows:
// 1) Start with the vehicle's current road node.
// 2) Create a line segment out of the current and next road nodes.
// 3) Test the closest distance from the position to the line segment.
// 4) If the distance is less than the threshold, the route is considered to pass by the position.
// 5) Check if the distance from the position to the next road node lies within a "care" threshold.
// 6) If the distance is outside the "care" threshold, the rest of the route is considered "don't care" and the route is NOT considered to pass by the position (yet).
// 7) If there are no road nodes left, the route is NOT considered to pass by the position.
// 8) Assign the current road node to the next road node.
// 9) Repeat from Step 2.
//Ensure the node list is valid.
CVehicleNodeList* pNodeList = GetNodeList();
if(!pNodeList)
{
return false;
}
//Grab the current node.
s32 iCurNode = pNodeList->GetTargetNodeIndex();
Assertf(iCurNode >= 0 && iCurNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED, "Current road node is invalid.");
//Iterate over the road nodes.
for(s32 iNode = iCurNode; iNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED - 1; ++iNode)
{
//Ensure the road node is not empty.
const CNodeAddress& rAddress = pNodeList->GetPathNodeAddr(iNode);
if(rAddress.IsEmpty())
{
continue;
}
//Ensure the road node is loaded.
if(!ThePaths.IsRegionLoaded(rAddress))
{
continue;
}
//Ensure the next road node is not empty.
const CNodeAddress& rNextAddress = pNodeList->GetPathNodeAddr(iNode + 1);
if(rNextAddress.IsEmpty())
{
continue;
}
//Ensure the next road node is loaded.
if(!ThePaths.IsRegionLoaded(rNextAddress))
{
continue;
}
//Grab the road nodes.
const CPathNode* pNode = ThePaths.FindNodePointer(rAddress);
const CPathNode* pNextNode = ThePaths.FindNodePointer(rNextAddress);
//Grab the coordinates of the road nodes.
Vector3 vNodePos;
pNode->GetCoors(vNodePos);
Vector3 vNextNodePos;
pNextNode->GetCoors(vNextNodePos);
//Check if the line segment intersects the sphere generated by the position/threshold combination.
if( geomSpheres::TestSphereToSeg(vPos, ScalarVFromF32(fThreshold), VECTOR3_TO_VEC3V(vNodePos), VECTOR3_TO_VEC3V(vNextNodePos)) )
{
return true;
}
//Check if the care threshold is valid.
if(!IsZeroAll(scCareThresholdSq))
{
//Check if the distance from the next node segment to the position exceeds the care threshold.
ScalarV scDistSq = DistSquared(VECTOR3_TO_VEC3V(vNextNodePos), vPos);
if(IsGreaterThanAll(scDistSq, scCareThresholdSq))
{
return false;
}
}
}
return false;
}
bool CVehicleIntelligence::AreFutureRoutePointsCloseToPoint(Vec3V_In vPosition, ScalarV_In scRadius) const
{
//Square the radius.
ScalarV scRadiusSq = Scale(scRadius, scRadius);
//Grab the vehicle position.
Vec3V vVehiclePosition = m_pVehicle->GetTransform().GetPosition();
//Calculate the distance squared from the vehicle to the position.
ScalarV scDistSq = DistSquared(vVehiclePosition, vPosition);
if(IsLessThanAll(scDistSq, scRadiusSq))
{
return true;
}
//Ensure the node list is valid.
CVehicleNodeList* pNodeList = GetNodeList();
if(!pNodeList)
{
return false;
}
//Grab the current node.
s32 iCurNode = pNodeList->GetTargetNodeIndex();
Assertf(iCurNode >= 0 && iCurNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED, "Current road node is invalid.");
//Iterate over the road nodes.
for(s32 iNode = iCurNode; iNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED; ++iNode)
{
//Ensure the road node is not empty.
const CNodeAddress& rAddress = pNodeList->GetPathNodeAddr(iNode);
if(rAddress.IsEmpty())
{
continue;
}
//Ensure the road node is loaded.
if(!ThePaths.IsRegionLoaded(rAddress))
{
continue;
}
//Grab the road node.
const CPathNode* pNode = ThePaths.FindNodePointer(rAddress);
//Grab the coordinates of the road node.
Vec3V vNodePosition;
pNode->GetCoors(RC_VECTOR3(vNodePosition));
//Calculate the distance squared from the node to the position.
scDistSq = DistSquared(vNodePosition, vPosition);
if(IsLessThanAll(scDistSq, scRadiusSq))
{
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindMaxSteerAngle
// PURPOSE : Finds the maximum angle the steering wheel will go. Bigger vehicles
// might need bigger values to reduce turning radius.
/////////////////////////////////////////////////////////////////////////////////
float CVehicleIntelligence::FindMaxSteerAngle() const
{
const float fSpeed = m_pVehicle->GetAiXYSpeed();
const float fBaseVal = 0.7f;
const float fMaxForSpeed = fSpeed > 42.0f
? 0.2f
: 0.9f - (fSpeed / 60.0f);
// if(pVeh->GetModelIndex() == MODELID_CAR_ENFORCER || pVeh->GetModelIndex() == MODELID_CAR_PACKER)
// {
// return 0.7f;
// }
return rage::Min(fBaseVal, fMaxForSpeed);
}
float CVehicleIntelligence::GetTurnRadiusAtCurrentSpeed() const
{
const float fMaxSteeringAngle = FindMaxSteerAngle();
CWheel* pFrontWheel = m_pVehicle->GetWheelFromId(VEH_WHEEL_LF);
CWheel* pRearWheel = m_pVehicle->GetWheelFromId(VEH_WHEEL_LR);
//Assertf(pFrontWheel && pRearWheel, "CVehicleIntelligence::GetTurnRadiusAtCurrentSpeed expected VEH_WHEEL_LF and VEH_WHEEL_LR to exist! Returning 0.0");
if (!pFrontWheel || !pRearWheel)
{
return 0.0f;
}
Vector3 vFrontWheelPos, vRearWheelPos;
pFrontWheel->GetWheelPosAndRadius(vFrontWheelPos);
pRearWheel->GetWheelPosAndRadius(vRearWheelPos);
const float fWheelBase = vFrontWheelPos.Dist(vRearWheelPos);
//const float fWheelBase = m_pVehicle->GetVehicleModelInfo()->GetFragType()->GetPhysics(0)->GetArchetype()->GetBound()->GetBoundingBoxMax().GetYf()
// - m_pVehicle->GetVehicleModelInfo()->GetFragType()->GetPhysics(0)->GetArchetype()->GetBound()->GetBoundingBoxMin().GetYf();
const float fSinSteeringAngle = Sinf(fMaxSteeringAngle);
return fMaxSteeringAngle > 0.0f ? fWheelBase / fSinSteeringAngle : 0.0f;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateJustBeenGivenNewCommand
// PURPOSE : Called by other code when this vehicle is given a new command.
// This should make sure the car starts with a clean sheet and doesn't start
// by reversing or a 3-point turn.
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::UpdateJustBeenGivenNewCommand()
{
LastTimeNotStuck = fwTimer::GetTimeInMilliseconds();
MillisecondsNotMoving = 0;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetActiveTask
// PURPOSE : Gets the current active vehicle task from the taskmanager
// RETURNS : current active taskmission
/////////////////////////////////////////////////////////////////////////////////
CTaskVehicleMissionBase *CVehicleIntelligence::GetActiveTask() const
{
aiTask *pTask = m_TaskManager.GetActiveTask(VEHICLE_TASK_TREE_PRIMARY);
if(pTask)
{
#if __ASSERT
//Assertf(), "pTask was valid but not a CTaskVehicleMissionBase in CVehicleIntelligence::GetActiveTask().");
const CTaskVehicleMissionBase* pTaskMissionBaseDebug = dynamic_cast<CTaskVehicleMissionBase*>(pTask);
Assertf(pTaskMissionBaseDebug, "pTask was valid but not a CTaskVehicleMissionBase in CVehicleIntelligence::GetActiveTask().");
Assertf(pTaskMissionBaseDebug, "Trying to get task name: %s", pTask->GetTaskName());
#endif //__ASSERT
return static_cast<CTaskVehicleMissionBase*>(pTask);
}
else
{
return NULL;
}
}
CTaskVehicleMissionBase *CVehicleIntelligence::GetActiveTaskSimplest() const
{
CTask * pTask = GetActiveTask();
while(pTask && pTask->GetSubTask())
{
pTask = pTask->GetSubTask();
}
if(pTask)
{
Assertf((dynamic_cast<CTaskVehicleMissionBase*>(pTask)), "pTask was null in CVehicleIntelligence::GetActiveTaskSimplest().");
return static_cast<CTaskVehicleMissionBase*>(pTask);
}
else
{
return NULL;
}
}
void CVehicleIntelligence::GetActiveTaskInfo(s32& taskType, sVehicleMissionParams& params) const
{
const CTaskVehicleMissionBase *pActiveTask = NULL;
if (m_pVehicle->IsNetworkClone())
{
pActiveTask = static_cast<CNetObjVehicle*>(m_pVehicle->GetNetworkObject())->GetCloneAITask();
}
else
{
pActiveTask = CVehicleIntelligence::GetActiveTask();
}
if (pActiveTask)
{
taskType = pActiveTask->GetTaskType();
params = pActiveTask->GetParams();
}
else
{
taskType = CTaskTypes::TASK_INVALID_ID;
}
}
void CVehicleIntelligence::SetRecordingNumber(s8 recordingNumber)
{
Assert(m_pVehicle);
m_pVehicle->m_nVehicleFlags.bIsInRecording = (recordingNumber != -1);
m_RecordingNumber = recordingNumber;
}
#if __BANK
void CVehicleIntelligence::PrintTasks()
{
// Print the task hierarchy for this vehicle
Printf("Task hierarchy\n");
CTask* pActiveTask = GetActiveTask();
if(pActiveTask)
{
CTask* pTaskToPrint = pActiveTask;
while(pTaskToPrint)
{
Printf("name: %s\n", (const char*) pTaskToPrint->GetName());
pTaskToPrint=pTaskToPrint->GetSubTask();
}
}
}
#endif
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateCarHasReasonToBeStopped
// PURPOSE : Should be called if this car is stopping but it has a reason for it.
// Calling this will make sure the driver doesn't get impatient.
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::UpdateCarHasReasonToBeStopped()
{
MillisecondsNotMoving = 0;
}
/////////////////////////////////////////////////////////////////////////////////
aiTask *CVehicleIntelligence::GetGotoTaskForVehicle(CVehicle *pVehicle, CPhysical* pTargetEntity, Vector3 *vTarget, s32 iDrivingFlags, float fTargetReached, float fStraightLineDistance, float fCruiseSpeed)
{
CTaskVehicleMissionBase* pCarTask = NULL;
sVehicleMissionParams params;
params.SetTargetEntity(pTargetEntity);
params.m_iDrivingFlags = iDrivingFlags;
params.m_fTargetArriveDist = fTargetReached;
params.m_fCruiseSpeed = fCruiseSpeed;
params.SetTargetPosition(vTarget);
VehicleType vehicleType = pVehicle->GetVehicleType();
if(vehicleType == VEHICLE_TYPE_SUBMARINECAR)
{
vehicleType = pVehicle->IsInSubmarineMode() ? VEHICLE_TYPE_SUBMARINE : VEHICLE_TYPE_CAR;
}
if( vehicleType == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE ||
vehicleType == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE )
{
vehicleType = VEHICLE_TYPE_CAR;
}
switch(vehicleType)
{
case VEHICLE_TYPE_CAR:
case VEHICLE_TYPE_BIKE:
case VEHICLE_TYPE_BICYCLE:
case VEHICLE_TYPE_QUADBIKE:
pCarTask = CVehicleIntelligence::CreateAutomobileGotoTask(params, fStraightLineDistance);
break;
case VEHICLE_TYPE_BOAT:
pCarTask = rage_new CTaskVehicleGoToBoat(params);
break;
case VEHICLE_TYPE_SUBMARINE:
pCarTask = rage_new CTaskVehicleGoToSubmarine(params);
break;
case VEHICLE_TYPE_PLANE:
if(iDrivingFlags & DF_PlaneTaxiMode)
{
pCarTask = CVehicleIntelligence::CreateAutomobileGotoTask(params, fStraightLineDistance);
}
else
{
pCarTask = rage_new CTaskVehicleGoToPlane(params);
}
break;
case VEHICLE_TYPE_HELI:
case VEHICLE_TYPE_BLIMP:
pCarTask = rage_new CTaskVehicleGoToHelicopter(params);
break;
default:
BANK_ONLY(AI_LOG_WITH_ARGS("Vehicle type %s is not supported for this task type.", pVehicle->GetVehicleTypeString(vehicleType)));
break;
}
return pCarTask;
}
/////////////////////////////////////////////////////////////////////////////////
bool CVehicleIntelligence::GetSlowingDownForPed()
{
aiTask *pTask = m_TaskManager.FindTaskByTypeActive(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_GOTO_POINT_WITH_AVOIDANCE_AUTOMOBILE);
if(pTask)
{
CTaskVehicleGoToPointWithAvoidanceAutomobile *pAvoidanceTask = static_cast<CTaskVehicleGoToPointWithAvoidanceAutomobile*>(pTask);
return pAvoidanceTask->GetSlowingDownForPed();
}
return false;//just return false if no valid task found
}
/////////////////////////////////////////////////////////////////////////////////
bool CVehicleIntelligence::GetSlowingDownForCar()
{
aiTask *pTask = m_TaskManager.FindTaskByTypeActive(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_GOTO_POINT_WITH_AVOIDANCE_AUTOMOBILE);
if(pTask)
{
CTaskVehicleGoToPointWithAvoidanceAutomobile *pAvoidanceTask = static_cast<CTaskVehicleGoToPointWithAvoidanceAutomobile*>(pTask);
return pAvoidanceTask->GetSlowingDownForCar();
}
return false;//just return false if no valid task found
}
bool CVehicleIntelligence::GetShouldObeyTrafficLights()
{
CTaskVehicleMissionBase * pTask = GetActiveTask();
if(pTask)
{
return pTask->GetShouldObeyTrafficLights();
}
return false;
}
void CVehicleIntelligence::ClearScanners()
{
m_vehicleScanner.Clear();
m_pedScanner.Clear();
m_objectScanner.Clear();
}
aiTask *CVehicleIntelligence::GetSubTaskFromMissionIdentifier( CVehicle *UNUSED_PARAM(pVehicle),
int iMission,
CPhysical* pTargetEntity,
Vector3 *vTarget,
float fTargetReached,
float fCruiseSpeed,
float fSubOrientation,
s32 iMinHeightAboveTerrain,
float fSlowDownDistance,
int iSubFlags)
{
s32 flags = iSubFlags;
if( fSubOrientation >= 0.0f )
{
flags |= CTaskVehicleGoToSubmarine::SF_AttainRequestedOrientation;
}
//Assertf(!(pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR && !pVehicle->IsInSubmarineMode()), "TASK_SUB_MISSION called when not in sub mode");
sVehicleMissionParams params;
params.SetTargetEntity(pTargetEntity);
params.SetTargetPosition(vTarget);
params.m_fTargetArriveDist = fTargetReached;
params.m_fCruiseSpeed = fCruiseSpeed;
ASSERT_ONLY(params.IsTargetValid());
CTaskVehicleMissionBase* pSubTask = NULL;
switch(iMission)
{
case MISSION_RAM:
pSubTask = rage_new CTaskVehicleRam(params);
break;
case MISSION_BLOCK:
pSubTask = rage_new CTaskVehicleBlock(params);
break;
case MISSION_GOTO:
pSubTask = rage_new CTaskVehicleGoToSubmarine(params, flags, iMinHeightAboveTerrain, fSubOrientation);
if( fSlowDownDistance > 0.0f )
{
static_cast<CTaskVehicleGoToSubmarine*>(pSubTask)->SetSlowDownDistance(fSlowDownDistance);
}
if (iMission == MISSION_GOTO && CTaskSequences::ms_iActiveSequence < 0) // In sequences we do finish the goto task. Otherwise we hover.
{
pSubTask->SetDrivingFlag(DF_DontTerminateTaskWhenAchieved, true);
}
break;
case MISSION_STOP:
pSubTask = rage_new CTaskVehicleStop();
break;
case MISSION_ATTACK:
pSubTask = rage_new CTaskVehicleAttack(params);
static_cast<CTaskVehicleAttack*>(pSubTask)->SetSubmarineSpecificParams(iMinHeightAboveTerrain, flags);
break;
case MISSION_FOLLOW:
pSubTask = rage_new CTaskVehicleFollow(params);
break;
case MISSION_FLEE:
params.m_iDrivingFlags.SetFlag(DF_AvoidTargetCoors);
pSubTask = rage_new CTaskVehicleFlee(params);
break;
case MISSION_CIRCLE:
pSubTask = rage_new CTaskVehicleCircle(params);
break;
case MISSION_ESCORT_LEFT:
pSubTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_LEFT);
break;
case MISSION_ESCORT_RIGHT:
pSubTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_RIGHT);
break;
case MISSION_ESCORT_REAR:
pSubTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_REAR);
break;
case MISSION_ESCORT_FRONT:
pSubTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_FRONT);
break;
case MISSION_FOLLOW_RECORDING:
pSubTask = rage_new CTaskVehicleFollowRecording(params);
break;
case MISSION_CRASH:
pSubTask = rage_new CTaskVehicleCrash(); //I suppose this is valid - will probably want to subclass it
break;
case MISSION_CRUISE:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't cruise");
break;
case MISSION_POLICE_BEHAVIOUR:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't be police!");
break;
case MISSION_GOTO_RACING:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't use goto racing.");
break;
case MISSION_PARK_PERPENDICULAR:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't park perpendicular.");
break;
case MISSION_PARK_PARALLEL:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't park parallel.");
break;
case MISSION_LAND:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't land.");
break;
case MISSION_LAND_AND_WAIT:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't land.");
break;
case MISSION_PULL_OVER:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't pull over.");
break;
case MISSION_PROTECT:
Assertf(0, "TASK_SUB_MISSION incorrect mission type, Sub's can't protect.");
break;
default:
Assertf(0,"TASK_SUB_MISSION Invalid mission type %d", iMission);
}
return pSubTask;
}
aiTask *CVehicleIntelligence::GetHeliTaskFromMissionIdentifier(CVehicle *UNUSED_PARAM(pVehicle),
int iMission,
CPhysical* pTargetEntity,
Vector3 *vTarget,
float fTargetReached,
float fCruiseSpeed,
float fHeliOrientation,
s32 iFlightHeight,
s32 iMinHeightAboveTerrain,
float fSlowDownDistance,
int iHeliFlags)
{
s32 flags = iHeliFlags;
if( fHeliOrientation >= 0.0f )
{
flags |= CTaskVehicleGoToHelicopter::HF_AttainRequestedOrientation;
}
sVehicleMissionParams params;
params.SetTargetEntity(pTargetEntity);
params.SetTargetPosition(vTarget);
params.m_fTargetArriveDist = fTargetReached;
params.m_fCruiseSpeed = fCruiseSpeed;
ASSERT_ONLY(params.IsTargetValid());
CTaskVehicleMissionBase* pHeliTask = NULL;
switch(iMission)
{
case MISSION_CRUISE:
Assertf(0, "TASK_HELI_MISSION incorrect mission type, Heli's can't cruise");
break;
case MISSION_RAM:
pHeliTask = rage_new CTaskVehicleRam(params);
break;
case MISSION_BLOCK:
pHeliTask = rage_new CTaskVehicleBlock(params);
break;
case MISSION_GOTO:
pHeliTask = rage_new CTaskVehicleGoToHelicopter(params,
flags,
fHeliOrientation,
iMinHeightAboveTerrain);
if( fSlowDownDistance > 0.0f )
{
static_cast<CTaskVehicleGoToHelicopter*>(pHeliTask)->SetSlowDownDistance(fSlowDownDistance);
}
if (iMission == MISSION_GOTO && CTaskSequences::ms_iActiveSequence < 0) // In sequences we do finish the goto task. Otherwise we hover.
{
pHeliTask->SetDrivingFlag(DF_DontTerminateTaskWhenAchieved, true);
}
break;
case MISSION_STOP:
pHeliTask = rage_new CTaskVehicleStop();
break;
case MISSION_ATTACK:
pHeliTask = rage_new CTaskVehicleAttack(params);
static_cast<CTaskVehicleAttack*>(pHeliTask)->SetHelicopterSpecificParams(fHeliOrientation, iFlightHeight, iMinHeightAboveTerrain, flags);
break;
case MISSION_FOLLOW:
pHeliTask = rage_new CTaskVehicleFollow(params);
static_cast<CTaskVehicleFollow*>(pHeliTask)->SetHelicopterSpecificParams(fHeliOrientation, iFlightHeight, iMinHeightAboveTerrain, flags);
break;
case MISSION_FLEE:
params.m_iDrivingFlags.SetFlag(DF_AvoidTargetCoors);
//pHeliTask = rage_new CTaskVehicleFlee(params);
pHeliTask = rage_new CTaskVehicleFleeAirborne(params, iFlightHeight, iMinHeightAboveTerrain, (flags & CTaskVehicleGoToHelicopter::HF_AttainRequestedOrientation) != 0, fHeliOrientation);
break;
case MISSION_CIRCLE:
pHeliTask = rage_new CTaskVehicleCircle(params);
static_cast<CTaskVehicleCircle*>(pHeliTask)->SetHelicopterSpecificParams(fHeliOrientation, iFlightHeight, iMinHeightAboveTerrain, flags);
break;
case MISSION_ESCORT_LEFT:
pHeliTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_LEFT, -1.0f, iMinHeightAboveTerrain);
break;
case MISSION_ESCORT_RIGHT:
pHeliTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_RIGHT, -1.0f, iMinHeightAboveTerrain);
break;
case MISSION_ESCORT_REAR:
pHeliTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_REAR, -1.0f, iMinHeightAboveTerrain);
break;
case MISSION_ESCORT_FRONT:
pHeliTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_FRONT, -1.0f, iMinHeightAboveTerrain);
break;
case MISSION_GOTO_RACING:
Assertf(0, "TASK_HELI_MISSION incorrect mission type, Heli's can't use goto racing.");
break;
case MISSION_FOLLOW_RECORDING:
pHeliTask = rage_new CTaskVehicleFollowRecording(params);
break;
case MISSION_POLICE_BEHAVIOUR:
pHeliTask = rage_new CTaskVehiclePoliceBehaviour(params);
break;
case MISSION_PARK_PERPENDICULAR:
Assertf(0, "TASK_HELI_MISSION incorrect mission type, Heli's can't park perpendicular.");
break;
case MISSION_PARK_PARALLEL:
Assertf(0, "TASK_HELI_MISSION incorrect mission type, Heli's can't park parallel.");
break;
case MISSION_LAND:
pHeliTask = rage_new CTaskVehicleLand(params, flags, fHeliOrientation);
break;
case MISSION_LAND_AND_WAIT:
params.m_iDrivingFlags.SetFlag(DF_DontTerminateTaskWhenAchieved);
pHeliTask = rage_new CTaskVehicleLand(params, flags, fHeliOrientation);
break;
case MISSION_CRASH:
pHeliTask = rage_new CTaskVehicleCrash();
break;
case MISSION_PULL_OVER:
Assertf(0, "TASK_HELI_MISSION incorrect mission type, Heli's can't pull over.");
break;
case MISSION_PROTECT:
pHeliTask = rage_new CTaskVehicleHeliProtect(params, fTargetReached, iMinHeightAboveTerrain, flags);
break;
default:
Assertf(0,"TASK_HELI_MISSION Invalid mission type %d", iMission);
}
return pHeliTask;
}
/////////////////////////////////////////////////////////////////////////////////
aiTask* CVehicleIntelligence::GetPlaneTaskFromMissionIdentifier(CVehicle* UNUSED_PARAM( pVehicle ), int iMission, CPhysical* pTargetEntity,
Vector3 *vTarget, float fTargetReached, float fCruiseSpeed,
float fOrientation, s32 iFlightHeight, s32 iMinHeightAboveTerrain,
bool bPrecise)
{
bool bUseOrientation = false;
if( fOrientation >= 0.0f )
{
bUseOrientation = true;
}
sVehicleMissionParams params;
params.SetTargetPosition(vTarget);
params.SetTargetEntity(pTargetEntity);
params.m_fTargetArriveDist = fTargetReached;
params.m_fCruiseSpeed = fCruiseSpeed;
ASSERT_ONLY(params.IsTargetValid());
CTaskVehicleMissionBase* pPlaneTask = NULL;
switch(iMission)
{
case MISSION_CRUISE:
Assertf(0, "TASK_PLANE_MISSION incorrect mission type, Plane's can't cruise");
break;
case MISSION_RAM:
pPlaneTask = rage_new CTaskVehicleRam(params);
break;
case MISSION_BLOCK:
pPlaneTask = rage_new CTaskVehicleBlock(params);
break;
case MISSION_GOTO:
pPlaneTask = rage_new CTaskVehicleGoToPlane( params,
iFlightHeight,
iMinHeightAboveTerrain,
bPrecise,
bUseOrientation,
fOrientation);
if( pPlaneTask && iMission == MISSION_GOTO && CTaskSequences::ms_iActiveSequence < 0) // In sequences we do finish the goto task. Otherwise we hover.
{
pPlaneTask->SetDrivingFlag(DF_DontTerminateTaskWhenAchieved, true);
}
break;
case MISSION_STOP:
pPlaneTask = rage_new CTaskVehicleStop();
break;
case MISSION_ATTACK:
pPlaneTask = rage_new CTaskVehicleAttack(params);
static_cast<CTaskVehicleAttack*>(pPlaneTask)->SetHelicopterSpecificParams(fOrientation, iFlightHeight, iMinHeightAboveTerrain, 0);
break;
case MISSION_FOLLOW:
pPlaneTask = rage_new CTaskVehicleFollow(params);
break;
case MISSION_FLEE:
params.m_iDrivingFlags.SetFlag(DF_AvoidTargetCoors);
//pPlaneTask = rage_new CTaskVehicleFlee(params);
pPlaneTask = rage_new CTaskVehicleFleeAirborne(params, iFlightHeight, iMinHeightAboveTerrain, bUseOrientation, fOrientation);
break;
case MISSION_CIRCLE:
pPlaneTask = rage_new CTaskVehicleCircle(params);
static_cast<CTaskVehicleCircle*>(pPlaneTask)->SetHelicopterSpecificParams(fOrientation, iFlightHeight, iMinHeightAboveTerrain, 0);
break;
case MISSION_ESCORT_LEFT:
pPlaneTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_LEFT);
break;
case MISSION_ESCORT_RIGHT:
pPlaneTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_RIGHT);
break;
case MISSION_ESCORT_REAR:
pPlaneTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_REAR);
break;
case MISSION_ESCORT_FRONT:
pPlaneTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_FRONT);
break;
case MISSION_GOTO_RACING:
Assertf(0, "TASK_PLANE_MISSION incorrect mission type, Plane's can't use goto racing.");
break;
case MISSION_FOLLOW_RECORDING:
pPlaneTask = rage_new CTaskVehicleFollowRecording(params);
break;
case MISSION_POLICE_BEHAVIOUR:
pPlaneTask = rage_new CTaskVehiclePoliceBehaviour(params);
break;
case MISSION_PARK_PERPENDICULAR:
Assertf(0, "TASK_PLANE_MISSION incorrect mission type, Plane's can't park perpendicular.");
break;
case MISSION_PARK_PARALLEL:
Assertf(0, "TASK_PLANE_MISSION incorrect mission type, Plane's can't park parallel.");
break;
case MISSION_LAND:
Assertf(0, "TASK_PLANE_LAND NOT SUPPORTTED ANYMORE.");
//pPlaneTask = rage_new CTaskVehicleLandPlane(params);
break;
case MISSION_LAND_AND_WAIT:
Assertf(0, "TASK_PLANE_LAND NOT SUPPORTTED ANYMORE.");
//params.m_iDrivingFlags.SetFlag(DF_DontTerminateTaskWhenAchieved);
//pPlaneTask = rage_new CTaskVehicleLandPlane(params);
break;
case MISSION_CRASH:
pPlaneTask = rage_new CTaskVehicleCrash();
break;
case MISSION_PULL_OVER:
Assertf(0, "TASK_PLANE_MISSION incorrect mission type, Plane's can't pull over.");
break;
case MISSION_PROTECT:
Assertf(0, "TASK_PLANE_MISSION incorrect mission type, Plane's can't MISSION_PROTECT.");
break;
default:
Assertf(0,"TASK_PLANE_MISSION Invalid mission type %d", iMission);
}
return pPlaneTask;
}
aiTask* CVehicleIntelligence::GetBoatTaskFromMissionIdentifier(CVehicle *pVehicle, int iMission, CPhysical* pTargetEntity, Vector3 *vTarget, s32 iDrivingFlags, float fTargetReached, float fCruiseSpeed, int iBoatFlags )
{
float fStraightLineDistance = 10.0f;
switch(iMission)
{
case MISSION_GOTO:
case MISSION_GOTO_RACING:
{
sVehicleMissionParams params;
params.SetTargetEntity(pTargetEntity);
params.m_iDrivingFlags = iDrivingFlags;
params.m_fTargetArriveDist = fTargetReached;
params.m_fCruiseSpeed = fCruiseSpeed;
params.SetTargetPosition(vTarget);
return rage_new CTaskVehicleGoToBoat(params, static_cast<rage::u16>(iBoatFlags));
}
default:
return GetTaskFromMissionIdentifier(pVehicle, iMission, pTargetEntity, vTarget, iDrivingFlags, fTargetReached, fStraightLineDistance, fCruiseSpeed, (iDrivingFlags & DF_DriveIntoOncomingTraffic)>0, (iDrivingFlags & DF_DriveInReverse)>0);
}
}
/////////////////////////////////////////////////////////////////////////////////
aiTask *CVehicleIntelligence::GetTaskFromMissionIdentifier(CVehicle *pVehicle, int iMission, CPhysical* pTargetEntity, Vector3 *vTarget, s32 iDrivingFlags, float fTargetReached, float fStraightLineDistance, float fCruiseSpeed, bool bAllowedToGoAgainstTraffic, bool bDoEverythingInReverse)
{
if( bDoEverythingInReverse )
{
iDrivingFlags |= DF_DriveInReverse;
}
if( bAllowedToGoAgainstTraffic )
{
iDrivingFlags |= DF_DriveIntoOncomingTraffic;
}
sVehicleMissionParams params;
// Set target entity prior to setting target position to avoid false positive in SetTargetPosition(),
// the target position may be used as an offset from the target entity, and otherwise invalid target positions (zero) become valid
params.SetTargetEntity(pTargetEntity);
params.SetTargetPosition(vTarget);
params.m_iDrivingFlags = iDrivingFlags;
params.m_fTargetArriveDist = fTargetReached;
params.m_fCruiseSpeed = fCruiseSpeed;
//for pull-over
Vector3 dummyDir(VEC3_ZERO);
aiTask* pCarTask = NULL;
switch(iMission)
{
case MISSION_CRUISE:
pCarTask = CreateCruiseTask(*pVehicle, params);
break;
case MISSION_RAM:
pCarTask = rage_new CTaskVehicleRam(params);
break;
case MISSION_BLOCK:
pCarTask = rage_new CTaskVehicleBlock(params);
break;
case MISSION_GOTO:
case MISSION_GOTO_RACING:
pCarTask = GetGotoTaskForVehicle(pVehicle, pTargetEntity, vTarget, iDrivingFlags, fTargetReached, fStraightLineDistance, fCruiseSpeed);
break;
case MISSION_STOP:
pCarTask = rage_new CTaskVehicleStop(0);
break;
case MISSION_ATTACK:
pCarTask = rage_new CTaskVehicleAttack(params);
break;
case MISSION_FOLLOW:
pCarTask = rage_new CTaskVehicleFollow(params);
break;
case MISSION_FLEE:
{
params.m_iDrivingFlags.SetFlag(DF_AvoidTargetCoors);
if ( pVehicle->InheritsFromBoat() )
{
pCarTask = rage_new CTaskVehicleFleeBoat(params);
}
else
{
pCarTask = rage_new CTaskVehicleFlee(params);
}
}
break;
case MISSION_CIRCLE:
pCarTask = rage_new CTaskVehicleCircle(params);
break;
case MISSION_ESCORT_LEFT:
pCarTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_LEFT);
break;
case MISSION_ESCORT_RIGHT:
pCarTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_RIGHT);
break;
case MISSION_ESCORT_REAR:
pCarTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_REAR);
break;
case MISSION_ESCORT_FRONT:
pCarTask = rage_new CTaskVehicleEscort(params, CTaskVehicleEscort::VEHICLE_ESCORT_FRONT);
break;
case MISSION_FOLLOW_RECORDING:
pCarTask = rage_new CTaskVehicleFollowRecording(params);
break;
case MISSION_POLICE_BEHAVIOUR:
pCarTask = rage_new CTaskVehiclePoliceBehaviour(params);
break;
case MISSION_PARK_PERPENDICULAR:
pCarTask = rage_new CTaskVehicleParkNew(params, VEC3_ZERO, CTaskVehicleParkNew::Park_Perpendicular_NoseIn, 30.0f * DtoR);
break;
case MISSION_PARK_PARALLEL:
pCarTask = rage_new CTaskVehicleParkNew(params, VEC3_ZERO, CTaskVehicleParkNew::Park_Parallel, 30.0f * DtoR);
break;
case MISSION_LAND:
pCarTask = rage_new CTaskVehicleLand(params);
break;
case MISSION_CRASH:
pCarTask = rage_new CTaskVehicleCrash();
break;
case MISSION_PULL_OVER:
//pCarTask = rage_new CTaskVehiclePullOver();
pCarTask = rage_new CTaskVehicleParkNew(params, dummyDir, CTaskVehicleParkNew::Park_PullOver, 0.175f);
break;
case MISSION_TURN_CLOCKWISE_GOING_BACKWARD:
case MISSION_TURN_CLOCKWISE_GOING_FORWARD:
case MISSION_TURN_COUNTERCLOCKWISE_GOING_BACKWARD:
case MISSION_TURN_COUNTERCLOCKWISE_GOING_FORWARD:
pCarTask = rage_new CTaskVehicleThreePointTurn(params, false);
break;
default:
Assertf(0,"TASK_VEHICLE_MISSION Invalid mission type %d", iMission);
}
return pCarTask;
}
VehMissionType CVehicleIntelligence::GetMissionIdentifierFromTaskType(int iTaskType, sVehicleMissionParams& params)
{
switch (iTaskType)
{
case CTaskTypes::TASK_VEHICLE_HELI_PROTECT:
return MISSION_PROTECT;
case CTaskTypes::TASK_VEHICLE_CRUISE_NEW:
return MISSION_CRUISE;
case CTaskTypes::TASK_VEHICLE_RAM:
return MISSION_RAM;
case CTaskTypes::TASK_VEHICLE_BLOCK:
return MISSION_BLOCK;
case CTaskTypes::TASK_VEHICLE_GOTO_AUTOMOBILE_NEW:
case CTaskTypes::TASK_VEHICLE_GOTO_PLANE:
case CTaskTypes::TASK_VEHICLE_GOTO_HELICOPTER:
case CTaskTypes::TASK_VEHICLE_GOTO_SUBMARINE:
case CTaskTypes::TASK_VEHICLE_GOTO_BOAT:
case CTaskTypes::TASK_VEHICLE_GOTO_POINT_AUTOMOBILE:
case CTaskTypes::TASK_VEHICLE_GOTO_POINT_WITH_AVOIDANCE_AUTOMOBILE:
return MISSION_GOTO;
case CTaskTypes::TASK_VEHICLE_STOP:
return MISSION_STOP;
case CTaskTypes::TASK_VEHICLE_ATTACK:
return MISSION_ATTACK;
case CTaskTypes::TASK_VEHICLE_FOLLOW:
return MISSION_FOLLOW;
case CTaskTypes::TASK_VEHICLE_FLEE:
return MISSION_FLEE;
case CTaskTypes::TASK_VEHICLE_CIRCLE:
return MISSION_CIRCLE;
case CTaskTypes::TASK_VEHICLE_ESCORT:
aiWarningf("Trying to get a mission type from TASK_VEHICLE_ESCORT. Multiple escort types not supported, returning MISSION_ESCORT_REAR");
return MISSION_ESCORT_REAR;
break;
//case TASK_TASKNAME:
// return MISSION_GOTO_RACING;
case CTaskTypes::TASK_VEHICLE_FOLLOW_RECORDING:
return MISSION_FOLLOW_RECORDING;
case CTaskTypes::TASK_VEHICLE_POLICE_BEHAVIOUR:
case CTaskTypes::TASK_VEHICLE_POLICE_BEHAVIOUR_HELICOPTER:
case CTaskTypes::TASK_VEHICLE_POLICE_BEHAVIOUR_BOAT:
return MISSION_POLICE_BEHAVIOUR;
case CTaskTypes::TASK_VEHICLE_LAND:
if (params.m_iDrivingFlags & DF_DontTerminateTaskWhenAchieved)
{
return MISSION_LAND_AND_WAIT;
}
else
{
return MISSION_LAND;
}
break;
case CTaskTypes::TASK_VEHICLE_CRASH:
return MISSION_CRASH;
case CTaskTypes::TASK_VEHICLE_PULL_OVER:
return MISSION_PULL_OVER;
default:
return MISSION_NONE;
}
}
aiTask* CVehicleIntelligence::GetTempActionTaskFromTempActionType(int iTempActionType, u32 nDuration, fwFlags8 uConfigFlags)
{
CTaskVehicleMissionBase *pTempTask = NULL;
//TODO: think of a better way to initialize the "swerve to road edge" stuff
Vector2 vRoadEdgeNormal(0.0f, 0.0f);
Vector2 vRoadEdgePos(0.0f, 0.0f);
switch(iTempActionType)
{
case TEMPACT_WAIT:
pTempTask = rage_new CTaskVehicleWait(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_REVERSE:
pTempTask = rage_new CTaskVehicleReverse(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleReverse::Reverse_Opposite_Direction);
break;
case TEMPACT_HANDBRAKETURNLEFT:
pTempTask = rage_new CTaskVehicleHandBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleHandBrake::HandBrake_Left);
break;
case TEMPACT_HANDBRAKETURNRIGHT:
pTempTask = rage_new CTaskVehicleHandBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleHandBrake::HandBrake_Right);
break;
case TEMPACT_HANDBRAKESTRAIGHT:
pTempTask = rage_new CTaskVehicleHandBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleHandBrake::HandBrake_Straight);
break;
case TEMPACT_TURNLEFT:
pTempTask = rage_new CTaskVehicleTurn(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleTurn::Turn_Left);
break;
case TEMPACT_TURNRIGHT:
pTempTask = rage_new CTaskVehicleTurn(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleTurn::Turn_Right);
break;
case TEMPACT_GOFORWARD:
pTempTask = rage_new CTaskVehicleGoForward(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_SWERVELEFT:
pTempTask = rage_new CTaskVehicleSwerve(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleSwerve::Swerve_Left, false, false, vRoadEdgeNormal, vRoadEdgePos);
break;
case TEMPACT_SWERVERIGHT:
pTempTask = rage_new CTaskVehicleSwerve(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleSwerve::Swerve_Right, false, false, vRoadEdgeNormal, vRoadEdgePos);
break;
case TEMPACT_REVERSE_LEFT:
pTempTask = rage_new CTaskVehicleReverse(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleReverse::Reverse_Left, uConfigFlags);
break;
case TEMPACT_REVERSE_RIGHT:
pTempTask = rage_new CTaskVehicleReverse(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleReverse::Reverse_Right, uConfigFlags);
break;
case TEMPACT_PLANE_FLY_UP:
pTempTask = rage_new CTaskVehicleFlyDirection(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleFlyDirection::Fly_Up);
break;
case TEMPACT_PLANE_FLY_STRAIGHT:
pTempTask = rage_new CTaskVehicleFlyDirection(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleFlyDirection::Fly_Straight);
break;
case TEMPACT_PLANE_SHARP_LEFT:
pTempTask = rage_new CTaskVehicleFlyDirection(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleFlyDirection::Fly_Left);
break;
case TEMPACT_PLANE_SHARP_RIGHT:
pTempTask = rage_new CTaskVehicleFlyDirection(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleFlyDirection::Fly_Right);
break;
case TEMPACT_HEADON_COLLISION:
pTempTask = rage_new CTaskVehicleHeadonCollision(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_SWERVELEFT_STOP:
pTempTask = rage_new CTaskVehicleSwerve(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleSwerve::Swerve_Left, true, false, vRoadEdgeNormal, vRoadEdgePos);
break;
case TEMPACT_SWERVERIGHT_STOP:
pTempTask = rage_new CTaskVehicleSwerve(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleSwerve::Swerve_Right, true, false, vRoadEdgeNormal, vRoadEdgePos);
break;
case TEMPACT_REVERSE_STRAIGHT:
pTempTask = rage_new CTaskVehicleReverse(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleReverse::Reverse_Straight, uConfigFlags);
break;
case TEMPACT_BOOST_USE_STEERING_ANGLE:
pTempTask = rage_new CTaskVehicleBoostUseSteeringAngle(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_BRAKE:
pTempTask = rage_new CTaskVehicleBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_HANDBRAKETURNLEFT_INTELLIGENT:
pTempTask = rage_new CTaskVehicleHandBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleHandBrake::HandBrake_Left_Intelligence);
break;
case TEMPACT_HANDBRAKETURNRIGHT_INTELLIGENT:
pTempTask = rage_new CTaskVehicleHandBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleHandBrake::HandBrake_Right_Intelligence);
break;
case TEMPACT_HANDBRAKESTRAIGHT_INTELLIGENT:
pTempTask = rage_new CTaskVehicleHandBrake(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleHandBrake::HandBrake_Straight_Intelligence);
break;
case TEMPACT_REVERSE_STRAIGHT_HARD:
pTempTask = rage_new CTaskVehicleReverse(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, CTaskVehicleReverse::Reverse_Straight_Hard, uConfigFlags);
break;
case TEMPACT_BURNOUT:
pTempTask = rage_new CTaskVehicleBurnout(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_REV_ENGINE:
pTempTask = rage_new CTaskVehicleRevEngine(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_GOFORWARD_HARD:
pTempTask = rage_new CTaskVehicleGoForward(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration, true);
break;
case TEMPACT_SURFACE_SUBMARINE:
pTempTask = rage_new CTaskVehicleSurfaceInSubmarine(NetworkInterface::GetSyncedTimeInMilliseconds() + nDuration);
break;
case TEMPACT_NONE:
break;
default:
Assertf(0, "CarSetTempAction_OnUpdate invalid temp action");
}
return pTempTask;
}
/////////////////////////////////////////////////////////////////////////////////
aiTask* CVehicleIntelligence::CreateVehicleTaskForNetwork(u32 taskType)
{
CAITarget target;
sVehicleMissionParams params;
switch (taskType)
{
case CTaskTypes::TASK_VEHICLE_ANIMATION:
//return rage_new CTaskVehicleAnimation();
break;
case CTaskTypes::TASK_VEHICLE_APPROACH:
return rage_new CTaskVehicleApproach(params);
case CTaskTypes::TASK_VEHICLE_ATTACK:
return rage_new CTaskVehicleAttack(params);
case CTaskTypes::TASK_VEHICLE_BLOCK:
return rage_new CTaskVehicleBlock(params);
case CTaskTypes::TASK_VEHICLE_BLOCK_CRUISE_IN_FRONT:
return rage_new CTaskVehicleBlockCruiseInFront(params);
case CTaskTypes::TASK_VEHICLE_BLOCK_BRAKE_IN_FRONT:
return rage_new CTaskVehicleBlockBrakeInFront(params);
case CTaskTypes::TASK_VEHICLE_BLOCK_BACK_AND_FORTH:
return rage_new CTaskVehicleBlockBackAndForth(params);
case CTaskTypes::TASK_VEHICLE_BRAKE:
return rage_new CTaskVehicleBrake();
case CTaskTypes::TASK_VEHICLE_CIRCLE:
return rage_new CTaskVehicleCircle(params);
case CTaskTypes::TASK_VEHICLE_CONVERTIBLE_ROOF:
//return rage_new CTaskVehicleConvertibleRoof();
break;
case CTaskTypes::TASK_VEHICLE_TRANSFORM_TO_SUBMARINE:
//return rage_new CTaskVehicleTransformToSubmarine();
break;
case CTaskTypes::TASK_VEHICLE_CRASH:
return rage_new CTaskVehicleCrash();
case CTaskTypes::TASK_VEHICLE_CRUISE_NEW:
return rage_new CTaskVehicleCruiseNew(params);
case CTaskTypes::TASK_VEHICLE_CRUISE_BOAT:
return rage_new CTaskVehicleCruiseBoat(params);
case CTaskTypes::TASK_VEHICLE_DEAD_DRIVER:
return rage_new CTaskVehicleDeadDriver();
case CTaskTypes::TASK_VEHICLE_ESCORT:
return rage_new CTaskVehicleEscort(params);
case CTaskTypes::TASK_VEHICLE_FLEE:
return rage_new CTaskVehicleFlee(params);
case CTaskTypes::TASK_VEHICLE_FLEE_AIRBORNE:
return rage_new CTaskVehicleFleeAirborne(params);
case CTaskTypes::TASK_VEHICLE_FLEE_BOAT:
return rage_new CTaskVehicleFleeBoat(params);
case CTaskTypes::TASK_VEHICLE_FOLLOW:
return rage_new CTaskVehicleFollow(params);
case CTaskTypes::TASK_VEHICLE_FOLLOW_RECORDING:
return rage_new CTaskVehicleFollowRecording(params);
case CTaskTypes::TASK_VEHICLE_GOTO_AUTOMOBILE_NEW:
return CVehicleIntelligence::CreateAutomobileGotoTask(params, sVehicleMissionParams::DEFAULT_SWITCH_TO_STRAIGHT_LINE_DIST);
case CTaskTypes::TASK_VEHICLE_GOTO_LONGRANGE:
return rage_new CTaskVehicleGotoLongRange(params);
case CTaskTypes::TASK_VEHICLE_GOTO_NAVMESH:
return rage_new CTaskVehicleGoToNavmesh(params);
case CTaskTypes::TASK_VEHICLE_GOTO_PLANE:
return rage_new CTaskVehicleGoToPlane(params);
case CTaskTypes::TASK_VEHICLE_GOTO_HELICOPTER:
return rage_new CTaskVehicleGoToHelicopter(params);
case CTaskTypes::TASK_VEHICLE_GOTO_SUBMARINE:
return rage_new CTaskVehicleGoToSubmarine(params);
case CTaskTypes::TASK_VEHICLE_GOTO_BOAT:
return rage_new CTaskVehicleGoToBoat(params);
case CTaskTypes::TASK_VEHICLE_GOTO_POINT_AUTOMOBILE:
return rage_new CTaskVehicleGoToPointAutomobile(params);
case CTaskTypes::TASK_VEHICLE_GOTO_POINT_WITH_AVOIDANCE_AUTOMOBILE:
return rage_new CTaskVehicleGoToPointWithAvoidanceAutomobile(params);
case CTaskTypes::TASK_VEHICLE_HELI_PROTECT:
return rage_new CTaskVehicleHeliProtect(params);
case CTaskTypes::TASK_VEHICLE_HOVER:
return rage_new CTaskVehicleHover();
case CTaskTypes::TASK_VEHICLE_LAND:
return rage_new CTaskVehicleLand(params);
case CTaskTypes::TASK_VEHICLE_LAND_PLANE:
return rage_new CTaskVehicleLandPlane();
case CTaskTypes::TASK_VEHICLE_NO_DRIVER:
return rage_new CTaskVehicleNoDriver();
case CTaskTypes::TASK_VEHICLE_PARK_NEW:
return rage_new CTaskVehicleParkNew(params);
case CTaskTypes::TASK_VEHICLE_PLANE_CHASE:
return rage_new CTaskVehiclePlaneChase(params, target);
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_AUTOMOBILE:
return rage_new CTaskVehiclePlayerDriveAutomobile();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_AMPHIBIOUS_AUTOMOBILE:
return rage_new CTaskVehiclePlayerDriveAmphibiousAutomobile();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_BIKE:
return rage_new CTaskVehiclePlayerDriveBike();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_BOAT:
return rage_new CTaskVehiclePlayerDriveBoat();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_SUBMARINE:
return rage_new CTaskVehiclePlayerDriveSubmarine();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_SUBMARINECAR:
return rage_new CTaskVehiclePlayerDriveSubmarineCar();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_PLANE:
return rage_new CTaskVehiclePlayerDrivePlane();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_HELI:
return rage_new CTaskVehiclePlayerDriveHeli();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_AUTOGYRO:
return rage_new CTaskVehiclePlayerDriveAutogyro();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_DIGGER_ARM:
return rage_new CTaskVehiclePlayerDriveDiggerArm();
case CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_TRAIN:
return rage_new CTaskVehiclePlayerDriveTrain();
case CTaskTypes::TASK_VEHICLE_POLICE_BEHAVIOUR:
return rage_new CTaskVehiclePoliceBehaviour(params);
case CTaskTypes::TASK_VEHICLE_POLICE_BEHAVIOUR_BOAT:
return rage_new CTaskVehiclePoliceBehaviourBoat(params);
case CTaskTypes::TASK_VEHICLE_POLICE_BEHAVIOUR_HELICOPTER:
return rage_new CTaskVehiclePoliceBehaviourHelicopter(params);
case CTaskTypes::TASK_VEHICLE_PULL_ALONGSIDE:
return rage_new CTaskVehiclePullAlongside(params);
case CTaskTypes::TASK_VEHICLE_PULL_OVER:
return rage_new CTaskVehiclePullOver();
case CTaskTypes::TASK_VEHICLE_PURSUE:
return rage_new CTaskVehiclePursue(params);
case CTaskTypes::TASK_VEHICLE_RAM:
return rage_new CTaskVehicleRam(params);
case CTaskTypes::TASK_VEHICLE_REACT_TO_COP_SIREN:
return rage_new CTaskVehicleReactToCopSiren();
case CTaskTypes::TASK_VEHICLE_STOP:
return rage_new CTaskVehicleStop();
case CTaskTypes::TASK_VEHICLE_SPIN_OUT:
return rage_new CTaskVehicleSpinOut(params);
case CTaskTypes::TASK_VEHICLE_THREE_POINT_TURN:
return rage_new CTaskVehicleThreePointTurn(params, false);
default:
Assertf(0, "CreateVehicleTaskForNetwork: Task %s is not supported", TASKCLASSINFOMGR.GetTaskName(taskType));
#if ENABLE_NETWORK_LOGGING
// we have received a vehicle task we don't sync over the network yet, so log the task type
NetworkInterface::GetObjectManagerLog().WriteDataValue("NON_SYNCED_VEHICLE_TASK", TASKCLASSINFOMGR.GetTaskName(taskType));
#endif
break;
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::Process_DummyFlags()
{
//Ensure this is a law enforcement vehicle.
if(!m_pVehicle->m_nVehicleFlags.bIsLawEnforcementVehicle)
{
return;
}
//Ensure the driver is valid.
const CPed* pDriver = m_pVehicle->GetDriver();
if(!pDriver)
{
return;
}
//Ensure the driver has an order.
if(!pDriver->GetPedIntelligence()->GetOrder())
{
return;
}
//At this point, we have:
// 1) A law enforcement vehicle,
// 2) with a driver,
// 3) that has an order.
//Vehicles that meet these conditions should always be real, unless there is no collision.
//If there is no collision, and the vehicle is forced to be real, it will fall through the map.
//Force the vehicle to be real, unless there is no collision.
CVehicleDummyHelper::Process(*m_pVehicle, CVehicleDummyHelper::ForceRealUnlessNoCollision);
}
void CVehicleIntelligence::Process_NearbyEntityLists()
{
// Would be good to do this, I think, but we would have to be careful to check
// if there are any users of the scanners for vehicles that are parked. Possibly
// car alarms or reflections.
// if(sConsiderParkedVehicleForLod(m_pVehicle))
// {
// // Parked vehicles shouldn't need to scan, hopefully.
// return;
// }
//Need to force updates during fast-moving chases, to ensure data for avoidance is up-to-date.
bool bForceVehicleAndPedScans = m_pVehicle->m_TimeOfCreation == fwTimer::GetTimeInMilliseconds();
bool bForceObjectScans = false;
if(m_pVehicle->m_nVehicleFlags.bForceEntityScansAtHighSpeeds)
{
static dev_float s_fMinSpeed = 20.0f;
bForceObjectScans = (m_pVehicle->GetAiXYSpeed() >= s_fMinSpeed);
bForceVehicleAndPedScans |= bForceObjectScans;
}
//Update all peds and vehicles and objects in range.
m_vehicleScanner.ScanForEntitiesInRange(*m_pVehicle, bForceVehicleAndPedScans);
m_pedScanner.ScanForEntitiesInRange(*m_pVehicle, bForceVehicleAndPedScans);
m_objectScanner.ScanForEntitiesInRange(*m_pVehicle, bForceObjectScans);
}
void CVehicleIntelligence::Process_CarBehind()
{
//Ensure we are finding the car we are behind.
#if __BANK
if(!CVehicleIntelligence::m_bFindCarBehind)
{
return;
}
#endif // __BANK
CVehicle* pCarWeAreBehind = m_pCarWeAreBehind;
aiAssert(m_TailgateDistributer.IsRegistered());
//Check if the tailgate can be processed this frame.
// Note: the "simple" version of ShouldBeProcessThisFrame() doesn't support the m_iMaxFramesBetweenUpdatesForIrregularUse feature.
const bool processThisFrame = m_TailgateDistributer.ShouldBeProcessedThisFrameSimple();
if(!pCarWeAreBehind && !processThisFrame)
{
return;
}
//Grab the vehicle.
const CVehicle* pVeh = m_pVehicle;
aiAssert(pVeh);
//Grab the position.
const Vec3V vPos = pVeh->GetVehiclePosition();
//Grab the direction.
const Vec3V vFwd = pVeh->GetVehicleForwardDirection();
const Vec3V bndBoxMaxV = pVeh->GetBaseModelInfo()->GetBoundingBox().GetMax();
//Calculate the front bumper position.
const Vec3V vFrontBumperPos = AddScaled(vPos, vFwd, bndBoxMaxV.GetY());
//Generate the max distance squared.
ScalarV scMaxDistSq(Vec::V4VConstantSplat<FLOAT_TO_INT(625.0f)>());
ScalarV scMaxDistSqForPlane(1225);
if( m_pVehicle->InheritsFromPlane() )
{
scMaxDistSq = scMaxDistSqForPlane;
}
ScalarV scDistSq;
if(processThisFrame)
{
//Update the vehicle we are behind.
pCarWeAreBehind = FindCarWeAreBehind(vFwd, vFrontBumperPos, scMaxDistSq, scDistSq);
SetCarWeAreBehind(pCarWeAreBehind);
if(!pCarWeAreBehind)
{
m_pCarWeCouldBeSlipStreaming = NULL;
return;
}
#if __ASSERT
// Should hold up, AreWeBehindCar() is called as a part of FindCarWeAreBehind().
// ScalarV scDistSq2;
// aiAssert(AreWeBehindCar(pCarWeAreBehind, vFwd, vFrontBumperPos, scMaxDistSq, scDistSq2, ScalarV(V_ZERO)));
// aiAssert(IsEqualAll(scDistSq2, scDistSq));
#endif
}
else
{
//Ensure we are still behind the car.
if(!AreWeBehindCar(pCarWeAreBehind, vFwd, vFrontBumperPos, scMaxDistSq, scDistSq, ScalarV(V_ZERO)))
{
//Clear the car.
SetCarWeAreBehind(NULL);
pCarWeAreBehind = NULL;
}
}
//Assign the distance behind the car, squared.
StoreScalar32FromScalarV(m_fDistanceBehindCarSq, scDistSq);
m_pCarWeCouldBeSlipStreaming = NULL;
//Check to see if the players car should be slip streaming another car.
if(CVehicle::sm_bSlipstreamingEnabled && pCarWeAreBehind)
{
CTaskVehicleMissionBase* pActiveTask = pVeh->GetIntelligence()->GetActiveTask();
const bool bIsDriverPlayer = pActiveTask && CTaskTypes::IsPlayerDriveTask(pActiveTask->GetTaskType());
if(bIsDriverPlayer)
{
scMaxDistSq = ScalarV(CVehicle::ms_fSlipstreamMaxDistance*CVehicle::ms_fSlipstreamMaxDistance);
m_pCarWeCouldBeSlipStreaming = FindCarWeAreSlipstreamingBehind(vPos, vFwd, vFrontBumperPos, scMaxDistSq);
m_fDistanceBehindCarWeCouldBeSlipStreamingSq = scDistSq.Getf();
}
}
#if __BANK
if (CVehicleIntelligence::m_bDisplayDebugCarBehind && CVehicleIntelligence::AllowCarAiDebugDisplayForThisVehicle(pVeh))
{
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(vFrontBumperPos), 0.25f, Color_red);
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(vFrontBumperPos), 0.25f, Color_blue);
if(pCarWeAreBehind)
{
grcDebugDraw::Line(VEC3V_TO_VECTOR3(vFrontBumperPos), VEC3V_TO_VECTOR3(pCarWeAreBehind->GetTransform().GetPosition()), Color_red, Color_blue);
}
}
#endif
}
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::SetCarWeAreBehind(CVehicle* pCarWeAreBehind)
{
if (m_pCarWeAreBehind)
{
m_pCarWeAreBehind->GetIntelligence()->AddCarsBehindUs(-m_NumCarsBehindContributedToCarWeAreBehind);
}
if (pCarWeAreBehind)
{
pCarWeAreBehind->GetIntelligence()->AddCarsBehindUs(m_NumCarsBehindUs + 1);
}
m_NumCarsBehindContributedToCarWeAreBehind = m_NumCarsBehindUs + 1;
m_pCarWeAreBehind = pCarWeAreBehind;
}
void CVehicleIntelligence::AddCarsBehindUs(const s32 numCarsBehindUs)
{
m_NumCarsBehindUs += numCarsBehindUs;
Assertf(m_NumCarsBehindUs >= 0, "m_NumCarsBehindUs: %i, numCarsBehindUs: %i", m_NumCarsBehindUs, numCarsBehindUs);
}
void CVehicleIntelligence::Process_NumCarsBehindUs()
{
SetCarWeAreBehind(m_pCarWeAreBehind);
}
bool CVehicleIntelligence::AreWeBehindCar(const CVehicle* pOtherVeh, Vec3V_In vFwd, Vec3V_In vFrontBumperPos, ScalarV_In scMaxDistSq, ScalarV_InOut RESTRICT scDistSq, ScalarV_In scDotLimit) const
{
// For the math to work here, we can only handle a non-negative dot product limit,
// i.e. no more than a 180 degree range.
aiAssert(IsGreaterThanOrEqualAll(scDotLimit, ScalarV(V_ZERO)));
// Grab the other position.
const Vec3V vOtherPos = pOtherVeh->GetVehiclePosition();
// Grab the other forward direction.
const Vec3V vOtherFwd = pOtherVeh->GetVehicleForwardDirection();
// Calculate the other back bumper position.
const Vec3V bndBoxMinV = pOtherVeh->GetBaseModelInfo()->GetBoundingBox().GetMin();
const Vec3V vOtherBackBumperPosNew = AddScaled(vOtherPos, vOtherFwd, bndBoxMinV.GetY());
// Compute the delta vector between the bumper positions.
const Vec3V vDiff = Subtract(vOtherBackBumperPosNew, vFrontBumperPos);
// Ignore if Z diff is too great (Stunt races allow for cars to be driving on top of each other on overlapping tracks)
TUNE_GROUP_FLOAT(VEH_INTELLIGENCE, HeightLimitForBehindCarDetection, 10.0f, 0.0f, 100.0f, 0.1f);
if (Abs(vDiff.GetZf()) > HeightLimitForBehindCarDetection)
{
return false;
}
// Compute a 4D vector that contains the X and Y coordinates from the delta vector and the forward direction.
const Vec4V diffAndFwdXXYYV = GetFromTwo<Vec::X1, Vec::X2, Vec::Y1, Vec::Y2>(Vec4V(vDiff), Vec4V(vFwd));
// Compute the squared magnitude of both of these vectors by multiplying each element
// by itself, then rotating the elements and adding them.
const Vec4V diffAndFwdXXYYSqV = Scale(diffAndFwdXXYYV, diffAndFwdXXYYV);
const Vec4V diffAndFwdXXYYSqRotV = diffAndFwdXXYYSqV.Get<Vec::Z, Vec::W, Vec::X, Vec::Y>();
const Vec4V diffAndFwdXXYYSqAddV = Add(diffAndFwdXXYYSqV, diffAndFwdXXYYSqRotV);
const ScalarV diffFlatMagSqV = diffAndFwdXXYYSqAddV.GetX();
const ScalarV fwdFlatMagSqV = diffAndFwdXXYYSqAddV.GetY();
// Check if the squared magnitude of the flattened delta vector is less than the squared max distance.
const BoolV withinRangeV = IsLessThanOrEqual(diffFlatMagSqV, scMaxDistSq);
// Compute a 2D dot product of the delta vector and the forward vector
// (neither of which is normalized at this point).
// Note that a 2D dot product here is likely to be faster than a 3D dot product with Z cleared out,
// since we have to multiply-add them together, or use the 360-specific dot product instruction that has high latency.
const ScalarV dotV = Dot(Vec2V(vDiff.GetIntrin128()), Vec2V(vFwd.GetIntrin128()));
// We basically want to check if the cosine of the angle between the
// vectors is larger than the threshold, i.e. something like this:
// (a*b)/(|a|*|b|) >= threshold
// To avoid square roots and division, we multiply by |a|*|b| on both sides,
// and then square:
// (a*b) >= threshold*|a|*|b|
// (a*b)^2 >= threshold^2*|a|^2*|b|^2
// We know that the threshold is not negative and the lengths are not negative,
// so the only thing we have to be careful about is the dot product
// being negative, so we check for that here.
const BoolV dotPositiveV = IsGreaterThanOrEqual(dotV, ScalarV(V_ZERO));
// Compute the square of the dot product, and the square of the threshold (cosine of the angle).
const ScalarV dotSqV = Scale(dotV, dotV);
const ScalarV dotNormThresholdSqV = Scale(scDotLimit, scDotLimit); // Could have just passed this in pre-squared.
// Multiply together the right hand side of the inequality.
const ScalarV dotThresholdSqV = Scale(dotNormThresholdSqV, Scale(diffFlatMagSqV, fwdFlatMagSqV));
// Check if the dot product is above the threshold (after squaring both sides).
const BoolV dotAboveThresholdV = IsGreaterThanOrEqual(dotSqV, dotThresholdSqV);
// Combine the result: we need to be within range, have a positive dot product,
// and the square of the dot product has to be large enough.
const BoolV resultV = And(withinRangeV, And(dotPositiveV, dotAboveThresholdV));
scDistSq = diffFlatMagSqV;
return resultV.Getb();
// Less optimized older code:
//
// //Grab the other position.
// Vec3V vOtherPos = pOtherVeh->GetVehiclePosition();
//
// //Grab the other forward direction.
// Vec3V vOtherFwd = pOtherVeh->GetVehicleForwardDirection();
//
// //Calculate the other back bumper position.
// Vec3V vOtherBackBumperPos = Add(vOtherPos, Scale(vOtherFwd, pOtherVeh->GetBaseModelInfo()->GetBoundingBox().GetMin().GetY()));
//
// //Generate the difference.
// Vec3V vDiff = Subtract(vOtherBackBumperPos, vFrontBumperPos);
//
// //We only care about the XY plane when checking distance.
// Vec3V vDiffNoZ = vDiff;
// vDiffNoZ.SetZ(ScalarV(V_ZERO));
//
// //Ensure the other vehicle is within range.
// ScalarV scDistSqReg = MagSquared(vDiffNoZ);
// scDistSq = scDistSqReg;
// if(IsGreaterThanAll(scDistSqReg, scMaxDistSq))
// {
// return false;
// }
//
// //We only care about the XY plane when checking direction.
// Vec3V vFwdNoZ = vFwd;
// vFwdNoZ.SetZ(ScalarV(V_ZERO));
// Vec3V vFwdN = NormalizeFastSafe(vFwdNoZ, Vec3V(V_ZERO));
// vDiffNoZ = NormalizeFastSafe(vDiffNoZ, Vec3V(V_ZERO));
//
// //Ensure the other vehicle is generally in front.
// ScalarV scDot = Dot(vFwdN, vDiffNoZ);
// if(IsLessThanAll(scDot, scDotLimit))
// {
// return false;
// }
//
// return true;
}
CVehicle* CVehicleIntelligence::FindCarWeAreSlipstreamingBehind(Vec3V_ConstRef UNUSED_PARAM(vPos), Vec3V_ConstRef vFwd, Vec3V_ConstRef vFrontBumperPos, ScalarV_ConstRef scMaxDistSq) const
{
//Grab the vehicle.
CVehicle* pVeh = m_pVehicle;
if(!pVeh)
{
return NULL;
}
//Keep track of the results.
struct Results
{
Results()
: m_pVehicle(NULL)
, m_scDistSq()
{
}
CVehicle* m_pVehicle;
ScalarV m_scDistSq;
};
Results lResults;
//Scan the nearby vehicles.
CEntityScannerIterator entityList = GetVehicleScanner().GetIterator();
for(CEntity* pEntity = entityList.GetFirst(); pEntity; pEntity = entityList.GetNext())
{
//Ensure the entity does not match the vehicle.
if(pEntity == pVeh)
{
continue;
}
//Grab the other vehicle.
CVehicle* pOtherVeh = static_cast<CVehicle *>(pEntity);
//Ensure we are behind the other vehicle.
ScalarV scDistSq;
static dev_float sfDotLimit = 0.98f;
if(!AreWeBehindCar(pOtherVeh, vFwd, vFrontBumperPos, scMaxDistSq, scDistSq, ScalarV(sfDotLimit)))
{
continue;
}
//Ensure the other vehicle is a better potential result.
if(lResults.m_pVehicle && IsGreaterThanAll(scDistSq, lResults.m_scDistSq))
{
continue;
}
//Assign the result.
lResults.m_pVehicle = pOtherVeh;
lResults.m_scDistSq = scDistSq;
}
return lResults.m_pVehicle;
}
// Adapted from geomBoxes::TestSegmentToBoxMinMax(), but tests four separate
// segments against one AABB.
// Note: p01X/Y/Z is the segment delta, rather than the second point on the segment,
// which is a difference compared to TestSegmentToBoxMinMax().
static VecBoolV_Out sTestSegmentToBoxMinMax(Vec4V_In p0X, Vec4V_In p0Y, Vec4V_In p0Z,
Vec4V_In p01X, Vec4V_In p01Y, Vec4V_In p01Z,
Vec3V_In boxMinV, Vec3V_In boxMaxV)
{
const ScalarV halfV(V_HALF);
const Vec3V boxHalfMaxV = Scale(boxMaxV, halfV);
const Vec3V boxCenterV = AddScaled(boxHalfMaxV, boxMinV, halfV);
const Vec3V boxHalfExtentsV = Subtract(boxMaxV, boxCenterV);
// const Vec3V boxHalfExtentsV = SubtractScaled(boxHalfMaxV, boxMinV, halfV);
const Vec4V boxHalfExtentsX = Vec4V(boxHalfExtentsV.GetX());
const Vec4V boxHalfExtentsY = Vec4V(boxHalfExtentsV.GetY());
const Vec4V boxHalfExtentsZ = Vec4V(boxHalfExtentsV.GetZ());
const Vec4V boxCenterX = Vec4V(boxCenterV.GetX());
const Vec4V boxCenterY = Vec4V(boxCenterV.GetY());
const Vec4V boxCenterZ = Vec4V(boxCenterV.GetZ());
// Note: only depend on the segments, could be reused for multiple boxes
const Vec4V signedSegHalfExtentsX = Scale(p01X, halfV);
const Vec4V signedSegHalfExtentsY = Scale(p01Y, halfV);
const Vec4V signedSegHalfExtentsZ = Scale(p01Z, halfV);
const Vec4V segCenterX = Add(p0X, signedSegHalfExtentsX);
const Vec4V segCenterY = Add(p0Y, signedSegHalfExtentsY);
const Vec4V segCenterZ = Add(p0Z, signedSegHalfExtentsZ);
const Vec4V absSegHalfExtentsX = Abs(signedSegHalfExtentsX);
const Vec4V absSegHalfExtentsY = Abs(signedSegHalfExtentsY);
const Vec4V absSegHalfExtentsZ = Abs(signedSegHalfExtentsZ);
// Translate segment relative to box center.
const Vec4V centerDeltaX = Subtract(segCenterX, boxCenterX);
const Vec4V centerDeltaY = Subtract(segCenterY, boxCenterY);
const Vec4V centerDeltaZ = Subtract(segCenterZ, boxCenterZ);
const Vec4V absCenterDeltaX = Abs(centerDeltaX);
const Vec4V absCenterDeltaY = Abs(centerDeltaY);
const Vec4V absCenterDeltaZ = Abs(centerDeltaZ);
// Test for separating axis using aabb axes.
const Vec4V sumHalfExtentsX = Add(absSegHalfExtentsX, boxHalfExtentsX);
const Vec4V sumHalfExtentsY = Add(absSegHalfExtentsY, boxHalfExtentsY);
const Vec4V sumHalfExtentsZ = Add(absSegHalfExtentsZ, boxHalfExtentsZ);
const VecBoolV disjoint0X = IsGreaterThan(absCenterDeltaX, sumHalfExtentsX);
const VecBoolV disjoint0Y = IsGreaterThan(absCenterDeltaY, sumHalfExtentsY);
const VecBoolV disjoint0Z = IsGreaterThan(absCenterDeltaZ, sumHalfExtentsZ);
// Test for separating axis using segment axes.
// Almost a cross product, but not quite.
const Vec4V tX1 = Scale(boxHalfExtentsY, absSegHalfExtentsZ);
const Vec4V tY1 = Scale(boxHalfExtentsZ, absSegHalfExtentsX);
const Vec4V tZ1 = Scale(boxHalfExtentsX, absSegHalfExtentsY);
const Vec4V tX = AddScaled(tX1, boxHalfExtentsZ, absSegHalfExtentsY);
const Vec4V tY = AddScaled(tY1, boxHalfExtentsX, absSegHalfExtentsZ);
const Vec4V tZ = AddScaled(tZ1, boxHalfExtentsY, absSegHalfExtentsX);
const Vec4V crossSegCenterSignedHalfExtentX1 = Scale(centerDeltaY, signedSegHalfExtentsZ);
const Vec4V crossSegCenterSignedHalfExtentY1 = Scale(centerDeltaZ, signedSegHalfExtentsX);
const Vec4V crossSegCenterSignedHalfExtentZ1 = Scale(centerDeltaX, signedSegHalfExtentsY);
const Vec4V crossSegCenterSignedHalfExtentX = SubtractScaled(crossSegCenterSignedHalfExtentX1, centerDeltaZ, signedSegHalfExtentsY);
const Vec4V crossSegCenterSignedHalfExtentY = SubtractScaled(crossSegCenterSignedHalfExtentY1, centerDeltaX, signedSegHalfExtentsZ);
const Vec4V crossSegCenterSignedHalfExtentZ = SubtractScaled(crossSegCenterSignedHalfExtentZ1, centerDeltaY, signedSegHalfExtentsX);
const VecBoolV disjoint1X = IsGreaterThan(Abs(crossSegCenterSignedHalfExtentX), tX);
const VecBoolV disjoint1Y = IsGreaterThan(Abs(crossSegCenterSignedHalfExtentY), tY);
const VecBoolV disjoint1Z = IsGreaterThan(Abs(crossSegCenterSignedHalfExtentZ), tZ);
const VecBoolV disjointX = Or(disjoint0X, disjoint1X);
const VecBoolV disjointY = Or(disjoint0Y, disjoint1Y);
const VecBoolV disjointZ = Or(disjoint0Z, disjoint1Z);
const VecBoolV disjoint = Or(Or(disjointX, disjointY), disjointZ);
return InvertBits(disjoint);
}
CVehicle* CVehicleIntelligence::FindCarWeAreBehind(Vec3V_In vFwd, Vec3V_In vFrontBumperPos, ScalarV_In scMaxDistSq, ScalarV_InOut RESTRICT scDistSqOut) const
{
//Grab the vehicle.
aiAssert(m_pVehicle);
CVehicle* pVeh = m_pVehicle;
CVehicle* resultVehicle = NULL;
ScalarV resultDistSqV = scMaxDistSq;
aiAssert(pVeh->GetIntelligence() == this);
CTaskVehicleMissionBase* pActiveTask = GetActiveTask();
const bool bIsDriverPlayer = pActiveTask && CTaskTypes::IsPlayerDriveTask(pActiveTask->GetTaskType());
//Check if we are using the route to find the car we are behind.
if(CVehicleIntelligence::m_bUseRouteToFindCarBehind && !bIsDriverPlayer)
{
static const int numEntitiesToPrefetch = 4;
CVehicle* pOtherVehicles[CEntityScanner::MAX_NUM_ENTITIES];
int numOtherVehicles = 0;
const CVehicleScanner& scanner = GetVehicleScanner();
int numEntities = scanner.GetNumEntities();
int prefetchCtr = numEntitiesToPrefetch;
aiAssert(numEntities <= CEntityScanner::MAX_NUM_ENTITIES);
for(int entIndex = 0; entIndex < numEntities; entIndex++)
{
CEntity* pEntity = scanner.GetEntityByIndex(entIndex);
//Ensure the entity exists and does not match the vehicle.
if(pEntity && pEntity != pVeh)
{
CVehicle* pOtherVeh = static_cast<CVehicle*>(pEntity);
if(prefetchCtr > 0)
{
pOtherVeh->PrefetchPtrToMatrix();
pOtherVeh->PrefetchPtrToModelInfo();
prefetchCtr--;
}
pOtherVehicles[numOtherVehicles++] = pOtherVeh;
}
}
//Keep track of the path segments.
static const int sMaxPathSegments = 4;
// We do this now using vectors, all start X coordinates in one, all start Y in another, etc.
// If we needed to support more than four, we would need small arrays of vectors, which shouldn't
// be too hard to set up but would be good to not have to worry about.
CompileTimeAssert(sMaxPathSegments <= 4);
Vec4V pathSegmentsStartX(V_ZERO);
Vec4V pathSegmentsStartY(V_ZERO);
Vec4V pathSegmentsStartZ(V_ZERO);
Vec4V pathSegmentsEndX(V_ZERO);
Vec4V pathSegmentsEndY(V_ZERO);
Vec4V pathSegmentsEndZ(V_ZERO);
VecBoolV pathSegmentsValid(V_ZERO); // For keeping track of which elements are in use.
int iNumSegments = 0;
if(CVehicleIntelligence::m_bUseFollowRouteToFindCarBehind)
{
//Grab the follow route.
const CVehicleFollowRouteHelper* pFollowRouteHelper = GetFollowRouteHelper();
if(!pFollowRouteHelper)
{
return NULL;
}
//Grab the route points.
const CRoutePoint* pRoutePoints = pFollowRouteHelper->GetRoutePoints();
if(!pRoutePoints)
{
return NULL;
}
//Grab the number of points.
s16 iNumPoints = pFollowRouteHelper->GetNumPoints();
if(iNumPoints == 0)
{
return NULL;
}
//Grab the target point.
const int iterStart = Max((int)pFollowRouteHelper->GetCurrentTargetPoint(), 1);
aiAssert(iNumSegments < sMaxPathSegments); // We only check after we add, now.
//Generate the path segments.
for(s32 i = iterStart; i < iNumPoints; ++i)
{
//Grab the route points.
const CRoutePoint& rLastPoint = pRoutePoints[i - 1];
const CRoutePoint& rNextPoint = pRoutePoints[i];
//Make sure the lane has not changed a significant amount.
if(IsGreaterThanAll(Abs(Subtract(rLastPoint.GetLane(), rNextPoint.GetLane())), ScalarV(V_HALF)))
{
break;
}
//Calculate the start and end positions.
Vec3V vStart = rLastPoint.GetPosition();
Vec3V vEnd = rNextPoint.GetPosition();
//Account for the lane width.
vStart = Add(vStart, rLastPoint.GetLaneCenterOffset());
vEnd = Add(vEnd, rNextPoint.GetLaneCenterOffset());
// Note: possibly we could use CVehicleFollowRouteHelper::m_vPathWaypointsAndSpeed here, to
// access more compact memory and not have to add the lane center. Not sure to what extent
// we can be sure that it's always up to date, etc, so holding off on that for now.
//Append the start and end positions, y shifting the coordinates into the vectors.
pathSegmentsStartX = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsStartX, Vec4V(vStart.GetX()));
pathSegmentsStartY = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsStartY, Vec4V(vStart.GetY()));
pathSegmentsStartZ = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsStartZ, Vec4V(vStart.GetZ()));
pathSegmentsEndX = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsEndX, Vec4V(vEnd.GetX()));
pathSegmentsEndY = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsEndY, Vec4V(vEnd.GetY()));
pathSegmentsEndZ = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsEndZ, Vec4V(vEnd.GetZ()));
pathSegmentsValid.SetIntrin128(GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(Vec4V(pathSegmentsValid), Vec4V(V_T_T_T_T)).GetIntrin128());
if(++iNumSegments >= sMaxPathSegments)
{
break;
}
//Check if the end position has exceeded the maximum distance.
ScalarV scDistSq = DistSquared(vFrontBumperPos, vEnd);
if(IsGreaterThanAll(scDistSq, scMaxDistSq))
{
break;
}
}
}
else if(CVehicleIntelligence::m_bUseNodeListToFindCarBehind)
{
//Grab the node list.
CVehicleNodeList* pNodeList = GetNodeList();
if(!pNodeList)
{
return NULL;
}
//Initialize the current node.
s32 iCurNode = pNodeList->GetTargetNodeIndex();
Assertf(iCurNode < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED, "Invalid current node.");
//Initialize the lane.
s32 iLane = pNodeList->GetPathLaneIndex(iCurNode);
aiAssert(iNumSegments < sMaxPathSegments); // We only check after we add, now.
//Generate the path segments.
for(s32 i = iCurNode; i < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED; ++i)
{
//Grab the last node.
const CPathNode* pLastNode = pNodeList->GetPathNode(i - 1);
if(!pLastNode)
{
break;
}
//Grab the next node.
const CPathNode* pNode = pNodeList->GetPathNode(i);
if(!pNode)
{
break;
}
//Find the link index.
s16 iLinkIndex = -1;
ThePaths.FindNodesLinkIndexBetween2Nodes(pLastNode, pNode, iLinkIndex);
Assertf(iLinkIndex >= 0, "Invalid link index.");
//Grab the link.
const CPathNodeLink* pLink = &ThePaths.GetNodesLink(pLastNode, iLinkIndex);
Assertf(pLink, "Invalid link.");
//Calculate the lane center offset.
float fLaneCenterOffset = ThePaths.GetLinksLaneCentreOffset(*pLink, iLane);
//Calculate the lane center offset vector.
Vector2 vSide = Vector2(pNode->GetPos().y - pLastNode->GetPos().y, pLastNode->GetPos().x - pNode->GetPos().x);
vSide.Normalize();
Vector3 vLaneCenterOffset((vSide.x * fLaneCenterOffset),(vSide.y * fLaneCenterOffset), 0.0f);
//Calculate the start and end positions.
Vec3V vStart = VECTOR3_TO_VEC3V(pLastNode->GetPos() + vLaneCenterOffset);
Vec3V vEnd = VECTOR3_TO_VEC3V(pNode->GetPos() + vLaneCenterOffset);
//Append the start and end positions.
pathSegmentsStartX = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsStartX, Vec4V(vStart.GetX()));
pathSegmentsStartY = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsStartY, Vec4V(vStart.GetY()));
pathSegmentsStartZ = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsStartZ, Vec4V(vStart.GetZ()));
pathSegmentsEndX = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsEndX, Vec4V(vEnd.GetX()));
pathSegmentsEndY = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsEndY, Vec4V(vEnd.GetY()));
pathSegmentsEndZ = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(pathSegmentsEndZ, Vec4V(vEnd.GetZ()));
pathSegmentsValid.SetIntrin128(GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(Vec4V(pathSegmentsValid), Vec4V(V_T_T_T_T)).GetIntrin128());
if(++iNumSegments >= sMaxPathSegments)
{
break;
}
//Check if the end position has exceeded the maximum distance.
ScalarV scDistSq = DistSquared(vFrontBumperPos, vEnd);
if(IsGreaterThanAll(scDistSq, scMaxDistSq))
{
break;
}
}
}
//Count the segments.
if(iNumSegments == 0)
{
return NULL;
}
// Raise the segments up a bit, so we don't go underneath any bounding boxes.
const Vec4V raiseV = Vec4V(V_HALF); // Bumped up to 0.5 from 0.25, since the bounding boxes we get now tend to not extend as far down.
pathSegmentsStartZ = Add(pathSegmentsStartZ, raiseV);
pathSegmentsEndZ = Add(pathSegmentsEndZ, raiseV);
#if __BANK
if(CVehicleIntelligence::m_bDisplayDebugCarBehindSearch && CVehicleIntelligence::AllowCarAiDebugDisplayForThisVehicle(pVeh))
{
for(s32 i = 0; i < iNumSegments; ++i)
{
const int index = 3 - i;
const Vec3V startV(pathSegmentsStartX[index], pathSegmentsStartY[index], pathSegmentsStartZ[index]);
const Vec3V endV(pathSegmentsEndX[index], pathSegmentsEndY[index], pathSegmentsEndZ[index]);
grcDebugDraw::Line(startV, endV, Color_purple, Color_purple, -1);
}
}
#endif
// Scan the nearby vehicles.
for(int vehIndex = 0; vehIndex < numOtherVehicles; vehIndex++)
{
const int prefetchIndex1 = vehIndex + numEntitiesToPrefetch;
if(prefetchIndex1 < numOtherVehicles)
{
CVehicle* prefetchVeh = pOtherVehicles[prefetchIndex1];
prefetchVeh->PrefetchPtrToMatrix();
prefetchVeh->PrefetchPtrToModelInfo();
}
const int prefetchIndex2 = vehIndex + 2;
if(prefetchIndex2 < numOtherVehicles)
{
CVehicle* prefetchVeh = pOtherVehicles[prefetchIndex2];
PrefetchDC((void*)&prefetchVeh->GetMatrixRef());
PrefetchDC((void*)((u8*)prefetchVeh->GetBaseModelInfo() + 32 /* OffsetOf(m_BoundingBox) */));
}
//Grab the other vehicle.
CVehicle* pOtherVeh = pOtherVehicles[vehIndex];
// Ensure we are behind the other vehicle.
// Note: we pass in resultDistSqV here, may as well let AreWeBehindCar()
// reject vehicles further away than the closest we've found so far.
ScalarV scDistSq;
if(!AreWeBehindCar(pOtherVeh, vFwd, vFrontBumperPos, resultDistSqV, scDistSq, ScalarV(V_ZERO)))
{
continue;
}
// Ensure the other vehicle is a better potential result.
// - no longer needed, already rejected by AreWeBehindCar.
// if(resultVehicle && IsGreaterThanAll(scDistSq, resultDistSqV))
// {
// continue;
// }
aiAssert(!resultVehicle || !IsGreaterThanAll(scDistSq, resultDistSqV));
//Grab the other forward direction.
const Vec3V vOtherFwd = pOtherVeh->GetVehicleForwardDirection();
// Grab the min/max bounding box values.
// We used to get these like this:
// otherBoundBoxMinV = RCC_VEC3V(pOtherVeh->GetBoundingBoxMin());
// otherBoundBoxMaxV = RCC_VEC3V(pOtherVeh->GetBoundingBoxMax());
// but there is quiet a bit more overhead that way than using the bounding
// box from the model info, which should generally be accurate enough.
const CBaseModelInfo* pOtherVehModelInfo = pOtherVeh->GetBaseModelInfo();
Vec3V otherBoundBoxMinV = RCC_VEC3V(pOtherVehModelInfo->GetBoundingBoxMin());
Vec3V otherBoundBoxMaxV = RCC_VEC3V(pOtherVehModelInfo->GetBoundingBoxMax());
//Fatten up the bounding box a bit.
const Vec3V fattenV(V_QUARTER);
otherBoundBoxMinV = Subtract(otherBoundBoxMinV, fattenV);
otherBoundBoxMaxV = Add(otherBoundBoxMaxV, fattenV);
#if __BANK
if(CVehicleIntelligence::m_bDisplayDebugCarBehindSearch && CVehicleIntelligence::AllowCarAiDebugDisplayForThisVehicle(pVeh))
{
grcDebugDraw::BoxOriented(otherBoundBoxMinV, otherBoundBoxMaxV, pOtherVeh->GetMatrix(), Color_purple, false, -1);
}
#endif
// Test each segment against the box.
// Since we currently only have up to four segments and they all fit in one
// set of vectors, there is no actual loop, but we would have to do one here if
// we needed more than four.
do
{
// What we do here is to test all (up to) four segments in parallel, which
// should be making efficient use of the vector pipeline.
//Calculate the segments.
const Vec4V segmentXV = Subtract(pathSegmentsEndX, pathSegmentsStartX);
const Vec4V segmentYV = Subtract(pathSegmentsEndY, pathSegmentsStartY);
const Vec4V segmentZV = Subtract(pathSegmentsEndZ, pathSegmentsStartZ);
const Vec4V segmentXSqV = Scale(segmentXV, segmentXV);
const Vec4V segmentXYSqV = AddScaled(segmentXSqV, segmentYV, segmentYV);
const Vec4V segmentMagSqV = AddScaled(segmentXYSqV, segmentZV, segmentZV);
const Vec4V otherFwdXV = Vec4V(vOtherFwd.GetX());
const Vec4V otherFwdYV = Vec4V(vOtherFwd.GetY());
const Vec4V otherFwdZV = Vec4V(vOtherFwd.GetZ());
//The vehicle must be pointed in the same general direction as the segment.
const Vec4V dotXV = Scale(segmentXV, otherFwdXV);
const Vec4V dotXYV = AddScaled(dotXV, segmentYV, otherFwdYV);
const Vec4V dotV = AddScaled(dotXYV, segmentZV, otherFwdZV);
const Vec4V dotSqV = Scale(dotV, dotV);
const Vec4V dotThresholdSqV = Scale(segmentMagSqV, ScalarV(V_QUARTER)); // cosine of the angle has to be more than 0.5
const ScalarV zeroV(V_ZERO);
const VecBoolV pointingInSameDirectionV = And(IsGreaterThanOrEqual(dotV, Vec4V(zeroV)), IsGreaterThanOrEqual(dotSqV, dotThresholdSqV));
//Transform the segment into car space.
const Mat34V& mtrx = pOtherVeh->GetMatrixRef();
const Vec3V mtrxA = mtrx.GetCol0();
const Vec3V mtrxB = mtrx.GetCol1();
const Vec3V mtrxC = mtrx.GetCol2();
const Vec3V mtrxD = mtrx.GetCol3();
const ScalarV mtrxDXV = mtrxD.GetX();
const ScalarV mtrxDYV = mtrxD.GetY();
const ScalarV mtrxDZV = mtrxD.GetZ();
const Vec4V tmpXV = Subtract(pathSegmentsStartX, Vec4V(mtrxDXV));
const Vec4V tmpYV = Subtract(pathSegmentsStartY, Vec4V(mtrxDYV));
const Vec4V tmpZV = Subtract(pathSegmentsStartZ, Vec4V(mtrxDZV));
const ScalarV mtrxAXV = mtrxA.GetX();
const ScalarV mtrxAYV = mtrxA.GetY();
const ScalarV mtrxAZV = mtrxA.GetZ();
const Vec4V start1LXxV = Scale(tmpXV, mtrxAXV);
const Vec4V start1LXxyV = AddScaled(start1LXxV, tmpYV, mtrxAYV);
const Vec4V start1LXV = AddScaled(start1LXxyV, tmpZV, mtrxAZV);
const Vec4V segment1LXxV = Scale(segmentXV, mtrxAXV);
const Vec4V segment1LXxyV = AddScaled(segment1LXxV, segmentYV, mtrxAYV);
const Vec4V segment1LXV = AddScaled(segment1LXxyV, segmentZV, mtrxAZV);
const ScalarV mtrxBXV = mtrxB.GetX();
const ScalarV mtrxBYV = mtrxB.GetY();
const ScalarV mtrxBZV = mtrxB.GetZ();
const Vec4V start1LYxV = Scale(tmpXV, mtrxBXV);
const Vec4V start1LYxyV = AddScaled(start1LYxV, tmpYV, mtrxBYV);
const Vec4V start1LYV = AddScaled(start1LYxyV, tmpZV, mtrxBZV);
const Vec4V segment1LYxV = Scale(segmentXV, mtrxBXV);
const Vec4V segment1LYxyV = AddScaled(segment1LYxV, segmentYV, mtrxBYV);
const Vec4V segment1LYV = AddScaled(segment1LYxyV, segmentZV, mtrxBZV);
const ScalarV mtrxCXV = mtrxC.GetX();
const ScalarV mtrxCYV = mtrxC.GetY();
const ScalarV mtrxCZV = mtrxC.GetZ();
const Vec4V start1LZxV = Scale(tmpXV, mtrxCXV);
const Vec4V start1LZxyV = AddScaled(start1LZxV, tmpYV, mtrxCYV);
const Vec4V start1LZV = AddScaled(start1LZxyV, tmpZV, mtrxCZV);
const Vec4V segment1LZxV = Scale(segmentXV, mtrxCXV);
const Vec4V segment1LZxyV = AddScaled(segment1LZxV, segmentYV, mtrxCYV);
const Vec4V segment1LZV = AddScaled(segment1LZxyV, segmentZV, mtrxCZV);
// Test the segments.
VecBoolV segsIntersectBoxV = sTestSegmentToBoxMinMax(start1LXV, start1LYV, start1LZV,
segment1LXV, segment1LYV, segment1LZV,
otherBoundBoxMinV, otherBoundBoxMaxV);
VecBoolV matchesV = And(pathSegmentsValid, And(pointingInSameDirectionV, segsIntersectBoxV));
if(!IsEqualIntAll(matchesV, VecBoolV(zeroV.GetIntrin128())))
{
//Assign the result.
resultVehicle = pOtherVeh;
resultDistSqV = scDistSq;
// No need to continue testing segments.
// - but no real loop to break out of now.
break;
}
} while(0);
}
}
else if(CVehicleIntelligence::m_bUseScannerToFindCarBehind || bIsDriverPlayer)
{
//Scan the nearby vehicles.
CEntityScannerIterator entityList = GetVehicleScanner().GetIterator();
for(CEntity* pEntity = entityList.GetFirst(); pEntity; pEntity = entityList.GetNext())
{
//Ensure the entity does not match the vehicle.
if(pEntity == pVeh)
{
continue;
}
//Grab the other vehicle.
CVehicle* pOtherVeh = static_cast<CVehicle *>(pEntity);
//Ensure we are behind the other vehicle.
ScalarV scDistSq;
if(!AreWeBehindCar(pOtherVeh, vFwd, vFrontBumperPos, scMaxDistSq, scDistSq, ScalarV(V_ZERO)))
{
continue;
}
//Ensure the other vehicle is a better potential result.
if(resultVehicle && IsGreaterThanAll(scDistSq, resultDistSqV))
{
continue;
}
//Assign the result.
resultVehicle = pOtherVeh;
resultDistSqV = scDistSq;
}
}
else if(CVehicleIntelligence::m_bUseProbesToFindCarBehind)
{
//Calculate the probe end position.
Vec3V vEnd = vFrontBumperPos + Scale(vFwd, ScalarVFromF32(25.0f));
#if __BANK
if (CVehicleIntelligence::m_bDisplayDebugCarBehindSearch && CVehicleIntelligence::AllowCarAiDebugDisplayForThisVehicle(pVeh))
{
grcDebugDraw::Line(VEC3V_TO_VECTOR3(vFrontBumperPos), VEC3V_TO_VECTOR3(vEnd), Color_purple, Color_purple, -1);
}
#endif
//Shoot a probe from the front bumper position in the forward direction.
WorldProbe::CShapeTestFixedResults<> probeResult;
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&probeResult);
probeDesc.SetStartAndEnd(VEC3V_TO_VECTOR3(vFrontBumperPos), VEC3V_TO_VECTOR3(vEnd));
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_BOX_VEHICLE_TYPE);
probeDesc.SetExcludeEntity(pVeh);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
//Grab the entity.
CEntity* pEntity = CPhysics::GetEntityFromInst(probeResult[0].GetHitInst());
Assertf(pEntity, "Entity is not valid.");
Assertf(pEntity->GetIsTypeVehicle(), "Entity is not a vehicle.");
//Grab the other vehicle.
CVehicle* pOtherVeh = static_cast<CVehicle *>(pEntity);
//Ensure we are behind the other vehicle.
ScalarV scDistSq;
if(AreWeBehindCar(pOtherVeh, vFwd, vFrontBumperPos, scMaxDistSq, scDistSq, ScalarV(V_ZERO)))
{
//Assign the result.
resultVehicle = pOtherVeh;
resultDistSqV = scDistSq;
}
}
}
scDistSqOut = resultDistSqV;
return resultVehicle;
}
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::GetOverriddenGameplayCameraSettings(tGameplayCameraSettings& settings) const
{
//Find the lowest/simplest task in the hierarchy that overrides the camera and use its settings.
aiTask *task = m_TaskManager.GetActiveLeafTask(VEHICLE_TASK_TREE_PRIMARY);
CTask* activeTask = static_cast<CTask*>(task);
while(activeTask)
{
tGameplayCameraSettings tempSettings(settings);
activeTask->GetOverriddenGameplayCameraSettings(tempSettings);
//NOTE: A task must override the camera in order to override the other settings.
if(tempSettings.m_CameraHash)
{
settings = tempSettings;
break;
}
activeTask = activeTask->GetParent();
}
}
#if 0
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindClosestIndex
// PURPOSE : Given a coordinate this function will return the index of a
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
bool CVehPointPath::FindClosestIndex(const Vector3 &Crs, int &index, float &distOnLine)
{
if (m_numPoints <= 1)
{
return false;
}
float closestDistSqr = (9999999.9f * 9999999.9f);
index = 0;
distOnLine = 0.0f;
for (s32 i = 0; i < m_numPoints-1; i++)
{
float lineDirX = m_points[i+1].x - m_points[i].x;
float lineDirY = m_points[i+1].y - m_points[i].y;
float deltaX = Crs.x - m_points[i].x;
float deltaY = Crs.y - m_points[i].y;
float fDistanceAlongLine = deltaX * lineDirX + deltaY * lineDirY;
float lineDirLengthSqr = lineDirX*lineDirX + lineDirY*lineDirY;
fDistanceAlongLine /= lineDirLengthSqr;
float distSqr;
if (fDistanceAlongLine <= 0.0f)
{
float dX = Crs.x - m_points[i].x;
float dY = Crs.y - m_points[i].y;
distSqr = dX * dX + dY * dY;
fDistanceAlongLine = 0.0f;
}
else if (fDistanceAlongLine >= 1.0f)
{
float dX = Crs.x - m_points[i+1].x;
float dY = Crs.y - m_points[i+1].y;
distSqr = dX * dX + dY * dY;
fDistanceAlongLine = 1.0f;
}
else
{
float perpX = lineDirY;
float perpY = -lineDirX;
float dX = Crs.x - m_points[i].x;
float dY = Crs.y - m_points[i].y;
distSqr = (perpX * dX + perpY * dY);
distSqr = (distSqr * distSqr) / lineDirLengthSqr;
}
if (distSqr < closestDistSqr)
{
closestDistSqr = distSqr;
index = i;
distOnLine = fDistanceAlongLine;
}
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Shift
// PURPOSE : Given a coordinate this function will return the index of a
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
void CVehPointPath::Shift(int number)
{
if (number == 0)
{
return;
}
Assert(number <= m_numPoints);
number = MIN(number, m_numPoints);
m_numPoints -= number;
for (int i = 0; i < m_numPoints; i++)
{
m_points[i].x = m_points[i+number].x;
m_points[i].y = m_points[i+number].y;
}
}
#if __BANK
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CVehPointPath::Display
// PURPOSE : A debug function to display point paths. (Collection of points that a vehicle
// has passed and that can be used to drive back out of trouble once stuck.
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
void CVehPointPath::RenderDebug() const
{
rage::Color32 col = rage::Color32(255, 200, 255, 255);
//rage::Color32 col2 = rage::Color32(128, 128, 128, 128);
for (s32 i = 0; i < m_numPoints; i++)
{
grcDebugDraw::Line(Vector3(m_points[i].x, m_points[i].y, 0.0f),
Vector3(m_points[i].x, m_points[i].y, 30.0f),
col);
}
for (s32 i = 0; i < m_numPoints-1; i++)
{
grcDebugDraw::Line(Vector3(m_points[i].x, m_points[i].y, CGameWorld::FindLocalPlayerCoors().z+3.0f),
Vector3(m_points[i+1].x, m_points[i+1].y, CGameWorld::FindLocalPlayerCoors().z+3.0f),
col);
}
}
#endif // __BANK
#endif //0
#if __BANK
float CVehicleIntelligence::ms_fDisplayVehicleAIRange = 30.0f;
bool CVehicleIntelligence::m_bDisplayCarAiDebugInfo = false;
int CVehicleIntelligence::m_eFilterVehicleDisplayByType = 0;
bool CVehicleIntelligence::ms_debugDisplayFocusVehOnly = false;
bool CVehicleIntelligence::m_bDisplayCarAddresses = false;
bool CVehicleIntelligence::m_bDisplayDirtyCarPilons = false;
bool CVehicleIntelligence::m_bDisplayCarAiTaskHistory = false;
bool CVehicleIntelligence::m_bDisplayVehicleFlagsInfo = false;
bool CVehicleIntelligence::m_bDisplayCarAiTaskInfo = false;
bool CVehicleIntelligence::m_bFindCoorsAndSpeed = false;
bool CVehicleIntelligence::m_bDisplayCarFinalDestinations = false;
bool CVehicleIntelligence::m_bDisplayVehicleRecordingInfo = false;
bool CVehicleIntelligence::m_bDisplayCarAiTaskDetails = false;
bool CVehicleIntelligence::m_bDisplayDebugInfoOkToDropOffPassengers = false;
bool CVehicleIntelligence::m_bDisplayDebugJoinWithRoadSystem = false;
bool CVehicleIntelligence::m_bDisplayAvoidanceCache = false;
bool CVehicleIntelligence::m_bDisplayRouteInterceptionTests = false;
bool CVehicleIntelligence::m_bHideFailedRouteInterceptionTests = false;
bool CVehicleIntelligence::m_bDisplayDebugFutureNodes = false;
bool CVehicleIntelligence::m_bDisplayDebugFutureFollowRoute = false;
bool CVehicleIntelligence::m_bDisplayDrivableExtremes = false;
bool CVehicleIntelligence::m_bDisplayControlDebugInfo = false;
bool CVehicleIntelligence::m_bDisplayAILowLevelControlInfo = false;
bool CVehicleIntelligence::m_bDisplayControlTransmissionInfo = false;
bool CVehicleIntelligence::m_bDisplayStuckDebugInfo = false;
bool CVehicleIntelligence::m_bDisplaySlowDownForBendDebugInfo = false;
bool CVehicleIntelligence::m_bDisplayDebugSlowForTraffic = false;
bool CVehicleIntelligence::m_bDisplayDebugCarBehind = false;
bool CVehicleIntelligence::m_bDisplayDebugCarBehindSearch = false;
bool CVehicleIntelligence::m_bDisplayDebugTailgate = false;
bool CVehicleIntelligence::m_bAICarHandlingDetails = false;
bool CVehicleIntelligence::m_bAICarHandlingCurve = false;
bool CVehicleIntelligence::m_bOnlyForPlayerVehicle = false;
bool CVehicleIntelligence::m_bAllCarsStop = false;
bool CVehicleIntelligence::m_bDisplayInterestingDrivers = false;
bool CVehicleIntelligence::m_bFindCarBehind = true;
bool CVehicleIntelligence::m_bDisplayStatusInfo = false;
#endif
float CVehicleIntelligence::ms_fMoveSpeedBelowWhichToCheckIfStuck = 0.4f;
#if (RSG_PC || RSG_DURANGO || RSG_ORBIS)
bool CVehicleIntelligence::m_bTailgateOtherCars = false;
#else
bool CVehicleIntelligence::m_bTailgateOtherCars = true;
#endif // (RSG_PC || RSG_DURANGO || RSG_ORBIS)
bool CVehicleIntelligence::m_bUseRouteToFindCarBehind = true;
bool CVehicleIntelligence::m_bUseScannerToFindCarBehind = false;
bool CVehicleIntelligence::m_bUseProbesToFindCarBehind = false;
bool CVehicleIntelligence::m_bUseFollowRouteToFindCarBehind = true;
bool CVehicleIntelligence::m_bUseNodeListToFindCarBehind = false;
bool CVehicleIntelligence::m_bEnableNewJoinToRoadInAnyDir = true;
bool CVehicleIntelligence::m_bEnableThreePointTurnCenterProbe = true;
#if __DEV
s32 Debug_Mission = MISSION_CRUISE;
float Debug_CruiseSpeed = 10.0f;
Vector3 Debug_TargetPos = Vector3(0.0f, 0.0f, 0.0f);
bool Debug_DoEverythingInReverse = false;
bool Debug_UseLastCreatedVehicleAsTarget = false;
u32 Debug_DrivingMode = DMode_StopForCars;
bool Debug_bHasHeliRequestedOrientation = false;
float Debug_HeliRequestedOrientation = 0.0f;
s32 Debug_FlightHeight = 30;
s32 Debug_MinHeightAboveTerrain = 20;
float Debug_TargetReachedDist = 4.0f;
float Debug_StraightLineDist = sVehicleMissionParams::DEFAULT_SWITCH_TO_STRAIGHT_LINE_DIST;
///////////////////////////////////////////////////////////////////////////
void CopySettingsIntoVeh(CVehicle* pVeh)
{
Assert(pVeh);
// First make sure the vehicle has a driver(or it won't go anywhere)
CVehiclePopulation::AddDriverAndPassengersForVeh(pVeh, 0, CVehicleAILodManager::ComputeRealDriversAndPassengersRemaining());
pVeh->PopTypeSet(POPTYPE_TOOL);
if(pVeh->GetDriver())
{
pVeh->GetDriver()->m_PedConfigFlags.SetCantBeKnockedOffVehicle( KNOCKOFFVEHICLE_NEVER );
pVeh->GetDriver()->PopTypeSet(POPTYPE_TOOL);
pVeh->GetDriver()->SetDefaultDecisionMaker();
pVeh->GetDriver()->SetCharParamsBasedOnManagerType();
pVeh->GetDriver()->GetPedIntelligence()->SetDefaultRelationshipGroup();
CTaskControlVehicle *pTask = NULL;
CTask* pCarTask = rage_new CTaskVehicleStop();
pTask = rage_new CTaskControlVehicle(pVeh, pCarTask);
pVeh->GetDriver()->GetPedIntelligence()->AddTaskAtPriority(pTask, PED_TASK_PRIORITY_DEFAULT);
}
aiTask* pNewTask = CVehicleIntelligence::GetTaskFromMissionIdentifier(pVeh,
(VehMissionType)Debug_Mission,
NULL,
&Debug_TargetPos,
Debug_DrivingMode,
Debug_TargetReachedDist,
(u16)Debug_StraightLineDist,
Debug_CruiseSpeed,
false,
Debug_DoEverythingInReverse);
CTaskVehicleMissionBase* pVehMissionTask= static_cast<CTaskVehicleMissionBase*>(pNewTask);
// For certain missions we assume the player is the target.
switch(Debug_Mission)
{
case MISSION_BLOCK:
case MISSION_FOLLOW:
case MISSION_ATTACK:
case MISSION_RAM:
case MISSION_FLEE:
case MISSION_CIRCLE:
case MISSION_ESCORT_LEFT:
case MISSION_ESCORT_RIGHT:
case MISSION_ESCORT_REAR:
case MISSION_ESCORT_FRONT:
if(Debug_UseLastCreatedVehicleAsTarget && CVehicleFactory::GetPreviouslyCreatedVehicle())
{
if(CVehicleFactory::GetPreviouslyCreatedVehicle()->GetDriver())
{
pVehMissionTask->SetTargetEntity(CVehicleFactory::GetPreviouslyCreatedVehicle()->GetDriver());
}
else
{
pVehMissionTask->SetTargetEntity(CVehicleFactory::GetPreviouslyCreatedVehicle());
}
}
else
{
pVehMissionTask->SetTargetEntity(FindPlayerPed());
}
break;
}
pVeh->GetIntelligence()->GetTaskManager()->SetTask(PED_TASK_TREE_PRIMARY, pNewTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
CVehicleNodeList * pNodeList = pVeh->GetIntelligence()->GetNodeList();
if(pNodeList)
pNodeList->ClearPathNodes();
// Also set a radar blip for this car to make it easier to follow.
CEntityPoolIndexForBlip vehicleIndex(pVeh, BLIP_TYPE_CAR);
CMiniMap::CreateBlip(true, BLIP_TYPE_CAR, vehicleIndex, BLIP_DISPLAY_BOTH, "car ai debug");
}
///////////////////////////////////////////////////////////////////////////
void CopySettingsIntoFocusVehs()
{
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX; ++i)
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(i);
if(pEnt && pEnt->GetIsTypeVehicle())
{
CVehicle* pVeh = static_cast<CVehicle*>(pEnt);
Assert(pVeh);
CopySettingsIntoVeh(pVeh);
}
}
}
///////////////////////////////////////////////////////////////////////////
void CopySettingsIntoLastCreatedVeh()
{
CVehicle* pVeh = CVehicleFactory::GetCreatedVehicle();
if(!pVeh)
{
return;
}
CopySettingsIntoVeh(pVeh);
}
///////////////////////////////////////////////////////////////////////////
void CopySettingsFromVeh(CVehicle* ASSERT_ONLY(pVeh))
{
Assert(pVeh);
/* TODO
CVehMission* pMission = pVeh->GetIntelligence()->FindUberMissionForCar();
//Assert(pMission);
if(pMission)
{
Debug_Mission = pMission->m_missionType;
Debug_CruiseSpeed = pMission->m_cruiseSpeed;
Debug_TargetPos = pMission->m_targetPos;
Debug_DoEverythingInReverse = pMission->m_bDoEverythingInReverse;
Debug_DrivingMode = pMission->m_drivingMode;
Debug_bHasHeliRequestedOrientation = pMission->m_bHasHeliRequestedOrientation;
Debug_HeliRequestedOrientation = pMission->m_heliRequestedOrientation;
Debug_FlightHeight = pMission->m_flightHeight;
Debug_MinHeightAboveTerrain = pMission->m_minHeightAboveTerrain;
Debug_TargetReachedDist = pMission->m_targetArriveDist;
Debug_StraightLineDist = pMission->m_straightLineDist;
}
*/
}
void CopySettingsFromFocusVeh()
{
CVehicle* pVeh = CVehicleDebug::GetFocusVehicle();
if(!pVeh)
{
return;
}
Assert(pVeh);
CopySettingsFromVeh(pVeh);
}
void CopySettingsFromLastCreatedVeh()
{
CVehicle* pVeh = CVehicleFactory::GetCreatedVehicle();
if(!pVeh)
{
return;
}
CopySettingsFromVeh(pVeh);
}
void AllCarsPullOver()
{
CVehicle::Pool *VehiclePool = CVehicle::GetPool();
CVehicle* pVeh;
s32 i = (s32) VehiclePool->GetSize();
while(i--)
{
pVeh = VehiclePool->GetSlot(i);
if(pVeh && !pVeh->IsNetworkClone())
{
Vector3 dummyDir;
sVehicleMissionParams params;
params.SetTargetPosition(VEC3V_TO_VECTOR3(pVeh->GetTransform().GetPosition()));
aiTask *pCarTask = rage_new CTaskVehicleParkNew(params,dummyDir,CTaskVehicleParkNew::Park_PullOver, 0.08f);
pVeh->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pCarTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
}
}
}
#endif // __DEV
#if __DEV
void CVehicleIntelligence::AddBankWidgets(bkBank& bank)
{
bank.PushGroup("Set Autopilot", false);
bank.AddCombo("Select Mission", &Debug_Mission, MISSION_LAST, CVehicleIntelligence::MissionStrings, NullCB);
bank.AddSlider("Select Cruise speed", &Debug_CruiseSpeed, 0, 300, 1);
bank.AddSlider("TargetPos X", &Debug_TargetPos.x, -4000.0f, 4000.0f, 10.0f);
bank.AddSlider("TargetPos Y", &Debug_TargetPos.y, -4000.0f, 4000.0f, 10.0f);
bank.AddSlider("TargetPos Z", &Debug_TargetPos.z, -4000.0f, 4000.0f, 10.0f);
bank.AddToggle("Do everything in reverse", &Debug_DoEverythingInReverse);
bank.AddToggle("Use last created vehicle as target", &Debug_UseLastCreatedVehicleAsTarget);
bank.AddSlider("Select driving mode", &Debug_DrivingMode, 0, 0xFFFFFFFF, 1);
bank.AddSlider("TargetReachedDist", &Debug_TargetReachedDist, -1, 10000, 1);
bank.AddSlider("StraightLineDist", &Debug_StraightLineDist, -1, 10000, 1);
bank.AddToggle("HasHeliRequestedOrientation", &Debug_bHasHeliRequestedOrientation);
bank.AddSlider("HeliRequestedOrientation", &Debug_HeliRequestedOrientation, -1.0f, 6.28f, 1.0f);
bank.AddSlider("FlightHeight", &Debug_FlightHeight, 0, 10000, 5);
bank.AddSlider("MinHeightAboveTerrain", &Debug_MinHeightAboveTerrain, 0, 10000, 5);
bank.AddButton("Copy settings into focus veh(s)", datCallback(CFA(CopySettingsIntoFocusVehs)));
bank.AddButton("Copy settings from focus veh", datCallback(CFA(CopySettingsFromFocusVeh)));//not hooked up yet
bank.AddButton("Copy settings into last created veh", datCallback(CFA(CopySettingsIntoLastCreatedVeh)));
bank.AddButton("Copy settings from last created veh", datCallback(CFA(CopySettingsFromLastCreatedVeh)));
bank.AddButton("Toggle Scripted Autopilot On Focus Vehicle", CVehicle::ToggleScriptedAutopilot);
bank.PopGroup();
bank.AddButton("Make all cars pull over", datCallback(CFA(AllCarsPullOver)));
bank.AddToggle("Make all cars stop", &CVehicleIntelligence::m_bAllCarsStop);
}
#endif // __BANK
#if __BANK
bool CVehicleIntelligence::AllowCarAiDebugDisplayForThisVehicle(const CVehicle* const pVeh)
{
bool displayDebugInfo =(!CVehicleIntelligence::ms_debugDisplayFocusVehOnly ||(CDebugScene::FocusEntities_IsInGroup(pVeh)));
if(!displayDebugInfo)
{
return false;
}
if ( CVehicleIntelligence::m_eFilterVehicleDisplayByType-1 != VEHICLE_TYPE_NONE
&& CVehicleIntelligence::m_eFilterVehicleDisplayByType-1 != pVeh->GetVehicleType() )
{
return false;
}
const Vector3 vVehiclePosition = VEC3V_TO_VECTOR3(pVeh->GetTransform().GetPosition());
Vector3 vDiff = vVehiclePosition - camInterface::GetPos();
float fDist = vDiff.Mag();
if(fDist >= CVehicleIntelligence::ms_fDisplayVehicleAIRange && !CVehicleIntelligence::ms_debugDisplayFocusVehOnly && !CVehicleIntelligence::m_bOnlyForPlayerVehicle)
return false;
if(!CVehicleIntelligence::m_bOnlyForPlayerVehicle)
{
return true;
}
CPed * pPlayer = CGameWorld::FindLocalPlayer();
Assert(pPlayer);
if(pPlayer->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && (pPlayer->GetMyVehicle() == pVeh))
{
return true;
}
else
{
return false;
}
}
#endif // __BANK
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindNodeOffsetForCar
// PURPOSE : To give traffic a bit more of a chaotic feel the cars randomise the
// coordinates that they go to by a small amount.
// This function finds the offset that this particular car uses.
// This offset gets applied to each autopilotNodeIndex.
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::FindNodeOffsetForCarFromRandomSeed(u16 randomSeed, Vector3 &Result)
{
float Angle =(randomSeed & 255) *(6.28f / 256.0f);
Result = Vector3(0.7f * rage::Sinf(Angle), 0.7f * rage::Cosf(Angle), 0.0f);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CalculateSpeedMultiplierBend
// PURPOSE : Works out how fast we want to go considering the orientation of the path
// ahead of us.
/////////////////////////////////////////////////////////////////////////////////
float CVehicleIntelligence::CalculateSpeedMultiplierBend(const CVehicle* const pVeh, float vehDriveOrientation, float StraightLineSpeed)
{
CVehicleNodeList * pNodeList = pVeh->GetIntelligence()->GetNodeList();
Assertf(pNodeList, "CVehicleIntelligence::CalculateSpeedMultiplierBend - vehicle has no node list");
if(!pNodeList)
return 1.0f;
static float maxSpeedMult;
maxSpeedMult = 1.0f;
static float maxBrakeDist = 60.0f;
const Vector3 vVehiclePosition = VEC3V_TO_VECTOR3(pVeh->GetTransform().GetPosition());
s32 iOldNode = Clamp(pNodeList->GetTargetNodeIndex() - 1, 0, CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED);
// This is the new method inherited from the racing ai
for(s32 node = iOldNode; node < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-2; node++) // This used to start at NODE_NEW. This would cause cars to accelerate on junctions even though they hadn't cleared the junction yet.
{
s32 node2 = node+1;
CNodeAddress nodeAddress = pNodeList->GetPathNodeAddr(node);
CNodeAddress node2Address = pNodeList->GetPathNodeAddr(node2);
if(nodeAddress.IsEmpty() || node2Address.IsEmpty())
{
continue;
}
if(!ThePaths.IsRegionLoaded(nodeAddress) || !ThePaths.IsRegionLoaded(node2Address))
{
continue;
}
const CPathNode *pNode = ThePaths.FindNodePointer(nodeAddress);
const CPathNode *pNode2 = ThePaths.FindNodePointer(node2Address);
Vector3 coors, coors2;
pNode->GetCoors(coors);
float dist =(coors - vVehiclePosition).XYMag();
dist = rage::Max(dist - 20.0f, 0.0f); // A certain area of max braking. We never quite get there anyway.
if(dist > maxBrakeDist)
{
node = 999;
}
else
{
pNode2->GetCoors(coors2);
Vector3 diff = coors2 - coors;
diff.z = 0.0f;
diff.Normalize();
float linkOrientation = fwAngle::GetATanOfXY(diff.x, diff.y);
float mult = FindSpeedMultiplier(pVeh, linkOrientation-vehDriveOrientation, PI * 0.05f, PI * 0.35f, 0.25f, 1.0f);
mult = 1.0f -((1.0f - mult) *(1.0f - dist/maxBrakeDist));
maxSpeedMult = rage::Min(maxSpeedMult, mult);
// Now also calculate an absolute maximum speed.
// This will fix cases where the cruiseSpeed for a car is set very high.
// A maxSpeedMult of 0.5f will still not slow it down enough.
static float slowSpeed = 11.0f;
static float fastSpeed = 62.0f;
float maxSpeed = FindSpeedMultiplier(pVeh, linkOrientation-vehDriveOrientation, PI * 0.05f, PI * 0.35f, slowSpeed, fastSpeed);
mult = maxSpeed / StraightLineSpeed;
mult = 1.0f -((1.0f - mult) *(1.0f - dist/maxBrakeDist));
maxSpeedMult = rage::Min(maxSpeedMult, mult);
}
}
// We never want to slow cars down below a certain speed because of a bend.(6 m/s is pretty slow)
float smallestSpeedMult = 6.0f / StraightLineSpeed;
maxSpeedMult = Clamp(maxSpeedMult, smallestSpeedMult, 1.0f);
#if __DEV
if(m_bDisplaySlowDownForBendDebugInfo && AllowCarAiDebugDisplayForThisVehicle(pVeh))
{
char debugText[15];
sprintf(debugText, "Bend:%.2f", maxSpeedMult);
grcDebugDraw::Text(vVehiclePosition + Vector3(0.f,0.f,1.5f), CRGBA(255, 255, 255, 255), debugText);
}
#endif
return maxSpeedMult;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindSpeedMultiplier
// PURPOSE : From the angle to the target this function calculates what the
// speed multiplier should be.
// MinSpeedAngle(angle for which minimum speed is returned)
// MaxSpeedAngle(angle for which maximum speed is returned)
// MinSpeed(the minimum speed, say 0.5)
/////////////////////////////////////////////////////////////////////////////////
float CVehicleIntelligence::FindSpeedMultiplier(const CVehicle* pVehicle, float Angle, float MinSpeedAngle, float MaxSpeedAngle, float MinSpeed, float MaxSpeed)
{
float Result;
#if __BANK
if (CVehiclePopulation::ms_bEverybodyCruiseInReverse)
{
Angle = fwAngle::LimitRadianAngleSafe(Angle);
}
else
#endif //BANK
{
Angle = fwAngle::LimitRadianAngle(Angle);
}
// DirAngleDiff is between 0.0f and PI.
// 0.0f .. 0.5f -> full speed
// 0.5f .. 1.2f -> slow down. 1.2f and larger results in 0.3f
Angle = ABS(Angle);
Angle = rage::Max(0.0f, Angle - MinSpeedAngle);
// Modify grip loss based on handling and material we're on
float fTractionLoss = 1.0f;
if (pVehicle)
{
TUNE_GROUP_FLOAT(VEH_INTELLIGENCE, MaterialFrictionCoefficient, 1.0f, 0.0f, 100.0f, 0.1f);
float fWheelGrip = 0.0f;
for(int i=0; i<pVehicle->GetNumWheels(); i++)
{
fWheelGrip += pVehicle->GetWheel(i)->GetMaterialGrip();
}
fWheelGrip = pVehicle->GetNumWheels() > 0 ? fWheelGrip / pVehicle->GetNumWheels() : 1.0f;
fTractionLoss += ((fWheelGrip - 1.0f)) * MaterialFrictionCoefficient * pVehicle->pHandling->m_fTractionLossMult;
}
//const float fOrigResult = MaxSpeed -(Angle /(MaxSpeedAngle - MinSpeedAngle)) *(MaxSpeed - MinSpeed);
//const float fDriverAbility = CDriverPersonality::FindDriverAbility(pVehicle->GetDriver());
//the turning circles are tuned for roads, so allow us to slow down
//more for non-road surfaces
MinSpeed *= fTractionLoss;
Result = MaxSpeed - (Angle /((MaxSpeedAngle - MinSpeedAngle) /* * fTractionLoss*/)) *(MaxSpeed - MinSpeed);
// if (fOrigResult > Result)
// {
// aiDisplayf("fOrigResult: %.2f, NewResult: %.2f", fOrigResult, Result);
// }
if(Angle >(MaxSpeedAngle - MinSpeedAngle))
{
Result = MinSpeed;
}
Result = Max(Result, MinSpeed);
return Result;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindTargetCoorsWithNode
// PURPOSE : Given a autopilotNodeIndex and the lane that the car is in this function returns the
// actual world coordinates the car will aim for.
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::FindTargetCoorsWithNode(const CVehicle* const pVeh, CNodeAddress PreviousNode, CNodeAddress Node, CNodeAddress NextNode, s32 regionLinkIndexPreviousNodeToNode, s32 regionLinkIndexNodeToNextNode, s32 LaneIndexOld, s32 LaneIndexNew, Vector3 &Result)
{
Assert(!Node.IsEmpty());
Assert(ThePaths.IsRegionLoaded(Node));
Assert(LaneIndexOld >= 0 && LaneIndexOld <= 6);
Assert((LaneIndexNew >= 0 && LaneIndexNew <= 6) || NextNode.IsEmpty());
Assert(PreviousNode != Node);
Assert(NextNode != Node);
Assert(pVeh);
Vector2 Dir(0.0f, 0.0f);
Vector3 laneCentreOffsetVec(0.0f, 0.0f, 0.0f);
Vector2 CombinedDir(0.0f, 0.0f);
const CPathNode *pNode = ThePaths.FindNodePointer(Node);
Vector3 thisNodeCoors;
pNode->GetCoors(thisNodeCoors);
bool bUseReducedOffset = false;
Assert(pVeh->GetVehicleModelInfo());
if( pVeh->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_BIG) /*|| (pVeh->GetIntelligence()->m_LaneShift!=0.0f)*/ )
{
bUseReducedOffset = true;
}
bool bIsSingleTrackRoad = false;
// Calculate the offset from the actual autopilotNodeIndex coordinates based on the previous and next nodes.
TUNE_GROUP_BOOL(CAR_AI, treatAllRoadsAsNarrow, false);
if(!NextNode.IsEmpty() && !Node.IsEmpty() && ThePaths.IsRegionLoaded(NextNode))
{
Assert(regionLinkIndexNodeToNextNode >= 0 && regionLinkIndexNodeToNextNode < 30000);
CPathNodeLink *pLink = ThePaths.FindLinkPointerSafe(Node.GetRegion(),regionLinkIndexNodeToNextNode);
Vector2 nextNodeCoors;
ThePaths.FindNodePointer(NextNode)->GetCoors2(nextNodeCoors);
Dir = nextNodeCoors - Vector2(thisNodeCoors, Vector2::kXY);
Dir.Normalize();
CombinedDir += Dir;
float laneCentreOffset = ThePaths.GetLinksLaneCentreOffset(*pLink, LaneIndexNew);
laneCentreOffsetVec.x += Dir.y * laneCentreOffset;
laneCentreOffsetVec.y -= Dir.x * laneCentreOffset;
laneCentreOffsetVec.z = 0.0f;
if (pLink->IsSingleTrack())
{
bIsSingleTrackRoad = true;
}
if(treatAllRoadsAsNarrow || pLink->m_1.m_NarrowRoad)
{
bUseReducedOffset = true;
}
}
if(!PreviousNode.IsEmpty() && ThePaths.IsRegionLoaded(PreviousNode))
{
Assert(regionLinkIndexPreviousNodeToNode >= 0 && regionLinkIndexPreviousNodeToNode < 30000);
CPathNodeLink *pLink = ThePaths.FindLinkPointerSafe(PreviousNode.GetRegion(),regionLinkIndexPreviousNodeToNode);
Vector2 previousNodeCoors;
ThePaths.FindNodePointer(PreviousNode)->GetCoors2(previousNodeCoors);
Dir = Vector2(thisNodeCoors, Vector2::kXY) - previousNodeCoors;
Dir.Normalize();
CombinedDir += Dir;
// If there already is a laneCentreOffsetVec(from the next autopilotNodeIndex) we reduce it's contribution to that in
// the direction of this segment.
float dot = laneCentreOffsetVec.x * Dir.x + laneCentreOffsetVec.y * Dir.y;
laneCentreOffsetVec.x = Dir.x * dot;
laneCentreOffsetVec.y = Dir.y * dot;
laneCentreOffsetVec.z = 0.0f;
float laneCentreOffset = ThePaths.GetLinksLaneCentreOffset(*pLink, LaneIndexOld);
if (pLink->IsSingleTrack())
{
bIsSingleTrackRoad = true;
}
laneCentreOffsetVec.x += Dir.y * laneCentreOffset;
laneCentreOffsetVec.y -= Dir.x * laneCentreOffset;
laneCentreOffsetVec.z = 0.0f;
if(treatAllRoadsAsNarrow || pLink->m_1.m_NarrowRoad)
{
bUseReducedOffset = true;
}
}
// Vehicles can have a consistent offset from the centre line of the road.(Used to have 2 convoys of bikes next to each other in e1)
CombinedDir.NormalizeSafe();
//laneCentreOffsetVec.x += CombinedDir.y * pVeh->GetIntelligence()->m_LaneShift; //laneshift was always zero
//laneCentreOffsetVec.y -= CombinedDir.x * pVeh->GetIntelligence()->m_LaneShift; //laneshift was always 0
laneCentreOffsetVec.z = 0.0f;
Result = thisNodeCoors + laneCentreOffsetVec;
Vector3 RandomOffset(VEC3_ZERO);
TUNE_GROUP_BOOL(CAR_AI, useRandomOffsets, true);
if(useRandomOffsets)
{
FindNodeOffsetForCar(pVeh, RandomOffset);
}
TUNE_GROUP_FLOAT(CAR_AI, sfDefaultOffsetMult, 1.0f, 0.0f, 20.0f, 0.01f);
TUNE_GROUP_FLOAT(CAR_AI, sfWaterOffsetMult, 10.0f, 0.0f, 20.0f, 0.01f);
TUNE_GROUP_FLOAT(CAR_AI, sfReducedOffsetMult, 0.25f, 0.0f, 1.0f, 0.01f);
float offsetMultiplier =(pNode->m_2.m_waterNode)? sfWaterOffsetMult : sfDefaultOffsetMult;
if( bUseReducedOffset )
{
offsetMultiplier *= sfReducedOffsetMult;
}
//if we're on a single track road, only apply a random offset to the right
if (bIsSingleTrackRoad)
{
Vector3 CombinedRight(CombinedDir.y, -CombinedDir.x, 0.0f);
if (RandomOffset.Dot(CombinedRight) < 0.0f)
{
RandomOffset.Negate();
}
}
Result.x += offsetMultiplier*RandomOffset.x;
Result.y += offsetMultiplier*RandomOffset.y;
// And leave z untouched.
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindNodeOffsetForCar
// PURPOSE : To give traffic a bit more of a chaotic feel the cars randomise the
// coordinates that they go to by a small amount.
// This function finds the offset that this particular car uses.
// This offset gets applied to each autopilotNodeIndex.
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::FindNodeOffsetForCar(const CVehicle* const pVeh, Vector3 &Result)
{
FindNodeOffsetForCarFromRandomSeed(pVeh->GetRandomSeed(), Result);
if (pVeh && pVeh->m_nVehicleFlags.bIsBig)
{
Result *= 0.15f;
}
else if (pVeh && pVeh->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_SPORTS))
{
//kind of hacky: sports cars tend to go faster so try to keep them toward the middle
//of the lane
Result *= 0.5f;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindSpeedMultiplierWithSpeedFromNodes
// PURPOSE : Given the speed value that comes from the nodes we return the speed for a car.
/////////////////////////////////////////////////////////////////////////////////
float CVehicleIntelligence::MultiplyCruiseSpeedWithMultiplierAndClip(float CruiseSpeed, float SpeedMultiplierFromNodes, const bool bShouldCapSpeedToSpeedZones, const bool bIsMission, const Vector3& vPosition)
{
TUNE_GROUP_FLOAT(CAR_AI, MaxSpeed, 35.0f, 0.0f, 100.0f, 1.0f);
// When the cruise speed gets multiplier with the speed from the node we still want it to be a reasonable number so it has to be clipped.
float retVal = CruiseSpeed * SpeedMultiplierFromNodes;
if(SpeedMultiplierFromNodes > 1.0f)
{
retVal = Clamp(retVal, CruiseSpeed, MaxSpeed); // 25 is fast enough(90 km/h)
}
if (bShouldCapSpeedToSpeedZones)
{
retVal = rage::Min(retVal, CRoadSpeedZoneManager::GetInstance().FindMaxSpeedForThisPosition(vPosition, bIsMission));
}
return retVal;
}
void CVehicleIntelligence::UpdateIsThisADriveableCar()
{
CVehicle* pVehicle = m_pVehicle;
bool bCheckForPlayer = !pVehicle->GetDriver() || (FindPlayerPed() == pVehicle->GetDriver());
const bool wasDriveableCar = pVehicle->m_nVehicleFlags.bIsThisADriveableCar;
pVehicle->m_nVehicleFlags.bIsThisADriveableCar = pVehicle->GetVehicleDamage()->GetIsDriveable(bCheckForPlayer);
//Set an event for the vehicle being undrivable.
if (NetworkInterface::IsGameInProgress() && wasDriveableCar && !pVehicle->m_nVehicleFlags.bIsThisADriveableCar)
{
if (!pVehicle->IsNetworkClone() && pVehicle->GetNetworkObject())
{
GetEventScriptNetworkGroup()->Add(CEventNetworkVehicleUndrivable(pVehicle, pVehicle->GetWeaponDamageEntity(), (int)pVehicle->GetWeaponDamageHash()));
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateIsThisAParkedCar
// PURPOSE : Returns true if this car should be treated as an obstacle and not as
// traffic. This will be the case for parked cars.
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::UpdateIsThisAParkedCar()
{
CVehicle* pVehicle = m_pVehicle;
pVehicle->m_nVehicleFlags.bIsThisAParkedCar = false;
if (pVehicle->PopTypeGet() == POPTYPE_RANDOM_PARKED)
{
pVehicle->m_nVehicleFlags.bIsThisAParkedCar = true;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateIsThisAStationaryCar
// PURPOSE : Returns true if this car should be treated as an obstacle and not as
// traffic. This will be the case for parked cars.
/////////////////////////////////////////////////////////////////////////////////
void CVehicleIntelligence::UpdateIsThisAStationaryCar()
{
CVehicle* pVehicle = m_pVehicle;
pVehicle->m_nVehicleFlags.bIsOvertakingStationaryCar = false;
pVehicle->m_nVehicleFlags.bForceOtherVehiclesToOvertakeThisVehicle = false;
// If this car is set to be marked as stationary cars will steer round it.
// If the override flag is set to make sure other cars stop, make sure its not flagged.
// bForceOtherVehiclesToStopForThisVehicle is a reset flag so ensure its reset here so it can be set anywhere
// else in the frame.
if( pVehicle->m_nVehicleFlags.bForceOtherVehiclesToStopForThisVehicle )
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = false;
pVehicle->m_nVehicleFlags.bForceOtherVehiclesToStopForThisVehicle = false;
return;
}
// The following may still be the case even when IsThisAParkedCar returned false. Perhaps setting the avoidance data didn't happen.
if (pVehicle->PopTypeGet() == POPTYPE_RANDOM_PARKED)
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = true;
return;
}
CVehicle* pParentVehicle = pVehicle->m_nVehicleFlags.bHasParentVehicle
? CTaskVehicleGoToPointWithAvoidanceAutomobile::GetTrailerParentFromVehicle(pVehicle)
: NULL;
// Trailers inherit their stationary status from the cab
if( pVehicle->InheritsFromTrailer() )
{
if(pParentVehicle)
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = pParentVehicle->m_nVehicleFlags.bIsThisAStationaryCar;
return;
}
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = true;
return;
}
// If we're a car attached to a trailer, inherit our stationary status from the trailer
if (pParentVehicle && pParentVehicle->InheritsFromTrailer())
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = pParentVehicle->m_nVehicleFlags.bIsThisAStationaryCar;
return;
}
// If the car hasn't had a driver for a little while or has a dead driver it is also considered a parked car.
u32 uTimeSinceLastDriverForStationary = pVehicle->m_nVehicleFlags.bBecomeStationaryQuicker ? 500 : 5000;
if ( /*pVehicle->m_LastTimeWeHadADriver && not sure why we needed this. Caused problems with mission vehicles blocking road before being used.*/
(NetworkInterface::GetSyncedTimeInMilliseconds() > pVehicle->m_LastTimeWeHadADriver + uTimeSinceLastDriverForStationary) ||
(pVehicle->GetDriver() && pVehicle->GetDriver()->IsDead()))
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = true;
return;
}
// If the car was told to stop (like the taxis in a taxi rank) it is also considered a parked car.
if (pVehicle->GetDriver() && !pVehicle->GetDriver()->IsPlayer())
{
CTaskVehicleMissionBase* pActiveTask = dynamic_cast<CTaskVehicleMissionBase*>(GetActiveTask());
if(pActiveTask == NULL || pActiveTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_STOP || pActiveTask->GetCruiseSpeed() <= 0.0f )
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = true;
return;
}
}
// If the car is upside down or blown up it will not move.
if (pVehicle->m_nPhysicalFlags.bRenderScorched || !pVehicle->m_nVehicleFlags.bIsThisADriveableCar || pVehicle->GetVehicleUpDirection().GetZf() < 0.2f)
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = true;
return;
}
// If the player is controlling this car but he is not moving and it's not a mission vehicle
// except if it's a personal vehicle (always classed as mission vehicle)
if (pVehicle->GetDriver() && pVehicle->GetDriver()->IsPlayer() && (!pVehicle->PopTypeIsMission() || pVehicle->IsPersonalVehicle()))
{
if ((fwTimer::GetTimeInMilliseconds() - LastTimeNotStuck) > 4000)
{
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = true;
return;
}
}
pVehicle->m_nVehicleFlags.bIsThisAStationaryCar = false;
}
void CVehicleIntelligence::UpdateTimeNotMoving(float fTimeStep)
{
//TUNE_GROUP_FLOAT( VEH_AI_3_POINT_TURNS, threePointTurnInit_moveSpeedBelowWhichToCheckIfStuck, 0.4f, 0.0f, 100.0f, 0.1f);
const CPed* driver = m_pVehicle->GetDriver();
bool bIsDriverPlayer = driver ? driver->IsPlayer() : false;
if (bIsDriverPlayer) // Make sure the player is actually in control of the car
{
CTaskVehicleMissionBase* pActiveTask = GetActiveTask();
bIsDriverPlayer = pActiveTask && CTaskTypes::IsPlayerDriveTask(pActiveTask->GetTaskType());
}
if (!bIsDriverPlayer && m_pVehicle->GetAiXYSpeed() < ms_fMoveSpeedBelowWhichToCheckIfStuck)
{
const u32 timeStepInMs = (u32)(1000.0f*fTimeStep);
MillisecondsNotMoving += timeStepInMs;
}
else
{
MillisecondsNotMoving = 0;
}
}
CTaskVehicleMissionBase* CVehicleIntelligence::CreateCruiseTask( CVehicle& in_Vehicle, sVehicleMissionParams& params, const bool bSpeedComesFromVehPopulation )
{
if ( in_Vehicle.InheritsFromBoat() )
{
return rage_new CTaskVehicleCruiseBoat(params, bSpeedComesFromVehPopulation);
}
else
{
return rage_new CTaskVehicleCruiseNew(params, bSpeedComesFromVehPopulation);
}
}
CTaskVehicleMissionBase* CVehicleIntelligence::CreateAutomobileGotoTask( sVehicleMissionParams& params, const float fStraightLineDist)
{
return rage_new CTaskVehicleGoToAutomobileNew(params, fStraightLineDist);
}
int CVehicleIntelligence::GetAutomobileGotoTaskType()
{
return CTaskTypes::TASK_VEHICLE_GOTO_AUTOMOBILE_NEW;
}
void CVehicleIntelligence::HandleEvents()
{
//don't bother handling any events if we don't have pretend occupants
if (!ShouldHandleEvents())
{
return;
}
VehicleEventPriority::VehicleEventPriority currentEventPriority = m_PretendOccupantEventPriority;
AI_EVENT_GROUP_LOCK(&m_eventGroup);
CEvent* pHighestPriorityEvent = m_eventGroup.GetHighestPriorityEvent();
if (pHighestPriorityEvent)
{
VehicleEventPriority::VehicleEventPriority highestEventPriority = pHighestPriorityEvent->GetVehicleResponsePriority();
if (highestEventPriority > currentEventPriority)
{
sVehicleMissionParams params;
CTask* pTask = pHighestPriorityEvent->CreateVehicleReponseTaskWithParams(params);
Assert(pTask);
SetPretendOccupantEventData(highestEventPriority, params);
//Assert(!m_CurrentEvent);
if (m_CurrentEvent)
{
delete m_CurrentEvent;
}
m_CurrentEvent = static_cast<CEvent*>(pHighestPriorityEvent->Clone());
AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
}
}
m_eventGroup.TickEvents();
}
bool CVehicleIntelligence::ShouldStopForSiren()
{
bool bIsSirenValid = (m_pVehicle->m_nVehicleFlags.GetIsSirenOn() &&
(m_pVehicle->m_nVehicleFlags.bIsLawEnforcementVehicle ||
m_pVehicle->m_nVehicleFlags.bIsAmbulanceOnDuty ||
m_pVehicle->m_nVehicleFlags.bIsFireTruckOnDuty));
//check siren is on
if( !bIsSirenValid )
{
return false;
}
//just obey all mission vehicles or if we've never had a driver (road block)
if( m_pVehicle->PopTypeIsMission() || m_pVehicle->m_LastTimeWeHadADriver == 0 )
{
return true;
}
//not valid if our driver is dead
const CPed* pDriver = m_pVehicle->GetDriver();
if( pDriver && pDriver->IsDead() )
{
return false;
}
//not valid if we don't have a driver AND we have a last driver AND the last driver is dead
const CPed* pLastDriver = m_pVehicle->GetLastDriver();
if( !pDriver && pLastDriver && pLastDriver->IsDead() )
{
return false;
}
const bool bNoDriverInfo = !pDriver && !pLastDriver;
const bool bDriverIsAlive = pDriver && !pDriver->IsDead();
const bool bNoDriverButLastAlive = !pDriver && pLastDriver && !pLastDriver->IsDead();
//Valid if current driver is alive OR no current driver but last driver is alive
//OR we've no driver information but 30 seconds since we've been driven (happens drivers are removed)
return bDriverIsAlive || bNoDriverButLastAlive || ( bNoDriverInfo && NetworkInterface::GetSyncedTimeInMilliseconds() < m_pVehicle->m_LastTimeWeHadADriver + 30000 );
}
/////////////////////////////////////////////////////////////////////////////////
CHeliIntelligence::CHeliIntelligence(CVehicle *pVehicle)
: CVehicleIntelligence(pVehicle)
, m_AvoidanceDistributer(CExpensiveProcess::VPT_HeliAvoidanceDistributer)
, m_fTimeSpentLanded(0.0f)
, m_fTimeLandingAreaBlocked(0.0f)
, m_bProcessAvoidance(false)
{
m_crashTurnSpeed = fwRandom::GetRandomNumberInRange(2.0f, 8.0f);
m_OldOrientation = 0.0f;
m_OldTilt = 0.0f;
m_fHeliAvoidanceHeight = 0.0f;
m_pExtraEntityToDoHeightChecksFor = NULL;
bHasCombatOffset = false;
}
/////////////////////////////////////////////////////////////////////////////////
void CHeliIntelligence::Process(bool fullUpdate, float fFullUpdateTimeStep)
{
CVehicleIntelligence::Process(fullUpdate, fFullUpdateTimeStep);
UpdateOldPositionVaraibles();
//Process the avoidance.
ProcessAvoidance();
//Process the landed state.
ProcessLanded();
}
/////////////////////////////////////////////////////////////////////////////////
void CHeliIntelligence::UpdateOldPositionVaraibles()
{
// The following 2 are used by the ai steering code.
const Vector3 vecForward(VEC3V_TO_VECTOR3(m_pVehicle->GetTransform().GetB()));
m_OldOrientation = fwAngle::GetATanOfXY(vecForward.x, vecForward.y);
m_OldTilt = rage::Atan2f(vecForward.z, vecForward.XYMag());
}
void CHeliIntelligence::MarkLandingAreaBlocked()
{
m_fTimeLandingAreaBlocked = 1.0f;
}
bool CHeliIntelligence::IsLandingAreaBlocked() const
{
return m_fTimeLandingAreaBlocked > 0.0f;
}
/////////////////////////////////////////////////////////////////////////////////
bool CHeliIntelligence::IsLanded() const
{
return (m_pVehicle->GetNumContactWheels() > 1);
}
void CHeliIntelligence::ProcessAvoidance()
{
//Ensure we should process avoidance.
if(!m_bProcessAvoidance)
{
return;
}
//Reset the flag.
m_bProcessAvoidance = false;
//Ensure the avoidance distributer is registered.
if(!m_AvoidanceDistributer.IsRegistered())
{
m_AvoidanceDistributer.RegisterSlot();
}
//Ensure the avoidance should be processed this frame.
if(!m_AvoidanceDistributer.ShouldBeProcessedThisFrame())
{
return;
}
//Define the closest heli structure.
struct ClosestHeli
{
ClosestHeli()
: m_pHeli(NULL)
, m_scHeight(V_ZERO)
{
}
const CHeli* m_pHeli;
ScalarV m_scHeight;
};
//Find the heli that is:
//1) Within range
//2) Closest to our height
//3) Below us
//The goal of this is to adjust ourselves upward based on the heli that is closest below us.
ClosestHeli closestHeli;
//Initialize the tuning values.
TUNE_GROUP_FLOAT(HELI_AI, fAvoidanceMaxDist, 40.0f, 0.0f, 50.0f, 1.0f);
TUNE_GROUP_FLOAT(HELI_AI, fAvoidanceOffset, 15.0f, 0.0f, 50.0f, 1.0f);
//Grab the position.
Vec3V vPosition = m_pVehicle->GetTransform().GetPosition();
//Grab the height.
ScalarV scHeight = vPosition.GetZ();
//Iterate over the nearby vehicles.
CEntityScannerIterator entityList = GetVehicleScanner().GetIterator();
for(CEntity* pEntity = entityList.GetFirst(); pEntity; pEntity = entityList.GetNext() )
{
//Grab the vehicle.
Assert(pEntity->GetIsTypeVehicle());
const CVehicle* pVehicle = static_cast<const CVehicle *>(pEntity);
//Ensure the vehicle is a heli.
if(!pVehicle->InheritsFromHeli())
{
continue;
}
//Grab the heli.
const CHeli* pHeli = static_cast<const CHeli *>(pVehicle);
//Grab the other position.
Vec3V vOtherPosition = pVehicle->GetTransform().GetPosition();
//Ensure the helis are within a certain range of each other.
ScalarV scDistSq = DistSquared(vPosition, vOtherPosition);
ScalarV scMaxDistSq = ScalarVFromF32(square(fAvoidanceMaxDist));
if(IsGreaterThanAll(scDistSq, scMaxDistSq))
{
continue;
}
//Grab the other height.
ScalarV scOtherHeight = vOtherPosition.GetZ();
//Ensure the heli is above the other heli.
ScalarV scHeightDifference = Subtract(scHeight, scOtherHeight);
if(IsLessThanAll(scHeightDifference, ScalarV(V_ZERO)))
{
continue;
}
//Check if the closest heli is valid.
if(closestHeli.m_pHeli)
{
//Ensure the other heli is above the closest heli.
if(IsLessThanAll(scOtherHeight, closestHeli.m_scHeight))
{
continue;
}
}
//Assign the closest heli.
closestHeli.m_pHeli = pHeli;
closestHeli.m_scHeight = scOtherHeight;
}
//Check if the closest heli is valid.
if(closestHeli.m_pHeli)
{
//Set the avoidance height to be above the closest heli.
m_fHeliAvoidanceHeight = closestHeli.m_scHeight.Getf() + fAvoidanceOffset;
}
else
{
//Clear the avoidance height.
m_fHeliAvoidanceHeight = 0.0f;
}
}
void CHeliIntelligence::ProcessLanded()
{
if ( m_fTimeLandingAreaBlocked >= 0.0f )
{
m_fTimeLandingAreaBlocked -= fwTimer::GetTimeStep();
}
if(IsLanded())
{
m_fTimeSpentLanded += fwTimer::GetTimeStep();
}
else
{
m_fTimeSpentLanded = 0.0f;
}
}