10292 lines
352 KiB
C++
10292 lines
352 KiB
C++
//
|
|
// filename: vehicleDamage.cpp
|
|
// description:
|
|
//
|
|
|
|
// C headers
|
|
|
|
#if __PS3
|
|
// PS3 headers
|
|
#include <altivec.h>
|
|
#include <ppu_altivec_internals.h>
|
|
#endif
|
|
// Rage headers
|
|
#include "grcore/config.h"
|
|
#include "system/floattoint.h"
|
|
#include "system/timemgr.h"
|
|
#include "system/xtl.h"
|
|
#include "system/param.h"
|
|
#include "fwdebug/picker.h"
|
|
#include "math/intrinsics.h"
|
|
|
|
// Microsoft headers
|
|
#if __D3D
|
|
#include "system/d3d9.h"
|
|
#endif
|
|
|
|
// Rage headers
|
|
#include "fragment/cacheManager.h"
|
|
#include "fragment/instance.h"
|
|
#include "fragment/type.h"
|
|
#include "grcore/debugdraw.h"
|
|
#include "grcore/device.h"
|
|
#include "grcore/image.h"
|
|
#include "grcore/texture.h"
|
|
#include "phbound/boundcomposite.h"
|
|
#include "phbound/boundgeom.h"
|
|
#include "phBound/support.h"
|
|
#include "system/param.h"
|
|
#include "fwdebug/picker.h"
|
|
#include "creature/componentskeleton.h" // CVehicleDamage::ReattachAllPanels
|
|
#include "creature/creature.h" // CVehicleDamage::ReattachAllPanels
|
|
|
|
// Game headers
|
|
#include "audio/vehicleaudioentity.h"
|
|
#include "audio/policescanner.h"
|
|
#include "audio/scannermanager.h"
|
|
#include "audio/scriptaudioentity.h"
|
|
#include "audio/speechaudioentity.h"
|
|
#include "camera/CamInterface.h"
|
|
#include "camera/Gameplay/GameplayDirector.h"
|
|
#include "camera/cinematic/CinematicDirector.h"
|
|
#include "Control/replay/Replay.h"
|
|
#include "control/replay/ReplayBufferMarker.h"
|
|
#include "control/replay/ReplayInterfaceVeh.h"
|
|
#include "control/replay/vehicle/vehiclepacket.h"
|
|
#include "debug/debugglobals.h"
|
|
#include "event/events.h"
|
|
#include "event/EventDamage.h"
|
|
#include "event/EventShocking.h"
|
|
#include "event/ShockingEvents.h"
|
|
#include "frontend/pausemenu.h"
|
|
#include "network/Objects/Entities/NetObjPlayer.h"
|
|
#include "Stats/StatsMgr.h"
|
|
#include "peds/popcycle.h"
|
|
#include "physics/gtaInst.h"
|
|
#include "physics/physics.h"
|
|
#include "renderer/water.h"
|
|
#include "shaders/CustomShaderEffectVehicle.h"
|
|
#include "vehicleAi/VehicleIntelligence.h"
|
|
#include "vehicles/AmphibiousAutomobile.h"
|
|
#include "vehicles/Automobile.h"
|
|
#include "vehicles/Bike.h"
|
|
#include "vehicles/Boat.h"
|
|
#include "vehicles/Heli.h"
|
|
#include "vehicles/transmission.h"
|
|
#include "vehicles/Planes.h"
|
|
#include "vehicles/vehicle.h"
|
|
#include "vehicles/vehicleDamage.h" // Dw - migrated form top of file
|
|
#include "vehicles/vehicleDamage_parser.h"
|
|
#include "vehicles/VehicleDefines.h"
|
|
#include "vehicles/VehicleFactory.h"
|
|
#include "vehicles/vehiclegadgets.h"
|
|
#include "vehicles/wheel.h"
|
|
#include "vehicles/Trailer.h"
|
|
#include "vehicles/Submarine.h"
|
|
#include "vehicles/Metadata/VehicleExplosionInfo.h"
|
|
#include "vfx/Systems/VfxVehicle.h"
|
|
#include "vfx/vehicleglass/VehicleGlassManager.h"
|
|
#include "vfx/decals/DecalManager.h"
|
|
#include "shaders/CustomShaderEffectBase.h"
|
|
#include "system/controlMgr.h"
|
|
#include "system/TamperActions.h"
|
|
#include "task/Movement/TaskParachute.h"
|
|
#include "peds/ped.h"
|
|
#include "peds/PedIntelligence.h"
|
|
#include "peds/Ped.h"
|
|
#include "Weapons/Info/WeaponInfoManager.h"
|
|
#include "scene/world/GameWorld.h"
|
|
#include "game/ModelIndices.h"
|
|
#include "Network/NetworkInterface.h"
|
|
#include "network/events/NetworkEventTypes.h"
|
|
#include "network/general/NetworkUtil.h"
|
|
#include "modelinfo/BaseModelInfo.h"
|
|
|
|
#if __BANK
|
|
#include "renderer/rendertargets.h"
|
|
#include "../game/script/commands_object.h"
|
|
#include "grcore/quads.h"
|
|
#endif
|
|
|
|
#if __D3D11
|
|
#include "grcore/texturedefault.h"
|
|
#endif
|
|
|
|
AI_VEHICLE_OPTIMISATIONS()
|
|
VEHICLE_OPTIMISATIONS()
|
|
NETWORK_OPTIMISATIONS()
|
|
WEAPON_OPTIMISATIONS()
|
|
|
|
#define NO_VEH_DAMAGE_PARAM (!__FINAL)
|
|
|
|
#if NO_VEH_DAMAGE_PARAM
|
|
PARAM(novehicledamage, "No vehicle deformation.");
|
|
static bool bTestNoVehicleDeformation = FALSE;
|
|
#endif
|
|
|
|
// --- Defines ------------------------------------------------------------------
|
|
#define VEHICLE_DAMAGE_ON 1
|
|
#include "modelinfo/vehiclemodelInfo.h"
|
|
|
|
|
|
#if VEHICLE_DEFORMATION_SOFT_CAR
|
|
static dev_float damageMultiply = 5.0f;
|
|
#endif // VEHICLE_DEFORMATION_SOFT_CAR
|
|
|
|
dev_float sfMinimumMassForCrushingVehiclesNetworkGame = 34000.0f;
|
|
|
|
#if VEHICLE_DEFORMATION_TIMING
|
|
#include "system/timer.h"
|
|
static float DTT_maxTime = 0.0f;
|
|
static float DTT_minTime = 3000.0f;
|
|
static float DTT_accumTime = 0.0f;
|
|
|
|
static int DTT_maxImpact = 0;
|
|
static int DTT_minImpact = 3000;
|
|
static int DTT_accumImpact = 0;
|
|
|
|
static int DTT_maxOverload = 0;
|
|
static int DTT_minOverload = 3000;
|
|
static int DTT_accumOverload = 0;
|
|
|
|
static int DTT_timerHit = 0;
|
|
#endif // VEHICLE_DEFORMATION_TIMING
|
|
|
|
PF_PAGE(VehicleDamageImpulsesPage, "Damage Impulses");
|
|
PF_GROUP(VehicleDamageImpulses);
|
|
PF_LINK(VehicleDamageImpulsesPage, VehicleDamageImpulses);
|
|
|
|
PF_VALUE_FLOAT(DamageForce, VehicleDamageImpulses);
|
|
PF_VALUE_FLOAT(DamageImpulse, VehicleDamageImpulses);
|
|
PF_VALUE_FLOAT(DamageVelocity, VehicleDamageImpulses);
|
|
|
|
#define ENABLE_APPLY_DAMAGE_PF 0
|
|
#if ENABLE_APPLY_DAMAGE_PF
|
|
PF_PAGE(VehicleDamageTimersPage, "Vehicle Damage Timers");
|
|
PF_GROUP(VehicleDamageTimers);
|
|
PF_LINK(VehicleDamageTimersPage, VehicleDamageTimers);
|
|
PF_TIMER(ApplyDamage,VehicleDamageTimers);
|
|
PF_TIMER(ApplyDeformations, VehicleDamageTimers);
|
|
PF_TIMER(RecomputeOctantMap,VehicleDamageTimers);
|
|
PF_TIMER(AddToPixel,VehicleDamageTimers);
|
|
|
|
PF_PAGE(VehicleDamageCountersPage, "Vehicle Damage Counters");
|
|
PF_GROUP(VehicleDamageCounters);
|
|
PF_LINK(VehicleDamageCountersPage, VehicleDamageCounters);
|
|
PF_COUNTER(ApplyDamage,VehicleDamageCounters);
|
|
PF_COUNTER(ApplyDeformations, VehicleDamageCounters);
|
|
PF_COUNTER(AddToPixel,VehicleDamageCounters);
|
|
PF_COUNTER(RecomputeOctantMap,VehicleDamageCounters);
|
|
#endif // ENABLE_APPLY_DAMAGE_PF
|
|
|
|
IMPLEMENT_VEHICLE_DAMAGE_TUNABLES(CVehicleDamage, 0xD606BA84)
|
|
CVehicleDamage::Tunables CVehicleDamage::sm_Tunables;
|
|
// --- Constants ----------------------------------------------------------------
|
|
|
|
const float MIN_DAMAGE_RANGE = 4.975f; // tailgater = 0, min damage size multiplier
|
|
const float MAX_DAMAGE_RANGE = 68.8f - MIN_DAMAGE_RANGE; // jet = 1.0, max damage size multiplier
|
|
|
|
// --- Structure/Class Definitions ----------------------------------------------
|
|
|
|
// --- Globals ------------------------------------------------------------------
|
|
static dev_float GlassDamageThreshold = 0.5f;
|
|
static dev_float DeformationRadiusThreshold = 0.01f;
|
|
|
|
// Rumble variables
|
|
dev_float DIST_TO_CONSIDER_MOV = 0.5f;
|
|
dev_float SPEED_TO_CONSIDER_MOV = 0.3f;
|
|
|
|
dev_float sfRumbleIntensModifier = 0.005f;
|
|
dev_float sfRumbleDurModifier = 5.0f;
|
|
dev_float sfMinRumbleDamThreshold = 4.0f;
|
|
dev_float sfRumbleDamageWhenHittingRumbleOnLightColWithObject = 3.5f;
|
|
|
|
dev_float sfMaxRumbleDur = 200;
|
|
dev_float sfMinRumbleDur = 80;
|
|
|
|
mthVecRand g_vehDeformationVecRandom;
|
|
|
|
// --- Static Globals -----------------------------------------------------------
|
|
#define NUM_GLASS_BONES (25)
|
|
#define NUM_LIGHT_BONES TAXI_IDX
|
|
static const eHierarchyId aGlassBones[NUM_GLASS_BONES] =
|
|
{
|
|
VEH_HEADLIGHT_L, // 00
|
|
VEH_HEADLIGHT_R, // 01
|
|
VEH_TAILLIGHT_L, // 02
|
|
VEH_TAILLIGHT_R, // 03
|
|
VEH_INDICATOR_LF, // 04
|
|
VEH_INDICATOR_RF, // 05
|
|
VEH_INDICATOR_LR, // 06
|
|
VEH_INDICATOR_RR, // 07
|
|
VEH_BRAKELIGHT_L, // 08
|
|
VEH_BRAKELIGHT_R, // 09
|
|
VEH_BRAKELIGHT_M, // 10
|
|
VEH_REVERSINGLIGHT_L, // 11
|
|
VEH_REVERSINGLIGHT_R, // 12
|
|
VEH_NEON_L, // 13
|
|
VEH_NEON_R, // 14
|
|
VEH_NEON_F, // 15
|
|
VEH_NEON_B, // 16
|
|
VEH_EXTRALIGHT_1, // 17
|
|
VEH_EXTRALIGHT_2, // 18
|
|
VEH_EXTRALIGHT_3, // 19
|
|
VEH_EXTRALIGHT_4, // 20
|
|
|
|
VEH_EXTRA_5, // 21
|
|
VEH_EXTRA_6, // 22
|
|
VEH_EXTRA_9 // 23
|
|
};
|
|
|
|
float CVehicleDeformation::networkDamageModifiers[NUM_NETWORK_DAMAGE_DIRECTIONS][MAX_NET_DAMAGE_MODIFIERS] =
|
|
{
|
|
{// FRONT_LEFT_DAMAGE
|
|
0.05f,
|
|
0.5f,
|
|
1.0f,
|
|
1.5f
|
|
},
|
|
{// FRONT_RIGHT_DAMAGE
|
|
0.05f,
|
|
0.5f,
|
|
1.0f,
|
|
1.5f
|
|
},
|
|
{// MIDDLE_LEFT_DAMAGE
|
|
0.05f,
|
|
0.5f,
|
|
1.0f,
|
|
1.5f
|
|
},
|
|
{// MIDDLE_RIGHT_DAMAGE
|
|
0.05f,
|
|
0.5f,
|
|
1.0f,
|
|
1.5f
|
|
},
|
|
{// REAR_LEFT_DAMAGE
|
|
0.05f,
|
|
0.1f,
|
|
0.3f,
|
|
1.0f
|
|
},
|
|
{// REAR_RIGHT_DAMAGE
|
|
0.05f,
|
|
0.1f,
|
|
0.3f,
|
|
1.0f
|
|
},
|
|
};
|
|
|
|
// --- Static class members -----------------------------------------------------
|
|
|
|
#if USE_EDGE
|
|
#define DAMAGE_ZERO float(0.0f) // EDGE: RGB is in range <-128; 127>
|
|
#elif GPU_DAMAGE_WRITE_ENABLED
|
|
#define DAMAGE_ZERO 0
|
|
#else
|
|
#define DAMAGE_ZERO float(128.0f + YPACK*128.0f + ZPACK*128.0f) // RGB is in range <0; 255>
|
|
#endif
|
|
|
|
atArray<VehTexCacheEntry> CVehicleDeformation::ms_TextureCache;
|
|
bool CVehicleDeformation::ms_bUpdateDamageFromPhysicsThread = true;
|
|
|
|
#if __BANK
|
|
bool CVehicleDeformation::ms_bShowTextureAndRenderTarget = false;
|
|
float CVehicleDeformation::ms_fDisplayTextureScale = 3.0f;
|
|
bool CVehicleDeformation::ms_bDisplayTexCacheStats = false;
|
|
bool CVehicleDeformation::ms_bDisplayDamageMap = false;
|
|
bool CVehicleDeformation::ms_bDisplayDamageMult = false;
|
|
bool CVehicleDeformation::ms_bForceDamageMapScale = false;
|
|
bool CVehicleDeformation::ms_bFullDamage = false;
|
|
float CVehicleDeformation::ms_fForcedDamageMapScale = 1.0f;
|
|
float CVehicleDeformation::ms_fWeaponImpactDamageScale = 0.0f;
|
|
|
|
bank_float CVehicleDeformation::m_ImpactPositionLocal_X = 0.0f;
|
|
bank_float CVehicleDeformation::m_ImpactPositionLocal_Y = 0.0f;
|
|
bank_float CVehicleDeformation::m_ImpactPositionLocal_Z = 0.0f;
|
|
bank_float CVehicleDeformation::m_Impulse_X = 0.0f;
|
|
bank_float CVehicleDeformation::m_Impulse_Y = 0.0f;
|
|
bank_float CVehicleDeformation::m_Impulse_Z = 0.0f;
|
|
bank_float CVehicleDeformation::m_OffsetDamageToMods_X = 0.0f;
|
|
bank_float CVehicleDeformation::m_OffsetDamageToMods_Y = 0.0f;
|
|
bank_float CVehicleDeformation::m_OffsetDamageToMods_Z = 0.0f;
|
|
bank_float CVehicleDeformation::m_DamageTextureOffset_X = 0.0f;
|
|
bank_float CVehicleDeformation::m_DamageTextureOffset_Y = 0.0f;
|
|
bank_float CVehicleDeformation::m_DamageTextureOffset_Z = 0.0f;
|
|
bank_float CVehicleDeformation::ms_fDamageAmount = 20.0f;
|
|
bank_float CVehicleDeformation::ms_fDamagePercent = 10.0f;
|
|
bank_float CVehicleDeformation::ms_fContainerDropHeight = 10.0f;
|
|
bool CVehicleDeformation::ms_bAutoFix = false;
|
|
bool CVehicleDeformation::ms_bAutoSaveDamageTexture = false;
|
|
bool CVehicleDeformation::ms_bUpdateBoundsEnabled = true;
|
|
bool CVehicleDeformation::ms_bUpdateBonesEnabled = true;
|
|
CVehicle *CVehicleDamage::ms_SourceVehicle = NULL;
|
|
CVehicle *CVehicleDamage::ms_DestinationVehicle = NULL;
|
|
|
|
u32 CVehicleDamage::g_DamageDebug[CVehicleDeformation::NUM_NETWORK_DAMAGE_DIRECTIONS] = {0,0,0,0,0,0};
|
|
float CVehicleDamage::g_DamageValue[CVehicleDeformation::NUM_NETWORK_DAMAGE_DIRECTIONS] = {-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f};
|
|
|
|
bool CVehicleDamage::ms_bDisplayDamageVectors = false;
|
|
bool CVehicleDeformation::ms_bHidePropellers = false;
|
|
float CVehicleDeformation::ms_fForcedPropellerSpeed = 0.0f;
|
|
|
|
ScalarV CVehicleDeformation::ms_fVehDefColVarMultMin = VEHICLE_DEFORMATION_COLOR_VAR_MULT_MIN;
|
|
ScalarV CVehicleDeformation::ms_fVehDefColVarMultMax = VEHICLE_DEFORMATION_COLOR_VAR_MULT_MAX;
|
|
ScalarV CVehicleDeformation::ms_fVehDefColVarAddMin = VEHICLE_DEFORMATION_COLOR_VAR_ADD_MIN;
|
|
ScalarV CVehicleDeformation::ms_fVehDefColVarAddMax = VEHICLE_DEFORMATION_COLOR_VAR_ADD_MAX;
|
|
|
|
void CVehicleDamage::SetSourceVehicleCB()
|
|
{
|
|
ms_SourceVehicle = CVehicleDebug::GetFocusVehicle();
|
|
}
|
|
|
|
void CVehicleDamage::SetDestinationVehicleCB()
|
|
{
|
|
ms_DestinationVehicle = CVehicleDebug::GetFocusVehicle();
|
|
}
|
|
|
|
void CVehicleDamage::DisplayDamageImpulse(const Vector3& impulseDirection, const Vector3& impulsePosition, CVehicle* vehicle, float damageRadius, bool inLocalCoordinates, int framesToLive)
|
|
{
|
|
if (vehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 vecDamageEndWorld = inLocalCoordinates ? VEC3V_TO_VECTOR3(vehicle->GetTransform().Transform(VECTOR3_TO_VEC3V(impulsePosition))) : impulsePosition;
|
|
|
|
Vector3 normalizedImpulse = impulseDirection;
|
|
normalizedImpulse.NormalizeSafe();
|
|
|
|
Vector3 vecDamageStartWorld = vecDamageEndWorld - normalizedImpulse;
|
|
|
|
grcDebugDraw::Sphere(vecDamageEndWorld, damageRadius * 0.1f, Color_red, false, framesToLive);
|
|
grcDebugDraw::Sphere(vecDamageStartWorld, 0.1f, Color_green, false, framesToLive);
|
|
grcDebugDraw::Line(vecDamageStartWorld, vecDamageEndWorld, Color_green, Color_red, framesToLive);
|
|
}
|
|
|
|
void CVehicleDamage::DamageCurrentCar()
|
|
{
|
|
Vector3 impulseLocal = Vector3(CVehicleDeformation::m_Impulse_X, CVehicleDeformation::m_Impulse_Y, CVehicleDeformation::m_Impulse_Z);
|
|
Vector3 localPosition = Vector3(CVehicleDeformation::m_ImpactPositionLocal_X, CVehicleDeformation::m_ImpactPositionLocal_Y, CVehicleDeformation::m_ImpactPositionLocal_Z);
|
|
DamageCurrentCar(impulseLocal, localPosition, CVehicleDeformation::ms_fDamageAmount, CVehicleDeformation::ms_bAutoFix);
|
|
}
|
|
|
|
void CVehicleDamage::DamageCurrentCar(const Vector3& impulseLocal, const Vector3& damagePosLocal, float damage, bool autoFix)
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if ((ms_SourceVehicle == NULL) || (damage < 0.01f))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (autoFix)
|
|
{
|
|
ms_SourceVehicle->Fix();
|
|
}
|
|
|
|
const float fMass = ms_SourceVehicle->GetMass();
|
|
const float fMassIndependenceFactor = (fMass / ms_SourceVehicle->pHandling->m_fDeformationDamageMult);
|
|
const float fDeformationMag = damage * fMassIndependenceFactor;
|
|
|
|
DamageVehicleByDriver(impulseLocal, damagePosLocal, fDeformationMag, DAMAGE_TYPE_UNKNOWN, ms_SourceVehicle, CVehicleDeformation::ms_bFullDamage);
|
|
|
|
ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->ApplyDeformations(true);
|
|
}
|
|
|
|
void CVehicleDamage::DamageCurrentCarByPercentage()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (ms_SourceVehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 impulseLocal = Vector3(CVehicleDeformation::m_Impulse_X, CVehicleDeformation::m_Impulse_Y, CVehicleDeformation::m_Impulse_Z);
|
|
Vector3 localPosition = Vector3(CVehicleDeformation::m_ImpactPositionLocal_X, CVehicleDeformation::m_ImpactPositionLocal_Y, CVehicleDeformation::m_ImpactPositionLocal_Z);
|
|
|
|
float totalVehicleHealth = ms_SourceVehicle->GetMaxHealth();
|
|
CVehicleDeformation::ms_fDamageAmount = totalVehicleHealth * CVehicleDeformation::ms_fDamagePercent / 100.0f;
|
|
|
|
DamageCurrentCar(impulseLocal, localPosition, CVehicleDeformation::ms_fDamageAmount, CVehicleDeformation::ms_bAutoFix);
|
|
}
|
|
|
|
void CVehicleDamage::DamageDriverSideRoof()
|
|
{
|
|
CVehicleDeformation::m_Impulse_X = 0.0f;
|
|
CVehicleDeformation::m_Impulse_Y = 0.0f;
|
|
CVehicleDeformation::m_Impulse_Z = -1.0f;
|
|
|
|
CVehicleDeformation::m_ImpactPositionLocal_X = -0.44f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Y = -0.1f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Z = 0.5f;
|
|
|
|
DamageCurrentCar();
|
|
}
|
|
|
|
static bool randomizeCollisions = true;
|
|
|
|
void CVehicleDamage::HeadOnCollision()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (ms_SourceVehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HeadOnCollision(ms_SourceVehicle, CVehicleDeformation::ms_fDamageAmount, randomizeCollisions);
|
|
}
|
|
|
|
void CVehicleDamage::RearEndCollision()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (ms_SourceVehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RearEndCollision(ms_SourceVehicle, CVehicleDeformation::ms_fDamageAmount, randomizeCollisions);
|
|
}
|
|
|
|
#if VEHICLE_DEFORMATION_PROPORTIONAL
|
|
void CVehicleDamage::UpdateDamageMultiplier()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (ms_SourceVehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void *basePtr = ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->HasDamageTexture() ? ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead|grcsWrite) : NULL;
|
|
|
|
if (basePtr)
|
|
{
|
|
ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->CalculateDamageMultiplier();
|
|
ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->HandleDamageAdded(basePtr, true, true, true, true, true, true);
|
|
ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CVehicleDamage::TailRotorCollision()
|
|
{
|
|
CVehicleDeformation::m_Impulse_X = 1.0f;
|
|
CVehicleDeformation::m_Impulse_Y = 1.0f;
|
|
CVehicleDeformation::m_Impulse_Z = 1.0f;
|
|
|
|
CVehicleDeformation::m_ImpactPositionLocal_X = 0.0f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Y = -10.0f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Z = 2.72f;
|
|
|
|
DamageCurrentCar();
|
|
}
|
|
|
|
void CVehicleDamage::LeftSideCollision()
|
|
{
|
|
CVehicleDeformation::m_Impulse_X = 1.0f;
|
|
CVehicleDeformation::m_Impulse_Y = 0.0f;
|
|
CVehicleDeformation::m_Impulse_Z = 0.0f;
|
|
|
|
CVehicleDeformation::m_ImpactPositionLocal_X = -0.44f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Y = 2.0f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Z = 0.2f;
|
|
|
|
DamageCurrentCar();
|
|
}
|
|
|
|
void CVehicleDamage::RightSideCollision()
|
|
{
|
|
CVehicleDeformation::m_Impulse_X = -1.0f;
|
|
CVehicleDeformation::m_Impulse_Y = 0.0f;
|
|
CVehicleDeformation::m_Impulse_Z = 0.0f;
|
|
|
|
CVehicleDeformation::m_ImpactPositionLocal_X = 0.44f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Y = 2.0f;
|
|
CVehicleDeformation::m_ImpactPositionLocal_Z = 0.2f;
|
|
|
|
DamageCurrentCar();
|
|
}
|
|
|
|
|
|
void CVehicleDamage::ImplodeSubmarine()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if ((ms_SourceVehicle == NULL) || (ms_SourceVehicle->GetVehicleType() != VEHICLE_TYPE_SUBMARINE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
CSubmarineHandling* subHandling = ms_SourceVehicle->GetSubHandling();
|
|
|
|
if( subHandling )
|
|
{
|
|
static bool shouldImplodeFully = false;
|
|
subHandling->Implode(ms_SourceVehicle, shouldImplodeFully);
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::RandomSmash()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if ((ms_SourceVehicle == NULL) || (CVehicleDeformation::ms_fDamageAmount < 0.01f))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CVehicleDeformation::ms_bAutoFix)
|
|
{
|
|
ms_SourceVehicle->Fix();
|
|
}
|
|
|
|
AddVehicleExplosionDeformations(ms_SourceVehicle, NULL, DAMAGE_TYPE_EXPLOSIVE, CVehicleDeformation::ms_iExtraExplosionDeformations, CVehicleDeformation::ms_fExtraExplosionsDamage);
|
|
}
|
|
|
|
void CVehicleDamage::NetworkSmashDebug()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if ((ms_SourceVehicle == NULL) || (CVehicleDeformation::ms_fDamageAmount < 0.01f))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CVehicleDeformation::ms_bAutoFix)
|
|
{
|
|
ms_SourceVehicle->Fix();
|
|
}
|
|
ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->ApplyDeformationsFromNetwork(g_DamageDebug);
|
|
}
|
|
|
|
void CVehicleDamage::NetworkSmashDebugRandom()
|
|
{
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
g_DamageDebug[i] = (int)fwRandom::GetRandomNumberInRange(0.0f, 3.999f);
|
|
}
|
|
|
|
NetworkSmashDebug();
|
|
}
|
|
|
|
void CVehicleDamage::UpdateNetworkDamageDebug()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (ms_SourceVehicle == NULL)
|
|
{
|
|
for(int i=0;i<CVehicleDeformation::NUM_NETWORK_DAMAGE_DIRECTIONS;i++)
|
|
{
|
|
g_DamageValue[i] = -1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int i=0;i<CVehicleDeformation::NUM_NETWORK_DAMAGE_DIRECTIONS;i++)
|
|
{
|
|
g_DamageValue[i] = ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->GetNetworkDamage(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::RemoveWheel()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
CAutomobile* automobile = dynamic_cast<CAutomobile*>(ms_SourceVehicle);
|
|
if ((ms_SourceVehicle == NULL) || (ms_SourceVehicle->GetVehicleType() != VEHICLE_TYPE_CAR) || (automobile == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
s32 numWheels = automobile->GetNumWheels();
|
|
|
|
if (numWheels == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static int wheelId = 0;
|
|
float fxChance = 0.0f;
|
|
float deleteChance = 1.0f;
|
|
float restoreTireChance = 0.0f;
|
|
|
|
ms_SourceVehicle->GetVehicleDamage()->BreakOffWheel(wheelId, fxChance, deleteChance, restoreTireChance);
|
|
|
|
++wheelId;
|
|
wheelId %= numWheels;
|
|
}
|
|
|
|
void CVehicleDamage::RemoveHelicopterTail()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
CRotaryWingAircraft* helicopter = dynamic_cast<CRotaryWingAircraft*>(ms_SourceVehicle);
|
|
|
|
if ((ms_SourceVehicle == NULL) || (ms_SourceVehicle->GetVehicleType() != VEHICLE_TYPE_HELI) || (helicopter == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
helicopter->BreakOffTailBoom();
|
|
}
|
|
|
|
void CVehicleDamage::RemoveHelicopterPropellers()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
CRotaryWingAircraft* helicopter = dynamic_cast<CRotaryWingAircraft*>(ms_SourceVehicle);
|
|
|
|
if ((ms_SourceVehicle == NULL) || (ms_SourceVehicle->GetVehicleType() != VEHICLE_TYPE_HELI) || (helicopter == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
static bool mainPropRemove = true;
|
|
|
|
if (mainPropRemove)
|
|
helicopter->BreakOffMainRotor();
|
|
|
|
static bool rearPropRemove = true;
|
|
|
|
if (rearPropRemove)
|
|
helicopter->BreakOffRearRotor();
|
|
}
|
|
|
|
void CVehicleDamage::DropFiveTonContainer()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if ((ms_SourceVehicle == NULL) || (CVehicleDeformation::ms_fContainerDropHeight < 0.0f))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CVehicleDeformation::ms_bAutoFix)
|
|
{
|
|
ms_SourceVehicle->Fix();
|
|
}
|
|
|
|
int newContainerIndex = 0;
|
|
int containerID = 874602658;
|
|
|
|
const Matrix34& matrix = RCC_MATRIX34(ms_SourceVehicle->GetMatrixRef());
|
|
|
|
Vector3 createPosWorldSpace;
|
|
Vector3 createPosLocalSpace = Vector3(CVehicleDeformation::m_ImpactPositionLocal_X, CVehicleDeformation::m_ImpactPositionLocal_X, CVehicleDeformation::m_ImpactPositionLocal_Z + CVehicleDeformation::ms_fContainerDropHeight);
|
|
matrix.Transform(createPosLocalSpace, createPosWorldSpace);
|
|
|
|
object_commands::ObjectCreationFunction(containerID, createPosWorldSpace.x, createPosWorldSpace.y, createPosWorldSpace.z, false, true, 0, newContainerIndex, true, true, false);
|
|
|
|
float mass = 5000.0f;
|
|
float gravityFactor = -1.0f;
|
|
scrVector vecTranslationDamping;
|
|
scrVector vecRotationDamping;
|
|
|
|
static CObject *pObj = NULL;
|
|
|
|
if (pObj == NULL)
|
|
{
|
|
pObj = CTheScripts::GetEntityToModifyFromGUID<CObject>(newContainerIndex);
|
|
|
|
if (pObj)
|
|
{
|
|
rage::phInst* instance = pObj->GetCurrentPhysicsInst();
|
|
|
|
if (!instance)
|
|
{
|
|
pObj->ActivatePhysics();
|
|
instance = pObj->GetCurrentPhysicsInst();
|
|
}
|
|
|
|
if(instance)
|
|
{
|
|
CBaseModelInfo *pModelInfo = pObj->GetBaseModelInfo();
|
|
|
|
// check if object still has original physics archetype
|
|
if(instance->GetArchetype() == pModelInfo->GetPhysics())
|
|
{
|
|
// if so then we need to clone it so that we can modify values
|
|
phArchetypeDamp* pNewArchetype = static_cast<phArchetypeDamp*>(instance->GetArchetype()->Clone());
|
|
// pass new archetype to the objects physinst -> should have 1 ref only
|
|
instance->SetArchetype(pNewArchetype);
|
|
}
|
|
|
|
phArchetypeDamp* pGtaArchetype = static_cast<phArchetypeDamp*>(instance->GetArchetype());
|
|
|
|
if (pGtaArchetype)
|
|
{
|
|
pGtaArchetype->SetMass(mass);
|
|
|
|
if (gravityFactor != -1.0f)
|
|
pGtaArchetype->SetGravityFactor(gravityFactor);
|
|
|
|
if(vecTranslationDamping.x > -1.0f)
|
|
pGtaArchetype->ActivateDamping(phArchetypeDamp::LINEAR_C, Vector3(vecTranslationDamping.x, vecTranslationDamping.x, vecTranslationDamping.x));
|
|
if(vecTranslationDamping.y > -1.0f)
|
|
pGtaArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V, Vector3(vecTranslationDamping.y, vecTranslationDamping.y, vecTranslationDamping.y));
|
|
if(vecTranslationDamping.z > -1.0f)
|
|
pGtaArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V2, Vector3(vecTranslationDamping.z, vecTranslationDamping.z, vecTranslationDamping.z));
|
|
|
|
if(vecRotationDamping.x > -1.0f)
|
|
pGtaArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_C, Vector3(vecRotationDamping.x, vecRotationDamping.x, vecRotationDamping.x));
|
|
if(vecRotationDamping.y > -1.0f)
|
|
pGtaArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V, Vector3(vecRotationDamping.y, vecRotationDamping.y, vecRotationDamping.y));
|
|
if(vecRotationDamping.z > -1.0f)
|
|
pGtaArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V2, Vector3(vecRotationDamping.z, vecRotationDamping.z, vecRotationDamping.z));
|
|
|
|
Vector3 nudge(0.0f, 0.0f, -0.2f);
|
|
pObj->ClearBaseFlag(fwEntity::IS_FIXED);
|
|
pObj->SetVelocity(nudge);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pObj->Teleport(createPosWorldSpace);
|
|
Vector3 nudge(0.0f, 0.0f, -0.2f);
|
|
pObj->SetVelocity(nudge);
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::DuplicateDamageCB()
|
|
{
|
|
CVehicleDamage::Copy(ms_SourceVehicle, ms_DestinationVehicle);
|
|
}
|
|
|
|
void CVehicleDamage::SaveDamageTexture()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (!ms_SourceVehicle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CVehicleDeformation::SaveDamageTexture(ms_SourceVehicle, false);
|
|
}
|
|
|
|
void CVehicleDamage::LoadDamageTexture()
|
|
{
|
|
SetSourceVehicleCB();
|
|
CVehicleDeformation::LoadDamageTexture(ms_SourceVehicle);
|
|
}
|
|
|
|
void CVehicleDamage::FixDamageForTests()
|
|
{
|
|
SetSourceVehicleCB();
|
|
|
|
if (ms_SourceVehicle)
|
|
{
|
|
ms_SourceVehicle->Fix();
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::BreakOffWheelsCB()
|
|
{
|
|
if(ms_SourceVehicle && ms_SourceVehicle->GetVehicleDamage())
|
|
{
|
|
for(u32 index = 0; index < ms_SourceVehicle->GetNumWheels(); index++)
|
|
ms_SourceVehicle->GetVehicleDamage()->BreakOffWheel(index);
|
|
}
|
|
}
|
|
|
|
#endif //bank
|
|
|
|
|
|
void CVehicleDamage::HeadOnCollision(CVehicle* pVehicle, float damage, bool randomize)
|
|
{
|
|
if (pVehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 boundingBoxMax = pVehicle->GetChassisBoundMax(true);
|
|
|
|
Vector3 damagePosLocal = Vector3(0.0f, boundingBoxMax.y, 0.0f);
|
|
Vector3 impulseLocal = Vector3(0.0f, -1.0f, 0.0f);
|
|
|
|
if (randomize)
|
|
{
|
|
Vector3 boundingBoxMin = pVehicle->GetChassisBoundMin(true);
|
|
float xRange = (boundingBoxMax - boundingBoxMin).x;
|
|
float randomVal = fwRandom::GetRandomNumberInRange(-xRange, xRange);
|
|
damagePosLocal.x -= randomVal * 0.75f;
|
|
|
|
static float angleMax = 30.0f / 360.0f;
|
|
impulseLocal.RotateZ(fwRandom::GetRandomNumberInRange(-angleMax, angleMax));
|
|
}
|
|
|
|
const float fMass = pVehicle->GetMass();
|
|
const float fMassIndependenceFactor = pVehicle->pHandling->m_fDeformationDamageMult < 0.00001f ? fMass: (fMass / pVehicle->pHandling->m_fDeformationDamageMult);
|
|
const float fDeformationMag = damage * fMassIndependenceFactor;
|
|
|
|
DamageVehicle(NULL, impulseLocal, damagePosLocal, fDeformationMag, DAMAGE_TYPE_UNKNOWN, pVehicle, true);
|
|
}
|
|
|
|
|
|
void CVehicleDamage::RearEndCollision(CVehicle* pVehicle, float damage, bool randomize)
|
|
{
|
|
if (pVehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 boundingBoxMin = pVehicle->GetChassisBoundMin(true);
|
|
Vector3 impulseLocal = Vector3(0.0f, 1.0f, 0.0f);
|
|
Vector3 damagePosLocal = Vector3(0.0f, boundingBoxMin.y, 0.0f);
|
|
|
|
if (randomize)
|
|
{
|
|
Vector3 boundingBoxMax = pVehicle->GetChassisBoundMin(true);
|
|
float xRange = (boundingBoxMax - boundingBoxMin).x;
|
|
float randomVal = fwRandom::GetRandomNumberInRange(-xRange, xRange);
|
|
damagePosLocal.x -= randomVal * 0.75f;
|
|
|
|
static float angleMax = 30.0f / 360.0f;
|
|
impulseLocal.RotateZ(fwRandom::GetRandomNumberInRange(-angleMax, angleMax));
|
|
}
|
|
|
|
const float fMass = pVehicle->GetMass();
|
|
const float fMassIndependenceFactor = (pVehicle->pHandling->m_fDeformationDamageMult < 0.00001f ? fMass : (fMass / pVehicle->pHandling->m_fDeformationDamageMult));
|
|
const float fDeformationMag = damage * fMassIndependenceFactor;
|
|
|
|
DamageVehicle(NULL, impulseLocal, damagePosLocal, fDeformationMag, DAMAGE_TYPE_UNKNOWN, pVehicle, true);
|
|
}
|
|
|
|
|
|
#if VEHICLE_DEFORMATION_INVERSE_SQUARE_FIELD
|
|
dev_float sfVehDefColRadiusMult = 0.75f;
|
|
dev_float sfVehDefColRadiusMax = 0.75f;
|
|
#else
|
|
dev_float sfVehDefColRadiusMult = 0.5f;
|
|
dev_float sfVehDefColRadiusMax = 0.5f;
|
|
#endif
|
|
|
|
bank_float CVehicleDeformation::ms_fVehDefColMultX = 0.15f;
|
|
bank_float CVehicleDeformation::ms_fVehDefColMultY = 0.06f;
|
|
bank_float CVehicleDeformation::ms_fVehDefColMultYNeg = 0.08f;
|
|
bank_float CVehicleDeformation::ms_fVehDefColMultZ = 0.12f;
|
|
bank_float CVehicleDeformation::ms_fVehDefColMultZNeg = 0.10f;
|
|
bank_float CVehicleDeformation::ms_fVehDefRoofColMultZNeg = 0.15f;
|
|
bank_float CVehicleDeformation::ms_fDeformationCarRoofMinZ = -0.25f;
|
|
bank_float CVehicleDeformation::ms_fDeformationSuperCarRoofMinZ = -0.04f;
|
|
bank_float CVehicleDeformation::ms_fDeformationPlaneHeadMaxZ = 0.25f;
|
|
bank_float CVehicleDeformation::ms_fRollcageMinZ = -0.05f;
|
|
bank_bool CVehicleDeformation::ms_bEnableRoofZDeformationClamping = true;
|
|
bank_float CVehicleDeformation::ms_fCarModBoneDeformMult = 1.0f;
|
|
int CVehicleDeformation::ms_iExtraExplosionDeformations = MAX_IMPACTS_PER_VEHICLE_PER_FRAME - 3; // allow 1 to be from a rocket, and 2 for front / rear extra damage points
|
|
bank_float CVehicleDeformation::ms_fExtraExplosionsDamage = 20.0f;
|
|
bank_float CVehicleDeformation::m_sfVehDefColMax1 = 0.5f;
|
|
bank_float CVehicleDeformation::m_sfVehDefColMax2 = 1.2f;
|
|
bank_float CVehicleDeformation::m_sfVehDefColMult2 = 0.5f;
|
|
bank_float CVehicleDeformation::ms_fLargeVehicleRadiusMultiplier = 0.6f;
|
|
bank_bool CVehicleDeformation::ms_bEnableExtraBoneDeformations = true;
|
|
|
|
#if VEHICLE_DEFORMATION_PROPORTIONAL
|
|
bank_float CVehicleDeformation::ms_fDamageMagnitudeMult = GTA_VEHICLE_DAMAGE_DELTA_SCALE;
|
|
#endif
|
|
|
|
float CVehicleDeformation::ms_SmoothedPerlinNoise[VEHICLE_DEFORMATION_NOISE_HEIGHT][VEHICLE_DEFORMATION_NOISE_WIDTH_EXPANDED] ;
|
|
|
|
__forceinline float Interpolate(float x0, float x1, float alpha)
|
|
{
|
|
return x0 * (1 - alpha) + alpha * x1;
|
|
}
|
|
__forceinline Vec4V_Out Interpolate(Vec4V_In x0, Vec4V_In x1, Vec4V_In alpha)
|
|
{
|
|
return Lerp(alpha, x0, x1);
|
|
}
|
|
|
|
void GenerateSmoothNoise(float smoothNoise[VEHICLE_DEFORMATION_NOISE_HEIGHT][VEHICLE_DEFORMATION_NOISE_WIDTH], float baseNoise[VEHICLE_DEFORMATION_NOISE_HEIGHT][VEHICLE_DEFORMATION_NOISE_WIDTH], int octave)
|
|
{
|
|
int samplePeriod = 1 << octave; // calculates 2 ^ k
|
|
float sampleFrequency = 1.0f / samplePeriod;
|
|
|
|
for (int i = 0; i < VEHICLE_DEFORMATION_NOISE_HEIGHT; i++)
|
|
{
|
|
//calculate the vertical sampling indices
|
|
int sample_i0 = (i / samplePeriod) * samplePeriod;
|
|
int sample_i1 = (sample_i0 + samplePeriod) % VEHICLE_DEFORMATION_NOISE_HEIGHT; //wrap around
|
|
float vertical_blend = (i - sample_i0) * sampleFrequency;
|
|
|
|
for (int j = 0; j < VEHICLE_DEFORMATION_NOISE_WIDTH; j++)
|
|
{
|
|
//calculate the horizontal sampling indices
|
|
int sample_j0 = (j / samplePeriod) * samplePeriod;
|
|
int sample_j1 = (sample_j0 + samplePeriod) % VEHICLE_DEFORMATION_NOISE_WIDTH; //wrap around
|
|
float horizontal_blend = (j - sample_j0) * sampleFrequency;
|
|
|
|
|
|
//blend the top two corners
|
|
float top = Interpolate(baseNoise[sample_i0][sample_j0],
|
|
baseNoise[sample_i1][sample_j0], horizontal_blend);
|
|
|
|
//blend the bottom two corners
|
|
float bottom = Interpolate(baseNoise[sample_i0][sample_j1],
|
|
baseNoise[sample_i1][sample_j1], horizontal_blend);
|
|
|
|
//final blend
|
|
smoothNoise[i][j] = Interpolate(top, bottom, vertical_blend);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDeformation::InitSmoothedPerlinNoise()
|
|
{
|
|
float whiteNoise[VEHICLE_DEFORMATION_NOISE_HEIGHT][VEHICLE_DEFORMATION_NOISE_WIDTH];
|
|
|
|
for (int x=0; x < VEHICLE_DEFORMATION_NOISE_HEIGHT; ++x )
|
|
{
|
|
for (int y=0; y < VEHICLE_DEFORMATION_NOISE_WIDTH; ++y )
|
|
{
|
|
ms_SmoothedPerlinNoise[x][y] = 0.0f;
|
|
whiteNoise[x][y] = fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
//generate smooth noise
|
|
float smoothNoise[VEHICLE_DEFORMATION_NOISE_SMOOTHING_OCTAVES][VEHICLE_DEFORMATION_NOISE_HEIGHT][VEHICLE_DEFORMATION_NOISE_WIDTH];
|
|
const int octaveCount = VEHICLE_DEFORMATION_NOISE_SMOOTHING_OCTAVES;
|
|
for(int octaveIndex=0; octaveIndex < octaveCount; ++octaveIndex )
|
|
{
|
|
GenerateSmoothNoise(smoothNoise[octaveIndex], whiteNoise, octaveIndex);
|
|
}
|
|
|
|
const float persistance = 0.5f;
|
|
float amplitude = 1.0f;
|
|
float totalAmplitude = 0.0f;
|
|
|
|
//blend noise together
|
|
for (int octave = octaveCount - 1; octave >= 0; octave--)
|
|
{
|
|
amplitude *= persistance;
|
|
totalAmplitude += amplitude;
|
|
|
|
for (int i = 0; i < VEHICLE_DEFORMATION_NOISE_HEIGHT; i++)
|
|
{
|
|
for (int j = 0; j < VEHICLE_DEFORMATION_NOISE_WIDTH; j++)
|
|
{
|
|
ms_SmoothedPerlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
|
|
}
|
|
}
|
|
}
|
|
|
|
//normalization
|
|
for (int i = 0; i < VEHICLE_DEFORMATION_NOISE_HEIGHT; i++)
|
|
{
|
|
for (int j = 0; j < VEHICLE_DEFORMATION_NOISE_WIDTH; j++)
|
|
{
|
|
ms_SmoothedPerlinNoise[i][j] /= totalAmplitude;
|
|
}
|
|
}
|
|
|
|
//Duplicate the first 3 columns for the last
|
|
for (int i = 0; i < VEHICLE_DEFORMATION_NOISE_HEIGHT; i++)
|
|
{
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
ms_SmoothedPerlinNoise[i][VEHICLE_DEFORMATION_NOISE_WIDTH + j] = ms_SmoothedPerlinNoise[i][j];
|
|
}
|
|
}
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
CApplyDamage::InitializePerlinNoiseTexture();// this texture consumes the data generated here
|
|
#endif
|
|
}
|
|
|
|
#if __BANK
|
|
static void copyscan_image(u8 *dest, void *base, int row, int width, int stride, u8 *gammalut)
|
|
{
|
|
u32 *src = (u32*)((char*)base + row * stride);
|
|
while (width--)
|
|
{
|
|
u32 n = *src++;
|
|
dest[0] = gammalut[u8(n >> 16)];
|
|
dest[1] = gammalut[u8(n >> 8)];
|
|
dest[2] = gammalut[u8(n)];
|
|
dest += 3;
|
|
}
|
|
}
|
|
|
|
bool SavePNG(const char *filename, int width, int height, void* buffer, int stride)
|
|
{
|
|
return grcImage::WritePNG(filename, copyscan_image, width, height, buffer, stride, 0);
|
|
}
|
|
|
|
|
|
bool SaveJPEG(const char *filename, int width, int height, u8* buffer)
|
|
{
|
|
grcImage* image = grcImage::Create(width, height, 1, grcImage::A8R8G8B8, grcImage::STANDARD, 0, 0);
|
|
|
|
for (int y=0; y < height; y++ )
|
|
{
|
|
for (int x=0; x < width; x++ )
|
|
{
|
|
int offset = (x + y * width) * 4;
|
|
|
|
Color32 color;
|
|
color.SetRed(buffer[offset+1]);
|
|
color.SetGreen(buffer[offset+2]);
|
|
color.SetBlue(buffer[offset+3]);
|
|
|
|
image->SetPixel(x, y, color.GetColor());
|
|
}
|
|
}
|
|
|
|
bool okSave = image->SaveJPEG(filename, 100);
|
|
image->Release();
|
|
return okSave;
|
|
}
|
|
|
|
|
|
bool LoadJPEG(const char *filename, int width, int height, u8* buffer)
|
|
{
|
|
grcImage* image = grcImage::LoadJPEG(filename);
|
|
|
|
if (image == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int y=0; y < height; y++ )
|
|
{
|
|
for (int x=0; x < width; x++ )
|
|
{
|
|
int offset = (x + y * width) * 3;
|
|
|
|
Color32 color = image->GetPixelColor32(x, y);
|
|
|
|
buffer[offset] = color.GetRed();
|
|
buffer[offset+1] = color.GetGreen();
|
|
buffer[offset+2] = color.GetBlue();
|
|
}
|
|
}
|
|
|
|
image->Release();
|
|
return true;
|
|
}
|
|
|
|
int filenameCounter = 0;
|
|
#define MAX_DAMAGE_TEXTURES_PER_VEHICLE_TYPE 1
|
|
|
|
char* GetDamageTextureFilename(CVehicle* vehicle)
|
|
{
|
|
if (vehicle == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
size_t digitLength = (filenameCounter == 0) ? 1 : (size_t)log10((double)filenameCounter) + 1;
|
|
size_t characterCount = strlen("DamageTexture__.jpg") + strlen(vehicle->GetModelName()) + digitLength + 1; // null terminated
|
|
char* filenameBuf = rage_new char[characterCount];
|
|
filenameBuf[characterCount - 1] = 0;
|
|
|
|
sprintf(filenameBuf, "DamageTexture_%s_%d.jpg", vehicle->GetModelName(), filenameCounter);
|
|
|
|
++filenameCounter;
|
|
filenameCounter %= MAX_DAMAGE_TEXTURES_PER_VEHICLE_TYPE;
|
|
return filenameBuf;
|
|
}
|
|
|
|
void CVehicleDeformation::SaveDamageTexture(CVehicle* vehicle, bool skipSaveIfSameAsLast)
|
|
{
|
|
if((vehicle == NULL) || !vehicle->GetVehicleDamage()->GetDeformation()->HasDamageTexture())
|
|
{
|
|
return;
|
|
}
|
|
|
|
static CVehicle* lastVehicle = NULL;
|
|
|
|
if (skipSaveIfSameAsLast && (vehicle == lastVehicle))
|
|
{
|
|
return;
|
|
}
|
|
|
|
lastVehicle = vehicle;
|
|
|
|
void *basePtr = vehicle->GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead);
|
|
if (basePtr)
|
|
{
|
|
int width = GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH;
|
|
int height = GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT;
|
|
|
|
int floatCount = (width * height);
|
|
int byteCount = (floatCount * 4);
|
|
|
|
unsigned char* tempColourBuf = rage_new unsigned char[byteCount]; //Used for random Gaussian noise and also as a buffer to save the texture.
|
|
|
|
#if 0
|
|
//To save the noise texture map.
|
|
int stride = 4 * width;
|
|
int byteIndex = 0;
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED && 0
|
|
grcTexture* pNoiseTexture = CApplyDamage::GetNoiseTexture();
|
|
|
|
if (pNoiseTexture)
|
|
{
|
|
grcTextureLock lock;
|
|
if (pNoiseTexture->LockRect(0, 0, lock, grcsRead | grcsAllowVRAMLock))
|
|
{
|
|
for (int y=0; y < height; y++ )
|
|
{
|
|
for (int x=0; x < width; x++ )
|
|
{
|
|
const float* pNoiseVal = TEXTUREOFFSET((const float*)lock.Base, x % VEHICLE_DEFORMATION_NOISE_WIDTH, y % VEHICLE_DEFORMATION_NOISE_HEIGHT);
|
|
|
|
float val = *pNoiseVal;
|
|
val *= 128.0f;
|
|
val = Clamp(val, 0.0f, 255.0f);
|
|
|
|
tempColourBuf[byteIndex] = (unsigned char)val;
|
|
tempColourBuf[byteIndex+1] = (unsigned char)val;
|
|
tempColourBuf[byteIndex+2] = (unsigned char)val;
|
|
tempColourBuf[byteIndex+3] = 255;
|
|
|
|
byteIndex += 4;
|
|
}
|
|
}
|
|
|
|
pNoiseTexture->UnlockRect(lock);
|
|
}
|
|
}
|
|
|
|
#else
|
|
for (int y=0; y < height; y++ )
|
|
{
|
|
for (int x=0; x < width; x++ )
|
|
{
|
|
float val = 128.0f;
|
|
val *= GetSmoothedPerlinNoise(x, y).Getf();
|
|
val = Clamp(val, 0.0f, 255.0f);
|
|
|
|
tempColourBuf[byteIndex] = (unsigned char)val;
|
|
tempColourBuf[byteIndex+1] = (unsigned char)val;
|
|
tempColourBuf[byteIndex+2] = (unsigned char)val;
|
|
tempColourBuf[byteIndex+3] = 255;
|
|
|
|
byteIndex += 4;
|
|
|
|
}
|
|
}
|
|
#endif
|
|
const char *noiseFilename = "DamageNoise.png";
|
|
SavePNG(noiseFilename, width, height, tempColourBuf, stride);
|
|
#endif
|
|
|
|
for (int y=0; y < height; y++ )
|
|
{
|
|
for (int x=0; x < width; x++ )
|
|
{
|
|
Vec3V_Out vDamage = ReadFromPixel(basePtr, x, y);
|
|
Vector3 damage = VEC3V_TO_VECTOR3(vDamage); // -1 to 1
|
|
|
|
if ( IsNanInMemory(&damage.x) || !FPIsFinite(damage.x) || (abs(damage.x) > GTA_VEHICLE_MAX_DAMAGE_RESOLUTION)
|
|
|| IsNanInMemory(&damage.y) || !FPIsFinite(damage.y) || (abs(damage.y) > GTA_VEHICLE_MAX_DAMAGE_RESOLUTION)
|
|
|| IsNanInMemory(&damage.z) || !FPIsFinite(damage.z) || (abs(damage.z) > GTA_VEHICLE_MAX_DAMAGE_RESOLUTION) )
|
|
{
|
|
Assert(false);
|
|
return;
|
|
}
|
|
|
|
damage *= 128.0f;
|
|
damage += Vector3(128.0f, 128.0f, 128.0f);
|
|
|
|
unsigned char red = (unsigned char) (Clamp(damage.x, 0.0f, 255.0f));
|
|
unsigned char green = (unsigned char) (Clamp(damage.y, 0.0f, 255.0f));
|
|
unsigned char blue = (unsigned char) (Clamp(damage.z, 0.0f, 255.0f));
|
|
|
|
int offset = (x + y * width) * 4;
|
|
tempColourBuf[offset] = 255;
|
|
tempColourBuf[offset + 1] = red;
|
|
tempColourBuf[offset + 2] = green;
|
|
tempColourBuf[offset + 3] = blue;
|
|
}
|
|
}
|
|
|
|
char* filenameJPG = GetDamageTextureFilename(vehicle);
|
|
SaveJPEG(filenameJPG, width, height, tempColourBuf);
|
|
delete filenameJPG;
|
|
delete tempColourBuf;
|
|
|
|
vehicle->GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CVehicleDeformation::LoadDamageTexture(CVehicle* vehicle)
|
|
{
|
|
if(vehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int width = GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH;
|
|
int height = GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT;
|
|
int floatCount = (width * height);
|
|
int byteCount = (floatCount * 3);
|
|
|
|
unsigned char* tempColourBuf = rage_new unsigned char[byteCount];
|
|
bool okLoadImage = false;
|
|
|
|
//Try to find the next image in the series
|
|
for (int i = 0; i < MAX_DAMAGE_TEXTURES_PER_VEHICLE_TYPE; i++)
|
|
{
|
|
char* filenameJPG = GetDamageTextureFilename(vehicle);
|
|
okLoadImage = LoadJPEG(filenameJPG, width, height, tempColourBuf);
|
|
delete filenameJPG;
|
|
|
|
if (okLoadImage)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!okLoadImage)
|
|
{
|
|
//Nothing found.
|
|
delete tempColourBuf;
|
|
return;
|
|
}
|
|
|
|
if (!vehicle->GetVehicleDamage()->GetDeformation()->HasDamageTexture())
|
|
{
|
|
vehicle->GetVehicleDamage()->GetDeformation()->DamageTextureAllocate(false); // don't reset the damage here, it can cause problems due to async damage resetting using GPU, erasing what we are about to read in.
|
|
Assert(vehicle->GetVehicleDamage()->GetDeformation()->HasDamageTexture());
|
|
}
|
|
|
|
void *basePtr = vehicle->GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead|grcsWrite);
|
|
|
|
if (basePtr)
|
|
{
|
|
for (int x=0; x < width; x++ )
|
|
{
|
|
for (int y=0; y < height; y++ )
|
|
{
|
|
int offset = (x + y * width) * 3;
|
|
|
|
unsigned char red = tempColourBuf[offset+2];
|
|
unsigned char green = tempColourBuf[offset+1];
|
|
unsigned char blue = tempColourBuf[offset];
|
|
|
|
Vector3 damage = Vector3((float)red, (float)green, (float)blue);
|
|
damage += Vector3(-128.0f, -128.0f, -128.0f);
|
|
damage /= 128.0f;
|
|
WriteToPixel(basePtr, damage, x, y);
|
|
}
|
|
}
|
|
|
|
vehicle->GetVehicleDamage()->GetDeformation()->HandleDamageAdded(basePtr, true, true, true, true, true, true);
|
|
vehicle->GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
|
|
}
|
|
delete tempColourBuf;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// --- Code ---------------------------------------------------------------------
|
|
#if VEHICLE_DEFORMATION_TIMING
|
|
void VD_DisplayTiming()
|
|
{
|
|
static int StartX = 100;
|
|
static int StartY = 10;
|
|
|
|
int y = StartY;
|
|
|
|
char debugText[50];
|
|
|
|
sprintf(debugText,"Total Damage Processed : %4d",DTT_timerHit);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tMin Time : %f",DTT_minTime);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tMax Time : %f",DTT_maxTime);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tAvg Time : %f",DTT_accumTime / (float)DTT_timerHit);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tMin Impact Applied : %d",DTT_minImpact);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tMax Impact Applied : %d",DTT_maxImpact);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tAvg Impact Applied : %d",(int)((float)DTT_accumImpact / (float)DTT_timerHit));
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tMin Impact Found : %d",DTT_minOverload);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tMax Impact Found : %d",DTT_maxOverload);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
|
|
sprintf(debugText,"\tAvg Impact Found : %d",(int)((float)DTT_accumOverload / (float)DTT_timerHit));
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
}
|
|
#endif
|
|
|
|
CVehicleDeformation::CVehicleDeformation()
|
|
{
|
|
m_pParentVehicle = NULL;
|
|
m_nTextureCacheIdx = -1;
|
|
|
|
ClearStoredImpacts();
|
|
#if VEHICLE_DEFORMATION_TIMING
|
|
m_nImpactFound = 0;
|
|
#endif // VEHICLE_DEFORMATION_TIMING
|
|
|
|
m_fBoundRadiusScaledInv = 1.0f;
|
|
|
|
memset(m_networkDamages,0,sizeof(m_networkDamages));
|
|
}
|
|
|
|
void CVehicleDeformation::Init(CVehicle* pVehicle)
|
|
{
|
|
#if NO_VEH_DAMAGE_PARAM
|
|
if(PARAM_novehicledamage.Get() || bTestNoVehicleDeformation)
|
|
{
|
|
bTestNoVehicleDeformation=TRUE;
|
|
return; // return before the damage texture is hooked up
|
|
}
|
|
#endif
|
|
|
|
m_pParentVehicle = NULL; //ensure this is cleared out and initialized properly below
|
|
|
|
if(pVehicle->GetDrawable()==NULL)
|
|
{
|
|
#if __ASSERT
|
|
Assertf(0,"CVehicleDeformation::Init GetDrawable==NULL therefore mpParentVehicle will be NULL and ApplyDeformations calls will not work after this.");
|
|
#else
|
|
Warningf("CVehicleDeformation::Init GetDrawable==NULL therefore mpParentVehicle will be NULL and ApplyDeformations calls will not work after this.");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// only to deformation on cars (automobiles) for the moment
|
|
if(!pVehicle->m_nVehicleFlags.bUseDeformation)
|
|
return;
|
|
|
|
// if define is off then variables won't get initialised and all other functions will skip out if trying to apply damage
|
|
#if VEHICLE_DAMAGE_ON
|
|
m_pParentVehicle = pVehicle;
|
|
CVehicleModelInfo *pVMI = m_pParentVehicle->GetVehicleModelInfo();
|
|
|
|
Assert(pVMI->GetDamageOffsetScale() > 0.0f);
|
|
m_fBoundRadiusScaledInv = 1.0f / ( pVMI->GetBoundingSphereRadius() / pVMI->GetDamageOffsetScale() );
|
|
|
|
CalculateDamageMultiplier();
|
|
|
|
#if ADD_PED_SAFE_ZONE_TO_VEHICLES
|
|
crSkeletonData* pSkeletonData = pVMI->GetHDFragType() ? pVMI->GetHDFragType()->GetCommonDrawable()->GetSkeletonData() : pVMI->GetFragType()->GetCommonDrawable()->GetSkeletonData();
|
|
Vector3 boundBoxMin = m_pParentVehicle->GetChassisBoundMin(false);
|
|
Vector3 boundBoxMax = m_pParentVehicle->GetChassisBoundMax(false);
|
|
m_PedSafeZone.ReComputePedSafeArea(pSkeletonData, boundBoxMin, boundBoxMax);
|
|
#endif
|
|
|
|
#endif //VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
void CVehicleDeformation::Shutdown()
|
|
{
|
|
DamageTextureFree();
|
|
|
|
m_pParentVehicle = NULL;
|
|
m_fBoundRadiusScaledInv = 1.0f;
|
|
}
|
|
|
|
// convert from a normalized offset into a texture coordinate in UV coordinates or in Pixels (default)
|
|
Vec::Vector_4V_Out CVehicleDeformation::GetDamageTexCoordFromOffset(Vec::Vector_4V_In vecLocalNormalisedOffset, bool inPixels /* = true */)
|
|
{
|
|
using namespace Vec;
|
|
Vector_4V localOffset = vecLocalNormalisedOffset;
|
|
Vector_4V zoffset = V4SplatZ(localOffset);
|
|
Vector_4V half = V4VConstant(V_HALF);
|
|
zoffset = V4SubtractScaled(half, half, zoffset);
|
|
|
|
// store 2d component of offset
|
|
Vector_4V texSampleCoords = V4And(localOffset, V4VConstant(V_MASKXY));
|
|
|
|
// and re-normalize just this (will get scaled by previously normalized z)
|
|
// since we have zeroed out z, texSampleCoords could now be zero magnitude, need to handle this
|
|
texSampleCoords = V4Normalize(texSampleCoords);
|
|
texSampleCoords = V4And(texSampleCoords, V4IsNotNanV(texSampleCoords));
|
|
|
|
Vector_4V add = inPixels ? V4VConstantSplat<FLOAT_TO_INT(GTA_VEHICLE_DAMAGE_TEXTURE_SIZE*0.5f)>() : V4VConstantSplat<FLOAT_TO_INT(0.5f)>();
|
|
Vector_4V mul = V4Scale(zoffset, add);
|
|
texSampleCoords = V4AddScaled(add, mul, texSampleCoords);
|
|
|
|
return texSampleCoords;
|
|
}
|
|
|
|
// convert from a texture coordinate into a normalized offset
|
|
Vector3 CVehicleDeformation::GetDamageOffsetFromTexCoord(const Vector3& vecTexSampleCoords, bool bCoordsInPixels)
|
|
{
|
|
Vector3 texSampleCoords(vecTexSampleCoords);
|
|
texSampleCoords.z = 0.0f;
|
|
|
|
if(bCoordsInPixels)
|
|
texSampleCoords /= GTA_VEHICLE_DAMAGE_TEXTURE_SIZE;
|
|
|
|
texSampleCoords.x -= 0.5f;
|
|
texSampleCoords.y -= 0.5f;
|
|
texSampleCoords *= 2.0f;
|
|
|
|
Vector3 localOffset(0.0f, 0.0f, 0.0f);
|
|
|
|
localOffset.z = texSampleCoords.Mag();
|
|
float fXYMag = localOffset.z;
|
|
|
|
if(localOffset.z > 1.0f)
|
|
localOffset.z = 1.0f;
|
|
|
|
localOffset.z = 1.0f - 2.0f * localOffset.z;
|
|
|
|
if(localOffset.z < 1.0f && localOffset.z > -1.0f)
|
|
{
|
|
if(fXYMag > 0.0f)
|
|
fXYMag = rage::Sqrtf(1.0f - localOffset.z*localOffset.z) / fXYMag;
|
|
}
|
|
else
|
|
{
|
|
fXYMag = 0.0f;
|
|
}
|
|
|
|
localOffset.x = texSampleCoords.x*fXYMag;
|
|
localOffset.y = texSampleCoords.y*fXYMag;
|
|
|
|
return localOffset;
|
|
}
|
|
|
|
#if ADD_PED_SAFE_ZONE_TO_VEHICLES
|
|
|
|
ScalarV_Out CVehicleDeformation::GetPedSafeAreaMultiplier(Vec3V_In vecOffset) const
|
|
{
|
|
return m_PedSafeZone.GetPedSafeAreaMultiplier(vecOffset);
|
|
}
|
|
|
|
void CVehicleDeformation::GetPedSafeAreaMinMax(Vector3& safeMin, Vector3& safeMax, Vector3& unSafeMin, Vector3& unSafeMax) const
|
|
{
|
|
m_PedSafeZone.GetPedSafeAreaMinMax(safeMin, safeMax, unSafeMin, unSafeMax);
|
|
}
|
|
|
|
#endif // ADD_PED_SAFE_ZONE_TO_VEHICLES
|
|
|
|
#if __PPU
|
|
Vec3V_Out CVehicleDeformation::ReadFromVectorOffset(const void* base, Vec3V_In vecOffset, bool UNUSED_PARAM(interpolate))
|
|
{
|
|
if (!base)
|
|
{
|
|
return Vec3V(VEC3_ZERO);
|
|
}
|
|
|
|
using namespace Vec;
|
|
|
|
// This could be a rubbish result (if vecOffset is zero length), but thats ok, we'll mask it out later
|
|
Vector_4V offset = vecOffset.GetIntrin128();
|
|
Vector_4V offsetMag2 = V3MagSquaredV(offset);
|
|
Vector_4V invOffsetMag = V4InvSqrt(offsetMag2);
|
|
Vector_4V normOffset = V4Scale(offset, invOffsetMag);
|
|
Vector_4V isValid = V4IsFiniteV(invOffsetMag);
|
|
|
|
Vector_4V texCoord = GetDamageTexCoordFromOffset(normOffset);
|
|
|
|
union { Vector_4V i; u32 u[4]; } v;
|
|
|
|
Vector_4V texCoordInt = (Vector_4V)vec_vctuxs((vec_float4)texCoord, 0);
|
|
v.i = texCoordInt;
|
|
|
|
Vector_4V offsetMag = V4Scale(offsetMag2, invOffsetMag);
|
|
Vector_4V damageDeltaScale = GetDamageMultiplierScalar();
|
|
Vector_4V damageDeltaScaleDivRadius = V4Scale(damageDeltaScale, GetBoundRadiusScaledInvV().GetIntrin128());
|
|
offsetMag = V4Min(damageDeltaScale, V4Scale(offsetMag, damageDeltaScaleDivRadius));
|
|
|
|
Vector_4V texCoordFloor = (Vector_4V)vec_vcfux((vec_uint4)texCoordInt, 0);
|
|
Vector_4V texCoordFrac = V4Subtract(texCoord, texCoordFloor);
|
|
|
|
Vector_4V dx = V4SplatX(texCoordFrac);
|
|
Vector_4V dy = V4SplatY(texCoordFrac);
|
|
|
|
unsigned byteOffset = v.u[0]*3 + v.u[1]*3*GTA_VEHICLE_DAMAGE_TEXTURE_SIZE; // LHS here!
|
|
unsigned char *quad0 = (unsigned char *)base + byteOffset;
|
|
unsigned char *quad1 = quad0 + GTA_VEHICLE_DAMAGE_TEXTURE_SIZE*3;
|
|
|
|
Vector_4V pel00 = (Vector_4V)vec_lvlx(0, quad0);
|
|
Vector_4V pel01 = (Vector_4V)vec_lvrx(16, quad0);
|
|
Vector_4V pel10 = (Vector_4V)vec_lvlx(0, quad1);
|
|
Vector_4V pel11 = (Vector_4V)vec_lvrx(16, quad1);
|
|
|
|
pel01 = V4Or(pel00, pel01); // QW contains 2 3byte PELs
|
|
pel11 = V4Or(pel10, pel11);
|
|
|
|
Vector_4V perm0 = V4VConstant<0x00101010,0x01101010,0x02101010,0x10101010>(); // Also used for 0 byte!!
|
|
Vector_4V perm1 = V4VConstant<0x03101010,0x04101010,0x05101010,0x10101010>();
|
|
|
|
// Extract bytes to MSB of word (using MSB means no sign extension required)
|
|
pel00 = (Vector_4V)vec_vperm((vec_uchar16)pel01, (vec_uchar16)perm0, (vec_uchar16)perm0);
|
|
pel01 = (Vector_4V)vec_vperm((vec_uchar16)pel01, (vec_uchar16)perm0, (vec_uchar16)perm1);
|
|
pel10 = (Vector_4V)vec_vperm((vec_uchar16)pel11, (vec_uchar16)perm0, (vec_uchar16)perm0);
|
|
pel11 = (Vector_4V)vec_vperm((vec_uchar16)pel11, (vec_uchar16)perm0, (vec_uchar16)perm1);
|
|
|
|
// Convert back to floats with range adjust
|
|
pel00 = V4IntToFloatRaw<31>(pel00);
|
|
pel01 = V4IntToFloatRaw<31>(pel01);
|
|
pel10 = V4IntToFloatRaw<31>(pel10);
|
|
pel11 = V4IntToFloatRaw<31>(pel11);
|
|
|
|
// Interpolate
|
|
pel11 = V4SubtractScaled(pel11, pel11, dx);
|
|
pel01 = V4SubtractScaled(pel01, pel01, dx);
|
|
pel10 = V4AddScaled(pel11, pel10, dx);
|
|
pel00 = V4AddScaled(pel01, pel00, dx);
|
|
pel10 = V4SubtractScaled(pel10, pel10, dy);
|
|
pel00 = V4AddScaled(pel10, pel00, dy);
|
|
|
|
// do the same scaling as the shader to get the correct result
|
|
Vector_4V vecResult = V4Scale(pel00, offsetMag);
|
|
|
|
// return zero if input was invalid
|
|
vecResult = V4And(vecResult, isValid);
|
|
|
|
return Vec3V(vecResult);
|
|
}
|
|
|
|
|
|
void CVehicleDeformation::WriteToPixel(void *base, const Vector3& vecDamage, int x, int y)
|
|
{
|
|
Assert(x >= 0 && x<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE && y>=0 && y<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE);
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
TexelValue_R8G8B8A8_SNORM* pTexel = TEXTUREOFFSET((TexelValue_R8G8B8A8_SNORM *)base,x,y);
|
|
Vector3 vecDamageMult = vecDamage;
|
|
vecDamageMult *= GTA_VEHICLE_MAX_DAMAGE_RESOLUTION;
|
|
|
|
vecDamageMult.x = rage::Clamp(vecDamageMult.x, -128.0f, 127.0f); // clamp component-wise
|
|
vecDamageMult.y = rage::Clamp(vecDamageMult.y, -128.0f, 127.0f);
|
|
vecDamageMult.z = rage::Clamp(vecDamageMult.z, -128.0f, 127.0f);
|
|
|
|
pTexel->red = (s8)vecDamageMult.x;
|
|
pTexel->green = (s8)vecDamageMult.y;
|
|
pTexel->blue = (s8)vecDamageMult.z;
|
|
pTexel->alpha = (s8)127;
|
|
|
|
#else
|
|
_ivector4 pel = vec_cts( vecDamage.xyzw,7 ); // Int between -128 and +127
|
|
|
|
#if USE_EDGE
|
|
|
|
unsigned char *pByteEntry = (unsigned char *)base + (x*3 + (y*3*GTA_VEHICLE_DAMAGE_TEXTURE_SIZE));
|
|
|
|
__builtin_altivec_stvebx( __builtin_altivec_vspltb( (_cvector4)pel,3 ),0,pByteEntry );
|
|
__builtin_altivec_stvebx( __builtin_altivec_vspltb( (_cvector4)pel,7 ),1,pByteEntry );
|
|
__builtin_altivec_stvebx( __builtin_altivec_vspltb( (_cvector4)pel,11 ),2,pByteEntry );
|
|
|
|
|
|
#else
|
|
_ivector4 bias = { 128,128,128,0 };
|
|
|
|
pel = vec_add( pel,bias ); // 0 - 255
|
|
|
|
float *pFloatEntry = TEXTUREOFFSET((float *)base,x,y);
|
|
|
|
_ivector4 perm = { 0x001b1713,0x001b1713,0x001b1713,0x001b1713 };
|
|
|
|
pel = __builtin_altivec_vperm_4si( perm,pel,(_cvector4)perm );
|
|
|
|
pel = (_ivector4)vec_ctf( pel,0 ); // Single float splatted across 4..
|
|
|
|
__stvewx( pel,pFloatEntry,0 ); // Store single packed float
|
|
|
|
#endif
|
|
|
|
#endif // GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
}
|
|
|
|
|
|
void CVehicleDeformation::AddToPixel(void* base, Vec3V_In vecDamage, int x, int y)
|
|
{
|
|
#if ENABLE_APPLY_DAMAGE_PF
|
|
PF_FUNC(AddToPixel);
|
|
PF_INCREMENT(AddToPixel);
|
|
#endif // ENABLE_APPLY_DAMAGE_PF
|
|
Assertf(x >= 0 && x<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE && y>=0 && y<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE, "x/y is out of bounds : %d, %d", x, y);
|
|
|
|
#if USE_EDGE
|
|
|
|
unsigned char *pByteEntry = (unsigned char *)base + (x*3 + (y*3*GTA_VEHICLE_DAMAGE_TEXTURE_SIZE));
|
|
|
|
|
|
_ivector4 perm = { 0x000408ff,0xffffffff,0xffffffff,0xffffffff };
|
|
|
|
_ivector4 pel = vec_or( (_ivector4)__lvlx( pByteEntry,0 ),(_ivector4)__lvrx( pByteEntry,16 ) );
|
|
|
|
_ivector4 damage = vec_cts( vecDamage.GetIntrin128(),31 ); // Damage range between -128 and +127 in MSB
|
|
|
|
damage = __builtin_altivec_vperm_4si( damage,perm,(_cvector4)perm ); // Place in first 3 bytes
|
|
|
|
pel = (_ivector4)__builtin_altivec_vaddsbs( (_cvector4)pel,(_cvector4)damage ); // Add and clamp between -128 and +127
|
|
|
|
__builtin_altivec_stvebx( __builtin_altivec_vspltb( (_cvector4)pel,0 ),0,pByteEntry );
|
|
__builtin_altivec_stvebx( __builtin_altivec_vspltb( (_cvector4)pel,1 ),1,pByteEntry );
|
|
__builtin_altivec_stvebx( __builtin_altivec_vspltb( (_cvector4)pel,2 ),2,pByteEntry );
|
|
#else
|
|
|
|
_ivector4 perm = { 0x001b1713,0x001b1713,0x001b1713,0x001b1713 };
|
|
_ivector4 splat = { 0x00111213,0x00111213,0x00111213,0x00111213 };
|
|
_ivector4 sign = { 0x00808080,0x00808080,0x00808080,0x00808080 };
|
|
|
|
float *pFloatEntry = TEXTUREOFFSET((float *)base,x,y);
|
|
|
|
_ivector4 damage = vec_cts( vecDamage.GetIntrin128(),7 ); // Damage range between -128 and +127
|
|
|
|
damage = __builtin_altivec_vperm_4si( perm,damage,(_cvector4)perm ); // Place across vector
|
|
|
|
|
|
_ivector4 pel = (_ivector4)__lvlx( pFloatEntry,0 );
|
|
|
|
pel = (_ivector4)vec_ctu( (__vector4)pel,0 ); // Get 24 bit int containing X,Y,Z
|
|
|
|
pel = __builtin_altivec_vperm_4si( splat,pel,(_cvector4)splat ); // Splat input across 4 vectors
|
|
|
|
pel = vec_xor( pel,sign ); // Convert excess128 to 2's complement
|
|
|
|
pel = (_ivector4)__builtin_altivec_vaddsbs( (_cvector4)pel,(_cvector4)damage ); // Add and clamp between -128 and +127
|
|
|
|
pel = vec_xor( pel,sign ); // Convert back to excess128
|
|
|
|
pel = (_ivector4)vec_ctf( pel,0 ); // Back to floating point
|
|
|
|
__stvewx( pel,pFloatEntry,0 );
|
|
|
|
#endif
|
|
}
|
|
|
|
#else // __PPU
|
|
|
|
|
|
Vec3V_Out CVehicleDeformation::ReadFromTexPosition(const void *base, Vec2V_In xy, bool bInterpolate)
|
|
{
|
|
if (!base)
|
|
{
|
|
return Vec3V(VEC3_ZERO);
|
|
}
|
|
|
|
Assert(xy.GetXf() >= 0.0f && xy.GetXf()<float(GTA_VEHICLE_DAMAGE_TEXTURE_SIZE) && xy.GetYf()>=0.0f && xy.GetYf()<float(GTA_VEHICLE_DAMAGE_TEXTURE_SIZE));
|
|
|
|
const Vec2V xyi = FloatToIntRaw<0>(xy);
|
|
int xi = xyi.GetXi();
|
|
int yi = xyi.GetYi();
|
|
if(bInterpolate)
|
|
{
|
|
Vec3V sample = ReadFromPixel(base, xi, yi);
|
|
Vec3V sampleR = ReadFromPixel(base, (xi + 1) & (GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1), yi);
|
|
Vec3V sampleU = ReadFromPixel(base, xi, (yi + 1) & (GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1));
|
|
Vec3V sampleRU = ReadFromPixel(base, (xi + 1) & (GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1), (yi + 1) & (GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1));
|
|
|
|
Vec2V sampleOffset = xy - RoundToNearestIntZero(xy);
|
|
|
|
Vec4V offsets = Vec4V(sampleOffset, Vec2V(V_ONE) - sampleOffset);
|
|
|
|
Vec3V smoothedSample = sample* offsets.GetZ() * offsets.GetW();
|
|
smoothedSample += sampleR*offsets.GetX()*offsets.GetW();
|
|
smoothedSample += sampleU*offsets.GetZ()*offsets.GetY();
|
|
smoothedSample += sampleRU*offsets.GetX()*offsets.GetY();
|
|
|
|
return smoothedSample;
|
|
}
|
|
else
|
|
{
|
|
return ReadFromPixel(base, xi, yi);
|
|
}
|
|
}
|
|
|
|
Vec3V_Out CVehicleDeformation::ReadFromVectorOffset(const void *base, Vec3V_In vecOffset, bool interpolate) const
|
|
{
|
|
if (!base)
|
|
{
|
|
return Vec3V(VEC3_ZERO);
|
|
}
|
|
|
|
Vec3V vecNormalisedOffset = vecOffset;
|
|
const ScalarV vecOffsetMagSq = MagSquared(vecOffset);
|
|
|
|
ScalarV fOffsetMag(V_ZERO);
|
|
if(IsGreaterThanAll(vecOffsetMagSq, ScalarVConstant<FLOAT_TO_INT(0.0001f*0.0001f)>()))
|
|
{
|
|
fOffsetMag = Sqrt(vecOffsetMagSq);
|
|
vecNormalisedOffset /= fOffsetMag;
|
|
}
|
|
else
|
|
{
|
|
vecNormalisedOffset.ZeroComponents();
|
|
return vecNormalisedOffset;
|
|
}
|
|
|
|
Vec3V vecTexOffset = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(vecNormalisedOffset.GetIntrin128()));
|
|
Vec3V vecResult = ReadFromTexPosition(base, vecTexOffset.GetXY(), interpolate);
|
|
|
|
// do the same scaling as the shader to get the correct result
|
|
vecResult *= Min(ScalarV(V_ONE), fOffsetMag * GetBoundRadiusScaledInvV());
|
|
vecResult *= GetDamageMultiplierScalar();
|
|
|
|
#if ADD_PED_SAFE_ZONE_TO_VEHICLES
|
|
vecResult *= GetPedSafeAreaMultiplier(vecOffset);
|
|
#endif
|
|
return vecResult;
|
|
}
|
|
|
|
void CVehicleDeformation::WriteToPixel(void *base, const Vector3& vecDamage, int x, int y)
|
|
{
|
|
Assert(x >= 0 && x<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE && y>=0 && y<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE);
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
# if VEHICLE_DEFORMATION_USE_HALF_FLOATS
|
|
TexelValue_A16B16G16R16F* pTexel = TEXTUREOFFSET((TexelValue_A16B16G16R16F *)base,x,y);
|
|
pTexel->red = Float16Compressor::compress(vecDamage.x);
|
|
pTexel->green = Float16Compressor::compress(vecDamage.y);
|
|
pTexel->blue = Float16Compressor::compress(vecDamage.z);
|
|
pTexel->alpha = Float16Compressor::compress(1.0);
|
|
# else
|
|
TexelValue_R8G8B8A8_SNORM* pTexel = TEXTUREOFFSET((TexelValue_R8G8B8A8_SNORM *)base,x,y);
|
|
|
|
Vector3 vecDamageMult = vecDamage;
|
|
vecDamageMult *= GTA_VEHICLE_MAX_DAMAGE_RESOLUTION;
|
|
|
|
vecDamageMult.x = rage::Clamp(vecDamageMult.x, -128.0f, 127.0f); // clamp component-wise
|
|
vecDamageMult.y = rage::Clamp(vecDamageMult.y, -128.0f, 127.0f);
|
|
vecDamageMult.z = rage::Clamp(vecDamageMult.z, -128.0f, 127.0f);
|
|
|
|
pTexel->red = (s8)vecDamageMult.x;
|
|
pTexel->green = (s8)vecDamageMult.y;
|
|
pTexel->blue = (s8)vecDamageMult.z;
|
|
pTexel->alpha = (s8)127;
|
|
#endif //VEHICLE_DEFORMATION_USE_HALF_FLOATS
|
|
|
|
#else
|
|
float *pFloatEntry = TEXTUREOFFSET((float *)base,x,y);
|
|
|
|
int nEntryX = int(vecDamage.x * 128.0f + 128.0f);
|
|
int nEntryY = int(vecDamage.y * 128.0f + 128.0f);
|
|
int nEntryZ = int(vecDamage.z * 128.0f + 128.0f);
|
|
|
|
*pFloatEntry = Pack(nEntryX, nEntryY, nEntryZ);
|
|
#endif
|
|
}
|
|
|
|
#define OLD_PACKING_CODE (__PPU || __WIN32PC || RSG_DURANGO || RSG_ORBIS)
|
|
#define NEW_PACKING_CODE __XENON
|
|
#define USE_VECTOR4 0
|
|
|
|
|
|
void CVehicleDeformation::AddToPixel(void* base, Vec3V_In vecDamage, int x, int y)
|
|
{
|
|
#if ENABLE_APPLY_DAMAGE_PF
|
|
PF_FUNC(AddToPixel);
|
|
PF_INCREMENT(AddToPixel);
|
|
#endif // ENABLE_APPLY_DAMAGE_PF
|
|
Assertf(x >= 0 && x<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE && y>=0 && y<GTA_VEHICLE_DAMAGE_TEXTURE_SIZE, "x/y is out of bounds : %d, %d", x, y);
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
# if VEHICLE_DEFORMATION_USE_HALF_FLOATS
|
|
TexelValue_A16B16G16R16F* pTexel = TEXTUREOFFSET((TexelValue_A16B16G16R16F *)base,x,y);
|
|
pTexel->red = Float16Compressor::compress(Float16Compressor::decompress(pTexel->red) + vecDamage.GetXf());
|
|
pTexel->green = Float16Compressor::compress(Float16Compressor::decompress(pTexel->green) + vecDamage.GetYf());
|
|
pTexel->blue = Float16Compressor::compress(Float16Compressor::decompress(pTexel->blue) + vecDamage.GetZf());
|
|
pTexel->alpha = Float16Compressor::compress(1.0);
|
|
# else
|
|
TexelValue_R8G8B8A8_SNORM* pTexel = TEXTUREOFFSET((TexelValue_R8G8B8A8_SNORM *)base,x,y);
|
|
Vector3 vecBase = Vector3((f32)pTexel->red, (f32)pTexel->green, (f32)pTexel->blue);
|
|
|
|
Vector3 vecNew = RCC_VECTOR3(vecDamage);
|
|
vecNew *= GTA_VEHICLE_MAX_DAMAGE_RESOLUTION;
|
|
vecNew += vecBase;
|
|
|
|
vecNew.x = rage::Clamp(vecNew.x, -128.0f, 127.0f);
|
|
vecNew.y = rage::Clamp(vecNew.y, -128.0f, 127.0f);
|
|
vecNew.z = rage::Clamp(vecNew.z, -128.0f, 127.0f);
|
|
|
|
pTexel->red = (s8)vecNew.x;
|
|
pTexel->green = (s8)vecNew.y;
|
|
pTexel->blue = (s8)vecNew.z;
|
|
pTexel->alpha = (s8)127;
|
|
# endif //VEHICLE_DEFORMATION_USE_HALF_FLOATS
|
|
|
|
#else
|
|
float *pFloatEntry = TEXTUREOFFSET((float *)base, x, y);
|
|
|
|
#if OLD_PACKING_CODE
|
|
float initialValue = *pFloatEntry;
|
|
#endif
|
|
|
|
#if NEW_PACKING_CODE
|
|
// unpack what's in the texture already
|
|
// This could be done completely in the vector unit to avoid
|
|
// a load-hit-store when first using the vector.
|
|
#if USE_VECTOR4
|
|
__vector4 vecResults;
|
|
#else
|
|
Vector3 vecResults;
|
|
#endif
|
|
//UnpackFast(initialValue, vecResults);
|
|
static const __vector4 multipliers = {1/65536.0f, 256.0f, 0.0f, 0.0f};
|
|
// Load the float value and scale it down by 65536
|
|
__vector4 temp = __lvlx(pFloatEntry, 0) * multipliers;
|
|
vecResults = __vspltw(__vrfiz(temp), VEC_PERM_X); // Grab the z component
|
|
__vector4 scale256 = __vspltw(multipliers, VEC_PERM_Y);
|
|
__vector4 yz = (temp - vecResults) * scale256;
|
|
__vector4 yVec = __vrfiz(yz); // Grab the y component
|
|
// Insert the y component. 4 means insert to y. 3 shift means rotate left 3 words, which takes x to y
|
|
vecResults = __vrlimi(vecResults, yVec, 4, 3);
|
|
// Grab and insert the z component
|
|
// Insert the z component. 8 means insert to x. 0 shift means rotate left 0 words, which takes x to x.
|
|
vecResults = __vrlimi(vecResults, (yz - yVec) * scale256, 8, 0);
|
|
#if OLD_PACKING_CODE
|
|
Vector3 unpacked = vecResults;
|
|
#endif
|
|
|
|
// scale into -1.0 to 1.0 range
|
|
static const __vector4 constants = {128.0f, 1/128.0f, -1.0f, 0.99f};
|
|
const __vector4 sOffset = __vspltw(constants, VEC_PERM_X);
|
|
const __vector4 sScale = __vspltw(constants, VEC_PERM_Y);
|
|
//static const __vector4 sOffset = {128.0f, 128.0f, 128.0f, 128.0f};
|
|
//static const __vector4 sScale = {1/128.0f, 1/128.0f, 1/128.0f, 1/128.0f};
|
|
vecResults = (vecResults - sOffset) * sScale;
|
|
|
|
// sum results and clamp limits
|
|
vecResults += RCC_VECTOR3(vecDamage);
|
|
const __vector4 vecMinimum = __vspltw(constants, VEC_PERM_Z);
|
|
const __vector4 vecMaximum = __vspltw(constants, VEC_PERM_W);
|
|
//static const __vector4 vecMinimum = {-1.0f, -1.0f, -1.0f, -1.0f};
|
|
//static const __vector4 vecMaximum = {0.99f, 0.99f, 0.99f, 0.99f};
|
|
vecResults = __vmaxfp(vecResults, vecMinimum);
|
|
vecResults = __vminfp(vecResults, vecMaximum);
|
|
|
|
#if OLD_PACKING_CODE
|
|
Vector3 tempResults = vecResults;
|
|
#endif
|
|
|
|
// scale back to -128 to 128
|
|
vecResults = vecResults * sOffset + sOffset;
|
|
|
|
// pack the result back into the pixel
|
|
// Truncate to integral floating-point values in preparation
|
|
// for packing
|
|
vecResults = __vrfiz(vecResults);
|
|
|
|
// Multiply the x, y, and z components by the appropriate scale
|
|
// factors.
|
|
static const __vector4 vecScaleFactors = {1.0f, YPACK, ZPACK, 0.0f};
|
|
vecResults *= vecScaleFactors;
|
|
|
|
// Add the components together and store them.
|
|
// Need to splat all inputs (or y and z inputs and the output) because
|
|
// stvewx is unpredictable about which element it will store.
|
|
vecResults = __vspltw(vecResults, VEC_PERM_X) + __vspltw(vecResults, VEC_PERM_Y) + __vspltw(vecResults, VEC_PERM_Z);
|
|
__stvewx(vecResults, pFloatEntry, 0);
|
|
#endif
|
|
|
|
#if OLD_PACKING_CODE
|
|
// unpack what's in the texture already
|
|
Vector3 vecBase;
|
|
vecBase.x = initialValue;
|
|
Unpack(vecBase);
|
|
#if NEW_PACKING_CODE
|
|
FastAssert(vecBase.x == unpacked.x);
|
|
FastAssert(vecBase.y == unpacked.y);
|
|
FastAssert(vecBase.z == unpacked.z);
|
|
#endif
|
|
|
|
// scale into -1.0 to 1.0 range
|
|
vecBase -= Vector3(128.0f, 128.0f, 128.0f);
|
|
vecBase /= 128.0f;
|
|
|
|
// sum results and clamp limits
|
|
Vector3 vecNew = vecBase + RCC_VECTOR3(vecDamage);
|
|
vecNew.x = rage::Clamp(vecNew.x, -1.0f, 0.99f);
|
|
vecNew.y = rage::Clamp(vecNew.y, -1.0f, 0.99f);
|
|
vecNew.z = rage::Clamp(vecNew.z, -1.0f, 0.99f);
|
|
|
|
#if NEW_PACKING_CODE
|
|
FastAssert(tempResults.x == vecNew.x);
|
|
FastAssert(tempResults.y == vecNew.y);
|
|
FastAssert(tempResults.z == vecNew.z);
|
|
#endif
|
|
|
|
// scale back to -128 to 128
|
|
int nEntryX = int(vecNew.x * 128.0f + 128.0f);
|
|
int nEntryY = int(vecNew.y * 128.0f + 128.0f);
|
|
int nEntryZ = int(vecNew.z * 128.0f + 128.0f);
|
|
|
|
// pack the result back into the pixel
|
|
float result = Pack(nEntryX, nEntryY, nEntryZ);
|
|
#if NEW_PACKING_CODE
|
|
FastAssert(result == *pFloatEntry);
|
|
#else
|
|
*pFloatEntry = result;
|
|
#endif
|
|
#endif
|
|
|
|
#endif //GPU_DAMAGE_WRITE_ENABLED
|
|
}
|
|
#endif // !__PPU...
|
|
|
|
inline Vec4V_Out Get4MagnitudesOfVec2FromCenter(Vec2V_In v0, Vec2V_In v1, Vec2V_In v2, Vec2V_In v3, Vec2V_In center)
|
|
{
|
|
Vec2V vOffsetToCenter0 = v0 - center;
|
|
Vec2V vOffsetToCenter1 = v1 - center;
|
|
Vec2V vOffsetToCenter2 = v2 - center;
|
|
Vec2V vOffsetToCenter3 = v3 - center;
|
|
|
|
Vec4V magnitudes = Vec4V( MagFast(vOffsetToCenter0), MagFast(vOffsetToCenter1), MagFast(vOffsetToCenter2), MagFast(vOffsetToCenter3)); // could do these mag2 s component-wise in parallel too, not sure the appropriate calls
|
|
|
|
return magnitudes;
|
|
}
|
|
|
|
ScalarV_Out CVehicleDeformation::GetTexRange(const Vector3& vecOffset, const float fRadius, bool inPixels)
|
|
{
|
|
const Vec3V vVecOffset = RCC_VEC3V(vecOffset);
|
|
const ScalarV vRadius = ScalarVFromF32(fRadius);
|
|
// The clamping shouldn't be required as vecOffset has been normalized earlier,
|
|
// but we still somehow end up with values that are ever so slightly over 1.0f (say 1.00000012f) due to FP inaccuracies.
|
|
ScalarV damageAlpha = Arcsin(Clamp(vVecOffset.GetZ(), ScalarV(V_ZERO), ScalarV(V_ONE)));
|
|
ScalarV damageBeta = Arctan2(-vVecOffset.GetX(), vVecOffset.GetY());
|
|
|
|
ScalarV minAlpha = damageAlpha - vRadius;
|
|
ScalarV maxAlpha = damageAlpha + vRadius;
|
|
ScalarV minBeta = damageBeta - vRadius;
|
|
ScalarV maxBeta = damageBeta + vRadius;
|
|
|
|
Vec3V damageLimits;
|
|
Vec3V damageTexPos[5];
|
|
|
|
// centre position
|
|
damageTexPos[0] = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(vecOffset.xyzw, inPixels));
|
|
|
|
Vec4V sincosMinMaxAlpha = Vec4V(SinCos(minAlpha), SinCos(maxAlpha));
|
|
Vec4V sincosMinMaxBeta = Vec4V(SinCos(minBeta), SinCos(maxBeta));
|
|
|
|
// min alpha, min beta
|
|
damageLimits = Vec3V(sincosMinMaxAlpha.GetY() * -sincosMinMaxBeta.GetX(),
|
|
sincosMinMaxAlpha.GetY() * sincosMinMaxBeta.GetY(),
|
|
sincosMinMaxAlpha.GetX()
|
|
);
|
|
damageTexPos[1] = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(damageLimits.GetIntrin128(), inPixels));
|
|
|
|
// max alpha, min beta
|
|
damageLimits = Vec3V(sincosMinMaxAlpha.GetW() * -sincosMinMaxBeta.GetX(),
|
|
sincosMinMaxAlpha.GetW() * sincosMinMaxBeta.GetY(),
|
|
sincosMinMaxAlpha.GetZ()
|
|
);
|
|
damageTexPos[2] = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(damageLimits.GetIntrin128(), inPixels));
|
|
|
|
// min alpha, max beta
|
|
damageLimits = Vec3V(sincosMinMaxAlpha.GetY() * -sincosMinMaxBeta.GetZ(),
|
|
sincosMinMaxAlpha.GetY() * sincosMinMaxBeta.GetW(),
|
|
sincosMinMaxAlpha.GetX()
|
|
);
|
|
damageTexPos[3] = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(damageLimits.GetIntrin128(), inPixels));
|
|
|
|
// max alpha, max beta
|
|
damageLimits = Vec3V(sincosMinMaxAlpha.GetW() * -sincosMinMaxBeta.GetZ(),
|
|
sincosMinMaxAlpha.GetW() * sincosMinMaxBeta.GetW(),
|
|
sincosMinMaxAlpha.GetZ()
|
|
);
|
|
damageTexPos[4] = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(damageLimits.GetIntrin128(), inPixels));
|
|
|
|
Vec2V damageTexMinXY = damageTexPos[0].GetXY();
|
|
Vec2V damageTexMaxXY = damageTexPos[0].GetXY();
|
|
for(int i=1; i<5; i++)
|
|
{
|
|
damageTexMinXY = Min(damageTexMinXY, damageTexPos[i].GetXY());
|
|
damageTexMaxXY = Max(damageTexMaxXY, damageTexPos[i].GetXY());
|
|
}
|
|
|
|
ScalarV fTexRange = Max(damageTexPos[0].GetX() - damageTexMinXY.GetX(), damageTexMaxXY.GetX() - damageTexPos[0].GetX());
|
|
fTexRange = Max(fTexRange, damageTexPos[0].GetY() - damageTexMinXY.GetY());
|
|
fTexRange = Max(fTexRange, damageTexMaxXY.GetY() - damageTexPos[0].GetY());
|
|
|
|
return fTexRange;
|
|
}
|
|
|
|
void CVehicleDeformation::ApplyDamageToCircularArea(void* base, const Vector3& vecDamage, const Vector3& vecOffset, const float fRadius)
|
|
{
|
|
Assert(base);
|
|
|
|
if (base == NULL || vecDamage.IsZero())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const Vec3V vVecDamage = RCC_VEC3V(vecDamage);
|
|
|
|
ScalarV fTexRange = GetTexRange(vecOffset, fRadius, true);
|
|
ScalarV fTexRangeInv = Invert(fTexRange);
|
|
|
|
const Vec4V vTexMinMaxRanges = Vec4V(-fTexRange, fTexRange, -fTexRange, fTexRange);
|
|
Vec3V vDamageCenter = Vec3V(CVehicleDeformation::GetDamageTexCoordFromOffset(vecOffset.xyzw));
|
|
Vec4V vDamageTexMinMaxXY = Vec4V(Vec::V4Permute<Vec::X, Vec::X, Vec::Y, Vec::Y>(vDamageCenter.GetIntrin128()));
|
|
vDamageTexMinMaxXY = RoundToNearestIntZero(vDamageTexMinMaxXY + vTexMinMaxRanges);
|
|
vDamageTexMinMaxXY += Vec4V(V_Y_AXIS_WONE);
|
|
|
|
const Vec2V vDamageTexMin = Vec2V(vDamageTexMinMaxXY.GetX(), vDamageTexMinMaxXY.GetZ());
|
|
const Vec4V viDamageTexMinMaxXY = FloatToIntRaw<0>(vDamageTexMinMaxXY);
|
|
|
|
int iDamageTexMinX = viDamageTexMinMaxXY.GetXi();
|
|
int iDamageTexMaxX = viDamageTexMinMaxXY.GetYi();
|
|
int iDamageTexMinY = viDamageTexMinMaxXY.GetZi();
|
|
int iDamageTexMaxY = viDamageTexMinMaxXY.GetWi();
|
|
|
|
const ScalarV fDamageRange = Mag(vVecDamage);
|
|
const ScalarV noiseScale = BANK_SWITCH(ms_fVehDefColVarAddMax, VEHICLE_DEFORMATION_COLOR_VAR_ADD_MAX);
|
|
const ScalarV noiseFraction = Min(ScalarV(V_FLT_SMALL_1), noiseScale/fDamageRange);
|
|
const ScalarV noiseRange = Clamp(BANK_SWITCH(ms_fVehDefColVarMultMax, VEHICLE_DEFORMATION_COLOR_VAR_MULT_MAX) - BANK_SWITCH(ms_fVehDefColVarMultMin, VEHICLE_DEFORMATION_COLOR_VAR_MULT_MIN),
|
|
ScalarV(V_ZERO), ScalarV(V_ONE));
|
|
|
|
Vec2V fxy = vDamageTexMin;
|
|
Vec2V fxy1, fxy2, fxy3;
|
|
for(int y=iDamageTexMinY; y<=iDamageTexMaxY; y++, fxy += Vec2V(V_Y_AXIS_WZERO))
|
|
{
|
|
// Wrap the coordinate around if passes the boundary
|
|
int yWrap = rage::Wrap(y, 0, GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1);
|
|
fxy.SetX(vDamageTexMin.GetX());
|
|
for(int x=iDamageTexMinX; x<=iDamageTexMaxX; x+=4, fxy += Vec2V(ScalarV(V_FOUR), ScalarV(V_ZERO)))
|
|
{
|
|
fxy1 = fxy + Vec2V(V_X_AXIS_WZERO);
|
|
fxy2 = fxy1 + Vec2V(V_X_AXIS_WZERO);
|
|
fxy3 = fxy2 + Vec2V(V_X_AXIS_WZERO);
|
|
|
|
Vec4V vOffsetToCenterMags = Get4MagnitudesOfVec2FromCenter(fxy, fxy1, fxy2, fxy3, vDamageCenter.GetXY());
|
|
if(!IsGreaterThanOrEqualAll(vOffsetToCenterMags, Vec4V(fTexRange)))
|
|
{
|
|
Vec4V ratio = vOffsetToCenterMags * fTexRangeInv;
|
|
Vec4V damageMult = Vec4V(V_ONE) - ratio;
|
|
damageMult = Clamp(damageMult, Vec4V(V_ZERO), Vec4V(V_ONE));
|
|
|
|
#if VEHICLE_DEFORMATION_INVERSE_SQUARE_FIELD
|
|
damageMult *= damageMult;
|
|
#endif
|
|
|
|
Vec3V applyDamage[4] = {
|
|
vVecDamage * damageMult.GetX(),
|
|
vVecDamage * damageMult.GetY(),
|
|
vVecDamage * damageMult.GetZ(),
|
|
vVecDamage * damageMult.GetW()
|
|
};
|
|
Vec4V randomNoise = GetSmoothedPerlinNoise(x,y);
|
|
Vec4V noiseMult = (randomNoise * Vec4V(noiseRange)) + Vec4V(BANK_SWITCH(ms_fVehDefColVarMultMin, VEHICLE_DEFORMATION_COLOR_VAR_MULT_MIN));
|
|
Vec4V noiseToAdd = (randomNoise - Vec4V(V_HALF)) * Vec4V(noiseFraction);
|
|
noiseMult += noiseToAdd;
|
|
|
|
// Wrap the coordinate around if passes the boundary
|
|
int xWrap0 = rage::Wrap(x, 0, GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1);
|
|
int xWrap1 = rage::Wrap(x+1, 0, GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1);
|
|
int xWrap2 = rage::Wrap(x+2, 0, GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1);
|
|
int xWrap3 = rage::Wrap(x+3, 0, GTA_VEHICLE_DAMAGE_TEXTURE_SIZE-1);
|
|
AddToPixel(base, applyDamage[0] * noiseMult.GetX(), xWrap0, yWrap);
|
|
AddToPixel(base, applyDamage[1] * noiseMult.GetY(), xWrap1, yWrap);
|
|
AddToPixel(base, applyDamage[2] * noiseMult.GetZ(), xWrap2, yWrap);
|
|
AddToPixel(base, applyDamage[3] * noiseMult.GetW(), xWrap3, yWrap);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define WINDOW_DAMAGE_USE_COLLISION 1
|
|
void CVehicleDeformation::ApplyDamageToWindows(const Vector3& vecPos, const float fRadius)
|
|
{
|
|
CSubmarineHandling* subHandling = m_pParentVehicle->GetSubHandling();
|
|
if( ( m_pParentVehicle->InheritsFromSubmarine() ||
|
|
m_pParentVehicle->InheritsFromSubmarineCar() ) &&
|
|
!subHandling->IsUnderCrushDepth( m_pParentVehicle ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if WINDOW_DAMAGE_USE_COLLISION
|
|
// Go over all the windows and test of intersection with the deformation sphere
|
|
const fragInst* pFragInst = m_pParentVehicle->GetFragInst();
|
|
const phBound* pBound = pFragInst->GetArchetype()->GetBound();
|
|
|
|
if (pBound->GetType() == phBound::COMPOSITE)
|
|
{
|
|
const phBoundComposite* pBoundComposite = static_cast<const phBoundComposite*>(pBound);
|
|
|
|
for (int componentId = 0; componentId < pBoundComposite->GetNumBounds(); componentId++)
|
|
{
|
|
if (CVehicleGlassManager::IsComponentSmashableGlass(m_pParentVehicle, componentId))
|
|
{
|
|
const fragTypeChild* pFragChild = pFragInst->GetTypePhysics()->GetChild(componentId);
|
|
const int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragChild->GetBoneID());
|
|
|
|
if (boneIndex != -1)
|
|
{
|
|
// Do a sphere vs. box bound to see if the deformation effected this window
|
|
phBound* pWindowBound = pBoundComposite->GetBound(componentId);
|
|
Matrix34 toWorldMat;
|
|
m_pParentVehicle->GetGlobalMtx(boneIndex, toWorldMat);
|
|
|
|
TUNE_GROUP_FLOAT(VEHICLE_DAMAGE,WINDOW_COLLISION_AABB_MODIFICATION,-0.05f,-1.0f,1.0f,0.01f);
|
|
const Vec3V windowBoundAABBModification = Vec3VFromF32(WINDOW_COLLISION_AABB_MODIFICATION);
|
|
const Vec3V windowHalfExtents = Scale(pWindowBound->GetBoundingBoxSize(),ScalarV(V_HALF));
|
|
const Vec3V windowShrunkHalfExtents = Max(Add(windowHalfExtents,windowBoundAABBModification),Vec3V(V_ZERO));
|
|
const Vec3V windowCenter = Average(pWindowBound->GetBoundingBoxMax(),pWindowBound->GetBoundingBoxMin());
|
|
if(pWindowBound && geomBoxes::TestSphereToBox(RCC_VEC3V(vecPos), ScalarVFromF32(fRadius), Subtract(windowCenter,windowShrunkHalfExtents), Add(windowCenter,windowShrunkHalfExtents), toWorldMat))
|
|
{
|
|
// Window is hit - add a new collision to the glass system
|
|
VfxCollInfo_s vfxCollInfo;
|
|
vfxCollInfo.vPositionWld = Vec3V(V_FLT_MAX);
|
|
vfxCollInfo.vNormalWld = Vec3V(V_Z_AXIS_WZERO);
|
|
vfxCollInfo.vDirectionWld = Vec3V(V_Z_AXIS_WZERO);
|
|
vfxCollInfo.regdEnt = m_pParentVehicle;
|
|
vfxCollInfo.materialId = pWindowBound->GetMaterialId(0);
|
|
vfxCollInfo.componentId = componentId;
|
|
vfxCollInfo.force = 0.25f;
|
|
vfxCollInfo.weaponGroup = WEAPON_EFFECT_GROUP_EXPLOSION;
|
|
vfxCollInfo.isBloody = false;
|
|
vfxCollInfo.isWater = false;
|
|
vfxCollInfo.isExitFx = false;
|
|
vfxCollInfo.noPtFx = false;
|
|
vfxCollInfo.noPedDamage = false;
|
|
vfxCollInfo.noDecal = false;
|
|
vfxCollInfo.isSnowball = false;
|
|
vfxCollInfo.isFMJAmmo = false;
|
|
|
|
g_vehicleGlassMan.StoreCollision(vfxCollInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
// Notify the glass system about an explosion which will effect the windows that intersect with the sphere
|
|
VfxExpInfo_s vfxExpInfo;
|
|
vfxExpInfo.vPositionWld = RCC_VEC3V(vecPos);
|
|
vfxExpInfo.regdEnt = m_pParentVehicle;
|
|
vfxExpInfo.radius = fRadius / (GTA_VEHICLE_DAMAGE_DELTA_SCALE * GetBoundRadiusScaledInv());
|
|
vfxExpInfo.radius *= 1.2f; // Make the damage radius a bit bigger
|
|
vfxExpInfo.force = 0.5f;
|
|
vfxExpInfo.flags = 0; // Do not force the glass to detach
|
|
g_vehicleGlassMan.StoreExplosion(vfxExpInfo);
|
|
#endif
|
|
}
|
|
|
|
dev_float sfTakeLessDamageMult = 0.5f;
|
|
dev_float sfHitByPlayerMult = 2.0f;
|
|
|
|
#if __BANK
|
|
bool CVehicleDamage::ms_bNeverAvoidVehicleExplosionChainReactions = false;
|
|
bool CVehicleDamage::ms_bAlwaysAvoidVehicleExplosionChainReactions = false;
|
|
|
|
bool CVehicleDamage::ms_bUseVehicleExplosionBreakChanceMultiplierOverride = false;
|
|
float CVehicleDamage::ms_fVehicleExplosionBreakChanceMultiplierOverride = 0.0f;
|
|
#endif // BANK
|
|
|
|
bool CVehicleDamage::ms_bDisableVehiclePartCollisionOnBreak = false;
|
|
bool CVehicleDamage::ms_bDisableVehicleExplosionBreakOffParts = false;
|
|
|
|
float CVehicleDamage::ms_fArmorModMagnitude = 0.5f;
|
|
float CVehicleDamage::ms_fLooseLatchedDoorOpenAngle = 0.025f;
|
|
float CVehicleDamage::ms_fLooseLatchedBonnetOpenAngle = 0.05f;
|
|
bank_float bfFirstPersonViewDamageRadiusMult = 2.0f;
|
|
bank_float bfFirstPersonViewDamageDeltaMult = 1.5f;
|
|
bank_float bfDeformationThresholdMPMult = 2.0f;
|
|
bank_float bfDeformationThresholdAircraftMult = 5.0f;
|
|
dev_float dfDeformationThresholdGlassMult = 8.0f;
|
|
// called when an impact occurs
|
|
bool CVehicleDeformation::ApplyCollisionImpact(const Vector3& vecImpulseWorldSpace, const Vector3& vecPosWorldSpace, CEntity* pOtherEntity, bool bFullScaleDeformation, bool bShouldApplyDamageToGlass)
|
|
{
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
if(!m_pParentVehicle || !m_pParentVehicle->GetVehicleDamage() || !m_pParentVehicle->GetVehicleDamage()->CanVehicleBeDamagedBasedOnDriverInvincibility())
|
|
return false;
|
|
}
|
|
|
|
#if !__FINAL
|
|
if (gbStopVehiclesDamaging)
|
|
return false;
|
|
#endif
|
|
#if NO_VEH_DAMAGE_PARAM
|
|
if(bTestNoVehicleDeformation)
|
|
return false;
|
|
#endif
|
|
|
|
if(m_pParentVehicle==NULL)
|
|
return false;
|
|
|
|
if(!m_pParentVehicle->m_nVehicleFlags.bUseDeformation || !m_pParentVehicle->m_nVehicleFlags.bCanBeVisiblyDamaged)
|
|
return false;
|
|
|
|
// Do not deform the wrecked vehicle, as it will make the broken off parts' meshes differ from their bounds
|
|
if(m_pParentVehicle->GetStatus() == STATUS_WRECKED && !m_pParentVehicle->HasBoundUpdatePending())
|
|
return false;
|
|
|
|
Vector3 damageDelta = vecImpulseWorldSpace;
|
|
|
|
// script can reduce the damage taken by vehicles
|
|
if(m_pParentVehicle->m_nVehicleFlags.bTakeLessDamage)
|
|
{
|
|
damageDelta.Scale(sfTakeLessDamageMult);
|
|
}
|
|
// if hit by the player, do more damage.
|
|
else if(pOtherEntity && pOtherEntity->GetIsDynamic() && static_cast<CDynamicEntity*>(pOtherEntity)->GetStatus()==STATUS_PLAYER)
|
|
{
|
|
if(m_pParentVehicle->GetStatus()!=STATUS_PLAYER && !m_pParentVehicle->PopTypeIsMission())
|
|
damageDelta.Scale(sfHitByPlayerMult);
|
|
}
|
|
|
|
damageDelta = VEC3V_TO_VECTOR3(m_pParentVehicle->GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(damageDelta)));
|
|
|
|
Assert(damageDelta.x == damageDelta.x && damageDelta.y == damageDelta.y && damageDelta.z == damageDelta.z);
|
|
|
|
Vector3 objSpaceVecPos = VEC3V_TO_VECTOR3(m_pParentVehicle->GetTransform().UnTransform(VECTOR3_TO_VEC3V(vecPosWorldSpace)));
|
|
Vector3 damageOffset = objSpaceVecPos;
|
|
float damageOffsetOriginalLength = 0.0f;
|
|
if( Dot(damageOffset, damageOffset) > 0.00001f )
|
|
{
|
|
damageOffsetOriginalLength = damageOffset.Mag();
|
|
}
|
|
damageOffset.NormalizeSafe();
|
|
damageOffset.w = damageOffsetOriginalLength;
|
|
|
|
bool bPlayerFirstPersonView = m_pParentVehicle->GetStatus()==STATUS_PLAYER && m_pParentVehicle->GetDriver() && m_pParentVehicle->GetDriver()->IsPlayer() && camInterface::GetCinematicDirector().IsRenderingCinematicMountedCamera();
|
|
if(bPlayerFirstPersonView)
|
|
{
|
|
// scale the deformation only if it's a head on collision
|
|
bPlayerFirstPersonView = objSpaceVecPos.y > m_pParentVehicle->GetBoundingBoxMax().y * 0.9f
|
|
&& Abs(objSpaceVecPos.x) < m_pParentVehicle->GetBoundingBoxMax().x * 0.5f;
|
|
}
|
|
const float fMass = m_pParentVehicle->GetMass();
|
|
damageDelta *= m_pParentVehicle->pHandling->m_fDeformationDamageMult / fMass;
|
|
|
|
float armourMultiplier = m_pParentVehicle->GetVariationInstance().GetArmourDamageMultiplier();
|
|
damageDelta *= armourMultiplier;
|
|
|
|
#if VEHICLE_DEFORMATION_PROPORTIONAL
|
|
//Compensate for the fact that the shader is scaling up the damage magnitude by the size of the vehicle, so apply the inverse here. Typically in the [0.5, 1.0] range;
|
|
damageDelta *= m_pParentVehicle->GetVehicleDamage()->GetDeformation()->GetDamageMultiplierInv();
|
|
#endif
|
|
|
|
if(bPlayerFirstPersonView)
|
|
{
|
|
damageDelta *= bfFirstPersonViewDamageDeltaMult;
|
|
}
|
|
damageDelta.x *= ms_fVehDefColMultX;
|
|
damageDelta.y *= damageDelta.y < 0.0f ? ms_fVehDefColMultY : ms_fVehDefColMultYNeg;
|
|
if(damageOffset.z > m_pParentVehicle->GetBoundingBoxMax().z * 0.5f && damageDelta.z < 0.0f)
|
|
{
|
|
damageDelta.z *= ms_fVehDefRoofColMultZNeg;
|
|
}
|
|
else
|
|
{
|
|
damageDelta.z *= damageDelta.z < 0.0f ? ms_fVehDefColMultZ : ms_fVehDefColMultZNeg;
|
|
}
|
|
|
|
#if VEHICLE_DEFORMATION_SOFT_CAR
|
|
damageDelta *= damageMultiply;
|
|
#endif // VEHICLE_DEFORMATION_SOFT_CAR
|
|
|
|
float fDamageDeltaMagOrig = damageDelta.Mag();
|
|
float fDamageDeltaMag = fDamageDeltaMagOrig;
|
|
|
|
if(!bFullScaleDeformation)
|
|
{
|
|
//Always clamp the damage magnitude
|
|
if(damageDelta.x * damageOffset.x > 0.0f)
|
|
damageDelta.x = 0.0f;
|
|
if(damageDelta.y * damageOffset.y > 0.0f)
|
|
damageDelta.y = 0.0f;
|
|
if(damageDelta.z * damageOffset.z > 0.0f)
|
|
damageDelta.z = 0.0f;
|
|
|
|
fDamageDeltaMag = damageDelta.Mag();
|
|
float fDamageMag = fDamageDeltaMag;
|
|
if(fDamageMag > m_sfVehDefColMax1)
|
|
fDamageMag = m_sfVehDefColMax1 + m_sfVehDefColMult2*(fDamageMag - m_sfVehDefColMax1);
|
|
|
|
if(fDamageMag > m_sfVehDefColMax2)
|
|
fDamageMag = m_sfVehDefColMax2;
|
|
|
|
if(fDamageMag > GTA_VEHICLE_MIN_DAMAGE_RESOLUTION)
|
|
damageDelta.Scale(fDamageMag / fDamageDeltaMag);
|
|
|
|
fDamageDeltaMag = fDamageMag;
|
|
}
|
|
|
|
// if damage we wish to apply is smaller than the accuracy of what can be stored in the texture.
|
|
float fThresholdMulti = NetworkInterface::IsGameInProgress() ? bfDeformationThresholdMPMult : 1.0f;
|
|
if (m_pParentVehicle->InheritsFromPlane())
|
|
{
|
|
fThresholdMulti = Max(fThresholdMulti, bfDeformationThresholdAircraftMult);
|
|
}
|
|
if(rage::Abs(damageDelta.x) < (GTA_VEHICLE_MIN_DAMAGE_RESOLUTION * fThresholdMulti) && rage::Abs(damageDelta.y) < (GTA_VEHICLE_MIN_DAMAGE_RESOLUTION * fThresholdMulti) && rage::Abs(damageDelta.z) < (GTA_VEHICLE_MIN_DAMAGE_RESOLUTION * fThresholdMulti))
|
|
return false;
|
|
|
|
float damageRadius = fDamageDeltaMagOrig;
|
|
damageRadius *= sfVehDefColRadiusMult;
|
|
|
|
if(bPlayerFirstPersonView)
|
|
{
|
|
damageRadius *= bfFirstPersonViewDamageRadiusMult;
|
|
}
|
|
|
|
damageRadius = rage::Min(damageRadius, sfVehDefColRadiusMax);
|
|
|
|
if (m_pParentVehicle->InheritsFromPlane() || m_pParentVehicle->InheritsFromHeli())
|
|
{
|
|
damageRadius *= ms_fLargeVehicleRadiusMultiplier;
|
|
}
|
|
|
|
if( damageRadius < (DeformationRadiusThreshold * fThresholdMulti) )
|
|
{
|
|
// Too small : we're out
|
|
return false;
|
|
}
|
|
|
|
#if __BANK
|
|
if (CVehicleDamage::ms_bDisplayDamageVectors)
|
|
{
|
|
CVehicleDamage::DisplayDamageImpulse(vecImpulseWorldSpace, vecPosWorldSpace, m_pParentVehicle, damageRadius, false, 10);
|
|
}
|
|
#endif
|
|
|
|
// Flag the vehicle wheels about this damage, unless it was ped melee damage.
|
|
if(!pOtherEntity || !pOtherEntity->GetIsTypePed())
|
|
{
|
|
m_pParentVehicle->FlagWheelsWithDeformationImpact(VECTOR3_TO_VEC3V(damageOffset), ScalarVFromF32(damageRadius));
|
|
}
|
|
|
|
#if VEHICLE_DEFORMATION_TIMING
|
|
m_nImpactFound++;
|
|
#endif // VEHICLE_DEFORMATION_TIMING
|
|
|
|
int nStoreImpact = -1;
|
|
if(m_nImpactStoreIdx < VEHICLE_DEFORMATION_IMPACT_STORE_SIZE)
|
|
{
|
|
nStoreImpact = m_nImpactStoreIdx;
|
|
m_nImpactStoreIdx++;
|
|
}
|
|
else
|
|
{
|
|
float fSmallestMag2 = damageDelta.Mag2();
|
|
for(int nFindImpact=0; nFindImpact < m_nImpactStoreIdx; nFindImpact++)
|
|
{
|
|
if(m_ImpactStore[nFindImpact].damage.Mag32() < fSmallestMag2)
|
|
{
|
|
nStoreImpact = nFindImpact;
|
|
fSmallestMag2 = m_ImpactStore[nFindImpact].damage.Mag32();
|
|
}
|
|
}
|
|
}
|
|
|
|
// grab global damage map scale:
|
|
CVehicleModelInfo *pVMI = m_pParentVehicle->GetVehicleModelInfo();
|
|
Assert(pVMI);
|
|
float fDamageMapScale = pVMI ? pVMI->GetDamageMapScale() : 1.0f;
|
|
BANK_ONLY( if(CVehicleDeformation::ms_bForceDamageMapScale) { fDamageMapScale = CVehicleDeformation::ms_fForcedDamageMapScale; } )
|
|
damageDelta *= fDamageMapScale;
|
|
|
|
if(nStoreImpact > -1)
|
|
{
|
|
Assert(damageDelta.x==damageDelta.x && damageDelta.y==damageDelta.y && damageDelta.z==damageDelta.z);
|
|
m_ImpactStore[nStoreImpact].damage.SetVector3(damageDelta);
|
|
|
|
Assert(damageRadius==damageRadius);
|
|
m_ImpactStore[nStoreImpact].damage.w = damageRadius;
|
|
|
|
Assert(damageOffset.x==damageOffset.x && damageOffset.y==damageOffset.y && damageOffset.z==damageOffset.z);
|
|
m_ImpactStore[nStoreImpact].offset.SetVector3(damageOffset);
|
|
m_ImpactStore[nStoreImpact].offset.SetW(damageOffset.w);
|
|
|
|
if((fDamageDeltaMag > GTA_VEHICLE_MIN_DAMAGE_RESOLUTION * dfDeformationThresholdGlassMult) && bShouldApplyDamageToGlass)
|
|
{
|
|
ApplyDamageToWindows(vecPosWorldSpace, damageRadius);
|
|
}
|
|
|
|
// Disable spoiler if we get a large impact near the spoiler strut bones.
|
|
int nStrutsBone = m_pParentVehicle->GetBoneIndex(VEH_STRUTS);
|
|
if(nStrutsBone != -1)
|
|
{
|
|
Matrix34 matStruts;
|
|
m_pParentVehicle->GetGlobalMtx(nStrutsBone, matStruts);
|
|
|
|
float fDistSq = (matStruts.d - vecPosWorldSpace).Mag2();
|
|
|
|
TUNE_FLOAT(SPOILER_DAMAGE_THRESHOLD, 3.0f, 0.0f, 100.0f, 0.001f);
|
|
TUNE_FLOAT(SPOILER_DAMAGE_RADIUS, 2.0f, 0.0f, 100.0f, 0.001f);
|
|
const float fMaxSpoilerDamageRadiusSq = SPOILER_DAMAGE_RADIUS * SPOILER_DAMAGE_RADIUS;
|
|
|
|
if(fDistSq <= fMaxSpoilerDamageRadiusSq)
|
|
{
|
|
m_pParentVehicle->GetVehicleDamage()->GetDynamicSpoilerDamage() += fDamageDeltaMag;
|
|
if(m_pParentVehicle->GetVehicleDamage()->GetDynamicSpoilerDamage() >= SPOILER_DAMAGE_THRESHOLD)
|
|
{
|
|
for(int i = 0; i < m_pParentVehicle->GetNumberOfVehicleGadgets(); i++)
|
|
{
|
|
if(m_pParentVehicle->GetVehicleGadget(i)->GetType() == VGT_DYNAMIC_SPOILER)
|
|
{
|
|
((CVehicleGadgetDynamicSpoiler*)m_pParentVehicle->GetVehicleGadget(i))->SetDynamicSpoilerEnabled(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if GTA_REPLAY
|
|
if(CReplayMgr::ShouldRecord())
|
|
{
|
|
if( m_pParentVehicle->IsDriverAPlayer() )
|
|
{
|
|
CReplayMgr::RecordFx(CPacketVehDamageUpdate_PlayerVeh(m_ImpactStore[nStoreImpact].damage, m_ImpactStore[nStoreImpact].offset, m_pParentVehicle), m_pParentVehicle);
|
|
}
|
|
else
|
|
{
|
|
CReplayMgr::RecordFx(CPacketVehDamageUpdate(m_ImpactStore[nStoreImpact].damage, m_ImpactStore[nStoreImpact].offset, m_pParentVehicle), m_pParentVehicle);
|
|
}
|
|
}
|
|
#endif // GTA_REPLAY
|
|
}
|
|
|
|
return true; // Return true to indicate that we actually did some deformation.
|
|
}
|
|
|
|
|
|
//static const unsigned FRONT_LEFT_DAMAGE = 0;
|
|
//static const unsigned FRONT_RIGHT_DAMAGE = 1;
|
|
//static const unsigned MIDDLE_LEFT_DAMAGE = 2;
|
|
//static const unsigned MIDDLE_RIGHT_DAMAGE = 3;
|
|
//static const unsigned REAR_LEFT_DAMAGE = 4;
|
|
//static const unsigned REAR_RIGHT_DAMAGE = 5;
|
|
|
|
bank_float fDefaultRadiusDamage[3] = { 0.2f, 0.3f, 0.4f };
|
|
bank_float DamageLevelPct[3] = { 0.4f, 0.55f, 0.7f };
|
|
bank_float DamagePosition[CVehicleDeformation::NUM_NETWORK_DAMAGE_DIRECTIONS][2] = { { -1.0f, 1.0f },
|
|
{ 1.0f, 1.0f },
|
|
{ -1.0f, 0.0f },
|
|
{ 1.0f, 0.0f },
|
|
{ -1.0f, -1.0f },
|
|
{ 1.0f, -1.0f } };
|
|
bank_float randomXMin = 0.9f;
|
|
bank_float randomXMax = 1.2f;
|
|
|
|
bank_float randomYMin = 1.5f; // push it toward the front/back.
|
|
bank_float randomYMax = 3.0f;
|
|
|
|
bank_float randomZMin = -0.25f;
|
|
bank_float randomZMax = 0.25f;
|
|
|
|
void CVehicleDeformation::ApplyDeformationsFromNetwork(u32 damageLevel[CVehicleDeformation::NUM_NETWORK_DAMAGE_DIRECTIONS])
|
|
{
|
|
// override the network modifiers to match what was passed in
|
|
ResetDamage();
|
|
ClearStoredImpacts();
|
|
|
|
FastAssert( m_nImpactStoreIdx < VEHICLE_DEFORMATION_IMPACT_STORE_SIZE );
|
|
|
|
for(int i=0;i<6;i++)
|
|
{
|
|
Assert(damageLevel[i] <= 3);
|
|
damageLevel[i] = MIN(3, damageLevel[i]);
|
|
if( damageLevel[i] > 0 )
|
|
{
|
|
float fRandomX = fwRandom::GetRandomNumberInRange(randomXMin, randomXMax) * DamagePosition[i][0];
|
|
float fRandomY = fwRandom::GetRandomNumberInRange(randomYMin, randomYMax) * DamagePosition[i][1];
|
|
float fRandomZ = fwRandom::GetRandomNumberInRange(randomZMin, randomZMax);
|
|
|
|
Vector4 offset = Vector4(fRandomX, fRandomY, fRandomZ, 0.0f);
|
|
offset.Normalize3();
|
|
|
|
float damageIntensity = DamageLevelPct[damageLevel[i] - 1];
|
|
Vector4 damage = Vector4(-offset.x*damageIntensity, -offset.y*damageIntensity, -offset.z*damageIntensity, fDefaultRadiusDamage[damageLevel[i] - 1]);
|
|
|
|
m_ImpactStore[m_nImpactStoreIdx].damage = damage;
|
|
m_ImpactStore[m_nImpactStoreIdx].offset = offset;
|
|
m_nImpactStoreIdx++;
|
|
|
|
if(m_nImpactStoreIdx >= VEHICLE_DEFORMATION_IMPACT_STORE_SIZE)
|
|
{
|
|
ApplyDeformations(false, true);
|
|
ClearStoredImpacts();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_nImpactStoreIdx > 0)
|
|
{
|
|
ApplyDeformations(false, true);
|
|
ClearStoredImpacts();
|
|
}
|
|
|
|
// ensure the network damage levels are consistent, if they are not we set them to a value
|
|
// midway between the current and previous damage values. This stops the modifiers from
|
|
// going out of sync from the first tiny bump received.
|
|
for(int i=0;i<6;i++)
|
|
{
|
|
m_networkDamages[i] = (damageLevel[i] > 0) ? networkDamageModifiers[i][damageLevel[i]-1] + ((networkDamageModifiers[i][damageLevel[i]] - networkDamageModifiers[i][damageLevel[i]-1])*0.5f) : 0.0f;
|
|
}
|
|
|
|
// flag vehicle glass components as needing damage update
|
|
g_vehicleGlassMan.ApplyVehicleDamage(m_pParentVehicle);
|
|
}
|
|
|
|
#if __BANK
|
|
|
|
void CVehicleDeformation::ApplyDamageWithOffset(const Vector4 &damage, const Vector4 &offset)
|
|
{
|
|
m_ImpactStore[0].damage = damage;
|
|
m_ImpactStore[0].offset = offset;
|
|
m_ImpactStore[0].offset.Normalize();
|
|
m_ImpactStore[0].offset.w = offset.Mag();
|
|
m_nImpactStoreIdx = 1;
|
|
|
|
ApplyDeformations();
|
|
}
|
|
|
|
void CVehicleDeformation::RenderBank()
|
|
{
|
|
if (CVehicleDamage::ms_SourceVehicle && ms_bShowTextureAndRenderTarget)
|
|
{
|
|
grcTexture *const texture = CVehicleDamage::ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->GetDamageTexture();
|
|
|
|
if (texture)
|
|
{
|
|
Vector4 positionAndSize;
|
|
positionAndSize.z = (float)texture->GetWidth() * ms_fDisplayTextureScale;
|
|
positionAndSize.w = (float)texture->GetHeight() * ms_fDisplayTextureScale;
|
|
positionAndSize.x = 10.0f;//0.5f*((float)VideoResManager::GetNativeWidth() - positionAndSize.z);
|
|
positionAndSize.y = 10.0f;//0.5f*((float)VideoResManager::GetNativeHeight() - positionAndSize.w);
|
|
|
|
const float deviceWidth = float( VideoResManager::GetNativeWidth() );
|
|
const float deviceHeight = float( VideoResManager::GetNativeHeight() );
|
|
|
|
const float normX = positionAndSize.x / deviceWidth;
|
|
const float normWidth = positionAndSize.z / deviceWidth;
|
|
const float normY = positionAndSize.y / deviceHeight;
|
|
const float normHeight = positionAndSize.w / deviceHeight;
|
|
|
|
const float x1 = normX * 2.0f - 1.0f;
|
|
const float y1 = -(normY * 2.0f - 1.0f);
|
|
const float x2 = (normX + normWidth) * 2.0f - 1.0f;
|
|
const float y2 = -((normY + normHeight) * 2.0f - 1.0f);
|
|
|
|
grcStateBlock::SetBlendState(grcStateBlock::BS_Default);
|
|
grcStateBlock::SetDepthStencilState(grcStateBlock::DSS_IgnoreDepth);
|
|
grcStateBlock::SetRasterizerState(grcStateBlock::RS_NoBackfaceCull);
|
|
|
|
CSprite2d spriteBlit;
|
|
spriteBlit.BeginCustomList(CSprite2d::CS_SIGNED_AS_UNSIGNED, texture);
|
|
grcDrawSingleQuadf(
|
|
x1, y1, x2, y2,
|
|
0.0f,
|
|
0.0f, 0.0f, 1.0f, 1.0f,
|
|
Color32(255, 255, 255, 255));
|
|
spriteBlit.EndCustomList();
|
|
|
|
spriteBlit.SetTexture( static_cast<grcTexture*>(NULL) );
|
|
}
|
|
|
|
grcRenderTarget *const rt = CVehicleDamage::ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->GetDamageRenderTarget();
|
|
|
|
if (rt)
|
|
{
|
|
Vector4 positionAndSize;
|
|
positionAndSize.z = (float)rt->GetWidth() * ms_fDisplayTextureScale;
|
|
positionAndSize.w = (float)rt->GetHeight() * ms_fDisplayTextureScale;
|
|
positionAndSize.x = VideoResManager::GetNativeWidth() - positionAndSize.z - 10.0f;
|
|
positionAndSize.y = 10.0f;
|
|
|
|
const float deviceWidth = float( VideoResManager::GetNativeWidth() );
|
|
const float deviceHeight = float( VideoResManager::GetNativeHeight() );
|
|
|
|
const float normX = positionAndSize.x / deviceWidth;
|
|
const float normWidth = positionAndSize.z / deviceWidth;
|
|
const float normY = positionAndSize.y / deviceHeight;
|
|
const float normHeight = positionAndSize.w / deviceHeight;
|
|
|
|
const float x1 = normX * 2.0f - 1.0f;
|
|
const float y1 = -(normY * 2.0f - 1.0f);
|
|
const float x2 = (normX + normWidth) * 2.0f - 1.0f;
|
|
const float y2 = -((normY + normHeight) * 2.0f - 1.0f);
|
|
|
|
grcStateBlock::SetBlendState(grcStateBlock::BS_Default);
|
|
grcStateBlock::SetDepthStencilState(grcStateBlock::DSS_IgnoreDepth);
|
|
grcStateBlock::SetRasterizerState(grcStateBlock::RS_NoBackfaceCull);
|
|
|
|
CSprite2d spriteBlit;
|
|
spriteBlit.BeginCustomList(CSprite2d::CS_SIGNED_AS_UNSIGNED, rt);
|
|
grcDrawSingleQuadf(
|
|
x1, y1, x2, y2,
|
|
0.0f,
|
|
0.0f, 0.0f, 1.0f, 1.0f,
|
|
Color32(255, 255, 255, 255));
|
|
spriteBlit.EndCustomList();
|
|
|
|
spriteBlit.SetTexture( static_cast<grcTexture*>(NULL) );
|
|
}
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
grcTexture *const textureNext = CVehicleDamage::ms_SourceVehicle->GetVehicleDamage()->GetDeformation()->GetTexCache()->GetDamageDownloadTexture();
|
|
|
|
if (textureNext)
|
|
{
|
|
Vector4 positionAndSize;
|
|
positionAndSize.z = (float)rt->GetWidth() * ms_fDisplayTextureScale;
|
|
positionAndSize.w = (float)rt->GetHeight() * ms_fDisplayTextureScale;
|
|
positionAndSize.x = VideoResManager::GetNativeWidth() - positionAndSize.z - 10.0f;
|
|
positionAndSize.y = VideoResManager::GetNativeHeight() - positionAndSize.w - 10.0f;
|
|
|
|
const float deviceWidth = float( VideoResManager::GetNativeWidth() );
|
|
const float deviceHeight = float( VideoResManager::GetNativeHeight() );
|
|
|
|
const float normX = positionAndSize.x / deviceWidth;
|
|
const float normWidth = positionAndSize.z / deviceWidth;
|
|
const float normY = positionAndSize.y / deviceHeight;
|
|
const float normHeight = positionAndSize.w / deviceHeight;
|
|
|
|
const float x1 = normX * 2.0f - 1.0f;
|
|
const float y1 = -(normY * 2.0f - 1.0f);
|
|
const float x2 = (normX + normWidth) * 2.0f - 1.0f;
|
|
const float y2 = -((normY + normHeight) * 2.0f - 1.0f);
|
|
|
|
grcStateBlock::SetBlendState(grcStateBlock::BS_Default);
|
|
grcStateBlock::SetDepthStencilState(grcStateBlock::DSS_IgnoreDepth);
|
|
grcStateBlock::SetRasterizerState(grcStateBlock::RS_NoBackfaceCull);
|
|
|
|
CSprite2d spriteBlit;
|
|
spriteBlit.BeginCustomList(CSprite2d::CS_SIGNED_AS_UNSIGNED, textureNext);
|
|
grcDrawSingleQuadf(
|
|
x1, y1, x2, y2,
|
|
0.0f,
|
|
0.0f, 0.0f, 1.0f, 1.0f,
|
|
Color32(255, 255, 255, 255));
|
|
spriteBlit.EndCustomList();
|
|
|
|
spriteBlit.SetTexture( static_cast<grcTexture*>(NULL) );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if GTA_REPLAY
|
|
void CVehicleDeformation::SetNewImpactToStore(const Vector4& rDamage, const Vector4& rOffset)
|
|
{
|
|
// Do not deform the wrecked vehicle, as it will make the broken off parts' meshes differ from their bounds
|
|
if(m_pParentVehicle->GetStatus() == STATUS_WRECKED && !m_pParentVehicle->HasBoundUpdatePending())
|
|
return;
|
|
|
|
if(Verifyf(m_nImpactStoreIdx < VEHICLE_DEFORMATION_IMPACT_STORE_SIZE, "Run out of replay impact stores for vehicle damage."))
|
|
{
|
|
m_ImpactStore[m_nImpactStoreIdx].damage = rDamage;
|
|
m_ImpactStore[m_nImpactStoreIdx].offset = rOffset;
|
|
m_ImpactStore[m_nImpactStoreIdx].offset.w = 0.0f;
|
|
m_ImpactStore[m_nImpactStoreIdx].offset.Normalize();
|
|
m_nImpactStoreIdx++;
|
|
FastAssert(m_nImpactStoreIdx <= VEHICLE_DEFORMATION_IMPACT_STORE_SIZE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
void VehTexCacheEntry::IncrementQueuedDownload()
|
|
{
|
|
Assert(GRCDEVICE.IsMessagePumpThreadThatCreatedTheD3DDevice());
|
|
m_ActiveGPUIndex = (m_ActiveGPUIndex + 1) % VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE;
|
|
m_FrameToDownloadOn[m_ActiveGPUIndex] = GRCDEVICE.GetFrameCounter() + 2 * GRCDEVICE.GetGPUCount();
|
|
Assertf(m_ActiveCPUIndex != m_ActiveGPUIndex, "The vehicle damage texture queue has overrun itself");
|
|
}
|
|
|
|
grcTexture *VehTexCacheEntry::GetDownloadQueuedTexture()
|
|
{
|
|
Assert(GRCDEVICE.IsMessagePumpThreadThatCreatedTheD3DDevice());
|
|
Assertf(m_ActiveCPUIndex != m_ActiveGPUIndex, "The vehicle damage texture queue has overrun itself");
|
|
return m_DamageTextures[m_ActiveGPUIndex].GetTexture();
|
|
}
|
|
|
|
bool VehTexCacheEntry::IsDamageQueued()
|
|
{
|
|
return m_ActiveCPUIndex != m_ActiveGPUIndex;
|
|
}
|
|
|
|
bool VehTexCacheEntry::DamageQueuedIsReady()
|
|
{
|
|
Assert(GRCDEVICE.IsMessagePumpThreadThatCreatedTheD3DDevice());
|
|
int nextIndex = (m_ActiveCPUIndex + 1) % VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE;
|
|
return (m_FrameToDownloadOn[nextIndex] <= GRCDEVICE.GetFrameCounter());
|
|
}
|
|
|
|
grcTexture *VehTexCacheEntry::GetDamageDownloadTexture()
|
|
{
|
|
Assert(GRCDEVICE.IsMessagePumpThreadThatCreatedTheD3DDevice());
|
|
Assertf(m_ActiveCPUIndex != m_ActiveGPUIndex, "The vehicle damage texture queue has overrun itself");
|
|
int nextIndex = (m_ActiveCPUIndex + 1) % VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE; //ms_TextureCache[m_nTextureCacheIdx].m_Active;//
|
|
return m_DamageTextures[nextIndex].GetTexture();
|
|
}
|
|
|
|
void VehTexCacheEntry::AdvanceActiveTexture()
|
|
{
|
|
Assert(GRCDEVICE.IsMessagePumpThreadThatCreatedTheD3DDevice());
|
|
rage::sysIpcLockMutex(m_DamageMutex);
|
|
m_ActiveCPUIndex = (m_ActiveCPUIndex + 1) % VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE;
|
|
rage::sysIpcUnlockMutex(m_DamageMutex);
|
|
}
|
|
|
|
void VehTexCacheEntry::ClearDamageDownloads()
|
|
{
|
|
Assert(!GRCDEVICE.IsMessagePumpThreadThatCreatedTheD3DDevice());
|
|
rage::sysIpcLockMutex(m_DamageMutex);
|
|
m_ActiveCPUIndex = m_ActiveGPUIndex;
|
|
rage::sysIpcUnlockMutex(m_DamageMutex);
|
|
}
|
|
#endif //VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
|
|
void CVehicleDeformation::UpdateNetworkDamageLevels()
|
|
{
|
|
for(s32 index = 0; index < m_nImpactStoreIdx; index++)
|
|
{
|
|
// calculate which side of the car has been hit
|
|
float dotProductTop = m_ImpactStore[index].offset.Dot3(Vector3(0.0f, 1.0f, 0.0f));
|
|
float dotProductRight = m_ImpactStore[index].offset.Dot3(Vector3(1.0f, 0.0f, 0.0f));
|
|
|
|
int idx = FRONT_LEFT_DAMAGE;
|
|
|
|
if(dotProductTop >= 0.0f)
|
|
{
|
|
// front half of car
|
|
if(dotProductTop >= 0.5f)
|
|
{
|
|
if(dotProductRight < 0.0f)
|
|
{
|
|
// left of car
|
|
idx = FRONT_LEFT_DAMAGE;
|
|
}
|
|
else
|
|
{
|
|
// right of car
|
|
idx = FRONT_RIGHT_DAMAGE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(dotProductRight < 0.0f)
|
|
{
|
|
// left of car
|
|
idx = MIDDLE_LEFT_DAMAGE;
|
|
}
|
|
else
|
|
{
|
|
// right of car
|
|
idx = MIDDLE_RIGHT_DAMAGE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// rear half of car
|
|
if(-dotProductTop >= 0.5f)
|
|
{
|
|
if(dotProductRight < 0.0f)
|
|
{
|
|
// left of car
|
|
idx = REAR_LEFT_DAMAGE;
|
|
}
|
|
else
|
|
{
|
|
// right of car
|
|
idx = REAR_RIGHT_DAMAGE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(dotProductRight < 0.0f)
|
|
{
|
|
// left of car
|
|
idx = MIDDLE_LEFT_DAMAGE;
|
|
}
|
|
else
|
|
{
|
|
// right of car
|
|
idx = MIDDLE_RIGHT_DAMAGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_networkDamages[idx] += m_ImpactStore[index].damage.w;
|
|
}
|
|
}
|
|
|
|
dev_float fSmashLightDeformationThreshold = 0.02f;
|
|
dev_float fSmashSirenDeformationThreshold = 0.02f;
|
|
|
|
void CVehicleDeformation::ApplyDeformations(bool bBoundsNeedUpdating, bool UNUSED_PARAM(bNetworkDamageOnly))
|
|
{
|
|
#if ENABLE_APPLY_DAMAGE_PF
|
|
PF_FUNC(ApplyDeformations);
|
|
PF_INCREMENT(ApplyDeformations);
|
|
#endif // ENABLE_APPLY_DAMAGE_PF
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
if(!m_pParentVehicle || !m_pParentVehicle->GetVehicleDamage()
|
|
|| !(m_pParentVehicle->GetVehicleDamage()->CanVehicleBeDamagedBasedOnDriverInvincibility() || bBoundsNeedUpdating))// we still want to update the ground clearance
|
|
return;
|
|
}
|
|
|
|
#if !__FINAL
|
|
if (gbStopVehiclesDamaging)
|
|
return;
|
|
#endif
|
|
|
|
#if NO_VEH_DAMAGE_PARAM
|
|
if(bTestNoVehicleDeformation)
|
|
return;
|
|
#endif
|
|
|
|
if(!m_pParentVehicle)
|
|
return;
|
|
|
|
if(!m_nImpactStoreIdx && !bBoundsNeedUpdating)// If there has been a request for the bounds to change, just process the damage anyway
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENABLE_FRAG_OPTIMIZATION_ONLY(m_pParentVehicle->GiveFragCacheEntry(NetworkInterface::IsGameInProgress());)
|
|
|
|
if(!HasDamageTexture())
|
|
{
|
|
DamageTextureAllocate();
|
|
}
|
|
|
|
if(!HasDamageTexture())
|
|
{
|
|
// still no luck, bail out
|
|
return;
|
|
}
|
|
|
|
#if VEHICLE_DEFORMATION_TIMING
|
|
sysTimer DTT_timer;
|
|
u32 TimeStart = DTT_timer.GetSystemMsTime();
|
|
#endif // VEHICLE_DEFORMATION_TIMING
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
UpdateNetworkDamageLevels();
|
|
}
|
|
|
|
bool isCar = (m_pParentVehicle->GetVehicleType() == VEHICLE_TYPE_CAR);
|
|
bool hasRollCageMod = false;
|
|
|
|
if (isCar && m_pParentVehicle->IsModded())
|
|
{
|
|
const CVehicleVariationInstance& var = m_pParentVehicle->GetVariationInstance();
|
|
u32 chassisModEntry = var.GetMods()[VMT_CHASSIS];
|
|
bool dummy = false;
|
|
int index = var.GetModIndexForType(VMT_CHASSIS, m_pParentVehicle, dummy);
|
|
hasRollCageMod = (chassisModEntry != INVALID_MOD) && (index != -1);
|
|
}
|
|
|
|
bool HasLandingGear = false;
|
|
|
|
Vector3 vFrontLandingGearPos;
|
|
if(m_pParentVehicle->InheritsFromPlane() && m_pParentVehicle->GetDefaultBonePositionForSetup(LANDING_GEAR_F, vFrontLandingGearPos))
|
|
{
|
|
HasLandingGear = true;
|
|
vFrontLandingGearPos.y -= 0.5f; // subtract 0.5m for landing gear length
|
|
}
|
|
|
|
PF_PUSH_TIMEBAR_DETAIL("DrawDamage");
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
CApplyDamage::EnqueueDamage(m_pParentVehicle, false);
|
|
PF_POP_TIMEBAR_DETAIL();
|
|
|
|
// the HandleDamageAdded will be called in CVehicle::HandleDamageUpdatedByGPU if we have impacts
|
|
if(bBoundsNeedUpdating && m_nImpactStoreIdx == 0)
|
|
{
|
|
void* basePtr = LockDamageTexture(grcsRead);
|
|
if(basePtr)
|
|
{
|
|
HandleDamageAdded(basePtr, true, /*!bDeformationOnly*/true, true, true, true, /*bUpdateVehicleGlass*/true);
|
|
UnLockDamageTexture();
|
|
}
|
|
}
|
|
#else
|
|
void* basePtr = LockDamageTexture(grcsRead|grcsWrite);
|
|
if(basePtr)
|
|
{
|
|
for(int i=0; i < m_nImpactStoreIdx; i++)
|
|
{
|
|
const Vector3 damage(m_ImpactStore[i].damage.xyzw);
|
|
const Vector3 offset(m_ImpactStore[i].offset.xyzw);
|
|
const float radius = m_ImpactStore[i].damage.w;
|
|
|
|
ApplyDamageToCircularArea(basePtr, damage, offset, radius);
|
|
}
|
|
PF_POP_TIMEBAR_DETAIL();
|
|
|
|
HandleDamageAdded(basePtr, true, /*!bDeformationOnly*/true, true, true, true, /*bUpdateVehicleGlass*/true);
|
|
UnLockDamageTexture();
|
|
}
|
|
#endif
|
|
|
|
#if __BANK
|
|
if (ms_bAutoSaveDamageTexture GPU_VEHICLE_DAMAGE_ONLY(&& !CVehicleDamage::ms_bEnableGPUDamage))
|
|
{
|
|
SaveDamageTexture(m_pParentVehicle, false);
|
|
}
|
|
#endif
|
|
|
|
ClearStoredImpacts();
|
|
|
|
}
|
|
|
|
float CVehicleDeformation::GetDamageMultiplier(float* sizeMult) const
|
|
{
|
|
if (sizeMult)
|
|
{
|
|
*sizeMult = m_fSizeMultiplier;
|
|
}
|
|
|
|
return m_fDamageMultByVehicleSize;
|
|
}
|
|
|
|
void CVehicleDeformation::CalculateDamageMultiplier()
|
|
{
|
|
#if VEHICLE_DEFORMATION_PROPORTIONAL
|
|
|
|
fragInstGta* fragInstPtr = m_pParentVehicle ? m_pParentVehicle->GetVehicleFragInst() : NULL;
|
|
Assert(fragInstPtr);
|
|
|
|
if (!fragInstPtr)
|
|
{
|
|
m_fSizeMultiplier = 0.0f;
|
|
m_fDamageMultByVehicleSize = GTA_VEHICLE_DAMAGE_DELTA_SCALE;
|
|
m_fDamageMultByVehicleSizeInv = 1.0f / m_fDamageMultByVehicleSize;
|
|
return;
|
|
}
|
|
|
|
Vector3 boundingBoxMin = m_pParentVehicle->GetChassisBoundMin(false);
|
|
Vector3 boundingBoxMax = m_pParentVehicle->GetChassisBoundMax(false);
|
|
float vehicleLength = boundingBoxMax.y - boundingBoxMin.y;
|
|
Assert(vehicleLength > 0.1f);
|
|
|
|
m_fSizeMultiplier = Min(1.0f, (vehicleLength - MIN_DAMAGE_RANGE) / MAX_DAMAGE_RANGE);
|
|
m_fSizeMultiplier *= VEHICLE_DEFORMATION_PROPORTIONAL_SCALE_SLOPE;
|
|
m_fDamageMultByVehicleSize = 1.0f + m_fSizeMultiplier;
|
|
m_fDamageMultByVehicleSize *= ms_fDamageMagnitudeMult;
|
|
m_fDamageMultByVehicleSizeInv = 1.0f / m_fDamageMultByVehicleSize;
|
|
#else
|
|
m_fSizeMultiplier = 0.0f;
|
|
m_fDamageMultByVehicleSize = GTA_VEHICLE_DAMAGE_DELTA_SCALE;
|
|
m_fDamageMultByVehicleSizeInv = 1.0f / m_fDamageMultByVehicleSize;
|
|
#endif
|
|
|
|
}
|
|
|
|
void CVehicleDeformation::UpdateShaderDamageVars(bool bEnableDamage, float radius, float multiplier)
|
|
{
|
|
Assert(m_pParentVehicle != NULL);
|
|
Assert(m_pParentVehicle->GetDrawHandlerPtr() != NULL);
|
|
CCustomShaderEffectVehicle* pShader = static_cast<CCustomShaderEffectVehicle*>(m_pParentVehicle->GetDrawHandler().GetShaderEffect());
|
|
Assert(pShader);
|
|
|
|
if (pShader)
|
|
{
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
if (bEnableDamage && m_nTextureCacheIdx != -1)
|
|
{
|
|
pShader->SetEnableDamage(ms_TextureCache[m_nTextureCacheIdx].m_HasDamageDrawn);
|
|
}
|
|
else
|
|
{
|
|
pShader->SetEnableDamage(false);
|
|
}
|
|
#else
|
|
pShader->SetEnableDamage(bEnableDamage);
|
|
#endif
|
|
pShader->SetDamageMultiplier(multiplier);
|
|
pShader->SetBoundRadius(radius);
|
|
Vector4 vDeformedWheelOffsets[2];
|
|
vDeformedWheelOffsets[0].Zero();
|
|
vDeformedWheelOffsets[1].Zero();
|
|
if(CWheel *pWheel = m_pParentVehicle->GetWheelFromId(VEH_WHEEL_LF))
|
|
{
|
|
if(!pWheel->GetDynamicFlags().IsFlagSet(WF_BROKEN_OFF))
|
|
{
|
|
int iWheelBone = m_pParentVehicle->GetBoneIndex(VEH_WHEEL_LF);
|
|
if(iWheelBone > -1 && m_pParentVehicle->GetSkeletonData().GetBoneData(iWheelBone))
|
|
{
|
|
Vector3 vDefaultPos = VEC3V_TO_VECTOR3(m_pParentVehicle->GetSkeletonData().GetBoneData(iWheelBone)->GetDefaultTranslation());
|
|
float fDeformationSqr = geomDistances::Distance2SegToPoint(pWheel->GetProbeSegment().A, pWheel->GetProbeSegment().B - pWheel->GetProbeSegment().A, vDefaultPos);
|
|
if(fDeformationSqr > 0.0025f)
|
|
{
|
|
vDeformedWheelOffsets[0].SetVector3(pWheel->GetProbeSegment().A);
|
|
vDeformedWheelOffsets[0].SetW(pWheel->GetWheelRadius());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(CWheel *pWheel = m_pParentVehicle->GetWheelFromId(VEH_WHEEL_RF))
|
|
{
|
|
if(!pWheel->GetDynamicFlags().IsFlagSet(WF_BROKEN_OFF))
|
|
{
|
|
int iWheelBone = m_pParentVehicle->GetBoneIndex(VEH_WHEEL_RF);
|
|
if(iWheelBone > -1 && m_pParentVehicle->GetSkeletonData().GetBoneData(iWheelBone))
|
|
{
|
|
Vector3 vDefaultPos = VEC3V_TO_VECTOR3(m_pParentVehicle->GetSkeletonData().GetBoneData(iWheelBone)->GetDefaultTranslation());
|
|
float fDeformationSqr = geomDistances::Distance2SegToPoint(pWheel->GetProbeSegment().A, pWheel->GetProbeSegment().B - pWheel->GetProbeSegment().A, vDefaultPos);
|
|
if(fDeformationSqr > 0.0025f)
|
|
{
|
|
vDeformedWheelOffsets[1].SetVector3(pWheel->GetProbeSegment().A);
|
|
vDeformedWheelOffsets[1].SetW(pWheel->GetWheelRadius());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pShader->SetDamagedFrontWheelOffsets(vDeformedWheelOffsets[0], vDeformedWheelOffsets[1]);
|
|
|
|
if (bEnableDamage && m_nTextureCacheIdx != -1)
|
|
{
|
|
#if GPU_DAMAGE_WRITE_ENABLED && __D3D11
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
pShader->SetDamageTex(ms_TextureCache[m_nTextureCacheIdx].m_DamageRenderTarget.GetTexture());
|
|
#else
|
|
pShader->SetDamageTex(ms_TextureCache[m_nTextureCacheIdx].m_Payloads[1].GetTexture());
|
|
#endif
|
|
#else
|
|
pShader->SetDamageTex(ms_TextureCache[m_nTextureCacheIdx].m_Payload.GetTexture());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
pShader->SetDamageTex(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDeformation::HandleDamageAdded(void *basePtr, bool bEnableDamageShader, bool bUpdateBounds, bool bUpdateWheels, bool bUpdateAudio, bool bUpdateBones, bool bUpdateGlass)
|
|
{
|
|
if (!AssertVerify(m_pParentVehicle))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Assert(!CVehicleFactory::GetFactory()->IsVehInDestroyedCache(m_pParentVehicle));
|
|
|
|
CVehicleDamage* pVehDamage = m_pParentVehicle->GetVehicleDamage();
|
|
Assert(pVehDamage);
|
|
|
|
if(bUpdateBounds BANK_ONLY(&& (CVehicleDeformation::ms_bUpdateBoundsEnabled)) )
|
|
{
|
|
//Expensive
|
|
ApplyDamageToBound(basePtr);
|
|
}
|
|
|
|
// We apply the deformation on selected bones and break each parts depending on the result.
|
|
if(bUpdateAudio && !m_pParentVehicle->IsNetworkClone() && !m_pParentVehicle->m_nVehicleFlags.bUnbreakableLights BANK_ONLY(&& (CVehicleDeformation::ms_bUpdateBonesEnabled)))
|
|
{
|
|
audVehicleAudioEntity* audEntity = m_pParentVehicle->GetVehicleAudioEntity();
|
|
Assert(audEntity);
|
|
|
|
// Lights
|
|
for(int i=0; i<NUM_LIGHT_BONES; i++)
|
|
{
|
|
const int boneIdx = m_pParentVehicle->GetBoneIndex(aGlassBones[i]);
|
|
const bool lightState = pVehDamage->GetLightState(aGlassBones[i]);
|
|
if( boneIdx != -1 && false == lightState)
|
|
{
|
|
Vector3 vertPos;
|
|
m_pParentVehicle->GetDefaultBonePositionSimple(boneIdx,vertPos);
|
|
const Vector3 damage = VEC3V_TO_VECTOR3(ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vertPos)));
|
|
// We got damage, and an actual point
|
|
if( (vertPos.Mag2() > 0.0f) && (damage.Mag2() > fSmashLightDeformationThreshold * fSmashLightDeformationThreshold) )
|
|
{
|
|
pVehDamage->SetLightStateImmediate(i,true);
|
|
|
|
#if GTA_REPLAY
|
|
//don't do the break effect and audio if this is happening while the scene is precaching
|
|
if(CReplayMgr::IsPreCachingScene() == false)
|
|
#endif
|
|
{
|
|
// Use damage offset as normal ?
|
|
Vector3 normal = -damage;
|
|
normal.Normalize();
|
|
g_vfxVehicle.TriggerPtFxLightSmash(m_pParentVehicle,aGlassBones[i],RCC_VEC3V(vertPos),RCC_VEC3V(normal));
|
|
audEntity->TriggerHeadlightSmash(aGlassBones[i],vertPos);
|
|
audEntity->GetCollisionAudio().HeadLightSmash();
|
|
}
|
|
|
|
pVehDamage->SetUpdateLightBones(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extras
|
|
const CVehicleVariationInstance& variationInstance = m_pParentVehicle->GetVariationInstance();
|
|
if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX && variationInstance.GetVehicleRenderGfx())
|
|
{
|
|
for (u32 ii = VEH_EXTRALIGHT_1; ii <= VEH_EXTRALIGHT_4; ++ii)
|
|
{
|
|
const bool lightState = pVehDamage->GetLightState(ii);
|
|
if( false == lightState )
|
|
{
|
|
Vector3 vertPos;
|
|
int boneIdx = m_pParentVehicle->GetExtraLightPosition((eHierarchyId)ii, vertPos);
|
|
if( boneIdx != -1 )
|
|
{
|
|
const Vector3 damage = VEC3V_TO_VECTOR3(ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vertPos)));
|
|
|
|
// We got damage, and an actual point
|
|
if( (vertPos.Mag2() > 0.0f) && (damage.Mag2() > fSmashLightDeformationThreshold * fSmashLightDeformationThreshold) )
|
|
{
|
|
pVehDamage->SetLightState(ii,true);
|
|
|
|
#if GTA_REPLAY
|
|
//don't do the break effect and audio if this is happening while the scene is precaching
|
|
if(CReplayMgr::IsPreCachingScene() == false)
|
|
#endif
|
|
{
|
|
// Use damage offset as normal ?
|
|
Vector3 normal = -damage;
|
|
normal.Normalize();
|
|
g_vfxVehicle.TriggerPtFxLightSmash(m_pParentVehicle,ii,RCC_VEC3V(vertPos),RCC_VEC3V(normal));
|
|
audEntity->TriggerHeadlightSmash(ii,vertPos);
|
|
audEntity->GetCollisionAudio().HeadLightSmash();
|
|
}
|
|
|
|
pVehDamage->SetUpdateLightBones(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// sirens
|
|
if( true == m_pParentVehicle->UsesSiren())
|
|
{
|
|
for(int i=VEH_SIREN_1; i<VEH_SIREN_MAX+1; i++)
|
|
{
|
|
const bool sirenState = pVehDamage->GetSirenState(i);
|
|
const int boneIdx = m_pParentVehicle->GetBoneIndex((eHierarchyId)i);
|
|
|
|
if( (boneIdx != -1) && (false == sirenState) )
|
|
{
|
|
Vector3 vertPos;
|
|
m_pParentVehicle->GetDefaultBonePositionSimple(boneIdx,vertPos);
|
|
const Vector3 damage = VEC3V_TO_VECTOR3(ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vertPos)));
|
|
// We got damage, and an actual point
|
|
if( (vertPos.Mag2() > 0.0f) && (damage.Mag2() > fSmashSirenDeformationThreshold * fSmashSirenDeformationThreshold) )
|
|
{
|
|
pVehDamage->SetUpdateSirenBones(true);
|
|
pVehDamage->SetSirenState(i,true);
|
|
|
|
// No Fxs
|
|
// pop/break the glass casing
|
|
const int boneGlassID = m_pParentVehicle->GetBoneIndex((eHierarchyId)(i + (VEH_SIREN_GLASS_1-VEH_SIREN_1)));
|
|
const int component = m_pParentVehicle->GetVehicleFragInst()->GetComponentFromBoneIndex(boneGlassID);
|
|
if( component != -1 )
|
|
{
|
|
Vec3V vSmashNormal(V_Z_AXIS_WZERO);
|
|
g_vehicleGlassMan.SmashCollision(m_pParentVehicle,component, VEHICLEGLASSFORCE_SIREN_SMASH, vSmashNormal);
|
|
audEntity->SmashSiren();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bUpdateBones BANK_ONLY(&& (CVehicleDeformation::ms_bUpdateBonesEnabled)))
|
|
{
|
|
// vehicle type specific deformation stuff (e.g. moving rotors on heli's, seats in cars).
|
|
m_pParentVehicle->ApplyDeformationToBones(basePtr);
|
|
}
|
|
|
|
if (bUpdateGlass BANK_ONLY(&& (CVehicleDeformation::ms_bUpdateBonesEnabled))) //re-use the same bank variable
|
|
{
|
|
// flag vehicle glass components as needing damage update
|
|
g_vehicleGlassMan.ApplyVehicleDamage(m_pParentVehicle);
|
|
}
|
|
|
|
// Postpone the wheel deformation update as it has dependence with bone deformation update
|
|
if(bUpdateWheels && m_pParentVehicle->m_nVehicleFlags.bCanDeformWheels BANK_ONLY(&& (CVehicleDeformation::ms_bUpdateBonesEnabled)))
|
|
{
|
|
m_pParentVehicle->SetupWheels(basePtr);
|
|
}
|
|
|
|
UpdateShaderDamageVars(bEnableDamageShader);
|
|
}
|
|
|
|
void CVehicleDeformation::ResetDamage()
|
|
{
|
|
if (m_pParentVehicle==NULL)
|
|
return;
|
|
|
|
if (HasDamageTexture() == false)
|
|
return;
|
|
|
|
memset(m_networkDamages,0,sizeof(m_networkDamages));
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
if (CVehicleDamage::ms_bEnableGPUDamage)
|
|
{
|
|
CApplyDamage::ResetDamage(m_pParentVehicle);
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
if (m_nTextureCacheIdx >= 0)
|
|
{
|
|
ms_TextureCache[m_nTextureCacheIdx].m_HasDamageDrawn = false;
|
|
}
|
|
#endif //VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
}
|
|
#endif
|
|
|
|
|
|
#if USE_EDGE
|
|
void *basePtr = LockDamageTexture(grcsRead|grcsWrite);
|
|
if( basePtr )
|
|
{
|
|
float *pFloatEntry = (float *)basePtr;
|
|
sysMemSet(pFloatEntry, 0, GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH * GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT*sizeof(float));
|
|
HandleDamageAdded(basePtr, false, true, true, false, true, false);
|
|
UnLockDamageTexture();
|
|
}
|
|
return;
|
|
#else
|
|
|
|
void* basePtr = LockDamageTexture(grcsRead|grcsWrite);
|
|
if( basePtr )
|
|
{
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
sysMemSet((s8 *)basePtr, 0, GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH * GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT * 4 *
|
|
(VEHICLE_DEFORMATION_USE_HALF_FLOATS ? sizeof(u16) : sizeof(s8)));
|
|
#else
|
|
float *pFloatEntry = (float *)basePtr;
|
|
|
|
for(int i=0; i<GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH * GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT; i++)
|
|
{
|
|
// write zero into each pixel
|
|
*pFloatEntry = DAMAGE_ZERO;
|
|
pFloatEntry++;
|
|
}
|
|
#endif
|
|
|
|
HandleDamageAdded(basePtr, false, true, true, false, true, false);
|
|
UnLockDamageTexture();
|
|
}
|
|
#endif // USE_EDGE
|
|
}
|
|
|
|
#if !__FINAL && !__TOOL && !__RESOURCECOMPILER
|
|
// Hack to work around not linking the fragment cache allocator into the physics samples.
|
|
void ValidateFragCachePointer(const void * ptr, const char * ptrName)
|
|
{
|
|
// It's critical that this allocation comes out of the frag cache heap!
|
|
if (!AssertVerify(fragManager::GetFragCacheAllocator()->IsValidHeaderPointer(ptr)))
|
|
Quitf("INVALID %s pointer: %p. The game will crash soon!", ptrName, ptr);
|
|
}
|
|
#endif
|
|
|
|
void CVehicleDeformation::ApplyDamageToBound(const void* basePtr, const fragInst::ComponentBits* pOnlySubBoundGroups, const Vector3 *pSubBoundGroupsOffset) const
|
|
{
|
|
#if ENABLE_APPLY_DAMAGE_PF
|
|
PF_FUNC(ApplyDamage);
|
|
PF_INCREMENT(ApplyDamage);
|
|
#endif // ENABLE_APPLY_DAMAGE_PF
|
|
|
|
#if !__FINAL
|
|
if (gbStopVehiclesDamaging)
|
|
return;
|
|
#endif
|
|
#if NO_VEH_DAMAGE_PARAM
|
|
if(bTestNoVehicleDeformation)
|
|
return;
|
|
#endif
|
|
|
|
// needs to be a fragment, and have flag set to clone bound
|
|
if(m_pParentVehicle->GetVehicleFragInst()==NULL)
|
|
return;
|
|
|
|
m_pParentVehicle->CloneBounds();
|
|
|
|
fragInst* pFragInst = m_pParentVehicle->GetVehicleFragInst();
|
|
phBoundComposite* pCloneBound = (phBoundComposite* )pFragInst->GetArchetype()->GetBound();
|
|
phBoundComposite* pOrigBound = (phBoundComposite* )pFragInst->GetTypePhysics()->GetCompositeBounds();
|
|
if(pCloneBound == NULL || pOrigBound == NULL)
|
|
return;
|
|
|
|
Assert(pCloneBound->GetNumBounds() == pOrigBound->GetNumBounds());
|
|
|
|
float fOriginalBoundRadius = pFragInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid();
|
|
|
|
float fHeightAboveGround = 0.0f;
|
|
bool bModifyGroundClearance = false;
|
|
if(m_pParentVehicle->InheritsFromAutomobile() && !m_pParentVehicle->InheritsFromHeli() && m_pParentVehicle->GetModelIndex() != MI_DIGGER)
|
|
{
|
|
CAutomobile *pAutomobile = static_cast<CAutomobile*>(m_pParentVehicle);
|
|
//modify ground clearance depending on speed
|
|
fHeightAboveGround = -(pAutomobile->GetHeightAboveRoad() - pAutomobile->GetGroundClearance());
|
|
bModifyGroundClearance = true;
|
|
}
|
|
ScalarV vHeightAboveGround = ScalarVFromF32(fHeightAboveGround);
|
|
|
|
|
|
#if REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
|
|
const int needsUpdateListSize = pCloneBound->GetNumBounds();
|
|
phBoundGeometry ** needsUpdateList = Alloca(phBoundGeometry*,needsUpdateListSize);
|
|
int needsUpdateListCount = 0;
|
|
#endif // REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
|
|
|
|
{
|
|
// Grab the write lock to prevent shapetests on other threads from reading these geometry bounds during modification.
|
|
PHLOCK_SCOPEDWRITELOCK;
|
|
|
|
bool reduceVehicleFront = false;
|
|
if( bModifyGroundClearance &&
|
|
// NOTE: B*6928239 vehicels clip deeply in props in stuntmode at certain speed range ... so dont reduce front at all, even in stunt mode
|
|
0 && CPhysics::ms_bInStuntMode &&
|
|
!CPhysics::ms_bInArenaMode &&
|
|
!( m_pParentVehicle->pHandling->mFlags & MF_IS_RC ) &&
|
|
m_pParentVehicle->InheritsFromAutomobile() &&
|
|
static_cast<CAutomobile*>( m_pParentVehicle )->GetGroundClearance() > 0.0f )
|
|
{
|
|
reduceVehicleFront = true;
|
|
}
|
|
|
|
for(int i=0; i<pCloneBound->GetNumBounds(); i++)
|
|
{
|
|
if(pOnlySubBoundGroups)
|
|
{
|
|
fragTypeChild* child = pFragInst->GetTypePhysics()->GetChild(i);
|
|
int groupIndex = child->GetOwnerGroupPointerIndex();
|
|
|
|
if (pOnlySubBoundGroups->IsClear(groupIndex))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( m_pParentVehicle->InheritsFromTrailer() )
|
|
{
|
|
CTrailer* pTrailer = static_cast< CTrailer* >( m_pParentVehicle );
|
|
const CTrailerLegs* pTrailerLegs = pTrailer->GetTrailerLegs();
|
|
|
|
if( pTrailerLegs->GetFragChild() == i )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( m_pParentVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_OUTRIGGER_LEGS ) )
|
|
{
|
|
bool outriggerLeg = false;
|
|
for( int j = (int)VEH_LEG_FL; j <= (int)VEH_LEG_RR; j++ )
|
|
{
|
|
int boneIndex = m_pParentVehicle->GetBoneIndex( (eHierarchyId)j );
|
|
int group = pFragInst->GetGroupFromBoneIndex( boneIndex );
|
|
|
|
if( group > -1 )
|
|
{
|
|
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetGroup( group );
|
|
int childFragmentIndex = pGroup->GetChildFragmentIndex();
|
|
|
|
if( i == childFragmentIndex )
|
|
{
|
|
outriggerLeg = true;
|
|
}
|
|
}
|
|
}
|
|
if( outriggerLeg )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bool arenaWeapon = false;
|
|
|
|
if( m_pParentVehicle->GetMass() < 3499.0f )
|
|
{
|
|
for( int j = (int)VEH_FIRST_ARENA_MOD; j <= (int)VEH_LAST_ARENA_MOD; j++ )
|
|
{
|
|
int boneIndex = m_pParentVehicle->GetBoneIndex( (eHierarchyId)j );
|
|
int group = pFragInst->GetGroupFromBoneIndex( boneIndex );
|
|
|
|
if( group > -1 )
|
|
{
|
|
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetGroup( group );
|
|
int childFragmentIndex = pGroup->GetChildFragmentIndex();
|
|
|
|
if( i == childFragmentIndex )
|
|
{
|
|
arenaWeapon = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool rampBone = false;
|
|
for( int j = (int)VEH_FIRST_RAMP_MOD; j <= (int)VEH_LAST_RAMP_MOD; j++ )
|
|
{
|
|
int boneIndex = m_pParentVehicle->GetBoneIndex( (eHierarchyId)j );
|
|
int group = pFragInst->GetGroupFromBoneIndex( boneIndex );
|
|
|
|
if( group > -1 )
|
|
{
|
|
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetGroup( group );
|
|
int childFragmentIndex = pGroup->GetChildFragmentIndex();
|
|
|
|
if( i == childFragmentIndex )
|
|
{
|
|
rampBone = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( rampBone )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
if(pCloneBound->GetBound(i) && pCloneBound->GetBound(i)->GetType()==phBound::GEOMETRY
|
|
&& pOrigBound->GetBound(i) && pOrigBound->GetBound(i)->GetType()==phBound::GEOMETRY)
|
|
{
|
|
phBoundGeometry* pBoundGeom = (phBoundGeometry* )pCloneBound->GetBound(i);
|
|
phBoundGeometry* pOrigBoundGeom = (phBoundGeometry* )pOrigBound->GetBound(i);
|
|
|
|
Assert(pBoundGeom != pOrigBoundGeom);//make sure the bounds are clones of each other
|
|
|
|
int numVertices = pOrigBoundGeom->GetNumVertices();
|
|
Vector3* newVerts = Alloca(Vector3, numVertices);
|
|
Vector3* newShrunkVerts = NULL;
|
|
|
|
if (pOrigBoundGeom->GetShrunkVertexPointer() && pBoundGeom->GetShrunkVertexPointer())
|
|
{
|
|
newShrunkVerts = Alloca(Vector3, numVertices);
|
|
}
|
|
|
|
const Mat34V& boundMatrix = pCloneBound->GetCurrentMatrix(i);
|
|
|
|
// MN - check the num verts in the 2 bounds are equal as the smashable
|
|
// code may have removed a child bound and replaced it with a smashed bound
|
|
if (pBoundGeom->GetNumVertices()==numVertices)
|
|
{
|
|
bool damaged = false;
|
|
ScalarV maxDamageMagSquared = ScalarV(V_ZERO);
|
|
const Mat34V boundMat = pOrigBound->GetCurrentMatrix(i);
|
|
|
|
for(int vert = 0; vert < numVertices; vert++)
|
|
{
|
|
Vec3V vertPos = pOrigBoundGeom->GetVertex(vert);
|
|
Vec3V vecOffset = vertPos + boundMat.GetCol3();;
|
|
// Do not use interpolation for bound damage
|
|
const Vec3V damage = ReadFromVectorOffset(basePtr, vecOffset, false);
|
|
|
|
ScalarV damageMagSqaured = MagSquared(damage);
|
|
damaged |= ((IsGreaterThanAll(damageMagSqaured, ScalarV(V_ZERO))) != 0);
|
|
maxDamageMagSquared = Max(damageMagSqaured,maxDamageMagSquared);
|
|
|
|
vertPos += damage;
|
|
|
|
if(pOnlySubBoundGroups && pSubBoundGroupsOffset)
|
|
{
|
|
vertPos += RCC_VEC3V(*pSubBoundGroupsOffset);
|
|
}
|
|
|
|
newVerts[vert] = RCC_VECTOR3(vertPos);
|
|
|
|
// also need to deform the shrunk geometry vertices
|
|
if(newShrunkVerts)
|
|
{
|
|
// Use the same offset as the unshrunk vertices since they will only differ by a a few percent. The deformation is towards the center of the vehicle
|
|
// scaled by the distance to the center. The shrunk and unshrunk vertices are close enough together that their deformation vectors should be nearly identical.
|
|
|
|
const Vec3V shrunkVertPos = pOrigBoundGeom->GetShrunkVertex(vert);
|
|
Vec3V newShrunkVertPos = shrunkVertPos + damage;
|
|
|
|
if(pOnlySubBoundGroups && pSubBoundGroupsOffset)
|
|
{
|
|
newShrunkVertPos += RCC_VEC3V(*pSubBoundGroupsOffset);
|
|
}
|
|
|
|
// Only modify bounds which will actually collide with the map (we might break them off and don't want
|
|
// them in the munged shape).
|
|
if(bModifyGroundClearance && (pCloneBound->GetIncludeFlags(i)&ArchetypeFlags::GTA_MAP_TYPE_VEHICLE || pCloneBound->GetIncludeFlags(i)&ArchetypeFlags::GTA_OBJECT_TYPE))
|
|
{
|
|
//Sort out the ground clearance
|
|
ScalarV heightLimit = -boundMatrix.GetCol3().GetZ();
|
|
|
|
if( arenaWeapon )
|
|
{
|
|
if( m_pParentVehicle->InheritsFromAutomobile() && !m_pParentVehicle->InheritsFromHeli() && m_pParentVehicle->GetModelIndex() != MI_DIGGER )
|
|
{
|
|
static dev_float sfArenaModeWeaponHeightScale = 0.7f;
|
|
CAutomobile *pAutomobile = static_cast<CAutomobile*>( m_pParentVehicle );
|
|
// we don't want to raise the bounds of the arena mode weapons as much as other bounds but we do still want to raise them
|
|
heightLimit += ScalarV( -( pAutomobile->GetHeightAboveRoad() - ( pAutomobile->GetGroundClearance() * sfArenaModeWeaponHeightScale ) ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
heightLimit += vHeightAboveGround;
|
|
}
|
|
|
|
if(IsLessThanAll(newShrunkVertPos.GetZ(), heightLimit))
|
|
{
|
|
newShrunkVertPos.SetZ(heightLimit);
|
|
}
|
|
|
|
if( reduceVehicleFront )
|
|
{
|
|
const CWheel* pWheel = m_pParentVehicle->GetWheelFromId( VEH_WHEEL_LF );
|
|
|
|
if( pWheel )
|
|
{
|
|
int boneIndex = m_pParentVehicle->GetBoneIndex( VEH_WHEEL_LF );
|
|
Matrix34 wheelMtx = m_pParentVehicle->GetLocalMtx( boneIndex );
|
|
|
|
static float lengthLimitIncrease = 0.075f;
|
|
const float boundOffset = boundMatrix.GetCol3().GetYf();
|
|
const float lengthLimit = wheelMtx.d.GetY() + pWheel->GetWheelRadius() + lengthLimitIncrease - boundOffset;
|
|
|
|
if( newShrunkVertPos.GetYf() > lengthLimit )
|
|
{
|
|
newShrunkVertPos.SetY( lengthLimit );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
newShrunkVerts[vert] = RCC_VECTOR3(newShrunkVertPos);
|
|
}
|
|
}
|
|
|
|
// Compute new bounding box for the bound, so that verts can move outside of the box they start with
|
|
pBoundGeom->CalculateBoundingBox(newVerts, newShrunkVerts);
|
|
|
|
#if COMPRESSED_VERTEX_METHOD > 0
|
|
pBoundGeom->CalculateQuantizationValues();
|
|
#endif
|
|
|
|
for(int vert = 0; vert < numVertices; ++vert)
|
|
{
|
|
pBoundGeom->SetVertex(vert, RCC_VEC3V(newVerts[vert]));
|
|
}
|
|
bool shrunkVertsChanged = false;
|
|
if(newShrunkVerts)
|
|
{
|
|
for(int vert = 0; vert < numVertices; ++vert)
|
|
{
|
|
shrunkVertsChanged |= pBoundGeom->SetShrunkVertex(vert, RCC_VEC3V(newShrunkVerts[vert]));
|
|
}
|
|
}
|
|
|
|
pBoundGeom->CalculatePolyNormals();
|
|
|
|
#if REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
|
|
if(shrunkVertsChanged)
|
|
{
|
|
Assert(needsUpdateListCount < needsUpdateListSize);
|
|
needsUpdateList[needsUpdateListCount] = pBoundGeom;
|
|
needsUpdateListCount++;
|
|
}
|
|
#endif // REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
|
|
|
|
if( damaged )
|
|
{ // This was damaged, do we need to do something about it ?
|
|
phMaterialMgr::Id matid = PGTAMATERIALMGR->UnpackMtlId(pOrigBoundGeom->GetMaterialId(0));
|
|
bool glassBound = ( PGTAMATERIALMGR->GetIsSmashableGlass(matid) && !m_pParentVehicle->HasBulletResistantGlass());
|
|
if( glassBound )
|
|
{
|
|
TUNE_GROUP_FLOAT(VEHICLE_DAMAGE,MIN_DEFORM_MAG_TO_SMASH_GLASS,0.001f,0.0f,1.0f,0.001f);
|
|
|
|
BANK_SWITCH(const, static const) ScalarV vMIN_DEFORM_MAG_TO_SMASH_GLASS = ScalarVFromF32(MIN_DEFORM_MAG_TO_SMASH_GLASS);
|
|
BANK_SWITCH(const, static const) ScalarV vMIN_DEFORM_MAG_TO_SMASH_GLASS_SQR = vMIN_DEFORM_MAG_TO_SMASH_GLASS * vMIN_DEFORM_MAG_TO_SMASH_GLASS;
|
|
|
|
if(IsGreaterThanAll(maxDamageMagSquared, vMIN_DEFORM_MAG_TO_SMASH_GLASS_SQR))
|
|
{
|
|
Vec3V normal(V_Z_AXIS_WZERO);
|
|
|
|
if(pOrigBoundGeom->GetNumPolygons() > 0)
|
|
{
|
|
Vec3V normalAccum(V_ZERO);
|
|
for( int j=0;j<pOrigBoundGeom->GetNumPolygons();j++)
|
|
{
|
|
normalAccum += pOrigBoundGeom->GetPolygonUnitNormal(j);
|
|
}
|
|
|
|
normal = m_pParentVehicle->GetTransform().Transform3x3(NormalizeSafe(normalAccum, normal));
|
|
}
|
|
|
|
vehicleDebugf3("CVehicleDeformation::ApplyDamageToBound - Smashing window with %f deform mag. Threshold = %f.",sqrt(maxDamageMagSquared.Getf()),MIN_DEFORM_MAG_TO_SMASH_GLASS);
|
|
g_vehicleGlassMan.SmashCollision(m_pParentVehicle,i, VEHICLEGLASSFORCE_WINDOW_DEFORM, normal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
|
|
if (needsUpdateListCount > 0)
|
|
{
|
|
#if ENABLE_APPLY_DAMAGE_PF
|
|
PF_FUNC(RecomputeOctantMap);
|
|
PF_INCREMENTBY(RecomputeOctantMap,needsUpdateListCount);
|
|
#endif // ENABLE_APPLY_DAMAGE_PF
|
|
//sysTimer timer;
|
|
fragMemStartCacheHeapFunc(pFragInst->GetCacheEntry());
|
|
{
|
|
for (int i = 0 ; i < needsUpdateListCount ; i++)
|
|
needsUpdateList[i]->RecomputeOctantMap();
|
|
}
|
|
fragMemEndCacheHeap();
|
|
//float time = timer.GetUsTime();
|
|
//Displayf("Took %f to regenerate octant maps for %d bounds.",time,needsUpdateListCount);
|
|
}
|
|
#endif // REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
|
|
}
|
|
|
|
// recalculate bounding box and sphere for the composite bound
|
|
pCloneBound->CalculateCompositeExtents();
|
|
if(!m_pParentVehicle->IsColliderArticulated())
|
|
{
|
|
PHLEVEL->UpdateCompositeBvh(pFragInst->GetLevelIndex());
|
|
}
|
|
|
|
// we have probably changed the bound radius of the fragInst so need to tell the level that's changed if it has.
|
|
if(pFragInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid() != fOriginalBoundRadius)
|
|
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(pFragInst->GetLevelIndex(),(Mat34V_Ptr)(NULL));
|
|
|
|
#if 1
|
|
fragCacheEntry* entry = m_pParentVehicle->GetFragInst()->GetCacheEntry();
|
|
ENABLE_FRAG_OPTIMIZATION_ONLY(if( entry ))
|
|
{
|
|
fragHierarchyInst* pFragHierInst = entry->GetHierInst();
|
|
if( pFragHierInst && pFragHierInst->envCloth )
|
|
{
|
|
phVerletCloth* pCloth = pFragHierInst->envCloth->GetCloth();
|
|
Assert( pCloth );
|
|
|
|
phClothData& clothData = pCloth->GetClothData();
|
|
if( clothData.GetPrevPositionVertexCount() )
|
|
{
|
|
Vec3V* RESTRICT pinVerts = clothData.GetPinVertexPointer();
|
|
Vec3V* RESTRICT damageOffsets = clothData.GetVertexPrevPointer();
|
|
#if NO_PIN_VERTS_IN_VERLET
|
|
int numPinVerts = clothData.GetNumPinVerts();
|
|
#else
|
|
int numPinVerts = pCloth->GetPinCount();
|
|
#endif
|
|
for(int i = 0; i < numPinVerts; ++i )
|
|
{
|
|
damageOffsets[i] = ReadFromVectorOffset(basePtr, pinVerts[i], false);
|
|
clothDebugf1(" Cloth vehicle damage: %f %f %f ", damageOffsets[i].GetXf(), damageOffsets[i].GetYf(), damageOffsets[i].GetZf() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // 0
|
|
}
|
|
|
|
|
|
void CVehicleDeformation::DamageTextureAllocate(bool bResetDamage /* = true */)
|
|
{
|
|
CCustomShaderEffectVehicle* pShader = m_pParentVehicle ? static_cast<CCustomShaderEffectVehicle*>(m_pParentVehicle->GetDrawHandler().GetShaderEffect()) : NULL;
|
|
if (!m_pParentVehicle || !Verifyf(pShader, "Failed to find shader for damage"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if VEHICLE_DAMAGE_ON
|
|
const int basePriority = GetTexCachePriority();
|
|
int minPriority = basePriority;
|
|
int entryIdx = -1;
|
|
|
|
const int cacheSize = GetTextureCacheSize();
|
|
for(int i=0;i<cacheSize;i++)
|
|
{
|
|
if( ms_TextureCache[i].m_pOwner == NULL )
|
|
{
|
|
entryIdx = i;
|
|
break;
|
|
}
|
|
|
|
if( ms_TextureCache[i].m_nPriority < minPriority )
|
|
{
|
|
minPriority = ms_TextureCache[i].m_nPriority;
|
|
entryIdx = i;
|
|
// No break : we're looking for the lowest priority possible.
|
|
}
|
|
}
|
|
|
|
if(entryIdx != -1)
|
|
{
|
|
if(ms_TextureCache[entryIdx].m_pOwner)
|
|
{ // release from previous owner:
|
|
CVehicleDeformation::FreeEntry(ms_TextureCache[entryIdx]);
|
|
}
|
|
|
|
ms_TextureCache[entryIdx].m_pOwner = this;
|
|
ms_TextureCache[entryIdx].m_nPriority = basePriority;
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED && __D3D11
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
VehTexCacheEntry &entry = (ms_TextureCache[entryIdx]);
|
|
pShader->SetDamageTex(entry.m_DamageTextures[entry.m_ActiveCPUIndex].GetTexture());
|
|
#else
|
|
pShader->SetDamageTex(ms_TextureCache[entryIdx].m_Payloads[0].GetTexture());
|
|
#endif
|
|
#else
|
|
pShader->SetDamageTex(ms_TextureCache[entryIdx].m_Payload.GetTexture());
|
|
#endif
|
|
|
|
m_nTextureCacheIdx = entryIdx;
|
|
m_pParentVehicle->CloneBounds();
|
|
|
|
if (bResetDamage)
|
|
{
|
|
ResetDamage();
|
|
}
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
else
|
|
{
|
|
ms_TextureCache[entryIdx].m_HasDamageDrawn = true;
|
|
}
|
|
#endif
|
|
}
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
void CVehicleDeformation::DamageTextureFree()
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
if (m_pParentVehicle && HasDamageTexture())
|
|
{
|
|
Assert(m_nTextureCacheIdx != -1);
|
|
CVehicleDeformation::FreeEntry(ms_TextureCache[m_nTextureCacheIdx]);
|
|
}
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
#define TC_PRIORITY_DEFAULT 0x00000001
|
|
#define TC_PRIORITY_MISSION 0x00000002
|
|
#define TC_PRIORITY_OWNEDPLAYER 0x00000004
|
|
#define TC_PRIORITY_PLAYER 0x00000008
|
|
|
|
int CVehicleDeformation::GetTexCachePriority()
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
s32 priority = TC_PRIORITY_DEFAULT;
|
|
|
|
if(!m_pParentVehicle)
|
|
return priority;
|
|
|
|
if (m_pParentVehicle->GetDriver())
|
|
{
|
|
if( m_pParentVehicle->GetDriver()->PopTypeIsMission() )
|
|
{
|
|
priority |= TC_PRIORITY_MISSION;
|
|
}
|
|
else if ( m_pParentVehicle->GetDriver()->IsPlayer() )
|
|
{
|
|
priority |= TC_PRIORITY_PLAYER;
|
|
}
|
|
}
|
|
|
|
// Test for status, but isn't the driver test enough ?
|
|
if( m_pParentVehicle->GetStatus() == STATUS_PLAYER )
|
|
{
|
|
priority |= TC_PRIORITY_PLAYER;
|
|
}
|
|
|
|
if( m_pParentVehicle->PopTypeIsMission() )
|
|
{
|
|
priority |= TC_PRIORITY_MISSION;
|
|
}
|
|
|
|
if( m_pParentVehicle->m_nVehicleFlags.bHasBeenOwnedByPlayer == true )
|
|
{
|
|
priority |= TC_PRIORITY_OWNEDPLAYER;
|
|
}
|
|
|
|
return priority;
|
|
#else // VEHICLE_DAMAGE_ON
|
|
return 0;
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
void CVehicleDeformation::FreeEntry(VehTexCacheEntry &
|
|
#if VEHICLE_DAMAGE_ON
|
|
entry
|
|
#endif
|
|
)
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
Assert(entry.m_pOwner);
|
|
//entry.m_pOwner->m_bSwitchOn = false; // The damage code will turn it on, but we need to turn it off manually.
|
|
//entry.m_pOwner->m_pDamageTexture = NULL;
|
|
|
|
CVehicle* pVehicle = entry.m_pOwner->m_pParentVehicle;
|
|
Assert(pVehicle);
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
CPhysics::RemoveVehicleDeformation(pVehicle);
|
|
CApplyDamage::RemoveVehicleDeformation(pVehicle);
|
|
pVehicle->SetDamageUpdatedByGPU(false, false);
|
|
#endif
|
|
|
|
CCustomShaderEffectVehicle* pShader = static_cast<CCustomShaderEffectVehicle*>(pVehicle->GetDrawHandler().GetShaderEffect());
|
|
Assert(pShader);
|
|
|
|
pShader->SetEnableDamage(false);
|
|
pShader->SetDamageTex(NULL);
|
|
|
|
if (fragInst* inst = pVehicle->GetFragInst())
|
|
{
|
|
if (fragCacheEntry* entry = inst->GetCacheEntry())
|
|
{
|
|
entry->DecloneBoundParts();
|
|
}
|
|
}
|
|
|
|
entry.m_pOwner->m_nTextureCacheIdx = -1;
|
|
entry.m_pOwner = NULL;
|
|
entry.m_nPriority = -1;
|
|
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
static u32 ConfigureTextureCacheSize(float m)
|
|
{
|
|
float multiplier = m;
|
|
#if __PS3 || __XENON
|
|
switch (CPauseMenu::GetMenuPreference(PREF_CURRENT_LANGUAGE))
|
|
{
|
|
case LANGUAGE_KOREAN:
|
|
multiplier *= 0.75f;
|
|
case LANGUAGE_JAPANESE:
|
|
multiplier *= 0.50f;
|
|
case LANGUAGE_CHINESE:
|
|
case LANGUAGE_CHINESE_SIMPLIFIED:
|
|
multiplier *= 0.25f;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
u32 cacheSize = u32(VEHICLE_DEFORMATION_TEXTURE_CACHE_SIZE_INIT * multiplier);
|
|
cacheSize = rage::Max((s32)cacheSize, 4);
|
|
return cacheSize;
|
|
}
|
|
|
|
grcTexture* CVehicleDeformation::CreateDamageTexture(int width, int height)
|
|
{
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
grcTextureFactory::TextureCreateParams param(grcTextureFactory::TextureCreateParams::STAGING,
|
|
grcTextureFactory::TextureCreateParams::LINEAR, grcsRead, NULL,
|
|
grcTextureFactory::TextureCreateParams::RENDERTARGET, grcTextureFactory::TextureCreateParams::MSAA_NONE);
|
|
|
|
#if RSG_DURANGO
|
|
grcTexture* texture = grcTextureFactory::GetInstance().Create(width, height, grctfA8B8G8R8_SNORM, NULL, 1U, ¶m);
|
|
|
|
grcTextureLock textureLock;
|
|
texture->LockRect(0, 0, textureLock, grcsWrite);
|
|
sysMemSet((s8 *)textureLock.Base, 0, width * height * sizeof(s8) * 4);
|
|
texture->UnlockRect(textureLock);
|
|
#elif !RSG_PC
|
|
sysMemStartTemp();
|
|
TexelValue_R8G8B8A8_SNORM* buffer = rage_new TexelValue_R8G8B8A8_SNORM[width * height];
|
|
sysMemEndTemp();
|
|
|
|
BANK_ONLY(grcTexture::SetCustomLoadName("CVehicleDeformation::CreateDamageTexture");)
|
|
sysMemSet((s8 *)buffer, 0, width * height * sizeof(s8) * 4);
|
|
grcTexture* texture = grcTextureFactory::GetInstance().Create(width, height, grctfA8B8G8R8_SNORM, buffer, 1U, ¶m);
|
|
BANK_ONLY(grcTexture::SetCustomLoadName(NULL);)
|
|
|
|
sysMemStartTemp();
|
|
delete [] buffer;
|
|
sysMemEndTemp();
|
|
#else
|
|
grcTexture* texture = grcTextureFactory::GetInstance().Create(width, height,
|
|
VEHICLE_DEFORMATION_USE_HALF_FLOATS ? grctfA16B16G16R16F : grctfA8B8G8R8_SNORM, NULL, 1U, ¶m);
|
|
#endif // RSG_DURANGO
|
|
|
|
#else // GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
#if VEHICLEDAMAGE_USE_LINEAR_TEXTURE
|
|
grcTextureFactory::TextureCreateParams param(grcTextureFactory::TextureCreateParams::SYSTEM, grcTextureFactory::TextureCreateParams::LINEAR);
|
|
#else
|
|
grcTextureFactory::TextureCreateParams param(grcTextureFactory::TextureCreateParams::SYSTEM, grcTextureFactory::TextureCreateParams::TILED);
|
|
#endif
|
|
|
|
sysMemStartTemp();
|
|
float* buffer = rage_new float[width * height];
|
|
sysMemEndTemp();
|
|
|
|
sysMemSet(buffer, 0, width * height * sizeof(float));
|
|
|
|
#if RSG_PS3
|
|
u32 format = (u32)grcImage::R32F;
|
|
#elif RSG_XENON
|
|
|
|
#if VEHICLEDAMAGE_USE_LINEAR_TEXTURE
|
|
u32 format = (u32)MAKELINFMT(D3DFMT_R32F);
|
|
#else
|
|
u32 format = (u32)D3DFMT_R32F;
|
|
#endif
|
|
|
|
#else
|
|
u32 format = (u32)grctfR32F;
|
|
#endif
|
|
|
|
grcTexture* texture = grcTextureFactory::GetInstance().Create(width, height, format, buffer, 1U, ¶m);
|
|
|
|
#if !RSG_XENON
|
|
sysMemStartTemp();
|
|
delete [] buffer;
|
|
sysMemEndTemp();
|
|
#endif
|
|
|
|
#endif // GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
return texture;
|
|
}
|
|
|
|
|
|
grcRenderTarget* CVehicleDeformation::CreateDamageRenderTarget(grcTexture* texture, int width, int height, int index, bool lockable)
|
|
{
|
|
const char* baseName = (height > 1) ? "DamageRT_" : "BoundsRT_";
|
|
size_t digitLength = (index == 0) ? 1 : (size_t)log10((double)index) + 1;
|
|
size_t characterCount = strlen(baseName) + digitLength + 1; // null terminated
|
|
char* targetName = rage_new char[characterCount];
|
|
targetName[characterCount - 1] = 0;
|
|
|
|
sprintf(targetName, "%s%d", baseName, index);
|
|
|
|
grcRenderTarget* renderTarget = NULL;
|
|
|
|
if (texture)
|
|
{
|
|
grcTextureObject* texObj = texture->GetTexturePtr();
|
|
Assert(texObj);
|
|
|
|
renderTarget = grcTextureFactory::GetInstance().CreateRenderTarget(targetName, texObj);
|
|
Assert(renderTarget);
|
|
delete targetName;
|
|
return renderTarget;
|
|
}
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
# if VEHICLE_DEFORMATION_USE_HALF_FLOATS
|
|
grcTextureFormat textureFormat = grctfA16B16G16R16F;
|
|
int bitsPerPixel = 64;
|
|
# else
|
|
grcTextureFormat textureFormat = grctfA8B8G8R8_SNORM;
|
|
int bitsPerPixel = 32;
|
|
# endif
|
|
#else
|
|
grcTextureFormat textureFormat = grctfR32F;
|
|
int bitsPerPixel = 32;
|
|
#endif //GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
grcRenderTargetType type = grcrtPermanent;
|
|
|
|
grcTextureFactory::CreateParams params;
|
|
params.UseFloat = true;
|
|
params.Multisample = 0;
|
|
params.HasParent = true;
|
|
params.Parent = NULL;
|
|
params.IsResolvable = true;
|
|
params.IsRenderable = true;
|
|
params.UseHierZ = false;
|
|
params.Format = textureFormat;
|
|
params.Lockable = lockable;
|
|
|
|
#if RSG_ORBIS
|
|
params.ForceNoTiling = true;
|
|
params.EnableFastClear = true;
|
|
#endif
|
|
|
|
grcRenderTarget* rt = grcTextureFactory::GetInstance().CreateRenderTarget(targetName, type, width, height, bitsPerPixel, ¶ms);
|
|
delete targetName;
|
|
return rt;
|
|
}
|
|
|
|
void CVehicleDeformation::TexCacheInit(unsigned /*initMode*/)
|
|
{
|
|
TexCacheBuild(false);
|
|
}
|
|
|
|
void CVehicleDeformation::TexCacheBuild(bool forMp)
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
int width = GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH;
|
|
int height = GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT;
|
|
|
|
sysMemUseMemoryBucket b(MEMBUCKET_RENDER);
|
|
|
|
#if RSG_PC || RSG_DURANGO || RSG_ORBIS
|
|
(void)forMp;
|
|
float multiplier = 1.f;
|
|
#else
|
|
float multiplier = forMp ? 0.5f : 1.f;
|
|
#endif
|
|
|
|
int nTextureCacheSize = ConfigureTextureCacheSize(multiplier);
|
|
|
|
Assert(ms_TextureCache.GetCapacity()==0);
|
|
ms_TextureCache.Reset();
|
|
ms_TextureCache.Reserve(nTextureCacheSize);
|
|
ms_TextureCache.Resize(nTextureCacheSize);
|
|
|
|
const int cacheSize = GetTextureCacheSize();
|
|
const int bufferCount = 1;
|
|
|
|
for(int i=0; i < cacheSize; i++)
|
|
{
|
|
// create a texture from the image
|
|
ms_TextureCache[i].m_nPriority = -1;
|
|
ms_TextureCache[i].m_pOwner = NULL;
|
|
|
|
for(int bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++)
|
|
{
|
|
grcTexture* texture = NULL;
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
|
|
# if RSG_ORBIS
|
|
texture = CreateDamageRenderTarget(NULL, width, height, i, true);
|
|
ms_TextureCache[i].m_Payload.SetTexture(texture);
|
|
# elif __D3D11
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
ms_TextureCache[i].m_HasDamageDrawn = false;
|
|
ms_TextureCache[i].m_ActiveCPUIndex = 0;
|
|
ms_TextureCache[i].m_ActiveGPUIndex = 0;
|
|
ms_TextureCache[i].m_DamageMutex = rage::sysIpcCreateMutex();
|
|
|
|
ZeroMemory(ms_TextureCache[i].m_FrameToDownloadOn, sizeof(int) *VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE);
|
|
|
|
for (long index = 0; index < VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE; index++)
|
|
{
|
|
texture = CreateDamageTexture(width, height);
|
|
ms_TextureCache[i].m_DamageTextures[index].SetTexture(texture);
|
|
}
|
|
#else
|
|
texture = CreateDamageTexture(width, height);
|
|
ms_TextureCache[i].m_Payloads[0].SetTexture(texture);
|
|
#endif
|
|
|
|
bool bSeparate = false;
|
|
# if RSG_PC
|
|
bSeparate = CApplyDamage::UseSeparateRenderTarget();
|
|
# endif //RSG_PC
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
grcRenderTarget* copyRT = CreateDamageRenderTarget(NULL, width, height, i, false);
|
|
ms_TextureCache[i].m_DamageRenderTarget.SetTexture(copyRT);
|
|
#else
|
|
grcRenderTarget* copyRT = CreateDamageRenderTarget(bSeparate ? NULL : texture, width, height, i, !bSeparate);
|
|
ms_TextureCache[i].m_Payloads[1].SetTexture(copyRT);
|
|
#endif
|
|
# else
|
|
texture = CreateDamageTexture(width, height);
|
|
ms_TextureCache[i].m_Payload.SetTexture(texture);
|
|
# endif // platforms
|
|
|
|
#else // GPU_DAMAGE_WRITE_ENABLED
|
|
texture = CreateDamageTexture(width, height);
|
|
ms_TextureCache[i].m_Payload.SetTexture(texture);
|
|
|
|
void* basePtr = NULL;
|
|
VehTexCacheEntry* entry = &(ms_TextureCache[i]);
|
|
|
|
# if GPU_DAMAGE_WRITE_ENABLED && __D3D11
|
|
basePtr = entry->m_Payloads[0].AcquireBasePtr(grcsRead|grcsWrite);
|
|
# else
|
|
basePtr = entry->m_Payload.AcquireBasePtr(grcsRead|grcsWrite);
|
|
# endif
|
|
|
|
// assume texture is non-defragmentable (as created in MainRam)
|
|
// in case it is, then it's necessary register this ptr with defrag system
|
|
Assert( pgBase::IsTrackedAddress(texture)==false );
|
|
|
|
if( basePtr )
|
|
{
|
|
# if GPU_DAMAGE_WRITE_ENABLED
|
|
sysMemSet((s8 *)basePtr, 0, GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH * GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT * sizeof(s8) * 4);
|
|
# else
|
|
float *pFloatEntry = (float *)basePtr;
|
|
sysMemSet(pFloatEntry, 0, GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH * GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT*sizeof(float));
|
|
# endif
|
|
}
|
|
|
|
# if GPU_DAMAGE_WRITE_ENABLED && __D3D11
|
|
entry->m_Payloads[0].ReleaseBasePtr();
|
|
# else
|
|
entry->m_Payload.ReleaseBasePtr();
|
|
# endif
|
|
#endif // GPU_DAMAGE_WRITE_ENABLED
|
|
}
|
|
}
|
|
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
void CVehicleDeformation::TexCacheUpdate()
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
const int cacheSize = GetTextureCacheSize();
|
|
for(int i=0;i<cacheSize;i++)
|
|
{
|
|
if( ms_TextureCache[i].m_pOwner )
|
|
{
|
|
ms_TextureCache[i].m_nPriority = ms_TextureCache[i].m_pOwner->GetTexCachePriority();
|
|
}
|
|
}
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
void CVehicleDeformation::TexCacheShutdown(unsigned /*shutdownMode*/)
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
Assert(GetTextureCacheSize() > 0);
|
|
|
|
const int cacheSize = GetTextureCacheSize();
|
|
for(int i=0;i<cacheSize;i++)
|
|
{
|
|
Assert(ms_TextureCache[i].m_pOwner == NULL);
|
|
// create a texture from the image
|
|
ms_TextureCache[i].m_nPriority = -1;
|
|
ms_TextureCache[i].m_pOwner = NULL;
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED && __D3D11
|
|
#if VEHICLE_ROLLING_TEXTURE_ARRAY
|
|
for (long index = 0; index < VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE; index++)
|
|
{
|
|
ms_TextureCache[i].m_DamageTextures[index].GetTexture()->Release();
|
|
ms_TextureCache[i].m_DamageTextures[index].ClearTexture();
|
|
}
|
|
ms_TextureCache[i].m_DamageRenderTarget.GetTexture()->Release();
|
|
ms_TextureCache[i].m_DamageRenderTarget.ClearTexture();
|
|
sysIpcDeleteMutex(ms_TextureCache[i].m_DamageMutex);
|
|
ms_TextureCache[i].m_ActiveCPUIndex = 0;
|
|
ms_TextureCache[i].m_ActiveGPUIndex = 0;
|
|
ZeroMemory(ms_TextureCache[i].m_FrameToDownloadOn, sizeof(int) *VEHICLE_DEFORMATION_TEXTURE_ARRAY_SIZE);
|
|
|
|
#else
|
|
ms_TextureCache[i].m_Payloads[0].GetTexture()->Release();
|
|
ms_TextureCache[i].m_Payloads[0].ClearTexture();
|
|
|
|
ms_TextureCache[i].m_Payloads[1].GetTexture()->Release();
|
|
ms_TextureCache[i].m_Payloads[1].ClearTexture();
|
|
#endif
|
|
#else
|
|
ms_TextureCache[i].m_Payload.GetTexture()->Release();
|
|
ms_TextureCache[i].m_Payload.ClearTexture();
|
|
#endif
|
|
}
|
|
|
|
ms_TextureCache.Reset();
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
#if __BANK
|
|
void CVehicleDeformation::DisplayTexCacheStats()
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
static dev_s32 StartX = 100;
|
|
static dev_s32 StartY = 10;
|
|
static int maxUsage = 0;
|
|
static int hit = 0;
|
|
static int accum = 0;
|
|
|
|
int y = StartY;
|
|
int usage = 0;
|
|
|
|
char debugText[50];
|
|
|
|
const int cacheSize = GetTextureCacheSize();
|
|
for(int i=0;i<cacheSize;i++)
|
|
{
|
|
sprintf(debugText,"Slot %2d: %p pri: %2d",i, ms_TextureCache[i].m_pOwner, ms_TextureCache[i].m_nPriority);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
if( ms_TextureCache[i].m_pOwner )
|
|
{
|
|
usage++;
|
|
}
|
|
}
|
|
y++;
|
|
|
|
if( usage > maxUsage )
|
|
{
|
|
maxUsage = usage;
|
|
}
|
|
|
|
hit++;
|
|
accum += usage;
|
|
|
|
sprintf(debugText,"Usage avg: %2d max: %2d",(s32)((float)accum/(float)hit), maxUsage);
|
|
grcDebugDraw::PrintToScreenCoors(debugText,StartX,y);
|
|
y++;
|
|
#endif // VEHICLE_DAMAGE_ON
|
|
}
|
|
|
|
void CVehicleDeformation::ClearTexCache()
|
|
{
|
|
#if VEHICLE_DAMAGE_ON
|
|
const int cacheSize = GetTextureCacheSize();
|
|
for(int i=0;i<cacheSize;i++)
|
|
{
|
|
if( ms_TextureCache[i].m_pOwner )
|
|
{
|
|
CVehicleDeformation::FreeEntry(ms_TextureCache[i]);
|
|
}
|
|
}
|
|
#endif //VEHICLE_DAMAGE_ON...
|
|
}
|
|
#endif // __BANK...
|
|
|
|
float CVehicleDamage::ms_fPetrolTankFireBurnRateMin = 60.0f;
|
|
float CVehicleDamage::ms_fPetrolTankFireBurnRateMax = 150.0f;
|
|
|
|
bank_float CVehicleDamage::sfVehDamPetrolTankApplyGunDamageMult = 3.0f;
|
|
bank_float CVehicleDamage::sfVehDamPetrolTankApplyGunDamageMultAI = 1.0f;
|
|
bank_float CVehicleDamage::ms_fChanceToBreak = 0.30f;
|
|
dev_float sfVehDamPetrolTankApplyFireDamageMult = 0.5f;
|
|
dev_float sfWeaponDrivebyForceDamageFrac = 0.7f;
|
|
dev_float sfUpsideDownEngineDamageMult = 2.0f;
|
|
|
|
float CVehicleDamage::ms_fBreakingPartsReduction = 0.0f;
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
bool CVehicleDamage::ms_bEnableGPUDamage = true;
|
|
#if __BANK
|
|
int CVehicleDamage::ms_iForcedDamageEveryFrame = 0;
|
|
#endif
|
|
|
|
#endif
|
|
|
|
CVehicleDamage::CVehicleDamage()
|
|
{
|
|
m_pParent = NULL;
|
|
m_fBodyHealth = VEH_DAMAGE_HEALTH_STD;
|
|
m_fPetrolTankHealth = VEH_DAMAGE_HEALTH_STD;
|
|
m_fBodyDamageScale = 1.0f;
|
|
m_fPetrolTankDamageScale = 1.0f;
|
|
m_fCollisionWithMapDamageScale = 1.0f;
|
|
|
|
m_fPetrolTankLevel = VEH_DAMAGE_PETROL_LEVEL_STD;
|
|
m_fOilLevel = VEH_DAMAGE_OIL_LEVEL_STD;
|
|
|
|
m_pEntityThatSetUsOnFire = NULL;
|
|
m_vecOldMoveSpeed.Zero();
|
|
m_vecOldTurnSpeed.Zero();
|
|
m_UpdateLightBones = false;
|
|
m_UpdateSirenBones = false;
|
|
m_fCountDownToTimeAnotherPartCanBreakOff = 0.0f;
|
|
m_vPetrolSprayPosLocal.Zero();
|
|
m_vPetrolSprayNrmLocal.Zero();
|
|
m_bHasHandBrake = true;
|
|
m_bDisableDamageWithPickedUpEntity = false;
|
|
m_fPadShakeIntensity = 0.0f;
|
|
m_uPadShakeDuration = 0;
|
|
m_uBlowUpCarPartsPending = Break_Off_Car_Parts_Immediately;
|
|
|
|
m_fDynamicSpoilerDamage = 0.0f;
|
|
m_fScriptDamageScale = 1.0f;
|
|
m_fScriptWeaponDamageScale = 1.0f;
|
|
|
|
ResetStuckCheck();
|
|
}
|
|
|
|
CVehicleDamage::~CVehicleDamage()
|
|
{
|
|
}
|
|
|
|
dev_float VEHICLE_EXPLODE_IMPULSE_THRESHOLD = 15.0f; // Mas normalised (so basically v delta t)
|
|
dev_float VEHICLE_EXPLODE_UPRIGHT_ANGLE_COS = 0.0f; // Angles are from vertical. 0.707 = cos(45deg)
|
|
dev_float VEHICLE_EXPLODE_NORMAL_ANGLE_COS = 0.707f;
|
|
dev_float VEHICLE_EXPLODE_WHEN_ON_FIRE_IMPACT = 2.0f;
|
|
dev_float VEHICLE_EXPLODE_AGAINST_TRAIN_IMPULSE_THRESHOLD = 25.0f;
|
|
|
|
dev_float sfHeliCrashThreshold = 4.5f;
|
|
dev_float sfHeliCrashNoRotorsThreshold = 0.5f;
|
|
dev_float sfHeliCrashOccupentHealthMult = 10.0f;
|
|
dev_float sfHeliCrashExplode = 25.0f;
|
|
|
|
void CVehicleDamage::Init(CVehicle* pParentVehicle)
|
|
{
|
|
m_fBodyHealth = GetDefaultBodyHealthMax(pParentVehicle);
|
|
m_bHasHandBrake = pParentVehicle->pHandling ? !(pParentVehicle->pHandling->hFlags & HF_NO_HANDBRAKE) && (pParentVehicle->GetVehicleType() == VEHICLE_TYPE_CAR || pParentVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE || pParentVehicle->GetVehicleType() == VEHICLE_TYPE_QUADBIKE || pParentVehicle->GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE)
|
|
: false;
|
|
|
|
SetParent(pParentVehicle);
|
|
GetDeformation()->Init(pParentVehicle);
|
|
RefillOilAndPetrolTanks();
|
|
}
|
|
|
|
void CVehicleDamage::Process(float fTimeStep, bool bBoundsNeedUpdating)
|
|
{
|
|
#if GTA_REPLAY
|
|
if(CReplayMgr::IsEditModeActive())
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
PrefetchObject(this); // This is a large-ish object and we'll be updating various parts of it in this function, so prefetch the whole thing first
|
|
|
|
const CCollisionHistory * pColHistory = m_pParent->GetFrameCollisionHistory();
|
|
const CCollisionRecord * pColRecord = pColHistory->GetMostSignificantCollisionRecord();
|
|
bool bIsSuperDummy = m_pParent->IsSuperDummy();
|
|
Vector3 vMyCollisionPos;
|
|
if(pColRecord)
|
|
{
|
|
const Matrix34& myMat = RCC_MATRIX34(m_pParent->GetMatrixRef());
|
|
myMat.Transform(pColRecord->m_MyCollisionPosLocal, vMyCollisionPos);
|
|
}
|
|
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
if(!CanVehicleBeDamagedBasedOnDriverInvincibility())
|
|
{
|
|
m_Deformation.ClearStoredImpacts();
|
|
// we still want to update the ground clearance
|
|
if(bBoundsNeedUpdating && !bIsSuperDummy)
|
|
{
|
|
m_Deformation.ApplyDeformations(bBoundsNeedUpdating);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Process the collision.
|
|
m_pParent->GetIntelligence()->ProcessCollision( pColRecord );
|
|
|
|
if( CPhysics::ms_bInArenaMode )
|
|
{
|
|
// check for this car getting stuck
|
|
ProcessStuckCheck( fTimeStep );
|
|
|
|
// Don't rumble if vehicle is stationary
|
|
if( m_pParent && m_pParent->GetVelocity().Mag2() >= 0.001f )
|
|
{
|
|
ApplyPadShakeInternal();
|
|
}
|
|
if( bBoundsNeedUpdating && !bIsSuperDummy )
|
|
{
|
|
m_Deformation.ApplyDeformations( bBoundsNeedUpdating );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// damage for network clones is determined by the machine which owns them
|
|
if (m_pParent->IsNetworkClone())
|
|
{
|
|
const float fCollisionImpulseMagSum = pColHistory->GetCollisionImpulseMagSum();
|
|
if(pColRecord && fCollisionImpulseMagSum > 0.0f)
|
|
{
|
|
ApplyDamage(pColRecord->m_pRegdCollisionEntity.Get(), DAMAGE_TYPE_COLLISION, WEAPONTYPE_RAMMEDBYVEHICLE,
|
|
fCollisionImpulseMagSum, vMyCollisionPos, pColRecord->m_MyCollisionNormal,
|
|
Vector3(0,0,0), pColRecord->m_MyCollisionComponent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert(!pColRecord || pColRecord->m_fCollisionImpulseMag <= pColHistory->GetCollisionImpulseMagSum());
|
|
if(pColRecord && pColRecord->m_fCollisionImpulseMag > 1.0f)
|
|
{
|
|
// Check for vehicle colliding at high speed at an awkward angle
|
|
// If so trigger explosion
|
|
if( (m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR || m_pParent->GetVehicleType() == VEHICLE_TYPE_BOAT) // Only do for cars and boats
|
|
&& pColRecord->m_MyCollisionNormal.z > VEHICLE_EXPLODE_NORMAL_ANGLE_COS // We collided with something from above (ish)
|
|
&& pColHistory->GetCollisionImpulseMagSum() > VEHICLE_EXPLODE_IMPULSE_THRESHOLD*m_pParent->GetMass()
|
|
&& m_pParent->IsInAir()
|
|
&& m_pParent->GetTransform().GetC().GetZf() < VEHICLE_EXPLODE_UPRIGHT_ANGLE_COS // Check up vector of vehicle
|
|
//&& !m_pParent->ContainsPlayer() // Do not do this to player car, turned this off because we want cars to blow up more often in crashes
|
|
&& !m_pParent->PopTypeIsMission()
|
|
&& !CStuntJumpManager::IsAStuntjumpInProgress() // don't explode the car when a stunt jump is in progress
|
|
&& !m_pParent->HasIncreasedRammingForce()
|
|
&& !m_pParent->HasRammingScoop() )
|
|
{
|
|
m_pParent->BlowUpCar(m_pParent);
|
|
}
|
|
|
|
// Check for large impulses from a vehicle crashing into a train
|
|
if(m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR && pColRecord->m_pRegdCollisionEntity.Get() && pColRecord->m_pRegdCollisionEntity.Get()->GetIsTypeVehicle())
|
|
{
|
|
CVehicle *pOtherVehicle = static_cast<CVehicle*>(pColRecord->m_pRegdCollisionEntity.Get());
|
|
if(pOtherVehicle->InheritsFromTrain())
|
|
{
|
|
if(pColRecord->m_fCollisionImpulseMag > VEHICLE_EXPLODE_AGAINST_TRAIN_IMPULSE_THRESHOLD*m_pParent->GetMass())
|
|
{
|
|
m_pParent->BlowUpCar(pOtherVehicle);
|
|
}
|
|
}
|
|
|
|
if(m_pParent->IsDriverAPlayer() && pOtherVehicle->HasAliveLawPedsInIt())
|
|
{
|
|
REPLAY_ONLY(ReplayBufferMarkerMgr::AddMarker(4000, 4000, IMPORTANCE_NORMAL, CODE, OVERLAP_VEHICLE_DAMAGE);)
|
|
}
|
|
}
|
|
|
|
// Check for heli's crashing out of the sky
|
|
if(m_pParent->GetVehicleType()==VEHICLE_TYPE_HELI &&
|
|
(!GetIsDriveable(false) || !m_pParent->IsEngineOn() || m_pParent->GetStatus() == STATUS_ABANDONED || m_pParent->GetStatus() == STATUS_OUT_OF_CONTROL))
|
|
{
|
|
CEntity* pInflictor = m_pParent;
|
|
|
|
if(m_pParent->GetIsRotaryAircraft())
|
|
{
|
|
CRotaryWingAircraft * pRotaryWingAircraft = static_cast<CRotaryWingAircraft*>(m_pParent);
|
|
|
|
if( pRotaryWingAircraft->GetHeliRotorDestroyedByPed())
|
|
{
|
|
if (m_pParent->GetWeaponDamageEntity())
|
|
{
|
|
pInflictor = m_pParent->GetWeaponDamageEntity();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bHeliRotorsMissing = false;
|
|
CHeli *pHeli = static_cast<CHeli*>(m_pParent);
|
|
if(pHeli->GetRearRotorHealth() <= 0.0f || pHeli->GetMainRotorHealth() <= 0.0f)// explode helis that are missing rotors and have hit the ground.
|
|
{
|
|
bHeliRotorsMissing = true;
|
|
}
|
|
|
|
float fApplyDamage = pColHistory->GetCollisionImpulseMagSum() * InvertSafe(m_pParent->pHandling->m_fMass, m_pParent->GetInvMass());
|
|
|
|
if( !bHeliRotorsMissing &&
|
|
!CanVehicleBeDamaged(pInflictor, m_pParent->GetWeaponDamageHash(), false) )
|
|
{
|
|
fApplyDamage = 0.0f;
|
|
}
|
|
|
|
if( fApplyDamage > sfHeliCrashThreshold || (bHeliRotorsMissing && fApplyDamage > sfHeliCrashNoRotorsThreshold) )
|
|
{
|
|
// if heli falls very hard, there's a chance it'll explode
|
|
float fCrashChance = 0.5f;
|
|
if(!pHeli->IsEngineOn() || pHeli->GetStatus() == STATUS_ABANDONED || pHeli->GetStatus() == STATUS_OUT_OF_CONTROL)
|
|
{
|
|
fCrashChance = 1.0f;
|
|
}
|
|
|
|
if( (fApplyDamage > sfHeliCrashExplode && fwRandom::GetRandomNumberInRange(0.0f, 1.0f) <= fCrashChance) || (bHeliRotorsMissing && fApplyDamage > sfHeliCrashNoRotorsThreshold) )
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor);
|
|
}
|
|
else // otherwise it just hurts the occupants
|
|
{
|
|
if(fApplyDamage > sfHeliCrashExplode && fwRandom::GetRandomNumberInRange(0.0f, 1.0f) > 0.5f)
|
|
m_pParent->m_Transmission.ApplyEngineDamage(m_pParent, pInflictor, DAMAGE_TYPE_COLLISION, 1000.0f, true);
|
|
|
|
for(s32 i=0; i<m_pParent->GetSeatManager()->GetMaxSeats(); i++)
|
|
{
|
|
CPed* pPed = m_pParent->GetSeatManager()->GetPedInSeat(i);
|
|
|
|
if(pPed)
|
|
{
|
|
if(NetworkUtils::IsNetworkCloneOrMigrating(pPed))
|
|
{
|
|
CWeaponDamageEvent::Trigger(pInflictor, pPed, VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), 0, true, WEAPONTYPE_RAMMEDBYVEHICLE, fApplyDamage * sfHeliCrashOccupentHealthMult, -1, -1, CPedDamageCalculator::DF_None, 0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
CEventDamage tempDamageEvent(pInflictor, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE);
|
|
CPedDamageCalculator damageCalculator(pInflictor, fApplyDamage * sfHeliCrashOccupentHealthMult, WEAPONTYPE_RAMMEDBYVEHICLE, 0, false);
|
|
damageCalculator.ApplyDamageAndComputeResponse(pPed, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
|
|
if(tempDamageEvent.AffectsPed(pPed))
|
|
pPed->GetPedIntelligence()->AddEvent(tempDamageEvent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CEntity* pCollidedEntity = pColRecord->m_pRegdCollisionEntity.Get();
|
|
float fDamage = pColHistory->GetCollisionImpulseMagSum();
|
|
|
|
ApplyDamage(pCollidedEntity, DAMAGE_TYPE_COLLISION, WEAPONTYPE_RAMMEDBYVEHICLE,
|
|
fDamage, vMyCollisionPos, pColRecord->m_MyCollisionNormal,
|
|
Vector3(0,0,0), pColRecord->m_MyCollisionComponent);
|
|
|
|
// For vehicle to vehicle collisions we will trigger the car alarm
|
|
if (pColRecord->m_pRegdCollisionEntity.Get() && pColRecord->m_pRegdCollisionEntity->GetIsTypeVehicle())
|
|
{
|
|
if (!m_pParent->GetDriver()) // If this car has a driver triggering the alarm would be silly (sometimes happenend in network games)
|
|
{
|
|
if(m_pParent->GetStatus()!=STATUS_WRECKED)
|
|
{
|
|
m_pParent->TriggerCarAlarm();
|
|
}
|
|
}
|
|
// this should get processed for both vehicles, no need to trigger both ways?
|
|
//((CVehicle *)m_pParent->GetLatestCollisionEntity())->TriggerCarAlarm();
|
|
|
|
// Cops & Crooks: Vehicle collisions should incur endurance damage to Crooks
|
|
if (pCollidedEntity && pCollidedEntity->GetIsTypeVehicle())
|
|
{
|
|
bool bCalculatedEnduranceDamage = false;
|
|
float fEnduranceDamage = 0.0f;
|
|
|
|
s32 maxSeats = m_pParent->GetSeatManager()->GetMaxSeats();
|
|
for (s32 seatIndex = 0; seatIndex < maxSeats; ++seatIndex)
|
|
{
|
|
CPed* pPassenger = m_pParent->GetSeatManager()->GetPedInSeat(seatIndex);
|
|
if (pPassenger && pPassenger->GetPlayerInfo() && pPassenger->GetPlayerInfo()->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_CROOK)
|
|
{
|
|
if (!bCalculatedEnduranceDamage)
|
|
{
|
|
if (fDamage >= sm_Tunables.m_MinImpulseForEnduranceDamage)
|
|
{
|
|
const float fImpulseDamageScale = ClampRange(fDamage, sm_Tunables.m_MinImpulseForEnduranceDamage, sm_Tunables.m_MaxImpulseForEnduranceDamage);
|
|
const float fImpulseMultiplier = 1.0f - (square(1.0f - fImpulseDamageScale)); // Inverse exponential scale
|
|
fEnduranceDamage = sm_Tunables.m_MinEnduranceDamage + (sm_Tunables.m_MaxEnduranceDamage - sm_Tunables.m_MinEnduranceDamage) * fImpulseMultiplier;
|
|
|
|
CVehicle* pOtherVehicle = static_cast<CVehicle*>(pCollidedEntity);
|
|
bool bIsOwnFault = false;
|
|
const float fAngleSameDirRadians = DEGREES_TO_RADIANS(sm_Tunables.m_AngleVectorsPointSameDir);
|
|
if (PhysicsHelpers::FindVehicleCollisionFault(m_pParent, pOtherVehicle, bIsOwnFault, fAngleSameDirRadians, sm_Tunables.m_MinFaultVelocityThreshold)
|
|
&& bIsOwnFault)
|
|
{
|
|
fEnduranceDamage *= sm_Tunables.m_InstigatorDamageMultiplier;
|
|
}
|
|
vehicleDebugf3("[ENDURANCE] Raw vehicle damage: %f, Impulse scale: %f, Impulse multiplier: %f, Is instigator: %s, Final endurance damage: %f", fDamage, fImpulseDamageScale, fImpulseMultiplier, bIsOwnFault ? "True" : "False", fEnduranceDamage);
|
|
}
|
|
#if !__NO_OUTPUT
|
|
else
|
|
{
|
|
vehicleDebugf3("[ENDURANCE] Endurance damage not applied: fDamage (%.2f) < sm_Tunables.m_MinImpulseForEnduranceDamage (%.2f) ", fDamage, sm_Tunables.m_MinImpulseForEnduranceDamage);
|
|
}
|
|
#endif // !__NO_OUTPUT
|
|
|
|
bCalculatedEnduranceDamage = true;
|
|
}
|
|
|
|
if (fEnduranceDamage > SMALL_FLOAT)
|
|
{
|
|
if (NetworkUtils::IsNetworkCloneOrMigrating(pPassenger))
|
|
{
|
|
CWeaponDamageEvent::Trigger(m_pParent,
|
|
pPassenger,
|
|
VEC3V_TO_VECTOR3(pPassenger->GetTransform().GetPosition()),
|
|
0,
|
|
true,
|
|
WEAPONTYPE_RAMMEDBYVEHICLE,
|
|
fEnduranceDamage,
|
|
-1,
|
|
-1,
|
|
CPedDamageCalculator::DF_EnduranceDamageOnly,
|
|
0,
|
|
0,
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
CEventDamage tempDamageEvent(pCollidedEntity, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE);
|
|
CPedDamageCalculator damageCalculator(pCollidedEntity, fEnduranceDamage, WEAPONTYPE_RAMMEDBYVEHICLE, 0, false);
|
|
damageCalculator.ApplyDamageAndComputeResponse(pPassenger, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_EnduranceDamageOnly);
|
|
|
|
// Trigger directional endurance hit markers.
|
|
PostFX::RegisterBulletImpact(VEC3V_TO_VECTOR3(pCollidedEntity->GetTransform().GetPosition()), 1.0f, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const CVehicleModelInfo* pModelInfo = m_pParent->GetVehicleModelInfo();
|
|
if (!bIsSuperDummy && pModelInfo && !pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_ELECTRIC))
|
|
{
|
|
ProcessOilLeak(fTimeStep);
|
|
ProcessPetrolTankDamage(fTimeStep);
|
|
}
|
|
|
|
ProcessFuelConsumption(fTimeStep);
|
|
|
|
if(m_pParent->m_nVehicleFlags.bBlowUpWhenMissingAllWheels)
|
|
{
|
|
int numWheels = m_pParent->GetNumWheels();
|
|
if(numWheels > 0)
|
|
{
|
|
CWheel* const * wheels = m_pParent->GetWheels();
|
|
int numBrokenOffWheels = 0;
|
|
for(int i = 0; i < numWheels; i++)
|
|
{
|
|
if(wheels[i]->GetDynamicFlags().IsFlagSet(WF_BROKEN_OFF))
|
|
numBrokenOffWheels++;
|
|
}
|
|
|
|
if(numBrokenOffWheels == numWheels)
|
|
{
|
|
m_pParent->BlowUpCar(m_pParent);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pParent->m_Transmission.ProcessEngineFire(m_pParent, fTimeStep);
|
|
|
|
// check for this car getting stuck
|
|
ProcessStuckCheck(fTimeStep);
|
|
}
|
|
|
|
// Needs to be called on clones and non-clones
|
|
const float fPetrolTankHealth = GetPetrolTankHealth();
|
|
if(!bIsSuperDummy && fPetrolTankHealth>VEH_DAMAGE_HEALTH_PTANK_FINISHED && fPetrolTankHealth<VEH_DAMAGE_HEALTH_PTANK_ONFIRE)
|
|
{
|
|
CPed* pCulPrit = NULL;
|
|
CEntity* damager = m_pParent->GetWeaponDamageEntity();
|
|
if (damager)
|
|
{
|
|
if (damager->GetIsTypePed())
|
|
{
|
|
pCulPrit = static_cast<CPed*>(damager);
|
|
}
|
|
else if (damager->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* vehicle = static_cast<CVehicle*>(damager);
|
|
|
|
pCulPrit = vehicle->GetDriver();
|
|
if(!pCulPrit && vehicle->GetSeatManager())
|
|
{
|
|
pCulPrit = vehicle->GetSeatManager()->GetLastPedInSeat(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_vfxVehicle.ProcessPetrolTankDamage(m_pParent, pCulPrit);
|
|
}
|
|
|
|
m_fCountDownToTimeAnotherPartCanBreakOff = rage::Max(0.0f,m_fCountDownToTimeAnotherPartCanBreakOff-fTimeStep);
|
|
|
|
if(!bIsSuperDummy)
|
|
{
|
|
m_Deformation.ApplyDeformations(bBoundsNeedUpdating);
|
|
|
|
int nNumWheels = m_pParent->GetNumWheels();
|
|
for(int nWheel=0; nWheel < nNumWheels; nWheel++)
|
|
{
|
|
CWheel* wheel = m_pParent->GetWheel(nWheel);
|
|
wheel->ProcessWheelDamage(fTimeStep);
|
|
|
|
// Might break off
|
|
const float chanceToBreak = CVehicleDamage::ms_fChanceToBreak * (m_pParent->IsLawEnforcementVehicle() ? 0.5f : 1.0f);
|
|
const float fxChance = wheel->GetTyreHealth() > 0.0f ? 0.12f : 0.03f;
|
|
const float deleteChance = 0.29f;
|
|
const float burstTyreChance = 0.21f;
|
|
if(wheel->GetTyreShouldBreakFromDamage() && !m_pParent->IsNetworkClone())
|
|
{
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
CVehicle* pInflictor = NULL;
|
|
if(m_pParent && m_pParent->GetWeaponDamageEntity())
|
|
{
|
|
CEntity* pDamageEnt = m_pParent->GetWeaponDamageEntity();
|
|
if(pDamageEnt && pDamageEnt->GetIsTypeVehicle())
|
|
{
|
|
pInflictor = static_cast<CVehicle*>(m_pParent->GetWeaponDamageEntity());
|
|
}
|
|
}
|
|
if(pInflictor && (pInflictor->GetMass() > sfMinimumMassForCrushingVehiclesNetworkGame))
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < (chanceToBreak * 0.1f))
|
|
{
|
|
BreakOffWheel(nWheel, fxChance, deleteChance, burstTyreChance);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(wheel->GetFrictionDamage() >= CWheel::ms_fFrictionDamageThreshForBreak && fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < chanceToBreak)
|
|
{
|
|
BreakOffWheel(nWheel, fxChance, deleteChance, burstTyreChance);
|
|
}
|
|
}
|
|
|
|
wheel->GetDynamicFlags().ClearFlag(WF_WITHIN_HEAVYDAMAGE_REGION);
|
|
}
|
|
}
|
|
|
|
// Don't rumble if vehicle is stationary
|
|
if(m_pParent && m_pParent->GetVelocity().Mag2() >= 0.001f)
|
|
{
|
|
ApplyPadShakeInternal();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::ProcessOilLeak(float fTimeStep)
|
|
{
|
|
Assert(m_pParent);
|
|
if (m_pParent->CanLeakOil())
|
|
{
|
|
// check if the engine health says we should leak oil
|
|
float engineHealth = GetEngineHealth();
|
|
if (engineHealth<ENGINE_DAMAGE_OIL_LEAKING)
|
|
{
|
|
// calc oil damage (0.1 at ENGINE_DAMAGE_OIL_LEAKING, 1.0 at ENGINE_DAMAGE_ONFIRE)
|
|
float oilLeakRate = 1.0f;
|
|
if (engineHealth>ENGINE_DAMAGE_ONFIRE)
|
|
{
|
|
oilLeakRate = CVfxHelper::GetInterpValue(engineHealth, ENGINE_DAMAGE_OIL_LEAKING, ENGINE_DAMAGE_ONFIRE, true);
|
|
}
|
|
oilLeakRate += 0.1f;// Make sure we always leak some oil when we are in the ENGINE_DAMAGE_OIL_LEAKING state.
|
|
|
|
oilLeakRate *= ENGINE_DAMAGE_OIL_LEAK_RATE;
|
|
m_fOilLevel -= oilLeakRate * fTimeStep;
|
|
|
|
if(m_fOilLevel < 0.0f)
|
|
{
|
|
m_fOilLevel = 0.0f;
|
|
}
|
|
|
|
|
|
// Damage the engine when running low on oil and the engines on
|
|
float fOilLevelFraction = m_fOilLevel/m_pParent->pHandling->m_fOilVolume;
|
|
if( m_pParent->m_nVehicleFlags.bEngineOn && fOilLevelFraction < ENGINE_DAMAGE_OIL_FRACTION_BEFORE_DAMAGE && engineHealth > ENGINE_DAMAGE_ONFIRE)
|
|
{
|
|
float fOilLevelFractionDamageMult = (ENGINE_DAMAGE_OIL_FRACTION_BEFORE_DAMAGE - fOilLevelFraction)/ENGINE_DAMAGE_OIL_FRACTION_BEFORE_DAMAGE;
|
|
|
|
//increase the amount of damage done by running the car low on oil based on the amount of oil left and how much the car is being revved
|
|
float fDamage = ENGINE_DAMAGE_OIL_LOW * fOilLevelFractionDamageMult * fabs(m_pParent->m_Transmission.GetRevRatio());
|
|
|
|
float damageMult = 1.0f;
|
|
|
|
// Increase the damage done to the engine when upside down
|
|
if(m_pParent->IsUpsideDown())
|
|
{
|
|
damageMult *= sfUpsideDownEngineDamageMult;
|
|
}
|
|
|
|
const float damage = fDamage * damageMult * fTimeStep;//make this a damage over time effect.
|
|
m_pParent->m_Transmission.ApplyEngineDamage(m_pParent, m_pParent, DAMAGE_TYPE_NONE, damage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tunings from Planes.cpp
|
|
extern float dfPlaneEngineMissFireStartingHealth;
|
|
extern float sfPlaneEngineBreakDownHealth;
|
|
extern float bfPlaneEngineMissFireMinTime;
|
|
extern float bfPlaneEngineMissFireMaxTime;
|
|
extern float bfPlaneEngineMissFireMinRecoverTime;
|
|
extern float bfPlaneEngineMissFireMaxRecoverTime;
|
|
|
|
// Tunings from Heli.cpp
|
|
extern float dfHeliEngineMissFireStartingHealth;
|
|
extern float sfHeliEngineBreakDownHealth;
|
|
extern float bfHeliEngineMissFireMinTime;
|
|
extern float bfHeliEngineMissFireMaxTime;
|
|
extern float bfHeliEngineMissFireMinRecoverTime;
|
|
extern float bfHeliEngineMissFireMaxRecoverTime;
|
|
|
|
// Global object from audio/scannerManager.cpp
|
|
extern audScannerManager g_AudioScannerManager;
|
|
|
|
void CVehicleDamage::SpewEntityThatSetUsOnFire(const char* DEV_ONLY(text))
|
|
{
|
|
#if __DEV
|
|
if (!m_pParent)
|
|
return;
|
|
|
|
if (!m_pParent->GetNetworkObject())
|
|
return;
|
|
|
|
gnetDebug3("'%s' - Vehicle is '%s'", text, m_pParent->GetNetworkObject()->GetLogName());
|
|
|
|
if (!m_pEntityThatSetUsOnFire.Get())
|
|
{
|
|
gnetDebug3("...... m_pEntityThatSetUsOnFire is NULL.");
|
|
sysStack::PrintStackTrace( );
|
|
return;
|
|
}
|
|
|
|
CEntity* entity = m_pEntityThatSetUsOnFire.Get();
|
|
|
|
if (!entity->GetIsPhysical())
|
|
{
|
|
gnetDebug3("...... m_pEntityThatSetUsOnFire is not physical.");
|
|
return;
|
|
}
|
|
|
|
if (!static_cast<CPhysical*>(entity)->GetNetworkObject())
|
|
{
|
|
gnetDebug3("...... m_pEntityThatSetUsOnFire does not have a network object.");
|
|
return;
|
|
}
|
|
|
|
gnetDebug3("...... m_pEntityThatSetUsOnFire = '%s'", static_cast<CPhysical*>(entity)->GetNetworkObject()->GetLogName());
|
|
|
|
#endif
|
|
}
|
|
|
|
void CVehicleDamage::ProcessFuelConsumption(float fTimeStep)
|
|
{
|
|
// If we are consuming fuel and there was ever some fuel to consume, try to consume it now.
|
|
if (CVehicle::GetConsumePetrol() && !m_pParent->HasInfiniteFuel())
|
|
{
|
|
float newFuelTankLevel = m_fPetrolTankLevel - (m_pParent->GetFuelConsumptionRate() * fTimeStep);
|
|
|
|
if (newFuelTankLevel <= 0.0f)
|
|
{
|
|
newFuelTankLevel = 0.0f;
|
|
|
|
if (m_pParent->IsEngineOn())
|
|
m_pParent->SwitchEngineOff();
|
|
}
|
|
|
|
SetPetrolTankLevel(newFuelTankLevel);
|
|
}
|
|
|
|
#if __BANK
|
|
if (CVehicle::m_bDebugVisPetrolLevel)
|
|
{
|
|
Vec3V spherePos = m_pParent->GetVehiclePosition();
|
|
Vec3V bumpVec = Vec3V(0.0f, 0.0f, 2.0f);
|
|
|
|
grcDebugDraw::Sphere(spherePos + bumpVec, m_fPetrolTankLevel * 0.005f, Color_red);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CVehicleDamage::ProcessPetrolTankDamage(float fTimeStep)
|
|
{
|
|
Assert(m_pParent);
|
|
//@@: range CVEHICLEDAMAGE_PROCESSPETROLTANKDAMAGE {
|
|
bool bForceLeakingPetrol = false;
|
|
bool bForceExplodingPetrolTank = false;
|
|
#if TAMPERACTIONS_ENABLED && TAMPERACTIONS_LEAKPETROL
|
|
if(TamperActions::DoVehiclesLeakPetrol() && m_pParent->GetDriver() && m_pParent->GetDriver()->IsLocalPlayer())
|
|
{
|
|
bForceLeakingPetrol = true;
|
|
}
|
|
#endif
|
|
|
|
if (m_pParent->IsLeakingPetrol() || bForceLeakingPetrol)
|
|
{
|
|
// calc petrol leak rate (0.1 at VEH_DAMAGE_HEALTH_PTANK_LEAKING, 1.0 at VEH_DAMAGE_HEALTH_PTANK_ONFIRE)
|
|
float petrolLeakRate = CVfxHelper::GetInterpValue(m_fPetrolTankHealth, VEH_DAMAGE_HEALTH_PTANK_LEAKING, VEH_DAMAGE_HEALTH_PTANK_ONFIRE, true);
|
|
petrolLeakRate += 0.1f;// Make sure we always leak some petrol when we are in the VEH_DAMAGE_HEALTH_PTANK_LEAKING state.
|
|
|
|
petrolLeakRate *= VEH_DAMAGE_PTANK_LEAK_RATE;
|
|
if(bForceLeakingPetrol)
|
|
{
|
|
petrolLeakRate = 2.5f;
|
|
}
|
|
float newTankLevel = m_fPetrolTankLevel - petrolLeakRate * fTimeStep;
|
|
|
|
if(newTankLevel <= 0.0f)
|
|
{
|
|
newTankLevel = 0.0f;
|
|
bForceExplodingPetrolTank = bForceLeakingPetrol;
|
|
}
|
|
SetPetrolTankLevel(newTankLevel);
|
|
}
|
|
//@@: } CVEHICLEDAMAGE_PROCESSPETROLTANKDAMAGE
|
|
|
|
//Is the petrol so low that we should get a miss fire
|
|
if(m_fPetrolTankLevel > 0.0f)
|
|
{
|
|
if(m_pParent->m_nVehicleFlags.bCanEngineMissFire)
|
|
{
|
|
float fPetrolTankLevelFraction = m_fPetrolTankLevel/m_pParent->pHandling->m_fPetrolTankVolume;
|
|
if(fPetrolTankLevelFraction < VEH_DAMAGE_PTANK_LEVEL_BEFOREMISFIRE)
|
|
{
|
|
//miss fires get longer when lower on petrol and miss fire recoveries get shorter.
|
|
float fPetrolTankLevelFractionMissFireMult = (VEH_DAMAGE_PTANK_LEVEL_BEFOREMISFIRE - fPetrolTankLevelFraction)/VEH_DAMAGE_PTANK_LEVEL_BEFOREMISFIRE;
|
|
float fPetrolTankLevelFractionRecoveryMult = 1.0f - (VEH_DAMAGE_PTANK_LEVEL_BEFOREMISFIRE - fPetrolTankLevelFraction)/VEH_DAMAGE_PTANK_LEVEL_BEFOREMISFIRE;
|
|
|
|
if(!m_pParent->m_Transmission.GetCurrentlyMissFiring() && !m_pParent->m_Transmission.GetCurrentlyRecoveringFromMissFiring() )
|
|
{
|
|
m_pParent->m_Transmission.SetMissFireTime( fwRandom::GetRandomNumberInRange(0.5f, 1.5f*fPetrolTankLevelFractionMissFireMult),
|
|
fwRandom::GetRandomNumberInRange(0.5f*fPetrolTankLevelFractionRecoveryMult, 4.0f*fPetrolTankLevelFractionRecoveryMult));
|
|
}
|
|
}
|
|
else if(m_pParent->InheritsFromPlane() && GetEngineHealth() < dfPlaneEngineMissFireStartingHealth)
|
|
{
|
|
float fPlaneEngineHealthFractionMissFireMult = (dfPlaneEngineMissFireStartingHealth - GetEngineHealth())/(dfPlaneEngineMissFireStartingHealth - sfPlaneEngineBreakDownHealth);
|
|
fPlaneEngineHealthFractionMissFireMult = Clamp(fPlaneEngineHealthFractionMissFireMult, 0.0f, 1.0f);
|
|
if(!m_pParent->m_Transmission.GetCurrentlyMissFiring() && !m_pParent->m_Transmission.GetCurrentlyRecoveringFromMissFiring() )
|
|
{
|
|
float fMissingFireTime = fwRandom::GetRandomNumberInRange(bfPlaneEngineMissFireMinTime, bfPlaneEngineMissFireMinTime + (bfPlaneEngineMissFireMaxTime - bfPlaneEngineMissFireMinTime) * fPlaneEngineHealthFractionMissFireMult);
|
|
float fRecoverTime = fwRandom::GetRandomNumberInRange(bfPlaneEngineMissFireMaxRecoverTime + (bfPlaneEngineMissFireMaxRecoverTime - bfPlaneEngineMissFireMinRecoverTime) * fPlaneEngineHealthFractionMissFireMult, bfPlaneEngineMissFireMaxRecoverTime);
|
|
m_pParent->m_Transmission.SetMissFireTime(fMissingFireTime, fRecoverTime);
|
|
}
|
|
}
|
|
else if(m_pParent->InheritsFromHeli() && GetEngineHealth() < dfHeliEngineMissFireStartingHealth)
|
|
{
|
|
float fHeliEngineHealthFractionMissFireMult = (dfHeliEngineMissFireStartingHealth - GetEngineHealth())/(dfHeliEngineMissFireStartingHealth - sfHeliEngineBreakDownHealth);
|
|
fHeliEngineHealthFractionMissFireMult = Clamp(fHeliEngineHealthFractionMissFireMult, 0.0f, 1.0f);
|
|
if(!m_pParent->m_Transmission.GetCurrentlyMissFiring() && !m_pParent->m_Transmission.GetCurrentlyRecoveringFromMissFiring() )
|
|
{
|
|
float fMissingFireTime = fwRandom::GetRandomNumberInRange(bfHeliEngineMissFireMinTime, bfHeliEngineMissFireMinTime + (bfHeliEngineMissFireMaxTime - bfHeliEngineMissFireMinTime) * fHeliEngineHealthFractionMissFireMult);
|
|
float fRecoverTime = fwRandom::GetRandomNumberInRange(bfHeliEngineMissFireMaxRecoverTime + (bfHeliEngineMissFireMaxRecoverTime - bfHeliEngineMissFireMinRecoverTime) * fHeliEngineHealthFractionMissFireMult, bfHeliEngineMissFireMaxRecoverTime);
|
|
m_pParent->m_Transmission.SetMissFireTime(fMissingFireTime, fRecoverTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(m_pParent->IsEngineOn())
|
|
{
|
|
//turn the engine off when completely out of petrol
|
|
m_pParent->SwitchEngineOff();
|
|
}
|
|
|
|
//Process petrol tank fire
|
|
if(IsPetrolTankBurning() || bForceExplodingPetrolTank)
|
|
{
|
|
//Calculate the petrol tank fire burn rate.
|
|
float fPetrolTankFireBurnRate = bForceExplodingPetrolTank ? 0.0f : CalculatePetrolTankFireBurnRate();
|
|
|
|
m_fPetrolTankHealth -= fPetrolTankFireBurnRate * fTimeStep * m_fPetrolTankDamageScale;
|
|
|
|
bool bForceExplosionFromImpact = false;
|
|
// if petrol tank is already on fire and car hit's something hard enough, explode immediately
|
|
if(!m_pParent->GetDriver() || !m_pParent->GetDriver()->IsPlayer())
|
|
{
|
|
if(m_pParent->GetFrameCollisionHistory()->GetCollisionImpulseMagSum()*InvertSafe(m_pParent->pHandling->m_fMass, m_pParent->GetInvMass()) > VEHICLE_EXPLODE_WHEN_ON_FIRE_IMPACT)
|
|
bForceExplosionFromImpact = true;
|
|
else if(m_pParent->GetIsRotaryAircraft() && m_pParent->GetIsInWater())
|
|
bForceExplosionFromImpact = true;
|
|
}
|
|
|
|
if(m_fPetrolTankHealth < VEH_DAMAGE_HEALTH_PTANK_FINISHED || bForceExplosionFromImpact || bForceExplodingPetrolTank)
|
|
{
|
|
u32 weaponHash = WEAPONTYPE_EXPLOSION;
|
|
if(m_pParent->GetWeaponDamageHash())
|
|
weaponHash = m_pParent->GetWeaponDamageHash();
|
|
|
|
m_pParent->BlowUpCar(m_pEntityThatSetUsOnFire, false, true, false, weaponHash, false);
|
|
|
|
m_fPetrolTankHealth = VEH_DAMAGE_HEALTH_PTANK_FINISHED;
|
|
|
|
// If we hit a non-mission vehicle, then deal some extra damage to that vehicle as well. (Explosion itself will also deal damage.)
|
|
const CCollisionRecord * pColRecord = m_pParent->GetFrameCollisionHistory()->GetMostSignificantCollisionRecord();
|
|
if( pColRecord && pColRecord->m_pRegdCollisionEntity.Get() && pColRecord->m_pRegdCollisionEntity->GetIsTypeVehicle() )
|
|
{
|
|
CVehicle* pVehicleHit = static_cast<CVehicle*>( pColRecord->m_pRegdCollisionEntity.Get() );
|
|
if( !pVehicleHit->IsNetworkClone()
|
|
&& ( pVehicleHit->GetVehicleType() == VEHICLE_TYPE_CAR || pVehicleHit->GetVehicleType() == VEHICLE_TYPE_BOAT )
|
|
&& !pVehicleHit->ContainsPlayer()
|
|
&& !pVehicleHit->PopTypeIsMission()
|
|
&& pVehicleHit->GetStatus() != STATUS_WRECKED )
|
|
{
|
|
const float fExtraDamage = VEH_DAMAGE_HEALTH_STD * 0.35f; // 35% of full health... can be moved to a tuning value if desired
|
|
float fNewPetrolTankHealth = pVehicleHit->GetVehicleDamage()->GetPetrolTankHealth() - ( fExtraDamage * m_fPetrolTankDamageScale );
|
|
pVehicleHit->GetVehicleDamage()->SetPetrolTankHealth(Max(fNewPetrolTankHealth, VEH_DAMAGE_HEALTH_PTANK_FINISHED));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CVehicleDamage::IsPetrolTankBurning() const
|
|
{
|
|
if(m_fPetrolTankHealth > VEH_DAMAGE_HEALTH_PTANK_FINISHED && m_fPetrolTankHealth < VEH_DAMAGE_HEALTH_PTANK_ONFIRE && m_fPetrolTankLevel > 0.0f)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
float CVehicleDamage::CalculatePetrolTankFireBurnRate() const
|
|
{
|
|
//Assert that the petrol tank is burning.
|
|
vehicleAssertf(IsPetrolTankBurning(), "The petrol tank is not burning.");
|
|
|
|
float fPetrolTankFireBurnRate = ms_fPetrolTankFireBurnRateMin+(((m_pParent->GetRandomSeed()&0xff)/255.0f)*(ms_fPetrolTankFireBurnRateMax-ms_fPetrolTankFireBurnRateMin));
|
|
|
|
// force the burn rate for the player to be slow, to give you a chance to get out and run away
|
|
if(m_pParent->GetDriver() && m_pParent->GetDriver()->IsPlayer())
|
|
fPetrolTankFireBurnRate = ms_fPetrolTankFireBurnRateMin;
|
|
|
|
return fPetrolTankFireBurnRate;
|
|
}
|
|
|
|
float CVehicleDamage::CalculateTimeUntilPetrolTankExplosion() const
|
|
{
|
|
//Assert that the petrol tank is burning.
|
|
vehicleAssertf(IsPetrolTankBurning(), "The petrol tank is not burning.");
|
|
|
|
//Calculate the petrol tank fire burn rate (this value appears to be in seconds).
|
|
float fPetrolTankFireBurnRate = CalculatePetrolTankFireBurnRate();
|
|
|
|
//Calculate the remaining health until the explosion.
|
|
float fRemainingHealthUntilExplosion = m_fPetrolTankHealth - VEH_DAMAGE_HEALTH_PTANK_FINISHED;
|
|
|
|
return (fRemainingHealthUntilExplosion / fPetrolTankFireBurnRate);
|
|
}
|
|
|
|
void CVehicleDamage::PreRender()
|
|
{
|
|
if (fwTimer::IsGamePaused())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bIsSuperDummy = m_pParent->IsSuperDummy();
|
|
if (!bIsSuperDummy)
|
|
{
|
|
g_vfxVehicle.ProcessEngineDamage(m_pParent);
|
|
}
|
|
|
|
for(int i=0; i<MAX_BOUNCING_PANELS; i++)
|
|
{
|
|
if(m_BouncingPanels[i].GetCompIndex()>-1)
|
|
m_BouncingPanels[i].ProcessPanel(m_pParent, m_vecOldMoveSpeed, m_vecOldTurnSpeed);
|
|
}
|
|
|
|
m_vecOldMoveSpeed = m_pParent->GetVelocity();
|
|
m_vecOldTurnSpeed = m_pParent->GetAngVelocity();
|
|
}
|
|
|
|
void CVehicleDamage::PreRender2()
|
|
{
|
|
if( (true == m_pParent->m_nVehicleFlags.bHasSiren) && GetUpdateSirenBones() )
|
|
{
|
|
Matrix34 mat;
|
|
mat.Zero();
|
|
|
|
// We don't care about extras : they're done just up there.
|
|
for(int i=VEH_SIREN_1; i<VEH_SIREN_MAX+1; i++)
|
|
{
|
|
if( GetSirenState(i) )
|
|
{
|
|
int boneIdx = m_pParent->GetBoneIndex((eHierarchyId)i);
|
|
if( Verifyf(boneIdx!=-1, "Invalid bone index in vehicle siren %i (%s)", i, m_pParent->GetBaseModelInfo()->GetModelName() ))
|
|
{
|
|
m_pParent->SetGlobalMtx(boneIdx,mat);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( GetUpdateLightBones() )
|
|
{
|
|
Matrix34 mat;
|
|
mat.Zero();
|
|
|
|
for(int i=VEH_HEADLIGHT_L; i<VEH_LASTBREAKABLELIGHT+1; i++)
|
|
{
|
|
if( GetLightState(i) )
|
|
{
|
|
int boneIdx = m_pParent->GetBoneIndex((eHierarchyId)i);
|
|
if(boneIdx == -1)
|
|
{
|
|
// Neons and extra are optional
|
|
Assertf((i >= VEH_NEON_L && i <= VEH_NEON_B) || (i >= VEH_EXTRALIGHT_1 && i <= VEH_LASTBREAKABLELIGHT), "Invalid bone index in vehicle light loop %i (%s)", i, m_pParent->GetBaseModelInfo()->GetModelName() );
|
|
}
|
|
else
|
|
{
|
|
m_pParent->SetGlobalMtx(boneIdx,mat);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dev_float BIKE_KNOCKOVER_DAMAGE = 50.0f;
|
|
dev_float CAR_CRASH_SHOCKING_EVENT_COL_DELTAV = 15.0f;
|
|
dev_float CAR_CRASH_SHOCKING_EVENT_COL_IMPULSE = 50000.0f;
|
|
dev_float PLAYER_CRASH_MULTIPLIER = 3.0f;
|
|
dev_float AI_CRASH_MULTIPLIER = 1.5f;
|
|
dev_float OBJECT_CRASH_MULTIPLIER = 2.5f;
|
|
dev_float VEHICLE_CRASH_MULTIPLIER = 6.0f;
|
|
|
|
dev_float sfVehDamImpactThreshold = 15.0f;
|
|
float sfVehAircraftDamImpactThreshold = 1.5f; //ORBIS doesn't like extern const
|
|
float sfVehBoatDamImpactThreshold = 1.5f;
|
|
dev_float sfObjectDamageSpeedThreshold = 5.0f;
|
|
dev_float sfHighSpeedObjectDamageThreshold = 0.5f;
|
|
dev_float sfLowSpeedObjectDamageThreshold = 3.0f;
|
|
dev_float sfVehDamImpactMult = 10.0f;
|
|
dev_float sfVehDamShakeThreshold = 0.1f;
|
|
dev_float sfVehTakeLessDamageMult = 0.5f;
|
|
dev_float sfAircraftFireDamageMult = 0.05f;
|
|
dev_float sfCarCollisionDamageMultiplayerMult = 0.5f;
|
|
dev_float sfCarCollisionDamagePlayerMult = 0.5f;
|
|
dev_float sfPlayerDamageMultiplayerMult = 0.5f;
|
|
dev_float sfVehDamSetWantedLevelThreshold = 3.0f;
|
|
dev_float sfMinInflictorVelocitySetWantedLevel = 1.0f;
|
|
dev_float sfAbandonedAircraftLandingDamageMult = 2.0f;
|
|
dev_float sfAmbientCarLandingDamageMulti = 2.0f;
|
|
dev_float sfVehicleRoofDamageMult = 1.5f;
|
|
dev_float sfHydraulicsBounceLandingDamageMulti = 10.0f;
|
|
dev_float sfHydraulicsBounceLandingDamageThreshold = 100.0f;
|
|
dev_float sfHydraulicsBounceRaisingDamageMulti = 5.0f;
|
|
dev_float sfSubmarineLandDamageMult = 2.0f;
|
|
dev_float sfSubmarineWaterDamageMult = 0.3f;
|
|
dev_float sfPlaneExplosionDamageCapInSP = 350.0f; //Make it able to survive from 2 rocket explosions
|
|
dev_float sfPlaneCollisionWithCarMult = 0.1f;
|
|
dev_float sfPlaneDamagedByMeleeMult = 0.5f;
|
|
|
|
// Cloud tunables
|
|
bool CVehicleDamage::sbPlaneDamageCapLargePlanesOnly = true;
|
|
float CVehicleDamage::sfPlaneExplosionDamageCapInMP = 350.0f;
|
|
float CVehicleDamage::sfSpecialAmmoFMJDamageMultiplierAgainstVehicles = 2.0f;
|
|
float CVehicleDamage::sfSpecialAmmoFMJDamageMultiplierAgainstVehicleGasTank = 0.5f;
|
|
|
|
void CVehicleDamage::InitTunables()
|
|
{
|
|
sbPlaneDamageCapLargePlanesOnly = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("MP_EXPLOSION_DAMAGE_CAP_LARGE_PLANES_ONLY", 0x1f0d74d9), sbPlaneDamageCapLargePlanesOnly);
|
|
sfPlaneExplosionDamageCapInMP = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("PLANE_MP_EXPLOSION_DAMAGE_CAP", 0x8234442b), sfPlaneExplosionDamageCapInMP);
|
|
|
|
sfSpecialAmmoFMJDamageMultiplierAgainstVehicles = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("GUNRUN_AMMO_FMJ_VEH_DAMAGEMULT", 0xA349D60A), sfSpecialAmmoFMJDamageMultiplierAgainstVehicles);
|
|
sfSpecialAmmoFMJDamageMultiplierAgainstVehicleGasTank = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("GUNRUN_AMMO_FMJ_VEH_GASTANKDAMAGEMULT", 0x4C0A13A1), sfSpecialAmmoFMJDamageMultiplierAgainstVehicleGasTank);
|
|
|
|
sm_Tunables.m_MinImpulseForEnduranceDamage = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ENDURANCE_VEHICLE_DMG_MIN_THRESHOLD", 0xC4F8A8AD), sm_Tunables.m_MinImpulseForEnduranceDamage);
|
|
sm_Tunables.m_MaxImpulseForEnduranceDamage = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ENDURANCE_VEHICLE_DMG_MAX_THRESHOLD", 0xDF1E475D), sm_Tunables.m_MaxImpulseForEnduranceDamage);
|
|
sm_Tunables.m_MinEnduranceDamage = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ENDURANCE_VEHICLE_DMG_MIN_APPLIED", 0xB20B2506), sm_Tunables.m_MinEnduranceDamage);
|
|
sm_Tunables.m_MaxEnduranceDamage = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ENDURANCE_VEHICLE_DMG_MAX_APPLIED", 0xFF2B790F), sm_Tunables.m_MaxEnduranceDamage);
|
|
sm_Tunables.m_InstigatorDamageMultiplier = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("CNC_ENDURANCE_DMG_MULTIPLIER_INSTIGATOR", 0x345B5A04), sm_Tunables.m_InstigatorDamageMultiplier);
|
|
}
|
|
|
|
bool CVehicleDamage::HasBoneCollidedWithComponent(eHierarchyId boneId, int iComponent)
|
|
{
|
|
if( m_pParent && m_pParent->GetVehicleFragInst() )
|
|
{
|
|
int iBoneIndex = m_pParent->GetBoneIndex(boneId);
|
|
|
|
if( iBoneIndex > -1 && iComponent == m_pParent->GetVehicleFragInst()->GetComponentFromBoneIndex(iBoneIndex) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
u32 CVehicleDamage::sm_TimeOfLastShotAtHeliMegaphone = 0;
|
|
|
|
float CVehicleDamage::ApplyDamage(CEntity* pInflictor, eDamageType nDamageType, u32 nWeaponHash, float fDamage, const Vector3& vecPos, const Vector3& vecNorm, const Vector3& vecDirn, int nComponent, phMaterialMgr::Id nMaterialId, int nPieceIndex, const bool bFireDriveby, const bool bIsAccurate, const float fDamageRadius, const bool bAvoidExplosions, const bool bChainExplosion, const bool bMeleeDamage, const bool isFlameThrowerFire, const bool forceMinDamage )
|
|
{
|
|
ENABLE_FRAG_OPTIMIZATION_ONLY(m_pParent->GiveFragCacheEntry(true);)
|
|
|
|
float fApplyDamage = fDamage;
|
|
NOTFINAL_ONLY(vehicleDebugf3("CVehicleDamage::ApplyDamage - '%s' '%s'- Damage = %f type: %d, Pos: %.2f %.2f %.2f",m_pParent->GetDebugName(), m_pParent->GetNetworkObject() ? m_pParent->GetNetworkObject()->GetLogName() : "none", fDamage, (int)nDamageType, vecPos.x, vecPos.y, vecPos.z));
|
|
float armourMultiplier = m_pParent->GetVariationInstance().GetArmourDamageMultiplier();
|
|
fApplyDamage *= armourMultiplier;
|
|
fApplyDamage *= m_fScriptDamageScale;
|
|
|
|
bool bLandingDamage = false;
|
|
bool bNoEngineDamage = false;
|
|
|
|
// script flag to reduce damage to all vehicles
|
|
if(m_pParent->m_nVehicleFlags.bTakeLessDamage)
|
|
{
|
|
fApplyDamage *= sfVehTakeLessDamageMult;
|
|
vehicleDebugf3("\tm_nVehicleFlags.bTakeLessDamage - Damage = %f",fApplyDamage);
|
|
}
|
|
|
|
if(NetworkInterface::IsGameInProgress() && pInflictor && pInflictor->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pInflictorVehicle = static_cast<const CVehicle*>(pInflictor);
|
|
if(pInflictorVehicle->InheritsFromHeli() && pInflictorVehicle->GetDriver() && pInflictorVehicle->GetDriver()->IsPlayer())
|
|
{
|
|
static const u32 BUZZARD_WEAPON_HASH = ATSTRINGHASH("VEHICLE_WEAPON_PLAYER_BUZZARD", 0x46b89c8e);
|
|
if (nWeaponHash == BUZZARD_WEAPON_HASH)
|
|
{
|
|
// Applying modifier to Annihilator and Buzzard machine gun weapons
|
|
TUNE_GROUP_FLOAT(DAMAGE_TUNE, BUZZARD_MP_DAMAGE_MODIFIER, 5.0f, 0.0f, 1000.0f, 0.01f);
|
|
fApplyDamage *= BUZZARD_MP_DAMAGE_MODIFIER;
|
|
vehicleDebugf3("\tBUZZARD_MG_MP_DAMAGE_MODIFIER - Damage = %f",fApplyDamage);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if __BANK
|
|
vehicleDebugf3( "CVehicleDamage::ApplyDamage: inflictor: %s, Inflictor Pos: %.2f, %.2f, %.2f, Inflictor prev pos: %.2f, %.2f, %.2f",
|
|
pInflictor ? pInflictor->GetIsPhysical() ? static_cast< CPhysical* >( pInflictor )->GetNetworkObject() ? static_cast< CPhysical* >( pInflictor )->GetNetworkObject()->GetLogName() : pInflictor->GetDebugName() : "none" : "None",
|
|
pInflictor ? pInflictor->GetTransform().GetPosition().GetXf() : 0.0f,
|
|
pInflictor ? pInflictor->GetTransform().GetPosition().GetYf() : 0.0f,
|
|
pInflictor ? pInflictor->GetTransform().GetPosition().GetZf() : 0.0f,
|
|
pInflictor ? pInflictor->GetCurrentPhysicsInst() ? PHLEVEL->GetLastInstanceMatrix( pInflictor->GetCurrentPhysicsInst() ).d().GetXf() : 0.0f : 0.0f,
|
|
pInflictor ? pInflictor->GetCurrentPhysicsInst() ? PHLEVEL->GetLastInstanceMatrix( pInflictor->GetCurrentPhysicsInst() ).d().GetYf() : 0.0f : 0.0f,
|
|
pInflictor ? pInflictor->GetCurrentPhysicsInst() ? PHLEVEL->GetLastInstanceMatrix( pInflictor->GetCurrentPhysicsInst() ).d().GetZf() : 0.0f : 0.0f );
|
|
|
|
vehicleDebugf3( "CVehicleDamage::ApplyDamage: victim: %s, victim Pos: %.2f, %.2f, %.2f, victim prev pos: %.2f, %.2f, %.2f",
|
|
m_pParent->GetNetworkObject() ? m_pParent->GetNetworkObject()->GetLogName() : m_pParent->GetDebugName(),
|
|
m_pParent->GetTransform().GetPosition().GetXf(),
|
|
m_pParent->GetTransform().GetPosition().GetYf(),
|
|
m_pParent->GetTransform().GetPosition().GetZf(),
|
|
m_pParent->GetCurrentPhysicsInst() ? PHLEVEL->GetLastInstanceMatrix( m_pParent->GetCurrentPhysicsInst() ).d().GetXf() : 0.0f,
|
|
m_pParent->GetCurrentPhysicsInst() ? PHLEVEL->GetLastInstanceMatrix( m_pParent->GetCurrentPhysicsInst() ).d().GetYf() : 0.0f,
|
|
m_pParent->GetCurrentPhysicsInst() ? PHLEVEL->GetLastInstanceMatrix( m_pParent->GetCurrentPhysicsInst() ).d().GetZf() : 0.0f );
|
|
#endif //
|
|
|
|
|
|
bool bIncludeBodyDamage = nWeaponHash == ATSTRINGHASH("VEHICLE_WEAPON_WATER_CANNON",0x67D18297) || nWeaponHash == ATSTRINGHASH("WEAPON_HIT_BY_WATER_CANNON",0xCC34325E);
|
|
|
|
//Cache if the vehicle is already destroyed and the health.
|
|
const bool isAlreadyWrecked = (m_pParent->GetStatus() == STATUS_WRECKED);
|
|
float previousHealth = m_pParent->GetHealth() + GetEngineHealth() + GetPetrolTankHealth();
|
|
if (bIncludeBodyDamage)
|
|
{
|
|
previousHealth += m_fBodyHealth;
|
|
}
|
|
|
|
for(s32 i=0; i<m_pParent->GetNumWheels(); i++)
|
|
{
|
|
previousHealth += GetTyreHealth(i);
|
|
previousHealth += GetSuspensionHealth(i);
|
|
}
|
|
|
|
// if this is physical damage (from crashing into something)
|
|
if(nDamageType==DAMAGE_TYPE_COLLISION)
|
|
{
|
|
if( pInflictor==NULL ||
|
|
pInflictor->GetIsTypeBuilding() ||
|
|
pInflictor->GetIsTypeAnimatedBuilding() ||
|
|
pInflictor->GetIsTypeObject() )
|
|
{
|
|
fApplyDamage *= m_fCollisionWithMapDamageScale;
|
|
}
|
|
|
|
bool forceMinimumDamage = forceMinDamage;
|
|
if(pInflictor==NULL || pInflictor->GetIsTypeBuilding())
|
|
{
|
|
const float kfMinSpeedForSubDamage2 = 0.5f;
|
|
if(m_pParent->InheritsFromSubmarine() && m_pParent->GetVelocity().Mag2() > kfMinSpeedForSubDamage2)
|
|
{
|
|
if(m_pParent->GetIsInWater())
|
|
{
|
|
fApplyDamage *= sfSubmarineWaterDamageMult;
|
|
vehicleDebugf3("\tSubmarine Water Collision Scaling - Damage = %f",fApplyDamage);
|
|
}
|
|
else
|
|
{
|
|
fApplyDamage *= sfSubmarineLandDamageMult;
|
|
vehicleDebugf3("\tSubmarine Land Collision Scaling - Damage = %f",fApplyDamage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float normalDotUp = DotProduct(vecNorm, VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetC()));
|
|
if(normalDotUp > 0.6f)
|
|
{
|
|
// Collisions from the road don't do any damage.
|
|
if(m_pParent->GetIsAircraft())
|
|
{
|
|
if(m_pParent->GetStatus() == STATUS_ABANDONED)
|
|
{
|
|
fApplyDamage *= sfAbandonedAircraftLandingDamageMult;
|
|
vehicleDebugf3("\tAbandoned Scaling - Damage = %f",fApplyDamage);
|
|
}
|
|
|
|
if( m_pParent->InheritsFromPlane() )
|
|
{
|
|
CPlane* pPlane = static_cast<CPlane*>( m_pParent );
|
|
if( pPlane->GetLandingGearDamage().IsComponentBreakable( pPlane, nComponent ) )
|
|
{
|
|
bLandingDamage = true;
|
|
}
|
|
}
|
|
|
|
if(CSeaPlaneHandlingData* pSeaPlaneHandling = m_pParent->pHandling->GetSeaPlaneHandlingData())
|
|
{
|
|
if((u32)nComponent == pSeaPlaneHandling->m_fLeftPontoonComponentId
|
|
|| (u32)nComponent == pSeaPlaneHandling->m_fRightPontoonComponentId
|
|
|| bLandingDamage)
|
|
{
|
|
bNoEngineDamage = true;
|
|
}
|
|
}
|
|
}
|
|
else if(m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR && m_pParent->PopTypeIsRandom() && !m_pParent->ContainsLocalPlayer() && (m_pParent->GetDriver() == NULL || m_pParent->GetDriver()->PopTypeIsRandom()))
|
|
{
|
|
// Apply landing damage for empty or ambient AI cars
|
|
fApplyDamage *= sfAmbientCarLandingDamageMulti;
|
|
}
|
|
else if(m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR &&
|
|
fApplyDamage > sfHydraulicsBounceLandingDamageThreshold )
|
|
{
|
|
CAutomobile* pAutomobile = static_cast< CAutomobile* >( m_pParent );
|
|
|
|
// Apply damage for cars using hydraulics
|
|
if( pAutomobile->m_nAutomobileFlags.bHydraulicsBounceRaising )
|
|
{
|
|
fApplyDamage *= sfHydraulicsBounceRaisingDamageMulti;
|
|
forceMinimumDamage = true;
|
|
}
|
|
else if( pAutomobile->m_nAutomobileFlags.bHydraulicsBounceLanding )
|
|
{
|
|
fApplyDamage *= sfHydraulicsBounceLandingDamageMulti;
|
|
forceMinimumDamage = true;
|
|
}
|
|
else
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
else if( !forceMinDamage )
|
|
{
|
|
vehicleDebugf3("\tRoad Collision - 0 Damage");
|
|
return 0.0f;
|
|
}
|
|
}
|
|
else if(normalDotUp < -0.6f)
|
|
{
|
|
fApplyDamage *= sfVehicleRoofDamageMult;
|
|
vehicleDebugf3("\tRoof Collision - %f Damage",fApplyDamage);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exclude turret collisions from vehicle damage
|
|
if( pInflictor )
|
|
{
|
|
// If hit a part of a vehicle turret, don't apply damage because turrets spin like crazy
|
|
if(m_pParent && m_pParent->GetVehicleWeaponMgr() && m_pParent->GetVehicleFragInst() )
|
|
{
|
|
CVehicleWeaponMgr* pWeaponMgr = m_pParent->GetVehicleWeaponMgr();
|
|
|
|
// Loop through the turrets on the weapon and check if we hit their barrels or their bases
|
|
for(int i = 0; i < pWeaponMgr->GetNumTurrets(); i++)
|
|
{
|
|
CTurret* pTurret = pWeaponMgr->GetTurret(i);
|
|
|
|
if(pTurret->IsPhysicalTurret())
|
|
{
|
|
if( HasBoneCollidedWithComponent( pTurret->GetBarrelBoneId(), nComponent ) ||
|
|
HasBoneCollidedWithComponent( pTurret->GetBaseBoneId(), nComponent ) )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop through the fixed weapons on the vehicle and check if we've hit the weapon
|
|
for(int i = 0; i < pWeaponMgr->GetNumVehicleWeapons(); i++)
|
|
{
|
|
CVehicleWeapon* pWeapon = pWeaponMgr->GetVehicleWeapon(i);
|
|
|
|
if( pWeapon->GetType() == VGT_FIXED_VEHICLE_WEAPON )
|
|
{
|
|
CFixedVehicleWeapon* pFixedWeapon = static_cast<CFixedVehicleWeapon*>(pWeapon);
|
|
|
|
if( HasBoneCollidedWithComponent( pFixedWeapon->GetWeaponBone(), nComponent ) )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use a lower damage threshold for vehicles vs. aircrafts so they can inflict small amounts of damage
|
|
float fVehDamImpactThreshold = sfVehDamImpactThreshold;
|
|
bool bInflictorIsAVehicle = pInflictor && pInflictor->GetIsTypeVehicle();
|
|
if( bInflictorIsAVehicle && m_pParent->GetIsAircraft())
|
|
{
|
|
fVehDamImpactThreshold = sfVehAircraftDamImpactThreshold;
|
|
}
|
|
else if( bInflictorIsAVehicle && m_pParent->GetIsAquatic())
|
|
{
|
|
fVehDamImpactThreshold = sfVehBoatDamImpactThreshold;
|
|
}
|
|
|
|
if( m_bDisableDamageWithPickedUpEntity &&
|
|
pInflictor &&
|
|
m_pParent->InheritsFromHeli() &&
|
|
static_cast< CHeli* >( m_pParent )->GetIsCargobob() )
|
|
{
|
|
CHeli* pCargobob = static_cast< CHeli* >( m_pParent );
|
|
|
|
for( int i = 0; i < pCargobob->GetNumberOfVehicleGadgets(); i++ )
|
|
{
|
|
CVehicleGadget *pVehicleGadget = pCargobob->GetVehicleGadget(i);
|
|
|
|
if( pVehicleGadget && ( pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET ) )
|
|
{
|
|
CVehicleGadgetPickUpRope *pPickUpRope = static_cast<CVehicleGadgetPickUpRope*>(pVehicleGadget);
|
|
|
|
if( pPickUpRope->GetAttachedEntity() == pInflictor )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this is a titan plane reduce the amount of damage done to it in collisions with cars
|
|
if( m_pParent->InheritsFromPlane() &&
|
|
( m_pParent->GetModelIndex() == MI_PLANE_TITAN || m_pParent->GetModelIndex() == MI_PLANE_BOMBUSHKA || m_pParent->GetModelIndex() == MI_PLANE_VOLATOL) &&
|
|
bInflictorIsAVehicle )
|
|
{
|
|
CVehicle* pInflictorVehicle = static_cast<CVehicle*>(pInflictor);
|
|
|
|
if( pInflictorVehicle->GetVehicleType() == VEHICLE_TYPE_CAR )
|
|
{
|
|
fApplyDamage *= sfPlaneCollisionWithCarMult;
|
|
}
|
|
}
|
|
|
|
// physical damage gets scaled by the collision damage multiplier, and by mass
|
|
fApplyDamage *= sfVehDamImpactMult * m_pParent->pHandling->m_fCollisionDamageMult * InvertSafe(m_pParent->pHandling->m_fMass, m_pParent->GetInvMass());
|
|
vehicleDebugf3("\tMass Scaling - Damage = %f",fApplyDamage);
|
|
|
|
// SHAKE PAD
|
|
PF_SET(DamageImpulse, fApplyDamage);
|
|
PF_SET(DamageVelocity, m_pParent->GetVelocity().Mag() );
|
|
|
|
float fRumbleDamage = fApplyDamage;
|
|
|
|
if(pInflictor)
|
|
{
|
|
if(pInflictor->GetIsTypeObject())
|
|
{
|
|
if(fragInst* pFragInst = pInflictor->GetFragInst())
|
|
{
|
|
if(pFragInst->GetCacheEntry())
|
|
{
|
|
if(pFragInst->GetCacheEntry()->GetHierInst()->age < 0.25f)
|
|
{
|
|
// Recently broken fragments should do some damage
|
|
forceMinimumDamage = true;
|
|
}
|
|
}
|
|
|
|
CBaseModelInfo* pModelInfo = pInflictor->GetBaseModelInfo();
|
|
if (pModelInfo && pModelInfo->TestAttribute(MODEL_ATTRIBUTE_RUMBLE_ON_LIGHT_COLLISION_WITH_VEHICLE))// for the highway paddles we want to have a bit of rumble
|
|
{
|
|
fRumbleDamage = sfRumbleDamageWhenHittingRumbleOnLightColWithObject + fApplyDamage;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(fApplyDamage > sfVehDamShakeThreshold && m_pParent->GetVelocity().Mag2() >= 0.001f)
|
|
ApplyPadShake(pInflictor, fRumbleDamage);
|
|
|
|
if(m_pParent->ContainsLocalPlayer())
|
|
g_ScriptAudioEntity.RegisterPlayerCarCollision(m_pParent, pInflictor, fApplyDamage);
|
|
|
|
if(forceMinimumDamage)
|
|
{
|
|
fApplyDamage = Max(fVehDamImpactThreshold + 1.0f, fApplyDamage);
|
|
vehicleDebugf3("\tForce Minimum Damage - Damage = %f",fApplyDamage);
|
|
}
|
|
|
|
vehicleDebugf3("\tFinal Damage = %f, Threshold = %f",fApplyDamage,fVehDamImpactThreshold);
|
|
if(fApplyDamage < fVehDamImpactThreshold)
|
|
{
|
|
if(pInflictor && pInflictor->GetIsTypeObject())
|
|
{
|
|
const bool bHitLargeFixedObject = !pInflictor->IsBaseFlagSet(fwEntity::IS_FIXED) && pInflictor->GetBoundRadius() > 0.3f;
|
|
bool bAddMadDriverEvent = false;
|
|
//If there wasn't much damage it could have been with an object which we will still want peds to react to.
|
|
if (bHitLargeFixedObject)
|
|
{
|
|
// Different thresholds for different speeds.
|
|
float fDamageThreshold = m_pParent->GetAiXYSpeed() > sfObjectDamageSpeedThreshold ? sfHighSpeedObjectDamageThreshold : sfLowSpeedObjectDamageThreshold;
|
|
if (fApplyDamage > fDamageThreshold)
|
|
{
|
|
if (m_pParent->InheritsFromBicycle())
|
|
{
|
|
CEventShockingBicycleCrash ev(*m_pParent, pInflictor);
|
|
CShockingEventsManager::Add(ev);
|
|
}
|
|
else
|
|
{
|
|
CEventShockingCarCrash ev(*m_pParent, pInflictor);
|
|
CShockingEventsManager::Add(ev);
|
|
}
|
|
|
|
if(m_pParent->GetDriver() && m_pParent->GetDriver()->IsPlayer())
|
|
{
|
|
REPLAY_ONLY(ReplayBufferMarkerMgr::AddMarker(4000, 4000, IMPORTANCE_NORMAL, CODE, OVERLAP_VEHICLE_DAMAGE);)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bAddMadDriverEvent = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bAddMadDriverEvent = true;
|
|
}
|
|
|
|
// Enough damage might not have been done for a full car crash event, but we still probably want peds to react somehow.
|
|
// MadDriver is a much more light response.
|
|
CPed* pDriver = m_pParent->GetDriver();
|
|
if (bAddMadDriverEvent && pDriver)
|
|
{
|
|
if (m_pParent->InheritsFromBicycle())
|
|
{
|
|
CEventShockingMadDriverBicycle ev(*pDriver);
|
|
CShockingEventsManager::Add(ev);
|
|
}
|
|
else
|
|
{
|
|
CEventShockingMadDriverExtreme ev(*pDriver);
|
|
CShockingEventsManager::Add(ev);
|
|
}
|
|
|
|
if(pDriver->IsPlayer())
|
|
{
|
|
REPLAY_ONLY(ReplayBufferMarkerMgr::AddMarker(2500, 2500, IMPORTANCE_NORMAL, CODE, OVERLAP_VEHICLE_DAMAGE);)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if(fApplyDamage > sfVehDamSetWantedLevelThreshold && pInflictor && m_pParent->HasAliveLawPedsInIt())
|
|
{
|
|
CPed* pInflictorPed = NULL;
|
|
if(pInflictor->GetIsTypePed())
|
|
{
|
|
pInflictorPed = static_cast<CPed*>(pInflictor);
|
|
}
|
|
else if(pInflictor->GetIsTypeVehicle())
|
|
{
|
|
pInflictorPed = static_cast<CVehicle*>(pInflictor)->GetDriver();
|
|
}
|
|
|
|
if( pInflictorPed && pInflictorPed->IsLocalPlayer() && pInflictorPed->GetVehiclePedInside() )
|
|
{
|
|
float fVehicleVelocitySq = pInflictorPed->GetVehiclePedInside()->GetVelocity().Mag2();
|
|
if(fVehicleVelocitySq > square(sfMinInflictorVelocitySetWantedLevel) && fVehicleVelocitySq > m_pParent->GetVelocity().Mag2() )
|
|
{
|
|
CWanted* pPlayerWanted = pInflictorPed->GetPlayerWanted();
|
|
if(pPlayerWanted && pPlayerWanted->GetWantedLevel() == WANTED_CLEAN && pPlayerWanted->m_fMultiplier )
|
|
{
|
|
pPlayerWanted->SetWantedLevelNoDrop(VEC3V_TO_VECTOR3(pInflictorPed->GetTransform().GetPosition()), WANTED_LEVEL1, WANTED_CHANGE_DELAY_INSTANT, WL_FROM_LAW_RESPONSE_EVENT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Still call this function here even if there's no damage, as this is also used to detect vehicle crashes
|
|
CStatsMgr::RegisterVehicleDamage(GetParent(), pInflictor, nDamageType, 0.0f, false);
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
fApplyDamage -= fVehDamImpactThreshold;
|
|
vehicleDebugf3("\tThreshold Reduction - Damage = %f",fApplyDamage);
|
|
|
|
if(m_pParent->GetIsAircraft() && nDamageType == DAMAGE_TYPE_COLLISION)
|
|
{
|
|
// By pass the damage scales, aircrafts are vulnerable to collision impacts
|
|
}
|
|
else if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
//Reduce all car collision damage in MP and other vehicle collision damage for the player only
|
|
if(m_pParent->InheritsFromAutomobile())
|
|
{
|
|
fApplyDamage *= sfCarCollisionDamageMultiplayerMult;
|
|
vehicleDebugf3("\tMP Scaling - Damage = %f",fApplyDamage);
|
|
}
|
|
|
|
if(m_pParent->ContainsPlayer())
|
|
{
|
|
fApplyDamage *= sfPlayerDamageMultiplayerMult;
|
|
vehicleDebugf3("\tMP Player Scaling - Damage = %f",fApplyDamage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(m_pParent->InheritsFromAutomobile() && m_pParent->ContainsPlayer())
|
|
{
|
|
fApplyDamage *= sfCarCollisionDamagePlayerMult;
|
|
vehicleDebugf3("\tSP Player Scaling - Damage = %f",fApplyDamage);
|
|
}
|
|
}
|
|
|
|
float fMult = (m_pParent->GetDriver() && m_pParent->GetDriver()->IsAPlayerPed()) ? PLAYER_CRASH_MULTIPLIER : AI_CRASH_MULTIPLIER;
|
|
|
|
if (pInflictor)
|
|
{
|
|
if (pInflictor->GetIsTypeObject() && !pInflictor->IsBaseFlagSet(fwEntity::IS_FIXED))
|
|
{
|
|
fMult *= OBJECT_CRASH_MULTIPLIER;
|
|
}
|
|
else if (pInflictor->GetIsTypeVehicle())
|
|
{
|
|
fMult *= VEHICLE_CRASH_MULTIPLIER;
|
|
}
|
|
}
|
|
|
|
if((fMult*fDamage*InvertSafe(m_pParent->pHandling->m_fMass, m_pParent->GetInvMass())) > CAR_CRASH_SHOCKING_EVENT_COL_DELTAV || (fMult*fDamage) > CAR_CRASH_SHOCKING_EVENT_COL_IMPULSE)
|
|
{
|
|
// Note: we used to pass in nWeaponHash to the shocking event, but it was actually
|
|
// ignored, was only used for the EVisibleWeapon event.
|
|
if (m_pParent->InheritsFromBicycle())
|
|
{
|
|
CEventShockingBicycleCrash ev(*m_pParent, pInflictor);
|
|
CShockingEventsManager::Add(ev);
|
|
}
|
|
else
|
|
{
|
|
CEventShockingCarCrash ev(*m_pParent, pInflictor);
|
|
CShockingEventsManager::Add(ev);
|
|
}
|
|
}
|
|
|
|
//apply the unadjusted damage value so we can compute the velocity of the impact
|
|
camInterface::GetGameplayDirector().RegisterVehicleDamage(m_pParent, fDamage);
|
|
camInterface::GetCinematicDirector().RegisterVehicleDamage(m_pParent, -vecNorm, fDamage);
|
|
|
|
// do any visual effect due to collision
|
|
m_pParent->DoCollisionEffects();
|
|
}
|
|
else
|
|
{
|
|
// For shots we need to report the crime
|
|
bool bHeliOrPlaneExplosion = nDamageType == DAMAGE_TYPE_EXPLOSIVE && (m_pParent->InheritsFromHeli() || m_pParent->InheritsFromPlane());
|
|
bool bShotVehicle = (nDamageType == DAMAGE_TYPE_BULLET || nDamageType == DAMAGE_TYPE_BULLET_RUBBER);
|
|
bool bShotPoliceVehicleWithWaterCannon = nDamageType == DAMAGE_TYPE_WATER_CANNON && m_pParent->HasAliveLawPedsInIt();
|
|
if( pInflictor &&
|
|
(bHeliOrPlaneExplosion || bShotVehicle || bShotPoliceVehicleWithWaterCannon) &&
|
|
m_pParent->HasAlivePedsInIt() )
|
|
{
|
|
CPed* pInflictorPed = NULL;
|
|
if(pInflictor->GetIsTypePed())
|
|
{
|
|
pInflictorPed = static_cast<CPed*>(pInflictor);
|
|
}
|
|
else if(pInflictor->GetIsTypeVehicle())
|
|
{
|
|
pInflictorPed = static_cast<CVehicle*>(pInflictor)->GetDriver();
|
|
}
|
|
|
|
if(pInflictorPed && pInflictorPed->GetVehiclePedInside() != m_pParent)
|
|
{
|
|
if(bShotVehicle || bShotPoliceVehicleWithWaterCannon)
|
|
{
|
|
CCrime::ReportCrime(CRIME_SHOOT_VEHICLE, m_pParent, pInflictorPed);
|
|
|
|
u32 audioTime = g_AudioEngine.GetTimeInMilliseconds();
|
|
if(m_pParent->InheritsFromHeli() && pInflictorPed->IsLocalPlayer() &&
|
|
m_pParent->IsLawEnforcementVehicle() && m_pParent->GetSeatManager() && g_AudioScannerManager.ShouldTryToPlayShotAtHeli(audioTime))
|
|
{
|
|
s32 maxSeats = m_pParent->GetSeatManager()->GetMaxSeats();
|
|
if(maxSeats > 0)
|
|
{
|
|
int startSeat = audEngineUtil::GetRandomInteger() % maxSeats;
|
|
for( s32 iSeat = 0; iSeat < maxSeats; iSeat++ )
|
|
{
|
|
int index = (startSeat + iSeat) % maxSeats;
|
|
CPed* pPed = m_pParent->GetSeatManager()->GetPedInSeat(index);
|
|
if( pPed && pPed->GetSpeechAudioEntity())
|
|
{
|
|
if( pPed->GetSpeechAudioEntity()->SetPedVoiceForContext(ATPARTIALSTRINGHASH("SHOT_AT_HELI_MEGAPHONE", 0x31DD519A)) )
|
|
{
|
|
pPed->GetSpeechAudioEntity()->SayWhenSafe("SHOT_AT_HELI_MEGAPHONE");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sm_TimeOfLastShotAtHeliMegaphone = audioTime;
|
|
}
|
|
}
|
|
|
|
if(bHeliOrPlaneExplosion && pInflictorPed->IsLocalPlayer())
|
|
{
|
|
CVehicle *pVehicle = static_cast<CVehicle*>(m_pParent);
|
|
if (pVehicle && pVehicle->m_nVehicleFlags.bInfluenceWantedLevel)
|
|
{
|
|
CWanted* pPlayerWanted = pInflictorPed->GetPlayerWanted();
|
|
if(pPlayerWanted && pPlayerWanted->MaximumWantedLevel > WANTED_LEVEL2 && pPlayerWanted->m_fMultiplier)
|
|
{
|
|
pPlayerWanted->SetWantedLevelNoDrop(VEC3V_TO_VECTOR3(pInflictorPed->GetTransform().GetPosition()), WANTED_LEVEL3, WANTED_CHANGE_DELAY_INSTANT, WL_FROM_LAW_RESPONSE_EVENT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//B*1721702/1732210/1819577: Scale explosive damage on tanks (online only). 4 rockets or 3 sticky bombs to destroy.
|
|
bool bUseWeaponDamageMult = true;
|
|
bool bUseMultiplayerMult = m_pParent->GetUseMPDamageMultiplierForPlayerVehicle();
|
|
if (nDamageType == DAMAGE_TYPE_EXPLOSIVE && m_pParent->IsTank() && NetworkInterface::IsGameInProgress())
|
|
{
|
|
bUseMultiplayerMult = false;
|
|
|
|
bool bInflicitorIsTank = false;
|
|
if (pInflictor && pInflictor->GetIsTypeVehicle())
|
|
{
|
|
CVehicle *pVehicle = static_cast<CVehicle*>(pInflictor);
|
|
if (pVehicle && pVehicle->IsTank())
|
|
{
|
|
bInflicitorIsTank = true;
|
|
}
|
|
}
|
|
|
|
// Don't scale damage if inflictor is a tank (m_pParent->pHandling->m_fWeaponDamageMult already scales it down by ~0.03 to 270 damage).
|
|
if (!bInflicitorIsTank)
|
|
{
|
|
//Don't use weapon damage multiplier (0.03 in handling.dat) and don't use multiplayer multiplier (halves damage done)
|
|
bUseWeaponDamageMult = false;
|
|
|
|
float fExplosiveTankDamageMult = 0.175f;
|
|
static const u32 STICKYBOMB_WEAPON_HASH = ATSTRINGHASH("WEAPON_STICKYBOMB", 0x2c3731d9);
|
|
if (nWeaponHash == STICKYBOMB_WEAPON_HASH)
|
|
{
|
|
fExplosiveTankDamageMult = 0.225f;
|
|
|
|
}
|
|
fApplyDamage *= fExplosiveTankDamageMult;
|
|
}
|
|
}
|
|
|
|
// B*1909265: Anti-vehicle sniper rifle: Do 50% of vehicles health per hit (or 33.3% for tanks).
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if (pWeaponInfo && pWeaponInfo->GetScalesDamageBasedOnMaxVehicleHealth())
|
|
{
|
|
// Don't do damage multipliers
|
|
bUseWeaponDamageMult = false;
|
|
bUseMultiplayerMult = false;
|
|
|
|
// Scale damage based on health and multiplier set in handling data (defaults to 0.5, tanks 0.34).
|
|
fApplyDamage = m_pParent->GetMaxHealth() * m_pParent->pHandling->m_fWeaponDamageScaledToVehHealthMult;
|
|
}
|
|
|
|
// Add multipler for weapon damage against vehicles (B*2109304)
|
|
fApplyDamage *= pWeaponInfo ? pWeaponInfo->GetVehicleDamageModifier() : 1.0f;
|
|
|
|
// Add multipler for Full Metal Jacket ammunition
|
|
if(pInflictor && pInflictor->GetIsTypePed())
|
|
{
|
|
const CPed* pInflictorPed = static_cast<const CPed*>(pInflictor);
|
|
if (pInflictorPed && pWeaponInfo)
|
|
{
|
|
const CAmmoInfo* pAmmoInfo = pWeaponInfo->GetAmmoInfo(pInflictorPed);
|
|
if (pAmmoInfo && pAmmoInfo->GetIsFMJ())
|
|
{
|
|
fApplyDamage *= sfSpecialAmmoFMJDamageMultiplierAgainstVehicles;
|
|
}
|
|
}
|
|
}
|
|
|
|
// all other damage gets scaled by the weapon damage multiplier
|
|
if (bUseWeaponDamageMult)
|
|
{
|
|
fApplyDamage *= m_pParent->pHandling->m_fWeaponDamageMult * m_fScriptWeaponDamageScale;
|
|
}
|
|
|
|
// Scale down the fire damage for Helis/Planes, so they can last longer when in contact with fire
|
|
if(nDamageType == DAMAGE_TYPE_FIRE && (m_pParent->InheritsFromHeli() || m_pParent->InheritsFromPlane()))
|
|
{
|
|
fApplyDamage *= sfAircraftFireDamageMult;
|
|
}
|
|
|
|
// Cap explosive damage to planes in certain situations, so they take more than one hit to kill
|
|
if( nDamageType == DAMAGE_TYPE_EXPLOSIVE &&
|
|
( m_pParent->InheritsFromPlane() ||
|
|
( m_pParent->InheritsFromHeli() &&
|
|
( m_pParent->m_nVehicleFlags.bPlaneResistToExplosion ||
|
|
( static_cast<CHeli*>(m_pParent) && static_cast<CHeli*>(m_pParent)->GetIsCargobob() ) ) ) ) )
|
|
{
|
|
if (!NetworkInterface::IsGameInProgress())
|
|
{
|
|
// Singleplayer
|
|
fApplyDamage = Min(fApplyDamage, sfPlaneExplosionDamageCapInSP);
|
|
}
|
|
else if(m_pParent->m_nVehicleFlags.bPlaneResistToExplosion)
|
|
{
|
|
// Multiplayer
|
|
fApplyDamage = Min(fApplyDamage, sfPlaneExplosionDamageCapInMP);
|
|
bUseWeaponDamageMult = false;
|
|
bUseMultiplayerMult = false;
|
|
}
|
|
else if (pWeaponInfo && pWeaponInfo->GetUsePlaneExplosionDamageCapInMP() &&
|
|
NetworkInterface::IsSessionEstablished() && !NetworkInterface::IsActivitySession() && !NetworkInterface::IsTransitionActive())
|
|
{
|
|
// Multiplayer (Freemode only)
|
|
CPlane* pPlane = static_cast<CPlane*>(m_pParent);
|
|
if (pPlane && ((pPlane->IsLargePlane() || pPlane->GetSeatManager()->GetMaxSeats() > 6 ) || !sbPlaneDamageCapLargePlanesOnly) )
|
|
{
|
|
// Large planes (Cargoplane, Jet, Titan) and high capacity planes (Luxor, Miljet, Shamal)
|
|
fApplyDamage = Min(fApplyDamage, sfPlaneExplosionDamageCapInMP);
|
|
bUseWeaponDamageMult = false;
|
|
bUseMultiplayerMult = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also cap explosive damage to flagged armored cars
|
|
if (nDamageType == DAMAGE_TYPE_EXPLOSIVE && m_pParent->GetVehicleModelInfo() && m_pParent->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_CAPPED_EXPLOSION_DAMAGE))
|
|
{
|
|
TUNE_GROUP_FLOAT(VEHICLE_DAMAGE, EXPLOSIVE_DAMAGE_CAP_FOR_FLAGGED_VEHICLES, 550.0f, 0.0f, 2000.0f, 1.0f);
|
|
fApplyDamage = Min(fApplyDamage, EXPLOSIVE_DAMAGE_CAP_FOR_FLAGGED_VEHICLES);
|
|
bUseWeaponDamageMult = false;
|
|
bUseMultiplayerMult = false;
|
|
}
|
|
|
|
if(m_pParent->ContainsPlayer() && NetworkInterface::IsGameInProgress() && bUseMultiplayerMult)
|
|
{
|
|
if(!m_pParent->InheritsFromPlane() || nDamageType != DAMAGE_TYPE_EXPLOSIVE)
|
|
{
|
|
fApplyDamage *= sfPlayerDamageMultiplayerMult;
|
|
}
|
|
}
|
|
|
|
// GTAV DLC - B*1765844 - reduce melee damage against planes by 50%
|
|
if( m_pParent->InheritsFromPlane() &&
|
|
nDamageType == DAMAGE_TYPE_MELEE )
|
|
{
|
|
fApplyDamage *= sfPlaneDamagedByMeleeMult;
|
|
}
|
|
|
|
// Inform the audio if a rocket collided with the vehicle
|
|
if (pInflictor && pInflictor->GetIsTypeObject() && ((CObject*)pInflictor)->GetAsProjectileRocket())
|
|
{
|
|
if(m_pParent->GetVehicleAudioEntity())
|
|
{
|
|
m_pParent->GetVehicleAudioEntity()->SetHitByRocket();
|
|
}
|
|
}
|
|
|
|
// B*988465- Add vibrations for weapon damage to the vehicle
|
|
if(fApplyDamage > sfVehDamShakeThreshold && nDamageType != DAMAGE_TYPE_FIRE)
|
|
ApplyPadShake(pInflictor, fApplyDamage);
|
|
|
|
//if( fApplyDamage > CAR_CRASH_EVENT_DAMAGE_THRESHOLD )
|
|
// CShockingEventsManager::Add(ECarCrash,m_pParent->GetPosition(),m_pParent,pInflictor);
|
|
if(m_pParent->GetIsRotaryAircraft())
|
|
{
|
|
CRotaryWingAircraft * pRotaryWingAircraft = static_cast<CRotaryWingAircraft*>(m_pParent);
|
|
|
|
if( pRotaryWingAircraft->ApplyDamageToPropellers( pInflictor, fApplyDamage, nComponent ) )
|
|
{
|
|
if (!m_pParent->IsNetworkClone())
|
|
{
|
|
UpdateDamageTrackers(pInflictor, nWeaponHash, nDamageType, fDamage, isAlreadyWrecked, bMeleeDamage);
|
|
}
|
|
|
|
return fApplyDamage;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply vehicle damage modifier
|
|
if( pInflictor && pInflictor->GetIsTypeVehicle() )
|
|
{
|
|
CVehicle* pInflictorVehicle = static_cast<CVehicle*>( pInflictor );
|
|
Assert( pInflictorVehicle );
|
|
CPed* pPedDriver = pInflictorVehicle->GetDriver();
|
|
if( pPedDriver && pPedDriver->IsPlayer() )
|
|
{
|
|
Assert( pPedDriver->GetPlayerInfo() );
|
|
fApplyDamage *= pPedDriver->GetPlayerInfo()->GetPlayerVehicleDamageModifier();
|
|
}
|
|
}
|
|
|
|
// check if this vehicle will take damage from collisions
|
|
// do this after calculations above so we still get pad shake from collisions
|
|
if( ( !forceMinDamage ||
|
|
!CPhysics::ms_bInArenaMode ) &&
|
|
!CanVehicleBeDamaged(pInflictor, nWeaponHash, false))
|
|
{
|
|
if(NetworkInterface::ShouldTriggerDamageEventForZeroDamage(m_pParent))
|
|
{
|
|
UpdateDamageTrackers(pInflictor, nWeaponHash, nDamageType, 0.0f, isAlreadyWrecked, bMeleeDamage, nMaterialId);
|
|
}
|
|
return 0.0f;
|
|
}
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
if (!CanVehicleBeDamagedBasedOnDriverInvincibility())
|
|
return 0.0f;
|
|
|
|
TUNE_GROUP_FLOAT( VEHICLE_DAMAGE_STUNT_MODE, COLLISION_DAMAGE_THRESHOLD, 35.0f, 0.0f, 100000.0f, 1.0f );
|
|
if( CPhysics::ms_bInStuntMode &&
|
|
!CPhysics::ms_bInArenaMode &&
|
|
nDamageType == DAMAGE_TYPE_COLLISION )
|
|
{
|
|
if( fApplyDamage < COLLISION_DAMAGE_THRESHOLD )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector3 vecImpactPosLocal = vecPos;
|
|
if(vecPos.IsNonZero())
|
|
vecImpactPosLocal = VEC3V_TO_VECTOR3(m_pParent->GetTransform().UnTransform(VECTOR3_TO_VEC3V(vecPos)));
|
|
|
|
Vector3 vecImpactDirnLocal = vecDirn;
|
|
if(vecDirn.IsNonZero())
|
|
vecImpactDirnLocal = VEC3V_TO_VECTOR3(m_pParent->GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecDirn)));
|
|
|
|
Vector3 vecImpactNormLocal = vecNorm;
|
|
if(vecNorm.IsNonZero())
|
|
vecImpactNormLocal = VEC3V_TO_VECTOR3(m_pParent->GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecNorm)));
|
|
|
|
// some impacts come specifically from faked wheel impacts
|
|
bool bWheelDamage = false;
|
|
if(PGTAMATERIALMGR->GetPolyFlagVehicleWheel(nMaterialId))
|
|
bWheelDamage = true;
|
|
|
|
if(!bWheelDamage && m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR && nDamageType == DAMAGE_TYPE_MELEE)
|
|
{
|
|
if (!m_pParent->GetDriver()) // If this car has a driver triggering the alarm would be silly (sometimes happened in network games)
|
|
{
|
|
if(m_pParent->GetVehicleAudioEntity())
|
|
{
|
|
m_pParent->GetVehicleAudioEntity()->TriggerAlarm();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ignore wheel impacts for damage to certain parts of the vehicle
|
|
// Except when it is of damage type explosive
|
|
if( (!bWheelDamage || nDamageType == DAMAGE_TYPE_EXPLOSIVE) && m_pParent->GetVehicleType() != VEHICLE_TYPE_BICYCLE) //Don't apply damage to bicyles
|
|
{
|
|
// apply damage to the body as a whole
|
|
ApplyDamageToBody(pInflictor, nDamageType, nWeaponHash, fApplyDamage, vecImpactPosLocal, vecImpactNormLocal, nComponent, isFlameThrowerFire );
|
|
|
|
if(NetworkInterface::IsGameInProgress() && pInflictor && bChainExplosion)
|
|
{
|
|
CPed* pInflictorPed = NULL;
|
|
if(pInflictor->GetIsTypePed())
|
|
{
|
|
pInflictorPed = static_cast<CPed*>(pInflictor);
|
|
|
|
if(pInflictorPed->GetPlayerWanted() && pInflictorPed->GetPlayerWanted()->GetWantedLevel() >= WANTED_LEVEL1)
|
|
{
|
|
bNoEngineDamage = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// apply damage specifically to the engine
|
|
if(!bNoEngineDamage &&
|
|
!CPhysics::ms_bInArenaMode )
|
|
{
|
|
ApplyDamageToEngine(pInflictor, nDamageType, fApplyDamage, vecImpactPosLocal, vecImpactNormLocal, vecImpactDirnLocal, bFireDriveby, bIsAccurate, fDamageRadius, bAvoidExplosions, nWeaponHash, bChainExplosion);
|
|
}
|
|
|
|
// apply damage to smashable components (mainly lights, and glass smashing due to deformation)
|
|
ApplyDamageToGlass(fApplyDamage, vecImpactPosLocal, vecImpactNormLocal, vecImpactDirnLocal);
|
|
}
|
|
|
|
// can't apply damage to network clones
|
|
if (NetworkUtils::IsNetworkCloneOrMigrating(m_pParent))
|
|
{
|
|
if (fApplyDamage > 0.0f)
|
|
{
|
|
static_cast<CNetObjVehicle*>(m_pParent->GetNetworkObject())->CacheCollisionFault(pInflictor, nWeaponHash);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
// apply damage to each wheel
|
|
ApplyDamageToWheels(pInflictor, nDamageType, fApplyDamage, vecImpactPosLocal, vecImpactNormLocal, vecImpactDirnLocal, nComponent, nMaterialId, nPieceIndex);
|
|
|
|
if( m_pParent->GetVehicleType() != VEHICLE_TYPE_BICYCLE )
|
|
{
|
|
// do other responses to damage
|
|
ApplyDamageToOverallHealth(pInflictor, nDamageType, nWeaponHash, fApplyDamage, vecImpactPosLocal, vecImpactDirnLocal, nComponent, bChainExplosion);
|
|
}
|
|
|
|
if(m_pParent->InheritsFromBike())
|
|
{
|
|
if(fApplyDamage > BIKE_KNOCKOVER_DAMAGE)
|
|
((CBike*)m_pParent)->m_nBikeFlags.bOnSideStand = false;
|
|
}
|
|
|
|
if(m_pParent->InheritsFromPlane())
|
|
{
|
|
CPlane* pPlane = static_cast<CPlane*>(m_pParent);
|
|
pPlane->GetAircraftDamage().ApplyDamageToPlane(pPlane,pInflictor, nDamageType, nWeaponHash, fApplyDamage, vecPos, vecNorm, vecDirn, nComponent, nMaterialId, nPieceIndex, bFireDriveby, bLandingDamage, fDamageRadius);
|
|
pPlane->GetLandingGearDamage().ApplyDamageToPlane(pPlane,pInflictor, nDamageType, nWeaponHash, fApplyDamage, vecPos, vecNorm, vecDirn, nComponent, nMaterialId, nPieceIndex, bFireDriveby, bLandingDamage, fDamageRadius);
|
|
|
|
if(bLandingDamage && !m_pParent->GetIsRotaryAircraft()) // Don't apply the landing damage to non-rotary airplane itself
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
//Update the damage tracker for the network game.
|
|
float newHealth = m_pParent->GetHealth() + GetEngineHealth() + GetPetrolTankHealth();
|
|
if (bIncludeBodyDamage)
|
|
{
|
|
newHealth += m_fBodyHealth;
|
|
}
|
|
|
|
for(s32 i=0; i<m_pParent->GetNumWheels(); i++)
|
|
{
|
|
newHealth += GetTyreHealth(i);
|
|
newHealth += GetSuspensionHealth(i);
|
|
}
|
|
UpdateDamageTrackers(pInflictor, nWeaponHash, nDamageType, previousHealth - newHealth, isAlreadyWrecked, bMeleeDamage, nMaterialId);
|
|
|
|
// if the player ped has caused this vehicle damage then store this vehicle (so we can get it to leak petrol)
|
|
if (pInflictor && pInflictor->GetIsTypePed())
|
|
{
|
|
CPed* pInflictorPed = static_cast<CPed*>(pInflictor);
|
|
if (pInflictorPed && pInflictorPed->IsLocalPlayer())
|
|
{
|
|
g_vfxVehicle.SetRecentlyDamagedByPlayer(m_pParent);
|
|
}
|
|
}
|
|
|
|
// if the player ped has caused this vehicle damage then store this vehicle (so we can get it to leak petrol)
|
|
if (pInflictor && (pInflictor->GetIsTypeVehicle() || pInflictor->GetIsTypeBuilding()))
|
|
{
|
|
if (fApplyDamage>=VFXVEHICLE_RECENTLY_CRASHED_DAMAGE_THRESH)
|
|
{
|
|
g_vfxVehicle.SetRecentlyCrashed(m_pParent, fApplyDamage);
|
|
}
|
|
}
|
|
|
|
if (NetworkInterface::IsGameInProgress() && !isAlreadyWrecked)
|
|
{
|
|
if (m_pParent->GetNetworkObject() && !m_pParent->IsNetworkClone())
|
|
{
|
|
CNetObjVehicle* pNetObjVehicle = static_cast<CNetObjVehicle*>(m_pParent->GetNetworkObject());
|
|
if (pNetObjVehicle)
|
|
pNetObjVehicle->DirtyVehicleDamageStatusDataSyncNode();
|
|
}
|
|
}
|
|
|
|
return fDamage;
|
|
}
|
|
|
|
void CVehicleDamage::UpdateDamageTrackers(CEntity* pInflictor, u32 nWeaponHash, const eDamageType nDamageType, const float totalDamage, const bool isAlreadyWrecked, const bool isMeleeDamage, phMaterialMgr::Id nMaterialId)
|
|
{
|
|
//Check if we need to lock for future damage tracking.
|
|
if (m_pParent->m_nVehicleFlags.bDamageTrackingLocked)
|
|
return;
|
|
|
|
u32 lastDamgedTime = m_pParent->GetWeaponDamagedTime();
|
|
|
|
//Need to clear this in a network game as the WeaponDamageEntity
|
|
//is used to determine player kills.
|
|
if (NetworkInterface::IsGameInProgress() && !pInflictor && (0.0f<totalDamage || NetworkInterface::ShouldTriggerDamageEventForZeroDamage(m_pParent)) && !isAlreadyWrecked)
|
|
{
|
|
m_pParent->ResetWeaponDamageInfo();
|
|
}
|
|
|
|
CEntity* damager = pInflictor;
|
|
if(damager)
|
|
{
|
|
//Time last since last damage applied to the vehicle
|
|
const u32 timeSinceLastDamage = fwTimer::GetTimeInMilliseconds() - lastDamgedTime;
|
|
|
|
if(isAlreadyWrecked)
|
|
{
|
|
//Don't set weapon damage after the vehicle is already destroyed.
|
|
damager = 0;
|
|
}
|
|
else if(nWeaponHash == WEAPONTYPE_RAMMEDBYVEHICLE && !pInflictor->GetIsTypeVehicle() && timeSinceLastDamage < 1000)
|
|
{
|
|
//Don't set weapon damage if it was rammed by a vehicle and the inflictor is not a vehicle for the first 1s.
|
|
damager = 0;
|
|
}
|
|
else if(nWeaponHash == WEAPONTYPE_RAMMEDBYVEHICLE && pInflictor->GetIsTypePed())
|
|
{
|
|
//Don't set weapon damage if it was rammed by a vehicle and the inflictor is a ped.
|
|
damager = 0;
|
|
}
|
|
else
|
|
{
|
|
//Previous damage entity was a vehicle
|
|
const bool previousDamagerIsVehicle = m_pParent->GetWeaponDamageEntity() ? m_pParent->GetWeaponDamageEntity()->GetIsTypeVehicle() : false;
|
|
|
|
//Damages done by vehicles are attributed to the 1st vehicle causing damage in the 1st place, only if the WeaponDamageHash is WEAPONTYPE_RUNOVERBYVEHICLE
|
|
if (timeSinceLastDamage <= 1000 && nWeaponHash == WEAPONTYPE_RUNOVERBYVEHICLE && previousDamagerIsVehicle && nWeaponHash == m_pParent->GetWeaponDamageHash())
|
|
{
|
|
damager = m_pParent->GetWeaponDamageEntity();
|
|
}
|
|
else if (pInflictor->GetIsTypeBuilding() || pInflictor->GetIsTypeAnimatedBuilding() || pInflictor->GetIsTypeLight())
|
|
{
|
|
if (WEAPONTYPE_RAMMEDBYVEHICLE == nWeaponHash || WEAPONTYPE_EXPLOSION == nWeaponHash)
|
|
{
|
|
//Previous damage entity was a ped
|
|
const bool previousDamagerIsPed = m_pParent->GetWeaponDamageEntity() ? m_pParent->GetWeaponDamageEntity()->GetIsTypePed() : false;
|
|
if (timeSinceLastDamage <= 1000 && previousDamagerIsPed)
|
|
{
|
|
damager = m_pParent->GetWeaponDamageEntity();
|
|
nWeaponHash = m_pParent->GetWeaponDamageHash();
|
|
}
|
|
else if (m_pParent->GetDriver())
|
|
{
|
|
damager = m_pParent->GetDriver();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pParent->SetWeaponDamageInfo(damager, nWeaponHash, fwTimer::GetTimeInMilliseconds(), isMeleeDamage);
|
|
}
|
|
}
|
|
|
|
//Update the network stuff
|
|
UpdateNetworkDamageTracker(damager, totalDamage, nWeaponHash, isAlreadyWrecked, isMeleeDamage, nMaterialId);
|
|
|
|
//Setup stats damage to vehicles
|
|
CStatsMgr::RegisterVehicleDamage(GetParent(), damager, nDamageType, totalDamage, isAlreadyWrecked);
|
|
|
|
#if VEHICLE_DEFORMATION_STOP_AFTER_WRECKAGE
|
|
//Check if we need to lock for future damage tracking.
|
|
if (!m_pParent->m_nVehicleFlags.bDamageTrackingLocked && m_pParent->GetStatus() == STATUS_WRECKED)
|
|
{
|
|
m_pParent->m_nVehicleFlags.bDamageTrackingLocked = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Depending on the flags that have been set for this vehicle we might
|
|
// decide not to do damage.
|
|
bool CVehicleDamage::CanVehicleBeDamaged(CEntity* pInflictor, u32 nWeaponHash, bool bDoNetworkCloneCheck)
|
|
{
|
|
CPed* pInflictorPed = NULL;
|
|
if(pInflictor && pInflictor->GetIsTypePed())
|
|
pInflictorPed = (CPed*)pInflictor;
|
|
|
|
// Player can't damage the vehicle he is attached to
|
|
if (pInflictorPed && pInflictorPed->IsPlayer() && pInflictorPed->GetAttachParent() == m_pParent && nWeaponHash != 0)
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if(pWeaponInfo && pWeaponInfo->GetDamageType()!=DAMAGE_TYPE_EXPLOSIVE && pWeaponInfo->GetDamageType()!=DAMAGE_TYPE_FIRE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(pInflictor && pInflictor->GetIsTypeObject())
|
|
{
|
|
// Do not apply collision damage to vehicle if hits a parachute
|
|
if(CTaskParachute::IsParachute(*((CObject *)pInflictor)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(pInflictorPed && pInflictorPed->GetPedResetFlag(CPED_RESET_FLAG_IsParachuting))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_pParent->CanPhysicalBeDamaged(pInflictor, nWeaponHash, bDoNetworkCloneCheck) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CVehicleDamage::CanVehicleBeDamagedBasedOnDriverInvincibility()
|
|
{
|
|
if(NetworkInterface::IsGameInProgress() && m_pParent)
|
|
{
|
|
CPed* pDriver = m_pParent->GetDriver();
|
|
if (pDriver && pDriver->IsPlayer() && pDriver->GetNetworkObject())
|
|
{
|
|
CNetObjPlayer* netObjPlayer = static_cast<CNetObjPlayer *>(pDriver->GetNetworkObject());
|
|
if(netObjPlayer && netObjPlayer->GetRespawnInvincibilityTimer() > 0)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CVehicleDamage::RefillOilAndPetrolTanks()
|
|
{
|
|
if(m_pParent && m_pParent->pHandling)
|
|
{
|
|
m_fPetrolTankLevel = m_pParent->pHandling->m_fPetrolTankVolume;
|
|
m_fOilLevel = m_pParent->pHandling->m_fOilVolume;
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::FixVehicleDamage(bool resetFrag, bool allowNetwork)
|
|
{
|
|
m_fBodyHealth = GetDefaultBodyHealthMax(m_pParent);
|
|
|
|
m_fPetrolTankHealth = Max(m_pParent->GetMaxHealth(), VEH_DAMAGE_HEALTH_STD);
|
|
|
|
RefillOilAndPetrolTanks();
|
|
|
|
if( m_pParent->m_bHasDeformationBeenApplied )
|
|
{
|
|
GetDeformation()->ResetDamage();
|
|
}
|
|
|
|
// remove bouncing panels
|
|
for(int i=0; i<MAX_BOUNCING_PANELS; i++)
|
|
m_BouncingPanels[i].m_nComponentIndex = -1;
|
|
|
|
if(m_pParent->GetVehicleFragInst())
|
|
{
|
|
// Store the speed to restore it later
|
|
Vector3 StoredSpeed = m_pParent->GetVelocity();
|
|
Vector3 StoredTurnSpeed = m_pParent->GetAngVelocity();
|
|
|
|
CTrailer* pAttachedTrailer = m_pParent->GetAttachedTrailer();
|
|
|
|
// Don't want stale contacts left around with bad collider pointers
|
|
if(!CPhysics::GetLevel()->IsInLevel(m_pParent->GetCurrentPhysicsInst()->GetLevelIndex()))
|
|
{
|
|
CPhysics::GetSimulator()->AddActiveObject(m_pParent->GetCurrentPhysicsInst(), false);
|
|
}
|
|
|
|
// Ronseal...
|
|
if (resetFrag || m_pParent->GetHavePartsBrokenOff())
|
|
ResetFragCacheEntry();
|
|
m_pParent->SetHavePartsBrokenOff(false);
|
|
|
|
// Restore the speed to what it was before
|
|
|
|
//Zero velocity if above 80m/s
|
|
if(StoredSpeed.Mag2() > rage::square(80.0f))
|
|
{
|
|
//Clear the velocity.
|
|
StoredSpeed.Zero();
|
|
}
|
|
m_pParent->SetVelocity(StoredSpeed);
|
|
|
|
|
|
//Zero angular velocity if above 80m/s
|
|
if(StoredTurnSpeed.Mag2() > rage::square(80.0f))
|
|
{
|
|
//Clear the angular velocity.
|
|
StoredTurnSpeed.Zero();
|
|
}
|
|
m_pParent->SetAngVelocity(StoredTurnSpeed);
|
|
|
|
if(pAttachedTrailer)
|
|
{
|
|
pAttachedTrailer->AttachToParentVehicle(m_pParent, false);
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<m_pParent->GetNumWheels(); i++)
|
|
m_pParent->GetWheel(i)->ResetDamage();
|
|
|
|
for(int i=0; i<m_pParent->GetNumDoors(); i++)
|
|
m_pParent->GetDoor(i)->Fix(m_pParent);
|
|
|
|
|
|
//Checks if any network clone plane's broken doors have been reattached above and breaks them off again: B*1931220
|
|
if (m_pParent->IsNetworkClone() && m_pParent->GetNetworkObject() && m_pParent->InheritsFromPlane())
|
|
{
|
|
CNetObjPlane* pNetObjPlane = static_cast<CNetObjPlane*>(m_pParent->GetNetworkObject());
|
|
|
|
pNetObjPlane->UpdateCloneDoorsBroken();
|
|
}
|
|
|
|
m_pParent->m_Transmission.SetEngineHealth(Max(m_pParent->GetMaxHealth(), ENGINE_HEALTH_MAX));
|
|
m_pParent->m_Transmission.RefillKERS();
|
|
m_pParent->ResetBrokenAndHiddenFlags();
|
|
|
|
m_LightState.Reset();
|
|
m_UpdateLightBones = false;
|
|
m_SirenState.Reset();
|
|
m_UpdateSirenBones = false;
|
|
|
|
m_pParent->ResetWheelBroken();
|
|
|
|
if (!m_pParent->IsNetworkClone() || allowNetwork)
|
|
m_pParent->SetHealth( CREATED_VEHICLE_HEALTH, allowNetwork );
|
|
|
|
m_fDynamicSpoilerDamage = 0.0f;
|
|
}
|
|
|
|
|
|
void CVehicleDamage::FixVehicleDeformation()
|
|
{
|
|
GetDeformation()->ResetDamage();
|
|
}
|
|
|
|
void CVehicleDamage::ResetFragCacheEntry()
|
|
{
|
|
if(fragInst* pFragInst = m_pParent->GetVehicleFragInst())
|
|
{
|
|
// Preserve the disabled breaking flags since that gets reset when we lose our cache entry
|
|
bool isBreakingFlagOverridden = pFragInst->IsBreakingFlagOverridden();
|
|
bool isBreakingDisabled = pFragInst->IsBreakingDisabled();
|
|
|
|
// This will fix broken off frag parts
|
|
fragCacheEntry *pCacheEntry = pFragInst->GetCacheEntry();
|
|
if(pCacheEntry)
|
|
{
|
|
FRAGCACHEMGR->ReleaseCacheEntry(pCacheEntry);
|
|
}
|
|
pFragInst->PutIntoCache();
|
|
|
|
if(isBreakingFlagOverridden)
|
|
{
|
|
pFragInst->SetDisableBreakable(isBreakingDisabled);
|
|
}
|
|
|
|
m_pParent->ActivatePhysics();
|
|
|
|
fwAttachmentEntityExtension* pExtension = m_pParent->GetAttachmentExtension();
|
|
if (pExtension)
|
|
pExtension->SetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE, true); // Necessary to stop spurious assert in SetMatrix().
|
|
|
|
m_pParent->SetMatrix(MAT34V_TO_MATRIX34(m_pParent->GetMatrix()), true, true, true);
|
|
|
|
if (pExtension)
|
|
pExtension->SetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_MATRIX_CHANGE, false);
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
NetworkInterface::OnVehiclePartDamage(m_pParent, VEH_BUMPER_F, VEH_BB_NONE);
|
|
NetworkInterface::OnVehiclePartDamage(m_pParent, VEH_BUMPER_R, VEH_BB_NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
float CVehicleDamage::GetOverallHealth() const
|
|
{
|
|
return m_pParent->GetHealth();
|
|
}
|
|
float CVehicleDamage::GetEngineHealth() const
|
|
{
|
|
return m_pParent->m_Transmission.GetEngineHealth();
|
|
}
|
|
bool CVehicleDamage::GetEngineOnFire() const
|
|
{
|
|
return m_pParent->m_Transmission.GetEngineOnFire();
|
|
}
|
|
void CVehicleDamage::ResetEngineHealth()
|
|
{
|
|
m_pParent->m_Transmission.ResetEngineHealth();
|
|
}
|
|
void CVehicleDamage::SetEngineHealth( float fHealth )
|
|
{
|
|
m_pParent->m_Transmission.SetEngineHealth(fHealth);
|
|
}
|
|
|
|
void CVehicleDamage::SetBodyHealth(float fHealth)
|
|
{
|
|
ENABLE_FRAG_OPTIMIZATION_ONLY(m_pParent->GiveFragCacheEntry();)
|
|
|
|
m_fBodyHealth = fHealth;
|
|
vehicleDebugf3("%s setting body health: %.2f", m_pParent->GetLogName(), m_fBodyHealth);
|
|
}
|
|
|
|
float CVehicleDamage::GetSuspensionHealth(int i) const
|
|
{
|
|
if(const CWheel * pWheel = m_pParent->GetWheel(i))
|
|
return pWheel->GetSuspensionHealth();
|
|
return 0.0f;
|
|
}
|
|
float CVehicleDamage::GetTyreHealth(int i) const
|
|
{
|
|
if(const CWheel * pWheel = m_pParent->GetWheel(i))
|
|
return pWheel->GetTyreHealth();
|
|
return 0.0f;
|
|
}
|
|
|
|
int CVehicleDamage::GetAnyWheelsBurst() const
|
|
{
|
|
for(int i=0; i<m_pParent->GetNumWheels(); i++)
|
|
{
|
|
if(m_pParent->GetWheel(i)->GetTyreHealth() < TYRE_HEALTH_DEFAULT)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
#define NUM_MISC_CAR_BREAK_PARTS (4)
|
|
eHierarchyId aMiscCarBreakParts[NUM_MISC_CAR_BREAK_PARTS] =
|
|
{
|
|
VEH_BUMPER_F,
|
|
VEH_BUMPER_R,
|
|
VEH_WING_RF,
|
|
VEH_WING_LF
|
|
};
|
|
|
|
#define NUM_EXTRA_TRAILER_BREAK_PARTS (VEH_LAST_BREAKABLE_EXTRA + 1 - VEH_BREAKABLE_EXTRA_1)
|
|
eHierarchyId aExtraTrailerBreakParts[NUM_EXTRA_TRAILER_BREAK_PARTS] =
|
|
{
|
|
VEH_BREAKABLE_EXTRA_1,
|
|
VEH_BREAKABLE_EXTRA_2,
|
|
VEH_BREAKABLE_EXTRA_3,
|
|
VEH_BREAKABLE_EXTRA_4,
|
|
VEH_BREAKABLE_EXTRA_5,
|
|
VEH_BREAKABLE_EXTRA_6,
|
|
VEH_BREAKABLE_EXTRA_7,
|
|
VEH_BREAKABLE_EXTRA_8,
|
|
VEH_BREAKABLE_EXTRA_9,
|
|
VEH_BREAKABLE_EXTRA_10
|
|
};
|
|
|
|
dev_float sfBlowUpCarDoorProb = 0.5f;
|
|
dev_float sfBlowUpCarWheelProb = 0.2f;
|
|
dev_float sfBlowUpBikeWheelProb = 0.5f;
|
|
dev_float sfBlowUpCarExtrasProb = 0.5f;
|
|
dev_float sfBlowUpCarDeleteCompProbNetwork = 0.6f;
|
|
|
|
|
|
void CVehicleDamage::BreakOffWheel(int wheelIndex, float ptfxProbability, float deleteProbability, float burstTyreProbability, bool dueToExplosion, bool bNetworkCheck)
|
|
{
|
|
if (NetworkInterface::IsGameInProgress() && bNetworkCheck && m_pParent->IsNetworkClone())
|
|
return;
|
|
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
bool bDeleteParts = !m_pParent->CarPartsCanBreakOff();
|
|
|
|
CWheel* pWheel = m_pParent->GetWheel(wheelIndex);
|
|
int nFragChild = pWheel->GetFragChild();
|
|
if(nFragChild > -1)
|
|
{
|
|
fragPhysicsLOD* pTypePhysics = pFragInst->GetTypePhysics();
|
|
fragTypeChild* pChild = pTypePhysics->GetAllChildren()[nFragChild];
|
|
if(pChild->GetOwnerGroupPointerIndex() > 0 && !pFragInst->GetChildBroken(nFragChild))
|
|
{
|
|
m_pParent->PartHasBrokenOff(pWheel->GetHierarchyId());
|
|
|
|
m_pParent->SetWheelBroken(wheelIndex);
|
|
|
|
// depending on distance, just delete component instead of having it come flying off
|
|
if(bDeleteParts || fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < deleteProbability)
|
|
{
|
|
pFragInst->DeleteAbove(nFragChild);
|
|
}
|
|
else
|
|
{
|
|
fragInst* pNewFragInst = pFragInst->BreakOffAbove(nFragChild);
|
|
if (pNewFragInst)
|
|
{
|
|
CEntity* pNewEntity = CPhysics::GetEntityFromInst(pNewFragInst);
|
|
if (pNewEntity)
|
|
{
|
|
// Clone the vehicle archetype in order to increase damping for the wheels.
|
|
phArchetypeDamp* pNewArch = static_cast<phArchetypeDamp*>(pNewEntity->GetPhysArch()->Clone());
|
|
pNewArch->ActivateDamping(phArchetypeDamp::LINEAR_C,Vector3(0.3f,0.3f,0.3f));
|
|
pNewArch->ActivateDamping(phArchetypeDamp::LINEAR_V,Vector3(0.1f,0.1f,0.1f));
|
|
pNewArch->ActivateDamping(phArchetypeDamp::LINEAR_V2,Vector3(0.05f,0.05f,0.05f));
|
|
pNewArch->ActivateDamping(phArchetypeDamp::ANGULAR_C,Vector3(0.02f,0.02f,0.02f));
|
|
pNewArch->ActivateDamping(phArchetypeDamp::ANGULAR_V,Vector3(0.1f,0.1f,0.1f));
|
|
pNewArch->ActivateDamping(phArchetypeDamp::ANGULAR_V2,Vector3(0.15f,0.15f,0.15f));
|
|
|
|
pNewFragInst->SetArchetype(pNewArch);
|
|
|
|
if(dueToExplosion && g_DrawRand.GetFloat() < ptfxProbability)
|
|
{
|
|
g_vfxVehicle.TriggerPtFxVehicleDebris(pNewEntity);
|
|
}
|
|
|
|
// If it is already burst we need to tell the shader to render the new wheel without the tyre
|
|
// - And sometimes we put the tyre back just to keep things interesting
|
|
if(pWheel->GetTyreHealth() <= 0.0f || fwRandom::GetRandomNumberInRange(0.0f, 1.0f) <= burstTyreProbability)
|
|
{
|
|
CCustomShaderEffectVehicle* pShader = static_cast<CCustomShaderEffectVehicle*>(pNewEntity->GetDrawHandler().GetShaderEffect());
|
|
Assert(pShader);
|
|
// Flag that tyres need to be deformed
|
|
pShader->SetEnableTyreDeform(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::ApplyAdditionalVelocityToVehiclePart(phInst& oldInst, phInst& newInst, const CVehicleExplosionInfo& vehicleExplosionInfo)
|
|
{
|
|
if(CEntity* pNewEntity = CPhysics::GetEntityFromInst(&newInst))
|
|
{
|
|
if(pNewEntity->GetIsPhysical())
|
|
{
|
|
Vec3V explosionDirection = NormalizeSafe(Subtract(newInst.GetCenterOfMass(),oldInst.GetCenterOfMass()),Vec3V(V_Z_AXIS_WZERO));
|
|
|
|
// GTAV - B*1803633 - rotate the explosion direction into world space so objects don't behave strangely when
|
|
// the parent vehicle is upside down.
|
|
explosionDirection = m_pParent->GetTransform().Transform3x3( explosionDirection );
|
|
const Vec3V additionalVelocity = vehicleExplosionInfo.ComputeAdditionalPartVelocity(explosionDirection);
|
|
CPhysical* pNewPhysical = static_cast<CPhysical*>(pNewEntity);
|
|
pNewPhysical->ApplyImpulseCg(VEC3V_TO_VECTOR3(Scale(additionalVelocity,ScalarVFromF32(pNewPhysical->GetMass()))),CPhysical::IF_InitialImpulse);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::BreakOffPart(eHierarchyId hierarchyId, int fragChild, fragInst *pFragInst, const CVehicleExplosionInfo* vehicleExplosionInfo, bool bDeleteParts, float fDeleteProb, float fxProb, bool bApplyAdditionalVelocityToPart)
|
|
{
|
|
m_pParent->PartHasBrokenOff(hierarchyId);
|
|
|
|
// depending on distance, just delete component instead of having it come flying off
|
|
if(bDeleteParts || fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < fDeleteProb || CVehicle::TooManyVehicleBreaksRecently() )
|
|
{
|
|
pFragInst->DeleteAbove(fragChild);
|
|
}
|
|
else
|
|
{
|
|
fragInst* pNewFragInst = pFragInst->BreakOffAbove(fragChild);
|
|
if (pNewFragInst)
|
|
{
|
|
if(bApplyAdditionalVelocityToPart)
|
|
{
|
|
ApplyAdditionalVelocityToVehiclePart(*pFragInst,*pNewFragInst,*vehicleExplosionInfo);
|
|
}
|
|
CEntity* pNewEntity = CPhysics::GetEntityFromInst(pNewFragInst);
|
|
if (pNewEntity)
|
|
{
|
|
if (g_DrawRand.GetFloat()<fxProb)
|
|
{
|
|
g_vfxVehicle.TriggerPtFxVehicleDebris(pNewEntity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
}
|
|
|
|
void CVehicleDamage::BlowUpCarParts(CEntity* pInflictor, int eBreakingState)
|
|
{
|
|
if(ms_fBreakingPartsReduction >= 1.0f)
|
|
return;
|
|
|
|
#if __BANK
|
|
if( m_pParent->GetFragInst() &&
|
|
m_pParent->GetFragInst()->IsBreakingDisabled() )
|
|
{
|
|
return;
|
|
}
|
|
#endif // #if __BANK
|
|
|
|
if(eBreakingState != Break_Off_Car_Parts_Immediately && CApplyDamage::GetNumDamagePending(m_pParent) > 0)
|
|
{
|
|
m_uBlowUpCarPartsPending = (u8)eBreakingState;
|
|
return;
|
|
}
|
|
|
|
Assert(!ms_bDisableVehiclePartCollisionOnBreak);
|
|
ms_bDisableVehiclePartCollisionOnBreak = true;
|
|
Assert(m_pParent->GetVehicleFragInst());
|
|
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
|
|
bool bDeleteParts = !m_pParent->CarPartsCanBreakOff();
|
|
|
|
float fxProb = 0.0f;
|
|
if (m_pParent->GetVehicleType()==VEHICLE_TYPE_HELI)
|
|
fxProb = 0.75f;
|
|
else if (m_pParent->GetVehicleType()==VEHICLE_TYPE_CAR)
|
|
fxProb = 0.25f;
|
|
|
|
float blowUpWheelProb = sfBlowUpCarWheelProb;
|
|
if (m_pParent->GetVehicleType()==VEHICLE_TYPE_BIKE)
|
|
blowUpWheelProb = sfBlowUpBikeWheelProb;
|
|
|
|
float fDistToCamSqr = LARGE_FLOAT;
|
|
if(m_pParent->IsVisible())
|
|
{
|
|
// if heli's are on screen, don't delete components
|
|
if(m_pParent->GetVehicleType()==VEHICLE_TYPE_HELI)
|
|
fDistToCamSqr = 0.0f;
|
|
else
|
|
{
|
|
fDistToCamSqr = CVfxHelper::GetDistSqrToCamera(m_pParent->GetTransform().GetPosition());
|
|
}
|
|
}
|
|
|
|
// Compute a chance to deletion probability
|
|
const CVehicleExplosionInfo* vehicleExplosionInfo = m_pParent->GetVehicleModelInfo()->GetVehicleExplosionInfo();
|
|
const CVehicleExplosionLOD& vehicleExplosionLOD = vehicleExplosionInfo->GetVehicleExplosionLODFromDistanceSq(fDistToCamSqr);
|
|
float fDeleteProb = vehicleExplosionLOD.GetPartDeletionChance();
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
fDeleteProb = Max(sfBlowUpCarDeleteCompProbNetwork,fDeleteProb);
|
|
|
|
// doors
|
|
if (!m_pParent->IsNetworkClone())
|
|
{
|
|
for(int i=0; i<m_pParent->GetNumDoors(); i++)
|
|
{
|
|
CCarDoor* door = m_pParent->GetDoor(i);
|
|
if(door->GetFragChild() > 0 && door->GetIsIntact(m_pParent) && door->GetDoorAllowedToBeBrokenOff())
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < sfBlowUpCarDoorProb*GetVehicleExplosionBreakChanceMultiplier())
|
|
{
|
|
float fPartFxProb = fxProb;
|
|
float fPartDeleteProb = fDeleteProb;
|
|
if(ms_fBreakingPartsReduction >= 0.6f)
|
|
{
|
|
fPartDeleteProb = 100;
|
|
}
|
|
|
|
BreakOffPart( door->GetHierarchyId(), door->GetFragChild(), pFragInst, vehicleExplosionInfo, bDeleteParts, fPartDeleteProb, fPartFxProb );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// wheels
|
|
if (!m_pParent->IsNetworkClone())
|
|
{
|
|
if(m_pParent->m_nVehicleFlags.bCanBreakOffWheelsWhenBlowUp)
|
|
{
|
|
for(int i=0; i<m_pParent->GetNumWheels(); i++)
|
|
{
|
|
// GTAV - B*1808115 - bike will sink into the ground 100% of the time.
|
|
// don't blow both wheels of a bike to stop it sinking into the ground
|
|
bool bCanBreakOff = true;
|
|
if(m_pParent->InheritsFromBike() && m_pParent->GetWheel(i) && m_pParent->GetWheel(i)->GetConfigFlags().IsFlagSet(WCF_REARWHEEL))
|
|
{
|
|
CBike *pBike = static_cast<CBike*>(m_pParent);
|
|
if(!pBike->m_nBikeFlags.bHasRearSuspension)
|
|
{
|
|
bCanBreakOff = false;
|
|
}
|
|
}
|
|
|
|
if( bCanBreakOff && !m_pParent->GetWheelBroken( 1 - i ) )
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < blowUpWheelProb*GetVehicleExplosionBreakChanceMultiplier())
|
|
{
|
|
float fPartFxProb = fxProb;
|
|
float fPartDeleteProb = fDeleteProb;
|
|
if(ms_fBreakingPartsReduction >= 0.5f)
|
|
{
|
|
fPartDeleteProb = 100;
|
|
}
|
|
|
|
BreakOffWheel(i, fPartFxProb, fPartDeleteProb, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// extras and misc
|
|
|
|
// go thru all the fragment children (there's more of them)
|
|
for(int nChild=0; nChild<pFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
|
|
{
|
|
if(pFragInst->GetChildBroken(nChild))
|
|
continue;
|
|
|
|
int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID());
|
|
|
|
if(m_pParent->GetIsRotaryAircraft())
|
|
{
|
|
CRotaryWingAircraft *pRotaryAircraft = (CRotaryWingAircraft *)m_pParent;
|
|
if(boneIndex == pRotaryAircraft->GetBoneIndex(HELI_TAIL))
|
|
{
|
|
if(!pRotaryAircraft->GetCanBreakOffTailBoom() || pRotaryAircraft->GetBreakOffTailBoomPending())
|
|
continue;
|
|
}
|
|
}
|
|
|
|
float blowUpCarExtrasProb = sfBlowUpCarExtrasProb*GetVehicleExplosionBreakChanceMultiplier();
|
|
|
|
// in MP blow off all extras, so the vehicle can sync correctly - otherwise it may bounce around trying to blend to an unobtainable position
|
|
if (m_pParent->GetNetworkObject())
|
|
{
|
|
blowUpCarExtrasProb = 1.0f;
|
|
}
|
|
|
|
// go through each of the possible extras
|
|
if(!m_pParent->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_STRONG))
|
|
{
|
|
int nExtra=VEH_EXTRA_1;
|
|
|
|
// GTAV - B*2362495 - We can't break off VEH_EXTRA_1 on the insurgent because the turret weapon is attached to it
|
|
if( ( MI_CAR_INSURGENT.IsValid() &&
|
|
m_pParent->GetModelIndex() == MI_CAR_INSURGENT.GetModelIndex() ) ||
|
|
( MI_CAR_LIMO2.IsValid() &&
|
|
m_pParent->GetModelIndex() == MI_CAR_LIMO2.GetModelIndex() ) ||
|
|
( MI_CAR_INSURGENT3.IsValid() &&
|
|
m_pParent->GetModelIndex() == MI_CAR_INSURGENT3.GetModelIndex() ) )
|
|
{
|
|
nExtra = VEH_EXTRA_2;
|
|
}
|
|
|
|
for(; nExtra<=VEH_LAST_EXTRA; nExtra++)
|
|
{
|
|
// if the bone index of this child matches the bone index of the extra that's turned off then delete this component
|
|
if(m_pParent->GetBoneIndex((eHierarchyId)nExtra) == boneIndex)
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f, 1.0f) <= sfBlowUpCarExtrasProb)
|
|
{
|
|
float fPartFxProb = fxProb;
|
|
float fPartDeleteProb = fDeleteProb;
|
|
if(ms_fBreakingPartsReduction >= 0.2f)
|
|
{
|
|
fPartDeleteProb = 100;
|
|
}
|
|
|
|
BreakOffPart( (eHierarchyId)nExtra, nChild, pFragInst, vehicleExplosionInfo, bDeleteParts, fPartDeleteProb, fPartFxProb );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for( int nPanel = VEH_FIRST_BREAKABLE_PANEL; nPanel <= VEH_LAST_BREAKABLE_PANEL; nPanel++ )
|
|
{
|
|
// if the bone index of this child matches the bone index of the extra that's turned off then delete this component
|
|
if( m_pParent->GetBoneIndex( (eHierarchyId)nPanel ) == boneIndex )
|
|
{
|
|
if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) <= sfBlowUpCarExtrasProb )
|
|
{
|
|
float fPartFxProb = fxProb;
|
|
float fPartDeleteProb = fDeleteProb;
|
|
if( ms_fBreakingPartsReduction >= 0.2f )
|
|
{
|
|
fPartDeleteProb = 100;
|
|
}
|
|
|
|
BreakOffPart( (eHierarchyId)nPanel, nChild, pFragInst, vehicleExplosionInfo, bDeleteParts, fPartDeleteProb, fPartFxProb );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
int nNumBreakableParts = NUM_MISC_CAR_BREAK_PARTS;
|
|
eHierarchyId *pBreakableParts = aMiscCarBreakParts;
|
|
if(m_pParent->InheritsFromTrailer() && (static_cast<CTrailer*>(m_pParent))->HasBreakableExtras())
|
|
{
|
|
nNumBreakableParts = NUM_EXTRA_TRAILER_BREAK_PARTS;
|
|
pBreakableParts = aExtraTrailerBreakParts;
|
|
}
|
|
|
|
// misc components
|
|
for(int nMisc=0; nMisc<nNumBreakableParts; nMisc++)
|
|
{
|
|
// if the bone index of this child matches the bone index of the extra that's turned off then delete this component
|
|
if(m_pParent->GetBoneIndex((eHierarchyId)pBreakableParts[nMisc]) == boneIndex)
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f, 1.0f) <= sfBlowUpCarExtrasProb)
|
|
{
|
|
float fPartFxProb = fxProb;
|
|
float fPartDeleteProb = fDeleteProb;
|
|
if(ms_fBreakingPartsReduction >= 0.2f)
|
|
{
|
|
fPartDeleteProb = 100;
|
|
}
|
|
|
|
BreakOffPart( ((eHierarchyId)pBreakableParts[nMisc]), nChild, pFragInst, vehicleExplosionInfo, bDeleteParts, fPartDeleteProb, fPartFxProb );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//blow up any vehicle gadgets
|
|
for(int i = 0; i < m_pParent->GetNumberOfVehicleGadgets(); i++)
|
|
{
|
|
CVehicleGadget *pVehicleGadget = m_pParent->GetVehicleGadget(i);
|
|
|
|
pVehicleGadget->BlowUpCarParts(m_pParent);
|
|
}
|
|
|
|
//blow up the weapons
|
|
if(m_pParent->GetVehicleWeaponMgr())
|
|
{
|
|
for(int i = 0; i < m_pParent->GetVehicleWeaponMgr()->GetNumTurrets(); i++)
|
|
{
|
|
m_pParent->GetVehicleWeaponMgr()->GetTurret(i)->BlowUpCarParts(m_pParent);
|
|
}
|
|
}
|
|
|
|
|
|
BlowUpVehicleParts(pInflictor);
|
|
|
|
ms_bDisableVehiclePartCollisionOnBreak = false;
|
|
// reset the pending state
|
|
m_uBlowUpCarPartsPending = Break_Off_Car_Parts_Immediately;
|
|
}
|
|
|
|
|
|
void CVehicleDamage::BlowUpVehicleParts(CEntity* UNUSED_PARAM(pInflictor))
|
|
{
|
|
const bool isTaxi = CVehicle::IsTaxiModelId(m_pParent->GetModelId());
|
|
int lightCount = isTaxi ? NUM_GLASS_BONES : NUM_LIGHT_BONES;
|
|
|
|
if (NetworkInterface::IsGameInProgress() && m_pParent->IsNetworkClone())
|
|
return;
|
|
|
|
for(int i=0; i<lightCount; i++)
|
|
{
|
|
const int boneIdx = m_pParent->GetBoneIndex(aGlassBones[i]);
|
|
const int lightIdx = ( i > TAXI_IDX ) ? TAXI_IDX : i;
|
|
const bool lightState = GetLightStateImmediate(lightIdx);
|
|
if( boneIdx != -1 && (false == lightState))
|
|
{
|
|
SetLightStateImmediate(lightIdx,true);
|
|
SetUpdateLightBones(true);
|
|
}
|
|
}
|
|
|
|
if( true == m_pParent->UsesSiren())
|
|
{
|
|
for(int i=VEH_SIREN_1; i<VEH_SIREN_MAX+1; i++)
|
|
{
|
|
int boneID = m_pParent->GetBoneIndex((eHierarchyId)i);
|
|
const bool sirenState = m_pParent->GetVehicleDamage()->GetSirenState(i);
|
|
if( boneID != -1 && (false == sirenState) )
|
|
{
|
|
// We got damage, and an actual point
|
|
m_pParent->GetVehicleDamage()->SetSirenState(i,true);
|
|
}
|
|
}
|
|
|
|
m_pParent->GetVehicleDamage()->SetUpdateSirenBones(true);
|
|
}
|
|
|
|
if (m_pParent->CarPartsCanBreakOff())
|
|
{
|
|
g_vehicleGlassMan.SmashExplosion(m_pParent, m_pParent->GetTransform().GetPosition(), 8.0f, 1.0f);
|
|
}
|
|
|
|
if (m_pParent->GetVehicleType() == VEHICLE_TYPE_BICYCLE) // Blow bicycle's wheels off
|
|
{
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
|
|
bool bDeleteParts = !m_pParent->CarPartsCanBreakOff();
|
|
|
|
for(int i=0; i<m_pParent->GetNumWheels(); i++)
|
|
{
|
|
CWheel* wheel = m_pParent->GetWheel(i);
|
|
int nFragChild = wheel->GetFragChild();
|
|
if(nFragChild > -1)
|
|
{
|
|
fragPhysicsLOD* pTypePhysics = pFragInst->GetTypePhysics();
|
|
fragTypeChild* pChild = pTypePhysics->GetAllChildren()[nFragChild];
|
|
if(pChild->GetOwnerGroupPointerIndex() > 0 && !pFragInst->GetChildBroken(nFragChild))
|
|
{
|
|
m_pParent->PartHasBrokenOff(wheel->GetHierarchyId());
|
|
|
|
if(bDeleteParts || CVehicle::TooManyVehicleBreaksRecently())
|
|
pFragInst->DeleteAbove(nFragChild);
|
|
else
|
|
{
|
|
pFragInst->BreakOffAbove(nFragChild);
|
|
}
|
|
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
|
|
m_pParent->SetWheelBroken(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static float sfPopDoorMinDamage = 40.0f;
|
|
static float sfPopDoorMaxDamage = 100.0f;
|
|
static float sfPopDoorSideDamage = 50.0f;
|
|
static float sfPopDoorChance = 0.5f;
|
|
static float sfLoosenLatchDamage = 10.0f;
|
|
static float sfLoosenLatchSideDamage = 15.0f;
|
|
static float sfLoosenLatchChance = 0.8f;
|
|
static float sfBreakDoorChance = 0.2f;
|
|
static float sfPopBonnetChance = 0.5f;
|
|
static float sfPopWindScreenChance = 0.1f;
|
|
static float sfBreakMiscChance = 0.9f;
|
|
dev_float sfPlaneInstantCrashDamage = 150.0f;
|
|
dev_float sfPlaneInstantCrashFromVehicleImpactDamage = 100.0f;
|
|
dev_float sfLargePlaneInstantCrashImpactDamage = 100.0f;
|
|
dev_float sfPlaneInstantCrashFromFlyingAircraftImpactDamage = 50.0f;
|
|
dev_float sfDamagedPlaneInstantCrashDamage = 40.0f;
|
|
dev_float sfHeliInstantCrashDamage = 300.0f;
|
|
dev_float sfHeliInstantCrashFromVehicleImpactDamage = 200.0f;
|
|
dev_float sfHeliInstantCrashFromFlyingAircraftImpactDamage = 100.0f;
|
|
dev_float sfSubmarineInstantCrashDamage = 500.0f;
|
|
dev_float sfCarInstantCrashDamage = 150.0f;
|
|
dev_float sfPopDoorBulletForce = 1000.0f;
|
|
dev_float dfBlowUpVehicleChanceByBulletWhenFullyOnFire = 0.05f;
|
|
|
|
void CVehicleDamage::ApplyDamageToBody(CEntity* pInflictor, eDamageType nDamageType, u32 nWeaponHash, float fDamage, const Vector3& vecPosLocal, const Vector3& vecNormLocal, int nComponent, bool isFlameThrowerFire )
|
|
{
|
|
((void)pInflictor);
|
|
((void)nDamageType);
|
|
((void)nComponent);
|
|
|
|
// apply damage to body
|
|
m_fBodyHealth -= fDamage * m_fBodyDamageScale;
|
|
|
|
if(m_fBodyHealth < 0.0f)
|
|
m_fBodyHealth = 0.0f;
|
|
|
|
vehicleDebugf3("%s applying body damage. New body health: %.2f", m_pParent->GetLogName(), m_fBodyHealth);
|
|
|
|
PF_SET(DamageForce, fDamage);
|
|
const bool bIsNetworkCloneOrMigrating = NetworkUtils::IsNetworkCloneOrMigrating(m_pParent);
|
|
if(!bIsNetworkCloneOrMigrating && (m_pParent->InheritsFromPlane() || m_pParent->InheritsFromHeli() || m_pParent->InheritsFromSubmarine() || m_pParent->GetVehicleType() == VEHICLE_TYPE_BIKE || m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR || (NetworkInterface::IsGameInProgress() && m_pParent->GetVehicleType() == VEHICLE_TYPE_TRAILER)) && nDamageType != DAMAGE_TYPE_FIRE)
|
|
{
|
|
float fInstantCrashDamage = FLT_MAX;
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if(((pWeaponInfo && pWeaponInfo->CanBlowUpVehicleAtZeroBodyHealth()) || (nDamageType == DAMAGE_TYPE_EXPLOSIVE && m_pParent->GetShouldVehicleExplodesOnExplosionDamageAtZeroBodyHealth())) && !m_pParent->DisableExplodeOnContact() && m_fBodyHealth <= 0.0f)
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
else if(m_pParent->InheritsFromHeli())
|
|
{
|
|
if(pInflictor && pInflictor->GetIsTypeVehicle())
|
|
{
|
|
fInstantCrashDamage = sfHeliInstantCrashFromVehicleImpactDamage;
|
|
|
|
CVehicle *fInflictorVehicle = (CVehicle *)pInflictor;
|
|
if((fInflictorVehicle->InheritsFromPlane() || fInflictorVehicle->InheritsFromHeli()) && fInflictorVehicle->IsInAir())
|
|
{
|
|
fInstantCrashDamage = sfHeliInstantCrashFromFlyingAircraftImpactDamage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fInstantCrashDamage = sfHeliInstantCrashDamage;
|
|
}
|
|
}
|
|
else if(m_pParent->InheritsFromPlane())
|
|
{
|
|
CPlane *pParentPlane = (CPlane *) m_pParent;
|
|
if(pInflictor && pInflictor->GetIsTypeVehicle())
|
|
{
|
|
fInstantCrashDamage = sfPlaneInstantCrashFromVehicleImpactDamage;
|
|
|
|
CVehicle *fInflictorVehicle = (CVehicle *)pInflictor;
|
|
if((fInflictorVehicle->InheritsFromPlane() || fInflictorVehicle->InheritsFromHeli()) && fInflictorVehicle->IsInAir())
|
|
{
|
|
fInstantCrashDamage = sfPlaneInstantCrashFromFlyingAircraftImpactDamage;
|
|
}
|
|
}
|
|
else if(pParentPlane->GetAircraftDamage().HasSectionBrokenOff(pParentPlane,CAircraftDamage::WING_L)
|
|
|| pParentPlane->GetAircraftDamage().HasSectionBrokenOff(pParentPlane, CAircraftDamage::WING_R)
|
|
|| m_fBodyHealth <= 0.0f || GetEngineHealth() < sfPlaneEngineBreakDownHealth)
|
|
{
|
|
fInstantCrashDamage = sfDamagedPlaneInstantCrashDamage;
|
|
}
|
|
else if(pParentPlane->IsLargePlane() && pParentPlane->IsInAir())
|
|
{
|
|
fInstantCrashDamage = sfLargePlaneInstantCrashImpactDamage;
|
|
}
|
|
else
|
|
{
|
|
fInstantCrashDamage = sfPlaneInstantCrashDamage;
|
|
}
|
|
}
|
|
else if(m_pParent->InheritsFromSubmarine())
|
|
{
|
|
if(!m_pParent->GetIsInWater() && pInflictor && pInflictor->GetIsTypeBuilding())
|
|
{
|
|
fInstantCrashDamage = sfSubmarineInstantCrashDamage;
|
|
}
|
|
}
|
|
else if((m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR) || (m_pParent->GetVehicleType() == VEHICLE_TYPE_TRAILER))
|
|
{
|
|
if((pInflictor==NULL || pInflictor->GetIsTypeBuilding()) && m_pParent->IsInAir() && nDamageType == DAMAGE_TYPE_COLLISION) // is landing damage
|
|
{
|
|
if(m_pParent->PopTypeIsRandom() && !m_pParent->ContainsLocalPlayer() && (m_pParent->GetDriver() == NULL || m_pParent->GetDriver()->PopTypeIsRandom())) // is ambient AI car
|
|
{
|
|
fInstantCrashDamage = sfCarInstantCrashDamage;
|
|
}
|
|
}
|
|
else if(NetworkInterface::IsGameInProgress() && pInflictor && pInflictor->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pInflictorVehicle = static_cast<CVehicle*>(pInflictor);
|
|
if(pInflictorVehicle->GetMass() > sfMinimumMassForCrushingVehiclesNetworkGame &&
|
|
( m_pParent->GetSpecialFlightModeRatio() < 1.0f ||
|
|
m_pParent->HasGlider() ) )
|
|
{
|
|
fInstantCrashDamage = sfCarInstantCrashDamage;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool canBlowUpFromBodyDamage = ( !m_pParent->InheritsFromHeli() || !static_cast< CHeli* >( m_pParent )->GetDisableExplodeFromBodyDamage() ) &&
|
|
( !m_pParent->InheritsFromPlane() || !static_cast< CPlane* >( m_pParent )->GetDisableExplodeFromBodyDamage() );
|
|
|
|
bool canBlowUpFromBodyDamageFromCollision = ( !m_pParent->InheritsFromPlane() || !static_cast< CPlane* >( m_pParent )->GetDisableExplodeFromBodyDamageOnCollision() );
|
|
|
|
if( ( m_fBodyHealth <= 0.0f &&
|
|
fDamage > 40.0f &&
|
|
canBlowUpFromBodyDamage &&
|
|
m_pParent->GetVehicleType() != VEHICLE_TYPE_CAR &&
|
|
( m_pParent->GetVehicleType() != VEHICLE_TYPE_BIKE || m_pParent->m_nVehicleFlags.bCarCrushingBike ) ) ||
|
|
( canBlowUpFromBodyDamageFromCollision && fDamage > fInstantCrashDamage && nDamageType == DAMAGE_TYPE_COLLISION &&
|
|
!m_pParent->HasRamp() ) )
|
|
{
|
|
if(m_pParent->InheritsFromPlane())
|
|
{
|
|
//Can the hit component break off? if so let's break it off instead of blowing up entire plane
|
|
CPlane *pParentPlane = (CPlane *)m_pParent;
|
|
|
|
if(pInflictor && pInflictor->GetIsTypeVehicle())
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
else if(pParentPlane->IsLargePlane())
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
else if(pParentPlane->GetAircraftDamage().HasSectionBrokenOff(pParentPlane,CAircraftDamage::WING_L)
|
|
|| pParentPlane->GetAircraftDamage().HasSectionBrokenOff(pParentPlane, CAircraftDamage::WING_R)
|
|
|| m_fBodyHealth <= 0.0f || GetEngineHealth() < sfPlaneEngineBreakDownHealth)
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
else if(!pParentPlane->GetAircraftDamage().BreakOffComponent(pInflictor, pParentPlane, nComponent) && !pParentPlane->GetLandingGearDamage().BreakOffComponent(pInflictor, pParentPlane, nComponent))
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
}
|
|
else if(nDamageType != DAMAGE_TYPE_MELEE )
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bBulletCanKnockDoorOpen = false;
|
|
if (!bIsNetworkCloneOrMigrating)
|
|
{
|
|
if( nDamageType == DAMAGE_TYPE_BULLET)
|
|
{
|
|
static dev_float fMinZForPetrolTanker = -1.0f;
|
|
// Should this be an instant explosion?
|
|
if( m_pParent->ShouldExplodeOnContact() &&
|
|
!m_pParent->DisableExplodeOnContact() &&
|
|
m_fBodyHealth <= 0.0f &&
|
|
vecPosLocal.z > fMinZForPetrolTanker)
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
else if(m_pParent->SprayPetrolBeforeExplosion() && vecPosLocal.z > fMinZForPetrolTanker)
|
|
{
|
|
if(m_fPetrolTankHealth > VEH_DAMAGE_HEALTH_PTANK_LEAKING)
|
|
m_fPetrolTankHealth = VEH_DAMAGE_HEALTH_PTANK_LEAKING - 0.1f;
|
|
|
|
if(m_vPetrolSprayPosLocal.IsZero())
|
|
{
|
|
m_vPetrolSprayPosLocal = vecPosLocal;
|
|
m_vPetrolSprayNrmLocal = vecNormLocal;
|
|
}
|
|
}
|
|
|
|
if(GetEngineHealth() < ENGINE_DAMAGE_FIRE_FULL && fwRandom::GetRandomNumberInRange(0.0f,1.0f) < dfBlowUpVehicleChanceByBulletWhenFullyOnFire)
|
|
{
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
}
|
|
|
|
if (nWeaponHash != 0)
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if(pWeaponInfo && (pWeaponInfo->GetGroup() == WEAPONGROUP_SHOTGUN || pWeaponInfo->GetGroup() == WEAPONGROUP_HEAVY || pWeaponInfo->GetForceHitVehicle() >= sfPopDoorBulletForce))
|
|
{
|
|
bBulletCanKnockDoorOpen = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(nDamageType == DAMAGE_TYPE_COLLISION)
|
|
{
|
|
if(m_pParent->m_nVehicleFlags.bExplodesOnNextImpact && !bIsNetworkCloneOrMigrating)
|
|
m_pParent->BlowUpCar(pInflictor ? pInflictor : m_pParent);
|
|
else
|
|
GetParent()->GetVehicleAudioEntity()->GetCollisionAudio().UpdateDeformation(fDamage, vecPosLocal, pInflictor);
|
|
}
|
|
|
|
const Vector3 transformC(VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetC()));
|
|
const float transformCZ = transformC.z;
|
|
|
|
// trigger bouncing panels (just bumpers?)
|
|
if((m_fCountDownToTimeAnotherPartCanBreakOff <= 0.0f || transformCZ < 0.0f) && (fDamage > 20.0f || (m_fBodyHealth < 600.0f && fDamage > 5.0f)))
|
|
{
|
|
//Bumper damage is applied in SP and locally in MP here - remotely it comes through repliction in NetObjVehicle. lavalley
|
|
if (!NetworkInterface::IsGameInProgress() || !m_pParent->IsNetworkClone())
|
|
{
|
|
// front bumper
|
|
if(m_BouncingPanels[0].GetCompIndex()==-1 && m_pParent->GetBoneIndex(VEH_BUMPER_F)!=-1
|
|
&& (fwRandom::GetRandomNumber() &1) == 0)
|
|
{
|
|
if(vecPosLocal.y > 0.8f * m_pParent->GetBoundingBoxMax().y)
|
|
{
|
|
// if root of bone is on the left, then bounce axis is positive
|
|
CBouncingPanel::eBounceAxis nAxis = CBouncingPanel::BOUNCE_AXIS_X;
|
|
// if root of bone is on the right, then bounce axis is negative
|
|
if(m_pParent->GetSkeletonData().GetBoneData(m_pParent->GetBoneIndex(VEH_BUMPER_F))->GetDefaultTranslation().GetXf() > 0.0f)
|
|
nAxis = CBouncingPanel::BOUNCE_AXIS_NEG_X;
|
|
|
|
m_BouncingPanels[0].SetPanel(VEH_BUMPER_F, nAxis, fwRandom::GetRandomNumberInRange(0.2f, 0.5f));
|
|
ResetBrokenPartCountdown();
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
NetworkInterface::OnVehiclePartDamage(m_pParent, VEH_BUMPER_F, VEH_BB_BOUNCING);
|
|
}
|
|
}
|
|
}
|
|
|
|
// rear bumper
|
|
if(m_BouncingPanels[1].GetCompIndex()==-1 && m_pParent->GetBoneIndex(VEH_BUMPER_R)!=-1
|
|
&& (fwRandom::GetRandomNumber() &1) == 0)
|
|
{
|
|
if(vecPosLocal.y < 0.8f * m_pParent->GetBoundingBoxMin().y)
|
|
{
|
|
// if root of bone is on the left, then bounce axis is positive
|
|
CBouncingPanel::eBounceAxis nAxis = CBouncingPanel::BOUNCE_AXIS_X;
|
|
// if root of bone is on the right, then bounce axis is negative
|
|
if(m_pParent->GetSkeletonData().GetBoneData(m_pParent->GetBoneIndex(VEH_BUMPER_R))->GetDefaultTranslation().GetXf() > 0.0f)
|
|
nAxis = CBouncingPanel::BOUNCE_AXIS_NEG_X;
|
|
|
|
m_BouncingPanels[1].SetPanel(VEH_BUMPER_R, nAxis, fwRandom::GetRandomNumberInRange(0.2f, 0.5f));
|
|
ResetBrokenPartCountdown();
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
NetworkInterface::OnVehiclePartDamage(m_pParent, VEH_BUMPER_R, VEH_BB_BOUNCING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_pParent->GetModelIndex() == MI_TANK_KHANJALI )
|
|
{
|
|
ApplyDamageToBreakablePanels( nDamageType, fDamage, vecPosLocal, nComponent );
|
|
}
|
|
|
|
fragInstGta* pFragInst = static_cast<fragInstGta*>(m_pParent->GetVehicleFragInst());
|
|
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pFragInst->GetArchetype()->GetBound());
|
|
Assert(pFragInst);
|
|
Assert(pBoundComposite);
|
|
|
|
float popDoorDamage = sfPopDoorMinDamage;
|
|
float popDoorSideDamage = sfPopDoorSideDamage;
|
|
|
|
float armourMultiplier = m_pParent->GetVariationInstance().GetArmourDamageMultiplier();
|
|
|
|
float popDoorChance = sfPopDoorChance * armourMultiplier;
|
|
float loosenLatchDamage = sfLoosenLatchDamage;
|
|
float loosenLatchSideDamage = sfLoosenLatchSideDamage;
|
|
float loosenLatchChance = sfLoosenLatchChance * armourMultiplier;
|
|
float popBonnetChance = sfPopBonnetChance * armourMultiplier;
|
|
float popWindscreenChance = sfPopWindScreenChance * armourMultiplier;
|
|
float breakDoorChance = sfBreakDoorChance * armourMultiplier;
|
|
float breakMiscChance = sfBreakMiscChance * armourMultiplier;
|
|
|
|
float fpopDoorDamageRate = rage::Clamp((fDamage - popDoorDamage) / (sfPopDoorMaxDamage - sfPopDoorMinDamage), 0.0f, 1.0f);
|
|
popDoorChance += (1.0f - popDoorChance) * fpopDoorDamageRate;
|
|
popBonnetChance += (1.0f - popBonnetChance) * fpopDoorDamageRate;
|
|
loosenLatchChance += (1.0f - loosenLatchChance) * fpopDoorDamageRate;
|
|
|
|
Vector3 angVelocity = m_pParent->GetAngVelocity();
|
|
static dev_float angVelocityX = 2.0f;//speed at which any impact should cause a door to pop open when car is rolling
|
|
float angVelocityMult = fabs(angVelocity.x)/angVelocityX;
|
|
|
|
static dev_float upsideDownMult = 10.0f;
|
|
if(transformCZ < 0.0f)
|
|
{
|
|
angVelocityMult *= upsideDownMult;
|
|
}
|
|
|
|
popDoorDamage *= rage::Clamp(1.0f - angVelocityMult, 0.0f, 1.0f);
|
|
popDoorSideDamage *= rage::Clamp(1.0f - angVelocityMult, 0.0f, 1.0f);
|
|
loosenLatchDamage *= rage::Clamp(1.0f - angVelocityMult, 0.0f, 1.0f);
|
|
loosenLatchSideDamage *= rage::Clamp(1.0f - angVelocityMult, 0.0f, 1.0f);
|
|
|
|
popBonnetChance = rage::Clamp(angVelocityMult, popBonnetChance, 1.0f);
|
|
popWindscreenChance = rage::Clamp(angVelocityMult, popWindscreenChance, 1.0f);
|
|
breakDoorChance = rage::Clamp(angVelocityMult, breakDoorChance, 1.0f);
|
|
breakMiscChance = rage::Clamp(angVelocityMult, breakMiscChance, 1.0f);
|
|
|
|
// Explosions have a chance to blow up parts instead of break off
|
|
float deleteMiscChance = 0.0f;
|
|
if(nDamageType==DAMAGE_TYPE_EXPLOSIVE)
|
|
{
|
|
float fDistToCamSqr = LARGE_FLOAT;
|
|
if(m_pParent->IsVisible())
|
|
{
|
|
// if heli's are on screen, don't delete components
|
|
if(m_pParent->GetVehicleType()==VEHICLE_TYPE_HELI)
|
|
fDistToCamSqr = 0.0f;
|
|
else
|
|
{
|
|
fDistToCamSqr = CVfxHelper::GetDistSqrToCamera(m_pParent->GetTransform().GetPosition());
|
|
}
|
|
}
|
|
|
|
// Compute a chance to deletion probability
|
|
const CVehicleExplosionInfo* vehicleExplosionInfo = m_pParent->GetVehicleModelInfo()->GetVehicleExplosionInfo();
|
|
const CVehicleExplosionLOD& vehicleExplosionLOD = vehicleExplosionInfo->GetVehicleExplosionLODFromDistanceSq(fDistToCamSqr);
|
|
deleteMiscChance = vehicleExplosionLOD.GetPartDeletionChance();
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
deleteMiscChance = Max(sfBlowUpCarDeleteCompProbNetwork,deleteMiscChance);
|
|
|
|
const float vehicleBreakChanceMultiplier = CVehicleDamage::GetVehicleExplosionBreakChanceMultiplier();
|
|
breakDoorChance *= vehicleBreakChanceMultiplier;
|
|
breakMiscChance *= vehicleBreakChanceMultiplier;
|
|
popDoorChance *= vehicleBreakChanceMultiplier;
|
|
popBonnetChance *= vehicleBreakChanceMultiplier;
|
|
popWindscreenChance *= vehicleBreakChanceMultiplier;
|
|
|
|
// Any car part that breaks in this function should disable collision with the parent vehicle until they're separated
|
|
Assert(!ms_bDisableVehiclePartCollisionOnBreak);
|
|
ms_bDisableVehiclePartCollisionOnBreak = true;
|
|
}
|
|
|
|
if( (m_fCountDownToTimeAnotherPartCanBreakOff <= 0.0f || transformCZ < 0.0f) &&
|
|
((fDamage > 120.0f) ||
|
|
(m_fBodyHealth < 700.0f && fDamage > 30.0f) ||
|
|
(m_fBodyHealth < 500.0f && fDamage > 10.0f) ||
|
|
(transformCZ < 0.0f && fDamage > 5.0f)) &&
|
|
!pFragInst->IsBreakingDisabled()
|
|
&& (nDamageType==DAMAGE_TYPE_COLLISION || nDamageType==DAMAGE_TYPE_EXPLOSIVE))
|
|
{
|
|
// misc components and breakable parts
|
|
BreakOffMiscComponents(deleteMiscChance, breakMiscChance, vecPosLocal);
|
|
}
|
|
|
|
|
|
if( (m_fCountDownToTimeAnotherPartCanBreakOff <= 0.0f || transformCZ < 0.0f) &&
|
|
m_pParent->GetCarDoorLocks()==CARLOCK_UNLOCKED &&
|
|
!pFragInst->IsBreakingDisabled() &&
|
|
(nDamageType==DAMAGE_TYPE_COLLISION || nDamageType==DAMAGE_TYPE_EXPLOSIVE || nDamageType == DAMAGE_TYPE_MELEE || (nDamageType == DAMAGE_TYPE_BULLET && bBulletCanKnockDoorOpen)) &&
|
|
m_pParent->GetModelIndex() != MI_TRAILER_TRAILERLARGE )
|
|
{
|
|
for(int nDoor=0; nDoor<m_pParent->GetNumDoors(); nDoor++)
|
|
{
|
|
CCarDoor* pDoor = m_pParent->GetDoor(nDoor);
|
|
bool bCanLoosenLatch = !pDoor->GetFlag(CCarDoor::LOOSE_LATCHED_DOOR);
|
|
|
|
if(nDamageType == DAMAGE_TYPE_BULLET)
|
|
{
|
|
// Bullets can only knock the door open when shot directly on it
|
|
if(pDoor->GetFragChild() != nComponent)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Bullet can only pop doors to loosed latch position, but not knock open them (except boot and bonnet)
|
|
if( pDoor->GetHierarchyId()!=VEH_BOOT &&
|
|
pDoor->GetHierarchyId()!=VEH_BONNET &&
|
|
pDoor->GetHierarchyId()!=VEH_BOOT_2 &&
|
|
!bCanLoosenLatch)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if(nDamageType == DAMAGE_TYPE_MELEE)
|
|
{
|
|
// Melee strike can only knock the door to loosed latch position
|
|
if(!bCanLoosenLatch)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Melee strike can not pop the hoods
|
|
if(pDoor->GetHierarchyId()==VEH_BONNET && m_pParent->GetBoneIndex(VEH_BONNET) != -1)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( (fDamage > popDoorDamage) ||
|
|
(m_fBodyHealth < 500.0f && fDamage > 15.0f) ||
|
|
(transformCZ < 0.0f && fDamage > 5.0f) ||
|
|
(bCanLoosenLatch && fDamage > loosenLatchDamage) )
|
|
{
|
|
if(pDoor->GetFragChild() > 0 && pBoundComposite->GetBound(pDoor->GetFragChild()) && pDoor->GetFlag(CCarDoor::DRIVEN_SHUT))
|
|
{
|
|
float fDoorSideOffset = pBoundComposite->GetCurrentMatrix(pDoor->GetFragChild()).GetCol3().GetXf();
|
|
if(fDoorSideOffset > 0.5f) fDoorSideOffset = 1.0f;
|
|
else if(fDoorSideOffset < -0.5f) fDoorSideOffset = -1.0f;
|
|
else fDoorSideOffset = 0.0f;
|
|
|
|
float fPopDoorSideDamage = popDoorSideDamage;
|
|
float fPopDoorChance = popDoorChance;
|
|
|
|
if(bCanLoosenLatch)
|
|
{
|
|
fPopDoorSideDamage = loosenLatchSideDamage;
|
|
fPopDoorChance = loosenLatchChance;
|
|
}
|
|
|
|
if(fDoorSideOffset*vecNormLocal.x*fDamage < -popDoorSideDamage && fwRandom::GetRandomNumberInRange(0.0f,1.0f) < popDoorChance)
|
|
{
|
|
if(pDoor->GetDoorAllowedToBeBrokenOff() && !(m_pParent->pHandling && m_pParent->pHandling->hFlags &HF_ARMOURED))
|
|
{
|
|
if(!(NetworkInterface::IsGameInProgress() &&
|
|
(m_pParent->GetModelIndex() == MI_CAR_BENSON_TRUCK ||
|
|
m_pParent->GetModelIndex() == MI_PLANE_TITAN ||
|
|
m_pParent->GetModelIndex() == MI_PLANE_AVENGER ||
|
|
m_pParent->GetModelIndex() == MI_CAR_INSURGENT2 ) &&
|
|
( pDoor->GetHierarchyId() == VEH_BOOT || pDoor->GetHierarchyId() == VEH_BOOT_2 )) )//Benson rear door shouldn't open in MP
|
|
{
|
|
if(bCanLoosenLatch)
|
|
{
|
|
pDoor->SetLooseLatch(m_pParent);
|
|
ResetBrokenPartCountdown();
|
|
}
|
|
else
|
|
{
|
|
pDoor->SetSwingingFree(m_pParent);
|
|
pDoor->SetHasBeenKnockedOpen(true);
|
|
ResetBrokenPartCountdown();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const Vector3 sVecDoorBoxMinAdd(-0.2f, -0.2f, 0.1f);
|
|
const Vector3 sVecDoorBoxMaxAdd(0.2f, 0.2f, 0.2f);
|
|
const Vector3 sVecDoorBoxMinAddBoot(-0.2f, -0.3f, -0.4f);
|
|
const Vector3 sVecDoorBoxMinAddBonnet(-0.2f, -0.2f, -0.4f);
|
|
|
|
float fPopDoorChance = popDoorChance;
|
|
|
|
Vector3 vecPosLocalDoor, vecDoorBoxMin, vecDoorBoxMax;
|
|
RCC_MATRIX34(pBoundComposite->GetCurrentMatrix(pDoor->GetFragChild())).UnTransform(vecPosLocal, vecPosLocalDoor);
|
|
vecDoorBoxMax = VEC3V_TO_VECTOR3(pBoundComposite->GetBound(pDoor->GetFragChild())->GetBoundingBoxMax()) + sVecDoorBoxMaxAdd;
|
|
vecDoorBoxMin = VEC3V_TO_VECTOR3(pBoundComposite->GetBound(pDoor->GetFragChild())->GetBoundingBoxMin()) + sVecDoorBoxMinAdd;
|
|
|
|
if(pDoor->GetHierarchyId()==VEH_BOOT)
|
|
{
|
|
vecDoorBoxMin += sVecDoorBoxMinAddBoot - sVecDoorBoxMinAdd;
|
|
}
|
|
else if(pDoor->GetHierarchyId()==VEH_BONNET)
|
|
{
|
|
vecDoorBoxMin += sVecDoorBoxMinAddBonnet - sVecDoorBoxMinAdd;
|
|
}
|
|
|
|
if(bCanLoosenLatch)
|
|
{
|
|
fPopDoorChance = loosenLatchChance;
|
|
}
|
|
else if(pDoor->GetHierarchyId()==VEH_BOOT)
|
|
{
|
|
fPopDoorChance = popBonnetChance;
|
|
}
|
|
else if(pDoor->GetHierarchyId()==VEH_BONNET)
|
|
{
|
|
fPopDoorChance = popBonnetChance;
|
|
|
|
// Don't pop open the bonnet from side impacts
|
|
static dev_float sfYNormalPopLimit = 0.6f;
|
|
if(vecNormLocal.x * vecNormLocal.x > sfYNormalPopLimit * sfYNormalPopLimit)
|
|
{
|
|
fPopDoorChance = 0.0f;
|
|
}
|
|
}
|
|
else if(!pDoor->GetDoorAllowedToBeBrokenOff())
|
|
{
|
|
fPopDoorChance = breakDoorChance;
|
|
}
|
|
|
|
if( geomPoints::IsPointInBox(vecPosLocalDoor, vecDoorBoxMin, vecDoorBoxMax) && fwRandom::GetRandomNumberInRange(0.0f,1.0f) < fPopDoorChance)
|
|
{
|
|
if(pDoor->GetDoorAllowedToBeBrokenOff() && !(m_pParent->pHandling && m_pParent->pHandling->hFlags &HF_ARMOURED))
|
|
{
|
|
if(!(NetworkInterface::IsGameInProgress() &&
|
|
(m_pParent->GetModelIndex() == MI_CAR_BENSON_TRUCK ||
|
|
m_pParent->GetModelIndex() == MI_PLANE_TITAN ||
|
|
m_pParent->GetModelIndex() == MI_PLANE_AVENGER ||
|
|
m_pParent->GetModelIndex() == MI_CAR_INSURGENT2 ) &&
|
|
( pDoor->GetHierarchyId() == VEH_BOOT || pDoor->GetHierarchyId() == VEH_BOOT_2 )))//Benson rear door shouldn't open in MP
|
|
{
|
|
if(bCanLoosenLatch)
|
|
{
|
|
pDoor->SetLooseLatch(m_pParent);
|
|
ResetBrokenPartCountdown();
|
|
}
|
|
else
|
|
{
|
|
pDoor->SetSwingingFree(m_pParent);
|
|
pDoor->SetHasBeenKnockedOpen(true);
|
|
ResetBrokenPartCountdown();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Knock off the bonnet if its attachment point is above the front bumper and front bumper has been knocked off or partially detached
|
|
if(pDoor->GetHierarchyId()==VEH_BONNET && m_pParent->GetBoneIndex(VEH_BONNET) != -1 && pDoor->GetFragChild() > 0 && !pFragInst->GetChildBroken(pDoor->GetFragChild()) && pDoor->GetIsIntact(m_pParent) && pDoor->GetDoorAllowedToBeBrokenOff() && !CVehicle::TooManyVehicleBreaksRecently())
|
|
{
|
|
u32 modelNameHash = m_pParent->GetVehicleModelInfo()->GetModelNameHash();
|
|
|
|
const Matrix34& doorMatrix = m_pParent->GetLocalMtx(m_pParent->GetBoneIndex(VEH_BONNET));
|
|
if(doorMatrix.d.y > 0.8f * m_pParent->GetBoundingBoxMax().y &&
|
|
modelNameHash != MI_CAR_MONROE.GetName().GetHash() &&
|
|
modelNameHash != MI_CAR_BRAWLER.GetName().GetHash() &&
|
|
modelNameHash != MI_CAR_SENTINEL3.GetName().GetHash() &&
|
|
modelNameHash != MI_CAR_DELUXO.GetName().GetHash() &&
|
|
modelNameHash != MI_CAR_Z190.GetName().GetHash() &&
|
|
modelNameHash != MI_CAR_ZION3.GetName().GetHash() ) // Don't knock off bonnet of Monroe, it will pop underneath the car
|
|
{
|
|
if((!pDoor->GetIsLatched(m_pParent) || pDoor->GetFlag(CCarDoor::DRIVEN_SWINGING))
|
|
&& (m_BouncingPanels[0].GetCompIndex() == VEH_BUMPER_F || m_pParent->GetBoneIndex(VEH_BUMPER_F) == -1))
|
|
{
|
|
pDoor->BreakOff(m_pParent);
|
|
ResetBrokenPartCountdown();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ms_bDisableVehiclePartCollisionOnBreak = false;
|
|
|
|
if( ( CPhysics::ms_bInArenaMode ||
|
|
isFlameThrowerFire ) &&
|
|
m_fBodyHealth <= 0.0f &&
|
|
!m_pParent->m_nVehicleFlags.bBlownUp &&
|
|
!bIsNetworkCloneOrMigrating)
|
|
{
|
|
m_pParent->BlowUpCar( pInflictor ? pInflictor : m_pParent );
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::BreakOffMiscComponents(float fDeleteMiscChance, float fBreakMiscChance, const Vector3& vecPosLocal)
|
|
{
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
Assert(pFragInst);
|
|
|
|
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pFragInst->GetArchetype()->GetBound());
|
|
Assert(pBoundComposite);
|
|
|
|
int nNumBreakableParts = NUM_MISC_CAR_BREAK_PARTS;
|
|
eHierarchyId *pBreakableParts = aMiscCarBreakParts;
|
|
|
|
bool bIsBreakableTrailer = m_pParent->InheritsFromTrailer() && (static_cast<CTrailer*>(m_pParent))->HasBreakableExtras() &&
|
|
m_pParent->GetAttachParentVehicle() && m_pParent->GetAttachParentVehicle()->GetDriver() && m_pParent->GetAttachParentVehicle()->GetDriver()->IsPlayer();
|
|
if(bIsBreakableTrailer)
|
|
{
|
|
nNumBreakableParts = NUM_EXTRA_TRAILER_BREAK_PARTS;
|
|
pBreakableParts = aExtraTrailerBreakParts;
|
|
}
|
|
|
|
// go through all the fragment children (there's more of them)
|
|
for(int nChild=0; nChild<pFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
|
|
{
|
|
if(pFragInst->GetChildBroken(nChild))
|
|
continue;
|
|
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
|
|
int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID());
|
|
|
|
for(int nMisc=0; nMisc<nNumBreakableParts; nMisc++)
|
|
{
|
|
eHierarchyId hierarchyID = static_cast<eHierarchyId>(pBreakableParts[nMisc]);
|
|
|
|
// B*1808787: Prevent the rear bumper from being broken off from the trailer to stop cars from being driven and getting wedged in underneath.
|
|
if(m_pParent->InheritsFromTrailer() && hierarchyID == VEH_BUMPER_R)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( m_pParent->HasRamp() &&
|
|
hierarchyID == VEH_BUMPER_F )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(m_pParent->GetBoneIndex(hierarchyID) == boneIndex)
|
|
{
|
|
Vector3 vecPosLocalPanel, vecPanelBoxMin, vecPanelBoxMax;
|
|
RCC_MATRIX34(pBoundComposite->GetCurrentMatrix(nChild)).UnTransform(vecPosLocal, vecPosLocalPanel);
|
|
vecPanelBoxMax = VEC3V_TO_VECTOR3(pBoundComposite->GetBound(nChild)->GetBoundingBoxMax());
|
|
vecPanelBoxMin = VEC3V_TO_VECTOR3(pBoundComposite->GetBound(nChild)->GetBoundingBoxMin());
|
|
|
|
if( geomPoints::IsPointInBox(vecPosLocalPanel, vecPanelBoxMin, vecPanelBoxMax) || bIsBreakableTrailer )
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f,1.0f) < fBreakMiscChance)
|
|
{
|
|
if(fwRandom::GetRandomNumberInRange(0.0f,1.0f) < fDeleteMiscChance)
|
|
{
|
|
m_pParent->PartHasBrokenOff(hierarchyID);
|
|
pFragInst->DeleteAbove(nChild);
|
|
ResetBrokenPartCountdown();
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
}
|
|
else if(!CVehicle::TooManyVehicleBreaksRecently())
|
|
{
|
|
m_pParent->PartHasBrokenOff(hierarchyID);
|
|
pFragInst->BreakOffAbove(nChild);
|
|
ResetBrokenPartCountdown();
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
}
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
NetworkInterface::OnVehiclePartDamage(m_pParent, hierarchyID, VEH_BB_BROKEN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
u8 CVehicleDamage::GetPartDamage(eHierarchyId ePart)
|
|
{
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
if (pFragInst)
|
|
{
|
|
int partBoneIndex = m_pParent->GetBoneIndex(ePart);
|
|
for(int nChild=0; nChild<pFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
|
|
{
|
|
int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID());
|
|
if (boneIndex == partBoneIndex)
|
|
{
|
|
if (pFragInst->GetChildBroken(nChild))
|
|
{
|
|
return VEH_BB_BROKEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (s32 i = 0; i < MAX_BOUNCING_PANELS; ++i)
|
|
{
|
|
CBouncingPanel* panel = GetBouncingPanel(i);
|
|
if (!panel)
|
|
continue;
|
|
|
|
if (panel->m_nComponentIndex == ePart)
|
|
{
|
|
return VEH_BB_BOUNCING;
|
|
}
|
|
}
|
|
}
|
|
|
|
return VEH_BB_NONE;
|
|
}
|
|
|
|
void CVehicleDamage::SetPartDamage(eHierarchyId ePart, u8 state)
|
|
{
|
|
if (state != VEH_BB_NONE)
|
|
{
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
if (pFragInst)
|
|
{
|
|
if (state == VEH_BB_BROKEN)
|
|
{
|
|
int partBoneIndex = m_pParent->GetBoneIndex(ePart);
|
|
for(int nChild=0; nChild<pFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
|
|
{
|
|
int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID());
|
|
if (boneIndex == partBoneIndex)
|
|
{
|
|
if (!pFragInst->GetChildBroken(nChild))
|
|
{
|
|
pFragInst->SetBroken(true);
|
|
m_pParent->PartHasBrokenOff(ePart);
|
|
pFragInst->BreakOffAbove(nChild);
|
|
ResetBrokenPartCountdown();
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (state == VEH_BB_BOUNCING)
|
|
{
|
|
bool bFoundPanel = false;
|
|
bool bFoundEmptyPanel = false;
|
|
int pos = -1;
|
|
for (s32 i = 0; i < MAX_BOUNCING_PANELS; ++i)
|
|
{
|
|
CBouncingPanel* panel = GetBouncingPanel(i);
|
|
if (!panel)
|
|
continue;
|
|
|
|
if (!bFoundPanel && panel->m_nComponentIndex == ePart)
|
|
bFoundPanel = true;
|
|
|
|
if (!bFoundEmptyPanel && panel->m_nComponentIndex == -1)
|
|
{
|
|
bFoundEmptyPanel = true;
|
|
pos = i;
|
|
}
|
|
}
|
|
|
|
//If the bouncing panel wasn't found, then it needs to be set on the remote in order for the bumper to hang off and look funky there also.
|
|
if (!bFoundPanel && bFoundEmptyPanel)
|
|
{
|
|
m_BouncingPanels[pos].SetPanel(ePart, CBouncingPanel::BOUNCE_AXIS_X, fwRandom::GetRandomNumberInRange(0.2f, 0.5f));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
NetworkInterface::OnVehiclePartDamage(m_pParent, ePart, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
|
|
void CVehicleDamage::SetupVehicleDoorDamageBank(bkBank& bank)
|
|
{
|
|
bank.PushGroup("Door Damage Tweaking", false);
|
|
bank.AddSlider("Pop door min damage", &sfPopDoorMinDamage, 0.0f, 100.0f, 1.0f );
|
|
bank.AddSlider("Pop door max damage", &sfPopDoorMaxDamage, 0.0f, 500.0f, 1.0f );
|
|
bank.AddSlider("Pop door side damage", &sfPopDoorSideDamage, 0.0f, 100.0f, 1.0f );
|
|
bank.AddSlider("Loosen latch damage", &sfPopDoorChance, 0.0f, 100.0f, 1.0f );
|
|
bank.AddSlider("Loosen latch side damage", &sfLoosenLatchSideDamage, 0.0f, 100.0f, 1.0f );
|
|
|
|
bank.AddSlider("Pop door chance", &sfPopDoorChance, 0.0f, 1.0f, 0.01f );
|
|
bank.AddSlider("Loosen latch chance", &sfLoosenLatchChance, 0.0f, 1.0f, 0.01f );
|
|
bank.AddSlider("Break door chance", &sfBreakDoorChance, 0.0f, 1.0f, 0.01f );
|
|
bank.AddSlider("Pop bonnet chance", &sfPopBonnetChance, 0.0f, 1.0f, 0.01f );
|
|
bank.AddSlider("Pop wind screen chance", &sfPopWindScreenChance, 0.0f, 1.0f, 0.01f );
|
|
bank.AddSlider("Break misc chance", &sfBreakMiscChance, 0.0f, 1.0f, 0.01f );
|
|
|
|
bank.AddAngle("Loose latched door open angle", &ms_fLooseLatchedDoorOpenAngle, bkAngleType::RADIANS, 0.0, PI);
|
|
bank.AddAngle("Loose latched bonnet open angle", &ms_fLooseLatchedBonnetOpenAngle, bkAngleType::RADIANS, 0.0, PI);
|
|
bank.PopGroup();
|
|
}
|
|
|
|
#endif
|
|
|
|
int CVehicleDamage::GetNumOfBrokenOffParts() const
|
|
{
|
|
int iNumOfBrokenOffDoors = 0;
|
|
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
|
|
Assert(pFragInst);
|
|
if(pFragInst == NULL)
|
|
return 0;
|
|
|
|
for(int nChild=0; nChild<pFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
|
|
{
|
|
if(pFragInst->GetChildBroken(nChild))
|
|
{
|
|
iNumOfBrokenOffDoors++;
|
|
}
|
|
}
|
|
|
|
return iNumOfBrokenOffDoors;
|
|
}
|
|
|
|
int CVehicleDamage::GetNumOfBrokenLoosenParts() const
|
|
{
|
|
int iNumOfBrokenLoosenParts = 0;
|
|
|
|
fragInst* pFragInst = m_pParent->GetVehicleFragInst();
|
|
Assert(pFragInst);
|
|
if(pFragInst == NULL)
|
|
return 0;
|
|
|
|
// count the number of doors or bonnets that are knocked open
|
|
for(int nDoor=0; nDoor<m_pParent->GetNumDoors(); nDoor++)
|
|
{
|
|
CCarDoor* pDoor = m_pParent->GetDoor(nDoor);
|
|
if(pDoor->GetFragChild() > 0 && !pFragInst->GetChildBroken(pDoor->GetFragChild()))
|
|
{
|
|
if(pDoor->GetEverKnockedOpen())
|
|
{
|
|
iNumOfBrokenLoosenParts++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// count the number of bumpers that are broken loose
|
|
for (s32 i = 0; i < MAX_BOUNCING_PANELS; ++i)
|
|
{
|
|
const CBouncingPanel &panel = m_BouncingPanels[i];
|
|
if (panel.m_nComponentIndex == VEH_BUMPER_R || panel.m_nComponentIndex == VEH_BUMPER_F)
|
|
{
|
|
iNumOfBrokenLoosenParts++;
|
|
}
|
|
}
|
|
|
|
return iNumOfBrokenLoosenParts;
|
|
}
|
|
|
|
float sfEngineMinDistance = 0.3f;
|
|
dev_float sfEngineMinDistanceSq = 0.3f * 0.3f;
|
|
bool CVehicleDamage::AvoidVehicleExplosionChainReactions()
|
|
{
|
|
#if __BANK
|
|
if(ms_bNeverAvoidVehicleExplosionChainReactions)
|
|
{
|
|
return false;
|
|
}
|
|
else if(ms_bAlwaysAvoidVehicleExplosionChainReactions)
|
|
{
|
|
return true;
|
|
}
|
|
#endif // __BANK
|
|
|
|
// Disable chain reactions when the player is very wanted.
|
|
|
|
// GTAV - B*1724681 - Increase explosion chain reactions on next gen
|
|
//if(!NetworkInterface::IsGameInProgress())
|
|
//{
|
|
// if(CWanted* pWanted = CGameWorld::FindLocalPlayerWanted())
|
|
// {
|
|
// return pWanted->GetWantedLevel() >= WANTED_LEVEL4;
|
|
// }
|
|
//}
|
|
//else
|
|
//{
|
|
// return CNetObjPlayer::GetHighestWantedLevelInArea() >= WANTED_LEVEL4;
|
|
//}
|
|
|
|
return CVehicle::sm_bDisableExplosionDamage;
|
|
}
|
|
|
|
float CVehicleDamage::GetVehicleExplosionBreakChanceMultiplier()
|
|
{
|
|
#if __BANK
|
|
if(ms_bUseVehicleExplosionBreakChanceMultiplierOverride)
|
|
{
|
|
return ms_fVehicleExplosionBreakChanceMultiplierOverride;
|
|
}
|
|
#endif // __BANK
|
|
|
|
//This is set through script. Will be reset to false at the end of physics
|
|
if(ms_bDisableVehicleExplosionBreakOffParts)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
eWantedLevel wantedLevel = CNetObjPlayer::GetHighestWantedLevelInArea();
|
|
if(wantedLevel >= WANTED_LEVEL4)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
else if (wantedLevel >= WANTED_LEVEL3)
|
|
{
|
|
return 0.5f;
|
|
}
|
|
}
|
|
|
|
return 1.0f;
|
|
}
|
|
|
|
dev_float sfTankEngineRearDamageMulti = 2.0f;
|
|
//
|
|
void CVehicleDamage::ApplyDamageToEngine(CEntity* pInflictor, eDamageType nDamageType, float fDamage, const Vector3& vecPosLocal, const Vector3& vecNormLocal, const Vector3& vecDirnLocal, const bool bFireDriveby, const bool bIsAccurate, const float fDamageRadius, const bool bAvoidExplosions, u32 nWeaponHash, const bool bChainExplosion)
|
|
{
|
|
// can't apply damage to network clones
|
|
if(m_pParent->IsNetworkClone())
|
|
{
|
|
//Need to update entity that set us on fire in case the vehicle changes ownership
|
|
if (fDamage > 0.0f && pInflictor)
|
|
{
|
|
m_pEntityThatSetUsOnFire = pInflictor;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool bInflictorIsAPed = pInflictor && pInflictor->GetIsTypePed();
|
|
bool bInflictorIsAVehicle = pInflictor && pInflictor->GetIsTypeVehicle();
|
|
|
|
/*
|
|
// let the plane engine overheat and the fuel tank explode
|
|
if(m_pParent->InheritsFromPlane())
|
|
{
|
|
// Planes handle engine and petrol tank damage themselves
|
|
return;
|
|
}
|
|
*/
|
|
|
|
if(m_pParent->InheritsFromTrailer())
|
|
{
|
|
if (!m_pParent->SprayPetrolBeforeExplosion() && !m_pParent->ShouldExplodeOnContact())
|
|
{
|
|
// Trailers have no petrol tank or engine - unless it's a tanker...
|
|
return;
|
|
}
|
|
}
|
|
|
|
//B*1721702/1732210: Don't apply rear damage multiplier for explosives in MP
|
|
if(m_pParent->IsTank() && ((nDamageType == DAMAGE_TYPE_EXPLOSIVE && !NetworkInterface::IsGameInProgress()) || nDamageType == DAMAGE_TYPE_BULLET))
|
|
{
|
|
if(vecPosLocal.y < m_pParent->GetBoundingBoxMin().y * 0.5f)
|
|
{
|
|
fDamage *= sfTankEngineRearDamageMulti;
|
|
}
|
|
}
|
|
|
|
// calc if damage should be applied to engine
|
|
{
|
|
bool bDamageEngine = false;
|
|
|
|
if( nDamageType == DAMAGE_TYPE_EXPLOSIVE || (m_pParent->m_nVehicleFlags.bForceEngineDamageByBullet && nDamageType == DAMAGE_TYPE_BULLET))
|
|
{
|
|
bDamageEngine = true;
|
|
}
|
|
else if(nDamageType == DAMAGE_TYPE_MELEE && GetEngineHealth() <= ENGINE_DAMAGE_CAP_BY_MELEE)
|
|
{
|
|
// No more melee damage if engine health reaches the cap
|
|
}
|
|
else
|
|
{
|
|
Vector3 VecEnginePosLocal = VEC3_ZERO;
|
|
if(m_pParent->GetBoneIndex(VEH_ENGINE)>-1)
|
|
{
|
|
Matrix34 matEngineLocal = m_pParent->GetLocalMtx(m_pParent->GetBoneIndex(VEH_ENGINE));
|
|
VecEnginePosLocal = matEngineLocal.d;
|
|
}
|
|
|
|
//Make sure the Z is reasonable before checking if the Y is around the engine
|
|
const float fDistZSq = square(vecPosLocal.z - VecEnginePosLocal.z);
|
|
if(nDamageType == DAMAGE_TYPE_FIRE || nDamageType == DAMAGE_TYPE_EXPLOSIVE || fDistZSq < sfEngineMinDistanceSq)
|
|
{
|
|
if(VecEnginePosLocal.y > 0.0f)
|
|
{
|
|
if(vecPosLocal.y > VecEnginePosLocal.y - sfEngineMinDistance && vecPosLocal.y < m_pParent->GetBoundingBoxMax().y + sfEngineMinDistance)
|
|
bDamageEngine = true;
|
|
}
|
|
else
|
|
{
|
|
if(vecPosLocal.y < VecEnginePosLocal.y + sfEngineMinDistance && vecPosLocal.y > m_pParent->GetBoundingBoxMin().y - sfEngineMinDistance)
|
|
bDamageEngine = true;
|
|
}
|
|
}
|
|
|
|
if(bFireDriveby && vecPosLocal.y > 0.0f && fwRandom::GetRandomNumberInRange(0.0f,1.0f) > sfWeaponDrivebyForceDamageFrac)
|
|
bDamageEngine = true;
|
|
|
|
// Damage the engine if we have had a reasonably large collision when upside down, simulating the shock on the engine.
|
|
if(m_pParent->IsUpsideDown() && fDamage > 5.0f)
|
|
{
|
|
bDamageEngine = true;
|
|
}
|
|
|
|
// Inflict half of the damage to engine when large bodies are hit
|
|
if(!bDamageEngine && (m_pParent->InheritsFromPlane() || m_pParent->InheritsFromHeli() || m_pParent->InheritsFromSubmarine() || m_pParent->IsTank()))
|
|
{
|
|
bDamageEngine = true;
|
|
float fDistanceMult = 1.0f - (vecPosLocal.Dist(VecEnginePosLocal) / m_pParent->GetBoundRadius());
|
|
fDistanceMult = Clamp(fDistanceMult, 0.1f, 0.5f);
|
|
fDamage *= fDistanceMult;
|
|
}
|
|
}
|
|
|
|
// If a weapon flagged ApplyVehicleDamageToEngine doesn't hit the engine, it should still apply 33% of it's damage to the engine
|
|
if (!bDamageEngine && (bInflictorIsAPed || bInflictorIsAVehicle))
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if (pWeaponInfo && pWeaponInfo->GetApplyVehicleDamageToEngine())
|
|
{
|
|
bDamageEngine = true;
|
|
fDamage = 0.33f * fDamage;
|
|
}
|
|
}
|
|
|
|
if(bDamageEngine)
|
|
{
|
|
float damageMult = 1.0f;
|
|
if( nDamageType == DAMAGE_TYPE_COLLISION )
|
|
{
|
|
damageMult *= m_pParent->pHandling->m_fEngineDamageMult;
|
|
}
|
|
|
|
// Increase the damage done to the engine when upside down
|
|
if(m_pParent->IsUpsideDown())
|
|
{
|
|
damageMult *= sfUpsideDownEngineDamageMult;
|
|
}
|
|
|
|
float damage = fDamage * damageMult;
|
|
|
|
// Scale down the damage to engines by melee hits.
|
|
if(nDamageType == DAMAGE_TYPE_MELEE)
|
|
{
|
|
damage *= 0.3f;
|
|
}
|
|
|
|
if(m_pParent->InheritsFromPlane())
|
|
{
|
|
damage = ((CPlane *)m_pParent)->GetAircraftDamage().ApplyDamageToEngine(pInflictor, ((CPlane *)m_pParent), damage, nDamageType, vecPosLocal, vecNormLocal);
|
|
}
|
|
|
|
float currentEngineHealth = m_pParent->m_Transmission.GetEngineHealth();
|
|
if(bAvoidExplosions && currentEngineHealth >= ENGINE_DAMAGE_ONFIRE)
|
|
{
|
|
float maximumAllowedDamage = currentEngineHealth - Max(currentEngineHealth-fDamage,ENGINE_DAMAGE_ONFIRE + 0.1f);
|
|
damage = Max(maximumAllowedDamage,0.0f);
|
|
}
|
|
|
|
m_pParent->m_Transmission.ApplyEngineDamage(m_pParent, pInflictor, nDamageType, damage, true);
|
|
}
|
|
}
|
|
|
|
if(nDamageType == DAMAGE_TYPE_BULLET && !bIsAccurate && fwRandom::GetRandomNumberInRange(0.0f,1.0f) < m_pParent->GetHealth()/VEH_DAMAGE_HEALTH_STD)
|
|
{
|
|
// Only accurate shots can damage petrol tank
|
|
return;
|
|
}
|
|
|
|
bool bForceDamageToPetrolTank = false;
|
|
|
|
if(nDamageType==DAMAGE_TYPE_FIRE && GetEngineHealth()<=0)
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
}
|
|
else if(nDamageType == DAMAGE_TYPE_EXPLOSIVE)
|
|
{
|
|
Vector3 vecPetrolTankTestPos;
|
|
FindClosestPetrolTankPos(vecPosLocal, &vecPetrolTankTestPos);
|
|
|
|
if(vecPetrolTankTestPos.Dist2(vecPosLocal) > fDamageRadius*fDamageRadius)
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
}
|
|
|
|
// Allow certain explosion types to ignore the distance check to petrol tank and always force damage
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if (pWeaponInfo)
|
|
{
|
|
const CAmmoInfo* pAmmoInfo = pWeaponInfo->GetAmmoInfo();
|
|
if (pAmmoInfo && pAmmoInfo->GetIsClassId(CAmmoProjectileInfo::GetStaticClassId()))
|
|
{
|
|
const CAmmoProjectileInfo* pAmmoProjectileInfo = static_cast<const CAmmoProjectileInfo*>(pAmmoInfo);
|
|
if (pAmmoProjectileInfo && pAmmoProjectileInfo->GetExplosionTag() != EXP_TAG_DONTCARE)
|
|
{
|
|
CExplosionTagData& explosionTagData = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(pAmmoProjectileInfo->GetExplosionTag());
|
|
if (explosionTagData.bForcePetrolTankDamage)
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if((m_pParent->pHandling->hFlags &HF_ARMOURED)==0)
|
|
{
|
|
Vector3 vecPetrolTankTestPos;
|
|
FindClosestPetrolTankPos(vecPosLocal, &vecPetrolTankTestPos);
|
|
|
|
if(vecPetrolTankTestPos.IsNonZero())
|
|
{
|
|
bool bDamagedByMonsterTruckCollision = false;
|
|
|
|
if( nDamageType == DAMAGE_TYPE_COLLISION &&
|
|
bInflictorIsAVehicle )
|
|
{
|
|
if( m_pParent->pHandling->hFlags & HF_REDUCED_DRIVE_OVER_DAMAGE )
|
|
{
|
|
bDamagedByMonsterTruckCollision = true;
|
|
}
|
|
}
|
|
|
|
if( ( nDamageType == DAMAGE_TYPE_BULLET || nDamageType == DAMAGE_TYPE_COLLISION ) &&
|
|
!bDamagedByMonsterTruckCollision )
|
|
{
|
|
Vector3 vecTankBoxHalfLimits(m_pParent->GetBoundingBoxMax().x * 0.5f, 0.5f, 0.5f);
|
|
|
|
// GTAV - B*1894041 - the above values are much to big for a motorbike fuel tank
|
|
if( m_pParent->GetVehicleType() == VEHICLE_TYPE_BIKE )
|
|
{
|
|
vecTankBoxHalfLimits.SetY( 0.25f );
|
|
vecTankBoxHalfLimits.SetZ( 0.1f );
|
|
}
|
|
|
|
if( m_pParent->GetModelIndex() == MI_CAR_SCRAMJET.GetModelIndex() )
|
|
{
|
|
vecTankBoxHalfLimits *= 0.25f;
|
|
}
|
|
|
|
// GTAV - B*2787979 - reduce the size of the tugboat fuel tank so it doesn't explode to easily
|
|
if( m_pParent->GetVehicleType() == VEHICLE_TYPE_BOAT &&
|
|
( MI_BOAT_TUG.IsValid() && ( m_pParent->GetModelIndex() == MI_BOAT_TUG.GetModelIndex() ) ) )
|
|
{
|
|
vecTankBoxHalfLimits.SetX( 0.25f );
|
|
}
|
|
|
|
if( bInflictorIsAPed && ((CPed*)pInflictor)->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) )
|
|
{
|
|
vecTankBoxHalfLimits.z += 0.2f;
|
|
}
|
|
|
|
DEV_ONLY(Color32 hitPetrolTankCol = Color_green);
|
|
Vector3 vTankBoxMin = vecPetrolTankTestPos - vecTankBoxHalfLimits;
|
|
Vector3 vTankBoxMax = vecPetrolTankTestPos + vecTankBoxHalfLimits;
|
|
if( vecPosLocal.x >= vTankBoxMin.x && vecPosLocal.x <= vTankBoxMax.x &&
|
|
vecPosLocal.y >= vTankBoxMin.y && vecPosLocal.y <= vTankBoxMax.y &&
|
|
vecPosLocal.z >= vTankBoxMin.z && vecPosLocal.z <= vTankBoxMax.z)
|
|
{
|
|
if(nDamageType == DAMAGE_TYPE_BULLET || m_fPetrolTankHealth > VEH_DAMAGE_HEALTH_PTANK_LEAKING)
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
DEV_ONLY(hitPetrolTankCol = Color_orange);
|
|
}
|
|
}
|
|
|
|
if(!bForceDamageToPetrolTank)
|
|
{
|
|
if(nDamageType == DAMAGE_TYPE_BULLET)
|
|
{
|
|
Vector3 vecStart = vecPosLocal - vecPetrolTankTestPos;
|
|
// Move the start back a bit along the direction
|
|
float fProbeLength = Dot( vecTankBoxHalfLimits, vecDirnLocal ) * 0.5f + 0.15f;
|
|
Vector3 vecStartToEnd = vecDirnLocal * -fProbeLength;
|
|
Vec3V boxHalfWidths = VECTOR3_TO_VEC3V( vecTankBoxHalfLimits ); //Scale( Subtract( VECTOR3_TO_VEC3V(vecPetrolTankTestPos + vecTankBoxHalfLimits), VECTOR3_TO_VEC3V(vecPetrolTankTestPos - vecTankBoxHalfLimits)), ScalarV(V_HALF));
|
|
|
|
if(geomBoxes::TestSegmentToBox(RCC_VEC3V(vecStart), RCC_VEC3V(vecStartToEnd), boxHalfWidths, NULL, NULL, NULL, NULL, NULL, NULL))
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
DEV_ONLY(hitPetrolTankCol = Color_orange);
|
|
}
|
|
|
|
#if __DEV
|
|
if(gbRenderPetrolTankDamage)
|
|
{
|
|
Vector3 vecWorldStart = VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform(VECTOR3_TO_VEC3V(vecStart + vecPetrolTankTestPos)));
|
|
Vector3 vecWorldEnd = vecWorldStart + VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform3x3(VECTOR3_TO_VEC3V(vecStartToEnd)));
|
|
grcDebugDraw::Sphere(vecWorldStart, 0.1f, Color_red, true, -30);
|
|
grcDebugDraw::Sphere(vecWorldEnd, 0.1f, Color_green, true, -30);
|
|
grcDebugDraw::Line(vecWorldStart, vecWorldEnd, Color_red, Color_green, -30);
|
|
grcDebugDraw::BoxOriented(VECTOR3_TO_VEC3V(vecPetrolTankTestPos - vecTankBoxHalfLimits), VECTOR3_TO_VEC3V(vecPetrolTankTestPos + vecTankBoxHalfLimits), m_pParent->GetMatrix(), hitPetrolTankCol, false, -30);
|
|
}
|
|
#endif // __DEV
|
|
|
|
}
|
|
else if(nDamageType == DAMAGE_TYPE_COLLISION && m_fPetrolTankHealth > VEH_DAMAGE_HEALTH_PTANK_LEAKING)
|
|
{
|
|
if(m_pParent->GetVehicleType() == VEHICLE_TYPE_CAR)
|
|
{
|
|
CAutomobile* pAutomobile = static_cast< CAutomobile* >( m_pParent );
|
|
|
|
dev_float collisionDamageWhenUsingHydraulicsThreshold = 5.0f;
|
|
|
|
// don't apply fuel tank damage caused by cars using hydraulics
|
|
if( ( pAutomobile->m_nAutomobileFlags.bHydraulicsBounceRaising ||
|
|
pAutomobile->m_nAutomobileFlags.bHydraulicsBounceLanding ) &&
|
|
fDamage < collisionDamageWhenUsingHydraulicsThreshold )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Inflict some of the damage to petrol tank, requested from B*1218172
|
|
bForceDamageToPetrolTank = true;
|
|
float fDistanceMult = 1.0f - (vecPosLocal.Dist(vecPetrolTankTestPos) / m_pParent->GetBoundRadius());
|
|
fDistanceMult = Clamp(fDistanceMult, 0.1f, 0.5f);
|
|
fDamage *= fDistanceMult;
|
|
|
|
// B*1569654 - reduce the amount of damage that planes take to the petrol tank from collision damage
|
|
if( m_pParent->InheritsFromPlane() &&
|
|
( m_pParent->GetModelIndex() == MI_PLANE_TITAN ||
|
|
m_pParent->GetModelIndex() == MI_PLANE_BOMBUSHKA ||
|
|
m_pParent->GetModelIndex() == MI_PLANE_VOLATOL) )
|
|
{
|
|
fDamage *= sfPlaneCollisionWithCarMult;
|
|
}
|
|
|
|
fDamage = Min(fDamage, m_fPetrolTankHealth - VEH_DAMAGE_HEALTH_PTANK_LEAKING);
|
|
|
|
#if __DEV
|
|
if(gbRenderPetrolTankDamage)
|
|
{
|
|
Vector3 vecHitPos = VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform(VECTOR3_TO_VEC3V(vecPosLocal)));
|
|
grcDebugDraw::Sphere(vecHitPos, 0.1f, Color_red, true, -30);
|
|
grcDebugDraw::BoxOriented(VECTOR3_TO_VEC3V(vecPetrolTankTestPos - vecTankBoxHalfLimits), VECTOR3_TO_VEC3V(vecPetrolTankTestPos + vecTankBoxHalfLimits), m_pParent->GetMatrix(), hitPetrolTankCol, false, -30);
|
|
}
|
|
#endif // __DEV
|
|
}
|
|
}
|
|
#if __DEV
|
|
else if(gbRenderPetrolTankDamage)
|
|
{
|
|
Vector3 vecHitPos = VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform(VECTOR3_TO_VEC3V(vecPosLocal)));
|
|
grcDebugDraw::Sphere(vecHitPos, 0.1f, Color_red, true, -30);
|
|
grcDebugDraw::BoxOriented(VECTOR3_TO_VEC3V(vecPetrolTankTestPos - vecTankBoxHalfLimits), VECTOR3_TO_VEC3V(vecPetrolTankTestPos + vecTankBoxHalfLimits), m_pParent->GetMatrix(), hitPetrolTankCol, false, -30);
|
|
}
|
|
#endif // __DEV
|
|
}
|
|
else if (nDamageType == DAMAGE_TYPE_FIRE)
|
|
{
|
|
// check if the fire is close enough to the petrol tank
|
|
Vec3V vPetrolTankPosLcl = VECTOR3_TO_VEC3V(vecPetrolTankTestPos);
|
|
Vec3V vFirePosLcl = VECTOR3_TO_VEC3V(vecPosLocal) + Vec3V(0.0f, 0.0f, 0.25f);
|
|
Vec3V vPetrolTankToFire = vFirePosLcl-vPetrolTankPosLcl;
|
|
if (Mag(vPetrolTankToFire).Getf()<1.0f)
|
|
{
|
|
// now check that the fire is within the vehicle bound
|
|
spdAABB aabbVehicleLcl;
|
|
aabbVehicleLcl = m_pParent->GetLocalSpaceBoundBox(aabbVehicleLcl);
|
|
spdSphere sphereLcl(vFirePosLcl, ScalarVFromF32(0.2f));
|
|
if (aabbVehicleLcl.IntersectsSphere(sphereLcl))
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bFireDriveby && vecPosLocal.y < 0.0f && fwRandom::GetRandomNumberInRange(0.0f,1.0f) > sfWeaponDrivebyForceDamageFrac)
|
|
{
|
|
bForceDamageToPetrolTank = true;
|
|
}
|
|
}
|
|
|
|
if( bForceDamageToPetrolTank )
|
|
{
|
|
if(m_fPetrolTankHealth > VEH_DAMAGE_HEALTH_PTANK_FINISHED && !m_pParent->m_nVehicleFlags.bDisablePetrolTankDamage)
|
|
{
|
|
float fApplyDamageToTank = fDamage;
|
|
|
|
bool bVehicleSkipsDamageScaling = (m_pParent->InheritsFromHeli() && ((CHeli *)m_pParent)->GetIsCargobob()) || (m_pParent->IsTank() && NetworkInterface::IsGameInProgress())
|
|
|| (m_pParent->GetVehicleModelInfo() && m_pParent->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_CAPPED_EXPLOSION_DAMAGE));
|
|
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
bool bWeaponSkipsDamageScaling = pWeaponInfo && pWeaponInfo->GetShouldSkipVehiclePetrolTankDamage(); // Homing Launcher, Valkyrie Explosive Cannon
|
|
|
|
// HACK - B*2828467 - This flag is an easy way to identify Contraband vehicles, where we need to always force scaling for script balancing. Seperate flag would be cleaner...
|
|
bool bVehicleForcesDamageScaling = !m_pParent->GetUseMPDamageMultiplierForPlayerVehicle();
|
|
|
|
// Add reduction multiplier for Full Metal Jacket ammunition
|
|
if(pInflictor && pInflictor->GetIsTypePed())
|
|
{
|
|
const CPed* pInflictorPed = static_cast<const CPed*>(pInflictor);
|
|
if (pInflictorPed && pWeaponInfo)
|
|
{
|
|
const CAmmoInfo* pAmmoInfo = pWeaponInfo->GetAmmoInfo(pInflictorPed);
|
|
if (pAmmoInfo && pAmmoInfo->GetIsFMJ())
|
|
{
|
|
fApplyDamageToTank *= sfSpecialAmmoFMJDamageMultiplierAgainstVehicleGasTank;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( (bVehicleSkipsDamageScaling || bWeaponSkipsDamageScaling) && !bVehicleForcesDamageScaling)
|
|
{
|
|
// Don't scale the petrol tank damage to Cargobob, as we want it to survive through a rocket explosion - B*1183762
|
|
// Don't scale the petrol tank damage to tanks in MP - B*1721702/1732210
|
|
// Don't scale the petrol tank damage of Homing Launcher or Valkyrie Explosive Cannon, as this is tuned for script without gas tank multipliers - B*2122525
|
|
}
|
|
else if( bInflictorIsAPed && ((CPed*)pInflictor)->IsPlayer() )
|
|
{
|
|
fApplyDamageToTank *= sfVehDamPetrolTankApplyGunDamageMult;
|
|
}
|
|
else if( bVehicleForcesDamageScaling && bInflictorIsAVehicle && ((CVehicle*)pInflictor)->GetDriver() && ((CVehicle*)pInflictor)->GetDriver()->IsPlayer() )
|
|
{
|
|
// HACK - B*2828467 - We need to force scaling against Contraband vehicles for script balancing when attacking with vehicle explosives.
|
|
fApplyDamageToTank *= sfVehDamPetrolTankApplyGunDamageMult;
|
|
}
|
|
else
|
|
{
|
|
fApplyDamageToTank *= sfVehDamPetrolTankApplyGunDamageMultAI;
|
|
}
|
|
|
|
if (nDamageType == DAMAGE_TYPE_FIRE)
|
|
{
|
|
fApplyDamageToTank *= sfVehDamPetrolTankApplyFireDamageMult;
|
|
}
|
|
|
|
if(bAvoidExplosions && m_fPetrolTankHealth > VEH_DAMAGE_HEALTH_PTANK_LEAKING)
|
|
{
|
|
// Don't make the petrol tank start leaking if the user wants to avoid explosions.
|
|
m_fPetrolTankHealth = Max(m_fPetrolTankHealth - ( fApplyDamageToTank * m_fPetrolTankDamageScale ),VEH_DAMAGE_HEALTH_PTANK_LEAKING + 0.1f);
|
|
}
|
|
else
|
|
{
|
|
if(m_fPetrolTankHealth - fApplyDamageToTank < VEH_DAMAGE_HEALTH_PTANK_ONFIRE)
|
|
{
|
|
if (nDamageType == DAMAGE_TYPE_BULLET || (nDamageType == DAMAGE_TYPE_EXPLOSIVE && !bChainExplosion))
|
|
{
|
|
// want to do the explosions one single place, so set very low health so it'll happen next frame.
|
|
m_fPetrolTankHealth = VEH_DAMAGE_HEALTH_PTANK_FINISHED + 0.1f;
|
|
m_pEntityThatSetUsOnFire = pInflictor;
|
|
}
|
|
else
|
|
{
|
|
// set vehicle on fire with a fixed amount of health, so it will blow up after a predictable about of time
|
|
SetPetrolTankOnFire(pInflictor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fPetrolTankHealth -= fApplyDamageToTank * m_fPetrolTankDamageScale;
|
|
|
|
// want to do the explosions one single place, so set very low health so it'll happen next frame.
|
|
if(m_fPetrolTankHealth <= VEH_DAMAGE_HEALTH_PTANK_FINISHED)
|
|
{
|
|
m_fPetrolTankHealth = VEH_DAMAGE_HEALTH_PTANK_FINISHED + 0.1f;
|
|
m_pEntityThatSetUsOnFire = pInflictor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::UpdateLightsOnBreakOff(int nComponentID)
|
|
{
|
|
if (NetworkInterface::IsNetworkOpen() && !m_pParent->m_bAllowRemoteDamageOnCreation && m_pParent->IsNetworkClone())
|
|
return;
|
|
|
|
// Lookup bone index from child
|
|
Assert(m_pParent->GetVehicleFragInst());
|
|
Assert(m_pParent->GetVehicleFragInst()->GetTypePhysics()->GetNumChildren() > nComponentID);
|
|
|
|
fragInstGta* fragInst = m_pParent->GetVehicleFragInst();
|
|
int boneIdx = fragInst->GetType()->GetBoneIndexFromID(fragInst->GetTypePhysics()->GetAllChildren()[nComponentID]->GetBoneID());
|
|
|
|
const bool isTaxi = CVehicle::IsTaxiModelId(m_pParent->GetModelId());
|
|
int lightCount = isTaxi ? NUM_GLASS_BONES : NUM_LIGHT_BONES;
|
|
|
|
if(boneIdx > -1)
|
|
{
|
|
const crSkeleton *skel = m_pParent->GetSkeleton();
|
|
u32 terminalIdx = skel->GetTerminatingPartialBone(boneIdx);
|
|
|
|
for (u32 i=boneIdx; i<terminalIdx; i++)
|
|
{
|
|
for(int j=0; j<lightCount; j++)
|
|
{
|
|
const u32 lightBoneIdx = (u32)m_pParent->GetBoneIndex(aGlassBones[j]);
|
|
if( i == lightBoneIdx )
|
|
{
|
|
const int lightIdx = ( j > TAXI_IDX ) ? TAXI_IDX : j;
|
|
SetLightStateImmediate(lightIdx,true);
|
|
SetUpdateLightBones(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CVehicleDamage::SetPetrolTankOnFire(CEntity* pEntityResponsible)
|
|
{
|
|
if (m_pParent->m_nVehicleFlags.bDisablePetrolTankFires==false)
|
|
{
|
|
if(m_fPetrolTankHealth >= VEH_DAMAGE_HEALTH_PTANK_ONFIRE)
|
|
{
|
|
m_fPetrolTankHealth = VEH_DAMAGE_HEALTH_PTANK_ONFIRE - 1.0f;
|
|
|
|
m_pEntityThatSetUsOnFire = pEntityResponsible;
|
|
}
|
|
}
|
|
}
|
|
|
|
dev_float sfStuckDistSqr = 2.0f*2.0f;
|
|
dev_float sfStuckDistBoatSqr = 1.0f;
|
|
dev_float sfStuckJammedDistSqr = 3.0f*3.0f;
|
|
dev_float sfStuckJammedDistBoatSqr = 1.0f;
|
|
//
|
|
void CVehicleDamage::ProcessStuckCheck(float fTimeStep)
|
|
{
|
|
Vec3f vParentPosition;
|
|
Vec::V3LoadAsScalar(vParentPosition.GetIntrinRef(), m_pParent->GetMatrixRef().GetCol3ConstRef().GetIntrin128ConstRef());
|
|
|
|
Vec3f vecPosDelta;
|
|
vecPosDelta = vParentPosition - RCC_VEC3F(m_vecLastStuckPos);
|
|
|
|
bool isWatercraft = m_pParent->GetIsAquatic();
|
|
|
|
// we don't treat the submarine car as aquatic if it isn't in the water.
|
|
if( isWatercraft &&
|
|
( !m_pParent->GetIsInWater() &&
|
|
( m_pParent->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR ||
|
|
m_pParent->GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE ||
|
|
m_pParent->GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE ) ) )
|
|
{
|
|
isWatercraft = false;
|
|
}
|
|
|
|
if( !isWatercraft &&
|
|
m_pParent->GetIsInWater() &&
|
|
m_pParent->pHandling->GetSeaPlaneHandlingData() )
|
|
{
|
|
isWatercraft = true;
|
|
}
|
|
|
|
bool isPlane = m_pParent->GetVehicleType()==VEHICLE_TYPE_PLANE;
|
|
bool isAbandonedBike = m_pParent->InheritsFromBike() && m_pParent->GetStatus() == STATUS_ABANDONED;
|
|
|
|
float fDistLimitSqr = sfStuckDistSqr;
|
|
// boats require bigger distance check because they tend to move around on the water
|
|
if(isWatercraft)
|
|
{
|
|
fDistLimitSqr = sfStuckDistBoatSqr;
|
|
vecPosDelta.SetZ( vecPosDelta.GetZ() * 0.2f);
|
|
}
|
|
|
|
u32 timeStepMs = (u32)(fTimeStep*1000.0f);
|
|
|
|
u32 newStuckCounterOnRoof = m_nStuckCounterOnRoof;
|
|
u32 newStuckCounterOnSide = m_nStuckCounterOnSide;
|
|
u32 newStuckCounterHungUp = m_nStuckCounterHungUp;
|
|
|
|
const CCollisionHistory* pCollisionHistory = m_pParent->GetFrameCollisionHistory();
|
|
if(MagSquared(vecPosDelta) < fDistLimitSqr)
|
|
{
|
|
|
|
const float fParentCz = m_pParent->GetMatrixRef().GetCol2ConstRef().GetZf();
|
|
// On roof check
|
|
if(fParentCz < -0.3f)
|
|
{
|
|
if(newStuckCounterOnRoof + timeStepMs < BIT(16) - 1)
|
|
newStuckCounterOnRoof = (u16)(newStuckCounterOnRoof + timeStepMs);
|
|
else
|
|
newStuckCounterOnRoof = (u16)(BIT(16) - 1);
|
|
}
|
|
else
|
|
newStuckCounterOnRoof = 0;
|
|
|
|
|
|
// On side check
|
|
if(Abs(fParentCz) < 0.5f && m_pParent->GetNumContactWheels() < m_pParent->GetNumWheels() && !isAbandonedBike)
|
|
{
|
|
if(newStuckCounterOnSide + timeStepMs < BIT(16) - 1)
|
|
newStuckCounterOnSide = (u16)(newStuckCounterOnSide + timeStepMs);
|
|
else
|
|
newStuckCounterOnSide = (u16)(BIT(16) - 1);
|
|
}
|
|
else
|
|
newStuckCounterOnSide = 0;
|
|
|
|
// Hung up check (no drive wheels on ground)
|
|
if(!m_pParent->GetIsAnyFixedFlagSet() && (!isAbandonedBike || m_pParent->m_nVehicleFlags.bVehicleInaccesible))
|
|
{
|
|
bool bHungUp = false;
|
|
if(m_pParent->GetCollider() && isWatercraft)
|
|
{
|
|
CSubmarineHandling* pSubHandling = m_pParent->GetSubHandling();
|
|
|
|
if(m_pParent->GetVehicleType()==VEHICLE_TYPE_BOAT)
|
|
{
|
|
// Boats are 'hung up' if they aren't in water or if they are in water but their propeller isn't.
|
|
CBoat* pBoat = static_cast<CBoat*>(m_pParent);
|
|
bool bBoatInWater = pBoat->m_BoatHandling.IsInWater();
|
|
bool bPropellerInWater = pBoat->m_BoatHandling.GetPropellerSubmerged() == 1;
|
|
bHungUp = (pCollisionHistory->GetMaxCollisionImpulseMagLastFrame() > 0.0f && (!bBoatInWater || !bPropellerInWater));
|
|
}
|
|
else if( pSubHandling )
|
|
{
|
|
bool bSubInWater = m_pParent->GetIsInWater();
|
|
bool bPropellerInWater = pSubHandling->GetIsPropellerSubmerged();
|
|
bHungUp = (pCollisionHistory->GetMaxCollisionImpulseMagLastFrame() > 0.0f && (!bSubInWater || !bPropellerInWater));
|
|
}
|
|
}
|
|
else if(m_pParent->InheritsFromHeli() ||
|
|
( m_pParent->InheritsFromPlane() &&
|
|
static_cast< CPlane* >( m_pParent )->GetVerticalFlightModeAvaliable() ) )
|
|
{
|
|
// The code further down was used for helicopters, and when flying and the wheels weren't
|
|
// touching, they were considered hung up. There is probably no case where that check would really
|
|
// be valid for a helicopter, so now we never consider them hung up. Feels like there are probably
|
|
// other types of vehicles where that check isn't accurate.
|
|
bHungUp = false;
|
|
}
|
|
else if(m_pParent->m_nVehicleFlags.bVehicleInaccesible)
|
|
{
|
|
// For bicycles and other open-seat vehicles marked 'inaccessible' by the enter vehicle task, we don't care if the wheels are touching the ground or not.
|
|
bHungUp = true;
|
|
}
|
|
else
|
|
{
|
|
if( m_pParent->GetSpecialFlightModeRatio() == 1.0f )
|
|
{
|
|
bHungUp = false;
|
|
}
|
|
else
|
|
{
|
|
bHungUp = true;
|
|
int nNumWheels = m_pParent->GetNumWheels();
|
|
const CWheel * const * ppWheels = m_pParent->GetWheels();
|
|
for(int i=0; i<nNumWheels; i++)
|
|
{
|
|
if(ppWheels[i]->GetConfigFlags().IsFlagSet(WCF_POWERED) && (m_pParent->GetWheel(i)->GetIsTouching() || m_pParent->GetWheel(i)->GetWasTouching()) )
|
|
{
|
|
bHungUp = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( bHungUp &&
|
|
m_pParent->InheritsFromAmphibiousQuadBike() )
|
|
{
|
|
CAmphibiousQuadBike* amphibiousQuadBike = static_cast< CAmphibiousQuadBike* >( m_pParent );
|
|
if( !amphibiousQuadBike->IsWheelsFullyOut() )
|
|
{
|
|
bHungUp = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Boats using low LOD anchor mode can't get stuck.
|
|
if( ( m_pParent->GetVehicleType()==VEHICLE_TYPE_BOAT && static_cast<CBoat*>(m_pParent)->GetAnchorHelper().UsingLowLodMode() ) ||
|
|
( m_pParent->InheritsFromAmphibiousAutomobile() && static_cast<CAmphibiousAutomobile*>(m_pParent)->GetAnchorHelper().UsingLowLodMode() ) )
|
|
{
|
|
bHungUp = false;
|
|
}
|
|
|
|
if( m_pParent->IsInSubmarineMode() )
|
|
{
|
|
bHungUp = false;
|
|
}
|
|
|
|
//Can't be hung up if no collision is loaded
|
|
if(bHungUp && isWatercraft && !m_pParent->IsCollisionLoadedAroundPosition())
|
|
{
|
|
bHungUp = false;
|
|
}
|
|
|
|
if(bHungUp)
|
|
{
|
|
if(newStuckCounterHungUp + timeStepMs < BIT(16) - 1)
|
|
newStuckCounterHungUp = (u16)(newStuckCounterHungUp + timeStepMs);
|
|
else
|
|
newStuckCounterHungUp = (u16)(BIT(16) - 1);
|
|
}
|
|
else
|
|
newStuckCounterHungUp = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RC_VEC3F(m_vecLastStuckPos) = vParentPosition;
|
|
newStuckCounterOnRoof = 0;
|
|
newStuckCounterOnSide = 0;
|
|
newStuckCounterHungUp = 0;
|
|
}
|
|
|
|
m_nStuckCounterOnRoof = (u16)newStuckCounterOnRoof;
|
|
m_nStuckCounterOnSide = (u16)newStuckCounterOnSide;
|
|
m_nStuckCounterHungUp = (u16)newStuckCounterHungUp;
|
|
|
|
// do a separate jammed distance check,
|
|
// want to allow the vehicle to move further but still be considered jammed
|
|
vecPosDelta = vParentPosition - RCC_VEC3F(m_vecLastStuckJammedPos);
|
|
|
|
u16 newStuckCounterJammed = m_nStuckCounterJammed;
|
|
|
|
fDistLimitSqr = sfStuckJammedDistSqr;
|
|
// boats require bigger distance check because they tend to move around on the water
|
|
if( isWatercraft )
|
|
{
|
|
if( ( m_pParent->InheritsFromBoat() && ((CBoat*)m_pParent)->m_BoatHandling.IsInWater() ) || m_pParent->GetIsInWater() )
|
|
{
|
|
fDistLimitSqr = sfStuckJammedDistBoatSqr;
|
|
vecPosDelta.SetZ( vecPosDelta.GetZ() * 0.2f);
|
|
}
|
|
}
|
|
|
|
if( MagSquared( vecPosDelta ) < fDistLimitSqr )
|
|
{
|
|
// Jammed check
|
|
// can't be jammed if you've been set to be fixed
|
|
// can't be jammed when engine is starting
|
|
bool bIsEngineStarting = (!m_pParent->m_nVehicleFlags.bEngineOn && m_pParent->m_nVehicleFlags.bEngineStarting);
|
|
if(!m_pParent->GetIsAnyFixedFlagSet() && !bIsEngineStarting)
|
|
{
|
|
// check a lower threshold for AI vehicles because they often use low throttle values when trying to turn
|
|
float fGasPedalThreshold = m_pParent->GetStatus()==STATUS_PLAYER ? 0.5f : 0.2f;
|
|
float throttle = m_pParent->m_vehControls.GetThrottle();
|
|
|
|
bool bJammed = (rage::Abs( throttle ) > fGasPedalThreshold &&
|
|
(!m_bHasHandBrake || !m_pParent->GetHandBrake()) &&
|
|
!m_pParent->GetBrake());
|
|
|
|
float collisionThreshold = 0.0f;
|
|
|
|
if( m_pParent->GetModelIndex() == MI_PLANE_TITAN )
|
|
{
|
|
collisionThreshold = 5.0f;
|
|
}
|
|
|
|
if( !bJammed &&
|
|
!isAbandonedBike && // If this is an abandoned bike it could be left on its side with no wheels on the ground. If it is inaccessible that will be handled in the hung up check. If it is accessible then we should wait till the ped is on it before seeing if it is jammed.
|
|
pCollisionHistory->GetMaxCollisionImpulseMagLastFrame() > collisionThreshold &&
|
|
!m_pParent->InheritsFromHeli() && // There's no point checking that it is stuck on top of something if it can take off vertically
|
|
!m_pParent->IsInSubmarineMode() &&
|
|
m_pParent->GetOutriggerDeployRatio() != 1.0f )
|
|
{
|
|
const CCollisionRecord * pColRecord = pCollisionHistory->GetMostSignificantCollisionRecord();
|
|
|
|
bool bAllWheelsTouching = true;// I was getting "jammed" whilst driving onto a car transporter
|
|
for(int i = 0; i < m_pParent->GetNumWheels(); i++)
|
|
{
|
|
CPhysical *pHitPhysical = m_pParent->GetWheel(i)->GetHitPhysical();
|
|
|
|
if( !m_pParent->GetWheel(i)->GetIsTouching() ||
|
|
// If wheel is touching a physical and it's not a trailer, consider it not touching anything for jammed checks.
|
|
(m_pParent->GetWheel(i)->GetIsTouching() && pHitPhysical &&
|
|
!(pHitPhysical->GetType() == ENTITY_TYPE_VEHICLE && (static_cast<CVehicle*>(pHitPhysical))->GetVehicleType() == VEHICLE_TYPE_TRAILER))
|
|
)
|
|
{
|
|
bAllWheelsTouching = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bAllWheelsTouching && pColRecord && pColRecord->m_MyCollisionNormal.z > 0.5f)
|
|
{
|
|
bJammed = true;
|
|
}
|
|
}
|
|
|
|
// If amphibious quad has its wheels retracted, don't count it as jammed since wheels won't be touching the ground
|
|
if(m_pParent->InheritsFromAmphibiousQuadBike() && !static_cast<CAmphibiousQuadBike*>(m_pParent)->IsWheelsFullyOut())
|
|
{
|
|
bJammed = false;
|
|
}
|
|
|
|
if(isWatercraft)
|
|
{
|
|
if((m_pParent->InheritsFromBoat() && !((CBoat*)m_pParent)->m_BoatHandling.IsInWater()) || !m_pParent->GetIsInWater())
|
|
{
|
|
if(!static_cast<CBoat*>(m_pParent)->GetAnchorHelper().UsingLowLodMode())
|
|
{
|
|
bJammed = true;
|
|
}
|
|
}
|
|
|
|
//Can't be jammed if no collision is loaded
|
|
if(bJammed && !m_pParent->IsCollisionLoadedAroundPosition())
|
|
{
|
|
bJammed = false;
|
|
}
|
|
}
|
|
|
|
if(isPlane && !m_pParent->IsInAir())
|
|
{
|
|
if(!((CPlane *)m_pParent)->GetIsLandingGearintact())
|
|
{
|
|
bJammed = true;
|
|
}
|
|
}
|
|
|
|
if(m_pParent->GetVehicleType() == VEHICLE_TYPE_TRAILER)
|
|
{
|
|
bJammed = false;
|
|
}
|
|
|
|
if(bJammed)
|
|
{
|
|
if(newStuckCounterJammed + timeStepMs < BIT(16) - 1)
|
|
{
|
|
newStuckCounterJammed = (u16)(newStuckCounterJammed + timeStepMs);
|
|
}
|
|
else
|
|
{
|
|
newStuckCounterJammed = (u16)(BIT(16) - 1);
|
|
}
|
|
}
|
|
// don't reset jammed check when gas pedal is lifted, just stop counting up
|
|
//else
|
|
// m_nStuckCounterJammed = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RC_VEC3F(m_vecLastStuckJammedPos) = vParentPosition;
|
|
newStuckCounterJammed = 0;
|
|
}
|
|
m_nStuckCounterJammed = (u16)newStuckCounterJammed;
|
|
}
|
|
|
|
void CVehicleDamage::ResetStuckCheck()
|
|
{
|
|
m_nStuckCounterOnRoof = 0;
|
|
m_nStuckCounterOnSide = 0;
|
|
m_nStuckCounterHungUp = 0;
|
|
m_nStuckCounterJammed = 0;
|
|
m_vecLastStuckPos.Set(VEC3_LARGE_FLOAT);
|
|
m_vecLastStuckJammedPos.Set(VEC3_LARGE_FLOAT);
|
|
}
|
|
|
|
bool CVehicleDamage::GetIsStuck(int nStuckType, u16 nRequiredTime) const
|
|
{
|
|
switch(nStuckType)
|
|
{
|
|
case VEH_STUCK_ON_ROOF:
|
|
if(m_nStuckCounterOnRoof > nRequiredTime)
|
|
return true;
|
|
break;
|
|
case VEH_STUCK_ON_SIDE:
|
|
if(m_nStuckCounterOnSide > nRequiredTime)
|
|
return true;
|
|
break;
|
|
case VEH_STUCK_HUNG_UP:
|
|
if(m_nStuckCounterHungUp > nRequiredTime)
|
|
return true;
|
|
break;
|
|
case VEH_STUCK_JAMMED:
|
|
if(m_nStuckCounterJammed > nRequiredTime)
|
|
return true;
|
|
break;
|
|
default:
|
|
Assertf(false, "Unknown stuck check type");
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CVehicleDamage::ResetStuckTimer(int nStuckType)
|
|
{
|
|
switch(nStuckType)
|
|
{
|
|
case VEH_STUCK_ON_ROOF:
|
|
m_nStuckCounterOnRoof = 0;
|
|
break;
|
|
case VEH_STUCK_ON_SIDE:
|
|
m_nStuckCounterOnSide = 0;
|
|
break;
|
|
case VEH_STUCK_HUNG_UP:
|
|
m_nStuckCounterHungUp = 0;
|
|
break;
|
|
case VEH_STUCK_JAMMED:
|
|
m_nStuckCounterJammed = 0;
|
|
break;
|
|
case VEH_STUCK_RESET_ALL:
|
|
m_nStuckCounterOnRoof = 0;
|
|
m_nStuckCounterOnSide = 0;
|
|
m_nStuckCounterHungUp = 0;
|
|
m_nStuckCounterJammed = 0;
|
|
break;
|
|
default:
|
|
Assertf(false, "Unknown stuck check type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
float CVehicleDamage::GetStuckTimer(int nStuckType) const
|
|
{
|
|
switch(nStuckType)
|
|
{
|
|
case VEH_STUCK_ON_ROOF:
|
|
return m_nStuckCounterOnRoof;
|
|
break;
|
|
case VEH_STUCK_ON_SIDE:
|
|
return m_nStuckCounterOnSide;
|
|
break;
|
|
case VEH_STUCK_HUNG_UP:
|
|
return m_nStuckCounterHungUp;
|
|
break;
|
|
case VEH_STUCK_JAMMED:
|
|
return m_nStuckCounterJammed;
|
|
break;
|
|
default:
|
|
Assertf(false, "Unknown stuck check type");
|
|
break;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
static dev_float sfBrokenPartsResetTime = 1.0f;
|
|
void CVehicleDamage::ResetBrokenPartCountdown()
|
|
{
|
|
m_fCountDownToTimeAnotherPartCanBreakOff = sfBrokenPartsResetTime;
|
|
}
|
|
|
|
dev_float dfHeliRotorUndriveableSpeed = 0.3f;
|
|
|
|
#if __BANK
|
|
#define IS_DRIVEABLE_FAILURE_DEBUG(msg) \
|
|
if(CVehicle::ms_bDebugVehicleIsDriveableFail) \
|
|
{ \
|
|
vehicleDisplayf("IsDriveable check failed for %s at (%5.3f, %5.3f, %5.3f). Reason: %s.", m_pParent->GetModelName(), \
|
|
m_pParent->GetVehiclePosition().GetXf(), m_pParent->GetVehiclePosition().GetYf(), m_pParent->GetVehiclePosition().GetZf(), msg); \
|
|
}
|
|
#else // __BANK
|
|
#define IS_DRIVEABLE_FAILURE_DEBUG(x)
|
|
#endif // __BANK
|
|
|
|
bool CVehicleDamage::GetIsDriveable(bool bCheckForPlayerPed, bool bIgnoreHealthCheck, bool bIgnorePetrolCheck) const
|
|
{
|
|
if(m_pParent->GetStatus() == STATUS_WRECKED)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Status is 'wrecked'");
|
|
return false;
|
|
}
|
|
|
|
float tooMuchDamage = SelectFT(IsLessThan(GetPetrolTankHealth(), bCheckForPlayerPed ? VEH_DAMAGE_HEALTH_PTANK_FINISHED : VEH_DAMAGE_HEALTH_PTANK_ONFIRE), 0.0f, 1.0f);
|
|
tooMuchDamage = SelectFT(IsLessThanOrEqual(GetEngineHealth(), bCheckForPlayerPed ? ENGINE_DAMAGE_FINSHED : ENGINE_DAMAGE_ONFIRE), tooMuchDamage, 1.0f);
|
|
|
|
if (tooMuchDamage > 0.0f)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Too much damage");
|
|
return false;
|
|
}
|
|
|
|
// engine is dead from collision (decided in CTransmission::ApplyEngineDamage)
|
|
// Script can set the vehicle health to zero also, B*815022
|
|
if(!bIgnoreHealthCheck && !m_pParent->m_nVehicleFlags.bEngineOn && GetEngineHealth() <= ENGINE_DAMAGE_ONFIRE)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Engine is dead from collision");
|
|
return false;
|
|
}
|
|
|
|
// make sure we have petrol left
|
|
if(!bIgnorePetrolCheck && m_pParent->GetVehicleType() != VEHICLE_TYPE_BICYCLE)
|
|
{
|
|
if(m_pParent->m_nVehicleFlags.bPetrolTankEmpty && m_pParent->pHandling->m_fPetrolTankVolume > 0.0f)
|
|
{
|
|
if (!m_pParent->HasInfiniteFuel())
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Out of petrol");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// for heli's check rotors and tail, but ignore wheels
|
|
if(Unlikely(m_pParent->GetIsRotaryAircraft()))
|
|
{
|
|
CRotaryWingAircraft* pAircraft = (CRotaryWingAircraft*)m_pParent;
|
|
if(GetEngineHealth() <= sfHeliEngineBreakDownHealth && pAircraft->GetMainRotorSpeed() < dfHeliRotorUndriveableSpeed)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Rotary aircraft low rotor speed");
|
|
return false;
|
|
}
|
|
if(pAircraft->GetMainRotorHealth() <= 0.0f || pAircraft->GetRearRotorHealth() <= 0.0f)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Rotary aircraft rotor health");
|
|
return false;
|
|
}
|
|
if(pAircraft->GetIsTailBoomBroken())
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Rotary aircraft tail boom broken");
|
|
return false;
|
|
}
|
|
}
|
|
// for planes make sure we have both wings
|
|
else if(Unlikely(m_pParent->InheritsFromPlane()))
|
|
{
|
|
CPlane* pAircraft = (CPlane*)m_pParent;
|
|
if( (pAircraft->GetAircraftDamage().GetSectionHealth( CAircraftDamage::WING_L ) <= 0.0f) ||
|
|
(pAircraft->GetAircraftDamage().GetSectionHealth( CAircraftDamage::WING_R ) <= 0.0f) )
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Aircraft section health");
|
|
return false;
|
|
}
|
|
if(GetEngineHealth() <= sfPlaneEngineBreakDownHealth && pAircraft->GetEngineSpeed() < SMALL_FLOAT)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Aircraft engine health");
|
|
return false;
|
|
}
|
|
if(pAircraft->GetNumPropellors() > 0)
|
|
{
|
|
bool bAnyAlivePropeller = false;
|
|
for(int i =0; i< pAircraft->GetNumPropellors(); i++)
|
|
{
|
|
if(pAircraft->GetPropellerHealth(i) > 0.0f)
|
|
{
|
|
bAnyAlivePropeller = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!bAnyAlivePropeller)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Aircraft without any alive propeller");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(Unlikely(m_pParent->InheritsFromSubmarine()))
|
|
{
|
|
//Subs cannot be driven after their engine health reaches 0.0f
|
|
float tooMuchDamage = SelectFT(IsLessThan(GetEngineHealth(), 0.0f), 0.0f, 1.0f);
|
|
|
|
if (tooMuchDamage > 0.0f)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Submarine too much damage");
|
|
return false;
|
|
}
|
|
}
|
|
else if( Unlikely(m_pParent->InheritsFromBoat()) )
|
|
{
|
|
CBoat* pBoat = static_cast< CBoat* >( m_pParent );
|
|
if( pBoat->m_BoatHandling.GetCapsizedTimer() >= 1.0f)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Boat capsized");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check for wheels being jammed
|
|
float tooMuchDamage = 0.0f;
|
|
|
|
CWheel* const * wheels = m_pParent->GetWheels();
|
|
int numWheels = m_pParent->GetNumWheels();
|
|
for(int i = 0; i < numWheels; i++)
|
|
{
|
|
// If a wheel has been broken off, this vehicle is undriveable for AI.
|
|
if(!bCheckForPlayerPed && wheels[i]->GetDynamicFlags().IsFlagSet(WF_BROKEN_OFF))
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Wheel broken off");
|
|
return false;
|
|
}
|
|
|
|
// If frictionDamage > 1.0, set to true
|
|
tooMuchDamage = SelectFT(IsGreaterThan(wheels[i]->GetFrictionDamage(), 1.0), tooMuchDamage, 1.0f );
|
|
}
|
|
|
|
if (tooMuchDamage > 0.0f)
|
|
{
|
|
IS_DRIVEABLE_FAILURE_DEBUG("Too much wheel damage");
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CVehicleDamage::ApplyDamageToWheels(CEntity* pInflictor, eDamageType nDamageType, float fDamage, const Vector3& vecPosLocal, const Vector3& vecNormLocal, const Vector3& vecDirnLocal, int nComponent, phMaterialMgr::Id nMaterialId, int nPieceIndex)
|
|
{
|
|
((void)pInflictor);
|
|
((void)nDamageType);
|
|
((void)vecDirnLocal);
|
|
((void)nComponent);
|
|
|
|
|
|
if(!m_pParent->InheritsFromBike()) // Don't damage suspension on bikes
|
|
{
|
|
Vector3 vecSusPos;
|
|
float fSusRadius;
|
|
for(int i=0; i<m_pParent->GetNumWheels(); i++)
|
|
{
|
|
if(!m_pParent->GetWheel(i)->GetConfigFlags().IsFlagSet(WCF_CENTRE_WHEEL)) // Similarly to bikes, don't damage centre wheels
|
|
{
|
|
fSusRadius = m_pParent->GetWheel(i)->GetSuspensionPos(vecSusPos);
|
|
if((vecPosLocal - vecSusPos).Mag2() < fSusRadius*fSusRadius)
|
|
{
|
|
m_pParent->GetWheel(i)->ApplySuspensionDamage(fDamage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(PGTAMATERIALMGR->GetPolyFlagVehicleWheel(nMaterialId) && PGTAMATERIALMGR->UnpackMtlId(nMaterialId)==PGTAMATERIALMGR->g_idRubber)
|
|
{
|
|
// wheel index is stored in the piece type
|
|
m_pParent->GetWheel(nPieceIndex)->ApplyTyreDamage(pInflictor, fDamage, vecPosLocal, vecNormLocal, nDamageType, nPieceIndex);
|
|
}
|
|
}
|
|
|
|
|
|
void CVehicleDamage::ApplyDamageToGlass(float fDamage, const Vector3& vecPosLocal, const Vector3& vecNormLocal, const Vector3& vecDirnLocal)
|
|
{
|
|
if( fDamage < GlassDamageThreshold ) // to small, not worth processing.
|
|
return;
|
|
|
|
if (NetworkInterface::IsGameInProgress() && m_pParent->IsNetworkClone())
|
|
return;
|
|
|
|
// calc the damage position in world space
|
|
Vector3 worldDamagePos;
|
|
m_pParent->TransformIntoWorldSpace(worldDamagePos, vecPosLocal);
|
|
|
|
const bool isTaxi = CVehicle::IsTaxiModelId(m_pParent->GetModelId());
|
|
int lightCount = isTaxi ? NUM_GLASS_BONES : NUM_LIGHT_BONES;
|
|
|
|
// Glass
|
|
if( !m_pParent->m_nVehicleFlags.bUnbreakableLights )
|
|
{
|
|
for(int i=0; i<lightCount; i++)
|
|
{
|
|
const int boneIdx = m_pParent->GetBoneIndex(aGlassBones[i]);
|
|
const int lightIdx = ( i > TAXI_IDX ) ? TAXI_IDX : i;
|
|
const bool lightState = GetLightStateImmediate(lightIdx);
|
|
if( boneIdx != -1 && (false == lightState))
|
|
{
|
|
// get the light position in world space
|
|
Matrix34 worldLightMat;
|
|
m_pParent->GetGlobalMtx(boneIdx, worldLightMat);
|
|
|
|
// check if the damage is close to the light
|
|
if((worldLightMat.d - worldDamagePos).Mag2() < 0.2f*0.2f)
|
|
{
|
|
// it is - smash the light
|
|
Vector3 localLightPos = VEC3V_TO_VECTOR3(m_pParent->GetTransform().UnTransform(VECTOR3_TO_VEC3V(worldLightMat.d)));
|
|
|
|
// find a suitable normal
|
|
Vector3 localNormal(0.0f, 0.0f, 1.0f);
|
|
if (!vecNormLocal.IsZero())
|
|
{
|
|
localNormal = vecNormLocal;
|
|
}
|
|
else if (!vecDirnLocal.IsZero())
|
|
{
|
|
localNormal = -vecDirnLocal;
|
|
}
|
|
else if (!localLightPos.IsZero())
|
|
{
|
|
localNormal = localLightPos;
|
|
}
|
|
|
|
localNormal.Normalize();
|
|
// check that the normal is valid
|
|
Assertf(localNormal.Mag()>0.997f && localNormal.Mag()<1.003f, "Normal is not valid on %s %.4f,%.4f,%.4f for light %d,%d", m_pParent->GetModelName(), localNormal.x, localNormal.y, localNormal.z,boneIdx,lightIdx);
|
|
|
|
g_vfxVehicle.TriggerPtFxLightSmash(m_pParent, aGlassBones[i], RCC_VEC3V(localLightPos), RCC_VEC3V(localNormal));
|
|
m_pParent->GetVehicleAudioEntity()->TriggerHeadlightSmash(aGlassBones[i],localLightPos);
|
|
SetLightStateImmediate(lightIdx,true);
|
|
SetUpdateLightBones(true);
|
|
|
|
if(NetworkInterface::IsGameInProgress() && m_pParent->GetNetworkObject() && !m_pParent->GetNetworkObject()->IsClone())
|
|
{
|
|
CNetObjVehicle* netParent = SafeCast(CNetObjVehicle, m_pParent->GetNetworkObject());
|
|
netParent->ForceSendVehicleDamageNode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extras
|
|
const CVehicleVariationInstance& variationInstance = m_pParent->GetVariationInstance();
|
|
if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX && variationInstance.GetVehicleRenderGfx())
|
|
{
|
|
for (u32 ii = VEH_EXTRALIGHT_1; ii <= VEH_EXTRALIGHT_4; ++ii)
|
|
{
|
|
const bool lightState = GetLightState(ii);
|
|
if( false == lightState )
|
|
{
|
|
// get the light position in world space
|
|
int boneIdx;
|
|
Mat34V worldLightMat;
|
|
m_pParent->GetExtraLightMatrix((eHierarchyId)ii, worldLightMat, boneIdx, NULL);
|
|
if( boneIdx != -1 )
|
|
{
|
|
// check if the damage is close to the light
|
|
if((VEC3V_TO_VECTOR3(worldLightMat.d()) - worldDamagePos).Mag2() < 0.2f*0.2f)
|
|
{
|
|
// it is - smash the light
|
|
Vector3 localLightPos = VEC3V_TO_VECTOR3(m_pParent->GetTransform().UnTransform(worldLightMat.d()));
|
|
|
|
// find a suitable normal
|
|
Vector3 localNormal(0.0f, 0.0f, 1.0f);
|
|
if (!vecNormLocal.IsZero())
|
|
{
|
|
localNormal = vecNormLocal;
|
|
}
|
|
else if (!vecDirnLocal.IsZero())
|
|
{
|
|
localNormal = -vecDirnLocal;
|
|
}
|
|
else if (!localLightPos.IsZero())
|
|
{
|
|
localNormal = localLightPos;
|
|
}
|
|
|
|
localNormal.Normalize();
|
|
// check that the normal is valid
|
|
Assertf(localNormal.Mag()>0.997f && localNormal.Mag()<1.003f, "Normal is not valid on %s %.4f,%.4f,%.4f for light %d,%d", m_pParent->GetModelName(), localNormal.x, localNormal.y, localNormal.z,boneIdx,ii);
|
|
|
|
g_vfxVehicle.TriggerPtFxLightSmash(m_pParent, ii, RCC_VEC3V(localLightPos), RCC_VEC3V(localNormal));
|
|
m_pParent->GetVehicleAudioEntity()->TriggerHeadlightSmash(ii,localLightPos);
|
|
SetLightState(ii,true);
|
|
SetUpdateLightBones(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_pParent->UsesSiren() )
|
|
{
|
|
// Siren
|
|
for(int i=VEH_SIREN_1; i<VEH_SIREN_MAX + 1; i++)
|
|
{
|
|
const int boneIdx = m_pParent->GetBoneIndex((eHierarchyId)i);
|
|
const bool sirenState = GetSirenState(i);
|
|
if( boneIdx != -1 && (false == sirenState))
|
|
{
|
|
// get the siren position in world space
|
|
Matrix34 worldLightMat;
|
|
m_pParent->GetGlobalMtx(boneIdx, worldLightMat);
|
|
|
|
// check if the damage is close to the siren
|
|
static dev_float sirenRadius = 0.10f;
|
|
if((worldLightMat.d - worldDamagePos).Mag2() < sirenRadius*sirenRadius)
|
|
{
|
|
// find a suitable normal
|
|
SetSirenState(i,true);
|
|
SetUpdateSirenBones(true);
|
|
|
|
// No Fxs
|
|
// pop/break the glass casing
|
|
const int boneGlassID = m_pParent->GetBoneIndex((eHierarchyId)(i + (VEH_SIREN_GLASS_1-VEH_SIREN_1)));
|
|
const int component = m_pParent->GetVehicleFragInst()->GetComponentFromBoneIndex(boneGlassID);
|
|
if( component != -1 )
|
|
{
|
|
Vec3V vSmashNormal(V_Z_AXIS_WZERO);
|
|
g_vehicleGlassMan.SmashCollision(m_pParent, component, VEHICLEGLASSFORCE_SIREN_SMASH, vSmashNormal);
|
|
m_pParent->GetVehicleAudioEntity()->SmashSiren();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::ApplyDamageToOverallHealth(CEntity* pInflictor, eDamageType nDamageType, u32 nWeaponHash, float fDamage, const Vector3& vecPosLocal, const Vector3& vecDirnLocal, int nComponent, const bool bChainExplosion)
|
|
{
|
|
((void)nComponent);
|
|
|
|
CPed* pInflictorPed = NULL;
|
|
|
|
// If the vehicle has already blown up there is no point doing more damage.
|
|
// if (m_pParent->m_nPhysicalFlags.bRenderScorched) Wait till decision on cars blowing up is made
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
if (pInflictor) // pInflictor can be NULL
|
|
{
|
|
if(pInflictor->GetIsTypePed())
|
|
pInflictorPed = (CPed*)pInflictor;
|
|
else if(pInflictor->GetIsTypeVehicle())
|
|
pInflictorPed = ((CVehicle*)pInflictor)->GetDriver();
|
|
}
|
|
|
|
// Ignore damage caused by collisions with peds, other reactions should handle this
|
|
if( nWeaponHash == WEAPONTYPE_RAMMEDBYVEHICLE && pInflictor && pInflictor->GetIsTypePed() )
|
|
{
|
|
// Ignore
|
|
}
|
|
else
|
|
{
|
|
// update player damage stats
|
|
if(fDamage > 10.0f && pInflictorPed && pInflictorPed->IsLocalPlayer())
|
|
{
|
|
if(m_pParent->GetStatus() != STATUS_WRECKED)
|
|
{
|
|
pInflictorPed->GetPlayerInfo()->HavocCaused += HAVOC_DENTCAR;
|
|
}
|
|
}
|
|
|
|
// If we want to do generic vehicle hit registration with the hud do it here.
|
|
|
|
// If the vehicle being damaged has an alive law enforcement ped in it
|
|
// increase the wanted level to a minimum of 1
|
|
if(pInflictorPed && pInflictorPed->IsLocalPlayer())
|
|
{
|
|
if(m_pParent->HasAliveLawPedsInIt())
|
|
{
|
|
// Make an exception if there has been a collision between player car and police car and the police car went faster. It was probably its fault.
|
|
if (!pInflictorPed->GetVehiclePedInside() || nDamageType != DAMAGE_TYPE_COLLISION ||
|
|
pInflictorPed->GetVehiclePedInside()->GetVelocity().Mag2() > m_pParent->GetVelocity().Mag2() )
|
|
{
|
|
//Notify the passengers that they were rammed in a vehicle.
|
|
for(int i = 0; i < m_pParent->GetSeatManager()->GetMaxSeats(); ++i)
|
|
{
|
|
CPed* pPedInSeat = m_pParent->GetSeatManager()->GetPedInSeat(i);
|
|
if(pPedInSeat)
|
|
{
|
|
pPedInSeat->GetPedIntelligence()->RammedInVehicle(pInflictorPed);
|
|
}
|
|
}
|
|
|
|
const Vector3 vInflictorPosition = VEC3V_TO_VECTOR3(pInflictorPed->GetTransform().GetPosition());
|
|
|
|
if(pInflictorPed->GetPlayerInfo()->GetWanted().GetWantedLevel() == WANTED_CLEAN && pInflictorPed->GetPlayerInfo()->GetWanted().m_fMultiplier)
|
|
{
|
|
if(nDamageType != DAMAGE_TYPE_COLLISION || !pInflictorPed->GetPlayerInfo()->GetWanted().IsCrimeSuppressedThisFrame(CRIME_DAMAGE_TO_PROPERTY))
|
|
{
|
|
pInflictorPed->GetPlayerWanted()->SetWantedLevelNoDrop(vInflictorPosition, WANTED_LEVEL1, WANTED_CHANGE_DELAY_INSTANT, WL_FROM_LAW_RESPONSE_EVENT);
|
|
}
|
|
|
|
// if the car was shot then report the shooting at cop crime
|
|
if(nDamageType == DAMAGE_TYPE_BULLET || nDamageType == DAMAGE_TYPE_BULLET_RUBBER)
|
|
{
|
|
CCrime::ReportCrime(CRIME_SHOOT_AT_COP, m_pParent, pInflictorPed);
|
|
}
|
|
}
|
|
//Moving this outside of the WANTED_CLEAN check. I think we want scanner audio even if we already have stars.
|
|
#if NA_POLICESCANNER_ENABLED
|
|
if(nDamageType == DAMAGE_TYPE_COLLISION)
|
|
{
|
|
g_PoliceScanner.ReportPlayerCrime(vInflictorPosition, CRIME_RECKLESS_DRIVING, WANTED_CHANGE_DELAY_INSTANT);
|
|
}
|
|
else if(nDamageType == DAMAGE_TYPE_BULLET || nDamageType == DAMAGE_TYPE_BULLET_RUBBER)
|
|
{
|
|
g_PoliceScanner.ReportPlayerCrime(vInflictorPosition, CRIME_SHOOT_AT_COP, WANTED_CHANGE_DELAY_INSTANT);
|
|
}
|
|
else
|
|
{
|
|
g_PoliceScanner.ReportPlayerCrime(vInflictorPosition, CRIME_DAMAGE_TO_PROPERTY, WANTED_CHANGE_DELAY_INSTANT);
|
|
}
|
|
#endif // NA_POLICESCANNER_ENABLED
|
|
}
|
|
}
|
|
else if(m_pParent->IsLawEnforcementVehicle() && nDamageType == DAMAGE_TYPE_COLLISION)
|
|
{
|
|
CCrime::ReportCrime(CRIME_DAMAGE_TO_PROPERTY, m_pParent, pInflictorPed);
|
|
}
|
|
}
|
|
|
|
if(m_pParent->GetHealth() > 0.0f)
|
|
{
|
|
// add events to let peds in this car know about damage
|
|
if(pInflictorPed && (m_pParent->GetStatus()==STATUS_PLAYER || m_pParent->GetStatus()==STATUS_PHYSICS))
|
|
{
|
|
for(int iSeat=0; iSeat<m_pParent->GetSeatManager()->GetMaxSeats(); iSeat++)
|
|
{
|
|
if(m_pParent->GetSeatManager()->GetPedInSeat(iSeat))
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
// Check if the weapon triggers disputes instead of combat.
|
|
if (pInflictorPed && pInflictorPed->IsAPlayerPed() && pWeaponInfo && pWeaponInfo->GetDamageCausesDisputes())
|
|
{
|
|
// Give the ped an agitated event.
|
|
CEventAgitated event(pInflictorPed, AT_Griefing);
|
|
m_pParent->GetSeatManager()->GetPedInSeat(iSeat)->GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
else
|
|
{
|
|
if( !m_pParent->HasRamp() && !m_pParent->HasRammingScoop() )
|
|
{
|
|
CEventVehicleDamageWeapon event(m_pParent, pInflictor, nWeaponHash, fDamage, vecPosLocal, vecDirnLocal);
|
|
m_pParent->GetSeatManager()->GetPedInSeat(iSeat)->GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_pParent->GetHealth() > fDamage)
|
|
{
|
|
m_pParent->ChangeHealth(-fDamage);
|
|
}
|
|
else
|
|
{
|
|
m_pParent->SetHealth(0.0f);
|
|
|
|
if(bChainExplosion)
|
|
{
|
|
CCrime::ReportCrime(CRIME_CHAIN_EXPLOSION, m_pParent, pInflictorPed);
|
|
}
|
|
else
|
|
{
|
|
CCrime::ReportDestroyVehicle(m_pParent, pInflictorPed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector3 CVehicleDamage::RecomputeImpulseFromTrain(CTrain *pTrain, const Vector3& vImpulse) const
|
|
{
|
|
static dev_float sf_MinTrainSpeedSqToRecomputeImpulse = 4.0f;
|
|
if(pTrain->GetNumCarriages() > 1 && pTrain->GetVelocity().Mag2() > sf_MinTrainSpeedSqToRecomputeImpulse)
|
|
{
|
|
// Recompute the impulse to include the mass of all train carriages
|
|
Vector3 vTrainForward = VEC3V_TO_VECTOR3(pTrain->GetTransform().GetForward());
|
|
Vector3 vParentFoward = VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetForward());
|
|
float vAligned = vTrainForward.Dot(vParentFoward);
|
|
|
|
int nNumCarriages = Max(pTrain->GetNumCarriages(), 1);
|
|
float fMassRatio = (1.0f / (float)nNumCarriages);
|
|
fMassRatio = Max(fMassRatio * (1.0f + vAligned), fMassRatio);
|
|
|
|
Vector3 vDeltaVelParent = vImpulse * InvertSafe(m_pParent->GetMass() * fMassRatio, m_pParent->GetInvMass());
|
|
return m_pParent->GetMass() * vDeltaVelParent;
|
|
}
|
|
|
|
return vImpulse;
|
|
}
|
|
|
|
|
|
void CVehicleDamage::ApplyPadShake(const CEntity* pInflictor, float fDamage)
|
|
{
|
|
if(fDamage < sfMinRumbleDamThreshold)
|
|
return;
|
|
|
|
// SHAKE PAD
|
|
if (m_pParent && m_pParent->ContainsLocalPlayer( ))
|
|
{
|
|
|
|
if (pInflictor && pInflictor->GetIsTypePed() && ((CPed*)pInflictor)->GetIsAttached())
|
|
{
|
|
CEntity* pAttachToEntity = (CPhysical *) ((CPed*)pInflictor)->GetAttachParent();
|
|
if (pAttachToEntity && pAttachToEntity->GetIsTypeVehicle() && pAttachToEntity == m_pParent)
|
|
return; // Do not Shake with ped's attached to the vehicle
|
|
}
|
|
else if (pInflictor && pInflictor->GetIsTypeObject() && ((CObject*)pInflictor)->GetIsAttached())
|
|
{
|
|
CEntity* pAttachToEntity = (CPhysical *) ((CPed*)pInflictor)->GetAttachParent();
|
|
if (pAttachToEntity && pAttachToEntity->GetIsTypeVehicle() && pAttachToEntity == m_pParent)
|
|
return; // Do not Shake with objects attached to the vehicle
|
|
}
|
|
// Do not shake if Infictor's vehicle is not moving
|
|
else if (pInflictor && pInflictor->GetIsTypeVehicle() && ((CVehicle*)pInflictor)->GetVelocity().Mag2() < SPEED_TO_CONSIDER_MOV && ((CVehicle*)pInflictor)->GetDistanceTravelled() < DIST_TO_CONSIDER_MOV)
|
|
return;
|
|
// Do not shake if Vehicle is not moving
|
|
// else if (m_pParent->GetVelocity().Mag2() < SPEED_TO_CONSIDER_MOV && m_pParent->GetDistanceTravelled() < DIST_TO_CONSIDER_MOV)
|
|
// return;
|
|
|
|
float fIntensity = (sfRumbleIntensModifier * fDamage);
|
|
float fDur = (sfRumbleDurModifier * fDamage);
|
|
|
|
#if __BANK
|
|
if (CControlMgr::sm_bUseThisMultiplier)
|
|
fIntensity = (CControlMgr::sm_fMultiplier * fDamage);
|
|
#endif // __BANK
|
|
|
|
|
|
if(fIntensity > m_fPadShakeIntensity)
|
|
{
|
|
m_fPadShakeIntensity = fIntensity;
|
|
m_uPadShakeDuration = (u8)rage::Clamp(fDur,sfMinRumbleDur,sfMaxRumbleDur);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::ApplyPadShakeInternal()
|
|
{
|
|
if(m_fPadShakeIntensity > 0.0f && m_uPadShakeDuration > 0)
|
|
{
|
|
|
|
#if __BANK
|
|
if(CVehicle::ms_nVehicleDebug==VEH_DEBUG_PERFORMANCE && (m_pParent->GetStatus()==STATUS_PLAYER || m_pParent == CDebugScene::FocusEntities_Get(0)) )
|
|
{
|
|
grcDebugDraw::PrintToScreenCoors("Vehicle damage rumble", 4, 34, Color32(0.90f,0.90f,0.90f), 0, m_uPadShakeDuration/24);
|
|
}
|
|
#endif
|
|
CControlMgr::StartPlayerPadShakeByIntensity(m_uPadShakeDuration, m_fPadShakeIntensity);
|
|
m_fPadShakeIntensity = 0.0f;
|
|
m_uPadShakeDuration = 0;
|
|
}
|
|
}
|
|
|
|
bool CVehicleDamage::GetPetrolTankPosWld(Vector3* pPetrolTankWldPosL, Vector3* pPetrolTankWldPosR, Vector3* pPetrolTankWldPosCap)
|
|
{
|
|
Vector3 petrolTankLclPosL;
|
|
Vector3 petrolTankLclPosR;
|
|
Vector3 petrolTankLclPosCap;
|
|
bool bValidPosition = GetPetrolTankPosLcl(&petrolTankLclPosL, &petrolTankLclPosR, &petrolTankLclPosCap);
|
|
|
|
if (Verifyf(pPetrolTankWldPosL, "must pass a valid pPetrolTankWldPosL into this function"))
|
|
{
|
|
*pPetrolTankWldPosL = VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform(VECTOR3_TO_VEC3V(petrolTankLclPosL)));
|
|
}
|
|
|
|
if (pPetrolTankWldPosR)
|
|
{
|
|
if (IsZeroAll(VECTOR3_TO_VEC3V(petrolTankLclPosR)))
|
|
{
|
|
// if the local position is zero then it isn't defined - keep the world position as zero as well
|
|
*pPetrolTankWldPosR = petrolTankLclPosR;
|
|
}
|
|
else
|
|
{
|
|
*pPetrolTankWldPosR = VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform(VECTOR3_TO_VEC3V(petrolTankLclPosR)));
|
|
}
|
|
}
|
|
|
|
if (pPetrolTankWldPosCap)
|
|
{
|
|
*pPetrolTankWldPosCap = VEC3V_TO_VECTOR3(m_pParent->GetTransform().Transform(VECTOR3_TO_VEC3V(petrolTankLclPosCap)));
|
|
}
|
|
|
|
return bValidPosition;
|
|
}
|
|
|
|
bool CVehicleDamage::GetPetrolTankPosLcl(Vector3* pPetrolTankLclPosL, Vector3* pPetrolTankLclPosR, Vector3* pPetrolTankLclPosCap)
|
|
{
|
|
// we used to always generate a right hand side petrol tank position when one wasn't explicitly set
|
|
// however, url:bugstar:928172 requested that we no longer do this
|
|
#define FORCE_RHS_PETROL_TANK_POS (0)
|
|
|
|
bool bValidPosition = false;
|
|
|
|
if (Verifyf(pPetrolTankLclPosL, "must pass a valid pPetrolTankLclPosL into this function"))
|
|
{
|
|
if (m_pParent->GetBoneIndex(VEH_PETROLTANK)!=-1)
|
|
{
|
|
// get the left sided petrol tank position from the bone
|
|
Matrix34 petrolTankMtxLcl = m_pParent->GetLocalMtx(m_pParent->GetBoneIndex(VEH_PETROLTANK));
|
|
Vector3 petrolTankPosLcl = petrolTankMtxLcl.d;
|
|
|
|
*pPetrolTankLclPosL = petrolTankPosLcl;
|
|
|
|
if(pPetrolTankLclPosR)
|
|
{
|
|
#if FORCE_RHS_PETROL_TANK_POS
|
|
petrolTankPosLcl.x = -petrolTankPosLcl.x;
|
|
*pPetrolTankLclPosR = petrolTankPosLcl;
|
|
#else
|
|
pPetrolTankLclPosR->Zero();
|
|
#endif
|
|
}
|
|
|
|
if(pPetrolTankLclPosCap)
|
|
{
|
|
pPetrolTankLclPosCap->Zero();
|
|
}
|
|
|
|
bValidPosition = true;
|
|
}
|
|
|
|
if(!bValidPosition && (m_pParent->GetBoneIndex(VEH_PETROLTANK_L)!=-1 || m_pParent->GetBoneIndex(VEH_PETROLTANK_R)!=-1))
|
|
{
|
|
//If we have left and right petrol tanks set up then use them
|
|
if(Verifyf(m_pParent->GetBoneIndex(VEH_PETROLTANK_L)!=-1, "Vehicle has a petroltank_r bone without petroltank_l - pPetrolTankLclPosL will be zero!"))
|
|
{
|
|
Vector3 petrolTankPosLcl;
|
|
m_pParent->GetDefaultBonePositionSimple(m_pParent->GetBoneIndex(VEH_PETROLTANK_L), petrolTankPosLcl);
|
|
|
|
*pPetrolTankLclPosL = petrolTankPosLcl;
|
|
}
|
|
else
|
|
{
|
|
pPetrolTankLclPosL->Zero();
|
|
}
|
|
|
|
if(m_pParent->GetBoneIndex(VEH_PETROLTANK_R)!=-1)
|
|
{
|
|
Vector3 petrolTankPosLcl;
|
|
m_pParent->GetDefaultBonePositionSimple(m_pParent->GetBoneIndex(VEH_PETROLTANK_R), petrolTankPosLcl);
|
|
|
|
if(pPetrolTankLclPosR)
|
|
{
|
|
*pPetrolTankLclPosR = petrolTankPosLcl;
|
|
}
|
|
}
|
|
else if(pPetrolTankLclPosR)
|
|
{
|
|
pPetrolTankLclPosR->Zero();
|
|
}
|
|
|
|
if(pPetrolTankLclPosCap)
|
|
{
|
|
pPetrolTankLclPosCap->Zero();
|
|
}
|
|
|
|
bValidPosition = true;
|
|
}
|
|
|
|
bool bHasPetrolCap = false;
|
|
if(!bValidPosition && m_pParent->GetBoneIndex(VEH_PETROLCAP)!=-1)
|
|
{
|
|
Matrix34 petrolTankMtxLcl = m_pParent->GetLocalMtx(m_pParent->GetBoneIndex(VEH_PETROLCAP));
|
|
Vector3 petrolTankPosLcl = petrolTankMtxLcl.d;
|
|
|
|
*pPetrolTankLclPosL = petrolTankPosLcl;
|
|
|
|
if(pPetrolTankLclPosCap)
|
|
{
|
|
*pPetrolTankLclPosCap = petrolTankPosLcl;
|
|
}
|
|
|
|
if(pPetrolTankLclPosR)
|
|
{
|
|
pPetrolTankLclPosR->Zero();
|
|
}
|
|
|
|
bValidPosition = true;
|
|
}
|
|
else if(pPetrolTankLclPosCap && m_pParent->GetBoneIndex(VEH_PETROLCAP)!=-1)
|
|
{
|
|
Matrix34 petrolTankMtxLcl = m_pParent->GetLocalMtx(m_pParent->GetBoneIndex(VEH_PETROLCAP));
|
|
Vector3 petrolTankPosCapLcl = petrolTankMtxLcl.d;
|
|
|
|
*pPetrolTankLclPosCap = petrolTankPosCapLcl;
|
|
bHasPetrolCap = true;
|
|
}
|
|
|
|
// For planes we position the fuel tanks in the wings
|
|
if( !bValidPosition && m_pParent->InheritsFromPlane() && m_pParent->GetBoneIndex( PLANE_WING_L ) !=-1 )
|
|
{
|
|
const Vector3 vParentPosition = VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetPosition());
|
|
*pPetrolTankLclPosL = vParentPosition;
|
|
|
|
// get the left wing position
|
|
if(m_pParent->GetSkeleton())
|
|
{
|
|
// get the local bone position of the left wing
|
|
*pPetrolTankLclPosL = RCC_VECTOR3(m_pParent->GetSkeleton()->GetSkeletonData().GetBoneData(m_pParent->GetBoneIndex( PLANE_WING_L ))->GetDefaultTranslation());
|
|
|
|
if (pPetrolTankLclPosR)
|
|
{
|
|
*pPetrolTankLclPosR = *pPetrolTankLclPosL;
|
|
pPetrolTankLclPosR->x = -pPetrolTankLclPosR->x;
|
|
|
|
bValidPosition = true;
|
|
}
|
|
|
|
if(pPetrolTankLclPosCap && !bHasPetrolCap)
|
|
{
|
|
pPetrolTankLclPosCap->Zero();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bValidPosition && m_pParent->GetBoneIndex(VEH_WHEEL_LR)!=-1)
|
|
{
|
|
const Vector3 vParentPosition = VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetPosition());
|
|
*pPetrolTankLclPosL = vParentPosition;
|
|
|
|
if (pPetrolTankLclPosR)
|
|
{
|
|
#if FORCE_RHS_PETROL_TANK_POS
|
|
*pPetrolTankLclPosR = vParentPosition;
|
|
#else
|
|
pPetrolTankLclPosR->Zero();
|
|
#endif
|
|
}
|
|
|
|
// get the left rear wheel position and move slightly forward
|
|
if(m_pParent->GetSkeleton())
|
|
{
|
|
// get the local bone position of the left rear wheel
|
|
*pPetrolTankLclPosL = RCC_VECTOR3(m_pParent->GetSkeleton()->GetSkeletonData().GetBoneData(m_pParent->GetBoneIndex(VEH_WHEEL_LR))->GetDefaultTranslation());
|
|
|
|
// calc the local bone position of the right rear wheel from this
|
|
if (pPetrolTankLclPosR)
|
|
{
|
|
#if FORCE_RHS_PETROL_TANK_POS
|
|
*pPetrolTankLclPosR = *pPetrolTankLclPosL;
|
|
pPetrolTankLclPosR->x = -pPetrolTankLclPosR->x;
|
|
#else
|
|
pPetrolTankLclPosR->Zero();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// move forward and out
|
|
pPetrolTankLclPosL->y += 0.25f;
|
|
pPetrolTankLclPosL->x -= 0.1f;
|
|
|
|
if (pPetrolTankLclPosR)
|
|
{
|
|
#if FORCE_RHS_PETROL_TANK_POS
|
|
pPetrolTankLclPosR->y += 0.25f;
|
|
pPetrolTankLclPosR->x += 0.1f;
|
|
#else
|
|
pPetrolTankLclPosR->Zero();
|
|
#endif
|
|
}
|
|
|
|
if(pPetrolTankLclPosCap && !bHasPetrolCap)
|
|
{
|
|
pPetrolTankLclPosCap->Zero();
|
|
}
|
|
|
|
bValidPosition = true;
|
|
}
|
|
|
|
}
|
|
|
|
return bValidPosition;
|
|
}
|
|
|
|
void CVehicleDamage::FindClosestPetrolTankPos(const Vector3& vecPosLocal, Vector3* vecPetrolTankTestPos)
|
|
{
|
|
Vector3 vecPetrolTankPosL, vecPetrolTankPosR, vecPetrolTankPosCap;
|
|
GetPetrolTankPosLcl(&vecPetrolTankPosL, &vecPetrolTankPosR, &vecPetrolTankPosCap );
|
|
|
|
float fClosestTankDist2 = FLT_MAX;
|
|
if(vecPetrolTankPosL.IsNonZero())
|
|
{
|
|
fClosestTankDist2 = vecPosLocal.Dist2(vecPetrolTankPosL);
|
|
*vecPetrolTankTestPos = vecPetrolTankPosL;
|
|
}
|
|
|
|
if(vecPetrolTankPosR.IsNonZero())
|
|
{
|
|
float fDist2TankBoneR = FLT_MAX;
|
|
fDist2TankBoneR = vecPosLocal.Dist2(vecPetrolTankPosR);
|
|
if(fDist2TankBoneR < fClosestTankDist2)
|
|
{
|
|
fClosestTankDist2 = fDist2TankBoneR;
|
|
*vecPetrolTankTestPos = vecPetrolTankPosR;
|
|
}
|
|
}
|
|
|
|
if(vecPetrolTankPosCap.IsNonZero())
|
|
{
|
|
float fDist2TankBoneCap = FLT_MAX;
|
|
fDist2TankBoneCap = vecPosLocal.Dist2(vecPetrolTankPosCap);
|
|
if(fDist2TankBoneCap < fClosestTankDist2)
|
|
{
|
|
fClosestTankDist2 = fDist2TankBoneCap;
|
|
*vecPetrolTankTestPos = vecPetrolTankPosCap;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
dev_float sfWindscreenBreakSpdImpulse = 1.85f;
|
|
dev_float sfWindscreenBreakUpImpulse = 1.3f;
|
|
dev_float sfWindscreenBreakSideOffset = -0.4f;
|
|
dev_float sfWindscreenBreakUpOffset = -0.25f;
|
|
|
|
dev_float sfWindscreenSpeedUpMultiplier = 0.2f;
|
|
dev_float sfWindscreenSpeedFwdMultiplier = -0.12f;
|
|
//
|
|
void CVehicleDamage::PopOutWindScreen()
|
|
{
|
|
fragInstGta* pFragInst = m_pParent->GetVehicleFragInst();
|
|
|
|
// pop out the windscreen
|
|
if(m_pParent->GetBoneIndex(VEH_WINDSCREEN)!=-1 && pFragInst)
|
|
{
|
|
int windowFrag = pFragInst->GetComponentFromBoneIndex( m_pParent->GetBoneIndex(VEH_WINDSCREEN) );
|
|
|
|
if (!m_pParent->CarPartsCanBreakOff())
|
|
{
|
|
pFragInst->DeleteAbove(windowFrag);
|
|
}
|
|
else if( windowFrag >= 0 && windowFrag < pFragInst->GetType()->GetPhysics(0)->GetNumChildren())
|
|
{
|
|
s32 nGroup = pFragInst->GetType()->GetPhysics(0)->GetAllChildren()[windowFrag]->GetOwnerGroupPointerIndex();
|
|
if(pFragInst->GetCacheEntry() && pFragInst->GetCacheEntry()->GetHierInst()->groupBroken->IsClear(nGroup))// Make sure windscreen isn't already broken off
|
|
{
|
|
m_pParent->PartHasBrokenOff(VEH_WINDSCREEN);
|
|
fragInst* pNewFragInst = pFragInst->BreakOffAbove(windowFrag);
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
|
|
phCollider* pWindscreenCollider = pNewFragInst ? CPhysics::GetSimulator()->GetCollider(pNewFragInst->GetLevelIndex()) : NULL;
|
|
|
|
if(pNewFragInst && pWindscreenCollider)
|
|
{
|
|
const Vector3 vecParentRight(VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetA()));
|
|
const Vector3 vecParentForward(VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetB()));
|
|
const Vector3 vecParentUp(VEC3V_TO_VECTOR3(m_pParent->GetTransform().GetC()));
|
|
|
|
float fFwdSpeed = DotProduct(m_pParent->GetVelocity(), vecParentForward);
|
|
|
|
Vector3 vecWindscreenImpulse;
|
|
vecWindscreenImpulse.Set(sfWindscreenBreakSpdImpulse * vecParentForward + (sfWindscreenBreakSpdImpulse * fFwdSpeed * sfWindscreenSpeedFwdMultiplier * vecParentForward));
|
|
vecWindscreenImpulse.Add(sfWindscreenBreakUpImpulse * vecParentUp + (sfWindscreenBreakUpImpulse * fFwdSpeed * sfWindscreenSpeedUpMultiplier * vecParentUp));
|
|
vecWindscreenImpulse.Scale(pNewFragInst->GetArchetype()->GetMass());
|
|
|
|
Vector3 vecWindscreenImpulsePos;
|
|
Matrix34 m = RCC_MATRIX34(pNewFragInst->GetMatrix());
|
|
vecWindscreenImpulsePos.Set(m.d);
|
|
vecWindscreenImpulsePos.Add(sfWindscreenBreakSideOffset * vecParentRight);
|
|
vecWindscreenImpulsePos.Add(sfWindscreenBreakUpOffset * vecParentUp);
|
|
|
|
pWindscreenCollider->ApplyImpulse(vecWindscreenImpulse, vecWindscreenImpulsePos);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
void CVehicleDamage::PopOffRoof(const Vector3 &vRoofImpulse)
|
|
{
|
|
// pop out the roof
|
|
if(m_pParent->GetBoneIndex(VEH_ROOF)!=-1 && m_pParent->GetFragInst())
|
|
{
|
|
m_pParent->PartHasBrokenOff(VEH_ROOF);
|
|
|
|
int roofFrag = m_pParent->GetFragInst()->GetType()->GetComponentFromBoneIndex(
|
|
m_pParent->GetVehicleFragInst()->GetCurrentPhysicsLOD(),m_pParent->GetBoneIndex(VEH_ROOF));
|
|
|
|
if (!m_pParent->CarPartsCanBreakOff())
|
|
{
|
|
m_pParent->GetFragInst()->DeleteAbove(roofFrag);
|
|
}
|
|
else
|
|
{
|
|
fragInst* pNewFragInst = m_pParent->GetFragInst()->BreakOffAbove(roofFrag);
|
|
|
|
phCollider* pRoofCollider = pNewFragInst ? CPhysics::GetSimulator()->GetCollider(pNewFragInst->GetLevelIndex()) : NULL;
|
|
|
|
if(pNewFragInst && pRoofCollider)
|
|
{
|
|
Vector3 vecRoofImpulsePos;
|
|
Matrix34 m = RCC_MATRIX34(pNewFragInst->GetMatrix());
|
|
Vector3 vecRoofImpulse(vRoofImpulse);
|
|
|
|
m.Transform3x3(vecRoofImpulse);
|
|
vecRoofImpulse.Scale(pNewFragInst->GetArchetype()->GetMass());
|
|
vecRoofImpulsePos.Set(m.d);
|
|
|
|
pRoofCollider->ApplyImpulse(vecRoofImpulse, vecRoofImpulsePos);
|
|
}
|
|
}
|
|
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::UpdateNetworkDamageTracker(CEntity* pInflictor, const float fDamage, const u32 uWeaponHash, const bool vehicleIsDestroyed, const bool isMeleeDamage, phMaterialMgr::Id nMaterialId) const
|
|
{
|
|
//Ensure a network game is in progress.
|
|
if(!NetworkInterface::IsGameInProgress())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the parent is valid
|
|
if(!m_pParent || m_pParent->IsNetworkClone())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the parent net object is valid and the it is not a network clone (network clones get damage from the network update synch trees)
|
|
CNetObjPhysical* pParentNetObj = static_cast<CNetObjPhysical *>(m_pParent->GetNetworkObject());
|
|
if(!pParentNetObj || pParentNetObj->IsClone())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//If we've wrecked a vehicle, register it.... if the vehicle is a clone, the registration gets taken care of
|
|
// elsewhere (NetObjPhysical::SetPhysicalHealthData)
|
|
NetworkInterface::RegisterKillWithNetworkTracker(m_pParent);
|
|
|
|
//Update the network damage tracker.
|
|
if ((fDamage > 0.0f || NetworkInterface::ShouldTriggerDamageEventForZeroDamage(m_pParent)) && !vehicleIsDestroyed)
|
|
{
|
|
//Register Hits on Peds for explosion weapons - projectiles and such
|
|
CStatsMgr::RegisterExplosionHit(pInflictor, m_pParent, uWeaponHash, fDamage);
|
|
|
|
CNetObjPhysical::NetworkDamageInfo damageInfo(pInflictor, fDamage, 0.0f, 0.0f, false, uWeaponHash, -1, false, m_pParent->GetStatus() == STATUS_WRECKED, isMeleeDamage, nMaterialId);
|
|
pParentNetObj->UpdateDamageTracker(damageInfo);
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::SetPetrolTankLevel( float fPetrolLevel )
|
|
{
|
|
m_fPetrolTankLevel = fPetrolLevel;
|
|
m_pParent->m_nVehicleFlags.bPetrolTankEmpty = (m_fPetrolTankLevel <= 0.0f);
|
|
}
|
|
|
|
void CVehicleDamage::Copy(CVehicle *srcVehicle, CVehicle* dstVehicle)
|
|
{
|
|
if( srcVehicle != NULL && dstVehicle != NULL && srcVehicle != dstVehicle)
|
|
{
|
|
Assertf(srcVehicle->GetModelIndex() == dstVehicle->GetModelIndex(), "Copying damage from different models might lead to unfortunate consequences.");
|
|
|
|
CVehicleDamage *srcDmg = srcVehicle->GetVehicleDamage();
|
|
CVehicleDamage *dstDmg = dstVehicle->GetVehicleDamage();
|
|
|
|
#if ENABLE_FRAG_OPTIMIZATION
|
|
if(srcDmg->m_fBodyHealth < dstVehicle->GetVehicleModelInfo()->GetDefaultBodyHealth())
|
|
{
|
|
dstVehicle->GiveFragCacheEntry(true);
|
|
}
|
|
#endif
|
|
// General values
|
|
dstVehicle->SetHealth( srcVehicle->GetHealth() );
|
|
|
|
dstDmg->m_fBodyHealth = srcDmg->m_fBodyHealth;
|
|
dstDmg->m_fPetrolTankHealth = srcDmg->m_fPetrolTankHealth;
|
|
dstDmg->m_fPetrolTankLevel = srcDmg->m_fPetrolTankLevel;
|
|
dstDmg->m_fOilLevel = srcDmg->m_fOilLevel;
|
|
dstDmg->m_pEntityThatSetUsOnFire = srcDmg->m_pEntityThatSetUsOnFire;
|
|
dstDmg->m_LightState = srcDmg->m_LightState;
|
|
dstDmg->m_UpdateLightBones = srcDmg->m_UpdateLightBones;
|
|
dstDmg->m_UpdateSirenBones = srcDmg->m_UpdateSirenBones;
|
|
dstDmg->m_SirenState = srcDmg->m_SirenState;
|
|
sysMemCpy(dstDmg->m_BouncingPanels, srcDmg->m_BouncingPanels,sizeof(dstDmg->m_BouncingPanels));
|
|
dstDmg->m_vPetrolSprayPosLocal = srcDmg->m_vPetrolSprayPosLocal;
|
|
dstDmg->m_vPetrolSprayNrmLocal = srcDmg->m_vPetrolSprayNrmLocal;
|
|
|
|
// Engine and suspension health and fire.
|
|
if(dstDmg->m_fPetrolTankHealth>VEH_DAMAGE_HEALTH_PTANK_FINISHED && dstDmg->m_fPetrolTankHealth<VEH_DAMAGE_HEALTH_PTANK_ONFIRE)
|
|
{
|
|
CPed* pCulPrit = NULL;
|
|
if (dstDmg->m_pEntityThatSetUsOnFire.Get())
|
|
{
|
|
CEntity* entityThatSetUsOnFire = dstDmg->m_pEntityThatSetUsOnFire.Get();
|
|
|
|
if (entityThatSetUsOnFire->GetIsTypePed())
|
|
{
|
|
pCulPrit = static_cast< CPed* >(entityThatSetUsOnFire);
|
|
}
|
|
else if (entityThatSetUsOnFire->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* vehicle = static_cast< CVehicle* >(entityThatSetUsOnFire);
|
|
|
|
pCulPrit = vehicle->GetDriver();
|
|
|
|
if (!pCulPrit && vehicle->GetSeatManager())
|
|
{
|
|
pCulPrit = vehicle->GetSeatManager()->GetLastPedInSeat(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_vfxVehicle.ProcessPetrolTankDamage(dstDmg->m_pParent, pCulPrit);
|
|
}
|
|
|
|
const float srcEngineHealth = srcVehicle->m_Transmission.GetEngineHealth();
|
|
dstVehicle->m_Transmission.SetEngineHealth(srcEngineHealth);
|
|
|
|
// Wheels & Tires
|
|
int numWheels = Min(dstVehicle->GetNumWheels(),srcVehicle->GetNumWheels());
|
|
for(int i=0;i<numWheels;i++)
|
|
{
|
|
const CWheel *srcWheel = srcVehicle->GetWheel(i);
|
|
CWheel *dstWheel = dstVehicle->GetWheel(i);
|
|
|
|
const float srcSuspensionHealth = srcWheel->GetSuspensionHealth();
|
|
const float srcTyreHealth = srcWheel->GetTyreHealth();
|
|
dstWheel->SetSuspensionHealth(srcSuspensionHealth);
|
|
dstWheel->SetTyreHealth(srcTyreHealth);
|
|
}
|
|
|
|
CCustomShaderEffectVehicle* srcCSE = static_cast<CCustomShaderEffectVehicle*>(srcVehicle->GetDrawHandler().GetShaderEffect());
|
|
Assert(srcCSE);
|
|
bool srcTyreDeform = srcCSE->GetEnableTyreDeform();
|
|
|
|
CCustomShaderEffectVehicle* dstCSE = static_cast<CCustomShaderEffectVehicle*>(dstVehicle->GetDrawHandler().GetShaderEffect());
|
|
Assert(dstCSE);
|
|
bool prevDstTyreDeform = dstCSE->GetEnableTyreDeform();
|
|
dstCSE->SetEnableTyreDeform(srcTyreDeform);
|
|
|
|
if( srcTyreDeform != prevDstTyreDeform)
|
|
{
|
|
dstVehicle->ActivatePhysics();
|
|
}
|
|
|
|
// Deformation
|
|
CVehicleDeformation * srcDeformation = srcDmg->GetDeformation();
|
|
CVehicleDeformation * dstDeformation = dstDmg->GetDeformation();
|
|
if( srcDeformation->HasDamageTexture() )
|
|
{
|
|
if( !dstDeformation->HasDamageTexture() )
|
|
dstDeformation->DamageTextureAllocate();
|
|
|
|
// Allocation can fail...
|
|
if( dstDeformation->HasDamageTexture() )
|
|
{
|
|
const void *srcPtr = srcDeformation->LockDamageTexture(grcsRead);
|
|
void *dstPtr = dstDeformation->LockDamageTexture(grcsWrite);
|
|
|
|
if( dstPtr && srcPtr )
|
|
{
|
|
sysMemCpy(dstPtr,srcPtr, GTA_DAMAGE_TEXTURE_BYTES);
|
|
|
|
srcDeformation->ApplyDamageToBound(dstPtr);
|
|
dstVehicle->SetupWheels(dstPtr);
|
|
|
|
dstCSE->SetEnableDamage(true);
|
|
dstCSE->SetBoundRadius(srcCSE->GetBoundRadius());
|
|
}
|
|
|
|
|
|
if( dstPtr )
|
|
dstDeformation->UnLockDamageTexture();
|
|
|
|
if( srcPtr )
|
|
srcDeformation->UnLockDamageTexture();
|
|
}
|
|
}
|
|
|
|
// Broken off parts
|
|
fragInst* dstFragInst = dstVehicle->GetVehicleFragInst();
|
|
fragInst* srcFragInst = srcVehicle->GetVehicleFragInst();
|
|
|
|
if( dstFragInst && srcFragInst )
|
|
{
|
|
// doors
|
|
for(int i=0; i<srcVehicle->GetNumDoors(); i++)
|
|
{
|
|
if(srcVehicle->GetDoor(i)->GetFragChild() > 0 && !srcVehicle->GetDoor(i)->GetIsIntact(srcVehicle) )
|
|
{
|
|
dstFragInst->DeleteAbove(dstVehicle->GetDoor(i)->GetFragChild());
|
|
}
|
|
}
|
|
|
|
// wheels
|
|
for(int i=0; i<srcVehicle->GetNumWheels(); i++)
|
|
{
|
|
CWheel* wheel = srcVehicle->GetWheel(i);
|
|
int nFragChild = wheel->GetFragChild();
|
|
if(nFragChild > -1)
|
|
{
|
|
fragPhysicsLOD* srcTypePhysics = srcFragInst->GetTypePhysics();
|
|
fragTypeChild* srcChild = srcTypePhysics->GetAllChildren()[nFragChild];
|
|
if(srcChild->GetOwnerGroupPointerIndex() > 0 && srcFragInst->GetChildBroken(nFragChild))
|
|
{
|
|
dstVehicle->PartHasBrokenOff(wheel->GetHierarchyId());
|
|
dstFragInst->DeleteAbove(nFragChild);
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
}
|
|
}
|
|
}
|
|
|
|
// extras and misc
|
|
|
|
// go thru all the fragment children (there's more of them)
|
|
for(int nChild=0; nChild<srcFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
|
|
{
|
|
if(!srcFragInst->GetChildBroken(nChild))
|
|
continue;
|
|
|
|
int boneIndex = srcFragInst->GetType()->GetBoneIndexFromID(srcFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID());
|
|
|
|
// go through each of the possible extras
|
|
for(int nExtra=VEH_EXTRA_1; nExtra<=VEH_LAST_EXTRA; nExtra++)
|
|
{
|
|
// if the bone index of this child matches the bone index of the extra that's turned off then delete this component
|
|
if(srcVehicle->GetBoneIndex((eHierarchyId)nExtra) == boneIndex)
|
|
{
|
|
dstVehicle->PartHasBrokenOff((eHierarchyId)nExtra);
|
|
dstFragInst->DeleteAbove(nChild);
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// misc components
|
|
int nNumBreakableParts = NUM_MISC_CAR_BREAK_PARTS;
|
|
eHierarchyId *pBreakableParts = aMiscCarBreakParts;
|
|
if(srcVehicle->InheritsFromTrailer() && (static_cast<CTrailer*>(srcVehicle))->HasBreakableExtras())
|
|
{
|
|
nNumBreakableParts = NUM_EXTRA_TRAILER_BREAK_PARTS;
|
|
pBreakableParts = aExtraTrailerBreakParts;
|
|
}
|
|
|
|
for(int nMisc=0; nMisc<nNumBreakableParts; nMisc++)
|
|
{
|
|
// if the bone index of this child matches the bone index of the extra that's turned off then delete this component
|
|
if(srcVehicle->GetBoneIndex((eHierarchyId)pBreakableParts[nMisc]) == boneIndex)
|
|
{
|
|
dstVehicle->PartHasBrokenOff((eHierarchyId)pBreakableParts[nMisc]);
|
|
dstFragInst->DeleteAbove(nChild);
|
|
CVehicle::ClearLastBrokenOffPart();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if __DEV
|
|
|
|
const eHierarchyId* CVehicleDamage::GetGlassBoneHierarchyIds()
|
|
{
|
|
return aGlassBones;
|
|
}
|
|
|
|
int CVehicleDamage::GetGlassBoneHierarchyIdCount()
|
|
{
|
|
CompileTimeAssert(NELEM(aGlassBones) == NUM_GLASS_BONES);
|
|
return NELEM(aGlassBones);
|
|
}
|
|
|
|
#endif // __DEV
|
|
|
|
void CVehicleDamage::DamageVehicleByDriver(const Vector3& impulseLocal, const Vector3& damagePosLocal, float damageAmount, eDamageType nDamageType, CVehicle* vehicle, bool fullDamage)
|
|
{
|
|
if (vehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CPed* driver = vehicle->GetDriver();
|
|
CEntity* pInflictor = static_cast<CEntity*>(driver);
|
|
|
|
DamageVehicle(pInflictor, impulseLocal, damagePosLocal, damageAmount, nDamageType, vehicle, fullDamage);
|
|
}
|
|
|
|
void CVehicleDamage::DamageVehicle(CEntity* pInflictor, const Vector3& impulseLocal, const Vector3& damagePosLocal, float damageAmount, eDamageType nDamageType, CVehicle* vehicle, bool fullDamage)
|
|
{
|
|
if (vehicle == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 damagePosWorldSpace = vehicle->TransformIntoWorldSpace(damagePosLocal);
|
|
|
|
Vector3 normalizedImpulse = impulseLocal;
|
|
normalizedImpulse.NormalizeSafe();
|
|
|
|
Vector3 modulatedImpulse = normalizedImpulse * damageAmount;
|
|
Vector3 impulseWorldSpace = VEC3V_TO_VECTOR3(vehicle->GetTransform().Transform3x3(VECTOR3_TO_VEC3V(modulatedImpulse)));
|
|
|
|
// Deformation
|
|
vehicle->GetVehicleDamage()->GetDeformation()->ApplyCollisionImpact(impulseWorldSpace, damagePosWorldSpace, pInflictor, fullDamage);
|
|
|
|
// Damage
|
|
vehicle->GetVehicleDamage()->ApplyDamage(pInflictor, nDamageType, 0, damageAmount, damagePosWorldSpace, normalizedImpulse, modulatedImpulse, 0);
|
|
}
|
|
|
|
void CVehicleDamage::AddVehicleExplosionDeformations(CVehicle* vehicle, CEntity* pCulprit, eDamageType nDamageType, int numberOfImpulses, float explosionDamageAmount)
|
|
{
|
|
if ((numberOfImpulses < 1) || (explosionDamageAmount < 0.0f) || (vehicle == NULL) || (vehicle->pHandling == NULL) || !CVehicleDeformation::ms_bEnableExtraBoneDeformations)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if VEHICLE_DEFORMATION_PROPORTIONAL
|
|
float sizeMultiplier = 0.0f;
|
|
float multiplier = vehicle->GetVehicleDamage()->GetDeformation()->GetDamageMultiplier(&sizeMultiplier);
|
|
explosionDamageAmount *= multiplier;
|
|
#endif
|
|
|
|
HeadOnCollision(vehicle, explosionDamageAmount, true);
|
|
RearEndCollision(vehicle, explosionDamageAmount, true);
|
|
numberOfImpulses -= 2; // always smash the front and rear of the vehicle
|
|
|
|
if (numberOfImpulses <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float fMass = vehicle->GetMass();
|
|
const float fMassIndependenceFactor = (fMass * InvertSafe(vehicle->pHandling->m_fDeformationDamageMult,0.0f));
|
|
const float fDeformationMag = explosionDamageAmount * fMassIndependenceFactor;
|
|
|
|
fragInstGta* fragInstPtr = vehicle->GetVehicleFragInst();
|
|
fragPhysicsLOD* fragPhysicsLODPtr = fragInstPtr ? fragInstPtr->GetTypePhysics() : NULL;
|
|
CVehicleModelInfo* modelInfo = (CVehicleModelInfo *)vehicle->GetBaseModelInfo();
|
|
|
|
if (!fragPhysicsLODPtr || !modelInfo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static float randomAngleRange = 80.0f;
|
|
|
|
int extraBoneCount = 0;
|
|
const eHierarchyId* extraBones = vehicle->GetExtraBonesToDeform(extraBoneCount);
|
|
|
|
if ((extraBones == NULL) || (extraBoneCount == 0))
|
|
{
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
vehicle->GetVehicleDamage()->GetDeformation()->ApplyDeformations(false);
|
|
#else
|
|
vehicle->GetVehicleDamage()->GetDeformation()->ApplyDeformations(true);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
extraBoneCount = Min(numberOfImpulses, extraBoneCount);
|
|
|
|
for (int i = 0; i < extraBoneCount; ++i)
|
|
{
|
|
eHierarchyId componentId = extraBones[i];
|
|
int iBoneId = modelInfo->GetBoneIndex(componentId);
|
|
|
|
if (iBoneId !=-1 )
|
|
{
|
|
int nChildIndex = fragInstPtr->GetComponentFromBoneIndex(iBoneId);
|
|
|
|
if (nChildIndex == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
phBound* boundPtr = fragPhysicsLODPtr->GetCompositeBounds()->GetBound(nChildIndex);
|
|
|
|
if ((boundPtr == NULL) || (boundPtr->GetType() != phBound::GEOMETRY))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
phBoundGeometry* pOrigBoundGeom = static_cast<phBoundGeometry*>(boundPtr);
|
|
|
|
int numVertices = pOrigBoundGeom->GetNumVertices();
|
|
int vertexIndexToTarget = (int)fwRandom::GetRandomNumberInRange(0, numVertices);
|
|
Vec3V vertPos = pOrigBoundGeom->GetVertex(vertexIndexToTarget);
|
|
Vector3 damagePosLocal = VEC3V_TO_VECTOR3(vertPos);
|
|
|
|
const Matrix34 boundMatrix = MAT34V_TO_MATRIX34(fragPhysicsLODPtr->GetCompositeBounds()->GetCurrentMatrix(nChildIndex));
|
|
boundMatrix.Transform(damagePosLocal);
|
|
|
|
Vector3 boundingBoxMin = VEC3V_TO_VECTOR3(pOrigBoundGeom->GetBoundingBoxMin());
|
|
Vector3 boundingBoxMax = VEC3V_TO_VECTOR3(pOrigBoundGeom->GetBoundingBoxMax());
|
|
Vector3 boundingCornerVec = boundingBoxMax - boundingBoxMin;
|
|
|
|
bool isYMajorAxis = (boundingCornerVec.y >= boundingCornerVec.x);
|
|
|
|
float randomAlpha = fwRandom::GetRandomNumberInRange(-randomAngleRange, randomAngleRange) / 360.0f;
|
|
float randomTheta = fwRandom::GetRandomNumberInRange(-randomAngleRange, randomAngleRange) / 360.0f;
|
|
|
|
float damagePerImpulse = fDeformationMag;
|
|
|
|
#if VEHICLE_DEFORMATION_PROPORTIONAL
|
|
// Make it depend on the relative size of the vehicle, 0 = tailgater, 1 = titan
|
|
float outwardDamageChance = VEHICLE_OUTWARD_EXPLOSION_CHANCE;
|
|
outwardDamageChance *= sizeMultiplier;
|
|
|
|
bool bOutwardDamage = fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < outwardDamageChance;
|
|
Vector3 impulseLocal = bOutwardDamage ? damagePosLocal : -damagePosLocal;
|
|
|
|
if (bOutwardDamage)
|
|
{
|
|
damagePerImpulse /= 2.0f;
|
|
}
|
|
#else
|
|
Vector3 impulseLocal = -damagePosLocal;
|
|
#endif
|
|
|
|
if (isYMajorAxis)
|
|
{
|
|
//Damage the vehicle along its main axes, not pointing towards the center
|
|
impulseLocal.y = 0.0f;
|
|
impulseLocal.z *= 0.2f;
|
|
}
|
|
else
|
|
{
|
|
impulseLocal.x = 0.0f;
|
|
impulseLocal.y *= 0.4f; //probably wings, you mostly want to hit them downward or upward
|
|
}
|
|
|
|
impulseLocal.NormalizeSafe();
|
|
impulseLocal.RotateAboutAxis(randomAlpha, isYMajorAxis ? 'y' : 'x');
|
|
impulseLocal.RotateZ(randomTheta);
|
|
|
|
CVehicleDamage::DamageVehicle(pCulprit, impulseLocal, damagePosLocal, damagePerImpulse, nDamageType, vehicle, true);
|
|
}
|
|
}
|
|
|
|
#if GPU_DAMAGE_WRITE_ENABLED
|
|
vehicle->GetVehicleDamage()->GetDeformation()->ApplyDeformations(false);
|
|
#else
|
|
vehicle->GetVehicleDamage()->GetDeformation()->ApplyDeformations(true);
|
|
#endif
|
|
}
|
|
|
|
float CVehicleDamage::GetVehicleHealthPercentage(const CVehicle* pVehicle, float maxEngineHealth, float maxPetrolTankHealth, float maxHealth, float maxMainRotorHealth, float maxRearRotorHealth, float maxTailBoomHealth ) const
|
|
{
|
|
//For vehicles that explode on contact, the body health is the most important aspect as as soon as that reaches 0 they explode.
|
|
if( pVehicle->ShouldExplodeOnContact() &&
|
|
!pVehicle->DisableExplodeOnContact())
|
|
{
|
|
float fEntityHealthPercent = Max(0.0f, pVehicle->GetHealth() / pVehicle->GetMaxHealth());
|
|
float fEngineHealthPercent = Max(0.0f, pVehicle->GetVehicleDamage()->GetEngineHealth() / maxEngineHealth);
|
|
float fGasTankHealthPercent = Max(0.0f, pVehicle->GetVehicleDamage()->GetPetrolTankHealth() / maxPetrolTankHealth);
|
|
|
|
float fBodyHealth = (pVehicle->GetVehicleDamage()->GetBodyHealth() / maxHealth) * 100.0f;
|
|
static dev_float sfBodyHealthProportion = 0.9f;
|
|
|
|
float fHealth = (((fEntityHealthPercent + fEngineHealthPercent + fGasTankHealthPercent) / 3.0f) * (1.0f-sfBodyHealthProportion)) + fBodyHealth * sfBodyHealthProportion;
|
|
|
|
if(fHealth > 100)
|
|
fHealth = 100;
|
|
|
|
return fHealth;
|
|
}
|
|
else
|
|
{
|
|
float fHealthPercent = pVehicle->GetHealth() / pVehicle->GetMaxHealth();
|
|
float fEngineHealthPercent = pVehicle->GetVehicleDamage()->GetEngineHealth() / maxEngineHealth;
|
|
float fGasTankHealthPercent = pVehicle->GetVehicleDamage()->GetPetrolTankHealth() / maxPetrolTankHealth;
|
|
|
|
if( pVehicle->InheritsFromHeli() )
|
|
{
|
|
const CHeli* pHeli = static_cast< const CHeli* >( pVehicle );
|
|
|
|
float fRotorHealth = pHeli->GetMainRotorHealth() / maxMainRotorHealth;
|
|
|
|
if( fEngineHealthPercent > fRotorHealth )
|
|
{
|
|
fEngineHealthPercent = fRotorHealth;
|
|
}
|
|
|
|
fRotorHealth = pHeli->GetRearRotorHealth() / maxRearRotorHealth;
|
|
if( fEngineHealthPercent > fRotorHealth )
|
|
{
|
|
fEngineHealthPercent = fRotorHealth;
|
|
}
|
|
|
|
fRotorHealth = pHeli->GetTailBoomHealth() / maxTailBoomHealth;
|
|
if( fEngineHealthPercent > fRotorHealth )
|
|
{
|
|
fEngineHealthPercent = fRotorHealth;
|
|
}
|
|
}
|
|
|
|
if(fEngineHealthPercent < 0)
|
|
fEngineHealthPercent = 0;
|
|
|
|
if(fGasTankHealthPercent < 0)
|
|
fGasTankHealthPercent = 0;
|
|
|
|
// Save whichever value out of the engine health and the petrol tank health is the lowest. Either one of these reaching the minimum will affect driveability.
|
|
float fLowestDriveabilityPercent = fEngineHealthPercent < fGasTankHealthPercent ? fEngineHealthPercent : fGasTankHealthPercent;
|
|
|
|
// Calculate the contribution of the aesthetic, non-impactful body damage and the damage to elements that actually affect the driveability.
|
|
// The idea is to allow the body health to contribute to the overall vehicle health, while ensuring that the values that affect the vehicle's driveability ultimately dictate the health level.
|
|
// So long as the body health is larger than the either engine or petrol tank health, it will contribute to a maximum of 50% of the overall vehicle health.
|
|
// If the engine or petrol tank health drop below the body health, the contribution of the body health to the overall vehicle health is proportionally diminished such that
|
|
// when either the engine health or the petrol tank health is zero, the overall vehicle health is zero.
|
|
float fHealthContribution = 0.5f;
|
|
float fDriveabilityContribution = 0.5f;
|
|
|
|
if(fLowestDriveabilityPercent < fHealthPercent)
|
|
{
|
|
fHealthContribution = 0.5f - (0.5f * ((fHealthPercent - fLowestDriveabilityPercent) / fHealthPercent));
|
|
fDriveabilityContribution = 1.0f - fHealthContribution;
|
|
}
|
|
|
|
float fHealth = ((fHealthPercent * fHealthContribution) + (fLowestDriveabilityPercent * fDriveabilityContribution)) * 100.0f;
|
|
|
|
if(fHealth > 100)
|
|
fHealth = 100;
|
|
|
|
return fHealth;
|
|
}
|
|
}
|
|
|
|
|
|
float CVehicleDamage::GetDefaultBodyHealthMax(const CVehicle *pParentVehicle) const
|
|
{
|
|
CVehicleModelInfo* pModelInfo = pParentVehicle->GetVehicleModelInfo();
|
|
return (pModelInfo ? pModelInfo->GetDefaultBodyHealth() : Max(m_pParent->GetMaxHealth(), VEH_DAMAGE_HEALTH_STD));
|
|
}
|
|
|
|
void CVehicleDamage::SetDamageScales( float bodyDamageScale, float petrolTankDamageScale, float engineDamageScale )
|
|
{
|
|
m_fBodyDamageScale = bodyDamageScale;
|
|
m_fPetrolTankDamageScale = petrolTankDamageScale;
|
|
|
|
m_pParent->m_Transmission.SetEngineDamageScale( engineDamageScale );
|
|
|
|
if( m_pParent->GetSecondTransmission() )
|
|
{
|
|
m_pParent->GetSecondTransmission()->SetEngineDamageScale( engineDamageScale );
|
|
}
|
|
}
|
|
|
|
|
|
void CVehicleDamage::SetHeliDamageScales( float mainRotorDamageScale, float rearRotorDamageScale, float tailBoomDamageScale )
|
|
{
|
|
if( m_pParent->InheritsFromHeli() )
|
|
{
|
|
CHeli* pHeli = static_cast< CHeli* >( m_pParent );
|
|
pHeli->SetMainRotorDamageScale( mainRotorDamageScale );
|
|
pHeli->SetRearRotorDamageScale( rearRotorDamageScale );
|
|
pHeli->SetTailBoomDamageScale( tailBoomDamageScale );
|
|
}
|
|
}
|
|
|
|
void CVehicleDamage::ApplyDamageToBreakablePanels( eDamageType nDamageType, float fDamage, const Vector3& vecPosLocal, int nComponent )
|
|
{
|
|
static dev_float sfBodyHealthMaxForBreakablePanel = 750.0f;
|
|
static dev_float sfMinDamageToBreakPanel = 30.0f;
|
|
static dev_float sfMaxDamageToBreakPanel = 150.0f;
|
|
|
|
if( !m_pParent || !m_pParent->GetVehicleFragInst() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if body health is less than some threshold and fDamage is greater than some other
|
|
if( m_fBodyHealth < sfBodyHealthMaxForBreakablePanel &&
|
|
fDamage > sfMinDamageToBreakPanel )
|
|
{
|
|
static dev_float sfBreakOffPanelProb = 0.4f;
|
|
|
|
if( ( nDamageType == DAMAGE_TYPE_EXPLOSIVE ||
|
|
nDamageType == DAMAGE_TYPE_BULLET ||
|
|
nDamageType == DAMAGE_TYPE_COLLISION ) &&
|
|
( fDamage > sfMaxDamageToBreakPanel ||
|
|
fwRandom::GetRandomNumberInRange(0.0f, 1.0f) <= sfBreakOffPanelProb ) )
|
|
{
|
|
eHierarchyId panelToBreakOff = VEH_INVALID_ID;
|
|
|
|
for( int nPanel = VEH_FIRST_BREAKABLE_PANEL; nPanel <= VEH_LAST_BREAKABLE_PANEL; nPanel++ )
|
|
{
|
|
if( HasBoneCollidedWithComponent( (eHierarchyId)nPanel, nComponent ) )
|
|
{
|
|
// store the panel to break off
|
|
panelToBreakOff = (eHierarchyId)nPanel;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( panelToBreakOff == VEH_INVALID_ID &&
|
|
nDamageType == DAMAGE_TYPE_COLLISION )
|
|
{
|
|
float closestDistance = FLT_MAX;
|
|
eHierarchyId closestPanel = VEH_INVALID_ID;
|
|
|
|
// find the closest panel to the impact and if it is close enough break it off
|
|
for( int nPanel = VEH_FIRST_BREAKABLE_PANEL; nPanel <= VEH_LAST_BREAKABLE_PANEL; nPanel++ )
|
|
{
|
|
if( m_pParent && m_pParent->GetVehicleFragInst() )
|
|
{
|
|
int iBoneIndex = m_pParent->GetBoneIndex( (eHierarchyId)nPanel );
|
|
|
|
if( iBoneIndex != -1 )
|
|
{
|
|
Vector3 bonePos = RCC_VECTOR3( m_pParent->GetSkeleton()->GetSkeletonData().GetBoneData( iBoneIndex )->GetDefaultTranslation() );
|
|
float distance = ( bonePos - vecPosLocal ).Mag2();
|
|
|
|
if( distance < closestDistance )
|
|
{
|
|
closestDistance = distance;
|
|
closestPanel = (eHierarchyId)nPanel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static dev_float sfDistanceTollerance = 0.3f * 0.3f;
|
|
|
|
if( closestPanel != VEH_INVALID_ID &&
|
|
closestDistance < sfDistanceTollerance )
|
|
{
|
|
panelToBreakOff = closestPanel;
|
|
}
|
|
}
|
|
|
|
if( panelToBreakOff != VEH_INVALID_ID )
|
|
{
|
|
// break off panel
|
|
BreakOffPart( panelToBreakOff, nComponent, m_pParent->GetVehicleFragInst(), m_pParent->GetVehicleModelInfo()->GetVehicleExplosionInfo(),
|
|
!m_pParent->CarPartsCanBreakOff(), 0, 0.1f );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|