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

1301 lines
38 KiB
C++

// Class Header
#include "Task/Combat/TaskSharkAttack.h"
// Rage Headers
#include "crclip/clip.h"
#include "fwanimation/animmanager.h"
#include "fwanimation/directorcomponentsyncedscene.h"
#include "fwmaths/angle.h"
#include "fwmaths/random.h"
// Game Headers
#include "animation/EventTags.h"
#include "audio/speechaudioentity.h"
#include "Camera/Base/BaseCamera.h"
#include "Camera/CamInterface.h"
#include "camera/syncedscene/SyncedSceneDirector.h"
#include "Camera/Scripted/ScriptDirector.h"
#include "Event/Events.h"
#include "Peds/PedIntelligence.h"
#include "Peds/PlayerInfo.h"
#include "Peds/PedMotionData.h"
#include "Peds/rendering/PedDamage.h"
#include "Physics/WaterTestHelper.h"
#include "Physics/WorldProbe/shapetestmgr.h"
#include "Physics/WorldProbe/shapetestcapsuledesc.h"
#include "Physics/WorldProbe/shapetestresults.h"
#include "Physics/WorldProbe/wpcommon.h"
#include "system/controlMgr.h"
#include "Task/Animation/TaskAnims.h"
#include "Task/Combat/TaskCombatMounted.h"
#include "Task/Combat/TaskDamageDeath.h"
#include "Task/General/TaskBasic.h"
#include "Task/Motion/TaskMotionBase.h"
#include "Task/Motion/Locomotion/TaskFishLocomotion.h"
#include "Task/Movement/TaskGotoPoint.h"
#include "Task/Response/TaskFlee.h"
#include "Vfx/Systems/VfxBlood.h"
AI_OPTIMISATIONS()
//////////////////////////////////////////////////////////////////////////
// CTaskSharkCircle
//////////////////////////////////////////////////////////////////////////
CTaskSharkCircle::Tunables CTaskSharkCircle::sm_Tunables;
IMPLEMENT_COMBAT_TASK_TUNABLES(CTaskSharkCircle, 0xee0de2fd);
CTaskSharkCircle::CTaskSharkCircle(float fMBR, const CAITarget& rTarget, float fRadius, float fAngularSpeed)
: CTaskMoveBase(fMBR)
, m_Target(rTarget)
, m_fAngleOffset(0.0f)
, m_fRadius(fRadius)
, m_fAngularSpeed(fAngularSpeed)
, m_bSetupPattern(false)
{
SetInternalTaskType(CTaskTypes::TASK_SHARK_CIRCLE);
}
bool CTaskSharkCircle::ProcessMove(CPed* pPed)
{
// Early out if there is no target.
if (!m_Target.GetIsValid())
{
return true;
}
if (!m_bSetupPattern)
{
// Project the initial point along the line connecting the shark to the victim.
ProjectInitialPoint();
// Decide on clockwise or counterclockwise circling.
PickCirclingDirection();
}
if (IsCloseToCirclePoint())
{
// Move the circling point along the circle.
float fChange = m_bCirclePositively ? 1.0f : -1.0f;
fChange *= DtoR * m_fAngularSpeed * GetTimeStep();
m_fAngleOffset += fChange;
m_fAngleOffset = fwAngle::LimitRadianAngleSafe(m_fAngleOffset);
}
pPed->GetMotionData()->SetDesiredMoveRateOverride(sm_Tunables.m_MoveRateOverride);
// Project out the point for the shark to aim towards.
ProjectPoint(m_fAngleOffset);
// Point the shark in the direction of the projected point.
m_fTheta = fwAngle::GetRadianAngleBetweenPoints(m_vCurrentTarget.x, m_vCurrentTarget.y, pPed->GetTransform().GetPosition().GetXf(), pPed->GetTransform().GetPosition().GetYf());
// Compute the pitch.
// Compute the delta to the target, the squared magnitude in the XY plane,
// and the squared magnitude of the whole vector.
static const float fEps = 0.0001f;
const float negDeltaX = pPed->GetTransform().GetPosition().GetXf() - m_vCurrentTarget.x;
const float deltaY = m_vCurrentTarget.y - pPed->GetTransform().GetPosition().GetYf();
const float deltaZ = m_vCurrentTarget.z - pPed->GetTransform().GetPosition().GetZf();
const float deltaMagXYSq = negDeltaX*negDeltaX + deltaY*deltaY;
const float deltaMagSq = deltaMagXYSq + deltaZ*deltaZ;
// Compute the cosine of the pitch value. We clamp using fEps here to avoid
// division by zero, but in that case, the result won't actually be used anyway.
const float cosPitch = Min(sqrtf(deltaMagXYSq/Max(deltaMagSq, fEps)), 1.0f);
// Get the absolute value of the pitch angle, and then set the appropriate sign.
const float absPitch = acosf(cosPitch);
m_fPitch = Selectf(deltaZ, absPitch, -absPitch);
// Circling is performed at the MBR passed in by the user.
pPed->GetMotionData()->SetDesiredMoveBlendRatio(GetMoveBlendRatio());
// Circling is never finished, so return false.
return false;
}
// The point is m_fRadius away from the victim.
void CTaskSharkCircle::ProjectPoint(float fDegrees)
{
Vector3 vPos;
m_Target.GetPosition(vPos);
vPos.x -= m_fRadius * rage::Sinf(fDegrees);
vPos.y += m_fRadius * rage::Cosf(fDegrees);
// You never want to circle the target out of the water.
CTaskSharkAttack::CapHeightForWater(RC_VEC3V(vPos));
m_vCurrentTarget = vPos;
}
// Initially the projected point is the point on the circle aligned with the vector between the shark and victim.
void CTaskSharkCircle::ProjectInitialPoint()
{
Vector3 vVictim;
m_Target.GetPosition(vVictim);
// You never want to circle the target out of the water.
CTaskSharkAttack::CapHeightForWater(RC_VEC3V(vVictim));
CPed* pShark = GetPed();
float fDegrees = fwAngle::GetRadianAngleBetweenPoints(pShark->GetTransform().GetPosition().GetXf(), pShark->GetTransform().GetPosition().GetYf(), vVictim.x, vVictim.y);
ProjectPoint(fDegrees);
m_fAngleOffset = fDegrees;
m_bSetupPattern = true;
}
// Decide to go clockwise or counterclockwise.
void CTaskSharkCircle::PickCirclingDirection()
{
Vec3V vForward = GetPed()->GetTransform().GetForward();
vForward.SetZ(ScalarV(V_ZERO));
vForward = NormalizeSafe(vForward, Vec3V(V_ZERO));
// Compute a positive tangent vector to the circle -
// this is the vector in 2D space that is from the initial target point to another point dA degrees along the circle.
const float fDA = 10.0f * DtoR;
Vec3V vCircle1 = VECTOR3_TO_VEC3V(m_vCurrentTarget);
vCircle1.SetZ(ScalarV(V_ZERO));
vCircle1 = NormalizeSafe(vCircle1, Vec3V(V_ZERO));
Vector3 vNext;
m_Target.GetPosition(vNext);
vNext.x -= m_fRadius * rage::Sinf(m_fAngleOffset + fDA);
vNext.y += m_fRadius * rage::Cosf(m_fAngleOffset + fDA);
vNext.z = 0.0f;
Vec3V vCircle2 = VECTOR3_TO_VEC3V(vNext);
vCircle2 = NormalizeSafe(vCircle2, Vec3V(V_ZERO));
Vec3V vPositiveTangent = vCircle2 - vCircle1;
vPositiveTangent = NormalizeSafe(vPositiveTangent, Vec3V(V_ZERO), Vec3V(V_FLT_EPSILON));
// If the forward direction lies in the same direction as the positive tangent vector, then circle positively.
if (IsGreaterThanAll(Dot(vForward, vPositiveTangent), ScalarV(V_ZERO)))
{
m_bCirclePositively = true;
}
else
{
m_bCirclePositively = false;
}
}
// Return true if the local target point is close to the shark (only in the XY plane).
bool CTaskSharkCircle::IsCloseToCirclePoint() const
{
Vec3V vShark = GetPed()->GetTransform().GetPosition();
vShark.SetZ(ScalarV(V_ZERO));
Vec3V vTarget = VECTOR3_TO_VEC3V(m_vCurrentTarget);
vTarget.SetZ(ScalarV(V_ZERO));
if (IsLessThanAll(DistSquared(vTarget, vShark), ScalarV(sm_Tunables.m_AdvanceDistanceSquared)))
{
return true;
}
else
{
return false;
}
}
// Return the position of the ped being circled.
Vector3 CTaskSharkCircle::GetTarget() const
{
Vector3 vPos;
m_Target.GetPosition(vPos);
return vPos;
}
Vector3 CTaskSharkCircle::GetLookAheadTarget() const
{
return GetTarget();
}
void CTaskSharkCircle::SetTarget(const CPed* pPed, Vec3V_In vTarget, const bool UNUSED_PARAM(bForce))
{
if (pPed)
{
m_Target.SetEntity(pPed);
}
else
{
m_Target.SetPosition(VEC3V_TO_VECTOR3(vTarget));
}
}
// Return the local projected circling point.
const Vector3& CTaskSharkCircle::GetCurrentTarget()
{
return m_vCurrentTarget;
}
void CTaskSharkCircle::CleanUp()
{
CPed* pPed = GetPed();
CTaskFishLocomotion::TogglePedSwimNearSurface(pPed, false);
}
#if !__FINAL
void CTaskSharkCircle::Debug() const
{
#if DEBUG_DRAW
Vector3 vCircleArea;
m_Target.GetPosition(vCircleArea);
const Vector3& vLocalTarget = m_vCurrentTarget;
grcDebugDraw::Circle(vCircleArea, m_fRadius, Color_white, XAXIS, YAXIS);
grcDebugDraw::Sphere(vLocalTarget, 1.0f, Color_white, false);
#endif
}
#endif
//////////////////////////////////////////////////////////////////////////
// CTaskSharkAttack
//////////////////////////////////////////////////////////////////////////
CTaskSharkAttack::Tunables CTaskSharkAttack::sm_Tunables;
IMPLEMENT_COMBAT_TASK_TUNABLES(CTaskSharkAttack, 0xa3b85000);
const strStreamingObjectName CTaskSharkAttack::ms_CameraAnimation("attack_cam",0x392B39BC);
const strStreamingObjectName CTaskSharkAttack::ms_SharkBiteDict("creatures@shark@move",0xA943497F);
s32 CTaskSharkAttack::sm_NumActiveSharks = 0;
CTaskSharkAttack::CTaskSharkAttack(CPed* pTarget)
: m_pTarget(pTarget)
, m_fCirclingCounter(0.0f)
, m_vCachedLungeLocation(V_ZERO)
, m_vFleePosition()
, m_SyncedSceneID(INVALID_SYNCED_SCENE_ID)
, m_iNumberOfFakeApproachesLeft(0)
, m_bHasAppliedPedDamage(false)
, m_bIsFleeingShore(false)
, m_bSharkCountNeedsCleanup(false)
{
SetInternalTaskType(CTaskTypes::TASK_SHARK_ATTACK);
}
CTaskSharkAttack::~CTaskSharkAttack()
{
}
void CTaskSharkAttack::CleanUp()
{
//cleanup the camera
camInterface::GetSyncedSceneDirector().StopAnimatingCamera(camSyncedSceneDirector::TASK_SHARK_ATTACK_CLIENT, 2000);
// Cleanup collision.
CPed* pPed = GetPed();
pPed->SetNoCollision(NULL, 0);
// Cleanup local avoidance.
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_SteersAroundPeds, true);
// Cleanup the pitch.
pPed->GetMotionData()->SetDesiredPitch(0.0f);
// Let them die for being out of water again.
CTaskFishLocomotion::ToggleFishSurvivesBeingOutOfWater(pPed, false);
// Reset the shark count if necessary.
if (m_bSharkCountNeedsCleanup)
{
sm_NumActiveSharks -= 1;
m_bSharkCountNeedsCleanup = false;
}
}
CTask::FSM_Return CTaskSharkAttack::ProcessPreFSM()
{
// Target no longer exists.
// TODO - Enable this task for things other than the local player.
if(!m_pTarget || !m_pTarget->IsLocalPlayer() || m_pTarget->IsInjured() || m_pTarget->GetIsDeadOrDying())
{
return FSM_Quit;
}
CPed* pPed = GetPed();
// Never slow for cornering.
pPed->SetPedResetFlag(CPED_RESET_FLAG_SuppressSlowingForCorners, true);
// Disable timeslicing while running this task.
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
return FSM_Continue;
}
CTask::FSM_Return CTaskSharkAttack::UpdateFSM(const s32 iState, const FSM_Event iEvent)
{
FSM_Begin
FSM_State(State_Start)
FSM_OnUpdate
return Start_OnUpdate();
FSM_State(State_Surface)
FSM_OnEnter
Surface_OnEnter();
FSM_OnUpdate
return Surface_OnUpdate();
FSM_State(State_Follow)
FSM_OnEnter
Follow_OnEnter();
FSM_OnUpdate
return Follow_OnUpdate();
FSM_State(State_Circle)
FSM_OnEnter
Circle_OnEnter();
FSM_OnUpdate
return Circle_OnUpdate();
FSM_State(State_Dive)
FSM_OnEnter
Dive_OnEnter();
FSM_OnUpdate
return Dive_OnUpdate();
FSM_State(State_FakeLunge)
FSM_OnEnter
FakeLunge_OnEnter();
FSM_OnUpdate
return FakeLunge_OnUpdate();
FSM_State(State_Lunge)
FSM_OnEnter
Lunge_OnEnter();
FSM_OnUpdate
return Lunge_OnUpdate();
FSM_OnExit
Lunge_OnExit();
FSM_State(State_Bite)
FSM_OnEnter
Bite_OnEnter();
FSM_OnUpdate
return Bite_OnUpdate();
FSM_OnExit
Bite_OnExit();
FSM_State(State_Flee)
FSM_OnEnter
Flee_OnEnter();
FSM_OnUpdate
return Flee_OnUpdate();
FSM_State(State_Finish)
FSM_OnUpdate
return FSM_Quit;
FSM_End
}
CTask::FSM_Return CTaskSharkAttack::Start_OnUpdate()
{
// Sharks never die when out of water while in this task.
CTaskFishLocomotion::ToggleFishSurvivesBeingOutOfWater(GetPed(), true);
m_iNumberOfFakeApproachesLeft = rage::round(fwRandom::GetRandomNumberInRange((float)sm_Tunables.m_MinNumberFakeApproaches, (float)sm_Tunables.m_MaxNumberFakeApproaches));
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (TargetInVehicle())
{
SetState(State_Follow);
}
else
{
if (IsTargetUnderwater())
{
SetState(State_Circle);
}
else
{
SetState(State_Surface);
}
}
// Note that a shark is attacking the player.
sm_NumActiveSharks += 1;
m_bSharkCountNeedsCleanup = true;
return FSM_Continue;
}
void CTaskSharkAttack::Surface_OnEnter()
{
CPed* pPed = GetPed();
// Attempt to get near the surface so the player can see the fin.
Vec3V vPed = pPed->GetTransform().GetPosition();
Vec3V vForward = pPed->GetTransform().GetForward();
float fTargetZ = m_pTarget->m_Buoyancy.GetAvgAbsWaterLevel() - sm_Tunables.m_SurfaceZOffset;
Vec3V vSurfaceTarget(vPed.GetXf() + (vForward.GetXf() * sm_Tunables.m_SurfaceProjectionDistance),
vPed.GetYf() + (vForward.GetYf() * sm_Tunables.m_SurfaceProjectionDistance), fTargetZ);
CTaskMoveGoToPoint* pMoveTask = rage_new CTaskMoveGoToPoint(MOVEBLENDRATIO_WALK, VEC3V_TO_VECTOR3(vSurfaceTarget), 0.1f);
// Don't stick to the surface when surfacing.
CTaskFishLocomotion::TogglePedSwimNearSurface(pPed, false);
SetNewTask(rage_new CTaskComplexControlMovement(pMoveTask));
}
CTask::FSM_Return CTaskSharkAttack::Surface_OnUpdate()
{
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (TargetInVehicle())
{
SetState(State_Follow);
}
else if (IsTargetUnderwater())
{
SetState(State_Circle);
}
else if (GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
SetState(State_Circle);
}
else
{
// Check if we have achieved our target depth.
float fWaterZ = 0.0f;
CPed* pPed = GetPed();
Vec3V vPed = pPed->GetTransform().GetPosition();
if (Verifyf(CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(VEC3V_TO_VECTOR3(vPed), &fWaterZ, true), "Shark was mysteriously out of the water while surfacing."))
{
float fDesiredZ = pPed->GetTaskData().GetSurfaceSwimmingDepthOffset();
if (fWaterZ - fDesiredZ <= vPed.GetZf())
{
SetState(State_Circle);
}
}
}
return FSM_Continue;
}
void CTaskSharkAttack::Follow_OnEnter()
{
// Goto a point below and behind the target.
m_vCachedLungeLocation = GenerateFollowLocation();
Vector3 vTarget = VEC3V_TO_VECTOR3(m_vCachedLungeLocation);
CTaskMoveGoToPoint* pMoveTask = rage_new CTaskMoveGoToPoint(MOVEBLENDRATIO_SPRINT, vTarget, sm_Tunables.m_LungeTargetRadius);
SetNewTask(rage_new CTaskComplexControlMovement(pMoveTask));
}
CTask::FSM_Return CTaskSharkAttack::Follow_OnUpdate()
{
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (!TargetInVehicle())
{
if (IsTargetUnderwater())
{
SetState(State_Circle);
}
else
{
SetState(State_Surface);
}
}
else if (GetTimeInState() > sm_Tunables.m_FollowTimeout)
{
SetState(State_Flee);
}
else
{
if (GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
SetState(State_Circle);
}
else
{
CPed* pPed = GetPed();
// Enable surface sticking if close.
CTaskFishLocomotion::CheckAndRespectSwimNearSurface(pPed);
if (GetSubTask() && GetSubTask()->GetTaskType() == CTaskTypes::TASK_COMPLEX_CONTROL_MOVEMENT)
{
// Check if the target has moved, if so update the position of the goto point.
Vec3V vNewPosition = GenerateFollowLocation();
if (IsGreaterThanAll(DistSquared(m_vCachedLungeLocation, vNewPosition), ScalarV(sm_Tunables.m_LungeChangeDistance * sm_Tunables.m_LungeChangeDistance)))
{
m_vCachedLungeLocation = vNewPosition;
CTaskComplexControlMovement* pCCM = static_cast<CTaskComplexControlMovement*>(GetSubTask());
pCCM->SetTarget(m_pTarget, VEC3V_TO_VECTOR3(m_vCachedLungeLocation));
}
}
}
}
return FSM_Continue;
}
void CTaskSharkAttack::Circle_OnEnter()
{
m_fCirclingCounter = 0.0f;
CPed* pPed = GetPed();
// Turn off avoidance.
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_SteersAroundPeds, false);
float fRadius = Clamp(Dist(pPed->GetTransform().GetPosition(), m_pTarget->GetTransform().GetPosition()).Getf(), sm_Tunables.m_MinCircleRadius, sm_Tunables.m_MaxCircleRadius);
float fAngularSpeed = sm_Tunables.m_CirclingAngularSpeed;
CAITarget target;
target.SetEntity(m_pTarget);
// Circle wider if the target is in/on a vehicle.
CTaskSharkCircle* pTaskCircle = rage_new CTaskSharkCircle(sm_Tunables.m_CirclingMBR, target, TargetInVehicle() ? fRadius * 2.0f : fRadius , fAngularSpeed);
SetNewTask(rage_new CTaskComplexControlMovement(pTaskCircle));
}
CTask::FSM_Return CTaskSharkAttack::Circle_OnUpdate()
{
const bool bTargetInVehicle = TargetInVehicle();
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (bTargetInVehicle && !ChasingAStationaryVehicle())
{
SetState(State_Follow);
}
else if (!bTargetInVehicle)
{
// Otherwise increment the circling counter.
m_fCirclingCounter += GetTimeStep();
}
// When circling, stay near the surface if possible.
CTaskFishLocomotion::CheckAndRespectSwimNearSurface(GetPed());
if (m_fCirclingCounter > sm_Tunables.m_TimeToCircle)
{
if (ShouldDoAFakeLunge())
{
SetState(State_FakeLunge);
}
else
{
SetState(State_Dive);
}
}
return FSM_Continue;
}
void CTaskSharkAttack::Dive_OnEnter()
{
CPed* pPed = GetPed();
Vec3V vPed = pPed->GetTransform().GetPosition();
Vec3V vForward = pPed->GetTransform().GetForward();
Vector3 vTarget(vPed.GetXf() + (vForward.GetXf() * sm_Tunables.m_DiveProjectionDistance),
vPed.GetYf() + (vForward.GetYf() * sm_Tunables.m_DiveProjectionDistance), vPed.GetZf() - sm_Tunables.m_DiveDepth);
CTaskMoveGoToPoint* pMoveTask = rage_new CTaskMoveGoToPoint(sm_Tunables.m_DiveMBR, vTarget, 0.5f);
// Don't stick near the surface when diving.
CTaskFishLocomotion::TogglePedSwimNearSurface(pPed, false);
SetNewTask(rage_new CTaskComplexControlMovement(pMoveTask));
}
CTask::FSM_Return CTaskSharkAttack::Dive_OnUpdate()
{
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (TargetInVehicle())
{
SetState(State_Follow);
}
else if (GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
// Check if we have achieved our target depth - if so lunge towards the target.
SetState(State_Lunge);
}
return FSM_Continue;
}
// A place to lunge based on the target's current position.
// Slightly under the target and behind the target.
Vec3V_Out CTaskSharkAttack::GenerateLungeLocation() const
{
Vec3V vPosition = m_pTarget->GetTransform().GetPosition();
vPosition.SetZf(vPosition.GetZf() - sm_Tunables.m_LungeZOffset);
Vec3V vForward = m_pTarget->GetTransform().GetForward();
vForward = Scale(vForward, ScalarV(sm_Tunables.m_LungeForwardOffset));
vPosition -= vForward;
return vPosition;
}
// A place to head to when following based on the target's current position.
// Slightly under and behind.
Vec3V_Out CTaskSharkAttack::GenerateFollowLocation() const
{
Vec3V vPosition = m_pTarget->GetTransform().GetPosition();
vPosition.SetZf(vPosition.GetZf() - sm_Tunables.m_FollowZOffset);
Vec3V vForward = m_pTarget->GetTransform().GetForward();
vForward = Scale(vForward, ScalarV(sm_Tunables.m_FollowYOffset));
vPosition -= vForward;
// We never want to try to follow something out of the water.
// The locomotion task is trying to stick to the waves, but it lerps to the right Z which takes a few frames.
// It's possible for the shark to outrun the lerping, thus launching into the air.
CapHeightForWater(vPosition);
return vPosition;
}
void CTaskSharkAttack::FakeLunge_OnEnter()
{
CPed* pShark = GetPed();
// Compute a target point along the vector from the shark to the prey, but slightly past the prey.
Vec3V vPrey = m_pTarget->GetTransform().GetPosition();
Vec3V vShark = pShark->GetTransform().GetPosition();
Vec3V vToPrey = vPrey - vShark;
vToPrey = NormalizeSafe(vToPrey, Vec3V(V_ZERO));
ScalarV vDist = Dist(vPrey, vShark) + ScalarV(sm_Tunables.m_FakeLungeOffset);
Vec3V vPos = Scale(vToPrey, vDist) + vShark;
// Adjust the target slightly to the right or left.
Vec3V vSideOffset = m_pTarget->GetTransform().GetRight();
if (fwRandom::GetRandomTrueFalse())
{
vPos += vSideOffset;
}
else
{
vPos -= vSideOffset;
}
CTaskMoveGoToPoint* pMoveTask = rage_new CTaskMoveGoToPoint(MOVEBLENDRATIO_SPRINT, VEC3V_TO_VECTOR3(vPos), sm_Tunables.m_LungeTargetRadius);
SetNewTask(rage_new CTaskComplexControlMovement(pMoveTask));
// Note that we have attempted a fake lunge.
m_iNumberOfFakeApproachesLeft -= 1;
}
CTask::FSM_Return CTaskSharkAttack::FakeLunge_OnUpdate()
{
CPed* pPed = GetPed();
// If possible, stick near the surface during this state.
if (IsTargetUnderwater() && (m_pTarget->GetTransform().GetPosition().GetZf() < pPed->GetTransform().GetPosition().GetZf()))
{
CTaskFishLocomotion::TogglePedSwimNearSurface(pPed, false);
}
else
{
CTaskFishLocomotion::CheckAndRespectSwimNearSurface(pPed);
}
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
SetState(State_Circle);
}
// Apply extra steering to get the shark at the lunge location.
if (!pPed->GetUsingRagdoll())
{
float fCurrentHeading = pPed->GetCurrentHeading();
float fDesiredHeading = pPed->GetDesiredHeading();
float fDelta = CTaskMotionBase::InterpValue(fCurrentHeading, fDesiredHeading, 1.0f, true);
pPed->GetMotionData()->SetExtraHeadingChangeThisFrame(fDelta);
}
return FSM_Continue;
}
void CTaskSharkAttack::Lunge_OnEnter()
{
m_vCachedLungeLocation = GenerateLungeLocation();
Vector3 vTarget = VEC3V_TO_VECTOR3(m_vCachedLungeLocation);
CTaskMoveGoToPoint* pMoveTask = rage_new CTaskMoveGoToPoint(MOVEBLENDRATIO_SPRINT, vTarget, sm_Tunables.m_LungeTargetRadius);
// Turn the shark invincible during it's final approach.
GetPed()->m_nPhysicalFlags.bNotDamagedByAnything = true;
SetNewTask(rage_new CTaskComplexControlMovement(pMoveTask));
}
CTask::FSM_Return CTaskSharkAttack::Lunge_OnUpdate()
{
if (ImminentCollisionWithShore())
{
SetState(State_Flee);
}
else if (TargetInVehicle())
{
SetState(State_Follow);
}
else if (!CanTryToKillTarget())
{
// The player is invincible. Reset the number of times to do fake lunges and return to circling.
m_iNumberOfFakeApproachesLeft = rage::round(fwRandom::GetRandomNumberInRange((float)sm_Tunables.m_MinNumberFakeApproaches, (float)sm_Tunables.m_MaxNumberFakeApproaches));
SetState(State_Circle);
}
if (GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
if (m_pTarget->GetUsingRagdoll())
{
// If the ped is already ragdolling then they won't be able to accept the sync scene task.
// Go back to circling.
SetState(State_Circle);
}
else
{
// Arrived at the target, play the animation and kill the prey.
SetState(State_Bite);
}
}
else if (GetSubTask() && GetSubTask()->GetTaskType() == CTaskTypes::TASK_COMPLEX_CONTROL_MOVEMENT)
{
CPed* pPed = GetPed();
// If possible, stick near the surface during this state.
if (IsTargetUnderwater() && (m_pTarget->GetTransform().GetPosition().GetZf() < pPed->GetTransform().GetPosition().GetZf()))
{
CTaskFishLocomotion::TogglePedSwimNearSurface(pPed, false);
}
else
{
CTaskFishLocomotion::CheckAndRespectSwimNearSurface(pPed);
}
Vec3V vNewPosition = GenerateLungeLocation();
if (IsGreaterThanAll(DistSquared(m_vCachedLungeLocation, vNewPosition), ScalarV(sm_Tunables.m_LungeChangeDistance * sm_Tunables.m_LungeChangeDistance)))
{
m_vCachedLungeLocation = vNewPosition;
CTaskComplexControlMovement* pCCM = static_cast<CTaskComplexControlMovement*>(GetSubTask());
pCCM->SetTarget(m_pTarget, VEC3V_TO_VECTOR3(m_vCachedLungeLocation));
}
// Apply extra steering to get the shark at the lunge location.
if (!pPed->GetUsingRagdoll())
{
float fCurrentHeading = pPed->GetCurrentHeading();
float fDesiredHeading = pPed->GetDesiredHeading();
float fDelta = CTaskMotionBase::InterpValue(fCurrentHeading, fDesiredHeading, 1.0f, true);
pPed->GetMotionData()->SetExtraHeadingChangeThisFrame(fDelta);
}
}
return FSM_Continue;
}
void CTaskSharkAttack::Lunge_OnExit()
{
// Make the shark vulnerable again.
GetPed()->m_nPhysicalFlags.bNotDamagedByAnything = false;
}
// Start the synched scene between the biter and the bitee.
void CTaskSharkAttack::Bite_OnEnter()
{
CPed* pPed = GetPed();
pPed->SetNoCollision(m_pTarget, NO_COLLISION_NEEDS_RESET);
// Turn the shark invincible during the bite.
pPed->m_nPhysicalFlags.bNotDamagedByAnything = true;
CreateSyncedScene();
eSyncedSceneFlagsBitSet flags;
flags.BitSet().Set(SYNCED_SCENE_USE_KINEMATIC_PHYSICS, true);
flags.BitSet().Set(SYNCED_SCENE_DONT_INTERRUPT, true);
flags.BitSet().Set(SYNCED_SCENE_PRESERVE_VELOCITY, true);
if (m_SyncedSceneID != INVALID_SYNCED_SCENE_ID)
{
// TODO - SynchronizedScene takes a partial hash, which doesn't seem to like being passed atHashString objects which don't have c-strings in final builds.
// The clip names would otherwise be able to be pulled into a static const somewhere, or preferably even reside in metadata.
CTaskSynchronizedScene* pSharkTask = rage_new CTaskSynchronizedScene(m_SyncedSceneID, ATPARTIALSTRINGHASH("attack", 0xED1199FE), ms_SharkBiteDict, NORMAL_BLEND_IN_DELTA, NORMAL_BLEND_OUT_DELTA, flags, CTaskSynchronizedScene::RBF_NONE, SLOW_BLEND_IN_DELTA);
SetNewTask(pSharkTask);
CTaskSynchronizedScene* pVictimTask = rage_new CTaskSynchronizedScene(m_SyncedSceneID, ATPARTIALSTRINGHASH("attack_player", 0x99F875AC), ms_SharkBiteDict, NORMAL_BLEND_IN_DELTA, NORMAL_BLEND_OUT_DELTA, flags, CTaskSynchronizedScene::RBF_NONE, SLOW_BLEND_IN_DELTA);
CEventGivePedTask sceneEvent(PED_TASK_PRIORITY_PRIMARY, pVictimTask);
m_pTarget->GetPedIntelligence()->AddEvent(sceneEvent);
if (m_pTarget->IsLocalPlayer())
{
CreateSyncedSceneCamera();
}
// Trigger VFX
g_vfxBlood.TriggerPtFxBloodSharkAttack(m_pTarget);
if(pPed->GetSpeechAudioEntity())
pPed->GetSpeechAudioEntity()->TriggerSharkAttackSound(m_pTarget);
}
}
// Kill the victim when the scene is finished playing.
CTask::FSM_Return CTaskSharkAttack::Bite_OnUpdate()
{
if (!fwAnimDirectorComponentSyncedScene::IsValidSceneId(m_SyncedSceneID))
{
// Something went wrong, so abort out.
SetState(State_Finish);
}
else
{
float fPhase = fwAnimDirectorComponentSyncedScene::GetSyncedScenePhase(m_SyncedSceneID);
// Possibly cause rumble on the player's controller.
ProcessRumble();
// Possibly trigger ped damage effects.
ProcessPedDamage();
// Block ragdolling - if the ped ragdolled while the sync scene played it would end which looks quite bad.
GetPed()->SetPedResetFlag(CPED_RESET_FLAG_NeverRagdoll, true);
m_pTarget->SetPedResetFlag(CPED_RESET_FLAG_NeverRagdoll, true);
if (fPhase >= 1.0f)
{
// The animation has finished playing.
SetState(State_Finish);
}
}
return FSM_Continue;
}
void CTaskSharkAttack::Bite_OnExit()
{
CPed* pPed = GetPed();
// Clear the flags so both peds can ragdoll this frame if they need to.
pPed->SetPedResetFlag(CPED_RESET_FLAG_NeverRagdoll, false);
m_pTarget->SetPedResetFlag(CPED_RESET_FLAG_NeverRagdoll, false);
if (Verifyf(fwAnimDirectorComponentSyncedScene::IsValidSceneId(m_SyncedSceneID), "Invalid synced scene!"))
{
// Release the synced scene ref.
fwAnimDirectorComponentSyncedScene::DecSyncedSceneRef(m_SyncedSceneID);
m_SyncedSceneID = INVALID_SYNCED_SCENE_ID;
// Release the camera if one was created.
camInterface::GetSyncedSceneDirector().StopAnimatingCamera(camSyncedSceneDirector::TASK_SHARK_ATTACK_CLIENT, 2000);
// Kill the target.
CDeathSourceInfo info(pPed);
CTask* pTaskDie = rage_new CTaskDyingDead(&info, CTaskDyingDead::Flag_ragdollDeath);
CEventGivePedTask deathEvent(PED_TASK_PRIORITY_PRIMARY, pTaskDie);
m_pTarget->GetPedIntelligence()->AddEvent(deathEvent);
}
// Make the shark vulnerable again.
pPed->m_nPhysicalFlags.bNotDamagedByAnything = false;
}
void CTaskSharkAttack::Flee_OnEnter()
{
// Swim away - either from the shore or the ped we are trying to kill.
CAITarget fleeTarget;
if (m_bIsFleeingShore)
{
fleeTarget.SetPosition(m_vFleePosition);
}
else
{
fleeTarget.SetEntity(m_pTarget);
}
CTaskSmartFlee* pFleeTask = rage_new CTaskSmartFlee(fleeTarget, sm_Tunables.m_SharkFleeDist);
pFleeTask->SetQuitTaskIfOutOfRange(true);
pFleeTask->GetConfigFlags().SetFlag(CTaskSmartFlee::CF_NeverLeaveWater | CTaskSmartFlee::CF_FleeAtCurrentHeight);
SetNewTask(pFleeTask);
}
CTask::FSM_Return CTaskSharkAttack::Flee_OnUpdate()
{
// Swim near the surface if possible.
CTaskFishLocomotion::CheckAndRespectSwimNearSurface(GetPed());
// The shark was fleeing the player, but an obstruction happened while swimming away.
// Change the target of the flee to be the shore by restarting this state.
if (!m_bIsFleeingShore && ImminentCollisionWithShore())
{
SetFlag(aiTaskFlags::RestartCurrentState);
}
// When the flee task ends, quit this task.
if (GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
SetState(State_Finish);
}
return FSM_Continue;
}
#if !__FINAL
const char * CTaskSharkAttack::GetStaticStateName( s32 iState )
{
taskAssert(iState >= State_Start && iState <= State_Finish);
switch (iState)
{
case State_Start: return "State_Start";
case State_Surface: return "State_Surface";
case State_Follow: return "State_Follow";
case State_Circle: return "State_Circle";
case State_Dive: return "State_Dive";
case State_FakeLunge: return "State_FakeLunge";
case State_Lunge: return "State_Lunge";
case State_Bite: return "State_Bite";
case State_Flee: return "State_Flee";
case State_Finish: return "State_Finish";
default: taskAssert(0);
}
return "State_Invalid";
}
#endif // !__FINAL
// A target can be lunged at if it is swimming or in the water.
bool CTaskSharkAttack::CanTargetBeLungedAt() const
{
return m_pTarget->GetIsInWater() || m_pTarget->GetIsSwimming();
}
// A target is underwater if they are at least one meter below the surface.
bool CTaskSharkAttack::IsTargetUnderwater() const
{
// In a boat.
if (m_pTarget->GetIsInVehicle())
{
return false;
}
// Standing on a box.
if (m_pTarget->GetGroundPhysical())
{
return false;
}
static const float s_fWaterHeightTolerance = 1.0f;
const float fTargetZ = m_pTarget->GetTransform().GetPosition().GetZf();
const float fHeight = m_pTarget->m_Buoyancy.GetAbsWaterLevel() - fTargetZ;
return fHeight > s_fWaterHeightTolerance;
}
bool CTaskSharkAttack::ImminentCollisionWithShore()
{
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
WorldProbe::CShapeTestFixedResults<> probeResults;
Vector3 vStart = VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetPosition());
Vector3 vEnd = VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetForward()) * sm_Tunables.m_LandProbeLength;
vEnd += vStart;
capsuleDesc.SetCapsule(vStart, vEnd, 0.4f);
capsuleDesc.SetResultsStructure(&probeResults);
capsuleDesc.SetIsDirected(true);
capsuleDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
capsuleDesc.SetContext(WorldProbe::EMovementAI);
if (WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST))
{
// Note the position of the impact so that the shark can flee this point.
if (probeResults.GetNumHits() >= 1)
{
m_vFleePosition = probeResults[0].GetHitPosition();
m_bIsFleeingShore = true;
}
return true;
}
else
{
return false;
}
}
bool CTaskSharkAttack::TargetInVehicle() const
{
// Check for being in a vehicle.
if (m_pTarget->GetIsInVehicle())
{
return true;
}
// Check for entering in a vehicle, which we treat as in a vehicle
// (i.e. we shouldn't attack while in this state)
if (m_pTarget->GetIsEnteringVehicle(true))
{
return true;
}
// Similarly, check for the exit task.
const bool bExitTaskRunning = m_pTarget->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE, true);
if (bExitTaskRunning)
{
return true;
}
// Check for standing on something, which we treat as in a vehicle.
CPhysical* pPhysical = m_pTarget->GetGroundPhysical();
if (pPhysical)
{
return true;
}
return false;
}
bool CTaskSharkAttack::ChasingAStationaryVehicle() const
{
CVehicle* pVehicle = m_pTarget->GetVehiclePedInside();
if (!pVehicle)
{
CPhysical* pPhysical = m_pTarget->GetGroundPhysical();
if (pPhysical && pPhysical->GetIsTypeVehicle())
{
pVehicle = static_cast<CVehicle*>(pPhysical);
}
}
if (!pVehicle)
{
return false;
}
else
{
Vector3 vVel = pVehicle->GetVelocity();
if (vVel.Mag2() < sm_Tunables.m_MovingVehicleVelocityThreshold)
{
return true;
}
else
{
return false;
}
}
}
bool CTaskSharkAttack::ShouldDoAFakeLunge() const
{
return m_iNumberOfFakeApproachesLeft > 0;
}
bool CTaskSharkAttack::CanTryToKillTarget() const
{
// Respect debug invulnerability.
if (false NOTFINAL_ONLY(|| (m_pTarget->IsDebugInvincible())))
{
return false;
}
// If the player is outside the world extents, they can always be killed.
if (m_pTarget->GetPlayerInfo() && m_pTarget->GetPlayerInfo()->HasPlayerLeftTheWorld())
{
return true;
}
// Respect cheat commands.
if (m_pTarget->m_nPhysicalFlags.bNotDamagedByAnything)
{
return false;
}
return true;
}
void CTaskSharkAttack::CreateSyncedScene()
{
// Place the root at the victim's position and heading, offset downwards slightly to prevent waves causing the scene to play out above the waterline.
CPed* pPed = m_pTarget;
Vector3 vPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
vPos.z -= 0.9f;
Vector3 vEulers(0.0f, 0.0f, pPed->GetCurrentHeading());
Quaternion qRot;
qRot.FromEulers(vEulers, "xyz");
fwSyncedSceneId sceneID = fwAnimDirectorComponentSyncedScene::StartSynchronizedScene();
if (Verifyf(sceneID != INVALID_SYNCED_SCENE_ID, "Unable to create new synchronized scene! Synced scene pool may be full."))
{
// Center it.
fwAnimDirectorComponentSyncedScene::SetSyncedSceneOrigin(sceneID, vPos, qRot);
// Increment ref count
fwAnimDirectorComponentSyncedScene::AddSyncedSceneRef(sceneID);
// Set it up to play once and then end.
fwAnimDirectorComponentSyncedScene::SetSyncedSceneLooped(sceneID, false);
fwAnimDirectorComponentSyncedScene::SetSyncedSceneHoldLastFrame(sceneID, false);
}
m_SyncedSceneID = sceneID;
}
void CTaskSharkAttack::CreateSyncedSceneCamera()
{
const s32 animDictIndex = fwAnimManager::FindSlotFromHashKey(ms_SharkBiteDict).Get();
const crClip* clip = fwAnimManager::GetClipIfExistsByDictIndex(animDictIndex, ms_CameraAnimation.GetHash());
if (taskVerifyf(clip, "Couldn't find camera animation for shark attack synced scene"))
{
// Animate the camera as part of the synced scene.
Matrix34 sceneOrigin;
fwAnimDirectorComponentSyncedScene::GetSyncedSceneOrigin(m_SyncedSceneID, sceneOrigin);
camInterface::GetSyncedSceneDirector().AnimateCamera(ms_SharkBiteDict, *clip, sceneOrigin, m_SyncedSceneID, 0 ,camSyncedSceneDirector::TASK_SHARK_ATTACK_CLIENT);
}
}
// Possibly apply ped damage based on the phase.
void CTaskSharkAttack::ProcessPedDamage()
{
if (m_bHasAppliedPedDamage)
{
return;
}
fwAnimDirector* pDirector = GetPed()->GetAnimDirector();
float fPhase = fwAnimDirectorComponentSyncedScene::GetSyncedScenePhase(m_SyncedSceneID);
if (pDirector && pDirector->GetComponent<fwAnimDirectorComponentSyncedScene>())
{
const crClip* pClip = pDirector->GetComponent<fwAnimDirectorComponentSyncedScene>()->GetSyncedSceneClip();
if (pClip)
{
float fDamagePhase = 1.0f;
if(CClipEventTags::FindEventPhase(pClip, CClipEventTags::SharkAttackPedDamage, fDamagePhase))
{
if (fPhase >= fDamagePhase)
{
// Add ped damage.
PEDDAMAGEMANAGER.AddPedDamagePack(m_pTarget, "SCR_SHARK", 0.0f, 1.0f);
// Note that we have applied ped damage.
m_bHasAppliedPedDamage = true;
}
}
}
}
}
// Possibly apply pad rumble based on the phase.
void CTaskSharkAttack::ProcessRumble()
{
if (m_bHasAppliedPedDamage)
{
return;
}
fwAnimDirector* pDirector = GetPed()->GetAnimDirector();
float fPhase = fwAnimDirectorComponentSyncedScene::GetSyncedScenePhase(m_SyncedSceneID);
if (pDirector && pDirector->GetComponent<fwAnimDirectorComponentSyncedScene>())
{
const crClip* pClip = pDirector->GetComponent<fwAnimDirectorComponentSyncedScene>()->GetSyncedSceneClip();
if (pClip)
{
float fDamagePhase = 1.0f;
if(CClipEventTags::FindEventPhase(pClip, CClipEventTags::SharkAttackPedDamage, fDamagePhase))
{
if (fPhase >= fDamagePhase)
{
CControlMgr::StartPlayerPadShakeByIntensity(1200, 0.5f);
}
}
}
}
}
// Cap the Z-position of a target based on the water height.
void CTaskSharkAttack::CapHeightForWater(Vec3V_InOut vPosition)
{
float fWaterHeight = vPosition.GetZf();
if (CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(RCC_VECTOR3(vPosition), &fWaterHeight, true))
{
vPosition.SetZf(Min(vPosition.GetZf(), fWaterHeight));
}
}
/////////////////////////////////////////////////////////////////////////////
// Clone task implementation.
// Sharks aren't networked yet but they could be in the future,
// this is mainly being done to get the queriable interface working with it.
/////////////////////////////////////////////////////////////////////////////
CTaskInfo* CTaskSharkAttack::CreateQueriableState() const
{
CTaskInfo* pInfo = rage_new CClonedTaskSharkAttackInfo(GetState(), m_pTarget);
return pInfo;
}
void CTaskSharkAttack::ReadQueriableState(CClonedFSMTaskInfo* pTaskInfo)
{
taskAssert(pTaskInfo->GetTaskInfoType() == CTaskInfo::INFO_TYPE_SHARK_ATTACK);
CClonedTaskSharkAttackInfo* pSharkAttackInfo = static_cast<CClonedTaskSharkAttackInfo*>(pTaskInfo);
m_pTarget = static_cast<CPed*>(pSharkAttackInfo->GetTarget());
// Call the base implementation - syncs the state
CTaskFSMClone::ReadQueriableState(pTaskInfo);
}
CTaskFSMClone* CTaskSharkAttack::CreateTaskForClonePed(CPed* UNUSED_PARAM(pPed))
{
CTaskSharkAttack* pTask = rage_new CTaskSharkAttack(m_pTarget);
pTask->SetState(GetState());
return pTask;
}
CTaskFSMClone* CTaskSharkAttack::CreateTaskForLocalPed(CPed *pPed)
{
// do the same as clone ped
return CreateTaskForClonePed(pPed);
}
CTask::FSM_Return CTaskSharkAttack::UpdateClonedFSM (const s32 iState, const FSM_Event iEvent)
{
return UpdateFSM(iState, iEvent);
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Task info for shark attack.
/////////////////////////////////////////////////////////////////////////////////////////////
CClonedTaskSharkAttackInfo::CClonedTaskSharkAttackInfo(s32 iState, CPed* pTargetPed)
: m_TargetPed(pTargetPed)
{
SetStatusFromMainTaskState(iState);
}
CTaskFSMClone* CClonedTaskSharkAttackInfo::CreateCloneFSMTask()
{
CTaskSharkAttack* pAttackTask = rage_new CTaskSharkAttack(m_TargetPed.GetPed());
return pAttackTask;
}