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

10835 lines
431 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
// Title : TaskCombatMelee.cpp
//
// These classes enables individual peds to engage in melee combat
// with other peds.
//
// Note: The major parts of our "Melee Combat AI" are marked with the quoted
// term in this sentence.
/////////////////////////////////////////////////////////////////////////////////
#include "task/combat/taskcombatmelee.h"
//Rage headers
#include "math/vecmath.h"
#include "math/angmath.h"
#include "vector/vector3.h"
#include "phbound/boundcomposite.h"
#include "phbound/boundcapsule.h"
#include "phbound/support.h"
#include "fragment/instance.h"
#include "fragment/typechild.h"
#include "string/string.h"
#include "grcore/debugdraw.h"
#include "fwmaths/angle.h"
#include "vfx/channel.h"
//Game headers.
#include "AI/Ambient/ConditionalAnimManager.h"
#include "animation/AnimDefines.h"
#include "animation/animManager.h"
#include "animation/FacialData.h"
#include "animation/MovePed.h"
#include "audio/dooraudioentity.h"
#include "camera/CamInterface.h"
#include "camera/cinematic/camera/animated/CinematicAnimatedCamera.h"
#include "camera/gameplay/GameplayDirector.h"
#include "camera/gameplay/aim/FirstPersonShooterCamera.h"
#include "control/gamelogic.h"
#include "debug/debugscene.h"
#include "event/eventdamage.h"
#include "event/eventweapon.h"
#include "Event/EventReactions.h"
#include "Event/EventShocking.h"
#include "event/shockingevents.h"
#include "event/Events.h"
#include "frontend/NewHud.h"
#include "Ik/solvers/RootSlopeFixupSolver.h"
#include "modelinfo/pedmodelinfo.h"
#include "network/objects/entities/NetObjPlayer.h"
#include "network/events/NetworkEventTypes.h"
#include "objects/Door.h"
#include "objects/object.h"
#include "peds/DefensiveArea.h"
#include "peds/ped.h"
#include "Peds/Action/BrawlingStyle.h"
#include "peds/pedgeometryanalyser.h"
#include "peds/PedIKSettings.h"
#include "peds/pedintelligence.h"
#include "peds/PedMoveBlend/pedmoveblend.h"
#include "peds/PedWeapons/PedTargetEvaluator.h"
#include "Peds/PlayerInfo.h"
#include "peds/PlayerSpecialAbility.h"
#include "Peds/HealthConfig.h"
#include "physics/gtaarchetype.h"
#include "physics/gtainst.h"
#include "physics/physics.h"
#include "physics/Tunable.h"
#include "physics/WorldProbe/worldprobe.h"
#include "pickups/PickupManager.h"
#include "scene/playerswitch/PlayerSwitchInterface.h"
#include "Stats/StatsInterface.h"
#include "Streaming/streaming.h"
#include "system/TamperActions.h"
#include "Task/Combat/CombatMeleeDebug.h"
#include "Task/Combat/Cover/Cover.h"
#include "Task/Combat/TaskDamageDeath.h"
#include "Task/Combat/TaskNewCombat.h"
#include "Task/Combat/TaskThreatResponse.h"
#include "Task/General/TaskBasic.h"
#include "Task/Default/TaskAmbient.h"
#include "Task/Default/TaskPlayer.h"
#include "Task/Motion/Locomotion/TaskMotionPed.h"
#include "Task/Motion/Locomotion/TaskMotionStrafing.h"
#include "Task/Motion/Locomotion/TaskQuadLocomotion.h"
#include "Task/Movement/taskseekentity.h"
#include "Task/Movement/TaskSlideToCoord.h"
#include "Task/Movement/TaskCollisionResponse.h"
#include "Task/Physics/TaskNM.h"
#include "Task/Physics/TaskBlendFromNM.h"
#include "Task/Physics/TaskNMRelax.h"
#include "Task/Scenario/TaskScenario.h"
#include "Task/System/TaskHelpers.h"
#include "Task/Vehicle/TaskEnterVehicle.h"
#include "Task/Vehicle/TaskGoToVehicleDoor.h"
#include "Task/Weapons/TaskPlayerWeapon.h"
#include "Task/Weapons/TaskSwapWeapon.h"
#include "Task/General/Phone/TaskMobilePhone.h"
#include "Task/Combat/TaskSharkAttack.h"
#include "Task/Animation/TaskAnims.h"
#include "vehicles/vehicle.h"
#include "vfx/systems/VfxPed.h"
#include "weapons/WeaponDamage.h"
#include "weapons/WeaponManager.h"
#if __BANK
#include "Peds/PedDebugVisualiser.h"
#endif // __BANK
#if __BANK
#define MELEE_NETWORK_DEBUGF(fmt,...) if(CCombatMeleeDebug::sm_bMeleeDisplayDebugEnabled) { Displayf(fmt,##__VA_ARGS__); }
#else
#define MELEE_NETWORK_DEBUGF(fmt,...)
#endif
AI_OPTIMISATIONS()
/////////////////////////////////////////////////////////////////////////////////
// CTaskMelee
//
// Enables individual peds to perform series of action moves in a
// smooth and convincing manner.
/////////////////////////////////////////////////////////////////////////////////
s32 CTaskMelee::sm_nMaxNumActiveCombatants = 1;
s32 CTaskMelee::sm_nMaxNumActiveSupportCombatants = 3;
s32 CTaskMelee::sm_nTimeInTaskAfterHitReaction = 5000;
s32 CTaskMelee::sm_nTimeInTaskAfterStardardAttack = 2500;
s32 CTaskMelee::ms_nCameraInterpolationDuration = 750;
const CActionDefinition *CTaskMelee::ms_LastFoundActionDefinition = NULL;
const CActionDefinition *CTaskMelee::ms_LastFoundActionDefinitionForNetworkDamage = NULL;
// Tuning Constants.
float CTaskMelee::sm_fMeleeStrafingMinDistance = 1.15f;
float CTaskMelee::sm_fMovingTargetStrafeTriggerRadius = 2.5f;
float CTaskMelee::sm_fMovingTargetStrafeBreakoutRadius = 4.0f;
float CTaskMelee::sm_fForcedStrafeRadius = 4.0f;
float CTaskMelee::sm_fBlockTimerDelayEnter = 0.0f;
float CTaskMelee::sm_fBlockTimerDelayExit = 0.15f;
float CTaskMelee::sm_fFrontTargetDotThreshold = 0.35f;
bool CTaskMelee::sm_bDoSpineIk = false;
bool CTaskMelee::sm_bDoHeadIk = true;
bool CTaskMelee::sm_bDoEyeIK = true;
bool CTaskMelee::sm_bDoLegIK = false;
float CTaskMelee::sm_fHeadLookMinYawDegs = -125.0f;
float CTaskMelee::sm_fHeadLookMaxYawDegs = 125.0f;
float CTaskMelee::sm_fSpineLookMinYawDegs = -30.0f;
float CTaskMelee::sm_fSpineLookMaxYawDegs = 30.0f;
float CTaskMelee::sm_fSpineLookMinPitchDegs = -30.0f;
float CTaskMelee::sm_fSpineLookMaxPitchDegs = 30.0f;
float CTaskMelee::sm_fSpineLookTorsoOffsetYawDegs = 0.0f;
float CTaskMelee::sm_fSpineLookMaxHeightDiff = 0.65f;
float CTaskMelee::sm_fSpineLookAmountPowerCurve = 0.25f;
float CTaskMelee::sm_fPlayerAttemptEscapeExitMultiplyer = 4.0f;
float CTaskMelee::sm_fPlayerMoveAwayExitMultiplyer = 3.0f;
float CTaskMelee::sm_fPlayerClipPlaybackSpeedMultiplier = 1.0f;
s32 CTaskMelee::sm_nPlayerTauntFrequencyMinInMs = 3000;
s32 CTaskMelee::sm_nPlayerTauntFrequencyMaxInMs = 5000;
u32 CTaskMelee::sm_nPlayerImpulseInterruptDelayInMs = 850;
s32 CTaskMelee::sm_nAiTauntHitDelayTimerInMs = 12500;
float CTaskMelee::sm_fAiBlockConsecutiveHitBuffIncrease = 0.075f;
float CTaskMelee::sm_fAiBlockActionBuffDecrease = 0.035f;
s32 CTaskMelee::sm_fAiAttackConsecutiveHitBuffIncreaseMin = 250;
s32 CTaskMelee::sm_fAiAttackConsecutiveHitBuffIncreaseMax = 700;
s32 CTaskMelee::sm_nAiForceMeleeMovementAtStartTimeMs = 1500;
float CTaskMelee::sm_fAiTargetSeparationDesiredSelfToTargetRange = 1.0f;
float CTaskMelee::sm_fAiTargetSeparationDesiredSelfToGetupTargetRange = 1.5f;
float CTaskMelee::sm_fAiTargetSeparationPrefdDiffMinForImpetus = 0.5f;
float CTaskMelee::sm_fAiTargetSeparationPrefdDiffMaxForImpetus = 1.50f;
float CTaskMelee::sm_fAiTargetSeparationForwardImpetusPowerFactor = 2.00f;
float CTaskMelee::sm_fAiTargetSeparationBackwardImpetusPowerFactor = 1.00f;
float CTaskMelee::sm_fAiTargetSeparationMinTargetApproachDesireSize = 0.1f;
s32 CTaskMelee::sm_nAiAvoidEntityDelayMinInMs = 1000;
s32 CTaskMelee::sm_nAiAvoidEntityDelayMaxInMs = 4000;
float CTaskMelee::sm_fAiAvoidSeparationDesiredSelfToTargetRange = 1.6f;
float CTaskMelee::sm_fAiAvoidSeparationPrefdDiffMinForImpetus = 1.25f;
float CTaskMelee::sm_fAiAvoidSeparationPrefdDiffMaxForImpetus = 3.00f;
float CTaskMelee::sm_fAiAvoidSeparationForwardImpetusPowerFactor = 2.00f;
float CTaskMelee::sm_fAiAvoidSeparationBackwardImpetusPowerFactor = 1.00f;
float CTaskMelee::sm_fAiAvoidSeparationMinTargetApproachDesireSize = 0.1f;
float CTaskMelee::sm_fAiAvoidSeparationCirlclingStrength = 0.25;
s32 CTaskMelee::sm_nAiSeekModeScanTimeMin = 500;
s32 CTaskMelee::sm_nAiSeekModeScanTimeMax = 3000;
float CTaskMelee::sm_fAiSeekModeTargetHeight = 2.5f;
float CTaskMelee::sm_fAiSeekModeInitialPursuitBreakoutDist = 15.0f;
float CTaskMelee::sm_fAiSeekModeChaseTriggerOffset = 4.0f;
float CTaskMelee::sm_fAiSeekModeFleeTriggerOffset = 2.5f;
float CTaskMelee::sm_fAiSeekModeActiveCombatantRadius = 0.25f;
float CTaskMelee::sm_fAiSeekModeSupportCombatantRadiusMin = 3.0f;
float CTaskMelee::sm_fAiSeekModeSupportCombatantRadiusMax = 5.0f;
float CTaskMelee::sm_fAiSeekModeInactiveCombatantRadiusMin = 10.0f;
float CTaskMelee::sm_fAiSeekModeInactiveCombatantRadiusMax = 15.0f;
float CTaskMelee::sm_fAiSeekModeRetreatDistanceMin = 3.0f;
float CTaskMelee::sm_fAiSeekModeRetreatDistanceMax = 5.0f;
float CTaskMelee::sm_fAiQueuePrefdPosDiffMinForImpetus = 3.0f;
float CTaskMelee::sm_fAiQueuePrefdPosDiffMaxForImpetus = 5.0f;
s32 CTaskMelee::sm_nAiLosTestFreqInMs = 2000;
float CTaskMelee::sm_fLookAtTargetHeadingBlendoutDegrees = 10.0f;
float CTaskMelee::sm_fLookAtTargetTimeout = 3.0f;
float CTaskMelee::sm_fLookAtTargetLooseToleranceDegrees = 5.0f;
float CTaskMelee::sm_fReTargetTimeIfNotStrafing = 0.5f;
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldCheckForMeleeAmbientMove
// PURPOSE : Determines if the player input conditions are appropriate to check for a
// melee ambient move.
// PARAMETERS : pPed - the ped of interest.
// bAttemptTaunt - Whether or not the given ped is pressing the special ability button
// RETURNS : Whether or not we should check for a melee action
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::ShouldCheckForMeleeAmbientMove( CPed* pPed, bool& bAttemptTaunt, bool bAllowHeavyAttack )
{
if( pPed->IsLocalPlayer() && CNewHud::IsShowingHUDMenu() )
return false;
Assert( pPed );
if( !pPed )
return false;
if( pPed->GetPedResetFlag(CPED_RESET_FLAG_DisableAmbientMeleeMoves) )
return false;
//! Can't set reset flag for mobile phone as it means we have nowhere to reset it (as script can set it too). Just check explicitly here.
if( CTaskMobilePhone::IsRunningMobilePhoneTask(*pPed) )
return false;
const CControl* pControl = pPed->GetControlFromPlayer();
Assert( pControl );
if( !pControl )
return false;
if (pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning))
{
if (pControl->GetMeleeAttackAlternate().IsPressed() || pControl->GetMeleeAttackLight().IsPressed())
{
return true;
}
return false;
}
// Only even check for moves from on foot if the ambient attack button just released.
bool bShouldCheckForMoveFromOnFoot = false;
if( pControl->GetPedTargetIsDown() )
{
bool bLight = pControl->GetMeleeAttackLight().IsPressed() || pControl->GetMeleeAttackAlternate().IsPressed();
bool bHeavy = false;
if( bAllowHeavyAttack )
bHeavy = pControl->GetMeleeAttackHeavy().IsPressed();
// Only allow manual taunts when the insult special ability is equipped
if( pControl->GetPedSpecialAbility().IsPressed() )
{
const CPlayerSpecialAbility* pSpecialAbility = pPed->GetSpecialAbility();
bAttemptTaunt = !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_PreventAllMeleeTaunts ) && pSpecialAbility && pSpecialAbility->GetType() == SAT_RAGE && !pSpecialAbility->IsActive();
}
bShouldCheckForMoveFromOnFoot = bLight || bHeavy;
}
else
bShouldCheckForMoveFromOnFoot = pControl->GetMeleeAttackLight().IsPressed() || pControl->GetMeleeAttackAlternate().IsPressed();
return bShouldCheckForMoveFromOnFoot;
}
CActionFlags::ActionTypeBitSetData CTaskMelee::GetActionTypeToLookFor(const CPed* pPed)
{
CActionFlags::ActionTypeBitSetData typeToLookFor;
if( pPed->IsAPlayerPed() )
{
if( pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_SwimmingTasksRunning ) || pPed->GetIsFPSSwimming())
typeToLookFor.Set( CActionFlags::AT_SWIMMING, true );
else
typeToLookFor.Set( CActionFlags::AT_FROM_ON_FOOT, true );
}
else
typeToLookFor.Set( CActionFlags::AT_FROM_ON_FOOT, true );
return typeToLookFor;
}
const CActionDefinition *CTaskMelee::FindSuitableActionForTarget(const CPed* pPed, const CEntity* pTargetEntity, bool bHasLockOnTarget, bool bCacheActionDefinition)
{
CActionFlags::ActionTypeBitSetData typeToLookFor = GetActionTypeToLookFor(pPed);
return FindSuitableActionForTargetType(pPed, pTargetEntity, bHasLockOnTarget, typeToLookFor, bCacheActionDefinition);
}
const CActionDefinition *CTaskMelee::FindSuitableActionForTargetType(const CPed* pPed, const CEntity* pTargetEntity, bool bHasLockOnTarget, const CActionFlags::ActionTypeBitSetData& typeToLookFor, bool bCacheActionDefinition)
{
// Attempt to find a suitable action
const CActionDefinition* pActionToDo = ACTIONMGR.SelectSuitableAction(
typeToLookFor, // Only look for moves that are appropriate from on foot
NULL, // No impulse combo
true, // Test whether or not it is an entry action.
true, // Only find entry actions.
pPed, // The acting ped.
0, // result Id forcing us.
-1, // Priority
pTargetEntity, // The entity being attacked.
bHasLockOnTarget, // Do we have a lock on the target entity.
false, // Should we only allow impulse initiated moves.
pPed->IsLocalPlayer(), // Test the controls (for local players only).
CActionManager::ShouldDebugPed( pPed ));
if(bCacheActionDefinition)
{
ms_LastFoundActionDefinition = pActionToDo;
}
return pActionToDo;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckForAndGetMeleeAmbientMove
// PURPOSE : Checks for any ambient melee moves the ped can use.
//
// We use the action manager to test the current conditions (some of
// which may use weapons or a complex input state such as a button
// being released and not just pressed down) to check to see if there
// is a request to do a pistol whip, a running attack, or a surprise
// attack (like a sucker punch or garot move), etc.
// PARAMETERS : pPed - the ped of interest.
// pTargetEntity - Target entity for ped of interest (could be NULL)
// bHasLockOnTarget - Do we have a physical lockon to the target entity?
// bGoIntoMeleeEvenWhenNoSpecificMoveFound - Create CTaskMelee even if move isn't found
// bPerformMeleeIntro - Whether or not to perform the melee intro animation transition
// bAllowStrafeMode - Flag that will allow the ped to enter strafe mode when locked on
// bCheckLastFound - Checks to see if an action definition was stored previously
// RETURNS : A task doing that move if appropriate or NULL if none found.
/////////////////////////////////////////////////////////////////////////////////
aiTask* CTaskMelee::CheckForAndGetMeleeAmbientMove( const CPed* pPed, CEntity* pTargetEntity, bool bHasLockOnTarget, bool bGoIntoMeleeEvenWhenNoSpecificMoveFound, bool bPerformMeleeIntro, bool bAllowStrafeMode, bool bCheckLastFound )
{
taskAssert( pPed );
CActionFlags::ActionTypeBitSetData typeToLookFor = GetActionTypeToLookFor(pPed);
if( !typeToLookFor.IsSet( CActionFlags::AT_SWIMMING ) && !pPed->GetCurrentMotionTask()->IsOnFoot() )
return NULL;
if(pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsArresting))
{
return NULL;
}
aiTask* pTaskToDo = NULL;
const CActionDefinition* pActionToDo = NULL;
if( bCheckLastFound && ms_LastFoundActionDefinition )
{
pActionToDo = ms_LastFoundActionDefinition;
}
else
{
pActionToDo = FindSuitableActionForTargetType( pPed, pTargetEntity, bHasLockOnTarget, typeToLookFor );
}
// Did we find a suitable action to execute?
if( pActionToDo )
{
// Try to get a pointer to the sequence via the sequence Id for the action.
const CActionResult* pActionResult = pActionToDo->GetActionResult( pPed );
if( pActionResult )
{
s32 iMeleeFlags = 0;
if( bHasLockOnTarget )
iMeleeFlags |= MF_HasLockOnTarget;
// Add a new action (with the movement layer still running underneath)
CSimpleImpulseTest::Impulse initialImpulse = CSimpleImpulseTest::ImpulseNone;
// Record the initial impulse
if( pPed->IsLocalPlayer() )
{
if( pActionToDo->GetImpulseTest() && pActionToDo->GetImpulseTest()->GetNumImpulses() > 0 )
initialImpulse = pActionToDo->GetImpulseTest()->GetImpulseByIndex( 0 )->GetImpulse();
}
pTaskToDo = rage_new CTaskMelee( pActionResult, pTargetEntity, iMeleeFlags, initialImpulse );
}
}
// Check if we should go into melee combat mode (in a melee idle stance).
if( !pTaskToDo && pTargetEntity && bGoIntoMeleeEvenWhenNoSpecificMoveFound )
{
// Get the weapon; note that pWeapon may be null if there is not a weapon in hand.
const CWeaponInfo* pWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
if( !pWeaponInfo || pWeaponInfo->GetIsMelee() )
{
// Add a new action.
s32 iMeleeFlags = MF_ShouldBeInMeleeMode;
if( bHasLockOnTarget )
iMeleeFlags |= MF_HasLockOnTarget;
if( bAllowStrafeMode )
{
iMeleeFlags |= MF_AllowStrafeMode;
// Only allow the melee intro when we allow strafing
if( bPerformMeleeIntro )
iMeleeFlags |= MF_PerformIntroAnim;
}
pTaskToDo = rage_new CTaskMelee( NULL, pTargetEntity, iMeleeFlags );
}
}
ms_LastFoundActionDefinition = NULL;
return pTaskToDo;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CTaskMelee
// PURPOSE : The initial action provided constructor.
// Note: the initial action may be NULL.
// PARAMETERS : pInitialActionResult - The action to immediately perform (may be null).
// pTargetEntity - The melee target.
// iFlags - Task flags like has lock on target, is doing a reaction etc
// initialImpulse - The first impulse from an on foot attack
/////////////////////////////////////////////////////////////////////////////////
CTaskMelee::CTaskMelee( const CActionResult* pInitialActionResult, CEntity* pTargetEntity, fwFlags32 iFlags, CSimpleImpulseTest::Impulse initialImpulse, s32 nTimeInTask, u16 uUniqueNetworkID )
: // Initializer list.
m_vStealthKillWorldOffset (VEC3_ZERO),
m_fStealthKillWorldHeading (0.0f),
m_pNextActionResult (pInitialActionResult),
m_bActionIsBranch (false),
m_pTargetEntity (pTargetEntity),
m_nCameraAnimId (0),
m_pAvoidEntity (NULL),
m_bStopAll (false),
m_queueType (QT_Invalid),
m_cloneQueueType (QT_Invalid),
m_bLocalReaction (false),
m_bSeekModeLosTestHasBeenRequested (false),
m_bLosBlocked (false),
m_impulseCombo (initialImpulse),
m_nFlags (iFlags),
m_fBlockDelayTimer (0.0f),
m_fBlockBuff (0.0f),
m_bIsBlocking (false),
m_bUsingStealthClipClose (false),
m_bUsingBlockClips (false),
m_fDesiredTargetDist (0.0f),
m_fAngleSpread (0.0f),
m_bCanPerformCombo (false),
m_bCanPerformDazedReaction (false),
m_bCanBlock (false),
m_bCanCounter (false),
m_bLastActionWasARecoil (false),
m_bEndsInAimingPose (false),
#if FPS_MODE_SUPPORTED
m_bInterruptForFirstPersonCamera (false),
#endif
m_nActionResultNetworkID (INVALID_ACTIONRESULT_ID),
m_nUniqueNetworkMeleeActionID (uUniqueNetworkID)
{
SetInternalTaskType( CTaskTypes::TASK_MELEE );
if( nTimeInTask > 0 )
m_forceStrafeTimer.Set( fwTimer::GetTimeInMilliseconds(), (u32)nTimeInTask );
else if( nTimeInTask < 0 )
SetTaskFlag( MF_ForceStrafeIndefinitely );
// Set initial lock-on
m_bHasLockOnTargetEntity = GetIsTaskFlagSet( MF_HasLockOnTarget );
AI_LOG_WITH_ARGS("CTaskMelee::CTaskMelee() - m_pTargetEntity = %s, task = %p \n", m_pTargetEntity ? m_pTargetEntity->GetLogName() : "Null", this);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Clone
// PURPOSE : The cloning operation- used by the task management system.
// RETURNS : A perfect copy of the task.
/////////////////////////////////////////////////////////////////////////////////
aiTask* CTaskMelee::Copy() const
{
return rage_new CTaskMelee( m_pNextActionResult, GetTargetEntity(), m_nFlags, m_impulseCombo.GetImpulseAtIndex(0), m_forceStrafeTimer.GetDuration(), m_nUniqueNetworkMeleeActionID );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldAbort
// PURPOSE : To receive an abort request and handle it.
// PARAMETERS : pPed - The ped of interest.
// iPriority - How important is it to abort.
// pEvent - What event is causing the abort (may be null).
// RETURNS : Whether or not the task has aborted.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::ShouldAbort( const AbortPriority iPriority, const aiEvent* pEvent )
{
taskAssertf( GetPed(), "pPed is null in CTaskMelee::ShouldAbort." );
DEV_BREAK_IF_FOCUS( CCombatMeleeDebug::sm_bFocusPedBreakOnTaskCombatMeleeShouldAbort, GetPed() );
// Ignore certain events while in melee (or doing a melee hit reaction).
if( CTask::ABORT_PRIORITY_IMMEDIATE != iPriority && pEvent )
{
const int nEventType = ((CEvent*)pEvent)->GetEventType();
if( nEventType == EVENT_MELEE_ACTION )
{
// Always ignore melee events.
return false;
}
else if( nEventType == EVENT_GUN_AIMED_AT )
{
// Note: If we are a player we should never get this message.
taskAssert( !GetPed()->IsPlayer() );
// Ignore being aimed at when we are currently doing a move, otherwise
// listen to the event and bail out.
// Note: If we are already in combat the CTackComplexCombat should handle
// and filter these events for us if the event is not a physical response.
if( IsCurrentlyDoingAMove( false, false, true ) )
return false;
}
else if( nEventType == EVENT_DAMAGE )
return CTask::ShouldAbort( iPriority, pEvent );
// Want to completely ignore these events when fighting.
else if( (((CEvent*)pEvent)->GetEventPriority() <= E_PRIORITY_POTENTIAL_WALK_INTO_FIRE || ((CEvent*)pEvent)->GetEventPriority() == E_PRIORITY_PED_COLLISION_WITH_PED ) )
{
return false;
}
else if (nEventType == EVENT_SCRIPT_COMMAND && GetPed()->IsDeadByMelee())
{
#if __ASSERT
const CEventScriptCommand* pEventScriptCommand = SafeCast(const CEventScriptCommand, pEvent);
const aiTask* pTask = pEventScriptCommand->GetTask();
const char* pszTaskName = pTask ? pTask->GetTaskName() : "NoTask";
taskAssertf(false, "CTaskMelee::ShouldAbort: EVENT_SCRIPT_COMMAND (%s, %s:%d) is trying to abort a melee task, but the ped (%s) is already DeadByMelee. Rejecting.",
pszTaskName,
pEventScriptCommand->GetScriptName(),
pEventScriptCommand->GetScriptProgramCounter(),
AILogging::GetEntityDescription(GetPed()).c_str());
#endif // __ASSERT
return false;
}
}
return CTask::ShouldAbort(iPriority, pEvent);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetOverriddenGameplayCameraSettings
// PURPOSE : Allow this task to override the active gameplay camera.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::GetOverriddenGameplayCameraSettings( tGameplayCameraSettings& settings ) const
{
//Request an aim camera when the player ped is locked-on with a melee weapon.
const CPed *pPed = GetPed(); //Get the ped ptr.
if( !pPed->IsLocalPlayer() )
return;
//Inhibit aim cameras during long and medium Switch transitions.
if( pPed->GetPlayerResetFlag( CPlayerResetFlags::PRF_DISABLE_AIM_CAMERA ) ||
( g_PlayerSwitch.IsActive() && ( g_PlayerSwitch.GetSwitchType() != CPlayerSwitchInterface::SWITCH_TYPE_SHORT ) ) )
{
return;
}
const CWeaponInfo* pWeaponInfo = camGameplayDirector::ComputeWeaponInfoForPed( *pPed );
if( taskVerifyf( pWeaponInfo, "Could not obtain a weapon info object for the ped's equipped weapon" ) )
{
// Special case first person scope hit reactions
const CTask* pSubTask = GetSubTask();
const bool bWeaponHasFirstPersonScope = pPed->GetWeaponManager() && pPed->GetWeaponManager()->GetEquippedWeaponHasFirstPersonScope();
if( bWeaponHasFirstPersonScope && pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
{
const CTaskMeleeActionResult* pActionResultTask = static_cast<const CTaskMeleeActionResult*>( pSubTask );
if( pActionResultTask->IsDoingReaction() )
{
settings.m_CameraHash = pWeaponInfo->GetRunAndGunCameraHash();
settings.m_InterpolationDuration = ms_nCameraInterpolationDuration;
return;
}
}
bool bIsMeleeWeapon = pWeaponInfo->GetIsMelee();
//NOTE: We render an aim camera when aim input is provided with a non-melee weapon to improve continuity with normal aiming.
if( ( bIsMeleeWeapon && pPed->GetPlayerInfo()->GetTargeting().GetLockOnTarget() ) ||
( !bIsMeleeWeapon && CPlayerInfo::IsAiming( false ) ) )
{
const CWeapon* pWeapon = pPed->GetWeaponManager()->GetEquippedWeapon();
settings.m_CameraHash = pWeapon ? pWeapon->GetDefaultCameraHash() : pWeaponInfo->GetDefaultCameraHash(); //Fall-back to the default aim camera.
settings.m_InterpolationDuration = ms_nCameraInterpolationDuration;
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CleanUp
// PURPOSE : Called when the task quits itself or is aborted, should cleanup
// any clips, assets etc...
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::CleanUp( void )
{
// Make sure any streaming melee clips are released.
ReleaseStreamingClipRequests();
// Clean up any LOS requests
CancelPendingLosRequest();
// Let the player on foot state know when the last time we finished a melee task
CPed* pPed = GetPed();
if( pPed )
{
bool bInFpsMode = false;
#if FPS_MODE_SUPPORTED
bInFpsMode = pPed->IsFirstPersonShooterModeEnabledForPlayer(false);
#endif // FPS_MODE_SUPPORTED
pPed->SetPedResetFlag( CPED_RESET_FLAG_DisableActionMode, false );
// aiTask* pTask = pPed->GetPedIntelligence()->FindTaskActiveMotionByType( CTaskTypes::TASK_HUMAN_LOCOMOTION );
// if( !pTask )
{
bool bWantsToActionMode = !GetIsTaskFlagSet( MF_EndsInIdlePose );
// Ensure action mode enabled
if(bWantsToActionMode)
{
pPed->SetUsingActionMode(true, CPed::AME_Melee, CTaskMeleeActionResult::sm_Tunables.m_ActionModeTime, false);
pPed->UpdateMovementMode();
}
if( GetIsTaskFlagSet( MF_TagSyncBlendOut ) && VelocityHighEnoughForTagSyncBlendOut( pPed ) )
{
float fDesiredMBR = pPed->GetMotionData()->GetDesiredMoveBlendRatio().Mag();
if( CPedMotionData::GetIsLessThanRun( fDesiredMBR ) )
pPed->ForceMotionStateThisFrame(bInFpsMode ? CPedMotionStates::MotionState_Aiming : (pPed->GetMotionData()->GetUsingStealth() ? CPedMotionStates::MotionState_Stealth_Walk : (bWantsToActionMode ? CPedMotionStates::MotionState_ActionMode_Walk : CPedMotionStates::MotionState_Walk)));
else
pPed->ForceMotionStateThisFrame(bInFpsMode ? CPedMotionStates::MotionState_Aiming : (pPed->GetMotionData()->GetUsingStealth() ? CPedMotionStates::MotionState_Stealth_Run : (bWantsToActionMode ? CPedMotionStates::MotionState_ActionMode_Run : CPedMotionStates::MotionState_Run)));
}
// Send them back to idle to eliminate any movement anims left over from before the CTaskMelee
else if( GetIsTaskFlagSet( MF_QuitTaskAfterNextAction ) )
{
bool bRunningSwimAimTasks = pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsSwimming) && pPed->GetPedIntelligence()->GetMotionTaskActiveSimplest()->GetTaskType()==CTaskTypes::TASK_MOTION_AIMING;
// Count the animal swimming tasks for players.
if (pPed->IsAPlayerPed() && pPed->GetPedType() == PEDTYPE_ANIMAL)
{
CTask* pMotionTaskActiveSimplest = pPed->GetPedIntelligence()->GetMotionTaskActiveSimplest();
if (pMotionTaskActiveSimplest && pMotionTaskActiveSimplest->GetTaskType() == CTaskTypes::TASK_ON_FOOT_FISH)
{
bRunningSwimAimTasks = true;
}
}
if (!pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning) && !bRunningSwimAimTasks)
pPed->ForceMotionStateThisFrame(bInFpsMode ? CPedMotionStates::MotionState_Aiming : (pPed->GetMotionData()->GetUsingStealth() ? CPedMotionStates::MotionState_Stealth_Idle : (bWantsToActionMode ? CPedMotionStates::MotionState_ActionMode_Idle : CPedMotionStates::MotionState_Idle)));
}
else if( GetIsTaskFlagSet( MF_AttemptMeleeControllerInterrupt ) )
{
// Based on the velocity force the associated motion frame
float fVelocity = pPed->GetVelocity().Mag2();
if( fVelocity < rage::square( CActionManager::ms_fMBRWalkBoundary ) )
pPed->ForceMotionStateThisFrame(bInFpsMode ? CPedMotionStates::MotionState_Aiming : (pPed->GetMotionData()->GetUsingStealth() ? CPedMotionStates::MotionState_Stealth_Idle : (bWantsToActionMode ? CPedMotionStates::MotionState_ActionMode_Idle : CPedMotionStates::MotionState_Idle)), true);
else
pPed->ForceMotionStateThisFrame(bInFpsMode ? CPedMotionStates::MotionState_Aiming : (pPed->GetMotionData()->GetUsingStealth() ? CPedMotionStates::MotionState_Stealth_Walk : (bWantsToActionMode ? CPedMotionStates::MotionState_ActionMode_Walk : CPedMotionStates::MotionState_Walk)));
}
}
if( pPed->IsPlayer() )
{
if( CPlayerInfo::IsAiming() && !bInFpsMode)
pPed->SetPedResetFlag( CPED_RESET_FLAG_InstantBlendToAim, true );
CTask* pTask = pPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_PLAYER_ON_FOOT );
if( pTask )
{
CTaskPlayerOnFoot* pPlayerOnFoot = static_cast<CTaskPlayerOnFoot*>( pTask );
pPlayerOnFoot->SetLastTimeInMeleeTask( fwTimer::GetTimeInMilliseconds() );
pPlayerOnFoot->SetWasMeleeStrafing( GetIsTaskFlagSet( MF_AllowStrafeMode ) );
}
}
else if( m_bEndsInAimingPose )
{
pPed->SetPedResetFlag( CPED_RESET_FLAG_InstantBlendToAim, true );
}
pPed->m_PedConfigFlags.SetPedLegIkMode( CIkManager::GetDefaultLegIkMode(pPed) );
pPed->GetMotionData()->SetGaitReductionBlockTime(); //block gait reduction while we ramp back up
}
UnRegisterMeleeOpponent();
// Clean up the route search helper if it is still around
if( m_RouteSearchHelper.IsSearchActive() )
m_RouteSearchHelper.ResetSearch();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CancelPendingLosRequest
// PURPOSE : To clear out any pathserver requests- used when shutting down the
// task.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::CancelPendingLosRequest( void )
{
m_seekModeLosTestHandle1.Reset();
m_seekModeLosTestHandle2.Reset();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : StopAll
// PURPOSE : Halt this task and its subtasks.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::StopAll( void )
{
taskAssertf( GetSubTask(), "GetSubTask() was null in CTaskMelee::StopAll." );
if( GetSubTask()->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
m_bStopAll = true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IsCurrentlyDoingAMove
// PURPOSE : Determine if a melee move is currently in progress.
// RETURNS : Whether or not a melee move is currently in progress.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::IsCurrentlyDoingAMove( bool bCountTauntAsAMove, bool bCountIntrosAsMove, bool bAllowInterrupt ) const
{
const CTask* pSubTask = GetSubTask();
if( !pSubTask || pSubTask->GetTaskType() != CTaskTypes::TASK_MELEE_ACTION_RESULT )
return false;
const CTaskMeleeActionResult* pActionResultTask = static_cast<const CTaskMeleeActionResult*>( pSubTask );
if( !bCountTauntAsAMove && pActionResultTask->IsDoingTaunt() )
return false;
if( !bCountIntrosAsMove && pActionResultTask->IsDoingIntro() )
return false;
if( bAllowInterrupt && pActionResultTask->ShouldAllowInterrupt() )
return false;
return !pActionResultTask->GetIsFlagSet( aiTaskFlags::HasFinished ) &&
!pActionResultTask->GetIsFlagSet( aiTaskFlags::IsAborted ) &&
pActionResultTask->IsDoingSomething();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckCloneForLocalBranch
// PURPOSE : Check if there were any local action branches we should take (i.e. dodge reactions).
// PARAMETERS : pPed - The clone ped of interest.
// RETURNS : If a valid branch was found.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::CheckCloneForLocalBranch( CPed* pPed )
{
CTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
{
CTaskMeleeActionResult* pActionResultTask = static_cast<CTaskMeleeActionResult*>(pSubTask);
//! Note: Deliberately only search for block branches atm.
if( IsBlocking() )
{
CActionFlags::ActionTypeBitSetData typeToLookFor;
typeToLookFor.Set( CActionFlags::AT_BLOCK, true );
u32 nBranchActionId = 0;
if( pActionResultTask->ShouldBranchOnCurrentFrame(pPed,
&m_impulseCombo,
&nBranchActionId,
false, //impulse initiated moves only
typeToLookFor,
0)) //nForcingResultId
{
u32 nActionDefnitionIdx = 0;
const CActionDefinition* pBranchesActionDefinition = ACTIONMGR.FindActionDefinition( nActionDefnitionIdx, nBranchActionId );
taskAssert( pBranchesActionDefinition );
// Get the pointer to the actionResult from the action Id.
const CActionResult* pBranchesActionResult = pBranchesActionDefinition->GetActionResult( pPed );
// Go to local branch?
if( pBranchesActionResult )
{
m_pNextActionResult = pBranchesActionResult;
ClearTaskFlag( MF_IsDoingReaction );
SetRunningLocally( true );
return true;
}
}
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckForBranch
// PURPOSE : Check if there were any action branches we should take.
// PARAMETERS : pPed - The ped of interest.
// pCurrentActionResultTask - What the ped is currently doing.
// nBranchActionId - An out val for the branch to take.
// RETURNS : If a valid branch was found.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::CheckForBranch( CPed* pPed, CTaskMeleeActionResult* pActionResultTask, u32& nBranchActionId )
{
taskAssertf( pPed, "pPed is null in CTaskMelee::CheckForBranch." );
CEntity* pTargetEntity = GetTargetEntity();
CPed* pTargetPed = ( pTargetEntity && pTargetEntity->GetIsTypePed() ) ? static_cast<CPed*>(pTargetEntity) : NULL;
CActionFlags::ActionTypeBitSetData typeToLookFor;
if( ShouldDaze() )
typeToLookFor.Set( CActionFlags::AT_DAZED_HIT_REACTION, true );
// If we do not currently have a CTaskMeleeActionResult
if( !pActionResultTask || ( pPed->IsPlayer() && GetIsTaskFlagSet( MF_AllowNoTargetInterrupt ) ) )
{
if( pPed->IsPlayer() )
{
bool bAttemptTaunt = false;
if( IsBlocking() )
typeToLookFor.Set( CActionFlags::AT_BLOCK, true );
else if( CTaskMelee::ShouldCheckForMeleeAmbientMove( pPed, bAttemptTaunt, HasLockOnTargetEntity() ) )
{
if( pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning ) )
typeToLookFor.Set( CActionFlags::AT_SWIMMING, true );
else
typeToLookFor.Set( CActionFlags::AT_MELEE, true );
// NULL out the last found action definition in hopes that we find a new one and possibly a new target
ms_LastFoundActionDefinition = NULL;
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
bool bTargetDeadPeds = ((pTargetEntity == NULL) || (pTargetPed && pTargetPed->IsDead())) && (rTargeting.GetLockOnTarget() == NULL);
// See if we can grab a better target if we are button mashing
CPedWeaponManager* pWeaponMgr = pPed->GetWeaponManager();
const CWeaponInfo* pWeaponInfo = pWeaponMgr ? pWeaponMgr->GetEquippedWeaponInfo() : NULL;
CEntity* pNewTarget = rTargeting.FindMeleeTarget( pPed, pWeaponInfo, true, bTargetDeadPeds, true, false, pTargetEntity );
if(pNewTarget == pTargetEntity)
{
// do nothing. same target.
}
else if(pNewTarget)
{
if(pNewTarget->GetIsTypePed())
{
CPed* pNewTargetPed = static_cast<CPed*>( pNewTarget );
pTargetPed = pNewTargetPed;
}
pTargetEntity = pNewTarget;
SetTargetEntity( pTargetPed, false, true);
// Check if we found an action to take.
if( ms_LastFoundActionDefinition )
{
nBranchActionId = ms_LastFoundActionDefinition->GetID();
return true;
}
}
}
else if( ShouldPerformIntroAnim( pPed ) )
typeToLookFor.Set( CActionFlags::AT_INTRO, true );
else if( !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_PreventAllMeleeTaunts ) && !bAttemptTaunt && GetIsTaskFlagSet( MF_AllowStrafeMode ) && !GetIsTaskFlagSet( MF_BehindTargetPed ) )
{
if( m_tauntTimer.IsSet() && m_tauntTimer.IsOutOfTime() )
typeToLookFor.Set( CActionFlags::AT_TAUNT, true );
}
else if( bAttemptTaunt )
typeToLookFor.Set( CActionFlags::AT_TAUNT, true );
}
else
{
// Note: This is really one of the major parts of our "Melee Combat AI"...
// Search for the quoted term in the line above to find the others.
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
// Make AI attacks not be totally incessant, but make it so that the AI can
// continue combos once started.
if( DEV_ONLY( CCombatMeleeDebug::sm_bAllowNpcInitiatedMoves && )
!pPed->GetPedResetFlag( CPED_RESET_FLAG_SuspendInitiatedMeleeActions ) &&
pTargetPed &&
taskVerifyf( pBrawlData, "CTaskMelee::CheckForBranch - Melee opponent without brawling style" ) )
{
// Only check for actions if we are within attack range
if( CanPerformMeleeAttack( pPed, pTargetEntity ) )
{
// Check to see if we are within attack range and not queued
if( m_nextRequiredMoveTimer.IsSet() && m_nextRequiredMoveTimer.IsOutOfTime() )
{
if( pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning ) )
typeToLookFor.Set( CActionFlags::AT_SWIMMING, true );
else
typeToLookFor.Set( CActionFlags::AT_MELEE, true );
}
// Check target to see if they are performing a move and if so check for dodging/blocking
CQueriableInterface* pQueriableInterface = pTargetPed->GetPedIntelligence()->GetQueriableInterface();
if( pQueriableInterface->IsTaskCurrentlyRunning( CTaskTypes::TASK_MELEE_ACTION_RESULT ) )
{
if( pTargetPed->GetPedIntelligence()->GetTaskMeleeActionResult() && (m_bCanBlock || pPed->GetPedResetFlag(CPED_RESET_FLAG_ForceMeleeCounter)) )
typeToLookFor.Set( CActionFlags::AT_BLOCK, true );
}
}
// If we are not performing an attack
if( !typeToLookFor.IsSet( CActionFlags::AT_SWIMMING ) && !typeToLookFor.IsSet( CActionFlags::AT_MELEE ) )
{
// Always perform a taunt if we were asked to
if( ShouldPerformIntroAnim( pPed ) )
typeToLookFor.Set( CActionFlags::AT_INTRO, true );
// Check taunt conditions
else if( !pPed->GetPedResetFlag(CPED_RESET_FLAG_ForceMeleeCounter) && !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_PreventAllMeleeTaunts ) && !typeToLookFor.IsSet( CActionFlags::AT_BLOCK ) && m_tauntTimer.IsSet() && m_tauntTimer.IsOutOfTime() )
{
if( GetIsTaskFlagSet( MF_PlayTauntBeforeAttacking ) )
typeToLookFor.Set( CActionFlags::AT_TAUNT, true );
else
{
float fTauntProbability = IsDoingQueueForTarget() ? pBrawlData->GetTauntProbabilityQueued() : pBrawlData->GetTauntProbability();
// AI still have a chance to not taunt based on fight proficiency
if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) < fTauntProbability )
{
if( GetQueueType() == QT_InactiveObserver )
{
pPed->NewSay( "FIGHT_BYSTANDER_TAUNT" );
ResetTauntTimer();
}
// otherwise attempt an animated melee taunt
else
typeToLookFor.Set( CActionFlags::AT_TAUNT, true );
}
}
}
}
}
}
// Only check if we need to
if( typeToLookFor.AreAnySet() )
{
CEntity* pTargetEntity = GetTargetEntity();
// For AI if the last action was a recoil we need to make an exception and allow
// non entry actions to be selected
const bool bTestEntryAction = pPed->IsPlayer() ? true : !WasLastActionARecoil();
// Attempt to find a suitable action
const CActionDefinition* pActionToDo = ACTIONMGR.SelectSuitableAction(
typeToLookFor, // Only look for moves that are appropriate from on foot
&m_impulseCombo, // Impulse combo
bTestEntryAction, // Test whether or not it is an entry action.
true, // Only find entry actions.
pPed, // The acting ped.
0, // result Id forcing us.
-1, // Priority
pTargetEntity, // The entity being attacked.
HasLockOnTargetEntity(), // Do we have a lock on the target entity.
false, // Should we only allow impulse initiated moves.
pPed->IsLocalPlayer(), // Test the controls (for local players only).
CActionManager::ShouldDebugPed( pPed ) );
// If we couldn't find an action to perform and had a valid target let us try to find a move that doesn't require a target entity
if( pTargetEntity && !pActionToDo )
{
pActionToDo = ACTIONMGR.SelectSuitableAction(
typeToLookFor, // Only look for moves that are appropriate from on foot
&m_impulseCombo, // Impulse combo
bTestEntryAction, // Test whether or not it is an entry action.
true, // Only find entry actions.
pPed, // The acting ped.
0, // result Id forcing us.
-1, // Priority
NULL, // The entity being attacked.
false, // Do we have a lock on the target entity.
false, // Should we only allow impulse initiated moves.
pPed->IsLocalPlayer(), // Test the controls (for local players only).
CActionManager::ShouldDebugPed( pPed ) );
if( pActionToDo )
{
pTargetEntity = NULL;
}
}
// Check if we found an action to take.
if( pActionToDo )
{
SetTargetEntity( pTargetEntity, false, true );
// Let the caller know what branch to go to.
nBranchActionId = pActionToDo->GetID();
return true;
}
// If we fell through and didn't find a taunt to play... reset the timer
if( typeToLookFor.IsSet( CActionFlags::AT_TAUNT ) )
ResetTauntTimer();
}
}
else
{
// Check for player initiated actions.
if( pPed->IsPlayer() )
{
if( IsBlocking() )
typeToLookFor.Set( CActionFlags::AT_BLOCK, true );
if( pActionResultTask->IsDoingBlock() )
typeToLookFor.Set( CActionFlags::AT_COUNTER, true );
else
{
if( pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning ) )
typeToLookFor.Set( CActionFlags::AT_SWIMMING, true );
else
typeToLookFor.Set( CActionFlags::AT_MELEE, true );
}
if( pActionResultTask->ShouldBranchOnCurrentFrame( pPed, &m_impulseCombo, &nBranchActionId, false, typeToLookFor, 0 ) )
return true;
}
// Check for combo actions.
else
{
// Only check for actions if we are within attack range
if( CanPerformMeleeAttack( pPed, pTargetEntity ) )
{
if( (pActionResultTask->IsDoingBlock() && m_bCanCounter) || pPed->GetPedResetFlag(CPED_RESET_FLAG_ForceMeleeCounter) )
typeToLookFor.Set( CActionFlags::AT_COUNTER, true );
// For now only allow the player to counter a hit reaction
else if( !pActionResultTask->IsDoingReaction() && m_bCanPerformCombo )
{
if( pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning ) )
typeToLookFor.Set( CActionFlags::AT_SWIMMING, true );
else
typeToLookFor.Set( CActionFlags::AT_MELEE, true );
}
// Check to see if we should attempt a block
if( pTargetPed )
{
// Check target to see if they are performing a move and if so check for dodging/blocking
CQueriableInterface* pQueriableInterface = pTargetPed->GetPedIntelligence()->GetQueriableInterface();
if( pQueriableInterface->IsTaskCurrentlyRunning( CTaskTypes::TASK_MELEE_ACTION_RESULT ) )
{
if( pTargetPed->GetPedIntelligence()->GetTaskMeleeActionResult() && (m_bCanBlock || pPed->GetPedResetFlag(CPED_RESET_FLAG_ForceMeleeCounter)) )
typeToLookFor.Set( CActionFlags::AT_BLOCK, true );
}
}
}
// Either check for all available branches or only for branches that don't
// require a move to be self initiated done... (i.e. have impulse test of none).
//const bool bImpulseInitiatedMovesOnly = !bAttemptNpcInitiatedAction;
if( typeToLookFor.AreAnySet() )
{
if( pActionResultTask->ShouldBranchOnCurrentFrame( pPed, &m_impulseCombo, &nBranchActionId, false, typeToLookFor, 0 ) )
return true;
}
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetTargetEntity
// PURPOSE : Set the target of the ped. This is used for movement, homing and
// attacks.
// PARAMETERS : pTargetEntity - the target.
// bHasLockOnTargetEntity - if the ped is locked on to the entity.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::SetTargetEntity( CEntity* pTargetEntity, bool bHasLockOnTargetEntity, bool bReplaceLockOn )
{
if(!CanChangeTarget())
{
return;
}
if(GetEntity())
{
CPed* pPed = GetPed();
if(pPed)
{
if( bReplaceLockOn && pPed->IsLocalPlayer() )
{
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
if( m_pTargetEntity && (rTargeting.GetLockOnTarget() == m_pTargetEntity) && (rTargeting.GetLockOnTarget() != pTargetEntity) )
{
rTargeting.SetLockOnTarget( pTargetEntity );
}
}
// Special case for family gang members that couldn't attack the player
if( pPed->PopTypeIsRandom() && pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>(pTargetEntity);
if( pTargetPed->IsLocalPlayer() && pPed->GetPedIntelligence() )
pPed->GetPedIntelligence()->SetFriendlyException( pTargetPed );
}
}
#if __BANK
AI_LOG_WITH_ARGS("CTaskMelee::SetTargetEntity() - pPed = %s, m_pTargetEntity = %s, pTargetEntity = %s, task = %p \n",pPed ? pPed->GetLogName() : "Null", m_pTargetEntity ? m_pTargetEntity->GetLogName() : "Null", pTargetEntity ? pTargetEntity->GetLogName() : "Null", this);
if(pPed && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DontChangeTargetFromMelee))
{
aiDisplayf("Frame %i, CTaskMelee::SetTargetEntity(), task = %p - callstack \n", fwTimer::GetFrameCount(), this);
sysStack::PrintStackTrace();
}
#endif
}
// Just set the has lock on flag to its current state (allowing us to
// acquire and break locks on a given ped even when they are acquired as
// an ambient target).
m_pTargetEntity = pTargetEntity;
m_bHasLockOnTargetEntity = bHasLockOnTargetEntity;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldPerformIntroAnim
// PURPOSE : Determines whether or not we should perform the intro anim
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::ShouldPerformIntroAnim( CPed* pPed ) const
{
if( !pPed )
return false;
if( !GetIsTaskFlagSet( MF_PerformIntroAnim ) )
return false;
if( GetIsTaskFlagSet( MF_ForceStrafe ) )
return false;
if( pPed->GetTaskData().GetIsFlagSet( CTaskFlags::DisableMeleeIntroAnimation ) )
return false;
// Determine whether or not we are fit to play the melee intro anim
if( pPed->IsPlayer() || GetQueueType() == QT_ActiveCombatant )
{
// We don't want to play intro anim when behind our target, they could be trying to stealth kill
CEntity* pTargetEntity = GetTargetEntity();
if( pPed->IsPlayer() && pTargetEntity )
{
static dev_float sfDotThreshold = -0.15f;
Vec3V v3PedToTarget = pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition();
v3PedToTarget = Normalize( v3PedToTarget );
// Check to see if we are behind the target
float fDotCompare = Dot( v3PedToTarget, pTargetEntity->GetTransform().GetB() ).Getf();
if( fDotCompare > sfDotThreshold )
return false;
}
// We are okay to perform the intro anims
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CanPerformMeleeAttack
// PURPOSE : Determines whether or not teh designated ped should be allowed
// to perform a melee attack
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::CanPerformMeleeAttack( CPed* pPed, CEntity* pTargetEntity ) const
{
if( pPed->IsLocalPlayer() && CNewHud::IsShowingHUDMenu() )
return false;
if( !pPed || pPed->IsPlayer() || !pTargetEntity )
return true;
// Check to see if we are within attack range
Vec3V vDelta = pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition();
if( !IsLosBlocked() && !ShouldBeInInactiveTauntMode() && !IsDoingQueueForTarget() && Abs( vDelta.GetZf() ) < sm_fAiSeekModeTargetHeight )
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
taskAssertf( pBrawlData, "CTaskMelee::IncreaseMeleeBuff - Melee opponent without brawling style" );
Vec3V vDeltaXY( vDelta.GetXf(), vDelta.GetYf(), 0.0f );
const float fTargetDistanceSq = MagSquared( vDeltaXY ).Getf();
return fTargetDistanceSq < rage::square( pBrawlData->GetAttackRangeMax() );
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CalculateDesiredTargetDistance
// PURPOSE : Given the current melee queue type, this function will calculate
// the desired target distance
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::CalculateDesiredTargetDistance( void )
{
if( IsLosBlocked() )
m_fDesiredTargetDist = sm_fAiSeekModeActiveCombatantRadius;
else
{
QueueType queueType = GetQueueType();
if( queueType == QT_SupportCombatant )
m_fDesiredTargetDist = fwRandom::GetRandomNumberInRange( sm_fAiSeekModeSupportCombatantRadiusMin, sm_fAiSeekModeSupportCombatantRadiusMax );
else if( queueType <= QT_InactiveObserver )
m_fDesiredTargetDist = fwRandom::GetRandomNumberInRange( sm_fAiSeekModeInactiveCombatantRadiusMin, sm_fAiSeekModeInactiveCombatantRadiusMax );
else
m_fDesiredTargetDist = sm_fAiSeekModeActiveCombatantRadius;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CalculateDesiredChasePosition
// PURPOSE : Predict where the target will be given the current velocity and
// adjust the scan time based on distance from the target
/////////////////////////////////////////////////////////////////////////////////
Vec3V_Out CTaskMelee::CalculateDesiredChasePosition( CPed* pPed )
{
Vec3V vPredictedPosition( VEC3_ZERO );
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
static dev_float sfPredictedTimeMin = 0.5f;
static dev_float sfPredictedTimeMax = 1.5f;
// based on distance from the player
static dev_float fMaxScaleDistance = 35.0f;
Vec3V vPedToTarget = pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition();
float fDistSq = MagSquared( vPedToTarget ).Getf();
const CBrawlingStyleData* pData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
s32 fSeekModeTimeMin = pData ? pData->GetSeekModeScanTimeMin() : sm_nAiSeekModeScanTimeMin;
s32 fSeekModeTimeMax = pData ? pData->GetSeekModeScanTimeMax() : sm_nAiSeekModeScanTimeMax;
float fPredictedTimeScale = 0.0f;
if( fDistSq < rage::square( fMaxScaleDistance ) )
{
float fTimeRatio = fDistSq / rage::square( fMaxScaleDistance );
fPredictedTimeScale = Lerp( fTimeRatio, sfPredictedTimeMin, sfPredictedTimeMax );
m_seekModeScanTimer.Set( fwTimer::GetTimeInMilliseconds(), Lerp( fTimeRatio, fSeekModeTimeMin, fSeekModeTimeMax ) );
}
else
{
fPredictedTimeScale = sfPredictedTimeMax;
m_seekModeScanTimer.Set( fwTimer::GetTimeInMilliseconds(), fSeekModeTimeMax );
}
// Set the desired offset distance
vPredictedPosition.SetYf( GetDesiredTargetDistance() );
// Rotate the desired offset in the direction of the desired heading
vPredictedPosition = RotateAboutZAxis( vPredictedPosition, ScalarV( GetAngleSpread() ) );
// Otherwise predict where the target will be
vPredictedPosition += CActionManager::CalculatePredictedTargetPosition( pPed, pTargetEntity, fPredictedTimeScale, BONETAG_ROOT );
}
return vPredictedPosition;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetQueueType
// PURPOSE : Changes the current queue type and recalculates the desired
// target distance.
// PARAMETERS : queueType - Type which you would like to change this ped to
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::SetQueueType( QueueType newQueueType )
{
if( newQueueType != m_queueType )
{
m_queueType = newQueueType;
// Audio wants this to happen whenever a new active combatant comes into scope
if( newQueueType == QT_ActiveCombatant )
{
CPed* pPed = GetPed();
if( pPed && !pPed->IsPlayer() )
pPed->NewSay("FIGHT", 0, false, false);
}
// Re-calculate the distance at which we want this melee combatant to be
CalculateDesiredTargetDistance();
// Release this as we will most likely need to switch to a new type
m_TauntClipSetRequestHelper.Release();
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetActionResultId
// PURPOSE : Get the action result Id of the action subtask that is currently
// being enacted.
// RETURNS : The action Id or 0 if no action is present.
/////////////////////////////////////////////////////////////////////////////////
u32 CTaskMelee::GetActionResultId( void ) const
{
// JB: The task may now also be doing a TASK_COMPLEX_CONTROL_MOVEMENT as its subtask to seek the target ped
const aiTask* pSubTask = GetSubTask();
if( !pSubTask || pSubTask->GetTaskType() != CTaskTypes::TASK_MELEE_ACTION_RESULT )
return 0;
const CTaskMeleeActionResult* pActionResultTask = static_cast<const CTaskMeleeActionResult*>( pSubTask );
return pActionResultTask->GetResultId();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetLocalReaction
// PURPOSE : Force the ped to react immediately to some situation.
// PARAMETERS : pActionResult - result to pay.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::SetLocalReaction( const CActionResult* pActionResult, bool bHitReaction, u16 uMeleeID)
{
if(GetPed()->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMelee::SetLocalReaction - Player = %s State = %d", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), GetState());
}
SetRunningLocally( true );
m_bLocalReaction = true;
m_pNextActionResult = pActionResult;
m_nUniqueNetworkMeleeActionID = uMeleeID;
if( bHitReaction )
SetTaskFlag( MF_IsDoingReaction );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ResetImpulseCombo
// PURPOSE : Force the ped to reset the impulse combo (whilst being held, allows us to keep pummeling the held ped).
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ResetImpulseCombo( void )
{
m_bActionIsBranch = false;
m_impulseCombo.Reset();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IncreaseMeleeBuff
// PURPOSE : Increase a melee buffer to allow AI to become more likely to attack
// and block after consecutive successful hits.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::IncreaseMeleeBuff( CPed* pPed )
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( taskVerifyf( pBrawlData, "CTaskMelee::IncreaseMeleeBuff - Melee opponent without brawling style" ) )
{
m_fBlockBuff += sm_fAiBlockConsecutiveHitBuffIncrease;
m_fBlockBuff = MIN( m_fBlockBuff, 1.0f - pBrawlData->GetBlockProbabilityMax() );
// Subtract a bit of time to the next move timer... allowing for quicker attack responses
float fFightProficiency = Clamp( pPed->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat( kAttribFloatFightProficiency ), 0.0f, 1.0f );
m_nextRequiredMoveTimer.SubtractTime( Lerp( fFightProficiency, sm_fAiAttackConsecutiveHitBuffIncreaseMin, sm_fAiAttackConsecutiveHitBuffIncreaseMax ) );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DecreaseMeleeBuff
// PURPOSE : Decay block buffer to slowly approach original block value
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::DecreaseMeleeBuff( void )
{
m_fBlockBuff -= sm_fAiBlockActionBuffDecrease;
m_fBlockBuff = MAX( m_fBlockBuff, 0.0f );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ResetTauntTimer
// PURPOSE : Helper function to facilitate reseting the taunt frequency
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ResetTauntTimer( void )
{
CPed* pPed = GetPed();
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( !pPed->IsPlayer() && taskVerifyf( pBrawlData, "CTaskMelee::ResetTauntTimer - Melee opponent without brawling style" ) )
{
if( IsDoingQueueForTarget() )
m_tauntTimer.Set( fwTimer::GetTimeInMilliseconds(), fwRandom::GetRandomNumberInRange( pBrawlData->GetTauntFrequencyQueuedMin(), pBrawlData->GetTauntFrequencyQueuedMax() ) );
else
m_tauntTimer.Set( fwTimer::GetTimeInMilliseconds(), fwRandom::GetRandomNumberInRange( pBrawlData->GetTauntFrequencyMin(), pBrawlData->GetTauntFrequencyMax() ) );
}
else
m_tauntTimer.Set( fwTimer::GetTimeInMilliseconds(), fwRandom::GetRandomNumberInRange( sm_nPlayerTauntFrequencyMinInMs, sm_nPlayerTauntFrequencyMaxInMs) );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : MakeStreamingClipRequests
// PURPOSE : Send requests to the streaming system for clips this ped
// needs or might likely need for melee combat.
// PARAMETERS : pPed - the ped we are interested in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::MakeStreamingClipRequests( CPed* pPed )
{
taskAssertf( pPed, "pPed is null in CTaskMelee::MakeStreamingClipRequests." );
// Get the peds model info (since that stores the necessary streaming melee clips).
const CPedModelInfo* pModelInfo = pPed->GetPedModelInfo();
taskAssert( pModelInfo );
// Make sure the melee movement clips are loaded/loading.
if( (fwMvClipSetId)pModelInfo->GetPersonalitySettings().GetMeleeMovementDictionaryGroupId().GetHash() != CLIP_SET_ID_INVALID )
m_MovementClipSetRequestHelper.Request( (fwMvClipSetId)pModelInfo->GetPersonalitySettings().GetMeleeMovementDictionaryGroupId().GetHash() );
// AI only clip set logic
if( !pPed->IsLocalPlayer() )
{
// Taunt and variation requests
bool bRequestedTauntClipSet = false, bRequestedVariationClipSet = false;
const CWeaponInfo* pEquippedWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
if( pEquippedWeaponInfo )
{
QueueType eQueueType = GetQueueType();
if( eQueueType == QT_ActiveCombatant )
{
bRequestedTauntClipSet = bRequestedVariationClipSet = true;
if( !m_TauntClipSetRequestHelper.IsLoaded() )
{
m_TauntClipSetRequestHelper.Request( pEquippedWeaponInfo->GetMeleeTauntClipSetId( *pPed ) );
}
if( !m_VariationClipSetRequestHelper.IsLoaded() )
{
m_VariationClipSetRequestHelper.Request( pEquippedWeaponInfo->GetMeleeVariationClipSetId( *pPed ) );
}
}
else if( eQueueType == QT_SupportCombatant || ShouldBeInInactiveTauntMode() )
{
bRequestedTauntClipSet = true;
if( !m_TauntClipSetRequestHelper.IsLoaded() )
{
m_TauntClipSetRequestHelper.Request( pEquippedWeaponInfo->GetMeleeSupportTauntClipSetId( *pPed ) );
}
}
}
if( !bRequestedTauntClipSet && m_TauntClipSetRequestHelper.IsLoaded() )
{
m_TauntClipSetRequestHelper.Release();
}
if( !bRequestedVariationClipSet && m_VariationClipSetRequestHelper.IsLoaded() )
{
m_VariationClipSetRequestHelper.Release();
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ReleaseStreamingClipRequests
// PURPOSE : Free the requests to the streaming system for clips.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ReleaseStreamingClipRequests( void )
{
// Release any streaming melee movement clips.
m_MovementClipSetRequestHelper.Release();
m_TauntClipSetRequestHelper.Release();
m_VariationClipSetRequestHelper.Release();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateTarget
// PURPOSE : Updates the melee target for the ped.
// PARAMETERS : pPed - the ped we are interest in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UpdateTarget( CPed* pPed )
{
Assertf( pPed, "pPed is null in CTaskMelee::UpdateTarget." );
CEntity* pTargetEntity = GetTargetEntity();
#if __BANK
if( CCombatMeleeDebug::sm_bDrawTargetPos && pTargetEntity)
{
if( (CCombatMeleeDebug::sm_bVisualisePlayer && pPed->IsLocalPlayer()) ||
(CCombatMeleeDebug::sm_bVisualiseNPCs && !pPed->IsLocalPlayer()) )
{
Vector3 vPos = VEC3V_TO_VECTOR3(pTargetEntity->GetTransform().GetPosition());
static dev_float s_fZOffset = 1.25f;
static dev_float s_fRadius = 0.15f;
vPos.z += s_fZOffset;
CCombatMeleeDebug::sm_debugDraw.AddSphere( VECTOR3_TO_VEC3V(vPos), s_fRadius, Color_green, 1 );
}
}
#endif
// Don't update if we have already decided to perform an action
if( m_pNextActionResult != NULL )
return;
// Don't allow switching of targets during a melee action of any type
const CTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
return;
bool bIsLocalPlayer = pPed->IsLocalPlayer();
// Update our lock on target so it is correct.
if( bIsLocalPlayer )
{
CEntity* pWeaponLockOnTargetEntity = pPed->GetPlayerInfo()->GetTargeting().GetLockOnTarget();
if( pWeaponLockOnTargetEntity )
SetTargetEntity( pWeaponLockOnTargetEntity, true );
else
SetTargetEntity( pTargetEntity );
// If the local player has a network object
if( pPed->GetNetworkObject() )
{
// pass on the current target to the player network object
const CEntity* pCurrentTargetEntity = GetTargetEntity();
if( pCurrentTargetEntity )
{
static_cast<CNetObjPlayer*>(pPed->GetNetworkObject())->SetAimTarget(*pCurrentTargetEntity);
}
}
// Check if we should search for ambient targets.
if( !pTargetEntity )
{
SetQueueType( QT_ActiveCombatant );
// Get our held weapon info to determine our targeting range.
u32 nWeaponHash = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponHash() : 0;
if( nWeaponHash != 0 )
{
const float fWeaponRange = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash)->GetLockOnRange();
// Try to find an auto target target within that range.
CPedTargetEvaluator::tHeadingLimitData headingData;
headingData.fHeadingAngularLimit = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetAngularLimitMelee;
headingData.fHeadingFalloffPower = 1.0f;
CEntity* pAmbientTargetEntity = CPedTargetEvaluator::FindTarget( pPed, CPedTargetEvaluator::MELEE_TARGET_FLAGS | CPedTargetEvaluator::TS_DESIRED_DIRECTION_HEADING, fWeaponRange, fWeaponRange, NULL, NULL, headingData );
// Update the old target and the new target.
if( pAmbientTargetEntity )
{
SetTargetEntity( pAmbientTargetEntity );
// if the local player has a network object
if( pPed->GetNetworkObject() )
{
// pass on the current target to the player network object for syncing
static_cast<CNetObjPlayer*>(pPed->GetNetworkObject())->SetAimTarget(*pAmbientTargetEntity);
}
}
}
}
}
// Since we don't currently have a target we cannot have a lock on.
else if( !pTargetEntity )
{
SetQueueType( QT_InactiveObserver );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateHeadAndSpineLooks
// PURPOSE : Updates the head and spine look IK.
// PARAMETERS : pPed - the ped we are interest in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UpdateHeadAndSpineLooksAndLegIK( CPed* pPed )
{
Assertf( pPed, "pPed is null in CTaskMelee::UpdateHeadAndSpineLooksAndLegIK." );
// Make sure we have and can get the target ped.
CEntity* pTargetEntity = GetTargetEntity();
if( !pTargetEntity || !pTargetEntity->GetIsTypePed() )
return;
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
Vec3V vPedPosition = pPed->GetTransform().GetPosition();
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
const float fHeightDiff = rage::Abs( vPedPosition.GetZf() - vTargetPosition.GetZf() );
// Do leg ik for AI only
if( !pPed->GetIKSettings().IsIKFlagDisabled( IKManagerSolverTypes::ikSolverTypeLeg ) && sm_bDoLegIK )
pPed->m_PedConfigFlags.SetPedLegIkMode( GetQueueType() == QT_ActiveCombatant ? LEG_IK_MODE_FULL_MELEE : CIkManager::GetDefaultLegIkMode(pPed) );
// Do the spine look to compensate for target peds that are standing on stairs, etc.
// make we don't do it if the height difference is too great though (otherwise it just
// looks bad).
if( !pPed->GetIKSettings().IsIKFlagDisabled( IKManagerSolverTypes::ikSolverTypeTorso ) && sm_bDoSpineIk )
{
pPed->GetIkManager().SetTorsoMinYaw(( DtoR * sm_fSpineLookMinYawDegs) );
pPed->GetIkManager().SetTorsoMaxYaw(( DtoR * sm_fSpineLookMaxYawDegs) );
pPed->GetIkManager().SetTorsoMinPitch(( DtoR * sm_fSpineLookMinPitchDegs) );
pPed->GetIkManager().SetTorsoMaxPitch(( DtoR * sm_fSpineLookMaxPitchDegs) );
pPed->GetIkManager().SetTorsoOffsetYaw(( DtoR * sm_fSpineLookTorsoOffsetYawDegs) );
float fSpineLookAmount = 0.0f;
if( fHeightDiff > sm_fSpineLookMaxHeightDiff )
fSpineLookAmount = 0.0f;
else
{
const float p = 1.0f - ( fHeightDiff / sm_fSpineLookMaxHeightDiff );
fSpineLookAmount = rage::Powf( p, sm_fSpineLookAmountPowerCurve );// Give the spine look a falloff curve.
}
if( fSpineLookAmount > 0.0f )
{
Matrix34 matTargetsSpine3Bone;
pTargetPed->GetGlobalMtx( pTargetPed->GetBoneIndexFromBoneTag( BONETAG_SPINE3 ), matTargetsSpine3Bone );
pPed->GetIkManager().PointGunAtPosition( matTargetsSpine3Bone.d, -1.0f /*fSpineLookAmount*/ );
}
}
// Do the head looks to make it look like there is an actual intelligence in the ped.
if( !pPed->GetIKSettings().IsIKFlagDisabled( IKManagerSolverTypes::ikSolverTypeHead ) && ( sm_bDoHeadIk || sm_bDoEyeIK ) )
{
Vec3V vLookAtPosition = pTargetPed->GetBonePositionCachedV( BONETAG_HEAD );
bool bAllowHeadIk = true;
CTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
{
CTaskMeleeActionResult* pActionResultTask = static_cast<CTaskMeleeActionResult*>( pSubTask );
bAllowHeadIk = !pActionResultTask->IsDoingReaction() || pActionResultTask->ShouldAllowInterrupt();
// Only use the cached position if we are processing melee collisions
// we do not want the ped to stop looking at the same height if we started a punch
if( pActionResultTask->ShouldProcessMeleeCollisions() )
vLookAtPosition = pPed->GetTransform().GetPosition() + pActionResultTask->GetCachedHeadIkOffset();
}
if( bAllowHeadIk )
{
// Should we use head ik?
if( sm_bDoHeadIk )
{
CIkRequestBodyLook lookRequest( NULL, BONETAG_INVALID, vLookAtPosition, 0, CIkManager::IK_LOOKAT_HIGH);
lookRequest.SetRefDirHead( LOOKIK_REF_DIR_FACING );
lookRequest.SetRefDirNeck( LOOKIK_REF_DIR_FACING );
lookRequest.SetRotLimNeck( LOOKIK_ROT_ANGLE_YAW, LOOKIK_ROT_LIM_WIDE );
lookRequest.SetRotLimNeck( LOOKIK_ROT_ANGLE_PITCH, LOOKIK_ROT_LIM_NARROWEST );
lookRequest.SetHoldTime( 100 );
// Make the blend and turn rate as fast as possible if we are trying to sync
if( GetIsTaskFlagSet( MF_AnimSynced ) )
{
lookRequest.SetTurnRate( LOOKIK_TURN_RATE_FAST );
lookRequest.SetBlendInRate( LOOKIK_BLEND_RATE_INSTANT );
}
pPed->GetIkManager().Request( lookRequest );
}
else if( sm_bDoEyeIK && ( pPed->IsPlayer() || GetQueueType() == QT_ActiveCombatant ) )
pPed->GetIkManager().LookAt(0, NULL, 100, BONETAG_INVALID, &RCC_VECTOR3( vLookAtPosition ), LF_USE_EYES_ONLY, GetIsTaskFlagSet( MF_AnimSynced ) ? LOOKIK_BLEND_RATE_INSTANT : 500, 500, CIkManager::IK_LOOKAT_HIGH );
}
}
}
static const float HIT_BLOCKING_MOMENTUM_SCALER = 1.0f;
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : MeleeMoveTaskControl
// PURPOSE : Updates the movement task, if it is the Melee movement task &
// not controlled by a CtrlMove task.
// PARAMETERS : pPed - the ped we are interest in.
// bAllowMovementInput - Whether or not to allow movement inputs through
// bAllowStrafing - Whether or not to allow strafing
// bAllowHeadingUpdate - Whether or not to allow heading
// bAllowStealth - Whether or not we should process stealth mode
// pAvoidEntity - an optional entity to try to move away from.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::MeleeMoveTaskControl( CPed* pPed, bool bAllowMovementInput, bool bAllowStrafing, bool bAllowHeadingUpdate, bool bAllowStealth, CEntity* pAvoidEntity )
{
Assertf( pPed, "pPed is null in CTaskMelee::MeleeMoveTaskControl." );
Assert( pPed->GetPedIntelligence() );
// Get the movement task or create a new one if none exists.
CTask* pMovementTask = pPed->GetPedIntelligence()->GetGeneralMovementTask();
if( !pPed->IsNetworkClone() && ( !pMovementTask || pMovementTask->GetTaskType() != CTaskTypes::TASK_MOVE_MELEE_MOVEMENT ) )
{
// Start up the action movement task. This handles the shuffling
// forward/back/left/right when in melee mode.
pPed->GetPedIntelligence()->AddTaskMovement( rage_new CTaskMoveMeleeMovement( GetTargetEntity(), HasLockOnTargetEntity(), bAllowMovementInput, bAllowStrafing, bAllowHeadingUpdate, bAllowStealth ) );
// Update the pointer to the movement task.
pMovementTask = pPed->GetPedIntelligence()->GetGeneralMovementTask();
}
Assertf( pMovementTask, "pMovementTask was not found for pPed in CTaskMelee::MeleeMoveTaskControl." );
// Mark the movement task as still in use this frame (so it doesn't go away).
CTaskMoveInterface* pMoveInterface = pMovementTask->GetMoveInterface();
Assert( pMoveInterface );
pMoveInterface->SetCheckedThisFrame( true );
#if !__FINAL
pMoveInterface->SetOwner( this );
#endif
if( pMovementTask->GetTaskType() == CTaskTypes::TASK_MOVE_MELEE_MOVEMENT )
{
CTaskMoveMeleeMovement* pMeleeMoveTask = static_cast<CTaskMoveMeleeMovement*>( pMovementTask );
// Set whether or not to allow SimpleMeleeActionResults to use the movement
// input values or not.
pMeleeMoveTask->SetAllowMovementInput( bAllowMovementInput );
pMeleeMoveTask->SetAllowStrafing( bAllowStrafing );
pMeleeMoveTask->SetAllowHeadingUpdate( bAllowHeadingUpdate );
pMeleeMoveTask->SetAllowStealth( bAllowStealth );
bool bForcingRun = true;
if(pPed->IsLocalPlayer() && IsCurrentlyDoingAMove(false, false, false))
{
//! If we are performing a move & don't have sprint/run held, then don't force run.
const CControl* pControl = pPed->GetControlFromPlayer();
if(pControl && !pControl->GetPedSprintIsDown())
{
bForcingRun = false;
}
}
pMeleeMoveTask->SetForceRun( bForcingRun );
// Make sure the movement task has the same target as the melee task.
CEntity* pTargetEntity = GetTargetEntity();
pMeleeMoveTask->SetTargetEntity( pTargetEntity, HasLockOnTargetEntity() );
// Determine what range we would like to be at
float fDesiredSelfToTargetRange = sm_fAiTargetSeparationDesiredSelfToTargetRange;
if( IsDoingQueueForTarget() )
{
fDesiredSelfToTargetRange = GetDesiredTargetDistance();
}
// Check to see if the target ped is getting up and have them keep their distance to avoid clipping
else if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
if( pTargetPed->GetPedIntelligence() && pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_NM_CONTROL ) )
{
fDesiredSelfToTargetRange = sm_fAiTargetSeparationDesiredSelfToGetupTargetRange;
}
}
// Set our desired range and impetus control vars.
pMeleeMoveTask->SetTargetCurrentDesiredRange( fDesiredSelfToTargetRange );
pMeleeMoveTask->SetTargetCurrentMinRangeForImpetus( IsDoingQueueForTarget() ? sm_fAiQueuePrefdPosDiffMinForImpetus : sm_fAiTargetSeparationPrefdDiffMinForImpetus );
pMeleeMoveTask->SetTargetCurrentMaxRangeForImpetus( IsDoingQueueForTarget() ? sm_fAiQueuePrefdPosDiffMaxForImpetus : sm_fAiTargetSeparationPrefdDiffMaxForImpetus );
// Make sure to avoid any other attackers.
if( pAvoidEntity )
pMeleeMoveTask->SetAvoidEntity( pAvoidEntity );
}
else
pPed->RestoreHeadingChangeRate();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessLineOfSightTest
// PURPOSE : Do tests to see if we should be seeking or to update our seeking
// los tests.
// PARAMETERS : The ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ProcessLineOfSightTest( CPed* pPed )
{
Assertf( pPed, "pPed is null in CTaskMelee::ProcessLineOfSightTest." );
CEntity* pTargetEntity = GetTargetEntity();
if( !pTargetEntity )
return;
if( !m_bSeekModeLosTestHasBeenRequested )
{
// JB : I added this task-timer so that navmesh requests aren't being issued every frame, for every ped in melee combat.
// This task can also be recreated every frame, so need an initial delay time to be set also in CreateFirstSubTask
if( IsLosBlocked() || !m_seekModeLosTestNextRequestTimer.IsSet() || m_seekModeLosTestNextRequestTimer.IsOutOfTime() )
{
CancelPendingLosRequest(); // (Shouldn't be required really)
Vec3V vEntityCentre = pPed->GetTransform().GetPosition();
Vec3V vTargetCentre = pTargetEntity->GetTransform().GetPosition();
// Check to see if this ped is using a prop with the scenario
static const s32 iNumExceptions = 2;
const CEntity* ppExcludeEnts[iNumExceptions] = { pTargetEntity, NULL };
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
CTask* pPedScenarioTask = pTargetPed->GetPedIntelligence() ? pTargetPed->GetPedIntelligence()->FindTaskByType( CTaskTypes::TASK_USE_SCENARIO ) : NULL;
if( pPedScenarioTask )
ppExcludeEnts[1] = static_cast<CTaskScenario*>(pPedScenarioTask)->GetScenarioEntity();
}
// Do a high line of site test.
float fTimeToReachEntity = 0.0f;
int nBoneIdxCache = -1;
Vec3V vStartChestApprox( vEntityCentre );
Vec3V vStartChestDirection( V_ZERO );
Vec3V vEndChestApprox( vTargetCentre );
Vec3V vEndChestDirection( V_ZERO );
if( pPed->GetPedType() == PEDTYPE_ANIMAL )
{
CActionManager::TargetTypeGetPosDirFromEntity( vStartChestApprox, vStartChestDirection, CActionFlags::TT_ENTITY_ROOT, pPed, pPed, vEntityCentre, fTimeToReachEntity, nBoneIdxCache );
nBoneIdxCache = -1;
CActionManager::TargetTypeGetPosDirFromEntity( vEndChestApprox, vEndChestDirection, CActionFlags::TT_ENTITY_ROOT, pTargetEntity, pPed, vEntityCentre, fTimeToReachEntity, nBoneIdxCache );
}
else
{
vStartChestApprox.SetZf( vEntityCentre.GetZf() + 0.5f );
CActionManager::TargetTypeGetPosDirFromEntity( vStartChestApprox, vStartChestDirection, CActionFlags::TT_PED_CHEST, pPed, pPed, vEntityCentre, fTimeToReachEntity, nBoneIdxCache );
vEndChestApprox.SetZf( vTargetCentre.GetZf() + 0.5f );
nBoneIdxCache = -1;
CActionManager::TargetTypeGetPosDirFromEntity( vEndChestApprox, vEndChestDirection, CActionFlags::TT_PED_CHEST, pTargetEntity, pPed, vEntityCentre, fTimeToReachEntity, nBoneIdxCache );
}
// Slightly scale the start/end positions to compensate for the ped capsule
const float fCapsuleRadius = pPed->GetCapsuleInfo() ? pPed->GetCapsuleInfo()->GetProbeRadius() : CPedModelInfo::ms_fPedRadius;
vStartChestApprox += Scale( vEndChestDirection, ScalarV( fCapsuleRadius ) );
vEndChestApprox += Scale( Negate( vEndChestDirection ), ScalarV( fCapsuleRadius ) );
WorldProbe::CShapeTestCapsuleDesc probeData1;
probeData1.SetResultsStructure( &m_seekModeLosTestHandle1 );
probeData1.SetCapsule( VEC3V_TO_VECTOR3( vStartChestApprox ), VEC3V_TO_VECTOR3( vEndChestApprox ), fCapsuleRadius );
probeData1.SetContext( WorldProbe::EMelee );
probeData1.SetIncludeFlags( ArchetypeFlags::GTA_MELEE_VISIBILTY_TYPES );
probeData1.SetIsDirected( true );
probeData1.SetExcludeEntities( ppExcludeEnts, iNumExceptions );
WorldProbe::GetShapeTestManager()->SubmitTest( probeData1, WorldProbe::PERFORM_ASYNCHRONOUS_TEST );
if( pPed->GetPedType() != PEDTYPE_ANIMAL )
{
// Do a low line of site test.
nBoneIdxCache = -1;
Vec3V vStartKneeApprox( vEntityCentre.GetXf(), vEntityCentre.GetYf(), vEntityCentre.GetZf() - 0.5f );
Vec3V vStartKneeDriection( VEC3_ZERO );
CActionManager::TargetTypeGetPosDirFromEntity( vStartKneeApprox, vStartKneeDriection, CActionFlags::TT_PED_KNEE_RIGHT, pPed, pPed, vEntityCentre, fTimeToReachEntity, nBoneIdxCache );
nBoneIdxCache = -1;
Vec3V vEndKneeApprox( vTargetCentre.GetXf(), vTargetCentre.GetYf(), vTargetCentre.GetZf() - 0.5f );
Vec3V vEndKneeDirection( VEC3_ZERO );
CActionManager::TargetTypeGetPosDirFromEntity( vEndKneeApprox, vEndKneeDirection, CActionFlags::TT_PED_KNEE_RIGHT, pTargetEntity, pPed, vEntityCentre, fTimeToReachEntity, nBoneIdxCache );
// Slightly scale the start/end positions to compensate for the ped capsule
vStartKneeApprox += Scale( vEndKneeDirection, ScalarV( fCapsuleRadius ) );
vEndKneeApprox += Scale( Negate( vEndKneeDirection ), ScalarV( fCapsuleRadius ) );
WorldProbe::CShapeTestCapsuleDesc probeData2;
probeData2.SetResultsStructure( &m_seekModeLosTestHandle2 );
probeData2.SetCapsule( VEC3V_TO_VECTOR3( vStartKneeApprox ), VEC3V_TO_VECTOR3( vEndKneeApprox ), fCapsuleRadius );
probeData2.SetContext( WorldProbe::EMelee );
probeData2.SetIncludeFlags( ArchetypeFlags::GTA_MELEE_VISIBILTY_TYPES );
probeData2.SetIsDirected( true );
probeData2.SetExcludeEntities( ppExcludeEnts, iNumExceptions );
WorldProbe::GetShapeTestManager()->SubmitTest( probeData2, WorldProbe::PERFORM_ASYNCHRONOUS_TEST );
}
// Set our tracking flag.
m_bSeekModeLosTestHasBeenRequested = true;
}
}
if( m_bSeekModeLosTestHasBeenRequested )
{
bool bLosReady = false;
if( pPed->GetPedType() == PEDTYPE_ANIMAL )
bLosReady = !m_seekModeLosTestHandle1.GetWaitingOnResults();
else
bLosReady = !m_seekModeLosTestHandle1.GetWaitingOnResults() && !m_seekModeLosTestHandle2.GetWaitingOnResults();
if( bLosReady )
{
m_seekModeLosTestNextRequestTimer.Set( fwTimer::GetTimeInMilliseconds(), sm_nAiLosTestFreqInMs );
m_bSeekModeLosTestHasBeenRequested = false;
bool bHit1 = false;
if( m_seekModeLosTestHandle1.GetResultsReady() )
{
bHit1 = m_seekModeLosTestHandle1.GetNumHits() > 0u;
m_seekModeLosTestHandle1.Reset();
}
bool bHit2 = false;
if( m_seekModeLosTestHandle2.GetResultsReady() )
{
bHit2 = m_seekModeLosTestHandle2.GetNumHits() > 0u;
m_seekModeLosTestHandle2.Reset();
}
bool bPreviousValue = m_bLosBlocked;
m_bLosBlocked = bHit1 || bHit2;
if( m_bLosBlocked != bPreviousValue )
CalculateDesiredTargetDistance();
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessUnreachableNavMeshTest
// PURPOSE : Heartbeat for the unreachable nav mesh queries
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ProcessUnreachableNavMeshTest( CPed* pPed, CEntity* pTargetEntity )
{
static dev_float sfArriveRadius = 2.0f;
if( !m_RouteSearchHelper.IsSearchActive() )
{
if( pPed && pTargetEntity )
{
u32 iPathFlags = PATH_FLAG_NEVER_DROP_FROM_HEIGHT|PATH_FLAG_DONT_LIMIT_SEARCH_EXTENTS|PATH_FLAG_DONT_AVOID_DYNAMIC_OBJECTS|
PATH_FLAG_NEVER_ENTER_WATER|PATH_FLAG_NEVER_START_IN_WATER|PATH_FLAG_AVOID_POTENTIAL_EXPLOSIONS;
// Find the radius that we want to use
float fTargetRadius = sfArriveRadius;
if( IsLosBlocked() )
{
fTargetRadius = sm_fAiSeekModeActiveCombatantRadius;
}
else
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if(pBrawlData)
{
fTargetRadius = pBrawlData->GetTargetRadius();
}
}
m_RouteSearchHelper.StartSearch( pPed, VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ), VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() ), fTargetRadius, iPathFlags );
taskAssertf( m_RouteSearchHelper.IsSearchActive(), "CTaskMelee::ProcessUnreachableNavMeshTest - Inactive route search helper" );
}
}
else
{
float fDistance = 0.0f;
Vector3 vLastPoint(0.0f, 0.0f, 0.0f);
SearchStatus eSearchStatus;
// get our search results to get the status, total distance and last point
if( m_RouteSearchHelper.GetSearchResults( eSearchStatus, fDistance, vLastPoint ) )
{
if( eSearchStatus == SS_SearchSuccessful )
ClearTaskFlag( MF_CannotFindNavRoute );
else
SetTaskFlag( MF_CannotFindNavRoute );
// Clear its active status
m_RouteSearchHelper.ResetSearch();
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckDesiredSubtaskAndUpdateBreakoutConditions
// PURPOSE : Update and determine if we should still be running the current
// subtask, another, or quiting.
// PARAMETERS : pPed - the ped we are interested in.
// RETURNS : What the ped should be doing.
/////////////////////////////////////////////////////////////////////////////////
CTaskMelee::MeleeState CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions( CPed* pPed )
{
Assertf( pPed, "pPed is null in CTaskMelee::CheckDesiredSubtaskAndUpdateBreakoutConditions." );
Assertf( !pPed->IsNetworkClone(), "GetDesiredStateAndUpdateBreakoutConditions does not support clone peds!" );
// Check if someone has already requested a "stop all" or is in water
if( m_bStopAll )
{
m_bStopAll = false;
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: m_bStopAll");
return State_Finish;
}
const CPedWeaponManager* pPedWeaponManager = pPed->GetWeaponManager();
const CObject* pHeldWeaponObject = pPedWeaponManager ? pPedWeaponManager->GetEquippedWeaponObject() : NULL;
const CWeapon* pHeldWeapon = pHeldWeaponObject ? pHeldWeaponObject->GetWeapon() : NULL;
if( !pHeldWeapon )
pHeldWeapon = CWeaponManager::GetWeaponUnarmed( pPed->GetDefaultUnarmedWeaponHash() );
const CWeaponInfo* pHeldWeaponInfo = pHeldWeapon ? pHeldWeapon->GetWeaponInfo() : NULL;
u32 eCurrentState = GetState();
CEntity* pTargetEntity = GetTargetEntity();
if( !GetIsTaskFlagSet( MF_ShouldBeInMeleeMode ) )
{
if( !GetIsTaskFlagSet( MF_AttemptIdleControllerInterrupt ) )
{
if( pHeldWeaponInfo && pHeldWeaponInfo->GetIsMelee() )
{
CTaskMotionBase* pPrimaryTask = pPed->GetPrimaryMotionTask();
if( pPrimaryTask && pPrimaryTask->GetTaskType() == CTaskTypes::TASK_MOTION_PED )
{
// Grab the current weapon info and see if we should allow a strafe to on foot transition
CTaskMotionPed* pTask = static_cast<CTaskMotionPed*>(pPrimaryTask);
Assert( pTask );
pTask->SetTaskFlag( CTaskMotionPed::PMF_PerformStrafeToOnFootTransition );
}
}
}
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: !MF_ShouldBeInMeleeMode");
return State_Finish;
}
bool bLocalPlayerInUI = pPed->IsLocalPlayer() && CNewHud::IsShowingHUDMenu();
if( m_pNextActionResult)
{
bool bOffensiveMoveInUI = m_pNextActionResult->IsOffensiveMove() && bLocalPlayerInUI;
if(!bOffensiveMoveInUI)
{
return State_MeleeAction;
}
}
CTask* pSubTask = GetSubTask();
bool bRunningAction = pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT;
CTaskMeleeActionResult* pActionResultTask = NULL;
if( bRunningAction )
{
pActionResultTask = static_cast<CTaskMeleeActionResult*>( pSubTask );
if (pSubTask->GetIsFlagSet(aiTaskFlags::HasBegun))
{
// Check if we should break out due to swimming
if( pActionResultTask->ShouldInterruptWhenOutOfWater() )
{
if( !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_SwimmingTasksRunning ) && !pPed->GetIsFPSSwimming() )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: !CPED_CONFIG_FLAG_SwimmingTasksRunning");
return State_Finish;
}
}
else if( pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_SwimmingTasksRunning ) || pPed->GetIsFPSSwimming() )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: CPED_CONFIG_FLAG_SwimmingTasksRunning");
return State_Finish;
}
}
}
// If we are not running an action or should allow an interrupt
if( !bLocalPlayerInUI && (!bRunningAction || ( pActionResultTask && pActionResultTask->ShouldAllowInterrupt() ) ))
{
const CPedEquippedWeapon* pEquippedWeapon = pPedWeaponManager ? pPedWeaponManager->GetPedEquippedWeapon() : NULL;
if( !pPed->IsNetworkClone() && pEquippedWeapon && pPedWeaponManager->GetRequiresWeaponSwitch() )
{
const CWeaponInfo* pEquippedWeaponInfo = pEquippedWeapon->GetWeaponInfo();
// Only attempt to switch if the weapon anims are streamed in
if( pEquippedWeaponInfo && pEquippedWeapon->AreWeaponAnimsStreamedIn() && pEquippedWeaponInfo->GetIsMelee() )
{
return State_SwapWeapon;
}
}
}
if( pPed->IsPlayer() )
{
// We allow hot swapping during a melee move
if( !pPed->IsAPlayerPed() )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: !pPed->IsAPlayerPed()");
return State_Finish;
}
const bool bIsCurrentlyDoingMove = IsCurrentlyDoingAMove( false, false, true );
if( !bIsCurrentlyDoingMove && !pHeldWeaponInfo )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: !bIsCurrentlyDoingMove && !pHeldWeaponInfo");
return State_Finish;
}
if( !bIsCurrentlyDoingMove )
{
// Check if they are locked onto a target
if( HasLockOnTargetEntity() )
{
Vector3 vPlayerToTarget = VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition() );
if( vPlayerToTarget.Mag2() > rage::square( pHeldWeaponInfo->GetLockOnRange() ) )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: vPlayerToTarget.Mag2() (%.2f) > rage::square( pHeldWeaponInfo->GetLockOnRange() ) (%.2f)", vPlayerToTarget.Mag2(), rage::square( pHeldWeaponInfo->GetLockOnRange() ));
return State_Finish;
}
}
// Special case where we are forcing strafe but still want them to be able to run away
else if( GetIsTaskFlagSet( MF_AttemptRunControllerInterrupt ) || GetIsTaskFlagSet( MF_AttemptVehicleEnterInterrupt ) )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: GetIsTaskFlagSet( MF_AttemptRunControllerInterrupt ) (%d) || GetIsTaskFlagSet( MF_AttemptVehicleEnterInterrupt ) (%d)", GetIsTaskFlagSet( MF_AttemptRunControllerInterrupt ), GetIsTaskFlagSet( MF_AttemptVehicleEnterInterrupt ));
return State_Finish;
}
}
// check to see if we should interrupt the current move and go back to player movement
if( pActionResultTask && pActionResultTask->ShouldAllowInterrupt() )
{
// Some moves allow aim interrupt (like pistol whips)
const bool bAimInterupt = pActionResultTask->ShouldAllowAimInterrupt() && ( CPlayerInfo::IsAiming() || CPlayerInfo::IsFiring_s() ) && !pHeldWeapon->GetHasFirstPersonScope();
if( bAimInterupt ||
GetIsTaskFlagSet( MF_AttemptRunControllerInterrupt ) ||
GetIsTaskFlagSet( MF_AttemptVehicleEnterInterrupt ) ||
GetIsTaskFlagSet( MF_AttemptDiveInterrupt ) ||
GetIsTaskFlagSet( MF_AttemptCoverEnterInterrupt ) )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: bAimInterupt %d, MF_AttemptRunControllerInterrupt %d, MF_AttemptVehicleEnterInterrupt %d, MF_AttemptDiveInterrupt %d, MF_AttemptCoverEnterInterrupt %d", bAimInterupt, GetIsTaskFlagSet( MF_AttemptRunControllerInterrupt ), GetIsTaskFlagSet( MF_AttemptVehicleEnterInterrupt ), GetIsTaskFlagSet( MF_AttemptDiveInterrupt ), GetIsTaskFlagSet( MF_AttemptCoverEnterInterrupt ));
return State_Finish;
}
#if FPS_MODE_SUPPORTED
bool bIsFPSMode = pPed->IsFirstPersonShooterModeEnabledForPlayer( false );
const crClip* pClip = pActionResultTask->GetClip();
#endif
// Check to see if we have a target and only interrupt if player is clearly trying to move the other direction
if( GetIsTaskFlagSet( MF_AttemptMeleeControllerInterrupt ) )
{
bool bAllowInterrupt = true;
#if FPS_MODE_SUPPORTED
if( bIsFPSMode && pActionResultTask->IsDoingReaction() && pClip )
{
CTaskMotionBase* pTaskMotionBase = pPed->GetCurrentMotionTask( false );
if( pTaskMotionBase )
{
TUNE_GROUP_FLOAT(CAM_FPS, fMeleeReactionInterruptPhase, 0.75f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(CAM_FPS, fMeleeReactionInterruptHeading, 30.0f, 0.0f, 180.0f, 1.0f);
float fCurrentPhase = pClip->ConvertTimeToPhase(pActionResultTask->GetCurrentClipTime());
float fDesiredDirection = pTaskMotionBase->CalcDesiredDirection();
// If we haven't yet reached our reaction interrupt phase and haven't yet reached our desired direction...
if( fCurrentPhase < fMeleeReactionInterruptPhase && Abs( fDesiredDirection ) > (fMeleeReactionInterruptHeading * DtoR) )
{
Quaternion qRotationDiff = fwAnimHelpers::GetMoverTrackRotationDiff( *pClip, fCurrentPhase, fMeleeReactionInterruptPhase );
float fHeadingChangeRemaining = qRotationDiff.TwistAngle( ZAXIS );
// If there is still a decent amount of remaining animated rotation before our interrupt phase then don't allow the interrupt
if( Abs( fHeadingChangeRemaining ) > ( fMeleeReactionInterruptHeading * DtoR ) )
{
bAllowInterrupt = false;
}
}
}
}
#endif
if( HasLockOnTargetEntity() )
{
if( GetIsTaskFlagSet( MF_QuitTaskAfterNextAction ) )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: MF_QuitTaskAfterNextAction");
return State_Finish;
}
return State_WaitForMeleeAction;
}
if( GetIsTaskFlagSet( MF_ReactionResetTimeInTaskAfterNextAction ) || GetIsTaskFlagSet( MF_AttackResetTimeInTaskAfterNextAction ) )
return State_WaitForMeleeAction;
else if( bAllowInterrupt )
{
taskDebugf1("Quit: CTaskMelee::GetDesiredStateAndUpdateBreakoutConditions: MF_ReactionResetTimeInTaskAfterNextAction %d, MF_AttackResetTimeInTaskAfterNextAction %d", GetIsTaskFlagSet( MF_ReactionResetTimeInTaskAfterNextAction ), GetIsTaskFlagSet( MF_AttackResetTimeInTaskAfterNextAction ));
return State_Finish;
}
}
#if FPS_MODE_SUPPORTED
if( bIsFPSMode && !IsBlocking() && !IsUsingStealthClips() && !pActionResultTask->IsDoingReaction() )
{
if( pClip )
{
const crTag* pTag = CClipEventTags::FindLastEventTag(pClip, CClipEventTags::Interruptible);
TUNE_GROUP_FLOAT(CAM_FPS, fMeleeInterruptTime, 0.3f, 0.0f, 5.0f, 0.05f);
// Allow some time for combos
if( pTag && (pClip->ConvertPhaseToTime(pTag->GetStart()) + fMeleeInterruptTime) < pActionResultTask->GetCurrentClipTime() )
{
return State_Finish;
}
}
}
#endif
}
}
else
{
// Note: This is really one of the major parts of our "Melee Combat AI"...
// Search for the quoted term in the line above to find the others.
if( eCurrentState == State_WaitForTarget || eCurrentState == State_LookAtTarget )
return State_Invalid;
if( IsCurrentlyDoingAMove( GetIsTaskFlagSet( MF_PlayTauntBeforeAttacking ) || ShouldBeInInactiveTauntMode(), GetIsTaskFlagSet( MF_PerformIntroAnim ), !GetIsTaskFlagSet( MF_QuitTaskAfterNextAction ) ) )
return State_Invalid;
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
// Possible filter the seek/flee desires if the peds movement is constrained.
QueueType queueType = GetQueueType();
if( queueType <= QT_InactiveObserver && eCurrentState != State_InactiveObserverIdle )
return State_InactiveObserverIdle;
bool bCanSeek = queueType > QT_InactiveObserver && !GetIsTaskFlagSet( MF_ForceStrafe ) && !GetIsTaskFlagSet( MF_PlayTauntBeforeAttacking );
// Don't allow any seek/flee when ped is out side their defensive area
const CDefensiveArea* pDefensiveArea = pPed->GetPedIntelligence() ? pPed->GetPedIntelligence()->GetDefensiveArea() : NULL;
if( pDefensiveArea && pDefensiveArea->IsActive() && !pDefensiveArea->IsPointInDefensiveArea( VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() ) ) )
bCanSeek = false;
// Don't allow any seek/flee when ped is stationary
if( pPed->GetPedIntelligence()->GetCombatBehaviour().GetCombatMovement() == CCombatData::CM_Stationary )
bCanSeek = false;
// Check to see if we should attempt
if( bCanSeek )
{
Vec3V vDelta = pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition();
if( ShouldBeInInactiveTauntMode() )
{
// check to see if they are entering a vehicle
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
bool bWithinRetreatDistance = false;
const float fDistanceSq = MagSquared( vDelta ).Getf();
if( fDistanceSq > rage::square( sm_fAiSeekModeRetreatDistanceMin ) && fDistanceSq < rage::square( sm_fAiSeekModeRetreatDistanceMax ) )
bWithinRetreatDistance = true;
if( bWithinRetreatDistance || pTargetPed->GetVehiclePedInside() != NULL || ( pTargetPed->GetPedIntelligence() && pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_ENTER_VEHICLE_SEAT ) ) )
return State_WaitForTarget;
else
return State_RetreatToSafeDistance;
}
else if( IsLosBlocked() )
return State_ChaseAfterTarget;
else
{
float fChaseThreshold = GetDesiredTargetDistance() + sm_fAiSeekModeChaseTriggerOffset;
Vec3V vDeltaXY( vDelta.GetXf(), vDelta.GetYf(), 0.0f );
const float fDist2 = MagSquared( vDeltaXY ).Getf();
// First check target height
if( Abs( vDelta.GetZf() ) > sm_fAiSeekModeTargetHeight || fDist2 > rage::square( fChaseThreshold ) )
return State_ChaseAfterTarget;
}
}
}
}
// Let the caller know everything seems fine.
return State_Invalid;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateTargetFlags
// PURPOSE : Update the target flags for other functions to use
// PARAMETERS : pPed - the ped we are interested in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UpdateTargetFlags( CPed* pPed )
{
bool bIsPlayer = pPed->IsPlayer();
// Lock on conditions
bool bHasLockOnTarget = false;
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity && ( !bIsPlayer || m_bHasLockOnTargetEntity ) )
bHasLockOnTarget = true;
if( bHasLockOnTarget )
{
SetTaskFlag( MF_HasLockOnTarget );
ClearTaskFlag( MF_ForceStrafeIndefinitely );
}
else
ClearTaskFlag( MF_HasLockOnTarget );
bool bStrafeTimer = m_forceStrafeTimer.IsEnabled() && !m_forceStrafeTimer.IsOutOfTime() FPS_MODE_SUPPORTED_ONLY(&& !pPed->IsFirstPersonShooterModeEnabledForPlayer(false));
// Purposely do not check whether the timer is set
if( GetIsTaskFlagSet( MF_AnimSynced ) || GetIsTaskFlagSet( MF_ForceStrafeIndefinitely ) || pPed->GetPedResetFlag( CPED_RESET_FLAG_ForcePedToStrafe ) || ( bStrafeTimer ) )
SetTaskFlag( MF_ForceStrafe );
else
ClearTaskFlag( MF_ForceStrafe );
#if FPS_MODE_SUPPORTED
if(pPed->IsFirstPersonShooterModeEnabledForPlayer(false) && bHasLockOnTarget)
{
SetTaskFlag( MF_ForceStrafe );
}
#endif
// Should we force strafe based on movement and target distance
Vec3V vPedToTarget( VEC3_ZERO );
ScalarV scDistToTargetSq( 0.0f );
if( pTargetEntity )
{
vPedToTarget = pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition();
scDistToTargetSq = DistSquared( pTargetEntity->GetTransform().GetPosition(), pPed->GetTransform().GetPosition() );
}
// Is ped currently running or going to perform a move?
const bool bIsCurrentlyDoingAMove = m_pNextActionResult || IsCurrentlyDoingAMove( false );
// Determine whether or not the player is attempting to move in a direction opposite to the peds forward vector
if( pPed->IsLocalPlayer() )
{
bool bAttemptIdleControllerInterrupt = false;
bool bAttemptRunControllerInterrupt = false;
bool bAttemptMeleeControllerInterrupt = false;
bool bAttemptVehicleEnterInterrupt = false;
bool bAttemptCoverEnterInterrupt = false;
bool bAttemptDiveInterrupt = false;
const CControl* pControl = pPed->GetControlFromPlayer();
Assert( pControl );
const Vector2 vStickInput( pControl->GetPedWalkLeftRight().GetNorm(), -pControl->GetPedWalkUpDown().GetNorm() );
const float fInputMag = vStickInput.Mag2();
// Check vehicle enter interrupt
if( pControl->GetPedEnter().IsPressed() )
{
CVehicle * pTargetVehicle = CPlayerInfo::ScanForVehicleToEnter( pPed, VEC3V_TO_VECTOR3( pPed->GetTransform().GetB() ), false );
if( pTargetVehicle )
bAttemptVehicleEnterInterrupt = true;
}
if( bAttemptVehicleEnterInterrupt )
SetTaskFlag( MF_AttemptVehicleEnterInterrupt );
else
ClearTaskFlag( MF_AttemptVehicleEnterInterrupt );
// Check cover enter interrupt
if( pControl->GetPedCover().IsPressed() )
{
CTaskPlayerOnFoot* pTaskPlayerOnFoot = static_cast<CTaskPlayerOnFoot*>( pPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_PLAYER_ON_FOOT ) );
if( pTaskPlayerOnFoot && pTaskPlayerOnFoot->CheckForNearbyCover( pPed ) )
bAttemptCoverEnterInterrupt = true;
}
if( bAttemptCoverEnterInterrupt )
SetTaskFlag( MF_AttemptCoverEnterInterrupt );
else
ClearTaskFlag( MF_AttemptCoverEnterInterrupt );
// Check dive interrupt
if( pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning) && pControl->GetPedDive().IsPressed() )
bAttemptDiveInterrupt = true;
if( bAttemptDiveInterrupt )
SetTaskFlag( MF_AttemptDiveInterrupt );
else
ClearTaskFlag( MF_AttemptDiveInterrupt );
static dev_float s_fMinInterruptStickValueSq = 0.5f;
if( fInputMag > s_fMinInterruptStickValueSq )
{
bAttemptIdleControllerInterrupt = true;
bool bAllowRunInterrupt = !bHasLockOnTarget;
if (NetworkInterface::IsInCopsAndCrooks() && pTargetEntity && pTargetEntity->GetIsTypePed())
{
const CPed* pTargetPed = static_cast<const CPed*>(pTargetEntity);
if (pTargetPed->GetUsingRagdoll())
{
bAllowRunInterrupt = true;
}
}
bAttemptRunControllerInterrupt = bAllowRunInterrupt && (pControl->GetPedSprintIsDown( ) || pControl->GetPedSprintHistoryIsPressed( 300 ));
// Do not allow the movement interrupt unless we no longer have a pending impulse or the last impulse was recorded some time ago
if( !m_impulseCombo.HasPendingImpulse() || ( m_impulseCombo.GetLatestRecordedImpulseTime() + CTaskMelee::sm_nPlayerImpulseInterruptDelayInMs ) <= fwTimer::GetTimeInMilliseconds() )
bAttemptMeleeControllerInterrupt = GetSubTask() && GetSubTask()->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT && !static_cast<CTaskMeleeActionResult*>(GetSubTask())->IsUpperbodyOnly();
}
#if FPS_MODE_SUPPORTED
m_bInterruptForFirstPersonCamera = false;
TUNE_GROUP_FLOAT(CAM_FPS, c_FirstPersonTurnThresholdInterrupt, 10.0f, 0.0f, 45.0f, 0.05f);
const camFirstPersonShooterCamera* fpsCamera = camInterface::GetGameplayDirector().GetFirstPersonShooterCamera();
if( pPed->IsFirstPersonShooterModeEnabledForPlayer(false) &&
fpsCamera != NULL &&
Abs(fpsCamera->GetRelativeHeading(true)) > c_FirstPersonTurnThresholdInterrupt*DtoR )
{
m_bInterruptForFirstPersonCamera = true;
}
#endif
// Special case code when we are within 4m
if( !bIsCurrentlyDoingAMove )
{
float fDistToTarget = MagSquared( vPedToTarget ).Getf();
if( fDistToTarget < rage::square( sm_fForcedStrafeRadius ) )
{
// Stop the force strafe timer while we are strafing
if( bAttemptIdleControllerInterrupt )
{
if( m_forceStrafeTimer.IsEnabled() && !m_forceStrafeTimer.IsPaused() )
m_forceStrafeTimer.Pause();
}
// If we previously stopped the strafe timer then reset to minimum time
else if( m_forceStrafeTimer.IsEnabled() && m_forceStrafeTimer.IsPaused() )
m_forceStrafeTimer.Set( fwTimer::GetTimeInMilliseconds(), sm_nTimeInTaskAfterStardardAttack );
}
else
{
if( m_forceStrafeTimer.IsEnabled() && m_forceStrafeTimer.IsPaused() )
m_forceStrafeTimer.Set( fwTimer::GetTimeInMilliseconds(), 0 );
ClearTaskFlag( MF_ForceStrafeIndefinitely );
}
}
if( bAttemptIdleControllerInterrupt )
SetTaskFlag( MF_AttemptIdleControllerInterrupt );
else
ClearTaskFlag( MF_AttemptIdleControllerInterrupt );
if( bAttemptRunControllerInterrupt )
SetTaskFlag( MF_AttemptRunControllerInterrupt );
else
ClearTaskFlag( MF_AttemptRunControllerInterrupt );
if( bAttemptMeleeControllerInterrupt )
SetTaskFlag( MF_AttemptMeleeControllerInterrupt );
else
ClearTaskFlag( MF_AttemptMeleeControllerInterrupt );
}
bool bShouldResetTimeInTask = false;
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
bool bTargetIsUnreachable = false;
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
if( !pPed->IsLocalPlayer() )
{
static dev_float sfPhysicalHeightCutoff = -1.15f;
bool bOnTopOfUnreachablePhysical = false;
CPhysical* pPhysical = pTargetPed->GetGroundPhysical();
if( pPhysical )
{
if( pPhysical->GetIsTypeVehicle() )
bOnTopOfUnreachablePhysical = true;
else
{
// Check to see if this is an object and is unreachable
bool bIsUnreachableByMeleeNavigation = false;
if( pPhysical->GetIsTypeObject() )
{
const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry( pPhysical->GetBaseModelInfo()->GetModelNameHash() );
if( pTuning )
bIsUnreachableByMeleeNavigation = pTuning->GetIsUnreachableByMeleeNavigation();
}
// Is this a hard coded object that is unreachable?
if( bIsUnreachableByMeleeNavigation )
bOnTopOfUnreachablePhysical = true;
// Otherwise test the physical position difference
else
{
Vec3V vPositionDiff = pPed->GetTransform().GetPosition() - pTargetPed->GetTransform().GetPosition();
if( vPositionDiff.GetZf() < sfPhysicalHeightCutoff )
bOnTopOfUnreachablePhysical = true;
}
}
}
bTargetIsUnreachable = bOnTopOfUnreachablePhysical ||
pTargetPed->GetIsSwimming() ||
pTargetPed->GetPedResetFlag( CPED_RESET_FLAG_IsDiving ) ||
pTargetPed->GetPedResetFlag( CPED_RESET_FLAG_IsVaulting );
bTargetIsUnreachable |= ( pTargetPed->GetPlayerWanted() && pTargetPed->GetPlayerWanted()->m_EverybodyBackOff );
// in or entering vehicle
bTargetIsUnreachable |= ( pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_ENTER_VEHICLE_SEAT ) || pTargetPed->GetVehiclePedInside() != NULL );
// on a ladder
bTargetIsUnreachable |= pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_CLIMB_LADDER );
}
if( bTargetIsUnreachable )
{
SetTaskFlag( MF_TargetIsUnreachable );
pPed->SetPedResetFlag( CPED_RESET_FLAG_IsMeleeTargetUnreachable, true );
}
else
{
// If we were previously unreachable then lets reset the seek probe to happen now
if( GetIsTaskFlagSet( MF_TargetIsUnreachable ) )
m_seekModeLosTestNextRequestTimer.ChangeDuration( 0 );
ClearTaskFlag( MF_TargetIsUnreachable );
}
// Let the movement task know what the status of the target is
CTask* pMovementTask = pPed ? pPed->GetPedIntelligence()->GetGeneralMovementTask() : NULL;
if( pMovementTask && pMovementTask->GetTaskType() == CTaskTypes::TASK_MOVE_MELEE_MOVEMENT )
{
CTaskMoveMeleeMovement* pMeleeMoveTask = static_cast<CTaskMoveMeleeMovement*>( pMovementTask );
pMeleeMoveTask->SetAllowMovement( !ShouldBeInInactiveTauntMode() && !GetIsTaskFlagSet( MF_PlayTauntBeforeAttacking ) );
pMeleeMoveTask->SetMoveWhenTargetEnteringVehicle(true);
if( !bIsPlayer && pTargetPed->GetIsEnteringVehicle() )
{
ScalarV scMaxDistToStopMovementSq = LoadScalar32IntoScalarV( square( GetDesiredTargetDistance() ) );
if( IsLessThanOrEqualAll( scDistToTargetSq, scMaxDistToStopMovementSq ) )
pMeleeMoveTask->SetMoveWhenTargetEnteringVehicle(false);
}
}
bool bShouldAllowStrafeMode = false;
bool bShouldAllowStealthMode = false;
if( bIsPlayer )
{
bool bTargetIsInCombat = pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_COMBAT );
// Is the player behind the target ped?
vPedToTarget = Normalize( vPedToTarget );
float fPlayerToTargetDirection = Dot( vPedToTarget, pTargetEntity->GetTransform().GetB() ).Getf();
if( fPlayerToTargetDirection > CTaskMelee::sm_fFrontTargetDotThreshold )
SetTaskFlag( MF_BehindTargetPed );
else
ClearTaskFlag( MF_BehindTargetPed );
// Reset time in task under specific conditions
bool bDontRaiseFistsFlagSet = pTargetPed->GetPedResetFlag( CPED_RESET_FLAG_DontRaiseFistsWhenLockedOn );
if( !bIsCurrentlyDoingAMove )
{
if( bDontRaiseFistsFlagSet )
bShouldResetTimeInTask = !GetIsTaskFlagSet( MF_ForceStrafe );
else if( bHasLockOnTarget || pTargetPed->IsDead() )
bShouldResetTimeInTask = true;
}
// Conditions for allowing strafe mode
if( bTargetIsInCombat )
bShouldAllowStrafeMode = true;
else if( bDontRaiseFistsFlagSet )
{
bShouldAllowStrafeMode = ( pPed->GetMotionData()->GetUsingStealth() && GetIsTaskFlagSet( MF_BehindTargetPed ) ) || GetIsTaskFlagSet( MF_ForceStrafe );
bShouldAllowStealthMode = GetIsTaskFlagSet( MF_BehindTargetPed );
}
else
{
bShouldAllowStealthMode = GetIsTaskFlagSet( MF_BehindTargetPed );
// If the player is walking away from you and is moving then we have special conditions
float fTargetMoveBlendRatioSq = pTargetPed->GetMotionData()->GetCurrentMoveBlendRatio().Mag2();
if( fPlayerToTargetDirection > CTaskMelee::sm_fFrontTargetDotThreshold && fTargetMoveBlendRatioSq > rage::square( CActionManager::ms_fMBRWalkBoundary ) )
{
if ( GetIsTaskFlagSet( MF_AllowStrafeMode ) )
{
if( scDistToTargetSq.Getf() < rage::square( sm_fMovingTargetStrafeBreakoutRadius ) )
bShouldAllowStrafeMode = true;
}
else
{
if( scDistToTargetSq.Getf() < rage::square( sm_fMovingTargetStrafeTriggerRadius ) )
bShouldAllowStrafeMode = true;
}
}
else if( scDistToTargetSq.Getf() < rage::square( sm_fForcedStrafeRadius ) )
bShouldAllowStrafeMode = true;
}
//! Allow targeting to pick a new target if out current target is outside strafe range. This prevent initially locking
//! onto a target that is far away, when another better target is closer.
if( pPed->IsLocalPlayer() && (scDistToTargetSq.Getf() >= rage::square( sm_fForcedStrafeRadius )))
{
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
if(rTargeting.GetLockOnTarget() == pTargetEntity &&
!rTargeting.HasTargetSwitchedToLockOnTarget() &&
rTargeting.GetTimeLockedOn() < sm_fReTargetTimeIfNotStrafing)
{
CEntity *pNewTargetEntity = rTargeting.FindLockOnMeleeTarget(pPed);
if(pNewTargetEntity)
{
ScalarV scDistToNewTargetSq( 0.0f );
scDistToNewTargetSq = DistSquared( pNewTargetEntity->GetTransform().GetPosition(), pPed->GetTransform().GetPosition() );
if(scDistToNewTargetSq.Getf() < rage::square( sm_fForcedStrafeRadius ))
{
SetTargetEntity(pNewTargetEntity, true, true);
}
}
}
}
}
else
bShouldAllowStrafeMode = ( GetQueueType() >= QT_InactiveObserver && GetIsTaskFlagSet( MF_HasPerformedInitialPursuit ) );
if( pTargetPed->IsDead() )
ClearTaskFlag( MF_ForceStrafeIndefinitely );
if ( GetIsTaskFlagSet( MF_ForceStrafe ) || bShouldAllowStrafeMode )
SetTaskFlag( MF_AllowStrafeMode );
else
ClearTaskFlag( MF_AllowStrafeMode );
if ( bShouldAllowStealthMode )
SetTaskFlag( MF_AllowStealthMode );
else
{
// If we were previously allowing stealth mode there are a few cases we want to toggle it off
if( bIsPlayer && !bIsCurrentlyDoingAMove && GetIsTaskFlagSet( MF_AllowStealthMode ) && pPed->GetMotionData()->GetUsingStealth() )
pPed->GetMotionData()->SetUsingStealth( false );
ClearTaskFlag( MF_AllowStealthMode );
}
}
// Only reset the force strafe if it is enabled and has been evaluated to
if( m_forceStrafeTimer.IsEnabled() && bShouldResetTimeInTask )
m_forceStrafeTimer.Disable();
bool bShouldBeInMeleeMode = false;
if( bIsCurrentlyDoingAMove || pPed->IsNetworkClone() || GetIsTaskFlagSet( MF_ForceStrafe ) )
bShouldBeInMeleeMode = true;
else if( bHasLockOnTarget )
{
const CWeaponInfo* pWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
const bool bIsUnarmedOrHoldingMeleeWeapon = (!pWeaponInfo || pWeaponInfo->GetIsMelee() );
if( bIsUnarmedOrHoldingMeleeWeapon )
bShouldBeInMeleeMode = true;
}
if( bShouldBeInMeleeMode )
SetTaskFlag( MF_ShouldBeInMeleeMode );
else
ClearTaskFlag( MF_ShouldBeInMeleeMode );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessPreFSM
// PURPOSE : Runs before the main CTask update loop (UpdateFSM)
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::ProcessPreFSM()
{
CPed *pPed = GetPed(); //Get the ped ptr.
DEV_BREAK_IF_FOCUS( CCombatMeleeDebug::sm_bFocusPedBreakOnTaskCombatMeleeProcessPreFSM, pPed );
// Make any streaming clip requests.
MakeStreamingClipRequests( pPed );
// Make sure the movement system knows we are doing melee.
pPed->SetPedResetFlag( CPED_RESET_FLAG_MoveBlend_bMeleeTaskRunning, true );
pPed->SetPedResetFlag( CPED_RESET_FLAG_EnableSteepSlopePrevention, true );
pPed->SetPedResetFlag( CPED_RESET_FLAG_StreamActionModeAnimsIfDisabled, true );
pPed->SetPedResetFlag( CPED_RESET_FLAG_DisableCellphoneAnimations, true );
pPed->SetPedResetFlag( CPED_RESET_FLAG_BlockSecondaryAnim, true );
if( !pPed->IsNetworkClone() )
UpdateTarget( pPed );
#if __BANK
if(pPed->IsNetworkClone() && CCombatMeleeDebug::sm_bDrawClonePos)
{
//static dev_float s_fZOffset = 1.25f;
static dev_float s_fRadius = 0.1f;
static dev_float s_fArrowSize = 0.5f;
Vector3 vCloneMasterPosition = NetworkInterface::GetLastPosReceivedOverNetwork(pPed);
CCombatMeleeDebug::sm_debugDraw.AddSphere( VECTOR3_TO_VEC3V(vCloneMasterPosition), s_fRadius, Color_green, 1 );
float fCloneHeading = NetworkInterface::GetLastHeadingReceivedOverNetwork(pPed);
Vec3V vDesiredHeading( -rage::Sinf( fCloneHeading ), rage::Cosf( fCloneHeading ), 0.0f );
CCombatMeleeDebug::sm_debugDraw.AddArrow( VECTOR3_TO_VEC3V(vCloneMasterPosition), ( VECTOR3_TO_VEC3V(vCloneMasterPosition) + vDesiredHeading ), s_fArrowSize, Color_green, 1 );
}
#endif
// Update the melee target for the ped.
UpdateTargetFlags( pPed );
// Force the ped to strafe
if( GetIsTaskFlagSet( MF_ForceStrafe ) )
pPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePedToStrafe, true );
// Set this flag every frame when in combat, so the target ped won't get switched to ragdoll by the player bumping into them.
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity && pTargetEntity->GetIsTypePed() && static_cast<CPed*>(pTargetEntity)->GetPedResetFlag( CPED_RESET_FLAG_MoveBlend_bMeleeTaskRunning ) )
static_cast<CPed*>(pTargetEntity)->SetPedResetFlag( CPED_RESET_FLAG_DontActivateRagdollFromAnyPedImpactReset, true );
// Update any ik.
// Set up the spine look IK parameters.
UpdateHeadAndSpineLooksAndLegIK( pPed );
if( pPed->IsPlayer() )
{
const CControl* pControl = pPed->GetControlFromPlayer();
s32 nState = GetState();
// Reset the impulse combo every frame
if( nState == State_WaitForMeleeAction )
ResetImpulseCombo();
if( nState == State_MeleeAction ||
nState == State_WaitForMeleeAction )
{
m_impulseCombo.ScanForImpulses(pControl);
}
}
// AI only pre-process update
else if( !pPed->IsNetworkClone() )
{
// Continually register the opponent in case a slot opens up
RegisterMeleeOpponent( pPed, pTargetEntity );
// Process the LOS tests to make sure we can see the target
QueueType pedQueueType = GetQueueType();
if( pedQueueType > QT_InactiveObserver )
ProcessLineOfSightTest( pPed );
}
return FSM_Continue;
}
void CTaskMelee::ProcessPreRender2()
{
#if FPS_MODE_SUPPORTED
CPed* pPed = GetPed();
if( !pPed )
{
return;
}
if( pPed->IsFirstPersonShooterModeEnabledForPlayer( false ) && IsBlocking() && !fwTimer::IsGamePaused() )
{
const CWeaponInfo* pWeaponInfo = CTaskMotionPed::GetWeaponInfo(pPed);
if( pWeaponInfo && ( !pWeaponInfo->GetBlockLeftHandIKWhileAiming(*pPed) || pPed->GetMotionData()->GetUsingStealth() ) )
{
pPed->ProcessLeftHandGripIk( !pWeaponInfo->GetUseLeftHandIkWhenAiming(), false, CArmIkSolver::GetBlendDuration(ARMIK_BLEND_RATE_FAST), CArmIkSolver::GetBlendDuration(ARMIK_BLEND_RATE_FASTEST) );
}
}
#endif
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateFSM
// PURPOSE : Main heartbeat function for CTask
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::UpdateFSM( const s32 iState, const FSM_Event iEvent )
{
CPed *pPed = GetPed(); //Get the ped ptr.
FSM_Begin
// Start
FSM_State(State_Start)
FSM_OnEnter
Start_OnEnter( pPed );
FSM_OnUpdate
return Start_OnUpdate( pPed );
FSM_OnExit
Start_OnExit();
// Initial pursuit
FSM_State(State_InitialPursuit)
FSM_OnEnter
InitialPursuit_OnEnter( pPed );
FSM_OnUpdate
return InitialPursuit_OnUpdate( pPed );
FSM_OnExit
InitialPursuit_OnExit();
// Look at Target
FSM_State(State_LookAtTarget)
FSM_OnUpdate
return LookAtTarget_OnUpdate( pPed );
FSM_OnExit
LookAtTarget_OnExit();
// Chasing after a target outside melee range.
FSM_State(State_ChaseAfterTarget)
FSM_OnEnter
ChaseAfterTarget_OnEnter( pPed );
FSM_OnUpdate
return ChaseAfterTarget_OnUpdate( pPed );
FSM_OnExit
ChaseAfterTarget_OnExit();
FSM_State(State_RetreatToSafeDistance)
FSM_OnEnter
RetreatToSafeDistance_OnEnter( pPed );
FSM_OnUpdate
return RetreatToSafeDistance_OnUpdate( pPed );
// Waiting for a target to be in a reachable location
FSM_State(State_WaitForTarget)
FSM_OnEnter
WaitForTarget_OnEnter( pPed );
FSM_OnUpdate
return WaitForTarget_OnUpdate( pPed );
FSM_OnExit
WaitForTarget_OnExit();
// Decide whether this should go into player or AI melee action detection
FSM_State(State_WaitForMeleeActionDecide)
FSM_OnEnter
WaitForMeleeActionDecide_OnEnter( pPed );
FSM_OnUpdate
return WaitForMeleeActionDecide_OnUpdate();
// Scanning for a new melee action
FSM_State(State_WaitForMeleeAction)
FSM_OnEnter
WaitForMeleeAction_OnEnter();
FSM_OnUpdate
return WaitForMeleeAction_OnUpdate( pPed );
FSM_OnExit
WaitForMeleeAction_OnExit();
// Main wait state for those that are deemed as an Inactive Observer
FSM_State(State_InactiveObserverIdle)
FSM_OnUpdate
return InactiveObserverIdle_OnUpdate( pPed );
// Running a melee action
FSM_State(State_MeleeAction)
FSM_OnEnter
MeleeAction_OnEnter( pPed );
FSM_OnUpdate
return MeleeAction_OnUpdate( pPed );
FSM_OnExit
MeleeAction_OnExit( pPed );
// Swap weapon
FSM_State(State_SwapWeapon)
FSM_OnEnter
SwapWeapon_OnEnter( pPed );
FSM_OnUpdate
return SwapWeapon_OnUpdate();
// Quit
FSM_State(State_Finish)
FSM_OnUpdate
taskDebugf1("Quit: CTaskMelee::UpdateFSM: State_Finish");
return FSM_Quit;
FSM_End
}
bool CTaskMelee::AddGetUpSets( CNmBlendOutSetList& sets, CPed* pPed )
{
if (!GetEntity())
return false;
return SetupMeleeGetup(sets, pPed, m_pTargetEntity);
}
bool CTaskMelee::SetupMeleeGetup(CNmBlendOutSetList& sets, CPed* pPed, const CEntity* pTarget)
{
if (!pPed || !pTarget)
return false;
CAITarget target(pTarget);
// set up for a melee getup
sets.SetMoveTask(rage_new CTaskMoveFaceTarget(target));
sets.Add(NMBS_MELEE_STANDING);
sets.Add(NMBS_MELEE_GETUPS);
return true;
}
bool CTaskMelee::VelocityHighEnoughForTagSyncBlendOut(const CPed* pPed)
{
static dev_float MIN_VEL = 0.5f;
float fVelocitySq = pPed->GetVelocity().Mag2();
return fVelocitySq > square(MIN_VEL);
}
#if !__FINAL
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Debug
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::Debug() const
{
const aiTask* pSubTask = GetSubTask();
if( pSubTask )
pSubTask->Debug();
#if __BANK
#if DEBUG_DRAW
// Debug draw the collected information for this frame
u32 nFrameCount = fwTimer::GetSystemFrameCount();
if( CCombatMeleeDebug::sm_nLastDebugDrawFrame != nFrameCount )
{
CCombatMeleeDebug::sm_nLastDebugDrawFrame = nFrameCount;
CCombatMeleeDebug::sm_debugDraw.Render();
}
if( GetPed()->IsPlayer() )
{
// Draw the current combo pressed
if( GetState() != State_WaitForMeleeAction )
{
static const Vector2 s_vCombo1Pos( 0.85f, 0.15f );
grcDebugDraw::Text( s_vCombo1Pos, Color_green, CImpulse::GetImpulseName( m_impulseCombo.GetImpulseAtIndex(0) ) );
static const Vector2 s_vCombo2Pos( 0.85f, 0.25f );
grcDebugDraw::Text( s_vCombo2Pos, Color_blue, CImpulse::GetImpulseName( m_impulseCombo.GetImpulseAtIndex(1) ) );
static const Vector2 s_vCombo3Pos( 0.85f, 0.35f );
grcDebugDraw::Text( s_vCombo3Pos, Color_red, CImpulse::GetImpulseName( m_impulseCombo.GetImpulseAtIndex(2) ) );
}
// Give an indication as to how much time is left to get the combo in (player has until first attack hits)
if( GetState() == State_MeleeAction && pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
{
const CTaskMeleeActionResult* pMeleeActionTask = static_cast<const CTaskMeleeActionResult*>( pSubTask );
if( pMeleeActionTask->GetCurrentPhase() > 0.0f )
CCombatMeleeDebug::DebugDrawAttackWindow( pMeleeActionTask->GetCurrentPhase() );
char tempString[256];
formatf( tempString, 128, "ID %d %s\n", pMeleeActionTask->GetUniqueNetworkActionID(), pMeleeActionTask->GetActionResult() ? pMeleeActionTask->GetActionResult()->GetName() : NULL );
static dev_float sfRootOffsetZ = 1.0f;
Vec3V vPos = GetPed()->GetTransform().Transform3x3( Vec3V( 0.0f, 0.0f, sfRootOffsetZ ) ) + GetPed()->GetTransform().GetPosition();
CTask::ms_debugDraw.AddText( vPos, 0, 30, tempString, Color_red, 250, atStringHash( "Unique Action ID" ) );
}
}
#endif // DEBUG_DRAW
#endif // __BANK
#if __DEV
const CPed *pPed = GetPed();
const bool bIsPlayer = pPed->IsPlayer();
const bool bShouldDraw = (bIsPlayer && CCombatMeleeDebug::sm_bControlSubtaskDebugDisplayForPlayer) ||
(!bIsPlayer && CCombatMeleeDebug::sm_bControlSubtaskDebugDisplayForNPCs);
if( !bShouldDraw )
return;
static dev_float sfRootOffsetZ = 0.5f;
Vec3V vPedPosition = pPed->GetTransform().Transform3x3( Vec3V( 0.0f, 0.0f, sfRootOffsetZ ) ) + pPed->GetTransform().GetPosition();
Color32 debugColor( 0xc0, 0xc0, 0xff, 0xff );
if( HasLockOnTargetEntity() )
debugColor.Set( 0xff, 0xc0, 0xc0, 0xff );
if( CCombatMeleeDebug::sm_bDisplayQueueType && !pPed->IsAPlayerPed() )
{
QueueType pedQueueType = GetQueueType();
if( pedQueueType == QT_ActiveCombatant )
CTask::ms_debugDraw.AddText( vPedPosition, 0, 30, "Active", Color_red, 250, atStringHash( "QueueType" ) );
else if( pedQueueType == QT_SupportCombatant )
CTask::ms_debugDraw.AddText( vPedPosition, 0, 30, "Support", Color_red, 250, atStringHash( "QueueType" ) );
else if ( pedQueueType == QT_InactiveObserver )
CTask::ms_debugDraw.AddText( vPedPosition, 0, 30, "Inactive", Color_red, 250, atStringHash( "QueueType" ) );
else
CTask::ms_debugDraw.AddText( vPedPosition, 0, 30, "Invalid", Color_red, 250, atStringHash( "QueueType" ) );
}
const CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
CActionFlags::TargetType targetType = CActionFlags::TT_ENTITY_ROOT;
const CPed* pTargetPed = NULL;
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
pTargetPed = static_cast<const CPed*>( pTargetEntity );
targetType = CActionFlags::TT_PED_NECK;
}
Vec3V v3CurrentViewedPos( VEC3_ZERO );
Vec3V v3CurrentViewedDir( VEC3_ZERO );
int nBoneIdxCache = -1;
if( !CActionManager::TargetTypeGetPosDirFromEntity( v3CurrentViewedPos,
v3CurrentViewedDir,
targetType,
pTargetEntity,
pPed,
vPedPosition,
0.0f,
nBoneIdxCache ) )
{
v3CurrentViewedPos = vTargetPosition;
v3CurrentViewedDir = NormalizeSafe( vTargetPosition - vPedPosition, pPed->GetTransform().GetB() );
}
Vec3V vDelta = v3CurrentViewedPos - vPedPosition;
Vec3V vDeltaXY( vDelta.GetXf(), vDelta.GetYf(), 0.0f );
Vec3V vMiddle = vPedPosition + Scale( vDelta, ScalarV( 0.5f ) );
if( CCombatMeleeDebug::sm_bDrawQueueVolumes )
{
CTask::ms_debugDraw.AddSphere( vTargetPosition, sm_fAiSeekModeActiveCombatantRadius, Color_blue1, 250, atStringHash( "AiSeekModeActiveCombatantRadius" ) );
CTask::ms_debugDraw.AddSphere( vTargetPosition, sm_fAiSeekModeSupportCombatantRadiusMin, Color_green1, 250, atStringHash( "AiSeekModeSupportCombatantRadiusMin" ) );
CTask::ms_debugDraw.AddSphere( vTargetPosition, sm_fAiSeekModeSupportCombatantRadiusMax, Color_green2, 250, atStringHash( "AiSeekModeSupportCombatantRadiusMax" ) );
CTask::ms_debugDraw.AddSphere( vTargetPosition, sm_fAiSeekModeInactiveCombatantRadiusMin, Color_red1, 250, atStringHash( "AiSeekModeInactiveCombatantRadiusMin" ) );
CTask::ms_debugDraw.AddSphere( vTargetPosition, sm_fAiSeekModeInactiveCombatantRadiusMax, Color_red2, 250, atStringHash( "AiSeekModeInactiveCombatantRadiusMax" ) );
}
if( CCombatMeleeDebug::sm_bRelationshipDisplayDistanceXY && pTargetEntity )
{
// Draw a line to the position.
CTask::ms_debugDraw.AddLine( vPedPosition, v3CurrentViewedPos, debugColor, 250, atStringHash( "FLAT_DIST_LINE" ) );
// Draw the length of the line.
char tempString[128];
formatf( tempString, 128, "%f\n", Mag( vDeltaXY ).Getf() );
CTask::ms_debugDraw.AddText( vMiddle, 0, 0, tempString, debugColor, 250, atStringHash( "FLAT_DIST_TEXT" ) );
}
if( CCombatMeleeDebug::sm_bRelationshipDisplayHeightDiff && pTargetEntity )
{
// Draw a line to the position.
CTask::ms_debugDraw.AddLine( vPedPosition, v3CurrentViewedPos, debugColor, 250, atStringHash( "HEIGHT_DIFF_LINE" ) );
// Draw the height diff of the line.
char tempString[128];
formatf( tempString, 128, "%f\n", vDelta.GetZf() );
CTask::ms_debugDraw.AddText( vMiddle, 0, 0, tempString, debugColor, 250, atStringHash( "HEIGHT_DIFF_TEXT" ) );
}
if( CCombatMeleeDebug::sm_bRelationshipDisplayGroundHeightDiff && pTargetEntity )
{
static dev_float sfProbeZOffset = -10.0f;
float fViewerGroundHeight = CActionManager::GetEntityGroundHeight( pPed, vPedPosition, sfProbeZOffset );
float fGroundHeight = CActionManager::GetEntityGroundHeight( pTargetEntity, v3CurrentViewedPos, sfProbeZOffset );
float fGroundHeightDiff = fGroundHeight - fViewerGroundHeight;
// Draw a line to the position.
CTask::ms_debugDraw.AddLine( vPedPosition, v3CurrentViewedPos, debugColor, 250, atStringHash( "GROUND_HEIGHT_DIFF_LINE" ) );
// Draw the height diff of the line.
char tempString[128];
formatf( tempString, 128, "%f\n", fGroundHeightDiff );
CTask::ms_debugDraw.AddText( vMiddle, 0, 0, tempString, debugColor, 250, atStringHash( "GROUND_HEIGHT_DIFF_TEXT" ) );
}
if( CCombatMeleeDebug::sm_bRelationshipDisplayFacingAngleXY && pTargetEntity )
{
// Draw a line to the position.
CTask::ms_debugDraw.AddLine( vPedPosition, v3CurrentViewedPos, debugColor, 250, atStringHash( "FACING_ANGLE_LINE" ) );
// Draw the facing angle of the line.
const float fViewerHeading = fwAngle::LimitRadianAngle( pPed->GetTransform().GetHeading() );
const float fViewerToViewedHeading = fwAngle::LimitRadianAngle( rage::Atan2f( -vDeltaXY.GetXf(), vDeltaXY.GetYf() ) );
const float fViewerToViewedAngleDiff = fwAngle::LimitRadianAngle( fViewerToViewedHeading - fViewerHeading );
char tempString[128];
formatf( tempString, 128, "My Heading Difference (Rad)%f\n", fViewerToViewedAngleDiff );
CTask::ms_debugDraw.AddText( vPedPosition, 0, 0, tempString, debugColor, 250, atStringHash( "FACING_ANGLE_TEXT_A" ) );
const float fViewedHeading = fwAngle::LimitRadianAngle( pTargetEntity->GetTransform().GetHeading() );
const float fViewedToViewingHeading = fwAngle::LimitRadianAngle( rage::Atan2f( vDeltaXY.GetXf(), -vDeltaXY.GetYf() ) );
const float fViewedToViewerAngleDiff = fwAngle::LimitRadianAngle( fViewedToViewingHeading - fViewedHeading );
formatf( tempString, 128, "Viewed Heading Difference (Rad))%f\n", fViewedToViewerAngleDiff );
CTask::ms_debugDraw.AddText( v3CurrentViewedPos, 0, 0, tempString, debugColor, 250, atStringHash( "FACING_ANGLE_TEXT_B" ) );
}
if( CCombatMeleeDebug::sm_bRelationshipDisplayBoxAndClosestBoxPointDistXY && pTargetPed )
{
// Get the box which approximates the ped in the current pose.
// We will the use the nearest point on the box as the position.
const spdAABB boundingBox = pTargetPed->EstimateLocalBBoxFromPose();
// Get the transformation for the box.
const Mat34V& matBoundingBox = pTargetPed->GetMatrixRef();
// Draw the oriented box.
grcDebugDraw::BoxOriented( boundingBox.GetMin(), boundingBox.GetMax(), matBoundingBox, debugColor );
// Get the closest point and normal on the box in world space.
Vec3V vPosition = FindClosestPointOnAABB( boundingBox, vPedPosition );
CTask::ms_debugDraw.AddLine( vPedPosition, vPosition, debugColor, 250, atStringHash( "CLOSEST_BBBOX_FLAT_DIST_LINE" ) );
// Draw the length of the line.
char tempString[128];
formatf( tempString, 128, "%f\n", Mag( vDeltaXY ).Getf() );
CTask::ms_debugDraw.AddText( vMiddle, 0, 0, tempString, debugColor, 250, atStringHash( "CLOSEST_BBBOX_FLAT_DIST_TEXT" ) );
}
if( CCombatMeleeDebug::sm_bRelationshipDisplayBoxAndClosestBoxPointHeightDiff && pTargetPed )
{
// Get the box which approximates the ped in the current pose.
// We will the use the nearest point on the box as the position.
const spdAABB boundingBox = pTargetPed->EstimateLocalBBoxFromPose();
// Get the transformation for the box.
const Mat34V& matBoundingBox = pTargetPed->GetMatrixRef();
// Draw the oriented box.
grcDebugDraw::BoxOriented( boundingBox.GetMin(), boundingBox.GetMax(), matBoundingBox, debugColor );
// Get the closest point and normal on the box in world space.
Vec3V vPosition = FindClosestPointOnAABB( boundingBox, vPedPosition );
CTask::ms_debugDraw.AddLine( vPedPosition, vPosition, debugColor, 250, atStringHash( "CLOSEST_BBBOX_HEIGHT_DIFF_LINE" ) );
// Draw the length of the line.
char tempString[128];
formatf( tempString, 128, "%f\n", vDelta.GetZf() );
CTask::ms_debugDraw.AddText( vMiddle, 0, 0, tempString, debugColor, 250, atStringHash( "CLOSEST_BBBOX_HEIGHT_DIFF_TEXT" ) );
}
}
#endif //__DEV
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetStaticStateName
// PURPOSE : Retrieve the name for the identified state
// PARAMETERS : iState - State index we are interested in
/////////////////////////////////////////////////////////////////////////////////
const char * CTaskMelee::GetStaticStateName( s32 iState )
{
switch( iState )
{
case State_Start: return "State_Start";
case State_InitialPursuit: return "State_InitialPursuit";
case State_LookAtTarget: return "State_LookAtTarget";
case State_ChaseAfterTarget: return "State_ChaseAfterTarget";
case State_RetreatToSafeDistance: return "State_RetreatToSafeDistance";
case State_WaitForTarget: return "State_WaitForTarget";
case State_WaitForMeleeActionDecide: return "State_WaitForMeleeActionDecide";
case State_WaitForMeleeAction: return "State_WaitForMeleeAction";
case State_InactiveObserverIdle: return "State_InactiveObserverIdle";
case State_MeleeAction: return "State_MeleeAction";
case State_SwapWeapon: return "State_SwapWeapon";
case State_Finish: return "State_Finish";
default: taskAssert(0);
}
return "State_Invalid";
}
#endif //!__FINAL
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Start_OnEnter
// PURPOSE : Entry point of CTaskMelee which will request needed clip sets
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::Start_OnEnter( CPed* pPed )
{
DEV_BREAK_IF_FOCUS( CCombatMeleeDebug::sm_bFocusPedBreakOnTaskCombatMeleeStateStart, pPed );
if(GetPed()->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMelee::Start_OnEnter - Player = %s Action %p Previous State %d", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), m_pNextActionResult, GetPreviousState());
}
if( !pPed->IsPlayer() )
{
// Trigger the first LOS test as early as possible
m_seekModeLosTestNextRequestTimer.Set( fwTimer::GetTimeInMilliseconds(), 0 );
if( !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_PreventAllMeleeTaunts ) && !GetIsTaskFlagSet( MF_HasPerformedInitialPursuit ) )
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( pBrawlData && pBrawlData->ShouldPlayTauntBeforeAttack() )
SetTaskFlag( MF_PlayTauntBeforeAttacking );
}
}
if (pPed->GetPedResetFlag(CPED_RESET_FLAG_IgnoreCombatManager))
m_queueType = QT_ActiveCombatant;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : RegisterMeleeOpponent
// PURPOSE : Register the melee opponent with the combat manager
// PARAMETERS : pPed - Ped we want to register
// pTargetEntity - Target for the given ped
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::RegisterMeleeOpponent( CPed* pPed, CEntity* pTargetEntity )
{
if(pPed->GetPedResetFlag(CPED_RESET_FLAG_IgnoreCombatManager))
{
return;
}
if( !m_Opponent.GetPed() )
m_Opponent.Init( *pPed );
CCombatMeleeGroup* pMeleeGroup = m_Opponent.GetMeleeGroup();
const CEntity* pOldTargetEntity = pMeleeGroup ? pMeleeGroup->GetTarget() : NULL;
// If the group target doesn't match my current target, switch groups
// If the group isn't currently full and we are not properly in the group (may have been waiting for a spot to open), switch groups
if( pTargetEntity != pOldTargetEntity || ( pMeleeGroup && !pMeleeGroup->IsMeleeGroupFull() && !pMeleeGroup->IsPedInMeleeGroup( pPed ) ) )
{
if( pMeleeGroup )
{
pMeleeGroup->Remove( m_Opponent );
pMeleeGroup = NULL;
}
if( pTargetEntity )
{
pMeleeGroup = CCombatManager::GetMeleeCombatManager()->FindOrCreateMeleeGroup( *pTargetEntity );
if( pMeleeGroup )
pMeleeGroup->Insert( m_Opponent );
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UnRegisterMeleeOpponent
// PURPOSE : Reverse of RegisterMeleeOpponent
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UnRegisterMeleeOpponent( void )
{
CCombatMeleeGroup* pMeleeGroup = m_Opponent.GetMeleeGroup();
if( pMeleeGroup )
{
pMeleeGroup->Remove( m_Opponent );
pMeleeGroup = NULL;
}
if( m_Opponent.GetPed() )
m_Opponent.Shutdown();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Start_OnUpdate
// PURPOSE : Heartbeat of the initial state. Decides where to direct this
// instance.
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::Start_OnUpdate( CPed* pPed )
{
if(GetPed()->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMelee::Start_OnUpdate - Player = %s Action %p", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), m_pNextActionResult);
}
// If the task has an initial result, go straight into a melee action
CEntity* pTargetEntity = GetTargetEntity();
if( m_pNextActionResult )
{
if( !pPed->IsNetworkClone() )
{
// Calculate the animated camera that will be used for the following action
if( pPed->IsLocalPlayer() && ( m_pNextActionResult->GetIsAStealthKill() || m_pNextActionResult->GetIsATakedown() ) )
{
CHoming* pHoming = m_pNextActionResult->GetHoming();
if( pHoming && pTargetEntity && pPed->GetReferenceFrameVelocity().IsNonZero())
{
Vec3V vTargetDirection( VEC3_ZERO );
int rnBoneCacheIdx = -1;
if( !CActionManager::TargetTypeGetPosDirFromEntity( m_vStealthKillWorldOffset,
vTargetDirection,
pHoming->GetTargetType(),
pTargetEntity,
pPed,
pPed->GetTransform().GetPosition(),
0.0f,
rnBoneCacheIdx ) )
{
// Default to use the target entity position
m_vStealthKillWorldOffset = pTargetEntity->GetTransform().GetPosition();
vTargetDirection = m_vStealthKillWorldOffset - pPed->GetTransform().GetPosition();
}
Vec3V vMoverDisplacement( VEC3_ZERO );
pHoming->CalculateHomingWorldOffset( pPed,
m_vStealthKillWorldOffset,
vTargetDirection,
vMoverDisplacement,
m_vStealthKillWorldOffset,
m_fStealthKillWorldHeading );
}
m_nCameraAnimId = camCinematicAnimatedCamera::ComputeValidCameraAnimId( pTargetEntity, m_pNextActionResult );
if( m_nCameraAnimId != 0 )
{
pPed->Teleport( VEC3V_TO_VECTOR3( GetStealthKillWorldOffset() ), GetStealthKillHeading(), true );
SetTaskFlag( MF_SuppressMeleeActionHoming );
}
}
}
SetState( State_MeleeAction );
}
else
{
// Players or anim synced peds go straight into melee action decide
if( pPed->IsPlayer() || GetIsTaskFlagSet( MF_AnimSynced ) || pPed->GetPedResetFlag(CPED_RESET_FLAG_IgnoreCombatManager) )
SetState( State_WaitForMeleeActionDecide );
else
{
QueueType eQueueType = GetQueueType();
if( !pPed->IsNetworkClone() && eQueueType > QT_InactiveObserver && !GetIsTaskFlagSet( MF_ForceStrafe ) )
{
if( GetIsTaskFlagSet( MF_PlayTauntBeforeAttacking ) )
SetState( State_LookAtTarget );
else if( pPed->GetPedType() == PEDTYPE_ANIMAL )
SetState( State_WaitForMeleeActionDecide );
else
SetState( State_InitialPursuit );
}
else
SetState( State_WaitForMeleeActionDecide );
}
// Reset the taunt timer before moving on
ResetTauntTimer();
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Start_OnExit
// PURPOSE : Clean up for the start state
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::Start_OnExit( void )
{
ClearTaskFlag( MF_AnimSynced );
if( GetState() != State_InitialPursuit )
SetTaskFlag( MF_HasPerformedInitialPursuit );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : InitialPursuit_OnEnter
// PURPOSE : (AI ONLY) Will walk toward the target
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::InitialPursuit_OnEnter( CPed* pPed )
{
// Note: This is really one of the major parts of our "Melee Combat AI"...
// Search for the quoted term in the line above to find the others.
//Set up the nav mesh.
CNavParams params;
params.m_vTarget = VEC3V_TO_VECTOR3( CalculateDesiredChasePosition( pPed ) );
params.m_fMoveBlendRatio = MOVEBLENDRATIO_WALK;
params.m_fSlowDownDistance = 0.0f;
//Create the move task.
CTaskMoveFollowNavMesh* pMoveTask = rage_new CTaskMoveFollowNavMesh( params );
//Keep moving while waiting for a path.
float fKeepMovingWhilePathingDistance = 0.0f;
float fTargetRadius = CTaskMoveFollowNavMesh::ms_fTargetRadius;
if( IsLosBlocked() )
fTargetRadius = sm_fAiSeekModeActiveCombatantRadius;
else
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( taskVerifyf( pBrawlData, "CTaskMelee::ChaseAfterTarget_OnEnter - Melee opponent without brawling style" ) )
{
fKeepMovingWhilePathingDistance = pBrawlData->GetKeepMovingWhilePathingDistance();
fTargetRadius = pBrawlData->GetTargetRadius();
}
}
pMoveTask->SetKeepMovingWhilstWaitingForPath( true, fKeepMovingWhilePathingDistance );
pMoveTask->SetTargetRadius( fTargetRadius );
pMoveTask->SetAvoidObjects( true );
pMoveTask->SetNeverEnterWater( true );
pMoveTask->SetDontStandStillAtEnd( true );
CTask* pSubTask = rage_new CTaskAmbientClips( CTaskAmbientClips::Flag_PlayBaseClip | CTaskAmbientClips::Flag_ForceFootClipSetRestart, CONDITIONALANIMSMGR.GetConditionalAnimsGroup( "BRAVE" ) );
SetNewTask( rage_new CTaskComplexControlMovement( pMoveTask, pSubTask ) );
if( ShouldAssistMotionTask() )
ProcessMotionTaskAssistance( true );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : InitialPursuit_OnUpdate
// PURPOSE : (AI ONLY) Will continue to walk towards the target until we get
// close enough or are told to break out
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::InitialPursuit_OnUpdate( CPed* pPed )
{
// If we shouldn't be in melee any longer, we should quit out
if( !GetIsTaskFlagSet( MF_ShouldBeInMeleeMode ) )
{
taskDebugf1("Quit: CTaskMelee::InitialPursuit_OnUpdate: !MF_ShouldBeInMeleeMode");
SetState( State_Finish );
return FSM_Continue;
}
// Check to see somebody set a local reaction
if( m_pNextActionResult )
{
SetState( State_MeleeAction );
return FSM_Continue;
}
// If the task finishes, go back to searching for a new melee action
if( GetIsFlagSet( aiTaskFlags::SubTaskFinished ) ||
ShouldBeInInactiveTauntMode() ||
GetIsTaskFlagSet( MF_HasPerformedInitialPursuit ) )
{
ResetTauntTimer();
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
CTaskMoveFollowNavMesh* pNavMeshTask = static_cast<CTaskMoveFollowNavMesh*>( pPed->GetPedIntelligence()->FindMovementTaskByType( CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH ) );
if( pNavMeshTask )
{
if( pNavMeshTask->IsUnableToFindRoute() )
{
SetTaskFlag( MF_CannotFindNavRoute );
return FSM_Continue;
}
else if( pNavMeshTask->IsFollowingNavMeshRoute() )
{
// Determine if the distance is too far to walk and actually bail out
// seems strange to walk if they are extremly far away
if( pNavMeshTask->IsTotalRouteLongerThan( sm_fAiSeekModeInitialPursuitBreakoutDist ) )
{
ResetTauntTimer();
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
}
// Check to see if we should re-evaluate the seek timer
if( m_seekModeScanTimer.IsOutOfTime() )
pNavMeshTask->SetTarget( pPed, CalculateDesiredChasePosition( pPed ) );
//Keep moving while waiting for a path.
float fTargetRadius = CTaskMoveFollowNavMesh::ms_fTargetRadius;
if( IsLosBlocked() )
fTargetRadius = sm_fAiSeekModeActiveCombatantRadius;
else
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( taskVerifyf( pBrawlData, "CTaskMelee::InitialPursuit_OnUpdate - Melee opponent without brawling style" ) )
fTargetRadius = pBrawlData->GetTargetRadius();
}
pNavMeshTask->SetTargetRadius( fTargetRadius );
}
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
// Only test the stop conditions if our line of sight is not blocked
if( !IsLosBlocked() )
{
Vec3V vDelta = vTargetPosition - pPed->GetTransform().GetPosition();
Vec3V vDeltaXY( vDelta.GetXf(), vDelta.GetYf(), 0.0f );
float fArrivalThreshold = GetDesiredTargetDistance();
if( Abs( vDelta.GetZf() ) < sm_fAiSeekModeTargetHeight && MagSquared( vDeltaXY ).Getf() < rage::square( fArrivalThreshold ) )
{
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
}
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : InitialPursuit_OnExit
// PURPOSE : Let the task know we have performed the initial pursuit state
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::InitialPursuit_OnExit( void )
{
SetTaskFlag( MF_HasPerformedInitialPursuit );
// Reset any motion task assistance.
ProcessMotionTaskAssistance( false );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : LookAtTarget_OnUpdate
// PURPOSE : Test to see whether or not we are within certain look at criteria
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::LookAtTarget_OnUpdate( CPed* pPed )
{
// Calculate the distance to the player
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
if ( ShouldUpdateDesiredHeading( *pPed, vTargetPosition ) )
{
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( vTargetPosition ) );
}
// We do not want to proceed until the animations are properly streamed in
if( CEquipWeaponHelper::AreEquippedWeaponAnimsStreamedIn( *pPed ) )
{
// Check if the ped is reasonably close to look at their target.
float fDesiredHeading = pPed->GetMotionData()->GetDesiredHeading();
float fCurrentHeading = pPed->GetMotionData()->GetCurrentHeading();
bool bCloseEnoughForBlend = fabs(SubtractAngleShorter( fDesiredHeading, fCurrentHeading )) < sm_fLookAtTargetHeadingBlendoutDegrees * DtoR;
if (bCloseEnoughForBlend)
{
// Check if the ped can be seen.
CPedIntelligence* pPedIntelligence = pPed->GetPedIntelligence();
if( pPedIntelligence )
{
if( pPedIntelligence->GetPedPerception().ComputeFOVVisibility( pTargetEntity, NULL ) )
SetState( State_WaitForMeleeActionDecide );
}
else
SetState( State_WaitForMeleeActionDecide );
// If looking at the target, but not in the FoV (too far away), then proceed to the next state anyway if enough time has passed.
if (GetTimeInState() > sm_fLookAtTargetTimeout)
{
SetState( State_WaitForMeleeActionDecide );
}
}
}
}
else
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : LookAtTarget_OnExit
// PURPOSE : Reset anything important before moving on to the next state
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::LookAtTarget_OnExit( void )
{
// Force an attack and taunt
u32 nCurrentTimeInMs = fwTimer::GetTimeInMilliseconds();
m_nextRequiredMoveTimer.Set( nCurrentTimeInMs, 0 );
m_tauntTimer.Set( nCurrentTimeInMs, 0 );
// Allow this ped to shout the target's position now that it has finished looking.
GetPed()->GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_DisableShoutTargetPosition);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForMeleeActionDecide_OnEnter
// PURPOSE : Initializes special AI timers and resets the combo buffer
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::WaitForMeleeActionDecide_OnEnter( CPed* pPed )
{
// Make sure that AI peds always do another move within a certain amount of time.
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( !pPed->IsPlayer() && taskVerifyf( pBrawlData, "CTaskMelee::WaitForMeleeActionDecide_OnEnter - Melee opponent without brawling style" ) )
{
float fFightProficiency = Clamp( pPed->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat( kAttribFloatFightProficiency ), 0.0f, 1.0f );
if( !m_nextRequiredMoveTimer.IsSet() )
m_nextRequiredMoveTimer.Set( fwTimer::GetTimeInMilliseconds(), fwRandom::GetRandomNumberInRange( Lerp( fFightProficiency, pBrawlData->GetAttackFrequencyWorstFighterMin(), pBrawlData->GetAttackFrequencyBestFighterMin() ), Lerp( fFightProficiency, pBrawlData->GetAttackFrequencyWorstFighterMax(), pBrawlData->GetAttackFrequencyBestFighterMax() ) ) );
// Calculate probability of doing a block on the next opponent strike
if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) < ( Lerp( fFightProficiency, pBrawlData->GetBlockProbabilityMin(), pBrawlData->GetBlockProbabilityMax() ) + GetBlockBuff() ) )
m_bCanBlock = true;
else
m_bCanBlock = false;
}
ResetImpulseCombo();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForMeleeActionDecide_OnUpdate
// PURPOSE : Branch point which will decide to either swap weapon or send ped
// the the main melee waiting state
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::WaitForMeleeActionDecide_OnUpdate( void )
{
if( !GetPed()->IsPlayer() && (GetQueueType() <= QT_InactiveObserver) )
SetState( State_InactiveObserverIdle );
else
SetState( State_WaitForMeleeAction );
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : MeleeAction_OnEnter
// PURPOSE : In charge of allocating melee action instance and recalculating
// all important ai melee probabilities
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::MeleeAction_OnEnter( CPed* pPed )
{
DEV_BREAK_IF_FOCUS(CCombatMeleeDebug::sm_bBreakOnFocusPedActionTransition, pPed);
if(!pPed->IsNetworkClone())
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
taskAssertf( pBrawlData, "CTaskMelee::MeleeAction_OnEnter - Melee opponent without brawling style" );
float fFightProficiency = Clamp( pPed->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat( kAttribFloatFightProficiency ), 0.0f, 1.0f );
Assert( m_pNextActionResult );
// Disable the timers for now
if( !pPed->IsPlayer() )
{
// Only stop the next move time when we are not attacking
if( m_pNextActionResult->GetIsAStandardAttack() )
m_nextRequiredMoveTimer.Unset();
// Calculate whether or not we will perform a combo on our next attack
m_bCanBlock = m_bCanCounter = m_bCanPerformCombo = false;
if( pBrawlData )
{
// If we plan to execute a block, calculate whether or not we should counter attack
if( m_pNextActionResult->GetIsABlock() )
{
if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) < Lerp( fFightProficiency, pBrawlData->GetCounterProbabilityMin(), pBrawlData->GetCounterProbabilityMax() ) )
m_bCanCounter = true;
// Recalculate probability of doing a block on the next opponent strike
else if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) < ( Lerp( fFightProficiency, pBrawlData->GetBlockProbabilityMin(), pBrawlData->GetBlockProbabilityMax() ) + GetBlockBuff() ) )
m_bCanBlock = true;
}
// We do not want to allow AI combos if the target is unreachable
else if( CanPerformMeleeAttack( pPed, GetTargetEntity() ) )
{
// Determine whether or not we should perform a combination
if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) < Lerp( fFightProficiency, pBrawlData->GetAttackProbabilityToComboMin(), pBrawlData->GetAttackProbabilityToComboMax() ) )
m_bCanPerformCombo = true;
}
}
// If we are reacting to a melee action, increase the chances to block on the next
if( m_pNextActionResult->GetIsAHitReaction() )
IncreaseMeleeBuff( pPed );
// Any other action will decrease the block buffer
else
DecreaseMeleeBuff();
}
// Reset the daze reaction bool flag
m_bCanPerformDazedReaction = false;
// Re-evaluate the dazed reaction if allowed
if( pBrawlData && m_pNextActionResult->AllowDazedReaction() )
{
if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) < Lerp( fFightProficiency, pBrawlData->GetProbabilityToBeDazedMin(), pBrawlData->GetProbabilityToBeDazedMax() ) )
m_bCanPerformDazedReaction = true;
}
if( m_pNextActionResult->GetIsAnIntro() || m_pNextActionResult->GetIsATaunt() )
{
m_tauntTimer.Unset();
SetTaskFlag( MF_ResetTauntTimerAfterNextAction );
if( ShouldBeInInactiveTauntMode() )
pPed->NewSay( "GENERIC_INSULT_HIGH" );
else
pPed->NewSay( "FIGHT_BYSTANDER_TAUNT" );
}
if( m_pNextActionResult->AllowNoTargetInterrupt() )
SetTaskFlag( MF_AllowNoTargetInterrupt );
// Time to force out the secondary task as quick as possible
aiTask* pSecondaryTask = pPed->GetPedIntelligence() ? pPed->GetPedIntelligence()->GetTaskSecondaryActive() : NULL;
if( pSecondaryTask && pSecondaryTask->GetTaskType() == CTaskTypes::TASK_SHOVE_PED )
static_cast<CTaskShovePed*>(pSecondaryTask)->SetBlendOutDuration( FAST_BLEND_DURATION );
// Send data immediately if a player is involved.
if(pPed->GetNetworkObject() && GetTargetEntity() && GetTargetEntity()->GetIsTypePed())
{
CPed* pTargetPed = static_cast<CPed*>(GetTargetEntity());
if(!pPed->IsNetworkClone() && (pTargetPed->IsPlayer() || pPed->IsPlayer()))
{
CNetObjPed* pPedObj = SafeCast(CNetObjPed, pPed->GetNetworkObject());
pPedObj->ForceResendOfAllTaskData();
pPedObj->ForceResendAllData();
}
}
}
// Pause the force strafe timer
if( m_forceStrafeTimer.IsEnabled() )
m_forceStrafeTimer.Pause();
if( !pPed->IsNetworkClone() || IsRunningLocally() )
{
// Mark the last action as a recoil
m_bLastActionWasARecoil = m_pNextActionResult->GetIsARecoil();
if( m_pNextActionResult->ShouldGiveThreatResponseAfterAction() )
SetTaskFlag( MF_GiveThreatResponseAfterNextAction );
else
ClearTaskFlag( MF_GiveThreatResponseAfterNextAction );
if( m_pNextActionResult->ShouldQuitTaskAfterAction() )
SetTaskFlag( MF_QuitTaskAfterNextAction );
else
ClearTaskFlag( MF_QuitTaskAfterNextAction );
// Will this action end in an aiming pose?
m_bEndsInAimingPose = m_pNextActionResult->EndsInAimingPose();
if( m_pNextActionResult->GetIsAHitReaction() )
{
SetTaskFlag( MF_ResetTauntTimerAfterNextAction );
if( m_pNextActionResult->GetShouldResetMinTimeInMelee() )
SetTaskFlag( MF_ReactionResetTimeInTaskAfterNextAction );
}
else if( m_pNextActionResult->GetShouldResetMinTimeInMelee() && GetTargetEntity() )
{
SetTaskFlag( MF_AttackResetTimeInTaskAfterNextAction );
}
// Only set this MF_TagSyncBlendOut flag if MF_QuitTaskAfterNextAction is also set
if( GetIsTaskFlagSet( MF_QuitTaskAfterNextAction ) && m_pNextActionResult->TagSyncBlendOut() )
SetTaskFlag( MF_TagSyncBlendOut );
else
ClearTaskFlag( MF_TagSyncBlendOut );
if( m_pNextActionResult->EndsInIdlePose() )
SetTaskFlag( MF_EndsInIdlePose );
else
ClearTaskFlag( MF_EndsInIdlePose );
//! DMKH. Cache action result ID for networking as we reset 'm_pNextActionResult', so this can't be used :(
m_nActionResultNetworkID = m_pNextActionResult->GetID();
m_bLocalReaction = false;
// UGH! Cache off the target weapon hash
// This is needed because we play out an animated sequence involving a ped
// The ped might have a weapon in their hands but will not be reliable when
// we process self damage.
u32 nSelfDamageWeaponHash = pPed->GetDefaultUnarmedWeaponHash();
eAnimBoneTag eSelfDamageAnimBoneTag = BONETAG_INVALID;
const CDamageAndReaction* pDamageAndReaction = m_pNextActionResult->GetDamageAndReaction();
if( pDamageAndReaction )
eSelfDamageAnimBoneTag = pDamageAndReaction->GetForcedSelfDamageBoneTag();
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>(pTargetEntity );
if( pTargetPed->GetWeaponManager() )
nSelfDamageWeaponHash = pTargetPed->GetWeaponManager()->GetEquippedWeaponHash();
}
CTaskMeleeActionResult* pActionResultTask = rage_new CTaskMeleeActionResult( m_pNextActionResult, pTargetEntity, HasLockOnTargetEntity(), GetIsTaskFlagSet( MF_IsDoingReaction ), GetIsTaskFlagSet( MF_SuppressMeleeActionHoming ), nSelfDamageWeaponHash, eSelfDamageAnimBoneTag, pPed->GetMotionData()->GetCurrentMoveBlendRatio().Mag2() );
SetNewTask( pActionResultTask );
// This will save a history of all the performed recent actions so we can avoid repeating moves
CActionManager::RecordAction( m_pNextActionResult->GetID() );
if( pActionResultTask && IsRunningLocally() )
{
pActionResultTask->SetRunningLocally( true );
pActionResultTask->SetRunAsAClone( true );
if(pPed->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMelee::MeleeAction_OnEnter - Player: %s Address: %p Sub Address: %p Running Locally: %d Unique ID: %d", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), this, pActionResultTask, IsRunningLocally() ? 1 : 0, m_nUniqueNetworkMeleeActionID);
}
}
pActionResultTask->SetUniqueNetworkActionID(m_nUniqueNetworkMeleeActionID);
// If ID is non 0, indicates this melee move was triggered by another ped (e.g. a reaction). Consume ID here, so that is reset the next time we create an
// an action.
m_nUniqueNetworkMeleeActionID = 0;
#if FPS_MODE_SUPPORTED
m_bInterruptForFirstPersonCamera = false;
#endif
// Reset the action information
m_pNextActionResult = NULL;
m_bActionIsBranch = false;
ClearTaskFlag( MF_IsDoingReaction );
}
if(pPed->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::MeleeAction_OnEnter - sequence ID = %d", CNetwork::GetNetworkTime(), GetNetSequenceID() );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : MeleeAction_OnUpdate
// PURPOSE : Monitors the CTaskMeleeActionResult instance as well as manages
// any combos from the current action.
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::MeleeAction_OnUpdate( CPed* pPed )
{
// Always force ped into strafe in order to facilitate the melee_outro anim when we stop
if( !GetIsTaskFlagSet( MF_QuitTaskAfterNextAction ) )
pPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePedToStrafe, true );
if( GetIsFlagSet( aiTaskFlags::SubTaskFinished ) || ( GetIsTaskFlagSet( MF_AllowNoTargetInterrupt ) && !HasLockOnTargetEntity() ) )
{
if( GetIsTaskFlagSet( MF_ShouldStartGunTaskAfterNextAction ) )
{
aiTask* pTask = pPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_PLAYER_ON_FOOT );
if( pTask )
static_cast<CTaskPlayerOnFoot*>( pTask )->SetShouldStartGunTask( true );
taskDebugf1("Quit: CTaskMelee::MeleeAction_OnUpdate: aiTaskFlags::SubTaskFinished (%d), MF_AllowNoTargetInterrupt (%d), !HasLockOnTargetEntity() (%d), MF_ShouldStartGunTaskAfterNextAction", GetIsFlagSet( aiTaskFlags::SubTaskFinished ), GetIsTaskFlagSet( MF_AllowNoTargetInterrupt ), !HasLockOnTargetEntity());
return FSM_Quit;
}
if( (!pPed->IsNetworkClone() || IsRunningLocally()) && GetIsTaskFlagSet( MF_QuitTaskAfterNextAction ))
{
taskDebugf1("Quit: CTaskMelee::MeleeAction_OnUpdate: aiTaskFlags::SubTaskFinished (%d), MF_AllowNoTargetInterrupt (%d), !HasLockOnTargetEntity() (%d), (!pPed->IsNetworkClone() (%d) || IsRunningLocally() (%d)) && GetIsTaskFlagSet( MF_QuitTaskAfterNextAction )", GetIsFlagSet( aiTaskFlags::SubTaskFinished ), GetIsTaskFlagSet( MF_AllowNoTargetInterrupt ), !HasLockOnTargetEntity(), !pPed->IsNetworkClone(), IsRunningLocally());
SetState( State_Finish );
}
else
{
SetState( State_WaitForMeleeActionDecide );
}
return FSM_Continue;
}
// Update blocking
if( GetIsTaskFlagSet( MF_AllowStrafeMode ) && !GetIsTaskFlagSet( MF_BehindTargetPed ) )
{
UpdateBlocking( pPed, false );
}
bool bMoveAllowsBlockTargetEvaluation = IsCurrentlyDoingAMove(false, false, true);
if(bMoveAllowsBlockTargetEvaluation)
{
UpdateBlockingTargetEvaluation(pPed);
}
return ManageActionSubtaskAndBranches( pPed );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : MeleeAction_OnExit
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::MeleeAction_OnExit( CPed* pPed )
{
m_nActionResultNetworkID = INVALID_ACTIONRESULT_ID;
if( GetIsTaskFlagSet( MF_ResetTauntTimerAfterNextAction ) )
{
ClearTaskFlag( MF_ResetTauntTimerAfterNextAction );
ResetTauntTimer();
}
// Player only force strafe timer logic
if( pPed->IsLocalPlayer() )
{
// Reset the actual force strafe timer (if necessary)
if( GetIsTaskFlagSet( MF_ReactionResetTimeInTaskAfterNextAction ) )
{
ClearTaskFlag( MF_ReactionResetTimeInTaskAfterNextAction );
m_forceStrafeTimer.Set( fwTimer::GetTimeInMilliseconds(), sm_nTimeInTaskAfterHitReaction );
}
else if( GetIsTaskFlagSet( MF_AttackResetTimeInTaskAfterNextAction ) )
{
ClearTaskFlag( MF_AttackResetTimeInTaskAfterNextAction );
m_forceStrafeTimer.Set( fwTimer::GetTimeInMilliseconds(), sm_nTimeInTaskAfterStardardAttack );
}
else if( m_forceStrafeTimer.IsEnabled() && m_forceStrafeTimer.IsPaused() )
m_forceStrafeTimer.UnPause();
#if FPS_MODE_SUPPORTED
if(pPed->IsFirstPersonShooterModeEnabledForPlayer(false))
{
m_forceStrafeTimer.Set(0, 0, false);
}
#endif
}
if( GetIsTaskFlagSet( MF_GiveThreatResponseAfterNextAction ) && CanChangeTarget())
{
ClearTaskFlag( MF_GiveThreatResponseAfterNextAction );
if( !pPed->IsPlayer() )
{
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
CTaskThreatResponse* pTask = rage_new CTaskThreatResponse( pTargetPed );
pTask->SetThreatResponseOverride( CTaskThreatResponse::TR_Fight );
pTask->GetConfigFlags().SetFlag( CTaskThreatResponse::CF_CanFightArmedPedsWhenNotArmed );
pTask->GetConfigFlagsForCombat().SetFlag( CTaskCombat::ComF_DisableAimIntro );
pTask->GetConfigFlagsForCombat().SetFlag( CTaskCombat::ComF_MeleeAnimPhaseSync );
CEventGivePedTask event( PED_TASK_PRIORITY_PHYSICAL_RESPONSE, pTask, false, E_PRIORITY_DAMAGE_PAIRED_MOVE );
pPed->GetPedIntelligence()->AddEvent( event );
pPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAIUpdate, true );
}
}
}
ClearTaskFlag( MF_ForceStrafeIndefinitely );
ClearTaskFlag( MF_PerformIntroAnim );
ClearTaskFlag( MF_PlayTauntBeforeAttacking );
ClearTaskFlag( MF_AllowNoTargetInterrupt );
ClearTaskFlag( MF_SuppressMeleeActionHoming );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SwapWeapon_OnEnter
// PURPOSE : In charge of allocating the weapon swap task
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::SwapWeapon_OnEnter( CPed* pPed )
{
s32 iFlags = SWAP_HOLSTER;
const CPedWeaponManager* pPedWeaponManager = pPed->GetWeaponManager();
if( pPedWeaponManager )
{
const CWeaponInfo* pInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>( pPedWeaponManager->GetEquippedWeaponHash() );
if( pInfo && pInfo->GetIsCarriedInHand() )
iFlags |= SWAP_DRAW;
// Force release the current taunt and variation clip sets as these are based on the currently equipped weapon
m_TauntClipSetRequestHelper.Release();
m_VariationClipSetRequestHelper.Release();
}
CTask* pMovementTask = pPed->GetPedIntelligence()->GetGeneralMovementTask();
if( pMovementTask )
SetNewTask( rage_new CTaskComplexControlMovement( (CTask*)pMovementTask->Copy(), rage_new CTaskSwapWeapon( iFlags ), CTaskComplexControlMovement::TerminateOnSubtask ) );
else
SetNewTask( rage_new CTaskSwapWeapon( iFlags ) );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SwapWeapon_OnUpdate
// PURPOSE : Waits until previously allocated swap weapon task has finished
// and moves on accordingly
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::SwapWeapon_OnUpdate( void )
{
// Set state to decide what to do next
if( GetIsFlagSet( aiTaskFlags::SubTaskFinished ) || !GetSubTask() )
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ChaseAfterTarget_OnEnter
// PURPOSE : (AI ONLY) In charge of getting a melee ped within relative distance
// to its target
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ChaseAfterTarget_OnEnter( CPed* pPed )
{
taskAssertf( !pPed->IsPlayer(), "CTaskMelee::ChaseAfterTarget_OnEnter - Non player peds not allowed!" );
// Reset the attack timer to always allow an action from chase
m_nextRequiredMoveTimer.Set( fwTimer::GetTimeInMilliseconds(), 0 );
// Note: This is really one of the major parts of our "Melee Combat AI"...
// Search for the quoted term in the line above to find the others.
//Set up the nav mesh.
CNavParams params;
params.m_vTarget = VEC3V_TO_VECTOR3( CalculateDesiredChasePosition( pPed ) );
if( pPed->GetPedType() == PEDTYPE_ANIMAL )
{
params.m_fMoveBlendRatio = MOVEBLENDRATIO_SPRINT;
CEntity* pTargetEntity = GetTargetEntity();
if ( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>(pTargetEntity);
if ( pTargetPed->GetTaskData().GetIsFlagSet( CTaskFlags::ForceSlowChaseInAnimalMelee ) )
{
params.m_fMoveBlendRatio = MOVEBLENDRATIO_RUN;
}
}
}
else
{
params.m_fMoveBlendRatio = MOVEBLENDRATIO_RUN;
}
params.m_fSlowDownDistance = 0.0f;
//Create the move task.
CTaskMoveFollowNavMesh* pMoveTask = rage_new CTaskMoveFollowNavMesh( params );
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
taskAssertf( pBrawlData, "CTaskMelee::ChaseAfterTarget_OnEnter - Melee opponent without brawling style" );
//Keep moving while waiting for a path.
float fKeepMovingWhilePathingDistance = 2.0f;
float fMaxDistanceMayAdjustPathEndPosition = 1.5f;
float fTargetRadius = CTaskMoveFollowNavMesh::ms_fTargetRadius;
if( IsLosBlocked() )
{
fTargetRadius = sm_fAiSeekModeActiveCombatantRadius;
fKeepMovingWhilePathingDistance = pBrawlData ? pBrawlData->GetKeepMovingWhilePathingDistance() : 2.0f;
fMaxDistanceMayAdjustPathEndPosition = pBrawlData ? pBrawlData->GetMaxDistanceMayAdjustPathEndPosition() : 1.5f;
}
else if( pBrawlData )
{
fKeepMovingWhilePathingDistance = pBrawlData->GetKeepMovingWhilePathingDistance();
fMaxDistanceMayAdjustPathEndPosition = pBrawlData->GetMaxDistanceMayAdjustPathEndPosition();
fTargetRadius = pBrawlData->GetTargetRadius();
}
pMoveTask->SetKeepMovingWhilstWaitingForPath( true, fKeepMovingWhilePathingDistance );
pMoveTask->SetMaxDistanceMayAdjustPathEndPosition( fMaxDistanceMayAdjustPathEndPosition );
pMoveTask->SetTargetRadius( fTargetRadius );
pMoveTask->SetAvoidObjects( true );
pMoveTask->SetNeverEnterWater( true );
pMoveTask->SetDontStandStillAtEnd( true );
pMoveTask->SetUseLargerSearchExtents( true );
SetNewTask( rage_new CTaskComplexControlMovement( pMoveTask ) );
pPed->SetUsingActionMode( true, CPed::AME_Combat );
pPed->SetMovementModeOverrideHash( ATSTRINGHASH("DEFAULT_ACTION", 0x076f1bf8d) );
if( ShouldAssistMotionTask() )
ProcessMotionTaskAssistance( true );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ChaseAfterTarget_OnUpdate
// PURPOSE : (AI ONLY) Will monitor arrival conditions to stop the subtask also
// make sure we are facing the target when strafing
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::ChaseAfterTarget_OnUpdate( CPed* pPed )
{
// If the task finishes, go back to searching for a new melee action
if( GetIsFlagSet( aiTaskFlags::SubTaskFinished ) || ShouldBeInInactiveTauntMode() )
{
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
pPed->SetUsingActionMode( true, CPed::AME_Combat );
CTaskMoveFollowNavMesh* pNavMeshTask = static_cast<CTaskMoveFollowNavMesh*>( pPed->GetPedIntelligence()->FindMovementTaskByType( CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH ) );
if( pNavMeshTask )
{
if( pNavMeshTask->IsUnableToFindRoute() )
{
SetTaskFlag( MF_CannotFindNavRoute );
return FSM_Continue;
}
// Check to see if we should re-evaluate the seek timer
if( m_seekModeScanTimer.IsOutOfTime() )
pNavMeshTask->SetTarget( pPed, CalculateDesiredChasePosition( pPed ) );
//Keep moving while waiting for a path.
float fTargetRadius = CTaskMoveFollowNavMesh::ms_fTargetRadius;
if( IsLosBlocked() )
fTargetRadius = sm_fAiSeekModeActiveCombatantRadius;
else
{
const CBrawlingStyleData* pBrawlData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
if( taskVerifyf( pBrawlData, "CTaskMelee::ChaseAfterTarget_OnUpdate - Melee opponent without brawling style" ) )
fTargetRadius = pBrawlData->GetTargetRadius();
}
pNavMeshTask->SetTargetRadius( fTargetRadius );
}
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
// Always face the player when standing still waiting
float fMoveBlendRatioSq = pPed->GetMotionData()->GetCurrentMoveBlendRatio().Mag2();
if( fMoveBlendRatioSq < rage::square( CActionManager::ms_fMBRWalkBoundary ) )
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( vTargetPosition ) );
// Only test the stop conditions if our line of sight is not blocked
if( !IsLosBlocked() )
{
Vec3V vDelta = vTargetPosition - pPed->GetTransform().GetPosition();
Vec3V vDeltaXY( vDelta.GetXf(), vDelta.GetYf(), 0.0f );
float fArrivalThreshold = GetDesiredTargetDistance();
if( Abs( vDelta.GetZf() ) < sm_fAiSeekModeTargetHeight && MagSquared( vDeltaXY ).Getf() < rage::square( fArrivalThreshold ) )
{
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
}
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ChaseAfterTarget_OnExit
// PURPOSE : Clean up for the ChaseAfterTarget state
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ChaseAfterTarget_OnExit( void )
{
// Reset the seek mode probe
u32 uCurrentTime = fwTimer::GetTimeInMilliseconds();
m_seekModeLosTestNextRequestTimer.Set( uCurrentTime, 0 );
// Reset any motion task assistance.
ProcessMotionTaskAssistance( false );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : RetreatToSafeDistance_OnEnter
// PURPOSE : (AI ONLY) In charge of making the ped retreat while waiting for
// a valid path to be processed.
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::RetreatToSafeDistance_OnEnter( CPed* pPed )
{
taskAssertf( !pPed->IsPlayer(), "CTaskMelee::RetreatToSafeDistance_OnEnter - Player peds not allowed!" );
CEntity* pTargetEntity = GetTargetEntity();
if( taskVerifyf( pTargetEntity, "CTaskMelee::RetreatToSafeDistance_OnEnter - No target entity!" ) )
{
ProcessUnreachableNavMeshTest( pPed, pTargetEntity );
//Set up the nav mesh.
CNavParams params;
// Grab a random retreat distance which is in range
const float fDesiredRetreatDist = fwRandom::GetRandomNumberInRange( sm_fAiSeekModeRetreatDistanceMin, sm_fAiSeekModeRetreatDistanceMax );
// Set the desired offset distance
Vec3V vPredictedPosition( VEC3_ZERO );
vPredictedPosition.SetYf( fDesiredRetreatDist );
vPredictedPosition = RotateAboutZAxis( vPredictedPosition, ScalarV( GetAngleSpread() ) );
vPredictedPosition += pTargetEntity->GetTransform().GetPosition();
params.m_vTarget = VEC3V_TO_VECTOR3( vPredictedPosition );
params.m_fMoveBlendRatio = MOVEBLENDRATIO_RUN;
params.m_fSlowDownDistance = 0.0f;
//Create the move task.
CTaskMoveFollowNavMesh* pMoveTask = rage_new CTaskMoveFollowNavMesh( params );
// Set up some additional information
pMoveTask->SetKeepMovingWhilstWaitingForPath( false, 0.0f );
pMoveTask->SetTargetRadius( CTaskMoveFollowNavMesh::ms_fTargetRadius );
pMoveTask->SetAvoidObjects( true );
pMoveTask->SetNeverEnterWater( true );
pMoveTask->SetDontStandStillAtEnd( false );
SetNewTask( rage_new CTaskComplexControlMovement( pMoveTask ) );
pPed->SetUsingActionMode( true, CPed::AME_Combat );
pPed->SetMovementModeOverrideHash( ATSTRINGHASH("DEFAULT_ACTION", 0x076f1bf8d) );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : RetreatToSafeDistance_OnUpdate
// PURPOSE : (AI ONLY) Monitor conditions is which the retreat will move on
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::RetreatToSafeDistance_OnUpdate( CPed* pPed )
{
// If the task finishes, go back to searching for a new melee action
if( GetIsFlagSet( aiTaskFlags::SubTaskFinished ) )
{
SetState( State_WaitForTarget );
return FSM_Continue;
}
// If we have resolved the reason why we came into this state, then re-evaluate
if( !ShouldBeInInactiveTauntMode() )
{
SetState( State_WaitForMeleeActionDecide );
m_RouteSearchHelper.ResetSearch();
return FSM_Continue;
}
pPed->SetUsingActionMode( true, CPed::AME_Combat );
CTaskMoveFollowNavMesh* pNavMeshTask = static_cast<CTaskMoveFollowNavMesh*>( pPed->GetPedIntelligence()->FindMovementTaskByType( CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH ) );
if( pNavMeshTask )
{
if( pNavMeshTask->IsUnableToFindRoute() )
{
SetState( State_WaitForTarget );
return FSM_Continue;
}
}
// Make the ped try to face the target.
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() ) );
ProcessUnreachableNavMeshTest( pPed, pTargetEntity );
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForTarget_OnEnter
// PURPOSE : (AI ONLY) In charge of waiting in the current location until a
// reachable path has been processed
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::WaitForTarget_OnEnter( CPed* pPed )
{
taskAssertf( !pPed->IsPlayer(), "CTaskMelee::WaitForTarget_OnEnter - Player peds not allowed!" );
ProcessUnreachableNavMeshTest( pPed, GetTargetEntity() );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForTarget_OnUpdate
// PURPOSE : (AI ONLY) Monitor conditions is which the wait will finish
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::WaitForTarget_OnUpdate( CPed* pPed )
{
// If we have resolved the reason why we came into this state, then re-evaluate
if( !ShouldBeInInactiveTauntMode() )
{
SetState( State_WaitForMeleeActionDecide );
return FSM_Continue;
}
// Make the ped try to face the target.
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() ) );
ProcessUnreachableNavMeshTest( pPed, pTargetEntity );
return ManageActionSubtaskAndBranches( pPed );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForTarget_OnExit
// PURPOSE : (AI ONLY) Wait target clean up
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::WaitForTarget_OnExit( void )
{
if( m_RouteSearchHelper.IsSearchActive() )
m_RouteSearchHelper.ResetSearch();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForMeleeAction_OnEnter
// PURPOSE : Main wait state
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::WaitForMeleeAction_OnEnter( void )
{
ResetImpulseCombo();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForMeleeAction_OnUpdate
// PURPOSE : Monitor impulses(player) or when ai should attack
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::WaitForMeleeAction_OnUpdate( CPed* pPed )
{
#if FPS_MODE_SUPPORTED
bool bWasBlocking = IsBlocking();
#endif
// Check to see if we are in stealth mode
if( GetIsTaskFlagSet( MF_BehindTargetPed ) )
UpdateStealth( pPed );
else if( GetIsTaskFlagSet( MF_AllowStrafeMode ) )
UpdateBlocking( pPed );
else
{
// Abort upper body anims if we are not running either stealth nor blocking
aiTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_UPPERBODY_ANIM )
{
if( pSubTask->GetIsFlagSet( aiTaskFlags::HasBegun ) )
pSubTask->MakeAbortable( CTask::ABORT_PRIORITY_URGENT, NULL );
}
}
#if FPS_MODE_SUPPORTED
if( !GetNewTask() )
{
aiTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_PLAYER_WEAPON )
{
if( pSubTask->GetIsFlagSet( aiTaskFlags::HasBegun ) && !pPed->IsFirstPersonShooterModeEnabledForPlayer() )
{
pSubTask->MakeAbortable( aiTask::ABORT_PRIORITY_URGENT, NULL );
}
}
else if( ( GetIsFlagSet( aiTaskFlags::SubTaskFinished ) || !pSubTask || pSubTask->GetIsFlagSet( aiTaskFlags::IsAborted) ) && pPed->IsFirstPersonShooterModeEnabledForPlayer() )
{
s32 iGunFlags = GF_ForceAimState;
if( bWasBlocking && !IsBlocking() )
{
iGunFlags |= GF_InstantBlendToAim;
}
SetNewTask( rage_new CTaskPlayerWeapon( iGunFlags ) );
}
}
#endif
// Quit task if we are waiting and need to switch to a non-melee related weapon
// Unfortunately this bit of logic is somewhat duplicated in GetDesiredStateAndUpdateBreakoutConditions
// but is necessary to get around the fact that script changes weapons without caring much.
if( !pPed->IsNetworkClone() )
{
const CPedWeaponManager* pPedWeaponManager = pPed->GetWeaponManager();
if( pPedWeaponManager && pPedWeaponManager->GetRequiresWeaponSwitch() )
{
const CWeaponInfo* pWeaponInfo = pPedWeaponManager->GetEquippedWeaponInfo();
if( pWeaponInfo && !pWeaponInfo->GetIsMelee() )
{
taskDebugf1("Quit: CTaskMelee::WaitForMeleeAction_OnUpdate: pWeaponInfo && !pWeaponInfo->GetIsMelee()");
SetState( State_Finish );
return FSM_Continue;
}
}
//! If locked on to a friendly ped (e.g. chop) bail out of melee task (indicates we locked on after melee happened).
if(pPed->IsLocalPlayer())
{
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
if(rTargeting.GetLockOnTarget() && rTargeting.GetLockOnTarget()->GetIsTypePed())
{
CPed* pTargetPed = static_cast<CPed*>(rTargeting.GetLockOnTarget());
if(pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_AllowPlayerLockOnIfFriendly))
{
taskDebugf1("Quit: CTaskMelee::WaitForMeleeAction_OnUpdate: CPED_CONFIG_FLAG_AllowPlayerLockOnIfFriendly");
SetState( State_Finish );
return FSM_Continue;
}
}
#if FPS_MODE_SUPPORTED
// B*1966069: If FPS swimming, stop waiting for melee action (causes ped and camera to freak out)
if (pPed->GetIsFPSSwimming() && pPed->GetPedResetFlag(CPED_RESET_FLAG_FPSSwimUseSwimMotionTask))
{
SetState( State_Finish );
return FSM_Continue;
}
#endif //FPS_MODE_SUPPORTED
}
}
UpdateBlockingTargetEvaluation( pPed );
return ManageActionSubtaskAndBranches( pPed );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : WaitForMeleeAction_OnExit
// PURPOSE : Clean up any previously set upperbody anim flags
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::WaitForMeleeAction_OnExit( void )
{
m_bUsingStealthClipClose = m_bUsingBlockClips = false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : InactiveObserverIdle_OnUpdate
// PURPOSE : Main heartbeat for the Inactive Observer state
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::InactiveObserverIdle_OnUpdate( CPed* pPed )
{
// Make the ped try to face the target.
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
if ( ShouldUpdateDesiredHeading( *pPed, vTargetPosition ) )
{
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( vTargetPosition ) );
}
}
return ManageActionSubtaskAndBranches( pPed );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckForActionBranch
// PURPOSE : Clean up any previously set upperbody anim flags
// PARAMETERS : pPed - Ped we are interested in
// pActionResultTask - Currently running melee action
// newState - Reference to the new state we would like to transition to
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::CheckForActionBranch( CPed* pPed, CTaskMeleeActionResult* pActionResultTask, MeleeState& newState)
{
// Check for any branching off of the current action (including reactions).
u32 nBranchActionId = 0;
if( CheckForBranch( pPed, pActionResultTask, nBranchActionId ) )
{
u32 nActionDefnitionIdx = 0;
const CActionDefinition* pBranchesActionDefinition = ACTIONMGR.FindActionDefinition( nActionDefnitionIdx, nBranchActionId);
Assert( pBranchesActionDefinition );
// Get the pointer to the actionResult from the action Id.
const CActionResult* pBranchesActionResult = pBranchesActionDefinition->GetActionResult( pPed );
Assertf( pBranchesActionResult, "no suitable action result for definition (%s)\n", pBranchesActionDefinition->GetName() );
// Check if we should fall out of melee combat or go into the branch...
if( !pBranchesActionResult )
{
// Abort the action task (and possibly go into ragdoll mode).
if( pActionResultTask && pActionResultTask->MakeAbortable( CTask::ABORT_PRIORITY_URGENT, NULL ) )
{
taskDebugf1("Quit: CTaskMelee::CheckForActionBranch: pActionResultTask && pActionResultTask->MakeAbortable( CTask::ABORT_PRIORITY_URGENT, NULL )");
newState = State_Finish;
return true;
}
}
else
{
// Set up the reaction info
m_pNextActionResult = pBranchesActionResult;
// If we've just entered a held idle, we tell the melee task to reset the combo
// we do this by saying the new action is an initial combo action
m_bActionIsBranch = pActionResultTask ? pActionResultTask->IsDoingSomething() : false;
// Record the type of impulse for combo 1 (combos 2 and 3 can be found in m_impulseCombo
if( pBranchesActionDefinition && pBranchesActionDefinition->GetImpulseTest() )
{
if( !m_bActionIsBranch || pBranchesActionResult->GetShouldResetCombo() )
{
if( pBranchesActionDefinition->GetImpulseTest()->GetNumImpulses() > 0 )
m_impulseCombo.RecordFirstImpulse( pBranchesActionDefinition->GetImpulseTest()->GetImpulseByIndex( 0 )->GetImpulse() );
}
else
{
// Mark the impulse as executed so that button presses can no longer interrupt the impulse
const CSimpleImpulseTest* pImpulseTest = pBranchesActionDefinition->GetImpulseTest()->GetNumImpulses() > 0 ? pBranchesActionDefinition->GetImpulseTest()->GetImpulseByIndex( 0 ) : NULL;
if( pImpulseTest )
{
CSimpleImpulseTest::ImpulseState impulseState = pImpulseTest->GetImpulseState();
if( impulseState >= CSimpleImpulseTest::ImpulseStateCombo1 && impulseState <= CSimpleImpulseTest::ImpulseStateCombo3 )
{
s32 iIndex = impulseState - CSimpleImpulseTest::ImpulseStateCombo1;
if( m_impulseCombo.GetImpulsePtrAtIndex( iIndex ) )
m_impulseCombo.GetImpulsePtrAtIndex( iIndex )->SetHasExecuted( true );
}
}
}
}
if( m_pNextActionResult->GetIsAStealthKill() )
{
if( pBranchesActionDefinition->GetImpulseTest() && pBranchesActionDefinition->GetImpulseTest()->GetNumImpulses() > 0 )
m_impulseCombo.RecordFirstImpulse( pBranchesActionDefinition->GetImpulseTest()->GetImpulseByIndex( 0 )->GetImpulse() );
}
if(pPed->IsLocalPlayer() && !CNewHud::IsShowingHUDMenu())
{
newState = State_MeleeAction;
return true;
}
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ManageActionSubtaskAndBranches
// PURPOSE : Main update function in regards to starting subtasks and branching
// from the current subtask
// PARAMETERS : pPed - the ped we are interested in.
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::ManageActionSubtaskAndBranches( CPed* pPed )
{
// If clone, just take direction from host.
if( pPed->IsNetworkClone() )
{
MeleeState desiredState = State_Invalid;
if( m_bLocalReaction && m_pNextActionResult )
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMelee::ManageActionSubtaskAndBranches - Player = %s State = %d", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), GetState());
m_bLocalReaction = false;
desiredState = State_MeleeAction;
}
else
{
if( CheckCloneForLocalBranch( pPed ) )
desiredState = State_MeleeAction;
}
if( desiredState != State_Invalid )
{
if( desiredState == GetState() )
SetFlag( aiTaskFlags::RestartCurrentState );
else
SetState( desiredState );
}
return FSM_Continue;
}
// Make sure we don't want to stop this task (i.e. check for times when
// the ped should stop being in fight mode).
MeleeState desiredState = GetDesiredStateAndUpdateBreakoutConditions( pPed );
if( desiredState != State_Invalid )
{
if( desiredState == GetState() )
SetFlag( aiTaskFlags::RestartCurrentState );
else
SetState( desiredState );
return FSM_Continue;
}
// Get the action task.
CTaskMeleeActionResult* pActionResultTask = NULL;
CTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT )
pActionResultTask = static_cast<CTaskMeleeActionResult*>( pSubTask );
// Update the movement task, if it is the Melee movement task & not controlled by a CtrlMove task.
if( pPed->IsPlayer() || (GetQueueType() > QT_InactiveObserver) )
{
bool bAllowMovementInput = !ShouldBeInInactiveTauntMode() && !GetIsTaskFlagSet( MF_PlayTauntBeforeAttacking );
bool bAllowStrafing = GetIsTaskFlagSet( MF_AllowStrafeMode );
bool bAllowHeadingUpdate = true;
bool bAllowStealth = GetIsTaskFlagSet( MF_AllowStealthMode );
// Get the action task.
if( pActionResultTask )
{
// Make sure the simple action has the same target as the melee task.
pActionResultTask->SetTargetEntity( GetTargetEntity(), HasLockOnTargetEntity() );
bAllowMovementInput = pActionResultTask->AllowMovement();
bAllowStrafing = pActionResultTask->AllowStrafing();
bAllowHeadingUpdate = pActionResultTask->AllowHeadingUpdate();
bAllowStealth = pActionResultTask->AllowStealthMode();
}
// Special case code to prevent animals from doing small heading updates
else if( pPed->GetPedType() == PEDTYPE_ANIMAL && m_pTargetEntity )
{
static dev_float sfHeadingUpdateRadThreshold = 0.35f; // 20 degrees
Vec3V vTargetPosition = m_pTargetEntity->GetTransform().GetPosition();
Vec3V vPedPosition = pPed->GetTransform().GetPosition();
float fDesiredHeading = fwAngle::GetRadianAngleBetweenPoints( vTargetPosition.GetXf(), vTargetPosition.GetYf(), vPedPosition.GetXf(), vPedPosition.GetYf() );
const float fAngle = SubtractAngleShorter( pPed->GetCurrentHeading(), fDesiredHeading );
if( fabs( fAngle ) < sfHeadingUpdateRadThreshold )
{
bAllowHeadingUpdate = false;
}
}
MeleeMoveTaskControl( pPed, bAllowMovementInput, bAllowStrafing, bAllowHeadingUpdate, bAllowStealth, m_pAvoidEntity );
}
// Check for an action branch
desiredState = State_Invalid;
if( !m_pNextActionResult && CheckForActionBranch( pPed, pActionResultTask, desiredState ) )
{
taskAssertf( desiredState != State_Invalid, "Branch activated but no state returned!" );
if( desiredState == GetState() )
SetFlag( aiTaskFlags::RestartCurrentState );
else
SetState( desiredState );
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateBlocking
// PURPOSE : (PLAYER ONLY) In charge of monitoring blocking functionality
// including animations.
// PARAMETERS : pPed - the ped we are interested in.
// bManageUpperbodyAnims - Whether or not we should manage the upperbody anims
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UpdateBlocking( CPed* pPed, bool bManageUpperbodyAnims )
{
// Do not allow blocking for weapons that do not support it
const CWeaponInfo* pEquippedWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
if( !pEquippedWeaponInfo || !pEquippedWeaponInfo->GetAllowMeleeBlock() )
return;
bool bHasChanged = m_bUsingStealthClipClose;
m_bUsingStealthClipClose = false;
const CControl* pControl = pPed->GetControlFromPlayer();
if( pControl )
{
// Tick block timer
ioValue const * ioVal0 = NULL;
ioValue const * ioVal1 = NULL;
CSimpleImpulseTest::GetIOValuesForImpulse( pControl, CSimpleImpulseTest::ImpulseBlock, ioVal0, ioVal1 );
if( ioVal0->IsDown() )
{
if( !IsBlocking() )
{
m_fBlockDelayTimer += fwTimer::GetTimeStep();
if( m_fBlockDelayTimer > CTaskMelee::sm_fBlockTimerDelayEnter )
{
m_bIsBlocking = true;
m_fBlockDelayTimer = CTaskMelee::sm_fBlockTimerDelayExit;
}
}
else
m_fBlockDelayTimer = CTaskMelee::sm_fBlockTimerDelayExit;
}
else
{
if( IsBlocking() )
{
m_fBlockDelayTimer -= fwTimer::GetTimeStep();
if( m_fBlockDelayTimer < 0.0f )
{
m_fBlockDelayTimer = 0.0;
m_bIsBlocking = false;
}
}
else
m_fBlockDelayTimer = 0.0;
}
}
if( bManageUpperbodyAnims )
{
// Update blocking anims
aiTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_UPPERBODY_ANIM )
{
if( pSubTask->GetIsFlagSet( aiTaskFlags::HasBegun ) )
{
if( ( !IsBlocking() || bHasChanged ) )
{
pSubTask->MakeAbortable( CTask::ABORT_PRIORITY_URGENT, NULL );
}
#if FPS_MODE_SUPPORTED
else if( pPed->IsFirstPersonShooterModeEnabledForPlayer(false) )
{
// Need to bring in left hand IK ourselves which can only be processed later in the frame
pPed->SetPedResetFlag(CPED_RESET_FLAG_ProcessPreRender2, true);
}
#endif
}
}
else if( IsBlocking() )
{
const CWeaponInfo* pEquippedWeaponInfo = pPed->GetWeaponManager()->GetEquippedWeaponInfo();
if( pEquippedWeaponInfo )
{
fwMvFilterId filterId("Upperbody_filter",0x35B1D869);
float fBlendDuration = NORMAL_BLEND_DURATION;
#if FPS_MODE_SUPPORTED
// In first person when we start up the upper body animations that will automatically switch us from the aiming state to the
// strafe state. Need to ensure that we skip the strafe into anim in this case
if( pPed->IsFirstPersonShooterModeEnabledForPlayer(false) )
{
fBlendDuration = 0.3f;
CTaskMotionPed* pCurrentMotionTask = static_cast<CTaskMotionPed*>(pPed->GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_MOTION_PED));
if( pCurrentMotionTask )
{
pCurrentMotionTask->SetTaskFlag( CTaskMotionPed::PMF_SkipStrafeIntroAnim );
}
// Need to bring in left hand IK ourselves which can only be processed later in the frame
pPed->SetPedResetFlag(CPED_RESET_FLAG_ProcessPreRender2, true);
}
#endif
//! Blocking flag can be out of sync with equipped weapon, so ensure we have anims 1st.
if(fwAnimManager::GetClipIfExistsBySetId(pEquippedWeaponInfo->GetMeleeClipSetId(*pPed), CLIP_MELEE_BLOCK_INDICATOR))
{
SetNewTask( rage_new CTaskMeleeUpperbodyAnims( pEquippedWeaponInfo->GetMeleeClipSetId(*pPed), CLIP_MELEE_BLOCK_INDICATOR, filterId, fBlendDuration, CTaskMeleeUpperbodyAnims::AF_IsLooped ) );
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateBlockingTargetEvaluation
// PURPOSE : (PLAYER ONLY) In charge of switching to a better target when we
// attempt to block/dodge.
// PARAMETERS : pPed - the ped we are interested in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UpdateBlockingTargetEvaluation(CPed* pPed)
{
const CControl* pControl = pPed->GetControlFromPlayer();
if( pControl )
{
ioValue const * ioVal0 = NULL;
ioValue const * ioVal1 = NULL;
CSimpleImpulseTest::GetIOValuesForImpulse( pControl, CSimpleImpulseTest::ImpulseBlock, ioVal0, ioVal1 );
if( ioVal0->IsPressed() )
{
//! We are attempting to dodge - allow ped to re-pick lock on target.
if(pPed->IsLocalPlayer())
{
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
if(rTargeting.GetLockOnTarget())
{
CEntity *pTargetEntity = rTargeting.FindLockOnMeleeTarget(pPed);
if(pTargetEntity)
{
SetTargetEntity(pTargetEntity, true, true);
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateStealth
// PURPOSE : (PLAYER ONLY) In charge of monitoring stealth functionality
// including animations.
// PARAMETERS : pPed - the ped we are interested in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::UpdateStealth( CPed* pPed )
{
bool bHasChanged = m_bCanBlock || m_bIsBlocking;
m_bIsBlocking = false;
CImpulseCombo defaultStealthImpulse( CSimpleImpulseTest::ImpulseAttackLight );
CActionFlags::ActionTypeBitSetData typeToLookFor;
typeToLookFor.Set( CActionFlags::AT_MELEE, true );
const CActionDefinition* pActionDef = ACTIONMGR.SelectSuitableStealthKillAction( pPed, GetTargetEntity(), true, &defaultStealthImpulse, typeToLookFor );
if( pActionDef != NULL )
m_bUsingStealthClipClose = true;
else
{
bHasChanged |= m_bUsingStealthClipClose;
m_bUsingStealthClipClose = false;
}
// Update blocking anims
aiTask* pSubTask = GetSubTask();
if( pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_MELEE_UPPERBODY_ANIM )
{
if( pSubTask->GetIsFlagSet( aiTaskFlags::HasBegun ) && bHasChanged )
pSubTask->MakeAbortable( CTask::ABORT_PRIORITY_URGENT, NULL );
}
else if( m_bUsingStealthClipClose )
{
const CWeaponInfo* pEquippedWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
if( pEquippedWeaponInfo )
{
bool bAnimsStreamed = false;
fwMvFilterId filterId( "UpperbodyFeathered_filter", 0xfcdd409 );
fwMvClipSetId weaponClipSetId;
strLocalIndex nAnimBlockIndex = strLocalIndex(-1);
if( pEquippedWeaponInfo->GetIsUnarmed() || pEquippedWeaponInfo->GetIsMeleeFist())
{
weaponClipSetId = pEquippedWeaponInfo->GetMeleeBaseClipSetId( *pPed );
if(weaponClipSetId == CLIP_SET_ID_INVALID)
{
weaponClipSetId = CLIP_SET_MELEE_UNARMED_BASE;
}
}
else
{
weaponClipSetId = pEquippedWeaponInfo->GetMeleeClipSetId( *pPed );
}
if( weaponClipSetId != CLIP_SET_ID_INVALID )
nAnimBlockIndex = fwClipSetManager::GetClipDictionaryIndex( weaponClipSetId );
// If we are not yet streamed in always use the base resident version
if( nAnimBlockIndex != -1 && CStreaming::HasObjectLoaded( nAnimBlockIndex, fwAnimManager::GetStreamingModuleId() ) )
{
bAnimsStreamed = true;
}
if( bAnimsStreamed )
{
SetNewTask( rage_new CTaskMeleeUpperbodyAnims( weaponClipSetId, CLIP_MELEE_STEALTH_KILL_INDICATOR_CLOSE, filterId, SLOW_BLEND_DURATION, CTaskMeleeUpperbodyAnims::AF_IsLooped ) );
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldAssistMotionTask
// PURPOSE : Check the motion task and see if it needs help reaching
// the target.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::ShouldAssistMotionTask()
{
CPed* pPed = GetPed();
CTaskMotionBase* pTask = pPed->GetPrimaryMotionTask();
return pTask && pTask->GetTaskType() == CTaskTypes::TASK_ON_FOOT_QUAD;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldUpdateDesiredHeading
// PURPOSE : Check if the ped needs to update its heading towards the target.
// This is done to reduce "swiveling"
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::ShouldUpdateDesiredHeading(const CPed& ped, Vec3V_In vTargetPosition)
{
// Non dogs don't have a problem with this.
if (!ped.GetTaskData().GetIsFlagSet(CTaskFlags::UseLooseHeadingAdjustmentsInMelee))
{
return true;
}
float fCurrentHeading = ped.GetCurrentHeading();
// Compute our current heading offset.
float fTargetHeading = fwAngle::GetRadianAngleBetweenPoints(vTargetPosition.GetXf(), vTargetPosition.GetYf(), ped.GetTransform().GetPosition().GetXf(), ped.GetTransform().GetPosition().GetYf());
// Check if it is big enough.
if (fabs(SubtractAngleShorter(fCurrentHeading, fTargetHeading)) > DtoR * sm_fLookAtTargetLooseToleranceDegrees)
{
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessMotionTaskAssistance
// PURPOSE : Set parameters to help the motion task in reaching the target.
// PARAMETERS : bAssistance - whether assistance is being set or unset.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ProcessMotionTaskAssistance(bool bAssitance)
{
CPed* pPed = GetPed();
CTaskMotionBase* pTask = pPed->GetPrimaryMotionTask();
if (pTask &&pTask->GetTaskType() == CTaskTypes::TASK_ON_FOOT_QUAD)
{
CTaskQuadLocomotion* pTaskQuadLocomotion = static_cast<CTaskQuadLocomotion*>(pTask);
pTaskQuadLocomotion->SetUsePursuitMode(bAssitance);
}
}
bool CTaskMelee::CanChangeTarget() const
{
if(GetEntity())
{
if(const CPed* pPed = GetPed())
{
if(pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DontChangeTargetFromMelee))
{
if(const CTaskCombat* pCombatTask = pPed->GetPedIntelligence()->GetTaskCombat())
{
if(pCombatTask->GetIsPreventChangingTargetSet() && m_pTargetEntity && m_pTargetEntity.Get() == pCombatTask->GetPrimaryTarget())
{
AI_LOG_WITH_ARGS("CTaskMelee::CanChangeTarget() returning false - pPed = %s, m_pTargetEntity = %s, task = %p \n",pPed->GetLogName(), m_pTargetEntity->GetLogName(), this);
return false;
}
AI_LOG_WITH_ARGS("CTaskMelee::CanChangeTarget() returning true - pPed = %s, m_pTargetEntity = %s, PrimaryTarget = %s, GetIsPreventChangingTargetSet = %s, task = %p \n", pPed->GetLogName(), m_pTargetEntity ? m_pTargetEntity->GetLogName() : "Null", pCombatTask->GetPrimaryTarget() ? pCombatTask->GetPrimaryTarget()->GetLogName() : "Null", pCombatTask->GetIsPreventChangingTargetSet() ? "TRUE" : "FALSE", this);
}
}
}
}
else
{
AI_LOG_WITH_ARGS("CTaskMelee::CanChangeTarget() returning true, couldn't get entity - m_pTargetEntity = %s, task = %p \n", m_pTargetEntity ? m_pTargetEntity->GetLogName() : "Null", this);
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// CTaskMeleeUpperbodyAnims
/////////////////////////////////////////////////////////////////////////////////
const fwMvClipId CTaskMeleeUpperbodyAnims::ms_UpperbodyClipId("UpperbodyClipId",0xFF6318E6);
const fwMvFilterId CTaskMeleeUpperbodyAnims::ms_UpperbodyFilterId("UpperbodyFilterId",0xABFB0DAF);
const fwMvBooleanId CTaskMeleeUpperbodyAnims::ms_IsClipLoopedId("IsClipLoopedId",0x1AD1A9D8);
const fwMvBooleanId CTaskMeleeUpperbodyAnims::ms_AnimClipFinishedId("OnClipFinished",0x88B0DC15);
const fwMvFlagId CTaskMeleeUpperbodyAnims::ms_HasGrip("HasGrip",0x1bcd14c6);
const fwMvClipSetVarId CTaskMeleeUpperbodyAnims::ms_GripClipSetId("GripClipSet", 0xcbd9a0f0);
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CTaskMeleeUpperbodyAnims ctor
// PURPOSE : Used to define an upperbody animation which can be looped
/////////////////////////////////////////////////////////////////////////////////
CTaskMeleeUpperbodyAnims::CTaskMeleeUpperbodyAnims( const fwMvClipSetId& clipSetId, const fwMvClipId& clipId, const fwMvFilterId& filterId, float fBlendDuration, fwFlags32 iFlags)
: CTask()
, m_ClipSetId( clipSetId )
, m_ClipId( clipId )
, m_FilterId( filterId )
, m_pFilter( NULL )
, m_fBlendDuration( fBlendDuration )
, m_nFlags( iFlags )
{
SetInternalTaskType(CTaskTypes::TASK_MELEE_UPPERBODY_ANIM);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CleanUp
// PURPOSE : Main CTask clean up function in charge of shutting down anything
// related to CTaskMeleeUpperbodyAnims
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeUpperbodyAnims::CleanUp( void )
{
CPed* pPed = GetPed();
if( pPed && m_MoveNetworkHelper.GetNetworkPlayer() )
{
pPed->GetMovePed().ClearTaskNetwork( m_MoveNetworkHelper, m_fBlendDuration );
m_MoveNetworkHelper.ReleaseNetworkPlayer();
}
if( m_pFilter )
{
m_pFilter->Release();
m_pFilter = NULL;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateFSM
// PURPOSE : Main heartbeat function
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeUpperbodyAnims::UpdateFSM( const s32 iState, const FSM_Event iEvent )
{
CPed* pPed = GetPed();
FSM_Begin
FSM_State(State_Start)
FSM_OnUpdate
return Start_OnUpdate( pPed );
FSM_State(State_PlayAnim)
FSM_OnUpdate
return PlayAnim_OnUpdate();
FSM_State(State_Finish)
FSM_OnUpdate
return FSM_Quit;
FSM_End
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Start_OnUpdate
// PURPOSE : Preps the MoVE network associated with this instance
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeUpperbodyAnims::Start_OnUpdate( CPed* pPed )
{
if( PrepareMoveNetwork( pPed ) )
{
SetState( State_PlayAnim );
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlayAnim_OnUpdate
// PURPOSE : Monitors when the animation finishes (non looping) otherwise will
// wait for an outside influence to quit.
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeUpperbodyAnims::PlayAnim_OnUpdate()
{
taskAssert( m_MoveNetworkHelper.IsNetworkActive() );
if( m_MoveNetworkHelper.GetBoolean( ms_AnimClipFinishedId ) )
SetState( State_Finish );
return FSM_Continue;
}
#if !__FINAL
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION: GetStaticStateName
// PURPOSE: Simply returns the state name
// PARAMETERS: iState - State we are interested in
/////////////////////////////////////////////////////////////////////////////////
const char* CTaskMeleeUpperbodyAnims::GetStaticStateName( s32 iState )
{
switch( iState )
{
case State_Start: return "State_Start";
case State_PlayAnim: return "State_PlayAnim";
case State_Finish: return "State_Finish";
default: taskAssert(0);
}
return "State_Invalid";
}
#endif
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PrepareMoveNetwork
// PURPOSE : Initialize the move network associated with this upperbody animation
// PARAMETERS : pPed - the ped of interest.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeUpperbodyAnims::PrepareMoveNetwork( CPed* pPed )
{
// Wait until the network worker is streamed in and initialized
if ( !m_MoveNetworkHelper.IsNetworkActive() )
{
m_MoveNetworkHelper.RequestNetworkDef( CClipNetworkMoveInfo::ms_NetworkTaskMeleeUpperbodyAnims );
if( m_MoveNetworkHelper.IsNetworkDefStreamedIn( CClipNetworkMoveInfo::ms_NetworkTaskMeleeUpperbodyAnims ) )
{
m_MoveNetworkHelper.CreateNetworkPlayer( pPed, CClipNetworkMoveInfo::ms_NetworkTaskMeleeUpperbodyAnims );
pPed->GetMovePed().SetTaskNetwork( m_MoveNetworkHelper.GetNetworkPlayer(), m_fBlendDuration, CMovePed::Task_None );
// ClipSet and Clip
fwClipSet* pSet = fwClipSetManager::GetClipSet( m_ClipSetId );
//! If we can't find clip (which can happen for clones if they are holding wrong weapon), fall back to a known clip set
//! that works.
if(pSet && pSet->Request_DEPRECATED())
{
const crClip* pClip = fwClipSetManager::GetClip( m_ClipSetId, m_ClipId );
if(!pClip)
{
static const fwMvClipSetId CLIP_SET_MELEE_FALLBACK("MELEE@UNARMED@STREAMED_CORE",0xfd5a244a);
m_ClipSetId = CLIP_SET_MELEE_FALLBACK;
pSet = fwClipSetManager::GetClipSet( m_ClipSetId );
}
}
taskAssertf( pSet, "Failed to find clip set %s", m_ClipSetId.GetCStr() );
if( pSet && pSet->Request_DEPRECATED() )
{
m_MoveNetworkHelper.SetClipSet( m_ClipSetId );
const crClip* pClip = fwClipSetManager::GetClip( m_ClipSetId, m_ClipId );
Assertf( pClip, "Failed to find clip %s in clip set %s", m_ClipId.GetCStr(), m_ClipSetId.GetCStr() );
if( pClip )
{
m_MoveNetworkHelper.SetClip( pClip, ms_UpperbodyClipId );
m_MoveNetworkHelper.SetBoolean( ms_IsClipLoopedId, m_nFlags.IsFlagSet( AF_IsLooped ) );
}
}
// Anim filter
if( m_FilterId.GetHash() != 0 )
{
m_pFilter = CGtaAnimManager::FindFrameFilter( m_FilterId.GetHash(), pPed );
if( m_pFilter )
m_pFilter->AddRef();
m_MoveNetworkHelper.SetFilter( m_pFilter, ms_UpperbodyFilterId );
}
bool bUseGrip = false;
if (pPed->GetWeaponManager() && pPed->GetWeaponManager()->GetEquippedWeaponInfo() && pPed->GetWeaponManager()->GetEquippedWeaponInfo()->GetIsMeleeFist())
{
fwMvClipSetId gripClipSetId = pPed->GetWeaponManager()->GetEquippedWeaponInfo()->GetMeleeClipSetId(*pPed);
if (gripClipSetId != CLIP_SET_ID_INVALID)
{
fwMvClipId gripClipId("GRIP_IDLE", 0x3ec63b58);
const crClip* pGripClip = fwClipSetManager::GetClip( gripClipSetId, gripClipId );
if (pGripClip != NULL)
{
bUseGrip = true;
m_MoveNetworkHelper.SetClipSet(gripClipSetId, ms_GripClipSetId);
crFrameFilter *pFilter = CTaskMelee::GetMeleeGripFilter(gripClipSetId);
if (pFilter)
{
m_MoveNetworkHelper.SetFilter(pFilter, CTaskHumanLocomotion::ms_MeleeGripFilterId);
}
}
}
}
m_MoveNetworkHelper.SetFlag( bUseGrip, ms_HasGrip );
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// CTaskMoveMeleeMovement
//
// Enables individual peds to move around while in combat. This
// is usually running underneath any action moves. The action moves always
// have higher priority and override the clip being provided by this.
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CTaskMoveMeleeMovement
// PURPOSE : The default constructor.
/////////////////////////////////////////////////////////////////////////////////
CTaskMoveMeleeMovement::CTaskMoveMeleeMovement( CEntity* pTargetEntity, bool bHasLockOnTargetEntity, bool bAllowMovement, bool bAllowStrafing, bool bAllowHeadingUpdate, bool bAllowStealth )
: // Initializer list.
CTaskMove (MOVEBLENDRATIO_STILL),
m_bIsStrafing (bAllowStrafing),
m_pTargetEntity (NULL), // Set in function body, below.
m_bHasLockOnTargetEntity (false), // Set in function body, below.
m_fTargetSeparationDesiredRange (1.0f),
m_fTargetSeparationMinRangeForImpetus (0.2f),
m_fTargetSeparationMaxRangeForImpetus (1.5f),
m_pAvoidEntity (NULL),
m_fAvoidEntityDesiredRange (1.0f),
m_fAvoidEntityMinRangeForImpetus (0.2f),
m_fAvoidEntityMaxRangeForImpetus (1.5f),
m_vPedMoveMomentumDir (VEC3_ZERO),
m_fPedMoveMomentumPortion (0.0f),
m_fPedMoveMomentumTime (0.0f),
m_bIsDoingSomething (false),
m_bAllowMovement (bAllowMovement),
m_bAllowMovementInput (bAllowMovement),
m_bAllowStrafing (bAllowStrafing),
m_bAllowHeadingUpdate (bAllowHeadingUpdate),
m_bAllowStealth (bAllowStealth),
m_bForceRun (false),
m_bCanMoveWhenTargetEnteringVehicle (true),
m_fStickAngle (0.0f),
m_bStickAngleTweaked (false),
m_uStealthLastPressed (0)
{
// Update the old target and the new target.
SetTargetEntity(pTargetEntity, bHasLockOnTargetEntity);
SetInternalTaskType(CTaskTypes::TASK_MOVE_MELEE_MOVEMENT);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Copy
// PURPOSE : The cloning operation- used by the task management system.
// RETURNS : An exact copy of the task.
/////////////////////////////////////////////////////////////////////////////////
aiTask* CTaskMoveMeleeMovement::Copy() const
{
return rage_new CTaskMoveMeleeMovement( GetTargetEntity(), HasLockOnTargetEntity(), m_bAllowMovement, m_bAllowStrafing, m_bAllowHeadingUpdate, m_bAllowStealth );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetTarget
// PURPOSE : Find out what the target position currently is.
// RETURNS : The current target position.
/////////////////////////////////////////////////////////////////////////////////
Vector3 CTaskMoveMeleeMovement::GetTarget( void ) const
{
Assertf( false, "CTaskMoveMeleeMovement : GetTarget() was called on move task with no target. Need to call HasTarget() on this task first to determine if GetTarget() is valid." );
return VEC3_ZERO;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetTargetRadius
// PURPOSE : Find out what the target radius currently is.
// RETURNS : The current target radius.
/////////////////////////////////////////////////////////////////////////////////
float CTaskMoveMeleeMovement::GetTargetRadius( void ) const
{
Assertf( false, "CTaskMoveMeleeMovement : GetTargetRadius() was called on move task with no target. Need to call HasTarget() on this task first to determine if GetTargetRadius() is valid." );
return 0.0f;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateFSM
// PURPOSE : Main heartbeat function
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMoveMeleeMovement::UpdateFSM( const s32 iState, const FSM_Event iEvent )
{
CPed *pPed = GetPed(); //Get the ped ptr.
FSM_Begin
FSM_State(Running)
FSM_OnEnter
StateRunning_OnEnter( pPed );
FSM_OnUpdate
return StateRunning_OnUpdate( pPed );
FSM_End
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : StateRunning_OnEnter
// PURPOSE : Randomize phase at which we will blend into strafing
// PARAMETERS : pPed - the ped we are interest in.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::StateRunning_OnEnter( CPed* pPed )
{
aiTask* pMotionPedTask = pPed->GetPedIntelligence()->GetTaskManager()->FindTaskByTypeActive( PED_TASK_TREE_MOTION, CTaskTypes::TASK_MOTION_PED );
if( pMotionPedTask )
{
CTaskMotionPed* pCurrentMotionTask = static_cast<CTaskMotionPed*>(pMotionPedTask );
if( pCurrentMotionTask && !pCurrentMotionTask->IsTaskFlagSet( CTaskMotionPed::PMF_SkipStrafeIntroAnim ) )
pCurrentMotionTask->SetInstantStrafePhaseSync( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : StateRunning_OnUpdate
// PURPOSE : Updates the peds movement (clips and position).
// PARAMETERS : pPed - the ped we are interest in.
// RETURNS : Whether or not this task is done.
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMoveMeleeMovement::StateRunning_OnUpdate( CPed* pPed )
{
Assertf( pPed, "CTaskMoveMeleeMovement::StateRunning_OnUpdate - pPed is invalid." );
DEV_BREAK_IF_FOCUS( CCombatMeleeDebug::sm_bFocusPedBreakOnTaskMeleeMovementStateRunning, pPed );
CEntity* pTargetEntity = GetTargetEntity();
if( pPed->IsLocalPlayer() )
{
// Set the player into strafing or walkabout mode.
// Handle changing the facing angle if strafing.
m_bIsStrafing = ShouldAllowStrafing();
if( m_bIsStrafing && pTargetEntity )
{
if( ShouldAllowMovementInput() )
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() ) );
}
// Handle the movement directives from the player.
if( m_bIsStrafing )
ProcessStrafeMove( pPed );
else
ProcessStdMove( pPed );
// Only process stealth mode inputs while they are allowed
if( ShouldAllowStealth() )
ProcessStealthMode( pPed );
}
else // This is an NPC...
{
// Note: This is really one of the major parts of our "Melee Combat AI"...
// Search for the quoted term in the line above to find the others.
// Make the ped try to face the target.
if( ShouldAllowHeadingUpdate() && pTargetEntity && CTaskMelee::ShouldUpdateDesiredHeading( *pPed, pTargetEntity->GetTransform().GetPosition() ) )
{
pPed->SetDesiredHeading( VEC3V_TO_VECTOR3( pTargetEntity->GetTransform().GetPosition() ) );
}
else if ( pPed->GetTaskData().GetIsFlagSet( CTaskFlags::UseLooseHeadingAdjustmentsInMelee ) )
{
pPed->SetDesiredHeading( pPed->GetCurrentHeading() );
}
// Should we allow strafing?
m_bIsStrafing = ShouldAllowStrafing();
pPed->GetMotionData()->SetIsStrafing( m_bIsStrafing );
if( DEV_ONLY( CCombatMeleeDebug::sm_bAllowNpcInitiatedMovement && )
CanMoveWhenTargetEnteringVehicle() &&
pTargetEntity &&
ShouldAllowMovement() )
{
// Determine how much in each direction we want to move.
float fForwardMoveComponent = 0.0f;
float fRightwardMoveComponent = 0.0f;
if( ShouldAllowMovementInput() )
{
Vec3V vPedPosition = pPed->GetTransform().GetPosition();
Vec3V vTargetPosition = pTargetEntity->GetTransform().GetPosition();
Vec3V vForwardHeading = pPed->GetTransform().GetB();
Vec3V vRightwardHeading = pPed->GetTransform().GetA();
// Determine how much in each direction we want to move to
// make the ped try to maintain the desired distance to the target.
float fForwardTargetBasedMoveComponent = 0.0f;
float fRightwardTargetBasedMoveComponent = 0.0f;
const CDefensiveArea* pDefensiveArea = pPed->GetPedIntelligence()->GetDefensiveArea();
GetPrefdDistanceMoveComponents( fForwardTargetBasedMoveComponent,// Out
fRightwardTargetBasedMoveComponent,// Out
vPedPosition,
vForwardHeading,
vRightwardHeading,
vTargetPosition,
m_fTargetSeparationDesiredRange,
false,
m_fTargetSeparationMinRangeForImpetus,
m_fTargetSeparationMaxRangeForImpetus,
CTaskMelee::sm_fAiTargetSeparationForwardImpetusPowerFactor,
CTaskMelee::sm_fAiTargetSeparationBackwardImpetusPowerFactor,
CTaskMelee::sm_fAiTargetSeparationMinTargetApproachDesireSize,
pDefensiveArea );
// Determine how much in each direction we want to move to
// make the ped try to maintain the desired distance from the avoid entity.
float fForwardAvoidBasedMoveComponent = 0.0f;
float fRightwardAvoidBasedMoveComponent = 0.0f;
if( m_pAvoidEntity )
{
Vec3V vAvoidCenter = m_pAvoidEntity->GetTransform().GetPosition();
GetPrefdDistanceMoveComponents( fForwardAvoidBasedMoveComponent,// Out
fRightwardAvoidBasedMoveComponent,// Out
vPedPosition,
vForwardHeading,
vRightwardHeading,
vAvoidCenter,
CTaskMelee::sm_fAiAvoidSeparationDesiredSelfToTargetRange,
true,
CTaskMelee::sm_fAiAvoidSeparationPrefdDiffMinForImpetus,
CTaskMelee::sm_fAiAvoidSeparationPrefdDiffMaxForImpetus,
CTaskMelee::sm_fAiAvoidSeparationForwardImpetusPowerFactor,
CTaskMelee::sm_fAiAvoidSeparationBackwardImpetusPowerFactor,
CTaskMelee::sm_fAiAvoidSeparationMinTargetApproachDesireSize,
pDefensiveArea );
// Add some extra movement left or right to make us circle around the avoid entity if they are ahead of us.
Vec3V vAvoidToSelf = vAvoidCenter - vPedPosition;
if( MagSquared( vAvoidToSelf ).Getf() < rage::square( CTaskMelee::sm_fAiAvoidSeparationPrefdDiffMinForImpetus ) )
{
Vec3V vAvoidToSelf = vTargetPosition - vAvoidCenter;
Vec3V vAvoidToTargetDir = Normalize( vAvoidToSelf );
Vec3V vSelfToTarget = vTargetPosition - vPedPosition;
Vec3V vSelfToTargetDir = Normalize( vSelfToTarget );
const float fAlignment = Dot( vSelfToTargetDir, vAvoidToTargetDir ).Getf();
Vec3V vCross = Cross( vSelfToTargetDir, vAvoidToTargetDir );
const float fSide = Sign( vCross.GetZf() );
// Add the circling impetus.
fRightwardAvoidBasedMoveComponent -= ( fSide * fAlignment * CTaskMelee::sm_fAiAvoidSeparationCirlclingStrength );
}
}
// Sum and clamp the different movement desires.
fForwardMoveComponent = Clamp( ( fForwardTargetBasedMoveComponent + fForwardAvoidBasedMoveComponent), -1.0f, 1.0f );
fRightwardMoveComponent = Clamp( ( fRightwardTargetBasedMoveComponent + fRightwardAvoidBasedMoveComponent), -1.0f, 1.0f );
}
// Mark whether we tried to move of our own accord or not.
m_bIsDoingSomething = ( fForwardMoveComponent != 0.0f ) || ( fRightwardMoveComponent != 0.0f );
// Set up the desired move blend:
// (x = left right 0 to 1; y = up down 0 to 1;)
Vector2 vDesiredMoveBlend( fRightwardMoveComponent, fForwardMoveComponent );
const CBrawlingStyleData* pData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
// Fixup the desired move blend.
if( vDesiredMoveBlend.Mag2() > 1.0f )
vDesiredMoveBlend /= vDesiredMoveBlend.Mag();
if( vDesiredMoveBlend.Mag2() > 0.0f )
vDesiredMoveBlend *= pData ? pData->GetMeleeMovementMBR() : MOVEBLENDRATIO_RUN;
// Set the desired move blend (which is smoothed internally) and force
// the momentum (hit) based movement (which won't be smoothed internally).
pPed->GetMotionData()->SetDesiredMoveBlendRatio( vDesiredMoveBlend.y, vDesiredMoveBlend.x );
float fMoveBlendRatio;
float fLateralMoveBlendRatio;
pPed->GetMotionData()->GetDesiredMoveBlendRatio( fLateralMoveBlendRatio, fMoveBlendRatio );
AppliedPedMoveMomentumMoveBlendTweak( pPed, &fMoveBlendRatio, &fLateralMoveBlendRatio );
pPed->GetMotionData()->SetCurrentMoveBlendRatio( fMoveBlendRatio, fLateralMoveBlendRatio );
Vector2 vMBR( fLateralMoveBlendRatio, fMoveBlendRatio );
m_fMoveBlendRatio = vMBR.Mag();
}
else
{
m_bIsDoingSomething = false;
pPed->GetMotionData()->SetDesiredMoveBlendRatio( 0.0f, 0.0f );
pPed->GetMotionData()->SetCurrentMoveBlendRatio( 0.0f, 0.0f );
m_fMoveBlendRatio = 0.0f;
}
}
// Make sure the applied momentum decays over time.
UpdateAppliedPedMoveMomentum();
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetTargetEntity
// PURPOSE : Set what the target entity currently is and whether we have a
// lock on it or not.
// PARAMETERS : pTargetEntity - the target.
// bHasLockOnTargetEntity - whether or not a lock on is being applied.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::SetTargetEntity( CEntity* pTargetEntity, bool bHasLockOnTargetEntity )
{
// Just set the has lock on flag to its current state (allowing us to
// acquire and break locks on a given ped even when they are acquired as
// an ambient target).
m_bHasLockOnTargetEntity = bHasLockOnTargetEntity;
m_pTargetEntity = pTargetEntity;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetAppliedPedMoveMomentum
// PURPOSE : Set the amount of external momentum to apply to the ped. This is
// usually used to simulate movement due to being hit by something.
// PARAMETERS : dir - the direction of the desired movement.
// momentumPortion - how much movement to apply.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::SetAppliedPedMoveMomentum( Vector3& vDir, float fPedMoveMomentumPortion )
{
m_vPedMoveMomentumDir = vDir;
m_fPedMoveMomentumPortion = fPedMoveMomentumPortion;
m_fPedMoveMomentumTime = m_fPedMoveMomentumPortion;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateAppliedPedMoveMomentum
// PURPOSE : Update the affect of the external momentum applied.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::UpdateAppliedPedMoveMomentum( void )
{
static float fDecayAmountPerSecond = 1.0f;
m_fPedMoveMomentumTime -= ( fDecayAmountPerSecond * fwTimer::GetTimeStep() );
if( m_fPedMoveMomentumTime <= 0.0f )
m_fPedMoveMomentumTime = 0.0f;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AppliedPedMoveMomentumMoveBlendTweak
// PURPOSE : Alter the move blend ratios to account for the external momentum
// affects being applied.
// PARAMETERS : pPed - the ped of interest.
// fMoveBlendRatio - the forward back movement blend amount.
// fLateralMoveBlendRatio - the side to side movement blend amount.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::AppliedPedMoveMomentumMoveBlendTweak( CPed* pPed, float* fMoveBlendRatio, float* fLateralMoveBlendRatio )
{
Assertf( pPed, "pPed is null in CTaskMoveMeleeMovement::AppliedPedMoveMomentumMoveBlendTweak." );
Assertf( fMoveBlendRatio, "fMoveBlendRatio is null in CTaskMoveMeleeMovement::AppliedPedMoveMomentumMoveBlendTweak." );
Assertf( fLateralMoveBlendRatio, "fLateralMoveBlendRatio is null in CTaskMoveMeleeMovement::AppliedPedMoveMomentumMoveBlendTweak." );
const CBrawlingStyleData* pData = CBrawlingStyleManager::GetBrawlingData( pPed->GetBrawlingStyle().GetHash() );
// Make sure we need to apply the tweak at all.
if( m_fPedMoveMomentumTime <= 0.0f )
return;
// Determine how much to tweak the move blends.
float fPedMoveMomentumForward = 0.0f;
float fPedMoveMomentumLateral = 0.0f;
Vector3 vDesiredDirection( m_vPedMoveMomentumDir.x, m_vPedMoveMomentumDir.y, 0.0f );
vDesiredDirection.Normalize();
if( vDesiredDirection.Mag() > 0.0f )
{
Vector3 vPedForward = VEC3V_TO_VECTOR3( pPed->GetTransform().GetB() );
vPedForward.z = 0.0f;
fPedMoveMomentumForward = -DotProduct( vDesiredDirection, vPedForward );
fPedMoveMomentumForward *= ( m_fPedMoveMomentumPortion * ( pData ? pData->GetMeleeMovementMBR() : MOVEBLENDRATIO_RUN ) );
Vector3 vPedRightward = VEC3V_TO_VECTOR3( pPed->GetTransform().GetA() );
vPedRightward.z = 0.0f;
fPedMoveMomentumLateral = DotProduct( vDesiredDirection, vPedRightward );
fPedMoveMomentumLateral *= ( m_fPedMoveMomentumPortion * ( pData ? pData->GetMeleeMovementMBR() : MOVEBLENDRATIO_RUN ) );
}
// Apply the tweak.
// When momentum is being applied it fully controls the ped.
(*fMoveBlendRatio) = fPedMoveMomentumForward;
(*fLateralMoveBlendRatio) = fPedMoveMomentumLateral;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessStdMove
// PURPOSE : Process standard (walk about) movement for the ped.
// PARAMETERS : pPlayerPed - Player ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::ProcessStdMove( CPed* pPlayerPed )
{
pPlayerPed->GetMotionData()->SetIsStrafing( false );
const CControl *pControl = pPlayerPed->GetControlFromPlayer();
Vector2 vStickInput( 0.0f, 0.0f );
if( ShouldAllowMovementInput() )
{
vStickInput.x = pControl->GetPedWalkLeftRight().GetNorm();
vStickInput.y = -pControl->GetPedWalkUpDown().GetNorm();
}
float fMoveBlendRatio = 0.0f;
// don't let the player walk when attached
if( !pPlayerPed->GetIsAttached() )
fMoveBlendRatio = MIN( vStickInput.Mag(), 1.0f );
if( fMoveBlendRatio > 0.0f )
{
// get the orientation of the FIRST follow ped camera, for player controls
const float fCamOrient = camInterface::GetPlayerControlCamHeading( *pPlayerPed );
float fStickAngle = rage::Atan2f( -vStickInput.x, vStickInput.y );
fStickAngle = fStickAngle + fCamOrient;
fStickAngle = fwAngle::LimitRadianAngle(fStickAngle);
pPlayerPed->SetDesiredHeading( fStickAngle );
}
Assert( pPlayerPed->GetPlayerInfo() );
if( ShouldAllowMovementInput() && !pPlayerPed->GetPlayerInfo()->GetPlayerDataPlayerSprintDisabled() )
{
float fSprintResult = pPlayerPed->GetPlayerInfo()->ControlButtonSprint(CPlayerInfo::SPRINT_ON_FOOT);
if( NetworkInterface::IsGameInProgress() || NetworkInterface::IsInMPTutorial() )
{
// For multiplayer games the player's default movespeed is RUN, unless they are in an interior where WALK is used
const bool bInInterior = CTaskMovePlayer::ms_bDefaultNoSprintingInInteriors && pPlayerPed->GetPortalTracker()->IsInsideInterior() && !pPlayerPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IgnoreInteriorCheckForSprinting) && !pPlayerPed->GetPortalTracker()->IsAllowedToRunInInterior();
if( !bInInterior )
fSprintResult = Max( fSprintResult, 0.5f );
}
if( fSprintResult > 1.0f )
{
fMoveBlendRatio *= MOVEBLENDRATIO_SPRINT;
fMoveBlendRatio = MIN( fMoveBlendRatio, MOVEBLENDRATIO_SPRINT );
}
else if( fSprintResult > 0.0f )
{
fMoveBlendRatio *= MOVEBLENDRATIO_RUN;
fMoveBlendRatio = MIN( fMoveBlendRatio, MOVEBLENDRATIO_RUN );
}
else
{
fMoveBlendRatio *= MOVEBLENDRATIO_WALK;
fMoveBlendRatio = MIN( fMoveBlendRatio, MOVEBLENDRATIO_WALK );
}
}
else
{
fMoveBlendRatio *= MOVEBLENDRATIO_RUN;
fMoveBlendRatio = MIN( fMoveBlendRatio, MOVEBLENDRATIO_RUN );
}
fMoveBlendRatio = Clamp( fMoveBlendRatio, MOVEBLENDRATIO_STILL, MOVEBLENDRATIO_SPRINT );
// Mark whether we tried to move of our own accord or not.
m_bIsDoingSomething = ( fMoveBlendRatio != 0.0f );
// Set the desired move blend (which is smoothed internally) and force
// the momentum (hit) based movement (which won't be smoothed internally).
pPlayerPed->GetMotionData()->SetDesiredMoveBlendRatio( fMoveBlendRatio, 0.0f );
float fDesiredMoveBlendRatio, fLateralMoveBlendRatio;
pPlayerPed->GetMotionData()->GetDesiredMoveBlendRatio( fLateralMoveBlendRatio, fDesiredMoveBlendRatio );
AppliedPedMoveMomentumMoveBlendTweak( pPlayerPed, &fDesiredMoveBlendRatio, &fLateralMoveBlendRatio );
pPlayerPed->GetMotionData()->SetCurrentMoveBlendRatio( fDesiredMoveBlendRatio, fLateralMoveBlendRatio );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessStrafeMove
// PURPOSE : Process strafe (circling) movement for the ped.
// PARAMETERS : pPlayerPed - Player ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::ProcessStrafeMove( CPed* pPlayerPed )
{
pPlayerPed->GetMotionData()->SetIsStrafing( true );
Vector2 vStickInput( 0.0f, 0.0f );
if( ShouldAllowMovementInput() )
{
vStickInput = ProcessStrafeInputWithRestrictions( pPlayerPed, GetTargetEntity(), CTaskMelee::sm_fMeleeStrafingMinDistance, m_fStickAngle, m_bStickAngleTweaked );
if(!pPlayerPed->GetMotionData()->GetUsingStealth() && m_bForceRun)
{
vStickInput *= MOVEBLENDRATIO_RUN;
}
}
// Mark whether we tried to move of our own accord or not.
m_bIsDoingSomething = ( vStickInput.Mag2() > 0.0f );
// Set the desired move blend (which is smoothed internally) and force
// the momentum (hit) based movement (which won't be smoothed internally).
pPlayerPed->GetMotionData()->SetDesiredMoveBlendRatio( vStickInput.y, vStickInput.x );
float fMoveBlendRatio;
float fLateralMoveBlendRatio;
pPlayerPed->GetMotionData()->GetDesiredMoveBlendRatio( fLateralMoveBlendRatio, fMoveBlendRatio );
AppliedPedMoveMomentumMoveBlendTweak( pPlayerPed, &fMoveBlendRatio, &fLateralMoveBlendRatio );
pPlayerPed->GetMotionData()->SetCurrentMoveBlendRatio( fMoveBlendRatio, fLateralMoveBlendRatio );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessStealthMode
// PURPOSE : Checks whether or not we should be in stealth mode
// PARAMETERS : pPlayerPed - Player ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::ProcessStealthMode( CPed* pPlayerPed )
{
// We do not want to process stealth mode changes if we are using the RAGE special ability
const CPlayerSpecialAbility* pSpecialAbility = pPlayerPed->GetSpecialAbility();
if( pSpecialAbility && pSpecialAbility->GetType() == SAT_RAGE && pSpecialAbility->IsActive() )
return;
CControl *pControl = pPlayerPed->GetControlFromPlayer();
if( pControl )
{
u32 uTimePressed = 0;
static dev_u32 HISTORY_MS = 300;
if( pControl->GetPedDuck().HistoryPressed( HISTORY_MS, &uTimePressed ) )
{
if( uTimePressed > m_uStealthLastPressed )
{
m_uStealthLastPressed = uTimePressed;
if( CGameConfig::Get().AllowStealthMovement() )
{
if( pPlayerPed->GetMotionData()->GetUsingStealth() )
pPlayerPed->GetMotionData()->SetUsingStealth( false );
else if( pPlayerPed->CanPedStealth() )
pPlayerPed->GetMotionData()->SetUsingStealth( true );
}
else if( pPlayerPed->GetMotionData()->GetUsingStealth() )
pPlayerPed->GetMotionData()->SetUsingStealth( false );
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetPrefdDistanceMoveComponents
// PURPOSE : TO encapsulate the AI approach and avoid behaviors.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMoveMeleeMovement::GetPrefdDistanceMoveComponents( float& fForwardTargetBasedMoveComponentOut,
float& fRightwardTargetBasedMoveComponentOut,
Vec3V_In vPedPosition,
Vec3V_In vForwardHeading,
Vec3V_In vRightwardHeading,
Vec3V_In vTargetPosition,
const float fCurrentDesiredRange,
const bool bAvoidOnly,
const float fCurrentMinRangeForImpetus,
const float fCurrentMaxRangeForImpetus,
const float fDistancingForwardImpetusPowerFactor,
const float fDistancingBackwardImpetusPowerFactor,
const float fDistancingMinTargetApproachDesireSize,
const CDefensiveArea* pDefensiveArea )
{
Vec3V vSelfToTarget = vTargetPosition - vPedPosition;
Vec3V vSelfToTargetDir = Normalize( vSelfToTarget );
// Determine how strongly we are attracted to the target (based on distance, etc.)
float fTargetApproachMoveDesireSignedPortion = 0.0f;
// Determine the unfiltered movement desire.
const float fSelfToTargetRange = Mag( vSelfToTarget ).Getf();
const float fDiffToDesiredRange = fCurrentDesiredRange - fSelfToTargetRange;
const float fClampedAbsDiffToDesiredRange = Clamp( Abs( fDiffToDesiredRange ), 0.0f, fCurrentMaxRangeForImpetus );
if( fClampedAbsDiffToDesiredRange < fCurrentMinRangeForImpetus )
fTargetApproachMoveDesireSignedPortion = 0.0f;
else
{
float fDesirePortion = ( fClampedAbsDiffToDesiredRange - fCurrentMinRangeForImpetus ) / ( fCurrentMaxRangeForImpetus - fCurrentMinRangeForImpetus );
if( fDiffToDesiredRange < 0 )
fDesirePortion = rage::Powf( fDesirePortion, fDistancingForwardImpetusPowerFactor );
else
fDesirePortion = rage::Powf( fDesirePortion, fDistancingBackwardImpetusPowerFactor );
fTargetApproachMoveDesireSignedPortion = -( Sign( fDiffToDesiredRange ) * fDesirePortion );
// Make sure the desire is not fleetingly small.
if( Abs( fTargetApproachMoveDesireSignedPortion ) < fDistancingMinTargetApproachDesireSize )
fTargetApproachMoveDesireSignedPortion = Sign( fTargetApproachMoveDesireSignedPortion ) * fDistancingMinTargetApproachDesireSize;
}
// Make sure to only move away if that's all we can do.
if( bAvoidOnly && fTargetApproachMoveDesireSignedPortion > 0.0f )
fTargetApproachMoveDesireSignedPortion = 0.0f;
bool bCanSeek = true;
bool bCanFlee = true;
// Don't allow any seek/flee when ped is out side their defensive area
if( pDefensiveArea && pDefensiveArea->IsActive() && !pDefensiveArea->IsPointInDefensiveArea( VEC3V_TO_VECTOR3( vTargetPosition ) ) )
bCanSeek = bCanFlee = false;
// Modify the target approach move desire according to what we are allowed to do.
if( !bCanSeek && ( fTargetApproachMoveDesireSignedPortion > 0.0f ) )
fTargetApproachMoveDesireSignedPortion = 0.0f;
if( !bCanFlee && (fTargetApproachMoveDesireSignedPortion < 0.0f ) )
fTargetApproachMoveDesireSignedPortion = 0.0f;
// Apply the desired strength to the forward and rightward movement directions.
fForwardTargetBasedMoveComponentOut = Dot( vSelfToTargetDir, vForwardHeading ).Getf() * fTargetApproachMoveDesireSignedPortion;
fRightwardTargetBasedMoveComponentOut = Dot( vSelfToTargetDir, vRightwardHeading ).Getf() * fTargetApproachMoveDesireSignedPortion;
}
/////////////////////////////////////////////////////////////////////////////////
// CTaskMeleeActionResult
//
// Defines a melee action that can be anything from a strike to a taunt
/////////////////////////////////////////////////////////////////////////////////
dev_float RAGDOLL_APPLY_MELEE_FORCE_MULT = 75.0f;
const u32 SPECIAL_RESULT_SET_STOP_MOVES_HOMING_ID = ATSTRINGHASH("SetStopMovesHoming", 0x099225e96);
const u32 SPECIAL_RESULT_SET_OPPONENT_STOP_MOVES_HOMING_ID = ATSTRINGHASH("SetOpponentStopMovesHoming", 0x0e943d9af);
const u32 SPECIAL_RESULT_ADD_SMALL_SPECIAL_ABILITY_CHARGE_ID = ATSTRINGHASH("AddSmallSpecialAbilityCharge", 0x0ef4d1ced);
const u32 SPECIAL_RESULT_DISABLE_INJURED_GROUND_BEHAVIOR_ID = ATSTRINGHASH("DisableInjuredGroundBehavior", 0x01e73fec1);
static const u32 BOUND_MAX_STRIKE_CAPSULES = 16;
static const float BOUND_HAND_LENGTH_REDUCTION_AMOUNT = 0.06f;
static const float BOUND_ACCEPTABLE_DEPTH_ERROR_PORTION = 0.3f;
static const u32 BOUND_MAX_NUM_SPHERES_ALONG_LENGTH = 16;
const fwMvClipId CTaskMeleeActionResult::ms_ClipId("ClipId",0x4ED90D92);
const fwMvClipId CTaskMeleeActionResult::ms_AdditiveClipId("AdditiveClipId",0xDDBFA9F6);
const fwMvFilterId CTaskMeleeActionResult::ms_ClipFilterId("ClipFilterId",0x458A342);
const fwMvRequestId CTaskMeleeActionResult::ms_PlayClipRequestId("PlayClip",0x2E8FF75F);
const fwMvRequestId CTaskMeleeActionResult::ms_RestartPlayClipRequestId("RestartPlayClip",0x67fcc4c3);
const fwMvFloatId CTaskMeleeActionResult::ms_ClipPhaseId("PhaseId",0x8C064EBA);
const fwMvFloatId CTaskMeleeActionResult::ms_ClipRateId("RateId",0xF11C4C9B);
const fwMvFloatId CTaskMeleeActionResult::ms_IkShadowWeightId("IkShadowWeightId",0x1DD0C33F);
const fwMvBooleanId CTaskMeleeActionResult::ms_AnimClipFinishedId("OnClipFinished",0x88B0DC15);
const fwMvFlagId CTaskMeleeActionResult::ms_UseAdditiveClipId("UseAdditiveClip",0x24C4F88F);
phBoundComposite* CTaskMeleeActionResult::sm_pTestBound = NULL;
const fwMvBooleanId CTaskMeleeActionResult::ms_DropWeaponOnDeath("DropWeapon",0x9312D206);
const fwMvFlagId CTaskMeleeActionResult::ms_HasGrip("HasGrip",0x1bcd14c6);
const fwMvClipSetVarId CTaskMeleeActionResult::ms_GripClipSetId("GripClipSet", 0xcbd9a0f0);
IMPLEMENT_COMBAT_TASK_TUNABLES(CTaskMeleeActionResult, 0x2a2941a3)
CTaskMeleeActionResult::Tunables CTaskMeleeActionResult::sm_Tunables;
CTaskMeleeActionResult::MeleeNetworkDamage CTaskMeleeActionResult::sm_CachedMeleeDamage[CTaskMeleeActionResult::s_MaxCachedMeleeDamagePackets];
u8 CTaskMeleeActionResult::sm_uMeleeDamageCounter = 0;
void CTaskMeleeActionResult::CacheMeleeNetworkDamage(CPed *pHitPed,
CPed *pFiringEntity,
const Vector3 &vWorldHitPos,
s32 iComponent,
float fDamage,
u32 flags,
u32 uParentMeleeActionResultID,
u32 uForcedReactionDefinitionID,
u16 uNetworkActionID)
{
taskAssert(sm_uMeleeDamageCounter < s_MaxCachedMeleeDamagePackets);
//! Remove any existing melee reacts with this ID.
for(int i = 0; i < s_MaxCachedMeleeDamagePackets; ++i)
{
CTaskMeleeActionResult::MeleeNetworkDamage &CachedDamage = sm_CachedMeleeDamage[i];
if(CachedDamage.m_uNetworkActionID==uNetworkActionID)
{
CachedDamage.Reset();
}
}
CTaskMeleeActionResult::MeleeNetworkDamage NewCachedDamage;
NewCachedDamage.m_pHitPed = pHitPed;
NewCachedDamage.m_pInflictorPed = pFiringEntity;
NewCachedDamage.m_vWorldHitPos = vWorldHitPos;
NewCachedDamage.m_iComponent = iComponent;
NewCachedDamage.m_fDamage = fDamage;
NewCachedDamage.m_uFlags = flags;
NewCachedDamage.m_uParentMeleeActionResultID = uParentMeleeActionResultID;
NewCachedDamage.m_uForcedReactionDefinitionID = uForcedReactionDefinitionID;
NewCachedDamage.m_uNetworkActionID = uNetworkActionID;
NewCachedDamage.m_uNetworkTime = NetworkInterface::GetNetworkTime();
sm_CachedMeleeDamage[sm_uMeleeDamageCounter] = NewCachedDamage;
++sm_uMeleeDamageCounter;
if(sm_uMeleeDamageCounter >= s_MaxCachedMeleeDamagePackets)
sm_uMeleeDamageCounter = 0;
}
CTaskMeleeActionResult::MeleeNetworkDamage *CTaskMeleeActionResult::FindMeleeNetworkDamageForHitPed(CPed *pHitPed)
{
for(int i = 0; i < s_MaxCachedMeleeDamagePackets; ++i)
{
CTaskMeleeActionResult::MeleeNetworkDamage &CachedDamage = sm_CachedMeleeDamage[i];
if(CachedDamage.m_pHitPed == pHitPed &&
CachedDamage.m_pInflictorPed == GetPed() &&
CachedDamage.m_uNetworkActionID == m_nUniqueNetworkActionID )
{
return &sm_CachedMeleeDamage[i];
}
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CTaskMeleeActionResult
// PURPOSE : The default constructor.
// PARAMETERS : pActionResult - The action to perform (may be null).
// pParentPed - The ped of interest.
// pTargetEntity - The melee target.
// bHasLockOnTargetEntity - Is a lock on the target present.
// isDoingReaction - Is the action to perform a reaction.
/////////////////////////////////////////////////////////////////////////////////
CTaskMeleeActionResult::CTaskMeleeActionResult( const CActionResult* pActionResult, CEntity* pTargetEntity, bool bHasLockOnTargetEntity, bool bIsDoingReaction, bool bShouldSuppressHoming, u32 nSelfDamageWeaponHash, eAnimBoneTag eSelfDamageAnimBoneTag, float fMoveBlendRatioSq )
: // Initializer list.
m_pActionResult (pActionResult),
m_pForcedReactionActionDefinition (NULL),
m_pClipFilter (NULL),
m_pTargetEntity (NULL), // Set in function body, below.
m_bHasLockOnTargetEntity (false), // Set in function body, below.
m_bPlaysClip (true),
m_vCachedHomingPosition (V_ZERO),
m_vCachedHomingDirection (V_ZERO),
m_vCachedHeadIkOffset (V_ZERO),
m_vMoverDisplacement (V_ZERO),
m_vCachedHomingPedPosition (V_ZERO),
m_vCloneCachedHomingPosition (V_ZERO),
m_nResultId (0),
m_nResultPriority (0),
m_clipSetId (CLIP_SET_ID_INVALID),
m_clipId (CLIP_ID_INVALID),
m_bForceImmediateReaction (false),
m_fSelfDamageAmount (0.0f),
m_fInitialPhase (0.0f),
m_bIsDoingIntro (false),
m_bIsDoingTaunt (false),
m_bIsDazed (false),
m_bIsUpperbodyOnly (false),
m_bIsDoingBlock (false),
m_bDropWeapon (false),
m_bDisarmWeapon (false),
m_bIsDoingReaction (bIsDoingReaction),
m_bIsNonMeleeHitReaction (false),
m_bSelfDamageApplied (false),
m_bPreHitResultsApplied (false),
m_bPostHitResultsApplied (false),
m_bAllowHoming (true),
m_bAllowDistanceHoming (!bShouldSuppressHoming),
m_bDidAllowDistanceHoming (false),
m_bOwnerStoppedDistanceHoming (false),
m_bAllowDodge (false),
m_bHasJustStruckSomething (false),
m_bHasTriggerMeleeSwipeAudio (false),
m_nHitEntityComponentsCount (0),
m_nBoneIdxCache (-1),
m_fCriticalFrame (0.0f),
m_bPastCriticalFrame (false),
m_bAllowInterrupt (false),
m_bProcessCollisions (false),
m_bHasStartedProcessingCollisions (false),
m_bMeleeInvulnerability (false),
m_bFireWeapon (false),
m_bSwitchToRagdoll (false),
m_nActionResultNetworkID (INVALID_ACTIONRESULT_ID),
m_bFixupActionResultFromNetwork (false),
m_nSelfDamageWeaponHash (nSelfDamageWeaponHash),
m_eSelfDamageAnimBoneTag (eSelfDamageAnimBoneTag),
m_bIsStandardAttack (false),
m_bForceDamage (false),
m_bIgnoreArmor (false),
m_bIgnoreStatModifiers (false),
m_bShouldDamagePeds (false),
m_bForceHomingArrival (false),
m_bCloneKillAnimFinished (false),
m_bHeadingInitialized (false),
m_bLastKnowWeaponMatCached (false),
m_matLastKnownWeapon (Matrix34::IdentityType),
m_bPickupDropped (false),
m_fCachedMoveBlendRatioSq (fMoveBlendRatioSq),
m_bOffensiveMove (false),
m_bInterruptWhenOutOfWater (false),
m_bAllowScriptTaskAbort (false),
m_bHandledLocalToRemoteSwitch (false),
m_bInNetworkBlenderTolerance (false),
m_nUniqueNetworkActionID (0),
m_nCloneKillAnimFinishedTime (0)
{
// Update the old target and the new target.
SetTargetEntity( pTargetEntity, bHasLockOnTargetEntity );
SetInternalTaskType(CTaskTypes::TASK_MELEE_ACTION_RESULT);
if(m_pActionResult)
m_nActionResultNetworkID = m_pActionResult->GetID();
m_nNetworkTimeTriggered = NetworkInterface::GetNetworkTime();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CacheActionResult
// PURPOSE : Cache off the commonly used variables
// (might be able to optimize this out)
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::CacheActionResult( CPed* pPed )
{
// Make sure there is an actionResult, if not try to operate in a "do nothing"
// mode.
if( m_pActionResult )
{
bool bFirstPerson = false;
#if FPS_MODE_SUPPORTED
bFirstPerson = pPed->IsFirstPersonShooterModeEnabledForPlayer(false) && m_pActionResult->GetFirstPersonClipSetID() != CLIP_SET_ID_INVALID;
#endif
// Set local copies of the action result data.
// This lets us edit the action table mid-move without bad
// things happening.
m_nResultId = m_pActionResult->GetID();
m_nResultPriority = m_pActionResult->GetPriority();
m_clipSetId = bFirstPerson ? (fwMvClipSetId)m_pActionResult->GetFirstPersonClipSetID() : (fwMvClipSetId)m_pActionResult->GetClipSetID();
m_clipId = (fwMvClipId)m_pActionResult->GetAnimID();
m_bIsDoingIntro = m_pActionResult->GetIsAnIntro();
m_bIsDoingTaunt = m_pActionResult->GetIsATaunt();
m_bIsDazed = m_pActionResult->GetIsADazedHitReaction();
m_bIsUpperbodyOnly = pPed->IsPlayer() ? m_pActionResult->GetPlayerIsUpperbodyOnly() : m_pActionResult->GetAIIsUpperbodyOnly();
m_bIsDoingBlock = m_pActionResult->GetIsABlock();
m_bIsStandardAttack = m_pActionResult->GetIsAStandardAttack();
m_bIsNonMeleeHitReaction = m_pActionResult->GetIsNonMeleeHitReaction();
m_bForceDamage = m_pActionResult->GetShouldForceDamage();
m_bAllowAimInterrupt = m_pActionResult->AllowAimInterrupt();
m_bInterruptWhenOutOfWater = m_pActionResult->ShouldInterruptWhenOutOfWater();
m_bAllowScriptTaskAbort = m_pActionResult->ShouldAllowScriptTaskAbort();
m_bOffensiveMove = m_pActionResult->IsOffensiveMove();
const CDamageAndReaction* pDamageAndReaction = m_pActionResult->GetDamageAndReaction();
if( pDamageAndReaction )
{
m_bForceImmediateReaction = pDamageAndReaction->ShouldForceImmediateReaction();
m_fSelfDamageAmount = pDamageAndReaction->GetSelfDamageAmount();
m_bDropWeapon = pDamageAndReaction->ShouldDropWeapon();
m_bDisarmWeapon = pDamageAndReaction->ShouldDisarmOpponentWeapon();
m_bIgnoreArmor = pDamageAndReaction->ShouldIgnoreArmor();
m_bIgnoreStatModifiers = pDamageAndReaction->ShouldIgnoreStatModifiers();
m_bShouldDamagePeds = pDamageAndReaction->ShouldDamagePeds();
}
m_nSoundNameHash = m_pActionResult->GetSoundID();
BANK_ONLY(m_pszSoundName = m_pActionResult->GetSoundName() );
// Check if this result should try to play an clip.
m_bPlaysClip = false;
if( m_clipSetId != CLIP_SET_ID_INVALID && m_clipId != CLIP_ID_INVALID )
m_bPlaysClip = true;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PrepareMoveNetwork
// PURPOSE : Initialize the move network associated with this melee action
// PARAMETERS : pPed - the ped of interest.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::PrepareMoveNetwork( CPed* pPed )
{
if( !m_MoveNetworkHelper.IsNetworkActive() )
{
m_MoveNetworkHelper.RequestNetworkDef( CClipNetworkMoveInfo::ms_NetworkTaskMeleeActionResult );
if( m_MoveNetworkHelper.IsNetworkDefStreamedIn( CClipNetworkMoveInfo::ms_NetworkTaskMeleeActionResult ) )
m_MoveNetworkHelper.CreateNetworkPlayer( pPed, CClipNetworkMoveInfo::ms_NetworkTaskMeleeActionResult );
}
if( !m_MoveNetworkHelper.IsNetworkAttached() )
{
pPed->GetMovePed().SetTaskNetwork( m_MoveNetworkHelper.GetNetworkPlayer(), m_pActionResult ? m_pActionResult->GetAnimBlendInRate() : NORMAL_BLEND_DURATION, CMovePed::Task_IgnoreMoverBlend );
return true;
}
// Make sure to cover cases where it is active and attached on first pass
return m_MoveNetworkHelper.IsNetworkActive() && m_MoveNetworkHelper.IsNetworkAttached();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CleanUp
// PURPOSE : Main CTask cleanup function that is run once the current task
// has been marked to abort
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::CleanUp( void )
{
CPed *pPed = GetPed();
Assert( pPed );
if(pPed->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::CleanUp [%p] sequence ID = %d m_nUniqueNetworkActionID = %d", fwTimer::GetFrameCount(), this, GetNetSequenceID(), m_nUniqueNetworkActionID );
}
if( m_bHasTriggerMeleeSwipeAudio )
{
m_bHasTriggerMeleeSwipeAudio = false;
}
if( m_MoveNetworkHelper.GetNetworkPlayer() )
{
float fBlendDuration = m_pActionResult ? m_pActionResult->GetAnimBlendOutRate() : SLOW_BLEND_DURATION;
pPed->GetMovePed().ClearTaskNetwork( m_MoveNetworkHelper, fBlendDuration, ( m_pActionResult && m_pActionResult->TagSyncBlendOut() && CTaskMelee::VelocityHighEnoughForTagSyncBlendOut( pPed ) ) ? CMovePed::Task_TagSyncTransition : 0 );
m_MoveNetworkHelper.ReleaseNetworkPlayer();
#if FPS_MODE_SUPPORTED
if(pPed->IsFirstPersonShooterModeEnabledForPlayer(false))
{
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_WasPlayingFPSMeleeActionResult, true);
}
#endif
CTaskMotionBase* pPrimaryMotionTask = pPed->GetPrimaryMotionTask();
if(pPrimaryMotionTask && pPrimaryMotionTask->GetTaskType() == CTaskTypes::TASK_MOTION_PED)
{
CTaskMotionPed* pPedMotionTask = static_cast<CTaskMotionPed*>(pPrimaryMotionTask);
pPedMotionTask->SetBlockOnFootTransitionTime(fBlendDuration);
// Block aiming for AI peds to avoid glitch
if(!pPed->IsPlayer())
{
pPedMotionTask->SetBlockAimingTransitionIndependentMover(fBlendDuration);
}
}
}
// Clean up leg ik
if( !pPed->GetIKSettings().IsIKFlagDisabled( IKManagerSolverTypes::ikSolverTypeLeg ) )
pPed->m_PedConfigFlags.SetPedLegIkMode( CIkManager::GetDefaultLegIkMode(pPed) );
if( m_pClipFilter )
{
m_pClipFilter->Release();
m_pClipFilter = NULL;
}
// Ensure we apply network damage.
ProcessCachedNetworkDamage(pPed);
// In the case we exited via ragdoll collision we need to make sure we have applied self damage
ProcessSelfDamage( pPed );
//! DMKH. If we tried to kill local ped (and flushed their tasks), check if they are dead. If not, give default task, otherwise they will be
//! stuck forever.
if(!pPed->ShouldBeDead())
{
if(!pPed->IsNetworkClone() && GetParent() && static_cast<CTaskMelee*>( GetParent() )->GetTaskFlags().IsFlagSet( CTaskMelee::MF_ForcedReaction ))
{
if(pPed->IsDeadByMelee())
{
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_KilledByStealth, false);
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_KilledByTakedown, false);
}
static_cast<CTaskMelee*>( GetParent() )->GetTaskFlags().ClearFlag( CTaskMelee::MF_ForcedReaction );
}
}
pPed->SetRagdollOnCollisionIgnorePhysical( NULL );
pPed->ClearBound();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessPreFSM
// PURPOSE : Runs before the main CTask update loop (UpdateFSM)
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeActionResult::ProcessPreFSM( void )
{
CPed *pPed = GetPed(); //Get the ped ptr.
DEV_BREAK_IF_FOCUS( CCombatMeleeDebug::sm_bFocusPedBreakOnTaskMeleeActionProcessPreFSM, pPed );
pPed->SetPedResetFlag( CPED_RESET_FLAG_ProcessPostPreRender, true );
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate( true );
//! Clone peds may not run hand in hand with parent task, so ensure this is set here too.
pPed->SetPedResetFlag( CPED_RESET_FLAG_MoveBlend_bMeleeTaskRunning, true );
pPed->SetPedResetFlag( CPED_RESET_FLAG_DisableMotionBaseVelocityOverride, true );
#if FPS_MODE_SUPPORTED
if(pPed->IsLocalPlayer() && pPed->GetPlayerInfo())
{
pPed->SetPlayerResetFlag(CPlayerResetFlags::PRF_DISABLE_CAMERA_VIEW_MODE_CYCLE);
}
#endif
if( IsDoingSomething() )
{
pPed->SetPedResetFlag( CPED_RESET_FLAG_DisableDynamicCapsuleRadius, true);
pPed->SetPedResetFlag( CPED_RESET_FLAG_SyncDesiredHeadingToCurrentHeading, AllowHeadingUpdate() );
}
if( m_pActionResult && m_pActionResult->ShouldDisablePedToPedRagdoll() )
{
pPed->SetPedResetFlag( CPED_RESET_FLAG_DontActivateRagdollFromAnyPedImpactReset, true );
}
if( m_pActionResult && m_pActionResult->ShouldUseKinematicPhysics() )
{
pPed->SetPedResetFlag(CPED_RESET_FLAG_TaskUseKinematicPhysics, true);
}
// Make sure to disallow switching of weapons (otherwise all sorts of
// weird issues come up).
if( !pPed->IsNetworkClone() && !ShouldAllowInterrupt() )
pPed->SetPedResetFlag( CPED_RESET_FLAG_TemporarilyBlockWeaponSwitching, true );
//! If this is an offensive attack, and we have a target, set ped to be in action mode.
//! Note: We also force a delay to our auto run here.
if(m_bOffensiveMove)
{
pPed->SetUsingActionMode(true, CPed::AME_Melee, sm_Tunables.m_ActionModeTime, false);
pPed->SetForcedRunDelay((u32)(sm_Tunables.m_ActionModeTime * 1000.0f));
}
//! If a clone ped is ragdolling, take ownership of it.
if(HandlesRagdoll(pPed) && GetPed()->GetRagdollState() == RAGDOLL_STATE_PHYS)
{
//Notify the ped that this task is in control of the ragdoll.
GetPed()->TickRagdollStateFromTask(*this);
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateFSM
// PURPOSE : Main heartbeat function for CTask
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeActionResult::UpdateFSM( const s32 iState, const FSM_Event iEvent )
{
CPed *pPed = GetPed(); //Get the ped ptr.
FSM_Begin
// Starting state
FSM_State(State_Start)
FSM_OnEnter
Start_OnEnter( pPed );
FSM_OnUpdate
return Start_OnUpdate( pPed );
// Play the action result clip
FSM_State(State_PlayClip)
FSM_OnEnter
PlayClip_OnEnter( pPed );
FSM_OnUpdate
return PlayClip_OnUpdate( pPed );
FSM_OnExit
PlayClip_OnExit( pPed );
// Quit
FSM_State(State_Finish)
FSM_OnEnter
Finish_OnEnter( pPed );
FSM_OnUpdate
taskDebugf1("Quit: CTaskMeleeActionResult::UpdateFSM: State_Finish");
return FSM_Quit;
FSM_End
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Copy
// PURPOSE : The cloning operation- used by the task management system.
// RETURNS : A perfect copy of the task.
/////////////////////////////////////////////////////////////////////////////////
aiTask* CTaskMeleeActionResult::Copy( void ) const
{
return rage_new CTaskMeleeActionResult( m_pActionResult,
m_pTargetEntity,
m_bHasLockOnTargetEntity,
m_bIsDoingReaction,
m_bAllowDistanceHoming,
m_nSelfDamageWeaponHash,
m_eSelfDamageAnimBoneTag,
m_fCachedMoveBlendRatioSq );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessPostPreRender
// PURPOSE : Update what the ped is currently doing, but after other the
// skeletons have been updated since the last frame, so that
// collision detection is more reliable.
// RETURNS : If everything went fine.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::ProcessPostPreRender( void )
{
CPed *pPed = GetPed(); //Get the ped ptr.
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ProcessPostPreRender." );
if( fwTimer::IsGamePaused() )
return true;
// Check and handle any collisions.
ProcessMeleeCollisions( pPed );
// Check and handle any network damage events if we didn't process them locally.
if(HasStartedProcessingCollisions() && (IsPastCriticalFrame() || !ShouldProcessMeleeCollisions()))
{
ProcessCachedNetworkDamage(pPed);
}
const CRootSlopeFixupIkSolver* pSolver = static_cast<const CRootSlopeFixupIkSolver*>(pPed->GetIkManager().GetSolver(IKManagerSolverTypes::ikSolverTypeRootSlopeFixup));
if (pSolver != NULL && pSolver->GetBlendRate() >= 0.0f)
{
//! For clones, need to lift bound if we haven't received response from the owner ped as we'll slide along the floor.
//! For url:bugstar:1596423.
CTaskMelee* pParentMeleeTask = GetParent() ? static_cast<CTaskMelee*>( GetParent() ) : NULL;
bool bForcedReaction = pPed->IsNetworkClone() && pParentMeleeTask && pParentMeleeTask->GetIsTaskFlagSet( CTaskMelee::MF_ForcedReaction );
// Hack to support horizontal peds that are still playing animations
// This allows us to avoid collisions with the ground that make the horizontal ped inch away from the attacker (dog)
static dev_float sfAdditionalZOffset = 0.25f;
float fOffset;
if(m_pActionResult && (bForcedReaction || m_pActionResult->ShouldAddOrientToSpineOffset()))
{
fOffset = sfAdditionalZOffset;
}
else
{
fOffset = 0.0f;
}
pPed->OrientBoundToSpine( NORMAL_BLEND_IN_DELTA, fOffset );
}
else
{
pPed->ClearBound();
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldAbort
// PURPOSE : Query to determine if another task has a higher priority than this one
// PARAMETERS : AbortPriority - Priority of abort event
// aiEvent - Event thrown that will be tested against
// RETURNS : whether or not we should abort this task
// NOTES : Might want to refer to the CTaskVault::ShouldAbort for additional abort
// conditions.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::ShouldAbort( const AbortPriority iPriority, const aiEvent* pEvent )
{
// Cache the ped
CPed* pPed = GetPed();
if( iPriority == ABORT_PRIORITY_IMMEDIATE || pPed->IsFatallyInjured() )
{
return CTask::ShouldAbort( iPriority, pEvent );
}
else if( pEvent )
{
const CEvent* pGameEvent = static_cast<const CEvent*>(pEvent);
//! Bail out for shark attacks.
if(pEvent->GetEventType()==EVENT_GIVE_PED_TASK)
{
const CEventGivePedTask *pGivePedTask = static_cast<const CEventGivePedTask *>(pEvent);
if(pGivePedTask->GetTask() && pGivePedTask->GetTask()->GetTaskType()==CTaskTypes::TASK_SYNCHRONIZED_SCENE)
{
//! Hack - compare to shark dict.
const CTaskSynchronizedScene *pSyncScene = static_cast<const CTaskSynchronizedScene *>(pGivePedTask->GetTask());
if(pSyncScene->GetDictionary() == CTaskSharkAttack::ms_SharkBiteDict)
{
return CTask::ShouldAbort( iPriority, pEvent );
}
}
}
if( pGameEvent->RequiresAbortForMelee( pPed ) ||
pGameEvent->GetEventPriority() >= E_PRIORITY_SCRIPT_COMMAND_SP ||
pGameEvent->GetEventType() == EVENT_ON_FIRE ||
pGameEvent->GetEventType() == EVENT_DAMAGE ||
pGameEvent->GetEventType() == EVENT_INCAPACITATED ||
pGameEvent->GetEventType() == EVENT_SWITCH_2_NM_TASK ||
( pGameEvent->GetEventType() == EVENT_SCRIPT_COMMAND && ShouldAllowScriptTaskAbort() ) )
{
return CTask::ShouldAbort( iPriority, pEvent );
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetCurrentClipTime
// PURPOSE : Get the amount of time into the current clip that is being
// played.
// RETURNS : The clip time.
/////////////////////////////////////////////////////////////////////////////////
float CTaskMeleeActionResult::GetCurrentClipTime( void ) const
{
float fClipTime = 0.0f;
if( m_MoveNetworkHelper.IsNetworkActive() )
{
float fCurrentPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
const crClip* pClip = m_MoveNetworkHelper.GetClip( ms_ClipId );
if( pClip )
fClipTime = pClip->ConvertPhaseToTime( fCurrentPhase );
}
return fClipTime;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AllowMovement
// PURPOSE : Check if the clip that is being played allows movement
// impulses through or if it entirely controls the peds movement.
// RETURNS : Whether or not to allow external movement impulses.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::AllowMovement( void ) const
{
if( !m_MoveNetworkHelper.IsNetworkActive() || !m_MoveNetworkHelper.GetClip( ms_ClipId ) )
return true;
if( IsUpperbodyOnly() )
return true;
if( m_pActionResult && m_pActionResult->UpdateMoveBlendRatio() )
return true;
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AllowStrafing
// PURPOSE : Check if the clip that is being played allows strafing
// RETURNS : Whether or not to allow strafing movement impulses.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::AllowStrafing( void ) const
{
if( !m_MoveNetworkHelper.IsNetworkActive() || !m_MoveNetworkHelper.GetClip( ms_ClipId ) )
return true;
if( IsUpperbodyOnly() )
return true;
if( m_pActionResult )
return m_pActionResult->GetShouldAllowStrafing();
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AllowHeadingUpdate
// PURPOSE : Check if the clip that is being played allows strafing
// RETURNS : Whether or not to allow heading updates
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::AllowHeadingUpdate( void ) const
{
if( m_pActionResult && m_pActionResult->GetIsABlock() )
return true;
// Allow heading update if we are not processing melee collisions
bool bAllowDistanceHoming = ShouldAllowDistanceHoming();
if( !bAllowDistanceHoming && ShouldProcessMeleeCollisions() )
return false;
if( m_pActionResult && m_pActionResult->GetIsAStandardAttack() && !ShouldProcessMeleeCollisions() )
return true;
return bAllowDistanceHoming;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AllowStealthMode
// PURPOSE : Check if the clip that is being played allows strafing
// RETURNS : Whether or not to allow heading updates
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::AllowStealthMode( void ) const
{
if( m_pActionResult && m_pActionResult->GetIsAStealthKill() )
return true;
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetTargetEntity
// PURPOSE : Set what the target entity currently is and whether or not we
// have a lock on it.
// The lock lets us know if the target is an ambient target or an
// intended target.
// PARAMETERS : pTargetEntity - the target.
// bHasLockOnTargetEntity - whether or not it is a lock on target.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::SetTargetEntity( CEntity* pTargetEntity, bool bHasLockOnTargetEntity )
{
// Just set the has lock on flag to its current state (allowing us to
// acquire and break locks on a given ped even when they are acquired as
// an ambient target).
m_pTargetEntity = pTargetEntity;
m_bHasLockOnTargetEntity = bHasLockOnTargetEntity;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldBranchOnCurrentFrame
// PURPOSE : Determine if a new simple action should be invoked and this one
// should be stopped.
// This is used for beginning and continuing sequences of moves.
// PARAMETERS : pPed - The ped of interest.
// pImpulseCombo - History of the peds current input impulses
// pActionIdOut - id of the action to perform
// bImpulseInitiatedMovesOnly - Should these only be initial actions?
// typeToLookFor - Bitfield of the types to look for
// nForcingResultId - Id of action that we should force
// RETURNS : Whether or not a branch should be taken.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::ShouldBranchOnCurrentFrame( const CPed* pPed, const CImpulseCombo* pImpulseCombo, u32* pActionIdOut, bool bImpulseInitiatedMovesOnly, const CActionFlags::ActionTypeBitSetData& typeToLookFor, u32 nForcingResultId )
{
#if __DEV
if( !pPed->IsPlayer() && !CCombatMeleeDebug::sm_bAllowNpcInitiatedMoves )
return false;
#endif // __DEV
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ShouldBranchOnCurrentFrame." );
Assertf( pActionIdOut, "pActionId is null in CTaskMeleeActionResult::ShouldBranchOnCurrentFrame." );
const CActionDefinition* pActionToDo = NULL;
CEntity *pTargetEntity = GetTargetEntity();
// Check if we are reacting to something, currently doing nothing, or doing an idle move
// from which any entry move could be done.
if( !m_pActionResult || ( ShouldAllowInterrupt() && m_pActionResult->AllowInitialActionCombo() ) )
{
// We don't care about closest melee targets when attempting to block
if( pPed->IsPlayer() && ( !m_pActionResult || m_pActionResult->AllowMeleeTargetEvaluation() ) )
{
CPed* pTargetPed = ( pTargetEntity && pTargetEntity->GetIsTypePed() ) ? static_cast<CPed*>(pTargetEntity) : NULL;
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
bool bTargetDeadPeds = ((pTargetEntity == NULL) || (pTargetPed && pTargetPed->IsDead())) && (rTargeting.GetLockOnTarget() == NULL);
// First and foremost lets try and select a new target via FindMeleeTarget
CTaskMelee::ResetLastFoundActionDefinition();
const CWeaponInfo *pWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
CEntity* pNewBestTarget = rTargeting.FindMeleeTarget( pPed, pWeaponInfo, true, bTargetDeadPeds, false, false, pTargetEntity );
pActionToDo = CTaskMelee::GetLastFoundActionDefinition();
if( pNewBestTarget == pTargetEntity)
{
//! Assume we didn't even search for new target to keep combo moves going.
}
else if( pNewBestTarget )
{
if(pActionToDo)
{
// Set the parent task to use the new best target entity
CTask* pParentTask = GetParent();
if( pParentTask )
static_cast<CTaskMelee*>( pParentTask )->SetTargetEntity( pNewBestTarget, false, true );
// Let the caller know what branch to go to.
*pActionIdOut = pActionToDo->GetID();
return true;
}
pTargetEntity = pNewBestTarget;
}
}
bool bShouldDebugPed = CActionManager::ShouldDebugPed( pPed );
// Do the search.
pActionToDo = ACTIONMGR.SelectSuitableAction( typeToLookFor,
pImpulseCombo,
true, // Test whether or not it is an entry action.
true, // Only find entry actions.
pPed,
nForcingResultId, // What result was the hitter doing?
-1, // Priority
pTargetEntity,
HasLockOnTargetEntity(),
bImpulseInitiatedMovesOnly,
pPed->IsLocalPlayer(), // If this is a player then test the controls.
bShouldDebugPed );
// If we couldn't find an action to perform and had a valid target let us try to find a move that doesn't require a target entity
if( pTargetEntity && !pActionToDo )
{
pActionToDo = ACTIONMGR.SelectSuitableAction( typeToLookFor, // Only look for moves that are appropriate from on foot
pImpulseCombo, // No impulse combo
true, // Test whether or not it is an entry action.
true, // Only find entry actions.
pPed, // The acting ped.
0, // result Id forcing us.
-1, // Priority
NULL, // The entity being attacked.
false, // Do we have a lock on the target entity.
false, // Should we only allow impulse initiated moves.
pPed->IsLocalPlayer(), // If this is a player then test the controls.
bShouldDebugPed );
if( pActionToDo )
{
pTargetEntity = NULL;
}
}
// Check if we found an action to take.
if( pActionToDo )
{
// Let the caller know what branch to go to.
*pActionIdOut = pActionToDo->GetID();
CTask* pParentTask = GetParent();
if( pParentTask )
static_cast<CTaskMelee*>( pParentTask )->SetTargetEntity( pTargetEntity, false, true );
return true;
}
}
// Check for branches off our current move to do.
else if( ShouldAllowInterrupt() )
{
// Make sure there is an clip playing.
if ( !m_MoveNetworkHelper.IsNetworkActive() || !m_MoveNetworkHelper.GetClip( ms_ClipId ) )
return false;
// As a slight optimization, do not check branches unless we need to
const bool bCheckBranches = !pPed->IsPlayer() ||
( typeToLookFor.IsSet( CActionFlags::AT_BLOCK ) || typeToLookFor.IsSet( CActionFlags::AT_DAZED_HIT_REACTION ) ) ||
( pImpulseCombo && pImpulseCombo->HasPendingImpulse() );
if( bCheckBranches )
{
bool bAllowNoTargetFallback = false;
// We don't care about closest melee targets when attempting to block
if( pPed->IsPlayer() && m_pActionResult->AllowMeleeTargetEvaluation() )
{
bool bFindTargetMustUseDesiredDirection = false;
//! Currently targeting something that isn't an entity. If that is the case, require our target test uses stick direction (so that we can't just target
//! something that runs past).
if(!pTargetEntity &&
GetHoming(pPed) &&
GetHoming(pPed)->IsSurfaceProbe())
{
bFindTargetMustUseDesiredDirection = true;
}
CPed* pTargetPed = ( pTargetEntity && pTargetEntity->GetIsTypePed() ) ? static_cast<CPed*>(pTargetEntity) : NULL;
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
bool bTargetDeadPeds = ((pTargetEntity == NULL) || (pTargetPed && pTargetPed->IsDead())) && (rTargeting.GetLockOnTarget() == NULL);
// First and foremost lets try and select a new target via FindMeleeTarget
CTaskMelee::ResetLastFoundActionDefinition();
const CWeaponInfo *pWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
CEntity* pNewBestTarget = rTargeting.FindMeleeTarget( pPed, pWeaponInfo, true, bTargetDeadPeds, false, bFindTargetMustUseDesiredDirection, pTargetEntity );
pActionToDo = CTaskMelee::GetLastFoundActionDefinition();
if( pNewBestTarget == pTargetEntity)
{
//! Assume we didn't even search for new target to keep combo moves going.
}
else if( pNewBestTarget )
{
if(pActionToDo)
{
// Set the parent task to use the new best target entity
CTask* pParentTask = GetParent();
if( pParentTask )
static_cast<CTaskMelee*>( pParentTask )->SetTargetEntity( pNewBestTarget, false, true );
// Let the caller know what branch to go to.
*pActionIdOut = pActionToDo->GetID();
return true;
}
pTargetEntity = pNewBestTarget;
bAllowNoTargetFallback = true;
}
}
// Otherwise we should fallback to the ActionBranchSet
const CActionBranchSet* pActionBranchSet = m_pActionResult->GetActionBranchSet();
if( pActionBranchSet )
{
bool bShouldDebugPed = CActionManager::ShouldDebugPed( pPed );
// Go over all the available action branches for this actionResult and
// create a list of suitable actions to choose from.
atFixedArray<const CActionDefinition*,MAX_SUITABLE_DEFINITIONS> suitableActions;
const u32 nNumBranches = pActionBranchSet->GetNumBranches();
for( u32 nBranchIdx = 0; nBranchIdx < nNumBranches; ++nBranchIdx )
{
if( !CActionManager::PlayerAiFilterTypeDoesCatchPed( pActionBranchSet->GetBranchFilterType( nBranchIdx ), pPed ) )
continue;
const u32 nBranchActionId = pActionBranchSet->GetBranchActionID( nBranchIdx );
const bool bIsAcceptable = ACTIONMGR.IsActionSuitable(
nBranchActionId,
typeToLookFor,
pImpulseCombo,
false, // This doesn't have to be an entry action (since this is a branch).
false, // This value doesn't matter as we aren't going to test it.
pPed,
0, // There is no one forcing us, so there is no nForcingResultId.
-1,
pTargetEntity,
HasLockOnTargetEntity(),
bImpulseInitiatedMovesOnly,
pPed->IsLocalPlayer(), // If this is a player then test the controls.
bShouldDebugPed );
if( !bIsAcceptable )
continue;
// Try to get the action.
u32 nIndexFound = 0;
const CActionDefinition* pBranchesAction = ACTIONMGR.FindActionDefinition( nIndexFound, nBranchActionId );
Assertf( pBranchesAction, "pBranchesAction was not found for nBranchActionId in CTaskMeleeActionResult::ShouldBranchOnCurrentFrame (if you just reloaded the action table ignore this)." );
if( !pBranchesAction )
continue;
// Add it to the list of acceptable actions.
if( suitableActions.GetCount() < suitableActions.GetMaxCount() )
suitableActions.Append() = pBranchesAction;
}
// Should we check for no target actions if a target action wasn't found?
if( suitableActions.GetCount() == 0 && pTargetEntity && (m_pActionResult->ShouldAllowNoTargetBranches() || bAllowNoTargetFallback) )
{
const u32 nNumBranches = pActionBranchSet->GetNumBranches();
for( u32 nBranchIdx = 0; nBranchIdx < nNumBranches; ++nBranchIdx )
{
if( !CActionManager::PlayerAiFilterTypeDoesCatchPed( pActionBranchSet->GetBranchFilterType( nBranchIdx ), pPed ) )
continue;
const u32 nBranchActionId = pActionBranchSet->GetBranchActionID( nBranchIdx );
const bool bIsAcceptable = ACTIONMGR.IsActionSuitable(
nBranchActionId,
typeToLookFor,
pImpulseCombo,
false, // This doesn't have to be an entry action (since this is a branch).
false, // This value doesn't matter as we aren't going to test it.
pPed,
0, // There is no one forcing us, so there is no nForcingResultId.
-1,
NULL,
false,
bImpulseInitiatedMovesOnly,
pPed->IsLocalPlayer(), // If this is a player then test the controls.
bShouldDebugPed );
if( !bIsAcceptable )
continue;
// Try to get the action.
u32 nIndexFound = 0;
const CActionDefinition* pBranchesAction = ACTIONMGR.FindActionDefinition( nIndexFound, nBranchActionId );
Assertf( pBranchesAction, "pBranchesAction was not found for nBranchActionId in CTaskMeleeActionResult::ShouldBranchOnCurrentFrame (if you just reloaded the action table ignore this)." );
if( !pBranchesAction )
continue;
// Add it to the list of acceptable actions.
if( suitableActions.GetCount() < suitableActions.GetMaxCount() )
suitableActions.Append() = pBranchesAction;
}
// If we found an action make sure we clear out the target entity
if( suitableActions.GetCount() > 0 )
{
pTargetEntity = NULL;
}
}
// Try to select a single action branch to take from the list of actions
// to choose from.
pActionToDo = ACTIONMGR.SelectActionFromSet( &suitableActions, pPed, bShouldDebugPed );
// Check if we found an action to take.
if( pActionToDo )
{
// Let the caller know what branch to go to.
*pActionIdOut = pActionToDo->GetID();
CTask* pParentTask = GetParent();
if( pParentTask )
static_cast<CTaskMelee*>( pParentTask )->SetTargetEntity( pTargetEntity, false, true );
return true;
}
}
}
}
// We didn't find any branches to switch to.
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Shutdown
// PURPOSE : Shutdown this task and release any referenced resources.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::Shutdown( void )
{
if( sm_pTestBound )
{
sm_pTestBound->Release();
sm_pTestBound = NULL;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : HitImpulseCalculation
// PURPOSE : Determine the physical impulse to apply for this hit based on the
// intersection information.
// PARAMETERS : pIntersection - Data about the intersection causing the impulse.
// fImpulseMult - A scaling value for the impulse.
// vImpulseDir - The direction of the impulse.
// fImpulseMag - the strength of the impulse.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::HitImpulseCalculation( WorldProbe::CShapeTestHitPoint* pResult, float fImpulseMult, Vector3& vImpulseDir, float& fImpulseMag, CPed *hitPed )
{
Assertf( pResult, "No valid hit-point data in CTaskMeleeActionResult::HitImpulseCalculation." );
vImpulseDir = pResult->GetHitNormal();
// don't want to just push ped into the ground all the time
const float fHorizontalMag = vImpulseDir.XYMag();
if( fHorizontalMag > 0.01f )
{
vImpulseDir.x /= fHorizontalMag;
vImpulseDir.y /= fHorizontalMag;
vImpulseDir.z = 0.0f;
}
// really want to get the force from the action result that hit us?
if( fImpulseMult > 0.0f )
{
fImpulseMag = fImpulseMult;
}
else if (hitPed && hitPed->IsProne())
{
fImpulseMag = CTaskNMBehaviour::GetPronePedKickImpulse();
}
else
{
fImpulseMag = RAGDOLL_APPLY_MELEE_FORCE_MULT;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DoPhaseWindowsIntersect
// PURPOSE : Helper function to determine if 2 separate time frames intersect
// PARAMETERS : fCurrentPhase - Current clip phase
// fNextPhase - Current phase plus the next time step
// fStartPhase - Beginning of the time window
// fEndPhase - End of the time window
// RETURNS : Whether or not the time ranges intersect
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::DoPhaseWindowsIntersect( float fCurrentPhase, float fNextPhase, float fStartPhase, float fEndPhase )
{
// Check if they intersect.
const float fTimeIntervalDist = fCurrentPhase < fStartPhase ? ( fStartPhase - fNextPhase ) : ( fCurrentPhase - fEndPhase );
if( fTimeIntervalDist < 0 )
return true;
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessCachedNetworkDamage
// PURPOSE : Processes any cached network damage to sync it at correct point.
// PARAMETERS :
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessCachedNetworkDamage(CPed* pPed)
{
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ProcessCachedNetworkDamage." );
//! If we have pending network damage events, process them now.
for(int i = 0; i < s_MaxCachedMeleeDamagePackets; ++i)
{
CTaskMeleeActionResult::MeleeNetworkDamage &CachedDamage = sm_CachedMeleeDamage[i];
if(CachedDamage.m_uNetworkActionID == m_nUniqueNetworkActionID)
{
if(CachedDamage.m_pHitPed &&
(CachedDamage.m_pInflictorPed == GetPed()) &&
!EntityAlreadyHitByResult( CachedDamage.m_pHitPed ) )
{
const CDamageAndReaction* pDamageAndReaction = m_pActionResult->GetDamageAndReaction();
// Default weapon to unarmed
u32 uWeaponHash = pPed->GetWeaponManager() && pDamageAndReaction && !pDamageAndReaction->ShouldUseDefaultUnarmedWeapon() ? pPed->GetWeaponManager()->GetEquippedWeaponHash() : pPed->GetDefaultUnarmedWeaponHash();
fwFlags32 flags( CachedDamage.m_uFlags );
flags.SetFlag( CPedDamageCalculator::DF_AllowCloneMeleeDamage );
AddToHitEntityCount(CachedDamage.m_pHitPed, CachedDamage.m_iComponent);
CWeapon::ReceiveFireMessage(CachedDamage.m_pInflictorPed,
CachedDamage.m_pHitPed,
uWeaponHash,
CachedDamage.m_fDamage,
CachedDamage.m_vWorldHitPos,
CachedDamage.m_iComponent,
-1,
-1,
flags,
CachedDamage.m_uParentMeleeActionResultID,
CachedDamage.m_uNetworkActionID,
CachedDamage.m_uForcedReactionDefinitionID);
MELEE_NETWORK_DEBUGF("[%d] CTaskMeleeActionResult::ProcessCachedNetworkDamage - pFiringEntity = %s State = %d ID = %d Processing Collisions = %d %p",
fwTimer::GetFrameCount(),
CachedDamage.m_pInflictorPed->GetDebugName(),
GetState(),
m_nUniqueNetworkActionID,
m_bHasStartedProcessingCollisions ? 1 : 0,
this);
}
sm_CachedMeleeDamage[i].Reset();
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessSelfDamage
// PURPOSE : Apply any damage to the ped that occurs simply by doing this move.
// It is used to apply damaged during paired moves (where there is
// no hit occurring) and to apply damage to one's self during moves
// like head butts.
// PARAMETERS : pPed - the ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessSelfDamage( CPed* pPed, bool bForceFatalDamage )
{
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ProcessSelfDamage." );
// Make sure we haven't already applied the self damage
if( m_bSelfDamageApplied )
return;
// Make sure we have self damage to apply
if( m_fSelfDamageAmount <= 0.0f )
return;
// Don't process self damage if we are quitting because clone ped didn't run task.
if(pPed->IsNetworkClone() && IsRunningLocally() && CanCloneQuitForcedReaction(pPed))
return;
// Check friendly fire and skip damage if needed
if (!NetworkInterface::IsFriendlyFireAllowed(pPed, GetTargetEntity()))
{
m_bSelfDamageApplied = true;
return;
}
// Create a temp weapon to apply the damage.
u32 nSelfDamageWeaponHash = GetSelfDamageWeaponHash();
CWeapon selfDamageWeapon( nSelfDamageWeaponHash );
// Setup the damage flags
fwFlags32 flags( CPedDamageCalculator::DF_MeleeDamage | CPedDamageCalculator::DF_SelfDamage );
if( ShouldForceDamage() )
flags.SetFlag( CPedDamageCalculator::DF_ForceMeleeDamage );
if( ShouldIgnoreArmor() )
flags.SetFlag( CPedDamageCalculator::DF_IgnoreArmor );
if( ShouldIgnoreStatModifiers() )
flags.SetFlag( CPedDamageCalculator::DF_IgnoreStatModifiers );
if (NetworkInterface::IsInCopsAndCrooks())
{
// Takedown moves are usually an instant kill by dealing 500 damage from the DamageAndReaction result (defined in damages.meta)
// Override this with baseline melee damage
if (m_pActionResult->GetDamageAndReaction() && m_pActionResult->GetDamageAndReaction()->GetSelfDamageAmount() >= pPed->GetMaxHealth())
{
m_fSelfDamageAmount = (float)GetMeleeEnduranceDamage();
}
// Melee attacks by Cops against Crooks are always non-lethal
const CPed* pAttacker = SafeCast(const CPed, GetTargetEntity());
if (ShouldApplyEnduranceDamageOnly(pAttacker, pPed))
{
flags.SetFlag(CPedDamageCalculator::DF_EnduranceDamageOnly);
}
}
//! Force other ped to die if self damage is high enough.
if(m_fSelfDamageAmount > pPed->GetHealth() && bForceFatalDamage)
{
flags.SetFlag( CPedDamageCalculator::DF_FatalMeleeDamage );
}
if(NetworkInterface::IsGameInProgress() &&
pPed->IsNetworkClone() &&
m_pTargetEntity && m_pTargetEntity->GetIsTypePed())
{
// If local player is causing this ped to inflict self damage, send damage event to them as well. This ensures they take damage, even if ped
// didn't run melee task somehow.
CPed* pTargetPed = static_cast<CPed*>(m_pTargetEntity.Get());
bool fHealthMoreThanDamage = pPed && (pPed->GetMaxHealth() > m_fSelfDamageAmount);
if(pTargetPed->IsLocalPlayer() && !pTargetPed->IsDead() && !fHealthMoreThanDamage)
{
eAnimBoneTag eSelfDamageAnimBoneTag = GetSelfDamageAnimBoneTag();
s32 nSelfDamageWeaponComponent = -1;
if( eSelfDamageAnimBoneTag != BONETAG_INVALID )
nSelfDamageWeaponComponent = pPed->GetRagdollComponentFromBoneTag( eSelfDamageAnimBoneTag );
CWeaponDamageEvent::Trigger(pTargetPed,
pPed,
VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ),
nSelfDamageWeaponComponent,
false,
nSelfDamageWeaponHash,
m_fSelfDamageAmount,
-1,
-1,
flags,
0,
m_nUniqueNetworkActionID,
0);
}
}
// Clones do damage on remote side.
if( !pPed->IsNetworkClone() )
{
// Switch off the ragdoll on collision event
pPed->SetActivateRagdollOnCollision( false );
eAnimBoneTag eSelfDamageAnimBoneTag = GetSelfDamageAnimBoneTag();
s32 nSelfDamageWeaponComponent = -1;
if( eSelfDamageAnimBoneTag != BONETAG_INVALID )
nSelfDamageWeaponComponent = pPed->GetRagdollComponentFromBoneTag( eSelfDamageAnimBoneTag );
/// Apply the damage but give the credit to the opponent (if valid)
CEntity* pTargetEntity = GetTargetEntity();
CWeaponDamage::GeneratePedDamageEvent( pTargetEntity ? pTargetEntity : pPed,
pPed,
GetSelfDamageWeaponHash(),
m_fSelfDamageAmount,
VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ),
NULL,
flags,
&selfDamageWeapon,
nSelfDamageWeaponComponent );
// Revert the one hit kill config flags if the event failed to kill the ped
if( !pPed->IsInjured() )
{
pPed->SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth, false );
pPed->SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown, false );
pPed->SetPedConfigFlag( CPED_CONFIG_FLAG_Knockedout, false );
}
}
// The self damage has been applied so we can clear it.
m_bSelfDamageApplied = true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessMeleeClipEvents
// PURPOSE : Iterates through the current clip tag events and determines if
// we have any intersecting time windows. This will drive real- time
// clip events.
// PARAMETERS : pPed - Operating ped
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessMeleeClipEvents( CPed* pPed )
{
// Reset the clip tag flags
m_bAllowInterrupt = false;
m_bAllowDodge = false;
m_bAllowHoming = false;
m_bProcessCollisions = false;
m_bMeleeInvulnerability = false;
m_bFireWeapon = false;
m_bSwitchToRagdoll = false;
const crClip* pClip = fwClipSetManager::GetClip( m_clipSetId, m_clipId );
if( pClip )
{
float fCurrentPhase = 0.0f;
float fAnimRate = 1.0f;
if( m_MoveNetworkHelper.IsNetworkActive() )
{
float fMoveNetworkCurrentPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
if( fMoveNetworkCurrentPhase != -1.0f )
fCurrentPhase = fMoveNetworkCurrentPhase;
float fMoveNetworkAnimRate = m_MoveNetworkHelper.GetFloat( ms_ClipRateId );
if( fMoveNetworkAnimRate != -1.0f )
fAnimRate = fMoveNetworkAnimRate;
}
float fNextPhase = MIN( fCurrentPhase + ( fAnimRate * pClip->ConvertTimeToPhase( GetTimeStep() ) ), 1.0f );
crTag::Key uKey;
crTagIterator it( *pClip->GetTags() );
while(*it)
{
uKey = (*it)->GetKey();
if( uKey == CClipEventTags::Interruptible.GetHash() )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
m_bAllowInterrupt = true;
}
else if ( uKey == CClipEventTags::ObjectVfx.GetHash() )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
ProcessVfxClipTag( pPed, pClip, *it );
}
else if( uKey == CClipEventTags::MeleeHoming.GetHash() )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
m_bAllowHoming = true;
}
else if( uKey == CClipEventTags::MeleeCollision.GetHash() )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
{
m_bProcessCollisions = true;
m_bHasStartedProcessingCollisions = true;
}
}
else if( uKey == CClipEventTags::MeleeInvulnerability.GetHash() )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
m_bMeleeInvulnerability = true;
}
else if( uKey == CClipEventTags::MeleeDodge.GetHash() )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
m_bAllowDodge = true;
}
else if( uKey == CClipEventTags::SwitchToRagdoll )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
m_bSwitchToRagdoll = true;
}
else if( uKey == CClipEventTags::MeleeFacialAnim )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
ProcessFacialClipTag( pPed, pClip, *it );
}
else if( uKey == CClipEventTags::CanSwitchToNm )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
{
if( !pPed->IsNetworkClone() && m_pActionResult && m_pActionResult->ActivateRagdollOnCollision() )
{
CEventSwitch2NM event( 10000, rage_new CTaskNMRelax( 1000, 10000 ) );
pPed->SetActivateRagdollOnCollisionEvent( event );
pPed->SetActivateRagdollOnCollision( true );
}
}
}
else if( uKey == CClipEventTags::Fire )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
m_bFireWeapon = true;
}
else if( uKey == CClipEventTags::CriticalFrame )
{
// Logic we can apply after the critical frame
if( !IsPastCriticalFrame() && fNextPhase >= GetCriticalFrame() )
{
m_bPastCriticalFrame = true;
// Add a shocking event to make people aware of the player swinging a punch.
if( m_pActionResult && (m_pActionResult->GetIsAStandardAttack() || m_pActionResult->GetIsATakedown()) )
{
CEventShockingSeenMeleeAction shockingEvent( *pPed, m_pTargetEntity );
CShockingEventsManager::Add( shockingEvent );
// Check for a player (local or clone) striking a ped target entity
if( pPed->IsPlayer() && m_pTargetEntity && m_pTargetEntity->GetIsTypePed() )
{
// Get a handle to target ped
CPed* pTargetPed = static_cast<CPed*>(m_pTargetEntity.Get());
// If this target ped has a ped group and the config flag set to shout target on player melee
if( pTargetPed->GetPedsGroup() && pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_ShoutToGroupOnPlayerMelee) )
{
// shout to all group members
CEventShoutTargetPosition shoutTargetEvent(pTargetPed, pPed);
pTargetPed->GetPedsGroup()->GiveEventToAllMembers(shoutTargetEvent, pTargetPed);
}
}
}
}
}
else if( uKey == CClipEventTags::MeleeContact )
{
if( DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
{
// Special case where we need to process the post hit results on critical frame
if( m_pActionResult && m_pActionResult->ShouldProcessPostHitResultsOnContactFrame() )
{
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity && pTargetEntity->GetIsTypePed() )
{
Vec3V vPropImpulse( 30.0f, 0.0f, 25.0f );
float fPropImpulseMag = -1.0f;
const crPropertyAttribute* pAttrib = (*it)->GetProperty().GetAttribute("RagdollImpulseDir");
if( pAttrib && pAttrib->GetType() == crPropertyAttribute::kTypeVector3 )
{
const crPropertyAttributeVector3* pVector3Attrib = static_cast<const crPropertyAttributeVector3*>( pAttrib );
vPropImpulse = pVector3Attrib->GetVector3();
}
pAttrib = (*it)->GetProperty().GetAttribute("RagdollImpulseMag");
if( pAttrib && pAttrib->GetType() == crPropertyAttribute::kTypeFloat )
{
const crPropertyAttributeFloat* pFloatAttr = static_cast<const crPropertyAttributeFloat*>( pAttrib );
fPropImpulseMag = pFloatAttr->GetFloat();
}
ProcessPostHitResults( pPed, static_cast<CPed*>( pTargetEntity ), true, vPropImpulse, fPropImpulseMag );
}
}
}
}
else if( uKey == CClipEventTags::MoveEvent )
{
const crPropertyAttribute* pAttribute = (*it)->GetProperty().GetAttribute(CClipEventTags::MoveEvent);
if( pAttribute != NULL && pAttribute->GetType() == crPropertyAttribute::kTypeHashString )
{
static const atHashString ms_BlendInRootIkSlopeFixup("BlendInRootIkSlopeFixup",0xDBFC38CA);
const CRootSlopeFixupIkSolver* pSolver = static_cast<const CRootSlopeFixupIkSolver*>(pPed->GetIkManager().GetSolver(IKManagerSolverTypes::ikSolverTypeRootSlopeFixup));
if ( ( pSolver == NULL || pSolver->GetBlendRate() <= 0.0f ) &&
ms_BlendInRootIkSlopeFixup == static_cast<const crPropertyAttributeHashString*>(pAttribute)->GetHashString() &&
DoPhaseWindowsIntersect( fCurrentPhase, fNextPhase, (*it)->GetStart(), (*it)->GetEnd() ) )
{
pPed->GetIkManager().AddRootSlopeFixup(SLOW_BLEND_IN_DELTA);
}
}
}
++it;
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessVfxClipTag
// PURPOSE : Process a single Vfx tag
// PARAMETERS : pPed - Operating ped
// pClip - Debug use to print anim name
// pTag - Tag that has the current vfx data
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessVfxClipTag( CPed* pPed, const crClip* ASSERT_ONLY(pClip), const crTag* pTag )
{
const crPropertyAttribute* pAttrTrigger = pTag->GetProperty().GetAttribute( CClipEventTags::Trigger );
if ( pAttrTrigger && aiVerifyf( pAttrTrigger->GetType() == crPropertyAttribute::kTypeBool, "Clip [%s] has a tag attribute %s that is not of the expected type [BOOL].", pClip->GetName(), CClipEventTags::Trigger.GetCStr() ) )
{
bool bIsTriggered = ((const crPropertyAttributeBool*)pAttrTrigger)->GetBool() ? true : false;
const crPropertyAttribute* pAttrName = pTag->GetProperty().GetAttribute( CClipEventTags::VFXName );
if ( pAttrName && aiVerifyf( pAttrName->GetType() == crPropertyAttribute::kTypeHashString, "Clip [%s] has a tag attribute %s that is not of the expected type [HashString].", pClip->GetName(), CClipEventTags::VFXName.GetCStr()))
{
const crPropertyAttributeHashString* pHashAttr = (const crPropertyAttributeHashString*)pAttrName;
u32 nVfxIdx = 0;
const CActionVfx* pVfx = ACTIONMGR.FindActionVfx( nVfxIdx, pHashAttr->GetHashString() );
if( pVfx )
{
VfxMeleeTakedownInfo_s vfxInfo;
vfxInfo.ptFxHashName = pVfx->GetVfxID();
vfxInfo.dmgPackHashName = pVfx->GetDamagePackID();
vfxInfo.pAttachEntity = pPed;
vfxInfo.attachBoneTag = pVfx->GetBoneTag();
vfxInfo.vOffsetPos = VECTOR3_TO_VEC3V( pVfx->GetOffset() );
Quaternion tquat;
tquat.FromEulers( pVfx->GetRotation() );
vfxInfo.vOffsetRot.SetXf( tquat.x );
vfxInfo.vOffsetRot.SetYf( tquat.y );
vfxInfo.vOffsetRot.SetZf( tquat.z );
vfxInfo.vOffsetRot.SetWf( tquat.w );
vfxInfo.scale = pVfx->GetScale();
if( bIsTriggered )
g_vfxPed.TriggerPtFxMeleeTakedown( &vfxInfo );
else
g_vfxPed.UpdatePtFxMeleeTakedown( &vfxInfo );
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessFacialClipTag
// PURPOSE : Process a facial clip tag
// PARAMETERS : pPed - Operating ped
// pClip - Debug use to print anim name
// pTag - Tag that has the current facial data
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessFacialClipTag( CPed* pPed, const crClip* ASSERT_ONLY(pClip), const crTag* pTag )
{
// We only care if we have a facial data component
CFacialDataComponent* pFacialData = pPed->GetFacialData();
if( pFacialData )
{
const crPropertyAttribute* pAttrId = pTag->GetProperty().GetAttribute( CClipEventTags::Id );
if ( pAttrId && aiVerifyf( pAttrId->GetType() == crPropertyAttribute::kTypeHashString, "Clip [%s] has a tag attribute %s that is not of the expected type [HASH STRING].", pClip->GetName(), CClipEventTags::Id.GetCStr() ) )
{
const crPropertyAttributeHashString* pHashAttr = (const crPropertyAttributeHashString*)pAttrId;
u32 nFacialSetIdx = 0;
const CActionFacialAnimSet* pFacialSet = ACTIONMGR.FindActionFacialAnimSet( nFacialSetIdx, pHashAttr->GetHashString() );
if( pFacialSet && pFacialSet->GetNumFacialAnims() > 0 )
{
u32 nRandomIdx = fwRandom::GetRandomNumberInRange( 0, pFacialSet->GetNumFacialAnims() );
pFacialData->PlayFacialAnim( pPed, pFacialSet->GetFacialAnimClipId( nRandomIdx ) );
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessWeaponDisarmsAndDrops
// PURPOSE : Handle any dropping or taking away of weapons that occurs during
// this move.
// PARAMETERS : pPed - the ped of interest.
// RETURNS : If the melee task should quit.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::ProcessWeaponDisarmsAndDrops( CPed* pPed )
{
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ProcessWeaponDisarmsAndDrops." );
if( ( !m_bDropWeapon && !m_bDisarmWeapon ) || !pPed->GetWeaponManager() )
return false;
// Get the weapon; note that pWeapon may be null if there is not a weapon in hand.
const CObject* pHeldWeaponObject = pPed->GetWeaponManager()->GetEquippedWeaponObject();
if( pHeldWeaponObject )
{
// Check if we should drop our weapon.
if( m_bDropWeapon )
{
// See if we should drop the weapon in this frame.
// Collect any events since the last frame and if there
// was a AEF_DESTROY_OBJECT event then use it as
// our drop weapon trigger.
bool bDoWeaponDropThisFrame = false;
if( m_MoveNetworkHelper.IsNetworkActive() )
{
const crClip* pClip = m_MoveNetworkHelper.GetClip( ms_ClipId );
float fPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
if( CClipEventTags::FindFirstEventTag<crTag, crPropertyAttributeBool>( pClip, CClipEventTags::Object, CClipEventTags::Destroy, true, MAX( fPhase - 0.1f, 0.0f ), fPhase ) != NULL )
bDoWeaponDropThisFrame = true;
}
else
bDoWeaponDropThisFrame = true;
if( bDoWeaponDropThisFrame )
CPickupManager::CreatePickUpFromCurrentWeapon( pPed );
}
// See if we should grab away our opponents weapon.
CEntity* pTargetEntity = GetTargetEntity();
if( m_bDisarmWeapon && pTargetEntity )
{
bool bDoDisarmThisFrame = false;
const crClip* pClip = m_MoveNetworkHelper.GetClip( ms_ClipId );
if( pClip )
{
const CClipEventTags::CObjectEventTag* pTag = m_MoveNetworkHelper.GetEvent<CClipEventTags::CObjectEventTag>( CClipEventTags::Object );
if( pTag && pTag->IsCreate() )
bDoDisarmThisFrame = true;
}
else
bDoDisarmThisFrame = true;
if( bDoDisarmThisFrame )
{
pPed->GetWeaponManager()->DestroyEquippedWeaponObject();
// Try to get a target ped from the opponent entity.
CPed* pTargetPed = ( pTargetEntity && pTargetEntity->GetIsTypePed() ? static_cast<CPed*>(pTargetEntity) : NULL );
if( pTargetPed )
{
// Transfer the opponents weapon to us.
// Get the weapon; note that pWeapon may be null if there is not a weapon in hand.
const CObject* pTargetPedsHeldWeaponObject = pTargetPed->GetWeaponManager() ? pTargetPed->GetWeaponManager()->GetEquippedWeaponObject() : NULL;
if( pTargetPedsHeldWeaponObject )
{
// Get a pointer to the opponents weapon.
const CWeapon* pWeapon = pTargetPedsHeldWeaponObject->GetWeapon();
Assert( pWeapon );
// Give ourselves the same kind of weapon.
pPed->GetInventory()->AddWeaponAndAmmo( pWeapon->GetWeaponHash(), pWeapon->GetAmmoTotal() );
pPed->GetWeaponManager()->CreateEquippedWeaponObject();
// Equip the weapon straight away
pPed->GetWeaponManager()->EquipWeapon( pWeapon->GetWeaponHash() );
// Make the opponent no longer have their weapon.
pTargetPed->GetInventory()->RemoveWeapon( pWeapon->GetWeaponHash() );
return true;
}
}
}
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessPreHitResults
// PURPOSE : Perform the special result when the action begins
// PARAMETERS : pPed - The owning ped
// pTargetEntity - The current target of the ped.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessPreHitResults( CPed* pPed, CEntity* pTargetEntity )
{
Assert( pPed );
// Do not apply post hit results if already applied
if( m_bPreHitResultsApplied )
return;
const CDamageAndReaction* pDamageAndReaction = m_pActionResult ? m_pActionResult->GetDamageAndReaction() : false;
if( pDamageAndReaction )
{
if( pDamageAndReaction->ShouldStopDistanceHoming() )
{
CTaskMeleeActionResult* pSimpleMeleeActionResultTask = static_cast<CTaskMeleeActionResult*>(pPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_MELEE_ACTION_RESULT ) );
if( pSimpleMeleeActionResultTask )
pSimpleMeleeActionResultTask->StopDistanceHoming();
}
if( pTargetEntity && pTargetEntity->GetIsTypePed() && pDamageAndReaction->ShouldStopTargetDistanceHoming() )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
Assert( pTargetPed );
CTaskMeleeActionResult* pSimpleMeleeActionResultTask = static_cast<CTaskMeleeActionResult*>(pTargetPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_MELEE_ACTION_RESULT ) );
if( pSimpleMeleeActionResultTask)
pSimpleMeleeActionResultTask->StopDistanceHoming();
}
if( pDamageAndReaction->ShouldAddSmallSpecialAbilityCharge() )
{
CPlayerSpecialAbility* pSpecialAbility = pPed->GetSpecialAbility();
if( pSpecialAbility )
pSpecialAbility->AddSmallCharge( false );
}
if( pDamageAndReaction->ShouldDisableInjuredGroundBehvaior() )
{
CPedIntelligence* pIntelligence = pPed->GetPedIntelligence();
if( pIntelligence )
pIntelligence->GetCombatBehaviour().DisableInjuredOnGroundBehaviour();
}
if( !pDamageAndReaction->ShouldPreserveStealthMode() )
{
if( pPed->GetMotionData()->GetUsingStealth() )
pPed->GetMotionData()->SetUsingStealth( false );
}
}
// Toggle this off
m_bPreHitResultsApplied = true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessPostHitResults
// PURPOSE : Perform the special result (if one exists).
// PARAMETERS : pPed - The owning ped
// pTargetEntity - The current target of the ped.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessPostHitResults( CPed* pPed, CPed* pTargetPed, bool bForce, Vec3V_In vRagdollImpulseDir, float fRagdollImpulseMag )
{
// Do not apply post hit results if already applied
if( !bForce && m_bPostHitResultsApplied )
return;
const CDamageAndReaction* pDamageAndReaction = m_pActionResult ? m_pActionResult->GetDamageAndReaction() : nullptr;
if( pDamageAndReaction )
{
// Apply controller rumble
const CActionRumble* pActionRumble = pDamageAndReaction->GetActionRumble();
if( pActionRumble && pPed->IsLocalPlayer() )
CControlMgr::StartPlayerPadShakeByIntensity( pActionRumble->GetDuration(), pActionRumble->GetIntensity() );
#if FPS_MODE_SUPPORTED
camFirstPersonShooterCamera* pFpsCamera = camInterface::GetGameplayDirector().GetFirstPersonShooterCamera();
if( pFpsCamera )
{
if( pPed && pTargetPed )
{
// Trigger camera shake when player is hit by melee attack in first person camera.
// TODO: set using metadata? Directional shakes?
pFpsCamera->OnMeleeImpact(pPed, pTargetPed);
}
}
#endif // FPS_MODE_SUPPORTED
if( pTargetPed && !pTargetPed->IsNetworkClone() && pPed->IsAllowedToDamageEntity( NULL, pTargetPed ) )
{
// Should we knock off ped props? Note: Only knock props off peds we can damage.
if( pDamageAndReaction->ShouldKnockOffProps() )
{
Vector3 vImpulse = VEC3V_TO_VECTOR3( Scale( vRagdollImpulseDir, ScalarV( fRagdollImpulseMag ) ) );
// Limit the flat and up impulse of the hat to ensure that it clears the head instead of going through it.
Vector3 vFlatImpulse = vImpulse;
vFlatImpulse.z = 0.0f;
float fFlatMag = vFlatImpulse.Mag();
TUNE_GROUP_FLOAT(MELEE,HAT_MAX_FLAT_IMPULSE, 7.0f,0.0f,100.0f,1.0f);
vFlatImpulse *= Min(HAT_MAX_FLAT_IMPULSE,fFlatMag)*InvertSafe(fFlatMag,0.0f);
TUNE_GROUP_FLOAT(MELEE,HAT_MIN_UP_IMPULSE,12.0f,0.0f,100.0f,1.0f);
TUNE_GROUP_FLOAT(MELEE,HAT_MAX_UP_IMPULSE,16.0f,0.0f,100.0f,1.0f);
Vector3 vHatImpulse(vFlatImpulse.x,vFlatImpulse.y,Clamp(vImpulse.z,HAT_MIN_UP_IMPULSE,HAT_MAX_UP_IMPULSE));
// Assume that the head is going to move slower than the hat so roll the hat so that the top
// gets oriented towards the impulse vector
TUNE_GROUP_FLOAT(MELEE,HAT_ANG_IMPULSE_SCALE,1.0f,0.0f,10.0f,0.1f);
Vector3 vHatAngImpulse(0.0f,0.0f,1.0f);
vHatAngImpulse.Cross(vHatImpulse);
CPedPropsMgr::KnockOffProps( pTargetPed, true, true, true, false, &vHatImpulse, NULL, &vHatAngImpulse);
}
if( pDamageAndReaction->ShouldInvokeFacialPainAnimation() )
{
// Play facial pain animations
CFacialDataComponent* pFacialData = pTargetPed->GetFacialData();
if( pFacialData && !pTargetPed->IsDead() )
pFacialData->PlayPainFacialAnim( pTargetPed );
}
}
CWeapon* pWeapon = pPed->GetWeaponManager() ?
pPed->GetWeaponManager()->GetEquippedWeapon() : NULL;
if( pWeapon )
{
const CWeaponInfo *pWeaponInfo = pWeapon->GetWeaponInfo();
u32 uNextDiffuseCamoTexture = 0;
// Check that we're using our right hand to do the attack (so we don't apply blood on kicks etc)
// Currently don't have any left hand melee weapons to apply blood to (will need to update this if we want it for unarmed left hand attacks etc).
bool bIsValidStrike = false;
const CStrikeBoneSet* pStrikeBoneSet = m_pActionResult ? m_pActionResult->GetStrikeBoneSet() : NULL;
if (pStrikeBoneSet)
{
const u32 nStrikeBoneCount = pStrikeBoneSet->GetNumStrikeBones();
for( u32 i = 0; i < nStrikeBoneCount; ++i )
{
const eAnimBoneTag strikingBoneTag = pStrikeBoneSet->GetStrikeBoneTag( i );
if (strikingBoneTag == BONETAG_R_HAND)
{
bIsValidStrike = true;
}
}
}
if( bIsValidStrike && pWeaponInfo && pWeaponInfo->GetNumCammoDiffuseTextures() > 0 && pWeaponInfo->GetNextCamoDiffuseTexIdx( *pWeapon, uNextDiffuseCamoTexture ) )
{
pWeapon->SetCamoDiffuseTexIdx( uNextDiffuseCamoTexture );
}
}
}
if( !m_bPostHitResultsApplied && pTargetPed )
{
switch (CCrime::sm_eReportCrimeMethod)
{
case CCrime::REPORT_CRIME_DEFAULT:
if( pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByTakedown) )
{
CCrime::ReportCrime(pTargetPed->IsLawEnforcementPed() ? CRIME_KILL_COP : CRIME_KILL_PED, pTargetPed, pPed);
}
break;
case CCrime::REPORT_CRIME_ARCADE_CNC:
// NFA - ped and cop deaths are handled in script for CnC
break;
}
if(pTargetPed->GetSpeechAudioEntity())
{
pTargetPed->GetSpeechAudioEntity()->MarkToStopSpeech();
if( !pTargetPed->IsDead() && pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_UNALERTED ) )
{
// Probably want to check the last weapon hash that hit this ped and make sure it is 0
float fInsultProbability = fwRandom::GetRandomNumberInRange( 0.0, 1.0f );
if( fInsultProbability < 0.33f)
pTargetPed->GetSpeechAudioEntity()->SayWhenSafe( "ATTACKED_BY_SURPRISE" );
}
}
}
// Toggle this off
m_bPostHitResultsApplied = true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessHomingAndSeparation
// PURPOSE : Apply the minimum separation and homing values set in the
// action table.
// These are used to make moves appear cleaner by helping align the
// ped to it's target.
// It is used to do things like making a punch land exactly on a
// chin even when the punch can be invoked from a variety of
// distances.
// PARAMETERS : pPed - the ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessHomingAndSeparation( CPed* pPed, float fTimeStep, int nTimeSlice )
{
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ProcessHomingAndSeparation." );
if( !m_pActionResult )
return;
CEntity* pTargetEntity = GetTargetEntity();
// Apply homing to the ped.
CHoming* pHoming = GetHoming(pPed);
if( pHoming )
{
if(pPed->IsNetworkClone())
{
//! Don't allow clones to home in on probe homing types. Not really necessary - just use net blended owners position.
if(pHoming && pHoming->IsSurfaceProbe() )
{
m_bDidAllowDistanceHoming = false;
return;
}
//! Don't allow clones to home in on dead peds. Position is unreliable and may not match owner machine.
if(pTargetEntity && pTargetEntity->GetIsTypePed())
{
CPed *pTargetPed = static_cast<CPed*>(pTargetEntity);
if(pTargetPed->IsDead())
{
m_bDidAllowDistanceHoming = false;
return;
}
}
static dev_float s_fHomingToleranceDistance = 3.25f;
static dev_float s_fHomingToleranceKillMoveDistance = 5.0f;
bool bKillMove = m_pActionResult->GetIsATakedown() || m_pActionResult->GetIsAStealthKill() || m_pActionResult->GetIsAKnockout();
float fHomingTolerance = bKillMove ? s_fHomingToleranceKillMoveDistance : s_fHomingToleranceDistance;
Vector3 vCloneTargetPosition = VEC3V_TO_VECTOR3(m_vCloneCachedHomingPosition);
Vector3 vLocalTargetPosition = VEC3V_TO_VECTOR3(m_vCachedHomingPosition);
Vector3 vDist = vCloneTargetPosition - vLocalTargetPosition;
float fDistSq = vDist.Mag2();
if(fDistSq > (fHomingTolerance*fHomingTolerance) )
{
m_bDidAllowDistanceHoming = false;
return;
}
}
const crClip* pClip = NULL;
float fCurrentPhase = -1.0f;
if( m_MoveNetworkHelper.IsNetworkActive() )
{
fCurrentPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
pClip = m_MoveNetworkHelper.GetClip( ms_ClipId );
}
// Unfortunately there is a frame delay despite me setting this information in Start_OnUpdate
if( fCurrentPhase == -1.0f )
fCurrentPhase = GetInitialAnimPhase();
if( !pClip )
pClip = fwClipSetManager::GetClip( m_clipSetId, m_clipId );
bool bAllowHeadingUpdate = AllowHeadingUpdate();
bool bUpdateDesiredHeading = false;
if( bAllowHeadingUpdate && !m_bHeadingInitialized )
{
m_bHeadingInitialized = true;
bUpdateDesiredHeading = true;
}
bool bAllowDistanceHoming = ShouldAllowDistanceHoming();
if(!pPed->IsNetworkClone())
{
// CNC: stop shoulder barge homing if target is ragdolling.
if (NetworkInterface::IsInCopsAndCrooks() && pTargetEntity && pTargetEntity->GetIsTypePed() && m_pActionResult->GetIsShoulderBarge())
{
const CPed* pTargetPed = static_cast<CPed*>(pTargetEntity);
if (pTargetPed->GetUsingRagdoll())
{
bAllowDistanceHoming = false;
}
}
// Special distance homing considerations for standard attacks
if( bAllowDistanceHoming && !pHoming->ShouldIgnoreSpeedMatchingCheck() &&
m_pActionResult->GetIsAStandardAttack() && pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
// If the target ped is running
float fTargetPedmoveBlendRatioSq = pTargetPed->GetMotionData()->GetCurrentMoveBlendRatio().Mag2();
if( fTargetPedmoveBlendRatioSq > rage::square( MBR_RUN_BOUNDARY ) )
{
// Check to see if the target is attempting to run away from you
static dev_float sfTargetDotThreshold = -0.5f;
Vec3V vPedToTarget = pTargetEntity->GetTransform().GetPosition() - pPed->GetTransform().GetPosition();
vPedToTarget = Normalize( vPedToTarget );
const float fTargetDirectionDot = Dot( vPedToTarget, pTargetEntity->GetTransform().GetB() ).Getf();
if( fTargetDirectionDot > sfTargetDotThreshold )
{
static dev_float sfSpeedDifferenceThreshold = 0.95f;
const float fSpeedComparision = fTargetPedmoveBlendRatioSq - GetCachedMoveBlendRatioSq();
if( fSpeedComparision > sfSpeedDifferenceThreshold )
bAllowDistanceHoming = false;
}
}
}
}
//! Don't allow distance homing if owner ped isn't.
if(pPed->IsNetworkClone())
{
if(m_bOwnerStoppedDistanceHoming)
bAllowDistanceHoming = false;
}
else
{
//! Inform clone if owner stops homing.
if( (DidAllowDistanceHoming() || ShouldAllowDistanceHoming()) && !bAllowDistanceHoming)
{
m_bOwnerStoppedDistanceHoming = true;
}
}
pHoming->Apply( pPed, pTargetEntity, m_vCachedHomingPosition, m_vCachedHomingDirection, m_vMoverDisplacement, m_nBoneIdxCache, pClip, fCurrentPhase, bAllowDistanceHoming, DidAllowDistanceHoming(), bAllowHeadingUpdate, HasJustStruckSomething(), bAllowHeadingUpdate && bUpdateDesiredHeading, fTimeStep, nTimeSlice, m_vCachedHomingPedPosition );
// Cache off whether or not we allowed distance homing this time around
m_bDidAllowDistanceHoming = bAllowDistanceHoming;
// If we are a player who is attacking another ped and homing is allowed, set the appropriate reset flag
if( pPed->IsAPlayerPed() &&
(bAllowDistanceHoming || bAllowHeadingUpdate) &&
pTargetEntity && pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
pTargetPed->SetPedResetFlag(CPED_RESET_FLAG_IsBeingMeleeHomedByPlayer, true);
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessMeleeCollisions
// PURPOSE : Determine if during the playback of this move the ped has struck
// anything and if so handle the reactions to that strike.
// PARAMETERS : pPed - the ped of interest.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ProcessMeleeCollisions( CPed* pPed )
{
#if __BANK
if( !CCombatMeleeDebug::sm_bDoCollisions )
return;
#endif // __BANK
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::ProcessMeleeCollisions." );
// Make sure there is an clip playing.
if( !m_MoveNetworkHelper.IsNetworkActive() || !m_MoveNetworkHelper.GetClip( ms_ClipId ) )
return;
// Make sure the action result still exists as it can go away when reloading the action table.
if( !m_pActionResult )
return;
if( !ShouldProcessMeleeCollisions() )
return;
// Determine what bounds to check (this will include bounds
// that approximate weapons).
const phBound* pTestBound = GetCustomHitTestBound( pPed );
if( !pTestBound )
return;
// Make sure we can get the ragdoll data.
if( !pPed->GetRagdollInst() )
return;
const u32 MAX_MELEE_COLLISION_RESULTS_PER_PROBE = 4;
phBoundComposite* pCompositeBound = (phBoundComposite*) pTestBound;
const int nBoundCount = pCompositeBound->GetNumBounds();
if( Verifyf( (nBoundCount * MAX_MELEE_COLLISION_RESULTS_PER_PROBE) <= (BOUND_MAX_STRIKE_CAPSULES * MAX_MELEE_COLLISION_RESULTS_PER_PROBE), "CTaskMeleeActionResult::ProcessMeleeCollisions - Need to increase nMaxNumResults [NumBounds=%d NumCollisionResults=%d NumCollisionResultsPerProbe=%d]!", nBoundCount, BOUND_MAX_STRIKE_CAPSULES * MAX_MELEE_COLLISION_RESULTS_PER_PROBE, MAX_MELEE_COLLISION_RESULTS_PER_PROBE ) )
{
WorldProbe::CShapeTestFixedResults<BOUND_MAX_STRIKE_CAPSULES * MAX_MELEE_COLLISION_RESULTS_PER_PROBE> boundTestResults;
Matrix34 matPed = RCC_MATRIX34( pPed->GetRagdollInst()->GetMatrix() );
// treat the test as type ped, so it'll collide with stuff a ped will (fixes boats, want to collide with BVH not normal pBound)
const u32 nTypeFlags = ArchetypeFlags::GTA_WEAPON_TEST;
u32 nIncludeFlags = ArchetypeFlags::GTA_WEAPON_TYPES &~ ArchetypeFlags::GTA_RIVER_TYPE;
if( pPed->IsPlayer() )
nIncludeFlags |= ArchetypeFlags::GTA_BOX_VEHICLE_TYPE;
// Fill in the descriptor for the batch test.
WorldProbe::CShapeTestBatchDesc batchDesc;
batchDesc.SetOptions(0);
batchDesc.SetIncludeFlags( nIncludeFlags );
batchDesc.SetTypeFlags( nTypeFlags );
batchDesc.SetExcludeEntity( pPed );
batchDesc.SetTreatPolyhedralBoundsAsPrimitives( false );
WorldProbe::CShapeTestCapsuleDesc* pCapsuleDescriptors = Alloca(WorldProbe::CShapeTestCapsuleDesc, nBoundCount);
batchDesc.SetCapsuleDescriptors(pCapsuleDescriptors, nBoundCount);
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
capsuleDesc.SetResultsStructure( &boundTestResults );
capsuleDesc.SetMaxNumResultsToUse( MAX_MELEE_COLLISION_RESULTS_PER_PROBE );
capsuleDesc.SetExcludeEntity( pPed );
capsuleDesc.SetIncludeFlags( nIncludeFlags );
capsuleDesc.SetTypeFlags( nTypeFlags );
capsuleDesc.SetIsDirected( true );
capsuleDesc.SetTreatPolyhedralBoundsAsPrimitives( false );
for( int i = 0; i < nBoundCount; ++i )
{
Matrix34 matCapsule = RCC_MATRIX34( pCompositeBound->GetCurrentMatrix(i) );
matCapsule.Dot( matPed );
const phBound* pBound = pCompositeBound->GetBound(i);
if( pBound )
{
const phBoundCapsule* pCapsule = static_cast<const phBoundCapsule*>( pBound );
TUNE_GROUP_FLOAT( MELEE, fRadiusMult, 1.0f, 0.0f, 10000.0f, 0.1f );
const float fCapsuleRadius = pCapsule->GetRadius() * fRadiusMult;
const Vector3 vCapsuleStart = matCapsule * VEC3V_TO_VECTOR3( pCapsule->GetEndPointA() );
const Vector3 vCapsuleEnd = matCapsule * VEC3V_TO_VECTOR3( pCapsule->GetEndPointB() );
#if __BANK
char szDebugText[128];
formatf( szDebugText, "MELEE_COLLISION_SPHERE_START_%d", i );
CTask::ms_debugDraw.AddSphere( RCC_VEC3V(vCapsuleStart), fCapsuleRadius, Color_green, 2, atStringHash(szDebugText), false );
formatf( szDebugText, "MELEE_COLLISION_SPHERE_END_%d", i );
CTask::ms_debugDraw.AddSphere( RCC_VEC3V(vCapsuleEnd), fCapsuleRadius, Color_green4, 2, atStringHash(szDebugText), false );
formatf( szDebugText, "MELEE_COLLISION_ARROW_%d", i );
CTask::ms_debugDraw.AddArrow( RCC_VEC3V(vCapsuleStart), RCC_VEC3V(vCapsuleEnd), fCapsuleRadius, Color_blue, 2, atStringHash(szDebugText) );
#endif
capsuleDesc.SetFirstFreeResultOffset( i * MAX_MELEE_COLLISION_RESULTS_PER_PROBE );
capsuleDesc.SetCapsule( vCapsuleStart, vCapsuleEnd, fCapsuleRadius );
batchDesc.AddCapsuleTest( capsuleDesc );
}
}
if( WorldProbe::GetShapeTestManager()->SubmitTest( batchDesc ) )
{
// First we need to add all valid results to the sorted result array
atFixedArray< sCollisionInfo, BOUND_MAX_STRIKE_CAPSULES * MAX_MELEE_COLLISION_RESULTS_PER_PROBE > sortedCollisionResults;
for( int iStrikeCapsules = 0; iStrikeCapsules < nBoundCount; iStrikeCapsules++ )
{
for( int iResult = 0; iResult < MAX_MELEE_COLLISION_RESULTS_PER_PROBE; iResult++ )
{
int iIndex = (iStrikeCapsules * MAX_MELEE_COLLISION_RESULTS_PER_PROBE) + iResult;
// Ignore car void material
if( PGTAMATERIALMGR->UnpackMtlId( boundTestResults[iIndex].GetHitMaterialId() ) == PGTAMATERIALMGR->g_idCarVoid )
continue;
phInst* pHitInst = boundTestResults[iIndex].GetHitInst();
if( !pHitInst || !pHitInst->IsInLevel() )
continue;
CEntity* pHitEntity = CPhysics::GetEntityFromInst( pHitInst );
if( !pHitEntity )
continue;
sCollisionInfo& result = sortedCollisionResults.Append();
result.m_pResult = &boundTestResults[iIndex];
result.m_vImpactDirection = pCapsuleDescriptors[iStrikeCapsules].GetEnd() - pCapsuleDescriptors[iStrikeCapsules].GetStart();
result.m_vImpactDirection.NormalizeSafe();
}
}
// Quick sort based on melee collision criteria
sortedCollisionResults.QSort( 0, -1, CTaskMelee::CompareCollisionInfos );
static dev_float sfPedHitDotThreshold = 0.0f;
static dev_float sfSurfaceHitDotThreshold = 0.5f;
Vec3V vPedPosition = pPed->GetTransform().GetPosition();
Vec3V vDesiredDirection( V_ZERO );
// If we have a target entity then use that direction to do the dot threshold comparison
CEntity* pTargetEntity = GetTargetEntity();
//! If target entity is NULL, always hit hard lock target.
if(pPed->GetPlayerInfo() && !pTargetEntity)
{
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
pTargetEntity = rTargeting.GetLockOnTarget();
}
if( pTargetEntity )
{
vDesiredDirection = pTargetEntity->GetTransform().GetPosition() - vPedPosition;
vDesiredDirection.SetZf( 0.0f );
vDesiredDirection = Normalize( vDesiredDirection );
}
// As a backup lets use the desired direction
else
{
float fDesiredHeading = fwAngle::LimitRadianAngleSafe( pPed->GetDesiredHeading() );
vDesiredDirection.SetXf( -rage::Sinf( fDesiredHeading ) );
vDesiredDirection.SetYf( rage::Cosf( fDesiredHeading ) );
}
Vec3V vDirectionToPosition( V_ZERO );
bool bShouldIgnoreHitDotThrehold = m_pActionResult->ShouldIgnoreHitDotThreshold();
// Go over any impacts that were detected.
u32 nNumSortedResults = sortedCollisionResults.GetCount();
for( u32 i = 0; i < nNumSortedResults; ++i )
{
CEntity* pHitEntity = sortedCollisionResults[i].m_pResult->GetHitEntity();
if( !pHitEntity )
continue;
if (NetworkInterface::AreInteractionsDisabledInMP(*pPed, *pHitEntity))
continue;
const bool bIsBreakable = ( IsFragInst( sortedCollisionResults[i].m_pResult->GetHitInst() ) && sortedCollisionResults[i].m_pResult->GetHitInst()->IsBreakable( NULL ) );
// Make sure we haven't already hit this entity component with this
// action (so we don't get double or triple hits, etc. per action).
const bool bEntityAlreadyHitByThisAction = EntityAlreadyHitByResult( pHitEntity );
if( bIsBreakable || !bEntityAlreadyHitByThisAction )
{
// Do not allow non ped collisions that are in the opposite direction of the attack
vDirectionToPosition = sortedCollisionResults[i].m_pResult->GetHitPositionV() - vPedPosition;
vDirectionToPosition = Normalize( vDirectionToPosition );
if( pHitEntity->GetIsTypePed() )
{
// We allow a wider angle to go through on ped melee collisions
if( pHitEntity != pTargetEntity && !bIsBreakable && !bShouldIgnoreHitDotThrehold && Dot( vDesiredDirection, vDirectionToPosition ).Getf() < sfPedHitDotThreshold )
continue;
// Ignore collisions with actions that have a higher priority than the striking peds current action
CPed* pHitPed = static_cast<CPed*>(pHitEntity);
// Don't allow clones to hit other peds until we get notification that it is ok.
if(pPed->IsNetworkClone())
{
MeleeNetworkDamage *pNetworkDamage = FindMeleeNetworkDamageForHitPed(pHitPed);
if(!pNetworkDamage)
continue;
}
// Some peds have melee disabled entirely
if( pHitPed->GetPedResetFlag( CPED_RESET_FLAG_DisableMeleeHitReactions ) )
continue;
// Should we ignore friendly attacks?
if( CActionManager::ArePedsFriendlyWithEachOther( pPed, pHitPed ) )
{
if( pPed->IsLocalPlayer() && pHitPed->GetPedResetFlag( CPED_RESET_FLAG_DisablePlayerMeleeFriendlyAttacks ) )
continue;
else if( !pPed->IsLocalPlayer() && !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_CanAttackFriendly ) )
continue;
}
CTaskMeleeActionResult* pHitPedCurrentMeleeActionResultTask = pHitPed->GetPedIntelligence() ? pHitPed->GetPedIntelligence()->GetTaskMeleeActionResult() : NULL;
if( pHitPedCurrentMeleeActionResultTask )
{
// Invulnerable?
if( pHitPedCurrentMeleeActionResultTask->IsInvulnerableToMelee() )
continue;
// Skip if we currently do not allow interrupts and the current offensive action has a greater priority
bool bAllowInterrupt = pHitPedCurrentMeleeActionResultTask->ShouldAllowInterrupt() ||
pHitPedCurrentMeleeActionResultTask->HasJustStruckSomething();
bool bGreaterPriority = pHitPedCurrentMeleeActionResultTask->GetResultPriority() > GetResultPriority();
if( pHitPedCurrentMeleeActionResultTask->IsAStandardAttack() && ( bGreaterPriority && !bAllowInterrupt ) )
continue;
}
if(pHitPed->GetVehiclePedInside())
{
CVehicle *pVehicleHitPedInside = pHitPed->GetVehiclePedInside();
//! This is just a rough approximation for melee'ing peds in vehicles. A more thourough shapetest may be the way to go, but we can broadly rule the ped
//! in or out here.
if(pVehicleHitPedInside->InheritsFromBike() ||
pVehicleHitPedInside->InheritsFromQuadBike() ||
pVehicleHitPedInside->InheritsFromBoat() ||
pVehicleHitPedInside->InheritsFromAmphibiousQuadBike())
{
//! ok to target peds in these vehicles.
}
else
{
//! don't target peds in vehicles with roofs. Note: May want to do something about melee'ing peds if the door has been blown off, but we don't
//! really support this, so just disable.
if(pVehicleHitPedInside->HasRaisedRoof())
{
continue;
}
}
}
// Check against hands and lower arms as they do not count
u16 iHitComponent = sortedCollisionResults[i].m_pResult->GetHitComponent();
eAnimBoneTag nHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent( iHitComponent );
if( nHitBoneTag==BONETAG_R_FOREARM || nHitBoneTag==BONETAG_R_HAND ||
nHitBoneTag==BONETAG_L_FOREARM || nHitBoneTag==BONETAG_L_HAND )
continue;
}
else
{
//! All network damage is done from owner side.
if(pPed->IsNetworkClone())
continue;
// Tighten up the allow collision angle on all other surfaces
if( pHitEntity != pTargetEntity && !bIsBreakable && !bShouldIgnoreHitDotThrehold && Dot( vDesiredDirection, vDirectionToPosition ).Getf() < sfSurfaceHitDotThreshold )
continue;
if( pHitEntity->GetIsTypeObject() )
{
CObject* pObject = static_cast<CObject*>(pHitEntity);
if( pObject->IsADoor() )
{
audDoorAudioEntity* pDoorAudEntity = (audDoorAudioEntity *)(((CDoor *)pHitEntity)->GetDoorAudioEntity() );
if( pDoorAudEntity && !bEntityAlreadyHitByThisAction )
pDoorAudEntity->ProcessCollisionSound( sortedCollisionResults[i].m_pResult->GetHitTValue(), true, pPed );
}
// Otherwise check if we should ignore this object collision
else
{
if( pObject->GetWeapon() )
continue;
if( pObject->m_nObjectFlags.bAmbientProp || pObject->m_nObjectFlags.bPedProp || pObject->m_nObjectFlags.bIsPickUp )
continue;
if( pObject->GetAttachParent() )
continue;
// Toggle this off and allow melee attacks apply impulses here
if( pObject->m_nObjectFlags.bFixedForSmallCollisions )
{
pObject->m_nObjectFlags.bFixedForSmallCollisions = false;
pObject->SetFixedPhysics( false );
}
}
}
}
// We only ever want to play the audio once, even if it is a breakable
if( !bEntityAlreadyHitByThisAction )
pPed->GetPedAudioEntity()->PlayMeleeCombatHit( m_nSoundNameHash, *sortedCollisionResults[i].m_pResult, m_pActionResult BANK_ONLY(, m_pActionResult->GetName() ) );
// Add the entity component to the list of ones already hit by this result.
AddToHitEntityCount(pHitEntity, sortedCollisionResults[i].m_pResult->GetHitComponent());
// Mark that a hit has occurred.
m_bHasJustStruckSomething = true;
#if __BANK
char szDebugText[128];
formatf( szDebugText, "MELEE_COLLISION_SPHERE_%d", i );
CTask::ms_debugDraw.AddSphere( sortedCollisionResults[i].m_pResult->GetHitPositionV(), 0.05f, Color_red, 2, atStringHash(szDebugText), false );
#endif
}
// Make the hit entity react. If they are a ped that is already reacting to
// a hit then they will most likely only continue that reaction.
TriggerHitReaction( pPed, pHitEntity, sortedCollisionResults[i], bEntityAlreadyHitByThisAction, bIsBreakable );
// If we have hit anything besides a ped, break out of the loop
if( !pHitEntity->GetIsTypePed() )
break;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AddToHitEntityCount
// PURPOSE : Add entity to hit list so we know if we have already hit it.
// PARAMETERS : pHitEntity - the entity to add.
// RETURNS :
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::AddToHitEntityCount(CEntity* pEntity, s32 component)
{
const CEntityComponent entityComponent( pEntity, component );
if( m_nHitEntityComponentsCount < sm_nMaxHitEntityComponents )
{
m_hitEntityComponents[m_nHitEntityComponentsCount] = entityComponent;
++m_nHitEntityComponentsCount;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : EntityAlreadyHitByResult
// PURPOSE : Determine if this entity has already been struck by this action
// result.
// This is used to filter multiple hits coming back from the
// collision detection system and to allow the hit entity to
// properly react to the first strike.
// PARAMETERS : pHitEntity - the entity to check.
// RETURNS : Whether or not the have already been hit by this action.
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::EntityAlreadyHitByResult( const CEntity* pHitEntity ) const
{
// Check if the entity is already in the list (with
// a simple linear search).
for(u32 i = 0; i < m_nHitEntityComponentsCount; ++i)
{
if( pHitEntity == m_hitEntityComponents[i].m_pEntity )
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : HasActiveMeleeCollision
// PURPOSE : Determine if the current clip has a MeleeCollision clip tag
// given the current phase time.
// RETURNS : Whether or not we have any active bone tags
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::HasActiveMeleeCollision( CPed* pPed ) const
{
if( !pPed || !m_pActionResult )
return false;
// Make sure there is an clip playing.
const crClip* pClip = m_MoveNetworkHelper.GetClip( ms_ClipId );
if( !pClip )
return false;
float fPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
if( CClipEventTags::HasEvent( pClip, CClipEventTags::MeleeCollision, fPhase ) )
return true;
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetCustomHitTestBound
// PURPOSE : Create a temporary pBound to use for determining if the ped has
// struck anything or not.
// We don't use the full ragdoll collisions since that would be
// inefficient and would also create spurious collisions, such as
// the toes of the opponents touching during a fist fight.
// PARAMETERS : pPed - the ped to make the pBound for.
// RETURNS : The new custom pBound.
/////////////////////////////////////////////////////////////////////////////////
phBound* CTaskMeleeActionResult::GetCustomHitTestBound( CPed* pPed )
{
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::GetCustomHitTestBound." );
// Make sure there is an action playing.
if( !m_pActionResult )
return NULL;
// Make sure the action has an clip playing.
if( !m_MoveNetworkHelper.GetClip( ms_ClipId ) )
return NULL;
// Make sure there is ragdoll data, since we use it to determine the pBound.
if( !pPed->GetRagdollInst() || !pPed->GetRagdollInst()->GetArchetype() )
return NULL;
// Make sure to get the ragdolls composite pBound, which is used to get the
// individual bone's bounds.
const phBoundComposite* pRagdollBoundComp = dynamic_cast<phBoundComposite*>( pPed->GetRagdollInst()->GetArchetype()->GetBound() );
if( !pRagdollBoundComp )
return NULL;
// Go over all the active strikeBones for this actionResult and
// create a list of capsules to use to approximate the moving bounds.
u32 nNumStrikeCapsules = 0;
StrikeCapsule strikeCapsules[BOUND_MAX_STRIKE_CAPSULES];
const CStrikeBoneSet* pStrikeBoneSet = m_pActionResult->GetStrikeBoneSet();
if( !pStrikeBoneSet )
return NULL;
const u32 nStrikeBoneCount = pStrikeBoneSet->GetNumStrikeBones();
for( u32 i = 0; i < nStrikeBoneCount; ++i )
{
// Get the tag for this strikeBone (used to get the pBound from the ragdoll).
const eAnimBoneTag strikingBoneTag = pStrikeBoneSet->GetStrikeBoneTag( i );
// Get the ragdoll component number of this strikeBone (used to get the pBound from the ragdoll).
const s32 nRagdollComponent = pPed->GetRagdollComponentFromBoneTag( strikingBoneTag );
// Make sure to get the pBound for this strikeBone from the ragdoll.
const phBound* pStrikeBoneBound = pRagdollBoundComp->GetBound( nRagdollComponent );
if( !pStrikeBoneBound )
continue;
// Get the desired strike bone radius from the action table.
const float fDesiredStrikeBoneRadius = pStrikeBoneSet->GetStrikeBoneRadius( i );
// Make an approximation out of capsules for the volume swept though
// space by this ragdoll pBound and if it is holding a weapon then the weapon too.
for( u32 nBoundOrWeapon = 0; nBoundOrWeapon < 2; ++nBoundOrWeapon )
{
float fBoundRadius;
Vector3 vBoundBBoxMin;
Vector3 vBoundBBoxMax;
Matrix34 matBoundStart;
Matrix34 matBoundEnd;
phMaterialMgr::Id boundMaterialId = phMaterialMgr::DEFAULT_MATERIAL_ID;
// Check if we are trying to make an approximation for the bone pBound or
// a held weapon pBound and get some necessary information about the pBound.
if( nBoundOrWeapon == 0 )
{
// Get some information about the strike bone pBound.
fBoundRadius = pStrikeBoneBound->GetRadiusAroundCentroid();
vBoundBBoxMin = VEC3V_TO_VECTOR3( pStrikeBoneBound->GetBoundingBoxMin() );
vBoundBBoxMax = VEC3V_TO_VECTOR3( pStrikeBoneBound->GetBoundingBoxMax() );
matBoundStart = RCC_MATRIX34( pRagdollBoundComp->GetCurrentMatrix( nRagdollComponent ) );
matBoundEnd = RCC_MATRIX34( pRagdollBoundComp->GetLastMatrix( nRagdollComponent ) );
if( USE_GRIDS_ONLY(pStrikeBoneBound->GetType() != phBound::GRID &&) pStrikeBoneBound->GetType() != phBound::COMPOSITE )
boundMaterialId = pStrikeBoneBound->GetMaterialId(0);
}
else
{
// Check if this bone holds a weapon, if so we need to make an approximation
// out of capsules for the volume swept though space by the weapons pBound.
if( strikingBoneTag != BONETAG_R_HAND )
break;
// Get the weapon; note that pWeapon may be null if there is not a weapon in hand.
const CObject* pHeldWeaponObject = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponObject() : NULL;
const CWeapon* pWeapon = pHeldWeaponObject ? pHeldWeaponObject->GetWeapon() : NULL;
if( !pWeapon )
break;
// Get some information about the weapon pBound.
fBoundRadius = pHeldWeaponObject->GetBoundRadius();
vBoundBBoxMin = pHeldWeaponObject->GetBoundingBoxMin();
vBoundBBoxMax = pHeldWeaponObject->GetBoundingBoxMax();
// get current weapon matrix, it's in world space, so transform into local ped space
pHeldWeaponObject->GetMatrixCopy( matBoundStart );
const Matrix34& matPed = RCC_MATRIX34(pPed->GetMatrixRef());
matBoundStart.DotTranspose( matPed );
if( m_bLastKnowWeaponMatCached )
matBoundEnd = m_matLastKnownWeapon;
else
matBoundEnd = matBoundStart;
// Cache off last known weapon matrix
m_bLastKnowWeaponMatCached = true;
m_matLastKnownWeapon = matBoundStart;
// Set the material type.
const phInst* pInst = pHeldWeaponObject->GetFragInst();
if( pInst == NULL )
pInst = pHeldWeaponObject->GetCurrentPhysicsInst();
if( pInst )
{
const phBound* pBound = pInst->GetArchetype()->GetBound();
if( pBound USE_GRIDS_ONLY(&& pBound->GetType() != phBound::GRID) && pBound->GetType() != phBound::COMPOSITE )
boundMaterialId = pBound->GetMaterialId(0);
}
}
// Check if a single swept sphere approximation will be good enough
// to approximate the pBound.
if( fDesiredStrikeBoneRadius > fBoundRadius )
{
// Make a simple capsule for whole pBound based on the components animated start and
// end positions and the radius given in the action table.
if( nNumStrikeCapsules < BOUND_MAX_STRIKE_CAPSULES )
{
strikeCapsules[nNumStrikeCapsules].m_start = matBoundStart.d;
strikeCapsules[nNumStrikeCapsules].m_end = matBoundEnd.d;
strikeCapsules[nNumStrikeCapsules].m_radius = fDesiredStrikeBoneRadius;
strikeCapsules[nNumStrikeCapsules].m_materialId = boundMaterialId;
nNumStrikeCapsules++;
}
}
else
{
// We need to make a more accurate approximation.
// Use multiple capsules with:
// their start points along the fLongestAxisLength of the longest axis of the pBound in it's start position and
// their end points along the fLongestAxisLength of the longest axis of the pBound in it's end position.
// Determine which axis is the longest and get the two points
// at the center of the cap faces for that axis.
Vector3 vLongestAxisMin( VEC3_ZERO );
Vector3 vLongestAxisMax( VEC3_ZERO );
float fLongestAxisLength = 0.0f;
// Determine which axis is longest.
float xLength = Abs( vBoundBBoxMax.x - vBoundBBoxMin.x );
float yLength = Abs( vBoundBBoxMax.y - vBoundBBoxMin.y );
float zLength = Abs( vBoundBBoxMax.z - vBoundBBoxMin.z );
if( (xLength > yLength) && (xLength > zLength) )
{
// The x Axis is longest.
vLongestAxisMin.x = vBoundBBoxMin.x;
vLongestAxisMax.x = vBoundBBoxMax.x;
fLongestAxisLength = xLength;
}
else if( (yLength > xLength) && (yLength > zLength) )
{
// The y Axis is longest.
vLongestAxisMin.y = vBoundBBoxMin.y;
vLongestAxisMax.y = vBoundBBoxMax.y;
fLongestAxisLength = yLength;
}
else
{
// Assume the z axis is longest (which is also good for cases like cubes, etc.).
vLongestAxisMin.z = vBoundBBoxMin.z;
vLongestAxisMax.z = vBoundBBoxMax.z;
fLongestAxisLength = zLength;
}
// Hands need special handling since their bounds are generally too big (due
// to accommodating the hand being open or closed).
// We reduce the length of the pBound toward its start by about half to make
// it smaller so that it represents a closed fist better.
if( ( nBoundOrWeapon == 0 ) && ( ( strikingBoneTag == BONETAG_L_HAND ) || ( strikingBoneTag == BONETAG_R_HAND ) ) )
{
// Bring the vLongestAxisMax closer to vLongestAxisMin and
// reduce the length.
Vector3 vDirAlongLength( vLongestAxisMax - vLongestAxisMin );
vDirAlongLength.Normalize();
fLongestAxisLength -= BOUND_HAND_LENGTH_REDUCTION_AMOUNT;
vLongestAxisMin = vLongestAxisMax - ( vDirAlongLength * fLongestAxisLength );
}
// Determine if one sphere will suffice for this approximation.
if( fLongestAxisLength <= ( 2.0f * fDesiredStrikeBoneRadius ) )
{
// Make the one sphere into a capsule for this part of the
// approximation of the moving pBound.
if( nNumStrikeCapsules < BOUND_MAX_STRIKE_CAPSULES )
{
const Vector3 vPos = vLongestAxisMin + ( ( vLongestAxisMax - vLongestAxisMin ) * 0.5f );
strikeCapsules[nNumStrikeCapsules].m_start = matBoundStart * vPos;
strikeCapsules[nNumStrikeCapsules].m_end = matBoundEnd * vPos;
strikeCapsules[nNumStrikeCapsules].m_radius = fDesiredStrikeBoneRadius;
strikeCapsules[nNumStrikeCapsules].m_materialId = boundMaterialId;
nNumStrikeCapsules++;
}// else don't make any more strike capsules.
}
else
{
// Make the multiple spheres we need into multiple capsules for
// this part of the approximation of the moving pBound.
// Make it so the caps of the approximation round off exactly at
// the ends of the pBound.
const float fLengthToUse = fLongestAxisLength - ( 2.0f * fDesiredStrikeBoneRadius );
Vector3 vLongestAxisDir(vLongestAxisMax - vLongestAxisMin);
vLongestAxisDir.Normalize();
const Vector3 vCapsulesMiddle = ( vLongestAxisMin + vLongestAxisMax ) * 0.5f;
const Vector3 vCapsulesDelta = vLongestAxisDir * fLengthToUse;
const Vector3 vCapsulesStart = vCapsulesMiddle - ( vCapsulesDelta * 0.5f );
//const Vector3 vCapsulesEnd = vCapsulesMiddle + ( vCapsulesDelta * 0.5f );
// Determine how far apart we will allow the spheres in the
// approximation to be.
const float fMinRadiusFromAxis = fDesiredStrikeBoneRadius * (1.0f - BOUND_ACCEPTABLE_DEPTH_ERROR_PORTION);
const float fMaxDistBetweenSphereCenters = 2.0f * rage::Sqrtf( rage::square( fDesiredStrikeBoneRadius ) - rage::square( fMinRadiusFromAxis ) );
// Determine how many spheres we need to approximate this volume.
u32 nNumSpheresForAproximation = u32( rage::Floorf( ( fLengthToUse / fMaxDistBetweenSphereCenters ) + 1.0f ) ) + 1;
// Make the base points along the fLengthToUse for the approximation in local space then
// transform them to determine the start and end positions of the capsules.
nNumSpheresForAproximation = Min( nNumSpheresForAproximation, BOUND_MAX_NUM_SPHERES_ALONG_LENGTH );
for( u32 i =0; i < nNumSpheresForAproximation; ++i )
{
const float fPortion = (float)i / (float)( nNumSpheresForAproximation - 1 );
const Vector3 vPos = vCapsulesStart + ( vCapsulesDelta * fPortion );
// Check if we should make a capsule for this part of the
// approximation of the moving pBound.
if( nNumStrikeCapsules >= BOUND_MAX_STRIKE_CAPSULES )
break; // Don't make any more strike capsules.
else
{
// Make the capsule for this part of the approximation of
// the moving pBound.
strikeCapsules[nNumStrikeCapsules].m_start = matBoundStart * vPos;
strikeCapsules[nNumStrikeCapsules].m_end = matBoundEnd * vPos;
strikeCapsules[nNumStrikeCapsules].m_radius = fDesiredStrikeBoneRadius;
strikeCapsules[nNumStrikeCapsules].m_materialId = boundMaterialId;
nNumStrikeCapsules++;
}
}
}
}
}
}
// Make sure we have some strike capsules to make an approximate pBound from.
if( nNumStrikeCapsules <= 0 )
return NULL;
// Build the approximate pBound from the list of capsules.
if( !sm_pTestBound )
{
sm_pTestBound = rage_new phBoundComposite;
sm_pTestBound->Init(BOUND_MAX_STRIKE_CAPSULES);
for( unsigned int i = 0; i < BOUND_MAX_STRIKE_CAPSULES; ++i )
{
phBoundCapsule* pCapsule = rage_new phBoundCapsule;
sm_pTestBound->SetBound( i, pCapsule );
pCapsule->Release();
}
}
sm_pTestBound->SetNumBounds( nNumStrikeCapsules );
for( unsigned int i = 0; i < nNumStrikeCapsules; ++i )
{
phBoundCapsule* pCapsule = (phBoundCapsule*)sm_pTestBound->GetBound(i);
// Set the material for the capsule.
pCapsule->SetMaterial( strikeCapsules[i].m_materialId );
// Set the capsule size/shape.
const float fCapsuleRadius = strikeCapsules[i].m_radius;
Vector3 vCapsule = strikeCapsules[i].m_end - strikeCapsules[i].m_start;
pCapsule->SetCapsuleSize( fCapsuleRadius, vCapsule.Mag() );
// Set the capsule position/orientation, but don't bother
// orienting capsule if it's near zero length.
Matrix34 matCapsule(Matrix34::IdentityType);
matCapsule.d = strikeCapsules[i].m_start + 0.5f * vCapsule;
if( vCapsule.Mag2() > 0.0001f )
{
vCapsule.Normalize();
matCapsule.MakeRotateTo(YAXIS, vCapsule);
}
sm_pTestBound->SetCurrentMatrix( i, RCC_MAT34V( matCapsule ) );
sm_pTestBound->SetLastMatrix( i, RCC_MAT34V( matCapsule ) );
}
sm_pTestBound->CalculateCompositeExtents();
sm_pTestBound->UpdateBvh( true );
return sm_pTestBound;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : TriggerHitReaction
// PURPOSE : Process the effects of the ped striking something.
// PARAMETERS : pPed - The ped causing the hit.
// pHitEnt - The entity being struck.
// pResult - Data about the collision.
// bEntityAlreadyHit - Whether the hit entity has already been struck by the current action.
// bElementAlreadyHit - Whether the hit entity's element has already been struck by the current action.
// bIsBreakableObject - Whether the hit entity is a breakable
// pTestBound - The pBound casing the hit.
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::TriggerHitReaction( CPed* pPed, CEntity* pHitEnt, sCollisionInfo& refResult, bool bEntityAlreadyHit, bool bBreakableObject )
{
#if __ASSERT
bool bWasUsingRagdoll = pPed->GetUsingRagdoll();
#endif
Assertf( pPed, "pPed is null in CTaskMeleeActionResult::TriggerHitReaction." );
Assertf( pHitEnt, "pHitEnt is null in CTaskMeleeActionResult::TriggerHitReaction." );
Assertf( refResult.m_pResult->GetHitDetected(), "Invalid hit-point info in CTaskMeleeActionResult::TriggerHitReaction." );
weaponDebugf3("CTaskMeleeActionResult::TriggerHitReaction pPed[%p][%s][%s] pHitEnt[%p][%s] bEntityAlreadyHit[%d] bBreakableObject[%d]",pPed,pPed ? pPed->GetModelName() : "",pPed && pPed->GetNetworkObject() ? pPed->GetNetworkObject()->GetLogName() : "",pHitEnt,pHitEnt ? pHitEnt->GetModelName() : "",bEntityAlreadyHit,bBreakableObject);
if( !m_pActionResult )
return;
// Make sure there is an clip playing.
if( !m_MoveNetworkHelper.GetClip( ms_ClipId ) )
return;
WorldProbe::CShapeTestResults tempResults(refResult.m_pResult, 1);
tempResults.Update();
// If we used a custom pBound generated from the motion of the bones / weapon
// then we can use that information to get a normal based on the motion of the strike.
Vector3 vImpactNormal = refResult.m_pResult->GetHitNormal();
if( pHitEnt->GetIsTypePed() )
{
if( refResult.m_vImpactDirection.IsNonZero() )
{
// Get the direction the capsule was facing, and use that as the normal.
vImpactNormal = refResult.m_vImpactDirection;
// check we're not going to apply an force back towards the attacking ped
static bank_float sfCosMaxImpactAngle = Cosf(70.0f * DtoR);
Vector3 vPedForward( VEC3V_TO_VECTOR3( pPed->GetTransform().GetB() ) );
if( DotProduct( vPedForward, vImpactNormal ) < sfCosMaxImpactAngle )
vImpactNormal = vPedForward;
}
}
else if( vImpactNormal.Dot( VEC3V_TO_VECTOR3( pPed->GetTransform().GetB() ) ) < 0.0f )
vImpactNormal = -vImpactNormal;
refResult.m_pResult->SetHitNormal( vImpactNormal );
// DEBUG!! -AC, This should depend on if this is a rag-dolling ped we hit or not...
// Make it only check if the entity has been hit- since the ragdolls are doing silly things any way...
const bool bDoImpact = bBreakableObject || !bEntityAlreadyHit;
// END DEBUG!!
CPed* pTargetPed = NULL;
if( refResult.m_pResult->GetHitDetected() && bDoImpact )
{
const CDamageAndReaction* pDamageAndReaction = m_pActionResult->GetDamageAndReaction();
if( pDamageAndReaction )
{
// Default weapon to unarmed
u32 uWeaponHash = pPed->GetWeaponManager() && !pDamageAndReaction->ShouldUseDefaultUnarmedWeapon() ? pPed->GetWeaponManager()->GetEquippedWeaponHash() : pPed->GetDefaultUnarmedWeaponHash();
CWeapon tempWeapon( uWeaponHash );
bool bCauseFatalMeleeDamage = false;
float fStrikeDamage = pDamageAndReaction->GetOnHitDamageAmountMin();
if (pPed->IsPlayer())
{
float fMaxMeleeDamage = pDamageAndReaction->GetOnHitDamageAmountMax();
TUNE_GROUP_FLOAT(MELEE, fMaxMeleeOverrideModifier, 1.25f, -1.0f, 2.0f, 0.1f);
if(fMaxMeleeOverrideModifier > 0.f)
{
fMaxMeleeDamage = Max(fStrikeDamage, fStrikeDamage * fMaxMeleeOverrideModifier);
}
float fStrengthValue = Clamp(static_cast<float>(StatsInterface::GetIntStat(STAT_STRENGTH.GetStatId())) / 100.0f, 0.0f, 1.0f);
fStrikeDamage = Lerp(fStrengthValue, fStrikeDamage, fMaxMeleeDamage);
}
fStrikeDamage *= pPed->GetAttackStrength();
if(tempWeapon.GetWeaponInfo())
{
// Apply any melee damage multipliers
if (pHitEnt->GetIsTypePed())
{
CPed *pHitPed = static_cast<CPed*>(pHitEnt);
if (NetworkInterface::IsInCopsAndCrooks() && pHitPed->IsPlayer())
{
fStrikeDamage = (float)GetMeleeEnduranceDamage();
}
if (tempWeapon.GetWeaponInfo()->GetIsMeleeFist() && tempWeapon.GetWeaponInfo()->GetMeleeRightFistTargetHealthDamageScaler() != -1 && pHitPed && !pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IgnoreMeleeFistWeaponDamageMult))
{
// Only apply multiplier if strike bone is right fist.
const CStrikeBoneSet* pStrikeBoneSet = m_pActionResult ? m_pActionResult->GetStrikeBoneSet() : NULL;
if (pStrikeBoneSet)
{
const u32 nStrikeBoneCount = pStrikeBoneSet->GetNumStrikeBones();
for( u32 i = 0; i < nStrikeBoneCount; ++i )
{
const eAnimBoneTag strikingBoneTag = pStrikeBoneSet->GetStrikeBoneTag( i );
if (strikingBoneTag == BONETAG_R_HAND)
{
float fScaledDamage = tempWeapon.GetWeaponInfo()->GetMeleeRightFistTargetHealthDamageScaler() * (pHitPed->GetMaxHealth() - pPed->GetDyingHealthThreshold());
fStrikeDamage = fScaledDamage * sm_fMeleeRightFistMultiplier;
}
}
}
}
if (tempWeapon.GetWeaponInfo()->GetMeleeDamageMultiplier() > 0.0f)
{
fStrikeDamage *= tempWeapon.GetWeaponInfo()->GetMeleeDamageMultiplier();
}
}
// Apply the weapon damage modifier
fStrikeDamage *= tempWeapon.GetWeaponInfo()->GetWeaponDamageModifier();
}
MeleeNetworkDamage *pNetworkDamage = NULL;
bool bCanDamage = true;
if(pHitEnt->GetIsTypePed())
{
if (NetworkInterface::IsGameInProgress() && !ShouldDamagePeds())
{
bCanDamage = false;
}
pTargetPed = static_cast<CPed*>(pHitEnt);
//Increment successful counters
if (pPed->IsLocalPlayer() && m_pActionResult->GetIsACounterAttack())
{
StatId stat = StatsInterface::GetStatsModelHashId("SUCCESSFUL_COUNTERS");
if (StatsInterface::IsKeyValid(stat))
{
StatsInterface::IncrementStat(stat, 1.0f);
}
}
// Factor in damage flags and friendliness
bCanDamage &= !pTargetPed->m_nPhysicalFlags.bNotDamagedByAnything &&
( !CActionManager::ArePedsFriendlyWithEachOther( pPed, pTargetPed ) ||
pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_CanAttackFriendly ) );
//@@: range CTASKMELEEACTIONRESULT_TRIGGERHITREACTION {
#if TAMPERACTIONS_ENABLED && TAMPERACTIONS_INVINCIBLECOP
if(TamperActions::IsInvincibleCop(pTargetPed))
{
bCanDamage = false;
}
#endif
//@@: } CTASKMELEEACTIONRESULT_TRIGGERHITREACTION
// null out damage if we are attempting to hit a friendly ped.
if( NetworkInterface::IsGameInProgress() && pPed->IsAPlayerPed() && pTargetPed->IsAPlayerPed() )
{
bool bRespawning = false;
CNetObjPlayer* pNetObjPlayer = static_cast<CNetObjPlayer *>(pTargetPed->GetNetworkObject());
if( pNetObjPlayer && pNetObjPlayer->GetRespawnInvincibilityTimer() > 0)
bRespawning = true;
// Factor in respawning
bCanDamage &= !bRespawning;
}
if( bCanDamage )
{
u16 iHitComponent = refResult.m_pResult->GetHitComponent();
// Should we cause fatal damage?
if( pDamageAndReaction->ShouldCauseFatalHeadShot() )
{
eAnimBoneTag nHitBoneTag = pTargetPed->GetBoneTagFromRagdollComponent( iHitComponent );
bool bBlockingCriticalHitsInMP = NetworkInterface::IsGameInProgress() && pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_NoCriticalHits); //url:bugstar:5771401
if( nHitBoneTag == BONETAG_HEAD && !pTargetPed->m_nPhysicalFlags.bNotDamagedByMelee && !bBlockingCriticalHitsInMP
NOTFINAL_ONLY( && !( pTargetPed->IsDebugInvincible() ) )
)
{
bCauseFatalMeleeDamage = true;
}
}
// Should we force fatal damage?
if( pDamageAndReaction->ShouldCauseFatalShot() &&
pTargetPed->GetPedHealthInfo() && pTargetPed->GetPedHealthInfo()->ShouldCheckForFatalMeleeCardinalAttack() &&
!pTargetPed->PopTypeIsMission() && !pTargetPed->IsPlayer() )
{
bCauseFatalMeleeDamage = true;
}
// The iHitComponent here could have come from a low, medium or high lod ragdoll the ePhysicsLOD enum only really makes sense on
// iHitComponent from high lod
if ( pTargetPed && pTargetPed->GetRagdollInst() && pTargetPed->GetRagdollInst()->GetCurrentPhysicsLOD() == fragInst::RAGDOLL_LOD_HIGH)
{
// Intentionally done after the check for causing fatal head shots. Only want the reaction (and not the damage) to be altered by moving the hit location
u16 iNewHitComponent = iHitComponent;
if( pDamageAndReaction->ShouldMoveLowerArmAndHandHitsToClavicle() )
{
if( iHitComponent == RAGDOLL_LOWER_ARM_LEFT || iHitComponent == RAGDOLL_HAND_LEFT )
{
iNewHitComponent = RAGDOLL_CLAVICLE_LEFT;
}
else if( iHitComponent == RAGDOLL_LOWER_ARM_RIGHT || iHitComponent == RAGDOLL_HAND_RIGHT )
{
iNewHitComponent = RAGDOLL_CLAVICLE_RIGHT;
}
}
if( pDamageAndReaction->ShouldMoveUpperArmAndClavicleHitsToHead() )
{
if( iHitComponent == RAGDOLL_CLAVICLE_LEFT || iHitComponent == RAGDOLL_CLAVICLE_RIGHT || iHitComponent == RAGDOLL_UPPER_ARM_LEFT || iHitComponent == RAGDOLL_UPPER_ARM_RIGHT )
{
iNewHitComponent = RAGDOLL_HEAD;
}
}
if ( iNewHitComponent != iHitComponent )
{
refResult.m_pResult->SetHitComponent( iNewHitComponent );
Matrix34 matNewComponent;
if( pTargetPed->GetRagdollComponentMatrix( matNewComponent, iNewHitComponent ) )
{
refResult.m_pResult->SetHitPosition( matNewComponent.d );
}
}
}
}
CTaskMeleeActionResult* pTaskMeleeActionResult = static_cast<CTaskMeleeActionResult*>(pTargetPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_MELEE_ACTION_RESULT));
if( !pTargetPed->IsNetworkClone() && pTaskMeleeActionResult && pTaskMeleeActionResult->GetActionResult() && pTaskMeleeActionResult->GetActionResult()->ActivateRagdollOnCollision() )
{
CEventSwitch2NM event( 10000, rage_new CTaskNMRelax( 1000, 10000 ) );
pTargetPed->SetActivateRagdollOnCollisionEvent( event );
pTargetPed->SetActivateRagdollOnCollision( true );
}
if(GetPed()->IsNetworkClone())
pNetworkDamage = FindMeleeNetworkDamageForHitPed(pTargetPed);
}
// Reset the strike damage when we cannot damage the entity
if( !bCanDamage )
fStrikeDamage = 0.0f;
// Disable any forced fatal damage in Cops & Crooks
if (NetworkInterface::IsInCopsAndCrooks())
{
bCauseFatalMeleeDamage = false;
}
// We might not want to do the same damage to objects and vehicles as we do to peds
CWeaponDamage::sDamageModifiers damModifiers;
// Scale damage caused to vehicles from melee attacks down a bit.
damModifiers.fVehDamageMult = CWeaponDamage::MELEE_VEHICLE_DAMAGE_MODIFIER;
// Use the normal weapon impact code.
fwFlags32 flags( CPedDamageCalculator::DF_MeleeDamage );
if( ShouldForceDamage() )
flags.SetFlag( CPedDamageCalculator::DF_ForceMeleeDamage );
if( ShouldIgnoreArmor() )
flags.SetFlag( CPedDamageCalculator::DF_IgnoreArmor );
if( ShouldIgnoreStatModifiers() )
flags.SetFlag( CPedDamageCalculator::DF_IgnoreStatModifiers );
if( bCauseFatalMeleeDamage )
flags.SetFlag( CPedDamageCalculator::DF_FatalMeleeDamage );
if( bEntityAlreadyHit )
flags.SetFlag( CPedDamageCalculator::DF_SuppressImpactAudio );
if (NetworkInterface::IsInCopsAndCrooks() && pHitEnt->GetIsTypePed())
{
// Melee attacks by Cops against Crooks are always non-lethal
const CPed* pTargetPed = static_cast<const CPed*>(pHitEnt);
if (ShouldApplyEnduranceDamageOnly(pPed, pTargetPed))
{
flags.SetFlag(CPedDamageCalculator::DF_EnduranceDamageOnly);
}
}
CWeaponDamage::sMeleeInfo meleeInfo;
meleeInfo.uParentMeleeActionResultID = m_nResultId;
meleeInfo.uNetworkActionID = m_nUniqueNetworkActionID;
meleeInfo.uForcedReactionDefinitionID = INVALID_ACTIONDEFINITION_ID;
bool bDoDamage = false;
if(GetPed()->IsNetworkClone())
{
if(pNetworkDamage)
{
flags = pNetworkDamage->m_uFlags;
flags.SetFlag( CPedDamageCalculator::DF_AllowCloneMeleeDamage );
fStrikeDamage = pNetworkDamage->m_fDamage;
meleeInfo.uParentMeleeActionResultID = pNetworkDamage->m_uParentMeleeActionResultID;
meleeInfo.uNetworkActionID = pNetworkDamage->m_uNetworkActionID;
meleeInfo.uForcedReactionDefinitionID = pNetworkDamage->m_uForcedReactionDefinitionID;
bDoDamage = true;
//! NULL out if we apply damage.
pNetworkDamage->Reset();
}
}
else
{
bDoDamage = true;
}
Vector3 hitDirection = VEC3_ZERO;
u32 nForcedReactionDefinitionID = INVALID_ACTIONDEFINITION_ID;
if(bDoDamage)
{
CTaskMelee::ResetLastFoundActionDefinitionForNetworkDamage();
CWeaponDamage::DoWeaponImpact( &tempWeapon,
pPed,
VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ),
tempResults,
fStrikeDamage,
flags,
false,
false,
&damModifiers,
&meleeInfo, false, 0, 1.0f, 1.0f, 1.0f, false, true, pHitEnt->GetIsTypeVehicle() ? &hitDirection: 0);
const CActionDefinition* pMeleeActionToDo = CTaskMelee::GetLastFoundActionDefinitionForNetworkDamage();
if(pMeleeActionToDo)
{
nForcedReactionDefinitionID = pMeleeActionToDo->GetID();
}
}
static dev_bool bSendDamagePacket = true;
if(NetworkInterface::IsGameInProgress() && !pPed->IsNetworkClone() && bSendDamagePacket && bDoDamage)
{
tempWeapon.SendFireMessage( pPed,
VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ),
tempResults,
1,
true,
fStrikeDamage,
flags,
m_nResultId,
m_nUniqueNetworkActionID,
nForcedReactionDefinitionID, NULL, 1.f, 1.f, 1.f, &hitDirection);
}
}
}
#if __ASSERT
bool bUsingRagdoll = pPed->GetUsingRagdoll();
if(pPed->IsNetworkClone() && bUsingRagdoll)
{
taskAssertf(bWasUsingRagdoll, "Clone has triggered ragdoll as part of simulated hit reaction. This shouldn't happen!");
}
#endif
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CheckForForcedReaction
// PURPOSE : This will determine whether or not to force a reaction to the target ped
// used for synchronized animations
// PARAMETERS : pPed- Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::CheckForForcedReaction( CPed* pPed )
{
// If we have a valid action, check to see if there are any immediate results to apply
if( m_pActionResult )
{
// Try to get the parent ped if it is a ped.
if( pPed )
{
// Determine if the action is an attack and should thus trigger some audio effects.
if( m_pActionResult->GetIsAStandardAttack() )
{
const CStrikeBoneSet* pStrikeBoneSet = m_pActionResult->GetStrikeBoneSet();
if( pStrikeBoneSet && pStrikeBoneSet->GetNumStrikeBones() > 0 && m_nSoundNameHash != 0)
{
pPed->GetPedAudioEntity()->PlayMeleeCombatSwipe(m_nSoundNameHash/*, pStrikeBoneSet->GetStrikeBoneTag(0) */);
m_bHasTriggerMeleeSwipeAudio = true;
}
}
CEntity* pTargetEntity = GetTargetEntity();
CPed* pTargetPed = pTargetEntity && pTargetEntity->GetIsTypePed() ? static_cast<CPed*>(pTargetEntity) : NULL;
if( pTargetPed && m_bForceImmediateReaction )
{
const CActionDefinition* pForcedReactionDefinition = NULL;
if( GetPed()->IsNetworkClone() )
{
//! Don't flush tasks for a dead ped. This is bad as it can result in the dreaded "dead ped standing". If we need to play an animation whilst
//! dead, we'll need to actively support it through dead dying task. And, at this stage, I don't think we need to.
if( !pTargetPed->IsDead() && !pTargetPed->GetUsingRagdoll())
{
//! m_pForcedReactionActionDefinition is synced across network.
pForcedReactionDefinition = m_pForcedReactionActionDefinition;
}
}
else
{
// Check if by playing this actionResult we should force the target
// into a specific actionResult. If so try to force them to do it by
// sending them a CEventGivePedTask right now.
// Check to see if we can find an appropriate forced reaction that
// is available for the target to do.
CActionFlags::ActionTypeBitSetData typeToLookFor;
typeToLookFor.Set( CActionFlags::AT_HIT_REACTION, true );
pForcedReactionDefinition = ACTIONMGR.SelectSuitableAction(
typeToLookFor, // The type we want to search in.
NULL, // No Impulse combo
false, // Test whether or not it is an entry action.
true, // does not matter.
pTargetPed, // The hit ped
m_pActionResult->GetID(), // What result is invoking.
m_pActionResult->GetPriority(), // Priority
pPed, // the acting ped
false, // We don't have a lock on them.
false, // Should we only allow impulse initiated moves.
pTargetPed->IsLocalPlayer(), // If the target is a player then test the controls.
CActionManager::ShouldDebugPed( pTargetPed ) );
m_pForcedReactionActionDefinition = pForcedReactionDefinition;
if( !pTargetPed->IsNetworkClone() )
{
Assertf( m_pForcedReactionActionDefinition, "No appropriate reaction could be found for forced reaction %s in CTaskMeleeActionResult::CTaskMeleeActionResult.", m_pActionResult->GetName() );
}
}
// Check if we found an appropriate reaction, if not then do nothing.
if( pForcedReactionDefinition )
{
// Try to get a pointer to the actionResult via the
// actionResult Id for the reaction.
const CActionResult* pReactionResult = m_pForcedReactionActionDefinition->GetActionResult( pPed );
// Check if we found an appropriate reaction, if not then do nothing.
if( pReactionResult )
{
s32 iMeleeFlags = CTaskMelee::MF_IsDoingReaction | CTaskMelee::MF_ForcedReaction;
// Flush the target peds current task
taskAssertf(!pTargetPed->IsDead(), "Shouldn't flush tasks for a dead ped. This will abort the dead dying task, which is bad! See DMKH");
// Tell the ambient clips to match the blend rate. This is required otherwise there will be an animation pop
if( CTaskAmbientClips* pAmbientClipTask = static_cast<CTaskAmbientClips *>( pTargetPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_AMBIENT_CLIPS ) ) )
pAmbientClipTask->SetCleanupBlendOutDelta( pReactionResult->GetAnimBlendInRate() );
else if( CTaskComplexEvasiveStep* pEvasiveStepTask = static_cast<CTaskComplexEvasiveStep*>( pTargetPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_COMPLEX_EVASIVE_STEP ) ) )
pEvasiveStepTask->SetBlendOutDeltaOverride( pReactionResult->GetAnimBlendInRate() );
//! Try to stop ped moving from here on in.
pTargetPed->SetVelocity( pTargetPed->GetReferenceFrameVelocity() );
pTargetPed->StopAllMotion();
pTargetPed->SetDesiredVelocity( pTargetPed->GetReferenceFrameVelocity() );
pTargetPed->SetDesiredHeading( pTargetPed->GetCurrentHeading() );
// Force update the clip to play immediately - saves a frame delay
pTargetPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAIUpdate, true );
pTargetPed->SetPedResetFlag( CPED_RESET_FLAG_AllowCloneForcePostCameraAIUpdate, true );
// try and make the clone ragdoll on death if possible (the master ped may not be able to and in this case a standing animated
// death will happen)
if (pTargetPed->IsNetworkClone())
{
pTargetPed->SetPedConfigFlag(CPED_CONFIG_FLAG_ForceRagdollUponDeath, true);
}
MELEE_NETWORK_DEBUGF("[%d] CTaskMeleeActionResult::CheckForForcedReaction - Target = %s Previous State %d", fwTimer::GetFrameCount(), pTargetPed->GetDebugName(), GetPreviousState());
// let the target ped know they are dying via stealth
if( m_pActionResult->GetIsAStealthKill() )
{
pTargetPed->SetRagdollOnCollisionIgnorePhysical( pPed );
if(!pTargetPed->IsNetworkClone())
{
pTargetPed->SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth, true );
}
CEventShockingSeenPedKilled ev( *pPed, pTargetPed );
// Make this event less noticeable based on the pedtype.
float fKilledPerceptionModifier = pTargetPed->GetPedModelInfo()->GetKilledPerceptionRangeModifer();
if (fKilledPerceptionModifier >= 0.0f)
{
ev.SetVisualReactionRangeOverride(fKilledPerceptionModifier);
ev.SetAudioReactionRangeOverride(fKilledPerceptionModifier);
}
CShockingEventsManager::Add( ev );
}
else if( m_pActionResult->GetIsATakedown() )
{
// Takedowns can be unsuccessful
const CDamageAndReaction* pDamageAndReaction = pReactionResult->GetDamageAndReaction();
if( pDamageAndReaction && pDamageAndReaction->GetSelfDamageAmount() > 0 )
{
pTargetPed->SetRagdollOnCollisionIgnorePhysical( pPed );
if(!pTargetPed->IsNetworkClone())
{
pTargetPed->SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown, true );
}
CEventShockingSeenPedKilled ev( *pPed, pTargetPed );
// Make this event less noticeable based on the pedtype.
float fKilledPerceptionModifier = pTargetPed->GetPedModelInfo()->GetKilledPerceptionRangeModifer();
if (fKilledPerceptionModifier >= 0.0f)
{
ev.SetVisualReactionRangeOverride(fKilledPerceptionModifier);
ev.SetAudioReactionRangeOverride(fKilledPerceptionModifier);
}
CShockingEventsManager::Add( ev );
}
}
CTaskMelee* pReactionTask = rage_new CTaskMelee( pReactionResult, pPed, iMeleeFlags );
if( pReactionTask && pTargetPed->GetPedIntelligence()->GetTaskActive()->GetTaskType() != CTaskTypes::TASK_INCAPACITATED) // CTaskIncapacitated will not abort for this on the owner so prevent it running on the clone as well
{
Assert( pTargetPed->GetPedIntelligence() );
//! kick off a local task, this keeps animations in sync throughout.
if( pTargetPed->IsNetworkClone() )
pTargetPed->GetPedIntelligence()->AddLocalCloneTask(pReactionTask, PED_TASK_PRIORITY_PHYSICAL_RESPONSE);
else
{
CEventGivePedTask forcedReactionEvent( PED_TASK_PRIORITY_PHYSICAL_RESPONSE, pReactionTask, false, E_PRIORITY_DAMAGE_PAIRED_MOVE );
pTargetPed->GetPedIntelligence()->AddEvent( forcedReactionEvent );
}
}
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CanCloneQuitForcedReaction
// PURPOSE : Check that clone ped ran/is running forced reaction melee task.
// PARAMETERS : pTargetPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::CanCloneQuitForcedReaction(CPed *pTargetPed) const
{
taskAssert(pTargetPed && pTargetPed->IsNetworkClone());
bool bKillFlagSet = pTargetPed->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth) || pTargetPed->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown);
bool bIsUsingNm = pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_NM_CONTROL );
bool bIsIncapacitated = pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_INCAPACITATED );
if( (bIsUsingNm || pTargetPed->IsDead() || bIsIncapacitated) && !bKillFlagSet)
{
return true;
}
return false;
};
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ComputeInitialAnimPhase
// PURPOSE : Helper function used to determine what the best initial phase will
// be given the clip distance tags.
// PARAMETERS : pPed - Ped we are interested in
// pClip - Anim clip we are about to play
/////////////////////////////////////////////////////////////////////////////////
float CTaskMeleeActionResult::ComputeInitialAnimPhase( const crClip* pClip )
{
float fInitialPhase = 0.0f;
const CEntity* pTargetEntity = GetTargetEntity();
if( pClip && pClip->HasTags() && pTargetEntity )
{
CPed* pPed = GetPed();
Vector3 vPedToTarget = VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() - pTargetEntity->GetTransform().GetPosition() );
float fDistToTarget = vPedToTarget.Mag();
// Create an iterator to find the tag with the closest distance to our actual distance from cover
float fBestDist = -1.0f;
float fBestPhase = -1.0f;
float fLastKnownBestDist = 10000.0f;
float fLastKnownBestPhase = -1.0f;
CClipEventTags::CTagIteratorAttribute<CClipEventTags::CDistanceMarkerTag, crPropertyAttributeFloat> it( *pClip->GetTags(), CClipEventTags::DistanceMarker, CClipEventTags::Distance );
while( *it )
{
const float fTagDistance = (*it)->GetData()->GetDistance();
if( fTagDistance <= fDistToTarget )
{
if( fBestDist < fTagDistance )
{
fBestDist = fTagDistance;
fBestPhase = (*it)->GetMid();
}
}
else if( fTagDistance <= fLastKnownBestDist )
{
fLastKnownBestDist = fTagDistance;
fLastKnownBestPhase = (*it)->GetMid();
}
++it;
}
// Check if we have a better phase to use
if( fBestPhase != -1.0f )
{
// Should we lerp between the second best phase and best phase?
if( fLastKnownBestPhase != -1.0f )
fInitialPhase = Lerp( Abs( fLastKnownBestDist - fDistToTarget ) / Abs( fLastKnownBestDist - fBestDist ), fBestPhase, fLastKnownBestPhase );
else
fInitialPhase = fBestPhase;
}
else if( fLastKnownBestPhase != -1.0f )
fInitialPhase = fLastKnownBestPhase;
#if __DEV
TUNE_GROUP_BOOL( MELEE, DISPLAY_DISTANCE_TAG_DEBUG, false );
if( DISPLAY_DISTANCE_TAG_DEBUG )
{
Displayf("============================================");
Displayf("Dist To Target<%.2f>", fDistToTarget );
if( fBestPhase != -1.0f && fLastKnownBestPhase != -1.0f )
{
Displayf( "Dist To Prev Phase<%.2f> / Total Dist<%.2f> = <%.2f>", Abs( fLastKnownBestDist - fDistToTarget ), Abs( fBestDist - fLastKnownBestDist ), Abs( fLastKnownBestDist - fDistToTarget ) / Abs( fBestDist - fLastKnownBestDist ) );
Displayf( "Lerp<%.2f, %.2f> = Initial Phase<%.2f>", fBestPhase, fLastKnownBestPhase, fInitialPhase );
}
else
Displayf("Initial Phase<%.2f>", fInitialPhase );
Displayf("Best Dist<%.2f>", fBestDist );
Displayf("============================================");
}
#endif // __DEV
}
return fInitialPhase;
}
CHoming * CTaskMeleeActionResult::GetHoming(const CPed *pPed)
{
if(m_pActionResult)
{
// Homing doesn't support combat on moving platform, disabled it for now (B*1898494)
if(pPed && pPed->GetGroundPhysical() && pPed->GetGroundPhysical()->GetIsTypeVehicle())
{
if(pPed->GetReferenceFrameVelocity().IsNonZero())
{
return NULL;
}
}
return m_pActionResult->GetHoming();
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DropWeaponAndCreatePickup
// PURPOSE : Drop weapon and create pickup
// PARAMETERS : pPed- Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::DropWeaponAndCreatePickup(CPed* pPed)
{
CPedWeaponManager* pWeaponMgr = pPed->GetWeaponManager();
if(pWeaponMgr)
{
bool bCanCreatePickupIfUnarmed = CPickupManager::CanCreatePickupIfUnarmed(pPed);
const CWeaponInfo* pWeaponInfo = pWeaponMgr->GetEquippedWeaponInfo();
if(pWeaponInfo)
{
const bool bHasPickup = CPickupManager::ShouldUseMPPickups(pPed) ? (pWeaponInfo->GetMPPickupHash() != 0) : (pWeaponInfo->GetPickupHash() != 0);
CPickup* pPickup = bHasPickup ? CPickupManager::CreatePickUpFromCurrentWeapon(pPed) : NULL;
if (pPickup)
{
if (pPed->IsLocalPlayer() && pPickup->GetNetworkObject())
{
CObject* pPlayerWeaponObject = NULL;
if(pWeaponInfo && !pWeaponInfo->GetIsUnarmed())
{
pPlayerWeaponObject = pWeaponMgr->DropWeapon(pWeaponMgr->GetEquippedWeaponHash(), false, false);
}
// pass the weapon and pickup over to the player's network object. The pickup will remain hidden while the player is dead, and the weapon will be
// deleted once he respawns
NetworkInterface::SetLocalDeadPlayerWeaponPickupAndObject(pPickup, pPlayerWeaponObject);
}
else
{
pWeaponMgr->EquipWeapon(pPed->GetDefaultUnarmedWeaponHash(), -1, true);
}
}
else
{
// Drop the object
if(!pWeaponInfo->GetIsUnarmed())
{
pWeaponMgr->DropWeapon(pWeaponMgr->GetEquippedWeaponHash(), false);
}
else if(bCanCreatePickupIfUnarmed)
{
CPickupManager::CreatePickUpFromCurrentWeapon(pPed, false, true);
}
}
}
else if(bCanCreatePickupIfUnarmed)
{
CPickupManager::CreatePickUpFromCurrentWeapon(pPed, false, true);
}
}
if(!pPed->IsLocalPlayer())
{
if(pPed->GetInventory())
{
pPed->GetInventory()->RemoveAllWeaponsExcept( pPed->GetDefaultUnarmedWeaponHash() );
}
}
else
{
//! Don't let player's dead task create another pickup (which can happen as we don't clear inventory).
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_DoesntDropWeaponsWhenDead, true);
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ShouldApplyEnduranceDamageOnly
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::ShouldApplyEnduranceDamageOnly(const CPed* pAttackingPed, const CPed* pVictimPed) const
{
if (NetworkInterface::IsInCopsAndCrooks() && pVictimPed && pAttackingPed)
{
bool attackerIsCop = pAttackingPed->IsPlayer() && pAttackingPed->GetPlayerInfo()->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_COP;
bool victimIsCrook = pVictimPed->IsPlayer() && pVictimPed->GetPlayerInfo()->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_CROOK;
if (attackerIsCop && victimIsCrook)
{
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Start_OnEnter
// PURPOSE : CTaskMeleeActionResult entry point for the CTask state machine
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::Start_OnEnter( CPed* pPed )
{
if(GetPed()->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMeleeActionResult::Start_OnEnter - Player = %s Action %p Previous State %d", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), m_pActionResult, GetPreviousState());
}
// Set up/cache action result.
CacheActionResult( pPed );
// This will determine whether or not to force a reaction to the target ped
// used for synchronized animations
CheckForForcedReaction( pPed );
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity )
{
if( pTargetEntity->GetIsTypePed() )
{
CPed* pTargetPed = static_cast<CPed*>(pTargetEntity);
m_vCachedHeadIkOffset = pTargetPed->GetBonePositionCachedV( BONETAG_HEAD ) - pPed->GetTransform().GetPosition();
}
}
//! Create ID for this melee attack. Note: This needs to be unique across all machines, so we OR in player index to a static counter.
if(m_nUniqueNetworkActionID == 0 && NetworkInterface::IsGameInProgress() && NetworkInterface::GetLocalPlayer())
{
++CClonedTaskMeleeResultInfo::m_nActionIDCounter;
//! 1st 8 bits reserved for player ID. Last 8 bits reserved for counter. Note: I could probably infer player index from the
//! ped ID on remote side.
m_nUniqueNetworkActionID = NetworkInterface::GetLocalPhysicalPlayerIndex() << 8;
m_nUniqueNetworkActionID |= CClonedTaskMeleeResultInfo::m_nActionIDCounter;
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::Start_OnEnter - Assigning unique ID = %d Address %p", fwTimer::GetFrameCount(), m_nUniqueNetworkActionID, this );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Start_OnUpdate
// PURPOSE : Decides whether or not we plan to play an animation
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeActionResult::Start_OnUpdate( CPed* pPed )
{
if(GetPed()->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMeleeActionResult::Start_OnUpdate - Player = %s Action %p", fwTimer::GetFrameCount(), GetPed()->GetDebugName(), m_pActionResult);
}
//! If we are a clone and are locally ragdolling, see if we can fixup. If not, just bail.
if(pPed->GetUsingRagdoll())
{
bool bCanSwitchToAnimated = false;
if(pPed->IsNetworkClone())
{
//! Note: We fixup if this is an attack anim, or if ped is currently being killed.
if(m_pActionResult)
{
bCanSwitchToAnimated = m_pActionResult->GetIsATakedown() ||
m_pActionResult->GetIsAStealthKill() ||
m_pActionResult->GetIsAKnockout() ||
m_pActionResult->GetIsAStandardAttack() ||
pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByStealth) ||
pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByTakedown);
}
}
if(bCanSwitchToAnimated)
{
pPed->SwitchToAnimated();
}
else
{
taskDebugf1("Quit: CTaskMeleeActionResult::Start_OnUpdate: !bCanSwitchToAnimated");
SetState( State_Finish );
return FSM_Continue;
}
}
// If there is a valid action result, branch to either playing an clip or
// not depending on that action
if( m_bPlaysClip )
{
fwClipSet* pSet = fwClipSetManager::GetClipSet( m_clipSetId );
if( pSet )
{
pSet->Request_DEPRECATED();
}
const crClip* pClip = fwClipSetManager::GetClip( m_clipSetId, m_clipId );
//! Remove Post V.
//! HACK. url:bugstar:1598543. Can't patch data or anims, so hard code fallback clipset ID here.
static const fwMvClipId MISSING_CLIP_1("HIT_COUNTER_ATTACK_L",0x5d6d28ef);
static const fwMvClipId MISSING_CLIP_2("HIT_COUNTER_ATTACK_R",0x0d0f8831);
if(!pClip && ((m_clipId.GetHash() == MISSING_CLIP_1) || (m_clipId.GetHash() == MISSING_CLIP_2)) )
{
static const fwMvClipSetId CLIP_SET_MELEE_FALLBACK("MELEE@UNARMED@STREAMED_CORE",0xfd5a244a);
m_clipSetId = CLIP_SET_MELEE_FALLBACK;
fwClipSet* pSet = fwClipSetManager::GetClipSet( m_clipSetId );
if( pSet && pSet->Request_DEPRECATED() )
{
pClip = fwClipSetManager::GetClip( m_clipSetId, m_clipId );
}
}
//! Remove Post V
//! In network games, we may need to play a reaction for a weapon type that we haven't streamed in yet, so don't assert
//! as we allow a small amount of time to do so.
if(!NetworkInterface::IsNetworkOpen() && !pPed->IsNetworkClone())
{
if(!pClip)
{
taskAssertf( 0, "Failed to find clip %s in clip set %s", m_clipId.GetCStr(), m_clipSetId.GetCStr() );
//! Bail out if we couldn't find clip - don't want to get stuck.
taskDebugf1("Quit: CTaskMeleeActionResult::Start_OnUpdate: !pClip");
SetState( State_Finish );
}
}
if(NetworkInterface::IsGameInProgress() && m_pActionResult && !pClip)
{
bool bExecution = m_pActionResult->GetIsAStealthKill() ||
m_pActionResult->GetIsATakedown() ||
m_pActionResult->GetIsAKnockout() ||
pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByStealth) ||
pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_KilledByTakedown);
static dev_float s_fTimeOutExecution = 2.0f;
static dev_float s_fTimeOutOffensive = 0.5f;
static dev_float s_fTimeOut = 0.25f;
float fTimeOut;
if(bExecution)
{
fTimeOut = s_fTimeOutExecution;
}
else if(IsOffensiveMove())
{
fTimeOut = s_fTimeOutOffensive;
}
else
{
fTimeOut = s_fTimeOut;
}
if(GetTimeInState() > fTimeOut)
{
// Couldn't stream execution anim in time, or its something we aren't too bothered about, so
// just bail. This should be pretty rare.
Displayf("CTaskMeleeActionResult::Start_OnUpdate - failed to stream clip %s in clip set %s in time!", m_clipId.TryGetCStr(), m_clipSetId.TryGetCStr());
taskDebugf1("Quit: CTaskMeleeActionResult::Start_OnUpdate: GetTimeInState() (%.2f) > fTimeOut (%.2f)", GetTimeInState(), fTimeOut);
SetState( State_Finish );
}
}
else if( pClip && PrepareMoveNetwork( pPed ))
{
m_MoveNetworkHelper.SetClipSet( m_clipSetId );
m_MoveNetworkHelper.SetClip( pClip, ms_ClipId );
if( IsUpperbodyOnly() )
{
static const atHashWithStringNotFinal clipFilterId( "BONEMASK_UPPERONLY",0xD89921AE );
if( !m_pClipFilter )
m_pClipFilter = CGtaAnimManager::FindFrameFilter( clipFilterId.GetHash(), pPed );
if( m_pClipFilter )
m_pClipFilter->AddRef();
m_MoveNetworkHelper.SetFilter( m_pClipFilter, ms_ClipFilterId );
m_MoveNetworkHelper.SetFloat( ms_IkShadowWeightId, 1.0f );
}
else
{
m_MoveNetworkHelper.SetFilter( NULL, ms_ClipFilterId );
m_MoveNetworkHelper.SetFloat( ms_IkShadowWeightId, 0.0f );
}
// Based on the ideal distance attempt to find a phase tag to enter this attack
m_fInitialPhase = ComputeInitialAnimPhase( pClip );
m_MoveNetworkHelper.SetFloat( ms_ClipPhaseId, m_fInitialPhase );
// For optimization reasons we precompute the mover displacement here instead of doing every frame
if( CClipEventTags::FindEventPhase( pClip, CClipEventTags::CriticalFrame, m_fCriticalFrame ) )
{
m_vMoverDisplacement = VECTOR3_TO_VEC3V( fwAnimHelpers::GetMoverTrackDisplacement( *pClip, 0.f, m_fCriticalFrame ) );
m_bPastCriticalFrame = false;
}
else
m_vMoverDisplacement = VECTOR3_TO_VEC3V( fwAnimHelpers::GetMoverTrackDisplacement( *pClip, 0.f, 1.0f ) );
bool bUseAdditiveClip = false;
if( m_pActionResult && m_pActionResult->AllowAdditiveAnims() )
{
const CWeaponInfo* pWeaponInfo = pPed->GetWeaponManager()->GetEquippedWeaponInfo();
if( pWeaponInfo && pWeaponInfo->GetMeleeClipSetId(*pPed) != CLIP_SET_ID_INVALID )
{
fwMvClipId additiveClipId("additive_damage_idle_fix",0xEE85260C);
const crClip* pAdditiveClip = fwClipSetManager::GetClip( pWeaponInfo->GetMeleeClipSetId(*pPed), additiveClipId );
if( pAdditiveClip != NULL )
{
bUseAdditiveClip = true;
m_MoveNetworkHelper.SetClip( pAdditiveClip, ms_AdditiveClipId );
}
}
}
bool bUseGrip = false;
if (pPed->GetWeaponManager() && pPed->GetWeaponManager()->GetEquippedWeaponInfo() && pPed->GetWeaponManager()->GetEquippedWeaponInfo()->GetIsMeleeFist())
{
fwMvClipSetId gripClipSetId = pPed->GetWeaponManager()->GetEquippedWeaponInfo()->GetMeleeClipSetId(*pPed);
if (gripClipSetId != CLIP_SET_ID_INVALID)
{
fwMvClipId gripClipId("GRIP_IDLE", 0x3ec63b58);
const crClip* pGripClip = fwClipSetManager::GetClip( gripClipSetId, gripClipId );
if (pGripClip != NULL)
{
bUseGrip = true;
m_MoveNetworkHelper.SetClipSet(gripClipSetId, ms_GripClipSetId);
crFrameFilter *pFilter = CTaskMelee::GetMeleeGripFilter(gripClipSetId);
if (pFilter)
{
m_MoveNetworkHelper.SetFilter(pFilter, CTaskHumanLocomotion::ms_MeleeGripFilterId);
}
}
}
}
m_MoveNetworkHelper.SetFlag( bUseAdditiveClip, ms_UseAdditiveClipId );
m_MoveNetworkHelper.SetFlag( bUseGrip, ms_HasGrip );
SetState( State_PlayClip );
return FSM_Continue;
}
}
else
{
// Nothing to do
taskDebugf1("Quit: CTaskMeleeActionResult::Start_OnUpdate: !m_bPlaysClip");
SetState( State_Finish );
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlayClip_OnEnter
// PURPOSE : Setup to play the designated CTaskMeleeActionResult animation
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::PlayClip_OnEnter( CPed* pPed )
{
#if __ASSERT
if(pPed->IsNetworkClone())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::PlayClip_OnEnter [%p] - sequence ID = %onedd", CNetwork::GetNetworkTime(), this, GetNetSequenceID() );
}
#endif
taskFatalAssertf( m_pActionResult, "Valid action result expected at this point!" );
taskAssertf( m_MoveNetworkHelper.IsNetworkActive(), "Melee Move network isn't attached. Prev State: %s (%f)", GetStaticStateName(GetPreviousState()), GetTimeRunning());
bool bLockingOn = false;
if(pPed->GetPlayerInfo())
{
CPlayerPedTargeting& rTargeting = pPed->GetPlayerInfo()->GetTargeting();
bLockingOn = rTargeting.GetLockOnTarget() != NULL;
}
if(!bLockingOn && !pPed->IsNetworkClone())
{
float fDesiredHeading = 0.0f;
if( m_pActionResult->ShouldUseCachedDesiredHeading() )
fDesiredHeading = pPed->GetDesiredHeading();
else
fDesiredHeading = CActionManager::CalculateDesiredHeading( pPed );
pPed->SetDesiredHeading(fDesiredHeading);
}
// Turn on leg ik
if( !pPed->GetIKSettings().IsIKFlagDisabled( IKManagerSolverTypes::ikSolverTypeLeg ) && ( pPed->IsPlayer() || m_pActionResult->ShouldUseLegIk() ) )
{
pPed->m_PedConfigFlags.SetPedLegIkMode( LEG_IK_MODE_FULL_MELEE );
}
// Force update the clip to play immediately - saves a frame delay
pPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAnimUpdate, true );
CEntity* pTargetEntity = GetTargetEntity();
CHoming* pHoming = GetHoming(pPed);
if( pHoming )
{
InitHoming(pPed, pTargetEntity);
//! Clones need to do a test from where they are from, to the target position.
if(pPed->IsNetworkClone() && pTargetEntity && m_pActionResult)
{
static dev_float SPHERE_RADIUS = 0.2f;
static dev_float TEST_Z_OFFSET = 0.0f;
Vector3 vTestPos(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()));
vTestPos.z += TEST_Z_OFFSET;
Vector3 vTargetPos = VEC3V_TO_VECTOR3(pTargetEntity->GetTransform().GetPosition());
vTargetPos.z += TEST_Z_OFFSET;
const u32 TEST_FLAGS = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_GLASS_TYPE;
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
capsuleDesc.SetCapsule(vTestPos, vTargetPos, SPHERE_RADIUS);
capsuleDesc.SetIncludeFlags(TEST_FLAGS);
bool bKillMove = m_pActionResult->GetIsATakedown() || m_pActionResult->GetIsAStealthKill() || m_pActionResult->GetIsAKnockout();
static dev_bool s_TryToWarpToOwnerPos = false;
bool bWarpToOwnerPos = false;
if(s_TryToWarpToOwnerPos)
{
if(!vTestPos.IsClose(NetworkInterface::GetLastPosReceivedOverNetwork(pPed), 0.1f))
{
if((bKillMove && WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc)))
{
WorldProbe::CShapeTestCapsuleDesc capsuleDesc2;
capsuleDesc2.SetCapsule(NetworkInterface::GetLastPosReceivedOverNetwork(pPed), vTargetPos, SPHERE_RADIUS);
capsuleDesc2.SetIncludeFlags(TEST_FLAGS);
//! Check actual clone 'master' position is clear. If so, warp ped there and home in from that location.
if(!WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc2))
{
bWarpToOwnerPos = true;
}
}
}
}
static dev_bool s_bForceWarp = false;
if(s_bForceWarp || bWarpToOwnerPos)
{
//! Go to correct location.
NetworkInterface::GoStraightToTargetPosition(pPed);
Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
Vector3 vDirection = vTargetPos - vPedPos;
vDirection.NormalizeSafe();
float fOwnerPedHeading = rage::Atan2f(-vDirection.x, vDirection.y);
pPed->SetHeading(fOwnerPedHeading);
//! Init homing against using new position.
InitHoming(pPed, pTargetEntity);
}
}
}
else
{
StopDistanceHoming();
}
// Apply any special results that are defined
ProcessPreHitResults( pPed, pTargetEntity );
}
void CTaskMeleeActionResult::InitHoming(CPed *pPed, CEntity* pTargetEntity)
{
CHoming* pHoming = m_pActionResult->GetHoming();
if( pTargetEntity )
{
int rnBoneCacheIdx = -1;
if( !CActionManager::TargetTypeGetPosDirFromEntity( m_vCachedHomingPosition,
m_vCachedHomingDirection,
pHoming->GetTargetType(),
pTargetEntity,
pPed,
pPed->GetTransform().GetPosition(),
0.0f,
rnBoneCacheIdx ) )
{
m_vCachedHomingPosition = pTargetEntity->GetTransform().GetPosition();
m_vCachedHomingDirection = m_vCachedHomingPosition - pPed->GetTransform().GetPosition();
}
// Store the initial ped position
m_vCachedHomingPedPosition = pPed->GetTransform().GetPosition();
if( !pPed->IsNetworkClone() && pHoming->ShouldUseActivationDistance() )
{
Vec3V vPedToTarget = m_vCachedHomingPosition - pPed->GetTransform().GetPosition();
float fDistToTarget = Mag( vPedToTarget ).Getf();
if( fDistToTarget > pHoming->GetActivationDistance() )
{
//taskAssertf(0, "Stopped homing immediately - why did we pick this action!?");
StopDistanceHoming();
}
}
}
else if( pHoming->IsSurfaceProbe() )
{
// Super special case
float fRootOffsetZ = pHoming->ShouldUseLowRootZOffset() ? CActionManager::ms_fLowSurfaceRootOffsetZ : 0.0f;
int rnBoneCacheIdx = -1;
if( !CActionManager::TargetTypeGetPosDirFromEntity( m_vCachedHomingPosition,
m_vCachedHomingDirection,
pHoming->GetTargetType(),
NULL,
pPed,
pPed->GetTransform().GetPosition(),
0.0f,
rnBoneCacheIdx,
pHoming->GetActivationDistance(),
fRootOffsetZ ) )
{
// If we couldn't find anything they just let the move play out normally
StopDistanceHoming();
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlayClip_OnUpdate
// PURPOSE : Main update loop for a CTaskMeleeActionResult animation. In charge
// of monitoring clip events and when to switch to ragdoll.
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeActionResult::PlayClip_OnUpdate( CPed* pPed )
{
taskAssert( m_MoveNetworkHelper.IsNetworkActive() );
if( !m_MoveNetworkHelper.IsInTargetState() )
return FSM_Continue;
/*static dev_float s_fDamageTime = 0.2f;
if(GetTimeInState() > s_fDamageTime && m_bOffensiveMove && pPed->IsLocalPlayer())
{
u32 nWeaponHash = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponHash() : pPed->GetDefaultUnarmedWeaponHash();
CWeaponDamage::GeneratePedDamageEvent(m_pTargetEntity,
pPed,
nWeaponHash,
5000.0f,
VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ),
NULL,
CPedDamageCalculator::DF_MeleeDamage | CPedDamageCalculator::DF_ForceMeleeDamage | CPedDamageCalculator::DF_FatalMeleeDamage,
NULL,
-1,
0.0f,
m_nResultId,
false);
}*/
//! url:bugstar:1591188. If our target ped has gone into ragdoll, and hasn't been killed by melee, then abort. Indicates something else
//! has triggered ragdoll on them. (e.g. being run over by car).
CEntity* pTargetEntity = GetTargetEntity();
CPed* pTargetPed = pTargetEntity && pTargetEntity->GetIsTypePed() ? static_cast<CPed*>(pTargetEntity) : NULL;
if( pTargetPed && pTargetPed->IsNetworkClone() && m_bForceImmediateReaction )
{
CTaskMeleeActionResult* pSimpleMeleeActionResultTask = static_cast<CTaskMeleeActionResult*>(pTargetPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_MELEE_ACTION_RESULT ) );
if( !pSimpleMeleeActionResultTask && CanCloneQuitForcedReaction(pTargetPed))
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMeleeActionResult::PlayClip_OnUpdate: !pSimpleMeleeActionResultTask && CanCloneQuitForcedReaction(pTargetPed)");
return FSM_Quit;
}
}
CTask* pParentTask = GetParent();
CTaskMelee* pParentMeleeTask = pParentTask ? static_cast<CTaskMelee*>( GetParent() ) : NULL;
bool bForcedReaction = pParentMeleeTask && pParentMeleeTask->GetIsTaskFlagSet( CTaskMelee::MF_ForcedReaction );
// B*1724650 - Quit the melee task if the equipped weapon info is different to the equipped weapon info that the ped is actually using
// as the damage being applied will be incorrect
if(pPed->IsLocalPlayer() && IsOffensiveMove() && !bForcedReaction )
{
const CWeapon* pEquippedWeapon = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeapon() : NULL;
u32 uEquippedWeaponHash = pEquippedWeapon ? pEquippedWeapon->GetWeaponHash() : -1;
u32 uSelectedWeaponHash = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetSelectedWeaponHash() : -1;
if (uSelectedWeaponHash != uEquippedWeaponHash)
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMeleeActionResult::PlayClip_OnUpdate: uSelectedWeaponHash (%d) != uEquippedWeaponHash (%d)", uSelectedWeaponHash, uEquippedWeaponHash);
return FSM_Quit;
}
}
bool bQuitEarly = false;
if( !pPed->IsNetworkClone() && (!pPed->IsPlayer() || ShouldAllowPlayerToEarlyOut()) )
bQuitEarly = ShouldAllowInterrupt() && pParentMeleeTask && pParentMeleeTask->GetIsTaskFlagSet( CTaskMelee::MF_QuitTaskAfterNextAction );
if (pPed->GetUsingRagdoll() && pPed->IsNetworkClone())
bQuitEarly = true;
if (!m_bPastCriticalFrame && pTargetPed && pPed->IsNetworkClone() && pTargetPed->GetIsDeadOrDying() && !IsOffensiveMove())
{
// quit out of the melee reaction if the attacking ped has died before striking the local ped
m_bSelfDamageApplied = true;
bQuitEarly = true;
}
if( m_MoveNetworkHelper.GetBoolean( ms_AnimClipFinishedId ) || bQuitEarly )
{
//! If this is a network kill move, then don't let local ped exit clip until told to by the owner (or we migrate). This prevents the clone ped
//! standing back up when dead.
if(pPed->IsNetworkClone() && bForcedReaction)
{
m_bCloneKillAnimFinished = true;
m_nCloneKillAnimFinishedTime = fwTimer::GetTimeInMilliseconds();
}
else
{
if( pParentMeleeTask && pParentMeleeTask->GetIsTaskFlagSet( CTaskMelee::MF_ShouldStartGunTaskAfterNextAction ) )
{
taskDebugf1("Quit: CTaskMeleeActionResult::PlayClip_OnUpdate: m_MoveNetworkHelper.GetBoolean( ms_AnimClipFinishedId ) (%d), bQuitEarly (%d), MF_ShouldStartGunTaskAfterNextAction1", m_MoveNetworkHelper.GetBoolean( ms_AnimClipFinishedId ), bQuitEarly);
return FSM_Quit;
}
taskDebugf1("Quit: CTaskMeleeActionResult::PlayClip_OnUpdate: m_MoveNetworkHelper.GetBoolean( ms_AnimClipFinishedId ) (%d), bQuitEarly (%d), pPed->IsNetworkClone() (%d), bForcedReaction (%d)", m_MoveNetworkHelper.GetBoolean( ms_AnimClipFinishedId ), bQuitEarly, pPed->IsNetworkClone(), bForcedReaction);
SetState( State_Finish );
return FSM_Continue;
}
}
//! If flushed tasks, wait until both owner and clone have finished before exiting.
if(m_bCloneKillAnimFinished)
{
int nTimeout = CNetwork::DEFAULT_CONNECTION_TIMEOUT + 2000;
bool bTimeOutExpired = fwTimer::GetTimeInMilliseconds() > (m_nCloneKillAnimFinishedTime + nTimeout);
//! If we are waiting around for ped to respond, then don't leave this state as ped will stand back up. This can happen if ped has gone link dead.
bool bQuit = true;
if(!pPed->IsDead() && !bTimeOutExpired && !pPed->GetUsingRagdoll() && !ShouldApplyEnduranceDamageOnly(pTargetPed, pPed))
{
bQuit = false;
}
if(bQuit)
{
if( pParentMeleeTask && pParentMeleeTask->GetIsTaskFlagSet( CTaskMelee::MF_ShouldStartGunTaskAfterNextAction ) )
{
taskDebugf1("Quit: CTaskMeleeActionResult::PlayClip_OnUpdate: MF_ShouldStartGunTaskAfterNextAction2");
return FSM_Quit;
}
taskDebugf1("Quit: CTaskMeleeActionResult::PlayClip_OnUpdate: m_bCloneKillAnimFinished, bQuit");
SetState( State_Finish );
return FSM_Continue;
}
}
// Drop weapons if killed
if( !pPed->IsNetworkClone() && !m_bPickupDropped && m_MoveNetworkHelper.GetBoolean( ms_DropWeaponOnDeath ) && !pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DoesntDropWeaponsWhenDead) && ( pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown ) || pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth ) ) && pPed->IsFatallyInjured() )
{
DropWeaponAndCreatePickup(pPed);
m_bPickupDropped = true;
}
// Process any clip clip events
ProcessMeleeClipEvents( pPed );
// Should we fire our weapon?
if( ShouldFireWeapon() )
{
const CWeaponInfo* pWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL;
if( pWeaponInfo && pWeaponInfo->GetIsGun() )
{
CWeapon* pWeapon = pPed->GetWeaponManager()->GetEquippedWeapon();
if( pWeapon )
{
Matrix34 matWeapon = MAT34V_TO_MATRIX34( pPed->GetMatrix() );
CObject* pWeaponObject = pPed->GetWeaponManager()->GetEquippedWeaponObject();
if( pWeaponObject )
matWeapon = MAT34V_TO_MATRIX34( pWeaponObject->GetMatrix() );
// calculate the END of the firing vector using the specific position
Vector3 vStart, vEnd;
pWeapon->CalcFireVecFromGunOrientation( pPed, matWeapon, vStart, vEnd );
// fire the weapon
CWeapon::sFireParams params( pPed, matWeapon, &vStart, &vEnd );
pWeapon->Fire( params );
}
}
}
// Process any dropping or grabbing of weapons.
if( !pPed->IsNetworkClone() && ProcessWeaponDisarmsAndDrops( pPed ) && pParentMeleeTask )
pParentMeleeTask->SetTaskFlag( CTaskMelee::MF_ShouldStartGunTaskAfterNextAction );
// Check to see if somebody has sent us into ragdoll or if
// force the ragdoll state from an anim event
if(ShouldSwitchToRagdoll())
{
// Allow clones to process damage. This is essentially sending self damage over the network to the owner ped so that
// we can ensure they die.
if(pPed->IsNetworkClone())
{
ProcessSelfDamage( pPed, true );
}
if( !pPed->IsNetworkClone() && !pPed->GetUsingRagdoll())
{
if(CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_DIE))
{
MELEE_NETWORK_DEBUGF("[%d] CTaskMeleeActionResult::PlayClip_OnUpdate: Creating ragdoll event for %s", fwTimer::GetFrameCount(), pPed->GetDebugName());
CEventSwitch2NM event( 10000, rage_new CTaskNMRelax( 1000, 10000 ) );
pPed->SwitchToRagdoll( event );
}
return FSM_Continue; // stay in this state. The ragdoll event will take care of aborting us during the next event update
// if we exit now, we'll immediately run the next task whilst the ped is actually in ragdoll.
}
}
// Set the flag to force a process physics update
bool bProcessPhysics = pPed->IsNetworkClone() ? IsOffensiveMove() || bForcedReaction : true;
if(bProcessPhysics)
{
pPed->SetPedResetFlag( CPED_RESET_FLAG_ProcessPhysicsTasks, true );
// This is here because we had some trouble with jittering homing in some cases
// when not double stepping the ProcessPhysics() calls, see
// B* 1041109: "Peds wobble when melee homing when using -pedphyssinglestep".
// For performance, it would be best if we could remove this, but that may require additional
// work on the homing code.
pPed->SetPedResetFlag( CPED_RESET_FLAG_ForceProcessPhysicsUpdateEachSimStep, true );
}
return FSM_Continue;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlayClip_OnExit
// PURPOSE : CTaskMeleeActionResult animation clean up
// PARAMETERS : pPed - Ped we are interested in
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::PlayClip_OnExit( CPed* pPed )
{
// Ensure we apply network damage.
ProcessCachedNetworkDamage(pPed);
// Process any damage to apply to ourselves.
ProcessSelfDamage( pPed );
if( m_pActionResult )
{
// DMKH. Changed from an event to accommodate networking. This will get hit on local & clone tasks.
CEntity* pTargetEntity = GetTargetEntity();
if( pTargetEntity && pTargetEntity->GetIsTypePed() && ( m_pActionResult->GetIsAStealthKill() || m_pActionResult->GetIsATakedown() ) )
{
CPed* pTargetPed = static_cast<CPed*>( pTargetEntity );
// A generic disturbance event which tests if nearby friendlies have seen the event
CEventDisturbance event( pTargetPed, pPed, CEventDisturbance::ePedKilledByStealthKill );
event.CommunicateEvent( pTargetPed );
}
}
// Set the desired heading to match the current so we do not have extra turning post melee action
if(!pPed->IsNetworkClone())
{
pPed->SetDesiredHeading( pPed->GetCurrentHeading() );
}
}
void CTaskMeleeActionResult::Finish_OnEnter( CPed* UNUSED_PARAM(pPed) )
{
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessPhysics
// PURPOSE : Physics update
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::ProcessPhysics( float fTimeStep, int nTimeSlice )
{
#if __DEV
if( !CCombatMeleeDebug::sm_bDoHomingAndSeparation )
return false;
#endif // __DEV
if( GetState() == State_PlayClip )
{
CPed *pPed = GetPed(); //Get the ped ptr.
// Check and handle any homing during this action and
// also keep the peds a minimum distance apart.
CHoming* pHoming = GetHoming(pPed);
if( GetTargetEntity() || ( pHoming && pHoming->IsSurfaceProbe() ) )
ProcessHomingAndSeparation( pPed, fTimeStep, nTimeSlice );
else if( m_MoveNetworkHelper.IsNetworkActive() )
{
const crClip* pClip = m_MoveNetworkHelper.GetClip( ms_ClipId );
if( pClip && !IsPastCriticalFrame() )
{
float fCurrentPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
const float fHeadingDelta = SubtractAngleShorter( pPed->GetDesiredHeading(), pPed->GetCurrentHeading() );
const float fTimeRemaining = pClip->ConvertPhaseToTime( MAX( 0.0f, m_fCriticalFrame - fCurrentPhase ) );
if( fTimeRemaining > 0.0f )
pPed->SetDesiredAngularVelocity( pPed->GetReferenceFrameAngularVelocity() + Vector3( 0.0f, 0.0f, fHeadingDelta / fTimeRemaining ) );
}
}
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FixupKillMoveOnMigration
// PURPOSE : When a ped migrates, if we are flushing tasks, then we need to
// take responsibility and kill the ped.
// PARAMETERS :
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::FixupKillMoveOnMigration()
{
bool bForcedReaction = GetParent() && static_cast<CTaskMelee*>( GetParent() )->GetTaskFlags().IsFlagSet( CTaskMelee::MF_ForcedReaction );
if(bForcedReaction)
{
//! Try and ragdoll as ped isn't going to be in a great state to animate from here.
if(!GetPed()->GetUsingRagdoll() && CTaskNMBehaviour::CanUseRagdoll( GetPed(), RAGDOLL_TRIGGER_DIE ) )
{
CEventSwitch2NM event( 10000, rage_new CTaskNMRelax( 1000, 10000 ) );
GetPed()->SwitchToRagdoll( event );
}
//! Kill the ped.
m_bSelfDamageApplied=false;
ProcessSelfDamage(GetPed(), true);
}
}
bool CTaskMeleeActionResult::HandlesRagdoll(const CPed* pPed) const
{
//! Allow melee to handle a clone ragdoll if we are doing an execution move. This prevents move being terminated.
bool bForcedReaction = GetParent() && static_cast<const CTaskMelee*>( GetParent() )->GetIsTaskFlagSet( CTaskMelee::MF_ForcedReaction );
if(pPed->IsNetworkClone() && bForcedReaction)
{
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : InitTunables
// PURPOSE : Initializes cloud tunables
/////////////////////////////////////////////////////////////////////////////////
float CTaskMeleeActionResult::sm_fMeleeRightFistMultiplier = 1.0f;
s32 CTaskMeleeActionResult::sm_nMeleeEnduranceDamage = -1;
void CTaskMeleeActionResult::InitTunables()
{
sm_fMeleeRightFistMultiplier = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("KNUCKLE_DUSTER_DMG_MULT", 0x0cdb4825), sm_fMeleeRightFistMultiplier);
sm_nMeleeEnduranceDamage = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ENDURANCE_MELEE_DAMAGE", 0x518E4E17), -1);
}
u32 CTaskMeleeActionResult::GetMeleeEnduranceDamage()
{
return sm_nMeleeEnduranceDamage != -1 ? sm_nMeleeEnduranceDamage : sm_Tunables.m_MeleeEnduranceDamage;
}
#if !__FINAL
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetStateName
// PURPOSE : Returns debug state name along with some extra info
// PARAMETERS : iState - State we are interested in
/////////////////////////////////////////////////////////////////////////////////
const char * CTaskMeleeActionResult::GetStateName( s32 iState ) const
{
#if __DEV
static char szBuffer[256];
if( GetState() == State_PlayClip )
{
if( m_pActionResult )
{
sprintf( szBuffer, "State_PlayClip: %s", m_pActionResult->GetName() );
return szBuffer;
}
}
#endif // __DEV
return CTask::GetStateName( iState );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetStaticStateName
// PURPOSE : Simply returns debug state name
// PARAMETERS : iState - State we are interested in
/////////////////////////////////////////////////////////////////////////////////
const char* CTaskMeleeActionResult::GetStaticStateName( s32 iState )
{
switch( iState )
{
case State_Start: return "State_Start";
case State_PlayClip: return "State_PlayClip";
case State_Finish: return "State_Finish";
default: taskAssert(0);
}
return "State_Invalid";
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetCurrentPhase
// PURPOSE : Returns the current phase based on the animation that is currently
// being processed.
// PARAMETERS : iState - State we are interested in
/////////////////////////////////////////////////////////////////////////////////
const float CTaskMeleeActionResult::GetCurrentPhase( void ) const
{
float fReturnPhase = 0.0f;
if( m_MoveNetworkHelper.IsNetworkActive() )
fReturnPhase = m_MoveNetworkHelper.GetFloat( ms_ClipPhaseId );
return fReturnPhase;
}
#endif //!__FINAL
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetActionResultFromID
// PURPOSE : Returns the CActionResult given an id
// PARAMETERS : iResultID - Id that we would like to look up
/////////////////////////////////////////////////////////////////////////////////
const CActionResult* GetActionResultFromID( u32 iResultID )
{
u32 nIndexFound = 0;
return (iResultID != INVALID_ACTIONRESULT_ID) ? ACTIONMGR.FindActionResult( nIndexFound, iResultID ) : NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetActionResultFromID
// PURPOSE : Returns the CActionResult given an id
// PARAMETERS : iResultID - Id that we would like to look up
/////////////////////////////////////////////////////////////////////////////////
const CActionDefinition* GetActionDefinitionFromID( u32 iDefinitionID )
{
u32 nIndexFound = 0;
return (iDefinitionID != INVALID_ACTIONDEFINITION_ID) ? ACTIONMGR.FindActionDefinition( nIndexFound, iDefinitionID ) : NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CreateQueriableState
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CTaskInfo* CTaskMelee::CreateQueriableState( void ) const
{
// if the player weapon subtask is running, the ped is in first person and we don't want to create a melee clone task on the remote in this case
// (we want the anims to be driven by the gun task)
bool bInFP = (GetSubTask() && GetSubTask()->GetTaskType() == CTaskTypes::TASK_PLAYER_WEAPON);
CTaskInfo* pInfo = rage_new CClonedTaskMeleeInfo( m_nActionResultNetworkID,
m_pTargetEntity,
m_nFlags,
GetState(),
m_queueType,
IsBlocking(),
bInFP);
return pInfo;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ReadQueriableState
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::ReadQueriableState( CClonedFSMTaskInfo* pTaskInfo )
{
taskAssert( pTaskInfo->GetTaskInfoType() == CTaskInfo::INFO_TYPE_MELEE );
CTaskFSMClone::ReadQueriableState( pTaskInfo );
//! Fixup melee task.
CClonedTaskMeleeInfo *pMeleeInfo = static_cast<CClonedTaskMeleeInfo*>(pTaskInfo);
//! Update target entity.
m_nFlags = pMeleeInfo->m_nFlags;
m_cloneQueueType = (QueueType)pMeleeInfo->m_nQueueType;
if( m_nActionResultNetworkID != pMeleeInfo->m_nActionResultID )
m_nActionResultNetworkID = pMeleeInfo->m_nActionResultID;
m_bIsBlocking = pMeleeInfo->m_bBlocking;
SetTargetEntity( pMeleeInfo->m_pTarget.GetEntity(), HasLockOnTargetEntity() );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateClonedFSM
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMelee::UpdateClonedFSM( const s32 iState, const FSM_Event iEvent )
{
CPed* pPed = GetPed();
if( iEvent == OnUpdate )
{
//! Force queue type when ped is available.
SetQueueType(m_cloneQueueType);
//! If we are trying to finish, but want to trigger a local reaction, then go to a state that will process local reaction.
if(GetState() == State_Finish && m_bLocalReaction && m_pNextActionResult)
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::UpdateClonedFSM - delay finish for local reaction - sequence ID = %d", fwTimer::GetFrameCount(), GetNetSequenceID() );
SetState(State_WaitForMeleeActionDecide);
return FSM_Continue;
}
//! If we have tried to trigger a local melee reaction, but aren't in the correct state, attempt to force it.
bool bForceCheckForLocalReaction = false;
if(GetState()!=State_MeleeAction && m_bLocalReaction)
{
bForceCheckForLocalReaction = true;
}
if( !IsRunningLocally() || bForceCheckForLocalReaction)
{
u32 expectedCloneTaskType = 0;
if(bForceCheckForLocalReaction)
{
expectedCloneTaskType = CTaskTypes::TASK_MELEE_ACTION_RESULT;
}
else
{
switch(GetStateFromNetwork())
{
case(State_SwapWeapon):
expectedCloneTaskType = CTaskTypes::TASK_SWAP_WEAPON;
break;
default:
expectedCloneTaskType = CTaskTypes::TASK_MELEE_ACTION_RESULT;
break;
}
}
//! Kick off networked sub task (if available).
CTaskFSMClone *pCloneSubTask = NULL;
bool bSubTaskRunningLocally = false;
if( GetSubTask() &&
GetSubTask()->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT &&
expectedCloneTaskType == CTaskTypes::TASK_MELEE_ACTION_RESULT)
{
CTaskMeleeActionResult* pActionResultTask = static_cast<CTaskMeleeActionResult*>(GetSubTask());
bSubTaskRunningLocally = pActionResultTask->IsRunningLocally();
}
//! If melee task isn't sitting at correct seq num, don't create sub task as we'll end up deleting it immediately.
bool bDeferCreatingSubTask = false;
bool bRunningMeleeTask = pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_MELEE );
if(bRunningMeleeTask)
{
CTaskInfo* pMeleeTaskInfo = pPed->GetPedIntelligence()->GetQueriableInterface()->FindTaskInfoIfCurrentlyRunning( CTaskTypes::TASK_MELEE );
if(pMeleeTaskInfo && (pMeleeTaskInfo->GetNetSequenceID() != GetNetSequenceID()))
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::UpdateClonedFSM - Deferring sub task creation - sequence ID = %d new sequence ID = %d", fwTimer::GetFrameCount(), GetNetSequenceID(), pMeleeTaskInfo ? pMeleeTaskInfo->GetNetSequenceID() : 0 );
bDeferCreatingSubTask = true;
}
}
//! Don't spawn subtask whilst we have one running locally.
if(!bSubTaskRunningLocally && !bDeferCreatingSubTask)
{
pCloneSubTask = pPed->GetPedIntelligence()->CreateCloneTaskForTaskType( expectedCloneTaskType );
if( pCloneSubTask )
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::UpdateClonedFSM [Sub Task: %p] - sequence ID = %d", fwTimer::GetFrameCount(), pCloneSubTask, pCloneSubTask->GetNetSequenceID() );
SetNewTask( pCloneSubTask );
}
}
if( (GetStateFromNetwork() != -1) && ((GetStateFromNetwork() != GetState()) || pCloneSubTask))
{
SetFlag( aiTaskFlags::KeepCurrentSubtaskAcrossTransition );
if( GetStateFromNetwork() == GetState() )
{
SetFlag( aiTaskFlags::RestartCurrentState );
}
else
{
bool bSetState = true;
//! As local task will end before clone does, don't re-enter melee action if we don't have a sub task.
if(GetStateFromNetwork() == State_MeleeAction && !pCloneSubTask)
{
bSetState = false;
}
//! Don't repeat start state if we have already processed it.
if(GetState() > State_Start && GetStateFromNetwork() == State_Start)
{
bSetState = false;
}
if(bSetState)
{
SetState( GetStateFromNetwork() );
}
}
if(pCloneSubTask)
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::UpdateClonedFSM [%p] - changing state = %d/%d", CNetwork::GetNetworkTime(), this, GetState(), GetStateFromNetwork() ) ;
}
return FSM_Continue;
}
if( GetStateFromNetwork() == -1 && (GetSubTask() == NULL || bDeferCreatingSubTask))
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMelee::UpdateClonedFSM: GetStateFromNetwork() == -1 && (GetSubTask() (0x%p) == NULL || bDeferCreatingSubTask (%d))", GetSubTask(), bDeferCreatingSubTask);
return FSM_Quit;
}
}
else
{
CPed* pTargetPed = static_cast<CPed*>(GetTargetEntity());
// quit a locally running reaction if the target ped is not meleeing anymore
if (pTargetPed && pTargetPed->IsAPlayerPed() && pTargetPed->IsDead())
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMelee::UpdateClonedFSM: !pTargetPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_MELEE )");
return FSM_Quit;
}
//! If we are running locally, but don't have a task to go to, then just bail.
if(!m_pNextActionResult && (GetSubTask() == NULL) && (GetNewTask() == NULL) && GetStateFromNetwork() == -1)
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMelee::UpdateClonedFSM: !m_pNextActionResult && (GetSubTask() == NULL) && (GetNewTask() == NULL) && GetStateFromNetwork() == -1");
return FSM_Quit;
}
}
}
FSM_Begin
// Start
FSM_State(State_Start)
FSM_OnEnter
Start_OnEnter( pPed );
FSM_OnUpdate
return Start_OnUpdate( pPed );
FSM_OnExit
Start_OnExit();
// Decide whether this should go into player or AI melee action detection
FSM_State(State_WaitForMeleeActionDecide)
FSM_OnEnter
WaitForMeleeActionDecide_OnEnter( pPed );
FSM_OnUpdate
return WaitForMeleeActionDecide_OnUpdate();
// Running a melee action
FSM_State(State_MeleeAction)
FSM_OnEnter
MeleeAction_OnEnter( pPed );
FSM_OnUpdate
return MeleeAction_OnUpdate( pPed );
FSM_OnExit
MeleeAction_OnExit( pPed );
// Scanning for a new melee action
FSM_State(State_WaitForMeleeAction)
FSM_OnEnter
WaitForMeleeAction_OnEnter();
FSM_OnUpdate
return WaitForMeleeAction_OnUpdate( pPed );
FSM_OnExit
WaitForMeleeAction_OnExit();
// Main wait state for those that are deemed as an Inactive Observer
FSM_State(State_InactiveObserverIdle)
FSM_OnUpdate
return InactiveObserverIdle_OnUpdate( pPed );
// Quit
FSM_State(State_Finish)
FSM_OnUpdate
MELEE_NETWORK_DEBUGF("Quit: CTaskMelee::UpdateClonedFSM: State_Finish");
return FSM_Quit;
FSM_State(State_ChaseAfterTarget)
FSM_State(State_InitialPursuit)
FSM_State(State_LookAtTarget)
FSM_State(State_RetreatToSafeDistance)
FSM_State(State_WaitForTarget)
FSM_State(State_SwapWeapon)
FSM_End
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : OnCloneTaskNoLongerRunningOnOwner
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
void CTaskMelee::OnCloneTaskNoLongerRunningOnOwner( void )
{
if(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MELEE_ACTION_RESULT)
{
//! Don't kill melee parent task whilst still playing melee action.
SetStateFromNetwork(-1);
}
else
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMelee::OnCloneTaskNoLongerRunningOnOwner");
SetStateFromNetwork( State_Finish );
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : HandleLocalToRemoteSwitch
// PURPOSE :
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::HandleLocalToRemoteSwitch(CPed* UNUSED_PARAM(pPed), CClonedFSMTaskInfo* UNUSED_PARAM(pTaskInfo))
{
//! Don't fixup to remote if we haven't triggered action yet.
if(GetTaskFlags().IsFlagSet( CTaskMelee::MF_ForcedReaction ) && GetTaskFlags().IsFlagSet( CTaskMelee::MF_IsDoingReaction ))
{
return false;
}
//! Don't fixup if we are trying to trigger a local melee action.
if(m_pNextActionResult)
{
return false;
}
//! Defer to action sub task.
if(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MELEE_ACTION_RESULT)
{
if(!static_cast<CTaskMeleeActionResult*>(GetSubTask())->IsRunningLocally())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::HandleLocalToRemoteSwitch - Subtask not running locally", fwTimer::GetFrameCount());
return true;
}
else
{
return false;
}
}
MELEE_NETWORK_DEBUGF( "[%d] CTaskMelee::HandleLocalToRemoteSwitch - No sub task", fwTimer::GetFrameCount());
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AllowsLocalHitReactions
// PURPOSE :
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMelee::AllowsLocalHitReactions() const
{
const CTask* pSubTask = GetSubTask();
if(pSubTask && pSubTask->IsClonedFSMTask())
{
return static_cast<const CTaskFSMClone *>(pSubTask)->AllowsLocalHitReactions();
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CompareCollisionInfos
// PURPOSE : QSORT function to compare 2 melee collisions
/////////////////////////////////////////////////////////////////////////////////
int CTaskMelee::CompareCollisionInfos( const sCollisionInfo* pA1, const sCollisionInfo* pA2 )
{
WorldProbe::CShapeTestHitPoint* pResultA1 = pA1->m_pResult;
WorldProbe::CShapeTestHitPoint* pResultA2 = pA2->m_pResult;
const CEntity* pEntityA = pResultA1->GetHitEntity();
Assert( pEntityA );
const CEntity* pEntityB = pResultA2->GetHitEntity();
Assert( pEntityB );
// Check if the two impacts hit the same instance.
if( pEntityA == pEntityB )
{
// Head is considered the largest component and the most important for melee
if( pResultA1->GetHitComponent() > pResultA2->GetHitComponent() )
return 1;
// We should select glass above everything
else if( pEntityA->GetIsTypeVehicle() && PGTAMATERIALMGR->GetIsGlass( pResultA1->GetMaterialId() ) )
return 1;
}
// Always prioritize peds over objects
else if( pEntityA->GetIsTypePed() && !pEntityB->GetIsTypePed() )
return 1;
return -1;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetUniqueNetworkMeleeActionID
// PURPOSE : QSORT function to compare 2 melee collisions
/////////////////////////////////////////////////////////////////////////////////
u16 CTaskMelee::GetUniqueNetworkMeleeActionID() const
{
//! Get it from sub task if available, as 'm_nUniqueNetworkMeleeActionID' is consumed when we
//! create action.
if(GetSubTask() && GetSubTask()->GetTaskType() == CTaskTypes::TASK_MELEE_ACTION_RESULT)
{
return static_cast<const CTaskMeleeActionResult*>(GetSubTask())->GetUniqueNetworkActionID();
}
return m_nUniqueNetworkMeleeActionID;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetMeleeGripFilter
// PURPOSE : Gets melee grip filter specified in clipsets.xml
/////////////////////////////////////////////////////////////////////////////////
crFrameFilter* CTaskMelee::GetMeleeGripFilter(fwMvClipSetId clipsetId)
{
fwClipSet* pClipSet = fwClipSetManager::GetClipSet(clipsetId);
if (pClipSet)
{
// Grab the requested filter from the clip item
fwMvFilterId filter(FILTER_BOTHARMS);
fwMvClipId clipId("GRIP_IDLE", 0x3ec63b58);
const fwClipItem* pItem = pClipSet->GetClipItem(clipId);
if (pItem && pItem->IsClipItemWithProps())
{
const fwClipItemWithProps* pItemWithProps = static_cast<const fwClipItemWithProps*>(pItem);
filter.SetHash(pItemWithProps->GetBoneMask().GetHash());
if (filter != FILTER_ID_INVALID)
{
return g_FrameFilterDictionaryStore.FindFrameFilter(filter);
}
}
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
// Task serialization info for CClonedTaskMeleeInfo
//////////////////////////////////////////////////////////////////////////
CClonedTaskMeleeInfo::CClonedTaskMeleeInfo( u32 iActionResultID, CEntity* pTarget, u32 iFlags, s32 iState, s32 nQueueType, bool bBlocking, bool bInFP)
: m_nActionResultID( iActionResultID )
, m_nFlags( iFlags )
, m_nQueueType( nQueueType )
, m_bBlocking( bBlocking )
, m_bInFP( bInFP )
{
m_pTarget.SetEntity( pTarget );
SetStatusFromMainTaskState( iState );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CClonedTaskMeleeInfo
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CClonedTaskMeleeInfo::CClonedTaskMeleeInfo()
: m_nActionResultID( INVALID_ACTIONRESULT_ID )
, m_nFlags( 0 )
, m_nQueueType( -1 )
, m_bBlocking( false )
, m_bInFP( false )
{
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CreateCloneFSMTask
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CTaskFSMClone* CClonedTaskMeleeInfo::CreateCloneFSMTask()
{
// if the player is in first person and we don't want to create a melee clone task on the remote in this case (we want the anims to be driven by the gun task)
if (m_bInFP)
{
return NULL;
}
CTaskMelee* pTaskMelee = rage_new CTaskMelee( GetActionResultFromID( m_nActionResultID ), m_pTarget.GetEntity(), m_nFlags );
pTaskMelee->m_bIsBlocking = m_bBlocking;
return pTaskMelee;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CreateQueriableState
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CTaskInfo* CTaskMeleeActionResult::CreateQueriableState() const
{
u32 nActionResultID = m_pActionResult ? m_pActionResult->GetID() : INVALID_ACTIONRESULT_ID;
u32 nForcedReactionDefinitionID = m_pForcedReactionActionDefinition ? m_pForcedReactionActionDefinition->GetID() : INVALID_ACTIONDEFINITION_ID;
CTaskInfo* pInfo = rage_new CClonedTaskMeleeResultInfo( nActionResultID,
nForcedReactionDefinitionID,
m_pTargetEntity,
m_bHasLockOnTargetEntity,
GetState(),
m_bIsDoingReaction,
m_bAllowDistanceHoming,
m_bOwnerStoppedDistanceHoming,
m_nSelfDamageWeaponHash,
(s32)m_eSelfDamageAnimBoneTag,
m_nNetworkTimeTriggered,
m_nUniqueNetworkActionID,
VEC3V_TO_VECTOR3(m_vCachedHomingPosition));
return pInfo;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ReadQueriableState
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::ReadQueriableState( CClonedFSMTaskInfo* pTaskInfo )
{
taskAssert( pTaskInfo->GetTaskInfoType() == CTaskInfo::INFO_TYPE_MELEE_ACTION_RESULT );
CTaskFSMClone::ReadQueriableState( pTaskInfo );
CClonedTaskMeleeResultInfo* pMeleeInfo = static_cast<CClonedTaskMeleeResultInfo*>( pTaskInfo );
m_bIsDoingReaction = pMeleeInfo->m_bIsDoingReaction;
m_bAllowDistanceHoming = pMeleeInfo->m_bAllowDistanceHoming;
m_bOwnerStoppedDistanceHoming = pMeleeInfo->m_bOwnerStoppedDistanceHoming;
//! Update target entity.
SetTargetEntity( pMeleeInfo->m_pTarget.GetEntity(), pMeleeInfo->m_bHasLockOnTargetEntity );
//! Fix up action result. This might happen if we locally simulate a hit reaction that is different to what the ped
//! host created. It might just look worse to fix this, so I might disable it.
if( m_nActionResultNetworkID != pMeleeInfo->m_uActionResultID )
{
m_nActionResultNetworkID = pMeleeInfo->m_uActionResultID;
#if __ASSERT
const CActionResult *pResult = GetActionResultFromID( pMeleeInfo->m_uActionResultID );
if( pResult != m_pActionResult )
{
MELEE_NETWORK_DEBUGF( "CTaskMeleeActionResult::ReadQueriableState - Action ID changed" );
}
#endif
}
//! Fixup Forced Reaction.
m_pForcedReactionActionDefinition = GetActionDefinitionFromID(pMeleeInfo->m_uForcedReactionActionDefinitionID);
m_nNetworkTimeTriggered = pMeleeInfo->m_uNetworkTimeTriggered;
if(m_nUniqueNetworkActionID != pMeleeInfo->m_uActionID && !IsRunningLocally())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::ReadQueriableState - Changing unique ID = %d:%d Address %p", fwTimer::GetFrameCount(), m_nUniqueNetworkActionID, pMeleeInfo->m_uActionID, this );
m_nUniqueNetworkActionID = pMeleeInfo->m_uActionID;
}
m_vCloneCachedHomingPosition = VECTOR3_TO_VEC3V(pMeleeInfo->m_vCachedHomingPosition);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdateClonedFSM
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CTask::FSM_Return CTaskMeleeActionResult::UpdateClonedFSM( const s32 iState, const FSM_Event iEvent )
{
if( (GetState() != State_Finish) && m_bFixupActionResultFromNetwork && iEvent == OnUpdate )
{
m_bFixupActionResultFromNetwork = false;
m_bHeadingInitialized = false;
m_bDidAllowDistanceHoming = false;
const CActionResult* pNewResult = GetActionResultFromID( m_nActionResultNetworkID );
if( pNewResult && ( m_pActionResult != pNewResult ) )
{
MELEE_NETWORK_DEBUGF( "CTaskMeleeActionResult::UpdateClonedFSM - Action ID changed" );
m_pActionResult = pNewResult;
//! Avoid an immediate interrupt. Need to play new anim.
m_bAllowInterrupt = false;
//! Reset action result params. This prevents any unwanted logic running on invalid params (e.g. self damage if we have switched from
//! being killed to doing a kill).
CacheActionResult(GetPed());
if( GetState() == State_Start )
{
SetFlag( aiTaskFlags::RestartCurrentState );
}
else
{
SetState( State_Start );
}
//! If network has started, perform a restart.
if(m_MoveNetworkHelper.IsNetworkActive() && m_MoveNetworkHelper.IsNetworkAttached())
{
m_MoveNetworkHelper.SendRequest(ms_RestartPlayClipRequestId);
}
return FSM_Continue;
}
}
if(WasLocallyStarted())
{
//! Detect if we played a local anim reaction on the clone, whereas the owner picked a ragdoll.
bool bIsOwnerUsingNm = GetPed()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_NM_CONTROL );
if((bIsOwnerUsingNm || GetPed()->GetIsInVehicle()))
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMeleeActionResult::UpdateClonedFSM: (bIsOwnerUsingNm (%d) || GetPed()->GetIsInVehicle() (%d))", bIsOwnerUsingNm, GetPed()->GetIsInVehicle());
return FSM_Quit;
}
//! If owner isn't running melee & is far enough away, interrupt.
static dev_float uMaxTimeForLocalReact = 0.5f;
CTaskMelee* pParentMeleeTask = GetParent() ? static_cast<CTaskMelee*>( GetParent() ) : NULL;
bool bForcedReaction = pParentMeleeTask && pParentMeleeTask->GetIsTaskFlagSet( CTaskMelee::MF_ForcedReaction );
bool bOwnerRunningMelee = (GetStateFromNetwork() != -1) || GetPed()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_MELEE_ACTION_RESULT );
if(!bOwnerRunningMelee && !bForcedReaction && GetTimeInState() > uMaxTimeForLocalReact )
{
static dev_float s_fBreakoutDistance = 1.0f;
Vector3 vCloneMasterPosition = NetworkInterface::GetLastPosReceivedOverNetwork(GetPed());
Vector3 vCurrentPosition = VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetPosition());
Vector3 vDist = vCloneMasterPosition - vCurrentPosition;
float fDistSq = vDist.Mag2();
if(fDistSq > (s_fBreakoutDistance*s_fBreakoutDistance) )
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMeleeActionResult::UpdateClonedFSM: fDistSq (%.2f) > (s_fBreakoutDistance*s_fBreakoutDistance (%.2f))", fDistSq, s_fBreakoutDistance*s_fBreakoutDistance);
return FSM_Quit;
}
}
}
if( GetStateFromNetwork() == -1 && (!IsRunningLocally() && ShouldAllowInterrupt()))
{
MELEE_NETWORK_DEBUGF("Quit: CTaskMeleeActionResult::UpdateClonedFSM: GetStateFromNetwork() == -1 && (!IsRunningLocally() && ShouldAllowInterrupt())");
return FSM_Quit;
}
//! Locally simulate action. Don't update through network
return UpdateFSM( iState, iEvent );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : OnCloneTaskNoLongerRunningOnOwner
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
void CTaskMeleeActionResult::OnCloneTaskNoLongerRunningOnOwner()
{
SetStateFromNetwork(-1);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : OnCloneTaskNoLongerRunningOnOwner
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::OverridesNetworkBlender(CPed* pPed)
{
return IsOverridingNetBlenderCommon(pPed);
}
bool CTaskMeleeActionResult::OverridesNetworkHeadingBlender(CPed* pPed)
{
if(WasLocallyStarted() && IsDoingReaction())
{
return true;
}
return IsOverridingNetBlenderCommon(pPed);
}
bool CTaskMeleeActionResult::IsOverridingNetBlenderCommon(CPed *pPed)
{
TUNE_GROUP_BOOL(MELEE_DEBUG, bOverrideNetBlender, true);
if(bOverrideNetBlender)
{
if(WasLocallyStarted())
{
//! Allow a small error tolerance. If inside this, override blender.
static dev_float s_fErrorToleranceIn = 0.5f;
static dev_float s_fErrorToleranceOut = 0.75f;
Vector3 vCloneMasterPosition = NetworkInterface::GetLastPosReceivedOverNetwork(pPed);
Vector3 vCurrentPosition = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
Vector3 vDist = vCloneMasterPosition - vCurrentPosition;
float fDistSq = vDist.Mag2();
float fTolerance = m_bInNetworkBlenderTolerance ? s_fErrorToleranceOut : s_fErrorToleranceIn;
if(fDistSq < square(fTolerance))
{
m_bInNetworkBlenderTolerance = true;
return true;
}
else
{
m_bInNetworkBlenderTolerance = false;
}
//! Don't let net blender run if we are running this task locally.
if(IsRunningLocally() && !m_bHandledLocalToRemoteSwitch)
{
return true;
}
}
//! Do homing on clone side to ensure ped is in correct position relative to ped.
if(GetTargetEntity())
{
if(m_bDidAllowDistanceHoming && !m_bOwnerStoppedDistanceHoming)
{
return true;
}
}
if(GetPed()->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth) ||
GetPed()->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown) ||
(GetParent() && static_cast<CTaskMelee*>( GetParent() )->GetTaskFlags().IsFlagSet( CTaskMelee::MF_ForcedReaction ) ) )
{
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : OnCloneTaskNoLongerRunningOnOwner
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::HandleLocalToRemoteSwitch(CPed* pPed, CClonedFSMTaskInfo* pTaskInfo)
{
taskAssert(!m_bFixupActionResultFromNetwork);
taskAssert( pTaskInfo->GetTaskInfoType() == CTaskInfo::INFO_TYPE_MELEE_ACTION_RESULT );
CClonedTaskMeleeResultInfo* pMeleeInfo = static_cast<CClonedTaskMeleeResultInfo*>( pTaskInfo );
m_bHandledLocalToRemoteSwitch = true;
//! break out of reaction and do attack. TO DO - interrupt?
bool bWasKillMove = false;
bool bWasAttack = false;
bool bIsKillMove = false;
bool bIsAttack = false;
if( !pMeleeInfo->GetIsActiveTask() )
{
MELEE_NETWORK_DEBUGF( "%s [Frame %d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - m_pActionResult [%s] Task Info not active ",
pPed->GetDebugName(),
fwTimer::GetFrameCount(),
m_pActionResult ? m_pActionResult->GetName() : "Null");
return false;
}
bool bForcingReaction = (GetParent() && static_cast<CTaskMelee*>( GetParent() )->GetTaskFlags().IsFlagSet( CTaskMelee::MF_ForcedReaction ));
if(m_pActionResult)
{
bWasKillMove = m_pActionResult->GetIsATakedown() || m_pActionResult->GetIsAStealthKill() || m_pActionResult->GetIsAKnockout();
bWasAttack = bWasKillMove || m_pActionResult->GetIsAStandardAttack() || m_pActionResult->GetIsACounterAttack();
}
const CActionResult* pNewResult = GetActionResultFromID( pMeleeInfo->m_uActionResultID );
if(pNewResult)
{
bIsKillMove = pNewResult->GetIsATakedown() || pNewResult->GetIsAStealthKill() || pNewResult->GetIsAKnockout();
bIsAttack = pNewResult->GetIsAStandardAttack() || m_pActionResult->GetIsACounterAttack();
}
//! If reaction is the same, handle switch. otherwise continue to play.
u32 nCurrentID = m_pActionResult ? m_pActionResult->GetID() : 0;
if( nCurrentID == pMeleeInfo->m_uActionResultID )
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Same move [%s|%s] ", fwTimer::GetFrameCount(), m_pActionResult ? m_pActionResult->GetName() : "", pNewResult ? pNewResult->GetName() : "");
return true;
}
//! Arbitrate kill move. A clone is attempting to trigger a kill move, but is being killed already. Check timestamps & player ID's to
//! see who wins.
if(bForcingReaction)
{
if(bIsKillMove)
{
const CDamageAndReaction* pDamageAndReaction = pNewResult->GetDamageAndReaction();
if( pDamageAndReaction &&
pDamageAndReaction->ShouldForceImmediateReaction() &&
m_pTargetEntity &&
m_pTargetEntity->GetIsTypePed() &&
pMeleeInfo->m_pTarget.GetEntity() == m_pTargetEntity)
{
CPed* pTargetPed = static_cast<CPed*>(m_pTargetEntity.Get());
if(!pTargetPed->IsNetworkClone())
{
CTaskMeleeActionResult* pSimpleMeleeActionResultTask = static_cast<CTaskMeleeActionResult*>(pTargetPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_MELEE_ACTION_RESULT ) );
if( pSimpleMeleeActionResultTask )
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Arbitrate kill move start [%d|%d] ", fwTimer::GetFrameCount(), pSimpleMeleeActionResultTask->GetNetworkTimeTriggered(), pMeleeInfo->m_uNetworkTimeTriggered);
bool bFixup = false;
if(pMeleeInfo->m_uNetworkTimeTriggered < pSimpleMeleeActionResultTask->GetNetworkTimeTriggered())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Clone network time won! [%d|%d] ", fwTimer::GetFrameCount(), pSimpleMeleeActionResultTask->GetNetworkTimeTriggered(), pMeleeInfo->m_uNetworkTimeTriggered);
// clone won.
bFixup = true;
}
else if( (pSimpleMeleeActionResultTask->GetNetworkTimeTriggered() == pMeleeInfo->m_uNetworkTimeTriggered) &&
pPed->GetNetworkObject() && pPed->GetNetworkObject()->GetPlayerOwner() &&
pTargetPed->GetNetworkObject() && pTargetPed->GetNetworkObject()->GetPlayerOwner())
{
// use player owner ID's to determine who won.
u8 nOwnerIndex = pPed->GetNetworkObject()->GetPlayerOwner()->GetPhysicalPlayerIndex();
u8 nTargetIndex = pTargetPed->GetNetworkObject()->GetPlayerOwner()->GetPhysicalPlayerIndex();
if(nOwnerIndex < nTargetIndex)
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Clone ID won! [%d|%d] ", fwTimer::GetFrameCount(), nOwnerIndex, nTargetIndex);
bFixup = true;
}
else
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - local target won! [%d|%d] ", fwTimer::GetFrameCount(), nOwnerIndex, nTargetIndex);
return false;
}
}
else
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - local target won! [%d|%d] ", fwTimer::GetFrameCount(), pSimpleMeleeActionResultTask->GetNetworkTimeTriggered(), pMeleeInfo->m_uNetworkTimeTriggered);
return false;
}
if(bFixup)
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Arbitrated kill move end [%s|%s]", fwTimer::GetFrameCount(), m_pActionResult ? m_pActionResult->GetName() : "", pNewResult ? pNewResult->GetName() : "");
m_bFixupActionResultFromNetwork = true;
m_nActionResultNetworkID=pMeleeInfo->m_uActionResultID;
return true;
}
}
}
}
}
else
{
//! If being killed, don't break out for any move.
return false;
}
}
//! If doing a kill move, don't accept anything else.
if(!bWasKillMove)
{
//! If breaking out into a kill move, accept.
if(bIsKillMove)
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Special Move Switch [%s|%s]", fwTimer::GetFrameCount(), m_pActionResult ? m_pActionResult->GetName() : "", pNewResult ? pNewResult->GetName() : "");
m_bFixupActionResultFromNetwork = true;
m_nActionResultNetworkID=pMeleeInfo->m_uActionResultID;
return true;
}
//! Allow breaking out into an attack after x secs of local reaction.
static dev_float s_MinTimeToRun = 0.25f;
if(!bWasAttack && bIsAttack && GetTimeRunning() > s_MinTimeToRun)
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Attack Switch [%s|%s]", fwTimer::GetFrameCount(), m_pActionResult ? m_pActionResult->GetName() : "", pNewResult ? pNewResult->GetName() : "");
m_bFixupActionResultFromNetwork = true;
m_nActionResultNetworkID=pMeleeInfo->m_uActionResultID;
return true;
}
//! Allow breaking out to a new attack when we hit interrupt (e.g. dodging etc).
if(bIsAttack && pNewResult && ShouldAllowInterrupt())
{
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - Breakout [%s|%s]", fwTimer::GetFrameCount(), m_pActionResult ? m_pActionResult->GetName() : "", pNewResult ? pNewResult->GetName() : "");
m_bFixupActionResultFromNetwork = true;
m_nActionResultNetworkID=pMeleeInfo->m_uActionResultID;
return true;
}
//! If both moves are hit reactions, accept the switch, but don't fixup action result. This prevents a potential double reaction
//! animation due to seq numbers being out of sync.
if(m_pActionResult && m_pActionResult->GetIsAHitReaction() &&
pNewResult && pNewResult->GetIsAHitReaction())
{
static dev_bool s_FixupDifferentReactions = false;
if(s_FixupDifferentReactions)
{
m_bFixupActionResultFromNetwork = true;
m_nActionResultNetworkID=pMeleeInfo->m_uActionResultID;
}
MELEE_NETWORK_DEBUGF( "[%d] CTaskMeleeActionResult::HandleLocalToRemoteSwitch - hit reaction [%s|%s] ", fwTimer::GetFrameCount(), m_pActionResult ? m_pActionResult->GetName() : "", pNewResult ? pNewResult->GetName() : "");
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AllowsLocalHitReactions
// PURPOSE :
/////////////////////////////////////////////////////////////////////////////////
bool CTaskMeleeActionResult::AllowsLocalHitReactions() const
{
static dev_bool s_bAllowLocalHitReactions = true;
return s_bAllowLocalHitReactions;
}
//////////////////////////////////////////////////////////////////////////
// Task serialization info for CClonedTaskMeleeResultInfo
//////////////////////////////////////////////////////////////////////////
u8 CClonedTaskMeleeResultInfo::m_nActionIDCounter = 0;
CClonedTaskMeleeResultInfo::CClonedTaskMeleeResultInfo( u32 uActionResultID,
u32 uForcedReactionActionDefinitionID,
CEntity* pTarget,
bool bLockOn,
s32 iState,
bool bIsDoingReaction,
bool bAllowDistanceHoming,
bool bOwnerStoppedDistanceHoming,
u32 uSelfDamageWeaponHash,
s32 nSelfDamageAnimBoneTag,
u32 uNetworkTimeTriggered,
u16 uUniqueID,
const Vector3 &vHomingPosition)
: m_uActionResultID( uActionResultID )
, m_uForcedReactionActionDefinitionID( uForcedReactionActionDefinitionID )
, m_pTarget( pTarget )
, m_bHasLockOnTargetEntity( bLockOn )
, m_bIsDoingReaction( bIsDoingReaction )
, m_bAllowDistanceHoming( bAllowDistanceHoming )
, m_bOwnerStoppedDistanceHoming( bOwnerStoppedDistanceHoming )
, m_uSelfDamageWeaponHash( uSelfDamageWeaponHash )
, m_nSelfDamageAnimBoneTag( nSelfDamageAnimBoneTag )
, m_uNetworkTimeTriggered(uNetworkTimeTriggered)
, m_uActionID(uUniqueID)
, m_vCachedHomingPosition(vHomingPosition)
{
SetStatusFromMainTaskState( iState );
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Default Ctor
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CClonedTaskMeleeResultInfo::CClonedTaskMeleeResultInfo()
: m_uActionResultID( INVALID_ACTIONRESULT_ID )
, m_uForcedReactionActionDefinitionID ( INVALID_ACTIONDEFINITION_ID )
, m_pTarget( NULL )
, m_bHasLockOnTargetEntity( false )
, m_bIsDoingReaction( false )
, m_bAllowDistanceHoming( false )
, m_bOwnerStoppedDistanceHoming( false )
, m_uSelfDamageWeaponHash( 0 )
, m_nSelfDamageAnimBoneTag( -1 )
, m_uActionID(0)
, m_uNetworkTimeTriggered(0)
, m_vCachedHomingPosition(Vector3::ZeroType)
{
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CreateCloneFSMTask
// PURPOSE : Task Melee clone functionality
/////////////////////////////////////////////////////////////////////////////////
CTaskFSMClone * CClonedTaskMeleeResultInfo::CreateCloneFSMTask()
{
CTaskMeleeActionResult* pTaskMeleeResult = rage_new CTaskMeleeActionResult( GetActionResultFromID( m_uActionResultID ),
m_pTarget.GetEntity(),
m_bHasLockOnTargetEntity,
m_bIsDoingReaction,
!m_bAllowDistanceHoming,
m_uSelfDamageWeaponHash,
(eAnimBoneTag)m_nSelfDamageAnimBoneTag );
pTaskMeleeResult->SetForcedReactionActionDefinition(GetActionDefinitionFromID( m_uForcedReactionActionDefinitionID ));
return pTaskMeleeResult;
}
//////////////////////////////////////////////////////////////////////////