990 lines
35 KiB
C++
990 lines
35 KiB
C++
// File header
|
|
#include "Weapons/Projectiles/ProjectileRocket.h"
|
|
|
|
// Game headers
|
|
#include "Audio/WeaponAudioEntity.h"
|
|
#include "Debug/DebugScene.h"
|
|
#include "Peds/Ped.h"
|
|
#include "Peds/PedIntelligence.h"
|
|
#include "Scene/World/GameWorld.h"
|
|
#include "Vehicles/Planes.h"
|
|
|
|
// Debug includes
|
|
#if __BANK
|
|
#include "Physics/PhysicsHelpers.h"
|
|
#include "Weapons/Projectiles/ProjectileManager.h"
|
|
#endif
|
|
|
|
// Macro to disable optimisations if set
|
|
WEAPON_OPTIMISATIONS()
|
|
|
|
#if __BANK
|
|
bool CProjectileRocket::sm_bTargetNetPlayer = false;
|
|
bool CProjectileRocket::sm_bTargetVehicle = false;
|
|
float CProjectileRocket::sm_fHomingTestDistance = 100.0f;
|
|
float CProjectileRocket::sm_fLifeTimeOverride = -1.0f;
|
|
float CProjectileRocket::sm_fSpeedOverride = 0.0f;
|
|
char CProjectileRocket::sm_ProjectileInfo[64] = "AMMO_PLANE_ROCKET";
|
|
char CProjectileRocket::sm_WeaponInfo[64] = "VEHICLE_WEAPON_PLANE_ROCKET";
|
|
#endif //__BANK
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// CProjectileRocket
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
CProjectileRocket::CProjectileRocket(const eEntityOwnedBy ownedBy, u32 uProjectileNameHash, u32 uWeaponFiredFromHash, CEntity* pOwner, float fDamage, eDamageType damageType, eWeaponEffectGroup effectGroup, const CEntity* pTarget, const CNetFXIdentifier* pNetIdentifier, bool bCreatedFromScript)
|
|
: CProjectile(ownedBy, uProjectileNameHash, uWeaponFiredFromHash, pOwner, fDamage, damageType, effectGroup, pNetIdentifier, bCreatedFromScript)
|
|
, m_vCachedTargetPos(Vector3::ZeroType)
|
|
, m_vLaunchDir(Vector3::ZeroType)
|
|
, m_pTarget(pTarget)
|
|
, m_fPitch(0.0f)
|
|
, m_fRoll(0.0f)
|
|
, m_fYaw(0.0f)
|
|
, m_fSpeed(0.0f)
|
|
, m_fTimeBeforeHoming(0.0f)
|
|
, m_fTimeBeforeHomingAngleBreak(0.0f)
|
|
, m_fLauncherSpeed(0.0f)
|
|
, m_fTimeSinceLaunch(0.0f)
|
|
, m_pWhistleSound(NULL)
|
|
, m_bIsAccurate(true)
|
|
, m_bLerpToLaunchDir(false)
|
|
, m_bApplyThrust(true)
|
|
, m_bOnFootHomingWeaponLockedOn(false)
|
|
, m_bWasHoming(false)
|
|
, m_bStopHoming(false)
|
|
, m_vCachedDirection(Vector3::ZeroType)
|
|
, m_bHasBeenRedirected(false)
|
|
, m_bTorpHasBeenOutOfWater(false)
|
|
{
|
|
if(pOwner && pOwner->GetIsPhysical())
|
|
{
|
|
SetVelocity(((CPhysical*)pOwner)->GetVelocity());
|
|
}
|
|
}
|
|
|
|
CProjectileRocket::~CProjectileRocket()
|
|
{
|
|
if(m_pWhistleSound)
|
|
{
|
|
m_pWhistleSound->StopAndForget();
|
|
}
|
|
}
|
|
|
|
bool CProjectileRocket::ProcessControl()
|
|
{
|
|
// Tick down the timer
|
|
m_fTimeBeforeHoming -= fwTimer::GetTimeStep();
|
|
|
|
if (m_fTimeBeforeHoming < 0.0f)
|
|
{
|
|
m_fTimeBeforeHomingAngleBreak -= fwTimer::GetTimeStep();
|
|
}
|
|
|
|
if(m_pTarget)
|
|
{
|
|
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
const Vector3 vTargetPos = GetIsAccurate() ? VEC3V_TO_VECTOR3(m_pTarget->GetTransform().GetPosition()) : GetCachedTargetPosition();
|
|
|
|
Vector3 vDistToTarget(vTargetPos);
|
|
vDistToTarget -= vThisPosition;
|
|
float fDistToTarget = vDistToTarget.Mag();
|
|
if(GetInfo() && (fDistToTarget < GetInfo()->GetProximityRadius()))
|
|
{
|
|
const CCollisionRecord * pColRecord = GetFrameCollisionHistory() ? GetFrameCollisionHistory()->GetMostSignificantCollisionRecord() : NULL;
|
|
|
|
// This'll probably crash if we pass in a zero collision normal? TriggerExplosion() seems safer.
|
|
Explode(vThisPosition, pColRecord ? pColRecord->m_MyCollisionNormal : Vector3(Vector3::ZeroType),
|
|
pColRecord ? pColRecord->m_pRegdCollisionEntity.Get() : NULL, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool bShouldExplode = false;
|
|
TUNE_GROUP_FLOAT(HOMING_ATTRACTOR, THRUSTER_PROXIMITY_RADIUS, 2.25f, 0.01f, 50.0f, 0.01f);
|
|
if (!m_bStopHoming && m_pTarget && m_pTarget->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pTargetVeh = static_cast<const CVehicle*>(m_pTarget.Get());
|
|
if (MI_JETPACK_THRUSTER.IsValid() && pTargetVeh->GetModelIndex() == MI_JETPACK_THRUSTER)
|
|
{
|
|
if (fDistToTarget <= THRUSTER_PROXIMITY_RADIUS)
|
|
{
|
|
bShouldExplode = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
TUNE_GROUP_FLOAT(HOMING_ATTRACTOR, REDIRECTION_PROXIMITY_RADIUS, 3.5f, 0.01f, 50.0f, 0.01f);
|
|
if (m_bHasBeenRedirected && GetInfo() && fDistToTarget < REDIRECTION_PROXIMITY_RADIUS)
|
|
{
|
|
bShouldExplode = true;
|
|
}
|
|
|
|
if(bShouldExplode)
|
|
{
|
|
TriggerExplosion();
|
|
|
|
if (m_pTarget->GetIsTypeObject())
|
|
{
|
|
const CObject* pObject = static_cast<const CObject*>(m_pTarget.Get());
|
|
const CProjectile* pProjectileTarget = pObject->GetAsProjectile();
|
|
if (pProjectileTarget)
|
|
{
|
|
CProjectile* pNonConstProjectileTarget = const_cast<CProjectile*>(pProjectileTarget);
|
|
|
|
if(NetworkInterface::IsGameInProgress() && pNonConstProjectileTarget->GetOwner() && pNonConstProjectileTarget->GetOwner()->GetIsTypePed())
|
|
{
|
|
CPed* ownerPed = SafeCast(CPed, pNonConstProjectileTarget->GetOwner());
|
|
if(ownerPed && ownerPed->IsPlayer() && ownerPed->GetNetworkObject())
|
|
{
|
|
CRemoveProjectileEntity::Trigger(ownerPed->GetNetworkObject()->GetObjectID(), pNonConstProjectileTarget->GetTaskSequenceId());
|
|
}
|
|
}
|
|
|
|
pNonConstProjectileTarget->Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the homing information
|
|
// UGHHH! Unfortunately changing this const to non-const leads to a tree of compile issues
|
|
if(m_fTimeBeforeHoming < 0.0f && !m_bLerpToLaunchDir)
|
|
{
|
|
CEntity* pNonConstTargetEntity = const_cast<CEntity*>(m_pTarget.Get());
|
|
if( pNonConstTargetEntity )
|
|
{
|
|
pNonConstTargetEntity->SetHomingProjectileDistance( fDistToTarget );
|
|
if(pNonConstTargetEntity->GetIsTypePed())
|
|
{
|
|
CPed* pPed = static_cast<CPed*>(pNonConstTargetEntity);
|
|
|
|
if(pPed)
|
|
{
|
|
CVehicle* pVehicle = pPed->GetVehiclePedInside();
|
|
|
|
if(pVehicle)
|
|
{
|
|
pVehicle->SetHomingProjectileDistance( fDistToTarget );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Base class
|
|
return CProjectile::ProcessControl();
|
|
}
|
|
|
|
ePhysicsResult CProjectileRocket::ProcessPhysics(float fTimeStep, bool bCanPostpone, s32 iTimeSlice)
|
|
{
|
|
if( !GetIsScriptControlled() && GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel() && m_iFlags.IsFlagSet(PF_Active) && !GetIsAttached())
|
|
{
|
|
const CAmmoRocketInfo* pInfo = GetInfo();
|
|
bool bForceMatrixUpdate = pInfo->GetShouldThrustUnderwater() && pInfo->GetUseGravityOutOfWater();
|
|
|
|
if(m_bLerpToLaunchDir || bForceMatrixUpdate)
|
|
{
|
|
// Construct a new matrix
|
|
Matrix34 mat(MAT34V_TO_MATRIX34(GetTransform().GetMatrix()));
|
|
|
|
Vector3 vProjectileDirection;
|
|
if(bForceMatrixUpdate)
|
|
{
|
|
vProjectileDirection = GetVelocity();
|
|
vProjectileDirection.NormalizeSafe();
|
|
mat.b = vProjectileDirection;
|
|
}
|
|
else
|
|
{
|
|
vProjectileDirection = m_vLaunchDir;
|
|
static dev_float LERP_SPEED = 0.5f;
|
|
Approach(mat.b.x, vProjectileDirection.x, LERP_SPEED, fTimeStep);
|
|
Approach(mat.b.y, vProjectileDirection.y, LERP_SPEED, fTimeStep);
|
|
Approach(mat.b.z, vProjectileDirection.z, LERP_SPEED, fTimeStep);
|
|
}
|
|
|
|
static dev_float ACHIEVED_LAUNCH_DIR = 0.01f;
|
|
if(mat.b.Dist2(vProjectileDirection) < square(ACHIEVED_LAUNCH_DIR))
|
|
{
|
|
m_bLerpToLaunchDir = false;
|
|
}
|
|
|
|
if (m_pOwner && m_pOwner->GetIsTypeVehicle() && m_networkIdentifier.IsClone())
|
|
{
|
|
const CVehicle* pVehicleOwner = static_cast<const CVehicle*>(m_pOwner.Get());
|
|
if (pVehicleOwner && pVehicleOwner->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_AIRCRAFT_STYLE_WEAPON_TARGETING))
|
|
{
|
|
m_bLerpToLaunchDir = false;
|
|
}
|
|
}
|
|
|
|
mat.b.Normalize();
|
|
|
|
if(abs(vProjectileDirection.z) > 0.99f)
|
|
{
|
|
mat.a = Vector3(1.0f, 0.0f, 0.0f);
|
|
mat.c.Cross(mat.a, mat.b);
|
|
mat.c.Normalize();
|
|
mat.a.Cross(mat.b, mat.c);
|
|
mat.a.Normalize();
|
|
}
|
|
else
|
|
{
|
|
mat.c = Vector3(0.0f, 0.0f, 1.0f);
|
|
mat.a.Cross(mat.b, mat.c);
|
|
mat.a.Normalize();
|
|
mat.c.Cross(mat.a, mat.b);
|
|
mat.c.Normalize();
|
|
}
|
|
|
|
SetMatrix(mat);
|
|
}
|
|
|
|
// If we should start homing, calculate the pitch, roll and yaw
|
|
if(m_fTimeBeforeHoming <= 0.0f && !m_bLerpToLaunchDir)
|
|
{
|
|
bool bHasTarget = false;
|
|
Vector3 vTargetPos;
|
|
|
|
if(m_pTarget && !m_bStopHoming)
|
|
{
|
|
bHasTarget = true;
|
|
|
|
//Validate target lock after we start homing
|
|
if (m_bWasHoming)
|
|
{
|
|
if (!IsTargetAngleValid())
|
|
{
|
|
bHasTarget = false;
|
|
}
|
|
}
|
|
|
|
if (bHasTarget)
|
|
{
|
|
//Determine target position
|
|
Vector3 vAccurateTargetPos = VEC3V_TO_VECTOR3(m_pTarget->GetTransform().GetPosition());
|
|
|
|
TUNE_GROUP_BOOL(ROCKET_TUNE, USE_LOCK_ON_POS, TRUE);
|
|
if (USE_LOCK_ON_POS && m_pTarget->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle *pTargetVehicle = static_cast<const CVehicle*>(m_pTarget.Get());
|
|
if (pTargetVehicle)
|
|
{
|
|
pTargetVehicle->GetLockOnTargetAimAtPos(vAccurateTargetPos);
|
|
}
|
|
}
|
|
|
|
vTargetPos = GetIsAccurate() ? vAccurateTargetPos : GetCachedTargetPosition();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if __DEV
|
|
static dev_bool DEBUG_TARGETING = false;
|
|
if(DEBUG_TARGETING)
|
|
{
|
|
if(CDebugScene::FocusEntities_Get(0))
|
|
{
|
|
vTargetPos = VEC3V_TO_VECTOR3(CDebugScene::FocusEntities_Get(0)->GetTransform().GetPosition());
|
|
}
|
|
else
|
|
{
|
|
vTargetPos = VEC3V_TO_VECTOR3(CGameWorld::FindLocalPlayer()->GetTransform().GetPosition());
|
|
}
|
|
|
|
bHasTarget = true;
|
|
}
|
|
#endif // __DEV
|
|
}
|
|
|
|
// If using an on-foot homing weapon, ensure we have triggered full lock-on before homing.
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(m_uWeaponFiredFromHash);
|
|
bool bUsingOnFootHomingWeapon = pWeaponInfo && pWeaponInfo->GetIsOnFootHoming();
|
|
|
|
if( !m_bStopHoming && ((bHasTarget && !bUsingOnFootHomingWeapon) || (bHasTarget && bUsingOnFootHomingWeapon && m_bOnFootHomingWeaponLockedOn)))
|
|
{
|
|
CalcHomingProjectileInputs(vTargetPos);
|
|
|
|
m_bWasHoming = true;
|
|
Vector3 vVelocity = GetVelocity();
|
|
vVelocity.Normalize();
|
|
m_vCachedDirection = vVelocity;
|
|
|
|
if(m_pTarget && m_pTarget->GetIsTypeVehicle())
|
|
{
|
|
CEntity* pNonConstTargetEntity = const_cast<CEntity*>(m_pTarget.Get());
|
|
static_cast<CVehicle*>(pNonConstTargetEntity)->SetLastTimeHomedAt(fwTimer::GetTimeInMilliseconds());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// B*2221044: Calculate appropriate pitch/roll/yaw parameters if no longer homing to maintain direction of movement.
|
|
if (m_bWasHoming)
|
|
{
|
|
if(NetworkInterface::IsGameInProgress() && !m_bStopHoming)
|
|
{
|
|
PhysicalPlayerIndex ownerIndex = m_networkIdentifier.GetPlayerOwner();
|
|
netPlayer* netOwner = netInterface::GetPhysicalPlayerFromIndex(ownerIndex);
|
|
if(netOwner && netOwner->IsLocal())
|
|
{
|
|
CBreakProjectileTargetLock::Trigger(m_networkIdentifier);
|
|
}
|
|
}
|
|
#if __DEV
|
|
if(NetworkInterface::IsGameInProgress() && !m_bStopHoming)
|
|
{
|
|
weaponDebugf1("CProjectileRocket::ProcessPhysics - Setting m_bStopHoming = true '%s' bHasTarget %s, m_pTarget %s. m_networkIdentifier GetFXId %d, GetPlayerOwner %d",
|
|
GetModelName(),
|
|
bHasTarget?"TRUE":"FALSE",
|
|
m_pTarget?m_pTarget->GetDebugName():"Null target",
|
|
m_networkIdentifier.GetFXId(),
|
|
m_networkIdentifier.GetPlayerOwner());
|
|
}
|
|
#endif // __DEV
|
|
// Don't allow us to re-home once we've broken off.
|
|
m_bStopHoming = true;
|
|
Vector3 vTargetPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) + (m_vCachedDirection * 1000.0f);
|
|
CalcHomingProjectileInputs(vTargetPos);
|
|
}
|
|
else
|
|
{
|
|
m_fPitch = m_fRoll = m_fYaw = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We don't want to fight against the strong physical drag applied when traveling through water so only fake the drag if we
|
|
// aren't getting wet. Need to check 'was in water' because the 'in water' flag is only correct on the second time-slice.
|
|
if(pInfo)
|
|
{
|
|
// Disable thrust if projectile is underwater and isn't flagged to thrust underwater
|
|
bool bInWater = GetIsInWater() || GetWasInWater();
|
|
if(m_bApplyThrust && (bInWater && !pInfo->GetShouldThrustUnderwater()))
|
|
{
|
|
m_bApplyThrust = false;
|
|
m_nPhysicalFlags.bIgnoresExplosions = true;
|
|
}
|
|
}
|
|
|
|
ApplyProjectileInputs(fTimeStep);
|
|
}
|
|
|
|
// Base class
|
|
return CProjectile::ProcessPhysics(fTimeStep, bCanPostpone, iTimeSlice);
|
|
}
|
|
|
|
void CProjectileRocket::Fire(const Vector3& vDirection, const f32 fLifeTime, f32 fLaunchSpeedOverride, bool bAllowDamping, bool bScriptControlled, bool bCommandFireSingleBullet, bool bIsDrop, const Vector3* pTargetVelocity, bool UNUSED_PARAM(bDisableTrail), bool bAllowToSetOwnerAsNoCollision)
|
|
{
|
|
// If using an on-foot homing weapon, ensure we have triggered full lock-on when we fire before enabling homing.
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(m_uWeaponFiredFromHash);
|
|
bool bUsingOnFootHomingWeapon = pWeaponInfo && pWeaponInfo->GetIsOnFootHoming();
|
|
if (bUsingOnFootHomingWeapon && m_pOwner && m_pOwner->GetIsTypePed())
|
|
{
|
|
const CPed* pPedOwner = static_cast<const CPed*>(m_pOwner.Get());
|
|
if (pPedOwner && pPedOwner->IsPlayer() && pPedOwner->GetPlayerInfo())
|
|
{
|
|
CPlayerPedTargeting &rTargeting = pPedOwner->GetPlayerInfo()->GetTargeting();
|
|
if (rTargeting.GetOnFootHomingLockOnState(pPedOwner) == CPlayerPedTargeting::OFH_LOCKED_ON)
|
|
{
|
|
m_bOnFootHomingWeaponLockedOn = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const CAmmoRocketInfo* pInfo = GetInfo();
|
|
if(pInfo)
|
|
{
|
|
// If we want to customize this on a per weapon basis we can put it in the weapon info
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, fTimeBeforeHoming, 0.15f, 0.0f, 1.0f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, fTimeBeforeHomingRuiner, 0.10f, 0.0f, 1.0f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, fTimeBeforeHomingAngleBreak, 0.0f, 0.0f, 10.0f, 0.01f);
|
|
|
|
if (pInfo->GetShouldUseHomingParamsFromInfo())
|
|
{
|
|
m_fTimeBeforeHoming = pInfo->GetTimeBeforeStartingHoming();
|
|
m_fTimeBeforeHomingAngleBreak = pInfo->GetTimeBeforeHomingAngleBreak();
|
|
}
|
|
else
|
|
{
|
|
m_fTimeBeforeHoming = fTimeBeforeHoming;
|
|
m_fTimeBeforeHomingAngleBreak = fTimeBeforeHomingAngleBreak;
|
|
|
|
if (m_pOwner && m_pOwner->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pVehicleOwner = static_cast<const CVehicle*>(m_pOwner.Get());
|
|
if (pVehicleOwner && MI_CAR_RUINER2.IsValid() && pVehicleOwner->GetModelIndex() == MI_CAR_RUINER2)
|
|
{
|
|
m_fTimeBeforeHoming = fTimeBeforeHomingRuiner;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Slow down projectile from homing launcher against helicopters so they have time to evade
|
|
if (m_bOnFootHomingWeaponLockedOn)
|
|
{
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, LaunchSpeedModOnFootVsHeli, 0.75f, 0.1f, 2.0f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, LaunchSpeedModOnFootVsSlowPlane, 0.85f, 0.1f, 2.0f, 0.01f);
|
|
|
|
if (m_pTarget && m_pTarget->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle *pTargetVehicle = static_cast<const CVehicle*>(m_pTarget.Get());
|
|
if (pTargetVehicle && pTargetVehicle->GetIsHeli())
|
|
{
|
|
fLaunchSpeedOverride = pInfo->GetLaunchSpeed() * LaunchSpeedModOnFootVsHeli;
|
|
}
|
|
else if (pTargetVehicle && pTargetVehicle->InheritsFromPlane())
|
|
{
|
|
const CPlane* pTargetPlane = static_cast<const CPlane*>(pTargetVehicle);
|
|
if (pTargetPlane && !pTargetPlane->IsLargePlane() && !pTargetPlane->IsFastPlane())
|
|
{
|
|
fLaunchSpeedOverride = pInfo->GetLaunchSpeed() * LaunchSpeedModOnFootVsSlowPlane;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Audio
|
|
DoFireAudio();
|
|
|
|
// Base class
|
|
CProjectile::Fire(vDirection, fLifeTime, fLaunchSpeedOverride, bAllowDamping, false, bCommandFireSingleBullet, bIsDrop, pTargetVelocity, false, bAllowToSetOwnerAsNoCollision);
|
|
}
|
|
|
|
// Cache off whether or not this projectile is script controlled
|
|
SetIsScriptControlled( bScriptControlled );
|
|
|
|
// Store dir
|
|
m_vLaunchDir = vDirection;
|
|
m_bLerpToLaunchDir = true;
|
|
}
|
|
|
|
void CProjectileRocket::SetIsRedirected(bool bRedirected)
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(m_uWeaponFiredFromHash);
|
|
bool bUsingOnFootHomingWeapon = pWeaponInfo && pWeaponInfo->GetIsOnFootHoming();
|
|
if (bUsingOnFootHomingWeapon)
|
|
m_bOnFootHomingWeaponLockedOn = true;
|
|
|
|
m_bHasBeenRedirected = bRedirected;
|
|
}
|
|
|
|
void CProjectileRocket::SetInitialLauncherSpeed( CEntity* pOwner )
|
|
{
|
|
if( pOwner && pOwner->GetIsPhysical() && !pOwner->GetIsTypePed())
|
|
{
|
|
Vector3 vOwnerVelocity = static_cast<CPhysical&>(*pOwner).GetLocalSpeed(VEC3V_TO_VECTOR3(GetTransform().GetPosition()), true);
|
|
float fDot = vOwnerVelocity.Dot(VEC3V_TO_VECTOR3(GetTransform().GetB()));
|
|
if(fDot > 0.0f)
|
|
m_fLauncherSpeed = fDot;
|
|
Assert( m_fLauncherSpeed < 1000.0f );
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
void CProjectileRocket::RenderDebug() const
|
|
{
|
|
// Render the inputs using a local flight model
|
|
CFlightModelHelper tempFlightModel;
|
|
tempFlightModel.DebugDrawControlInputs(this, 1.0f, m_fPitch, m_fRoll, m_fYaw);
|
|
|
|
// Base class
|
|
CProjectile::RenderDebug();
|
|
}
|
|
#endif // __BANK
|
|
|
|
void CProjectileRocket::DoFireAudio()
|
|
{
|
|
const CAmmoProjectileInfo* pAmmoInfo = GetInfo();
|
|
if(pAmmoInfo && pAmmoInfo->GetAudioHash() != 0)
|
|
{
|
|
g_WeaponAudioEntity.AttachProjectileLoop(pAmmoInfo->GetAudioHash(), this, &m_pSound, GetIsInWater());
|
|
}
|
|
else
|
|
{
|
|
g_WeaponAudioEntity.AttachProjectileLoop("ROCKET_FLIGHT", this, &m_pSound, GetIsInWater());
|
|
}
|
|
}
|
|
|
|
void CProjectileRocket::ProcessAudio()
|
|
{
|
|
if(m_pSound)
|
|
{
|
|
u32 uClientVar;
|
|
m_pSound->GetClientVariable(uClientVar);
|
|
|
|
// Check our water state and whether we are playing the appropriate sound
|
|
// If not, kill the current sound and start a new one
|
|
if((GetIsInWater() && uClientVar == 0) ||
|
|
(!GetIsInWater() && uClientVar == 1))
|
|
{
|
|
m_pSound->StopAndForget();
|
|
const CAmmoProjectileInfo* pAmmoInfo = GetInfo();
|
|
if(pAmmoInfo && pAmmoInfo->GetAudioHash() != 0)
|
|
{
|
|
g_WeaponAudioEntity.AttachProjectileLoop(pAmmoInfo->GetAudioHash(), this, &m_pSound, GetIsInWater());
|
|
}
|
|
else
|
|
{
|
|
g_WeaponAudioEntity.AttachProjectileLoop("ROCKET_FLIGHT", this, &m_pSound, GetIsInWater());
|
|
}
|
|
}
|
|
}
|
|
|
|
CProjectile::ProcessAudio();
|
|
}
|
|
|
|
bool CProjectileRocket::IsTargetAngleValid() const
|
|
{
|
|
if (!m_pTarget)
|
|
return false;
|
|
|
|
const CAmmoRocketInfo* pInfo = GetInfo();
|
|
|
|
Vector3 v3Diff = VEC3V_TO_VECTOR3(m_pTarget->GetTransform().GetPosition() - GetTransform().GetPosition());
|
|
float fDistanceToTarget = v3Diff.Mag();
|
|
if (Abs(fDistanceToTarget) > 0.f)
|
|
v3Diff.Scale(1.f / fDistanceToTarget);
|
|
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, DefaultHomingRocketBreakLockAngle, 0.2f, -1.f, 1.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, DefaultHomingRocketBreakLockAngleClose, 0.6f, -1.f, 1.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, DefaultHomingRocketBreakLockCloseDistance, 20.f, 0.f, 1000.f, 0.01f);
|
|
TUNE_GROUP_BOOL(ROCKET_TUNE, OverrideCombatBehaviourWithDefaultBreakLock, FALSE);
|
|
|
|
float fHomingRocketBreakLockAngle = DefaultHomingRocketBreakLockAngle;
|
|
float fHomingRocketBreakLockAngleClose = DefaultHomingRocketBreakLockAngleClose;
|
|
float fHomingRocketBreakLockCloseDistance = DefaultHomingRocketBreakLockCloseDistance;
|
|
|
|
if (pInfo && pInfo->GetShouldUseHomingParamsFromInfo())
|
|
{
|
|
fHomingRocketBreakLockAngle = pInfo->GetDefaultHomingRocketBreakLockAngle();
|
|
fHomingRocketBreakLockAngleClose = pInfo->GetDefaultHomingRocketBreakLockAngleClose();
|
|
fHomingRocketBreakLockCloseDistance = pInfo->GetDefaultHomingRocketBreakLockCloseDistance();
|
|
}
|
|
|
|
if (pInfo && !pInfo->GetShouldIgnoreOwnerCombatBehaviour() && !OverrideCombatBehaviourWithDefaultBreakLock && m_pOwner)
|
|
{
|
|
const CPed* pPedOwner = m_pOwner->GetIsTypePed() ? static_cast<const CPed*>(m_pOwner.Get()) : (m_pOwner->GetIsTypeVehicle() ? static_cast<const CVehicle*>(m_pOwner.Get())->GetDriver() : NULL);
|
|
if (pPedOwner)
|
|
{
|
|
fHomingRocketBreakLockAngle = pPedOwner->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat(kAttribFloatHomingRocketBreakLockAngle);
|
|
fHomingRocketBreakLockAngleClose = pPedOwner->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat(kAttribFloatHomingRocketBreakLockAngleClose);
|
|
fHomingRocketBreakLockCloseDistance = pPedOwner->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat(kAttribFloatHomingRocketBreakLockCloseDistance);
|
|
}
|
|
}
|
|
|
|
// We should only home while target is within a certain threshold
|
|
// this will allow online players to dodge homing rockets at the last moment
|
|
float fDotThreshold = fDistanceToTarget < fHomingRocketBreakLockCloseDistance ? fHomingRocketBreakLockAngleClose : fHomingRocketBreakLockAngle;
|
|
|
|
float fDir = DotProduct(v3Diff, VEC3V_TO_VECTOR3(GetTransform().GetB()));
|
|
|
|
// B*1783431: ...except when using spycar vertical rockets as we want to home in on targets all around us
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(m_uWeaponFiredFromHash);
|
|
bool bIgnoreThresholdCheck = pWeaponInfo ? pWeaponInfo->GetIgnoreHomingCloseThresholdCheck() : false;
|
|
|
|
//Ignore angle break if we're still in the grace period
|
|
if (m_fTimeBeforeHomingAngleBreak > 0.0f)
|
|
{
|
|
bIgnoreThresholdCheck = true;
|
|
}
|
|
|
|
TUNE_GROUP_BOOL(ROCKET_TUNE, IGNORE_DOT_THRESHOLD, FALSE);
|
|
if (fDir > fDotThreshold || bIgnoreThresholdCheck || m_bHasBeenRedirected || IGNORE_DOT_THRESHOLD)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CProjectileRocket::CalcHomingProjectileInputs(const Vector3& vTargetPos)
|
|
{
|
|
Vector3 vSeperation = vTargetPos - VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
|
Vector3 vSeperationLocal = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vSeperation)));
|
|
|
|
// Not sure if these control variables will need to change per rocket
|
|
// Seems like we could just find one value that works for now
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, DefaultHomingRocketTurnRateModifier, 1.f, 0.f, 100.f, 0.01f);
|
|
TUNE_GROUP_BOOL(ROCKET_TUNE, OverrideCombatBehaviourWithDefaultTurnRate, FALSE);
|
|
|
|
float fHomingRocketTurnRateModifier = DefaultHomingRocketTurnRateModifier;
|
|
|
|
if(!OverrideCombatBehaviourWithDefaultTurnRate && m_pOwner)
|
|
{
|
|
const CPed* pPedOwner = m_pOwner->GetIsTypePed() ? static_cast<const CPed*>(m_pOwner.Get()) : (m_pOwner->GetIsTypeVehicle() ? static_cast<const CVehicle*>(m_pOwner.Get())->GetDriver() : NULL);
|
|
if(pPedOwner)
|
|
{
|
|
fHomingRocketTurnRateModifier = pPedOwner->GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat(kAttribFloatHomingRocketTurnRateModifier);
|
|
}
|
|
}
|
|
|
|
float fPitchDiff = vSeperationLocal.AngleX(YAXIS); // Not really an angle but will do for this
|
|
static dev_float sfTargetWidth = ( DtoR * 2.0f);
|
|
float fTargetWidth = sfTargetWidth / vSeperation.Mag();
|
|
|
|
// DO NOT ADD ANY MORE HACKS HERE! Tunables have been pulled into metadata: see HomingRocketParams.
|
|
// This code is here for old vehicles that did not have it added to their metadata yet
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, PitchYawRollClampDefault, 1.f, 0.f, 10.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, PitchYawRollClampFromNonAircraft, 8.5f, 0.f, 50.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, PitchYawRollClampOnFootVsNonHeli, 1.f, 0.f, 10.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, PitchYawRollClampOnFootVsHeli, 3.f, 0.f, 10.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, PitchYawRollClampOnFootVsSlowPlanes, 3.f, 0.f, 10.f, 0.01f);
|
|
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, TurnRateModFromNonAircraft, 4.0f, 0.f, 10.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, TurnRateModOnFootVsHeli, 0.65f, 0.f, 5.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, TurnRateModOnFootVsSlowPlanes, 0.80f, 0.f, 5.f, 0.01f);
|
|
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, PitchYawRollClampFromOppressor, 2.5f, 0.f, 50.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, TurnRateModFromOppressor, 2.0f, 0.f, 10.f, 0.01f);
|
|
|
|
float fClampValue = PitchYawRollClampDefault; // Default is for aircraft homing rockets
|
|
|
|
const CVehicle *pTargetVehicle = m_pTarget && m_pTarget->GetIsTypeVehicle() ? static_cast<const CVehicle*>(m_pTarget.Get()) : nullptr;
|
|
|
|
const CAmmoRocketInfo* pRocketAmmoInfo = GetInfo();
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(m_uWeaponFiredFromHash);
|
|
if (pRocketAmmoInfo && pRocketAmmoInfo->GetShouldUseHomingParamsFromInfo())
|
|
{
|
|
fClampValue = pRocketAmmoInfo->GetPitchYawRollClamp();
|
|
fHomingRocketTurnRateModifier = pRocketAmmoInfo->GetTurnRateModifier();
|
|
}
|
|
else
|
|
{
|
|
// B*3177976: Needs to be tighter for Ruiner 2000 in order to hit other targets on the ground.
|
|
if (m_pOwner && m_pOwner->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pOwnerVehicle = static_cast<const CVehicle*>(m_pOwner.Get());
|
|
if (MI_BIKE_OPPRESSOR.IsValid() && pOwnerVehicle->GetModelIndex() == MI_BIKE_OPPRESSOR)
|
|
{
|
|
fClampValue = PitchYawRollClampFromOppressor;
|
|
fHomingRocketTurnRateModifier = TurnRateModFromOppressor;
|
|
}
|
|
else if (!pOwnerVehicle->GetIsAircraft())
|
|
{
|
|
fClampValue = PitchYawRollClampFromNonAircraft;
|
|
fHomingRocketTurnRateModifier = TurnRateModFromNonAircraft;
|
|
}
|
|
}
|
|
|
|
// B*2159386: On-Foot Homing Launcher: allow for larger pitch/yaw/roll parameters when target is a heli (as they can dodge more easily).
|
|
if (pWeaponInfo && pWeaponInfo->GetIsOnFootHoming())
|
|
{
|
|
fClampValue = PitchYawRollClampOnFootVsNonHeli;
|
|
|
|
if (pTargetVehicle)
|
|
{
|
|
if (pTargetVehicle->GetIsHeli())
|
|
{
|
|
fClampValue = PitchYawRollClampOnFootVsHeli;
|
|
fHomingRocketTurnRateModifier = TurnRateModOnFootVsHeli;
|
|
}
|
|
else if (pTargetVehicle->InheritsFromPlane())
|
|
{
|
|
const CPlane* pTargetPlane = static_cast<const CPlane*>(pTargetVehicle);
|
|
if (pTargetPlane && !pTargetPlane->IsLargePlane() && !pTargetPlane->IsFastPlane())
|
|
{
|
|
fClampValue = PitchYawRollClampOnFootVsSlowPlanes;
|
|
fHomingRocketTurnRateModifier = TurnRateModOnFootVsSlowPlanes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pWeaponInfo && pWeaponInfo->GetHash() == WEAPONTYPE_VEHICLE_WEAPON_OPPRESSOR2_MISSILE)
|
|
{
|
|
if (sm_fOppressor2MissilePitchYawRollClampOverride != 0.0f)
|
|
{
|
|
fClampValue = sm_fOppressor2MissilePitchYawRollClampOverride;
|
|
}
|
|
if (sm_fOppressor2MissileTurnRateModifierOverride != 0.0f)
|
|
{
|
|
fHomingRocketTurnRateModifier = sm_fOppressor2MissileTurnRateModifierOverride;
|
|
}
|
|
}
|
|
|
|
//TODO: Probably wants a separate set of tunings...
|
|
TUNE_GROUP_BOOL(HOMING_ATTRACTOR, APPLY_RUINER_SETTINGS_TO_REDIRECTED_ROCKETS, true);
|
|
if (m_bHasBeenRedirected && APPLY_RUINER_SETTINGS_TO_REDIRECTED_ROCKETS)
|
|
{
|
|
fClampValue = PitchYawRollClampFromNonAircraft;
|
|
fHomingRocketTurnRateModifier = TurnRateModFromNonAircraft;
|
|
}
|
|
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, ExtraPitchYawRollClampVsThruster, 1.5f, 0.f, 50.f, 0.01f);
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, ExtraTurnRateModVsThruster, 0.5f, 0.f, 10.f, 0.01f);
|
|
if (pTargetVehicle && MI_JETPACK_THRUSTER.IsValid() && pTargetVehicle->GetModelIndex() == MI_JETPACK_THRUSTER)
|
|
{
|
|
fClampValue += ExtraPitchYawRollClampVsThruster;
|
|
fHomingRocketTurnRateModifier += ExtraTurnRateModVsThruster;
|
|
}
|
|
|
|
const float fPitchControlMult = pRocketAmmoInfo->GetPitchChangeRate() * fHomingRocketTurnRateModifier;
|
|
const float fYawControlMult = pRocketAmmoInfo->GetRollChangeRate() * fHomingRocketTurnRateModifier;
|
|
const float fRollControlMult = pRocketAmmoInfo->GetYawChangeRate() * fHomingRocketTurnRateModifier;
|
|
|
|
m_fPitch = CalcHomingInput(fPitchDiff,fTargetWidth,fPitchControlMult);
|
|
m_fPitch = rage::Clamp(m_fPitch, -fClampValue, fClampValue);
|
|
|
|
float fYawDiff = fwAngle::LimitRadianAngle(vSeperationLocal.AngleZ(YAXIS));
|
|
|
|
m_fYaw = CalcHomingInput(fYawDiff,fTargetWidth,fYawControlMult);
|
|
m_fYaw = rage::Clamp(m_fYaw, -fClampValue, fClampValue);
|
|
|
|
// we want rocket to roll to this angle
|
|
float fDesiredRollAngleSin = -pRocketAmmoInfo->GetMaxRollAngleSin() * m_fYaw;
|
|
float fCurrentRollAngleSin = GetTransform().GetA().GetZf();
|
|
|
|
// roll input changes our angular velocity, so figure out what angular velocity we want
|
|
// to achieve our desired roll
|
|
|
|
m_fRoll = CalcHomingInput(fDesiredRollAngleSin-fCurrentRollAngleSin,0.0f,fRollControlMult);
|
|
m_fRoll = rage::Clamp(m_fRoll,-fClampValue,fClampValue);
|
|
}
|
|
|
|
void CProjectileRocket::ApplyProjectileInputs(float fTimestep)
|
|
{
|
|
// Roll, pitch and yaw the rocket
|
|
// Based on input member variables which are in range 0->1
|
|
|
|
// We override our matrix directly with new orientation instead of using physical torques
|
|
|
|
Matrix34 matNew = MAT34V_TO_MATRIX34(GetMatrix());
|
|
|
|
// Want to roll about previous frames axis, so that rotation axis won't change after first operation
|
|
const fwTransform& Transform = GetTransform();
|
|
matNew.Rotate(VEC3V_TO_VECTOR3(Transform.GetA()),-m_fPitch*fTimestep);
|
|
matNew.Rotate(VEC3V_TO_VECTOR3(Transform.GetB()),-m_fRoll*fTimestep);
|
|
matNew.Rotate(VEC3V_TO_VECTOR3(Transform.GetC()),-m_fYaw*fTimestep);
|
|
|
|
SetMatrix(matNew);
|
|
|
|
//Nothing uses gravity but it's here if we need it.
|
|
float gravitystrength = 0.0f;
|
|
|
|
Vector3 velNormalised = GetVelocity();
|
|
velNormalised.NormalizeSafe();
|
|
|
|
// Thrust component (N)
|
|
Vector3 thrustDir;
|
|
|
|
const CAmmoRocketInfo* pInfo = GetInfo();
|
|
if(pInfo)
|
|
{
|
|
if(pInfo->GetShouldThrustUnderwater() && pInfo->GetUseGravityOutOfWater())
|
|
{
|
|
bool bInWater = GetIsInWater() || GetWasInWater();
|
|
if(!bInWater)
|
|
{
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, fTorpGravity, -1.5f, -100.0f, 100.0f, 0.1f);
|
|
m_vLaunchDir.z += fTorpGravity*fTimestep;
|
|
m_vLaunchDir.NormalizeSafe();
|
|
m_bTorpHasBeenOutOfWater = true;
|
|
}
|
|
else if(bInWater && m_bTorpHasBeenOutOfWater)
|
|
{
|
|
TUNE_GROUP_FLOAT(ROCKET_TUNE, fTorpZWaterAlignSpeed, 1.0f, -100.0f, 100.0f, 0.1f);
|
|
if(Approach(m_vLaunchDir.z, 0.0f, fTorpZWaterAlignSpeed, fTimestep))
|
|
{
|
|
m_bTorpHasBeenOutOfWater = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_bLerpToLaunchDir)
|
|
thrustDir = m_vLaunchDir; // While lerping, use launch dir
|
|
else
|
|
{
|
|
thrustDir = matNew.b;
|
|
|
|
// B*1910285: Fire straight along launch direction in MP if not a homing rocket. Ensures local & clone rockets are fired along same trajectory.
|
|
if (NetworkInterface::IsGameInProgress() && (m_fPitch == 0.0f && m_fRoll == 0.0f && m_fYaw == 0.0f))
|
|
{
|
|
thrustDir = m_vLaunchDir;
|
|
}
|
|
}
|
|
float forwardDrag = GetInfo()->GetForwardDragCoeff();
|
|
float launchSpeed = m_fLauncherSpeed != 0.0f ? m_fLauncherSpeed : GetInfo()->GetLaunchSpeed();
|
|
|
|
// Handle separation of the rocket
|
|
if(GetInfo()->GetSeparationTime() != 0.0f)
|
|
{
|
|
if(m_fTimeSinceLaunch < GetInfo()->GetSeparationTime())
|
|
{
|
|
m_fTimeSinceLaunch += fTimestep;
|
|
|
|
// Separation thrust direction is a cheat but saves a member variable, we just need a small force.
|
|
thrustDir = -matNew.c;
|
|
launchSpeed = 100.0f;
|
|
gravitystrength = GRAVITY;
|
|
forwardDrag = 0.0f;
|
|
m_iFlags.SetFlag(PF_TrailInactive);
|
|
}
|
|
else if (m_fTimeSinceLaunch < GetInfo()->GetSeparationTime() + 10.0f )
|
|
{
|
|
m_fTimeSinceLaunch = m_fTimeSinceLaunch + 10.0f;
|
|
m_iFlags.ClearFlag(PF_TrailInactive);
|
|
}
|
|
|
|
}
|
|
|
|
if(m_bApplyThrust)
|
|
{
|
|
// In order to allow decent missile steering we need to cheat drag coefficients to protect against perpendicular travel - similar to slip on a tyre
|
|
// There is some physical basis behind this!
|
|
// Also reference vector for the dot is important as dot product is not linear in the LERP.
|
|
// Designed to deliberately scale up as we head facing towards being perpendicular to velocity direction.
|
|
float dp = NetworkInterface::IsGameInProgress() && !m_pOwner ? 0.0f : Abs(Dot(velNormalised, matNew.a));
|
|
float dragCoeff = (dp * GetInfo()->GetSideDragCoeff()) + (1.0f-dp) * forwardDrag;
|
|
|
|
Vector3 resultant_a = (thrustDir * launchSpeed);
|
|
// with drag subtracted (N)
|
|
resultant_a -= velNormalised * dragCoeff * GetVelocity().Mag2();
|
|
|
|
// Now as acceleration (F/m = m/s^2)
|
|
resultant_a /= GetMass(); // Because mass is the same we work in accel space instead of force
|
|
|
|
// Summed with gravity (m/s^2)
|
|
const Vector3 gravity = Vector3(0,0,-1.0f) * gravitystrength;
|
|
resultant_a += gravity;
|
|
|
|
// Applied by dt to velocity.
|
|
Vector3 vMoveSpeed = GetVelocity() + (resultant_a * fTimestep);
|
|
|
|
// Have to clamp to max speed or physics will assert
|
|
if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->GetArchetype())
|
|
{
|
|
float fMaxSpeed = GetCurrentPhysicsInst()->GetArchetype()->GetMaxSpeed();
|
|
//Limit the rate of change so we don't apply a crazy force to the ped.
|
|
if(vMoveSpeed.Mag2() > (fMaxSpeed * fMaxSpeed) )
|
|
{
|
|
const float fSpeedScale = vMoveSpeed.Mag() / GetCurrentPhysicsInst()->GetArchetype()->GetMaxSpeed();
|
|
if(fSpeedScale > 1.0f)
|
|
vMoveSpeed /= fSpeedScale;
|
|
}
|
|
}
|
|
|
|
SetVelocity(vMoveSpeed);
|
|
|
|
if( m_fPitch != 0.0f || m_fRoll != 0.0f || m_fYaw != 0.0f )
|
|
{
|
|
Vector3 vRotAngles(
|
|
-m_fPitch*fTimestep,// pitch delta
|
|
-m_fRoll*fTimestep, // roll delta
|
|
-m_fYaw*fTimestep // yaw delta
|
|
);
|
|
|
|
float fNewVelMag = vMoveSpeed.Mag();
|
|
if(fNewVelMag < 1.0f)
|
|
vRotAngles.z *= fNewVelMag;
|
|
|
|
// Rotate angular velocity into worldspace & apply
|
|
matNew.Transform3x3(vRotAngles);
|
|
SetAngVelocity(vRotAngles.x, vRotAngles.y, vRotAngles.z);
|
|
}
|
|
else
|
|
{
|
|
// We need to zero the angular velocity so if we ricochet it doesn't come boomeranging back at us
|
|
SetAngVelocity(VEC3_ZERO);
|
|
}
|
|
}
|
|
}
|
|
|
|
float CProjectileRocket::CalcHomingInput(float fValueDiff, float fTargetWidth, float fChangeRateMult)
|
|
{
|
|
float fSignDiff = Sign(fValueDiff);
|
|
if(fSignDiff*fValueDiff <= fTargetWidth)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
// Remove dead zone
|
|
fValueDiff = fValueDiff - fSignDiff*fTargetWidth;
|
|
|
|
float fDesiredChangeRate = fValueDiff * fChangeRateMult;
|
|
|
|
return fDesiredChangeRate;
|
|
}
|
|
|
|
#if __BANK
|
|
void CProjectileRocket::InitWidgets()
|
|
{
|
|
bkBank& bank = BANKMGR.CreateBank("Projectile Rockets");
|
|
bank.PushGroup("Spawn Rockets Behind Current Vehicle", false);
|
|
bank.AddText("Projectile Info:", sm_ProjectileInfo, 64, false);
|
|
bank.AddText("Weapon Info:", sm_WeaponInfo, 64, false);
|
|
bank.AddSlider("Distance behind to spawn:", &sm_fHomingTestDistance, 1.0f, 500.0f, 0.01f);
|
|
bank.AddSlider("Rocket lifetime override:", &sm_fLifeTimeOverride, -1.0f, 1000.0f, 0.01f);
|
|
bank.AddSlider("Rocket speed override:", &sm_fSpeedOverride, -1.0f, 1000.0f, 0.01f);
|
|
bank.AddToggle("Target remote player", &sm_bTargetNetPlayer);
|
|
bank.AddToggle("Target vehicle", &sm_bTargetVehicle);
|
|
bank.AddButton("Spawn rocket", SpawnRocketOnTail);
|
|
bank.PopGroup();
|
|
}
|
|
|
|
void CProjectileRocket::SpawnRocketOnTail()
|
|
{
|
|
CPed *pPedTarget = CGameWorld::FindLocalPlayer();
|
|
CPed *pPedTargetRemote = CGameWorld::FindLocalPlayer();
|
|
bool bScriptProjectile = false;
|
|
|
|
if(sm_bTargetNetPlayer && NetworkInterface::IsGameInProgress())
|
|
{
|
|
unsigned numRemotePhysicalPlayers = netInterface::GetNumRemotePhysicalPlayers();
|
|
const netPlayer * const *remotePhysicalPlayers = netInterface::GetRemotePhysicalPlayers();
|
|
|
|
float fBestDist = FLT_MAX;
|
|
|
|
for(unsigned index = 0; index < numRemotePhysicalPlayers; index++)
|
|
{
|
|
const CNetGamePlayer *remotePlayer = SafeCast(const CNetGamePlayer, remotePhysicalPlayers[index]);
|
|
if(remotePlayer && remotePlayer->GetPlayerPed())
|
|
{
|
|
Vector3 vDiffRemote = VEC3V_TO_VECTOR3(remotePlayer->GetPlayerPed()->GetTransform().GetPosition() - CGameWorld::FindLocalPlayer()->GetTransform().GetPosition());
|
|
float fDistRemote = vDiffRemote.Mag();
|
|
if(fDistRemote < fBestDist)
|
|
{
|
|
fBestDist = fDistRemote;
|
|
pPedTargetRemote = remotePlayer->GetPlayerPed();
|
|
pPedTarget = pPedTargetRemote;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Going to fake this as a script projectile for local cases (no owner) to prevent friendly checks / collision problems
|
|
bScriptProjectile = true;
|
|
}
|
|
|
|
if(pPedTarget->GetVehiclePedInside())
|
|
{
|
|
Matrix34 m = MAT34V_TO_MATRIX34(pPedTarget->GetVehiclePedInside()->GetTransform().GetMatrix());
|
|
m.d -= m.b * sm_fHomingTestDistance;
|
|
|
|
atHashString projectileInfo = atHashString(sm_ProjectileInfo);
|
|
atHashString weaponInfo = atHashString(sm_WeaponInfo);
|
|
|
|
const CWeaponInfo *pInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>( weaponInfo );
|
|
|
|
CProjectile *pProjectile = CProjectileManager::CreateProjectile(projectileInfo,
|
|
weaponInfo,
|
|
bScriptProjectile ? NULL : pPedTargetRemote,
|
|
m,
|
|
pInfo->GetDamage(),
|
|
pInfo->GetDamageType(),
|
|
pInfo->GetEffectGroup(),
|
|
sm_bTargetVehicle ? (CEntity*)pPedTarget->GetVehiclePedInside() : (CEntity*)pPedTarget,
|
|
NULL,
|
|
0,
|
|
bScriptProjectile);
|
|
|
|
if(pProjectile)
|
|
{
|
|
float fSpeed = pPedTarget->GetVehiclePedInside()->GetVelocity().Mag();
|
|
pProjectile->Fire(m.b, sm_fLifeTimeOverride, fSpeed + sm_fSpeedOverride);
|
|
}
|
|
}
|
|
}
|
|
#endif //__BANK
|