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

446 lines
12 KiB
C++

// File header
#include "EventReactions.h"
// Framework headers
#include "ai/aichannel.h"
// Game Headers
#include "Event/EventResponse.h"
#include "Peds/Ped.h"
#include "Peds/PedIntelligence.h"
#include "Task/Combat/TaskThreatResponse.h"
#include "Task/Combat/TaskInvestigate.h"
#include "Task/Combat/TaskReact.h"
#include "Task/Response/TaskReactToDeadPed.h"
#include "vehicleAi/VehicleIntelligence.h"
#include "vehicleAi/task/TaskVehicleCruise.h"
#include "event/EventReactions_parser.h"
AI_OPTIMISATIONS()
CEventReactionEnemyPed::CEventReactionEnemyPed(CPed* pThreatPed, bool bForceThreatResponse)
:
m_pThreatPed(pThreatPed),
m_bForceThreatResponse(bForceThreatResponse)
{
#if !__FINAL
m_EventType = EVENT_REACTION_ENEMY_PED;
#endif
}
bool CEventReactionEnemyPed::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
if (pPed->IsLocalPlayer())
{
return false;
}
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskThreatResponse(m_pThreatPed));
return response.HasEventResponse();
}
CEventReactionInvestigateThreat::CEventReactionInvestigateThreat(const CAITarget& threatTarget, s32 iEventType, s32 iThreatType)
:
m_threatTarget(threatTarget),
m_iEventType(iEventType),
m_iThreatType(iThreatType)
{
#if !__FINAL
m_EventType = EVENT_REACTION_INVESTIGATE_THREAT;
#endif
}
bool CEventReactionInvestigateThreat::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
if (pPed->IsLocalPlayer())
{
return false;
}
// Make sure threat target exists
if (!m_threatTarget.GetIsValid())
{
return false;
}
CCombatBehaviour& combatBehaviour = pPed->GetPedIntelligence()->GetCombatBehaviour();
// If we're allowed to move, investigate, otherwise go into default threat response
if (combatBehaviour.IsFlagSet(CCombatData::BF_CanInvestigate) && combatBehaviour.GetCombatMovement() != CCombatData::CM_Stationary)
{
Vector3 vTargetPos;
aiVerifyf(m_threatTarget.GetPosition(vTargetPos),"Failed to get target position");
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskInvestigate(vTargetPos, NULL, m_iEventType));
}
else if (m_threatTarget.GetEntity() && m_threatTarget.GetEntity()->GetIsTypePed())
{
const CEntity* pEntity = m_threatTarget.GetEntity();
const CPed* pThreatPed = static_cast<const CPed*>(pEntity);
if (!pThreatPed->GetPedIntelligence()->IsFriendlyWith(*pPed))
{
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskThreatResponse(pThreatPed));
}
}
return response.HasEventResponse();
}
bool CEventReactionInvestigateDeadPed::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
if (pPed->IsLocalPlayer())
{
return false;
}
// Make sure threat ped exists and is dead
if (!m_pDeadPed || !m_pDeadPed->IsDead())
{
return false;
}
// If we're allowed to move, investigate, otherwise go into default threat response
if (pPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanInvestigate))
{
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskInvestigate(VEC3V_TO_VECTOR3(m_pDeadPed->GetTransform().GetPosition())));
}
else
{
m_pDeadPed->SetPedConfigFlag( CPED_CONFIG_FLAG_HasDeadPedBeenReported, 1 );
}
return response.HasEventResponse();
}
bool CEventReactionCombatVictory::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
if (pPed->IsLocalPlayer())
{
return false;
}
// New scenario here?
// Make sure ped exists
if (m_pDeadPed)
{
if (fwRandom::GetRandomTrueFalse())
{
pPed->NewSay("TARGET_KILLED");
return false;
}
else
{
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskCheckPedIsDead(m_pDeadPed));
}
}
return response.HasEventResponse();
}
bank_float CEventWhistlingHeard::ms_fRange = 10.0f;
CEventWhistlingHeard::CEventWhistlingHeard(CPed* pPedHeardWhistling)
:
m_pPedHeardWhistling(pPedHeardWhistling)
{
#if !__FINAL
// Debug variable storing the event type
m_EventType = EVENT_WHISTLING_HEARD;
#endif // !__FINAL
#if __BANK
aiFatalAssertf(m_pPedHeardWhistling,"NULL pointer");
const Vec3V vWhistlingPosition = m_pPedHeardWhistling->GetTransform().GetPosition();
CEventDebug::ms_debugDraw.AddText(vWhistlingPosition, 0, 0, "Whistling Event Source", Color_blue, 5000);
CEventDebug::DrawCircle(vWhistlingPosition, ms_fRange, Color_blue);
#endif // __BANK
}
bool CEventWhistlingHeard::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
if (pPed->IsLocalPlayer())
{
return false;
}
// Make sure ped exists
if (m_pPedHeardWhistling)
{
pPed->GetPedIntelligence()->IncreaseAlertnessState();
//TODO: React to the whistle.
}
return response.HasEventResponse();
}
bool CEventWhistlingHeard::AffectsPedCore(CPed* UNUSED_PARAM(pPed)) const
{
return false;
}
bank_float CEventExplosionHeard::ms_fMaxLoudness = 100.0f; // Player footsteps = 7.5
IMPLEMENT_REACTION_EVENT_TUNABLES(CEventExplosionHeard, 0x0d143f2e);
CEventExplosionHeard::Tunables CEventExplosionHeard::sm_Tunables;
bool CEventExplosionHeard::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
if (pPed->IsLocalPlayer())
{
return false;
}
pPed->GetPedIntelligence()->IncreaseAlertnessState();
if (pPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanInvestigate))
{
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskInvestigate(m_vExplosionPosition, NULL, EVENT_EXPLOSION_HEARD));
}
return response.HasEventResponse();
}
CTask* CEventExplosionHeard::CreateVehicleReponseTaskWithParams(sVehicleMissionParams& paramsOut) const
{
paramsOut.Clear();
paramsOut.m_iDrivingFlags =DMode_AvoidCars|DF_AvoidTargetCoors|DF_SteerAroundPeds;
paramsOut.m_fCruiseSpeed = 40.0f;
paramsOut.SetTargetPosition(m_vExplosionPosition);
return rage_new CTaskVehicleFlee(paramsOut);
}
bool CEventExplosionHeard::AffectsVehicleCore(CVehicle* pVehicle) const
{
if (!pVehicle->GetIntelligence()->ShouldHandleEvents())
{
return false;
}
return CanHearExplosion(pVehicle);
}
bool CEventExplosionHeard::CanHearExplosion(const CEntity* pEntity) const
{
// Only accept explosions that the ped can hear
Vector3 vDiff = VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()) - m_vExplosionPosition;
float fDist2 = vDiff.Mag2();
static dev_float s_fHearingRangeForPretendOccupants = CPedPerception::ms_fSenseRange;
float fHearingRange = pEntity->GetIsTypePed()
? (static_cast<const CPed*>(pEntity))->GetPedIntelligence()->GetPedPerception().GetHearingRange()
: s_fHearingRangeForPretendOccupants;
if (fDist2 > (fHearingRange * fHearingRange))
{
// Explosion was out of range
return false;
}
else
{
return true; // If it's within hearing range treat all explosions as being heard
}
// else if (fDist2 > 1.0f) // Avoid divide by zero
// {
// // Scale to range 0.0f, 1.0f
// float fLoudnessScaled = m_fLoudness / ms_fMaxLoudness;
//
// // Footstep was in hearing range, scale the loudness depending on how far away the ped is
// fLoudnessScaled *= fHearingRange / Sqrtf(fDist2);
//
// // TODO: Make this detection criteria better
// // The bigger this number the louder the sound is / the closer we are
// // E.g if we take the rocket loudness = 16
// // if the ped has a hearing range of 60.0f and is 5m away
// // fLoudnessScaled = 0.16*60 / 20 = 0.48
// // fLoudnessScaled = 0.16*60 / 10 = 0.96
// if (fLoudnessScaled > 0.1f)
// {
// return true;
// }
// }
// else
// {
// // distance must be < 1m
// return true;
// }
}
bool CEventExplosionHeard::AffectsPedCore(CPed* pPed) const
{
if (pPed->IsDead() || pPed->IsPlayer())
{
return false;
}
else
{
return CanHearExplosion(pPed);
}
// return false;
}
bool CEventExplosionHeard::TryToCombine(const CEventExplosionHeard& other)
{
const Vec3V pos1V = RCC_VEC3V(m_vExplosionPosition);
const Vec3V pos2V = RCC_VEC3V(other.m_vExplosionPosition);
const ScalarV thresholdDistSqV = LoadScalar32IntoScalarV(sm_Tunables.m_MaxCombineDistThresholdSquared);
const ScalarV distSqV = DistSquared(pos1V, pos2V);
if(IsGreaterThanAll(distSqV, thresholdDistSqV))
{
return false;
}
// Recompute the explosion position as the average between the two.
const Vec3V avgPosV = Average(pos1V, pos2V);
m_vExplosionPosition = VEC3V_TO_VECTOR3(avgPosV);
// This parameter isn't really used right now. If it had been used and didn't
// always have the same value, we could use it for scaling the position adaptation,
// or do something to make the combined explosion louder.
m_fLoudness = Max(m_fLoudness, other.m_fLoudness);
return true;
}
bool CEventExplosionHeard::operator==( const fwEvent& otherEvent ) const
{
if( static_cast<const CEvent&>(otherEvent).GetEventType() == GetEventType() )
{
const CEventExplosionHeard& otherExplosionHeardEvent = static_cast<const CEventExplosionHeard&>(otherEvent);
if( m_vExplosionPosition.IsClose(otherExplosionHeardEvent.m_vExplosionPosition, 20.f) &&
Abs(m_fLoudness - otherExplosionHeardEvent.m_fLoudness) < SMALL_FLOAT )
{
return true;
}
}
return false;
}
CEventDisturbance::CEventDisturbance(CPed* pedBeingDisturbed, CPed* pedDoingDisturbance, eDisturbanceType disturbanceType)
: m_pPedBeingDisturbed(pedBeingDisturbed)
, m_pPedDoingDisturbance(pedDoingDisturbance)
, m_eDisturbanceType(disturbanceType)
{
#if !__FINAL
// Debug variable storing the event type
m_EventType = EVENT_DISTURBANCE;
#endif // !__FINAL
}
bool CEventDisturbance::CreateResponseTask(CPed* pPed, CEventResponse& response) const
{
switch (m_eDisturbanceType)
{
case ePedKilledByStealthKill:
return false;
case ePedHeardFalling:
case ePedInNearbyCar:
if (pPed->IsLocalPlayer())
{
return false;
}
if (pPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanInvestigate))
{
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskInvestigate(VEC3V_TO_VECTOR3(m_pPedDoingDisturbance->GetTransform().GetPosition()), NULL, EVENT_DISTURBANCE));
}
else
{
response.SetEventResponse(CEventResponse::EventResponse, rage_new CTaskThreatResponse(m_pPedDoingDisturbance));
}
return response.HasEventResponse();
default: aiAssertf(0,"Unhandled enum in CEventDisturbance::CreateResponseTask");
break;
}
return false;
}
bool CEventDisturbance::AffectsPedCore(CPed* pPed) const
{
if (pPed->IsDead() || pPed->IsPlayer() || !m_pPedDoingDisturbance)
{
return false;
}
return false;
}
#if __BANK
bool CEventDebug::ms_bRenderEventDebug = false;
#if DEBUG_DRAW
#define MAX_EVENT_DRAWABLES 100
CDebugDrawStore CEventDebug::ms_debugDraw(MAX_EVENT_DRAWABLES);
#endif // DEBUG_DRAW
void CEventDebug::InitWidgets()
{
bkBank* pBank = BANKMGR.FindBank("A.I.");
if (pBank)
{
pBank->PushGroup("Event Debug", false);
pBank->AddToggle("Render Debug", &ms_bRenderEventDebug);
#if __DEV
pBank->AddButton("Spew Event Pool", SpewPoolUsage);
#endif
pBank->PushGroup("Whistling Event", false);
pBank->AddSlider("Whistling Range", &CEventWhistlingHeard::ms_fRange, 0.0f, 50.0f, 0.1f);
pBank->PopGroup();
pBank->PopGroup();
}
}
#define NUM_CIRCLE_SEGMENTS 13
void CEventDebug::DrawCircle( Vec3V_In vCentre, const float fRadius, const Color32 colour )
{
static const float s_fNumLinesToUse = 10.0f;
const ScalarV fRadAngleSeparation = ScalarVFromF32(2.0f*PI / s_fNumLinesToUse);
Vec3V vStartPoint = VECTOR3_TO_VEC3V(YAXIS * fRadius);
Vec3V vEndPoint;
for (s32 i = 0; i <= s_fNumLinesToUse; ++i)
{
// Get the end point of the line by rotating about by the angle separation
vEndPoint = vStartPoint;
vEndPoint = RotateAboutZAxis(vEndPoint, fRadAngleSeparation);
// Get the world position of the vector
vStartPoint += vCentre;
vEndPoint += vCentre;
// Draw the line segment
ms_debugDraw.AddLine(vStartPoint,vEndPoint,colour,5000);
// Get the local points again
vStartPoint -= vCentre;
vEndPoint -= vCentre;
// Set the new start point to be the end point of this line
vStartPoint = vEndPoint;
}
}
#if __DEV
void CEventDebug::SpewPoolUsage()
{
CEvent::GetPool()->PoolFullCallback();
}
#endif // __DEV
void CEventDebug::Debug()
{
#if DEBUG_DRAW
if (ms_bRenderEventDebug)
{
ms_debugDraw.Render();
}
#endif // DEBUG_DRAW
}
#endif // __BANK