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

2484 lines
73 KiB
C++

// FILE : PlayerSpecialAbility.cpp
// PURPOSE : Load in special ability information and manage the abilities
// CREATED : 23-03-2011
// class header
#include "PlayerSpecialAbility.h"
// Framework headers
#include "fwsys/timer.h"
// Game headers
#include "camera/CamInterface.h"
#include "camera/cinematic/CinematicDirector.h"
#include "camera/system/CameraMetadata.h"
#include "control/replay/replay.h"
#include "Cutscene/CutSceneManagerNew.h"
#include "game/ModelIndices.h"
#include "scene/datafilemgr.h"
#include "modelinfo/PedModelInfo.h"
#include "Ped.h"
#include "Peds/PedIntelligence.h"
#include "PlayerInfo.h"
#include "scene/EntityIterator.h"
#include "scene/ExtraContent.h"
#if __BANK
#include "scene/world/gameworld.h"
#endif // __BANK
#include "Stats/StatsInterface.h"
#include "Network/NetworkInterface.h"
#include "renderer/PostProcessFXHelper.h"
#include "renderer/water.h"
#include "system/control.h"
#include "Task/Default/TaskPlayer.h"
#include "Task/Movement/TaskFall.h"
#include "task/System/TaskHelpers.h"
#include "vehicles/Vehicle.h"
#include "vehicles/Wheel.h"
#include "Vfx/Systems/VfxVehicle.h"
#include "Frontend/MobilePhone.h"
// Parser headers
#include "PlayerSpecialAbility_parser.h"
// rage headers
#include "bank/bank.h"
#include "fwsys/fileExts.h"
#include "parser/manager.h"
#include "string/stringhash.h"
AI_OPTIMISATIONS()
u32 s_specialEditionHash = ATSTRINGHASH("dlc_specialedition", 0x2040A77);
u32 s_collectorsEditionHash = ATSTRINGHASH("dlc_collectorsedition", 0x4f98c2a7);
//////////////////////////////////////////////////////////////////////////
// CPlayerSpecialAbility
//////////////////////////////////////////////////////////////////////////
static const float MIN_DAMAGE_FOR_CHARGE = 10.f;
static const float MIN_RAMMED_BY_CAR_IMPACT = 5000.f;
static const float MIN_RAMMED_INTO_CAR_IMPACT = 5000.f;
static const float MIN_CRASHED_CAR_IMPACT = 15000.f;
static const u32 MIN_NEAR_VEHICLE_MISS_TIME = 800;
static const float MIN_NEAR_MISS_VELOCITY_DIFF_SQR = 200.f; // difference in vehicle velocities for the near miss to apply
static const u32 TIME_SPAN_FOR_SUCCESSFUL_LANDING = 300; // after a vehicle jump how long to wait for all wheels to touch ground to count as a 4 wheel landing
static const u32 TIME_AIRBORN_BEFORE_REWARD = 500; // time needed to be airborn before we give the reward to avoid small bounces
static const s32 MIN_REMAINING_CHARGE_FOR_ACTIVATION = 3; // minimum charge (in absolute time) required for ability activation to be possible
float CPlayerSpecialAbility::timeToAddCharge = 0.5f; // in seconds
#if __BANK
static bool sigmoidCurve = false;
static bool halfSigmoidCurve = true;
static bool linearCurve = false;
u32 CPlayerSpecialAbilityManager::unlockedCapacityPercentage = 30;
const char* abilityNames[] =
{
"Car slowdown", // SAT_CAR_SLOWDOWN
"Rage", // SAT_RAGE
"Bullet time", // SAT_BULLET_TIME
"Snapshot", // SAT_SNAPSHOT
"Insult", // SAT_INSULT
"Enraged", // SAT_ENRAGED
"Ghost", // SAT_GHOST
"Sprint Speed Boost", // SAT_SPRINT_SPEED_BOOST
"Nitro (Cop)", // SAT_NITRO_COP
"Nitro (Crook)", // SAT_NITRO_CROOK
"EMP", // SAT_EMP
"Spike Mine", // SAT_SPIKE_MINE
"Full Throttle", // SAT_FULL_THROTTLE
"Pumped Up", // SAT_PUMPED_UP
"Back Up", // SAT_BACKUP
"Oil Slick", // SAT_OIL_SLICK
"Kinetic Ram" // SAT_KINETIC_RAM
};
static_assert(sizeof(abilityNames) / sizeof(char*) == SAT_MAX, "Mismatched number of Special Ability names");
#endif // __BANK
CPlayerSpecialAbility::CPlayerSpecialAbility(const CSpecialAbilityData* data) :
data(data),
isEnabled(true),
isActive(false),
addingContinuous(false),
addContinuousMultiplier(0.f),
lastNearVehicleMissTime(0),
nearMissChargeAmount(0.f),
appliedLowHealthCharge(false),
canToggle(true),
chargeToAdd(0.f),
currentRemaining(0.f),
sliceToAdd(0.f),
toggleTime(0),
lastTimeWarpFrame(0),
lastExplosionKillTime(0),
lastPedBumpedInto(NULL),
lastPedBumpTime(0),
lastPedRanOverTime(0),
accumulatedCharge(0.f),
unlockedCapacity(10.f),
vehJumpStartTime(0),
vehJumpTouchTime(0),
isVehJumpInProgress(false),
gaveKillCharge(false),
isFadingOut(false),
kineticRamActivationTime(0),
kineticRamExplosionCount(0)
{
m_statAbility = StatsInterface::GetStatsModelHashId("SPECIAL_ABILITY");
Assertf(StatsInterface::IsKeyValid(m_statAbility), "Player model %s should have SPECIAL_ABILITY stat since this model has ability type=%d.", FindPlayerPed() && FindPlayerPed()->GetPedModelInfo() ? FindPlayerPed()->GetPedModelInfo()->GetModelName() : "", data ? data->type : -1);
if(!NetworkInterface::IsGameInProgress())
{
m_statUnlockedAbility = StatsInterface::GetStatsModelHashId("SPECIAL_ABILITY_UNLOCKED");
Assertf(StatsInterface::IsKeyValid(m_statUnlockedAbility), "Player model %s should have SPECIAL_ABILITY_UNLOCKED stat since this model has ability type=%d.", FindPlayerPed() && FindPlayerPed()->GetPedModelInfo() ? FindPlayerPed()->GetPedModelInfo()->GetModelName() : "", data ? data->type : -1);
unlockedCapacity = (float)data->initialUnlockedCap;
if (StatsInterface::IsKeyValid(m_statUnlockedAbility))
{
s32 capacityStatValue = (s32)floor(100.f * (unlockedCapacity / data->duration));
if (StatsInterface::GetIntStat(m_statUnlockedAbility) <= capacityStatValue)
StatsInterface::SetStatData(m_statUnlockedAbility, capacityStatValue, STATUPDATEFLAG_ASSERTONLINESTATS); // the stat variable wants a percentage [0, 100] not absolute duration
else
unlockedCapacity = StatsInterface::GetIntStat(m_statUnlockedAbility) * 0.01f * data->duration;
}
UpdateFromStat();
}
else if (NetworkInterface::IsInCopsAndCrooks())
{
unlockedCapacity = (float)data->initialUnlockedCap;
currentRemaining = unlockedCapacity;
}
}
void CPlayerSpecialAbility::UpdateFromStat()
{
if (!Verifyf(StatsInterface::IsKeyValid(m_statAbility), "Player model %s should have SPECIAL_ABILITY stat since this model has ability type=%d.", FindPlayerPed() && FindPlayerPed()->GetPedModelInfo() ? FindPlayerPed()->GetPedModelInfo()->GetModelName() : "", data ? data->type : -1))
return;
currentRemaining = (float)StatsInterface::GetIntStat(m_statAbility);
// fix special ability value in case we had a bad stored one
if (currentRemaining >= unlockedCapacity)
currentRemaining = unlockedCapacity;
else if (currentRemaining <= 0.f)
currentRemaining = 0.f;
if (StatsInterface::IsKeyValid(m_statAbility))
{
StatsInterface::SetStatData(m_statAbility, (s32)floor(currentRemaining), STATUPDATEFLAG_ASSERTONLINESTATS);
}
}
CPlayerSpecialAbility::~CPlayerSpecialAbility()
{
m_ClipsetRequestHelper.Release();
}
s32 CPlayerSpecialAbility::GetTunedOrDefaultDuration(s32 abilityDuration) const
{
// cloud tunable durations are in ms, so need to be converted to seconds if returned
return abilityDuration >= 0 ? (s32)(abilityDuration * 0.001f) : data->duration;
}
s32 CPlayerSpecialAbility::GetDuration() const
{
switch (data->type)
{
case SAT_ENRAGED:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedDuration);
case SAT_GHOST:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilityGhostDuration);
case SAT_SPRINT_SPEED_BOOST:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilitySprintBoostDuration);
case SAT_BACKUP:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilityCallDuration);
case SAT_NITRO_COP:
case SAT_NITRO_CROOK:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilityNitroDuration);
case SAT_OIL_SLICK:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilityOilSlickDuration);
case SAT_SPIKE_MINE:
return GetTunedOrDefaultDuration(CPlayerSpecialAbilityManager::ms_CNCAbilitySpikeMineDurationLifeTime);
default:
break;
}
return data->duration;
}
float CPlayerSpecialAbility::GetDefenseMultiplier() const
{
switch (data->type)
{
case SAT_ENRAGED:
if (CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedHealthReduction >= 0)
return CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedHealthReduction;
break;
default:
break;
}
return data->defenseMultiplier;
}
float CPlayerSpecialAbility::GetStaminaMultiplier() const
{
switch (data->type)
{
case SAT_ENRAGED:
if (CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedStaminaReduction >= 0)
return CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedStaminaReduction;
break;
default:
break;
}
return data->staminaMultiplier;
}
float CPlayerSpecialAbility::GetSprintMultiplier() const
{
switch (data->type)
{
case SAT_SPRINT_SPEED_BOOST:
if (CPlayerSpecialAbilityManager::ms_CNCAbilitySprintBoostIncrease >= 0)
return CPlayerSpecialAbilityManager::ms_CNCAbilitySprintBoostIncrease;
break;
default:
break;
}
return data->sprintMultiplier;
}
void CPlayerSpecialAbility::Process(CPed *pPed)
{
gaveKillCharge = false;
if (!IsAbilityUnlocked())
{
currentRemaining = 0.f;
chargeToAdd = 0.f;
sliceToAdd = 0.f;
return;
}
HandleVehicleJump();
HandleKineticRam();
float deltaTime = fwTimer::GetSystemTimeStep();
// add small part of charge based on time delta
if (chargeToAdd > 0.f)
{
if (currentRemaining < unlockedCapacity)
{
float addThisFrame = rage::Min(chargeToAdd, deltaTime * sliceToAdd);
currentRemaining += addThisFrame;
// the rage ability gets charges from taking damage, which happens a lot. we need to slow down ability unlock a bit
if (GetType() == SAT_RAGE)
addThisFrame *= 0.5f;
accumulatedCharge += addThisFrame;
}
chargeToAdd = rage::Max(0.f, chargeToAdd - deltaTime * sliceToAdd);
}
else if (chargeToAdd < 0.f)
{
if (currentRemaining > 0.f)
{
float addThisFrame = rage::Max(chargeToAdd, deltaTime * sliceToAdd);
currentRemaining += addThisFrame;
}
chargeToAdd = rage::Min(0.f, chargeToAdd - deltaTime * sliceToAdd);
}
else
{
sliceToAdd = 0.f;
}
if (addingContinuous)
{
if (currentRemaining < unlockedCapacity)
{
float chargeByTime = deltaTime * CPlayerSpecialAbilityManager::GetContinuousCharge() * data->chargeMultiplier * CPlayerSpecialAbilityManager::GetChargeMultiplier() * addContinuousMultiplier;
currentRemaining += chargeByTime;
accumulatedCharge += chargeByTime;
}
addContinuousMultiplier = 0.f;
addingContinuous = false;
}
if (currentRemaining >= unlockedCapacity)
{
currentRemaining = unlockedCapacity;
if (chargeToAdd > 0.f)
chargeToAdd = 0.f;
}
else if (currentRemaining <= 0.f)
{
currentRemaining = 0.f;
if (chargeToAdd < 0.f)
chargeToAdd = 0.f;
}
if (StatsInterface::IsKeyValid(m_statAbility))
{
StatsInterface::SetStatData(m_statAbility, (s32)floor(currentRemaining), STATUPDATEFLAG_ASSERTONLINESTATS);
}
const u32 curTime = fwTimer::GetTimeInMilliseconds();
const float dt = ((float)curTime) - toggleTime;
bool chargeUnder50Percent = false;
if (IsActive())
{
SetInternalTimeWarp(data->timeWarpScale);
const fwMvClipSetId clipSetId(GetActiveAnimSetID());
if (clipSetId != CLIP_SET_ID_INVALID)
m_ClipsetRequestHelper.Request(clipSetId);
// deactivate during cutscenes
if (CutSceneManager::GetInstance() && CutSceneManager::GetInstance()->IsRunning())
{
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Stop(data->outFxName, AnimPostFXManager::kSpecialAbility);
Deactivate();
return;
}
if (Water::IsCameraUnderwater())
{
Deactivate();
return;
}
if (data->type == SAT_CAR_SLOWDOWN && !FindPlayerPed()->GetIsInVehicle())
{
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Start(data->outFxName, 0U, false, false, false, 0U, AnimPostFXManager::kSpecialAbility);
Deactivate();
return;
}
float depleteByTime = data->depletionMultiplier * deltaTime;
currentRemaining -= depleteByTime;
if ((s32)floor(currentRemaining) <= 0)
{
currentRemaining = 0.f;
if (StatsInterface::IsKeyValid(m_statAbility))
{
StatsInterface::SetStatData(m_statAbility, 0, STATUPDATEFLAG_ASSERTONLINESTATS);
}
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Start(data->outFxName, 0U, false, false, false, 0U, AnimPostFXManager::kSpecialAbility);
Deactivate();
}
isFadingOut = false;
}
else if (dt <= (CPlayerSpecialAbilityManager::GetFadeOutTime() * 1000.f))
{
isFadingOut = true;
if (pPed->IsLocalPlayer() && GetInternalTimeWarp() < 1.0f)
{
SetInternalTimeWarp(rage::Lerp(1.0f - GetFxStrength(), data->timeWarpScale, 1.f));
}
lastTimeWarpFrame = fwTimer::GetFrameCount();
}
else
{
if (lastTimeWarpFrame == fwTimer::GetFrameCount() - 1)
{
isFadingOut = false;
}
if (pPed->IsLocalPlayer())
{
SetInternalTimeWarp(1.f);
}
chargeUnder50Percent = true;
}
// only apply nearmiss, or the under 50% charge, when inactive
if (chargeUnder50Percent)
{
if (lastNearVehicleMissTime > 0 && lastNearVehicleMissTime + MIN_NEAR_VEHICLE_MISS_TIME <= fwTimer::GetTimeInMilliseconds())
{
AddToMeterNormalized(nearMissChargeAmount, true);
nearMissChargeAmount = 0.f;
lastNearVehicleMissTime = 0;
}
else
{
// ability not active, slowly charge the meter to 50% (as displayed)
float perc = (currentRemaining - MIN_REMAINING_CHARGE_FOR_ACTIVATION) / (unlockedCapacity - MIN_REMAINING_CHARGE_FOR_ACTIVATION);
if (perc < 0.5f)
{
const float step = 0.5f / 120.f; // 50% over 2 minutes
float chargeByTime = unlockedCapacity * step * deltaTime;
currentRemaining = Min(currentRemaining + chargeByTime, 0.5f * (unlockedCapacity + MIN_REMAINING_CHARGE_FOR_ACTIVATION));
}
}
}
if (StatsInterface::IsKeyValid(m_statAbility))
{
StatsInterface::SetStatData(m_statAbility, (s32)floor(currentRemaining), STATUPDATEFLAG_ASSERTONLINESTATS);
}
// if we've accumulated 60% of the entire unlocked bar we increase the current capacity by 10% untill it's fully unlocked
if (accumulatedCharge >= data->duration * 1.2f)
{
accumulatedCharge = 0.f;
unlockedCapacity = rage::Min((float)data->duration, unlockedCapacity + (data->duration * 0.1f));
// update SP stat data
if (!NetworkInterface::IsGameInProgress())
{
if (StatsInterface::IsKeyValid(m_statUnlockedAbility))
{
// the stat variable wants a percentage [0, 100] not absolute duration
StatsInterface::SetStatData(m_statUnlockedAbility, (s32)floor(100.f * (unlockedCapacity / data->duration)), STATUPDATEFLAG_ASSERTONLINESTATS);
}
}
}
#if __BANK
if (!NetworkInterface::IsGameInProgress())
{
if (StatsInterface::IsKeyValid(m_statUnlockedAbility))
{
CPlayerSpecialAbilityManager::unlockedCapacityPercentage = StatsInterface::GetIntStat(m_statUnlockedAbility);
}
}
#endif // __BANK
}
void CPlayerSpecialAbility::SetInternalTimeWarp(float fTimeWarp)
{
fwTimer::SetTimeWarpSpecialAbility(fTimeWarp);
}
float CPlayerSpecialAbility::GetInternalTimeWarp() const
{
return fwTimer::GetTimeWarpSpecialAbility();
}
CNetGamePlayer* GetNetGamePlayer(CPlayerSpecialAbility* playerSpecialAbility)
{
if (!playerSpecialAbility) return NULL;
CPed* pPlayerPed = FindPlayerPed();
if (pPlayerPed)
{
CNetObjGame* pNetObject = pPlayerPed->GetNetworkObject();
if (pNetObject)
{
CNetGamePlayer* pNetPlayer = pNetObject->GetPlayerOwner();
if (pNetPlayer) return pNetPlayer;
}
}
return NULL;
}
bool CPlayerSpecialAbility::CanBeActivatedInArcadeMode(CPed* pPed) const
{
Assertf(pPed, "Attempting to validate ability use for arcade mode but ped is not valid.");
// apply arcade mode specific exclusions
if (NetworkInterface::IsInCopsAndCrooks())
{
if (pPed->GetIsOnFoot())
{
return data->allowsActivationOnFoot;
}
else if (data->allowsActivationInVehicle)
{
// in C&C, secondary slot is used for vehicle abilities
if (data->type == pPed->GetSpecialAbilityType(PSAS_SECONDARY))
{
if (pPed->GetPlayerInfo() && pPed->GetIsDrivingVehicle())
{
// player may only use vehicle ability as the driver of their active vehicle
if (pPed->GetVehiclePedInside() == pPed->GetPlayerInfo()->GetArcadeInformation().GetActiveVehicle())
{
return true;
}
}
}
else return true;
}
}
return false;
}
bool CPlayerSpecialAbility::CanBeSelectedInArcadeMode() const
{
CPed* ped = FindPlayerPed();
if (!ped)
{
return false;
}
return CanBeActivatedInArcadeMode(ped);
}
bool CPlayerSpecialAbility::CanBeActivated() const
{
if ((NetworkInterface::IsGameInProgress() && (!isEnabled || isActive)) || (!NetworkInterface::IsGameInProgress() && (isActive || !IsAbilityUnlocked() || !isEnabled || (currentRemaining <= 0.f))))
{
return false;
}
if (Water::IsCameraUnderwater())
{
return false;
}
CPed* ped = FindPlayerPed();
if (!ped)
{
return false;
}
// can't use if we have not accumulated the minimum charge required for activation
// (get remaining is rounded to nearest otherwise it may never reach the minimum charge required for activation)
if (GetRemaining(false) < GetMinimumChargeForActivation())
{
return false;
}
// check for arcade mode
if (NetworkInterface::IsInArcadeMode() && !CanBeActivatedInArcadeMode(ped))
{
return false;
}
if (!NetworkInterface::IsGameInProgress()) // Fall checks for SP only
{
// turn off if we are going to fall further than our max fall height.
const CTask* pTask = ped->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_FALL);
if (pTask)
{
const CTaskFall* pFallTask = static_cast<const CTaskFall*>(pTask);
if ((pFallTask->GetMaxFallDistance() > pFallTask->GetMaxPedFallHeight(ped)) || (pFallTask->GetState() == CTaskFall::State_Parachute) || (pFallTask->GetState() == CTaskFall::State_HighFall))
{
return false;
}
}
if (ped->GetPedIntelligence()->GetQueriableInterface() && ped->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_HIGH_FALL))
{
return false;
}
}
if (ped->GetArrestState() == ArrestState_Arrested)
{
return false;
}
if (!NetworkInterface::IsGameInProgress() && (GetRemaining() < MIN_REMAINING_CHARGE_FOR_ACTIVATION))
{
return false;
}
// apply ability specific exclusions
switch (data->type)
{
case SAT_NONE:
return false;
case SAT_CAR_SLOWDOWN:
{
if (!ped->GetIsInVehicle())
return false;
CVehicle* veh = ped->GetMyVehicle();
if (!veh || (veh->GetVehicleType() != VEHICLE_TYPE_CAR && veh->GetVehicleType() != VEHICLE_TYPE_BIKE && veh->GetVehicleType() != VEHICLE_TYPE_QUADBIKE && veh->GetVehicleType() != VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE))
return false;
}
break;
default:
break;
}
return true;
}
bool CPlayerSpecialAbility::Activate()
{
if (!CanBeActivated())
{
if (NetworkInterface::IsGameInProgress())
{
if (CNetGamePlayer* pNetGamePlayer = GetNetGamePlayer(this))
{
GetEventScriptNetworkGroup()->Add(CEventNetworkPlayerSpecialAbilityFailedActivation(*pNetGamePlayer, data->type));
}
}
return false;
}
// validity of ped is checked in CanBeActivated
CPed* ped = FindPlayerPed();
switch (data->type)
{
case SAT_RAGE:
{
// Force stealth mode off if we are using rage
if( ped->GetMotionData()->GetUsingStealth() )
ped->GetMotionData()->SetUsingStealth( false );
if(ped->GetSpeechAudioEntity())
ped->GetSpeechAudioEntity()->PlayTrevorSpecialAbilitySpeech();
}
break;
case SAT_BULLET_TIME:
{
}
break;
case SAT_SNAPSHOT:
{
}
break;
case SAT_INSULT:
{
//Say the combat taunt.
audSpeechAudioEntity::MakeTrevorRage();
//Grab the ped position.
Vec3V vPedPosition = ped->GetTransform().GetPosition();
//Iterate over the nearby peds.
static float s_fMaxDistance = 50.0f;
CEntityIterator pedIterator(IteratePeds, ped, &vPedPosition, s_fMaxDistance);
for(CEntity* pNearbyEntity = pedIterator.GetNext(); pNearbyEntity; pNearbyEntity = pedIterator.GetNext())
{
//Grab the nearby ped.
CPed* pNearbyPed = static_cast<CPed *>(pNearbyEntity);
//Post the event.
CEventCombatTaunt event(ped);
pNearbyPed->GetPedIntelligence()->AddEvent(event);
}
}
break;
case SAT_ENRAGED:
{
// enraged is currently used in Arcade mode only
if (NetworkInterface::IsInArcadeMode())
{
CPlayerInfo* playerInfo = ped->GetPlayerInfo();
if (playerInfo)
{
// set values of the networked defense modifiers from ability data
float abilityDefenseMultiplier = GetDefenseMultiplier();
pedDebugf1("CPlayerSpecialAbility::Activate() [SAT_ENRAGED]: Applying defence multiplier (%f)", abilityDefenseMultiplier);
playerInfo->SetPlayerWeaponDefenseModifier(abilityDefenseMultiplier);
playerInfo->SetPlayerMeleeWeaponDefenseModifier(abilityDefenseMultiplier);
}
}
}
break;
case SAT_KINETIC_RAM:
{
if (ped->GetVehiclePedInside())
{
// Everything else processed in HandleKineticRam
StartKineticRam();
}
}
break;
default:
break;
}
SetInternalTimeWarp(data->timeWarpScale);
isActive = true;
toggleTime = fwTimer::GetTimeInMilliseconds();
ANIMPOSTFXMGR.Start(data->fxName, 0U, true, true, true, 0U, AnimPostFXManager::kSpecialAbility);
chargeToAdd = 0.f;
sliceToAdd = 0.f;
if (NetworkInterface::IsGameInProgress())
{
if (CNetGamePlayer* pNetGamePlayer = GetNetGamePlayer(this))
{
GetEventScriptNetworkGroup()->Add(CEventNetworkPlayerActivatedSpecialAbility(*pNetGamePlayer, data->type));
}
}
else
{
// Don't increment stats when the ability is activated in MP
StatsInterface::IncrementStat(STAT_SPECIAL_ABILITY_ACTIVE_NUM.GetStatId(), 1.0f);
}
CPlayerSpecialAbilityManager::UpdateDlcMultipliers();
return true;
}
void CPlayerSpecialAbility::Deactivate()
{
if ((!NetworkInterface::IsGameInProgress() && (!isActive || !IsAbilityUnlocked() || !isEnabled)) || (NetworkInterface::IsInArcadeMode() && !isActive))
{
return;
}
m_ClipsetRequestHelper.Release();
switch (data->type)
{
case SAT_NONE:
return;
case SAT_CAR_SLOWDOWN:
{
}
break;
case SAT_RAGE:
{
}
break;
case SAT_BULLET_TIME:
{
}
break;
case SAT_SNAPSHOT:
{
}
break;
case SAT_INSULT:
{
}
break;
case SAT_ENRAGED:
{
// enraged is currently used in Arcade mode only
CPed* ped = FindPlayerPed();
if (ped && NetworkInterface::IsInArcadeMode())
{
CPlayerInfo* playerInfo = ped->GetPlayerInfo();
if (playerInfo)
{
// restore default
pedDebugf1("CPlayerSpecialAbility::Deactivate() [SAT_ENRAGED]: Restoring defence multiplier to 1.0");
playerInfo->SetPlayerWeaponDefenseModifier(1.0f);
playerInfo->SetPlayerMeleeWeaponDefenseModifier(1.0f);
}
}
}
break;
default:
break;
}
if (NetworkInterface::IsGameInProgress())
{
if (CNetGamePlayer* pNetGamePlayer = GetNetGamePlayer(this))
{
GetEventScriptNetworkGroup()->Add(CEventNetworkPlayerDeactivatedSpecialAbility(*pNetGamePlayer, data->type));
}
}
else if (fwTimer::GetTimeInMilliseconds() > toggleTime)
{
StatsInterface::IncrementStat(STAT_SPECIAL_ABILITY_ACTIVE_TIME.GetStatId(), (float)(fwTimer::GetTimeInMilliseconds() - toggleTime));
}
isActive = false;
toggleTime = fwTimer::GetTimeInMilliseconds();
}
void CPlayerSpecialAbility::DeactivateNoFadeOut()
{
if (!NetworkInterface::IsGameInProgress() && (!isActive || !IsAbilityUnlocked() || !isEnabled))
{
// do these resets here too, in case this is called after the regular Deactivate and we need to kill the fade
SetInternalTimeWarp(1.f);
u32 curTime = fwTimer::GetTimeInMilliseconds();
toggleTime = curTime - (u32)(CPlayerSpecialAbilityManager::GetFadeOutTime() * 1000.f);
return;
}
Deactivate();
SetInternalTimeWarp(1.f);
//Set the toggle time as though the fade out has finished
u32 curTime = fwTimer::GetTimeInMilliseconds();
toggleTime = curTime - (u32)(CPlayerSpecialAbilityManager::GetFadeOutTime() * 1000.f);
}
void CPlayerSpecialAbility::Toggle(CPed* ped)
{
if (!IsAbilityUnlocked() || !canToggle || !isEnabled)
{
return;
}
if (isActive)
{
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Start(data->outFxName, 0U, false, false, false, 0U, AnimPostFXManager::kSpecialAbility);
Deactivate();
}
else
{
// don't allow activation under certain circumstances
if (ped->IsDead())
{
return;
}
Activate();
}
canToggle = false;
}
void CPlayerSpecialAbility::Reset()
{
Deactivate();
currentRemaining = 0.f;
chargeToAdd = 0.f;
sliceToAdd = 0.f;
accumulatedCharge = 0.f;
if (StatsInterface::IsKeyValid(m_statAbility))
{
StatsInterface::SetStatData(m_statAbility, 0, STATUPDATEFLAG_ASSERTONLINESTATS);
}
}
bool CPlayerSpecialAbility::IsMeterFull() const
{
s32 remaining = 0;
if (StatsInterface::IsKeyValid(m_statAbility))
{
remaining = StatsInterface::GetIntStat(m_statAbility);
}
return remaining == (s32)floor(unlockedCapacity);
}
void CPlayerSpecialAbility::FillMeter(bool ignoreActive)
{
if (!IsAbilityUnlocked() || (!ignoreActive && isActive))
{
return;
}
chargeToAdd = unlockedCapacity;
sliceToAdd = chargeToAdd / timeToAddCharge;
}
void CPlayerSpecialAbility::DepleteMeter(bool ignoreActive)
{
if (!IsAbilityUnlocked() || (!ignoreActive && isActive))
{
return;
}
s32 remaining = 0;
if (StatsInterface::IsKeyValid(m_statAbility))
{
remaining = StatsInterface::GetIntStat(m_statAbility);
}
chargeToAdd = -(float)remaining;
sliceToAdd = chargeToAdd / timeToAddCharge;
}
void CPlayerSpecialAbility::AddToMeter(s32 value, bool ignoreActive)
{
if (!IsAbilityUnlocked() || (!ignoreActive && isActive))
{
return;
}
if (value > 0)
value = (s32)(value * data->chargeMultiplier * CPlayerSpecialAbilityManager::GetChargeMultiplier());
chargeToAdd += value;
chargeToAdd = chargeToAdd > -currentRemaining ? chargeToAdd : -currentRemaining;
sliceToAdd = chargeToAdd / timeToAddCharge;
}
void CPlayerSpecialAbility::AddToMeterNormalized(float value, bool ignoreActive, bool applyLocalMultiplier)
{
if (!IsAbilityUnlocked() || (!ignoreActive && isActive))
{
return;
}
if (value > 0.f)
{
if (applyLocalMultiplier)
value *= data->chargeMultiplier;
value *= CPlayerSpecialAbilityManager::GetChargeMultiplier();
value = rage::Min(1.f, value);
value = rage::Max(0.f, value);
}
chargeToAdd += value * unlockedCapacity;
chargeToAdd = chargeToAdd > -currentRemaining ? chargeToAdd : -currentRemaining;
sliceToAdd = chargeToAdd / timeToAddCharge;
}
void CPlayerSpecialAbility::AddToMeterContinuous(bool ignoreActive, float multiplier)
{
if (!IsAbilityUnlocked() || (!ignoreActive && isActive))
{
return;
}
addingContinuous = true;
addContinuousMultiplier = rage::Max(addContinuousMultiplier, multiplier);
}
s32 CPlayerSpecialAbility::GetRemaining(bool roundDown) const
{
if (StatsInterface::IsKeyValid(m_statAbility))
{
Assertf(currentRemaining <= unlockedCapacity, "Current special ability charge is larger than what unlocked bar allows! (%f / %f)", currentRemaining, unlockedCapacity);
if (roundDown)
{
return (s32)floor(currentRemaining);
}
else
{
return (s32)floor(currentRemaining + 0.5f);
}
}
return 0;
}
float CPlayerSpecialAbility::GetRemainingNormalized() const
{
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
return replayRemainingNormalized;
}
#endif
return currentRemaining / unlockedCapacity;
}
float CPlayerSpecialAbility::GetRemainingPercentageForDisplay() const
{
// Any charge below MIN_REMAINING_CHARGE_FOR_ACTIVATION is displayed as no charge through the HUD
if (GetRemaining() < MIN_REMAINING_CHARGE_FOR_ACTIVATION)
{
return 0.0f;
}
float fRemainingNormalized = (currentRemaining - MIN_REMAINING_CHARGE_FOR_ACTIVATION) / (unlockedCapacity - MIN_REMAINING_CHARGE_FOR_ACTIVATION);
fRemainingNormalized = rage::Max(fRemainingNormalized, 0.01f);
return (fRemainingNormalized * GetCapacityPercentageForDisplay());
}
float CPlayerSpecialAbility::GetCapacity() const
{
return unlockedCapacity / data->duration;
}
float CPlayerSpecialAbility::GetCapacityPercentageForDisplay() const
{
// Adjusted to account for charge below MIN_REMAINING_CHARGE_FOR_ACTIVATION being displayed as no charge.
return ((unlockedCapacity - MIN_REMAINING_CHARGE_FOR_ACTIVATION) / (data->duration - MIN_REMAINING_CHARGE_FOR_ACTIVATION)) * 100.0f;
}
void CPlayerSpecialAbility::GetSpecialAbilityFx(atHashString &fxmod) const
{
fxmod = data->fxName;
}
float CPlayerSpecialAbility::GetFxStrength() const
{
u32 curTime = fwTimer::GetTimeInMilliseconds();
float dt = ((float)curTime) - toggleTime;
float timeRange = IsActive() ? CPlayerSpecialAbilityManager::GetFadeInTime() : CPlayerSpecialAbilityManager::GetFadeOutTime();
float fHalfSigmoid = IsActive() ? CPlayerSpecialAbilityManager::GetHalfSigmoidConstant() : -1.0f - CPlayerSpecialAbilityManager::GetHalfSigmoidConstant();
float strength = 0.f;
float x = dt / (timeRange * 1000.f);
switch (CPlayerSpecialAbilityManager::GetFadeCurveType())
{
case FCT_LINEAR:
strength = x;
break;
case FCT_HALF_SIGMOID:
x = rage::Min(x, 1.f);
strength = (fHalfSigmoid * x) / (fHalfSigmoid - x + 1.f);
break;
case FCT_SIGMOID:
x *= 2.f;
strength = x / (Sqrtf(CPlayerSpecialAbilityManager::GetSigmoidConstant() + x*x));
break;
case FCT_NONE:
default:
strength = 1.f;
}
strength = rage::Min(strength, 1.f);
strength = rage::Max(strength, 0.f);
if (!IsActive())
strength = 1.f - strength;
return strength;
}
bool CPlayerSpecialAbility::ShouldApplyFx() const
{
if( CNetwork::IsGameInProgress() )
{
return false;
}
u32 curTime = fwTimer::GetTimeInMilliseconds();
float dt = ((float)curTime) - toggleTime;
if (CPlayerSpecialAbilityManager::GetFadeCurveType() != FCT_NONE)
{
if (!IsActive() && dt > (CPlayerSpecialAbilityManager::GetFadeOutTime() * 1000.f))
{
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Stop(data->outFxName, AnimPostFXManager::kSpecialAbility);
return false;
}
return true;
}
if (!IsActive())
{
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Stop(data->outFxName, AnimPostFXManager::kSpecialAbility);
return false;
}
return true;
}
bool CPlayerSpecialAbility::IsPlayingFx() const
{
return ANIMPOSTFXMGR.IsRunning(data->fxName) || ANIMPOSTFXMGR.IsRunning(data->outFxName);
}
void CPlayerSpecialAbility::ApplyFx() const
{
if (ShouldApplyFx())
{
float strength = GetFxStrength();
ANIMPOSTFXMGR.SetUserFadeLevel(data->fxName, strength);
}
}
void CPlayerSpecialAbility::StartFx() const
{
ANIMPOSTFXMGR.Start(data->fxName, 0U, true, true, true, 0U, AnimPostFXManager::kSpecialAbility);
}
void CPlayerSpecialAbility::StopFx() const
{
ANIMPOSTFXMGR.Stop(data->fxName, AnimPostFXManager::kSpecialAbility);
ANIMPOSTFXMGR.Stop(data->outFxName, AnimPostFXManager::kSpecialAbility);
}
void CPlayerSpecialAbility::TriggerNearVehicleMiss()
{
if (!IsAbilityUnlocked())
{
return;
}
// when a near miss is triggered, the time is recorded and a charge is awarded only after
// a predefined amount of time has passed. This can be aborted if a crash happens in the mean time,
// i.e. the near miss was a bit too near.
// 0 works as a reset value for the time variable
// moreover, the charge accumulates to give a larger charge if many near misses took place before the next issue
if (lastNearVehicleMissTime == 0)
{
lastNearVehicleMissTime = fwTimer::GetTimeInMilliseconds();
}
nearMissChargeAmount += 0.0325f;
}
void CPlayerSpecialAbility::AbortNearVehicleMiss()
{
lastNearVehicleMissTime = 0;
nearMissChargeAmount = 0.f;
}
void CPlayerSpecialAbility::TriggerLowHealth()
{
if (!IsAbilityUnlocked())
{
return;
}
if (!appliedLowHealthCharge)
{
AddToMeterNormalized(0.065f, true);
appliedLowHealthCharge = true;
}
}
void CPlayerSpecialAbility::AbortLowHealth()
{
appliedLowHealthCharge = false;
}
void CPlayerSpecialAbility::TriggerExplosionKill()
{
// allow only an explosion kill charge per 3 seconds (to not get one for each kill)
if (fwTimer::GetTimeInMilliseconds() > lastExplosionKillTime + 3000)
{
AddToMeterNormalized(0.04875f, false);
lastExplosionKillTime = fwTimer::GetTimeInMilliseconds();
}
}
void CPlayerSpecialAbility::TriggerBumpedIntoPed(void* pedEa)
{
// restrict ped bumps to one every 3 seconds, or one for each ped but not more often than 1 per second,
// the bump is triggered by collision so it's continuous if you touch a ped, can't have it trigger
// all the time like that or the meter gets filled real quick
u32 currentTime = fwTimer::GetTimeInMilliseconds();
if ((currentTime > lastPedBumpTime + 3000) || (pedEa != lastPedBumpedInto && currentTime > lastPedBumpTime + 1000))
{
AddToMeterNormalized(0.0325f, false);
lastPedBumpedInto = pedEa;
lastPedBumpTime = currentTime;
}
}
void CPlayerSpecialAbility::TriggerRanOverPed(float damage)
{
if (damage > 0.f)
{
// can only run over peds once per second, or we might end up filling a whole bar when the player keeps
// driving into a ped
u32 currentTime = fwTimer::GetTimeInMilliseconds();
if (currentTime > lastPedRanOverTime + 1000)
{
AddToMeterNormalized(0.00325f, false);
lastPedRanOverTime = currentTime;
}
}
}
void CPlayerSpecialAbility::TriggerVehicleJump(CVehicle* pVeh)
{
if (!pVeh || isVehJumpInProgress || isActive)
return;
vehJumpStartTime = fwTimer::GetTimeInMilliseconds();
vehJumpTouchTime = 0;
isVehJumpInProgress = true;
jumpVeh = pVeh;
}
void CPlayerSpecialAbility::HandleVehicleJump()
{
if (isVehJumpInProgress && jumpVeh)
{
u32 duration = fwTimer::GetTimeInMilliseconds() - vehJumpStartTime;
// abort jump reward if xy velocity is lower than z velocity, vehicle is just falling down
const Vector3& vel = jumpVeh->GetVelocity();
if ((vel.x * vel.x + vel.y * vel.y) < vel.z * vel.z)
{
isVehJumpInProgress = false;
}
else
{
// after a certain period we start giving continuous rewards while airborn and before any wheel has touched the ground
if (vehJumpTouchTime == 0 && duration > TIME_AIRBORN_BEFORE_REWARD)
AddToMeterContinuous(false, 0.05f);
bool allWheelsTouching = true;
s32 numWheels = jumpVeh->GetNumWheels();
for (s32 i = 0; i < numWheels; ++i)
{
CWheel* wheel = jumpVeh->GetWheel(i);
if (wheel)
{
if (wheel->GetIsTouching())
{
// if a wheel touches the ground too early we abort the reward
if (duration < TIME_AIRBORN_BEFORE_REWARD)
{
allWheelsTouching = false;
isVehJumpInProgress = false;
break;
}
// record the time of the first wheel touching the ground
if (vehJumpTouchTime == 0)
vehJumpTouchTime = fwTimer::GetTimeInMilliseconds();
}
else
{
allWheelsTouching = false;
}
}
}
if (allWheelsTouching)
{
// we give the reward if all wheels touch the ground after we've been airborn for a certain amount of time
// and all wheels touched the ground within a certain period
if (duration > TIME_AIRBORN_BEFORE_REWARD && vehJumpTouchTime + TIME_SPAN_FOR_SUCCESSFUL_LANDING >= fwTimer::GetTimeInMilliseconds())
{
AddToMeterNormalized(0.0325f, false);
}
isVehJumpInProgress = false;
}
else
{
// if all wheels haven't touched the ground within the time span set, we fail the landing
if (vehJumpTouchTime && vehJumpTouchTime + TIME_SPAN_FOR_SUCCESSFUL_LANDING < fwTimer::GetTimeInMilliseconds())
{
isVehJumpInProgress = false;
}
}
}
}
else if (isVehJumpInProgress && !jumpVeh)
{
isVehJumpInProgress = false;
}
}
bool CPlayerSpecialAbility::IsAbilityUnlocked() const
{
return CPlayerSpecialAbilityManager::IsAbilityUnlocked(FindPlayerPed()->GetPedModelInfo()->GetModelNameHash());
}
void CPlayerSpecialAbility::ProcessVfx()
{
if (IsActive())
{
if (data->type == SAT_CAR_SLOWDOWN)
{
CPed* pPlayerPed = FindPlayerPed();
if (pPlayerPed && pPlayerPed->GetIsInVehicle())
{
CVehicle* pPlayerVehicle = pPlayerPed->GetMyVehicle();
if (pPlayerVehicle)
{
CVehicleModelInfo* pPlayerVehicleModelInfo = pPlayerVehicle->GetVehicleModelInfo();
if (pPlayerVehicleModelInfo)
{
s32 boneIdx = pPlayerVehicleModelInfo->GetBoneIndex(VEH_TAILLIGHT_L);
g_vfxVehicle.UpdatePtFxLightTrail(pPlayerVehicle, boneIdx, 0);
boneIdx = pPlayerVehicleModelInfo->GetBoneIndex(VEH_TAILLIGHT_R);
g_vfxVehicle.UpdatePtFxLightTrail(pPlayerVehicle, boneIdx, 1);
}
}
}
}
}
}
void CPlayerSpecialAbility::StartKineticRam()
{
kineticRamActivationTime = fwTimer::GetTimeInMilliseconds();
kineticRamExplosionCount = 0;
}
void CPlayerSpecialAbility::StopKineticRam()
{
kineticRamActivationTime = 0;
kineticRamExplosionCount = 0;
}
void CPlayerSpecialAbility::HandleKineticRam()
{
if (kineticRamActivationTime != 0)
{
static const eExplosionTag EXPLOSION_HASH = EXP_TAG_CNC_KINETICRAM;
TUNE_GROUP_INT(KINETIC_RAM_TUNE, EXPLOSION_COUNT, 4, 1, 5, 1);
TUNE_GROUP_FLOAT(KINETIC_RAM_TUNE, EXPLOSION_SPACING_DISTANCE, 2.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(KINETIC_RAM_TUNE, EXPLOSION_SPACING_TIMING, 0.075f, 0.0f, 10.0f, 0.01f);
CPed* pPed = FindPlayerPed();
if (pPed && pPed->GetVehiclePedInside())
{
CVehicle* pVehicle = pPed->GetVehiclePedInside();
const u32 timeSinceTrigger = fwTimer::GetTimeInMilliseconds() - kineticRamActivationTime;
if (timeSinceTrigger > (kineticRamExplosionCount * (u32)(EXPLOSION_SPACING_TIMING * 1000.0f)))
{
Vec3V vLocalExplosionPos = Vec3V(V_ZERO);
// Adjust position to directly between the front two wheels
const int wheelLFBoneIdx = pVehicle->GetVehicleModelInfo()->GetBoneIndex(VEH_WHEEL_LF);
const int wheelRFBoneIdx = pVehicle->GetVehicleModelInfo()->GetBoneIndex(VEH_WHEEL_RF);
if (wheelLFBoneIdx != VEH_INVALID_ID && wheelRFBoneIdx != VEH_INVALID_ID)
{
const Vec3V vWheelLFPos = pVehicle->GetSkeletonData().GetDefaultTransform(wheelLFBoneIdx).d();
const Vec3V vWheelRFPos = pVehicle->GetSkeletonData().GetDefaultTransform(wheelRFBoneIdx).d();
vLocalExplosionPos = (vWheelLFPos+vWheelRFPos)/ScalarV(2.0f);
}
else
{
// Backup for stuff like motorbikes
const int chassisBoneIdx = pVehicle->GetVehicleModelInfo()->GetBoneIndex(VEH_CHASSIS);
if (chassisBoneIdx != VEH_INVALID_ID)
{
vLocalExplosionPos = pVehicle->GetSkeletonData().GetDefaultTransform(chassisBoneIdx).d();
}
}
// Offset forward based on explosion count
const float fForwardOffset = kineticRamExplosionCount * EXPLOSION_SPACING_DISTANCE;
vLocalExplosionPos.SetYf(vLocalExplosionPos.GetYf() + fForwardOffset);
// Convert to world space
Vec3V vWorldExplosionPos = pVehicle->TransformIntoWorldSpace(vLocalExplosionPos);
#if __BANK
TUNE_GROUP_BOOL(KINETIC_RAM_TUNE, EXPLOSION_ORGINAL_POSITION_RENDER, false);
if (EXPLOSION_ORGINAL_POSITION_RENDER)
{
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(vWorldExplosionPos), 0.1f, Color_red, true, 30);
}
#endif // __BANK
// Adjust to ground via probe (within reasonable limit), cancel if we didn't find anything
TUNE_GROUP_FLOAT(KINETIC_RAM_TUNE, EXPLOSION_GROUND_OFFSET_LIMIT, 1.0f, 0.0f, 5.0f, 0.01f);
TUNE_GROUP_FLOAT(KINETIC_RAM_TUNE, EXPLOSION_GROUND_OFFSET_PROBE_HEIGHT, 20.0f, 0.0f, 5.0f, 0.01f);
bool bFoundGround = false;
const float fGroundZ = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, vWorldExplosionPos.GetXf(), vWorldExplosionPos.GetYf(), vWorldExplosionPos.GetZf() + EXPLOSION_GROUND_OFFSET_PROBE_HEIGHT, &bFoundGround);
if (bFoundGround)
{
const float fAdjustmentDiff = Abs(vWorldExplosionPos.GetZf() - fGroundZ);
if (fAdjustmentDiff < EXPLOSION_GROUND_OFFSET_LIMIT)
{
vWorldExplosionPos.SetZf(fGroundZ);
}
else
{
// Significant Z change? Cancel the effect
StopKineticRam();
return;
}
}
else
{
// No ground? Cancel the effect
StopKineticRam();
return;
}
#if __BANK
TUNE_GROUP_BOOL(KINETIC_RAM_TUNE, EXPLOSION_ADJUSTED_POSITION_RENDER, false);
if (EXPLOSION_ADJUSTED_POSITION_RENDER)
{
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(vWorldExplosionPos), 0.1f, Color_green, true, 30);
}
#endif // __BANK
// Shockwave VFX on the first explosion only
const bool bNoVfx = kineticRamExplosionCount != 0;
// Grab the radius of the last explosion, scale earlier ones down based on that (we can't scale up due to clamps...)
const CExplosionTagData& expTagData = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(EXPLOSION_HASH);
TUNE_GROUP_FLOAT(KINETIC_RAM_TUNE, EXPLOSION_START_RADIUS, 1.5f, 0.0f, 50.0f, 0.01f);
const float fScaleRatio = EXPLOSION_START_RADIUS / expTagData.endRadius;
const float t = kineticRamExplosionCount / (float)(EXPLOSION_COUNT - 1);
const float fExpScale = Lerp(t, fScaleRatio, 1.0f);
// Create explosion
CExplosionManager::CExplosionArgs explosionArgs(EXPLOSION_HASH, VEC3V_TO_VECTOR3(vWorldExplosionPos));
explosionArgs.m_pEntExplosionOwner = pPed;
explosionArgs.m_pEntIgnoreDamage = pVehicle;
explosionArgs.m_bInAir = pVehicle->IsInAir();
explosionArgs.m_pAttachEntity = pVehicle;
explosionArgs.m_bAttachedToVehicle = true;
explosionArgs.m_bNoFx = bNoVfx;
explosionArgs.m_sizeScale = fExpScale;
CExplosionManager::AddExplosion(explosionArgs);
// TODO - Can we force attach VFX to the car as well so this looks better with fast motion?
// Increment and check if we're all done
kineticRamExplosionCount++;
if (kineticRamExplosionCount == (u32)EXPLOSION_COUNT)
{
StopKineticRam();
}
}
}
else
{
// Something went wrong? Cancel the effect
StopKineticRam();
}
}
}
//////////////////////////////////////////////////////////////////////////
// CPlayerSpecialAbilityManager
//////////////////////////////////////////////////////////////////////////
// Static Manager object
CPlayerSpecialAbilityManager CPlayerSpecialAbilityManager::instance;
bool CPlayerSpecialAbilityManager::abilityUnlocked[5]; // michael, franklin, trevor, arcadeplayer, techtest
float CPlayerSpecialAbilityManager::specialChargeMultiplier = 1.f;
u32 CPlayerSpecialAbilityManager::pendingActivationTime1 = 0;
u32 CPlayerSpecialAbilityManager::pendingActivationTime2 = 0;
u32 CPlayerSpecialAbilityManager::timeForActivation = 0;
u32 CPlayerSpecialAbilityManager::activationTime = 100;
u32 CPlayerSpecialAbilityManager::activationTime_CNC = 150;
u32 CPlayerSpecialAbilityManager::audioActivationTime = 50;
u32 CPlayerSpecialAbilityManager::canTriggerAbilityTime = 0;
bool CPlayerSpecialAbilityManager::pendingActivation = false;
bool CPlayerSpecialAbilityManager::needsResetForActivation = false;
bool CPlayerSpecialAbilityManager::canTriggerAbility = false;
bool CPlayerSpecialAbilityManager::canUnTriggerAbility = false;
bool CPlayerSpecialAbilityManager::blockingInputs = false;
bool CPlayerSpecialAbilityManager::specialAbilityDisabledSprint = false;
bool CPlayerSpecialAbilityManager::activateSprintToggle = false;
// cloud tunables
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedDuration = -1;
float CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedHealthReduction = -1.f;
float CPlayerSpecialAbilityManager::ms_CNCAbilityEnragedStaminaReduction = -1.f;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityGhostDuration = -1;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilitySprintBoostDuration = -1;
float CPlayerSpecialAbilityManager::ms_CNCAbilitySprintBoostIncrease = -1.f;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityCallDuration = -1;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityNitroDuration = -1;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityOilSlickDuration = -1;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityEMPVehicleDuration = -1;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilityOilSlickVehicleDuration = -1;
float CPlayerSpecialAbilityManager::ms_CNCAbilityOilSlickFrictionReduction = -1.f;
float CPlayerSpecialAbilityManager::ms_CNCAbilityBullbarsAttackRadius = -1.f;
float CPlayerSpecialAbilityManager::ms_CNCAbilityBullbarsImpulseAmount = -1.f;
float CPlayerSpecialAbilityManager::ms_CNCAbilitySpikeMineRadius = -1.0f;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilitySpikeMineDurationArming = -1;
s32 CPlayerSpecialAbilityManager::ms_CNCAbilitySpikeMineDurationLifeTime = -1;
void CPlayerSpecialAbilityManager::Init(const char* fileName)
{
abilityUnlocked[0] = false;
abilityUnlocked[1] = false;
abilityUnlocked[2] = false;
abilityUnlocked[3] = false;
abilityUnlocked[4] = false;
pendingActivationTime1 = 0;
pendingActivationTime2 = 0;
canTriggerAbilityTime = 0;
timeForActivation = 0;
instance.chargeMultiplier = 1.f;
Load(fileName);
}
void CPlayerSpecialAbilityManager::Shutdown()
{
// Delete
Reset();
}
CPlayerSpecialAbility* CPlayerSpecialAbilityManager::Create(SpecialAbilityType type)
{
const CSpecialAbilityData* pData = GetSpecialAbilityData(type);
if(pData)
{
return rage_new CPlayerSpecialAbility(pData);
}
return NULL;
}
void CPlayerSpecialAbilityManager::Destroy(CPlayerSpecialAbility* ability)
{
ability->StopFx();
delete ability;
}
s32 CPlayerSpecialAbilityManager::GetSmallCharge()
{
return instance.smallCharge;
}
s32 CPlayerSpecialAbilityManager::GetMediumCharge()
{
return instance.mediumCharge;
}
s32 CPlayerSpecialAbilityManager::GetLargeCharge()
{
return instance.largeCharge;
}
s32 CPlayerSpecialAbilityManager::GetContinuousCharge()
{
return instance.continuousCharge;
}
eFadeCurveType CPlayerSpecialAbilityManager::GetFadeCurveType()
{
return instance.fadeCurveType;
}
float CPlayerSpecialAbilityManager::GetHalfSigmoidConstant()
{
return instance.halfSigmoidConstant;
}
float CPlayerSpecialAbilityManager::GetSigmoidConstant()
{
return instance.sigmoidConstant;
}
float CPlayerSpecialAbilityManager::GetFadeInTime()
{
return instance.fadeInTime;
}
float CPlayerSpecialAbilityManager::GetFadeOutTime()
{
return instance.fadeOutTime;
}
void CPlayerSpecialAbilityManager::UpdateDlcMultipliers()
{
// update charge SE/CE charge multipliers, in case any content has been mounted/unmounted
if (EXTRACONTENT.IsContentPresent(s_specialEditionHash) || EXTRACONTENT.IsContentPresent(s_collectorsEditionHash))
CPlayerSpecialAbilityManager::SetSpecialChargeMultiplier(1.25f);
}
void CPlayerSpecialAbilityManager::ChargeEvent(AbilityChargeEventType eventType, CPed* eventPed, float fData, u32 iData, void* pData)
{
if (!eventPed || !eventPed->GetSpecialAbility() || !IsAbilityUnlocked(eventPed->GetPedModelInfo()->GetModelNameHash()) || eventPed != FindPlayerPed())
{
return;
}
CPlayerSpecialAbility* ability = eventPed->GetSpecialAbility();
SpecialAbilityType type = ability->GetType();
switch (type)
{
case SAT_CAR_SLOWDOWN:
{
CPed* ped = FindPlayerPed();
if (!ped)
break;
bool isInVehicle = ped->GetIsInVehicle();
CVehicle* veh = ped->GetMyVehicle();
if (veh && veh->GetVehicleType() == VEHICLE_TYPE_BICYCLE)
isInVehicle = false;
switch (eventType)
{
case ACET_NEAR_VEHICLE_MISS:
if (!isInVehicle)
break;
if (iData + 2000 < fwTimer::GetTimeInMilliseconds() && fData > MIN_NEAR_MISS_VELOCITY_DIFF_SQR)
{
ability->TriggerNearVehicleMiss();
}
break;
case ACET_DRIFTING:
if (!isInVehicle)
break;
ability->AddToMeterContinuous(false, 0.325f);
break;
case ACET_DRIVING_TOWARDS_ONCOMING:
case ACET_DRIVING_IN_CINEMATIC:
case ACET_TOP_SPEED:
if (!isInVehicle)
break;
ability->AddToMeterContinuous(false, 0.325f);
break;
//case ACET_CRASHED_VEHICLE:
//if (!isInVehicle)
//break;
//if (fData >= MIN_CRASHED_CAR_IMPACT)
//{
//ability->AddToMeterNormalized(-0.0325f, false);
//}
//ability->AbortNearVehicleMiss();
//break;
case ACET_VEHICLE_JUMP:
if (!isInVehicle)
break;
ability->TriggerVehicleJump((CVehicle*)pData);
break;
case ACET_PICKUP:
ability->AddToMeterNormalized(0.25f, true);
break;
case ACET_HEADSHOT:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.125f, false);
ability->GiveKillCharge();
}
break;
case ACET_KILL:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.04166f, false);
ability->GiveKillCharge();
}
default:
break;
}
}
break;
case SAT_BULLET_TIME:
case SAT_SNAPSHOT:
switch (eventType)
{
case ACET_RESET_LOW_HEALTH:
ability->AbortLowHealth();
break;
case ACET_HEADSHOT:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.125f, false);
ability->GiveKillCharge();
}
break;
case ACET_CHANGED_HEALTH:
// if health is regained, remove flag so we can get the charge when health is lost again
if (fData > (eventPed->GetMaxHealth() * 0.25f))
{
ability->AbortLowHealth();
}
else
{
ability->TriggerLowHealth();
}
break;
case ACET_STEALTH_KILL:
case ACET_KNOCKOUT:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.125f, false);
ability->GiveKillCharge();
}
break;
case ACET_KILL:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.0625f, false);
ability->GiveKillCharge();
}
case ACET_NEAR_VEHICLE_MISS:
if (iData + 2000 < fwTimer::GetTimeInMilliseconds() && fData > MIN_NEAR_MISS_VELOCITY_DIFF_SQR)
{
ability->TriggerNearVehicleMiss();
}
break;
case ACET_TOP_SPEED:
ability->AddToMeterContinuous(false, 0.24f);
break;
case ACET_PICKUP:
ability->AddToMeterNormalized(0.25f, true);
break;
default:
break;
}
break;
case SAT_RAGE:
case SAT_INSULT:
switch (eventType)
{
case ACET_RESET_LOW_HEALTH:
ability->AbortLowHealth();
break;
case ACET_CHANGED_HEALTH:
// if health is regained, remove flag so we can get the charge when health is lost again
if (fData > (eventPed->GetMaxHealth() * 0.25f))
{
ability->AbortLowHealth();
}
else
{
ability->TriggerLowHealth();
}
break;
case ACET_PUNCHED:
ability->AddToMeterNormalized(0.065f, false);
break;
case ACET_WEAPON_TAKEDOWN:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.125f, true);
ability->GiveKillCharge();
}
break;
case ACET_HEADSHOT:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.125f, false);
ability->GiveKillCharge();
}
break;
case ACET_NEAR_VEHICLE_MISS:
if (iData + 2000 < fwTimer::GetTimeInMilliseconds() && fData > MIN_NEAR_MISS_VELOCITY_DIFF_SQR)
{
ability->TriggerNearVehicleMiss();
}
break;
case ACET_TOP_SPEED:
ability->AddToMeterContinuous(false, 0.24f);
break;
case ACET_MISSION_FAILED:
ability->AddToMeterNormalized(0.065f, true);
break;
case ACET_GOT_PUNCHED:
ability->AddToMeterNormalized(0.065f, false);
break;
case ACET_EXPLOSION_KILL:
if (!ability->HasGivenKillCharge())
{
ability->TriggerExplosionKill();
ability->GiveKillCharge();
}
break;
case ACET_KILL:
if (!ability->HasGivenKillCharge())
{
ability->AddToMeterNormalized(0.0625f, false);
ability->GiveKillCharge();
}
break;
case ACET_PICKUP:
ability->AddToMeterNormalized(0.25f, true);
break;
case ACET_DAMAGE:
if (fData > 0.f)
{
ability->AddToMeterNormalized(fData * 0.002f, false, false); // 0.2% charge for each 1% of damage received
}
break;
default:
break;
}
break;
default:
break;
}
}
bool CPlayerSpecialAbilityManager::IsAbilityUnlocked(u32 modelNameHash)
{
if( modelNameHash == MI_PLAYERPED_PLAYER_ZERO.GetName().GetHash() ) // Michael
{
return abilityUnlocked[0];
}
else if( modelNameHash == MI_PLAYERPED_PLAYER_ONE.GetName().GetHash() ) // Franklin
{
return abilityUnlocked[1];
}
else if (modelNameHash == MI_PLAYERPED_PLAYER_TWO.GetName().GetHash()) // Trevor
{
return abilityUnlocked[2];
}
else if (NetworkInterface::IsGameInProgress())
{
return abilityUnlocked[4];
}
return false;
}
void CPlayerSpecialAbilityManager::SetAbilityStatus(u32 modelNameHash, bool unlocked, bool isArcadePlayer)
{
if( modelNameHash == MI_PLAYERPED_PLAYER_ZERO.GetName().GetHash() ) // Michael
{
abilityUnlocked[0] = unlocked;
}
else if( modelNameHash == MI_PLAYERPED_PLAYER_ONE.GetName().GetHash() ) // Franklin
{
abilityUnlocked[1] = unlocked;
}
else if( modelNameHash == MI_PLAYERPED_PLAYER_TWO.GetName().GetHash() ) // Trevor
{
abilityUnlocked[2] = unlocked;
}
else if (NetworkInterface::IsGameInProgress() && isArcadePlayer)
{
abilityUnlocked[4] = unlocked;
}
}
void CPlayerSpecialAbilityManager::ProcessControl(CPed* ped)
{
if (!ped)
return;
CControl *pControl = ped->GetControlFromPlayer();
if (!pControl)
return;
// only process control for the ability in the selected slot
CPlayerSpecialAbility* ability = ped->GetSpecialAbility(ped->GetSelectedAbilitySlot());
if (!ability)
return;
if (ability->GetType() == SAT_CAR_SLOWDOWN || ability->GetType() == SAT_BULLET_TIME || ability->GetType() == SAT_RAGE)
{
// turn ability off during these tasks
if(ped->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE) ||
ped->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE) ||
ped->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_IN_VEHICLE_SEAT_SHUFFLE))
{
ability->Deactivate();
return;
}
if (!NetworkInterface::IsGameInProgress()) // Fall checks for SP only
{
// turn off if we are going to fall further than our max fall height.
const CTask* pTask = ped->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_FALL);
if(pTask)
{
const CTaskFall* pFallTask = static_cast<const CTaskFall*>(pTask);
if( (pFallTask->GetMaxFallDistance() > pFallTask->GetMaxPedFallHeight(ped)) || (pFallTask->GetState() == CTaskFall::State_Parachute) || (pFallTask->GetState() == CTaskFall::State_HighFall))
{
ability->Deactivate();
return;
}
}
if(ped->GetPedIntelligence()->GetQueriableInterface() && ped->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_HIGH_FALL))
{
ability->Deactivate();
return;
}
}
if(ped->IsDead())
{
ability->Deactivate();
return;
}
}
bool canUseAbility = true;
// car slowdown can only be used in cars
if (ability->GetType() == SAT_CAR_SLOWDOWN && !ped->GetIsDrivingVehicle())
canUseAbility = false;
// bullet time and rage can't be used in vehicles
if ((ability->GetType() == SAT_BULLET_TIME || ability->GetType() == SAT_RAGE) && ped->GetIsInVehicle() && !ped->GetPedResetFlag(CPED_RESET_FLAG_AllowSpecialAbilityInVehicle))
canUseAbility = false;
// rage can't be used if you have been killed by melee
if (ability->GetType() == SAT_RAGE && ped->IsDeadByMelee())
canUseAbility = false;
// can't use if camera is in selfie mode
if(CPhoneMgr::CamGetSelfieModeState())
{
canUseAbility = false;
ability->Deactivate();
}
if (canUseAbility)
{
if (ability->GetAllowsDeactivationByInput())
{
controlDebugf1("This ability allows deactivation by input");
if (CanTriggerSpecialPedAbility())
{
ability->Toggle(ped);
}
else if (CanUnTriggerSpecialPedAbility())
{
ability->UnlockToggle();
}
}
else if (CanTriggerSpecialPedAbility() && ability->IsAbilityUnlocked() && ability->IsAbilityEnabled() && !ability->IsActive() && !ped->IsDead())
{
controlDebugf1("This ability doesn't allow deactivation by input");
ability->Activate();
}
}
// In C&C we want to disable the currently active Vehicle ability if the player leaves their Personal Vehicle for any reason
if (NetworkInterface::IsInArcadeMode() && NetworkInterface::IsInCopsAndCrooks())
{
// The Secondary slot is used for vehicle abilities
CPlayerSpecialAbility* specialAbility = ped->GetSpecialAbility(PSAS_SECONDARY);
if (specialAbility && specialAbility->IsActive())
{
// We can't activate the ability without being in our personal vehicle so all we care about here is we're still driving
if (!ped->GetIsDrivingVehicle())
{
specialAbility->Deactivate();
return;
}
}
}
}
bool CPlayerSpecialAbilityManager::UpdateSpecialAbilityTrigger(CPed *ped)
{
if (!ped /*|| !ped->GetSpecialAbility()*/)
return false;
#if GTA_REPLAY
// Do not process the player special ability when we are replaying a clip (url:bugstar:2036086).
if(CReplayMgr::IsReplayInControlOfWorld())
{
return false;
}
#endif // GTA_REPLAY
CControl *pControl = ped->GetControlFromPlayer();
if (!pControl)
return false;
u32 uActivationTime = NetworkInterface::IsInCopsAndCrooks() ? activationTime_CNC : activationTime;
u32 potentialActivationTime = fwTimer::GetSystemTimeInMilliseconds() + uActivationTime;
//! If time is rewound, reset timers.
if( (pendingActivationTime1 > potentialActivationTime) ||
(pendingActivationTime2 > potentialActivationTime) ||
(canTriggerAbilityTime > potentialActivationTime) ||
(timeForActivation > potentialActivationTime) )
{
pendingActivationTime1 = 0;
pendingActivationTime2 = 0;
canTriggerAbilityTime = 0;
timeForActivation = 0;
needsResetForActivation = false;
}
canTriggerAbility = false;
canUnTriggerAbility = false;
if(ped->GetIsDrivingVehicle())
{
CVehicle* veh = ped->GetMyVehicle();
if(veh && (veh->GetIsAircraft() || veh->GetIsHeli()))
{
const CWeaponInfo *pCurrentWeaponInfo = ped->GetEquippedWeaponInfo();
if(pCurrentWeaponInfo && pCurrentWeaponInfo->GetIsVehicleWeapon())
{
return false;
}
}
if( veh && veh->HasJump() && CVehicle::sm_bDoubleClickForJump )
{
return false;
}
}
u32 currentTime = fwTimer::GetSystemTimeInMilliseconds();
if(pControl->GetSpecialAbilityPC().IsDown())
{
canTriggerAbility = true;
}
else
{
if(pControl->GetPedSpecialAbility().IsDown() && !pControl->GetPedSpecialAbility().WasDown())
{
if(!pendingActivation && !needsResetForActivation)
{
timeForActivation = currentTime + uActivationTime;
}
pendingActivationTime1 = currentTime;
}
if (pControl->GetPedSpecialAbilitySecondary().IsDown() && !pControl->GetPedSpecialAbilitySecondary().WasDown())
{
if(!pendingActivation && !needsResetForActivation)
{
timeForActivation = currentTime + uActivationTime;
}
pendingActivationTime2 = currentTime;
}
if( (currentTime < timeForActivation) )
{
pendingActivation = true;
needsResetForActivation = true;
}
else
{
pendingActivation = false;
}
if( pendingActivation &&
(abs((s32)pendingActivationTime1 - (s32)pendingActivationTime2) < uActivationTime) )
{
canTriggerAbility = true;
canTriggerAbilityTime = currentTime;
}
}
if(pControl->GetSpecialAbilityPC().WasDown())
{
canUnTriggerAbility = true;
}
else if ( timeForActivation > 0 &&
(currentTime > timeForActivation ) &&
(!pControl->GetPedSpecialAbility().IsDown() && !pControl->GetPedSpecialAbilitySecondary().IsDown()))
{
canUnTriggerAbility = true;
needsResetForActivation = false;
}
//! Keep disabling controls once activated for a short time. This prevents other controls mapped to the same inputs from
//! triggering when re-activated.
static dev_u32 nDisableTime = 1000;
bool bHasActivated = currentTime < (canTriggerAbilityTime + nDisableTime);
if(bHasActivated)
{
activateSprintToggle = false;
}
// Keep the horn enabled for Trevor and Michael when they are in vehicle.
bool keepHornInputEnabled = false;
bool bHornInputEnabled = pControl->GetVehicleHorn().IsEnabled();
if(ped->GetPedModelInfo())
{
keepHornInputEnabled = keepHornInputEnabled || ((ped->GetPedModelInfo()->GetHashKey() == ATSTRINGHASH("Player_Zero", 0xD7114C9)) && ped->GetIsDrivingVehicle());
keepHornInputEnabled = keepHornInputEnabled || ((ped->GetPedModelInfo()->GetHashKey() == ATSTRINGHASH("Player_Two", 0x9B810FA2)) && ped->GetIsDrivingVehicle());
}
// B*1868999: Keep horn input enabled in MP (as we don't have a special ability).
if (NetworkInterface::IsGameInProgress())
{
keepHornInputEnabled = true;
}
if(pendingActivation || canTriggerAbility || bHasActivated )
{
// we disable input updates when unless the special ability has been activated. In this case it should act normal.
ioValue::DisableOptions options = ioValue::DEFAULT_DISABLE_OPTIONS;
options.SetFlags(ioValue::DisableOptions::F_DISABLE_UPDATE_WHEN_DISABLED, pendingActivation);
bool bSprintPressed = pControl->GetPedSprint().IsPressed();
bool bWasSprintDisabled = pControl->IsInputDisabled(INPUT_SPRINT);
pControl->SetInputExclusive(INPUT_SPECIAL_ABILITY, options);
pControl->SetInputExclusive(INPUT_SPECIAL_ABILITY_SECONDARY, options);
pControl->EnableFrontendInputs();
if(!bWasSprintDisabled && bSprintPressed)
{
specialAbilityDisabledSprint = true;
}
//! Don't disable sprint if it is going to cause us to stop. Also, ensure we toggle sprint if we don't activate the special ability.
if(specialAbilityDisabledSprint && pControl->IsToggleRunOn())
{
pControl->EnableInput(INPUT_SPRINT);
if(!bHasActivated)
{
activateSprintToggle = true;
}
}
if((keepHornInputEnabled || (!canTriggerAbility && !bHasActivated )) && bHornInputEnabled)
{
if(keepHornInputEnabled || ((currentTime - Max(pendingActivationTime1,pendingActivationTime2)) > audioActivationTime ))
{
pControl->EnableInput(INPUT_VEH_HORN);
}
}
blockingInputs = true;
}
else
{
//! As soon as we stop pressing sprint, don't suppress sprint toggling anymore. Note: we need to wait for this as otherwise we may
//! end up toggling sprint twice in quick succession.
if(!pControl->GetPedSprint().IsDown())
{
specialAbilityDisabledSprint = false;
}
if(blockingInputs)
{
blockingInputs = false;
}
}
return true;
}
bool CPlayerSpecialAbilityManager::CanTriggerSpecialPedAbility()
{
return canTriggerAbility;
}
bool CPlayerSpecialAbilityManager::CanUnTriggerSpecialPedAbility()
{
return canUnTriggerAbility;
}
bool CPlayerSpecialAbilityManager::IsSpecialAbilityBlockingInputs()
{
return blockingInputs;
}
bool CPlayerSpecialAbilityManager::HasSpecialAbilityDisabledSprint()
{
return specialAbilityDisabledSprint;
}
bool CPlayerSpecialAbilityManager::CanActivateSprintToggle()
{
return activateSprintToggle;
}
void CPlayerSpecialAbilityManager::Load(const char* fileName)
{
if(Verifyf(!instance.specialAbilities.GetCount(), "Special abilities have already been loaded"))
{
// Reset/Delete the existing data before loading
Reset();
// Load
fwPsoStoreLoader loader = fwPsoStoreLoader::MakeSimpleFileLoader<CPlayerSpecialAbilityManager>();
fwPsoStoreLoadInstance inst(&instance);
loader.GetFlagsRef().Set(fwPsoStoreLoader::RUN_POSTLOAD_CALLBACKS);
char fullName[RAGE_MAX_PATH];
fullName[0] = '\0';
ASSET.AddExtension(fullName, RAGE_MAX_PATH, fileName, META_FILE_EXT);
loader.Load(fullName, inst);
}
UpdateDlcMultipliers();
}
void CPlayerSpecialAbilityManager::Reset()
{
instance.specialAbilities.Reset();
pendingActivationTime1 = 0;
pendingActivationTime2 = 0;
canTriggerAbilityTime = 0;
timeForActivation = 0;
}
#if __BANK
static s32 addAmount = 0;
static float addPercentage = 0.f;
static bool ignoreActive = false;
void ActivateCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->Activate();
}
}
void DeactivateCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->Deactivate();
}
};
void AddAmountCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->AddToMeter(addAmount, ignoreActive);
}
}
void AddNormalizedCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->AddToMeterNormalized(addPercentage, ignoreActive);
}
}
void FillMeterCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->FillMeter(ignoreActive);
}
}
void ResetAbilityCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->Reset();
}
}
void ResetAbilityStatCallback()
{
CPed* playerPed = FindPlayerPed();
CPlayerSpecialAbility* ability = playerPed->GetSpecialAbility();
if (ability)
{
ability->ResetStat();
}
}
void CPlayerSpecialAbilityManager::LoadCallback()
{
CPlayerSpecialAbilityManager::Reset();
const CDataFileMgr::DataFile* pData = DATAFILEMGR.GetFirstFile(CDataFileMgr::PED_SPECIAL_ABILITIES_FILE);
CPlayerSpecialAbilityManager::Load(pData->m_filename);
}
void CPlayerSpecialAbilityManager::SaveCallback()
{
const CDataFileMgr::DataFile* pData = DATAFILEMGR.GetFirstFile(CDataFileMgr::PED_SPECIAL_ABILITIES_FILE);
weaponVerifyf(PARSER.SaveObject(pData->m_filename, "meta", &instance, parManager::XML), "Failed to save special abilities file:%s", pData->m_filename);
}
void CPlayerSpecialAbilityManager::LinearCurveSelect()
{
if (linearCurve)
{
sigmoidCurve = false;
halfSigmoidCurve = false;
instance.fadeCurveType = FCT_LINEAR;
}
}
void CPlayerSpecialAbilityManager::SigmoidCurveSelect()
{
if (sigmoidCurve)
{
linearCurve = false;
halfSigmoidCurve = false;
instance.fadeCurveType = FCT_SIGMOID;
}
}
void CPlayerSpecialAbilityManager::HalfSigmoidCurveSelect()
{
if (halfSigmoidCurve)
{
sigmoidCurve = false;
linearCurve= false;
instance.fadeCurveType = FCT_HALF_SIGMOID;
}
}
void CPlayerSpecialAbilityManager::UnlockedCapacityCallback()
{
#if __BANK
StatId capacityStat = StatsInterface::GetStatsModelHashId("SPECIAL_ABILITY_UNLOCKED");
if (StatsInterface::IsKeyValid(capacityStat))
{
StatsInterface::SetStatData(capacityStat, (s32)unlockedCapacityPercentage, STATUPDATEFLAG_ASSERTONLINESTATS);
if (CGameWorld::FindLocalPlayer() && CGameWorld::FindLocalPlayer()->GetSpecialAbility())
CGameWorld::FindLocalPlayer()->GetSpecialAbility()->unlockedCapacity = StatsInterface::GetIntStat(capacityStat) * 0.01f * CGameWorld::FindLocalPlayer()->GetSpecialAbility()->data->duration;
}
#endif // __BANK
}
void CPlayerSpecialAbilityManager::AddWidgets(bkBank& bank)
{
bank.PushGroup("Player Special Abilities");
bank.AddButton("Activate", datCallback(CFA(ActivateCallback)));
bank.AddButton("Deactivate", datCallback(CFA(DeactivateCallback)));
bank.AddText("Add amount", &addAmount);
bank.AddButton("Add amount", datCallback(CFA(AddAmountCallback)));
bank.AddText("Add normalized", &addPercentage);
bank.AddButton("Add normalized", datCallback(CFA(AddNormalizedCallback)));
bank.AddButton("Fill meter", datCallback(CFA(FillMeterCallback)));
bank.AddButton("Reset ability", datCallback(CFA(ResetAbilityCallback)));
bank.AddButton("Reset stat", datCallback(CFA(ResetAbilityStatCallback)));
bank.AddToggle("Ignore active", &ignoreActive);
bank.AddButton("Save", SaveCallback);
bank.AddButton("Load", LoadCallback);
bank.PushGroup("Special Abilities");
for (s32 i = 0; i < SAT_MAX; ++i)
{
bank.PushGroup(abilityNames[i]);
PARSER.AddWidgets(bank, &instance.specialAbilities[i]);
bank.PopGroup();
}
bank.PopGroup();
bank.AddToggle("Unlock Michael's ability", &abilityUnlocked[0]);
bank.AddToggle("Unlock Franklin's ability", &abilityUnlocked[1]);
bank.AddToggle("Unlock Trevor's ability", &abilityUnlocked[2]);
bank.AddToggle("Unlock TechTest's ability", &abilityUnlocked[3]);
bank.AddToggle("Unlock Arcae Player's ability", &abilityUnlocked[4]);
bank.AddSlider("Unlocked capacity %", &CPlayerSpecialAbilityManager::unlockedCapacityPercentage, 0, 100, 1, datCallback(CFA(UnlockedCapacityCallback)));
bank.AddSlider("Time to animate charge", &CPlayerSpecialAbility::timeToAddCharge, 0.f, 2.f, 0.01f);
bank.AddSlider("Fade in time", &instance.fadeInTime, 0.f, 5.f, 0.01f);
bank.AddSlider("Fade out time", &instance.fadeOutTime, 0.f, 5.f, 0.01f);
bank.AddSlider("Activation Time", &activationTime, 0, 5000, 1);
bank.AddSlider("Activation Time (CNC)", &activationTime_CNC, 0, 5000, 1);
bank.AddSlider("Audio activation Time", &audioActivationTime, 0, 5000, 1);
bank.AddToggle("Linear curve", &linearCurve, datCallback(CFA(LinearCurveSelect)));
bank.AddToggle("Half-sigmoid curve", &halfSigmoidCurve, datCallback(CFA(HalfSigmoidCurveSelect)));
bank.AddToggle("Sigmoid curve", &sigmoidCurve, datCallback(CFA(SigmoidCurveSelect)));
bank.AddSlider("Half-sigmoid constant", &instance.halfSigmoidConstant, 0.1f, 10.f, 0.1f);
bank.AddSlider("Sigmoid constant", &instance.sigmoidConstant, 0.1f, 5.f, 0.1f);
bank.AddSlider("Charge multiplier", &instance.chargeMultiplier, -5.f, 5.f, 0.1f);
bank.AddSlider("Special charge multiplier", &specialChargeMultiplier, 0.f, 10.f, 0.1f);
bank.PopGroup();
}
void CPlayerSpecialAbility::ResetStat()
{
Reset();
unlockedCapacity = (float)data->initialUnlockedCap;
// update SP stats
if (!NetworkInterface::IsGameInProgress())
{
if (StatsInterface::IsKeyValid(m_statUnlockedAbility))
{
s32 capacityStatValue = (s32)floor(100.f * (unlockedCapacity / data->duration));
if (StatsInterface::GetIntStat(m_statUnlockedAbility) <= capacityStatValue)
StatsInterface::SetStatData(m_statUnlockedAbility, capacityStatValue, STATUPDATEFLAG_ASSERTONLINESTATS); // the stat variable wants a percentage [0, 100] not absolute duration
else
unlockedCapacity = StatsInterface::GetIntStat(m_statUnlockedAbility) * 0.01f * data->duration;
}
}
}
#endif // __BANK
void CPlayerSpecialAbilityManager::InitTunables()
{
ms_CNCAbilityEnragedDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_ENRAGED_DURATION", 0xC71F4CAA), -1);
ms_CNCAbilityEnragedHealthReduction = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_ENRAGED_HEALTH_REDUCTION", 0xFAC3916D), -1.f);
ms_CNCAbilityEnragedStaminaReduction = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_ENRAGED_STAMINA_REDUCTION", 0xB6A0B8A9), -1.f);
ms_CNCAbilityGhostDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_GHOST_DURATION", 0xE533F8FA), -1);
ms_CNCAbilitySprintBoostDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_SPRINT_BOOST_DURATION", 0x323E955C), -1);
ms_CNCAbilitySprintBoostIncrease = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_SPRINT_BOOST_INCREASE", 0x180A6362), -1.f);
ms_CNCAbilityCallDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_CALL_DURATION", 0x58D72D98), -1);
ms_CNCAbilityNitroDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_NITRO_DURATION", 0x4D3A1F30), -1);
ms_CNCAbilityOilSlickDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_OIL_SLICK_DURATION", 0x483C8007), -1);
ms_CNCAbilityEMPVehicleDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_EMP_VEHICLE_DURATION", 0x711FD0B2), 5000);
ms_CNCAbilityOilSlickVehicleDuration = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_OIL_SLICK_VEHICLE_DURATION", 0x40A30677), 5000);
ms_CNCAbilityOilSlickFrictionReduction = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_OIL_SLICK_FRICTION_REDUCTION", 0xAD3FE512), -1.f);
ms_CNCAbilityBullbarsAttackRadius = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_BULLBARS_ATTACK_RADIUS", 0xC06EF8FA), -1.f);
ms_CNCAbilityBullbarsImpulseAmount = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_BULLBARS_IMPULSE_AMOUNT", 0x36D757DC), -1.f);
ms_CNCAbilitySpikeMineRadius = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_SPIKE_MINE_RADIUS", 0xECFA7C17), -1.f);
ms_CNCAbilitySpikeMineDurationArming = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_SPIKE_MINE_DURATION_ARMING", 0x89EBCEFC), -1);
ms_CNCAbilitySpikeMineDurationLifeTime = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ABILITY_SPIKE_MINE_DURATION_LIFETIME", 0x41DAD91D), -1);
}
const CSpecialAbilityData* CPlayerSpecialAbilityManager::GetSpecialAbilityData(SpecialAbilityType type)
{
if (type == SAT_NONE)
{
return NULL;
}
if((u32)type < instance.specialAbilities.GetCount())
{
return &instance.specialAbilities[(u32)type];
}
Assertf(false, "Special ability type enum (%d) is larger than array count (%d)! Check pedspecialabilities.meta file.", type, instance.specialAbilities.GetCount());
return NULL;
}
void CPlayerSpecialAbilityManager::PostLoad()
{
for (u32 i = 0; i < instance.specialAbilities.GetCount(); ++i)
{
instance.specialAbilities[i].type = (SpecialAbilityType)i;
}
}