Files
GTASource/game/Vehicles/Automobile.cpp

10304 lines
360 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
// Title : Automobile.cpp
// Author : Richard Jobling/William Henderson
// Started : 25/08/99
//
//
//
// Rage headers
#include "crskeleton/skeleton.h"
#include "crskeleton/skeletondata.h"
#include "grcore/texture.h"
#include "pharticulated/articulatedcollider.h"
#include "physics/sleep.h"
#include "physics/constraintcylindrical.h"
#include "physics/constraintdistance.h"
#include "physics/intersection.h"
#include "profile/profiler.h"
#include "profile/timebars.h"
#include "rmptfx/ptxmanager.h"
#include "phBound/support.h"
#include "profile/cputrace.h"
#include "phbound/boundcomposite.h"
#include "math/vecmath.h"
// Framework headers
#include "fwanimation/animmanager.h"
#include "grcore/debugdraw.h"
#include "fwnet/netobject.h"
#include "fwmaths/angle.h"
#include "fwscene/stores/staticboundsstore.h"
// Game headers
#include "camera/CamInterface.h"
#include "camera/cinematic/CinematicDirector.h"
#include "camera/debug/DebugDirector.h"
#include "camera/helpers/Frame.h"
#include "vehicleAi/vehicleintelligence.h"
#include "vehicleAi/Task/TaskVehicleMissionBase.h"
#include "control/gamelogic.h"
#include "control/garages.h"
#include "control/record.h"
#include "control/remote.h"
#include "control/replay/Replay.h"
#include "debug/debugglobals.h"
#include "debug/debugscene.h"
#include "Event/EventDamage.h"
#include "Event/EventShocking.h"
#include "Event/ShockingEvents.h"
#include "game/modelIndices.h"
#include "Stats/StatsMgr.h"
#include "game/zones.h"
#include "modelInfo/modelInfo.h"
#include "modelInfo/vehicleModelInfo.h"
#include "modelinfo/VehicleModelInfoExtensions.h"
#include "network/Events/NetworkEventTypes.h"
#include "peds/pedDebugVisualiser.h"
#include "peds/pedIntelligence.h"
#include "peds/Ped.h"
#include "peds/popcycle.h"
#include "physics/gtaArchetype.h"
#include "physics/gtaInst.h"
#include "physics/physics.h"
#include "physics/gtaMaterialManager.h"
#include "physics/WorldProbe/worldprobe.h"
#include "pickups/PickupManager.h"
#include "renderer/lights/AsyncLightOcclusionMgr.h"
#include "renderer/lights/lights.h"
#include "renderer/renderer.h"
#include "renderer/zoneCull.h"
#include "script/script.h"
#include "shaders/CustomShaderEffectProp.h"
#include "streaming/streaming.h"
#include "system/pad.h"
#include "Task/Vehicle/TaskCar.h"
#include "Task/Vehicle/TaskCarAccessories.h"
#include "Task/Vehicle/TaskExitVehicle.h"
#include "Task/Vehicle/TaskInVehicle.h"
#include "Task/Movement/TaskParachuteObject.h"
#include "Task/Movement/TaskParachute.h"
#include "TimeCycle/TimeCycle.h"
#include "timecycle/TimeCycleConfig.h"
#include "weapons/explosion.h"
#include "vehicleAi/task/TaskVehicleGoToAutomobile.h"
#include "vehicles/Metadata/VehicleSeatInfo.h"
#include "vehicles/automobile.h"
#include "vehicles/AmphibiousAutomobile.h"
#include "vehicles/bike.h"
#include "vehicles/handlingMgr.h"
#include "vehicles/heli.h"
#include "vehicles/planes.h"
#include "vehicles/trailer.h"
#include "vehicles/Submarine.h"
#include "vehicles/wheel.h"
#include "vehicles/vehiclegadgets.h"
#include "vehicles/vehicleFactory.h"
#include "vehicles/vehiclepopulation.h"
#include "vehicles/virtualroad.h"
#include "Vfx/Misc/Coronas.h"
#include "Vfx/Decals/DecalManager.h"
#include "Vfx/Misc/Fire.h"
#include "vfx/Systems/VfxVehicle.h"
#include "vfx/Systems/VfxWater.h"
#include "network/NetworkInterface.h"
#include "weapons/weapondamage.h"
#include "scene/world/GameWorldHeightMap.h"
#include "Stats/StatsMgr.h"
#include "Stats/StatsInterface.h"
#include "network/Objects/Entities/NetObjPhysical.h"
#include "event/EventNetwork.h"
#include "vehicleAi/task/TaskVehicleAnimation.h"
#include "ai/debug/system/AIDebugLogManager.h"
#include "frontend/PauseMenu.h"
#include "Frontend/NewHud.h"
AI_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
ENTITY_OPTIMISATIONS()
VEHICLE_OPTIMISATIONS()
#define CAR_COMPONENT_REMOVE_LIFESPAN (20000)
//#define IGNORE_HIERARCHY
// BILL
extern CAutomobile* gpCar;
//extern tTerrain G_nTerrainType;
#define WHEEL_LOD
//#define TEST_DAMAGE
#define NEW_MODEL_HIERARCHY
float CAR_BALANCE_MULT = 0.08f;
#define JIMMY_TRANSFORM_TOTAL_TIME (250.0f)
//#define CAR_RAILTRACK_SURFACETYPE (SURFACE_TYPE_GRAVEL)
#define CAR_RAILTRACK_SURFACETYPE (SURFACE_TYPE_RAILTRACK)
static dev_float AUTOMOBILE_SINK_TIME = 5.0f;
static dev_float TRAILER_SINK_TIME = 0.5f;
static dev_float AUTOMOBILE_SINK_STEP = 0.002f; // How much is the fForceMult decreased by each frame once car starts sinking
static dev_float AUTOMOBILE_SINK_FORCE_MULT_MULTIPLIER = 0.5f; // Buoyancy forceMult is decreased by the sink step until it reaches this fraction of the original
static dev_float AUTOMOBILE_SINK_CRASH_EVENT_RANGE = 35.0f; // Magic number to force the perception range for peds reacting to a sinking car.
static dev_s32 AUTOMOBILE_SINK_EVENT_SUBMERGE_PERCENT = 20; // Magic number for the car to count as submerged for event purposes.
static dev_float PLANE_ENGINE_OFF_TIME = 1.0f;
atFixedArray<CAutomobile::PlaceOnRoadAsyncData, MAX_ASYNC_PLACE_ON_ROAD_ENTRIES> CAutomobile::ms_placeOnRoadArray;
float CAutomobile::m_sfPassengerMassMult = 0.05f;
bank_float CQuadBike::ms_fQuadBikeSelfRightingTorqueScale = 15.0f;
bank_float CQuadBike::ms_fQuadBikeAntiRollOverTorqueScale = -35.0f;
bool CAutomobile::ms_bUseAsyncWheelProbes = true;
float CAutomobile::ms_fBumpSeverityMulti = 1.0f;
BANK_ONLY(bool CAutomobile::ms_bDrawWheelProbeResults = false;)
#define ASBO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.07f))
BANK_SWITCH_NT(static, static const) Vec3V Asbo_AmbientVolume_Offset = ASBO_AMBIENT_OCCLUDER_OFFSET;
#define ARDENT_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V Ardent_AmbientVolume_Offset = ARDENT_AMBIENT_OCCLUDER_OFFSET;
#define BRIOSO2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.044f))
BANK_SWITCH_NT(static, static const) Vec3V Brioso2_AmbientVolume_Offset = BRIOSO2_AMBIENT_OCCLUDER_OFFSET;
#define BRIOSO3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.060f))
BANK_SWITCH_NT(static, static const) Vec3V Brioso3_AmbientVolume_Offset = BRIOSO3_AMBIENT_OCCLUDER_OFFSET;
#define CHAMPION_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.138f))
BANK_SWITCH_NT(static, static const) Vec3V Champion_AmbientVolume_Offset = CHAMPION_AMBIENT_OCCLUDER_OFFSET;
#define CHEETAH2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.17f))
BANK_SWITCH_NT(static, static const) Vec3V Cheetah2_AmbientVolume_Offset = CHEETAH2_AMBIENT_OCCLUDER_OFFSET;
#define CLIQUE_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.075f))
BANK_SWITCH_NT(static, static const) Vec3V Clique_AmbientVolume_Offset = CLIQUE_AMBIENT_OCCLUDER_OFFSET;
#define COMET4_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.05f))
BANK_SWITCH_NT(static, static const) Vec3V Comet4_AmbientVolume_Offset = COMET4_AMBIENT_OCCLUDER_OFFSET;
#define COMET6_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.15f))
BANK_SWITCH_NT(static, static const) Vec3V Comet6_AmbientVolume_Offset = COMET6_AMBIENT_OCCLUDER_OFFSET;
#define COMET7_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.13f))
BANK_SWITCH_NT(static, static const) Vec3V Comet7_AmbientVolume_Offset = COMET7_AMBIENT_OCCLUDER_OFFSET;
#define COQUETTE4_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.185f))
BANK_SWITCH_NT(static, static const) Vec3V Coquette4_AmbientVolume_Offset = COQUETTE4_AMBIENT_OCCLUDER_OFFSET;
#define CORSITA_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.13f))
BANK_SWITCH_NT(static, static const) Vec3V Corsita_AmbientVolume_Offset = CORSITA_AMBIENT_OCCLUDER_OFFSET;
#define CYCLONE2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.19f))
BANK_SWITCH_NT(static, static const) Vec3V Cyclone2_AmbientVolume_Offset = CYCLONE2_AMBIENT_OCCLUDER_OFFSET;
#define CYPHER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V Cypher_AmbientVolume_Offset = CYPHER_AMBIENT_OCCLUDER_OFFSET;
#define DEVESTE_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.22f))
BANK_SWITCH_NT(static, static const) Vec3V Deveste_AmbientVolume_Offset = DEVESTE_AMBIENT_OCCLUDER_OFFSET;
#define DOMINATOR8_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.1165f))
BANK_SWITCH_NT(static, static const) Vec3V Dominator8_AmbientVolume_Offset = DOMINATOR8_AMBIENT_OCCLUDER_OFFSET;
#define DRAFTER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.075f))
BANK_SWITCH_NT(static, static const) Vec3V Drafter_AmbientVolume_Offset = DRAFTER_AMBIENT_OCCLUDER_OFFSET;
#define DRAUGUR_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.155f))
BANK_SWITCH_NT(static, static const) Vec3V Draugur_AmbientVolume_Offset = DRAUGUR_AMBIENT_OCCLUDER_OFFSET;
#define DUNE4_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.200f))
BANK_SWITCH_NT(static, static const) Vec3V Dune4_AmbientVolume_Offset = DUNE4_AMBIENT_OCCLUDER_OFFSET;
#define DUNE5_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.200f))
BANK_SWITCH_NT(static, static const) Vec3V Dune5_AmbientVolume_Offset = DUNE5_AMBIENT_OCCLUDER_OFFSET;
#define DUKES3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.078f))
BANK_SWITCH_NT(static, static const) Vec3V Dukes3_AmbientVolume_Offset = DUKES3_AMBIENT_OCCLUDER_OFFSET;
#define DYNASTY_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.0125f))
BANK_SWITCH_NT(static, static const) Vec3V Dynasty_AmbientVolume_Offset = DYNASTY_AMBIENT_OCCLUDER_OFFSET;
#define ELLIE_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.003f))
BANK_SWITCH_NT(static, static const) Vec3V Ellie_AmbientVolume_Offset = ELLIE_AMBIENT_OCCLUDER_OFFSET;
#define EUROS_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.095f))
BANK_SWITCH_NT(static, static const) Vec3V Euros_AmbientVolume_Offset = EUROS_AMBIENT_OCCLUDER_OFFSET;
#define FLASHGT_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.15f))
BANK_SWITCH_NT(static, static const) Vec3V FlashGT_AmbientVolume_Offset = FLASHGT_AMBIENT_OCCLUDER_OFFSET;
#define FMJ_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.175f))
BANK_SWITCH_NT(static, static const) Vec3V Fmj_AmbientVolume_Offset = FMJ_AMBIENT_OCCLUDER_OFFSET;
#define FORMULA_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.16f))
BANK_SWITCH_NT(static, static const) Vec3V Formula_AmbientVolume_Offset = FORMULA_AMBIENT_OCCLUDER_OFFSET;
#define FURIA_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.15f))
BANK_SWITCH_NT(static, static const) Vec3V Furia_AmbientVolume_Offset = FURIA_AMBIENT_OCCLUDER_OFFSET;
#define IGNUS_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.185f))
BANK_SWITCH_NT(static, static const) Vec3V Ignus_AmbientVolume_Offset = IGNUS_AMBIENT_OCCLUDER_OFFSET;
#define IGNUS2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.192f))
BANK_SWITCH_NT(static, static const) Vec3V Ignus2_AmbientVolume_Offset = IGNUS2_AMBIENT_OCCLUDER_OFFSET;
#define GB200_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.125f))
BANK_SWITCH_NT(static, static const) Vec3V GB200_AmbientVolume_Offset = GB200_AMBIENT_OCCLUDER_OFFSET;
#define GP1_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.115f))
BANK_SWITCH_NT(static, static const) Vec3V GP1_AmbientVolume_Offset = GP1_AMBIENT_OCCLUDER_OFFSET;
#define GAUNTLET4_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.186f))
BANK_SWITCH_NT(static, static const) Vec3V Gauntlet4_AmbientVolume_Offset = GAUNTLET4_AMBIENT_OCCLUDER_OFFSET;
#define GAUNTLET5_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.1f))
BANK_SWITCH_NT(static, static const) Vec3V Gauntlet5_AmbientVolume_Offset = GAUNTLET5_AMBIENT_OCCLUDER_OFFSET;
#define GROWLER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.14f))
BANK_SWITCH_NT(static, static const) Vec3V Growler_AmbientVolume_Offset = GROWLER_AMBIENT_OCCLUDER_OFFSET;
#define HOTRING_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.17f))
BANK_SWITCH_NT(static, static const) Vec3V Hotring_AmbientVolume_Offset = HOTRING_AMBIENT_OCCLUDER_OFFSET;
#define HUSTLER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.30f))
BANK_SWITCH_NT(static, static const) Vec3V Hustler_AmbientVolume_Offset = HUSTLER_AMBIENT_OCCLUDER_OFFSET;
#define IMORGON_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.07f))
BANK_SWITCH_NT(static, static const) Vec3V Imorgon_AmbientVolume_Offset = IMORGON_AMBIENT_OCCLUDER_OFFSET;
#define INFERNUS2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.075f))
BANK_SWITCH_NT(static, static const) Vec3V Infernus2_AmbientVolume_Offset = INFERNUS2_AMBIENT_OCCLUDER_OFFSET;
#define ISSI3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.003f))
BANK_SWITCH_NT(static, static const) Vec3V Issi3_AmbientVolume_Offset = ISSI3_AMBIENT_OCCLUDER_OFFSET;
#define ISSI7_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.18f))
BANK_SWITCH_NT(static, static const) Vec3V Issi7_AmbientVolume_Offset = ISSI7_AMBIENT_OCCLUDER_OFFSET;
#define ITALIGTO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.11f))
BANK_SWITCH_NT(static, static const) Vec3V Italigto_AmbientVolume_Offset = ITALIGTO_AMBIENT_OCCLUDER_OFFSET;
#define ITALIRSX_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.14f))
BANK_SWITCH_NT(static, static const) Vec3V ItaliRsx_AmbientVolume_Offset = ITALIRSX_AMBIENT_OCCLUDER_OFFSET;
#define JESTER3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.04f))
BANK_SWITCH_NT(static, static const) Vec3V Jester3_AmbientVolume_Offset = JESTER3_AMBIENT_OCCLUDER_OFFSET;
#define JESTER4_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.05f))
BANK_SWITCH_NT(static, static const) Vec3V Jester4_AmbientVolume_Offset = JESTER4_AMBIENT_OCCLUDER_OFFSET;
#define JUBILEE_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.08f))
BANK_SWITCH_NT(static, static const) Vec3V Jubilee_AmbientVolume_Offset = JUBILEE_AMBIENT_OCCLUDER_OFFSET;
#define KOMODA_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Komoda_AmbientVolume_Offset = KOMODA_AMBIENT_OCCLUDER_OFFSET;
#define KRIEGER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Krieger_AmbientVolume_Offset = KRIEGER_AMBIENT_OCCLUDER_OFFSET;
#define MANANA2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.16f))
BANK_SWITCH_NT(static, static const) Vec3V Manana2_AmbientVolume_Offset = MANANA2_AMBIENT_OCCLUDER_OFFSET;
#define MENACER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.025f))
BANK_SWITCH_NT(static, static const) Vec3V Menacer_AmbientVolume_Offset = MENACER_AMBIENT_OCCLUDER_OFFSET;
#define NEO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Neo_AmbientVolume_Offset = NEO_AMBIENT_OCCLUDER_OFFSET;
#define NEON_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.120f))
BANK_SWITCH_NT(static, static const) Vec3V Neon_AmbientVolume_Offset = NEON_AMBIENT_OCCLUDER_OFFSET;
#define NERO2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.200f))
BANK_SWITCH_NT(static, static const) Vec3V Nero2_AmbientVolume_Offset = NERO2_AMBIENT_OCCLUDER_OFFSET;
#define OPENWHEEL1_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V Openwheel1_AmbientVolume_Offset = OPENWHEEL1_AMBIENT_OCCLUDER_OFFSET;
#define OPENWHEEL2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V Openwheel2_AmbientVolume_Offset = OPENWHEEL2_AMBIENT_OCCLUDER_OFFSET;
#define PARAGON_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.19f))
BANK_SWITCH_NT(static, static const) Vec3V Paragon_AmbientVolume_Offset = PARAGON_AMBIENT_OCCLUDER_OFFSET;
#define PENETRATOR_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.200f))
BANK_SWITCH_NT(static, static const) Vec3V Penetrator_AmbientVolume_Offset = PENETRATOR_AMBIENT_OCCLUDER_OFFSET;
#define PENUMBRA2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Penumbra2_AmbientVolume_Offset = PENUMBRA2_AMBIENT_OCCLUDER_OFFSET;
#define PEYOTE3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.198f))
BANK_SWITCH_NT(static, static const) Vec3V Peyote3_AmbientVolume_Offset = PEYOTE3_AMBIENT_OCCLUDER_OFFSET;
#define POLBUFFALO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.22f))
BANK_SWITCH_NT(static, static const) Vec3V Polbuffalo_AmbientVolume_Offset = POLBUFFALO_AMBIENT_OCCLUDER_OFFSET;
#define POLGREENWOOD_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.03f))
BANK_SWITCH_NT(static, static const) Vec3V Polgreenwood_AmbientVolume_Offset = POLGREENWOOD_AMBIENT_OCCLUDER_OFFSET;
#define RAPIDGT3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.05f))
BANK_SWITCH_NT(static, static const) Vec3V RapidGT3_AmbientVolume_Offset = RAPIDGT3_AMBIENT_OCCLUDER_OFFSET;
#define REMUS_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.07f))
BANK_SWITCH_NT(static, static const) Vec3V Remus_AmbientVolume_Offset = REMUS_AMBIENT_OCCLUDER_OFFSET;
#define RT3000_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V RT3000_AmbientVolume_Offset = RT3000_AMBIENT_OCCLUDER_OFFSET;
#define RUSTON_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.110f))
BANK_SWITCH_NT(static, static const) Vec3V Ruston_AmbientVolume_Offset = RUSTON_AMBIENT_OCCLUDER_OFFSET;
#define S80_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V S80_AmbientVolume_Offset = S80_AMBIENT_OCCLUDER_OFFSET;
#define SC1_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.10f))
BANK_SWITCH_NT(static, static const) Vec3V SC1_AmbientVolume_Offset = SC1_AMBIENT_OCCLUDER_OFFSET;
#define SEASPARROW2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.04f))
BANK_SWITCH_NT(static, static const) Vec3V Seasparrow2_AmbientVolume_Offset = SEASPARROW2_AMBIENT_OCCLUDER_OFFSET;
#define SCHLAGEN_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.185f))
BANK_SWITCH_NT(static, static const) Vec3V Schlagen_AmbientVolume_Offset = SCHLAGEN_AMBIENT_OCCLUDER_OFFSET;
#define SM722_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.09f))
BANK_SWITCH_NT(static, static const) Vec3V SM722_AmbientVolume_Offset = SM722_AMBIENT_OCCLUDER_OFFSET;
#define STAFFORD_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.0250f))
BANK_SWITCH_NT(static, static const) Vec3V Stafford_AmbientVolume_Offset = STAFFORD_AMBIENT_OCCLUDER_OFFSET;
#define STROMBERG_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.075f))
BANK_SWITCH_NT(static, static const) Vec3V Stromberg_AmbientVolume_Offset = STROMBERG_AMBIENT_OCCLUDER_OFFSET;
#define SWINGER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.25f))
BANK_SWITCH_NT(static, static const) Vec3V Swinger_AmbientVolume_Offset = SWINGER_AMBIENT_OCCLUDER_OFFSET;
#define SUGOI_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.09f))
BANK_SWITCH_NT(static, static const) Vec3V Sugoi_AmbientVolume_Offset = SUGOI_AMBIENT_OCCLUDER_OFFSET;
#define TAILGATER2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Tailgater2_AmbientVolume_Offset = TAILGATER2_AMBIENT_OCCLUDER_OFFSET;
#define TAMPA3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.015f))
BANK_SWITCH_NT(static, static const) Vec3V Tampa3_AmbientVolume_Offset = TAMPA3_AMBIENT_OCCLUDER_OFFSET;
#define THRAX_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.22f))
BANK_SWITCH_NT(static, static const) Vec3V Thrax_AmbientVolume_Offset = THRAX_AMBIENT_OCCLUDER_OFFSET;
#define TIGON_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.19f))
BANK_SWITCH_NT(static, static const) Vec3V Tigon_AmbientVolume_Offset = TIGON_AMBIENT_OCCLUDER_OFFSET;
#define TOREADOR_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.224f))
BANK_SWITCH_NT(static, static const) Vec3V Toreador_AmbientVolume_Offset = TOREADOR_AMBIENT_OCCLUDER_OFFSET;
#define TORERO2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.145f))
BANK_SWITCH_NT(static, static const) Vec3V Torero2_AmbientVolume_Offset = TORERO2_AMBIENT_OCCLUDER_OFFSET;
#define TYRANT_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.18f))
BANK_SWITCH_NT(static, static const) Vec3V Tyrant_AmbientVolume_Offset = TYRANT_AMBIENT_OCCLUDER_OFFSET;
#define VAGNER_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.18f))
BANK_SWITCH_NT(static, static const) Vec3V Vagner_AmbientVolume_Offset = VAGNER_AMBIENT_OCCLUDER_OFFSET;
#define VAGRANT_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Vagrant_AmbientVolume_Offset = VAGRANT_AMBIENT_OCCLUDER_OFFSET;
#define VERUS_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.045f))
BANK_SWITCH_NT(static, static const) Vec3V Verus_AmbientVolume_Offset = VERUS_AMBIENT_OCCLUDER_OFFSET;
#define VETO2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.07f))
BANK_SWITCH_NT(static, static const) Vec3V Veto2_AmbientVolume_Offset = VETO2_AMBIENT_OCCLUDER_OFFSET;
#define VOODOO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.025f))
BANK_SWITCH_NT(static, static const) Vec3V Voodoo_AmbientVolume_Offset = VOODOO_AMBIENT_OCCLUDER_OFFSET;
#define VSTR_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.10f))
BANK_SWITCH_NT(static, static const) Vec3V Vstr_AmbientVolume_Offset = VSTR_AMBIENT_OCCLUDER_OFFSET;
#define WARRENER2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.05f))
BANK_SWITCH_NT(static, static const) Vec3V Warrener2_AmbientVolume_Offset = WARRENER2_AMBIENT_OCCLUDER_OFFSET;
#define WEEVIL_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.06f))
BANK_SWITCH_NT(static, static const) Vec3V Weevil_AmbientVolume_Offset = WEEVIL_AMBIENT_OCCLUDER_OFFSET;
#define WEEVIL2_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.280f))
BANK_SWITCH_NT(static, static const) Vec3V Weevil2_AmbientVolume_Offset = WEEVIL2_AMBIENT_OCCLUDER_OFFSET;
#define XA21_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.075f))
BANK_SWITCH_NT(static, static const) Vec3V XA21_AmbientVolume_Offset = XA21_AMBIENT_OCCLUDER_OFFSET;
#define YOUGA3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, 0.22f))
BANK_SWITCH_NT(static, static const) Vec3V Youga3_AmbientVolume_Offset = YOUGA3_AMBIENT_OCCLUDER_OFFSET;
#define ZENO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.22f))
BANK_SWITCH_NT(static, static const) Vec3V Zeno_AmbientVolume_Offset = ZENO_AMBIENT_OCCLUDER_OFFSET;
#define ZION3_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.085f))
BANK_SWITCH_NT(static, static const) Vec3V Zion3_AmbientVolume_Offset = ZION3_AMBIENT_OCCLUDER_OFFSET;
#define ZORRUSSO_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.12f))
BANK_SWITCH_NT(static, static const) Vec3V Zorrusso_AmbientVolume_Offset = ZORRUSSO_AMBIENT_OCCLUDER_OFFSET;
#define ZR350_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.11f))
BANK_SWITCH_NT(static, static const) Vec3V ZR350_AmbientVolume_Offset = ZR350_AMBIENT_OCCLUDER_OFFSET;
#define ZR3802_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.14f))
BANK_SWITCH_NT(static, static const) Vec3V ZR3802_AmbientVolume_Offset = ZR3802_AMBIENT_OCCLUDER_OFFSET;
#define ZR3803_AMBIENT_OCCLUDER_OFFSET (Vec3V(0.0f, 0.0f, -0.14f))
BANK_SWITCH_NT(static, static const) Vec3V ZR3803_AmbientVolume_Offset = ZR3803_AMBIENT_OCCLUDER_OFFSET;
//
//
//
CAutomobile::CAutomobile(const eEntityOwnedBy ownedBy, u32 popType, VehicleType vehType) : CVehicle(ownedBy, popType, vehType)
{
m_nAutomobileFlags.bTaxiLight = false;
m_nAutomobileFlags.bWaterTight = false;
m_nAutomobileFlags.bIsBoggedDownInSand = false;
m_nAutomobileFlags.bFireNetworkCannon = false;
m_nAutomobileFlags.bBurnoutModeEnabled = false;
m_nAutomobileFlags.bInBurnout = false;
m_nAutomobileFlags.bReduceGripModeEnabled = false;
m_nAutomobileFlags.bPlayerIsRevvingEngineThisFrame = false;
m_nAutomobileFlags.bDropsMoneyWhenBlownUp = false;
m_nAutomobileFlags.bHydraulicsBounceRaising = false;
m_nAutomobileFlags.bHydraulicsBounceLanding = false;
m_nAutomobileFlags.bHydraulicsJump = false;
m_nAutomobileFlags.bForceUpdateGroundClearance = false;
m_nAutomobileFlags.bWheelieModeEnabled = false;
m_nAutomobileFlags.bInWheelieMode = false;
m_nAutomobileFlags.bInWheelieModeWheelsUp = false;
m_nVehicleFlags.bIsVan = false;
m_nVehicleFlags.bIsBig = false;
m_nVehicleFlags.bIsBus = false;
m_nVehicleFlags.bLowVehicle = false;
m_nVehicleFlags.bUseDeformation = true;
m_nAutoDoorTimer = 0;
m_nAutoDoorStart = 0;
m_fHeightAboveRoad = 0.0f;
m_dummyRotationalConstraint.Reset();
m_dummyStayOnRoadConstraintHandle.Reset();
ResetRealConversionFailedData();
m_fTimeInWater = 0.0f;
for(int i = 0; i < NUM_VEH_CWHEELS_MAX; i++)
{
m_pCarWheels[i] = NULL;
}
m_pWheelProbeResults = NULL;
m_pWheelProbePreviousResults = NULL;
m_fGroundClearance = 0.0f;
m_fLastGroundClearance = 0.0f;
m_fLastGroundClearanceHeightAboveGround = 0.0f;
m_uLastGroundClearanceCheck = 0;
m_nAutomobileFlags.bLastSpaceAvaliableToChangeBounds = true;
m_bIgnoreWorldHeightMapForWheelProbes = false;
m_fFakeRotationY = 0.0f;
m_fHighSpeedRotationY = 0.0f;
m_fHighSpeedDirectionY = 1.0f;
m_fHighSpeedYTime = 0.0f;
m_fBumpSeverityYDecay = 0.1f;
m_fBumpSeverityY = 0.0f;
m_fBumpRateY = 0.0f;
m_fFakeSuspensionLowerAmount = 0.0f;
m_fFakeSuspensionLowerAmountApplied = 0.0f;
m_fBurnoutRotationTime = 0.0f;
m_fHydraulicsUpperBound = 0.0f;
m_fHydraulicsLowerBound = 0.0f;
m_fHydraulicsRate = 0.0f;
m_iTimeHydraulicsModified = 0;
m_fSteeringBias = 0.0f;
m_fSteeringBiasLife = 0.0f;
m_fSteeringBiasScalar = 1.0f;
m_fLastDriveForce = 0.0f;
m_vSuspensionDeformationLF = VEC3_ZERO;
m_vSuspensionDeformationRF = VEC3_ZERO;
m_pParachuteObject = NULL;
m_wasCloneParachuting = false;
m_iParachuteModelIndex = fwModelId::MI_INVALID;
m_nParachuteTintIndex = 0;
m_carParachuteState = CAR_NOT_PARACHUTING;
m_fParachuteStickX = 0.0f;
m_fParachuteStickY = 0.0f;
m_fCloneParachuteStickX = 0.0f;
m_fCloneParachuteStickY = 0.0f;
m_fParachuteHatchOffset = 0.0f;
m_bCanDeployParachute = false;
m_bFinishParachutingRequested = false;
m_bCanPlayerDetachParachute = true;
m_bHasBeenParachuting = false;
}
CAutomobile::~CAutomobile()
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnDestroyOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfDestroyCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
for(int i = 0; i < NUM_VEH_CWHEELS_MAX; i++)
{
if(m_pCarWheels[i])
{
delete m_pCarWheels[i];
m_pCarWheels[i] = NULL;
}
}
delete [] m_pWheelProbeResults;
delete [] m_pWheelProbePreviousResults;
// Remove dummy constraints by switching the automobile back to VDM_REAL mode
ChangeDummyConstraints(VDM_REAL,false);
if(m_pParachuteObject)
{
DetachParachuteFromVehicle();
DestroyParachute();
}
}
void CAutomobile::RemoveDummyInst()
{
RemoveConstraints();
// This will call CPhysical::RemoveConstraints()
CVehicle::RemoveDummyInst();
}
void CAutomobile::SetModelId(fwModelId modelId)
{
CVehicle::SetModelId(modelId);
if (/*m_sAllTaxiLights &&*/ CVehicle::IsTaxiModelId(modelId) && (fwRandom::GetRandomNumberInRange(0.0f, 1.0f)<0.65f) )
{
m_nAutomobileFlags.bTaxiLight = true;
//Resetting the damage as it would be broken when we call CVehicle::SetModelId (which would call InitFragmentsExtra)
//That function breaks all the extra lights. We need to reset the taxi light if the car is a taxi
m_VehicleDamage.SetLightStateImmediate(TAXI_IDX, false);
}
else
{
m_nAutomobileFlags.bTaxiLight = false;
}
// Some cars have their door locked initially
if (GetCarDoorLocks() == CARLOCK_UNLOCKED)
{
if (IsLawEnforcementVehicle())
SetCarDoorLocks(CARLOCK_LOCKED_INITIALLY);
}
m_fOrigBuoyancyForceMult = m_Buoyancy.m_fForceMult;
if (GetModelIndex() == MI_CAR_SECURICAR && !NetworkInterface::IsGameInProgress())
{
m_nAutomobileFlags.bDropsMoneyWhenBlownUp = true;
}
// TODO: MAYBE PUT THIS IN A FLAG SO OTHER VEHICLES CAN HAVE THEM
if( MI_CAR_WASTELANDER2.IsValid() &&
GetModelIndex() == MI_CAR_WASTELANDER2 )
{
SlipperyBoxInitialise();
SlipperyBoxUpdateLimits( 0.01f );
}
}
void CAutomobile::InitWheels()
{
Assertf(m_nNumWheels == 0, "CAutomobile::InitWheels(): Expected 0 wheels, but found %i",m_nNumWheels);
if(m_nNumWheels > 0)
{
return;
}
m_ppWheels = m_pCarWheels;
// initialise wheels
CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo();
bool bSteerFrontWheels = true;
bool bSteerRearWheels = false;
const bool bPowerFrontWheels = pHandling->DriveFrontWheels();
const bool bPowerRearWheels = pHandling->DriveRearWheels();
const bool bPowerMidWheels = pHandling->DriveMidWheels();
const bool bDontRenderWheelSteering = (pHandling->mFlags & MF_DONT_RENDER_STEER) != 0;
if(pHandling->hFlags &HF_STEER_REARWHEELS)
{
bSteerFrontWheels = false;
bSteerRearWheels = true;
}
else if(pHandling->hFlags &(HF_HANDBRAKE_REARWHEELSTEER|HF_STEER_ALL_WHEELS))
{
bSteerRearWheels = true;
}
if(pHandling->hFlags & HF_STEER_NO_WHEELS)
{
bSteerFrontWheels = false;
bSteerRearWheels = false;
}
bool bSteerAllFrontWheels = false;
if( ( pHandling->hFlags & HF_STEER_REARWHEELS ) &&
( pHandling->hFlags & HF_STEER_ALL_WHEELS ) &&
!( pHandling->mFlags & MF_HAS_TRACKS ) )
{
bSteerAllFrontWheels = true;
bSteerFrontWheels = true;
bSteerRearWheels = true;
}
// Set up an array of this struct so we can create the wheels in a nice loop
struct sAutomobileWheelCreationStruct{
eHierarchyId iHierarchyId;
bool bSteer;
bool bPowered;
bool bIsFront;
bool bIsLeft;
bool bIsMiddle;
};
sAutomobileWheelCreationStruct wheelCreationData [NUM_VEH_CWHEELS_MAX] =
{
{VEH_WHEEL_LF, bSteerFrontWheels, bPowerFrontWheels, true, true, false},
{VEH_WHEEL_RF, bSteerFrontWheels, bPowerFrontWheels, true, false, false},
{VEH_WHEEL_LR, bSteerRearWheels, bPowerRearWheels, false, true, false},
{VEH_WHEEL_RR, bSteerRearWheels, bPowerRearWheels, false, false, false},
{VEH_WHEEL_LM1, bSteerRearWheels || bSteerAllFrontWheels, bPowerMidWheels, false, true , true},
{VEH_WHEEL_RM1, bSteerRearWheels || bSteerAllFrontWheels, bPowerMidWheels, false, false, true},
{VEH_WHEEL_LM2, bSteerRearWheels, bPowerMidWheels, false, true, true},
{VEH_WHEEL_RM2, bSteerRearWheels, bPowerMidWheels, false, false, true},
{VEH_WHEEL_LM3, bSteerRearWheels, bPowerMidWheels, false, true, true},
{VEH_WHEEL_RM3, bSteerRearWheels, bPowerMidWheels, false, false, true}
};
// Need to do one loop over all wheel ids to figure out how many wheels exist
// This is so we can inversely scale the suspension force with wheel number
int iNumWheelsToCreate = 0;
for(int iWheelIndex = 0; iWheelIndex < NUM_VEH_CWHEELS_MAX; iWheelIndex++)
{
if(GetBoneIndex(wheelCreationData[iWheelIndex].iHierarchyId) > -1)
{
iNumWheelsToCreate++;
}
}
#if __ASSERT
// All cars should have a LF wheel
Assertf(GetBoneIndex(VEH_WHEEL_LF) > -1 || InheritsFromTrailer(), "Missing WHEEL_LF");
#endif
m_nNumWheels = 0;
for(int iWheelIndex = 0; iWheelIndex < NUM_VEH_CWHEELS_MAX; iWheelIndex++)
{
if(GetBoneIndex(wheelCreationData[iWheelIndex].iHierarchyId) > -1)
{
int nFlags = 0;
if(wheelCreationData[iWheelIndex].bIsLeft)
nFlags |= WCF_LEFTWHEEL;
if(!wheelCreationData[iWheelIndex].bIsFront)
nFlags |= WCF_REARWHEEL;
if(wheelCreationData[iWheelIndex].bPowered)
nFlags |= WCF_POWERED;
if(wheelCreationData[iWheelIndex].bSteer)
nFlags |= WCF_STEER;
if(wheelCreationData[iWheelIndex].bIsFront)
{
if(pHandling->mFlags &MF_AXLE_F_SOLID)
nFlags |= WCF_TILT_SOLID;
else if(pHandling->mFlags &MF_AXLE_F_MCPHERSON)
nFlags |= WCF_TILT_INDEP;
}
else
{
if(pHandling->mFlags &MF_AXLE_R_SOLID)
nFlags |= WCF_TILT_SOLID;
else if(pHandling->mFlags &MF_AXLE_R_MCPHERSON)
nFlags |= WCF_TILT_INDEP;
}
if(GetVehicleType() == VEHICLE_TYPE_QUADBIKE || GetVehicleType() == VEHICLE_TYPE_DRAFT || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE)
{
nFlags |= WCF_QUAD_WHEEL;
}
if(GetVehicleType() == VEHICLE_TYPE_PLANE || (GetVehicleType() == VEHICLE_TYPE_HELI && static_cast< CHeli* >( this )->HasLandingGear()))
{
nFlags |= WCF_HIGH_FRICTION_WHEEL;
nFlags |= WCF_PLANE_WHEEL;
}
if( GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE )
{
nFlags |= WCF_AMPHIBIOUS_WHEEL;
}
if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RENDER_WHEELS_WITH_ZERO_COMPRESSION ))
{
nFlags |= WCF_RENDER_WITH_ZERO_COMPRESSION;
}
if(pHandling->mFlags & MF_HAS_TRACKS &&
( pHandling->hFlags & HF_STEER_ALL_WHEELS ||
!wheelCreationData[iWheelIndex].bSteer ) )
{
nFlags |= WCF_TRACKED_WHEEL;
}
if(pHandling->hFlags & HF_STEER_ALL_WHEELS && pHandling->hFlags & HF_EXT_WHEEL_BOUNDS_COL)
{
nFlags |= WCF_ROTATE_BOUNDS;
}
//get the opposite wheel index
int nOppositeIndex = -1;
if(iWheelIndex % 2 == 0)
nOppositeIndex = iWheelIndex+1;
else
nOppositeIndex = iWheelIndex-1;
//make sure the opposite wheel index is a valid wheel
if(nOppositeIndex >= 0 && nOppositeIndex < NUM_VEH_CWHEELS_MAX)
{
if(GetBoneIndex(wheelCreationData[nOppositeIndex].iHierarchyId) == -1)
nOppositeIndex = -1;
}
else
{
nOppositeIndex = -1;//not a valid index so set to -1
}
const float fRimRadius = pModelInfo->GetRimRadius(wheelCreationData[iWheelIndex].bIsFront);
float fFrontRearSelector = wheelCreationData[iWheelIndex].bIsFront ? 1.0f : -1.0f;
if(wheelCreationData[iWheelIndex].bIsMiddle)
{
eHierarchyId iFront = wheelCreationData[iWheelIndex].bIsLeft ? VEH_WHEEL_LF : VEH_WHEEL_RF;
eHierarchyId iRear = wheelCreationData[iWheelIndex].bIsLeft ? VEH_WHEEL_LR : VEH_WHEEL_RR;
// For a middle wheel to work, must have corresponding front and rear wheels to calculate the front / rear selection
// Check this here and assert meaningfully
// If we are missing a front right / rear right, try and use a left
// Fix for planes which just have one front wheel but mutliple rear wheels.
if(!wheelCreationData[iWheelIndex].bIsLeft)
{
int iFrontBoneIndex = pModelInfo->GetBoneIndex(iFront);
if(iFrontBoneIndex == -1)
{
iFront = VEH_WHEEL_LF;
}
}
#if __ASSERT
Assertf(pModelInfo->GetBoneIndex(iFront) > -1 || InheritsFromTrailer(), "Missing front %s wheel!",wheelCreationData[iWheelIndex].bIsLeft ? "left" : "right");
Assertf(pModelInfo->GetBoneIndex(iRear) > -1 || InheritsFromTrailer(), "Missing rear %s wheel!",wheelCreationData[iWheelIndex].bIsLeft ? "left" : "right");
// No need to verify since CalcFrontRearSelector ihandles this case
#endif
fFrontRearSelector = CalcFrontRearSelector(wheelCreationData[iWheelIndex].iHierarchyId,iFront,iRear);
}
m_ppWheels[m_nNumWheels] = rage_new CWheel();
if (Verifyf(m_ppWheels[m_nNumWheels],"Could not create a wheel! Wheel pool size probably needs increasing"))
{
m_ppWheels[m_nNumWheels]->Init(this, wheelCreationData[iWheelIndex].iHierarchyId, fRimRadius, pHandling->GetSuspensionBias(fFrontRearSelector) / (float)iNumWheelsToCreate, nFlags, s8(nOppositeIndex) );
m_ppWheels[m_nNumWheels]->SetFrontRearSelector(fFrontRearSelector);
if(bDontRenderWheelSteering)
{
m_ppWheels[m_nNumWheels]->GetConfigFlags().SetFlag(WCF_DONT_RENDER_STEER);
}
m_nNumWheels++;
}
}
}
if( m_nNumWheels == 3 &&
( GetVehicleType() == VEHICLE_TYPE_CAR ||
GetVehicleType() == VEHICLE_TYPE_QUADBIKE ) )
{
for( int iWheelIndex = 0; iWheelIndex < m_nNumWheels; iWheelIndex++ )
{
eHierarchyId oppositeWheelId = VEH_WHEEL_LF;
switch( m_ppWheels[ iWheelIndex ]->GetHierarchyId() )
{
case VEH_WHEEL_LF:
{
oppositeWheelId = VEH_WHEEL_RF;
break;
}
case VEH_WHEEL_RF:
{
oppositeWheelId = VEH_WHEEL_LF;
break;
}
case VEH_WHEEL_LR:
{
oppositeWheelId = VEH_WHEEL_RR;
break;
}
case VEH_WHEEL_RR:
{
oppositeWheelId = VEH_WHEEL_LR;
break;
}
default:
break;
}
bool oppositeWheelFound = false;
for( s8 oppositeWheelIndex = 0; oppositeWheelIndex < m_nNumWheels; oppositeWheelIndex++ )
{
if( m_ppWheels[ oppositeWheelIndex ]->GetHierarchyId() == oppositeWheelId )
{
m_ppWheels[ iWheelIndex ]->SetOppositeWheelIndex( oppositeWheelIndex );
oppositeWheelFound = true;
break;
}
}
if( !oppositeWheelFound )
{
m_ppWheels[ iWheelIndex ]->GetConfigFlags().SetFlag( WCF_CENTRE_WHEEL );
}
}
}
if( ( pHandling->mFlags & MF_HAS_TRACKS ) || ( pHandling->hFlags & HF_ARMOURED ) )
{
m_nVehicleFlags.bCanBreakOffWheelsWhenBlowUp = false;
}
// now need to setup wheels (suspension and stuff)
const void *basePtr = NULL;
if(GetVehicleDamage() && GetVehicleDamage()->GetDeformation() && GetVehicleDamage()->GetDeformation()->HasDamageTexture())
{
basePtr = GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead); //Lock the texture once for all wheels
}
SetupWheels(basePtr);
if (basePtr)
{
GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
}
InitCacheWheelsById();
#ifdef CHECK_VEHICLE_SETUP
#if !__NO_OUTPUT
if( m_WheelIndexById[ 0 ] != (u8)( -1 ) && // check that we have at least one valid wheel index before calling these functions or it will assert
GetWheelFromId(VEH_WHEEL_LF) && GetWheelFromId(VEH_WHEEL_LR))
{
float fFrontLength = rage::Abs(GetWheelFromId(VEH_WHEEL_LF)->GetProbeSegment().A.y);
float fRearLength = rage::Abs(GetWheelFromId(VEH_WHEEL_LR)->GetProbeSegment().A.y);
float fWheelbase = fFrontLength + fRearLength;
float fSuspensionBiasChange = (fWheelbase+pHandling->m_vecCentreOfMassOffset.GetYf())/fWheelbase;
modelinfoDisplayf( "%s If Centered Suggested Suspension Bias: %.2f", GetModelName(), fSuspensionBiasChange-0.5f);
modelinfoDisplayf( "%s Suggested Suspension Bias: %.2f Current: %.2f ", GetModelName(), (fRearLength+pHandling->m_vecCentreOfMassOffset.GetYf())/fWheelbase, pHandling->m_fSuspensionBiasFront*0.5f );
}
#endif // !__NO_OUTPUT
#endif
float fTempHeight;
CVehicle::CalculateHeightsAboveRoad(GetModelId(), &m_fHeightAboveRoad, &fTempHeight);
}
void CAutomobile::SetupWheels(const void* pDamageTexture)
{
for(int i=0; i<GetNumWheels(); i++)
{
GetWheel(i)->SuspensionSetup(pDamageTexture);
}
if(!IsColliderArticulated())
{
// If the suspension changed, the maximum extents used by non-articulated vehicles needs to changes as well.
CalculateNonArticulatedMaximumExtents();
}
}
//
// name: ProcessFlightHandling
// description: All the code to handle the control process of flying vehicles
// that used to reside in ProcessControl
//
void CAutomobile::ProcessFlightHandling(float UNUSED_PARAM(fTimeStep))
{
/*
if(GetStatus()==STATUS_PLAYER || GetStatus()==STATUS_PHYSICS)
{
if (CCheat::IsCheatActive(CCheat::FLYINGCARS_CHEAT) && GetVelocity().Mag() > 0.0f && fTimeStep > 0.0f)
{
FlyingControl(FLIGHTMODEL_PLANE, fTimeStep);
}
}
*/
}
float CAutomobile::CAR_BURNOUT_SPEED = 3.0f;
float CAutomobile::CAR_INAIR_ROT_X = 1.3f;
float CAutomobile::CAR_INAIR_ROT_Y = 1.3f;
float CAutomobile::CAR_INAIR_ROT_Z = -1.0f;
float CAutomobile::CAR_STUCK_ROT_X = 1.5f;
float CAutomobile::CAR_STUCK_ROT_Y = 3.0f;
float CAutomobile::CAR_STUCK_ROT_Y_SELF_RIGHT = 15.0f; // Torque only allowed in self righting direction
float CAutomobile::CAR_INAIR_ROTLIM = 1.0f;
float CAutomobile::CAR_INAIR_REDUCED_ROTLIM = 0.2f;
float CAutomobile::CAR_BURNOUT_ROT_Z = -2.0f;
float CAutomobile::CAR_BURNOUT_ROTLIM = 0.8f;
float CAutomobile::CAR_BURNOUT_WHEELS_OFFGROUND_MULT = 1.5f;
float CAutomobile::BIG_CAR_BURNOUT_ROT_Z = -1.0f;
float CAutomobile::BIG_CAR_BURNOUT_ROTLIM = 0.35f;
float CAutomobile::BIG_CAR_BURNOUT_WHEEL_SPEED_MULT = 0.015f;
float CAutomobile::QUADBIKE_BURNOUT_ROT_Z = -4.5f;
float CAutomobile::QUADBIKE_BURNOUT_ROTLIM = 2.0f;
float CAutomobile::QUADBIKE_BURNOUT_WHEEL_SPEED_MULT = 0.1f;
float CAutomobile::OFFROAD_VEH_BURNOUT_ROT_Z = -4.0f;
float CAutomobile::OFFROAD_VEH_BURNOUT_ROTLIM = 1.0f;
float CAutomobile::OFFROAD_VEH_BURNOUT_WHEEL_SPEED_MULT = 0.1f;
float CAutomobile::BURNOUT_WHEEL_SPEED_MULT = 0.04f;
float CAutomobile::BURNOUT_TORQUE_MULT = 0.5f;
float CAutomobile::POSSIBLY_STUCK_SPEED = 0.7f;
float CAutomobile::COMPLETELY_STUCK_SPEED = 0.1f;
//
void CAutomobile::ProcessDriverInputsForStability(float fTimeStep)
{
Assert(GetStatus()==STATUS_PLAYER);
Assert(GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_QUADBIKE || GetVehicleType()==VEHICLE_TYPE_SUBMARINECAR || GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE || GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE);
m_nAutomobileFlags.bPlayerIsRevvingEngineThisFrame = false;
if(!GetDriver() || !GetDriver()->IsPlayer())
return;
CControl *pControl = NULL;
if(GetDriver()) pControl = GetDriver()->GetControlFromPlayer();
if(pControl==NULL)
return;
int nNumContactWheels = GetNumContactWheels();
float fVelocityIncludingReferenceFrameMag2 = GetVelocityIncludingReferenceFrame().Mag2();
bool bPossiblyStuck = nNumContactWheels < 3 /*&& GetCollisionImpulseMag() > 0.0*/ && fVelocityIncludingReferenceFrameMag2 < square(POSSIBLY_STUCK_SPEED) && ( !HasHydraulicSuspension() || nNumContactWheels < 2 );
bool bCompletelyStuckUpsideDown = CVehicle::sm_bUseNewSelfRighting && IsUpsideDown() && fVelocityIncludingReferenceFrameMag2 < square(COMPLETELY_STUCK_SPEED);
float fRotSpeed, fRotForce;
float fStickX = 0.0f;
float fStickY = 0.0f;
const bool bExitVehicleTaskRunning = GetDriver()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE, true);
bool bIsUpright = (!IsUpsideDown() && !IsOnItsSide());
bool bCanPlayerUseStick = (bIsUpright ||
!bExitVehicleTaskRunning ||
IsInAir());
TUNE_GROUP_BOOL(VEHICLE_DEBUG, DISALLOW_VEHICLE_STICK_CONTROL_FOR_NON_UPRIGHT_EXIT, true);
if (DISALLOW_VEHICLE_STICK_CONTROL_FOR_NON_UPRIGHT_EXIT)
{
if (bExitVehicleTaskRunning)
{
const s32 iExitVehicleState = GetDriver()->GetPedIntelligence()->GetQueriableInterface()->GetStateForTaskType(CTaskTypes::TASK_EXIT_VEHICLE);
if (iExitVehicleState == CTaskExitVehicle::State_WaitForCarToSettle)
{
bCanPlayerUseStick = false;
}
}
if (GetDriver()->GetPedResetFlag(CPED_RESET_FLAG_IsExitingOnsideVehicle) || GetDriver()->GetPedResetFlag(CPED_RESET_FLAG_IsExitingUpsideDownVehicle))
{
bCanPlayerUseStick = false;
}
}
if( m_specialFlightModeRatio > 0.5f )
{
bCanPlayerUseStick = false;
}
if(bCanPlayerUseStick)
{
GetVehicleMovementStickInput(*pControl, fStickX, fStickY);
#if RSG_PC
bool bMouseSteeringInput = false;
bool bMouseOrKeyboardSteeringInput = false;
if(pControl->WasKeyboardMouseLastKnownSource())
{
bMouseOrKeyboardSteeringInput = true;
if(pControl->GetVehicleSteeringLeftRight().GetSource().m_Device == IOMS_MOUSE_SCALEDAXIS)
{
bMouseSteeringInput = true;
}
}
bool bMouseSteering = CControl::GetMouseSteeringMode(PREF_MOUSE_DRIVE) == CControl::eMSM_Vehicle;
bool bCameraMouseSteering = CControl::GetMouseSteeringMode(PREF_MOUSE_DRIVE) == CControl::eMSM_Camera;
if( ((fabs(fStickX) <= 0.01f && bMouseOrKeyboardSteeringInput) || bMouseSteeringInput) && ((bMouseSteering && !CControlMgr::GetMainPlayerControl().GetDriveCameraToggleOn()) || (bCameraMouseSteering && CControlMgr::GetMainPlayerControl().GetDriveCameraToggleOn())))
{
fStickX = -m_fSteerInput;
}
#endif
}
// Scale the in air ability by the players stats.
float fInAirAbilityMult = 1.0f;
if(nNumContactWheels==0 &&GetDriver() && GetDriver()->IsLocalPlayer())
{
StatId stat = STAT_WHEELIE_ABILITY.GetStatId();
float fWheelieStatValue = rage::Clamp(static_cast<float>(StatsInterface::GetIntStat(stat)) / 100.0f, 0.0f, 1.0f);
float fMinWheelieAbility = CPlayerInfo::GetPlayerStatInfoForPed(*GetDriver()).m_MinWheelieAbility;
float fMaxWheelieAbility = CPlayerInfo::GetPlayerStatInfoForPed(*GetDriver()).m_MaxWheelieAbility;
fInAirAbilityMult = ((1.0f - fWheelieStatValue) * fMinWheelieAbility + fWheelieStatValue * fMaxWheelieAbility)/100.0f;
}
if(GetAttachParentVehicle() && GetAttachParentVehicle()->InheritsFromHeli() && ((CHeli *)GetAttachParentVehicle())->GetIsCargobob())
{
fInAirAbilityMult = 0.0f;
}
f32 rotationalForceX = 0.0f;
f32 rotationalForceY = 0.0f;
float fAftertouchScaleFactor = 1.0f;
if(!bCompletelyStuckUpsideDown && (bPossiblyStuck || nNumContactWheels==0) && ( m_carParachuteState == CAR_NOT_PARACHUTING ) && !m_shuntModifierActive )
{
// We might not want to allow such severe "aftertouch" forces on pitch and roll on some vehicles e.g. forklift with
// its short wheel base and low centre of mass.
if(GetModelIndex() == MI_CAR_FORKLIFT || (pHandling && pHandling->mFlags & MF_HAS_TRACKS) || (GetVehicleType() == VEHICLE_TYPE_QUADBIKE) //|| GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE
|| IsBusModelId(GetModelId()))
{
fAftertouchScaleFactor = 0.3f;
}
// Reduce "aftertouch" when a vehicle is in water. Stops cars flipping around while they are sinking.
if(GetIsInWater() && !InheritsFromSubmarineCar())
{
fAftertouchScaleFactor *= 0.25f;
}
if(pHandling && pHandling->hFlags & HF_IMPROVED_RIGHTING_FORCE)//Some cars are harder to right due to their bounds so give them a helping hand.
{
fAftertouchScaleFactor = 2.0f;
}
else if( ( pHandling && pHandling->hFlags & HF_REDUCED_RIGHTING_FORCE ) ||
( MI_CAR_BRICKADE.IsValid() && GetModelIndex() == MI_CAR_BRICKADE.GetModelIndex() ) )
{
static float sf_ReducedAfterTouchScale = 0.45f;
fAftertouchScaleFactor = sf_ReducedAfterTouchScale;
}
else if(pHandling && ((pHandling->hFlags & HF_OFFROAD_ABILITIES_X2) || (pHandling->hFlags & HF_OFFROAD_INCREASED_GRAVITY_NO_FOLIAGE_DRAG)) )//Off road vehicles need to be able to cope with going in the air
{
fAftertouchScaleFactor = 1.5f;
}
if( MI_CAR_WASTELANDER.IsValid() && MI_CAR_WASTELANDER == GetModelIndex() )
{
static dev_float sfWastelanderAftertouchScale = 1.0f;
fAftertouchScaleFactor = sfWastelanderAftertouchScale;
}
if( MI_CAR_VETO2.IsValid() && GetModelIndex() == MI_CAR_VETO2 )
{
static dev_float sfVetoAftertouchScale = 2.5f;
fAftertouchScaleFactor = sfVetoAftertouchScale;
}
if( CPhysics::ms_bInArenaMode )
{
TUNE_GROUP_FLOAT( ARENA_MODE, AfterTouchScale, 1.5f, 1.0f, 10.0f, 0.1f );
fAftertouchScaleFactor *= AfterTouchScale;
}
if( pHandling->mFlags & MF_IS_RC )
{
static dev_float sfRCCarAftertouchScale = 2.5f;
fAftertouchScaleFactor = sfRCCarAftertouchScale;
}
//if( CVehicle::sm_bInDetonationMode )
//{
// TUNE_GROUP_FLOAT( ARENA_MODE, detonationModeAfterTouchPitchScale, 1.5f, 1.0f, 10.0f, 0.1f );
// fAftertouchScaleFactor *= detonationModeAfterTouchPitchScale;
// fAftertouchScaleFactor *= 1.0f - Abs( fStickX );
//}
// Rotation about X (pitch)
{
const Vector3 vecRight(VEC3V_TO_VECTOR3(GetTransform().GetA()));
fRotSpeed = DotProduct(GetAngVelocity(), vecRight);
fRotForce = bPossiblyStuck ? CAR_STUCK_ROT_X : CAR_INAIR_ROT_X;
if(fRotSpeed > 0.0f && fStickY < 0.0f)
{
fRotForce *= 1.0f + fRotSpeed/CAR_INAIR_ROTLIM;
}
else if(fRotSpeed < 0.0f && fStickY > 0.0f)
{
fRotForce *= 1.0f + fRotSpeed/-CAR_INAIR_ROTLIM;
}
float absRotationSpeed = fabs(fRotSpeed);
// make sure we don't rotate faster than the limit
if( (absRotationSpeed < CAR_INAIR_ROTLIM) || ( pHandling->mFlags & MF_IS_RC ) || fStickY != Sign(fRotSpeed) )
{
ApplyInternalTorque( fAftertouchScaleFactor * fStickY * fRotForce * GetAngInertia().x * vecRight * fInAirAbilityMult);
rotationalForceX = fStickY * fRotForce;
}
}
// Rotation about Y (roll)
{
fRotSpeed = DotProduct(GetAngVelocity(), VEC3V_TO_VECTOR3(GetTransform().GetB()));
if(!bPossiblyStuck)
{
if( ( /*!CVehicle::sm_bInDetonationMode &&*/ GetIsHandBrakePressed(pControl) ) )//||
//( CVehicle::sm_bInDetonationMode && !GetIsHandBrakePressed(pControl) ) )
{
// if the handbrake is pressed we apply yaw to the car instead of roll
fRotForce = 0.0f;
}
else
{
fRotForce = CAR_INAIR_ROT_Y;
if(fRotSpeed > 0.0f && fStickX < 0.0f)
{
fRotForce *= 1.0f + fRotSpeed/CAR_INAIR_ROTLIM;
}
else if(fRotSpeed < 0.0f && fStickX > 0.0f)
{
fRotForce *= 1.0f + fRotSpeed/-CAR_INAIR_ROTLIM;
}
}
}
else if(Sign(fStickX) == Sign(GetTransform().GetA().GetZf()) || abs(GetTransform().GetA().GetZf()) < 0.1f)
{
// Trying to self right car
// if a.z is negative then we need a negative torque to self right
fRotForce = CAR_STUCK_ROT_Y_SELF_RIGHT * CVehicle::ms_fGravityScale; // Scale this by gravity because it looks crazy on the moon
}
else
{
// We're stuck but pushing stick in opposite direction to what we think is sensible
fRotForce = CAR_STUCK_ROT_Y;
}
// when the vehicle is upright, flip roll control so it matches normal steering input
//if(GetC().z > 0.0f)
// fRotForce *= -1.0f;
if( fRotForce != 0.0f )
{
float absRotationSpeed = fabs(fRotSpeed);
float rotSpeedLimit = !pHandling->GetCarHandlingData() || !( pHandling->GetCarHandlingData()->aFlags & CF_REDUCED_SELF_RIGHTING_SPEED ) ? CAR_INAIR_ROTLIM : CAR_INAIR_REDUCED_ROTLIM;
// make sure we don't rotate faster than the limit
if( (absRotationSpeed < rotSpeedLimit ) || Sign(fStickX) != Sign(fRotSpeed) )
{
ApplyInternalTorque( fAftertouchScaleFactor * fStickX * fRotForce * GetAngInertia().y * VEC3V_TO_VECTOR3(GetTransform().GetB()) * fInAirAbilityMult);
rotationalForceY = fStickX * fRotForce;
}
}
}
//if( CVehicle::sm_bInDetonationMode &&
// fStickX == 0.0f &&
// fStickY == 0.0f &&
// ( bIsUpright || !pControl->GetVehicleArenaModeModifier().HistoryHeldDown( 250 ) ) )
//{
// Vector3 extraAngularDamping = -GetAngVelocity() * GetAngInertia() * fwTimer::GetInvTimeStep();
// static dev_float dampingScale = 0.25f;
// ApplyInternalTorque( extraAngularDamping * dampingScale );
//}
}
//if( !IsRightWayUp() &&
// fStickX == 0.0f &&
// fStickY == 0.0f &&
// pControl->GetVehicleArenaModeModifier().HistoryHeldDown( 250 ) &&
// CVehicle::sm_bInDetonationMode )
//{
// Vector3 rotationAxis = VEC3V_TO_VECTOR3( GetTransform().GetForward() );
// static dev_float torqueScale = 250.0f;
// Vector3 rightAxis = VEC3V_TO_VECTOR3( GetTransform().GetRight() );
// float rotationAmount = ( 1.25f - Abs( rightAxis.z ) );
// rotationAmount *= torqueScale * rotationAmount;
// float fZRotSpeed = GetAngVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) );
// rotationAmount *= Max( 0.0f, 1.0f - ( Abs( fZRotSpeed ) / CAR_INAIR_ROTLIM ) );
// rotationAxis *= rotationAmount;
// rotationAxis *= GetAngInertia().y;
// if( rightAxis.z < 0.0f )
// {
// rotationAxis *= -1.0f;
// }
// ApplyTorque( rotationAxis );
//}
m_VehicleAudioEntity->SetInAirRotationalForces(rotationalForceX, rotationalForceY);
bool bRotatingInBurnout = false;
// Rotation about Z (yaw - handBrake)
if( nNumContactWheels==0 &&
/*(*/ GetIsHandBrakePressed(pControl) //||
//( CVehicle::sm_bInDetonationMode &&
/*!GetIsHandBrakePressed(pControl) ) ))*/ &&
GetTransform().GetC().GetZf() > 0.0f &&
!m_nAutomobileFlags.bBurnoutModeEnabled &&
( m_carParachuteState == CAR_NOT_PARACHUTING ) &&
!m_shuntModifierActive )//don't let people spin the car when in burnout script mode
{
Vector3 vecUp(VEC3V_TO_VECTOR3(GetTransform().GetC()));
fRotSpeed = DotProduct(GetAngVelocity(), vecUp);
fRotForce = CAR_INAIR_ROT_Z;
float absRotationSpeed = fabs(fRotSpeed);
// make sure we don't rotate faster than the limit
if( (absRotationSpeed < CAR_INAIR_ROTLIM) || fStickX != Sign(fRotSpeed) )
{
//if( CVehicle::sm_bInDetonationMode )
//{
// TUNE_GROUP_FLOAT( ARENA_MODE, detonationModeAfterTouchScale, 10.0f, 1.0f, 100.0f, 0.1f );
// TUNE_GROUP_FLOAT( ARENA_MODE, detonationModeAfterTouchMaxYawSpeed, 3.0f, 1.0f, 100.0f, 0.1f );
// fAftertouchScaleFactor = 1.0f;
// fAftertouchScaleFactor *= detonationModeAfterTouchScale;
// //vecUp = Vector3( 0.0f, 0.0f, 1.0f );
// if( fRotSpeed > 0.0f && fStickX < 0.0f )
// {
// fRotForce *= 1.0f - fRotSpeed / detonationModeAfterTouchMaxYawSpeed;
// }
// else if( fRotSpeed < 0.0f && fStickX > 0.0f )
// {
// fRotForce *= 1.0f - fRotSpeed / -detonationModeAfterTouchMaxYawSpeed;
// }
// fRotForce *= fAftertouchScaleFactor;
//}
ApplyInternalTorque(fStickX * fRotForce * GetAngInertia().z * vecUp * fInAirAbilityMult);
}
}
// burnout controls (lets you rotate on the spot)
else if((GetThrottle() > 0.9f && (GetBrake() > 0.9f||GetReduceGripMode())) && !GetHandBrake() && GetVehicleUpDirection().GetZf() > 0.0f && m_nVehicleFlags.bEngineOn && !m_nAutomobileFlags.bBurnoutModeEnabled && !(pHandling->mFlags & MF_HAS_TRACKS))//don't let people spin the car when in burnout script mode
{
m_nAutomobileFlags.bInBurnout = true;
//Reduce how much the vehicle rotated bases on how many wheels are burst
int iBurnoutWheels = 0;
int iBurnoutWheelsNotOnGround = 0;
float fBurnoutWheelHealth = 0;
float fWheelRotSpeed = 0.0f;
for(int i = 0; i < GetNumWheels(); i++)
{
CWheel *pWheel = GetWheel(i);
if(pWheel->GetConfigFlags().IsFlagSet(WCF_POWERED) && (pWheel->GetConfigFlags().IsFlagSet(WCF_REARWHEEL) || pHandling->m_fDriveBiasRear < 0.1f))
{
fBurnoutWheelHealth += pWheel->GetTyreHealth();
fWheelRotSpeed += pWheel->GetRotSpeed();
iBurnoutWheels++;
if(!pWheel->GetIsTouching())
{
iBurnoutWheelsNotOnGround++;
}
}
}
float fWheelsOffGroundTorqueMult = 1.0f;
float fBurnoutForceMult = 1.0f;
if(iBurnoutWheels > 0)
{
fBurnoutForceMult = fBurnoutWheelHealth / (TYRE_HEALTH_DEFAULT * iBurnoutWheels);
fWheelRotSpeed = fWheelRotSpeed / iBurnoutWheels;
//increase the torque used in burnout when wheels are off the ground.
fWheelsOffGroundTorqueMult += CAR_BURNOUT_WHEELS_OFFGROUND_MULT * 1.0f - (iBurnoutWheelsNotOnGround / iBurnoutWheels);
}
const Vector3 vecUp(VEC3V_TO_VECTOR3(GetTransform().GetC()));
fRotSpeed = DotProduct(GetAngVelocity(), vecUp);
float fBurnoutRotZ = rage::Max(CAR_BURNOUT_ROT_Z, fWheelRotSpeed * BURNOUT_WHEEL_SPEED_MULT);
float fBurnoutRotLimit = CAR_BURNOUT_ROTLIM;
if(GetVehicleType() == VEHICLE_TYPE_QUADBIKE ||
GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE)
{
fBurnoutRotZ = rage::Max(QUADBIKE_BURNOUT_ROT_Z, fWheelRotSpeed * QUADBIKE_BURNOUT_WHEEL_SPEED_MULT);
fBurnoutRotLimit = QUADBIKE_BURNOUT_ROTLIM;
if( GetNumWheels() == 3 )
{
static dev_float trikeBurnoutMult = 0.5f;
fBurnoutRotZ *= trikeBurnoutMult;
fBurnoutRotLimit *= trikeBurnoutMult;
}
}
else if(pHandling && ((pHandling->hFlags & HF_OFFROAD_ABILITIES_X2) || (pHandling->hFlags & HF_OFFROAD_INCREASED_GRAVITY_NO_FOLIAGE_DRAG)))
{
fBurnoutRotZ = rage::Max(OFFROAD_VEH_BURNOUT_ROT_Z, fWheelRotSpeed * OFFROAD_VEH_BURNOUT_WHEEL_SPEED_MULT);
fBurnoutRotLimit = OFFROAD_VEH_BURNOUT_ROTLIM;
}
else if( GetNumWheels() == 3 )
{
static dev_float threeWheelerBurnoutMult = 2.5f;
fBurnoutForceMult *= threeWheelerBurnoutMult;
}
else if( ( MI_CAR_RUSTON.IsValid() && GetModelIndex() == MI_CAR_RUSTON ) ||
( MI_CAR_BRIOSO2.IsValid() && GetModelIndex() == MI_CAR_BRIOSO2 ) )
{
static dev_float rustonBurnoutMult = 2.0f;
fBurnoutForceMult *= rustonBurnoutMult;
}
if(m_nVehicleFlags.bIsBig)
{
if( !HasRammingScoop() )
{
fBurnoutRotZ = rage::Max(BIG_CAR_BURNOUT_ROT_Z, fWheelRotSpeed * BIG_CAR_BURNOUT_WHEEL_SPEED_MULT);
}
else
{
fBurnoutRotZ = rage::Max(BIG_CAR_BURNOUT_ROT_Z, fWheelRotSpeed * QUADBIKE_BURNOUT_WHEEL_SPEED_MULT);
}
fBurnoutRotLimit = BIG_CAR_BURNOUT_ROTLIM;
}
if( fabs(fStickX) > 0.001f )
{
m_fBurnoutRotationTime += fTimeStep * fStickX;
m_fBurnoutRotationTime = rage::Clamp( m_fBurnoutRotationTime, -1.0f, 1.0f );
bRotatingInBurnout = true;
float absRotationSpeed = fabs(fRotSpeed);
// make sure we don't rotate faster than the limit
if( (absRotationSpeed < fBurnoutRotLimit) || fStickX == Sign(fRotSpeed) )
{
ApplyInternalTorque(m_fBurnoutRotationTime * fWheelsOffGroundTorqueMult * fBurnoutForceMult * fBurnoutRotZ * GetAngInertia().z * vecUp);
}
}
else
{
bRotatingInBurnout = false;
}
}
else
{
m_nAutomobileFlags.bInBurnout = false;
bRotatingInBurnout = false;
}
if( !m_nVehicleFlags.bScriptSetHandbrake &&
( GetVehicleModelInfo()->GetVehicleClass() == VC_MUSCLE ||
( pHandling->GetCarHandlingData() && pHandling->GetCarHandlingData()->aFlags & CF_CAN_WHEELIE ) ||
GetModelIndex() == MI_CAR_TORNADO6 ) &&
( GetThrottle() > 0.9f || ( m_nAutomobileFlags.bWheelieModeEnabled && GetThrottle() >= 0.0 ) ) &&
( m_Transmission.GetRevRatio() > 0.8f || ( m_nAutomobileFlags.bWheelieModeEnabled && m_Transmission.GetRevRatio() > 0.3f ) ) &&
m_nVehicleFlags.bEngineOn && GetNumWheels() == GetNumContactWheels() && fVelocityIncludingReferenceFrameMag2 < 1.0f )
{
if( GetHandBrake() )
{
m_nAutomobileFlags.bWheelieModeEnabled = true;
}
else if( m_nAutomobileFlags.bWheelieModeEnabled )
{
m_nAutomobileFlags.bInWheelieMode = true;
m_nAutomobileFlags.bWheelieModeEnabled = false;
m_nAutomobileFlags.bInWheelieModeWheelsUp = false;
}
}
else
{
m_nAutomobileFlags.bWheelieModeEnabled = false;
}
int numContactWheels = GetNumContactWheels();
if( GetBrake() > 0.2f ||
( GetThrottle() == 0.0f &&
( fVelocityIncludingReferenceFrameMag2 < 10.0f ||
( numContactWheels == GetNumWheels() &&
Abs( GetSteerAngle() ) > 0.5f ) ) ) )
{
m_nAutomobileFlags.bInWheelieMode = false;
m_nAutomobileFlags.bInWheelieModeWheelsUp = false;
}
if( m_nAutomobileFlags.bInWheelieMode )
{
if( ( m_Transmission.GetGear() > 2 &&
numContactWheels == GetNumWheels() ) )// ||
//numContactWheels == 0 )
{
m_nAutomobileFlags.bInWheelieMode = false;
m_nAutomobileFlags.bInWheelieModeWheelsUp = false;
}
if( numContactWheels > 0 )
{
Vector3 localWheelOffset = CWheel::GetWheelOffset( GetVehicleModelInfo(), VEH_WHEEL_LR );
CWheel* wheel = GetWheelFromId( VEH_WHEEL_LR );
if( !wheel->GetIsTouching() )
{
wheel = GetWheelFromId( VEH_WHEEL_RR );
}
if( wheel->GetIsTouching() )
{
if( numContactWheels == 2 )
{
m_nAutomobileFlags.bInWheelieModeWheelsUp = true;
}
else if( numContactWheels > 2 )
{
m_nAutomobileFlags.bInWheelieModeWheelsUp = false;
}
Vector3 hitNormal = wheel->GetHitNormal();
Vector3 vehicleUp = VEC3V_TO_VECTOR3( GetTransform().GetUp() );
localWheelOffset.x = 0.0f;
static dev_float sfMinYOffset = -1.75f;
static dev_float sfMinZOffset = -0.175f;
localWheelOffset.y = Max( localWheelOffset.y, sfMinYOffset );
localWheelOffset.z = Max( localWheelOffset.z, sfMinZOffset );
Vector3 wheelOffset = localWheelOffset;
wheelOffset = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( wheelOffset ) ) );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfWheeliTorqueScale, 2.6f, 0.0f, 10.0f, 0.1f );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfwheelieTorqueMag, 8.0f, 0.0f, 20.0f, 0.1f );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfWheelitAngleTorqueScaleMin, 0.0f, 0.0f, 20.0f, 0.1f );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfWheelitAngleTorqueScaleMax, 1.0f, 0.0f, 20.0f, 0.1f );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfOptimumThrottleThreshold, 0.15f, 0.0f, 20.0f, 0.1f );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfOptimumThrottleTorqueScale, 0.5f, 0.0f, 20.0f, 0.1f );
TUNE_GROUP_FLOAT( VEHICLE_WHEELIE, sfMinEngineMod, 0.8f, 0.0f, 20.0f, 0.1f );
float angleToqueScale = Clamp( 1.0f - Abs( ( hitNormal.z - vehicleUp.z ) * 2.0f ), 0.0f, 1.0f );
if( pHandling->GetCarHandlingData() )
{
for( int i = 0; i < pHandling->GetCarHandlingData()->m_AdvancedData.GetCount(); i++ )
{
int advancedDataModSlot = pHandling->GetCarHandlingData()->m_AdvancedData[ i ].m_Slot;
if( advancedDataModSlot != -1 )
{
bool variation = false;
int modIndex = (int)GetVariationInstance().GetModIndexForType( (eVehicleModType)advancedDataModSlot, this, variation );
if( modIndex == pHandling->GetCarHandlingData()->m_AdvancedData[ i ].m_Index )
{
float advancedDataTorqueScale = pHandling->GetCarHandlingData()->m_AdvancedData[ i ].m_Value;
angleToqueScale *= advancedDataTorqueScale;
}
}
}
}
angleToqueScale *= angleToqueScale;
angleToqueScale = sfWheelitAngleTorqueScaleMin + ( sfWheelitAngleTorqueScaleMax * angleToqueScale );
float optimumThrottle = angleToqueScale;
float lastDriveForce = m_Transmission.GetLastCalculatedDriveForce() / rage::Min( 1.0f, 2.0f * m_Transmission.GetClutchRatio() ); // scale the drive force back up by the clutch ratio so that it continues to wheelie while changing gear
float torqueScaleWithEngineMod = Min( 1.0f, sfMinEngineMod + ( (float)GetVariationInstance().GetEngineModifier() * 0.002f ) );
float maxWheelieForce = Min( sfwheelieTorqueMag, lastDriveForce * sfWheeliTorqueScale * angleToqueScale * torqueScaleWithEngineMod );
if( Abs( GetThrottle() - optimumThrottle ) < sfOptimumThrottleThreshold )
{
lastDriveForce += ( lastDriveForce * sfOptimumThrottleTorqueScale ) * ( Abs( GetThrottle() - optimumThrottle ) / sfOptimumThrottleThreshold );
}
Vector3 wheelieTorque( maxWheelieForce, 0.0f, 0.0f );
wheelieTorque *= GetAngInertia();
wheelieTorque = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( wheelieTorque ) ) );
localWheelOffset = VEC3V_TO_VECTOR3( GetTransform().Transform( VECTOR3_TO_VEC3V( localWheelOffset ) ) );
ApplyInternalTorqueAndForce( wheelieTorque, localWheelOffset );
}
}
else if( fVelocityIncludingReferenceFrameMag2 < 2.0f ||
fVelocityIncludingReferenceFrameMag2 > 15.0f )
{
m_nAutomobileFlags.bInWheelieMode = false;
m_nAutomobileFlags.bInWheelieModeWheelsUp = false;
}
}
//reduce rotating burnout timers if not rotating.
if(bRotatingInBurnout == false)
{
float fOriginalBurnoutTime = m_fBurnoutRotationTime;
if(fOriginalBurnoutTime > 0.0f)
{
m_fBurnoutRotationTime -= fTimeStep;
if(fOriginalBurnoutTime < 0.0f)
{
m_fBurnoutRotationTime = 0.0f;
}
}
else
{
m_fBurnoutRotationTime += fTimeStep;
if(fOriginalBurnoutTime > 0.0f)
{
m_fBurnoutRotationTime = 0.0f;
}
}
}
m_fParachuteStickX = fStickX;
m_fParachuteStickY = fStickY;
}
PF_PAGE(GTAAutos, "GTA Autos");
PF_GROUP(AutosProcessControl);
PF_LINK(GTAAutos, AutosProcessControl);
PF_TIMER(Misc1, AutosProcessControl);
PF_TIMER(ProcessVehicle, AutosProcessControl);
PF_TIMER(Misc2, AutosProcessControl);
PF_TIMER(ControlOptions, AutosProcessControl);
PF_TIMER(ProcessDriverInputs, AutosProcessControl);
PF_TIMER(ProcessIntelligence, AutosProcessControl);
PF_TIMER(Damage, AutosProcessControl);
PF_GROUP(AutosAI);
PF_LINK(GTAAutos, AutosAI);
PF_TIMER(FindTargetCoorsAndSpeed, AutosAI);
// remember there's a bike version of this code as well
dev_float sfCarNotSlowResistanceMult = 0.9f;
dev_float sfCarSlowResistanceMult = 1.1f;
#if __PS3
dev_float sfCarSlowResistanceMultNetwork = 1.1f;
#else
dev_float sfCarSlowResistanceMultNetwork = 1.1f;
#endif
//dev_float sfCarSlowResistanceMultCheat1 = 1.5f;
//dev_float sfCarSlowResistanceMultCheat2 = 1.25f;
//
void CAutomobile::UpdateAirResistance()
{
CPed *pDriver = GetDriver();
CPlayerInfo *pPlayer = NULL;
if(pDriver)
{
pPlayer = pDriver->GetPlayerInfo();
}
// If it's not driven by a player,
fragInst* pFragInst = GetVehicleFragInst();
if(!pPlayer)
{
const int lod = pFragInst->GetCurrentPhysicsLOD();
// Compute some value representing the state of the vehicle that would affect it's
// damping constants, and check if that's any different than the values we have already set.
// The addition of one is here because we need to make sure that it never computes to zero,
// so that setting m_AirResistanceState to zero always refreshes the air resistance. The
// shift by five bits of bInSlownessZone is also fairly arbitrary, we just need it to be
// large enough to not overlap with the lod, and small enough so it still fits in a byte.
unsigned int airResState = lod + 1 + (m_nVehicleFlags.bInSlownessZone << 5);
if(m_AirResistanceState == airResState)
{
Assert(GetSlipStreamEffect() == 0.0f);
return;
}
Assert(airResState <= 0xff);
m_AirResistanceState = (u8)airResState;
}
else
{
// Make sure we refresh once the player is no longer the driver.
m_AirResistanceState = 0;
}
float fAirResistance = m_fDragCoeff * sfCarNotSlowResistanceMult;
if( m_nVehicleFlags.bInSlownessZone BANK_ONLY(|| CVehicle::ms_bForceSlowZone) )
{
if(NetworkInterface::IsGameInProgress())
fAirResistance = m_fDragCoeff*sfCarSlowResistanceMultNetwork;
else
fAirResistance = m_fDragCoeff*sfCarSlowResistanceMult;
}
float fSlipStreamEffect = 0.0f;
if(pPlayer)
{
if (pPlayer->m_fForceAirDragMult > 0.0f)
{
float fNewAirResistance = fAirResistance * pPlayer->m_fForceAirDragMult;
if(fNewAirResistance > fAirResistance)
{
fAirResistance = fNewAirResistance;
}
}
// Update air resistance for slip stream
fSlipStreamEffect = GetSlipStreamEffect();
}
else
{
// For performance, we are taking advantage of the current fact that GetSlipStreamEffect()
// doesn't return varying values for AI, and in fact, hopefully we won't have to call it
// at all. But, this assert would tell us if somebody tries to use it for AI:
Assert(GetSlipStreamEffect() == 0.0f);
}
if( pHandling->GetCarHandlingData() &&
pHandling->GetCarHandlingData()->aFlags & CF_USE_DOWNFORCE_BIAS )
{
float fDownforceModifierFront = m_fDownforceModifierFront;
float fDownforceModifierRear = m_fDownforceModifierRear;
static dev_float sfDownforceReductionForBrokenWing = 0.3f; // THIS IS A DIFFERENT VALUE TO THE ONE IN WHEEL AS WE WANT TO REDUCE DOWNFORCE BY MORE THAN WE REDUCE DRAG
int frontWingBoneIndex = GetBoneIndex( VEH_EXTRA_6 );
int childIndex = frontWingBoneIndex == -1 ? -1 : GetVehicleFragInst()->GetComponentFromBoneIndex( frontWingBoneIndex );
if( !GetIsExtraOn( VEH_EXTRA_6 ) ||
( childIndex != -1 &&
GetVehicleFragInst()->GetChildBroken( childIndex ) ) )
{
fDownforceModifierFront *= sfDownforceReductionForBrokenWing;
}
int rearWingBoneIndex = GetBoneIndex( VEH_EXTRA_1 );
childIndex = rearWingBoneIndex == -1 ? -1 : GetVehicleFragInst()->GetComponentFromBoneIndex( rearWingBoneIndex );
if( !GetIsExtraOn( VEH_EXTRA_1 ) ||
( childIndex != -1 &&
GetVehicleFragInst()->GetChildBroken( childIndex ) ) )
{
fDownforceModifierRear *= sfDownforceReductionForBrokenWing;
}
float averageDownforce = ( ( fDownforceModifierFront + fDownforceModifierRear ) * 0.5f ) - 1.0f;
TUNE_GROUP_FLOAT( VEHICLE_DOWNFORCE_TUNE, DRAG_INCREASE_FACTOR, 0.35f, 0.0f, 10.0f, 0.01f );
fAirResistance += fAirResistance * averageDownforce * DRAG_INCREASE_FACTOR;
}
fragPhysicsLOD* pFragPhysics = pFragInst->GetTypePhysics();
phArchetypeDamp* pArch = (phArchetypeDamp*)pFragInst->GetArchetype();
Vector3 vecLinearCDamping = pFragPhysics->GetDampingConstant(phArchetypeDamp::LINEAR_C);
Vector3 vecLinearVDamping = pFragPhysics->GetDampingConstant(phArchetypeDamp::LINEAR_V);
Vector3 vecLinearV2Damping;
vecLinearV2Damping.Set(fAirResistance, fAirResistance, fAirResistance);
vecLinearCDamping -= (vecLinearCDamping * fSlipStreamEffect);
vecLinearVDamping -= (vecLinearVDamping * fSlipStreamEffect);
vecLinearV2Damping -= (vecLinearV2Damping * fSlipStreamEffect);
pArch->ActivateDamping(phArchetypeDamp::LINEAR_C, vecLinearCDamping);
pArch->ActivateDamping(phArchetypeDamp::LINEAR_V, vecLinearVDamping);
TUNE_GROUP_FLOAT(VEHICLE_DAMPING, AUTOMOBILE_V2_DAMP_MULT, 1.0f, 0.0f, 5.0f, 0.01f);
pArch->ActivateDamping(phArchetypeDamp::LINEAR_V2, vecLinearV2Damping * AUTOMOBILE_V2_DAMP_MULT);
// Note: for the dummy instance, changing these values is a bit dubious since
// multiple instances actually point to the same phArchetypeDamp object. In practice,
// there are no known problems. We might want to not even do it at all, but now
// when we don't have to do this on each frame for all AI vehicles, it shouldn't matter much.
phInstGta* pDummyInst = GetDummyInst();
if(pDummyInst)
{
phArchetypeDamp* pDummyArch = ((phArchetypeDamp*)pDummyInst->GetArchetype());
pDummyArch->ActivateDamping(phArchetypeDamp::LINEAR_C, vecLinearCDamping);
pDummyArch->ActivateDamping(phArchetypeDamp::LINEAR_V, vecLinearVDamping);
pDummyArch->ActivateDamping(phArchetypeDamp::LINEAR_V2, vecLinearV2Damping);
}
}
dev_float RANDOM_SIREN_ACTIVATION_PROB = 0.1f;
static dev_float sfGroundClearanceFastVelocitySquared = 7.0f*7.0f;
static dev_float sfFastGroundClearance = 0.50f;
static dev_float sfRCCarFastGroundClearance = 0.30f;
static dev_float sfFastGroundClearanceForHydraulics = 0.35f;
static dev_float sfSlowGroundClearance = 0.0f;
static dev_float sfSlowGroundClearanceForHydraulics = 0.25f;
//
void CAutomobile::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
//////////////////////////////
// Do some debug stuff first (not that useful since rarely play Debug build)
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessControlOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfProcessControlCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
///////////////////////////////
if(fullUpdate)
{
PF_START(Misc1);
//reset
m_nAutomobileFlags.bIsBoggedDownInSand = false;
// Randomly activate the siren if set to stop them all being activated at the same time
if( m_nVehicleFlags.GetRandomlyDelaySiren() && (fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < RANDOM_SIREN_ACTIVATION_PROB ) )
m_nVehicleFlags.SetRandomlyDelaySiren(false);
// service the audio entity - this should be done once a frame
// we want to ALWAYS do it, even if car is totally stationary and inert
// m_VehicleAudioEntity.Update();
if (PopTypeGet() == POPTYPE_RANDOM_PARKED && GetDriver() && !GetDriver()->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_EXIT_VEHICLE))
{
// The vehicle now has a driver so it is no longer just a placed vehicle (as it can drive away now).
PopTypeSet(POPTYPE_RANDOM_AMBIENT);
}
// Generate a horn shocking event, if the horn is being played
if(IsHornOn() && ShouldGenerateMinorShockingEvent())
{
CEventShockingHornSounded ev(*this);
CShockingEventsManager::Add(ev);
}
// need to process a few things now in case fn returns e.g. for simple cars
if(m_nVehicleFlags.bIsBus)
ProcessAutoBusDoors();
else if (GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_TURRET_SEAT_ON_VEHICLE))
ProcessAutoTurretDoors();
ProcessCarAlarm(fFullUpdateTimeStep);
UpdateInSlownessZone();
if((GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_QUADBIKE || GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE))
{
UpdateAirResistance();
for( int i = 0; i < m_nNumWheels; i++ )
{
if( m_ppWheels[ i ] )
{
m_ppWheels[ i ]->UpdateDownforce();
}
}
}
PF_STOP(Misc1);
// process special options for cars (vehicle specific controls and other misc bits)
PF_START(ControlOptions);
ProcessDriverInputOptions();
PF_STOP(ControlOptions);
}
//process the tasks (unless we were just created on this frame, in which case we've already done this via CVehiclePopulation::MakeVehicleIntoDummyBasedOnDistance)
if (!m_nVehicleFlags.bHasProcessedAIThisFrame)
{
//Process AI and player tasks, this also processes player controls
PF_START(ProcessIntelligence);
ProcessIntelligence(fullUpdate, fFullUpdateTimeStep);
PF_STOP(ProcessIntelligence);
}
const bool updateVehicleDamageEveryFrame = ShouldUpdateVehicleDamageEveryFrame();
if(fullUpdate)
{
//modify ground clearance depending on speed
float fVehSpeedSq = GetVelocityIncludingReferenceFrame().Mag2();
float fFastGroundClearance = sfFastGroundClearance;
float fSlowGroundClearance = sfSlowGroundClearance;
static dev_float sfOutriggerVehicleFastGroundClearance = sfFastGroundClearance * 1.5f;
static dev_float sfOutriggerVehicleSlowGroundClearance = 0.35f;
if( GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_OUTRIGGER_LEGS ) )
{
fFastGroundClearance = sfOutriggerVehicleFastGroundClearance;
fSlowGroundClearance = sfOutriggerVehicleSlowGroundClearance;
}
if( GetModelIndex() == MI_TANK_KHANJALI )
{
fFastGroundClearance = sfOutriggerVehicleFastGroundClearance;
}
if( pHandling->mFlags & MF_IS_RC )
{
fSlowGroundClearance = 0.0f;
fFastGroundClearance = sfRCCarFastGroundClearance;
}
if(GetModelIndex() == MI_CAR_VETO || GetModelIndex() == MI_CAR_VETO2)
{
fFastGroundClearance = 0.30f;
}
if(HasHydraulicSuspension())
{
fFastGroundClearance = sfFastGroundClearanceForHydraulics;
bool hydraulicsActive = ( m_nVehicleFlags.bPlayerModifiedHydraulics ||
m_nAutomobileFlags.bHydraulicsBounceRaising ||
m_nAutomobileFlags.bHydraulicsBounceLanding ||
m_nAutomobileFlags.bHydraulicsJump );
if( !hydraulicsActive )
{
fSlowGroundClearance = sfSlowGroundClearanceForHydraulics;
}
}
float fGroundClearance = Selectf(fVehSpeedSq-sfGroundClearanceFastVelocitySquared,fFastGroundClearance,fSlowGroundClearance);
#if __BANK
if(ms_fForcedGroundClearance != 0.0f)// if force ground clearance has been set then use that
{
fGroundClearance = ms_fForcedGroundClearance;
}
#endif
bool brokenWheels = false;
if( GetWheelBrokenIndex() != 0 )
{
fGroundClearance = sfSlowGroundClearance;
brokenWheels = true;
}
// B*1853026: Make sure the bounds are lowered if we're in the air so we don't land on something and get stuck due to raised bounds.
if( IsInAir() &&
!CPhysics::ms_bInStuntMode &&
( !InheritsFromTrailer() ||
!GetAttachParent() ) )
{
fGroundClearance = fSlowGroundClearance;
}
if(InheritsFromAmphibiousQuadBike() && !static_cast<CAmphibiousQuadBike*>(this)->IsWheelsFullyOut())
{
fGroundClearance = fSlowGroundClearance;
}
bool forceUpdateGroundClearance = m_nAutomobileFlags.bForceUpdateGroundClearance;
// for the submarine car we need to force the bounds back down when the wheels move up to stop the vehicle getting stopped a long way in the ground
if( InheritsFromSubmarineCar() )
{
CSubmarineCar* submarineCar = static_cast< CSubmarineCar* >( this );
float animPhase = submarineCar->GetAnimationPhase();
float maxGroundClearance = sfFastGroundClearance;
fSlowGroundClearance = sfSlowGroundClearanceForHydraulics;
if( submarineCar->GetAnimationState() == CSubmarineCar::State_MoveWheelsUp ||
submarineCar->GetAnimationState() == CSubmarineCar::State_MoveWheelsDown )
{
maxGroundClearance = ( 1.0f - Min( 1.0f, submarineCar->GetAnimationPhase() * 2.0f ) ) * fSlowGroundClearance;
}
if( !submarineCar->AreWheelsActive() )
{
animPhase = 1.0f;
maxGroundClearance = 0.0f;
}
fGroundClearance = fSlowGroundClearance + ( Max( 0.0f, ( fGroundClearance - fSlowGroundClearance ) ) * ( 1.0f - animPhase ) );
fGroundClearance = Min( maxGroundClearance, fGroundClearance );
forceUpdateGroundClearance = animPhase > 0.0f && fGroundClearance < sfFastGroundClearance && fGroundClearance != m_fGroundClearance;
}
if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_OUTRIGGER_LEGS ) )
{
float animPhase = GetOutriggerDeployRatio();
float maxGroundClearance = sfOutriggerVehicleFastGroundClearance;
if( animPhase > 0.0f )
{
maxGroundClearance *= ( 1.0f - Min( 1.0f, animPhase * 2.0f ) );
}
fGroundClearance = Min( maxGroundClearance, fGroundClearance );
forceUpdateGroundClearance = animPhase > 0.0f && fGroundClearance < sfOutriggerVehicleFastGroundClearance && fGroundClearance != m_fGroundClearance;
}
bool bBoundsNeedUpdating = UpdateDesiredGroundGroundClearance(fGroundClearance, brokenWheels, forceUpdateGroundClearance );
if( bBoundsNeedUpdating )
{
m_nAutomobileFlags.bForceUpdateGroundClearance = false;
}
// If we raise the ground clearance, also make sure the bumper collision is turned off if applicable to this vehicle.
if(bBoundsNeedUpdating)
{
const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo();
physicsFatalAssertf(pVehicleModelInfo, "This vehicle (%s) has no model info. It is unsafe to continue.", GetModelName());
const CVehicleModelInfoBumperCollision* pBumperColExtension = pVehicleModelInfo->GetExtension<CVehicleModelInfoBumperCollision>();
const s32 NUM_BUMPERS_TO_ENABLE_COLLISION = 2;
static eHierarchyId aBumperPartsToEnableCollision[NUM_BUMPERS_TO_ENABLE_COLLISION] =
{
VEH_BUMPER_F,
VEH_BUMPER_R,
};
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
const fragPhysicsLOD* physicsLOD = GetVehicleFragInst()->GetTypePhysics();
for(u32 i = 0; i < NUM_BUMPERS_TO_ENABLE_COLLISION; ++i)
{
int nBoneIdx = GetBoneIndex(aBumperPartsToEnableCollision[i]);
if(nBoneIdx!=-1)
{
int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(nBoneIdx);
if(nGroup > -1)
{
fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup);
int iChild = pGroup->GetChildFragmentIndex();
for(int k = 0; k < pGroup->GetNumChildren(); ++k)
{
u32 nIncludeFlags = ArchetypeFlags::GTA_CAR_BUMPER_INCLUDE_TYPES;
// Decide if the bound is being raised or lowered.
if(fGroundClearance >= fFastGroundClearance)
{
nIncludeFlags &= ~ArchetypeFlags::GTA_RAGDOLL_TYPE;
nIncludeFlags &= ~ArchetypeFlags::GTA_HORSE_RAGDOLL_TYPE;
nIncludeFlags &= ~ArchetypeFlags::GTA_PED_TYPE;
}
else
{
//Assert(fGroundClearance <= fSlowGroundClearance);
if( ( pBumperColExtension && pBumperColExtension->GetBumpersNeedMapCollision() ) )
{
nIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES;
}
}
if( HasRamp() )
{
nIncludeFlags |= ArchetypeFlags::GTA_VEHICLE_BVH_TYPE;
nIncludeFlags |= ArchetypeFlags::GTA_VEHICLE_TYPE;
}
pBoundComp->SetIncludeFlags(iChild+k, nIncludeFlags);
}
}
}
} // Loop over bumpers.
}
//Clear vehicles flagged to raise bounds due to towing, if they are now back at regular clearance
if(m_nVehicleFlags.bTowedVehBoundsCanBeRaised && m_fGroundClearance == fSlowGroundClearance && !GetIsBeingTowed())
{
m_nVehicleFlags.bTowedVehBoundsCanBeRaised = false;
}
// check for and apply vehicle damage
PF_START(Damage);
float damageTimeStep = updateVehicleDamageEveryFrame ? fwTimer::GetTimeStep() : fFullUpdateTimeStep;
GetVehicleDamage()->Process(damageTimeStep, bBoundsNeedUpdating);
PF_STOP(Damage);
PF_START(Misc2);
ProcessSirenAndHorn(true) /*&& ! CCheat::IsCheatActive(CCheat::STRONGGRIP_CHEAT)*/;
if(GetStatus() == STATUS_PLAYER)
{
ProcessDirtLevel();
}
// Update the timer determining how dangerously the vehicle is being driven
UpdateDangerousDriverEvents(fFullUpdateTimeStep);
PF_STOP(Misc2);
}
else if(updateVehicleDamageEveryFrame)
{
const bool bBoundsNeedUpdating = false; // Hopefully this should be OK so we don't have to call UpdateDesiredGroundGroundClearance().
GetVehicleDamage()->Process(fwTimer::GetTimeStep(), bBoundsNeedUpdating);
}
// Don't allow hydraulic modifications and reset the tilt if a ped is trying to enter, exit or jack the vehicle (ie a vehicle seat has been reserved).
if (HasHydraulicSuspension())
{
m_bBlockDueToEnterExit = false;
const CModelSeatInfo* pModelSeatInfo = GetVehicleModelInfo() ? GetVehicleModelInfo()->GetModelSeatInfo() : NULL;
if (pModelSeatInfo && GetSeatManager())
{
for (int i = 0; i < pModelSeatInfo->GetNumSeats(); i++)
{
CPed* pPedInSeat = GetSeatManager()->GetPedInSeat(i);
CComponentReservationManager* pCompMgr = GetComponentReservationMgr();
CComponentReservation* pReservation = pCompMgr ? pCompMgr->GetSeatReservationFromSeat(i) : NULL;
// Don't allow hydraulic modification if a ped is trying to enter the vehicle (ie a vehicle seat has been reserved) or jack someone from it.
if (pReservation->IsComponentInUse() && (!pPedInSeat || pPedInSeat != pReservation->GetPedUsingComponent()))
{
m_bBlockDueToEnterExit = true;
}
// ... or if ped is exiting the vehicle
if (pPedInSeat && pPedInSeat->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE))
{
m_bBlockDueToEnterExit = true;
}
}
}
if (m_bBlockDueToEnterExit)
{
// Reset suspension height so it doesn't mess with enter/exit anims that much
for(int i=0; i < GetNumWheels(); i++)
{
CWheel* pWheel = GetWheel(i);
pWheel->SetSuspensionTargetRaiseAmount(0.0f,1.0f);
pWheel->SetSuspensionHydraulicTargetState(WHS_FREE);
}
ActivatePhysics();
CAutomobile* pAuto = static_cast<CAutomobile*>(this);
pAuto->SetTimeHyrdaulicsModified( fwTimer::GetTimeInMilliseconds() );
}
}
CVehicle::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
}//end of CAutomobile::ProcessControl()...
#if __BANK
void CAutomobile::ValidateDummyConstraints(Vec3V_In vVehPos, Vec3V_In vConstraintPos, float fConstraintLength)
{
if(!CVehicleAILodManager::ms_bValidateDummyConstraints)
{
return;
}
// Check the constraint and report if it is starting off unsatisfied
const ScalarV fConstraintLengthScalar(fConstraintLength);
const ScalarV fOverconstrainedThreshold(CVehicleAILodManager::ms_fValidateDummyConstraintsBuffer);
const ScalarV fVehDist = Dist(vVehPos,vConstraintPos);
if(IsGreaterThanAll(fVehDist-fConstraintLengthScalar,fOverconstrainedThreshold))
{
Warningf("Dummy constraint is too short for veh at (%0.2f,%0.2f), current LOD = %d, fVehDist = %0.2f, fConstraintLength = %0.2f",vVehPos.GetXf(),vVehPos.GetYf(),GetVehicleAiLod().GetDummyMode(),fVehDist.Getf(),fConstraintLength);
bool bCullingWasDisabled = grcDebugDraw::GetDisableCulling();
grcDebugDraw::SetDisableCulling(true);
const Vec3V vConstraintEnd = Lerp(fConstraintLengthScalar/fVehDist,vConstraintPos,vVehPos);
grcDebugDraw::Line(vConstraintPos,vConstraintEnd,Color_blue,-20);
grcDebugDraw::Line(vConstraintEnd,vVehPos,Color_red,-20);
grcDebugDraw::SetDisableCulling(bCullingWasDisabled);
}
}
#endif
float CAutomobile::GetMaxLengthForDummyConstraintsAfterFailedConversion(bool& bMaxShrinkage) const
{
//don't reduce limits when moving slowly as it causes the vehicle to noticably slide across the road
if (m_TimeStartedFailingRealConversion == 0 || m_fConstraintDistanceWhenRealConversionFailed < 0.0f || GetVelocity().Mag2() < 9.0f)
{
bMaxShrinkage = false;
return -1.0f;
}
const u32 nCurrentTime = fwTimer::GetTimeInMilliseconds();
const dev_float s_fSpeedToReduceConstraints = 0.5f; //in m/s
const float fTimeDelta = (nCurrentTime - m_TimeStartedFailingRealConversion) * 0.001f;
const float fReduceAmount = fTimeDelta * s_fSpeedToReduceConstraints;
const float fMaxLength = rage::Max(GetHeightAboveRoad()+0.1f, m_fConstraintDistanceWhenRealConversionFailed - fReduceAmount);
//have we gone as far as we can go?
bMaxShrinkage = fReduceAmount >= m_fConstraintDistanceWhenRealConversionFailed;
AI_LOG_WITH_ARGS("VEHICLE DUMMY: %s Shrinking constraint limits by %.3f. %.3f -> %.3f. At max: %s\n", AILogging::GetEntityLocalityNameAndPointer(this).c_str(), fReduceAmount,
m_fConstraintDistanceWhenRealConversionFailed, m_fConstraintDistanceWhenRealConversionFailed - fReduceAmount, bMaxShrinkage ? "TRUE" : "FALSE");
return fMaxLength;
}
void CAutomobile::ProcessTimeStartedFailingRealConversion()
{
//if this is unset, register that we failed here.
//if it's set, we want to keep the older time, so we
//can shorten the constraints the appropriate amount
if (m_TimeStartedFailingRealConversion == 0)
{
SetStartedFailingRealConversion();
}
}
void CAutomobile::SetStartedFailingRealConversion()
{
//set the time
m_TimeStartedFailingRealConversion = fwTimer::GetTimeInMilliseconds();
//now measure the distance from our constraint and save that off
phConstraintDistance * pConstraint = (phConstraintDistance*) CPhysics::GetConstraintManager()->GetTemporaryPointer(m_dummyStayOnRoadConstraintHandle);
if(pConstraint)
{
const Vec3V& posA = pConstraint->GetWorldPosA(); //the vehicle
const Vec3V& posB = pConstraint->GetWorldPosB(); //the road
const ScalarV sfDistance = Dist(posA, posB);
m_fConstraintDistanceWhenRealConversionFailed = sfDistance.Getf();
}
else
{
m_fConstraintDistanceWhenRealConversionFailed = -1.0f;
}
}
void CAutomobile::UpdateDummyConstraints(const Vec3V * pDistanceConstraintPos, const float * pDistanceConstraintMaxLength)
{
if (IsNetworkClone())
{
ChangeDummyConstraints(VDM_REAL, false);
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
//--------------------------------------------------------
// If using physical distance constraint, update it here
if(CVehicleAILodManager::ms_bDisableConstraintsForSuperDummyInactive && GetVehicleAiLod().GetDummyMode()==VDM_SUPERDUMMY && GetIsStatic())
{
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
const bool bDummyParentIsTrailer = m_nVehicleFlags.bHasParentVehicle && GetDummyAttachmentParent() && GetDummyAttachmentParent()->InheritsFromTrailer();
const bool bRealParentIsTrailer = m_nVehicleFlags.bHasParentVehicle && CVehicle::IsEntityAttachedToTrailer(this);
if (bDummyParentIsTrailer || bRealParentIsTrailer)
{
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
if(InheritsFromTrailer())
{
// Don't constrain trailers to the road. They don't have their own path, and if the parent's path is used it
// might be improperly constrained. Trailers are attached to the parent vehicle and that should be enough.
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
eVehicleDummyMode dummyMode = GetVehicleAiLod().GetDummyMode();
const bool bUseDistanceConstraint =
(dummyMode==VDM_DUMMY &&
(CVehicleAILodManager::GetDummyConstraintMode() == CVehicleAILodManager::DCM_PhysicalDistance ||
CVehicleAILodManager::GetDummyConstraintMode() == CVehicleAILodManager::DCM_PhysicalDistanceAndRotation))
|| (dummyMode==VDM_SUPERDUMMY &&
(CVehicleAILodManager::GetSuperDummyConstraintMode() == CVehicleAILodManager::DCM_PhysicalDistance ||
CVehicleAILodManager::GetSuperDummyConstraintMode() == CVehicleAILodManager::DCM_PhysicalDistanceAndRotation) );
if(!bUseDistanceConstraint)
{
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
// If we don't have a dummy constraint, then try to create it.
if(!m_dummyStayOnRoadConstraintHandle.IsValid())
{
ChangeDummyConstraints(dummyMode,GetIsStatic());
if(!m_dummyStayOnRoadConstraintHandle.IsValid())
{
// TODO: We may need to handle this situation differently. Find out when this is happening and make sure that
// it is being handled gracefully. E.g. If just dead cars, then they should fall physically or artificially.
return;
}
}
// Constraint is valid and desired to be used.
Vec3V vConstraintPos;
float constraintMaxLength;
bool validConstraintFound = (pDistanceConstraintPos && pDistanceConstraintPos);
if(!validConstraintFound)
{
const CVehicle* pVehicleConstraintsToUse = this;
if (InheritsFromTrailer() && m_nVehicleFlags.bHasParentVehicle)
{
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
if (pAttachParent && pAttachParent->GetIsTypeVehicle())
{
pVehicleConstraintsToUse = (CVehicle*)pAttachParent;
}
else if (GetDummyAttachmentParent())
{
pVehicleConstraintsToUse = GetDummyAttachmentParent();
}
}
validConstraintFound = CVirtualRoad::CalculatePathInfo(*pVehicleConstraintsToUse,false,false,vConstraintPos,constraintMaxLength, false);
}
else
{
vConstraintPos = *pDistanceConstraintPos;
constraintMaxLength = *pDistanceConstraintMaxLength;
}
if(validConstraintFound)
{
phConstraintDistance * pConstraint = (phConstraintDistance*) CPhysics::GetConstraintManager()->GetTemporaryPointer(m_dummyStayOnRoadConstraintHandle);
if(pConstraint)
{
const Vec3V vVehPos = GetTransform().GetPosition();
pConstraint->SetWorldPosA(vVehPos);
pConstraint->SetWorldPosB(vConstraintPos);
#if __BANK
ValidateDummyConstraints(vVehPos,vConstraintPos,constraintMaxLength);
// If validating constraints, add a buffer to the actual physical constraint to allow the vehicle to exist outside of the bounds while the situation is debugged.
constraintMaxLength += (CVehicleAILodManager::ms_bValidateDummyConstraints) ? 10.0f : 0.0f;
#endif
//if we're reducing the constraint due to failed conversions, do so here
bool bMaxShrinkage = false;
const float fMaxLengthAfterFailedDummyConvert = GetMaxLengthForDummyConstraintsAfterFailedConversion(bMaxShrinkage);
if (fMaxLengthAfterFailedDummyConvert >= 0.0f)
{
constraintMaxLength = rage::Min(constraintMaxLength, fMaxLengthAfterFailedDummyConvert);
//ensure we don't set a distance that'll make us assert straight away
float distToConstraint = Dist(vVehPos, vConstraintPos).Getf();
float minAllowableLength = distToConstraint - PH_CONSTRAINT_ASSERT_DEPTH + 0.25f;
constraintMaxLength = rage::Max(minAllowableLength, constraintMaxLength);
}
pConstraint->SetMaxDistance(constraintMaxLength);
}
}
}
bool CAutomobile::RemoveConstraints()
{
bool bRemovedConstraint = false;
// Clear any stay-on-road constraint.
if(m_dummyStayOnRoadConstraintHandle.IsValid())
{
PHCONSTRAINT->Remove(m_dummyStayOnRoadConstraintHandle);
m_dummyStayOnRoadConstraintHandle.Reset();
bRemovedConstraint = true;
}
// Clear any rotational constraint.
if(m_dummyRotationalConstraint.IsValid())
{
PHCONSTRAINT->Remove(m_dummyRotationalConstraint);
m_dummyRotationalConstraint.Reset();
bRemovedConstraint = true;
}
ResetRealConversionFailedData();
m_nVehicleFlags.bWasOffroadWithConstraint = false;
return bRemovedConstraint;
}
#if __BANK
Vector3 vDummyWheelForces[NUM_VEH_CWHEELS_MAX];
Vector3 vDummyWheelForcesInPlane[NUM_VEH_CWHEELS_MAX];
Vector3 vDummyForce;
Vector3 vDummyForceInPlane;
Vector3 vDummyAngVel;
void CAutomobile::RenderDebug() const
{
CVehicle::RenderDebug();
// Health Info for player car
if(CVehicle::ms_nVehicleDebug==VEH_DEBUG_DAMAGE && (GetStatus()==STATUS_PLAYER || this == CDebugScene::FocusEntities_Get(0)) )
{
char vehDebugString[1024];
grcDebugDraw::AddDebugOutput("");
sprintf(vehDebugString,"Overall %.2f", m_VehicleDamage.GetOverallHealth());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"Body %.2f", m_VehicleDamage.GetBodyHealth());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"Engine %.2f", m_VehicleDamage.GetEngineHealth());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"Oil Level %.2f", m_VehicleDamage.GetOilLevel());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"PetrolTank %.2f", m_VehicleDamage.GetPetrolTankHealth());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"PetrolTank Level %.2f", m_VehicleDamage.GetPetrolTankLevel());
grcDebugDraw::AddDebugOutput(vehDebugString);
if(m_Transmission.GetCurrentlyMissFiring())
{
sprintf(vehDebugString,"MISS FIRE!!");
grcDebugDraw::AddDebugOutput(vehDebugString);
}
else if(m_Transmission.GetCurrentlyRecoveringFromMissFiring())
{
sprintf(vehDebugString,"RECOVERING MISS FIRE!!");
grcDebugDraw::AddDebugOutput(vehDebugString);
}
for(int nWheel=0; nWheel < GetNumWheels(); nWheel++)
{
if(GetWheel(nWheel))
{
sprintf(vehDebugString,"Wheel %d %.2f, %.2f, temp: %.2f, tyre drag: %.2f", nWheel, GetWheel(nWheel)->GetSuspensionHealth(), GetWheel(nWheel)->GetFrictionDamage(), GetWheel(nWheel)->GetTyreTemp(), GetWheel(nWheel)->GetTyreDrag());
grcDebugDraw::AddDebugOutput(vehDebugString);
}
}
}
// draw a speedo for player car
if((CVehicle::ms_nVehicleDebug==VEH_DEBUG_PERFORMANCE || CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
&& ((GetDriver() && GetDriver()->IsLocalPlayer()) || this == CDebugScene::FocusEntities_Get(0)))
{
char vehDebugString[1024];
float fGroundClearance = 10.0f;
Vector3 vertPos;
phBoundComposite* pBoundComp = GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds();
for(int nBound=0; nBound < pBoundComp->GetNumBounds(); nBound++)
{
if(pBoundComp->GetBound(nBound) && pBoundComp->GetBound(nBound)->GetType()==phBound::GEOMETRY)
{
phBoundGeometry* pBoundGeom = static_cast<phBoundGeometry*>(pBoundComp->GetBound(nBound));
const Matrix34& boundMatrix = RCC_MATRIX34(pBoundComp->GetCurrentMatrix(nBound));
for(int nVert=0; nVert < pBoundGeom->GetNumVertices(); nVert++)
{
// want to deform the shrunk geometry vertices, ignore the preshrunk ones since we want shape tests to hit the original bike
if(pBoundGeom->GetShrunkVertexPointer())
{
vertPos.Set(VEC3V_TO_VECTOR3(pBoundGeom->GetShrunkVertex(nVert)));
vertPos += boundMatrix.d;
if(vertPos.z - pBoundGeom->GetMargin() < fGroundClearance)
fGroundClearance = vertPos.z - pBoundGeom->GetMargin();
}
}
}
}
fGroundClearance = fGroundClearance + GetHeightAboveRoad();
//float fGroundClearance = GetHeightAboveRoad() + GetVehicleFragInst()->GetType()->GetCompositeBounds()->GetBound(0)->GetBoundingBoxMin().z;
sprintf(vehDebugString, "Mass=%g Inertia=%1.1f, %1.1f, %1.1f GroundClearance(%1.3f)", GetMass(), GetAngInertia().GetX(), GetAngInertia().GetY(), GetAngInertia().GetZ(), fGroundClearance);
grcDebugDraw::AddDebugOutput(vehDebugString);
if(GetNumWheels() >= 3)
{
float fSuspensionWidth = GetWheel(2)->GetProbeSegment().A.x - GetWheel(0)->GetProbeSegment().A.x + GetWheel(0)->GetWheelWidth();
float fBaseSFF = 0.5f*fSuspensionWidth / GetHeightAboveRoad();
float fHandlingSFF = 0.5f*fSuspensionWidth / (GetHeightAboveRoad() + pHandling->m_vecCentreOfMassOffset.GetZf());
sprintf(vehDebugString, "SFF: Model(%1.3f) With COG Moved(%1.3f)", fBaseSFF, fHandlingSFF);
grcDebugDraw::AddDebugOutput(vehDebugString);
float fBaseWeightDistribution = 100.0f*GetWheel(0)->GetProbeSegment().A.y / (GetWheel(0)->GetProbeSegment().A.y - GetWheel(1)->GetProbeSegment().A.y);
float fHandlingWeightDistribution = 100.0f*(GetWheel(0)->GetProbeSegment().A.y - pHandling->m_vecCentreOfMassOffset.GetYf()) / (GetWheel(0)->GetProbeSegment().A.y - GetWheel(1)->GetProbeSegment().A.y);
sprintf(vehDebugString, "Weight Balance %2.1f:%2.1f With COG Moved(%2.1f:%2.1f)", (100.0f - fBaseWeightDistribution), fBaseWeightDistribution, (100.0f - fHandlingWeightDistribution), fHandlingWeightDistribution);
grcDebugDraw::AddDebugOutput(vehDebugString);
float fMinSusLength = 0.25f / pHandling->m_fSuspensionForce;
sprintf(vehDebugString, "Min sus extension %1.3f (%1.3f)", -fMinSusLength, pHandling->m_fSuspensionLowerLimit);
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString, "Current sus extension F=%1.3f, R=%1.3f", GetWheel(0)->GetCompression(), GetWheel(2)->GetCompression());
grcDebugDraw::AddDebugOutput(vehDebugString);
}
sprintf(vehDebugString, "Cheat power Increase=%1.3f", 1.0f - GetCheatPowerIncrease());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString, "MODS: Engine power Increase=%1.3f", (1.0f + (CVehicle::ms_fEngineVarianceMaxModifier * (GetVariationInstance().GetEngineModifier()/100.0f))) );
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString, "MODS: Brake Force Increase=%1.3f", ((ms_fBrakeVarianceMaxModifier * GetVariationInstance().GetBrakesModifier()/100.0f)));
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString, "MODS: Armour damage multiplier=%1.3f", GetVariationInstance().GetArmourDamageMultiplier());
grcDebugDraw::AddDebugOutput(vehDebugString);
// NOTE: need to const_cast here because it uses GetCarHandlingData() which doesn't have a const variant, so easiest thing to do is just const_cast for this function even though GetTurboPowerModifier doesn't modify any state.
sprintf(vehDebugString,"MODS: Turbo power Increase=%1.3f", GetVariationInstance().IsToggleModOn(VMT_TURBO) ? CTransmission::GetTurboPowerModifier(const_cast<CAutomobile*>(this)) : 0.0f);
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"MODS: Gearbox Increase=%1.3f", GetVariationInstance().GetGearboxModifier()/100.0f);
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf(vehDebugString,"KERS=%1.3f", m_Transmission.GetKERSRemaining());
grcDebugDraw::AddDebugOutput(vehDebugString);
sprintf( vehDebugString, "WheelieMode=%d", (int)m_nAutomobileFlags.bInWheelieMode );
grcDebugDraw::AddDebugOutput( vehDebugString );
phArchetypeDamp* pCurrentArchetype = (phArchetypeDamp*)GetCurrentPhysicsInst()->GetArchetype();
formatf(vehDebugString,512, "LINEAR_C %2.5f, %2.5f, %2.5f", pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_C).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_C).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_C).z);
grcDebugDraw::AddDebugOutput(vehDebugString);
formatf(vehDebugString,512, "LINEAR_V %2.5f, %2.5f, %2.5f", pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V).z);
grcDebugDraw::AddDebugOutput(vehDebugString);
if(GetDriver() && GetDriver()->IsPlayer())
{
float fScriptedDragMult = 0.0f;
CPlayerInfo *pPlayer = GetDriver()->GetPlayerInfo();
if (pPlayer && pPlayer->m_fForceAirDragMult > 0.0f)
{
fScriptedDragMult = pPlayer->m_fForceAirDragMult;
}
bool bIsActive = pPlayer->m_fForceAirDragMult > 0.0f && pPlayer->m_fForceAirDragMult != 1.0f;
formatf(vehDebugString,512, "LINEAR_V2 %2.5f, %2.5f, %2.5f (DragCoeff %2.5f, ScriptDragMult %2.5f, ScriptedDrag %s SlipstreamT: %f SlipstreamAbsT %f)",
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V2).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V2).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V2).z,
m_fDragCoeff, fScriptedDragMult,
bIsActive ? "YES" : "NO",
m_fTimeInSlipStream,
m_fSlipStreamRechargeAndDechargeTimer);
}
else
{
formatf(vehDebugString,512, "LINEAR_V2 %2.5f, %2.5f, %2.5f", pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V2).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V2).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::LINEAR_V2).z);
}
grcDebugDraw::AddDebugOutput(vehDebugString);
formatf(vehDebugString,512, "ANGULAR_C %2.5f, %2.5f, %2.5f", pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_C).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_C).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_C).z);
grcDebugDraw::AddDebugOutput(vehDebugString);
formatf(vehDebugString,512, "ANGULAR_V %2.5f, %2.5f, %2.5f", pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_V).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_V).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_V).z);
grcDebugDraw::AddDebugOutput(vehDebugString);
formatf(vehDebugString,512, "ANGULAR_V2 %2.5f, %2.5f, %2.5f", pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_V2).x,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_V2).y,
pCurrentArchetype->GetDampingConstant(phArchetypeDamp::ANGULAR_V2).z);
grcDebugDraw::AddDebugOutput(vehDebugString);
static u16 TEST_STUCK_CHECK = 10000;
if(GetVehicleDamage()->GetIsStuck(VEH_STUCK_JAMMED, TEST_STUCK_CHECK))
{
grcDebugDraw::AddDebugOutput("Stuck - Jammed");
}
else
{
sprintf(vehDebugString,"Stuck - Jammed Timer %1.2f", GetVehicleDamage()->GetStuckTimer(VEH_STUCK_JAMMED));
grcDebugDraw::AddDebugOutput(vehDebugString);
}
if(GetVehicleDamage()->GetIsStuck(VEH_STUCK_ON_SIDE, TEST_STUCK_CHECK))
{
grcDebugDraw::AddDebugOutput("Stuck - OnSide");
}
else
{
sprintf(vehDebugString,"Stuck - OnSide Timer %1.2f", GetVehicleDamage()->GetStuckTimer(VEH_STUCK_ON_SIDE));
grcDebugDraw::AddDebugOutput(vehDebugString);
}
if(GetVehicleDamage()->GetIsStuck(VEH_STUCK_ON_ROOF, TEST_STUCK_CHECK))
{
grcDebugDraw::AddDebugOutput("Stuck - OnRoof");
}
else
{
sprintf(vehDebugString,"Stuck - OnRoof Timer %1.2f", GetVehicleDamage()->GetStuckTimer(VEH_STUCK_ON_ROOF));
grcDebugDraw::AddDebugOutput(vehDebugString);
}
if(GetVehicleDamage()->GetIsStuck(VEH_STUCK_HUNG_UP, TEST_STUCK_CHECK))
{
grcDebugDraw::AddDebugOutput("Stuck - HungUp");
}
else
{
int bDriveWheelOnGround = 0;
for(int i=0; i<GetNumWheels(); i++)
{
if(GetWheel(i)->GetConfigFlags().IsFlagSet(WCF_POWERED) && (GetWheel(i)->GetIsTouching() || GetWheel(i)->GetWasTouching()) )
bDriveWheelOnGround++;
}
sprintf(vehDebugString,"Stuck - HungUp Timer %1.2f Wheels on ground %d", GetVehicleDamage()->GetStuckTimer(VEH_STUCK_HUNG_UP), bDriveWheelOnGround);
grcDebugDraw::AddDebugOutput(vehDebugString);
}
grcDebugDraw::AddDebugOutput("");
// Draw handbrake
if(GetHandBrake())
{
static float x = 0.75f;
static float y = 0.75f;
static float fLength = 0.2f;
float fHandbrakeForce = GetHandBrakeForce() / pHandling->m_fHandBrakeForce;
char strTitle[32];
formatf(strTitle, "Handbrake: %.2f", fHandbrakeForce);
grcDebugDraw::Meter(Vector2(x,y), Vector2(0.0f, -1.0f), fLength, 0.05f * fLength, Color_red, strTitle);
grcDebugDraw::MeterValue(Vector2(x,y), Vector2(0.0f, -1.0f), fLength, fHandbrakeForce, 0.05f * fLength, Color_white, true);
}
// Draw handbrake grip fall off
if(GetHandBrakeGripMult() != 1.0f)
{
static float x = 0.65f;
static float y = 0.75f;
static float fLength = 0.2f;
float fHandbrakeForce = GetHandBrakeGripMult();
char strTitle[32];
formatf(strTitle, "Handbrake grip: %.2f", fHandbrakeForce);
grcDebugDraw::Meter(Vector2(x,y), Vector2(0.0f, -1.0f), fLength, 0.05f * fLength, Color_red, strTitle);
grcDebugDraw::MeterValue(Vector2(x,y), Vector2(0.0f, -1.0f), fLength, fHandbrakeForce, 0.05f * fLength, Color_white, true);
}
if(CVehicle::ms_nVehicleDebug==VEH_DEBUG_PERFORMANCE)
{
DrawGForce();
DrawAngularAcceleration();
}
}
bool displayDebugInfo = (!CVehicleIntelligence::ms_debugDisplayFocusVehOnly || CDebugScene::FocusEntities_IsInGroup(this));
if(displayDebugInfo)
{
if(CVehicleAILodManager::ms_displayDummyVehicleMarkers)
{
if(IsDummy())
{
// Draw our position.
const Vector3 pos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Color32 col = m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy) ? Color_DarkOrchid1 : Color_SeaGreen1;
grcDebugDraw::Line(pos, Vector3(pos.x, pos.y,pos.z + 4.0f), col);
}
}
#if __DEV
if(CVehicleAILodManager::ms_displayDummyVehicleMarkers)
{
if(IsDummy())
{
const Vector3 pos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
grcDebugDraw::Sphere(Vector3(pos.x, pos.y,pos.z + 4.0f), 0.3f, Color_green, true, -1, 3);
}
}
if(CVehiclePopulation::ms_displayVehicleDirAndPath)
{
// Draw our direction.
const Vector3 pos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
const Vector3 dir = VEC3V_TO_VECTOR3(GetTransform().GetB());
grcDebugDraw::Line(pos,(pos+(dir*4.0f)),Color32(0xff,0xc0,0xc0,0xff));
CVehicleNodeList * pNodeList = GetIntelligence()->GetNodeList();
if(pNodeList)
{
// Draw our current path.
// Go through all of the path segments and render them.
for (s32 i = 0; i < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-1; i++)
{
const CNodeAddress addr1 = pNodeList->GetPathNodeAddr(i);
const CNodeAddress addr2 = pNodeList->GetPathNodeAddr(i+1);
if( !addr1.IsEmpty() && ThePaths.IsRegionLoaded(addr1) &&
!addr2.IsEmpty() && ThePaths.IsRegionLoaded(addr2))
{
Vector3 node1Coors;
const CPathNode* pNode1 = ThePaths.FindNodePointer(addr1);
Assert(pNode1);
pNode1->GetCoors(node1Coors);
static float radius = 0.5f;
grcDebugDraw::Sphere(node1Coors, radius, Color32(1.0f,1.0f,0.0f,0.5f));
Vector3 node2Coors;
const CPathNode* pNode2 = ThePaths.FindNodePointer(addr2);
Assert(pNode2);
pNode2->GetCoors(node2Coors);
static float radius2 = 0.25f;
grcDebugDraw::Sphere(node2Coors, radius2, Color32(0.0f,1.0f,1.0f,1.0f));
const float p1 = static_cast<float>(i) / static_cast<float>(CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-1);
const float p2 = static_cast<float>(i+1) / static_cast<float>(CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED-1);
grcDebugDraw::Line(node1Coors, node2Coors, Color32(0.0f,1.0f-p1,p1,1.0f),Color32(0.0f,1.0f-p2,p2,1.0f));
}
}
}
}
#endif // __DEV
#if __BANK && DEBUG_DRAW
if(CVehicleAILodManager::ms_displayDummyVehicleNonConvertReason)
{
if(!IsDummy())
{
// only want to do text for vehicles close to camera
// Set origin to be the debug cam, or the player..
//camDebugDirector& debugDirector = camInterface::GetDebugDirector();
//Vector3 vOrigin = debugDirector.IsFreeCamActive() ? debugDirector.GetFreeCamFrame().GetPosition() : VEC3V_TO_VECTOR3(FindPlayerPed()->GetTransform().GetPosition());
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 vDiff = vThisPosition - camInterface::GetPos();
float fDist = vDiff.Mag();
const float vehicleVisualizationRange = 60.0f;
if(fDist < vehicleVisualizationRange)
{
Vector3 WorldCoors = vThisPosition + Vector3(0,0,1.0f);
float fScale = 1.0f - (fDist / vehicleVisualizationRange);
u8 iAlpha = (u8)Clamp((int)(255.0f * fScale), 0, 255);
Color32 colour = CRGBA(255,192,96,iAlpha);
char vehDebugString[1024];
sprintf(vehDebugString,"Nonconvert Reason: %s", GetNonConversionReason());
grcDebugDraw::Text( WorldCoors, colour, vehDebugString);
}
}
}
#endif // __BANK && DEBUG_DRAW
}
}
void CAutomobile::DrawGForce() const
{
#if __DEV
if(GetCollider() && ((GetDriver() && GetDriver()->IsLocalPlayer()) || this == CDebugScene::FocusEntities_Get(0))) // This will only work for one vehicle at a time as I'm using a static.
{
static Vec3V vPreviousVelcity; // Static to hold previous velocity as we are in a const function
Color32 drawColour(1.0f, 1.0f, 0.0f, 1.0f);
Color32 drawGreen(0.0f, 1.0f, 0.0f, 0.5f);
Color32 drawRed(255,0,0,255);
static const float fScreenWidth = 1.0f;
static const float fScreenHeight = 1.0f;
float fLineWidthMult = 0.04f;
float fLineHeightMult = 0.04f;
float fIndicatorWidthMult = 0.01f;
float fIndicatorHeightMult = 0.01f;
Vector2 vecStart(0.35f, 0.8f);
vecStart.x *= fScreenWidth;
vecStart.y *= fScreenHeight;
const int iExpiryTime = (fwTimer::GetTimeStepInMilliseconds() * 3) / 2;
Vec3V vCurrentVelocity = GetCollider()->GetVelocity();
Vec3V vAcceleration = InvScale(Subtract(vCurrentVelocity, vPreviousVelcity), ScalarV(fwTimer::GetTimeStep()));
vPreviousVelcity = vCurrentVelocity;
Vec3V vGForce = InvScale(vAcceleration, ScalarV(-GRAVITY)); // hard coded gravity as we are interested in seeing gforce with regards to Earth.
vGForce = UnTransform3x3Full(GetCollider()->GetMatrix(), vGForce);
float fEndPosX = fLineHeightMult * fScreenWidth;
float fPeakPosX = fLineHeightMult * fScreenWidth;
float fEndPosY = fLineWidthMult * fScreenHeight;
float fPeakPosY = fLineWidthMult * fScreenHeight;
static float sfGforceScale = 0.5f;
float fCurrentPosX = vGForce.GetXf() * fLineHeightMult * fScreenHeight * sfGforceScale;
fCurrentPosX = Clamp(fCurrentPosX, -fEndPosX*2.0f, fEndPosX*2.0f);
float fCurrentPosY = vGForce.GetYf() * fLineWidthMult * fScreenHeight;
fCurrentPosY = Clamp(fCurrentPosY, -fEndPosY*2.0f, fEndPosY*2.0f);
// Draw Vertical lines
{
float fBoxHeight = fLineHeightMult * fScreenHeight;
float fMarkerWidth = fIndicatorHeightMult * fScreenHeight;
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fPeakPosX * 0.5f, vecStart.y - fBoxHeight * 0.5f),Vector2( vecStart.x + fPeakPosX * 0.5f, vecStart.y + fBoxHeight * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fPeakPosX * 0.5f, vecStart.y - fBoxHeight * 0.5f),Vector2( vecStart.x - fPeakPosX * 0.5f, vecStart.y + fBoxHeight * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fPeakPosX, vecStart.y - fBoxHeight),Vector2( vecStart.x + fPeakPosX, vecStart.y + fBoxHeight), drawColour,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fPeakPosX, vecStart.y - fBoxHeight),Vector2( vecStart.x - fPeakPosX, vecStart.y + fBoxHeight), drawColour,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fCurrentPosX, vecStart.y + fCurrentPosY - fMarkerWidth),Vector2( vecStart.x + fCurrentPosX, vecStart.y + fCurrentPosY + fMarkerWidth), drawRed,iExpiryTime);
}
// Draw Horizontal lines
{
float fBoxWidth = fLineWidthMult * fScreenWidth;
float fMarkerWidth = fIndicatorWidthMult * fScreenWidth;
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth * 0.5f, vecStart.y + fPeakPosY * 0.5f),Vector2( vecStart.x + fBoxWidth * 0.5f, vecStart.y + fPeakPosY * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth * 0.5f, vecStart.y - fPeakPosY * 0.5f),Vector2( vecStart.x + fBoxWidth * 0.5f, vecStart.y - fPeakPosY * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth, vecStart.y + fPeakPosY),Vector2( vecStart.x + fBoxWidth, vecStart.y + fPeakPosY), drawColour, iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth, vecStart.y - fPeakPosY),Vector2( vecStart.x + fBoxWidth, vecStart.y - fPeakPosY), drawColour, iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fCurrentPosX - fMarkerWidth, vecStart.y + fCurrentPosY),Vector2( vecStart.x + fCurrentPosX + fMarkerWidth, vecStart.y + fCurrentPosY), drawRed,iExpiryTime);
}
// Turn Radius
Vec3V vCurrentAngVelocity = GetCollider()->GetAngVelocity();
Vec3V vRadiusVel = vCurrentVelocity / vCurrentAngVelocity;
if(ABS(vRadiusVel.GetZf()) > 2.0f*PI) // clamp the radius.
{
vRadiusVel.SetZf(0.0f);
}
char vehDebugString[1024];
sprintf(vehDebugString,"Turn Radius %.2f Degrees %.2f", vRadiusVel.GetZf(), RADIANS_TO_DEGREES(vRadiusVel.GetZf()));
grcDebugDraw::AddDebugOutput(vehDebugString);
float fGforceMag = Mag(vGForce).Getf();
if(fGforceMag > CAutomobile::ms_fHighestGForce)
{
CAutomobile::ms_fHighestGForce = fGforceMag;
}
sprintf(vehDebugString,"G force Current %.2f Max %.2f", fGforceMag, CAutomobile::ms_fHighestGForce);
grcDebugDraw::AddDebugOutput(vehDebugString);
}
#endif
}
void CAutomobile::DrawAngularAcceleration() const
{
#if __DEV
if(GetCollider() && ((GetDriver() && GetDriver()->IsLocalPlayer()) || this == CDebugScene::FocusEntities_Get(0))) // This will only work for one vehicle at a time as I'm using a static.
{
static Vec3V vPreviousAngVelcity; // Static to hold previous velocity as we are in a const function
Color32 drawColour(1.0f, 1.0f, 0.0f, 1.0f);
Color32 drawGreen(0.0f, 1.0f, 0.0f, 0.5f);
Color32 drawRed(255,0,0,255);
static const float fScreenWidth = 1.0f;
static const float fScreenHeight = 1.0f;
float fLineWidthMult = 0.04f;
float fLineHeightMult = 0.04f;
float fIndicatorWidthMult = 0.01f;
float fIndicatorHeightMult = 0.01f;
Vector2 vecStart(0.75f, 0.8f);
vecStart.x *= fScreenWidth;
vecStart.y *= fScreenHeight;
const int iExpiryTime = (fwTimer::GetTimeStepInMilliseconds() * 3) / 2;
Vec3V vCurrentAngVelocity = GetCollider()->GetAngVelocity();
Vec3V vAngAcceleration = vCurrentAngVelocity;//InvScale(Subtract(vCurrentAngVelocity, vPreviousAngVelcity), ScalarV(fwTimer::GetTimeStep()));
vPreviousAngVelcity = vCurrentAngVelocity;
vAngAcceleration = UnTransform3x3Full(GetCollider()->GetMatrix(), vAngAcceleration);
float fEndPosX = fLineHeightMult * fScreenWidth;
float fPeakPosX = fLineHeightMult * fScreenWidth;
float fEndPosY = fLineWidthMult * fScreenHeight;
float fPeakPosY = fLineWidthMult * fScreenHeight;
static float sfGforceScale = 0.5f;
float fCurrentPosX = vAngAcceleration.GetYf() * fLineHeightMult * fScreenHeight * sfGforceScale;
fCurrentPosX = Clamp(fCurrentPosX, -fEndPosX*2.0f, fEndPosX*2.0f);
float fCurrentPosY = vAngAcceleration.GetXf() * fLineWidthMult * fScreenHeight;
fCurrentPosY = Clamp(fCurrentPosY, -fEndPosY*2.0f, fEndPosY*2.0f);
// Draw Vertical anglines
{
float fBoxHeight = fLineHeightMult * fScreenHeight;
float fMarkerWidth = fIndicatorHeightMult * fScreenHeight;
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fPeakPosX * 0.5f, vecStart.y - fBoxHeight * 0.5f),Vector2( vecStart.x + fPeakPosX * 0.5f, vecStart.y + fBoxHeight * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fPeakPosX * 0.5f, vecStart.y - fBoxHeight * 0.5f),Vector2( vecStart.x - fPeakPosX * 0.5f, vecStart.y + fBoxHeight * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fPeakPosX, vecStart.y - fBoxHeight),Vector2( vecStart.x + fPeakPosX, vecStart.y + fBoxHeight), drawColour,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fPeakPosX, vecStart.y - fBoxHeight),Vector2( vecStart.x - fPeakPosX, vecStart.y + fBoxHeight), drawColour,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fCurrentPosX, vecStart.y + fCurrentPosY - fMarkerWidth),Vector2( vecStart.x + fCurrentPosX, vecStart.y + fCurrentPosY + fMarkerWidth), drawRed,iExpiryTime);
}
// Draw Horizontal lines
{
float fBoxWidth = fLineWidthMult * fScreenWidth;
float fMarkerWidth = fIndicatorWidthMult * fScreenWidth;
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth * 0.5f, vecStart.y + fPeakPosY * 0.5f),Vector2( vecStart.x + fBoxWidth * 0.5f, vecStart.y + fPeakPosY * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth * 0.5f, vecStart.y - fPeakPosY * 0.5f),Vector2( vecStart.x + fBoxWidth * 0.5f, vecStart.y - fPeakPosY * 0.5f), drawGreen,iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth, vecStart.y + fPeakPosY),Vector2( vecStart.x + fBoxWidth, vecStart.y + fPeakPosY), drawColour, iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x - fBoxWidth, vecStart.y - fPeakPosY),Vector2( vecStart.x + fBoxWidth, vecStart.y - fPeakPosY), drawColour, iExpiryTime);
CPhysics::ms_debugDrawStore.AddLine(Vector2(vecStart.x + fCurrentPosX - fMarkerWidth, vecStart.y + fCurrentPosY),Vector2( vecStart.x + fCurrentPosX + fMarkerWidth, vecStart.y + fCurrentPosY), drawRed,iExpiryTime);
}
char vehDebugString[1024];
sprintf(vehDebugString,"Ang Acceleration Current %.2f", vAngAcceleration.GetXf());
grcDebugDraw::AddDebugOutput(vehDebugString);
}
#endif
}
u32 CAutomobile::GetAsyncQueueCount()
{
u32 count = 0;
for (s32 i = 0; i < ms_placeOnRoadArray.GetCount(); ++i)
if (ms_placeOnRoadArray[i].id != INVALID_ASYNC_ENTRY)
count++;
return count;
}
#endif // __BANK
RAGETRACE_DECL(CAutomobile_ProcessPhysics);
RAGETRACE_DECL(CAuto_Phys_Transmission);
RAGETRACE_DECL(CAuto_Phys_Doors);
RAGETRACE_DECL(CAuto_Phys_Bouyancy);
RAGETRACE_DECL(CAuto_Phys_Wheels);
RAGETRACE_DECL(CAuto_Phys_FlyingCar);
RAGETRACE_DECL(CAuto_Phys_DummyConstraints);
#define TRACK_VEHICLE_GRADIENT (0)
#if TRACK_VEHICLE_GRADIENT
PF_PAGE(vehicleGradientPage, "Vehicle gradient group");
PF_GROUP(vehicleGradientGroup);
PF_LINK(vehicleGradientPage, vehicleGradientGroup);
PF_VALUE_FLOAT(vehicleGradientDeg,vehicleGradientGroup);
#endif
dev_float sfPlayerSpeedSteerMult = 0.075f;
dev_float sfPlayerSpeedSteerFwdThreshold = 5.0f;
dev_float sfPlayerSpeedSteerSideRatioThreshold = 0.1f;
dev_bool sbPlayerSpeedSteerAutoCentre = true;
dev_float sfPlayerSpeedSteerAutoCentreMax = ( DtoR * 15.0f);
void CAutomobile::Fix(bool resetFrag, bool allowNetwork)
{
CVehicle::Fix(resetFrag, allowNetwork);
//reset the ground clearance, as the bounds will have been reset by the damage code
m_fGroundClearance = 0.0f;
m_fLastGroundClearance = 0.0f;
m_fLastGroundClearanceHeightAboveGround = 0.0f;
m_uLastGroundClearanceCheck = 0;
m_nAutomobileFlags.bLastSpaceAvaliableToChangeBounds = true;
RefreshAirResistance(); // Not sure if needed.
// Loop through all the attached stick bombs and update their positions
if(fwAttachmentEntityExtension *extension = GetAttachmentExtension())
{
const void *basePtr = NULL;
if(GetVehicleDamage() && GetVehicleDamage()->GetDeformation() && GetVehicleDamage()->GetDeformation()->HasDamageTexture())
{
basePtr = GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead); //Lock the texture once for all wheels
}
if(basePtr)
{
CPhysical* pCurChild = static_cast<CPhysical*>(extension->GetChildAttachment());
while(pCurChild)
{
if(pCurChild->GetIsTypeObject())
{
if(CProjectile *pProjectile = ((CObject *)pCurChild)->GetAsProjectile())
{
pProjectile->ApplyDeformation(this, basePtr);
}
}
fwAttachmentEntityExtension &curChildAttachExt = pCurChild->GetAttachmentExtensionRef();
pCurChild = static_cast<CPhysical*>(curChildAttachExt.GetSiblingAttachment());
}
GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
}
}
}
static u32 suGroundClearanceInterval = 500;
// Work out whether we should move the ground clearance
// returns true if bounds should be modified
bool CAutomobile::UpdateDesiredGroundGroundClearance(float fGroundClearance, bool brokenWheels, bool force)
{
bool updateTrailerGroundClearance = ( InheritsFromTrailer() &&
( ( MI_TRAILER_TRAILERLARGE.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERLARGE ) ||
( MI_TRAILER_TRAILERSMALL2.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERSMALL2 ) ) );
if( (GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_QUADBIKE || InheritsFromAmphibiousAutomobile() || GetVehicleType()==VEHICLE_TYPE_SUBMARINECAR || updateTrailerGroundClearance ) &&
!(pHandling->hFlags & HF_DONT_RAISE_BOUNDS_AT_SPEED ||
(pHandling->mFlags & MF_DONT_FORCE_GRND_CLEARANCE && !HasHydraulicSuspension())) )//some vehicles explicitly state they don't want their ground clearance enforced
{
if((GetDriver() && (GetDriver()->IsPlayer() || HasHydraulicSuspension())) || m_nVehicleFlags.bAllowBoundsToBeRaised || m_nVehicleFlags.bTowedVehBoundsCanBeRaised || brokenWheels || updateTrailerGroundClearance) // Only modify the bounds of the players vehicle.
{
float fHeightAboveRoad = m_fHeightAboveRoad;
bool bAdjustBounds = false;
//If we have hydraulic suspension we only want to raise the suspension when the vehicle is slammed on the ground.
if( HasHydraulicSuspension() )
{
// Check whether the hydraulics have moved or the ground clearance has been changed
if(m_uLastGroundClearanceCheck < m_iTimeHydraulicsModified || fGroundClearance != m_fGroundClearance)
{
float fTempHeight;
CVehicle::CalculateHeightsAboveRoad(GetModelId(), &fHeightAboveRoad, &fTempHeight);
float fSuspensionRaise = 0.0f;
for (int i=0; i<GetNumWheels(); i++)
{
float fSuspensionRaiseForWheel = GetWheel(i)->GetSuspensionRaiseAmount();
if(fSuspensionRaiseForWheel > fSuspensionRaise)
{
fSuspensionRaise = fSuspensionRaiseForWheel;
}
}
fHeightAboveRoad += fSuspensionRaise;
TUNE_FLOAT(sfHeightAboveRoadChangeForceUpdate, 0.05f, 0.0f, 5.0f, 0.001f)
// If the ground clearance has changed significantly force the ground clearance to be checked.
if(fabs(fHeightAboveRoad - m_fLastGroundClearanceHeightAboveGround) > sfHeightAboveRoadChangeForceUpdate )
{
m_fHeightAboveRoad = fHeightAboveRoad;
bAdjustBounds = true;
}
else if(fGroundClearance == m_fGroundClearance) // Don't need to update the bounds
{
m_uLastGroundClearanceCheck = m_iTimeHydraulicsModified; // Reset the ground check timer
}
}
}
if(fGroundClearance != m_fGroundClearance || force || bAdjustBounds )
{
if( GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetType() != phBound::COMPOSITE )
{
return false;
}
const phBoundComposite* pBoundCompOrig = static_cast<const phBoundComposite*>(GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds());
bool bSpaceAvaliableToChangeBounds = true;
u32 uCurrentTime = fwTimer::GetTimeInMilliseconds();
if(m_fLastGroundClearance == fGroundClearance && (uCurrentTime - m_uLastGroundClearanceCheck) < suGroundClearanceInterval)
return m_nAutomobileFlags.bLastSpaceAvaliableToChangeBounds;
if(fGroundClearance < m_fGroundClearance || bAdjustBounds)// if we are reducing our ground clearance do a test to see if we have enough space.
{
m_uLastGroundClearanceCheck = uCurrentTime;
const int iBoneIndexChassisLowLod=GetBoneIndex(VEH_CHASSIS_LOWLOD);
if( !force &&
iBoneIndexChassisLowLod != -1 )
{
WorldProbe::CShapeTestBoundDesc boundTestDesc;
int iComponent = GetVehicleFragInst()->GetComponentFromBoneIndex(iBoneIndexChassisLowLod);
if(iComponent > -1)
{
boundTestDesc.SetBound(pBoundCompOrig->GetBound(iComponent));
boundTestDesc.SetTransform(&RCC_MATRIX34(GetCurrentPhysicsInst()->GetMatrix()));
boundTestDesc.SetExcludeEntity(this);
boundTestDesc.SetIncludeFlags(ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
boundTestDesc.SetTypeFlags(ArchetypeFlags::GTA_VEHICLE_TYPE);
if(WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc))
{
bSpaceAvaliableToChangeBounds = false;
}
}
}
m_nAutomobileFlags.bLastSpaceAvaliableToChangeBounds = bSpaceAvaliableToChangeBounds;
}
else if(!HasContactWheels() || GetFrameCollisionHistory()->GetCollisionImpulseMagSum() > 0.0f)
{
bSpaceAvaliableToChangeBounds = false;
}
m_fLastGroundClearance = fGroundClearance;
if(bSpaceAvaliableToChangeBounds)
{
m_fGroundClearance = fGroundClearance;
m_fLastGroundClearanceHeightAboveGround = m_fHeightAboveRoad;
return true;// bounds should be modified
}
}
}
}
return false;
}
static dev_float sfSideImpactGripReduction = 1.0f;
static dev_float sfSideImpactGripAddition = 3.0f;
static dev_float sfPITGripMulti = 0.1f;
float sfEngineRevVelocityThreshold = 0.1f;
float sfEngineRevAcceleration = 2.0f;
float sfEngineRevMinRevs = 0.55f;
float sfEngineRevRandomMin = 0.0f;
bank_float sfEngineRevStandardSuspensionStiffness = 4.5f;
bank_float sfEngineRevStandardHeightOfVehicle = 0.85f;
bank_float sfEngineRevStandardEngineForce = 0.20f;
bank_float sfEngineRevStandardEngineForceMax = 0.25f;
float bfSteeringBiasWhenWheelPopped = 90.0f;
float bfSteeringBiasLifeAfterWheelPopped = 1.0f;
float bfSteeringBiasWhenHitCarOnTheSide = 45.0f;
float bfSteeringBiasLifeAfterHitCarOnTheSide = 0.5f;
bank_float bfSteeringBiasWhenBeingPIT = 45.0f;
bank_float bfSteeringBiasLifeAfterBeingPIT_PlayerDefault = 0.75f;
bank_float bfSteeringBiasLifeAfterBeingPIT_PlayerMission = 0.15f;
bank_float bfSteeringBiasLifeAfterBeingPIT_AI = 1.5f;
dev_float fFullPITImpactDeltaSpeed = 0.5f;
dev_float VEHICLE_MIN_SPEED_TO_PIT = 16.67f; //about 60km/h
extern float sfMaximumMassForPushingVehicles;
PF_PAGE(AutomobileWheelForcesPage, "Wheel forces");
PF_GROUP(AutomobileWheelForces);
PF_LINK(AutomobileWheelForcesPage, AutomobileWheelForces);
PF_VALUE_FLOAT(TyreLoadLF, AutomobileWheelForces);
PF_VALUE_FLOAT(TyreLoadRF, AutomobileWheelForces);
PF_VALUE_FLOAT(TyreLoadLR, AutomobileWheelForces);
PF_VALUE_FLOAT(TyreLoadRR, AutomobileWheelForces);
PF_VALUE_FLOAT(WheelSpeedLF, AutomobileWheelForces);
PF_VALUE_FLOAT(WheelSpeedRF, AutomobileWheelForces);
PF_VALUE_FLOAT(WheelSpeedLR, AutomobileWheelForces);
PF_VALUE_FLOAT(WheelSpeedRR, AutomobileWheelForces);
PF_VALUE_FLOAT(SpeedDifferenceOnAxleF, AutomobileWheelForces);
PF_VALUE_FLOAT(SpeedDifferenceOnAxleR, AutomobileWheelForces);
PF_VALUE_FLOAT(SpeedDifferenceOnAxlePercentF, AutomobileWheelForces);
PF_VALUE_FLOAT(SpeedDifferenceOnAxlePercentR, AutomobileWheelForces);
PF_VALUE_FLOAT(DriveForceLF, AutomobileWheelForces);
PF_VALUE_FLOAT(DriveForceRF, AutomobileWheelForces);
PF_VALUE_FLOAT(DriveForceLR, AutomobileWheelForces);
PF_VALUE_FLOAT(DriveForceRR, AutomobileWheelForces);
PF_VALUE_FLOAT(DriveForceScaleFront, AutomobileWheelForces);
PF_VALUE_FLOAT(DriveForceScaleRear, AutomobileWheelForces);
PF_VALUE_FLOAT( EngineRPM, AutomobileWheelForces );
ePhysicsResult CAutomobile::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
ProcessReplayPhysics(fTimeStep, nTimeSlice);
return PHYSICS_DONE;
}
#endif // GTA_REPLAY
RAGETRACE_SCOPE(CAutomobile_ProcessPhysics);
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessPhysicsOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfProcessPhysicsCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
// Prefetch the wheel point array
int iNumWheels = GetNumWheels();
CWheel * const * pWheels = GetWheels();
PrefetchObject(pWheels);
//when changing ownership, a vehicle can sometimes
//be left with its dummy constraints still active
//until the next time it updates its lod
if (IsNetworkClone() && HasDummyConstraint())
{
ChangeDummyConstraints(VDM_REAL, false);
}
if (HasParachute())
{
ProcessCarParachuteTint();
}
if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy))
{
if (GetVehicleAudioEntity() && (GetVehicleAudioEntity()->GetVehicleLOD() == AUD_VEHICLE_LOD_REAL))
{
ProcessLowLODAudioRequirements(fTimeStep);
}
return PHYSICS_DONE;
}
//------------------------------------------------------------------------------------------------------------
// Prefetch the wheels
for(int wheelIndex = 0; wheelIndex < iNumWheels; ++wheelIndex)
{
PrefetchObject(pWheels[wheelIndex]);
}
if(CVehicle::ProcessPhysics(fTimeStep, bCanPostpone, nTimeSlice)==PHYSICS_POSTPONE)
{
return PHYSICS_POSTPONE;
}
#if __BANK
if( GetDriver() &&
GetDriver()->IsLocalPlayer() )
{
float wheelSpeedLF = 0.0f;
float wheelSpeedRF = 0.0f;
float wheelSpeedLR = 0.0f;
float wheelSpeedRR = 0.0f;
for(int i = 0; i < iNumWheels && i < 4; i++ )
{
if( pWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_LEFTWHEEL ) )
{
if( pWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
PF_SET( TyreLoadLR, pWheels[ i ]->GetTyreLoad() );
PF_SET( WheelSpeedLR, pWheels[ i ]->GetRotSpeed() );
wheelSpeedLR = pWheels[ i ]->GetRotSpeed();
}
else
{
PF_SET( TyreLoadLF, pWheels[ i ]->GetTyreLoad() );
PF_SET( WheelSpeedLF, pWheels[ i ]->GetRotSpeed() );
wheelSpeedLF = pWheels[ i ]->GetRotSpeed();
}
}
else
{
if( pWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
PF_SET( TyreLoadRR, pWheels[ i ]->GetTyreLoad() );
PF_SET( WheelSpeedRR, pWheels[ i ]->GetRotSpeed() );
wheelSpeedRR = pWheels[ i ]->GetRotSpeed();
}
else
{
PF_SET( TyreLoadRF, pWheels[ i ]->GetTyreLoad() );
PF_SET( WheelSpeedRF, pWheels[ i ]->GetRotSpeed() );
wheelSpeedRF = pWheels[ i ]->GetRotSpeed();
}
}
}
PF_SET( SpeedDifferenceOnAxleF, wheelSpeedLF - wheelSpeedRF );
PF_SET( SpeedDifferenceOnAxleR, wheelSpeedLR - wheelSpeedRR );
float averageWheelSpeed = wheelSpeedLF - wheelSpeedRF;
if( wheelSpeedLF + wheelSpeedRF != 0.0f )
{
averageWheelSpeed = ( averageWheelSpeed / ( wheelSpeedLF + wheelSpeedRF ) ) * 100.0f;
}
PF_SET( SpeedDifferenceOnAxlePercentF, averageWheelSpeed );
averageWheelSpeed = wheelSpeedLR - wheelSpeedRR;
if( wheelSpeedLR + wheelSpeedRR != 0.0f )
{
averageWheelSpeed = ( averageWheelSpeed / ( wheelSpeedLR + wheelSpeedRR ) ) * 100.0f;
}
PF_SET( SpeedDifferenceOnAxlePercentR, averageWheelSpeed );
PF_SET( EngineRPM, m_Transmission.GetRevRatio() );
}
#endif // #if __BANK
if(GetCurrentPhysicsInst()==NULL || !GetCurrentPhysicsInst()->IsInLevel())
return PHYSICS_DONE;
// postpone if we're sitting on something physical
if(bCanPostpone)
{
for(int i=0; i<iNumWheels; i++)
{
if(pWheels[i]->GetHitPhysical())
{
return PHYSICS_POSTPONE;
}
}
}
// Cache out some values
phCollider* pCollider = GetCollider();
const Vector3 vVehVelocity(GetVelocityIncludingReferenceFrame());
int iNumDoors = GetNumDoors();
float fBrakeForce = GetBrakeForce()*GetBrake();
for(int doorIndex = 0; doorIndex < iNumDoors; ++doorIndex)
{
PrefetchObject(&m_pDoors[doorIndex]);
}
PrefetchObject(pCollider);
// if network has re-spotted this car and we don't want it to collide with other network vehicles for a while
ProcessRespotCounter(fTimeStep);
RAGETRACE_START(CAuto_Phys_Transmission);
// do transmission stuff before we skip out because want to process every frame
float fDriveForce = 0.0f;
bool shouldUpdateTransmission = false;
float fTransmissionTimeStep = fTimeStep;
if(m_nVehicleFlags.bEngineOn)
{
if(CVehicleAILodManager::ms_bDeTimesliceTransmissionAndSleep)
{
shouldUpdateTransmission = (nTimeSlice == 0);
fTransmissionTimeStep = fwTimer::GetTimeStep();
}
else
{
shouldUpdateTransmission = true;
}
if(!shouldUpdateTransmission)
{
fDriveForce = m_fLastDriveForce;
}
}
else
{
m_fLastDriveForce = fDriveForce;
}
if(shouldUpdateTransmission)
{
float fDriveWheelRotSpeedsAverage = 0.0f;
Vector3 vDriveWheelGroundVelocityAverage(Vector3::ZeroType);
int nNumDriveWheels = 0;
int nNumDriveWheelsOnGround = 0;
int nNumDriveWheelsBrokenOff = 0;
for(int i=0; i<iNumWheels; i++)
{
const CWheel & wheel = *pWheels[i];
if(wheel.GetIsDriveWheel())
{
bool bCountWheelAsADriveWheel = true;
if(m_nAutomobileFlags.bInBurnout)// if we're doing a burnout just ignore the locked wheels.
{
if(!(wheel.GetConfigFlags().IsFlagSet(WCF_REARWHEEL) || pHandling->m_fDriveBiasRear < 0.1f))
{
bCountWheelAsADriveWheel = false;
}
}
if(bCountWheelAsADriveWheel)
{
//if( pHandling->GetCarHandlingData() &&
// pHandling->GetCarHandlingData()->aFlags & CF_ALLOW_TURN_ON_SPOT &&
// m_Transmission.GetGear() == 0 )
//{
// fDriveWheelRotSpeedsAverage += Abs( wheel.GetGroundSpeed() );
//}
//else
{
fDriveWheelRotSpeedsAverage += wheel.GetGroundSpeed();
}
vDriveWheelGroundVelocityAverage += *(wheel.GetGroundBeneathWheelsVelocity());
nNumDriveWheels++;
if(wheel.GetIsTouching())
nNumDriveWheelsOnGround++;
}
if(wheel.GetDynamicFlags().IsFlagSet(WF_BROKEN_OFF))
{
// B*2531522: Make sure that the broken wheel count does not exceed the total drive wheel count.
// Issue was that the drive force reduction due to broken wheels would generate a non-zero throttle if you tried to do a burnout in a vehicle with all four wheels missing.
nNumDriveWheelsBrokenOff = Clamp(++nNumDriveWheelsBrokenOff, 0, nNumDriveWheels);
}
}
}
float fInvNumDriveWheels = 1.0f/nNumDriveWheels;
fDriveWheelRotSpeedsAverage *= fInvNumDriveWheels;
vDriveWheelGroundVelocityAverage *= fInvNumDriveWheels;
const Mat34V matrix = GetCurrentPhysicsInst()->GetMatrix();
bool isUnderLoad = false;
if(m_Transmission.GetGear() <= 3)
{
isUnderLoad = GetVehicleAudioEntity()->HasHeavyRoadNoise();
if(!isUnderLoad)
{
for( int i = 0; i < m_pVehicleGadgets.size(); i++)
{
if(m_pVehicleGadgets[i]->GetType()==VGT_TOW_TRUCK_ARM)
{
CVehicleGadgetTowArm * pTowArm = static_cast<CVehicleGadgetTowArm*>(m_pVehicleGadgets[i]);
if(pTowArm->GetAttachedVehicle() && pTowArm->GetAttachedVehicle()->GetVelocity().Mag2() > 1.0f)
{
isUnderLoad = true;
break;
}
}
}
}
}
m_Transmission.SetEngineUnderLoad(isUnderLoad);
fDriveForce = m_Transmission.Process(this, matrix, RCC_VEC3V(GetVelocity()), fDriveWheelRotSpeedsAverage, vDriveWheelGroundVelocityAverage, nNumDriveWheels, nNumDriveWheelsOnGround, fTransmissionTimeStep);
if(InheritsFromHeli())
{
fDriveForce = 0.0f;
}
fDriveForce *= ((nNumDriveWheels-nNumDriveWheelsBrokenOff) * fInvNumDriveWheels);//Reduce drive force if wheels are broken off.
m_fLastDriveForce = fDriveForce;
}
RAGETRACE_START(CAuto_Phys_Doors);
// process doors (doors can keep us awake?)
bool bColliderIsArticulated = pCollider && pCollider->IsArticulated();
u32 flagsToProcessWhenNotArticulated =
CCarDoor::DRIVEN_AUTORESET | CCarDoor::DRIVEN_NORESET | CCarDoor::DRIVEN_GAS_STRUT | CCarDoor::DRIVEN_SWINGING | CCarDoor::DRIVEN_SMOOTH | CCarDoor::DRIVEN_SPECIAL | CCarDoor::PROCESS_FORCE_AWAKE | CCarDoor::RELEASE_AFTER_DRIVEN;
for(int i=0; i<iNumDoors; i++)
{
CCarDoor & door = m_pDoors[i];
if(!m_nVehicleFlags.bAnimateJoints || door.GetFlag(CCarDoor::DRIVEN_BY_PHYSICS))
{
if((bColliderIsArticulated || door.GetFlag(flagsToProcessWhenNotArticulated)) || door.GetBreakOffNextUpdate() )
{
door.ProcessPhysics(this, fTimeStep, nTimeSlice);
}
}
}
// skip default physics update, gravity and air resistance applied here
if (ProcessIsAsleep())
{
bool updateAsleep = true;
float fAsleepTimeStep = fTimeStep;
if(CVehicleAILodManager::ms_bDeTimesliceTransmissionAndSleep && !IsRunningCarRecording())
{
updateAsleep = (nTimeSlice == 0);
fAsleepTimeStep = fwTimer::GetTimeStep();
}
if(!updateAsleep)
{
return PHYSICS_DONE;
}
float fApplySteerAngle = GetSteerAngle();
if(m_fSteeringBiasLife > 0.0f)
{
fApplySteerAngle = rage::Clamp(fApplySteerAngle + m_fSteeringBias * pHandling->m_fSteeringLock, -pHandling->m_fSteeringLock, pHandling->m_fSteeringLock);
if(m_fSteeringBiasLife > (fAsleepTimeStep * 2.0f))
{
m_fSteeringBias *= (m_fSteeringBiasLife - fAsleepTimeStep) / m_fSteeringBiasLife;
}
else
{
m_fSteeringBias = 0.0f;
}
m_fSteeringBiasLife -= fAsleepTimeStep;
}
float gravityForce = m_fGravityForWheelIntegrator*GetMass();
bool isInactiveRecording = IsRunningCarRecording();
for(int i=0; i<iNumWheels; i++)
{
CWheel & wheel = *pWheels[i];
const float fApplySteerAngleForWheel = fApplySteerAngle * wheel.GetFrontRearSelector();
wheel.SetSteerAngle(fApplySteerAngleForWheel);
wheel.SetBrakeAndDriveForce( fBrakeForce, fDriveForce, GetThrottle(), 0.0f );
if(pHandling->hFlags & HF_HANDBRAKE_REARWHEELSTEER)
{
// Form circular wheel formation
if(wheel.GetConfigFlags().IsFlagSet(WCF_REARWHEEL))
{
const float f2ndSteerAngle = GetSecondSteerAngle()* wheel.GetFrontRearSelector();
wheel.SetSteerAngle(f2ndSteerAngle);
}
}
else if(GetHandBrake() && !(pHandling->hFlags & HF_NO_HANDBRAKE))
{
wheel.SetHandBrakeForce(GetHandBrakeForce());
}
wheel.ProcessAsleep(vVehVelocity, fAsleepTimeStep, gravityForce, isInactiveRecording);
}
m_vecInternalForce.Zero();
m_vecInternalTorque.Zero();
// If the vehicle is inactive and playing back the recording, we still want to update it's suspension
if((CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this) || GetOwnedBy() == ENTITY_OWNEDBY_CUTSCENE) && CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex()) && iNumWheels > 0)
{
ProcessProbes(MAT34V_TO_MATRIX34(GetMatrix()));
}
return PHYSICS_DONE;
}
else
{
// If we activated as a result of ProcessIsAsleep() our ptr to the collider may be out of date, so refresh it here.
pCollider = GetCollider();
}
const CPed * pDriver = GetDriver();
float fVehSpeedSq = vVehVelocity.Mag2();
bool bIsUpsideDownAmphiCar = InheritsFromAmphibiousAutomobile() && VEC3V_TO_VECTOR3(GetTransform().GetC()).Dot(Vector3(0.0f, 0.0f, 1.0f)) < 0.0f;
bool bSubmarineCarUpsideDownNotInSubmarineMode = GetVehicleType()==VEHICLE_TYPE_SUBMARINECAR && !IsInSubmarineMode() && VEC3V_TO_VECTOR3(GetTransform().GetC()).Dot(Vector3(0.0f, 0.0f, 1.0f)) < 0.0f;
bool bIsCarOrQuadBike = ( GetVehicleType()==VEHICLE_TYPE_CAR ||
GetVehicleType()==VEHICLE_TYPE_QUADBIKE ||
( !m_nFlags.bPossiblyTouchesWater &&
( GetVehicleType()==VEHICLE_TYPE_SUBMARINECAR ||
GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE ||
GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE ) ) ||
bIsUpsideDownAmphiCar ||
bSubmarineCarUpsideDownNotInSubmarineMode );
bool bDriverIsPlayer = pDriver && pDriver->IsPlayer();
CVehicleModelInfo* pModelInfo = GetVehicleModelInfo();
RAGETRACE_START(CAuto_Phys_Bouyancy);
// Make sure the possbilyTouchesWater flag is up to date before acting on it...
if(m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate)
{
ProcessPossiblyTouchesWater(fTimeStep, nTimeSlice, pModelInfo);
}
for( int i = 0; i < m_pVehicleGadgets.size(); i++)
{
m_pVehicleGadgets[i]->ProcessPhysics(this,fTimeStep,nTimeSlice);
}
if(m_pVehicleWeaponMgr)
{
m_pVehicleWeaponMgr->ProcessPhysics(this,fTimeStep,nTimeSlice);
}
else if( GetStatus() == STATUS_WRECKED )
{
if( IsTank() )// Make sure the tank turret stops spinning when blown up.
{
fragInstGta* pFragInst = GetVehicleFragInst();
int iBoneIndex = GetBoneIndex(VEH_TURRET_1_BASE);
if(iBoneIndex != -1 && pFragInst)
{
int nChildIndex = pFragInst->GetComponentFromBoneIndex(iBoneIndex);
if(nChildIndex != -1)
{
Freeze1DofJointInCurrentPosition(pFragInst, nChildIndex);
}
}
}
else if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_OUTRIGGER_LEGS ) )
{
fragInstGta* pFragInst = GetVehicleFragInst();
if( pFragInst )
{
int iBoneIndex = GetBoneIndex(VEH_TURRET_1_BARREL);
if(iBoneIndex != -1)
{
int nChildIndex = pFragInst->GetComponentFromBoneIndex(iBoneIndex);
if(nChildIndex != -1)
{
Freeze1DofJointInCurrentPosition(pFragInst, nChildIndex, true);
//u8 groupIndex = GetVehicleFragInst()->GetTypePhysics()->GetChild( nChildIndex )->GetOwnerGroupPointerIndex();
//if( GetVehicleFragInst()->GetCacheEntry() &&
// GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->latchedJoints->IsClear(groupIndex) )
//{
// GetVehicleFragInst()->GetTypePhysics()->GetGroup( groupIndex )->SetLatchStrength( -1.0f );
//
// GetVehicleFragInst()->CloseLatchAbove( nChildIndex );
// static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound())->SetIncludeFlags( nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES );
//}
}
}
}
}
}
RAGETRACE_START(CAuto_Phys_FlyingCar);
ProcessFlightHandling(fTimeStep);
ModifyControlsIfDucked(pDriver, fTimeStep);
// give player in-air control, and chance to roll car back on it's wheels when it crashes
if(GetStatus()==STATUS_PLAYER && bIsCarOrQuadBike )
{
ProcessDriverInputsForStability(fTimeStep);
// Add some tiny torque to the vehicle that's generate from the engine rev
if(bDriverIsPlayer && IsEngineOn() && fVehSpeedSq < (sfEngineRevVelocityThreshold * sfEngineRevVelocityThreshold) && pModelInfo && pHandling)
{
const float fRevRatio = m_Transmission.GetRevRatio();
if( fRevRatio > sfEngineRevMinRevs )
{
float fScale = (fRevRatio - sfEngineRevMinRevs) / (1.0f - sfEngineRevMinRevs);
fScale *= fwRandom::GetRandomNumberInRange(sfEngineRevRandomMin, 1.0f);
const Vector3 vAxis(VEC3V_TO_VECTOR3(GetTransform().GetB()));
float fSuspensionForce = Clamp(pHandling->m_fSuspensionForce + pHandling->m_fAntiRollBarForce, 1.0f, sfEngineRevStandardSuspensionStiffness);
float fSuspensionForceScale = fSuspensionForce/ sfEngineRevStandardSuspensionStiffness;
float fHeightOfVehicle = Clamp(pModelInfo->GetBoundingBoxMax().z, sfEngineRevStandardHeightOfVehicle, 5.0f);
float fHeightOfVehicleScale = ( sfEngineRevStandardHeightOfVehicle / fHeightOfVehicle);
float fEngineForce = Clamp(m_Transmission.GetDriveForce(), 0.0f, sfEngineRevStandardEngineForce);
float fEngineForceScale = fEngineForce / sfEngineRevStandardEngineForceMax;
ApplyInternalTorque(fScale * fHeightOfVehicleScale * fEngineForceScale * fSuspensionForceScale * sfEngineRevAcceleration * GetAngInertia().x * vAxis);
}
}
if(pHandling && (pHandling->hFlags & HF_OFFROAD_ABILITIES_X2))// Do some auto leveling if in the air.
{
if(IsInAir() && !GetFrameCollisionHistory()->GetMostSignificantCollisionRecord())
{
bool bDoAutoLeveling = true;
if(GetDriver() && GetDriver()->IsAPlayerPed())
{
if( ( !MI_CAR_BRICKADE.IsValid() || GetModelIndex() != MI_CAR_BRICKADE.GetModelIndex() ) &&
( !MI_CAR_APC.IsValid() || GetModelIndex() != MI_CAR_APC.GetModelIndex() ) )
{
Vec3V vPosition = GetTransform().GetPosition();
float fMapMaxZ = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(vPosition.GetXf(), vPosition.GetYf());
//Let the player perform rolls when really high in the air
Vec3V vBBMinZGlobal = GetTransform().Transform(Vec3V(0.0f, 0.0f, GetBoundingBoxMin().z));
if(vBBMinZGlobal.GetZf() > fMapMaxZ)
{
bDoAutoLeveling = false;
}
}
else
{
bDoAutoLeveling = false;
}
}
if(bDoAutoLeveling)
{
static dev_float sfAutoLevelMult = -0.5f;
AutoLevelAndHeading( sfAutoLevelMult, true );
}
}
}
}
float fApplySteerAngle = GetSteerAngle();
float fFwdSpeed = DotProduct(vVehVelocity, VEC3V_TO_VECTOR3(GetTransform().GetB()));
// Only do this for player car as it is nice to be able to see what ai cars are trying to do.
// For ai cars there already is some code to stop extreme steering at high speed in carai.cpp
if(bDriverIsPlayer)
{
// do speed based steering angle
float fSideSpeed = DotProduct(vVehVelocity, VEC3V_TO_VECTOR3(GetTransform().GetA()));
//if(fSideSpeed * fApplySteerAngle > 0.0f)
//{
// fApplySteerAngle /= pHandling->m_fSteeringLock;
// fApplySteerAngle *= GetWheel(0)->GetSteerAngleForMaxTraction(GetVelocity().Mag());
//}
//static float SPEED_STEER_REDUCTION_MULT = 0.95f;
//float fMoveSpeedMag = GetVelocity().Mag();
//if(fSideSpeed * fApplySteerAngle >= -0.05f*fMoveSpeedMag && fFwdSpeed > 0.0f)
//{
// fApplySteerAngle *= rage::Powf(SPEED_STEER_REDUCTION_MULT, fFwdSpeed);
//}
if(fFwdSpeed > sfPlayerSpeedSteerFwdThreshold && !(fSideSpeed * fApplySteerAngle < -sfPlayerSpeedSteerSideRatioThreshold*rage::Abs(fFwdSpeed)))
{
fApplySteerAngle /= 1.0f + sfPlayerSpeedSteerMult * (fFwdSpeed - sfPlayerSpeedSteerFwdThreshold);
}
if(fFwdSpeed > 1.0f && sbPlayerSpeedSteerAutoCentre && !(pHandling->hFlags & HF_STEER_REARWHEELS))
{
float fAutoCentreSteerAngle = rage::Atan2f(-fSideSpeed, fFwdSpeed);
fAutoCentreSteerAngle = rage::Clamp(fAutoCentreSteerAngle, -sfPlayerSpeedSteerAutoCentreMax, sfPlayerSpeedSteerAutoCentreMax);
fApplySteerAngle = rage::Clamp(fApplySteerAngle + fAutoCentreSteerAngle, -pHandling->m_fSteeringLock, pHandling->m_fSteeringLock);
}
// do extra stability while braking (like a rudder effect)
if(GetVehicleType() == VEHICLE_TYPE_QUADBIKE ||
GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE)
{
if(m_vehControls.m_brake > 0.0f && vVehVelocity.Mag2() > 0.1f)
{
Vector3 vecBrakeStabiliseTorque(VEC3V_TO_VECTOR3(GetTransform().GetC()));
static dev_float sfBrakingStability = -1.0f;
vecBrakeStabiliseTorque.Scale(fSideSpeed * m_vehControls.m_brake * sfBrakingStability * GetAngInertia().z);
ApplyInternalTorque(vecBrakeStabiliseTorque);
}
}
if(InheritsFromPlane())
{
CPlane *pThisPlane = (CPlane *)this;
fApplySteerAngle *= pThisPlane->GetAircraftDamage().GetYawMult(pThisPlane);
}
}
if(m_fSteeringBiasLife > 0.0f)
{
fApplySteerAngle = rage::Clamp(fApplySteerAngle + m_fSteeringBias * pHandling->m_fSteeringLock, -pHandling->m_fSteeringLock, pHandling->m_fSteeringLock);
if(m_fSteeringBiasLife > (fTimeStep * 2.0f))
{
m_fSteeringBias *= (m_fSteeringBiasLife - fTimeStep) / m_fSteeringBiasLife;
}
else
{
m_fSteeringBias = 0.0f;
}
m_fSteeringBiasLife -= fTimeStep;
}
// setup cheat flags
u32 nResetWheelFlags = WF_ABS|WF_RESET_CHEATS|WF_REDUCE_GRIP;
if(CPhysics::GetIsFirstTimeSlice(nTimeSlice))
nResetWheelFlags |= WF_ABS_ACTIVE;
u32 nWheelCheatFlags = 0;
// Anti-lock brakes
if(GetCheatFlag(VEH_CHEAT_ABS|VEH_SET_ABS))
nWheelCheatFlags |= WF_ABS;
else if(GetCheatFlag(VEH_SET_ABS_ALT))
nWheelCheatFlags |= WF_ABS_ALT;
else if(GetStatus()==STATUS_PHYSICS)
nWheelCheatFlags |= WF_ABS;
// Usually-off cheats.
u32 nAnyCheatFlags = VEH_CHEAT_TC|VEH_SET_TC|VEH_CHEAT_SC|VEH_SET_SC|VEH_CHEAT_GRIP1|VEH_SET_GRIP1|VEH_CHEAT_GRIP2|VEH_SET_GRIP2;
if(GetCheatFlag(nAnyCheatFlags))
{
// Traction control
if(GetCheatFlag(VEH_CHEAT_TC|VEH_SET_TC))
nWheelCheatFlags |= WF_CHEAT_TC;
// Stability control
if(GetCheatFlag(VEH_CHEAT_SC|VEH_SET_SC))
nWheelCheatFlags |= WF_CHEAT_SC;
// Extra grip
if(GetCheatFlag(VEH_CHEAT_GRIP1|VEH_SET_GRIP1))
nWheelCheatFlags |= WF_CHEAT_GRIP1;
// Extra extra grip
if(GetCheatFlag(VEH_CHEAT_GRIP2|VEH_SET_GRIP2))
nWheelCheatFlags |= WF_CHEAT_GRIP2;
}
// Lower traction for a moment after a handbrake turn
float fGripMult = GetHandBrakeGripMult();
if(!m_nVehicleFlags.bIsBig && !m_nVehicleFlags.bIsBus && GetMass() < sfMaximumMassForPushingVehicles)
{
if(const CCollisionRecord *pVehicleCollision = GetFrameCollisionHistory()->GetMostSignificantCollisionRecordOfType(ENTITY_TYPE_VEHICLE))
{
if(pVehicleCollision->m_pRegdCollisionEntity && pVehicleCollision->m_pRegdCollisionEntity->GetIsTypeVehicle())
{
CVehicle *pOtherVehicle = (CVehicle *) pVehicleCollision->m_pRegdCollisionEntity.Get();
// only player, cop, AI that's ramming can perform PIT maneuver
bool bCanPIT = pOtherVehicle->GetDriver() && pOtherVehicle->GetDriver()->IsPlayer() && m_nVehicleFlags.bCarAgainstCarSideCollision;
bCanPIT |= pOtherVehicle->GetDriver() && (pOtherVehicle->GetDriver()->GetPedType() == PEDTYPE_COP || pOtherVehicle->GetDriver()->GetPedType() == PEDTYPE_SWAT);
bCanPIT |= pOtherVehicle->GetIntelligence()->GetActiveTask() && pOtherVehicle->GetIntelligence()->GetActiveTask()->GetTaskType() == CTaskTypes::TASK_VEHICLE_RAM;
bCanPIT |= (CTimeHelpers::GetTimeSince(pOtherVehicle->GetIntelligence()->GetLastTimeRamming()) < 100);
if (bCanPIT)
{
const CPhysical* pOtherAvoidanceTarget = pOtherVehicle->GetIntelligence()->GetAvoidanceCache().m_pTarget;
const CPhysical* pOurAvoidanceTarget = GetIntelligence()->GetAvoidanceCache().m_pTarget;
if (pOtherAvoidanceTarget && (pOurAvoidanceTarget == pOtherAvoidanceTarget))
{
bCanPIT = false;
}
}
if(bCanPIT)
{
Vector3 pOtherPosLocal = pVehicleCollision->m_OtherCollisionPos;
MAT34V_TO_MATRIX34(PHSIM->GetLastInstanceMatrix(pOtherVehicle->GetCurrentPhysicsInst())).UnTransform(pOtherPosLocal);
if(pVehicleCollision->m_fCollisionImpulseMag > SMALL_FLOAT && pVehicleCollision->m_MyCollisionPosLocal.y < (GetBoundingBoxMin().y * 0.5f) && pOtherPosLocal.y > (pOtherVehicle->GetBoundingBoxMax().y * 0.5f))
{
float fSteeringMult = Clamp(pVehicleCollision->m_fCollisionImpulseMag / (GetMass() * fFullPITImpactDeltaSpeed), 0.0f, 1.0f);
float fSpeedMult = Clamp(vVehVelocity.Mag() / VEHICLE_MIN_SPEED_TO_PIT, 0.0f, 1.0f);
float fNewSteeringBias = bfSteeringBiasWhenBeingPIT * DtoR * fSteeringMult * fSpeedMult * Sign(pVehicleCollision->m_MyCollisionNormal.Dot(VEC3V_TO_VECTOR3(GetMatrix().GetCol0())));
if(m_fSteeringBiasLife <= 0.0f || Sign(m_fSteeringBias) != Sign(fNewSteeringBias) || Abs(fNewSteeringBias) > (Abs(m_fSteeringBias) + SMALL_FLOAT))
{
m_fSteeringBias = fNewSteeringBias;
if(bDriverIsPlayer)
{
if(CTheScripts::GetPlayerIsOnAMission())
{
m_fSteeringBiasLife = bfSteeringBiasLifeAfterBeingPIT_PlayerMission * fSteeringMult;
}
else
{
m_fSteeringBiasLife = bfSteeringBiasLifeAfterBeingPIT_PlayerDefault * fSteeringMult;
}
}
else
{
m_fSteeringBiasLife = bfSteeringBiasLifeAfterBeingPIT_AI * fSteeringMult;
}
}
fGripMult *= sfPITGripMulti;
m_nVehicleFlags.bCarBrushAgainstCarSideCollision = false; //disable able the brushing against car effects, which steers car in the opposite direction
}
}
}
}
}
if( m_nVehicleFlags.bCarAgainstCarSideCollision )
{
fGripMult *= sfSideImpactGripReduction;
m_nVehicleFlags.bCarAgainstCarSideCollision = false;
}
if(m_nVehicleFlags.bCarBrushAgainstCarSideCollision)
{
if(GetDriver() && !GetDriver()->IsPlayer())
{
if(const CCollisionRecord *pVehicleCollision = GetFrameCollisionHistory()->GetMostSignificantCollisionRecordOfType(ENTITY_TYPE_VEHICLE))
{
if(pVehicleCollision->m_fCollisionImpulseMag > SMALL_FLOAT)
{
bool bApplySteeringBias = true;
CVehicle *pOtherVehicle = (CVehicle *) pVehicleCollision->m_pRegdCollisionEntity.Get();
if(pOtherVehicle->GetDriver())
{
const CPhysical* pOtherAvoidanceTarget = pOtherVehicle->GetIntelligence()->GetAvoidanceCache().m_pTarget;
const CPhysical* pOurAvoidanceTarget = GetIntelligence()->GetAvoidanceCache().m_pTarget;
if (pOtherAvoidanceTarget && (pOurAvoidanceTarget == pOtherAvoidanceTarget))
{
bApplySteeringBias = false;
}
}
if(bApplySteeringBias)
{
float fNewSteeringBias = bfSteeringBiasWhenHitCarOnTheSide * DtoR * Sign(pVehicleCollision->m_MyCollisionNormal.Dot(VEC3V_TO_VECTOR3(GetMatrix().GetCol0()))) * -1.0f;
if((m_fSteeringBiasLife <= 0.0f || Sign(m_fSteeringBias) != Sign(fNewSteeringBias)))
{
m_fSteeringBias = fNewSteeringBias;
m_fSteeringBiasLife = bfSteeringBiasLifeAfterHitCarOnTheSide;
}
}
}
}
}
m_nVehicleFlags.bCarBrushAgainstCarSideCollision = false;
}
if( HasParachute() )
{
ProcessCarParachute();
}
if( m_nVehicleFlags.bCarHitByHeavyVehicle )
{
fGripMult *= sfSideImpactGripAddition;
m_nVehicleFlags.bCarHitByHeavyVehicle = false;
}
u32 nWheelSetFlags = nWheelCheatFlags | (m_nAutomobileFlags.bReduceGripModeEnabled || GetExplosionEffectSlick() ? WF_REDUCE_GRIP : 0);
float fMassMultForAcceleration = 1.0f + (m_sfPassengerMassMult * static_cast<float>(GetNumberOfPassenger()));
phBound * pVehBound = (GetVehicleFragInst()->GetArchetype() ? GetVehicleFragInst()->GetArchetype()->GetBound() : NULL);
phBoundComposite * pVehCompositeBound = (pVehBound && pVehBound->GetType()==phBound::COMPOSITE ? static_cast<phBoundComposite*>(pVehBound) : NULL);
bool resetWheelCollision = false;
// update inputs for each wheel
bool bRestingOnPhysical = false;
float fMaxGripMult = fGripMult;
TUNE_GROUP_FLOAT( VEHICLE_SHUNT_TUNE, FRICTION_AFTER_SHUNT, 0.1f, 0.0f, 10.0f, 0.02f );
TUNE_GROUP_FLOAT( VEHICLE_SHUNT_TUNE, FRICTION_INCREASE_TIME_MODIFIER, 1.0f, 0.0f, 10.0f, 0.2f );
static dev_float sf_MaxAngularVelocityStart = 2.0f;
float velocityAroundZ = GetAngVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetUp() ) );
float velocityAroundZAbs = Abs( velocityAroundZ );
for(int i=0; i<iNumWheels; i++)
{
CWheel & wheel = *pWheels[i];
if( m_nVehicleFlags.bCarHitWhileInRoadBlock )
{
float fPrevGripMult = wheel.GetGripMult();
if( fPrevGripMult >= fGripMult )
{
fGripMult = FRICTION_AFTER_SHUNT;
}
else
{
fGripMult = fPrevGripMult + ( fTimeStep * FRICTION_INCREASE_TIME_MODIFIER );
if( fGripMult >= fMaxGripMult )
{
m_nVehicleFlags.bCarHitWhileInRoadBlock = false;
}
}
}
float fApplySteerAngleForWheel = fApplySteerAngle * wheel.GetFrontRearSelector();
if( GetModelIndex() == MI_CAR_CHERNOBOG &&
wheel.GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
static dev_float sfChernobogRearSteerReduction = 0.75f;
static dev_float sfChernobogMaxSpeedForRearSteer = ( 1.0f / 20.0f ) * sfChernobogRearSteerReduction;
static dev_float sfChernobogMaxReverseSpeedForRearSteer = ( 1.0f / 10.0f ) * sfChernobogRearSteerReduction;
float speedReductionScale = sfChernobogMaxSpeedForRearSteer;
if( fFwdSpeed < 0.0f )
{
speedReductionScale = sfChernobogMaxReverseSpeedForRearSteer;
}
//else
{
float steeringReduction = Max( 0.0f, sfChernobogRearSteerReduction - ( Abs( fFwdSpeed ) * speedReductionScale ) );
fApplySteerAngleForWheel *= steeringReduction;
}
}
//if( pHandling->GetCarHandlingData() &&
// pHandling->GetCarHandlingData()->aFlags & CF_ALLOW_TURN_ON_SPOT &&
// m_Transmission.GetGear() == 0 )
//{
// wheel.SetSteerAngle( -fApplySteerAngleForWheel );
//}
//else
{
wheel.SetSteerAngle( fApplySteerAngleForWheel );
}
wheel.SetMaterialFlags();
float fTankDriveForce = fDriveForce;
float throttle = GetThrottle();
static dev_float sf_MaxSpeedForSteeringTorque = 15.0f;
if( ( pHandling->GetCarHandlingData() &&
pHandling->GetCarHandlingData()->aFlags & CF_ALLOW_TURN_ON_SPOT ) &&
m_Transmission.GetGear() > 0 &&
m_vehControls.GetThrottle() >= 0.0f &&
Abs( fFwdSpeed ) < sf_MaxSpeedForSteeringTorque )
{
float steeringAngle = m_vehControls.GetSteerAngle() * ( wheel.GetConfigFlags().IsFlagSet( WCF_LEFTWHEEL ) ? -1.0f : 1.0f );
if( Abs( steeringAngle ) > 0.1f )
{
float throttleForWheel = Clamp( m_vehControls.GetThrottle() + ( ( 1.0f - m_vehControls.GetThrottle() ) * ( steeringAngle ) ), -1.0f, 1.0f );
if( GetThrottle() != 0.0f )
{
fTankDriveForce /= GetThrottle();
}
fTankDriveForce *= throttleForWheel;
static dev_float sf_ScaleDriveForceMin = 0.15f;
static dev_float sf_ScaleDriveForceMax = 1.0f;
static dev_float sf_MaxAngularVelocityEnd = 4.0f;
fTankDriveForce += fTankDriveForce * ( 1.0f - m_vehControls.GetThrottle() ) * ( sf_ScaleDriveForceMin + ( ( sf_ScaleDriveForceMax - sf_ScaleDriveForceMin ) * Min( 1.0f, 1.0f - Abs( fFwdSpeed ) / sf_MaxSpeedForSteeringTorque ) ) );
if( velocityAroundZAbs > sf_MaxAngularVelocityStart )
{
fTankDriveForce *= 1.0f - Min( 2.0f, ( velocityAroundZAbs - sf_MaxAngularVelocityStart ) / sf_MaxAngularVelocityEnd );
}
throttle = Abs( throttleForWheel );
wheel.SetSteerAngle( fApplySteerAngleForWheel * Abs( m_vehControls.GetThrottle() ) );
}
}
if( ( pHandling->GetCarHandlingData() &&
pHandling->GetCarHandlingData()->aFlags & CF_ALLOW_TURN_ON_SPOT ) &&
Abs( GetThrottle() ) < 0.05f )
{
static dev_float minBrakeForce = 0.025f;
fBrakeForce = Max( minBrakeForce, fBrakeForce );
}
// GTAV - B*1877402 - Stop the plane wheels spinning when it's in the air.
if( ( InheritsFromPlane() || InheritsFromHeli() ) && GetNumContactWheels() == 0 )
{
wheel.SetBrakeAndDriveForce( 0.1f, fDriveForce, GetThrottle(), fVehSpeedSq );
}
else if(InheritsFromAmphibiousAutomobile() && static_cast<CAmphibiousAutomobile*>(this)->IsPropellerSubmerged())
{
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_WATER_BRAKE_FORCE, 0.001f, 0.0f, 10.0f, 0.1f);
// Apply a small brake force on the wheels when in water for amphibious quads so that they don't spin indefinitely
wheel.SetBrakeAndDriveForce( WHEEL_WATER_BRAKE_FORCE, fDriveForce, GetThrottle(), fVehSpeedSq );
}
else
{
wheel.SetBrakeAndDriveForce( fBrakeForce, fTankDriveForce, throttle, fVehSpeedSq );
}
wheel.GetDynamicFlags().ClearFlag(nResetWheelFlags);
wheel.GetDynamicFlags().SetFlag(nWheelSetFlags);
wheel.SetGripMult(fGripMult);
wheel.SetMassMultForAcceleration( fMassMultForAcceleration );
wheel.UpdateGroundVelocity();
bRestingOnPhysical = ( wheel.GetHitPhysical() != NULL || bRestingOnPhysical );
if(m_nAutomobileFlags.bBurnoutModeEnabled)
{
wheel.SetBrakeAndDriveForce(1.0f, fDriveForce, 1.0f, 0.0f);//force into burnout mode
}
if(pHandling->hFlags & HF_HANDBRAKE_REARWHEELSTEER)
{
// Form circular wheel formation
if(wheel.GetConfigFlags().IsFlagSet(WCF_REARWHEEL))
{
const float f2ndSteerAngle = GetSecondSteerAngle()* wheel.GetFrontRearSelector();
wheel.SetSteerAngle(f2ndSteerAngle);
}
}
else if(GetHandBrake() && !(pHandling->hFlags & HF_NO_HANDBRAKE))
{
wheel.SetHandBrakeForce(GetHandBrakeForce());
}
// B*1858054: Make sure to flag if wheels need to have their collision re-enabled because the vehicle went from being on its side/upside down to the right way up or vice versa.
// This only needs to be done for vehicles that do not have the wheel timeslicing optimisation enabled; vehicles that do use it will have their wheel collision reset every other frame anyway.
if(pModelInfo && pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_DONT_TIMESLICE_WHEELS))
{
if(pVehCompositeBound)
{
int mainWheelComponentIndex = wheel.GetFragChild(0);
if(mainWheelComponentIndex >= 0)
{
bool usingVehicleCollisionArchetypeFlags = pVehCompositeBound->GetIncludeFlags(mainWheelComponentIndex) == ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES;
// We are checking the state of the vehicle (on its side, upside down, normal) and seeing if the appropriate wheel collision flags are set for that state.
// If the incorrect flags are set (e.g. upside down, but not set to use the standard vehicle collision archetype flags), the flag is set so the wheel collision is setup correctly.
if(!(IsOnItsSide() || IsUpsideDown()) && usingVehicleCollisionArchetypeFlags)
{
resetWheelCollision = true;
}
else if((IsOnItsSide() || IsUpsideDown()) && !usingVehicleCollisionArchetypeFlags)
{
resetWheelCollision = true;
}
}
}
}
}
if( ( pHandling->GetCarHandlingData() &&
pHandling->GetCarHandlingData()->aFlags & CF_ALLOW_TURN_ON_SPOT ) &&
m_Transmission.GetGear() > 0 )
{
//float totalBrakeForce = 0.0f;
//float totalDriveForce = 0.0f;
//for( int i = 0; i < GetNumWheels(); i++ )
//{
// totalBrakeForce += GetWheel( i )->GetBrakeForce();
// totalDriveForce += GetWheel( i )->GetDriveForce();
//}
//float targetDriveForce = fDriveForce * m_vehControls.GetThrottle() * GetNumWheels();
//if( targetDriveForce != totalDriveForce )
//{
// float driveForceDelta = targetDriveForce - totalDriveForce;
// for( int i = 0; i < GetNumWheels(); i++ )
// {
// float newBrakeForce = GetWheel( i )->GetBrakeForce();
// float newDriveForce = GetWheel( i )->GetDriveForce();
// if( Sign( newDriveForce ) != Sign( newDriveForce + driveForceDelta ) )
// {
// newBrakeForce += -( newDriveForce + driveForceDelta );
// newDriveForce = 0.0f;
// }
// else
// {
// newDriveForce += driveForceDelta;
// }
// GetWheel( i )->SetBrakeForce( newBrakeForce );
// GetWheel( i )->SetDriveForce( newDriveForce );
// }
//}
if( GetThrottle() == 0.0f )
{
static dev_float slowDownScale = 0.1f;
Vector3 slowDownTorque = Vector3( 0.0f, 0.0f, ( ( GetAngInertia().z * velocityAroundZ * slowDownScale ) / ( GetNumWheels() * fTimeStep ) ) );
for( int i = 0; i < GetNumWheels(); i++ )
{
//if( ( velocityAroundZ > 0.0f && GetWheel( i )->IsFlagSet( WCF_LEFTWHEEL ) ) ||
// ( velocityAroundZ < 0.0f && !GetWheel( i )->IsFlagSet( WCF_LEFTWHEEL ) ) )
{
Vector3 localWheelOffset = CWheel::GetWheelOffset( GetVehicleModelInfo(), GetWheel( i )->GetHierarchyId() );
Vector3 slowDownForce;
slowDownForce.Cross( localWheelOffset, slowDownTorque );
GetWheel( i )->SetBrakeForce( GetWheel( i )->GetBrakeForce() + ( -slowDownForce.y * GetInvMass() ) );
}
}
}
if( GetNumContactWheels() > ( int )( (float)GetNumWheels() * 0.5f ) )
{
static dev_float sfTurnOnSpotMaxTurnSpeed = 2.0f;
float targetRotationSpeed = GetSteerAngle() * sfTurnOnSpotMaxTurnSpeed;
if( Abs( targetRotationSpeed ) > velocityAroundZAbs &&
Sign( targetRotationSpeed ) == Sign( velocityAroundZ ) )
{
static dev_float maxForwardSpeedForExtraTorque = 2.5f;
static dev_float maxTorque = 2.0f;
float torqueReduction = 1.0f - Min( 1.0f, Abs( fFwdSpeed ) / maxForwardSpeedForExtraTorque );
static dev_float extraTorqueScale = 1.0f;
float extraTorqueFactor = Clamp( ( targetRotationSpeed - velocityAroundZ ) * extraTorqueScale * torqueReduction * ( 1.0f - m_vehControls.GetThrottle() ) / fTimeStep, -maxTorque, maxTorque );
Vector3 extraTorque = VEC3V_TO_VECTOR3( GetTransform().GetUp() ) * GetAngInertia().z * extraTorqueFactor;
ApplyInternalTorque( extraTorque );
}
}
}
//if( CVehicle::sm_bInDetonationMode &&
// m_sideShuntForce == 0.0f )
//{
// TUNE_GROUP_FLOAT( ARENA_MODE, EXTRA_LATERAL_DAMPING, -0.75f, -1.0f, 1.0f, 0.1f );
// Vector3 extraDamping = VEC3V_TO_VECTOR3( GetTransform().GetRight() );
// extraDamping *= GetVelocity().Dot( extraDamping ) * EXTRA_LATERAL_DAMPING;
// ApplyInternalForceCg( extraDamping * GetMass() );
//}
ApplyDifferentials();
if(iNumWheels > 0)
{
// if speed is Zero collision will not have been processed -> need to force if not skipping physics
if(!m_nVehicleFlags.bVehicleColProcessed)
{
ProcessProbes(MAT34V_TO_MATRIX34(GetMatrix()));
if(CVehicleAILodManager::ms_bDeTimesliceWheelCollisions)
{
if(!bDriverIsPlayer && pModelInfo && !pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_DONT_TIMESLICE_WHEELS))
{
static dev_float sfMaxSlopeAngle = 0.90f;
if(!m_nVehicleFlags.bWheelsDisabled && !m_nVehicleFlags.bDriveMusclesToAnim && !bRestingOnPhysical && GetMatrix().GetCol2().GetZf() > sfMaxSlopeAngle)// if we're driving muscles we want as much stability as we can get. Also if we are on a steep slope don't timeslice as it can hurt stability.
{
DisableWheelCollisions();
}
else
{
EnableWheelCollisions();
}
}
// resetWheelCollision indicates that vehicle needs to re-enable wheel collisions to make sure they are setup correctly after the vehicle changes orientation.
else if(m_nVehicleFlags.bWheelsDisabled || resetWheelCollision)
{
EnableWheelCollisions();
}
m_nVehicleFlags.bWheelsDisabledOnDeactivation = false;
}
}
// Make sure the wheels are setup for Franklin's special ability.
ProcessSlowMotionSpecialAbilityPrePhysics(pDriver);
RAGETRACE_START(CAuto_Phys_Wheels);
// Sanity check for optimisation of caching the current collider above.
Assertf(pCollider==GetCollider(), "Cached pCollider doesn't match current collider.");
if(pCollider)
{
if(!InheritsFromTrailer())
{
// Trailers are applying forces to themselves in CTrailer::ProcessPhysics after calling this function
// The function hierarchy here is really ugly...
StartWheelIntegratorTask(pCollider,fTimeStep);
}
return PHYSICS_NEED_SECOND_PASS;
}
}
return ProcessPhysics2(fTimeStep, nTimeSlice);
}
//
//
//
#if GTA_REPLAY
void CAutomobile::ProcessReplayPhysics(float fTimeStep, int nTimeSlice)
{
// On replay we still need to process the tow truck physics so the arm updates
for( int i = 0; i < m_pVehicleGadgets.size(); i++)
{
m_pVehicleGadgets[i]->ProcessPhysics(this,fTimeStep,nTimeSlice);
}
// On replay we still need to process the vehicle doors physics so the bounds can be updated for visibility.
phCollider* pCollider = GetCollider();
bool bColliderIsArticulated = pCollider && pCollider->IsArticulated();
int iNumDoors = GetNumDoors();
u32 flagsToProcessWhenNotArticulated =
CCarDoor::DRIVEN_AUTORESET | CCarDoor::DRIVEN_NORESET | CCarDoor::DRIVEN_GAS_STRUT | CCarDoor::DRIVEN_SWINGING | CCarDoor::DRIVEN_SMOOTH | CCarDoor::DRIVEN_SPECIAL | CCarDoor::PROCESS_FORCE_AWAKE | CCarDoor::RELEASE_AFTER_DRIVEN;
for(int i=0; i<iNumDoors; i++)
{
CCarDoor & door = m_pDoors[i];
if(!m_nVehicleFlags.bAnimateJoints || door.GetFlag(CCarDoor::DRIVEN_BY_PHYSICS))
{
if((bColliderIsArticulated || door.GetFlag(flagsToProcessWhenNotArticulated)) || door.GetBreakOffNextUpdate() )
{
door.ReplayUpdateDoors(this);
}
}
}
// update vehicle bounds
if(GetVehicleFragInst()->GetCached() &&
GetModelIndex() != MI_PLANE_AVENGER )
{
static_cast<phBoundComposite*>(GetVehicleFragInst()->GetCacheEntry()->GetBound())->CalculateCompositeExtents(true);
if(CVehicle::ms_bUseAutomobileBVHupdate)
{
CPhysics::GetLevel()->UpdateCompositeBvh(GetVehicleFragInst()->GetLevelIndex());
}
else
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetVehicleFragInst()->GetLevelIndex());
}
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetVehicleFragInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
}
}
#endif
//
//
//
void CAutomobile::ProcessPossiblyTouchesWater(float fTimeStep, int nTimeSlice, const CVehicleModelInfo* pModelInfo)
{
if( InheritsFromSubmarineCar() || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE )
{
float fBuoyancyAccel = 0.0f;
SetIsInWater( m_Buoyancy.Process(this, fTimeStep, false, CPhysics::GetIsLastTimeSlice(nTimeSlice), &fBuoyancyAccel) );
return;
}
if(InheritsFromAmphibiousQuadBike())
{
return;
}
// If bPossiblyTouchesWater is false we are either well above the surface of the water or in a tunnel.
if (!m_nFlags.bPossiblyTouchesWater)
{
// we can't set these flags for network clones of script objects directly as they are synced
if(!IsNetworkClone() || !(GetNetworkObject() && GetNetworkObject()->IsScriptObject(true)))
{
SetIsInWater( false );
m_nVehicleFlags.bIsDrowning = false;
}
m_Buoyancy.ResetBuoyancy();
m_fTimeInWater = 0.0f;
m_Buoyancy.m_fForceMult = m_fOrigBuoyancyForceMult;
if(pHandling->GetSeaPlaneHandlingData())
{
if(CSeaPlaneExtension* pSeaPlaneExtension = static_cast<CPlane*>(this)->GetExtension<CSeaPlaneExtension>())
{
pSeaPlaneExtension->m_fTimeOnWater = 0.0f;
}
}
}
// else process the buoyancy class
else
{
int iNumContactWheels = GetNumContactWheels();
float fBuoyancyAccel = 0.0f;
SetIsInWater( m_Buoyancy.Process(this, fTimeStep, false, CPhysics::GetIsLastTimeSlice(nTimeSlice), &fBuoyancyAccel) );
// we can't set these flags for network clones of script objects directly as they are synced
if(!IsNetworkClone() || !(GetNetworkObject() && GetNetworkObject()->IsScriptObject(true)))
{
m_nVehicleFlags.bIsDrowning = false;
}
bool bCanStartSinking = true;
bool bEngineUnderWater = false;
float fSinkTime = InheritsFromTrailer() ? TRAILER_SINK_TIME : AUTOMOBILE_SINK_TIME;
float fEngineOffTime = AUTOMOBILE_SINK_TIME;
if(InheritsFromPlane() && pModelInfo)
{
s32 nSingleEngineBoneId = pModelInfo->GetBoneIndex(VEH_ENGINE);
s32 nLeftEngineBoneId = pModelInfo->GetBoneIndex(PLANE_ENGINE_L);
s32 nRightEngineBoneId = pModelInfo->GetBoneIndex(PLANE_ENGINE_R);
if(nSingleEngineBoneId != -1)
{
Matrix34 matEngineBone;
GetGlobalMtx(nSingleEngineBoneId, matEngineBone);
if(m_Buoyancy.GetAbsWaterLevel() > matEngineBone.d.z)
bEngineUnderWater = true;
}
else if(nLeftEngineBoneId != -1)
{
Matrix34 matEngineBone;
GetGlobalMtx(nLeftEngineBoneId, matEngineBone);
if(m_Buoyancy.GetAbsWaterLevel() > matEngineBone.d.z)
bEngineUnderWater = true;
}
else if(nRightEngineBoneId != -1)
{
Matrix34 matEngineBone;
GetGlobalMtx(nRightEngineBoneId, matEngineBone);
if(m_Buoyancy.GetAbsWaterLevel() > matEngineBone.d.z)
bEngineUnderWater = true;
}
else
{
Assertf(0, "Plane without recognised engine bone - %s.", GetModelName());
}
fEngineOffTime = PLANE_ENGINE_OFF_TIME;
if(pHandling->GetSeaPlaneHandlingData())
{
bCanStartSinking = false;
if(bEngineUnderWater)
{
m_fTimeInWater += fTimeStep;
if(m_fTimeInWater > AUTOMOBILE_SINK_TIME)
{
fEngineOffTime = 0.0f;
bCanStartSinking = true; // doesn't actually sink but we need to set this so the engine gets turned off
}
}
// Force the seaplane landing gear to retract if floating in water for a short time.
if(GetIsInWater() && !HasContactWheels())
{
CPlane* pPlane = static_cast<CPlane*>(this);
if(CSeaPlaneExtension* pSeaPlaneExtension = pPlane->GetExtension<CSeaPlaneExtension>())
{
pSeaPlaneExtension->m_fTimeOnWater += fTimeStep;
dev_float kfSeaPlaneGearRetractInWaterTime = 1.0f;
if(pSeaPlaneExtension->m_fTimeOnWater > kfSeaPlaneGearRetractInWaterTime)
{
pPlane->GetLandingGear().ControlLandingGear(this, CLandingGear::COMMAND_RETRACT);
}
}
}
}
else
{
bCanStartSinking = (fBuoyancyAccel < m_fGravityForWheelIntegrator*1.25f && iNumContactWheels <= 0) || bEngineUnderWater
|| m_fTimeInWater > PLANE_ENGINE_OFF_TIME || (pHandling->m_fPercentSubmerged > 100.0f && m_Buoyancy.GetSubmergedLevel()>0.1f);
}
// Planes sink immediately (without bobbing upwards first) if they go in fast and get below a certain depth.
Vec3V vPosition = GetTransform().GetPosition();
const float kfDepthToSinkPlaneImmediately = 3.0f;
if(m_Buoyancy.GetBuoyancyInfoUpdatedThisFrame() && !pHandling->GetSeaPlaneHandlingData()
&& m_Buoyancy.GetAvgAbsWaterLevel() - vPosition.GetZf() > kfDepthToSinkPlaneImmediately)
{
bCanStartSinking = true;
fEngineOffTime = 0.0f;
fSinkTime = 0.0f;
m_Buoyancy.m_fForceMult = 0.0f;
}
}
else if(InheritsFromBlimp())
{
fEngineOffTime = PLANE_ENGINE_OFF_TIME;
bCanStartSinking = m_fTimeInWater > PLANE_ENGINE_OFF_TIME || m_Buoyancy.GetSubmergedLevel()>0.5f;
s32 nLeftEngineBoneId = pModelInfo->GetBoneIndex(BLIMP_ENGINE_L);
if(nLeftEngineBoneId != -1)
{
Matrix34 matEngineBone;
GetGlobalMtx(nLeftEngineBoneId, matEngineBone);
if(m_Buoyancy.GetAbsWaterLevel() > matEngineBone.d.z)
bEngineUnderWater = true;
}
s32 nRightEngineBoneId = pModelInfo->GetBoneIndex(BLIMP_ENGINE_R);
if(!bEngineUnderWater && nRightEngineBoneId != -1)
{
Matrix34 matEngineBone;
GetGlobalMtx(nRightEngineBoneId, matEngineBone);
if(m_Buoyancy.GetAbsWaterLevel() > matEngineBone.d.z)
bEngineUnderWater = true;
}
if( GetStatus() == STATUS_WRECKED &&
m_nPhysicalFlags.bRenderScorched )
{
bCanStartSinking = true;
fEngineOffTime = 0.0f;
fSinkTime = 0.0f;
m_Buoyancy.m_fForceMult = 0.0f;
}
Assertf(nRightEngineBoneId != -1 || nLeftEngineBoneId != -1, "Blimp without recognised engine bone - %s.", GetModelName());
}
else if(InheritsFromHeli())
{
if( ( static_cast< CHeli* >( this )->GetIsDrone() ) )
{
bCanStartSinking = m_fTimeInWater > PLANE_ENGINE_OFF_TIME;
}
else
{
fEngineOffTime = PLANE_ENGINE_OFF_TIME;
bCanStartSinking = m_fTimeInWater > PLANE_ENGINE_OFF_TIME || m_Buoyancy.GetSubmergedLevel()>0.5f;
}
s32 nEngineBoneId = pModelInfo->GetBoneIndex(VEH_ENGINE);
if(Verifyf(nEngineBoneId != -1, "Heli without engine bone - %s.", GetModelName()))
{
Matrix34 matEngineBone;
GetGlobalMtx(nEngineBoneId, matEngineBone);
if(m_Buoyancy.GetAbsWaterLevel() > matEngineBone.d.z)
bEngineUnderWater = true;
}
if( pHandling->GetSeaPlaneHandlingData() )
{
bCanStartSinking = false;
if( bEngineUnderWater )
{
m_fTimeInWater += fTimeStep;
if( m_fTimeInWater > AUTOMOBILE_SINK_TIME )
{
fEngineOffTime = 0.0f;
bCanStartSinking = true; // doesn't actually sink but we need to set this so the engine gets turned off
}
}
}
}
else
{
if(!InheritsFromTrailer() && pModelInfo)
{
s32 nEngineBoneIdx = pModelInfo->GetBoneIndex(VEH_ENGINE);
if(Verifyf(nEngineBoneIdx != -1, "Car without engine bone - %s.", GetModelName()))
{
Matrix34 matEngineBoneLocal = GetObjectMtx(nEngineBoneIdx);
Matrix34 vehicleMatrix = MAT34V_TO_MATRIX34(GetMatrix());
Vector3 vEnginePosLocal = matEngineBoneLocal.d;
Vector3 vQueryPosLocal = vEnginePosLocal;
s32 nWheelBoneIdx = pModelInfo->GetBoneIndex(VEH_WHEEL_LF);
if(Verifyf(nWheelBoneIdx != -1 && GetWheelFromId(VEH_WHEEL_LF), "Car without front left wheel - %s.", GetModelName()))
{
Vector3 vWheelPosLocal(VEC3_ZERO);
GetDefaultBonePosition(nWheelBoneIdx, vWheelPosLocal);
vWheelPosLocal.z += GetWheelFromId(VEH_WHEEL_LF)->GetWheelRadius();
vQueryPosLocal.z = rage::Max(vEnginePosLocal.z, vWheelPosLocal.z);
}
s32 nExhaustBoneIdx = pModelInfo->GetBoneIndex(VEH_EXHAUST);
if( nExhaustBoneIdx != -1 )
{
Vector3 vExhaustPosLocal(VEC3_ZERO);
GetDefaultBonePosition( nExhaustBoneIdx, vExhaustPosLocal );
vQueryPosLocal.z = rage::Max( vEnginePosLocal.z, vExhaustPosLocal.z );
}
if( MI_CAR_WASTELANDER.IsValid() &&
GetModelIndex() == MI_CAR_WASTELANDER )
{
vQueryPosLocal.z += 2.0f;
}
Vector3 vQueryPosWorld;
vehicleMatrix.Transform(vQueryPosLocal, vQueryPosWorld);
if(m_Buoyancy.GetAbsWaterLevel() > vQueryPosWorld.z)
{
bEngineUnderWater = true;
}
}
}
else if( MI_TRAILER_TRAILERSMALL2.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERSMALL2 )
{
s32 nEngineBoneIdx = pModelInfo->GetBoneIndex(VEH_ENGINE);
Matrix34 matEngineBoneLocal = GetObjectMtx(nEngineBoneIdx);
Matrix34 vehicleMatrix = MAT34V_TO_MATRIX34(GetMatrix());
Vector3 vQueryPosLocal = matEngineBoneLocal.d;
vQueryPosLocal.z += 0.5f;
Vector3 vQueryPosWorld;
vehicleMatrix.Transform(vQueryPosLocal, vQueryPosWorld);
if(m_Buoyancy.GetAbsWaterLevel() > vQueryPosWorld.z)
{
bEngineUnderWater = true;
}
}
bCanStartSinking = (fBuoyancyAccel < m_fGravityForWheelIntegrator*1.25f && iNumContactWheels < GetNumWheels()) || bEngineUnderWater
|| m_fTimeInWater > AUTOMOBILE_SINK_TIME || (pHandling->m_fPercentSubmerged > 100.0f && m_Buoyancy.GetSubmergedLevel()>0.2f);
}
// Riders of quadbikes get ejected as soon as we are unsupported in water.
if(InheritsFromQuadBike() && fBuoyancyAccel > m_fGravityForWheelIntegrator * 0.75f && iNumContactWheels==0)
{
// Use a damage event to knock the passengers off the quad.
for( s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++ )
{
CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat);
if( pPassenger && !pPassenger->IsInjured() )
{
pPassenger->KnockPedOffVehicle(false);
}
}
}
if (pHandling->m_fPercentSubmerged > AUTOMOBILE_SINK_EVENT_SUBMERGE_PERCENT )
{
// Throw an event so world peds will have something to react against.
if (GetDriver())
{
CEventShockingCarCrash carCrashEvent(*this, GetDriver());
carCrashEvent.SetVisualReactionRangeOverride(AUTOMOBILE_SINK_CRASH_EVENT_RANGE); //MAGIC - need a higher range than normal so peds on the beach can see this
carCrashEvent.SetAudioReactionRangeOverride(AUTOMOBILE_SINK_CRASH_EVENT_RANGE);
CShockingEventsManager::Add(carCrashEvent);
}
}
if(bCanStartSinking)
{
// Update time in water
m_fTimeInWater += fTimeStep; // This will accumulate until the car is driven to dry land
// Should we cut the engine and make this vehicle unusable?
bool bCutEngine;
if(InheritsFromHeli())
{
bCutEngine = m_fTimeInWater>fEngineOffTime && m_Buoyancy.GetSubmergedLevel()>0.6f;
}
else if(InheritsFromPlane())
{
bCutEngine = (m_fTimeInWater>fEngineOffTime&&bEngineUnderWater) || (!HasContactWheels()&&m_Buoyancy.GetStatus()==FULLY_IN_WATER);
}
else
{
bCutEngine = ( m_fTimeInWater > fEngineOffTime&&bEngineUnderWater ) ||
( !HasContactWheels() && m_Buoyancy.GetStatus() == FULLY_IN_WATER &&
( m_specialFlightModeRatio == 0.0f || bEngineUnderWater ) );
}
if(bCutEngine && !IsNetworkClone())
{
// Turn engine off here
if(m_nVehicleFlags.bEngineOn)
SwitchEngineOff();
const bool wasWrecked = (GetStatus() == STATUS_WRECKED);
// Don't allow the engine to be restarted.
if(!wasWrecked)
{
SetIsWrecked();
}
//It was not wrecked before and isWrecked from sink lets check if the plane was damaged by a ped.
if (GetNetworkObject() && !wasWrecked && GetStatus() == STATUS_WRECKED)
{
bool triggerNetworkEvent = false;
if (InheritsFromPlane())
{
if (((CPlane*)this)->GetDestroyedByPed())
{
triggerNetworkEvent = true;
}
//Attribute it to the driver...
else if (GetDriver() && GetDriver()->IsPlayer())
{
SetWeaponDamageInfo(GetDriver(), WEAPONTYPE_UNARMED, fwTimer::GetTimeInMilliseconds());
triggerNetworkEvent = true;
}
}
else if(GetIsRotaryAircraft() && ((CRotaryWingAircraft*)this)->GetHeliRotorDestroyedByPed())
{
triggerNetworkEvent = true;
}
else if (MI_JETPACK_THRUSTER.IsValid() && GetModelIndex() == MI_JETPACK_THRUSTER)
{
// Special case here for the jetpack to send damage event correctly. Although it is a rotary aircraft, it has no rotors so won't pass the check above if it's never been damaged.
if (GetDriver() && GetDriver()->IsPlayer())
{
SetWeaponDamageInfo(GetDriver(), WEAPONTYPE_UNARMED, fwTimer::GetTimeInMilliseconds());
triggerNetworkEvent = true;
}
}
else if (GetIsLandVehicle())
{
//Make sure if a remote driver is driving your vehicle that
// he gets charged with the correct insurance amount.
CPed* damager = GetVehicleWasSunkByAPlayerPed();
if (damager)
{
SetWeaponDamageInfo(damager, WEAPONTYPE_UNARMED, fwTimer::GetTimeInMilliseconds());
triggerNetworkEvent = true;
}
}
if(triggerNetworkEvent)
{
CNetObjPhysical::NetworkDamageInfo damageInfo(GetWeaponDamageEntity(),GetHealth(),0.0f,0.0f,false,GetWeaponDamageHash(),0,false,true,false);
static_cast<CNetObjPhysical*>(GetNetworkObject())->UpdateDamageTracker(damageInfo);
}
}
}
// Decrease buoyancy factor if been in water for long time. We don't want the blimp sinking ever (it's derived from CHeli).
if( m_fTimeInWater > fSinkTime &&
!InheritsFromBlimp() &&
!InheritsFromAmphibiousAutomobile() &&
!pHandling->GetSeaPlaneHandlingData() )
{
// we can't set these flags for network clones of script objects directly as they are synced
if((bEngineUnderWater || InheritsFromTrailer()) && (!IsNetworkClone() || !(GetNetworkObject() && GetNetworkObject()->IsScriptObject(true))))
{
m_nVehicleFlags.bIsDrowning = true;
}
m_Buoyancy.m_fForceMult -= AUTOMOBILE_SINK_STEP;
if(m_Buoyancy.m_fForceMult < AUTOMOBILE_SINK_FORCE_MULT_MULTIPLIER * m_fOrigBuoyancyForceMult)
m_Buoyancy.m_fForceMult = AUTOMOBILE_SINK_FORCE_MULT_MULTIPLIER * m_fOrigBuoyancyForceMult;
if(m_nVehicleFlags.GetIsSirenOn())
TurnSirenOn(false,false);
// CS: Removed due to underwater swimming
// // Check for vehicle well below water surface and then fix car if below 10m
// if(GetPosition().z < ms_fVehicleSubmergeSleepGlobalZ)
// DeActivatePhysics();
}
}
if(!GetIsInWater())
{
m_fTimeInWater = 0.0f; // Reset timer if we aren't in water
m_Buoyancy.m_fForceMult = m_fOrigBuoyancyForceMult;
}
}
}
BANK_ONLY(bool sbDisableMidairWheelIntegrator = false;)
void CAutomobile::StartWheelIntegratorTask(phCollider* pCollider, float fTimeStep)
{
// Only run the full integrator if we're touching the ground
// NOTE: I think this could be run for non-Helis it's just that we would almost never benefit from it and HasContactWheels isn't cheap.
if(!InheritsFromHeli() || HasContactWheels() BANK_ONLY(|| sbDisableMidairWheelIntegrator))
{
if(pCollider->IsArticulated())
{
pCollider->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, true);
}
else
{
pCollider->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, true);
}
CWheelIntegrator::ProcessIntegration(pCollider, this, m_WheelIntegrator, fTimeStep, fwTimer::GetFrameCount());
}
else
{
// Enable gravity
pCollider->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, false);
pCollider->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false);
// Ensure the user gravity is being used
pCollider->SetGravityFactor(m_fGravityForWheelIntegrator/-GRAVITY);
// Apply internal forces
ScalarV vTimeStep = ScalarVFromF32(fTimeStep);
pCollider->ApplyForceCenterOfMass(m_vecInternalForce,vTimeStep.GetIntrin128ConstRef());
pCollider->ApplyTorque(m_vecInternalTorque,vTimeStep.GetIntrin128ConstRef());
}
}
RAGETRACE_DECL(CAutomobile_ProcessPostPhysics);
ePhysicsResult CAutomobile::ProcessPhysics2(float fTimeStep, int nTimeSlice)
{
Assert(!m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy));
RAGETRACE_SCOPE(CAutomobile_ProcessPostPhysics);
if(GetNumWheels() > 0)
{
phCollider* pCollider = GetCollider();
if (pCollider)
{
CWheelIntegrator::ProcessResult(m_WheelIntegrator, 0);
#if PHARTICULATEDBODY_MULTITHREADED_VALDIATION
if(pCollider->IsArticulated())
{
static_cast<phArticulatedCollider*>(pCollider)->GetBody()->SetReadOnlyOnMainThread(false);
}
#endif // PHARTICULATEDBODY_MULTITHREADED_VALDIATION
}
if(GetSkeleton() && !IsDummy())
{
Mat34V matrix = GetVehicleFragInst()->GetMatrix();
float fWheelExtension = CWheel::ms_fWheelExtensionRate*fTimeStep;
bool bUpdateWheelsWithDamage = false;
m_nAutomobileFlags.bHydraulicsBounceRaising = false;
m_nAutomobileFlags.bHydraulicsBounceLanding = false;
for(int i=0; i<GetNumWheels() && m_nVehicleFlags.bCanDeformWheels; i++)
{
CWheel* wheel = GetWheel(i);
if( wheel )
{
wheel->UpdateHydraulics( nTimeSlice );
if ( (wheel->GetFragChild() > -1) && wheel->GetConfigFlags().IsFlagSet(WCF_UPDATE_SUSPENSION) )
{
bUpdateWheelsWithDamage = true;
}
}
}
const void *basePtr = NULL; //Lock the texture once for all wheels
if(bUpdateWheelsWithDamage && GetVehicleDamage() && GetVehicleDamage()->GetDeformation() && GetVehicleDamage()->GetDeformation()->HasDamageTexture())
{
basePtr = GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead);
}
bool wheelFlagsUpdated = false;
for(int i=0; i<GetNumWheels(); i++)
{
CWheel* wheel = GetWheel(i);
if ((wheel != NULL) && (wheel->GetFragChild() > -1))
{
wheel->ProcessPreSimUpdate( basePtr, pCollider, matrix, fWheelExtension);
}
if( !wheelFlagsUpdated &&
wheel->UpdateHydraulicsFlags( this ) )
{
wheelFlagsUpdated = true;
}
}
if(basePtr)
{
GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
}
}
// Make sure the wheels are reset from Franklin's special ability.
ProcessSlowMotionSpecialAbilityPostPhysics();
}
// Non-articulated vehicles already should have maximum extents calculated.
// Articulated vehicles will be out of date until they are integrated prior to collision. Since this
// is called from the PreSimUpdate there isn't anything testing against the extents until collision.
if(GetVehicleFragInst()->GetCached() && CVehicle::ms_bAlwaysUpdateCompositeBound)
{
static_cast<phBoundComposite*>(GetVehicleFragInst()->GetCacheEntry()->GetBound())->CalculateCompositeExtents(true);
if(CVehicle::ms_bUseAutomobileBVHupdate)
{
CPhysics::GetLevel()->UpdateCompositeBvh(GetVehicleFragInst()->GetLevelIndex());
}
else
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetVehicleFragInst()->GetLevelIndex());
}
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetVehicleFragInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
}
// wheel ratios have been reset, so ProcessEntityColision need to be
// called again before wheel ratios can been used
m_nVehicleFlags.bVehicleColProcessed = false;
m_nVehicleFlags.bRestingOnPhysical = false;
m_vecInternalForce.Zero();
m_vecInternalTorque.Zero();
// Pick-up hook and magnet need a third pass so they can apply forces and impulses to other vehicles without conflicting with the wheel integrator task.
bool bHasMagnetGadget = false;
for( int i = 0; i < m_pVehicleGadgets.size(); i++)
{
m_pVehicleGadgets[i]->ProcessPhysics2(this,fTimeStep);
if(m_pVehicleGadgets[i]->GetType() == VGT_PICK_UP_ROPE || m_pVehicleGadgets[i]->GetType() == VGT_PICK_UP_ROPE_MAGNET)
bHasMagnetGadget = true;
}
#if TRACK_VEHICLE_GRADIENT
if(GetStatus() == STATUS_PLAYER)
{
float fAverageGradient = 0.0f;
int iHitWheels = 0;
for(int i =0; i < GetNumWheels(); i++)
{
if(GetWheel(i) && GetWheel(i)->GetWasTouching())
{
Vector3 vHitNorm = GetWheel(i)->GetHitNormal();
Vector3 vSideWays = GetA();
Vector3 vFront;
vFront.Cross(vHitNorm,vSideWays);
vFront.NormalizeSafe();
fAverageGradient += -rage::Asinf(vFront.z);
iHitWheels ++;
}
}
if(iHitWheels > 0)
{
fAverageGradient *= 180.0f / (PI * (float)iHitWheels);
}
PF_SET(vehicleGradientDeg, fAverageGradient);
}
#endif
// Running a no-op third pass if we need to run the net blender update,
// to make sure the articulated body is unlocked from the wheel process
// before we accessing/modifying it from the net blender update.
if(CPhysics::GetIsFirstTimeSlice(nTimeSlice))
{
netObject *networkObject = GetNetworkObject();
if (networkObject && networkObject->IsClone() && networkObject->GetNetBlender() && networkObject->CanBlend())
{
return PHYSICS_NEED_THIRD_PASS;
}
}
if(m_nVehicleFlags.bMayHaveWheelGroundForcesToApply || bHasMagnetGadget)
{
return PHYSICS_NEED_THIRD_PASS;
}
else
{
return PHYSICS_DONE;
}
}
//
ePhysicsResult CAutomobile::ProcessPhysics3(float fTimeStep, int UNUSED_PARAM(nTimeSlice))
{
// If the ground wheel force application has been deferred, do it now
if(m_nVehicleFlags.bMayHaveWheelGroundForcesToApply)
{
if(phCollider* pCollider = GetCollider())
{
CWheelIntegrator::ProcessResultsFinal( pCollider, m_ppWheels, GetNumWheels(), ScalarVFromF32(fTimeStep) );
}
m_nVehicleFlags.bMayHaveWheelGroundForcesToApply = false;
}
for( int i = 0; i < m_pVehicleGadgets.size(); i++)
{
m_pVehicleGadgets[i]->ProcessPhysics3(this,fTimeStep);
}
return PHYSICS_DONE;
}
int CAutomobile::InitPhys()
{
if( (GetModelIndex() == MI_CAR_VISERIS.GetModelIndex() &&
pHandling->GetCarHandlingData()->aFlags == 0) ||
IsTunerVehicle())
{
pHandling->GetCarHandlingData()->aFlags |= CF_HARD_REV_LIMIT;
}
return CVehicle::InitPhys();
}
void CAutomobile::InitDummyInst()
{
Assert(!m_pDummyInst);
Assert(GetVehicleModelInfo()->GetPhysics());
// Check to see if this vehicle has the dummy bound already in the frag type, in which case we don't need a dummy inst
if (HasDummyBound())
{
return;
}
const Matrix34 matrix = MAT34V_TO_MATRIX34(GetMatrix());
phInstGta* pNewInst = rage_new phInstGta(PH_INST_VEH);
Assert(pNewInst);
pNewInst->Init(*GetVehicleModelInfo()->GetPhysics(), matrix);
Assert(pNewInst->GetArchetype());
Assert(pNewInst->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_BOX_VEHICLE_TYPE);
m_pDummyInst = pNewInst;
m_pDummyInst->SetUserData((void*)this);
m_pDummyInst->SetInstFlag(phfwInst::FLAG_USERDATA_ENTITY, true);
// This ensures that UpdateAirResistance() sets the damping parameters properly the next time it's called.
RefreshAirResistance();
// Make sure Dummy automobiles don't flip over.
/*
if(CVehicleAILodManager::ms_dummyUseUseConstrainedRotation)
{
static dev_float DUMMY_PITCH_AND_ROLL_LIMIT = 45.0f*DtoR;
phConstraint* pConstraint = CPhysics::GetSimulator()->GetConstraintMgr()->AttachObjectToWorldRotation('CAIP', *m_pDummyInst);
if(pConstraint)
{
pConstraint->SetDegreesConstrained(2,ZAXIS);
pConstraint->SetHardLimitMin(-DUMMY_PITCH_AND_ROLL_LIMIT);
pConstraint->SetHardLimitMax(DUMMY_PITCH_AND_ROLL_LIMIT);
pConstraint->SetFixedRotation();
}
}
*/
}
void CAutomobile::ChangeDummyConstraints(const eVehicleDummyMode dummyMode, bool bIsStatic)
{
//don't use dummy constraints for network clones
if (IsNetworkClone())
{
const bool bRemovedConstraint = RemoveConstraints();
if (bRemovedConstraint)
{
DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS);
}
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
// Don't use constraints for vehicles on a trailer or for trailers themselves.
const bool bDummyParentIsTrailer = m_nVehicleFlags.bHasParentVehicle && GetDummyAttachmentParent() && GetDummyAttachmentParent()->InheritsFromTrailer();
const bool bRealParentIsTrailer = m_nVehicleFlags.bHasParentVehicle && CVehicle::IsEntityAttachedToTrailer(this);
if (bDummyParentIsTrailer || bRealParentIsTrailer || InheritsFromTrailer())
{
Assert(!m_dummyStayOnRoadConstraintHandle.IsValid());
return;
}
s32 iNewConstraintsMode;
switch(dummyMode)
{
case VDM_REAL : case VDM_NONE : default : { iNewConstraintsMode = CVehicleAILodManager::DCM_None; break; }
case VDM_DUMMY : { iNewConstraintsMode = CVehicleAILodManager::GetDummyConstraintMode(); break; }
case VDM_SUPERDUMMY :
{
iNewConstraintsMode = (bIsStatic && CVehicleAILodManager::ms_bDisableConstraintsForSuperDummyInactive) ?
CVehicleAILodManager::DCM_None : CVehicleAILodManager::GetSuperDummyConstraintMode();
break;
}
}
//-----------------------------------------
// Set up a physical distance constraint
if(iNewConstraintsMode == CVehicleAILodManager::DCM_PhysicalDistance || iNewConstraintsMode == CVehicleAILodManager::DCM_PhysicalDistanceAndRotation)
{
if(!m_dummyStayOnRoadConstraintHandle.IsValid() || !CPhysics::GetConstraintManager()->GetTemporaryPointer(m_dummyStayOnRoadConstraintHandle))
{
// Make sure dummy automobiles don't go off the roads.
// do this by applying a stay on roads constraint.
// This acts like a cable with some slack attached to the centre of the road.
// The vehicle should be able to move freely under normal physics control unless
// the get too far from the centre of the road at which point the cable will
// prevent them form moving further
const CVehicle* pVehicleConstraintsToUse = this;
if (InheritsFromTrailer() && m_nVehicleFlags.bHasParentVehicle)
{
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
if (pAttachParent && pAttachParent->GetIsTypeVehicle())
{
pVehicleConstraintsToUse = (CVehicle*)pAttachParent;
}
else if (GetDummyAttachmentParent())
{
pVehicleConstraintsToUse = GetDummyAttachmentParent();
}
}
bool validConstraintFound = false;
Vec3V vConstraintPos;
float fConstraintMaxLength = 0.0f;
// Unless the follow route has m_bAllowUseForPhysicalConstraint == false, indicating a recent
// script teleport, let CalculatePathInfo() compute the constraint position/length.
const CVehicleFollowRouteHelper* pFollowRoute = pVehicleConstraintsToUse->GetIntelligence()->GetFollowRouteHelper();
if(!pFollowRoute || pFollowRoute->ShouldAllowUseForPhysicalConstraint())
{
validConstraintFound = CVirtualRoad::CalculatePathInfo(*pVehicleConstraintsToUse, false, false, vConstraintPos, fConstraintMaxLength, false);
}
if(validConstraintFound)
{
const Vec3V vVehPos = GetTransform().GetPosition();
phConstraintDistance::Params dummyStayOnRoadConstraintParams;
dummyStayOnRoadConstraintParams.worldAnchorA = vVehPos;
dummyStayOnRoadConstraintParams.worldAnchorB = vConstraintPos;
dummyStayOnRoadConstraintParams.instanceA = GetCurrentPhysicsInst();
float distToConstraint = Dist(vVehPos, vConstraintPos).Getf();
float minAllowableLength = distToConstraint - PH_CONSTRAINT_ASSERT_DEPTH + 0.25f;
#if __BANK
if(minAllowableLength > fConstraintMaxLength)
{
Displayf("Constraint being created with length that doesn't meet PH_CONSTRAINT_ASSERT_DEPTH");
}
ValidateDummyConstraints(vVehPos,vConstraintPos,fConstraintMaxLength);
// If validating constraints, add a buffer to the actual physical constraint to allow the vehicle to exist outside of the bounds while the situation is debugged.
fConstraintMaxLength += (CVehicleAILodManager::ms_bValidateDummyConstraints) ? 10.0f : 0.0f;
#endif
fConstraintMaxLength = rage::Max(fConstraintMaxLength, minAllowableLength);
dummyStayOnRoadConstraintParams.maxDistance = fConstraintMaxLength;
m_dummyStayOnRoadConstraintHandle = PHCONSTRAINT->Insert(dummyStayOnRoadConstraintParams);
physicsAssertf( m_dummyStayOnRoadConstraintHandle.IsValid(), "Unable to construct the dummy-stay-on-road constraint" );
}
}
}
//-----------------------------------------------------------------
// Set up a rotational distance constraint
if(iNewConstraintsMode == CVehicleAILodManager::DCM_PhysicalDistanceAndRotation)
{
if(!m_dummyRotationalConstraint.IsValid())
{
static dev_float DUMMY_PITCH_AND_ROLL_LIMIT = 40.0f*DtoR;
phConstraintCylindrical::Params rotationalConstraint;
rotationalConstraint.instanceA = GetCurrentPhysicsInst();
rotationalConstraint.worldAxis = Vec3V(V_Z_AXIS_WZERO);
rotationalConstraint.maxLimit = DUMMY_PITCH_AND_ROLL_LIMIT;
m_dummyRotationalConstraint = PHCONSTRAINT->Insert(rotationalConstraint);
}
}
//------------------------------------------------
// Switch off distance & rotational constraints
if(iNewConstraintsMode == CVehicleAILodManager::DCM_None)
{
RemoveConstraints();
DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS);
}
}
bool CAutomobile::UsesDummyPhysics(const eVehicleDummyMode vdm) const
{
switch(vdm)
{
case VDM_REAL:
return false;
case VDM_DUMMY:
{
if(GetAttachedTrailer())
return false;
return true;
}
case VDM_SUPERDUMMY:
return true;
default:
Assert(false);
return false;
}
}
void CAutomobile::InitCompositeBound()
{
CVehicle::InitCompositeBound();
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
Assert(pBoundComp);
if(pBoundComp->GetTypeAndIncludeFlags())
{
for(int nSiren=VEH_SIREN_GLASS_1; nSiren <= VEH_SIREN_GLASS_MAX; nSiren++)
{
if(GetBoneIndex((eHierarchyId)nSiren)!=-1)
{
int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex((eHierarchyId)nSiren));
if(nChildIndex != -1)
{
pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
}
}
}
void CAutomobile::ProcessDriverInputOptions()
{
//TODO
// If this vehicle has its siren switched on other cars should get out of the way
/* if (m_nVehicleFlags.GetIsSirenOn() && ((fwTimer::GetSystemFrameCount() & 7) == 5) && UsesSiren() && GetModelIndex() != MI_CAR_ICEVAN && FindPlayerVehicle() == this)
{
CCarAI::MakeWayForCarWithSiren(this);
}
//Now done as ped scanning for vehicle threat events.
// if peds haven't been warned of this cars approach by now - do it!
if(!m_nVehicleFlags.bWarnedPeds && GetVelocity().Mag2() > 1.0f)
{
CCarAI::ScanForPedDanger(this, GetIntelligence()->FindUberMissionForCar() );
}
*/
}
ePrerenderStatus CAutomobile::PreRender(const bool bIsVisibleInMainViewport)
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
const bool bIsDummy = IsDummy();
if(!bIsDummy)
{
GetVehicleDamage()->PreRender();
}
//////////////////////////////////////////////////////////////////
// Matrix stuff, modify local skeleton matrices before we do UpdateSkeleton()
if(!bIsDummy)
{
for(int i=0; i<GetNumDoors(); i++)
{
GetDoor(i)->ProcessPreRender(this);
}
}
if( GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
CSubmarineCar* pSubmarineCar = static_cast< CSubmarineCar* >( this );
if( !pSubmarineCar->AreWheelsActive() )
{
return CVehicle::PreRender(bIsVisibleInMainViewport);;
}
}
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
if((!bIsDummy || !m_nVehicleFlags.bLodFarFromPopCenter))
{
if(crSkeleton * pSkeleton = GetSkeleton())
{
for(int i=0; i<GetNumWheels(); i++)
{
PrefetchObject(m_ppWheels[i]);
}
for(int i=0; i<GetNumWheels(); i++)
{
if(!m_ppWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_CENTRE_WHEEL ) || GetModelIndex() == MI_TRIKE_RROCKET)
{
m_ppWheels[i]->ProcessWheelMatrixForAutomobile(*pSkeleton);
}
else
{
m_ppWheels[i]->ProcessWheelMatrixForTrike();
}
}
}
}
}
ePrerenderStatus status = CVehicle::PreRender(bIsVisibleInMainViewport);
// Swing arm code for the Raptor vehicle
if( IsReverseTrike() )
{
m_nVehicleFlags.bDisableSuperDummy = true;
CWheel* pWheelR = GetWheelFromId( BIKE_WHEEL_R );
int nWheelIndexR = GetBoneIndex( BIKE_WHEEL_R );
crSkeleton* pSkel = GetSkeleton();
const crBoneData* pBoneDataR = pSkel->GetSkeletonData().GetBoneData( nWheelIndexR );
const crBoneData* pParentBoneDataR = pBoneDataR->GetParent();
if( pParentBoneDataR )
{
Matrix34& parentBoneMatrixR = RC_MATRIX34(pSkel->GetLocalMtx(pParentBoneDataR->GetIndex()));
// Reset the parent bone translation (since it gets moved around)
parentBoneMatrixR.d = VEC3V_TO_VECTOR3(pParentBoneDataR->GetDefaultTranslation());
parentBoneMatrixR.FromQuaternion( RCC_QUATERNION( pParentBoneDataR->GetDefaultRotation() ) );
// We will want to rotate the swing arm, so first calculate the correct wheel position (where the wheel should end up)
Vector3 v3CorrectParentPosition = VEC3V_TO_VECTOR3(pParentBoneDataR->GetDefaultTranslation()) + pWheelR->GetSuspensionAxis() * pWheelR->GetWheelCompressionFromStaticIncBurstAndSink();
Vector3 vCorrectWheelTranslation = VEC3V_TO_VECTOR3(pBoneDataR->GetDefaultTranslation()) + ( v3CorrectParentPosition - VEC3V_TO_VECTOR3(pParentBoneDataR->GetDefaultTranslation()) );
// Rotate the swing arm (parent bone of the wheel)
float fAng = pWheelR->GetWheelCompressionFromStaticIncBurstAndSink() / RCC_VECTOR3( pBoneDataR->GetDefaultTranslation() ).Mag();
fAng = rage::Asinf( Clamp( -fAng, -1.0f, 1.0f ) );
parentBoneMatrixR.RotateLocalX( fAng );
// Figure out where the wheel would end up after the rotation
Vector3 vRotatedWheelTranslation;
parentBoneMatrixR.Transform3x3(VEC3V_TO_VECTOR3(pBoneDataR->GetDefaultTranslation()), vRotatedWheelTranslation);
// Calculate the diff between the correct wheel position and the actual wheel position and move the swing arm so that it matches up
Vector3 vDiff = vRotatedWheelTranslation - vCorrectWheelTranslation;
parentBoneMatrixR.d -= vDiff;
}
}
return status;
}
void CAutomobile::PreRender2(const bool bIsVisibleInMainViewport)
{
ENABLE_FRAG_OPTIMIZATION_ONLY(if (GetHasFragCacheEntry()))
{
if (m_StartedAnimDirectorPreRenderUpdate)
{
GetAnimDirector()->WaitForPreRenderUpdateToComplete();
m_StartedAnimDirectorPreRenderUpdate = false;
}
// Landing gear deformation update needs to be done after the CVehicle::Prerender and have to update the wheel matrices again as they
// have dependency with landing gear bones. This mess is caused by that we don't have deformation support for articulated body, once
// the plane/heli gets deformed, its landing gear bones are out of sync with articulated body. CVehicle::PreRender calls SyncSkeletonToArticulatedBody,
// which will overwrite the skeleton to the articulated body matrices. Therefore the landing gear deformation has to be applied after.
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
if( ( InheritsFromPlane() || (InheritsFromHeli() && static_cast<CHeli*>(this)->HasLandingGear())) &&
!IsAnimated() )
{
if(InheritsFromPlane())
{
static_cast<CPlane*>(this)->GetLandingGear().PreRenderWheels(this);
}
else
{
static_cast<CHeli*>(this)->GetLandingGear().PreRenderWheels(this);
}
// This hacky code here is caused by the update order conflict. The wheels matrices need to be processed before CVehicle::PreRender,
// and landing gear update has to be done after the CVehicle::PreRender, and then wheels bones have dependency with landing gear bones.
// So here we have to recompute the wheel matrices again...
if((!IsDummy() || !m_nVehicleFlags.bLodFarFromPopCenter))
{
if(crSkeleton * pSkeleton = GetSkeleton())
{
for(int i=0; i<GetNumWheels(); i++)
{
PrefetchObject(m_ppWheels[i]);
}
for(int i=0; i<GetNumWheels(); i++)
{
m_ppWheels[i]->ProcessWheelMatrixForAutomobile(*pSkeleton);
}
}
}
}
}
// Process door deformation here in PreRender2(), since we need animations to be done
// to do the car door deformation. Previously it was in PreRender(), immediately after
// we started the vehicles's PreRender animation update, which would cause a guaranteed
// block on the main thread as we waited for the animation we just launched to complete.
// Doing this in PreRender2() gives plenty of time for the animation to complete.
if(bIsVisibleInMainViewport && !IsDummy())
{
for(int i=0; i<GetNumDoors(); i++)
{
CCarDoor* door = GetDoor(i);
if (door->GetFlag(CCarDoor::IS_BROKEN_OFF))
continue;
door->ProcessPreRender2(this);
}
}
// If you change the way this condition for the fake body movement works, please update CVfxHelper::GetDefaultToWorldMatrix as well!
if(bIsVisibleInMainViewport && !IsDummy() &&
m_specialFlightModeRatio < 1.0f )
{
if(!camInterface::GetCinematicDirector().IsRenderingCinematicCameraInsideVehicle() && !camInterface::GetCinematicDirector().IsRenderingCinematicMountedCamera())
{
if( (GetDriver() && GetDriver()->IsPlayer()) ||
(GetLastDriver() && GetLastDriver()->IsPlayer()) ||
(GetVehicleAudioEntity() && GetVehicleAudioEntity()->IsInCarModShop()) )
{
REPLAY_ONLY(if(CReplayMgr::IsEditModeActive() == false))
ProcessFakeBodyMovement(fwTimer::IsGamePaused());
}
}
PreRenderSuspensionMod(m_fFakeSuspensionLowerAmount);
m_fFakeSuspensionLowerAmountApplied = m_fFakeSuspensionLowerAmount;
}
if(bIsVisibleInMainViewport && !IsDummy())
{
if(crSkeleton * pSkeleton = GetSkeleton())
{
for(int i=0; i<GetNumWheels(); i++)
{
PrefetchObject(m_ppWheels[i]);
}
for(int i=0; i<GetNumWheels(); i++)
{
m_ppWheels[i]->ProcessAnimatedWheelDeformationForAutomobile(*pSkeleton);
}
}
}
// ground disturbance vfx
if (MI_CAR_DELUXO.IsValid() && GetModelIndex()==MI_CAR_DELUXO)
{
if (m_nVehicleFlags.bEngineOn || IsRunningCarRecording())
{
if (GetSpecialFlightModeRatio()==1.0f)
{
g_vfxVehicle.UpdatePtFxGlideHaze(this, ATSTRINGHASH("veh_xm_deluxo_glide_haze", 0x5C7B51B9));
g_vfxVehicle.UpdatePtFxPlaneAfterburner(this, VEH_ROCKET_BOOST, 0);
g_vfxVehicle.ProcessGroundDisturb(this);
}
}
}
// Call this after the door deformation to make sure any broken glass stays hidden (ProcessPreRenderDeformation restores the matrix for the window)
CVehicle::PreRender2(bIsVisibleInMainViewport);
}
u32 iFlags = 0;
if( m_nVehicleFlags.bForceBrakeLightOn )
{
iFlags |= LIGHTS_FORCE_BRAKE_LIGHTS;
}
if( m_bInteriorLightForceOn )
{
iFlags |= LIGHTS_FORCE_INTERIOR_LIGHT;
}
Vec4V ambientVolumeParams = Vec4V(V_ZERO_WONE);
// lower AO Volume for selected vehicles:
const u32 vehNameHash = GetBaseModelInfo()->GetModelNameHash();
if (vehNameHash == MID_ASBO)
{
ambientVolumeParams = Vec4V(Asbo_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ARDENT)
{
ambientVolumeParams = Vec4V(Ardent_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_BRIOSO2)
{
ambientVolumeParams = Vec4V(Brioso2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_BRIOSO3)
{
ambientVolumeParams = Vec4V(Brioso3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_CHAMPION)
{
ambientVolumeParams = Vec4V(Champion_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_CHEETAH2)
{
ambientVolumeParams = Vec4V(Cheetah2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_CLIQUE)
{
ambientVolumeParams = Vec4V(Clique_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_COMET4)
{
ambientVolumeParams = Vec4V(Comet4_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_COMET6)
{
ambientVolumeParams = Vec4V(Comet6_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_COMET7)
{
ambientVolumeParams = Vec4V(Comet7_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_COQUETTE4)
{
ambientVolumeParams = Vec4V(Coquette4_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_CORSITA)
{
ambientVolumeParams = Vec4V(Corsita_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_CYCLONE2)
{
ambientVolumeParams = Vec4V(Cyclone2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_CYPHER)
{
ambientVolumeParams = Vec4V(Cypher_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DEVESTE)
{
ambientVolumeParams = Vec4V(Deveste_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DOMINATOR8)
{
ambientVolumeParams = Vec4V(Dominator8_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DRAFTER)
{
ambientVolumeParams = Vec4V(Drafter_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DRAUGUR)
{
ambientVolumeParams = Vec4V(Draugur_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DUNE4)
{
ambientVolumeParams = Vec4V(Dune4_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DUNE5)
{
ambientVolumeParams = Vec4V(Dune5_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DUKES3)
{
ambientVolumeParams = Vec4V(Dukes3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_DYNASTY)
{
ambientVolumeParams = Vec4V(Dynasty_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ELLIE)
{
ambientVolumeParams = Vec4V(Ellie_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_EUROS)
{
ambientVolumeParams = Vec4V(Euros_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_FLASHGT)
{
ambientVolumeParams = Vec4V(FlashGT_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_FMJ)
{
ambientVolumeParams = Vec4V(Fmj_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_FORMULA)
{
ambientVolumeParams = Vec4V(Formula_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_FURIA)
{
ambientVolumeParams = Vec4V(Furia_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_IGNUS)
{
ambientVolumeParams = Vec4V(Ignus_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_IGNUS2)
{
ambientVolumeParams = Vec4V(Ignus2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_GB200)
{
ambientVolumeParams = Vec4V(GB200_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_GP1)
{
ambientVolumeParams = Vec4V(GP1_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_GAUNTLET4)
{
ambientVolumeParams = Vec4V(Gauntlet4_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_GAUNTLET5)
{
ambientVolumeParams = Vec4V(Gauntlet5_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_GROWLER)
{
ambientVolumeParams = Vec4V(Growler_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_HOTRING)
{
ambientVolumeParams = Vec4V(Hotring_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_HUSTLER)
{
ambientVolumeParams = Vec4V(Hustler_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_IMORGON)
{
ambientVolumeParams = Vec4V(Imorgon_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_INFERNUS2)
{
ambientVolumeParams = Vec4V(Infernus2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ISSI3)
{
ambientVolumeParams = Vec4V(Issi3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ISSI7)
{
ambientVolumeParams = Vec4V(Issi7_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ITALIGTO)
{
ambientVolumeParams = Vec4V(Italigto_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ITALIRSX)
{
ambientVolumeParams = Vec4V(ItaliRsx_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_JESTER3)
{
ambientVolumeParams = Vec4V(Jester3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_JESTER4)
{
ambientVolumeParams = Vec4V(Jester4_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_JUBILEE)
{
ambientVolumeParams = Vec4V(Jubilee_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_KOMODA)
{
ambientVolumeParams = Vec4V(Komoda_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_KRIEGER)
{
ambientVolumeParams = Vec4V(Krieger_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_MANANA2)
{
ambientVolumeParams = Vec4V(Manana2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_MENACER)
{
ambientVolumeParams = Vec4V(Menacer_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_NEO)
{
ambientVolumeParams = Vec4V(Neo_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_NEON)
{
ambientVolumeParams = Vec4V(Neon_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_NERO2)
{
ambientVolumeParams = Vec4V(Nero2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_OPENWHEEL1)
{
ambientVolumeParams = Vec4V(Openwheel1_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_OPENWHEEL2)
{
ambientVolumeParams = Vec4V(Openwheel2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_PARAGON)
{
ambientVolumeParams = Vec4V(Paragon_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_PENETRATOR)
{
ambientVolumeParams = Vec4V(Penetrator_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_PENUMBRA2)
{
ambientVolumeParams = Vec4V(Penumbra2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_PEYOTE3)
{
ambientVolumeParams = Vec4V(Peyote3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_POLBUFFALO)
{
ambientVolumeParams = Vec4V(Polbuffalo_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_POLGREENWOOD)
{
ambientVolumeParams = Vec4V(Polgreenwood_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_RAPIDGT3)
{
ambientVolumeParams = Vec4V(RapidGT3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_REMUS)
{
ambientVolumeParams = Vec4V(Remus_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_RT3000)
{
ambientVolumeParams = Vec4V(RT3000_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_RUSTON)
{
ambientVolumeParams = Vec4V(Ruston_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_S80)
{
ambientVolumeParams = Vec4V(S80_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_SC1)
{
ambientVolumeParams = Vec4V(SC1_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_SEASPARROW2)
{
ambientVolumeParams = Vec4V(Seasparrow2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_SCHLAGEN)
{
ambientVolumeParams = Vec4V(Schlagen_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_SM722)
{
ambientVolumeParams = Vec4V(SM722_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_STAFFORD)
{
ambientVolumeParams = Vec4V(Stafford_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_STROMBERG)
{
ambientVolumeParams = Vec4V(Stromberg_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_SWINGER)
{
ambientVolumeParams = Vec4V(Swinger_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_SUGOI)
{
ambientVolumeParams = Vec4V(Sugoi_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_TAILGATER2)
{
ambientVolumeParams = Vec4V(Tailgater2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_TAMPA3)
{
ambientVolumeParams = Vec4V(Tampa3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_THRAX)
{
ambientVolumeParams = Vec4V(Thrax_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_TIGON)
{
ambientVolumeParams = Vec4V(Tigon_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_TOREADOR)
{
ambientVolumeParams = Vec4V(Toreador_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_TORERO2)
{
ambientVolumeParams = Vec4V(Torero2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_TYRANT)
{
ambientVolumeParams = Vec4V(Tyrant_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_VAGNER)
{
ambientVolumeParams = Vec4V(Vagner_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_VAGRANT)
{
ambientVolumeParams = Vec4V(Vagrant_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_VERUS)
{
ambientVolumeParams = Vec4V(Verus_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_VETO2)
{
ambientVolumeParams = Vec4V(Veto2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_VOODOO)
{
ambientVolumeParams = Vec4V(Voodoo_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_VSTR)
{
ambientVolumeParams = Vec4V(Vstr_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_WARRENER2)
{
ambientVolumeParams = Vec4V(Warrener2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_WEEVIL)
{
ambientVolumeParams = Vec4V(Weevil_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_WEEVIL2)
{
ambientVolumeParams = Vec4V(Weevil2_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_XA21)
{
ambientVolumeParams = Vec4V(XA21_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_YOUGA3)
{
ambientVolumeParams = Vec4V(Youga3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ZENO)
{
ambientVolumeParams = Vec4V(Zeno_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ZION3)
{
ambientVolumeParams = Vec4V(Zion3_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ZORRUSSO)
{
ambientVolumeParams = Vec4V(Zorrusso_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ZR350)
{
ambientVolumeParams = Vec4V(ZR350_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ZR3802)
{
ambientVolumeParams = Vec4V(ZR3802_AmbientVolume_Offset, ScalarV(V_ONE));
}
else if (vehNameHash == MID_ZR3803)
{
ambientVolumeParams = Vec4V(ZR3803_AmbientVolume_Offset, ScalarV(V_ONE));
}
DoVehicleLights(iFlags, ambientVolumeParams);
// Sets the indicator bits in the Vehicle flags straight after they have been queried for the lights.
m_nVehicleFlags.bForceBrakeLightOn = false;
}
void CAutomobile::ProcessSuspensionTransforms()
{
const bool bIsDummy = IsDummy();
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
if(!fwTimer::IsGamePaused() && !bIsDummy && !m_nVehicleFlags.bAnimateWheels && GetIsVisibleInSomeViewportThisFrame())
{
SuspensionType nType = GetSuspensionType(false);
bool bIsMiniTank = GetModelIndex() == MI_CAR_MINITANK.GetModelIndex();
if( bIsMiniTank )
{
ProcessSuspensionTransformsForMiniTank();
return;
}
if(!IsTrike())
{
SetSuspensionTransformation(VEH_SUSPENSION_LF, VEH_WHEEL_LF, nType, &RCC_VEC3V(m_vSuspensionDeformationLF));
SetSuspensionTransformation(VEH_SUSPENSION_RF, VEH_WHEEL_RF, nType, &RCC_VEC3V(m_vSuspensionDeformationRF));
//All vehicles with springs currently use Sus_Solid (squashing type) (Quadbikes, RC Cars)
//If this changes we'll need to move some model flags around to support it as they're currently full
SetSuspensionTransformation(VEH_SPRING_LF, VEH_WHEEL_LF, Sus_Solid);
SetSuspensionTransformation(VEH_SPRING_RF, VEH_WHEEL_RF, Sus_Solid);
}
else if (GetBoneIndex(VEH_TRANSMISSION_F) != -1)
{
SetTransmissionRotationForTrike(VEH_TRANSMISSION_F, VEH_WHEEL_LF);
}
nType = GetSuspensionType(true);
SetSuspensionTransformation(VEH_SUSPENSION_LR, VEH_WHEEL_LR, nType);
SetSuspensionTransformation(VEH_SUSPENSION_RR, VEH_WHEEL_RR, nType);
if( GetBoneIndex( VEH_TRANSMISSION_M1 ) == -1 )
{
SetSuspensionTransformation(VEH_SUSPENSION_LM, VEH_WHEEL_LM1, nType);
SetSuspensionTransformation(VEH_SUSPENSION_RM, VEH_WHEEL_RM1, nType);
}
else
{
SetSuspensionTransformation(VEH_SUSPENSION_LM1, VEH_WHEEL_LM1, nType);
SetSuspensionTransformation(VEH_SUSPENSION_RM1, VEH_WHEEL_RM1, nType);
SetSuspensionTransformation(VEH_SUSPENSION_LM, VEH_WHEEL_LM2, nType);
SetSuspensionTransformation(VEH_SUSPENSION_RM, VEH_WHEEL_RM2, nType);
}
SetSuspensionTransformation(VEH_SPRING_LR, VEH_WHEEL_LR, Sus_Solid);
SetSuspensionTransformation(VEH_SPRING_RR, VEH_WHEEL_RR, Sus_Solid);
//Trailers can be setup without front wheels and multiple middle/rear wheels
if(GetBoneIndex(VEH_WHEEL_LF) != -1 && GetBoneIndex(VEH_WHEEL_RF) != -1)
{
SetTransmissionRotation(VEH_TRANSMISSION_F, VEH_WHEEL_LF, VEH_WHEEL_RF);
if( GetBoneIndex( VEH_TRANSMISSION_M1 ) == -1 )
{
SetTransmissionRotation(VEH_TRANSMISSION_M, VEH_WHEEL_LM1, VEH_WHEEL_RM1);
}
else
{
SetTransmissionRotation(VEH_TRANSMISSION_M1, VEH_WHEEL_LM1, VEH_WHEEL_RM1);
SetTransmissionRotation(VEH_TRANSMISSION_M, VEH_WHEEL_LM2, VEH_WHEEL_RM2);
}
}
else
{
if(GetBoneIndex(VEH_WHEEL_LM2) != -1 && GetBoneIndex(VEH_WHEEL_RM2) != -1)
{
SetTransmissionRotation(VEH_TRANSMISSION_F, VEH_WHEEL_LM1, VEH_WHEEL_RM1);
SetTransmissionRotation(VEH_TRANSMISSION_M, VEH_WHEEL_LM2, VEH_WHEEL_RM2);
}
else
{
SetTransmissionRotation(VEH_TRANSMISSION_F, VEH_WHEEL_LM1, VEH_WHEEL_RM1);
SetTransmissionRotation(VEH_TRANSMISSION_M, VEH_WHEEL_LM1, VEH_WHEEL_RM1);
}
}
SetTransmissionRotation(VEH_TRANSMISSION_R, VEH_WHEEL_LR, VEH_WHEEL_RR);
}
}
}
void CAutomobile::ProcessSuspensionTransformsForMiniTank()
{
if( crSkeleton* pSkeleton = GetSkeleton() )
{
u16 trackHash = atHash16U( "track" );
int boneIdx = -1;
pSkeleton->GetSkeletonData().ConvertBoneIdToIndex( trackHash, boneIdx );
eHierarchyId miscBoneHierachyIds[ 4 ] = { VEH_MISC_A, VEH_MISC_C, VEH_MISC_B, VEH_MISC_D };
eHierarchyId wheelBoneHierachyIds[ 4 ] = { VEH_WHEEL_LF, VEH_WHEEL_RF, VEH_WHEEL_LR, VEH_WHEEL_RR };
Vector3 wheelPosnL = RCC_VECTOR3( pSkeleton->GetSkeletonData().GetBoneData( boneIdx )->GetDefaultTranslation() );
Matrix34 &wheelMatL = RC_MATRIX34( pSkeleton->GetLocalMtx( boneIdx ) );
Vector3 wheelPositions[ 4 ];
float averageSuspensionLength = 0.0f;
static dev_float sfWheelOffsetToGround = 0.08f;
for( int i = 0; i < 4; i++ )
{
GetWheel( i )->GetWheelPosAndRadius( wheelPositions[ i ] );
wheelPositions[ i ] = VEC3V_TO_VECTOR3( GetTransform().UnTransform( VECTOR3_TO_VEC3V( wheelPositions[ i ] ) ) );
wheelPositions[ i ].SetZ( wheelPositions[ i ].GetZ() - ( GetWheel( i )->GetWheelRadius() ) );
float suspensionCompression = GetWheel( i )->GetWheelCompressionFromStaticIncBurstAndSink() - ( GetWheel( i )->GetWheelImpactOffset() + sfWheelOffsetToGround );
averageSuspensionLength += suspensionCompression;
int nWheelBoneIndex = GetBoneIndex( wheelBoneHierachyIds[ i ] );
int nMiscBoneIndex = GetBoneIndex( wheelBoneHierachyIds[ i ] );
Vector3 defaultWheelPos = VEC3V_TO_VECTOR3( pSkeleton->GetSkeletonData().GetBoneData( nWheelBoneIndex )->GetDefaultTranslation() );
Vector3 defaultMiscBonePos = VEC3V_TO_VECTOR3( pSkeleton->GetSkeletonData().GetBoneData( nMiscBoneIndex )->GetDefaultTranslation() );
float zDiffBetweenWheelAndMiscBone = defaultWheelPos.GetZ() - defaultMiscBonePos.GetZ();
Vector3 wheelOffset( 0.0f, 0.0f, suspensionCompression - zDiffBetweenWheelAndMiscBone );
SetComponentRotation( miscBoneHierachyIds[ i ], ROT_AXIS_LOCAL_X, GetWheel( i )->GetRotAngle(), true, NULL, &wheelOffset );
}
averageSuspensionLength *= 0.25f;
Vector3 suspensionOffset = Vector3( 0.0f, 0.0f, averageSuspensionLength );
Vector3 vecForward = ( ( wheelPositions[ 0 ] + wheelPositions[ 1 ] ) - ( wheelPositions[ 2 ] + wheelPositions[ 3 ] ) ) * 0.5f;
Vector3 vecRight = ( ( wheelPositions[ 1 ] + wheelPositions[ 3 ] ) - ( wheelPositions[ 0 ] + wheelPositions[ 2 ] ) ) * 0.5f;
Vector3 vecUp = vecRight;
vecUp.Cross( vecForward );
vecForward.Normalize();
vecRight.Normalize();
vecUp.Normalize();
wheelMatL.a = vecRight;
wheelMatL.b = vecForward;
wheelMatL.c = vecUp;
wheelMatL.d = wheelPosnL + suspensionOffset;
}
}
void CAutomobile::PreRenderSuspensionMod(const float fFakeSuspensionLowerAmount)
{
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
return;
}
#endif //GTA_REPLAY
if(GetVehicleType() == VEHICLE_TYPE_CAR && fFakeSuspensionLowerAmount != 0.0f)
{
crSkeleton* skel = GetSkeleton();
const u32 boneCount = skel->GetBoneCount();
int wheelCount = GetNumWheels();
int hubCount = VEH_WHEELHUB_RM3 - VEH_WHEELHUB_LF + 1;
static bool bIgnoreLists [VEH_NUM_NODES];
sysMemSet(bIgnoreLists, 0, sizeof(bool) * boneCount);
// Make sure we don't modify the wheel matrices
for (int k=0; k<wheelCount; k++)
{
if(GetWheel(k))
{
int iBone = GetBoneIndex(GetWheel(k)->GetHierarchyId());
if(iBone >= 0)
{
bIgnoreLists [iBone] = true;
}
}
}
// ... or broken off doors
for (s32 f = 0; f < GetNumDoors(); ++f)
{
CCarDoor* door = GetDoor(f);
if (door->GetFlag(CCarDoor::IS_BROKEN_OFF) && door->GetBoneIndex() >= 0)
{
bIgnoreLists [door->GetBoneIndex()] = true;
}
}
for (int k=0; k<hubCount; k++)
{
eHierarchyId boneId = (eHierarchyId) (k + VEH_WHEELHUB_LF);
if (GetBoneIndex(boneId) >= 0)
{
bIgnoreLists [GetBoneIndex(boneId)] = true;
}
}
for(int i = 0; i < boneCount; i++)
{
if (bIgnoreLists[i])
continue;
Matrix34& boneMatrix1 = RC_MATRIX34(skel->GetObjectMtx(i));
boneMatrix1.Translate(0.0f, 0.0f, -fFakeSuspensionLowerAmount);
}
}
}
static dev_float velToStartFakeBodyMovement = 10.0f;
static dev_float velToEndFakeBodyMovement = 40.0f;
static dev_float sfRandomChanceToBump = 1.5f;
static dev_float sfBumpSeverityY = 0.02f;
//static dev_float sfBumpSeverityX = 0.0125f;
static dev_float sfBumpRateStart = 10.0f;
static dev_float sfBumpRateStop = 20.0f;
static dev_float sfBumpTimeStart = 0.2f;
static dev_float sfBumpTimeStop = 1.8f;
static dev_float sfBumpRandomNoiceScale = 0.5f;
static dev_float sfEngineRevSeverityY = 0.02f;
static dev_float sfEngineRevBumpSeverityY = 0.008f;
static dev_float sfEngineRevBumpRateStart = 50.0f;
static dev_float sfEngineRevBumpRateStop = 150.0f;
static dev_float sfEngineRevBumpTimeStart = 0.35f;
static dev_float sfEngineRevBumpTimeStop = 0.6f;
static dev_float sfEngineRevRandomNoiceScale = 0.0f;
static dev_float sfEngineRevMovementStart = 0.25f;
void CAutomobile::ProcessFakeBodyMovement(bool bIsGamePaused)
{
if(bIsGamePaused)
{
return;
}
TUNE_GROUP_BOOL(TURRET_TUNE, DISABLE_FAKE_BODY_MOVEMENT_FOR_LOCAL_PLAYER_IN_TURRET_SEAT, true);
// B*2150619
if (DISABLE_FAKE_BODY_MOVEMENT_FOR_LOCAL_PLAYER_IN_TURRET_SEAT)
{
if (GetVehicleModelInfo() && GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_TURRET_SEAT_ON_VEHICLE))
{
const CPed* pPlayer = CGameWorld::FindLocalPlayer();
if (pPlayer && pPlayer->GetIsInVehicle() && pPlayer->GetMyVehicle() == this)
{
const s32 iPlayersSeatIndex = GetSeatManager()->GetPedsSeatIndex(pPlayer);
if (IsTurretSeat(iPlayersSeatIndex))
{
return;
}
}
}
}
if(GetVehicleType() == VEHICLE_TYPE_CAR)
{
float speed = GetVelocityIncludingReferenceFrame().Mag();
float velModifier = (speed - velToStartFakeBodyMovement) / (velToEndFakeBodyMovement - velToStartFakeBodyMovement);
velModifier = Clamp(velModifier, 0.0f, 1.0f);
const float fRevRatio = m_Transmission.GetRevRatio();
float fRevModifier = (fRevRatio - sfEngineRevMovementStart) / (1.0f - sfEngineRevMovementStart);
float fRevRotationBase = 0.0f;
float fRandomNoise = 0.0f;
bool bShouldPlayEngineRev = GetIsStatic() && (GetStatus()==STATUS_PLAYER) && (fRevRatio > sfEngineRevMovementStart);
// if we're going fast enough then add a bump
if (speed > velToStartFakeBodyMovement)
{
//make sure were not already doing a bump
if( m_fHighSpeedYTime <= 0.0f && fwRandom::GetRandomNumberInRange(0.0f,1.0f) < sfRandomChanceToBump * velModifier)
{
m_fBumpSeverityY = fwRandom::GetRandomNumberInRange( 0.0f, sfBumpSeverityY * velModifier * ms_fBumpSeverityMulti);
m_fBumpRateY = fwRandom::GetRandomNumberInRange( sfBumpRateStart, sfBumpRateStop );
m_fHighSpeedYTime = fwRandom::GetRandomNumberInRange( sfBumpTimeStart, sfBumpTimeStop );
m_fBumpSeverityYDecay = m_fBumpSeverityY/m_fHighSpeedYTime;
// do we want to bump from the left or right?
if(fwRandom::GetRandomTrueFalse())
{
m_fHighSpeedRotationY = 2.0f*PI;
m_fHighSpeedDirectionY = -1.0f;
}
else
{
m_fHighSpeedRotationY = 0.0f;
m_fHighSpeedDirectionY = 1.0f;
}
}
fRandomNoise = rage::Sinf(fwRandom::GetRandomNumberInRange( -PI, PI )) * sfBumpRandomNoiceScale;
}
// if engine revs while car sits still
else if(bShouldPlayEngineRev)
{
//make sure were not already doing a bump
if( m_fHighSpeedYTime <= 0.0f && fwRandom::GetRandomNumberInRange(0.0f,1.0f) < sfRandomChanceToBump * fRevModifier)
{
m_fBumpSeverityY = fwRandom::GetRandomNumberInRange( 0.0f, sfEngineRevBumpSeverityY * fRevModifier);
m_fBumpRateY = fwRandom::GetRandomNumberInRange( sfEngineRevBumpRateStart, sfEngineRevBumpRateStop );
m_fHighSpeedYTime = fwRandom::GetRandomNumberInRange( sfEngineRevBumpTimeStart, sfEngineRevBumpTimeStop );
m_fBumpSeverityYDecay = m_fBumpSeverityY/m_fHighSpeedYTime;
// do we want to bump from the left or right?
if(fwRandom::GetRandomTrueFalse())
{
m_fHighSpeedRotationY = 2.0f*PI;
m_fHighSpeedDirectionY = -1.0f;
}
else
{
m_fHighSpeedRotationY = 0.0f;
m_fHighSpeedDirectionY = 1.0f;
}
}
fRevRotationBase = sfEngineRevSeverityY * fRevModifier;
fRandomNoise = rage::Sinf(fwRandom::GetRandomNumberInRange( -PI, PI )) * sfEngineRevRandomNoiceScale;
}
if(m_fHighSpeedYTime > 0.0f)
{
m_fHighSpeedYTime -= fwTimer::GetTimeStep();
m_fBumpSeverityY -= m_fBumpSeverityYDecay * fwTimer::GetTimeStep();
if(m_fBumpSeverityY < 0.0f)
m_fBumpSeverityY = 0.0f;
m_fHighSpeedRotationY += (m_fBumpRateY * fwTimer::GetTimeStep()) * m_fHighSpeedDirectionY;
if(m_fHighSpeedRotationY > 2.0f*PI)
{
m_fHighSpeedRotationY = 0.0f;
}
else if(m_fHighSpeedRotationY < 0.0f)
{
m_fHighSpeedRotationY = 2.0f*PI;
}
}
else
{
m_fHighSpeedRotationY = 0.0f;
}
bool bRotationY = m_fHighSpeedRotationY > 0.0f || fRevRotationBase > 0.0f;
if (!bRotationY)
return;
int wheelCount = VEH_WHEEL_LAST_WHEEL - VEH_WHEEL_FIRST_WHEEL + 1;
int hubCount = VEH_WHEELHUB_RM3 - VEH_WHEELHUB_LF + 1;
m_fFakeRotationY = 0.f;
if (bRotationY)
{
m_fFakeRotationY = (rage::Sinf(m_fHighSpeedRotationY) + fRandomNoise) * m_fBumpSeverityY;
}
m_fFakeRotationY += fRevRotationBase;
crSkeleton* skel = GetSkeleton();
const u32 boneCount = skel->GetBoneCount();
for(int i = 0; i < boneCount; i++)
{
bool bIgnoreBone = false;
// Make sure we don't modify the wheel matrices
for (int k=0; k<wheelCount; k++)
{
eHierarchyId boneId = (eHierarchyId) (k + VEH_WHEEL_FIRST_WHEEL);
if (GetBoneIndex(boneId) == i)
{
bIgnoreBone = true;
break;
}
}
// ... or broken off doors
for (s32 f = 0; f < GetNumDoors(); ++f)
{
CCarDoor* door = GetDoor(f);
if (door->GetFlag(CCarDoor::IS_BROKEN_OFF) && door->GetBoneIndex() == i)
{
bIgnoreBone = true;
break;
}
}
if (bIgnoreBone)
continue;
for (int k=0; k<hubCount; k++)
{
eHierarchyId boneId = (eHierarchyId) (k + VEH_WHEELHUB_LF);
if (GetBoneIndex(boneId) == i)
{
bIgnoreBone = true;
break;
}
}
for( int k = (int)VEH_TURRET_FIRST_MOD; k < (int)VEH_TURRET_LAST_MOD; k++ )
{
eHierarchyId boneId = (eHierarchyId) (k);
if (GetBoneIndex(boneId) == i)
{
bIgnoreBone = true;
break;
}
}
for( int k = (int)VEH_TURRET_A_FIRST_MOD; k < (int)VEH_TURRET_A_LAST_MOD; k++ )
{
eHierarchyId boneId = (eHierarchyId) (k);
if (GetBoneIndex(boneId) == i)
{
bIgnoreBone = true;
break;
}
}
for( int k = (int)VEH_TURRET_B_FIRST_MOD; k < (int)VEH_TURRET_B_LAST_MOD; k++ )
{
eHierarchyId boneId = (eHierarchyId) (k);
if (GetBoneIndex(boneId) == i)
{
bIgnoreBone = true;
break;
}
}
if(bIgnoreBone == false)
{
Matrix34& boneMatrix1 = RC_MATRIX34(skel->GetObjectMtx(i));
if(bRotationY)
{
boneMatrix1.RotateFullY(m_fFakeRotationY);
}
}
}
}
}
dev_float dfWheelPoppingSteeringBiasMaxSpeed = 16.67f; //about 60km/h
void CAutomobile::NotifyWheelPopped(CWheel *pWheel)
{
float fBiasMulti = Clamp(GetVelocityIncludingReferenceFrame().Mag() / dfWheelPoppingSteeringBiasMaxSpeed, 0.0f, 1.0f);
fBiasMulti *= m_fSteeringBiasScalar;
Vector3 vOffset = CWheel::GetWheelOffset(GetVehicleModelInfo(), pWheel->GetHierarchyId());
bool bIsLeftWheel = vOffset.x > 0.0f;
float fSteeringBias = bfSteeringBiasWhenWheelPopped * DtoR * (bIsLeftWheel ? 1.0f : -1.0f) * fBiasMulti;
float fSteeringBiasLife = bfSteeringBiasLifeAfterWheelPopped * fBiasMulti;
ApplySteeringBias(fSteeringBias, fSteeringBiasLife);
}
void CAutomobile::ApplySteeringBias(float bias, float lifetime)
{
m_fSteeringBias = bias;
m_fSteeringBiasLife = lifetime;
}
#define NUM_FX_BONES (22)
static eHierarchyId aFxBones[NUM_FX_BONES] =
{
VEH_EXHAUST, // 0
VEH_EXHAUST_2, // 1
VEH_EXHAUST_3, // 2
VEH_EXHAUST_4, // 3
VEH_EXHAUST_5, // 4
VEH_EXHAUST_6, // 5
VEH_EXHAUST_7, // 6
VEH_EXHAUST_8, // 7
VEH_EXHAUST_9, // 8
VEH_EXHAUST_10, // 9
VEH_EXHAUST_11, // 10
VEH_EXHAUST_12, // 11
VEH_EXHAUST_13, // 12
VEH_EXHAUST_14, // 13
VEH_EXHAUST_15, // 14
VEH_EXHAUST_16, // 15
VEH_OVERHEAT, // 16
VEH_OVERHEAT_2, // 17
VEH_ENGINE, // 18
VEH_PETROLCAP, // 19
VEH_TOW_MOUNT_A, // 20
VEH_TOW_MOUNT_B, // 21
};
void CAutomobile::ApplyDeformationToBones(const void* basePtr)
{
for(int i=0; i<GetNumDoors(); i++)
{
GetDoor(i)->ApplyDeformation(this, basePtr);
}
GetVariationInstance().ApplyDeformation(this, basePtr);
if(basePtr)
{
// FXs : we deform those FX nodes so that smokes, flames and other details will come from the right point.
for(int i=0; i<NUM_FX_BONES; i++)
{
// GTA V hack - If the vehicle has rocket boost, do not deform the exhaust bone since the booster is not skinned to the exhaust bone
// but the VFX is
if(HasRocketBoost() && aFxBones[i] == VEH_EXHAUST)
{
continue;
}
int boneIdx = GetBoneIndex(aFxBones[i]);
if( boneIdx != -1 )
{
Vector3 vertPos;
GetDefaultBonePositionSimple(boneIdx,vertPos);
Vector3 damageLocal = VEC3V_TO_VECTOR3(GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vertPos)));
int nParentBoneIndex = GetSkeletonData().GetBoneData(boneIdx)->GetParent()->GetIndex();
if(nParentBoneIndex != -1)
{
const Matrix34 &matParentBone = GetObjectMtx(nParentBoneIndex);
matParentBone.UnTransform3x3(damageLocal);
}
Matrix34 &mtxLocal = GetLocalMtxNonConst(boneIdx);
mtxLocal.d = VEC3V_TO_VECTOR3(GetSkeletonData().GetBoneData(boneIdx)->GetDefaultTranslation()) + damageLocal;
}
}
// Loop through all the attached stick bombs and update their positions
if(fwAttachmentEntityExtension *extension = GetAttachmentExtension())
{
CPhysical* pCurChild = static_cast<CPhysical*>(extension->GetChildAttachment());
while(pCurChild)
{
if(pCurChild->GetIsTypeObject())
{
if(CProjectile *pProjectile = ((CObject *)pCurChild)->GetAsProjectile())
{
pProjectile->ApplyDeformation(this, basePtr);
}
}
fwAttachmentEntityExtension &curChildAttachExt = pCurChild->GetAttachmentExtensionRef();
pCurChild = static_cast<CPhysical*>(curChildAttachExt.GetSiblingAttachment());
}
}
int iBone = GetBoneIndex(VEH_SUSPENSION_LF);
if(iBone != -1)
{
Vector3 vertPos;
GetDefaultBonePositionSimple(iBone,vertPos);
m_vSuspensionDeformationLF = VEC3V_TO_VECTOR3(GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vertPos)));
}
iBone = GetBoneIndex(VEH_SUSPENSION_RF);
if(iBone != -1)
{
Vector3 vertPos;
GetDefaultBonePositionSimple(iBone,vertPos);
m_vSuspensionDeformationRF = VEC3V_TO_VECTOR3(GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vertPos)));
}
}
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : BlowUpCar
// PURPOSE : Does everything needed to destroy a car
///////////////////////////////////////////////////////////////////////////////////
void CAutomobile::BlowUpCar( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion)
{
weaponDebugf3("CAutomobile::BlowUpCar bNetCall[%d] GetStatus()[%d]",bNetCall,GetStatus());
CVehicle::BlowUpCar(pCulprit);
#if __DEV
if (gbStopVehiclesExploding)
{
return;
}
#endif
if (GetStatus() == STATUS_WRECKED)
{
// Don't blow cars up a 2nd time (but make sure we kill any occupants in case they aren't already dead -- e.g. car drowned).
KillPedsInVehicle(pCulprit, weaponHash);
KillPedsGettingInVehicle(pCulprit);
return;
}
// we can't blow up cars controlled by another machine
// but we still have to change their status to wrecked
// so the car doesn't blow up if we take control of an
// already blown up car
if (IsNetworkClone())
{
if (!bNetCall)
{
weaponWarningf("CAutomobile::BlowUpCar--Note--IsNetworkClone--!bNetCall-->return");
return;
}
KillPedsInVehicle(pCulprit, weaponHash);
KillPedsGettingInVehicle(pCulprit);
// knock bits off the car
GetVehicleDamage()->BlowUpCarParts(pCulprit);
// Break lights, windows and sirens
GetVehicleDamage()->BlowUpVehicleParts(pCulprit);
if( bAddExplosion )
{
m_nPhysicalFlags.bRenderScorched = TRUE;
}
else
{
// GTAV - B*1948742 - If we're not adding an explosion
// check the last damage has to see if we need to render scorched.
u32 weaponDamageHash = GetWeaponDamageHash();
if( weaponDamageHash == WEAPONTYPE_FIRE ||
weaponDamageHash == WEAPONTYPE_EXPLOSION )
{
m_nPhysicalFlags.bRenderScorched = TRUE;
}
}
SetTimeOfDestruction();
SetHealth(0.0f, true);
SetIsWrecked();
// Switch off the engine. (For sound purposes)
this->SwitchEngineOff(false);
this->m_OverrideLights = NO_CAR_LIGHT_OVERRIDE;
this->m_nVehicleFlags.bLightsOn = FALSE;
this->TurnSirenOn(FALSE);
this->m_nVehicleFlags.bApproachingJunctionWithSiren = FALSE;
this->m_nVehicleFlags.bPreviousApproachingJunctionWithSiren = FALSE;
this->m_nAutomobileFlags.bTaxiLight = FALSE;
// set the engine temp super high so we get nice sounds as the chassis cools down
m_EngineTemperature = MAX_ENGINE_TEMPERATURE;
g_decalMan.Remove(this);
//Check to see that it is the player or a vehicle driven by the player
if( (pCulprit && pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer())
|| (pCulprit && pCulprit->GetIsTypeVehicle() && ((CVehicle*)pCulprit)->GetDriver() == CGameWorld::FindLocalPlayer()) )
{
CStatsMgr::RegisterVehicleBlownUpByPlayer(this);
}
return;
}
if (m_nPhysicalFlags.bNotDamagedByAnything)
{
return; // If the flag is set for don't damage this car (usually during a cutscene)
}
if (NetworkUtils::IsNetworkCloneOrMigrating(this))
{
// the vehicle is migrating. Create a weapon damage event to blow up the vehicle, which will be sent to the new owner. If the migration fails
// then the vehicle will be blown up a little later.
CBlowUpVehicleEvent::Trigger(*this, pCulprit, bAddExplosion, weaponHash, bDelayedExplosion);
return;
}
//Total damage done for the damage trackers
float totalDamage = GetHealth() + m_VehicleDamage.GetEngineHealth() + m_VehicleDamage.GetPetrolTankHealth();
for(s32 i=0; i<GetNumWheels(); i++)
{
totalDamage += m_VehicleDamage.GetTyreHealth(i);
totalDamage += m_VehicleDamage.GetSuspensionHealth(i);
}
totalDamage = totalDamage > 0.0f ? totalDamage : 1000.0f;
if (pCulprit && ((pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer()) || pCulprit == CGameWorld::FindLocalPlayerVehicle()))
{
// If this is the kill shot we need to report the destroying vehicle crime
if(GetHealth() > 0.0f)
{
CPed* pInflictorPed = pCulprit->GetIsTypeVehicle() ? static_cast<CVehicle*>(pCulprit)->GetDriver() : static_cast<CPed*>(pCulprit);
CCrime::ReportDestroyVehicle(this, pInflictorPed);
}
CGameWorld::FindLocalPlayer()->GetPlayerInfo()->HavocCaused += HAVOC_BLOWUPCAR;
}
if( bAddExplosion )
{
AddVehicleExplosion(pCulprit, bInACutscene, bDelayedExplosion);
if (CVehicleDeformation::ms_iExtraExplosionDeformations > 0)
{
CVehicleDamage::AddVehicleExplosionDeformations(this, pCulprit, DAMAGE_TYPE_EXPLOSIVE, CVehicleDeformation::ms_iExtraExplosionDeformations, CVehicleDeformation::ms_fExtraExplosionsDamage);
}
}
else
{
m_nPhysicalFlags.bRenderScorched = TRUE; // need to make Scorched BEFORE components blow off
}
// knock bits off the car
GetVehicleDamage()->BlowUpCarParts(pCulprit, CVehicleDamage::Break_Off_Car_Parts_Pending_Bound_Update);
// Break lights, windows and sirens
GetVehicleDamage()->BlowUpVehicleParts(pCulprit);
SetIsWrecked();
//Set the destruction information.
SetDestructionInfo(pCulprit, weaponHash);
SetHealth(0.0f); // Make sure this happens before AddExplosion or it will blow up twice
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
// Vector3 Temp = GetPosition();
KillPedsInVehicle(pCulprit, weaponHash);
KillPedsGettingInVehicle(pCulprit);
// Switch off the engine. (For sound purposes)
this->SwitchEngineOff(false);
this->m_OverrideLights = NO_CAR_LIGHT_OVERRIDE;
this->m_nVehicleFlags.bLightsOn = FALSE;
this->TurnSirenOn(FALSE);
this->m_nVehicleFlags.bApproachingJunctionWithSiren = FALSE;
this->m_nVehicleFlags.bPreviousApproachingJunctionWithSiren = FALSE;
this->m_nAutomobileFlags.bTaxiLight = FALSE;
// set the engine temp super high so we get nice sounds as the chassis cools down
m_EngineTemperature = MAX_ENGINE_TEMPERATURE;
//Update Damage Trackers
GetVehicleDamage()->UpdateDamageTrackers(pCulprit, weaponHash, DAMAGE_TYPE_EXPLOSIVE, totalDamage, false);
//Check to see that it is the player
if (pCulprit && ((pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer()) || pCulprit == CGameWorld::FindLocalPlayerVehicle()))
{
CStatsMgr::RegisterVehicleBlownUpByPlayer(this, (pCulprit == CGameWorld::FindLocalPlayerVehicle()) ? totalDamage : 0.0f);
}
if(GetAttachedTrailer())
{
if( !MI_TRAILER_TRAILERLARGE.IsValid() ||
GetAttachedTrailer()->GetModelIndex() != MI_TRAILER_TRAILERLARGE )
{
GetAttachedTrailer()->BlowUpCar( pCulprit, bInACutscene, bAddExplosion, bNetCall, weaponHash);
}
}
if (m_nAutomobileFlags.bDropsMoneyWhenBlownUp)
{
for (s32 i = 0; i < 5; i++)
{
Vector3 coors = vThisPosition + Vector3(fwRandom::GetRandomNumberInRange(-6.0f, 6.0f), fwRandom::GetRandomNumberInRange(-6.0f, 6.0f), 0.0f);
phIntersection testIntersection;
static phSegment testSegment;
testSegment.A = coors + Vector3(0.0f, 0.0f, 3.0f);
testSegment.B = coors + Vector3(0.0f, 0.0f, -3.0f);
if (CPhysics::GetLevel()->TestProbe(testSegment, &testIntersection, NULL, ArchetypeFlags::GTA_MAP_TYPE_MOVER))
{
coors.z = testIntersection.GetPosition().GetZf();
CPickupManager::CreateSomeMoney(coors, fwRandom::GetRandomNumberInRange(25, 50), 8);
}
}
}
if (GetModelIndex() == MI_CAR_SECURICAR)
{
if (pCulprit && pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer())
{
CPed * pCulpritPlayer = (CPed*)pCulprit;
pCulpritPlayer->GetPlayerWanted()->SetWantedLevelNoDrop(vThisPosition, WANTED_LEVEL2, 0, WL_FROM_LAW_RESPONSE_EVENT);
}
}
g_decalMan.Remove(this);
CPed* fireCulprit = NULL;
if (pCulprit && pCulprit->GetIsTypePed())
{
fireCulprit = static_cast<CPed*>(pCulprit);
}
g_vfxVehicle.ProcessWreckedFires(this, fireCulprit, FIRE_DEFAULT_NUM_GENERATIONS);
// Set the CG offset so that the collider rotates about the instance position
// NOTE: Modifying the CG offset doesn't work on articulated colliders at the moment. Do the modification at the
// end of the function so that we have a chance to make the vehicle simple-articulated by blowing up all the parts
if(!IsColliderArticulated())
{
GetVehicleFragInst()->GetArchetype()->GetBound()->SetCGOffset(Vec3V(V_ZERO));
phCollider *pCollider = GetCollider();
if(pCollider)
pCollider->SetColliderMatrixFromInstance();
}
}
//////////////////////////////////////////////////////////////////////////
// Damage the vehicle even more, close to these bones during BlowUpCar
//////////////////////////////////////////////////////////////////////////
//const int CAR_BONE_COUNT_TO_DEFORM = 6;
//const eHierarchyId ExtraCarBones[CAR_BONE_COUNT_TO_DEFORM] = { VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS };
const eHierarchyId* CAutomobile::GetExtraBonesToDeform(int& extraBoneCount)
{
//Leave extra deformations off for cars, they are damaged enough from the main chassis
//extraBoneCount = CAR_BONE_COUNT_TO_DEFORM;
//return ExtraCarBones;
extraBoneCount = 0;
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlayCarHorn
// PURPOSE : Play the car horn if not already triggered. Also shouts abuse
/////////////////////////////////////////////////////////////////////////////////
#define MIN_SPEAK_TIME 1500
#define SPEAK_TIME_OFFSET 3000
#define MIN_NO_HORN_TIME 150
void CAutomobile::PlayCarHorn(bool /*bOneShot*/, u32 hornTypeHash)
{
//u32 r;
if(!IsAlarmActivated())
{
//if(!IsHornOn())
{
// if(m_NoHornCount > 0 && !bOneShot)
// {
// m_NoHornCount--;
// return;
// }
//m_NoHornCount = u8(MIN_NO_HORN_TIME + (fwRandom::GetRandomNumber() & 0x7f));
// r = fwRandom::GetRandomNumber() & 0xf; //
//r = m_NoHornCount & 0x7;
//if(r < 2 || r < 4)
{
// Horn only
if(m_VehicleAudioEntity)
{
//@@: location CAUTOMOBILE_PLAYCARHORN
m_VehicleAudioEntity->SetHornType(hornTypeHash);
m_VehicleAudioEntity->PlayVehicleHorn(0.96f, true);
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CleanupMissionState
// PURPOSE : Cleans up any state set while this auto was a mission entity.
/////////////////////////////////////////////////////////////////////////////////
void CAutomobile::CleanupMissionState()
{
CVehicle::CleanupMissionState();
m_nAutomobileFlags.bWaterTight = FALSE;
m_nAutomobileFlags.bBurnoutModeEnabled = FALSE;
m_nAutomobileFlags.bReduceGripModeEnabled = FALSE;
}
// Name : ScanForCrimes
// Purpose : Checks area surrounding car for crimes being committed. These are reported immediately
// Parameters : None
// Returns : Nothing
#define COP_CAR_CRIME_DETECTION_RANGE (20.0f)
RAGETRACE_DECL(CAutomobile_ScanForCrimes);
void CAutomobile::ScanForCrimes()
{
RAGETRACE_SCOPE(CAutomobile_ScanForCrimes);
// If the player drives a cop with car alarm activated we set the wanted status
// to a set minimum in one go.
if (CGameWorld::FindLocalPlayerVehicle() && CGameWorld::FindLocalPlayerVehicle()->InheritsFromAutomobile())
{
if ( ((CAutomobile *)CGameWorld::FindLocalPlayerVehicle())->IsAlarmActivated() )
{
// Test whether we are close enough to notice player
Vector3 Dist = VEC3V_TO_VECTOR3(CGameWorld::FindLocalPlayerVehicle()->GetTransform().GetPosition() - this->GetTransform().GetPosition());
if (Dist.Mag2() < COP_CAR_CRIME_DETECTION_RANGE*COP_CAR_CRIME_DETECTION_RANGE)
{
CPed * pPlayerPed = CGameWorld::FindLocalPlayer();
if (pPlayerPed && pPlayerPed->GetPlayerWanted()->m_fMultiplier > 0.0f)
{
pPlayerPed->GetPlayerWanted()->SetOneStarParoleNoDrop();
}
}
}
}
}
void CAutomobile::SetAutoDoorTimer(u32 nTime, eStatus status, u32 uDoorTimeToOpen, u32 uDoorTimeToClose)
{
if(nTime < (uDoorTimeToOpen + uDoorTimeToClose))
{
nTime = uDoorTimeToOpen + uDoorTimeToClose;
}
if(status == OPEN_THEN_CLOSE)
{
m_nAutoDoorStart = fwTimer::GetTimeInMilliseconds();
}
else
m_nAutoDoorStart = fwTimer::GetTimeInMilliseconds()-uDoorTimeToOpen;
m_nAutoDoorTimer = m_nAutoDoorStart + nTime;
}
void CAutomobile::ProcessAutoBusDoors()
{
if(m_nAutoDoorTimer > fwTimer::GetTimeInMilliseconds())
{
float fRatioOpen;// fully open
// GetHealth() = 1000;
// keep opening
if((m_nAutoDoorTimer > 0)&&(fwTimer::GetTimeInMilliseconds() > m_nAutoDoorTimer-DEFAULT_DOOR_TIME_TO_CLOSE))
{
// keep going till closed
CCarDoor* pDoor = GetDoorFromId(VEH_DOOR_DSIDE_F);
if(pDoor)
{
if(!pDoor->GetIsClosed())
fRatioOpen = 1.0f - ((float)(fwTimer::GetTimeInMilliseconds()-(m_nAutoDoorTimer-DEFAULT_DOOR_TIME_TO_CLOSE))) / DEFAULT_DOOR_TIME_TO_CLOSE;
else
{
m_nAutoDoorTimer = fwTimer::GetTimeInMilliseconds();
fRatioOpen = 0.0f;
}
pDoor->SetTargetDoorOpenRatio(fRatioOpen, 0);
}
// keep going till closed
pDoor = GetDoorFromId(VEH_DOOR_PSIDE_F);
if(pDoor)
{
if(!pDoor->GetIsClosed())
fRatioOpen = 1.0f - ((float)(fwTimer::GetTimeInMilliseconds()-(m_nAutoDoorTimer-DEFAULT_DOOR_TIME_TO_CLOSE))) / DEFAULT_DOOR_TIME_TO_CLOSE;
else
{
m_nAutoDoorTimer = fwTimer::GetTimeInMilliseconds();
fRatioOpen = 0.0f;
}
pDoor->SetTargetDoorOpenRatio(fRatioOpen, 0);
}
}
}
else
{
if(m_nAutoDoorStart != 0)
{
if(GetDoorFromId(VEH_DOOR_DSIDE_F))
GetDoorFromId(VEH_DOOR_DSIDE_F)->SetShutAndLatched(this);
if(GetDoorFromId(VEH_DOOR_PSIDE_F))
GetDoorFromId(VEH_DOOR_PSIDE_F)->SetShutAndLatched(this);
m_nAutoDoorStart = 0;
m_nAutoDoorTimer = 0;
}
// door should be closed
}
}
void CAutomobile::ProcessAutoTurretDoors()
{
if (!GetVehicleModelInfo())
return;
const CModelSeatInfo* pModelSeatInfo = GetVehicleModelInfo()->GetModelSeatInfo();
if (!pModelSeatInfo)
return;
const u32 uCurrentTime = fwTimer::GetTimeInMilliseconds();
if (m_nAutoDoorTimer < uCurrentTime)
return;
if ((m_nAutoDoorTimer > 0) && (uCurrentTime > m_nAutoDoorTimer-DEFAULT_DOOR_TIME_TO_CLOSE))
{
// Find turret seat
const s32 iNumSeats = pModelSeatInfo->GetNumSeats();
for (s32 iSeatIndex = 0; iSeatIndex < iNumSeats; ++iSeatIndex)
{
const CVehicleSeatAnimInfo* pSeatAnimInfo = GetSeatAnimationInfo(iSeatIndex);
if (pSeatAnimInfo && pSeatAnimInfo->IsTurretSeat())
{
if (!CTaskVehicleFSM::GetPedInOrUsingSeat(*this, iSeatIndex))
{
const s32 iEntryPointIndex = pModelSeatInfo->GetEntryPointIndexForSeat(iSeatIndex, this);
if (IsEntryIndexValid(iEntryPointIndex))
{
CCarDoor* pDoor = CTaskVehicleFSM::GetDoorForEntryPointIndex(this, iEntryPointIndex);
if( pDoor &&
!pDoor->GetFlag( CCarDoor::DRIVEN_SPECIAL ) )
{
float fRatioOpen;
if (!pDoor->GetIsClosed())
{
fRatioOpen = 1.0f - ((float)(uCurrentTime-(m_nAutoDoorTimer-DEFAULT_DOOR_TIME_TO_CLOSE))) / DEFAULT_DOOR_TIME_TO_CLOSE;
}
else
{
m_nAutoDoorTimer = uCurrentTime;
fRatioOpen = 0.0f;
}
pDoor->SetTargetDoorOpenRatio(fRatioOpen, CCarDoor::DRIVEN_AUTORESET|CCarDoor::WILL_LOCK_DRIVEN|CCarDoor::DRIVEN_SPECIAL);
}
}
}
}
}
}
else
{
m_nAutoDoorStart = 0;
m_nAutoDoorTimer = 0;
}
}
void CAutomobile::GetVehicleMovementStickInput(const CControl& rControl, float& fStickX, float& fStickY)
{
fStickX = rControl.GetVehicleSteeringLeftRight().GetNorm();
fStickY = rControl.GetVehicleSteeringUpDown().GetNorm();
#if USE_SIXAXIS_GESTURES
if(CControlMgr::GetPlayerPad() && CPadGestureMgr::GetMotionControlEnabled(CPadGestureMgr::MC_TYPE_AFTERTOUCH))
{
CPadGesture* gesture = CControlMgr::GetPlayerPad()->GetPadGesture();
if(gesture)
{
if(fStickX == 0.0f && rControl.GetVehicleSteeringLeftRight().IsEnabled())
{
fStickX = gesture->GetPadRollInRange(CVehicle::ms_fDefaultMotionContolRollMin,CVehicle::ms_fDefaultMotionContolRollMax,true) * CVehicle::ms_fDefaultMotionContolAftertouchMult;
}
if(fStickY == 0.0f && rControl.GetVehicleSteeringUpDown().IsEnabled())
{
fStickY = gesture->GetPadPitchInRange(CVehicle::ms_fDefaultMotionContolPitchMin,CVehicle::ms_fDefaultMotionContolPitchMax,true,true) * CVehicle::ms_fDefaultMotionContolAftertouchMult;
}
}
}
#endif // USE_SIXAXIS_GESTURES
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlaceOnRoadProperly
// PURPOSE : If the car is placed kinda on the road this function will
// do it properly
// RETURNS : true if collision was actually found for front AND rear point.
/////////////////////////////////////////////////////////////////////////////////
PF_PAGE(GTAVehPlaceOnRoad,"GTA Veh place on road");
PF_GROUP(DoingPlaceOnRoad);
PF_LINK(GTAVehPlaceOnRoad, DoingPlaceOnRoad);
PF_TIMER(PlaceIt, DoingPlaceOnRoad);
bool CAutomobile::PlaceOnRoadProperly(CAutomobile* pAutomobile, Matrix34 *pMat, CInteriorInst*& pDestInteriorInst, s32& destRoomIdx, bool& setWaitForAllCollisionsBeforeProbe, u32 MI, phInst* pTestException, bool useVirtualRoad, CNodeAddress* aNodes, s16 * aLinks, s32 numNodes, float HeightSampleRangeUp, float HeightSampleRangeDown)
{
PF_FUNC(PlaceIt);
setWaitForAllCollisionsBeforeProbe = false;
const ScalarV fOne(V_ONE);
fwModelId modelId((strLocalIndex(MI)));
Assert(modelId.IsValid());
CVehicleModelInfo *pModelInfo = (CVehicleModelInfo *)CModelInfo::GetBaseModelInfo(modelId);
Assert(pModelInfo);
Assertf(pModelInfo->GetIsAutomobile() || pModelInfo->GetIsTrailer() || pModelInfo->GetIsBike(), "Unrecognized type for '%s'", pModelInfo->GetModelName());
// Deal with the vehicle being upside down. Flip it round.
if (pMat->c.z < 0.0f)
{
pMat->a = -pMat->a;
pMat->c = -pMat->c;
}
// Determine if we are forced to use the real road surface instead of the virtual road.
// This happens when the nodes are in tunnels.
if(useVirtualRoad)
{
Assert(aNodes);
bool inTunnel = false;
for(int i = 0; i < numNodes; ++i)
{
const CPathNode* pNode = ThePaths.FindNodePointer(aNodes[i]);
Assert(pNode);
if(pNode->m_2.m_inTunnel)
{
inTunnel = true;
break;
}
}
if(inTunnel)
{
// We will need to store data about interior and that data is only in the collision info so
// we need to use the world collision mesh and not the virtual road thus we also need to make
// sure the world collision mesh around us is loaded.
const bool worldCollisionLoadedHere = g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(RCC_VEC3V(pMat->d), fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER);
if(worldCollisionLoadedHere)
{
useVirtualRoad = false;
}
else
{
if(pAutomobile)
{
pAutomobile->GetPortalTracker()->SetWaitForAllCollisionsBeforeProbe(true);
}
else
{
setWaitForAllCollisionsBeforeProbe = true;
}
}
}
}
float frontHeightAboveGround, rearHeightAboveGround;
CVehicle::CalculateHeightsAboveRoad(modelId, &frontHeightAboveGround, &rearHeightAboveGround);
if( pAutomobile && pAutomobile->GetVehicleModelInfo() && pAutomobile->GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_DROP_SUSPENSION_WHEN_STOPPED ) &&
pAutomobile->GetVelocity().Mag2() < 1.0f )
{
frontHeightAboveGround -= pAutomobile->pHandling->m_fSuspensionRaise;
rearHeightAboveGround -= pAutomobile->pHandling->m_fSuspensionRaise;
}
CVehicleWeaponHandlingData* pWeaponHandling = (pAutomobile && pAutomobile->pHandling) ? pAutomobile->pHandling->GetWeaponHandlingData() : NULL;
float fWheelImpactOffset = (pAutomobile && pAutomobile->GetWheel(0)) ? pAutomobile->GetWheel(0)->GetWheelImpactOffset() : 0.0f;
if(pWeaponHandling)// weapon wheel impact offset is already taken into account.
{
fWheelImpactOffset -= pWeaponHandling->GetWheelImpactOffset();
}
frontHeightAboveGround += fWheelImpactOffset;
rearHeightAboveGround += fWheelImpactOffset;
// do a single probe first to find rough height first (not necessary if using virtual road as it doesn't really have layers
if(!useVirtualRoad)
{
const int nInitialNumResults = 8;
WorldProbe::CShapeTestProbeDesc probeDesc;
WorldProbe::CShapeTestFixedResults<nInitialNumResults> probeResults;
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetStartAndEnd(pMat->d + HeightSampleRangeUp*ZAXIS, pMat->d - HeightSampleRangeDown*ZAXIS);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
int nBestHit = -1;
float fBestDeltaZ = LARGE_FLOAT;
WorldProbe::ResultIterator it;
int i = 0;
for(it = probeResults.begin(); it < probeResults.end(); ++it, ++i)
{
if(it->GetHitDetected() && rage::Abs((it->GetHitPosition() - pMat->d).z) < fBestDeltaZ)
{
nBestHit = i;
fBestDeltaZ = rage::Abs((it->GetHitPosition() - pMat->d).z);
}
}
if(nBestHit > -1)
pMat->d.z = probeResults[nBestHit].GetHitPosition().z + CONCAVE_DISTANCE_MARGIN + 0.5f*(frontHeightAboveGround+rearHeightAboveGround);
}
const int MAX_NUM_WHEELS = 10;
const int MAX_NUM_TRAILER_WHEELS = 6;
Vector3 aWheelPos[MAX_NUM_WHEELS];
eHierarchyId aCarWheelIds[MAX_NUM_WHEELS] = {VEH_WHEEL_LF,VEH_WHEEL_LR,VEH_WHEEL_RF,VEH_WHEEL_RR, VEH_WHEEL_LM1, VEH_WHEEL_RM1, VEH_WHEEL_LM2, VEH_WHEEL_RM2, VEH_WHEEL_LM3, VEH_WHEEL_RM3};
eHierarchyId aTrailerWheelIds[MAX_NUM_TRAILER_WHEELS] = {VEH_WHEEL_LM1, VEH_WHEEL_LR, VEH_WHEEL_RM1, VEH_WHEEL_RR, VEH_WHEEL_LM2, VEH_WHEEL_RM2};
eHierarchyId *aWheelIds = aCarWheelIds;
if(pAutomobile && pAutomobile->InheritsFromTrailer())
{
aWheelIds = aTrailerWheelIds;
}
int numValidWheels = 0;
for(int i = 0; i < 4; i++)
{
// Fall back to LF wheel if this wheel doesn't exist
int iBoneId = pModelInfo->GetBoneIndex(aWheelIds[i]);
if(iBoneId > -1)
{
aWheelPos[i] = CWheel::GetWheelOffset(pModelInfo, aWheelIds[i]);
numValidWheels++;
}
else if(pAutomobile && pAutomobile->GetIsAircraft() && (i == 2 || i == 3))//planes have very large bounds so we're better off using the front left or rear left wheel if we are missing a wheel
{
if(i == 2)
{
aWheelPos[i] = aWheelPos[0];
}
else if(i == 3)
{
aWheelPos[i] = aWheelPos[1];
}
}
else
{
// No wheel here, use bounding box instead
aWheelPos[i] = pModelInfo->GetBoundingBoxMin();
if(i == 0 || i == 2)
{
// Front
aWheelPos[i].y += pModelInfo->GetBoundingBoxMax().y;
}
if(i == 2 || i == 3)
{
// Right
aWheelPos[i].x += pModelInfo->GetBoundingBoxMax().x;
}
}
}
// the schnuppe only has two wheels so it fails to generate a valid matrix from the hit results
// so slightly offset the x values of all the wheels.
if( numValidWheels < 4 &&
pAutomobile &&
pAutomobile->GetIsAircraft() )
{
for( int i = 0; i < 4; i++ )
{
if( Abs( aWheelPos[ i ].x ) <= 0.01f )
{
if( i < 2 )
{
aWheelPos[ i + 2 ] = aWheelPos[ i ];
aWheelPos[ i ].x -= 0.1f;
aWheelPos[ i + 2 ].x += 0.1f;
}
else
{
aWheelPos[ i - 2 ] = aWheelPos[ i ];
aWheelPos[ i ].x += 0.1f;
aWheelPos[ i - 2 ].x -= 0.1f;
}
}
}
}
float averageLocalWheelPosB = (aWheelPos[0].y + aWheelPos[1].y + aWheelPos[2].y + aWheelPos[3].y) * 0.25f;
WorldProbe::CShapeTestFixedResults<MAX_NUM_WHEELS> probeResults;
const CVehicleFollowRouteHelper* pFollowRoute = NULL;
int nExtraWheelsToSetCompressionOn = 0;
if(pAutomobile)
{
if(!pAutomobile->InheritsFromTrailer())
{
for(int i = 4; i < MAX_NUM_WHEELS; i++)//check if we have extra wheels
{
int iBoneId = pModelInfo->GetBoneIndex(aWheelIds[i]);
if(iBoneId > -1)
{
aWheelPos[i] = CWheel::GetWheelOffset(pModelInfo, aWheelIds[i]);
nExtraWheelsToSetCompressionOn++;
}
}
}
else if(pAutomobile->InheritsFromTrailer())
{
for(int i = 4; i < MAX_NUM_TRAILER_WHEELS; i++)//check if we have extra wheels
{
int iBoneId = pModelInfo->GetBoneIndex(aWheelIds[i]);
if(iBoneId > -1)
{
aWheelPos[i] = CWheel::GetWheelOffset(pModelInfo, aWheelIds[i]);
nExtraWheelsToSetCompressionOn++;
}
}
}
// Trailers use their parent's follow route
if (pAutomobile->m_nVehicleFlags.bHasParentVehicle && CVehicle::IsEntityAttachedToTrailer(pAutomobile) && pAutomobile->GetDummyAttachmentParent()
&& pAutomobile->GetDummyAttachmentParent()->GetDummyAttachmentParent())
{
pFollowRoute = pAutomobile->GetDummyAttachmentParent()->GetDummyAttachmentParent()->GetIntelligence()->GetFollowRouteHelper();
}
else if(pAutomobile->m_nVehicleFlags.bHasParentVehicle && pAutomobile->InheritsFromTrailer() && pAutomobile->GetDummyAttachmentParent())
{
pFollowRoute = pAutomobile->GetDummyAttachmentParent()->GetIntelligence()->GetFollowRouteHelper();
}
else
{
pFollowRoute = pAutomobile->GetIntelligence()->GetFollowRouteHelper();
}
}
int nNumberOfWheelsToSample(4 + nExtraWheelsToSetCompressionOn);
if(useVirtualRoad && pFollowRoute)
{
phSegment testSegments[MAX_NUM_WHEELS];
for(int i=0; i<nNumberOfWheelsToSample; i++)
{
testSegments[i].Set(aWheelPos[i], aWheelPos[i]);
testSegments[i].Transform(*pMat);
testSegments[i].A.Add(HeightSampleRangeUp*ZAXIS);
testSegments[i].B.Subtract(HeightSampleRangeDown*ZAXIS);
}
CVirtualRoad::TestProbesToVirtualRoadFollowRouteAndCenterPos(*pFollowRoute, pAutomobile->GetVehiclePosition(), probeResults, testSegments, nNumberOfWheelsToSample, fOne, pAutomobile->ShouldTryToUseVirtualJunctionHeightmaps());
}
else if(useVirtualRoad && numNodes==3) // as used by vehicle population code
{
phSegment testSegments[MAX_NUM_WHEELS];
for(int i=0; i<nNumberOfWheelsToSample; i++)
{
testSegments[i].Set(aWheelPos[i], aWheelPos[i]);
testSegments[i].Transform(*pMat);
testSegments[i].A.Add(HeightSampleRangeUp*ZAXIS);
testSegments[i].B.Subtract(HeightSampleRangeDown*ZAXIS);
}
CVehicleNodeList nodeList;
nodeList.SetPathNodeAddr(NODE_OLD, aNodes[0]);
nodeList.SetPathLinkIndex(LINK_OLD_TO_NEW, aLinks[0]);
nodeList.SetPathNodeAddr(NODE_NEW, aNodes[1]);
nodeList.SetPathLinkIndex(LINK_NEW_TO_FUTURE, aLinks[1]);
nodeList.SetPathNodeAddr(NODE_FUTURE, aNodes[2]);
nodeList.SetTargetNodeIndex(NODE_NEW);
CVirtualRoad::TestProbesToVirtualRoadNodeListAndCenterNode(nodeList, NODE_NEW, probeResults, testSegments, nNumberOfWheelsToSample, fOne);
}
else
{
const float fMaxOffset = 2.0f;
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&probeResults);
atArray<const phInst*> excludeInstances;
excludeInstances.Grow() = pTestException;
// Loop through all the attached objects and exclude them from this test.
if(pAutomobile)
{
if(fwAttachmentEntityExtension *extension = pAutomobile->GetAttachmentExtension())
{
CPhysical* pCurChild = static_cast<CPhysical*>(extension->GetChildAttachment());
while(pCurChild)
{
if(pCurChild->GetIsTypeObject() && pCurChild->GetCurrentPhysicsInst())
{
excludeInstances.Grow() = pCurChild->GetCurrentPhysicsInst();
}
fwAttachmentEntityExtension &curChildAttachExt = pCurChild->GetAttachmentExtensionRef();
pCurChild = static_cast<CPhysical*>(curChildAttachExt.GetSiblingAttachment());
}
}
}
const int numExcludeInstances = excludeInstances.GetCount();
probeDesc.SetExcludeInstances(excludeInstances.GetElements(), numExcludeInstances);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE|ArchetypeFlags::GTA_OBJECT_TYPE);
probeDesc.SetTypeFlags(TYPE_FLAGS_ALL);
for(int i=0; i<nNumberOfWheelsToSample; i++)
{
phSegment testSegment(aWheelPos[i] + fMaxOffset*ZAXIS, aWheelPos[i] - fMaxOffset*ZAXIS);
testSegment.Transform(*pMat);
probeDesc.SetStartAndEnd(testSegment.A, testSegment.B);
probeDesc.SetFirstFreeResultOffset(i);
probeDesc.SetMaxNumResultsToUse(1);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
}
}
Vector3 vPosns[MAX_NUM_WHEELS];
WorldProbe::ResultIterator it = probeResults.begin();
for(int i=0; i<nNumberOfWheelsToSample; ++i, ++it)
{
bool bFrontWheel = (i==0 || i==2);
if(it->GetHitDetected())
{
vPosns[i].Set(it->GetHitPosition());
vPosns[i].z += CONCAVE_DISTANCE_MARGIN;
}
else
{
vPosns[i].Set(aWheelPos[i]);
vPosns[i].z -= pModelInfo->GetTyreRadius(bFrontWheel);
pMat->Transform(vPosns[i]);
}
CWheel* pWheel = NULL;
if(pAutomobile)
{
pWheel = pAutomobile->GetWheelFromId(aWheelIds[i]);
}
if(pWheel)
{
// add on car height above road to intersection positions
float fHeightAboveGround = Selectf(pWheel->GetFrontRearSelector(), frontHeightAboveGround, rearHeightAboveGround);
if(pWheel->GetTyreHealth() < TYRE_HEALTH_DEFAULT)
fHeightAboveGround -= pWheel->GetTyreBurstCompression();
vPosns[i].Add( fHeightAboveGround*it->GetHitNormal() );
}
else
{
if(bFrontWheel)
vPosns[i].Add(frontHeightAboveGround*ZAXIS);
else
vPosns[i].Add(rearHeightAboveGround*ZAXIS);
}
}
// fwd = front - back
pMat->b = (vPosns[0] + vPosns[2]) - (vPosns[1] + vPosns[3]);
pMat->b.Normalize();
// right = right - left
pMat->a = (vPosns[2] + vPosns[3]) - (vPosns[0] + vPosns[1]);
pMat->c.Cross(pMat->a, pMat->b);
pMat->c.Normalize();
pMat->a.Cross(pMat->b, pMat->c);
// pos = average
pMat->d = vPosns[0] + vPosns[1] + vPosns[2] + vPosns[3];
pMat->d.Scale(0.25f);
pMat->d -= pMat->b * averageLocalWheelPosB;
if( pModelInfo->GetIsTrailer() &&
Abs( pMat->c.z ) < 0.2f )
{
pMat->c = Vector3( 0.0f, 0.0f, 1.0f );
pMat->a.Cross( pMat->c, pMat->b );
pMat->a.Normalize();
pMat->b.Cross( pMat->a, pMat->c );
pMat->b.Normalize();
}
// fix up wheel compression ratios so the wheels touch the intersection points correctly
if(pAutomobile)
{
float suspensionRaiseAmount = 0.0f;
if( pAutomobile->GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_DROP_SUSPENSION_WHEN_STOPPED ) &&
pAutomobile->GetVelocity().Mag2() < 1.0f )
{
suspensionRaiseAmount = -pAutomobile->pHandling->m_fSuspensionRaise;
}
// fix up wheel position
it = probeResults.begin();
for(int i=0; i<nNumberOfWheelsToSample; ++i, ++it)
{
CWheel* pWheel = pAutomobile->GetWheelFromId(aWheelIds[i]);
if(pWheel)
{
Vector3 vHitPosition = it->GetHitPosition();
vHitPosition.z += CONCAVE_DISTANCE_MARGIN;
pWheel->SetCompressionFromHitPos(pMat, vHitPosition, it->GetHitDetected(), it->GetHitNormal());
if( pAutomobile->GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_DROP_SUSPENSION_WHEN_STOPPED ) )
{
pWheel->SetSuspensionRaiseAmount( suspensionRaiseAmount );
pWheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION );
}
}
}
}
// store data about interior if any was found from the front probes (for when adding vehicle to world)
if(!useVirtualRoad)
{
it = probeResults.begin();
for(int i=0; i<nNumberOfWheelsToSample; ++i, ++it)
{
if(it->GetHitDetected())
{
s32 roomIdx = PGTAMATERIALMGR->UnpackRoomId(it->GetHitMaterialId());
if( (roomIdx > 0) && (it->GetHitInst()!=NULL) )
{
CEntity* pHitEntity = reinterpret_cast<CEntity*>(it->GetHitInst()->GetUserData());
CInteriorInst* pIntInst = NULL;
CInteriorProxy* pInteriorProxy = CInteriorProxy::GetInteriorProxyFromCollisionData(pHitEntity, &it->GetHitInst()->GetPosition());
Assert(pInteriorProxy);
if (pInteriorProxy)
{
pIntInst = pInteriorProxy->GetInteriorInst();
}
if(pIntInst && pIntInst->CanReceiveObjects())
{
pDestInteriorInst = pIntInst;
destRoomIdx = roomIdx;
break;
} else
{
return(false); // if interior not ready then immediately bail out
}
}
}
}
}
if(probeResults[0].GetHitDetected())
{
Assert(pMat->IsOrthonormal());
return true;
}
return false;
// JB : From here I removed old commented-out version in the interests of neatness
// If required, search P4 history of this file for "OLD VERSION - USED 2 SAMPLES, BACK AND FRONT, PLUS SURFACE NORMAL"
}
s32 CAutomobile::CleanUpPlacementArray(bool getFirstFree)
{
// find first available slot and invalidate any with no vehicle pointer (in case vehicle has been destroyed)
s32 availableIndex = -1;
for (s32 i = 0; i < ms_placeOnRoadArray.GetCount(); ++i)
{
if (ms_placeOnRoadArray[i].id == INVALID_ASYNC_ENTRY || !ms_placeOnRoadArray[i].automobile)
{
if (ms_placeOnRoadArray[i].roughHeightResults)
delete ms_placeOnRoadArray[i].roughHeightResults;
ms_placeOnRoadArray[i].roughHeightResults = NULL;
if (ms_placeOnRoadArray[i].wheelResults)
delete[] ms_placeOnRoadArray[i].wheelResults;
ms_placeOnRoadArray[i].wheelResults = NULL;
ms_placeOnRoadArray[i].id = INVALID_ASYNC_ENTRY;
if (availableIndex == -1)
{
availableIndex = i;
}
}
}
if (getFirstFree && availableIndex == -1)
{
if (ms_placeOnRoadArray.GetCount() < ms_placeOnRoadArray.GetMaxCount())
{
ms_placeOnRoadArray.Append();
}
}
return availableIndex;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlaceOnRoadProperlyAsync
// PURPOSE : If the car is placed kinda on the road this function will
// do it properly, the async version. if function returns INVALID_ASYNC_ENTRY client code should wait and retry.
// If the function returns a valid id, HasPlaceOnRoadProperlyFinished needs to be called every frame to poll
// the result, otherwise it will block the queue.
// NOTE: does not support virtual roads
// RETURNS : true if scheduling was successful
/////////////////////////////////////////////////////////////////////////////////
u32 CAutomobile::PlaceOnRoadProperlyAsync(CAutomobile* pAutomobile, Matrix34 *pMat, u32 MI, float HeightSampleRangeUp, float HeightSampleRangeDown)
{
s32 availableIndex = CleanUpPlacementArray(true);
if (availableIndex == -1)
return INVALID_ASYNC_ENTRY;
Assert(MI != fwModelId::MI_INVALID);
ASSERT_ONLY(CVehicleModelInfo *pModelInfo = (CVehicleModelInfo *)CModelInfo::GetBaseModelInfo(fwModelId((strLocalIndex(MI)))));
Assert(pModelInfo);
Assert(pModelInfo->GetIsAutomobile() || pModelInfo->GetIsTrailer() || pModelInfo->GetIsBike());
// Deal with the vehicle being upside down. Flip it round.
if (pMat->c.z < 0.0f)
{
pMat->a = -pMat->a;
pMat->c = -pMat->c;
}
PlaceOnRoadAsyncData& newData = ms_placeOnRoadArray[availableIndex];
newData.matrix = pMat;
newData.modelInfo = MI;
newData.roughHeightResults = rage_new PlaceOnRoadAsyncData::RoughHeightResults(8);
newData.id = (u32)availableIndex;
newData.automobile = pAutomobile;
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(newData.roughHeightResults);
probeDesc.SetStartAndEnd(pMat->d + HeightSampleRangeUp*ZAXIS, pMat->d - HeightSampleRangeDown*ZAXIS);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
if (!newData.roughHeightResults->GetWaitingOnResults())
{
if (!Verifyf(newData.roughHeightResults->GetResultsReady(), "CAutomobile::PlaceOnRoadProperlyAsync failed to schedule rought height probe, status: %d", newData.roughHeightResults->GetResultsStatus()))
{
delete newData.roughHeightResults;
newData.roughHeightResults = NULL;
newData.wheelResults = NULL;
newData.id = INVALID_ASYNC_ENTRY;
return INVALID_ASYNC_ENTRY;
}
}
return newData.id;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : HasPlaceOnRoadProperlyFinished
// PURPOSE : Needs to be called every frame after PlaceOnRoadProperlyAsync was called and returned a valid id.
// RETURNS : true when the work has finished, false otherwise
// failed will be set to true when the placement of the vehicle has failed.
/////////////////////////////////////////////////////////////////////////////////
bool CAutomobile::HasPlaceOnRoadProperlyFinished(u32 id, CInteriorInst*& pDestInteriorInst, s32& destRoomIdx, u32& failed, RegdEnt* hitEntities, float* hitPos)
{
eHierarchyId aCarWheelIds[4] = {VEH_WHEEL_LF,VEH_WHEEL_LR,VEH_WHEEL_RF,VEH_WHEEL_RR};
eHierarchyId aTrailerWheelIds[4] = {VEH_WHEEL_LM1, VEH_WHEEL_LR, VEH_WHEEL_RM1, VEH_WHEEL_RR};
eHierarchyId aBlimpWheelIds[4] = {VEH_WHEEL_LF, VEH_WHEEL_LR, VEH_WHEEL_LF, VEH_WHEEL_RR};
eHierarchyId* aWheelIds = aCarWheelIds;
failed = 0;
CleanUpPlacementArray(false);
if (!Verifyf(id < ms_placeOnRoadArray.GetCount(), "Invalid index %d passed to HasPlaceOnRoadProperlyFinished", id))
return false;
PlaceOnRoadAsyncData& top = ms_placeOnRoadArray[id];
fwModelId topModelId((strLocalIndex(top.modelInfo)));
Assert(topModelId.IsValid());
CVehicleModelInfo* pModelInfo = (CVehicleModelInfo *)CModelInfo::GetBaseModelInfo(topModelId);
if(!Verifyf(pModelInfo && topModelId.IsValid(), "CAutomobile::HasPlaceOnRoadProperlyFinished: Invalid modelinfo index %d", top.modelInfo))
{
failed = 1;
return true;
}
// rough height phase
if (top.roughHeightResults != NULL)
{
if (!top.roughHeightResults->GetWaitingOnResults())
{
if (!Verifyf(top.roughHeightResults->GetResultsReady(), "Bad state for rough height probe results (%d)", top.roughHeightResults->GetResultsStatus()))
{
top.id = INVALID_ASYNC_ENTRY;
failed = 2;
return true;
}
}
else
{
// results not ready
return false;
}
if (top.automobile)
{
if (top.automobile->InheritsFromTrailer())
{
aWheelIds = aTrailerWheelIds;
}
else if (top.automobile->GetVehicleType() == VEHICLE_TYPE_BLIMP)
{
aWheelIds = aBlimpWheelIds;
}
}
int nBestHit = -1;
float fBestDeltaZ = LARGE_FLOAT;
WorldProbe::ResultIterator it;
int i = 0;
for(it = top.roughHeightResults->begin(); it < top.roughHeightResults->end(); ++it, ++i)
{
if(it->GetHitDetected() && rage::Abs((it->GetHitPosition() - top.matrix->d).z) < fBestDeltaZ)
{
nBestHit = i;
fBestDeltaZ = rage::Abs((it->GetHitPosition() - top.matrix->d).z);
}
}
CVehicle::CalculateHeightsAboveRoad(topModelId, &top.frontHeightAboveGround, &top.rearHeightAboveGround);
if(nBestHit > -1)
top.matrix->d.z = (*top.roughHeightResults)[nBestHit].GetHitPosition().z + 0.5f*(top.frontHeightAboveGround+top.rearHeightAboveGround);
for(int i = 0; i < 4; i++)
{
// Fall back to LF wheel if this wheel doesn't exist
int iBoneId = pModelInfo->GetBoneIndex(aWheelIds[i]);
if(iBoneId > -1)
{
top.aWheelPos[i] = CWheel::GetWheelOffset(pModelInfo, aWheelIds[i]);
}
else if(top.automobile && (top.automobile->InheritsFromPlane() || top.automobile->InheritsFromBlimp()) && (i == 2 || i == 3))//planes have very large bounds so we're better off using the front left or rear left wheel if we are missing a wheel
{
if(i == 2)
{
top.aWheelPos[i] = top.aWheelPos[0];
}
else if(i == 3)
{
top.aWheelPos[i] = top.aWheelPos[1];
}
}
else
{
// No wheel here, use bounding box instead
top.aWheelPos[i] = pModelInfo->GetBoundingBoxMin();
if(i == 0 || i == 2)
{
// Front
top.aWheelPos[i].y += pModelInfo->GetBoundingBoxMax().y;
}
if(i == 2 || i == 3)
{
// Right
top.aWheelPos[i].x += pModelInfo->GetBoundingBoxMax().x;
}
}
}
// free rough height results, we're done with that one
delete top.roughHeightResults;
top.roughHeightResults = NULL;
const float fMaxOffset = 2.0f;
top.wheelResults = rage_new PlaceOnRoadAsyncData::WheelResults[4];
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_MOVER);
probeDesc.SetTypeFlags(TYPE_FLAGS_ALL);
for(int i=0; i<4; i++)
{
phSegment testSegment(top.aWheelPos[i] + fMaxOffset*ZAXIS, top.aWheelPos[i] - fMaxOffset*ZAXIS);
testSegment.Transform(*top.matrix);
probeDesc.SetStartAndEnd(testSegment.A, testSegment.B);
probeDesc.SetResultsStructure(&top.wheelResults[i]);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
if (!top.wheelResults[i].GetWaitingOnResults())
{
if (!Verifyf(top.wheelResults[i].GetResultsReady(), "CAutomobile::HasPlaceOnRoadProperlyFinished failed to schedule wheel probe"))
{
delete[] top.wheelResults;
top.wheelResults = NULL;
top.id = INVALID_ASYNC_ENTRY;
// return true, no point in keeping calling this function
failed = 3;
return true;
}
}
}
// not done yet
return false;
}
Assert(top.wheelResults && !top.roughHeightResults);
// all results need to be finished before we continue
for (s32 i = 0; i < 4; ++i)
if (!top.wheelResults[i].GetResultsReady())
return false;
float averageLocalWheelPosB = (top.aWheelPos[0].y + top.aWheelPos[1].y + top.aWheelPos[2].y + top.aWheelPos[3].y) * 0.25f;
Vector3 matPosns[4];
for(int i=0; i<4; ++i)
{
WorldProbe::ResultIterator it = top.wheelResults[i].begin();
bool bFrontWheel = (i==0 || i==2);
if(hitEntities)
hitEntities[i] = NULL;
if(it->GetHitDetected())
{
matPosns[i].Set(it->GetHitPosition());
matPosns[i].z += CONCAVE_DISTANCE_MARGIN;
if (hitEntities && it->GetHitInst() && i < 4)
hitEntities[i] = CPhysics::GetEntityFromInst(it->GetHitInst());
}
else
{
matPosns[i].Set(top.aWheelPos[i]);
matPosns[i].z -= pModelInfo->GetTyreRadius(bFrontWheel);
top.matrix->Transform(matPosns[i]);
}
// add on car height above road to intersection positions
if(bFrontWheel)
matPosns[i].Add(top.frontHeightAboveGround*ZAXIS);
else
matPosns[i].Add(top.rearHeightAboveGround*ZAXIS);
}
// fwd = front - back
top.matrix->b = (matPosns[0] + matPosns[2]) - (matPosns[1] + matPosns[3]);
top.matrix->b.Normalize();
// right = right - left
top.matrix->a = (matPosns[2] + matPosns[3]) - (matPosns[0] + matPosns[1]);
top.matrix->c.Cross(top.matrix->a, top.matrix->b);
top.matrix->c.Normalize();
top.matrix->a.Cross(top.matrix->b, top.matrix->c);
// pos = average
top.matrix->d = matPosns[0] + matPosns[1] + matPosns[2] + matPosns[3];
top.matrix->d.Scale(0.25f);
top.matrix->d -= top.matrix->b * averageLocalWheelPosB;
// fix up wheel compression ratios so the wheels touch the intersection points correctly
float fWheelImpactOffset = 0.0f;
if(top.automobile)
{
CVehicleWeaponHandlingData* pWeaponHandling = top.automobile->pHandling ? top.automobile->pHandling->GetWeaponHandlingData() : NULL;
//fix up car position
for(int i=0; i<4; ++i)
{
// WorldProbe::ResultIterator it = top.wheelResults[i].begin();
CWheel* pWheel = top.automobile->GetWheelFromId(aWheelIds[i]);
if(pWheel)
{
fWheelImpactOffset += pWheel->GetWheelImpactOffset();
if(pWeaponHandling)// weapon wheel impact offset is already taken into account.
{
fWheelImpactOffset -= pWeaponHandling->GetWheelImpactOffset();
}
}
}
fWheelImpactOffset *= 0.25f;
top.matrix->d.z += fWheelImpactOffset;
// fix up wheel position
for(int i=0; i<4; ++i)
{
WorldProbe::ResultIterator it = top.wheelResults[i].begin();
CWheel* pWheel = top.automobile->GetWheelFromId(aWheelIds[i]);
if(pWheel)
{
Vector3 vHitPosition = it->GetHitPosition();
vHitPosition.z += CONCAVE_DISTANCE_MARGIN;
pWheel->SetCompressionFromHitPos(top.matrix, vHitPosition, it->GetHitDetected());
}
}
}
// Hack for B1489355 - Forklift invisible in this corridor
const u32 forkliftNameHash = ATSTRINGHASH("forklift", 0x58e49664);
if (pModelInfo->GetModelNameHash() == forkliftNameHash)
{
const Vector3 forkliftCarGen(-921.0f, -2947.6f, 15.3f);
float magSqrd = (top.matrix->d - forkliftCarGen).Mag2();
if (magSqrd < 4.0f)
{
if (top.automobile)
{
CPortalTracker *pPortalTracker = top.automobile->GetPortalTracker();
if (pPortalTracker)
{
pPortalTracker->SetProbeType(CPortalTracker::PROBE_TYPE_NEAR);
pPortalTracker->ScanUntilProbeTrue();
}
}
}
}
// store data about interior if any was found from the front probes (for when adding vehicle to world)
for(int i=0; i<4; ++i)
{
WorldProbe::ResultIterator it = top.wheelResults[i].begin();
if(it->GetHitDetected())
{
s32 roomIdx = PGTAMATERIALMGR->UnpackRoomId(it->GetHitMaterialId());
if( (roomIdx > 0) && (it->GetHitInst()!=NULL) )
{
CEntity* pHitEntity = reinterpret_cast<CEntity*>(it->GetHitInst()->GetUserData());
CInteriorProxy* pInteriorProxy = CInteriorProxy::GetInteriorProxyFromCollisionData(pHitEntity, &it->GetHitInst()->GetPosition());
Assert(pInteriorProxy);
CInteriorInst* pIntInst = NULL;
if (pInteriorProxy){
pIntInst = pInteriorProxy->GetInteriorInst();
}
if(pIntInst && pIntInst->CanReceiveObjects())
{
pDestInteriorInst = pInteriorProxy->GetInteriorInst();
destRoomIdx = roomIdx;
} else
{
return(false); // quit immediately if interior not ready
}
}
break;
}
}
// we're done with this entry so drop it
top.id = INVALID_ASYNC_ENTRY;
if(!top.wheelResults[0][0].GetHitDetected())
{
failed = 4;
}
else
{
Assert(top.matrix->IsOrthonormal());
}
// FA: temp to catch floating cars issue
if(hitPos)
{
hitPos[0] = 0.f;
hitPos[1] = 0.f;
hitPos[2] = 0.f;
hitPos[3] = 0.f;
hitPos[4] = top.matrix->d.z;
}
if (top.wheelResults[0][0].GetHitDetected())
{
if (hitPos)
hitPos[0] = top.wheelResults[0][0].GetHitPosition().z;
failed |= 1 << 8;
}
if (top.wheelResults[1][0].GetHitDetected())
{
if (hitPos)
hitPos[1] = top.wheelResults[1][0].GetHitPosition().z;
failed |= 1 << 9;
}
if (top.wheelResults[2][0].GetHitDetected())
{
if (hitPos)
hitPos[2] = top.wheelResults[2][0].GetHitPosition().z;
failed |= 1 << 10;
}
if (top.wheelResults[3][0].GetHitDetected())
{
if (hitPos)
hitPos[3] = top.wheelResults[3][0].GetHitPosition().z;
failed |= 1 << 11;
}
delete[] top.wheelResults;
top.wheelResults = NULL;
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : PlaceOnRoadAdjustInternal
// PURPOSE : If the car is placed kinda on the road this function will
// do it properly
/////////////////////////////////////////////////////////////////////////////////
bool CAutomobile::PlaceOnRoadAdjustInternal(float HeightSampleRangeUp, float HeightSampleRangeDown, bool bJustSetCompression)
{
ResetSuspension();
Matrix34 Mat = MAT34V_TO_MATRIX34(GetMatrix());
CInteriorInst* pDestInteriorInst = 0;
s32 destRoomIdx = -1;
bool setWaitForAllCollisionsBeforeProbe = false;
float fHeightSampleRangeUp = IsPlaceOnRoadHeightOverriden() ? GetOverridenPlaceOnRoadHeight() : HeightSampleRangeUp;
bool bResult = PlaceOnRoadProperly(this, &Mat, pDestInteriorInst, destRoomIdx, setWaitForAllCollisionsBeforeProbe, GetModelIndex(), GetCurrentPhysicsInst(),
false, NULL, NULL, 0, fHeightSampleRangeUp, HeightSampleRangeDown);
if(bResult && !bJustSetCompression)
{
ResetOverridenPlaceOnRoadHeight();
SetMatrix(Mat);
}
return bResult;
}
dev_float AutoLights_Pos_PullTowardCam = 0.2f;
dev_float AutoLights_White_PullTowardCam = 0.2f;
void CAutomobile::DoWhiteLightEffects(s32 boneIdx, const ConfigVehicleWhiteLightSettings &lightParam, bool frontLight, float distFade)
{
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
const s32 boneId = pModelInfo->GetBoneIndex(boneIdx);
if (boneId > -1)
{
Matrix34 worldMtx;
GetGlobalMtx(boneId, worldMtx);
const tcKeyframeUncompressed& currKeyframe = g_timeCycle.GetCurrUpdateKeyframe();
float strength = Lerp(distFade,lightParam.nearStrength,lightParam.farStrength) * currKeyframe.GetVar(TCVAR_SPRITE_BRIGHTNESS);
float size = Lerp(distFade,lightParam.nearSize,lightParam.farSize) * currKeyframe.GetVar(TCVAR_SPRITE_SIZE);
g_coronas.Register(RCC_VEC3V(worldMtx.d),
size,
Color32(lightParam.color),
strength,
AutoLights_White_PullTowardCam,
Vec3V(V_ZERO),
1.0f,
CORONAS_DEF_DIR_LIGHT_CONE_ANGLE_INNER,
CORONAS_DEF_DIR_LIGHT_CONE_ANGLE_OUTER,
CORONA_DONT_REFLECT);
static dev_float innerConeAngle = 0;
static dev_float outerConeAngle = 90.0f;
static dev_float lightOffsetC = 0.75f;
const float sign = (true == frontLight) ? 1.0f : 0.0f;
const float fade = (0.5f - distFade) * 2.0f;
Vector3 worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(-Vec3V(V_Z_AXIS_WZERO)));
Vector3 worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO)));
fwInteriorLocation interiorLocation = this->GetInteriorLocation();
CLightSource* pLightSource = CAsyncLightOcclusionMgr::AddLight();
if (pLightSource)
{
pLightSource->Reset();
pLightSource->SetCommon(
LIGHT_TYPE_SPOT,
LIGHTFLAG_VEHICLE | LIGHTFLAG_NO_SPECULAR,
worldMtx.d + sign * lightOffsetC * VEC3V_TO_VECTOR3(GetTransform().GetC()),
VEC3V_TO_VECTOR3(lightParam.color),
lightParam.intensity * fade,
LIGHT_ALWAYS_ON);
pLightSource->SetDirTangent(worldDir, worldTan);
pLightSource->SetRadius(lightParam.radius);
pLightSource->SetSpotlight(innerConeAngle, outerConeAngle);
pLightSource->SetInInterior(interiorLocation);
// NOTE: we don't want to call Lights::AddSceneLight directly - the AsyncLightOcclusionMgr will handle that for us
}
}
}
dev_float lightOffset_Mav = 0.5f;
dev_float lightOffset_Ann = 0.0f;
void CAutomobile::DoPosLightEffects(s32 boneIdx, const ConfigVehiclePositionLightSettings &lightParam, bool isRight, bool isAnnihilator, float distFade)
{
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
const s32 boneId = pModelInfo->GetBoneIndex(boneIdx);
if (boneId > -1)
{
Matrix34 worldMtx;
GetGlobalMtx(boneId, worldMtx);
float sign;
Vec3V col;
if( isRight )
{
sign = 1.0f;
col = lightParam.rightColor;
}
else
{
sign = -1.0f;
col = lightParam.leftColor;
}
const tcKeyframeUncompressed& currKeyframe = g_timeCycle.GetCurrUpdateKeyframe();
float strength = Lerp(distFade,lightParam.nearStrength,lightParam.farStrength) * currKeyframe.GetVar(TCVAR_SPRITE_BRIGHTNESS);
float size = Lerp(distFade,lightParam.nearSize,lightParam.farSize) * currKeyframe.GetVar(TCVAR_SPRITE_SIZE);
g_coronas.Register(RCC_VEC3V(worldMtx.d),
size,
Color32(col),
strength,
AutoLights_Pos_PullTowardCam,
Vec3V(V_ZERO),
1.0f,
CORONAS_DEF_DIR_LIGHT_CONE_ANGLE_INNER,
CORONAS_DEF_DIR_LIGHT_CONE_ANGLE_OUTER,
CORONA_DONT_REFLECT);
static dev_float innerConeAngle = 0;
static dev_float outerConeAngle = 90.0f;
float lightOffset;
float intensity;
const float fade = (0.5f - distFade) * 2.0f;
if( isAnnihilator )
{
lightOffset = lightOffset_Ann;
intensity = lightParam.intensity_typeA * fade;
}
else
{
lightOffset = lightOffset_Mav;
intensity = lightParam.intensity_typeB * fade;
}
Vector3 worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(-Vec3V(V_Z_AXIS_WZERO)));
Vector3 worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO)));
CLightSource light(
LIGHT_TYPE_SPOT,
LIGHTFLAG_VEHICLE | LIGHTFLAG_NO_SPECULAR,
worldMtx.d + sign * lightOffset * VEC3V_TO_VECTOR3(GetTransform().GetA()) + lightOffset * VEC3V_TO_VECTOR3(GetTransform().GetC()),
VEC3V_TO_VECTOR3(col),
intensity * (1.0f - distFade),
LIGHT_ALWAYS_ON);
light.SetDirTangent(worldDir, worldTan);
light.SetRadius(lightParam.radius);
light.SetSpotlight(innerConeAngle, outerConeAngle);
light.SetInInterior(GetInteriorLocation());
light.SetFalloffExponent(32.0f);
Lights::AddSceneLight(light);
}
}
bool CAutomobile::ShouldUpdateVehicleDamageEveryFrame() const
{
// The petrol tank fires need to be updated every frame, otherwise they get
// restarted and don't work properly.
if(GetVehicleDamage()->GetPetrolTankOnFire())
{
return true;
}
// Need update the damage when there are collision impacts
if(GetFrameCollisionHistory()->GetCollisionImpulseMagSum() > 0.0f)
{
return true;
}
return false;
}
void CAutomobile::ModifyControlsIfDucked(const CPed* pDriver, float fTimeStep)
{
TUNE_GROUP_BOOL(DUCK_IN_VEHICLE_TUNE, DISABLE_CONTROL_MODIFICATION, false);
if (DISABLE_CONTROL_MODIFICATION)
return;
if (!pDriver || !pDriver->IsLocalPlayer())
return;
CPlayerInfo* pPlayerInfo = pDriver->GetPlayerInfo();
if (!pPlayerInfo)
return;
const bool bIsDucking = pDriver->GetPedConfigFlag(CPED_CONFIG_FLAG_IsDuckingInVehicle);
if (!bIsDucking)
{
return;
}
const CTaskMotionInAutomobile* pAutomobileTask = static_cast<const CTaskMotionInAutomobile*>(pDriver->GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_MOTION_IN_AUTOMOBILE));
if (!pAutomobileTask)
{
return;
}
// Allow a brief period of total control when first ducking, before penalising the player if they continue to duck
const float fTimeDucked = pAutomobileTask ? pAutomobileTask->GetTimeDucked() : 0.0f;
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, NO_CONTROL_MODIFICATION_TIME, 3.0f, 0.0f, 10.0f, 0.01f);
if (fTimeDucked < NO_CONTROL_MODIFICATION_TIME)
return;
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, LAGGED_CONTROL_BLEND_DURATION, 0.125f, 0.0f, 10.0f, 0.01f);
const float fIdleBlend = 1.0f - Clamp((fTimeDucked - NO_CONTROL_MODIFICATION_TIME) / LAGGED_CONTROL_BLEND_DURATION, 0.0f, 1.0f);
// Lag steering control input
const float fLaggedSteerControl = pPlayerInfo->GetLaggedSteerControl();
float fSteerControlUnmodified = m_vehControls.GetSteerAngle();
float fSteerControlModified = fSteerControlUnmodified;
TUNE_GROUP_BOOL(DUCK_IN_VEHICLE_TUNE, DISABLE_CONTROL_LAGGING, false);
float fControlLaggingBlendingRate = -1.0f;
if (!DISABLE_CONTROL_LAGGING)
{
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, AUTOMOBILE_MIN_LAGGING_BLEND_RATE, 5.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, AUTOMOBILE_MAX_LAGGING_BLEND_RATE, 20.0f, 0.0f, 40.0f, 0.01f);
fControlLaggingBlendingRate = (1.0f - fIdleBlend) * AUTOMOBILE_MIN_LAGGING_BLEND_RATE + fIdleBlend * AUTOMOBILE_MAX_LAGGING_BLEND_RATE;
fSteerControlModified = Clamp(fSteerControlModified, fLaggedSteerControl - fControlLaggingBlendingRate * fTimeStep, fLaggedSteerControl + fControlLaggingBlendingRate * fTimeStep);
pPlayerInfo->SetLaggedSteerControl(fSteerControlModified);
}
// Random control input modifier after having been settled (not moving stick)
TUNE_GROUP_BOOL(DUCK_IN_VEHICLE_TUNE, DISABLE_RANDOM_STEER_IMPULSE, false);
float fRandomControlSteerModifier = pPlayerInfo->GetRandomControlSteerModifier();
float fControlSettledTime = pPlayerInfo->GetControlSettledTime();
const float fDecayTime = pPlayerInfo->GetTimeSinceApplyingRandomControl();
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, RANDOM_CONTROL_MODIFIER_MAG, 1.0f, 0.0f, 3.0f, 0.1f);
if (!DISABLE_RANDOM_STEER_IMPULSE && pDriver->GetPedConfigFlag(CPED_CONFIG_FLAG_IsDuckingInVehicle))
{
// If not applying steer impulse
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, IS_RANDOM_STEER_DECAYED_THRESHOLD, 0.05f, 0.0f, 1.0f, 0.01f);
if (Abs(fRandomControlSteerModifier) < IS_RANDOM_STEER_DECAYED_THRESHOLD)
{
// Process settled timers
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, STEER_CONSIDER_SETTLED_TIME_THRESHOLD, 0.5f, 0.0f, 2.0f, 0.01f);
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, MAX_STEER_DELTA_FOR_SETTLED, 0.1f, 0.0f, 1.0f, 0.01f);
const bool bWasSettled = fControlSettledTime > STEER_CONSIDER_SETTLED_TIME_THRESHOLD;
const float fPreviousSteerValue = pPlayerInfo->GetPreviousSteerValue();
const float fDeltaSteerAngle = Abs(fPreviousSteerValue - fSteerControlUnmodified);
const bool bWithinSettleTolerance = fDeltaSteerAngle < MAX_STEER_DELTA_FOR_SETTLED;
if (bWithinSettleTolerance)
{
fControlSettledTime += fwTimer::GetTimeStep();
}
else
{
fControlSettledTime = 0.0f;
}
pPlayerInfo->SetControlSettledTime(fControlSettledTime);
pPlayerInfo->SetTimeSinceApplyingRandomControl(0.0f);
// If was settled but move out of range initiate steer impulse
if (bWasSettled && !bWithinSettleTolerance)
{
// Set a modifier impulse in the opposite direction to the steer direction
const float fControlSteer = pDriver->GetControlFromPlayer() ? pDriver->GetControlFromPlayer()->GetVehicleSteeringLeftRight().GetNorm() : 0.0f;
pPlayerInfo->SetRandomControlSteerModifier(fControlSteer < 0.0f ? -1.0f : 1.0f);
}
else
{
pPlayerInfo->SetRandomControlSteerModifier(0.0f);
}
}
// Else process steer impulse decay
else
{
TUNE_GROUP_FLOAT(DUCK_IN_VEHICLE_TUNE, EXP_DECAY_RATE, 10.0f, 0.0f, 100.0f, 0.1f);
fRandomControlSteerModifier = Sign(fRandomControlSteerModifier) * expf(-EXP_DECAY_RATE * fDecayTime);
pPlayerInfo->SetRandomControlSteerModifier(fRandomControlSteerModifier);
pPlayerInfo->SetTimeSinceApplyingRandomControl(fDecayTime + fwTimer::GetTimeStep());
fControlSettledTime = 0.0f;
}
fSteerControlModified = Clamp(fSteerControlModified + RANDOM_CONTROL_MODIFIER_MAG * fRandomControlSteerModifier, -1.0f, 1.0f);
}
pPlayerInfo->SetPreviousSteerValue(fSteerControlUnmodified);
// Reapply modified steering control
m_vehControls.SetSteerAngle(fSteerControlModified);
#if DEBUG_DRAW
TUNE_GROUP_BOOL(DUCK_IN_VEHICLE_TUNE, DRAW_DEBUG, false);
if (DRAW_DEBUG)
{
static Vector2 svDrawPos(0.1f, 0.3f);
static float sfVerticalSpaceBetweenTexts = 0.025f;
s32 iNumTexts = 0;
char szText[128];
formatf(szText, "fIdleBlend = %.2f", fIdleBlend);
Vec3V vDrawPos(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_cyan, 100, atStringHash("DUCK_fIdleBlend"));
formatf(szText, "fControlLaggingBlendingRate = %.2f", fControlLaggingBlendingRate);
vDrawPos = Vec3V(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_cyan, 100, atStringHash("DUCK_fControlLaggingBlendingRate"));
formatf(szText, "fRandomControlSteerModifier = %.2f (%.2f)", fRandomControlSteerModifier, RANDOM_CONTROL_MODIFIER_MAG * fRandomControlSteerModifier);
vDrawPos = Vec3V(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_cyan, 100, atStringHash("DUCK_fRandomControlSteerModifier"));
formatf(szText, "fControlSettledTime = %.2f", fControlSettledTime);
vDrawPos = Vec3V(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_green, 100, atStringHash("DUCK_fControlSettledTime"));
formatf(szText, "fSteerControlUnmodified = %.2f", fSteerControlUnmodified);
vDrawPos = Vec3V(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_green, 100, atStringHash("DUCK_fSteerControlUnmodified"));
formatf(szText, "fSteerControlModified = %.2f", fSteerControlModified);
vDrawPos = Vec3V(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_green, 100, atStringHash("DUCK_fSteerControlModified"));
formatf(szText, "fDecayTime = %.2f", fDecayTime);
vDrawPos = Vec3V(svDrawPos.x, svDrawPos.y + sfVerticalSpaceBetweenTexts * iNumTexts++, 0.0f);
CTask::ms_debugDraw.Add2DText(vDrawPos, szText, Color_green, 100, atStringHash("DUCK_fDecayTime"));
static float sfModifierRenderHeight = 0.3f;
static Vector2 svModifierDrawPosStart(0.4f, sfModifierRenderHeight);
static Vector2 svModifierDrawPosEnd(0.5f, sfModifierRenderHeight);
static float fMarkerHeight = 0.05f;
CTask::ms_debugDraw.AddLine(svModifierDrawPosStart, svModifierDrawPosEnd, Color_black, 100, atStringHash("DUCK_Bar"));
const float fHalfWidth = (svModifierDrawPosEnd.x - svModifierDrawPosStart.x) * 0.5f;
const float fMidXPoint = svModifierDrawPosStart.x + fHalfWidth;
const float fMarkerXPos = fMidXPoint + fRandomControlSteerModifier * fHalfWidth;
const float fMarkerYStartPos = sfModifierRenderHeight - fMarkerHeight;
const float fMarkerYEndPos = sfModifierRenderHeight + fMarkerHeight;
CTask::ms_debugDraw.AddLine(Vector2(fMarkerXPos, fMarkerYStartPos), Vector2(fMarkerXPos, fMarkerYEndPos), Color32(0, (int)(255.0f*fRandomControlSteerModifier), 0), 100, atStringHash("DUCK_Marker"));
}
#endif // DEBUG_DRAW
}
void CAutomobile::EnableBurnoutMode(bool bEnableBurnout)
{
m_nAutomobileFlags.bBurnoutModeEnabled = bEnableBurnout;
if(bEnableBurnout)
{
// Turn off the hand brake or else we won't get drive force in CTransmission::ProcessEngine
SetHandBrake(false);
}
}
void CAutomobile::SetReduceGripLevel( int reducedGripLevel )
{
for( int i = 0; i < m_nNumWheels; i++ )
{
if( m_ppWheels[ i ] )
{
m_ppWheels[ i ]->SetReducedGripLevel( reducedGripLevel );
}
}
}
void CAutomobile::SetReducedSuspensionForce( bool bReduceSuspensionForce )
{
for( int i = 0; i < m_nNumWheels; i++ )
{
if( m_ppWheels[ i ] )
{
m_ppWheels[ i ]->SetReducedSuspensionForce( bReduceSuspensionForce );
}
}
//Activate physics so the vehicle moves to its correct ride hide after this has been called.
ActivatePhysics();
if(m_VehicleAudioEntity)
{
m_VehicleAudioEntity->OnReducedSuspensionForceSet(bReduceSuspensionForce);
}
}
bool CAutomobile::GetReducedSuspensionForce()
{
for(int i = 0; i < m_nNumWheels; i++)
{
if(m_ppWheels[i])
{
return m_ppWheels[i]->GetReducedSuspensionForce();
}
}
return false;
}
static dev_float fHandbrakeFallOffMult = 0.0025f;
static dev_float fHandbrakeFalloffMin = 0.5f; // Handbrake decays from 1x to this value
float CAutomobile::GetHandBrakeForce() const
{
float fTime = (float) (fwTimer::GetTimeInMilliseconds() - m_uHandbrakeOnTime);
Assert(fTime >= 0.0f);
float fMult = exp(-fHandbrakeFallOffMult * fTime);
fMult = (fMult * (1.0f - fHandbrakeFalloffMin) ) + fHandbrakeFalloffMin;
return pHandling->m_fHandBrakeForce * fMult ;
}
static dev_float sfHandbrakeGripFalloffTime = 1500.0f; // milliseconds
static dev_float sfHandbrakeGripMultBase = 0.65f; // We drop to this much grip as soon as handbrake is pulled
float CAutomobile::GetHandBrakeGripMult() const
{
float fGripMult = 1.0f;
float fTime = (float) (fwTimer::GetTimeInMilliseconds() - m_uHandbrakeOnTime);
if(fTime >= 0.0f && fTime < sfHandbrakeGripFalloffTime)
{
fTime /= sfHandbrakeGripFalloffTime;
fGripMult = sfHandbrakeGripMultBase + (1.0f - sfHandbrakeGripMultBase) * fTime;
}
return fGripMult;
}
void CAutomobile::ResetHydraulics()
{
if( HasHydraulicSuspension() )
{
for( int i = 0; i < GetNumWheels(); i++ )
{
CWheel* pWheel = GetWheel( i );
if( pWheel )
{
pWheel->SetSuspensionRaiseAmount( 0.0f );
pWheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION );
pWheel->m_bSuspensionForceModified = false;
pWheel->SetSuspensionHydraulicTargetState(WHS_IDLE);
}
}
m_nVehicleFlags.bAreHydraulicsAllRaised = false;
}
}
RAGETRACE_DECL(CAutomobile_ProcessProbes);
PF_GROUP(CAutomobileProbes);
PF_PAGE(ProcessProbes, "CAutomobile::ProcessProbes");
PF_LINK(ProcessProbes, CAutomobileProbes);
PF_TIMER(Probes, CAutomobileProbes);
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : ProcessProbes
// PURPOSE : Handles the wheel intersections against the ground (whether
// the ground surface is real or virtual) and processes the
// wheel physics.
// PARAMETERS : None.
// RETURNS : Nothing.
/////////////////////////////////////////////////////////////////////////////////
void CAutomobile::ProcessProbes(const Matrix34& testMat)
{
PF_START(Probes);
if( m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy) ||
GetNumWheels() <= 0)
{
PF_STOP(Probes);
return;
}
bool bIsDummy = IsDummy();
bool bIsInactivePlayback = (CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this) || GetOwnedBy() == ENTITY_OWNEDBY_CUTSCENE) && CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex());
RAGETRACE_SCOPE(CAutomobile_ProcessProbes);
if(!bIsDummy && !bIsInactivePlayback && GetWheel(0)->GetFragChild() > 0)
{
m_nVehicleFlags.bDontSleepOnThisPhysical = false;
for(int i=0; i<GetNumWheels(); i++)
GetWheel(i)->ProcessImpactResults();
PF_STOP(Probes);
return;
}
if(GetIsAircraft() && GetDriver() && !GetDriver()->IsAPlayerPed() && !m_bIgnoreWorldHeightMapForWheelProbes )
{
Vec3V vPosition = GetTransform().GetPosition();
float fMapMaxZ = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(vPosition.GetXf(), vPosition.GetYf());
Vec3V vBBMinZGlobal = GetTransform().Transform(Vec3V(0.0f, 0.0f, GetBoundingBoxMin().z));
if(vBBMinZGlobal.GetZf() > fMapMaxZ)
{
for(int i=0; i<GetNumWheels(); i++)
{
GetWheel(i)->Reset(true);
}
PF_STOP(Probes);
return;
}
}
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessPhysicsOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfProcessPhysicsCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
static phSegment testSegments[NUM_VEH_CWHEELS_MAX];
static WorldProbe::CShapeTestFixedResults<NUM_VEH_CWHEELS_MAX> probeResults;
WorldProbe::CShapeTestHitPoint* resultsToProcess = &probeResults[0];
// Init the segment and intersection data.
probeResults.Reset();
// Setup the segments to test against the ground surface.
for(int i=0; i<GetNumWheels(); i++)
{
const CWheel& wheel = *GetWheel(i);
if(wheel.GetFragChild() == -1 && InheritsFromHeli())
{
// Rotate heli skid probes inwards to prevent large vertical changes in ground height from causing oscillation.
const ScalarV probeRotationAngle = ScalarVFromF32(25.0f*DtoR);
const Vec3V localBot = RCC_VEC3V(wheel.GetProbeSegment().B);
const Vec3V localTopOriginal = RCC_VEC3V(wheel.GetProbeSegment().A);
const ScalarV inwardsRotationAngle = SelectFT(IsGreaterThan(localBot.GetX(),ScalarV(V_ZERO)),probeRotationAngle,-probeRotationAngle);
const Vec3V localTopRotated = rage::Add(localBot, RotateAboutYAxis(Subtract(localTopOriginal,localBot),inwardsRotationAngle));
testSegments[i].Set(VEC3V_TO_VECTOR3(Transform(RCC_MAT34V(testMat),localTopRotated)), VEC3V_TO_VECTOR3(Transform(RCC_MAT34V(testMat),localBot)));
}
else
{
GetWheel(i)->ProbeGetTransfomedSegment(&testMat, testSegments[i]);
}
}
// Dummy vehicles drive on a fixed simplified surface.
if(bIsDummy)
{
Assert( !m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy) );
const CVehicleFollowRouteHelper* pFollowRoute = NULL;
const CVehicleNodeList* pNodeList = NULL;
// Trailers use their parent's follow route
if (m_nVehicleFlags.bHasParentVehicle && CVehicle::IsEntityAttachedToTrailer(this) && GetDummyAttachmentParent()
&& GetDummyAttachmentParent()->GetDummyAttachmentParent())
{
pFollowRoute = GetDummyAttachmentParent()->GetDummyAttachmentParent()->GetIntelligence()->GetFollowRouteHelper();
pNodeList = GetDummyAttachmentParent()->GetDummyAttachmentParent()->GetIntelligence()->GetNodeList();
}
else if(m_nVehicleFlags.bHasParentVehicle && InheritsFromTrailer() && GetDummyAttachmentParent())
{
pFollowRoute = GetDummyAttachmentParent()->GetIntelligence()->GetFollowRouteHelper();
pNodeList = GetDummyAttachmentParent()->GetIntelligence()->GetNodeList();
}
else
{
pFollowRoute = GetIntelligence()->GetFollowRouteHelper();
pNodeList = GetIntelligence()->GetNodeList();
}
bool bUseTwoVirtualRoadCenters = false;
const Vec3V vVehCenter = GetVehiclePosition();
const ScalarV fVehBoxMaxY = RCC_VEC3V(GetBoundingBoxMax()).GetY();
const ScalarV fVehBoxMinY = RCC_VEC3V(GetBoundingBoxMin()).GetY();
const Vec3V vVehForward = GetVehicleForwardDirection();
static const ScalarV fOffsetScale = ScalarVConstant<FLOAT_TO_INT(0.75f)>(); // Go most of the way toward the ends of the bounding box.
const Vec3V vVehFront = vVehCenter + (fVehBoxMaxY * fOffsetScale) * vVehForward;
const Vec3V vVehRear = vVehCenter + (fVehBoxMinY * fOffsetScale) * vVehForward;
//const Vec3V vOffset(0.0f,0.0f,3.0f);
//grcDebugDraw::Line(vVehFront,vVehFront+vOffset,Color_red,-1);
//grcDebugDraw::Line(vVehRear,vVehRear+vOffset,Color_red,-1);
static const ScalarV fLargeVehThreshold = ScalarV(V_NINE);
if(CVehicleAILodManager::ms_bAllowAltRouteInfoForRearWheels && IsGreaterThanAll(fVehBoxMaxY - fVehBoxMinY, fLargeVehThreshold))
{
if(IsGreaterThanAll((fVehBoxMaxY-fVehBoxMinY),fLargeVehThreshold))
{
bUseTwoVirtualRoadCenters = true;
}
}
if(pFollowRoute && pFollowRoute->GetNumPoints() > 1)
{
CVirtualRoad::TRouteInfo frontRouteInfo;
CVirtualRoad::TRouteInfo rearRouteInfo;
const bool frontRouteInfoCreated = CVirtualRoad::HelperCreateRouteInfoFromFollowRouteAndCenterPos(*pFollowRoute, vVehCenter, frontRouteInfo, ShouldTryToUseVirtualJunctionHeightmaps(), false);
if(frontRouteInfoCreated)
{
bUseTwoVirtualRoadCenters |= (frontRouteInfo.m_bConsiderJunction && CVehicleAILodManager::ms_bAllowAltRouteInfoForRearWheels);
if(bUseTwoVirtualRoadCenters)
{
const bool rearRouteInfoCreated = CVirtualRoad::HelperCreateRouteInfoFromFollowRouteAndCenterPos(*pFollowRoute, vVehRear, rearRouteInfo, ShouldTryToUseVirtualJunctionHeightmaps(), false);
if(rearRouteInfoCreated)
{
CVirtualRoad::TestProbesToVirtualRoadTwoRouteInfos(frontRouteInfo, vVehFront, rearRouteInfo, vVehRear, probeResults, testSegments, GetNumWheels(), GetTransform().GetC().GetZ());
}
}
else
{
CVirtualRoad::TestProbesToVirtualRoadRouteInfo(frontRouteInfo, probeResults, testSegments, GetNumWheels(), GetTransform().GetC().GetZ());
}
}
HandleDummyProbesBottomingOut(probeResults);
UpdateDummyConstraints();
}
else if (pNodeList && pNodeList->FindNumPathNodes() > 1)
{
Assertf(0,"CAutomobile::ProcessProbes with no follow route.");
#if __DEV
GetIntelligence()->PrintTasks();
//print the nodelist
Displayf("Target Index: %d", pNodeList->GetTargetNodeIndex());
for (s32 i = 0; i < CVehicleNodeList::CAR_MAX_NUM_PATHNODES_STORED; i++)
{
Displayf("aNodes[%d]=%d:%d aLinks[%d]=%d\n", i, pNodeList->GetPathNodeAddr(i).GetRegion(), pNodeList->GetPathNodeAddr(i).GetIndex(), i, pNodeList->GetPathLinkIndex(i));
}
#endif //__DEV
if(bUseTwoVirtualRoadCenters)
{
CVirtualRoad::TestProbesToVirtualRoadNodeListAndTwoPoints(*pNodeList, vVehFront, vVehRear, probeResults, testSegments, GetNumWheels(), GetTransform().GetC().GetZ() );
}
else
{
CVirtualRoad::TestProbesToVirtualRoadNodeListAndCenterPos(*pNodeList, vVehCenter, probeResults, testSegments, GetNumWheels(), GetTransform().GetC().GetZ() );
}
HandleDummyProbesBottomingOut(probeResults);
UpdateDummyConstraints();
}
#if (0 && (AI_OPTIMISATIONS_OFF || VEHICLE_OPTIMISATIONS_OFF || AI_VEHICLE_OPTIMISATIONS_OFF))
else
{
#if __DEV
GetIntelligence()->PrintTasks();
#endif //__DEV
Assertf(0, "CAutomobile::ProcessProbes with no valid path. Is there a navmesh path or something else we should be using here?");
}
#endif
}
else
{
int nTestTypes = (ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE);
static bool USE_PROBE_BATCH = false;
if(USE_PROBE_BATCH)
{
WorldProbe::CShapeTestBatchDesc batchProbeDesc;
batchProbeDesc.SetExcludeEntity(this);
batchProbeDesc.SetIncludeFlags(nTestTypes);
batchProbeDesc.SetTypeFlags(ArchetypeFlags::GTA_WHEEL_TEST);
ALLOC_AND_SET_PROBE_DESCRIPTORS(batchProbeDesc,GetNumWheels());
for(int i=0; i<GetNumWheels(); i++)
{
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetFirstFreeResultOffset(i);
probeDesc.SetMaxNumResultsToUse(1);
probeDesc.SetStartAndEnd(testSegments[i].A, testSegments[i].B);
batchProbeDesc.AddLineTest(probeDesc);
}
WorldProbe::GetShapeTestManager()->SubmitTest(batchProbeDesc);
}
else
{
if(ms_bUseAsyncWheelProbes)
{
const int numWheels = GetNumWheels();
if(m_pWheelProbeResults == NULL)
{
// Allocate the results structure for async tests
m_pWheelProbeResults = rage_new WorldProbe::CShapeTestSingleResult[numWheels];
Assert(m_pWheelProbePreviousResults == NULL);
m_pWheelProbePreviousResults = rage_new WorldProbe::CShapeTestHitPoint[numWheels];
}
resultsToProcess = m_pWheelProbePreviousResults;
for(int wheelIndex = 0; wheelIndex < numWheels; ++wheelIndex)
{
WorldProbe::CShapeTestSingleResult& wheelProbeResult = m_pWheelProbeResults[wheelIndex];
WorldProbe::CShapeTestHitPoint& wheelProbePreviousResult = m_pWheelProbePreviousResults[wheelIndex];
// Check if we're in the middle of a test
if(!wheelProbeResult.GetWaitingOnResults())
{
// If we aren't waiting on results but don't have results ready it means we have no results to process.
// We need to do a synchronous test first so the wheels have something this frame. There is no point doing
// an async test at the same time since it would be the exact same probes.
bool performTestSynchronously = true;
if(wheelProbeResult.GetResultsReady())
{
// We sent off a probe and got results for it
wheelProbePreviousResult = wheelProbeResult[0];
performTestSynchronously = false;
}
if(GetVehicleType() == VEHICLE_TYPE_HELI && ContainsLocalPlayer())
{
Vector3 vVelocity = GetVelocity();
static dev_float sfSuspensionLimitMult = 0.5f;
if(vVelocity.z*fwTimer::GetTimeStep() <= -pHandling->m_fSuspensionUpperLimit*sfSuspensionLimitMult)// If we are moving down faster than our suspension length use a sync test, otherwise we may miss the ground.
{
performTestSynchronously = true;
}
// Use sync probe if we sitting on a vehicle, this will prevent inconsistent probe result
if(wheelProbePreviousResult.GetHitEntity() && wheelProbePreviousResult.GetHitEntity()->GetIsTypeVehicle())
{
performTestSynchronously = true;
}
}
// There isn't a test in progress so we should start one
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&wheelProbeResult);
probeDesc.SetExcludeEntity(this);
probeDesc.SetIncludeFlags(nTestTypes);
probeDesc.SetTypeFlags(ArchetypeFlags::GTA_WHEEL_TEST);
probeDesc.SetContext(WorldProbe::EVehicle);
probeDesc.SetStartAndEnd(testSegments[wheelIndex].A, testSegments[wheelIndex].B);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, performTestSynchronously ? WorldProbe::PERFORM_SYNCHRONOUS_TEST : WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
if(performTestSynchronously)
{
// If we did a synchronous test, store the results right away so we can use them this frame
wheelProbePreviousResult = wheelProbeResult[0];
}
}
// It looks like we are touching the ground, but the probe out of date, we need to recompute the intersection
if(wheelProbePreviousResult.IsAHit())
{
// Treat the most recent ground as an infinite plane, and intersect the current suspension probe with it
const Vec3V groundNormal = wheelProbePreviousResult.GetNormal();
const Vec3V pointOnGround = wheelProbePreviousResult.GetPosition();
const Vec3V probeStart = RCC_VEC3V(testSegments[wheelIndex].A);
const Vec3V probeStartToEnd = Subtract(RCC_VEC3V(testSegments[wheelIndex].B),probeStart);
const ScalarV tValue = InvScaleSafe(Dot(groundNormal, Subtract(pointOnGround,probeStart)), Dot(groundNormal, probeStartToEnd), ScalarV(V_NEGONE));
if(And(IsGreaterThanOrEqual(tValue,ScalarV(V_ZERO)), IsLessThanOrEqual(tValue,ScalarV(V_ONE))).Getb())
{
// The current suspension hit the infinite plane so update the intersection
wheelProbePreviousResult.SetT(tValue);
wheelProbePreviousResult.SetPosition(AddScaled(probeStart,probeStartToEnd,tValue));
}
else
{
// The suspension didn't hit the previous ground plane so just clear it
wheelProbePreviousResult.Reset();
}
}
}
}
else
{
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetExcludeEntity(this);
probeDesc.SetIncludeFlags(nTestTypes);
probeDesc.SetTypeFlags(ArchetypeFlags::GTA_WHEEL_TEST);
for(int i=0; i<GetNumWheels(); i++)
{
probeDesc.SetStartAndEnd(testSegments[i].A, testSegments[i].B);
probeDesc.SetFirstFreeResultOffset(i);
probeDesc.SetMaxNumResultsToUse(1);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
}
}
}
}
bool bHighCompression = false;
bool bNoCompression = false;
// Process the results of the probes.
for(int i=0; i<GetNumWheels(); i++)
{
#if __BANK
if(ms_bDrawWheelProbeResults && resultsToProcess[i].IsAHit())
{
const Vec3V resultPosition = resultsToProcess[i].GetPosition();
const Vec3V resultNormal = resultsToProcess[i].GetNormal();
const float normalDrawLength = 1.0f;
const float drawRadius = 0.06f;
grcDebugDraw::Sphere(resultPosition, drawRadius, Color_red);
grcDebugDraw::Arrow(resultPosition,AddScaled(resultPosition,resultNormal,ScalarVFromF32(normalDrawLength)),drawRadius,Color_red);
}
#endif // __BANK
GetWheel(i)->ProbeProcessResults(&testMat, resultsToProcess[i]);
if(NetworkInterface::IsGameInProgress())
{
if(GetWheel(i)->GetCompression() > GetWheel(i)->GetSuspensionLength())
{
bHighCompression = true;
}
else if(!GetWheel(i)->GetIsTouching())
{
bNoCompression = true;
}
}
}
if(NetworkInterface::IsGameInProgress() && bHighCompression && bNoCompression && IsDummy())// Convert to real if we have one wheel through the floor and one wheel off the ground. Also limiting this to network games for the time being.
{
TryToMakeFromDummy(true);
}
// Check if we should move the vehicle bounding box to prevent ground penetrations.
if(CVehicleAILodManager::ms_convertUseFixupCollisionWithWheelSurface)
{
if(bIsDummy)
{
FixupIntersectionWithWheelSurface(probeResults);
}
}
// Mark the we processed the wheel collisions.
m_nVehicleFlags.bVehicleColProcessed = true;
PF_STOP(Probes);
}
static dev_float svVelTolerance2 = 0.005f;
bool CAutomobile::IsInAir(bool bCheckForZeroVelocity) const
{
if (HasContactWheels() || GetIsInWater() || (GetVelocityIncludingReferenceFrame().Mag2() < svVelTolerance2 && bCheckForZeroVelocity))
return false;
if(GetIsAnyFixedFlagSet())
return false;
return true;
}
static atHashString sHashTruck("TRUCK",0x428100C5);
void CAutomobile::InitDoors()
{
CVehicle::InitDoors();
Assert(GetNumDoors()==0);
m_pDoors = m_aCarDoors;
m_nNumDoors = 0;
float fOpenDoorAngle = 0.4f*PI;
if(m_nVehicleFlags.bIsBus)
fOpenDoorAngle = 0.46f*PI;
if(GetBoneIndex(VEH_DOOR_DSIDE_F) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_DSIDE_F, -fOpenDoorAngle, 0.0f, CCarDoor::AXIS_Z|CCarDoor::WILL_LOCK_SWINGING);
if(pHandling->dFlags & DF_DRIVER_SIDE_FRONT_DOOR)
{
m_pDoors[m_nNumDoors].SetFlag(CCarDoor::DONT_BREAK);
}
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_PSIDE_F) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_PSIDE_F, fOpenDoorAngle, 0.0f, CCarDoor::AXIS_Z|CCarDoor::WILL_LOCK_SWINGING);
if(pHandling->dFlags & DF_DRIVER_PASSENGER_SIDE_FRONT_DOOR)
{
m_pDoors[m_nNumDoors].SetFlag(CCarDoor::DONT_BREAK);
}
m_nNumDoors++;
}
fOpenDoorAngle = 0.4f*PI;
if(m_nVehicleFlags.bIsVan)
fOpenDoorAngle = HALF_PI;
if(GetBoneIndex(VEH_DOOR_DSIDE_R) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_DSIDE_R, -fOpenDoorAngle, 0.0f, CCarDoor::AXIS_Z|CCarDoor::WILL_LOCK_SWINGING);
if(pHandling->dFlags & DF_DRIVER_SIDE_REAR_DOOR)
{
m_pDoors[m_nNumDoors].SetFlag(CCarDoor::DONT_BREAK);
}
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_PSIDE_R) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_PSIDE_R, fOpenDoorAngle, 0.0f, CCarDoor::AXIS_Z|CCarDoor::WILL_LOCK_SWINGING);
if(pHandling->dFlags & DF_DRIVER_PASSENGER_SIDE_REAR_DOOR)
{
m_pDoors[m_nNumDoors].SetFlag(CCarDoor::DONT_BREAK);
}
m_nNumDoors++;
}
bool linkBoot2 = false;
if( GetBoneIndex( VEH_BOOT_2 ) > -1 )
{
u32 uInitFlags = CCarDoor::AXIS_X | CCarDoor::GAS_BOOT;
if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_FRONT_BOOT ) )
{
uInitFlags |= CCarDoor::AERO_BONNET;
}
m_pDoors[ m_nNumDoors ].Init( this, VEH_BOOT_2, 0.1f*PI, 0.0f, uInitFlags );
if( pHandling->dFlags & DF_BOOT )
{
m_pDoors[ m_nNumDoors ].SetFlag( CCarDoor::DONT_BREAK );
}
if( !GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_FRONT_BOOT ) )
{
linkBoot2 = true;
}
m_nNumDoors++;
}
if(GetBoneIndex(VEH_BONNET) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_BONNET, 0.3f*PI, 0.0f, CCarDoor::AXIS_X|CCarDoor::AERO_BONNET);
if(pHandling->dFlags & DF_BONNET)
{
m_pDoors[m_nNumDoors].SetFlag(CCarDoor::DONT_BREAK);
}
if( linkBoot2 &&
GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_TWO_BONNET_BONES ) )
{
m_pDoors[ m_nNumDoors ].SetLinkedDoor( VEH_BOOT_2 );
}
m_nNumDoors++;
}
if(GetBoneIndex(VEH_BOOT) > -1)
{
u32 uInitFlags = CCarDoor::AXIS_X|CCarDoor::GAS_BOOT;
if(GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_FRONT_BOOT))
{
uInitFlags |= CCarDoor::AERO_BONNET;
}
m_pDoors[m_nNumDoors].Init(this, VEH_BOOT, -0.3f*PI, 0.0f, uInitFlags);
if(pHandling->dFlags & DF_BOOT)
{
m_pDoors[m_nNumDoors].SetFlag(CCarDoor::DONT_BREAK);
}
if( linkBoot2 &&
!GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_TWO_BONNET_BONES ) )
{
m_pDoors[m_nNumDoors].SetLinkedDoor( VEH_BOOT_2 );
}
m_nNumDoors++;
}
}
bool CAutomobile::IsCarFloating()
{
if(GetIsInWater() &&
m_fTimeInWater > 0.0f &&
m_fTimeInWater < AUTOMOBILE_SINK_TIME)
return true;
else
return false;
}
void CAutomobile::SetMinHydraulicsGroundClearance()
{
if( (float)GetVariationInstance().GetHydraulicsModifier() > 0.0f )
{
bool hydraulicsActive = ( m_nVehicleFlags.bPlayerModifiedHydraulics ||
m_nAutomobileFlags.bHydraulicsBounceRaising ||
m_nAutomobileFlags.bHydraulicsBounceLanding ||
m_nAutomobileFlags.bHydraulicsJump );
if( !hydraulicsActive )
{
m_nAutomobileFlags.bForceUpdateGroundClearance = 1;
}
}
}
void CAutomobile::StreamInParachute()
{
if(GetDriver())
{
//Check if the parachute model index is invalid.
if(m_iParachuteModelIndex == fwModelId::MI_INVALID)
{
fwModelId iModelId;
CModelInfo::GetBaseModelInfoFromHashKey( GetParachuteModelOverride(), &iModelId );
m_iParachuteModelIndex = iModelId.GetModelIndex();
}
//Ensure the parachute model is valid.
fwModelId iModelId((strLocalIndex(m_iParachuteModelIndex)));
if(Verifyf(iModelId.IsValid(), "Parachute model is invalid."))
{
//Check if the parachute model request is not valid.
if(!m_ModelRequestHelper.IsValid())
{
//Stream the parachute model.
strLocalIndex transientLocalIdx = CModelInfo::AssignLocalIndexToModelInfo(iModelId);
m_ModelRequestHelper.Request(transientLocalIdx, CModelInfo::GetStreamingModuleId(), STRFLAG_PRIORITY_LOAD);
}
}
}
}
static CCustomShaderEffectProp* GetCsePropFromObject(CObject *pObj)
{
fwCustomShaderEffect *fwCSE = pObj->GetDrawHandler().GetShaderEffect();
if(fwCSE && fwCSE->GetType()==CSE_PROP)
{
return static_cast<CCustomShaderEffectProp*>(fwCSE);
}
return NULL;
}
void CAutomobile::CreateParachute()
{
if(GetDriver())
{
fwModelId iModelId;
CModelInfo::GetBaseModelInfoFromHashKey( GetParachuteModelOverride(), &iModelId );
//Ensure the model id is valid.
if(Verifyf(iModelId.IsValid(), "Invalid parachute model"))
{
//Create the input.
CObjectPopulation::CreateObjectInput input(iModelId, ENTITY_OWNEDBY_GAME, true);
input.m_bForceClone = true;
//Create the parachute object.
Assert(!m_pParachuteObject);
m_pParachuteObject = CObjectPopulation::CreateObject(input);
if(Verifyf(m_pParachuteObject, "Failed to create parachute"))
{
m_pParachuteObject->SetIsParachute(true);
m_pParachuteObject->SetIsCarParachute(true);
REPLAY_ONLY(CReplayMgr::RecordObject(m_pParachuteObject));
//Make the parachute invisible.
m_pParachuteObject->SetIsVisibleForModule(SETISVISIBLE_MODULE_SCRIPT, false);
m_pParachuteObject->m_nObjectFlags.bIsVehicleGadgetPart = true;
//Grab the ped.
CPed* pPed = GetDriver();
//Add the parachute to the world.
CGameWorld::Add(m_pParachuteObject, pPed->GetInteriorLocation(), false);
// Add the parachuteobject task to the parachute...
if(m_pParachuteObject->GetTask(CObjectIntelligence::OBJECT_TASK_TREE_SECONDARY) == NULL)
{
// TaskParachuteObject is now a cloned task so only create it on the owner....
if(GetDriver() && !GetDriver()->IsNetworkClone())
{
//Give the parachute its task.
CTaskParachuteObject* parachuteObjectTask = rage_new CTaskParachuteObject;
m_pParachuteObject->SetTask(parachuteObjectTask, CObjectIntelligence::OBJECT_TASK_TREE_SECONDARY);
//Update the task immediately.
if(m_pParachuteObject->GetObjectIntelligence())
{
m_pParachuteObject->GetObjectIntelligence()->ForcePostCameraTaskUpdate();
}
}
}
m_pParachuteObject->EnableCollision();
//Set the mass.
m_pParachuteObject->SetMass(100.0f);
//Prevent the camera shapetests from detecting this object, as we don't want the camera to pop if it passes near to the camera
//or collision root position.
phInst* pParachutePhysicsInst = m_pParachuteObject->GetCurrentPhysicsInst();
if(pParachutePhysicsInst)
{
if(pParachutePhysicsInst->IsInLevel())
{
CPhysics::GetLevel()->SetInstanceIncludeFlags(pParachutePhysicsInst->GetLevelIndex(), ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
//Grab the level index.
u16 uLevelIndex = pParachutePhysicsInst->GetLevelIndex();
//Set inactive collisions.
PHLEVEL->SetInactiveCollidesAgainstInactive(uLevelIndex, true);
PHLEVEL->SetInactiveCollidesAgainstFixed(uLevelIndex, true);
}
phArchetype* pParachuteArchetype = pParachutePhysicsInst->GetArchetype();
if(pParachuteArchetype)
{
//NOTE: This include flag change will apply to all new instances that use this archetype until it is streamed out.
//We can live with this for camera purposes, but it's worth noting in case it causes a problem.
pParachuteArchetype->RemoveIncludeFlags(ArchetypeFlags::GTA_CAMERA_TEST);
}
phArchetypeDamp* pPhysicsArchetype = static_cast<phArchetypeDamp*>( pParachuteArchetype );
pPhysicsArchetype->SetBuoyancyFactor( 0.0f );
}
// tint parachute:
m_pParachuteObject->SetTintIndex(m_nParachuteTintIndex);
CCustomShaderEffectProp *pPropCSE = GetCsePropFromObject(m_pParachuteObject);
if(pPropCSE)
{
pPropCSE->SelectTintPalette((u8)m_nParachuteTintIndex, m_pParachuteObject);
}
} // End Verifyf(m_pParachuteObject, "Failed to create parachute")
} // End Verifyf(iModelId.IsValid(), "Invalid parachute model")
}
}
void CAutomobile::AttachParachuteToVehicle()
{
//Ensure the parachute is valid.
if(Verifyf(m_pParachuteObject, "Parachute is invalid."))
{
//Ensure the parachute is not attached.
if(m_pParachuteObject->GetIsAttached())
{
m_pParachuteObject->DetachFromParent(0);
}
//Calculate the attach offset.
Assertf(GetBoneIndex(VEH_PARACHUTE_DEPLOY) > -1, "Vehicle with parachute is missing the parachute attachment bone 'parachute_deploy'!");
Vector3 vAttachOffset = VEC3_ZERO;
TUNE_GROUP_FLOAT(VEHICLE_PARACHUTE_TUNE, PARACHUTE_Z_OFFSET, 2.25f, -10.0f, 10.0f, 0.1f);
vAttachOffset.z += PARACHUTE_Z_OFFSET;
TUNE_GROUP_FLOAT(VEHICLE_PARACHUTE_TUNE, PARACHUTE_Y_OFFSET, -0.15f, -10.0f, 10.0f, 0.05f);
vAttachOffset.y += PARACHUTE_Y_OFFSET;
//Attach the parachute to the ped.
m_pParachuteObject->AttachToPhysicalBasic(this, (rage::s16)GetBoneIndex(VEH_PARACHUTE_DEPLOY), ATTACH_STATE_BASIC|ATTACH_FLAG_INITIAL_WARP | ATTACH_FLAG_COL_ON, &vAttachOffset, NULL);
}
}
void CAutomobile::DetachParachuteFromVehicle()
{
//Ensure the parachute is valid.
if(!m_pParachuteObject)
{
return;
}
//Ensure the parachute is attached.
if(!m_pParachuteObject->GetIsAttached())
{
return;
}
vehicleDisplayf("CAutomobile::DetachParachuteFromVehicle() Detaching parachute %s", m_pParachuteObject->GetNetworkObject() ? m_pParachuteObject->GetNetworkObject()->GetLogName() : "Parachute Not Networked!");
//Detach the parachute from the ped.
m_pParachuteObject->DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_APPLY_VELOCITY);
}
void CAutomobile::DestroyParachute()
{
//Ensure the parachute is valid.
if(m_pParachuteObject)
{
vehicleDisplayf("CAutomobile::DestroyParachute() Destroying parachute %s", m_pParachuteObject->GetNetworkObject() ? m_pParachuteObject->GetNetworkObject()->GetLogName() : "Parachute Not Networked!");
//Fade out the object.
m_pParachuteObject->m_nObjectFlags.bFadeOut = true;
//Disable collision. Having collision enabled here has caused issues
//with peds landing in MP games -- they end up intersecting with it on
//detach, and get shot to the side. I have seen this happen in SP as well.
m_pParachuteObject->DisableCollision(NULL, true);
//Remove the object from the world when it is not visible.
m_pParachuteObject->m_nObjectFlags.bRemoveFromWorldWhenNotVisible = true;
}
}
static dev_float sfParchuteHatchOpenOffset = 0.44f;
static dev_float sfParachuteHatchMoveSpeedMult = 1.5f;
static dev_float sfMaxStabiliseTorque = 20.0f;
static dev_float sfMaxDeployAngle = 1.0f;
static dev_float sfMaxAngVelForDeploy = 1.0f;
void CAutomobile::ProcessCarParachute()
{
Assertf(HasParachute(), "Process car parachute called for a car that can not have a parachute!\n");
// Update whether the player can deploy the parachute
bool bCollidingWithMap = false;
if(GetFrameCollisionHistory())
{
bCollidingWithMap = GetFrameCollisionHistory()->HasCollidedWithAnyOfTypes(ENTITY_TYPE_MASK_BUILDING | ENTITY_TYPE_MASK_ANIMATED_BUILDING);
}
// And wheels not touching the ground
// And has a driver
// And is not colliding with map
// And is not attached to anything
m_bCanDeployParachute = (
GetNumContactWheels() == 0 &&
GetDriver() &&
!bCollidingWithMap &&
!GetIsAttached() &&
!GetIsInWater() &&
GetStatus() != STATUS_WRECKED );
switch(m_carParachuteState)
{
case CAR_NOT_PARACHUTING:
{
CNetObjVehicle* netObjVehicle = SafeCast(CNetObjVehicle, GetNetworkObject());
if (netObjVehicle && netObjVehicle->GetPendingParachuteObjectId() != NETWORK_INVALID_OBJECT_ID)
{
// Clear the pending parachute object ID if we are not parachuting
netObjVehicle->SetPendingParachuteObjectId(NETWORK_INVALID_OBJECT_ID);
}
if (m_wasCloneParachuting && m_pParachuteObject)
{
m_pParachuteObject->m_nObjectFlags.bFadeOut = true;
m_pParachuteObject->DisableCollision(NULL, true);
}
m_wasCloneParachuting = false;
}
break;
case CAR_STABILISING:
{
// Stop jumping if parachuting
if( HasJump() )
{
if( GetIsDoingJump() )
{
vehicleDisplayf( "CAutomobile::ProcessCarParachute - Car Jump: %s, cancelled due to parachute stabilising", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?" );
}
SetIsDoingJump(false);
}
// If we're suddenly in a non-valid state for parachuting, abort
if(!m_bCanDeployParachute)
{
vehicleDisplayf("CAutomobile::ProcessCarParachute() - CAR_STABILISING - RequestFinishParachuting: Name[%s] Network log name[%s]", GetDebugName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "Unknown" );
RequestFinishParachuting();
break;
}
// Adjust desired up vector by stick input
Vector3 vCurrentUp = VEC3V_TO_VECTOR3(GetTransform().GetC());
Vector3 vDesiredUp = Vector3(0.0f, 0.0f, 1.0f);
// Get the angle between the current up and the desired up
float fAngle = AcosfSafe(Dot(vCurrentUp, vDesiredUp));
// Avoid cross product breaking down
if(Abs(fAngle) > 0.001f)
{
// Calculate the rotation axis by crossing the current and desired up vectors
Vector3 vAxisToRotateOn = vCurrentUp;
vAxisToRotateOn.Cross(vDesiredUp);
// Subtract the current angular velocity from the rotation axis to keep the vehicle from spinning out of control
vAxisToRotateOn -= GetAngVelocity();
// Stabilise pitch and roll
ApplyParachuteStabilisation( vAxisToRotateOn, fAngle, sfMaxStabiliseTorque );
}
float fAngVelocityNoYaw = GetAngVelocity().Mag() - Abs(GetAngVelocity().Dot(vCurrentUp));
// Can deploy parachute if leveled enough
// And not spinning too fast
if(Abs(fAngle) < sfMaxDeployAngle &&
fAngVelocityNoYaw < sfMaxAngVelForDeploy )
{
m_carParachuteState = CAR_CREATING_PARACHUTE;
}
}
break;
case CAR_CREATING_PARACHUTE:
{
StreamInParachute();
if(IsParachuteStreamedIn())
{
CreateParachute();
AttachParachuteToVehicle();
if(m_pParachuteObject)
{
m_carParachuteState = CAR_DEPLOYING_PARACHUTE;
}
// Failed to create parachute, abort
else
{
m_carParachuteState = CAR_NOT_PARACHUTING;
}
}
// Can no longer deploy parachute, abort
else if(!m_bCanDeployParachute)
{
m_carParachuteState = CAR_NOT_PARACHUTING;
}
}
break;
case CAR_DEPLOYING_PARACHUTE:
{
if(m_pParachuteObject)
{
//Ensure the task is valid.
CTask* pTask = m_pParachuteObject->GetTask(CObjectIntelligence::OBJECT_TASK_TREE_SECONDARY);
if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_PARACHUTE_OBJECT)
{
CTaskParachuteObject* pParachuteObjTask = static_cast<CTaskParachuteObject *>(pTask);
if(pParachuteObjTask->CanDeploy() )
{
pParachuteObjTask->Deploy();
if(m_VehicleAudioEntity)
{
m_VehicleAudioEntity->OnVehicleParachuteDeploy();
}
m_bHasBeenParachuting = true;
m_carParachuteState = CAR_PARACHUTING;
}
}
}
break;
}
case CAR_PARACHUTING:
{
if(!m_bFinishParachutingRequested)
{
ProcessParachutePhysics();
}
if (GetStatus() == STATUS_WRECKED)
{
RequestFinishParachuting();
}
if( GetNumContactWheels() != 0 || GetIsInWater() )
{
vehicleDisplayf("CAutomobile::ProcessCarParachute() - CAR_PARACHUTING - RequestFinishParachuting: Contact Wheels [%d] In Water[%d] Name[%s] Network Log Name[%s]",
GetNumContactWheels(),
(int)GetIsInWater(),
GetDebugName(),
GetNetworkObject() ? GetNetworkObject()->GetLogName() : "Unknown" );
bool dontFinishParachuting = CObject::ms_bDisableCarCollisionsWithCarParachute &&
( IsWheelContactPhysicalMoving() ||
GetReferenceFrameVelocity().Mag2() > 0.0f );
if( !dontFinishParachuting )
{
RequestFinishParachuting();
}
}
}
break;
case CLONE_CAR_PARACHUTING:
if (!m_pParachuteObject && GetNetworkObject())
{
CNetObjVehicle* netObjVehicle = static_cast<CNetObjVehicle*>(GetNetworkObject());
if (netObjVehicle->GetPendingParachuteObjectId() != NETWORK_INVALID_OBJECT_ID)
{
netObject* netObj = NetworkInterface::GetObjectManager().GetNetworkObject(netObjVehicle->GetPendingParachuteObjectId());
if (netObj && netObj->GetObjectType() == NET_OBJ_TYPE_OBJECT)
{
CNetObjObject* parachuteNetObj = SafeCast(CNetObjObject, netObj);
if (parachuteNetObj)
{
m_pParachuteObject = parachuteNetObj->GetCObject();
if (m_pParachuteObject)
{
m_wasCloneParachuting = true;
m_pParachuteObject->SetIsCarParachute(true);
phInst* pParachutePhysicsInst = m_pParachuteObject->GetCurrentPhysicsInst();
phArchetype* pParachuteArchetype = pParachutePhysicsInst->GetArchetype();
if(pParachuteArchetype)
{
pParachuteArchetype->RemoveIncludeFlags(ArchetypeFlags::GTA_CAMERA_TEST);
}
// tint parachute
m_pParachuteObject->SetTintIndex(m_nParachuteTintIndex);
CCustomShaderEffectProp *pPropCSE = GetCsePropFromObject(m_pParachuteObject);
if(pPropCSE)
{
pPropCSE->SelectTintPalette((u8)m_nParachuteTintIndex, m_pParachuteObject);
}
}
}
}
}
}
if (m_pParachuteObject && GetNetworkObject())
{
ProcessParachutePhysics();
if(!m_pParachuteObject->GetIsAttached() && m_wasCloneParachuting)
{
m_pParachuteObject->m_nObjectFlags.bFadeOut = true;
m_pParachuteObject->DisableCollision(NULL, true);
}
}
break;
default:
break;
}
if(m_carParachuteState != CAR_NOT_PARACHUTING && m_carParachuteState != CLONE_CAR_PARACHUTING)
{
// If in stunt camera mode, disable it
if(SStuntJumpManager::IsInstantiated() && CStuntJumpManager::IsAStuntjumpInProgress())
{
SStuntJumpManager::GetInstance().AbortStuntJumpInProgress();
}
}
Assertf(GetBoneIndex(VEH_PARACHUTE_OPEN) > -1, "Parachute car does not have a parachute_open bone!\n");
// Trap door movement
if(GetBoneIndex(VEH_PARACHUTE_OPEN) > -1)
{
float fDesiredHatchOffset = 0.0f;
if( IsParachuting() )
{
fDesiredHatchOffset = sfParchuteHatchOpenOffset;
}
SlideMechanicalPart(GetBoneIndex(VEH_PARACHUTE_OPEN), fDesiredHatchOffset, m_fParachuteHatchOffset, Vector3(0.0f, -1.0f, 0.0f), sfParachuteHatchMoveSpeedMult);
}
// Process requests to finish parachuting
if(m_bFinishParachutingRequested && !IsNetworkClone())
{
FinishParachuting();
}
// Make sure that after parachuting, when the car lands, we are in the appropriate gear for our speed
if( m_bHasBeenParachuting && GetNumContactWheels() > 0 )
{
m_bHasBeenParachuting = false;
SelectAppropriateGearForSpeed();
}
}
void CAutomobile::ProcessCarParachuteTint()
{
CNetObjVehicle* netObjVehicle = static_cast<CNetObjVehicle*>(GetNetworkObject());
if (netObjVehicle && netObjVehicle->GetPendingParachuteObjectId() != NETWORK_INVALID_OBJECT_ID)
{
netObject* netObj = NetworkInterface::GetObjectManager().GetNetworkObject(netObjVehicle->GetPendingParachuteObjectId());
if (netObj && netObj->GetObjectType() == NET_OBJ_TYPE_OBJECT)
{
CNetObjObject* parachuteNetObj = SafeCast(CNetObjObject, netObj);
if (parachuteNetObj && m_pParachuteObject && m_pParachuteObject->GetTintIndex() != m_nParachuteTintIndex)
{
// tint parachute
m_pParachuteObject->SetTintIndex(m_nParachuteTintIndex);
CCustomShaderEffectProp *pPropCSE = GetCsePropFromObject(m_pParachuteObject);
if(pPropCSE)
{
pPropCSE->SelectTintPalette((u8)m_nParachuteTintIndex, m_pParachuteObject);
}
}
}
}
}
bool CAutomobile::IsParachuteDeployed() const
{
//Ensure the task is valid.
const CTask* pTask = m_pParachuteObject ? m_pParachuteObject->GetTask(CObjectIntelligence::OBJECT_TASK_TREE_SECONDARY) : nullptr;
if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_PARACHUTE_OBJECT)
{
const CTaskParachuteObject* pParachuteObjTask = static_cast<const CTaskParachuteObject *>(pTask);
if(pParachuteObjTask->IsOut() )
{
return true;
}
}
return false;
}
static dev_float sfHeightForParachuteThreshold = 3.0f;
void CAutomobile::StartParachuting()
{
Assertf(HasParachute(), "Calling start parachute on car that does not have a parachute!\n");
if(!m_pParachuteObject && m_carParachuteState == CAR_NOT_PARACHUTING )
{
float fCarZ = GetTransform().GetPosition().GetZ().Getf();
float fHeightAboveGround = fCarZ - WorldProbe::FindGroundZFor3DCoord(0.0f, VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
if( fHeightAboveGround > sfHeightForParachuteThreshold )
{
if(m_bCanDeployParachute && m_VehicleAudioEntity)
{
m_VehicleAudioEntity->OnVehicleParachuteStabilize();
}
m_carParachuteState = CAR_STABILISING;
}
}
}
void CAutomobile::FinishParachuting()
{
Assertf(HasParachute(), "Calling finish parachute on car that does not have a parachute!\n");
bool bCanFinish = false;
if((m_carParachuteState == CAR_PARACHUTING || m_carParachuteState == CLONE_CAR_PARACHUTING) && m_pParachuteObject)
{
CTask* pTask = m_pParachuteObject->GetTask(CObjectIntelligence::OBJECT_TASK_TREE_SECONDARY);
if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_PARACHUTE_OBJECT)
{
CTaskParachuteObject* pParachuteObjTask = static_cast<CTaskParachuteObject *>(pTask);
if( pParachuteObjTask->IsOut() )
{
if(m_VehicleAudioEntity)
{
m_VehicleAudioEntity->OnVehicleParachuteDetach();
}
DetachParachuteFromVehicle();
DestroyParachute();
bCanFinish = true;
}
}
}
else if( m_carParachuteState == CAR_STABILISING && m_bFinishParachutingRequested )
{
bCanFinish = true;
}
if(bCanFinish)
{
// Reset state
m_carParachuteState = CAR_NOT_PARACHUTING;
m_fParachuteStickX = 0.0f;
m_fParachuteStickY = 0.0f;
m_fCloneParachuteStickX = 0.0f;
m_fCloneParachuteStickY = 0.0f;
m_bFinishParachutingRequested = false;
m_bCanPlayerDetachParachute = true;
}
}
static dev_float sfTargetBaseDownVelocity = -5.0f;
static dev_float sfPitchBackwardsUpMult = 3.75f;
static dev_float sfPitchForwardsDownMult = 10.0f;
static dev_float sfTargetBaseFwdVelocity = 19.0f;
static dev_float sfFwdVelToBackPitchMult = -7.0f;
static dev_float sfFwdVelToFwdPitchMult = -9.0f;
static dev_float sfFwdVelocityMult = -1.0f;
static dev_float sfFwdLeanMult = 0.3f;
static dev_float sfBackLeanMult = 0.5f;
static dev_float sfRollMult = 1.0f;
static dev_float sfSwingOffset = 5.0f;
static dev_float sfYawStabilisationMult = -10.0f;
static dev_float sfNegateLateralMult = -0.05f;
static dev_float sfSteerMult = -5000.0f;
void CAutomobile::ProcessParachutePhysics()
{
// If vehicle has jump, cancel it since its stabilisation can mess with the parachute controls
if(HasJump())
{
if( GetIsDoingJump() )
{
vehicleDisplayf( "CAutomobile::ProcessParachutePhysics - Car Jump: %s, cancelled due to parachuting", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?" );
}
SetIsDoingJump(false);
}
// Copy the inputs and flush them
float fStickX;
float fStickY;
netObject *networkObject = GetNetworkObject();
if (networkObject && networkObject->IsClone())
{
fStickX = m_fCloneParachuteStickX;
fStickY = m_fCloneParachuteStickY;
}
else
{
fStickX = m_fParachuteStickX;
fStickY = m_fParachuteStickY;
m_fCloneParachuteStickX = fStickX;
m_fCloneParachuteStickY = fStickY;
}
m_fParachuteStickX = 0.0f;
m_fParachuteStickY = 0.0f;
float fPitch = GetTransform().GetPitch();
float fPitchNorm = fPitch > 0.0f ? ( fPitch / sfFwdLeanMult ) : ( fPitch / sfBackLeanMult );
// UP FORCE
// Adjust target down velocity by pitch
float fTargetDownVelocity = sfTargetBaseDownVelocity;
float fPitchUpDownMult = 0.0f;
if(fPitchNorm > 0.0f)
{
fPitchUpDownMult = sfPitchBackwardsUpMult;
}
else if(fPitchNorm < 0.0f)
{
fPitchUpDownMult = sfPitchForwardsDownMult;
}
fTargetDownVelocity -= fPitchNorm * fPitchUpDownMult;
float fCurrentDownVelocity = GetVelocity().Dot(Vector3(0.0f, 0.0f, -1.0f));
// Apply a force up if we're going down faster than we'd like to
if( fCurrentDownVelocity > fTargetDownVelocity )
{
// Calculate the difference between current and target velocities and apply a force up scaled by this
float fDiffBetweenCurrentAndTargetVelocity = fCurrentDownVelocity - fTargetDownVelocity;
ApplyForce(Vector3(0.0f, 0.0f, 1.0f) * fDiffBetweenCurrentAndTargetVelocity * GetMass(), Vector3(0.0f, 0.0f, 0.0f));
}
// FWD FORCE
// Adjust the target velocity by pitch
float fTargetFwdVelocity = sfTargetBaseFwdVelocity;
float fPitchFwdMult = 0.0f;
if(fPitchNorm > 0.0f)
{
fPitchFwdMult = sfFwdVelToBackPitchMult;
}
else
{
fPitchFwdMult = sfFwdVelToFwdPitchMult;
}
fTargetFwdVelocity += fPitchNorm * fPitchFwdMult;
// Apply a force forwards based on the difference between the current and target forward velocities
float fCurrentFwdVelocity = GetVelocity().Dot(VEC3V_TO_VECTOR3(GetTransform().GetB()));
float fDiffBetweenCurrentAndTargetFwdVel = fCurrentFwdVelocity - fTargetFwdVelocity;
Vector3 vFwdForceAxis = VEC3V_TO_VECTOR3(GetTransform().GetB());
ApplyForce(vFwdForceAxis * fDiffBetweenCurrentAndTargetFwdVel * sfFwdVelocityMult * GetMass(), Vector3(0.0f, 0.0f, 0.0f) );
// STABILISATION/LEAN
// Adjust desired up vector by stick input
Vector3 vCurrentUp = VEC3V_TO_VECTOR3(GetTransform().GetC());
Vector3 vDesiredUp = Vector3(0.0f, 0.0f, 1.0f);
float fLeanMult = 0.0f;
if(fStickY > 0.0f)
{
fLeanMult = sfFwdLeanMult;
}
else if(fStickY < 0.0f)
{
fLeanMult = sfBackLeanMult;
}
// Pitch and roll the desired up vector based on stick input
Quaternion q;
q.FromRotation(VEC3V_TO_VECTOR3(GetTransform().GetA()), fStickY * fLeanMult);
q.Transform(vDesiredUp);
q.FromRotation(VEC3V_TO_VECTOR3(GetTransform().GetB()), fStickX * sfRollMult);
q.Transform(vDesiredUp);
// Get the angle between the current up and the desired up
float fAngle = AcosfSafe(Dot(vCurrentUp, vDesiredUp));
// Avoid cross product breaking down
if(Abs(fAngle) > 0.001f)
{
// Calculate the rotation axis by crossing the current and desired up vectors
Vector3 vAxisToRotateOn = vCurrentUp;
vAxisToRotateOn.Cross(vDesiredUp);
// LEAN
// Subtract the current angular velocity from the rotation axis to keep the vehicle from spinning out of control
vAxisToRotateOn -= GetAngVelocity();
static dev_float sfMaxPitchRollStabiliseTorque = 20.0f;
// Stabilise the pitch and roll
ApplyParachuteStabilisation( vAxisToRotateOn, fAngle, sfMaxPitchRollStabiliseTorque );
// Apply a yaw stabilisation force so that we don't spin out of control
Vector3 vUpAxis = Vector3(0.0f, 0.0f, 1.0f);
Vector3 vUpRotation = GetAngVelocity().Dot(vUpAxis) * vUpAxis;
Vector3 torque = vUpRotation * sfYawStabilisationMult;
const Vector3 maxTorque( 149.0f, 149.0f, 149.0f );
torque.Max( -maxTorque, torque );
torque.Min( maxTorque, torque );
ApplyInternalTorque(torque * GetAngInertia().z);
}
// STEER
Vector3 vFwdFlattened = VEC3V_TO_VECTOR3(GetTransform().GetB());
vFwdFlattened.SetZ(0.0f);
vFwdFlattened.Normalize();
Vector3 vRightFlattened = VEC3V_TO_VECTOR3(GetTransform().GetA());
vRightFlattened.SetZ(0.0f);
vRightFlattened.Normalize();
// Steer on the global Z axis
Vector3 torque = vFwdFlattened * fStickX * sfSteerMult;
const Vector3 maxTorque( 149.0f * GetAngInertia().z, 149.0f * GetAngInertia().z, 149.0f * GetAngInertia().z );
torque.Max( -maxTorque, torque );
torque.Min( maxTorque, torque );
ApplyInternalTorque( torque, vRightFlattened * sfSwingOffset );
// Negate a portion of the lateral velocity so that we don't strafe due to swinging or slide due to steering
Vector3 vRight = VEC3V_TO_VECTOR3(GetTransform().GetA());
vRight.SetZ(0.0f);
vRight.Normalize();
float sfLateralVelocity = GetVelocity().Dot(vRight);
ApplyImpulse(vRight * sfNegateLateralMult * sfLateralVelocity * GetMass(), Vector3(0.0f, 0.0f, 0.0f) );
}
static const int SLIPPERY_BOX_NUM_LINK_BONES = 2;
static const eHierarchyId SLIPPERY_BOX_LINK_BONES[ SLIPPERY_BOX_NUM_LINK_BONES ] = { VEH_MISC_B, VEH_MISC_C };
static dev_float SLIPPERY_BOX_MIN_STIFFNESS = 0.5f;
void CAutomobile::SlipperyBoxInitialise()
{
GetVehicleFragInst()->ForceArticulatedColliderMode();
fragCacheEntry* cacheEntry = GetVehicleFragInst()->GetCacheEntry();
if( cacheEntry )
{
fragHierarchyInst* pHierInst = cacheEntry->GetHierInst();
if( pHierInst &&
pHierInst->body )
{
phArticulatedCollider* pArticulatedCollider = pHierInst->articulatedCollider;
if( pArticulatedCollider &&
GetSkeleton() )
{
for( int i = 0; i < SLIPPERY_BOX_NUM_LINK_BONES; i++ )
{
s8 boneIndex = (s8)GetBoneIndex( SLIPPERY_BOX_LINK_BONES[ i ] );
s8 fragChild = (s8)GetVehicleFragInst()->GetComponentFromBoneIndex( boneIndex );
int linkIndex = pArticulatedCollider->GetLinkFromComponent(fragChild);
const crBoneData* pBoneData = GetSkeletonData().GetBoneData( boneIndex );
if(linkIndex > 0)
{
if( pBoneData &&
pBoneData->GetDofs() & crBoneData::HAS_TRANSLATE_LIMITS )
{
const crJointData* pJointData = GetDrawable()->GetJointData();
Vec3V dofMin, dofMax;
pJointData->GetTranslationLimits(*pBoneData, dofMin, dofMax);
if( dofMin.GetXf() != 0.0f || dofMax.GetXf() != 0.0f )
{
m_fSlipperyBoxLimitX = Max( Abs( dofMax.GetXf() ), Abs( dofMin.GetXf() ) );
}
if( dofMin.GetYf() != 0.0f || dofMax.GetYf() != 0.0f )
{
m_fSlipperyBoxLimitY = Max( Abs( dofMax.GetYf() ), Abs( dofMin.GetYf() ) );
}
}
}
}
}
}
}
}
void CAutomobile::SlipperyBoxUpdateLimits( float limitModifier )
{
GetVehicleFragInst()->ForceArticulatedColliderMode();
fragCacheEntry* cacheEntry = GetVehicleFragInst()->GetCacheEntry();
if( cacheEntry &&
GetSkeleton() )
{
fragHierarchyInst* pHierInst = cacheEntry->GetHierInst();
if( pHierInst &&
pHierInst->body )
{
phArticulatedCollider* pArticulatedCollider = pHierInst->articulatedCollider;
if( pArticulatedCollider )
{
for( int i = 0; i < SLIPPERY_BOX_NUM_LINK_BONES; i++ )
{
s8 boneIndex = (s8)GetBoneIndex( SLIPPERY_BOX_LINK_BONES[ i ] );
s8 fragChild = (s8)GetVehicleFragInst()->GetComponentFromBoneIndex( boneIndex );
s32 linkIndex = pArticulatedCollider->GetLinkFromComponent( fragChild );
const crBoneData* pBoneData = GetSkeletonData().GetBoneData( boneIndex );
const crJointData* pJointData = GetDrawable()->GetJointData();
if( linkIndex > 0 &&
pBoneData &&
pBoneData->GetDofs() & crBoneData::HAS_TRANSLATE_LIMITS )
{
phJoint* pJoint = &pHierInst->body->GetJoint(linkIndex - 1);
Vec3V dofMin, dofMax;
pJointData->GetTranslationLimits(*pBoneData, dofMin, dofMax);
if( dofMin.GetXf() != 0.0f || dofMax.GetXf() != 0.0f )
{
pJoint->SetAngleLimits( limitModifier * -m_fSlipperyBoxLimitX, limitModifier * m_fSlipperyBoxLimitX );
}
else if( dofMin.GetYf() != 0.0f || dofMax.GetYf() != 0.0f )
{
pJoint->SetAngleLimits( limitModifier * -m_fSlipperyBoxLimitY, limitModifier * m_fSlipperyBoxLimitY );
}
pJoint->SetStiffness( SLIPPERY_BOX_MIN_STIFFNESS + ( ( 1.0f - limitModifier ) * ( 1.0f - SLIPPERY_BOX_MIN_STIFFNESS ) ) );
}
}
}
}
}
}
void CAutomobile::SlipperyBoxBreakOff()
{
GetVehicleFragInst()->ForceArticulatedColliderMode();
fragCacheEntry* cacheEntry = GetVehicleFragInst()->GetCacheEntry();
if( cacheEntry &&
GetSkeleton() )
{
fragHierarchyInst* pHierInst = cacheEntry->GetHierInst();
if( pHierInst &&
pHierInst->body )
{
for( int i = SLIPPERY_BOX_NUM_LINK_BONES - 1; i >= 0; i-- )
{
s8 boneIndex = (s8)GetBoneIndex( SLIPPERY_BOX_LINK_BONES[ i ] );
s8 fragChild = (s8)GetVehicleFragInst()->GetComponentFromBoneIndex( boneIndex );
if( fragChild > -1 )
{
GetFragInst()->BreakOffAbove( fragChild );
return;
}
}
}
}
}
void CAutomobile::ApplyDifferentials()
{
CCarHandlingData* carHandling = pHandling->GetCarHandlingData();
if( !carHandling )
{
return;
}
u32 hasFrontDiff = ( carHandling->aFlags & CF_DIFF_FRONT );
u32 hasLimitedFrontDiff = ( carHandling->aFlags & CF_DIFF_LIMITED_FRONT );
u32 hasRearDiff = ( carHandling->aFlags & CF_DIFF_REAR );
u32 hasLimitedRearDiff = ( carHandling->aFlags & CF_DIFF_LIMITED_REAR );
u32 hasCentreDiff = ( carHandling->aFlags & CF_DIFF_CENTRE );
u32 hasViscousCoupling = hasCentreDiff + ( carHandling->aFlags & CF_DIFF_LIMITED_CENTRE );
if( !hasCentreDiff && !hasFrontDiff && !hasRearDiff )
{
return;
}
float frontRearDifferential = 1.0f;
if( hasCentreDiff )
{
int numFrontWheels = 0;
int numRearWheels = 0;
float averageFrontWheelSpeed = 0.0f;
float averageRearWheelSpeed = 0.0f;
for( int i = 0; i < m_nNumWheels; i++ )
{
CWheel* wheel = GetWheel( i );
if( wheel )
{
if( wheel->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
averageRearWheelSpeed += wheel->GetRotSpeed();
numRearWheels++;
}
else
{
averageFrontWheelSpeed += wheel->GetRotSpeed();
numFrontWheels++;
}
}
}
averageFrontWheelSpeed = Abs( numFrontWheels > 0 ? averageFrontWheelSpeed / (float)numFrontWheels : 0.0f );
averageRearWheelSpeed = Abs( numFrontWheels > 0 ? averageRearWheelSpeed / (float)numRearWheels : 0.0f );
if( averageFrontWheelSpeed != averageRearWheelSpeed )
{
frontRearDifferential = averageFrontWheelSpeed / ( averageRearWheelSpeed + averageFrontWheelSpeed );
// with a viscous coupling we'll send more torque to the wheels that are moving slower
if( hasViscousCoupling &&
!m_nAutomobileFlags.bInBurnout )
{
frontRearDifferential = 1.0f - frontRearDifferential;
}
frontRearDifferential *= 2.0f;
}
if( carHandling->m_fMaxDriveBiasTransfer != -1.0f )
{
float frontBias = pHandling->m_fDriveBiasFront * 2.0f;
float biasTransfer = carHandling->m_fMaxDriveBiasTransfer * 2.0f;
if( frontBias > biasTransfer )
{
frontRearDifferential = Clamp( frontRearDifferential, frontBias - biasTransfer, frontBias );
}
else
{
frontRearDifferential = Clamp( frontRearDifferential, frontBias, frontBias + biasTransfer );
}
}
}
float perWheelDiffFactor = 1.0f / (float)m_nNumWheels;
for( int i = 0; i < m_nNumWheels; i++ )
{
CWheel* wheel = GetWheel( i );
float diffFactor = perWheelDiffFactor;
if( wheel )
{
bool isRearWheel = wheel->GetConfigFlags().IsFlagSet( WCF_REARWHEEL );
if( isRearWheel )
{
diffFactor = perWheelDiffFactor * ( 2.0f - frontRearDifferential );
}
else
{
diffFactor = perWheelDiffFactor * frontRearDifferential;
}
if( ( isRearWheel &&
hasRearDiff ) ||
( !isRearWheel &&
hasFrontDiff ) )
{
CWheel* oppositeWheel = GetWheel( wheel->GetOppositeWheelIndex() );
float wheelSpeed = Abs( wheel->GetRotSpeed() );
float oppositeWheelSpeed = Abs( oppositeWheel ? oppositeWheel->GetRotSpeed() : 0.0f );
if( wheelSpeed != oppositeWheelSpeed )
{
float diffScale = ( wheelSpeed / ( wheelSpeed + oppositeWheelSpeed ) );
static dev_float sfLimitedDiffStartRatio = 0.12f;
if( Abs( diffScale - 0.5f ) > sfLimitedDiffStartRatio &&
( ( isRearWheel &&
hasLimitedRearDiff )||
( !isRearWheel &&
hasLimitedFrontDiff ) ) )
{
float limitedDiffScale = 1.0f - diffScale;
static dev_float sfMaxDiff = 0.25f;
float diffDifferences = limitedDiffScale - diffScale;
diffScale += diffDifferences * Min( 1.0f, Abs( diffScale - 0.5f ) / sfMaxDiff );
}
diffScale *= 2.0f;
diffFactor *= diffScale;
}
}
diffFactor *= m_nNumWheels;
wheel->ScaleDriveForce( diffFactor );
}
}
int j = 0;
for(int i = 0; i < m_nNumWheels && j < 4; i++ )
{
if( m_ppWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_LEFTWHEEL ) )
{
if( m_ppWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
PF_SET( DriveForceLR, m_ppWheels[ i ]->GetDriveForce() );
j++;
}
else
{
PF_SET( DriveForceLF, m_ppWheels[ i ]->GetDriveForce() );
j++;
}
}
else
{
if( m_ppWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
PF_SET( DriveForceRR, m_ppWheels[ i ]->GetDriveForce() );
j++;
}
else
{
PF_SET( DriveForceRF, m_ppWheels[ i ]->GetDriveForce() );
j++;
}
}
}
}
//
//
//
CQuadBike::CQuadBike(const eEntityOwnedBy ownedBy, u32 popType) : CAutomobile(ownedBy, popType, VEHICLE_TYPE_QUADBIKE)
{
}
//
//
//
CQuadBike::~CQuadBike()
{
}
//
//
// physics
ePhysicsResult CQuadBike::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
if( m_nNumWheels == 3 &&
GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE && (m_nVehicleFlags.bAnimateWheels==0))
{
m_nVehicleFlags.bDisableSuperDummy = true;
float fSteeringWheelMult = GetVehicleModelInfo()->GetSteeringWheelMult( GetDriver() );
SetComponentRotation( QUADBIKE_FORKS_U, ROT_AXIS_LOCAL_Z, GetSteerAngle() * fSteeringWheelMult, true );
}
//Use the fallen collider flag on quads all the time as they are open wheeled and not constrained
if(!GetDriver())
{
for(int i=0; i<GetNumWheels(); i++)
{
GetWheel(i)->GetConfigFlags().SetFlag(WCF_BIKE_FALLEN_COLLIDER);
}
// Allow peds to flip overturned bikes by walking into the side
if(const CCollisionRecord* pedCollisionRecord = GetFrameCollisionHistory()->GetMostSignificantCollisionRecordOfType(ENTITY_TYPE_PED))
{
const Mat34V quadBikeMatrix = GetMatrix();
const Vec3V quadBikeUp = quadBikeMatrix.GetCol2();
const Vec3V quadBikeForward = quadBikeMatrix.GetCol1();
const Vec3V collisionNormal = RCC_VEC3V(pedCollisionRecord->m_MyCollisionNormal);
// Only apply torques if the impact isn't with the front or bottom of the bike
const ScalarV frontImpactAngleCosine = ScalarVFromF32(0.766f); // 40 degrees
const ScalarV bottomImpactAngleCosine = ScalarV(V_HALF); // 60 degrees
if(And(IsLessThan(Abs(Dot(quadBikeForward,collisionNormal)),frontImpactAngleCosine),IsLessThan(Dot(quadBikeUp,collisionNormal),bottomImpactAngleCosine)).Getb())
{
// Only bother applying torque if we're offset to begin with
const float minRollForSelfRighting = 20.0f*DtoR;
float currentRoll = GetTransform().GetRoll();
if(abs(currentRoll) > minRollForSelfRighting)
{
// The roll angle favors whichever direction gets us to the z-axis quicker but we always want to rotate
// so that the top of the bike goes towards the ped.
Vec3V torque = Scale(quadBikeForward,ScalarVFromF32(currentRoll));
if(Cross(collisionNormal,torque).GetZf() < 0)
{
// Negate the rotation axis and increase how much roll we need to cover since we're going the long way.
torque = Scale(Negate(quadBikeForward), ScalarVFromF32(currentRoll > 0 ? 2.0f*PI - currentRoll : -2.0f*PI - currentRoll));
}
// Apply a torque around the forward axis, relative to the roll error and angular inertia around the forward axis.
ApplyInternalTorque(RCC_VECTOR3(torque) * ms_fQuadBikeSelfRightingTorqueScale * GetAngInertia().y);
}
}
}
}
if(GetDriver())// Do some autoleveling if in the air.
{
if( IsInAir() && pHandling->GetBikeHandlingData() && !GetFrameCollisionHistory()->GetMostSignificantCollisionRecord() && GetNumWheels() != 3 )
{
bool bDoAutoLeveling = true;
if(GetDriver()->IsAPlayerPed())
{
Vec3V vPosition = GetTransform().GetPosition();
float fMapMaxZ = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(vPosition.GetXf(), vPosition.GetYf());
//Let the player perform rolls when really high in the air
Vec3V vBBMinZGlobal = GetTransform().Transform(Vec3V(0.0f, 0.0f, GetBoundingBoxMin().z));
if(vBBMinZGlobal.GetZf() > fMapMaxZ)
{
bDoAutoLeveling = false;
}
}
if(bDoAutoLeveling)
{
AutoLevelAndHeading( pHandling->GetBikeHandlingData()->m_fInAirSteerMult );
}
}
else if(GetNumContactWheels() != GetNumWheels() && GetCollider()) // Roll over protection
{
static dev_float sfRollOverProtectionVelocity = 1.5f;
Vec3V vCurrentAngVelocity = GetCollider()->GetAngVelocity();
vCurrentAngVelocity = UnTransform3x3Full(GetCollider()->GetMatrix(), vCurrentAngVelocity);
if(rage::Abs(vCurrentAngVelocity.GetYf()) > sfRollOverProtectionVelocity)
{
float fAngVelocityY = rage::Clamp(vCurrentAngVelocity.GetYf(), -3.0f, 3.0f);
const Mat34V quadBikeMatrix = GetMatrix();
const Vec3V quadBikeForward = quadBikeMatrix.GetCol1();
ApplyInternalTorque(RCC_VECTOR3(quadBikeForward) * fAngVelocityY * ms_fQuadBikeAntiRollOverTorqueScale * GetAngInertia().y);
}
}
}
return CAutomobile::ProcessPhysics(fTimeStep, bCanPostpone, nTimeSlice);
}
void CQuadBike::ProcessPostPhysics()
{
CAutomobile::ProcessPostPhysics();
m_fVerticalVelocityToKnockOffThisBike = CVehicle::ms_fVerticalVelocityToKnockOffVehicle;
}
dev_float sfTrikeLeanMinSpeed = 1.0f;
dev_float sfTrikeLeanBlendSpeed = 10.0f;
dev_float sfTrikeLeanForceSpeedMult = 0.2f;
dev_float sfTrikeWheelieForceCgMult = -0.5f;
dev_float sfTrikeWheeliePlusTrigger = 0.5f;
dev_float sfTrikeWheeliePlusCap = 2.0f;
void CQuadBike::ProcessDriverInputsForStability( float fTimeStep )
{
CAutomobile::ProcessDriverInputsForStability( fTimeStep );
if( IsTrike() ||
( MI_QUADBIKE_BLAZER4.IsValid() && GetModelIndex() == MI_QUADBIKE_BLAZER4 ) )
{
if(!GetDriver() || !GetDriver()->IsLocalPlayer())
{
return;
}
bool bMoving = false;
float fSpeedBlend = 0.0f;
float fFwdSpeed = DotProduct( VEC3V_TO_VECTOR3(GetTransform().GetB() ), GetVelocity() );
bool bAllWheelsOffGround = (!GetWheel(0)->GetIsTouching() && !GetWheel(1)->GetIsTouching() && !GetWheel(1)->GetIsTouching());
if(fFwdSpeed > sfTrikeLeanMinSpeed)
{
bMoving = true;
if(fFwdSpeed > sfTrikeLeanBlendSpeed)
fSpeedBlend = 1.0f;
else
fSpeedBlend = fFwdSpeed / sfTrikeLeanBlendSpeed;
}
// Scale the wheelie ability by the players stats.
float fWheelieAbilityMult = 1.0f;
float fInAirAbilityMult = 1.0f;
StatId stat = STAT_WHEELIE_ABILITY.GetStatId();
float fWheelieStatValue = rage::Clamp(static_cast<float>(StatsInterface::GetIntStat(stat)) / 100.0f, 0.0f, 1.0f);
float fMinWheelieAbility = CPlayerInfo::GetPlayerStatInfoForPed(*GetDriver()).m_MinWheelieAbility;
float fMaxWheelieAbility = CPlayerInfo::GetPlayerStatInfoForPed(*GetDriver()).m_MaxWheelieAbility;
fInAirAbilityMult = fWheelieAbilityMult = ((1.0f - fWheelieStatValue) * fMinWheelieAbility + fWheelieStatValue * fMaxWheelieAbility)/100.0f;
//We don't want the lean back and forward forces set by player stats to be as different.
TUNE_GROUP_FLOAT(BICYCLE_TUNE, REDUCE_WHEELIE_ABILITY_DELTA_MULT, 0.5f, 0.0f, 1.0f, 0.01f);
fWheelieAbilityMult = (fMaxWheelieAbility/100.0f) - (((fMaxWheelieAbility/100.0f) - fWheelieAbilityMult) * REDUCE_WHEELIE_ABILITY_DELTA_MULT);
fWheelieAbilityMult = rage::Clamp(fWheelieAbilityMult, 0.1f, 2.0f);
CControl *pPlayerControl = GetDriver()->GetControlFromPlayer();
pPlayerControl->SetVehicleSteeringExclusive();
float fFwdInput = pPlayerControl->GetVehicleSteeringUpDown().GetNorm();
// apply torque from rider leaning fwd/back
if( bMoving && !bAllWheelsOffGround )
{
if(fFwdInput < 0.0f)
{
if( GetBrake() > 0.0f ) // Don't let people lean forward unless they are braking or their wheels are off the ground.
{
float fLeanForce = fFwdInput;
float fRotSpeed = DotProduct(GetAngVelocity(), VEC3V_TO_VECTOR3(GetTransform().GetA())) * sfTrikeLeanForceSpeedMult;
if(fRotSpeed < 0.0f)
{
fLeanForce = rage::Min(0.0f, fLeanForce - fRotSpeed);
}
float fSpeedBlendMult = fSpeedBlend;
fLeanForce *= GetAngInertia().x * pHandling->GetBikeHandlingData()->m_fLeanFwdForceMult * fSpeedBlendMult * fWheelieAbilityMult;
ApplyInternalTorque(fLeanForce * VEC3V_TO_VECTOR3(GetTransform().GetA()));
if( GetWheelFromId(VEH_WHEEL_LR)->GetIsTouching() || GetWheelFromId(VEH_WHEEL_RR)->GetIsTouching() )
{
if( GetWheelFromId(VEH_WHEEL_LR)->GetTyreHealth() > TYRE_HEALTH_FLAT )
{
ApplyInternalForceCg(fLeanForce*sfTrikeWheelieForceCgMult*ZAXIS*ms_fBikeGravityMult);
}
}
}
}
else
{
float fLeanForce = fFwdInput;
float fRotSpeed = DotProduct(GetAngVelocity(), VEC3V_TO_VECTOR3(GetTransform().GetA())) * sfTrikeLeanForceSpeedMult;
if(fRotSpeed > 0.0f)
{
fLeanForce = rage::Max(0.0f, fLeanForce - fRotSpeed);
}
// Reduce the lean force if the rear wheel is significantly underwater. Buoyancy and resistance forces make doing wheelies really easy
// non-shallow water.
if( m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate && m_nFlags.bPossiblyTouchesWater )
{
float fWaterZ = 0.0f;
Vec3V rearWheelPosition = Transform( GetMatrixRef(), VECTOR3_TO_VEC3V( CWheel::GetWheelOffset( GetVehicleModelInfo(), VEH_WHEEL_LR ) ) );
if( m_Buoyancy.GetWaterLevelIncludingRivers( VEC3V_TO_VECTOR3( rearWheelPosition ), &fWaterZ, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL ) )
{
static dev_float sfWaterLevelAboveWheelCenterSubmerged = 0.1f;
static dev_float sfWheelSubmergedLeaningMult = 0.1f;
if(rearWheelPosition.GetZf() + sfWaterLevelAboveWheelCenterSubmerged < fWaterZ)
{
fLeanForce *= sfWheelSubmergedLeaningMult;
}
}
}
float fForceMult = rage::Abs(GetThrottle());
if(pHandling->hFlags & HF_LOW_SPEED_WHEELIES)
{
fForceMult = 1.0f;
static dev_float sfWheelieSpeedBlendMinSpeed = 25.0f;
if(fFwdSpeed > sfWheelieSpeedBlendMinSpeed)
{
fLeanForce = 0.0f;
}
}
fLeanForce *= GetAngInertia().x*pHandling->GetBikeHandlingData()->m_fLeanBakForceMult * fWheelieAbilityMult * fForceMult;
ApplyInternalTorque( fLeanForce* VEC3V_TO_VECTOR3(GetTransform().GetA())*ms_fBikeGravityMult);
if( GetWheelFromId(VEH_WHEEL_LR)->GetIsTouching() || GetWheelFromId(VEH_WHEEL_RR)->GetIsTouching() )
{
ApplyInternalForceCg(-fLeanForce*sfTrikeWheelieForceCgMult*ZAXIS*ms_fBikeGravityMult);
}
}
}
Assert(GetStatus()==STATUS_PLAYER);
CBikeHandlingData* pBikeHandling = pHandling->GetBikeHandlingData();
// back wheels on ground, front wheel not = Wheelie
if( bMoving &&
!GetWheelFromId(VEH_WHEEL_LF)->GetIsTouching() &&
GetWheelFromId(VEH_WHEEL_LR)->GetIsTouching() &&
GetWheelFromId(VEH_WHEEL_RR)->GetIsTouching() )
{
Vector3 vBikeForwardInGroundLocal = VEC3V_TO_VECTOR3( GetTransform().GetB() );
Vector3 vGroundNormalAtRearWheel = ( GetWheelFromId(VEH_WHEEL_LR)->GetHitNormal() + GetWheelFromId(VEH_WHEEL_RR)->GetHitNormal() ) * 0.5f;
float fBikeForwardDotGroundNormal = vBikeForwardInGroundLocal.Dot( vGroundNormalAtRearWheel );
float fWheelieAngle = rage::Asinf( fBikeForwardDotGroundNormal ) - pBikeHandling->m_fWheelieBalancePoint;
float fWheelieStabiliseForce = 0.0f;
if(fWheelieAngle > sfTrikeWheeliePlusTrigger)
{
fWheelieStabiliseForce = ( ( fWheelieAngle - sfTrikeWheeliePlusTrigger ) / ( sfTrikeWheeliePlusCap - sfTrikeWheeliePlusTrigger ) );
fWheelieStabiliseForce = Clamp(fWheelieStabiliseForce, 0.0f, 1.0f);
fWheelieStabiliseForce *= -1.0f;
}
// blend stabilization force out smoothly at low speeds
fWheelieStabiliseForce *= pBikeHandling->m_fRearBalanceMult * GetAngInertia().x * fSpeedBlend;
ApplyInternalTorque(fWheelieStabiliseForce* VEC3V_TO_VECTOR3(GetTransform().GetA())*ms_fBikeGravityMult);
}
}
}
ePrerenderStatus CQuadBike::PreRender( const bool bIsVisibleInMainViewport )
{
if(GetModelIndex() == MI_TRIKE_CHIMERA)
{
ePrerenderStatus status = CVehicle::PreRender(bIsVisibleInMainViewport);
crSkeleton* pSkel = GetSkeleton();
const bool bIsDummy = IsDummy();
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
if((!bIsDummy || !m_nVehicleFlags.bLodFarFromPopCenter))
{
for(int i=0; i<GetNumWheels(); i++)
{
PrefetchObject(m_ppWheels[i]);
}
for(int i=0; i<GetNumWheels(); i++)
{
if( m_ppWheels[ i ]->GetConfigFlags().IsFlagSet( WCF_CENTRE_WHEEL ) && GetModelIndex() != MI_TRIKE_RROCKET)
{
m_ppWheels[i]->ProcessWheelMatrixForTrike();
}
else
{
m_ppWheels[i]->ProcessWheelMatrixForAutomobile( *pSkel );
}
}
}
}
// Front wheel first
//
CWheel* pWheelF = GetWheelFromId( BIKE_WHEEL_F );
int nWheelIndexF = GetBoneIndex( BIKE_WHEEL_F );
const crBoneData* pBoneDataF = pSkel->GetSkeletonData().GetBoneData( nWheelIndexF );
const crBoneData* pParentBoneDataF = pBoneDataF->GetParent();
float fZDiff = pWheelF->GetWheelCompressionFromStaticIncBurstAndSink();
const crBoneData* pLowerForkBoneData = NULL;
if( GetBoneIndex( QUADBIKE_FORKS_L ) != -1 )
{
pLowerForkBoneData = pSkel->GetSkeletonData().GetBoneData(GetBoneIndex(QUADBIKE_FORKS_L));
}
if( pLowerForkBoneData )
{
Matrix34& lowerForkBoneMatrix = RC_MATRIX34( pSkel->GetLocalMtx( pLowerForkBoneData->GetIndex() ) );
lowerForkBoneMatrix.FromQuaternion( RCC_QUATERNION( pLowerForkBoneData->GetDefaultRotation() ) );
lowerForkBoneMatrix.d = RCC_VECTOR3( pLowerForkBoneData->GetDefaultTranslation() );
}
const crBoneData* pForkBoneData = NULL;
if( GetBoneIndex( QUADBIKE_FORKS_U ) != -1 )
{
pForkBoneData = pSkel->GetSkeletonData().GetBoneData(GetBoneIndex(QUADBIKE_FORKS_U));
}
if( pForkBoneData && pForkBoneData != pParentBoneDataF )
{
Matrix34 forkBoneMatrix( RC_MATRIX34( pSkel->GetLocalMtx( pForkBoneData->GetIndex() ) ) );
forkBoneMatrix.FromQuaternion( RCC_QUATERNION( pForkBoneData->GetDefaultRotation() ) );
if( forkBoneMatrix.c.z > 0.0f )
{
fZDiff /= forkBoneMatrix.c.z;
}
}
Matrix34& parentBoneMatrixF = RC_MATRIX34( pSkel->GetLocalMtx(pParentBoneDataF->GetIndex() ) );
parentBoneMatrixF.d = RCC_VECTOR3( pParentBoneDataF->GetDefaultTranslation() );
parentBoneMatrixF.d += parentBoneMatrixF.c * fZDiff;
return status;
}
else
{
return CAutomobile::PreRender( bIsVisibleInMainViewport );
}
}
#if __BANK
void CQuadBike::InitWidgets(bkBank& bank)
{
bank.PushGroup("QuadBike");
bank.AddSlider("Self Righting Torque Scale",&ms_fQuadBikeSelfRightingTorqueScale,0.0f,100.0f,0.1f);
bank.AddSlider("Anti Rollover Torque Scale",&ms_fQuadBikeAntiRollOverTorqueScale,-100.0f,100.0f,0.1f);
bank.PopGroup();
}
#endif // __BANK