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

350 lines
15 KiB
C++

// Filename : TaskNMRiverRapids.cpp
// Description: GTA-side of the NM underwater behavior for river rapids
//---- Include Files ---------------------------------------------------------------------------------------------------------------------------------
// Rage headers
#include "crskeleton\Skeleton.h"
#include "fragment\Cache.h"
#include "fragment\Instance.h"
#include "fragment\Type.h"
#include "fragmentnm\messageparams.h"
// Framework headers
#include "grcore/debugdraw.h"
// Game headers
#include "Peds/ped.h"
#include "Task/Motion/Locomotion/TaskInWater.h"
#include "task/Physics/TaskNMHighFall.h"
#include "Task/Physics/TaskNMRiverRapids.h"
#include "Task/Physics/NmDebug.h"
#include "Renderer/Water.h"
#include "system/controlMgr.h"
//----------------------------------------------------------------------------------------------------------------------------------------------------
AI_OPTIMISATIONS()
// Tunable parameters. ///////////////////////////////////////////////////
CTaskNMRiverRapids::Tunables CTaskNMRiverRapids::sm_Tunables;
IMPLEMENT_PHYSICS_TASK_TUNABLES(CTaskNMRiverRapids, 0xd74b499c);
#if __BANK
extern const char* parser_RagdollComponent_Strings[];
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CTaskNMRiverRapids //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if __BANK
void CTaskNMRiverRapids::Tunables::PreAddWidgets(bkBank& bank)
{
bank.PushGroup("Ragdoll Component Buoyancy Multiplier", false);
for (int i = 0; i < RAGDOLL_NUM_COMPONENTS; i++)
{
bank.AddSlider(parser_RagdollComponent_Strings[i], &m_fRagdollComponentBuoyancy[i], 0.0f, 10.0f, 0.1f);
}
bank.PopGroup();
}
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CTaskNMRiverRapids::CTaskNMRiverRapids(u32 nMinTime, u32 nMaxTime)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
: CTaskNMBehaviour(nMinTime, nMaxTime),
m_fTimeSubmerged(0.0f),
m_pBuoyancyOverride(NULL)
{
#if !__FINAL
m_strTaskName = "NMRiverRapids";
#endif // !__FINAL
SetInternalTaskType(CTaskTypes::TASK_NM_RIVER_RAPIDS);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CTaskNMRiverRapids::~CTaskNMRiverRapids()
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CTaskNMRiverRapids::CleanUp()
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
CPed* pPed = GetPed();
if (pPed != NULL)
{
if (pPed->IsPlayer())
{
// Reset the buoyancy force multiplier and sprint control counter when leaving the task
pPed->m_Buoyancy.m_fForceMult = 1.0f;
if (pPed->GetPlayerInfo())
{
pPed->GetPlayerInfo()->SetPlayerDataSprintControlCounter(0.0f);
}
}
//may as well have this here as well, as there are rare cases where this is apparently not shutting off properly.
if(pPed->GetSpeechAudioEntity())
{
pPed->GetSpeechAudioEntity()->SetIsInRapids(false);
}
// Restore buoyancy info!
if(m_pBuoyancyOverride)
{
delete m_pBuoyancyOverride;
m_pBuoyancyOverride = NULL;
pPed->m_Buoyancy.SetBuoyancyInfoOverride(NULL);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CTaskNMRiverRapids::StartBehaviour(CPed* pPed)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
CBuoyancyInfo* pBuoyancyInfo = pPed->m_Buoyancy.GetBuoyancyInfo(pPed);
if (pBuoyancyInfo != NULL)
{
// Copy the buoyancy info prior to changing it.
m_pBuoyancyOverride = pBuoyancyInfo->Clone();
Assert(pPed->m_Buoyancy.GetBuoyancyInfoOverride()==NULL);
pPed->m_Buoyancy.SetBuoyancyInfoOverride(m_pBuoyancyOverride);
pBuoyancyInfo = pPed->m_Buoyancy.GetBuoyancyInfo(pPed);
for (int i = WS_PED_RAGDOLL_SAMPLE_START; i < pBuoyancyInfo->m_nNumWaterSamples; i++)
{
CWaterSample& sample = pBuoyancyInfo->m_WaterSamples[i];
if (sample.m_nComponent < RAGDOLL_NUM_COMPONENTS)
{
sample.m_fBuoyancyMult = sm_Tunables.m_fRagdollComponentBuoyancy[sample.m_nComponent];
}
}
}
sm_Tunables.m_Start.Post(*pPed, this);
if (pPed != NULL && pPed->GetSpeechAudioEntity())
{
pPed->GetSpeechAudioEntity()->SetIsInRapids(true);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CTaskNMRiverRapids::ControlBehaviour(CPed* pPed)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
sm_Tunables.m_Update.Post(*pPed, this);
HandleBuoyancy(pPed);
CTaskNMBehaviour::ControlBehaviour(pPed);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool CTaskNMRiverRapids::FinishConditions(CPed* pPed)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
if (!CTaskMotionSwimming::CheckForRapids(pPed) || pPed->ShouldBeDead() || pPed->GetIsDeadOrDying())
{
if (pPed->GetWaterLevelOnPed() > 0.6f)
{
m_nSuggestedNextTask = CTaskTypes::TASK_NM_BUOYANCY;
}
else
{
CTaskNMHighFall* pNewTask = rage_new CTaskNMHighFall(200, NULL, CTaskNMHighFall::HIGHFALL_IN_AIR);
CTaskNMControl* pControlTask = GetControlTask();
if (pNewTask && pControlTask)
{
pControlTask->ForceNewSubTask(pNewTask);
}
}
nmDebugf3("[%u]Finish %s on %s - not in river or river flow not fast enough. Next task: %s", fwTimer::GetFrameCount(), GetTaskName(), pPed->GetModelName(), TASKCLASSINFOMGR.GetTaskName(m_nSuggestedNextTask));
if(pPed && pPed->GetSpeechAudioEntity())
pPed->GetSpeechAudioEntity()->SetIsInRapids(false);
return true;
}
bool finished = ProcessFinishConditionsBase(pPed, MONITOR_FALL, FLAG_SKIP_DEAD_CHECK | FLAG_SKIP_WATER_CHECK);
if(finished && pPed && pPed->GetSpeechAudioEntity())
pPed->GetSpeechAudioEntity()->SetIsInRapids(false);
return finished;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CTaskNMRiverRapids::HandleBuoyancy(CPed* pPed)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
// Control how much the player thrashes about in the water!
float fSprintResult = 1.0f;
if (pPed->IsPlayer() && pPed->GetControlFromPlayer() != NULL && sm_Tunables.m_BodyWrithe.m_bControlledByPlayerSprintInput)
{
fSprintResult = pPed->GetPlayerInfo()->ProcessSprintControl(*pPed->GetControlFromPlayer(), *pPed, CPlayerInfo::SPRINT_UNDER_WATER, true);
const CPlayerInfo::sSprintControlData& rSprintControlData = CPlayerInfo::ms_Tunables.m_SprintControlData[CPlayerInfo::SPRINT_UNDER_WATER];
fSprintResult *= (rSprintControlData.m_Threshhold / rSprintControlData.m_MaxLimit);
}
ART::MessageParams msg;
msg.addFloat(NMSTR_PARAM(NM_WRITHE_ARM_AMPLITUDE), Lerp(fSprintResult, sm_Tunables.m_BodyWrithe.m_fMinArmAmplitude, sm_Tunables.m_BodyWrithe.m_fMaxArmAmplitude));
msg.addFloat(NMSTR_PARAM(NM_WRITHE_ARM_PERIOD), Lerp(fSprintResult, sm_Tunables.m_BodyWrithe.m_fMinArmPeriod, sm_Tunables.m_BodyWrithe.m_fMaxArmPeriod));
msg.addFloat(NMSTR_PARAM(NM_WRITHE_ARM_STIFFNESS), Lerp(fSprintResult, sm_Tunables.m_BodyWrithe.m_fMinArmStiffness, sm_Tunables.m_BodyWrithe.m_fMaxArmStiffness));
pPed->GetRagdollInst()->PostARTMessage(NMSTR_MSG(NM_WRITHE_MSG), &msg);
msg.reset();
msg.addBool(NMSTR_PARAM(NM_SET_CHARACTER_UNDERWATER_IS_UNDERWATER), true);
msg.addFloat(NMSTR_PARAM(NM_SET_CHARACTER_UNDERWATER_GRAVITY_FACTOR), 1.0f);
msg.addFloat(NMSTR_PARAM(NM_SET_CHARACTER_UNDERWATER_VISCOSITY), -1.0f);
msg.addBool(NMSTR_PARAM(NM_SET_CHARACTER_UNDERWATER_LINEAR_STROKE), false);
msg.addFloat(NMSTR_PARAM(NM_SET_CHARACTER_UNDERWATER_STROKE), Lerp(fSprintResult, sm_Tunables.m_BodyWrithe.m_fMinStroke, sm_Tunables.m_BodyWrithe.m_fMaxStroke));
pPed->GetRagdollInst()->PostARTMessage(NMSTR_MSG(NM_SET_CHARACTER_UNDERWATER_MSG), &msg);
pPed->m_Buoyancy.m_fForceMult = Lerp(fSprintResult, sm_Tunables.m_BodyWrithe.m_fMinBuoyancy, sm_Tunables.m_BodyWrithe.m_fMaxBuoyancy);
}
bool CTaskNMRiverRapids::ProcessPhysics(float fTimeStep, int iTimeSlice)
{
CPed *pPed = GetPed(); //Get the ped ptr.
phCollider* pCollider = pPed->GetCollider();
if (!nmVerifyf(pCollider != NULL && pCollider->IsArticulated(), "CTaskNMRiverRapids::ProcessPhysics: Should have a valid ragdoll inst at this point!"))
{
return CTaskNMBehaviour::ProcessPhysics(fTimeStep, iTimeSlice);
}
// Track how long we've been in the water. Clamp to 0-1 scaler. character should try
// to right himself more the longer he's in the water.
float fSubmergedLevel = 0.0f;
int iNumSamples = 0;
CBuoyancyInfo* pBuoyancyInfo = pPed->m_Buoyancy.GetBuoyancyInfo(pPed);
if (pBuoyancyInfo != NULL)
{
for (int iSample = 0; iSample < pBuoyancyInfo->m_nNumWaterSamples; iSample++)
{
if (pBuoyancyInfo->m_WaterSamples[iSample].m_nComponent == RAGDOLL_SPINE3)
{
fSubmergedLevel += pPed->m_Buoyancy.GetWaterLevelOnSample(iSample) / pBuoyancyInfo->m_WaterSamples[iSample].m_fSize;
iNumSamples++;
}
}
}
if (iNumSamples > 0 && (fSubmergedLevel / iNumSamples) > 0.0f)
{
m_fTimeSubmerged += fTimeStep;
int iLinkIndex = static_cast<phArticulatedCollider*>(pCollider)->GetLinkFromComponent(RAGDOLL_SPINE3);
Mat34V xSpineTransform;
Transpose3x3(xSpineTransform, static_cast<phArticulatedCollider*>(pCollider)->GetBody()->GetLink(iLinkIndex).GetMatrix());
Translate(xSpineTransform, xSpineTransform, pCollider->GetMatrix().d());
// Grab surface point and normal
Vec3V vSurfacePoint;
Vec3V vSurfaceNormal;
// If we are in a river, used the cached river bound probe result
if (!pPed->m_Buoyancy.GetCachedRiverBoundProbeResult(RC_VECTOR3(vSurfacePoint), RC_VECTOR3(vSurfaceNormal)))
{
float fDepth, fWaterHeight;
const Vec3V vPedPosition = pPed->GetTransform().GetPosition();
Water::GetWaterDepth(RCC_VECTOR3(vPedPosition), &fDepth, &fWaterHeight);
vSurfacePoint = vPedPosition;
vSurfacePoint.SetZf(fWaterHeight);
vSurfaceNormal = Vec3V(V_UP_AXIS_WZERO);
}
const ScalarV fDeadzone(V_HALF);
ScalarV rumbleScale(V_ZERO);
if (sm_Tunables.m_bHorizontalRighting)
{
// Measure horizontalness. Clamp into 0-1 scaler.
// Character should try to right himself more when he's horizontal.
ScalarV fHorizontalness = Subtract(ScalarV(V_ONE), Abs(Dot(vSurfaceNormal, xSpineTransform.a())));
// Compress somewhat to create a deadzone.
fHorizontalness *= Add(ScalarV(V_ONE), fDeadzone);
fHorizontalness -= fDeadzone;
fHorizontalness = Clamp(fHorizontalness, ScalarV(V_ZERO), ScalarV(V_ONE));
vSurfaceNormal = UnTransform3x3Ortho(xSpineTransform, vSurfaceNormal);
vSurfaceNormal.SetX(ScalarV(V_ZERO));
vSurfaceNormal = Normalize(Transform3x3(xSpineTransform, vSurfaceNormal));
ScalarV fAngle = AngleNormInput(vSurfaceNormal, Negate(xSpineTransform.c()));
if(IsLessThanAll(Dot(vSurfaceNormal, xSpineTransform.b()), ScalarV(V_ZERO)) != 0.0f)
{
fAngle = Negate(fAngle);
}
float fTimeSubmerged = Clamp(m_fTimeSubmerged, 0.0f, sm_Tunables.m_fHorizontalRightingTime);
fTimeSubmerged /= sm_Tunables.m_fHorizontalRightingTime;
Vec3V vAngImpulse = xSpineTransform.a() * fAngle * ScalarV(sm_Tunables.m_fHorizontalRightingStrength) * fHorizontalness * ScalarV(fTimeSubmerged);
static_cast<phArticulatedCollider*>(pCollider)->GetBody()->ApplyAngImpulse(iLinkIndex, vAngImpulse.GetIntrin128());
rumbleScale = MagSquared(vAngImpulse);
}
if (sm_Tunables.m_bVerticalRighting)
{
// Character should try to right himself more when up-side down, somewhat less when he's horizontal
// and not at all when right-side up
ScalarV fDotProduct = Dot(vSurfaceNormal, xSpineTransform.a());
ScalarV fVerticalness = Scale(fDotProduct, Negate(fDeadzone));
fVerticalness = Add(fVerticalness, fDeadzone);
fVerticalness = Clamp(fVerticalness, ScalarV(V_ZERO), ScalarV(V_ONE));
ScalarV fAngle = Arccos(fDotProduct);
Vec3V vTorqueAxis;
vTorqueAxis = Normalize(Cross(xSpineTransform.a(), vSurfaceNormal));
float fTimeSubmerged = Clamp(m_fTimeSubmerged, 0.0f, sm_Tunables.m_fVerticalRightingTime);
fTimeSubmerged /= sm_Tunables.m_fVerticalRightingTime;
Vec3V vAngImpulse = vTorqueAxis * fAngle * ScalarV(sm_Tunables.m_fVerticalRightingStrength) * fVerticalness * ScalarV(fTimeSubmerged);
static_cast<phArticulatedCollider*>(pCollider)->GetBody()->ApplyAngImpulse(iLinkIndex, vAngImpulse.GetIntrin128());
rumbleScale = MagSquared(vAngImpulse);
}
// Give the control pad a shake as the ragdoll is thrown around in the rapids.
if(pPed->IsLocalPlayer() && IsGreaterThanAll(rumbleScale, ScalarV(V_ZERO)))
{
// Scale the rumble intensity based on the angular impulse applied above.
const u32 knRumbleDuration = 50;
const float kfMaxRumbleIntensity = 0.3f;
const float kfMinAngImpSq = 1.0f;
const float kfMaxAngImpSq = 22.0f;
float fRange = kfMaxAngImpSq - kfMinAngImpSq;
float fRumbleScale = rumbleScale.Getf();
fRumbleScale -= kfMinAngImpSq;
fRumbleScale /= fRange;
fRumbleScale = Clamp(fRumbleScale, 0.0f, 1.0f);
CControlMgr::StartPlayerPadShakeByIntensity(knRumbleDuration, fRumbleScale*kfMaxRumbleIntensity);
}
}
else
{
m_fTimeSubmerged = 0.0f;
}
return CTaskNMBehaviour::ProcessPhysics(fTimeStep, iTimeSlice);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CTaskInfo* CTaskNMRiverRapids::CreateQueriableState() const
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
return rage_new CClonedNMRiverRapidsInfo();
}
CTaskFSMClone *CClonedNMRiverRapidsInfo::CreateCloneFSMTask()
{
return rage_new CTaskNMRiverRapids(4000, 30000);
}