Files
GTASource/game/vehicleAi/task/TaskVehicleCircle.cpp

461 lines
15 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
#include "TaskVehicleCircle.h"
// Framework headers
// Game headers
#include "Vehicles/vehicle.h"
#include "Vehicles/Planes.h"
#include "Vehicles/heli.h"
#include "VehicleAi\VehicleIntelligence.h"
#include "VehicleAi\task\TaskVehicleMissionBase.h"
#include "VehicleAi\task\TaskVehicleGoTo.h"
#include "VehicleAi\task\TaskVehicleGoToHelicopter.h"
#include "VehicleAi\task\TaskVehiclePark.h"
#include "game/modelindices.h"
AI_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
////////////////////////////////////////////////////////////////////
CTaskVehicleCircle::CTaskVehicleCircle(const sVehicleMissionParams& params)
: CTaskVehicleMissionBase(params)
, m_fHeliRequestedOrientation(-1.0f)
, m_iFlightHeight(7)
, m_iMinHeightAboveTerrain(20)
, m_iHeliFlags(0)
{
SetInternalTaskType(CTaskTypes::TASK_VEHICLE_CIRCLE);
}
////////////////////////////////////////////////////////////////////
CTaskVehicleCircle::~CTaskVehicleCircle()
{
}
////////////////////////////////////////////////////////////////////
aiTask::FSM_Return CTaskVehicleCircle::UpdateFSM(const s32 iState, const FSM_Event iEvent)
{
CVehicle *pVehicle = GetVehicle(); //Get the vehicle ptr.
FSM_Begin
// Startstate
FSM_State(State_Start)
FSM_OnUpdate
return Start_OnUpdate(pVehicle);
// Circle state
FSM_State(State_Circle)
FSM_OnUpdate
return Circle_OnUpdate(pVehicle);
FSM_State(State_CircleHeli)
FSM_OnEnter
CircleHeli_OnEnter(pVehicle);
FSM_OnUpdate
return CircleHeli_OnUpdate(pVehicle);
// Stop state
FSM_State(State_Stop)
FSM_OnEnter
Stop_OnEnter(pVehicle);
FSM_OnUpdate
return Stop_OnUpdate(pVehicle);
FSM_End
}
aiTask::FSM_Return CTaskVehicleCircle::Start_OnUpdate( CVehicle* pVehicle )
{
if( pVehicle->InheritsFromHeli() )
{
SetState(State_CircleHeli);
return FSM_Continue;
}
else
{
SetState(State_Circle);
return FSM_Continue;
}
}
void CTaskVehicleCircle::CircleHeli_OnEnter( CVehicle* ASSERT_ONLY(pVehicle) )
{
aiFatalAssertf(dynamic_cast<CHeli*>(pVehicle), "Vehicle not a heli!");
sVehicleMissionParams params = m_Params;
params.m_iDrivingFlags.SetFlag(DF_DontTerminateTaskWhenAchieved);
SetNewTask(rage_new CTaskVehicleGoToHelicopter(params, m_iHeliFlags | CTaskVehicleGoToHelicopter::HF_DontClampProbesToDestination, m_fHeliRequestedOrientation, m_iMinHeightAboveTerrain));
}
aiTask::FSM_Return CTaskVehicleCircle::CircleHeli_OnUpdate( CVehicle* pVehicle )
{
if( GetIsSubtaskFinished(CTaskTypes::TASK_VEHICLE_GOTO_HELICOPTER) )
{
SetFlag(aiTaskFlags::RestartCurrentState);
return FSM_Continue;
}
if( m_Params.GetTargetEntity().GetEntity() != NULL )
{
Vector3 vHeliPosition = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition());
//Vector3 vTargetPosition = VEC3V_TO_VECTOR3(m_Params.m_TargetEntity.GetEntity()->GetTransform().GetPosition());
Vector3 vTargetPosition = VEC3V_TO_VECTOR3(m_Params.GetTargetEntity().GetEntity()->GetTransform().Transform(VECTOR3_TO_VEC3V(m_Params.GetTargetPosition())));
Vector3 vToTarget = vTargetPosition - vHeliPosition;
vToTarget.z = 0.0f;
vToTarget.Normalize();
float fHeadingToTarget = fwAngle::GetATanOfXY(vToTarget.x, vToTarget.y);
aiFatalAssertf(GetSubTask() && GetSubTask()->GetTaskType() == CTaskTypes::TASK_VEHICLE_GOTO_HELICOPTER, "Unexpected subtask!");
CTaskVehicleGoToHelicopter* pSubtask = static_cast<CTaskVehicleGoToHelicopter*>(GetSubTask());
const bool bInRange = (vHeliPosition - vTargetPosition).XYMag2() < rage::square(m_Params.m_fTargetArriveDist + 20.0f);
const bool bAttainRequestedOrientation = m_iHeliFlags & CTaskVehicleGoToHelicopter::HF_AttainRequestedOrientation;
if( bInRange )
{
if( bAttainRequestedOrientation )
{
pSubtask->SetOrientation(m_fHeliRequestedOrientation+fHeadingToTarget);
}
pSubtask->SetOrientationRequested(m_iHeliFlags & CTaskVehicleGoToHelicopter::HF_AttainRequestedOrientation);
pSubtask->SetSlowDownDistance(Max(m_Params.m_fTargetArriveDist, 20.0f));
static float s_StepAngle = PI / 8; // about 15 degrees
if (m_iHeliFlags & CTaskVehicleGoToHelicopter::HF_CircleOppositeDirection)
{
vToTarget.RotateZ(-s_StepAngle);
}
else
{
vToTarget.RotateZ(s_StepAngle);
}
}
else
{
pSubtask->SetOrientationRequested(false);
}
const Vector3 vStrafePosition = vTargetPosition - (vToTarget*m_Params.m_fTargetArriveDist);
if( bInRange && !bAttainRequestedOrientation )
{
const Vec2V vForward = pVehicle->GetVehicleForwardDirection().GetXY();
const Vec2V vToStrafePosition = NormalizeSafe(VECTOR3_TO_VEC3V(vStrafePosition - vHeliPosition).GetXY(), vForward);
const ScalarV fToStrafePosition = Dot(vForward, vToStrafePosition);
// If the strafe position is greater than 90 degrees from our current heading
if( IsLessThanAll(fToStrafePosition, ScalarV(V_ZERO)) )
{
const Vec2V vRight = Vec2V(-vForward.GetY(), vForward.GetX());
const Vec2V vTargetEntity = m_Params.GetTargetEntity().GetEntity()->GetTransform().GetPosition().GetXY();
const Vec2V vToTargetEntity = NormalizeSafe(vTargetEntity - VECTOR3_TO_VEC3V(vHeliPosition).GetXY(), vForward);
const ScalarV fRightToStrafePosition = Dot(vRight, vToStrafePosition);
const ScalarV fRightToTargetEntity = Dot(vRight, vToTargetEntity);
const BoolV bOnOppositeSides = Or(And(IsLessThan(fRightToStrafePosition, ScalarV(V_ZERO)), IsGreaterThan(fRightToTargetEntity, ScalarV(V_ZERO))),
And(IsGreaterThan(fRightToStrafePosition, ScalarV(V_ZERO)), IsLessThan(fRightToTargetEntity, ScalarV(V_ZERO))));
// And the target entity is on the opposite side
if( IsTrue(bOnOppositeSides) )
{
// Turn toward the target entity first
const float fToTargetEntity = fwAngle::GetATanOfXY(vToTargetEntity.GetXf(), vToTargetEntity.GetYf());
pSubtask->SetOrientation(fToTargetEntity);
pSubtask->SetOrientationRequested(true);
}
}
}
pSubtask->SetTargetPosition(&vStrafePosition);
pSubtask->SetTargetEntity(NULL);
}
return FSM_Continue;
}
////////////////////////////////////////////////////////////////////
aiTask::FSM_Return CTaskVehicleCircle::Circle_OnUpdate (CVehicle* pVehicle)
{
// Sometimes the target may have been removed for a frame or so.
// In that case don't do anything.
if(!GetTargetEntity())
{
SetState(State_Stop);
return FSM_Continue;
}
FindTargetCoors(pVehicle, m_Params);
const Vector3 vVehiclePosition = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition());
const Vector3& vTargetPos = m_Params.GetTargetPosition();
float Distance = rage::Sqrtf( (vTargetPos.x - vVehiclePosition.x) * (vTargetPos.x - vVehiclePosition.x) +
(vTargetPos.y - vVehiclePosition.y) * (vTargetPos.y - vVehiclePosition.y) );
switch(pVehicle->GetVehicleType())
{
case VEHICLE_TYPE_BOAT:
if ( Distance < 30.0f)
{
SetCruiseSpeed(1.0f); // Police boats circling a target looks a bit silly. Make them go super slow.
}
Boat_SteerCirclingTarget( pVehicle, &pVehicle->m_vehControls, GetTargetEntity(), GetCruiseSpeed());
break;
case VEHICLE_TYPE_HELI:
case VEHICLE_TYPE_BLIMP:
Helicopter_SteerCirclingTarget((CHeli*)pVehicle, GetTargetEntity());
break;
default:
Assertf(false,"Vehicle type not supported by TaskVehicleCircle");
break;
}
return FSM_Continue;
}
///////////////////////////////////////////////////////////////////////////////////
void CTaskVehicleCircle::Stop_OnEnter(CVehicle* UNUSED_PARAM(pVehicle))
{
SetNewTask(rage_new CTaskVehicleStop());
}
//////////////////////////////////////////////////////////////////////////////////
aiTask::FSM_Return CTaskVehicleCircle::Stop_OnUpdate(CVehicle* UNUSED_PARAM(pVehicle))
{
if (GetIsSubtaskFinished(CTaskTypes::TASK_VEHICLE_STOP))
{
return FSM_Quit;
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SteerCirclingTarget
// PURPOSE : Circle the player so that the heli's front guns have a good shot.
/////////////////////////////////////////////////////////////////////////////////
void CTaskVehicleCircle::Helicopter_SteerCirclingTarget(CHeli *pHeli, const CEntity *pTarget)
{
Assertf(pTarget, "SteerCirclingTarget has a NULL target Entity");
const Vector3 vTargetPosition = VEC3V_TO_VECTOR3(pTarget->GetTransform().GetPosition());
const Vector3 vHeliPosition = VEC3V_TO_VECTOR3(pHeli->GetTransform().GetPosition());
const float fDesiredHeight = vTargetPosition.z + 7.0f;
static dev_float inFuture = 3.0f;
float dX = vTargetPosition.x - (vHeliPosition.x + pHeli->GetVelocity().x * inFuture);
float dY = vTargetPosition.y - (vHeliPosition.y + pHeli->GetVelocity().y * inFuture);
float TargetOrientation = fwAngle::GetATanOfXY(dX, dY);
Vector3 forwardVec = VEC3V_TO_VECTOR3(pHeli->GetTransform().GetB());
float HeliOrientation = fwAngle::GetATanOfXY(forwardVec.x, forwardVec.y);
float OrientationExtraDiff = HeliOrientation - pHeli->GetHeliIntelligence()->GetOldOrientation();
OrientationExtraDiff = fwAngle::LimitRadianAngleSafe(OrientationExtraDiff);
static float orientationTime = 0.3f;
HeliOrientation += OrientationExtraDiff * (orientationTime / fwTimer::GetTimeStep());
float PredictedHeight = vHeliPosition.z + pHeli->GetVelocity().z * 2.0f;
float HeightDiff = fDesiredHeight - PredictedHeight;
float Throttle = 0.0f; // Guess this to stay at same height. (was 0.3f)
if(HeightDiff > 0)
{ // We want to climb
Throttle += HeightDiff / 10.0f;
}
else
{ // We can be pretty rough in the descend since the heli physics slow the guy down a lot anyway.
Throttle += HeightDiff / 5.0f;
}
//if(bLimitDescentSpeed)
{
float limitThrottle;
static float limitSpeed = -3.0f; // limit in m/s
limitThrottle = (limitSpeed - pHeli->GetVelocity().z);
Throttle = rage::Max(Throttle, limitThrottle);
}
Throttle = Clamp(Throttle, -0.5f, 1.0f);
// Make the heli face the target coordinates.
float OrientationDiff = TargetOrientation - HeliOrientation;
OrientationDiff = fwAngle::LimitRadianAngleSafe(OrientationDiff);
const float fYawControlUnclamped = OrientationDiff * -0.5f;
const float fYawControl = rage::Clamp(fYawControlUnclamped, -1.0f, 1.0f);
pHeli->SetYawControl(fYawControl);
Vector3 VecToTarget = vTargetPosition - vHeliPosition;
VecToTarget.z = 0.0f;
Vector3 MoveSpeed = pHeli->GetVelocity();
MoveSpeed.z = 0.0f;
Vector3 rightward = VEC3V_TO_VECTOR3(pHeli->GetTransform().GetA());
rightward.z = 0.0f;
rightward.Normalize();
forwardVec = VEC3V_TO_VECTOR3(pHeli->GetTransform().GetB());
forwardVec.z = 0.0f;
forwardVec.Normalize();
{
// We'd like to move sideways a little bit to circle the target
static dev_float desiredRightSpeed = 3.0f;
static float fTweak2 = -0.025f;
pHeli->SetRollControl((desiredRightSpeed - rightward.Dot(MoveSpeed)) * fTweak2); // was -0.002
}
// Assert(steeringDownMult >= 0.0f && steeringDownMult <= 1.0f);
pHeli->SetRollControl(rage::Clamp(pHeli->GetRollControl(), -0.75f, 0.75f));
float distToTarget = rage::Sqrtf(dX*dX + dY*dY);
{ // Move forward to regulate our targetDistanceFlat to the target.
static float fTweak4 = -0.005f;
pHeli->SetPitchControl((distToTarget - 30.0f) * fTweak4);
}
pHeli->SetPitchControl(rage::Clamp(pHeli->GetPitchControl(), -0.7f, 0.7f));
pHeli->SetThrottleControl(1.0f + Throttle);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SteerAIBoatCirclingPlayer
// PURPOSE : Circle the player so that the gunner on the bow has a good shot.
/////////////////////////////////////////////////////////////////////////////////
void CTaskVehicleCircle::Boat_SteerCirclingTarget(CVehicle *pVehicle, CVehControls* pVehControls, const CEntity *pTarget, float fCruiseSpeed)
{
Assertf(pVehicle, "SteerAIBoatHeadingForTarget expected a valid set vehicle.");
Assertf(pVehControls, "SteerAIBoatHeadingForTarget expected a valid set of vehicle controls.");
float TargetOrientation, BoatOrientation, AngleDiff;
float BoatDirX, BoatDirY, Length, Ratio, Speed, SpeedDiff;
Vector3 SpeedVector;
Assertf(pTarget, "SteerAIBoatCirclingTarget has a NULL target Entity");
const Vector3 vTargetPosition = VEC3V_TO_VECTOR3(pTarget->GetTransform().GetPosition());
const Vector3 vVehiclePosition = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition());
// Calculate the target point (either to the left or to the right of the player)
Vector3 VecTo = (vTargetPosition - vVehiclePosition);
VecTo.z = 0.0f;
VecTo.Normalize();
float Temp;
Temp = VecTo.x;
VecTo.x = VecTo.y;
VecTo.y = -Temp;
if (pVehicle->GetRandomSeed() & 1) // Some boats go clockwise, some counter clockwise.
{
VecTo *= -12.0f;
}
else
{
VecTo *= 26.0f;
}
Vector3 TargetCoors = vTargetPosition + VecTo;
// Vector in the direction of the car
Vector3 vecBoatDir(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()));
BoatDirX = vecBoatDir.x;
BoatDirY = vecBoatDir.y;
Length = rage::Sqrtf( BoatDirX*BoatDirX + BoatDirY*BoatDirY );
if (Length)
{
BoatDirX /= Length;
BoatDirY /= Length;
}
else
{
BoatDirX = 1.0f;
}
// Work out the proper steering angle.
TargetOrientation = fwAngle::GetATanOfXY(TargetCoors.x - vVehiclePosition.x, TargetCoors.y - vVehiclePosition.y);
BoatOrientation = fwAngle::GetATanOfXY(BoatDirX, BoatDirY);
AngleDiff = TargetOrientation - BoatOrientation;
AngleDiff = fwAngle::LimitRadianAngle(AngleDiff);
// Work out the speed so that we can adjust it with gas/brakes.
SpeedVector = pVehicle->GetVelocity();
SpeedVector.z = 0.0f; // Only in horizontal plane
Speed = SpeedVector.Mag();
SpeedDiff = fCruiseSpeed - Speed; // Speed is in m/s. 40km/u=11.1m/s 60=16.7 80=22.2 100=27.8 120=33.3 140=38.9
{
if (SpeedDiff <= 0.0f)
{ // We're going too fast. Slow down a bit.
pVehControls->m_throttle = -0.1f;
if (SpeedDiff < -5.0f)
{
pVehControls->m_throttle = -0.2f;
}
}
else
{ // We're going not quite fast enough
Ratio = SpeedDiff / fCruiseSpeed;
if (Ratio > 0.25f)
{ // Way too slow->full throttle
pVehControls->m_throttle = 1.0f;
}
else
{ // Release gas as we approach desired speed
pVehControls->m_throttle = 1.0f - (0.25f - Ratio) * 4.0f;
}
}
}
pVehControls->m_brake = 0.0f;
pVehControls->SetSteerAngle( rage::Clamp(AngleDiff, -HALF_PI, HALF_PI) );
pVehControls->m_handBrake = false;
}
void CTaskVehicleCircle::SetHelicopterSpecificParams( float fHeliRequestedOrientation, int iFlightHeight, int iMinHeightAboveTerrain, s32 iHeliFlags )
{
m_fHeliRequestedOrientation = fHeliRequestedOrientation;
m_iFlightHeight = iFlightHeight;
m_iMinHeightAboveTerrain = iMinHeightAboveTerrain;
m_iHeliFlags = iHeliFlags;
}
aiTask* CTaskVehicleCircle::Copy() const
{
CTaskVehicleCircle* pTask = rage_new CTaskVehicleCircle(m_Params);
pTask->SetHelicopterSpecificParams(m_fHeliRequestedOrientation, m_iFlightHeight, m_iMinHeightAboveTerrain, m_iHeliFlags);
return pTask;
}
////////////////////////////////////////////////////////////////////
#if !__FINAL
const char * CTaskVehicleCircle::GetStaticStateName( s32 iState )
{
Assert(iState>=State_Start&&iState<=State_Stop);
static const char* aStateNames[] =
{
"State_Start",
"State_Circle",
"State_CircleHeli",
"State_Stop"
};
return aStateNames[iState];
}
#endif