summaryrefslogtreecommitdiff
path: root/src/vehicles
diff options
context:
space:
mode:
Diffstat (limited to 'src/vehicles')
-rw-r--r--src/vehicles/Automobile.cpp5884
-rw-r--r--src/vehicles/Automobile.h175
-rw-r--r--src/vehicles/Bike.cpp2961
-rw-r--r--src/vehicles/Bike.h172
-rw-r--r--src/vehicles/Boat.cpp1488
-rw-r--r--src/vehicles/Boat.h85
-rw-r--r--src/vehicles/CarGen.cpp305
-rw-r--r--src/vehicles/CarGen.h51
-rw-r--r--src/vehicles/Cranes.cpp659
-rw-r--r--src/vehicles/Cranes.h96
-rw-r--r--src/vehicles/DamageManager.cpp233
-rw-r--r--src/vehicles/DamageManager.h115
-rw-r--r--src/vehicles/Door.cpp170
-rw-r--r--src/vehicles/Door.h69
-rw-r--r--src/vehicles/Floater.cpp324
-rw-r--r--src/vehicles/Floater.h47
-rw-r--r--src/vehicles/HandlingMgr.cpp434
-rw-r--r--src/vehicles/HandlingMgr.h279
-rw-r--r--src/vehicles/Heli.cpp1049
-rw-r--r--src/vehicles/Heli.h101
-rw-r--r--src/vehicles/Plane.cpp1069
-rw-r--r--src/vehicles/Plane.h81
-rw-r--r--src/vehicles/Train.cpp766
-rw-r--r--src/vehicles/Train.h84
-rw-r--r--src/vehicles/Transmission.cpp130
-rw-r--r--src/vehicles/Transmission.h28
-rw-r--r--src/vehicles/Vehicle.cpp2517
-rw-r--r--src/vehicles/Vehicle.h451
28 files changed, 19823 insertions, 0 deletions
diff --git a/src/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp
new file mode 100644
index 0000000..06dc1b5
--- /dev/null
+++ b/src/vehicles/Automobile.cpp
@@ -0,0 +1,5884 @@
+#include "common.h"
+#include "main.h"
+
+#include "General.h"
+#include "RwHelper.h"
+#include "Pad.h"
+#include "ModelIndices.h"
+#include "VisibilityPlugins.h"
+#include "DMAudio.h"
+#include "Clock.h"
+#include "Timecycle.h"
+#include "ZoneCull.h"
+#include "Camera.h"
+#include "Darkel.h"
+#include "Rubbish.h"
+#include "Fire.h"
+#include "Explosion.h"
+#include "Particle.h"
+#include "ParticleObject.h"
+#include "Glass.h"
+#include "Antennas.h"
+#include "Skidmarks.h"
+#include "WindModifiers.h"
+#include "Shadows.h"
+#include "PointLights.h"
+#include "Coronas.h"
+#include "SpecialFX.h"
+#include "WaterCannon.h"
+#include "WaterLevel.h"
+#include "Floater.h"
+#include "World.h"
+#include "SurfaceTable.h"
+#include "Weather.h"
+#include "HandlingMgr.h"
+#include "Record.h"
+#include "Remote.h"
+#include "Population.h"
+#include "CarCtrl.h"
+#include "CarAI.h"
+#include "Stats.h"
+#include "Garages.h"
+#include "PathFind.h"
+#include "Replay.h"
+#include "AnimManager.h"
+#include "RpAnimBlend.h"
+#include "AnimBlendAssociation.h"
+#include "Ped.h"
+#include "PlayerPed.h"
+#include "Object.h"
+#include "Automobile.h"
+#include "Bike.h"
+#include "Wanted.h"
+
+bool bAllCarCheat;
+
+RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data);
+
+bool CAutomobile::m_sAllTaxiLights;
+
+const uint32 CAutomobile::nSaveStructSize =
+#ifdef COMPATIBLE_SAVES
+ 1500;
+#else
+ sizeof(CAutomobile);
+#endif
+
+CAutomobile::CAutomobile(int32 id, uint8 CreatedBy)
+ : CVehicle(CreatedBy)
+{
+ int i;
+
+ m_vehType = VEHICLE_TYPE_CAR;
+
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
+ m_fFireBlowUpTimer = 0.0f;
+ m_doingBurnout = 0;
+ bTaxiLight = m_sAllTaxiLights;
+ bFixedColour = false;
+ bBigWheels = false;
+ bWaterTight = false;
+
+ SetModelIndex(id);
+
+ // Already done in CVehicle...
+ switch(GetModelIndex()){
+ case MI_HUNTER:
+ case MI_ANGEL:
+ case MI_FREEWAY:
+ m_nRadioStation = V_ROCK;
+ break;
+ case MI_RCBARON:
+ case MI_RCBANDIT:
+ case MI_RCRAIDER:
+ case MI_RCGOBLIN:
+ case MI_TOPFUN:
+ case MI_CADDY:
+ case MI_BAGGAGE:
+ m_nRadioStation = RADIO_OFF;
+ break;
+ }
+
+ pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
+ pFlyingHandling = mod_HandlingManager.GetFlyingPointer((tVehicleType)mi->m_handlingId);
+
+ m_auto_unused1 = 20.0f;
+ m_auto_unused2 = 0;
+
+ mi->ChooseVehicleColour(m_currentColour1, m_currentColour2);
+
+ bIsVan = !!(pHandling->Flags & HANDLING_IS_VAN);
+ bIsBig = !!(pHandling->Flags & HANDLING_IS_BIG);
+ bIsBus = !!(pHandling->Flags & HANDLING_IS_BUS);
+ bLowVehicle = !!(pHandling->Flags & HANDLING_IS_LOW);
+
+ // Doors
+ if(bIsBus){
+ Doors[DOOR_FRONT_LEFT].Init(-HALFPI, 0.0f, 0, 2);
+ Doors[DOOR_FRONT_RIGHT].Init(0.0f, HALFPI, 1, 2);
+ }else{
+ Doors[DOOR_FRONT_LEFT].Init(-PI*0.4f, 0.0f, 0, 2);
+ Doors[DOOR_FRONT_RIGHT].Init(0.0f, PI*0.4f, 1, 2);
+ }
+ if(bIsVan){
+ Doors[DOOR_REAR_LEFT].Init(-HALFPI, 0.0f, 1, 2);
+ Doors[DOOR_REAR_RIGHT].Init(0.0f, HALFPI, 0, 2);
+ }else{
+ Doors[DOOR_REAR_LEFT].Init(-PI*0.4f, 0.0f, 0, 2);
+ Doors[DOOR_REAR_RIGHT].Init(0.0f, PI*0.4f, 1, 2);
+ }
+ if(pHandling->Flags & HANDLING_REV_BONNET)
+ Doors[DOOR_BONNET].Init(-PI*0.3f, 0.0f, 1, 0);
+ else
+ Doors[DOOR_BONNET].Init(0.0f, PI*0.3f, 1, 0);
+ if(pHandling->Flags & HANDLING_HANGING_BOOT)
+ Doors[DOOR_BOOT].Init(-PI*0.4f, 0.0f, 0, 0);
+ else if(pHandling->Flags & HANDLING_TAILGATE_BOOT)
+ Doors[DOOR_BOOT].Init(0.0, HALFPI, 1, 0);
+ else
+ Doors[DOOR_BOOT].Init(-PI*0.3f, 0.0f, 1, 0);
+ if(pHandling->Flags & HANDLING_NO_DOORS){
+ Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING);
+ Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING);
+ Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING);
+ Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING);
+ }
+
+ for(i = 0; i < 6; i++)
+ m_randomValues[i] = CGeneral::GetRandomNumberInRange(-0.15f, 0.15f);
+
+ m_fMass = pHandling->fMass;
+ m_fTurnMass = pHandling->fTurnMass;
+ m_vecCentreOfMass = pHandling->CentreOfMass;
+ m_fAirResistance = pHandling->Dimension.x*pHandling->Dimension.z/m_fMass;
+ m_fElasticity = 0.05f;
+ m_fBuoyancy = pHandling->fBuoyancy;
+
+ m_fOrientation = m_fPlaneSteer = 0.0f;
+
+ m_nBusDoorTimerEnd = 0;
+ m_nBusDoorTimerStart = 0;
+
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 0.0f;
+ m_pSetOnFireEntity = nil;
+ m_fGasPedalAudio = 0.0f;
+ bNotDamagedUpsideDown = false;
+ bMoreResistantToDamage = false;
+ bTankDetonateCars = true;
+ bStuckInSand = false;
+ bHeliDestroyed = false;
+ m_fVelocityChangeForAudio = 0.0f;
+ m_hydraulicState = 0;
+
+ for(i = 0; i < 4; i++){
+ m_aGroundPhysical[i] = nil;
+ m_aGroundOffset[i] = CVector(0.0f, 0.0f, 0.0f);
+ m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i] = 1.0f;
+ m_aWheelTimer[i] = 0.0f;
+ m_aWheelRotation[i] = 0.0f;
+ m_aWheelSpeed[i] = 0.0f;
+ m_aWheelState[i] = WHEEL_STATE_NORMAL;
+ m_aWheelSkidmarkType[i] = SKIDMARK_NORMAL;
+ m_aWheelSkidmarkBloody[i] = false;
+ }
+
+ m_nWheelsOnGround = 0;
+ m_nDriveWheelsOnGround = 0;
+ m_nDriveWheelsOnGroundPrev = 0;
+ m_fHeightAboveRoad = 0.0f;
+ m_fTraction = 1.0f;
+ m_fTireTemperature = 1.0f;
+
+ CColModel *colModel = mi->GetColModel();
+ if(colModel->lines == nil){
+ colModel->lines = (CColLine*)RwMalloc(4*sizeof(CColLine));
+ colModel->numLines = 4;
+ }
+
+ SetupSuspensionLines();
+
+ SetStatus(STATUS_SIMPLE);
+ bUseCollisionRecords = true;
+
+ m_nNumPassengers = 0;
+
+ if(m_nDoorLock == CARLOCK_UNLOCKED &&
+ (id == MI_POLICE || id == MI_ENFORCER || id == MI_RHINO))
+ m_nDoorLock = CARLOCK_LOCKED_INITIALLY;
+
+ m_fCarGunLR = 0.0f;
+ m_fCarGunUD = 0.05f;
+ m_fPropellerRotation = 0.0f;
+ m_fHeliOrientation = -1.0f;
+ m_weaponDoorTimerLeft = 0.0f;
+ m_weaponDoorTimerRight = m_weaponDoorTimerLeft;
+
+ if(GetModelIndex() == MI_DODO){
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
+ CMatrix mat1;
+ mat1.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
+ CMatrix mat2(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF]));
+ mat1.GetPosition() += CVector(mat2.GetPosition().x + 0.1f, 0.0f, mat2.GetPosition().z);
+ mat1.UpdateRW();
+ }else if(GetModelIndex() == MI_HUNTER){
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0);
+ }else if(IsRealHeli()){
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0);
+ }else if(GetModelIndex() == MI_RHINO){
+ bExplosionProof = true;
+ bBulletProof = true;
+ }
+}
+
+void
+CAutomobile::SetModelIndex(uint32 id)
+{
+ CVehicle::SetModelIndex(id);
+ SetupModelNodes();
+}
+
+#define SAND_SLOWDOWN (0.01f)
+float CAR_BALANCE_MULT = 0.3f;
+CVector vecSeaSparrowGunPos(-0.5f, 2.4f, -0.785f);
+CVector vecHunterGunPos(0.0f, 4.8f, -1.3f);
+CVector vecHunterRocketPos(2.5f, 1.0f, -0.5f);
+CVector vecDAMAGE_ENGINE_POS_SMALL(-0.1f, -0.1f, 0.0f);
+CVector vecDAMAGE_ENGINE_POS_BIG(-0.5f, -0.3f, 0.0f);
+
+#pragma optimize("", off) // a workaround for another compiler bug
+
+void
+CAutomobile::ProcessControl(void)
+{
+ int i;
+ float wheelRot;
+ CColModel *colModel;
+ float brake = 0.0f;
+
+ if(bUsingSpecialColModel)
+ colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel;
+ else
+ colModel = GetColModel();
+ bool drivingInSand = false;
+ bWarnedPeds = false;
+ m_doingBurnout = 0;
+ bStuckInSand = false;
+ bRestingOnPhysical = false;
+
+ bool carHasNitro = bAllTaxisHaveNitro && GetStatus() == STATUS_PLAYER && IsTaxi();
+
+ if(CReplay::IsPlayingBack())
+ return;
+
+ // Heli wind
+ if(IsRealHeli())
+ if((GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PHYSICS) && m_aWheelSpeed[1] > 0.075f ||
+ GetStatus() == STATUS_SIMPLE)
+ CWindModifiers::RegisterOne(GetPosition(), 1);
+
+ UpdatePassengerList();
+
+ // Improve grip of vehicles in certain cases
+ bool strongGrip1 = false;
+ bool strongGrip2 = false;
+ if(FindPlayerVehicle() && this != FindPlayerVehicle() && FindPlayerPed()->m_pWanted->GetWantedLevel() > 3 &&
+ (AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE ||
+ AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_FARAWAY || AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE) &&
+ FindPlayerSpeed().Magnitude() > 0.3f){
+
+ strongGrip1 = true;
+ if(FindPlayerSpeed().Magnitude() > 0.4f &&
+ m_vecMoveSpeed.Magnitude() < 0.3f)
+ strongGrip2 = true;
+ else if((GetPosition() - FindPlayerCoors()).Magnitude() > 50.0f)
+ strongGrip2 = true;
+ }else if(GetModelIndex() == MI_RCBANDIT && GetStatus() != STATUS_PLAYER_REMOTE)
+ strongGrip1 = true;
+
+ if(bIsBus)
+ ProcessAutoBusDoors();
+
+ ProcessCarAlarm();
+
+ // Scan if this car sees the player committing any crimes
+ if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED &&
+ GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PLAYER_DISABLED){
+ switch(GetModelIndex())
+ case MI_FBIRANCH:
+ case MI_POLICE:
+ case MI_ENFORCER:
+ case MI_SECURICA:
+ case MI_RHINO:
+ case MI_BARRACKS:
+ ScanForCrimes();
+ }
+
+ // Process driver
+ if(pDriver)
+ if(IsUpsideDown() && CanPedEnterCar()){
+ if(!pDriver->IsPlayer() &&
+ !(pDriver->m_leader && pDriver->m_leader->bInVehicle) &&
+ pDriver->CharCreatedBy != MISSION_CHAR)
+ pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this);
+ }
+
+ ActivateBombWhenEntered();
+
+ // Process passengers
+ if(m_nNumPassengers != 0 && IsUpsideDown() && CanPedEnterCar()){
+ for(i = 0; i < m_nNumMaxPassengers; i++)
+ if(pPassengers[i])
+ if(!pPassengers[i]->IsPlayer() &&
+ !(pPassengers[i]->m_leader && pPassengers[i]->m_leader->bInVehicle) &&
+ pPassengers[i]->CharCreatedBy != MISSION_CHAR)
+ pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this);
+ }
+
+ CRubbish::StirUp(this);
+
+ UpdateClumpAlpha();
+
+ AutoPilot.m_bSlowedDownBecauseOfCars = false;
+ AutoPilot.m_bSlowedDownBecauseOfPeds = false;
+
+ // Set Center of Mass to make car more stable
+ if(strongGrip1 || bCheat3)
+ m_vecCentreOfMass.z = 0.3f*m_aSuspensionSpringLength[0] + -1.0f*m_fHeightAboveRoad;
+ else if(pHandling->Flags & HANDLING_NONPLAYER_STABILISER && GetStatus() == STATUS_PHYSICS)
+ m_vecCentreOfMass.z = pHandling->CentreOfMass.z - 0.2f*pHandling->Dimension.z;
+ else
+ m_vecCentreOfMass = pHandling->CentreOfMass;
+
+ // Park car
+ if(bCanPark && !bParking && VehicleCreatedBy != MISSION_VEHICLE && AutoPilot.m_nCarMission == MISSION_CRUISE &&
+ ((CTimer::GetFrameCounter() + m_randomSeed)&0xF) == 0 && !IsTaxi()){
+ CVector parkPosition = GetPosition() + 3.0f*GetRight() + 10.0f*GetForward();
+ CEntity *ent = nil;
+ CColPoint colpoint;
+ if(!CWorld::ProcessLineOfSight(GetPosition(), parkPosition, colpoint, ent, true, true, true, false, false, false) ||
+ ent == this)
+ CCarAI::GetCarToParkAtCoors(this, &parkPosition);
+ }
+
+ // Process depending on status
+
+ bool playerRemote = false;
+ switch(GetStatus()){
+ case STATUS_PLAYER_REMOTE:
+#ifdef FIX_BUGS
+ if(CPad::GetPad(0)->CarGunJustDown() && !bDisableRemoteDetonation){
+#else
+ if(CPad::GetPad(0)->WeaponJustDown() && !bDisableRemoteDetonation){
+#endif
+ BlowUpCar(FindPlayerPed());
+ CRemote::TakeRemoteControlledCarFromPlayer();
+ }
+
+ if(GetModelIndex() == MI_RCBANDIT && !bDisableRemoteDetonationOnContact){
+ //CVector pos = GetPosition();
+ // FindPlayerCoors unused
+ if(RcbanditCheckHitWheels() || bIsInWater){
+ CRemote::TakeRemoteControlledCarFromPlayer();
+ BlowUpCar(FindPlayerPed());
+ }
+ }
+
+ if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle == this)
+ playerRemote = true;
+ // fall through
+ case STATUS_PLAYER:
+ if(playerRemote ||
+ pDriver && pDriver->GetPedState() != PED_EXIT_CAR && pDriver->GetPedState() != PED_DRAG_FROM_CAR && pDriver->GetPedState() != PED_ARRESTED){
+ // process control input if controlled by player
+ if(playerRemote || pDriver->m_nPedType == PEDTYPE_PLAYER1)
+ ProcessControlInputs(0);
+
+ PruneReferences();
+
+ if(GetStatus() == STATUS_PLAYER && !CRecordDataForChase::IsRecording())
+ DoDriveByShootings();
+
+ // Tweak center on mass when driving on two wheels
+ int twoWheelTime = CWorld::Players[CWorld::PlayerInFocus].m_nTimeNotFullyOnGround;
+ if(twoWheelTime > 500 && !IsRealHeli() && !IsRealPlane()){
+ float tweak = Min(twoWheelTime-500, 1000)/500.0f;
+ if(GetUp().z > 0.0f){
+ // positive when on left wheels, negative on right wheels
+ if(GetRight().z <= 0.0f)
+ tweak *= -1.0f;
+ m_vecCentreOfMass.z = pHandling->CentreOfMass.z +
+ CPad::GetPad(0)->GetSteeringLeftRight()/128.0f *
+ CAR_BALANCE_MULT * tweak * colModel->boundingBox.max.z;
+ }
+ }else
+ m_vecCentreOfMass.z = pHandling->CentreOfMass.z;
+
+ if(bHoverCheat)
+ DoHoverSuspensionRatios();
+
+ if(m_aSuspensionSpringRatio[0] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[0].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[1] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[1].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[2] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[2].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[3] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[3].surfaceB) == ADHESIVE_SAND){
+ if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_RHINO){
+ float slowdown;
+ CVector parallelSpeed = m_vecMoveSpeed - DotProduct(m_vecMoveSpeed, GetUp())*GetUp();
+ float fSpeed = parallelSpeed.MagnitudeSqr();
+ if(fSpeed > SQR(0.3f)){
+ fSpeed = Sqrt(fSpeed);
+ parallelSpeed *= 0.3f / fSpeed;
+ slowdown = SAND_SLOWDOWN * Max(1.0f - 2.0f*fSpeed, 0.2f);
+ }else{
+ bStuckInSand = true;
+ slowdown = SAND_SLOWDOWN;
+ }
+ if(pHandling->Flags & HANDLING_GOOD_INSAND)
+ slowdown *= 0.5f;
+ if(CWeather::WetRoads > 0.2f)
+ slowdown *= (1.2f - CWeather::WetRoads);
+ ApplyMoveForce(parallelSpeed * -CTimer::GetTimeStep()*slowdown*m_fMass);
+ drivingInSand = true;
+ }
+ }
+ }else if(pDriver && pDriver->IsPlayer() &&
+ (pDriver->GetPedState() == PED_ARRESTED ||
+ pDriver->GetPedState() == PED_DRAG_FROM_CAR ||
+ (pDriver->GetPedState() == PED_EXIT_CAR || pDriver->m_objective == OBJECTIVE_LEAVE_CAR) && !CanPedJumpOutCar())){
+ bIsHandbrakeOn = true;
+ m_fBrakePedal = 1.0f;
+ m_fGasPedal = 0.0f;
+ }
+ if(CPad::GetPad(0)->CarGunJustDown())
+ ActivateBomb();
+ break;
+
+ case STATUS_SIMPLE:
+ CCarAI::UpdateCarAI(this);
+ CPhysical::ProcessControl();
+ CCarCtrl::UpdateCarOnRails(this);
+
+ m_nWheelsOnGround = 4;
+ m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround;
+ m_nDriveWheelsOnGround = 4;
+
+ pHandling->Transmission.CalculateGearForSimpleCar(AutoPilot.m_fMaxTrafficSpeed/50.0f, m_nCurrentGear);
+
+ wheelRot = ProcessWheelRotation(WHEEL_STATE_NORMAL, GetForward(), m_vecMoveSpeed, 0.35f);
+ for(i = 0; i < 4; i++)
+ m_aWheelRotation[i] += wheelRot;
+
+ PlayHornIfNecessary();
+ ReduceHornCounter();
+ bVehicleColProcessed = false;
+ bAudioChangingGear = false;
+ // that's all we do for simple vehicles
+ return;
+
+ case STATUS_PHYSICS:
+ CCarAI::UpdateCarAI(this);
+ CCarCtrl::SteerAICarWithPhysics(this);
+ PlayHornIfNecessary();
+
+ if(bIsBeingCarJacked){
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ }
+
+ if(m_aSuspensionSpringRatio[0] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[0].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[1] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[1].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[2] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[2].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[3] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[3].surfaceB) == ADHESIVE_SAND){
+ if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_SANDKING && GetModelIndex() != MI_BFINJECT){
+ bStuckInSand = true;
+ if(CWeather::WetRoads > 0.0f)
+ ApplyMoveForce(m_vecMoveSpeed * -CTimer::GetTimeStep()*SAND_SLOWDOWN*m_fMass * (1.0f-CWeather::WetRoads));
+ else
+ ApplyMoveForce(m_vecMoveSpeed * -CTimer::GetTimeStep()*SAND_SLOWDOWN*m_fMass);
+ }
+ }
+ break;
+
+ case STATUS_ABANDONED:
+ if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f))
+ m_fBrakePedal = 0.2f;
+ else
+ m_fBrakePedal = 0.0f;
+ bIsHandbrakeOn = false;
+
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+ if(!IsAlarmOn())
+ m_nCarHornTimer = 0;
+
+ if(bIsBeingCarJacked){
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ }
+ break;
+
+ case STATUS_WRECKED:
+ m_fBrakePedal = 0.05f;
+ bIsHandbrakeOn = true;
+
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+ if(!IsAlarmOn())
+ m_nCarHornTimer = 0;
+ break;
+
+ case STATUS_PLAYER_DISABLED:
+ if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f) ||
+ (pDriver && pDriver->IsPlayer() &&
+ (pDriver->GetPedState() == PED_ARRESTED ||
+ pDriver->GetPedState() == PED_DRAG_FROM_CAR ||
+ (pDriver->GetPedState() == PED_EXIT_CAR || pDriver->m_objective == OBJECTIVE_LEAVE_CAR) && !CanPedJumpOutCar()))){
+ bIsHandbrakeOn = true;
+ m_fBrakePedal = 1.0f;
+ m_fGasPedal = 0.0f;
+ }else{
+ m_fBrakePedal = 0.0f;
+ bIsHandbrakeOn = false;
+ }
+
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+ if(!IsAlarmOn())
+ m_nCarHornTimer = 0;
+ break;
+ default: break;
+ }
+
+ // Skip physics if object is found to have been static recently
+ bool skipPhysics = false;
+ if(!bIsStuck && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED)){
+ bool makeStatic = false;
+ float moveSpeedLimit, turnSpeedLimit, distanceLimit;
+
+ if(!bVehicleColProcessed &&
+ m_vecMoveSpeed.IsZero() &&
+ // BUG? m_aSuspensionSpringRatioPrev[3] is checked twice in the game. also, why 3?
+ m_aSuspensionSpringRatioPrev[3] != 1.0f)
+ makeStatic = true;
+
+ if(GetStatus() == STATUS_WRECKED){
+ moveSpeedLimit = 0.006f;
+ turnSpeedLimit = 0.0015f;
+ distanceLimit = 0.015f;
+ }else{
+ moveSpeedLimit = 0.003f;
+ turnSpeedLimit = 0.0009f;
+ distanceLimit = 0.005f;
+ }
+
+ m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f;
+ m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f;
+
+ if(m_vecMoveSpeedAvg.MagnitudeSqr() <= sq(moveSpeedLimit*CTimer::GetTimeStep()) &&
+ m_vecTurnSpeedAvg.MagnitudeSqr() <= sq(turnSpeedLimit*CTimer::GetTimeStep()) &&
+ m_fDistanceTravelled < distanceLimit &&
+ !(m_fDamageImpulse > 0.0f && m_pDamageEntity && m_pDamageEntity->IsPed()) ||
+ makeStatic){
+ m_nStaticFrames++;
+
+ if(m_nStaticFrames > 10 || makeStatic)
+ if(!CCarCtrl::MapCouldMoveInThisArea(GetPosition().x, GetPosition().y)){
+ if(!makeStatic || m_nStaticFrames > 10)
+ m_nStaticFrames = 10;
+
+ skipPhysics = true;
+
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ }
+ }else
+ m_nStaticFrames = 0;
+ if(IsRealHeli() && m_aWheelSpeed[1] > 0.0f){
+ skipPhysics = false;
+ m_nStaticFrames = 0;
+ }
+ }
+
+ // Postpone
+ for(i = 0; i < 4; i++)
+ if(m_aGroundPhysical[i]){
+ bRestingOnPhysical = true;
+ if(!CWorld::bForceProcessControl && m_aGroundPhysical[i]->bIsInSafePosition){
+ bWasPostponed = true;
+ return;
+ }
+ }
+
+ if(bRestingOnPhysical){
+ skipPhysics = false;
+ m_nStaticFrames = 0;
+ }
+
+ VehicleDamage(0.0f, 0);
+
+ // special control
+ switch(GetModelIndex()){
+ case MI_FIRETRUCK:
+ FireTruckControl();
+ break;
+ case MI_RHINO:
+ TankControl();
+ BlowUpCarsInPath();
+ break;
+ case MI_VOODOO:
+ HydraulicControl();
+ break;
+ default:
+ if(CVehicle::bCheat3 || carHasNitro){
+ // Make vehicle jump when horn is sounded
+ if(GetStatus() == STATUS_PLAYER && m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f) &&
+ // BUG: game checks [0] four times, instead of all wheels
+ m_aSuspensionSpringRatio[0] < 1.0f &&
+ CPad::GetPad(0)->HornJustDown()){
+
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, 1.0f);
+
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM,
+ m_aWheelColPoints[0].point + 0.5f*GetUp(),
+ 1.3f*m_vecMoveSpeed, nil, 2.5f);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE,
+ m_aWheelColPoints[0].point + 0.5f*GetUp(),
+ 1.2f*m_vecMoveSpeed, nil, 2.0f);
+
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM,
+ m_aWheelColPoints[2].point + 0.5f*GetUp(),
+ 1.3f*m_vecMoveSpeed, nil, 2.5f);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE,
+ m_aWheelColPoints[2].point + 0.5f*GetUp(),
+ 1.2f*m_vecMoveSpeed, nil, 2.0f);
+
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM,
+ m_aWheelColPoints[0].point + 0.5f*GetUp() - GetForward(),
+ 1.3f*m_vecMoveSpeed, nil, 2.5f);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE,
+ m_aWheelColPoints[0].point + 0.5f*GetUp() - GetForward(),
+ 1.2f*m_vecMoveSpeed, nil, 2.0f);
+
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM,
+ m_aWheelColPoints[2].point + 0.5f*GetUp() - GetForward(),
+ 1.3f*m_vecMoveSpeed, nil, 2.5f);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE,
+ m_aWheelColPoints[2].point + 0.5f*GetUp() - GetForward(),
+ 1.2f*m_vecMoveSpeed, nil, 2.0f);
+
+ ApplyMoveForce(CVector(0.0f, 0.0f, 1.0f)*m_fMass*0.4f);
+ ApplyTurnForce(GetUp()*m_fTurnMass*0.01f, GetForward()*1.0f);
+ }
+ }
+ break;
+ }
+
+ if(GetStatus() == STATUS_PHYSICS || GetStatus() == STATUS_SIMPLE)
+ if(AutoPilot.m_nCarMission == MISSION_HELI_FLYTOCOORS ||
+ AutoPilot.m_nCarMission == MISSION_PLANE_FLYTOCOORS)
+ skipPhysics = true;
+
+ if(skipPhysics){
+ bHasContacted = false;
+ bIsInSafePosition = false;
+ bWasPostponed = false;
+ bHasHitWall = false;
+ m_nCollisionRecords = 0;
+ bHasCollided = false;
+ bVehicleColProcessed = false;
+ bAudioChangingGear = false;
+ m_nDamagePieceType = 0;
+ m_fDamageImpulse = 0.0f;
+ m_pDamageEntity = nil;
+ m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_fTireTemperature = 1.0f;
+ }else{
+
+ // This has to be done if ProcessEntityCollision wasn't called
+ if(!bVehicleColProcessed){
+ CMatrix mat(GetMatrix());
+ bIsStuck = false;
+ bHasContacted = false;
+ bIsInSafePosition = false;
+ bWasPostponed = false;
+ bHasHitWall = false;
+ m_fDistanceTravelled = 0.0f;
+ m_bIsVehicleBeingShifted = false;
+ bSkipLineCol = false;
+ ApplyMoveSpeed();
+ ApplyTurnSpeed();
+ for(i = 0; CheckCollision() && i < 5; i++){
+ GetMatrix() = mat;
+ ApplyMoveSpeed();
+ ApplyTurnSpeed();
+ }
+ bIsInSafePosition = true;
+ bIsStuck = false;
+ }
+
+ CPhysical::ProcessControl();
+
+ ProcessBuoyancy();
+
+ // Rescale spring ratios, i.e. subtract wheel radius
+ for(i = 0; i < 4; i++){
+ // wheel radius in relation to suspension line
+ float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i];
+ // rescale such that 0.0 is fully compressed and 1.0 is fully extended
+ m_aSuspensionSpringRatio[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius);
+ }
+
+ float fwdSpeed = Abs(DotProduct(m_vecMoveSpeed, GetForward()));
+ CVector contactPoints[4]; // relative to model
+ CVector contactSpeeds[4]; // speed at contact points
+ CVector springDirections[4]; // normalized, in world space
+
+ for(i = 0; i < 4; i++){
+ // Set spring under certain circumstances
+ if(Damage.GetWheelStatus(i) == WHEEL_STATUS_MISSING)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ else if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST){
+ // wheel more bumpy the faster we are
+ if(CGeneral::GetRandomNumberInRange(0, (uint16)(40*fwdSpeed) + 98) < 100){
+ m_aSuspensionSpringRatio[i] += 0.3f*(m_aSuspensionLineLength[i]-m_aSuspensionSpringLength[i])/m_aSuspensionSpringLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+ }else if(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[i].surfaceB) == ADHESIVE_SAND &&
+ GetModelIndex() != MI_RHINO){
+ fwdSpeed *= 0.7f;
+ float f = 1.0f - fwdSpeed/0.3f - 0.7f*CWeather::WetRoads;
+ f = Max(f, 0.4f);
+ m_aSuspensionSpringRatio[i] += 0.35f*f*(m_aSuspensionLineLength[i]-m_aSuspensionSpringLength[i])/m_aSuspensionSpringLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+
+ // get points and directions if spring is compressed
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ contactPoints[i] = m_aWheelColPoints[i].point - GetPosition();
+ springDirections[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1 - colModel->lines[i].p0);
+ springDirections[i].Normalise();
+ }
+ }
+
+ // Make springs push up vehicle
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ float bias = pHandling->fSuspensionBias;
+ if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT)
+ bias = 1.0f - bias;
+
+ ApplySpringCollisionAlt(pHandling->fSuspensionForceLevel,
+ springDirections[i], contactPoints[i],
+ m_aSuspensionSpringRatio[i], bias, m_aWheelColPoints[i].normal);
+
+ m_aWheelSkidmarkUnk[i] = false;
+ if(m_aWheelColPoints[i].surfaceB == SURFACE_GRASS ||
+ m_aWheelColPoints[i].surfaceB == SURFACE_MUD_DRY)
+ m_aWheelSkidmarkType[i] = SKIDMARK_MUDDY;
+ else if(m_aWheelColPoints[i].surfaceB == SURFACE_SAND ||
+ m_aWheelColPoints[i].surfaceB == SURFACE_SAND_BEACH){
+ m_aWheelSkidmarkType[i] = SKIDMARK_SANDY;
+ m_aWheelSkidmarkUnk[i] = true;
+ }else
+ m_aWheelSkidmarkType[i] = SKIDMARK_NORMAL;
+ }else{
+ contactPoints[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1);
+ }
+ }
+
+ // Get speed at contact points
+ for(i = 0; i < 4; i++){
+ contactSpeeds[i] = GetSpeed(contactPoints[i]);
+ if(m_aGroundPhysical[i]){
+ // subtract movement of physical we're standing on
+ contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]);
+#ifndef FIX_BUGS
+ // this shouldn't be reset because we still need it below
+ m_aGroundPhysical[i] = nil;
+#endif
+ }
+ if(m_aSuspensionSpringRatio[i] < 1.0f && m_aWheelColPoints[i].normal.z > 0.35f)
+ springDirections[i] = -m_aWheelColPoints[i].normal;
+ }
+
+ // dampen springs
+ for(i = 0; i < 4; i++)
+ if(m_aSuspensionSpringRatio[i] < 0.99999f)
+ ApplySpringDampening(pHandling->fSuspensionDampingLevel,
+ springDirections[i], contactPoints[i], contactSpeeds[i]);
+
+ // Get speed at contact points again
+ for(i = 0; i < 4; i++){
+ contactSpeeds[i] = GetSpeed(contactPoints[i]);
+ if(m_aGroundPhysical[i]){
+ // subtract movement of physical we're standing on
+ contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]);
+ m_aGroundPhysical[i] = nil;
+ }
+ }
+
+ bool gripCheat = true;
+ fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward());
+ if(!strongGrip1 && !CVehicle::bCheat3)
+ gripCheat = false;
+ float acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat);
+ acceleration /= m_fForceMultiplier;
+
+ if(IsRealHeli() || IsRealPlane())
+ acceleration = 0.0f;
+
+ if(bAudioChangingGear && m_fGasPedal > 0.4f && m_fBrakePedal < 0.1f && fwdSpeed > 0.15f &&
+ this == FindPlayerVehicle() && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON){
+ if(GetStatus() == STATUS_PLAYER && !(pHandling->Flags & HANDLING_IS_BUS)){
+ if(m_nBusDoorTimerEnd == 0)
+ m_nBusDoorTimerEnd = 1000;
+ else {
+ uint32 timeStepInMs = CTimer::GetTimeStepInMilliseconds();
+ if(m_nBusDoorTimerEnd > timeStepInMs)
+ m_nBusDoorTimerEnd -= timeStepInMs;
+ else
+ m_nBusDoorTimerEnd = 0;
+ }
+ }
+
+ if((m_aSuspensionSpringRatio[0] < 1.0f || m_aSuspensionSpringRatio[2] < 1.0f) &&
+ (m_aSuspensionSpringRatio[1] < 1.0f || m_aSuspensionSpringRatio[3] < 1.0f))
+ ApplyTurnForce(-GRAVITY*Min(m_fTurnMass, 2500.0f)*GetUp(), -1.0f*GetForward());
+ }
+
+ brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep();
+ bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING);
+ float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias;
+ float brakeBiasRear = neutralHandling ? 1.0f : 2.0f-pHandling->fBrakeBias; // looks like a bug, but it was correct in III...
+ float tractionBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fTractionBias;
+ float tractionBiasRear = neutralHandling ? 1.0f : 2.0f-tractionBiasFront;
+
+ // Count how many wheels are touching the ground
+
+ m_nWheelsOnGround = 0;
+ m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround;
+ m_nDriveWheelsOnGround = 0;
+
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatio[i] < 1.0f)
+ m_aWheelTimer[i] = 4.0f;
+ else
+ m_aWheelTimer[i] = Max(m_aWheelTimer[i]-CTimer::GetTimeStep(), 0.0f);
+
+ if(m_aWheelTimer[i] > 0.0f){
+ m_nWheelsOnGround++;
+ switch(pHandling->Transmission.nDriveType){
+ case '4':
+ m_nDriveWheelsOnGround++;
+ break;
+ case 'F':
+ if(i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT)
+ m_nDriveWheelsOnGround++;
+ break;
+ case 'R':
+ if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT)
+ m_nDriveWheelsOnGround++;
+ break;
+ }
+ }
+ }
+
+ float traction;
+ if(GetStatus() == STATUS_PHYSICS)
+ traction = 0.004f * m_fTraction;
+ else
+ traction = 0.004f;
+ traction *= pHandling->fTractionMultiplier / 4.0f;
+ traction /= m_fForceMultiplier;
+ if(CVehicle::bCheat3)
+ traction *= 4.0f;
+
+ if(FindPlayerVehicle() != this && (strongGrip1 || CVehicle::bCheat3)){
+ traction *= 1.2f;
+ acceleration *= 1.4f;
+ if(strongGrip2 || CVehicle::bCheat3){
+ traction *= 1.3f;
+ acceleration *= 1.4f;
+ }
+ }
+
+ static float fThrust;
+ static tWheelState WheelState[4];
+
+ bool rearWheelsFirst = !!(pHandling->Flags & HANDLING_REARWHEEL_1ST);
+
+ // Process front wheels on ground - first try
+
+ if(!rearWheelsFirst){
+ if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){
+ float s = Sin(m_fSteerAngle);
+ float c = Cos(m_fSteerAngle);
+
+ CVector wheelFwd, wheelRight, tmp;
+
+ if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier))
+ fThrust = acceleration;
+ else
+ fThrust = 0.0f;
+
+ wheelFwd = GetForward();
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal);
+ wheelRight.Normalise();
+ tmp = c*wheelFwd - s*wheelRight;
+ wheelRight = s*wheelFwd + c*wheelRight;
+ wheelFwd = tmp;
+
+ m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_LEFT])*traction;
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB);
+ WheelState[CARWHEEL_FRONT_LEFT] = m_aWheelState[CARWHEEL_FRONT_LEFT];
+
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST)
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect,
+ CARWHEEL_FRONT_LEFT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_LEFT],
+ &WheelState[CARWHEEL_FRONT_LEFT],
+ WHEEL_STATUS_BURST);
+ else
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront,
+ CARWHEEL_FRONT_LEFT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_LEFT],
+ &WheelState[CARWHEEL_FRONT_LEFT],
+ WHEEL_STATUS_OK);
+ }
+
+ if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier))
+ fThrust = acceleration;
+ else
+ fThrust = 0.0f;
+
+ wheelFwd = GetForward();
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal);
+ wheelRight.Normalise();
+ tmp = c*wheelFwd - s*wheelRight;
+ wheelRight = s*wheelFwd + c*wheelRight;
+ wheelFwd = tmp;
+
+ m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT])*traction;
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB);
+ WheelState[CARWHEEL_FRONT_RIGHT] = m_aWheelState[CARWHEEL_FRONT_RIGHT];
+
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST)
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect,
+ CARWHEEL_FRONT_RIGHT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT],
+ &WheelState[CARWHEEL_FRONT_RIGHT],
+ WHEEL_STATUS_BURST);
+ else
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront,
+ CARWHEEL_FRONT_RIGHT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT],
+ &WheelState[CARWHEEL_FRONT_RIGHT],
+ WHEEL_STATUS_OK);
+ }
+ }
+
+ // Process front wheels off ground
+
+ if(!IsRealHeli()){
+ if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] <= 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] < 2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_LEFT] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] > -2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_LEFT] += 0.1f;
+ }
+ }else{
+ m_aWheelSpeed[CARWHEEL_FRONT_LEFT] *= 0.95f;
+ }
+ m_aWheelRotation[CARWHEEL_FRONT_LEFT] += m_aWheelSpeed[CARWHEEL_FRONT_LEFT];
+ }
+ if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] <= 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] < 2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] > -2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] += 0.1f;
+ }
+ }else{
+ m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] *= 0.95f;
+ }
+ m_aWheelRotation[CARWHEEL_FRONT_RIGHT] += m_aWheelSpeed[CARWHEEL_FRONT_RIGHT];
+ }
+ }
+ }
+
+ // Process rear wheels
+
+ if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){
+ CVector wheelFwd = GetForward();
+ CVector wheelRight = GetRight(); // overwritten for resp. wheel
+
+ float rearBrake = brake;
+ float rearTraction = traction;
+ if(bIsHandbrakeOn){
+#ifdef FIX_BUGS
+ // Not sure if this is needed, but brake usually has timestep as a factor
+ rearBrake = 20000.0f * CTimer::GetTimeStepFix();
+#else
+ rearBrake = 20000.0f;
+#endif
+ if(fwdSpeed > 0.1f && pHandling->Flags & HANDLING_HANDBRAKE_TYRE){
+ m_fTireTemperature += 0.005*CTimer::GetTimeStep();
+ if(m_fTireTemperature > 2.0f)
+ m_fTireTemperature = 2.0f;
+ }
+ }else if(m_doingBurnout && mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)){
+ rearBrake = 0.0f;
+ rearTraction = 0.0f;
+ // BUG: missing timestep
+ ApplyTurnForce(contactPoints[CARWHEEL_REAR_LEFT], -0.001f*m_fTurnMass*m_fSteerAngle*GetRight());
+ }else if(m_fTireTemperature > 1.0f){
+ rearTraction *= m_fTireTemperature;
+ }
+
+ if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f){
+ if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier))
+ fThrust = acceleration;
+ else
+ fThrust = 0.0f;
+
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_LEFT].normal)*m_aWheelColPoints[CARWHEEL_REAR_LEFT].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_LEFT].normal);
+ wheelRight.Normalise();
+
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_LEFT])*rearTraction;
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB);
+ WheelState[CARWHEEL_REAR_LEFT] = m_aWheelState[CARWHEEL_REAR_LEFT];
+
+ if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST)
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_REAR_LEFT], contactPoints[CARWHEEL_REAR_LEFT],
+ m_nWheelsOnGround, fThrust,
+ rearBrake*brakeBiasRear,
+ adhesion*tractionBiasRear*Damage.m_fWheelDamageEffect,
+ CARWHEEL_REAR_LEFT,
+ &m_aWheelSpeed[CARWHEEL_REAR_LEFT],
+ &WheelState[CARWHEEL_REAR_LEFT],
+ WHEEL_STATUS_BURST);
+ else
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_REAR_LEFT], contactPoints[CARWHEEL_REAR_LEFT],
+ m_nWheelsOnGround, fThrust,
+ rearBrake*brakeBiasRear,
+ adhesion*tractionBiasRear,
+ CARWHEEL_REAR_LEFT,
+ &m_aWheelSpeed[CARWHEEL_REAR_LEFT],
+ &WheelState[CARWHEEL_REAR_LEFT],
+ WHEEL_STATUS_OK);
+ }
+
+#ifdef FIX_BUGS
+ // Shouldn't we reset these after the left wheel?
+ wheelFwd = GetForward();
+ wheelRight = GetRight(); // actually useless
+#endif
+
+ if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){
+ if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier))
+ fThrust = acceleration;
+ else
+ fThrust = 0.0f;
+
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].normal)*m_aWheelColPoints[CARWHEEL_REAR_RIGHT].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].normal);
+ wheelRight.Normalise();
+
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_RIGHT])*rearTraction;
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB);
+ WheelState[CARWHEEL_REAR_RIGHT] = m_aWheelState[CARWHEEL_REAR_RIGHT];
+
+ if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST)
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_REAR_RIGHT], contactPoints[CARWHEEL_REAR_RIGHT],
+ m_nWheelsOnGround, fThrust,
+ rearBrake*brakeBiasRear,
+ adhesion*tractionBiasRear*Damage.m_fWheelDamageEffect,
+ CARWHEEL_REAR_RIGHT,
+ &m_aWheelSpeed[CARWHEEL_REAR_RIGHT],
+ &WheelState[CARWHEEL_REAR_RIGHT],
+ WHEEL_STATUS_BURST);
+ else
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_REAR_RIGHT], contactPoints[CARWHEEL_REAR_RIGHT],
+ m_nWheelsOnGround, fThrust,
+ rearBrake*brakeBiasRear,
+ adhesion*tractionBiasRear,
+ CARWHEEL_REAR_RIGHT,
+ &m_aWheelSpeed[CARWHEEL_REAR_RIGHT],
+ &WheelState[CARWHEEL_REAR_RIGHT],
+ WHEEL_STATUS_OK);
+ }
+ }
+
+ if(m_doingBurnout && mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) &&
+ (m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING || m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING)){
+ m_fTireTemperature += 0.001f*CTimer::GetTimeStep();
+ if(m_fTireTemperature > 3.0f)
+ m_fTireTemperature = 3.0f;
+ }else if(m_fTireTemperature > 1.0f){
+ m_fTireTemperature = (m_fTireTemperature - 1.0f)*Pow(0.995f, CTimer::GetTimeStep()) + 1.0f;
+ }
+
+ // Process rear wheels off ground
+
+ if(!IsRealHeli()){
+ if(m_aWheelTimer[CARWHEEL_REAR_LEFT] <= 0.0f){
+ if(bIsHandbrakeOn)
+ m_aWheelSpeed[CARWHEEL_REAR_LEFT] = 0.0f;
+ else if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[CARWHEEL_REAR_LEFT] < 2.0f)
+ m_aWheelSpeed[CARWHEEL_REAR_LEFT] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[CARWHEEL_REAR_LEFT] > -2.0f)
+ m_aWheelSpeed[CARWHEEL_REAR_LEFT] += 0.1f;
+ }
+ }else{
+ m_aWheelSpeed[CARWHEEL_REAR_LEFT] *= 0.95f;
+ }
+ m_aWheelRotation[CARWHEEL_REAR_LEFT] += m_aWheelSpeed[CARWHEEL_REAR_LEFT];
+ }
+ if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] <= 0.0f){
+ if(bIsHandbrakeOn)
+ m_aWheelSpeed[CARWHEEL_REAR_RIGHT] = 0.0f;
+ else if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[CARWHEEL_REAR_RIGHT] < 2.0f)
+ m_aWheelSpeed[CARWHEEL_REAR_RIGHT] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[CARWHEEL_REAR_RIGHT] > -2.0f)
+ m_aWheelSpeed[CARWHEEL_REAR_RIGHT] += 0.1f;
+ }
+ }else{
+ m_aWheelSpeed[CARWHEEL_REAR_RIGHT] *= 0.95f;
+ }
+ m_aWheelRotation[CARWHEEL_REAR_RIGHT] += m_aWheelSpeed[CARWHEEL_REAR_RIGHT];
+ }
+ }
+
+ // Process front wheels on ground - second try
+
+ if(rearWheelsFirst){
+ if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){
+ float s = Sin(m_fSteerAngle);
+ float c = Cos(m_fSteerAngle);
+
+ CVector wheelFwd, wheelRight, tmp;
+
+ if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier))
+ fThrust = acceleration;
+ else
+ fThrust = 0.0f;
+
+ wheelFwd = GetForward();
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal);
+ wheelRight.Normalise();
+ tmp = c*wheelFwd - s*wheelRight;
+ wheelRight = s*wheelFwd + c*wheelRight;
+ wheelFwd = tmp;
+
+ m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_LEFT])*traction;
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB);
+ WheelState[CARWHEEL_FRONT_LEFT] = m_aWheelState[CARWHEEL_FRONT_LEFT];
+
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST)
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect,
+ CARWHEEL_FRONT_LEFT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_LEFT],
+ &WheelState[CARWHEEL_FRONT_LEFT],
+ WHEEL_STATUS_BURST);
+ else
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront,
+ CARWHEEL_FRONT_LEFT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_LEFT],
+ &WheelState[CARWHEEL_FRONT_LEFT],
+ WHEEL_STATUS_OK);
+ }
+
+ if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier))
+ fThrust = acceleration;
+ else
+ fThrust = 0.0f;
+
+ wheelFwd = GetForward();
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal);
+ wheelRight.Normalise();
+ tmp = c*wheelFwd - s*wheelRight;
+ wheelRight = s*wheelFwd + c*wheelRight;
+ wheelFwd = tmp;
+
+ m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT])*traction;
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB);
+ WheelState[CARWHEEL_FRONT_RIGHT] = m_aWheelState[CARWHEEL_FRONT_RIGHT];
+
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST)
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect,
+ CARWHEEL_FRONT_RIGHT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT],
+ &WheelState[CARWHEEL_FRONT_RIGHT],
+ WHEEL_STATUS_BURST);
+ else
+ ProcessWheel(wheelFwd, wheelRight,
+ contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT],
+ m_nWheelsOnGround, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront,
+ CARWHEEL_FRONT_RIGHT,
+ &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT],
+ &WheelState[CARWHEEL_FRONT_RIGHT],
+ WHEEL_STATUS_OK);
+ }
+ }
+
+ // Process front wheels off ground
+
+ if (!IsRealHeli()) {
+ if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] <= 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] < 2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_LEFT] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] > -2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_LEFT] += 0.1f;
+ }
+ }else{
+ m_aWheelSpeed[CARWHEEL_FRONT_LEFT] *= 0.95f;
+ }
+ m_aWheelRotation[CARWHEEL_FRONT_LEFT] += m_aWheelSpeed[CARWHEEL_FRONT_LEFT];
+ }
+ if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] <= 0.0f){
+ if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] < 2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] > -2.0f)
+ m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] += 0.1f;
+ }
+ }else{
+ m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] *= 0.95f;
+ }
+ m_aWheelRotation[CARWHEEL_FRONT_RIGHT] += m_aWheelSpeed[CARWHEEL_FRONT_RIGHT];
+ }
+ }
+ }
+
+ for(i = 0; i < 4; i++){
+ float wheelPos = colModel->lines[i].p0.z;
+ if(m_aSuspensionSpringRatio[i] > 0.0f)
+ wheelPos -= m_aSuspensionSpringRatio[i]*m_aSuspensionSpringLength[i];
+ if(GetModelIndex() == MI_VOODOO && bUsingSpecialColModel)
+ m_aWheelPosition[i] = wheelPos;
+ else
+ m_aWheelPosition[i] += (wheelPos - m_aWheelPosition[i])*0.75f;
+ }
+ for(i = 0; i < 4; i++)
+ m_aWheelState[i] = WheelState[i];
+ if(m_fGasPedal < 0.0f){
+ if(m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING)
+ m_aWheelState[CARWHEEL_REAR_LEFT] = WHEEL_STATE_NORMAL;
+ if(m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING)
+ m_aWheelState[CARWHEEL_REAR_RIGHT] = WHEEL_STATE_NORMAL;
+ }
+
+ // Process horn
+
+ if(GetStatus() != STATUS_PLAYER){
+ if(!IsAlarmOn())
+ ReduceHornCounter();
+ }else{
+ if(UsesSiren()){
+ if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory]){
+ if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+4) % 5] &&
+ Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+3) % 5])
+ m_nCarHornTimer = 1;
+ else
+ m_nCarHornTimer = 0;
+ }else if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+4) % 5] &&
+ !Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+1) % 5]){
+ m_nCarHornTimer = 0;
+ m_bSirenOrAlarm = !m_bSirenOrAlarm;
+ }else
+ m_nCarHornTimer = 0;
+ }else if(GetModelIndex() != MI_VOODOO && !CVehicle::bCheat3 && !carHasNitro){
+ if(!IsAlarmOn()){
+ if(Pads[0].GetHorn())
+ m_nCarHornTimer = 1;
+ else
+ m_nCarHornTimer = 0;
+ }
+ }
+ }
+
+ // Flying
+
+ bool playRotorSound = false;
+ bool isPlane = GetModelIndex() == MI_DODO || bAllDodosCheat;
+#ifdef FIX_BUGS
+ isPlane = isPlane && !IsRealHeli();
+#endif
+ if(GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PHYSICS){
+ if(IsRealHeli()){
+ bEngineOn = false;
+ m_aWheelSpeed[1] = Max(m_aWheelSpeed[1]-0.0005f, 0.0f);
+ if(GetModelIndex() != MI_RCRAIDER && GetModelIndex() != MI_RCGOBLIN)
+ if(m_aWheelSpeed[1] < 0.154f && m_aWheelSpeed[1] > 0.0044f)
+ playRotorSound = true;
+ }
+ }else if(isPlane && m_vecMoveSpeed.Magnitude() > 0.0f && CTimer::GetTimeStep() > 0.0f){
+ if(GetModelIndex() == MI_DODO)
+ FlyingControl(FLIGHT_MODEL_DODO);
+ else
+ FlyingControl(FLIGHT_MODEL_PLANE);
+ }else if(GetModelIndex() == MI_RCBARON){
+ FlyingControl(FLIGHT_MODEL_RCPLANE);
+ }else if(IsRealHeli() || bAllCarCheat){
+#ifdef RESTORE_ALLCARSHELI_CHEAT
+ if (bAllCarCheat)
+ FlyingControl(FLIGHT_MODEL_HELI);
+ else
+#endif
+ {
+ // Speed up rotor
+ if (m_aWheelSpeed[1] < 0.22f && !bIsInWater) {
+ if (GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN)
+ m_aWheelSpeed[1] += 0.003f;
+ else
+ m_aWheelSpeed[1] += 0.001f;
+ }
+
+ // Fly
+ if (m_aWheelSpeed[1] > 0.15f) {
+ if (GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN)
+ FlyingControl(FLIGHT_MODEL_RCHELI);
+ else if (m_nWheelsOnGround < 4 && !(GetModelIndex() == MI_SEASPAR && bTouchingWater) ||
+ CPad::GetPad(0)->GetAccelerate() != 0 ||
+#ifndef FREE_CAM
+ CPad::GetPad(0)->GetCarGunUpDown() > 1.0f ||
+#else
+ ((!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController)) && CPad::GetPad(0)->GetCarGunUpDown() > 1.0f) ||
+#endif
+ Abs(m_vecMoveSpeed.x) > 0.02f ||
+ Abs(m_vecMoveSpeed.y) > 0.02f ||
+ Abs(m_vecMoveSpeed.z) > 0.02f)
+ FlyingControl(FLIGHT_MODEL_HELI);
+ }
+ }
+
+ // Blade collision
+ if(m_aWheelSpeed[1] > 0.015f && m_aCarNodes[CAR_BONNET]){
+ CMatrix mat;
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET]));
+ if(GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN)
+ DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 0.72f, 0.9f);
+ else if(GetModelIndex() == MI_SPARROW || GetModelIndex() == MI_SEASPAR)
+ DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 5.15f, 0.8f);
+ else if(GetModelIndex() == MI_HUNTER)
+ DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 6.15f, 0.5f);
+ else
+ DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 6.15f, 1.0f);
+ }
+
+ // Heli weapons
+ if(GetModelIndex() == MI_HUNTER && GetStatus() == STATUS_PLAYER){
+ // Hunter rockets
+ if(CPad::GetPad(0)->CarGunJustDown() && CTimer::GetTimeInMilliseconds() > m_nGunFiringTime+350){
+ CWeapon gun(WEAPONTYPE_ROCKETLAUNCHER, 100);
+ CVector source = vecHunterRocketPos;
+ source = GetMatrix()*source + Max(DotProduct(m_vecMoveSpeed, GetForward()), 0.0f)*GetForward()*CTimer::GetTimeStep();
+ gun.FireProjectile(this, &source, 0.0f);
+
+ source = vecHunterRocketPos;
+ source.x = -source.x;
+ source = GetMatrix()*source + Max(DotProduct(m_vecMoveSpeed, GetForward()), 0.0f)*GetForward()*CTimer::GetTimeStep();
+ gun.FireProjectile(this, &source, 0.0f);
+
+ CStats::RoundsFiredByPlayer++;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds();
+ // Hunter gun
+ }else if(CPad::GetPad(0)->GetHandBrake() && CTimer::GetTimeInMilliseconds() > m_nGunFiringTime+60){
+ CWeapon gun(WEAPONTYPE_HELICANNON, 5000);
+ CVector source = vecHunterGunPos;
+ source = GetMatrix()*source + m_vecMoveSpeed*CTimer::GetTimeStep();
+ gun.FireInstantHit(this, &source);
+ gun.AddGunshell(this, source, CVector2D(0.0f, 0.1f), 0.025f);
+ CStats::RoundsFiredByPlayer++;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds();
+ }
+ }else if(GetModelIndex() == MI_SEASPAR && GetStatus() == STATUS_PLAYER){
+ // Sea sparrow gun
+ if(CPad::GetPad(0)->GetHandBrake() && CTimer::GetTimeInMilliseconds() > m_nGunFiringTime+40){
+ CWeapon gun(WEAPONTYPE_M4, 5000);
+ CVector source = vecSeaSparrowGunPos;
+ source = GetMatrix()*source + m_vecMoveSpeed*CTimer::GetTimeStep();
+ gun.FireInstantHit(this, &source);
+ gun.AddGunshell(this, source, CVector2D(0.0f, 0.1f), 0.025f);
+ CStats::RoundsFiredByPlayer++;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds();
+ }
+ }
+
+ if(GetModelIndex() != MI_RCRAIDER && GetModelIndex() != MI_RCGOBLIN)
+ if(m_aWheelSpeed[1] < 0.154f && m_aWheelSpeed[1] > 0.0044f)
+ playRotorSound = true;
+ }
+
+ // Play rotor sound
+ if(playRotorSound && m_aCarNodes[CAR_BONNET]){
+ CVector camDist = TheCamera.GetPosition() - GetPosition();
+ float distSq = camDist.MagnitudeSqr();
+ if(distSq < SQR(20.0f) && Abs(m_fPropellerRotation - m_aWheelRotation[1]) > DEGTORAD(30.0f)){
+ CMatrix mat;
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET]));
+ CVector blade = mat.GetRight();
+ blade = Multiply3x3(blade, GetMatrix());
+ camDist /= Max(Sqrt(distSq), 0.01f);
+ if(Abs(DotProduct(camDist, blade)) > 0.95f){
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_HELI_BLADE, 0.0f);
+ m_fPropellerRotation = m_aWheelRotation[1];
+ }
+ }
+ }
+ }
+
+
+
+ // Process car on fire
+ // A similar calculation of damagePos is done elsewhere for smoke
+
+ uint8 engineStatus = Damage.GetEngineStatus();
+ CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_HEADLIGHTS];
+
+ switch(Damage.GetDoorStatus(DOOR_BONNET)){
+ case DOOR_STATUS_OK:
+ case DOOR_STATUS_SMASHED:
+ // Bonnet is still there, smoke comes out at the edge
+ damagePos += vecDAMAGE_ENGINE_POS_SMALL;
+ break;
+ case DOOR_STATUS_SWINGING:
+ case DOOR_STATUS_MISSING:
+ // Bonnet is gone, smoke comes out at the engine
+ damagePos += vecDAMAGE_ENGINE_POS_BIG;
+ break;
+ }
+
+ // move fire forward if in first person
+ if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson())
+ if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){
+ if(GetModelIndex() == MI_FIRETRUCK)
+ damagePos += CVector(0.0f, 3.0f, -0.2f);
+ else
+ damagePos += CVector(0.0f, 1.2f, -0.8f);
+ }
+
+ damagePos = GetMatrix()*damagePos;
+ damagePos.z += 0.15f;
+
+ if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){
+ // Car is on fire
+
+ CParticle::AddParticle(PARTICLE_CARFLAME, damagePos,
+ CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.01125f, 0.09f)),
+ nil, 0.63f);
+
+ CVector coors = damagePos;
+ coors.x += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f),
+ coors.y += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f),
+ coors.z += CGeneral::GetRandomNumberInRange(0.5625f, 2.25f);
+ CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, coors, CVector(0.0f, 0.0f, 0.0f));
+
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, CVector(0.0f, 0.0f, 0.0f), nil, 0.5f);
+
+ // Blow up car after 5 seconds
+ m_fFireBlowUpTimer += CTimer::GetTimeStepInMilliseconds();
+ if(m_fFireBlowUpTimer > 5000.0f)
+ BlowUpCar(m_pSetOnFireEntity);
+ }else
+ m_fFireBlowUpTimer = 0.0f;
+
+ // Decrease car health if engine is damaged badly
+ if(engineStatus > ENGINE_STATUS_ON_FIRE && m_fHealth > 250.0f)
+ m_fHealth -= 2.0f;
+
+ ProcessDelayedExplosion();
+
+
+ if(m_bSirenOrAlarm && (CTimer::GetFrameCounter()&7) == 5 &&
+ UsesSiren() && GetModelIndex() != MI_MRWHOOP)
+ CCarAI::MakeWayForCarWithSiren(this);
+
+
+ // Find out how much to shake the pad depending on suspension and ground surface
+
+ float suspShake = 0.0f;
+ float surfShake = 0.0f;
+ float speedsq = m_vecMoveSpeed.MagnitudeSqr();
+ for(i = 0; i < 4; i++){
+ float suspChange = m_aSuspensionSpringRatioPrev[i] - m_aSuspensionSpringRatio[i];
+ if(suspChange > 0.3f && !drivingInSand && speedsq > 0.04f){
+ if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP_2, suspChange);
+ else
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, suspChange);
+ if(suspChange > suspShake)
+ suspShake = suspChange;
+ }
+
+ uint8 surf = m_aWheelColPoints[i].surfaceB;
+ if(surf == SURFACE_GRAVEL || surf == SURFACE_WATER || surf == SURFACE_HEDGE){
+ if(surfShake < 0.2f)
+ surfShake = 0.3f;
+ }else if(surf == SURFACE_MUD_DRY){
+ if(surfShake < 0.1f)
+ surfShake = 0.2f;
+ }else if(surf == SURFACE_GRASS){
+ if(surfShake < 0.05f)
+ surfShake = 0.1f;
+ }
+
+ if(this == FindPlayerVehicle())
+// BUG: this only observes one of the wheels
+ TheCamera.m_bVehicleSuspenHigh = Abs(suspChange) > 0.05f;
+
+ m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i];
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+
+ // Shake pad
+
+ if(!drivingInSand && (suspShake > 0.0f || surfShake > 0.0f) && GetStatus() == STATUS_PLAYER){
+ float speed = m_vecMoveSpeed.MagnitudeSqr();
+ if(speed > sq(0.1f)){
+ speed = Sqrt(speed);
+ if(suspShake > 0.0f){
+ uint8 freq = Min(200.0f*suspShake*speed*2000.0f/m_fMass + 100.0f, 250.0f);
+ CPad::GetPad(0)->StartShake(20000.0f*CTimer::GetTimeStep()/freq, freq);
+ }else{
+ uint8 freq = Min(200.0f*surfShake*speed*2000.0f/m_fMass + 40.0f, 150.0f);
+ CPad::GetPad(0)->StartShake(5000.0f*CTimer::GetTimeStep()/freq, freq);
+ }
+ }
+ }
+
+ bVehicleColProcessed = false;
+ bAudioChangingGear = false;
+
+ if(!bWarnedPeds && GetVehicleAppearance() != VEHICLE_APPEARANCE_HELI && GetVehicleAppearance() != VEHICLE_APPEARANCE_PLANE)
+ CCarCtrl::ScanForPedDanger(this);
+
+
+ // Turn around at the edge of the world
+ // TODO: make the numbers defines
+
+ float heading;
+ if(GetPosition().x > 1950.0f-400.0f){
+ if(m_vecMoveSpeed.x > 0.0f)
+ m_vecMoveSpeed.x *= -1.0f;
+ heading = GetForward().Heading();
+ if(heading > 0.0f) // going west
+ SetHeading(-heading);
+ }else if(GetPosition().x < -1950.0f-400.0f){
+ if(m_vecMoveSpeed.x < 0.0f)
+ m_vecMoveSpeed.x *= -1.0f;
+ heading = GetForward().Heading();
+ if(heading < 0.0f) // going east
+ SetHeading(-heading);
+ }
+ if(GetPosition().y > 1950.0f){
+ if(m_vecMoveSpeed.y > 0.0f)
+ m_vecMoveSpeed.y *= -1.0f;
+ heading = GetForward().Heading();
+ if(heading < HALFPI && heading > 0.0f)
+ SetHeading(PI-heading);
+ else if(heading > -HALFPI && heading < 0.0f)
+ SetHeading(-PI-heading);
+ }else if(GetPosition().y < -1950.0f){
+ if(m_vecMoveSpeed.y < 0.0f)
+ m_vecMoveSpeed.y *= -1.0f;
+ heading = GetForward().Heading();
+ if(heading > HALFPI)
+ SetHeading(PI-heading);
+ else if(heading < -HALFPI)
+ SetHeading(-PI-heading);
+ }
+
+ if(bInfiniteMass){
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
+ }else if(!skipPhysics &&
+ (m_fGasPedal == 0.0f && brake == 0.0f || GetStatus() == STATUS_WRECKED)){
+ if(Abs(m_vecMoveSpeed.x) < 0.005f &&
+ Abs(m_vecMoveSpeed.y) < 0.005f &&
+ Abs(m_vecMoveSpeed.z) < 0.005f &&
+ !(m_fDamageImpulse > 0.0f && m_pDamageEntity == FindPlayerPed()) &&
+ (m_aSuspensionSpringRatioPrev[0] < 1.0f || m_aSuspensionSpringRatioPrev[1] < 1.0f ||
+ m_aSuspensionSpringRatioPrev[2] < 1.0f || m_aSuspensionSpringRatioPrev[3] < 1.0f)){
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed.z = 0.0f;
+ }
+ }
+
+ if(IsRealHeli() && bHeliDestroyed && !bRenderScorched){
+ ApplyMoveForce(0.0f, 0.0f, -2.0f*CTimer::GetTimeStep());
+ m_vecTurnSpeed.z += -0.002f*CTimer::GetTimeStep();
+ m_vecTurnSpeed.x += -0.0002f*CTimer::GetTimeStep();
+
+ RwRGBA col = { 84, 84, 84, 255 };
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, GetMatrix()*CVector(0.0f, 0.0f, -10.0f),
+ CVector(0.0f, 0.0f, 0.0f), nil, 0.7f, col, 0, 0, 0, 3000);
+
+ if(CWorld::TestSphereAgainstWorld(GetPosition(), 10.0f, this, true, false, false, false, false, false) ||
+ GetPosition().z < 6.0f)
+ if(!bRenderScorched){ // we already know this is true...
+ CExplosion::AddExplosion(this, nil, EXPLOSION_CAR, GetPosition(), 0);
+ bRenderScorched = true;
+ }
+ }
+}
+
+#pragma optimize("", on)
+
+void
+CAutomobile::Teleport(CVector pos)
+{
+ CWorld::Remove(this);
+
+ SetPosition(pos);
+ SetOrientation(0.0f, 0.0f, 0.0f);
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+
+ ResetSuspension();
+
+ CWorld::Add(this);
+}
+
+void
+CAutomobile::PreRender(void)
+{
+ int i, j, n;
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ if(GetModelIndex() == MI_RHINO && m_aCarNodes[CAR_WINDSCREEN]){
+ // Rotate Rhino turret
+ CMatrix m;
+ CVector p;
+ m.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN]));
+ p = m.GetPosition();
+ m.SetRotateZ(m_fCarGunLR);
+ m.Translate(p);
+ m.UpdateRW();
+ }
+
+ if(GetModelIndex() == MI_RCBANDIT){
+ CVector pos = GetMatrix() * CVector(0.218f, -0.444f, 0.391f);
+ CAntennas::RegisterOne((uintptr)this, GetUp(), pos, 1.0f);
+ }
+
+ float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward())*180.0f;
+
+
+ // Wheel particles
+
+ if(GetModelIndex() == MI_DODO || GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR){
+ ; // nothing
+ }else if(GetModelIndex() == MI_RCBANDIT){
+ for(i = 0; i < 4; i++){
+ // Game has same code three times here
+ switch(m_aWheelState[i]){
+ case WHEEL_STATE_SPINNING:
+ case WHEEL_STATE_SKIDDING:
+ case WHEEL_STATE_FIXED:
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.05f),
+ CVector(0.0f, 0.0f, 0.0f), nil, 0.1f);
+ break;
+ default: break;
+ }
+ }
+ }else{
+ if(GetStatus() == STATUS_SIMPLE){
+ CMatrix mat;
+ CVector pos;
+
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB]));
+ pos = mat.GetPosition();
+ pos.z = 1.5f*m_aWheelPosition[CARWHEEL_REAR_RIGHT];
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point = GetMatrix() * pos;
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB = SURFACE_DEFAULT;
+
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB]));
+ pos = mat.GetPosition();
+ pos.z = 1.5f*m_aWheelPosition[CARWHEEL_REAR_LEFT];
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].point = GetMatrix() * pos;
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB = SURFACE_DEFAULT;
+
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
+ pos = mat.GetPosition();
+ pos.z = 1.5f*m_aWheelPosition[CARWHEEL_FRONT_RIGHT];
+ m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point = GetMatrix() * pos;
+ m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB = SURFACE_DEFAULT;
+
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF]));
+ pos = mat.GetPosition();
+ pos.z = 1.5f*m_aWheelPosition[CARWHEEL_FRONT_LEFT];
+ m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point = GetMatrix() * pos;
+ m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB = SURFACE_DEFAULT;
+ }
+
+ int drawParticles = Abs(fwdSpeed) < 90.0f;
+ if(GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS ||
+ GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PLAYER_PLAYBACKFROMBUFFER){
+ bool rearSkidding = false;
+ if(m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SKIDDING ||
+ m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SKIDDING)
+ rearSkidding = true;
+
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatioPrev[i] < 1.0f && m_aWheelColPoints[i].surfaceB != SURFACE_WATER)
+ switch(m_aWheelState[i]){
+ case WHEEL_STATE_SPINNING:
+ if(AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles)){
+ CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE,
+ m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.0f));
+
+ CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE,
+ m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.05f));
+ }
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.0f));
+
+ if(m_aWheelTimer[i] > 0.0f)
+ CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point,
+ GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]);
+ break;
+
+ case WHEEL_STATE_SKIDDING:
+ if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT || rearSkidding){
+ // same as below
+
+ if(Abs(fwdSpeed) > 5.0f){
+ AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.0f));
+ }
+
+ if(m_aWheelTimer[i] > 0.0f)
+ CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point,
+ GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]);
+ }
+ break;
+
+ case WHEEL_STATE_FIXED:
+ if(Abs(fwdSpeed) > 5.0f){
+ AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.0f));
+ }
+
+ if(m_aWheelTimer[i] > 0.0f)
+ CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point,
+ GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]);
+ break;
+
+ default:
+ if(Abs(fwdSpeed) > 0.5f)
+ AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles);
+ if((m_aWheelSkidmarkBloody[i] || m_aWheelSkidmarkUnk[i]) && m_aWheelTimer[i] > 0.0f)
+ CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point,
+ GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]);
+ }
+
+ // Sparks for friction of burst wheels
+ if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST && m_aSuspensionSpringRatioPrev[i] < 1.0f){
+ static float speedSq;
+ speedSq = m_vecMoveSpeed.MagnitudeSqr();
+ if(speedSq > SQR(0.1f) &&
+ m_aWheelColPoints[i].surfaceB != SURFACE_GRASS &&
+ m_aWheelColPoints[i].surfaceB != SURFACE_MUD_DRY &&
+ m_aWheelColPoints[i].surfaceB != SURFACE_SAND &&
+ m_aWheelColPoints[i].surfaceB != SURFACE_SAND_BEACH &&
+ m_aWheelColPoints[i].surfaceB != SURFACE_WATER){
+ CVector normalSpeed = m_aWheelColPoints[i].normal * DotProduct(m_aWheelColPoints[i].normal, m_vecMoveSpeed);
+ CVector frictionSpeed = m_vecMoveSpeed - normalSpeed;
+ if(i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_REAR_LEFT)
+ frictionSpeed -= 0.05f*GetRight();
+ else
+ frictionSpeed += 0.05f*GetRight();
+ CVector unusedRight = 0.15f*GetRight();
+ CVector sparkDir = 0.25f*frictionSpeed;
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir);
+
+ if(speedSq > 0.04f)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir);
+ if(speedSq > 0.16f){
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir);
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(m_aCarNodes[CAR_WHEEL_RM]){
+ // assume middle wheels are two units before rear ones
+ CVector offset = GetForward()*2.0f;
+
+ switch(m_aWheelState[CARWHEEL_REAR_LEFT]){
+ // Game has same code three times here
+ case WHEEL_STATE_SPINNING:
+ case WHEEL_STATE_SKIDDING:
+ case WHEEL_STATE_FIXED:
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].point + CVector(0.0f, 0.0f, 0.25f) + offset,
+ CVector(0.0f, 0.0f, 0.0f));
+
+ if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f)
+ CSkidmarks::RegisterOne((uintptr)this + CARWHEEL_REAR_LEFT,
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].point + offset,
+ GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[CARWHEEL_REAR_LEFT], &m_aWheelSkidmarkBloody[CARWHEEL_REAR_LEFT]);
+ break;
+ default: break;
+ }
+
+ switch(m_aWheelState[CARWHEEL_REAR_RIGHT]){
+ // Game has same code three times here
+ case WHEEL_STATE_SPINNING:
+ case WHEEL_STATE_SKIDDING:
+ case WHEEL_STATE_FIXED:
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point + CVector(0.0f, 0.0f, 0.25f) + offset,
+ CVector(0.0f, 0.0f, 0.0f));
+
+ if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f)
+ CSkidmarks::RegisterOne((uintptr)this + CARWHEEL_REAR_RIGHT,
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point + offset,
+ GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[CARWHEEL_REAR_RIGHT], &m_aWheelSkidmarkBloody[CARWHEEL_REAR_RIGHT]);
+ break;
+ default: break;
+ }
+ }
+
+
+ // Rain on roof
+ if(!CCullZones::CamNoRain() && !CCullZones::PlayerNoRain() &&
+ Abs(fwdSpeed) < 20.0f && CWeather::Rain > 0.02f){
+ CColModel *colModel = GetColModel();
+
+ for(i = 0; i < colModel->numTriangles; i++){
+ CVector p1, p2, p3, c;
+
+ colModel->GetTrianglePoint(p1, colModel->triangles[i].a);
+ p1 = GetMatrix() * p1;
+ colModel->GetTrianglePoint(p2, colModel->triangles[i].b);
+ p2 = GetMatrix() * p2;
+ colModel->GetTrianglePoint(p3, colModel->triangles[i].c);
+ p3 = GetMatrix() * p3;
+ c = (p1 + p2 + p3)/3.0f;
+
+ n = 6.0f*CWeather::Rain;
+ for(j = 0; j <= n; j++)
+ CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP,
+ c + CVector(CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), 0.0f),
+ CVector(0.0f, 0.0f, 0.0f),
+ nil, 0.0f, 0, 0, CGeneral::GetRandomNumber() & 1);
+ }
+ }
+
+ AddDamagedVehicleParticles();
+
+ // Exhaust smoke
+ if(bEngineOn && !(pHandling->Flags & HANDLING_NO_EXHAUST) && fwdSpeed < 130.0f){
+ CVector exhaustPos = mi->m_positions[CAR_POS_EXHAUST];
+ CVector pos1, pos2, dir1, dir2;
+
+ if(exhaustPos != CVector(0.0f, 0.0f, 0.0f)){
+ dir1.z = 0.0f;
+ dir2.z = 0.0f;
+ if(fwdSpeed < 10.0f){
+ CVector steerFwd(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f);
+ steerFwd = Multiply3x3(GetMatrix(), steerFwd);
+ float r = CGeneral::GetRandomNumberInRange(-0.06f, -0.03f);
+ dir1.x = steerFwd.x * r;
+ dir1.y = steerFwd.y * r;
+ }else{
+ dir1.x = m_vecMoveSpeed.x;
+ dir1.y = m_vecMoveSpeed.y;
+ }
+
+ bool dblExhaust = false;
+ pos1 = GetMatrix() * exhaustPos;
+ if(pHandling->Flags & HANDLING_DBL_EXHAUST){
+ dblExhaust = true;
+ pos2 = exhaustPos;
+ pos2.x = -pos2.x;
+ pos2 = GetMatrix() * pos2;
+ dir2 = dir1;
+ }
+
+ static float fumesLimit = 2.0f;
+ if(CGeneral::GetRandomNumberInRange(1.0f, 3.0f)*(m_fGasPedal+1.1f) > fumesLimit)
+ for(i = 0; i < 4;){
+ CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir1);
+ if(pHandling->Flags & HANDLING_DBL_EXHAUST)
+ CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir2);
+
+ static float extraFumesLimit = 0.5f;
+ if(m_fGasPedal > extraFumesLimit && m_nCurrentGear < 3){
+ if(CGeneral::GetRandomNumber() & 1)
+ CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir1);
+ else if(pHandling->Flags & HANDLING_DBL_EXHAUST)
+ CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir2);
+ }
+
+ // Fire on Cuban hermes
+ if(GetModelIndex() == MI_CUBAN && i == 1 && m_fGasPedal > 0.9f){
+ if(m_nCurrentGear == 1 || m_nCurrentGear == 3 && (CTimer::GetTimeInMilliseconds()%1500) > 750){
+ if(CGeneral::GetRandomNumber() & 1){
+ CParticle::AddParticle(PARTICLE_FIREBALL, pos1, dir1, nil, 0.05f, 0, 0, 2, 200);
+ CParticle::AddParticle(PARTICLE_FIREBALL, pos1, dir1, nil, 0.05f, 0, 0, 2, 200);
+ }else{
+ CParticle::AddParticle(PARTICLE_FIREBALL, pos2, dir2, nil, 0.05f, 0, 0, 2, 200);
+ CParticle::AddParticle(PARTICLE_FIREBALL, pos2, dir2, nil, 0.05f, 0, 0, 2, 200);
+ }
+ }
+ }
+
+ if(GetStatus() == STATUS_PLAYER && (CTimer::GetFrameCounter()&3) == 0 &&
+ CWeather::Rain == 0.0f && i == 0){
+ CVector camDist = GetPosition() - TheCamera.GetPosition();
+ if(DotProduct(GetForward(), camDist) > 0.0f ||
+ TheCamera.GetLookDirection() == LOOKING_LEFT ||
+ TheCamera.GetLookDirection() == LOOKING_RIGHT){
+ CParticle::AddParticle(PARTICLE_HEATHAZE, pos1, CVector(0.0f, 0.0f, 0.0f));
+ if(pHandling->Flags & HANDLING_DBL_EXHAUST)
+ CParticle::AddParticle(PARTICLE_HEATHAZE, pos2, CVector(0.0f, 0.0f, 0.0f));
+
+ CParticle::AddParticle(PARTICLE_HEATHAZE, pos1, CVector(0.0f, 0.0f, 0.0f));
+ if(pHandling->Flags & HANDLING_DBL_EXHAUST)
+ CParticle::AddParticle(PARTICLE_HEATHAZE, pos2, CVector(0.0f, 0.0f, 0.0f));
+ }
+ }
+
+ if(GetModelIndex() == MI_CUBAN && i < 1){
+ i = 1;
+ pos1 = GetMatrix() * CVector(1.134f, -1.276f, -0.56f);
+ pos2 = GetMatrix() * CVector(-1.134f, -1.276f, -0.56f);
+ dir1 += 0.05f*GetRight();
+ dir2 -= 0.05f*GetRight();
+ }else
+ i = 99;
+ }
+ }
+ }
+
+
+ // Siren and taxi lights
+ switch(GetModelIndex()){
+ case MI_FIRETRUCK:
+ case MI_AMBULAN:
+ case MI_POLICE:
+ case MI_ENFORCER:
+ if(m_bSirenOrAlarm){
+ CVector pos1, pos2;
+ uint8 r1, g1, b1;
+ uint8 r2, g2, b2;
+ uint8 r, g, b;
+
+ switch(GetModelIndex()){
+ case MI_FIRETRUCK:
+ pos1 = CVector(1.1f, 1.7f, 2.0f);
+ pos2 = CVector(-1.1f, 1.7f, 2.0f);
+ r1 = 255; g1 = 0; b1 = 0;
+ r2 = 255; g2 = 255; b2 = 0;
+ break;
+ case MI_AMBULAN:
+ pos1 = CVector(1.1f, 0.9f, 1.6f);
+ pos2 = CVector(-1.1f, 0.9f, 1.6f);
+ r1 = 255; g1 = 0; b1 = 0;
+ r2 = 255; g2 = 255; b2 = 255;
+ break;
+ case MI_POLICE:
+ pos1 = CVector(0.7f, -0.4f, 1.0f);
+ pos2 = CVector(-0.7f, -0.4f, 1.0f);
+ r1 = 255; g1 = 0; b1 = 0;
+ r2 = 0; g2 = 0; b2 = 255;
+ break;
+ case MI_ENFORCER:
+ pos1 = CVector(1.1f, 0.8f, 1.2f);
+ pos2 = CVector(-1.1f, 0.8f, 1.2f);
+ r1 = 255; g1 = 0; b1 = 0;
+ r2 = 0; g2 = 0; b2 = 255;
+ break;
+ }
+
+ uint32 t = CTimer::GetTimeInMilliseconds() & 0x3FF; // 1023
+ if(t < 512){
+ r = r1/6;
+ g = g1/6;
+ b = b1/6;
+ }else{
+ r = r2/6;
+ g = g2/6;
+ b = b2/6;
+ }
+
+ t = CTimer::GetTimeInMilliseconds() & 0x1FF; // 511
+ if(t < 100){
+ float f = t/100.0f;
+ r *= f;
+ g *= f;
+ b *= f;
+ }else if(t > 412){
+ float f = (512-t)/100.0f;
+ r *= f;
+ g *= f;
+ b *= f;
+ }
+
+ CVector pos = GetPosition();
+ float angle = (CTimer::GetTimeInMilliseconds() & 0x3FF)*TWOPI/0x3FF;
+ float s = 8.0f*Sin(angle);
+ float c = 8.0f*Cos(angle);
+ //CShadows::StoreCarLightShadow(this, (uintptr)this + 21, gpShadowHeadLightsTex,
+ // &pos, c, s, s, -c, r, g, b, 8.0f);
+
+ CPointLights::AddLight(CPointLights::LIGHT_POINT,
+ pos + GetUp()*2.0f, CVector(0.0f, 0.0f, 0.0f), 12.0f,
+ r*0.02f, g*0.02f, b*0.02f, CPointLights::FOG_NONE, true);
+
+ pos1 = GetMatrix() * pos1;
+ pos2 = GetMatrix() * pos2;
+
+ for(i = 0; i < 4; i++){
+ uint8 sirenTimer = ((CTimer::GetTimeInMilliseconds() + (i<<6))>>8) & 3;
+ pos = (pos1*i + pos2*(3.0f-i))/3.0f;
+
+ switch(sirenTimer){
+ case 0:
+ CCoronas::RegisterCorona((uintptr)this + 21 + i,
+ r1, g1, b1, 255,
+ pos, 0.4f, 50.0f,
+ CCoronas::TYPE_STAR,
+ i == 1 ? CCoronas::FLARE_HEADLIGHTS : CCoronas::FLARE_NONE,
+ CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ break;
+ case 2:
+ CCoronas::RegisterCorona((uintptr)this + 21 + i,
+ r2, g2, b2, 255,
+ pos, 0.4f, 50.0f,
+ CCoronas::TYPE_STAR,
+ CCoronas::FLARE_NONE,
+ CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ break;
+ default:
+ CCoronas::UpdateCoronaCoors((uintptr)this + 21 + i, pos, 50.0f, 0.0f);
+ break;
+ }
+ }
+ }
+ break;
+
+ case MI_FBIRANCH:
+ case MI_VICECHEE:
+ if(m_bSirenOrAlarm){
+ CVector pos = GetMatrix() * CVector(0.4f, 0.6f, 0.3f);
+ if(CTimer::GetTimeInMilliseconds() & 0x100 &&
+ DotProduct(GetForward(), GetPosition() - TheCamera.GetPosition()) < 0.0f)
+ if(GetModelIndex() == MI_VICECHEE)
+ CCoronas::RegisterCorona((uintptr)this + 21,
+ 255, 70, 70, 255,
+ pos, 0.4f, 50.0f,
+ CCoronas::TYPE_STAR,
+ CCoronas::FLARE_NONE,
+ CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ else
+ CCoronas::RegisterCorona((uintptr)this + 21,
+ 0, 0, 255, 255,
+ pos, 0.4f, 50.0f,
+ CCoronas::TYPE_STAR,
+ CCoronas::FLARE_NONE,
+ CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ else
+ CCoronas::UpdateCoronaCoors((uintptr)this + 21, pos, 50.0f, 0.0f);
+ }
+ break;
+
+ case MI_TAXI:
+ case MI_CABBIE:
+ case MI_ZEBRA:
+ case MI_KAUFMAN:
+ if(bTaxiLight){
+ CVector pos = GetPosition() + GetUp()*0.95f;
+ CCoronas::RegisterCorona((uintptr)this + 21,
+ 128, 128, 0, 255,
+ pos, 0.8f, 50.0f,
+ CCoronas::TYPE_NORMAL,
+ CCoronas::FLARE_NONE,
+ CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ CPointLights::AddLight(CPointLights::LIGHT_POINT,
+ pos, CVector(0.0f, 0.0f, 0.0f), 10.0f,
+ 1.0f, 1.0f, 0.5f, CPointLights::FOG_NONE, true);
+ }
+ break;
+ }
+
+ if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_DODO &&
+ GetModelIndex() != MI_RHINO && GetModelIndex() != MI_RCBARON &&
+ GetVehicleAppearance() != VEHICLE_APPEARANCE_HELI) {
+ // Process lights
+
+ // Turn lights on/off
+ bool shouldLightsBeOn =
+ CClock::GetHours() > 20 ||
+ CClock::GetHours() > 19 && CClock::GetMinutes() > (m_randomSeed & 0x3F) ||
+ CClock::GetHours() < 7 ||
+ CClock::GetHours() < 8 && CClock::GetMinutes() < (m_randomSeed & 0x3F) ||
+ m_randomSeed/50000.0f < CWeather::Foggyness ||
+ m_randomSeed/50000.0f < CWeather::WetRoads;
+ if(shouldLightsBeOn != bLightsOn && GetStatus() != STATUS_WRECKED){
+ if(GetStatus() == STATUS_ABANDONED){
+ // Turn off lights on abandoned vehicles only when we they're far away
+ if(bLightsOn &&
+ Abs(TheCamera.GetPosition().x - GetPosition().x) + Abs(TheCamera.GetPosition().y - GetPosition().y) > 100.0f)
+ bLightsOn = false;
+ }else
+ bLightsOn = shouldLightsBeOn;
+ }
+
+ // Actually render the lights
+ bool alarmOn = false;
+ bool alarmOff = false;
+ if(IsAlarmOn()){
+ if(CTimer::GetTimeInMilliseconds() & 0x100)
+ alarmOn = true;
+ else
+ alarmOff = true;
+ }
+ if(bEngineOn && bLightsOn || alarmOn || alarmOff){
+ CVector lookVector = GetPosition() - TheCamera.GetPosition();
+ float camDist = lookVector.Magnitude();
+ if(camDist != 0.0f)
+ lookVector *= 1.0f/camDist;
+ else
+ lookVector = CVector(1.0f, 0.0f, 0.0f);
+
+ // 1.0 if directly behind car, -1.0 if in front
+ float behindness = DotProduct(lookVector, GetForward());
+ behindness = clamp(behindness, -1.0f, 1.0f); // shouldn't be necessary
+ // 0.0 if behind car, PI if in front
+ // Abs not necessary
+ float angle = Abs(Acos(behindness));
+
+ // Headlights
+
+ CVector headLightPos = mi->m_positions[CAR_POS_HEADLIGHTS];
+ CVector lightR = GetMatrix() * headLightPos;
+ CVector lightL = lightR;
+ lightL -= GetRight()*2.0f*headLightPos.x;
+
+ // Headlight coronas
+ if(DotProduct(lightR-TheCamera.GetPosition(), GetForward()) < 0.0f &&
+ (TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON || this != FindPlayerVehicle())){
+ // In front of car
+ float intensity = -0.5f*behindness + 0.3f;
+ float size = 1.0f - behindness;
+
+ if(behindness < -0.97f && camDist < 30.0f){
+ // Directly in front and not too far away
+ if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 6, 150, 150, 195, 255,
+ lightL, 1.2f, 45.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 7, 150, 150, 195, 255,
+ lightR, 1.2f, 45.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle);
+ }else{
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 6, 160, 160, 140, 255,
+ lightL, 1.2f, 45.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 7, 160, 160, 140, 255,
+ lightR, 1.2f, 45.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle);
+ }
+ }
+
+ if(alarmOff){
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this, 0, 0, 0, 0,
+ lightL, size, 0.0f,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 1, 0, 0, 0, 0,
+ lightR, size, 0.0f,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this, 190*intensity, 190*intensity, 255*intensity, 255,
+ lightL, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 1, 190*intensity, 190*intensity, 255*intensity, 255,
+ lightR, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this, 210*intensity, 210*intensity, 195*intensity, 255,
+ lightL, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 1, 210*intensity, 210*intensity, 195*intensity, 255,
+ lightR, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }
+ }
+ }else{
+ // Behind car
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this, lightL, 50.0f*TheCamera.LODDistMultiplier, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 1, lightR, 50.0f*TheCamera.LODDistMultiplier, angle);
+ }
+
+ // bright lights
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK && !bNoBrightHeadLights)
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT);
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK && !bNoBrightHeadLights)
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT);
+
+ // Taillights
+
+ CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS];
+ lightR = GetMatrix() * tailLightPos;
+ lightL = lightR;
+ lightL -= GetRight()*2.0f*tailLightPos.x;
+
+ // Taillight coronas
+ if(DotProduct(lightR-TheCamera.GetPosition(), GetForward()) > 0.0f){
+ // Behind car
+ float intensity = 0.4f*behindness + 0.4f;
+ float size = (behindness + 1.0f)/2.0f;
+
+ if(m_fGasPedal < 0.0f){
+ // reversing
+ intensity += 0.4f;
+ size += 0.3f;
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 2, 128*intensity, 128*intensity, 128*intensity, 255,
+ lightL, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 3, 128*intensity, 128*intensity, 128*intensity, 255,
+ lightR, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ if(m_fBrakePedal > 0.0f){
+ intensity += 0.4f;
+ size += 0.3f;
+ }
+
+ if(alarmOff){
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 2, 0, 0, 0, 0,
+ lightL, size, 0.0f,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 3, 0, 0, 0, 0,
+ lightR, size, 0.0f,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 2, 128*intensity, 0, 0, 255,
+ lightL, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 3, 128*intensity, 0, 0, 255,
+ lightR, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }
+ }
+ }else{
+ // In front of car
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 2, lightL, 50.0f*TheCamera.LODDistMultiplier, angle);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 3, lightR, 50.0f*TheCamera.LODDistMultiplier, angle);
+ }
+
+ // bright lights
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
+
+ // Light shadows
+ if(!alarmOff){
+ CVector pos = GetPosition();
+ CVector2D fwd(GetForward());
+ fwd.Normalise();
+ float f = headLightPos.y + 6.0f;
+ pos += CVector(f*fwd.x, f*fwd.y, 2.0f);
+
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK ||
+ Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CShadows::StoreCarLightShadow(this, (uintptr)this + 22, gpShadowHeadLightsTex, &pos,
+ 7.0f*fwd.x, 7.0f*fwd.y, 5.5f*fwd.y, -5.5f*fwd.x, 45, 45, 45, 7.0f);
+
+ f = (tailLightPos.y - 2.5f) - (headLightPos.y + 6.0f);
+ pos += CVector(f*fwd.x, f*fwd.y, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK ||
+ Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CShadows::StoreCarLightShadow(this, (uintptr)this + 25, gpShadowExplosionTex, &pos,
+ 3.0f, 0.0f, 0.0f, -3.0f, 35, 0, 0, 4.0f);
+ }
+
+ if(this == FindPlayerVehicle() && !alarmOff){
+ if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK ||
+ Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK)
+ CPointLights::AddLight(CPointLights::LIGHT_DIRECTIONAL, GetPosition(), GetForward(),
+ 20.0f, 1.0f, 1.0f, 1.0f,
+ FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.45f) ? CPointLights::FOG_NORMAL : CPointLights::FOG_NONE,
+ false);
+ CVector pos = GetPosition() - 4.0f*GetForward();
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK ||
+ Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) {
+ if(m_fBrakePedal > 0.0f)
+ CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f),
+ 10.0f, 1.0f, 0.0f, 0.0f,
+ CPointLights::FOG_NONE, false);
+ else
+ CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f),
+ 7.0f, 0.6f, 0.0f, 0.0f,
+ CPointLights::FOG_NONE, false);
+ }
+ }
+ }else if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED){
+ // Lights off
+
+ CVector lightPos = mi->m_positions[CAR_POS_TAILLIGHTS];
+ CVector lightR = GetMatrix() * lightPos;
+ CVector lightL = lightR;
+ lightL -= GetRight()*2.0f*lightPos.x;
+
+ if(m_fBrakePedal > 0.0f || m_fGasPedal < 0.0f){
+ CVector lookVector = GetPosition() - TheCamera.GetPosition();
+ lookVector.Normalise();
+ float behindness = DotProduct(lookVector, GetForward());
+ if(behindness > 0.0f){
+ if(m_fGasPedal < 0.0f){
+ // reversing
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 2, 120, 120, 120, 255,
+ lightL, 1.2f, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 3, 120, 120, 120, 255,
+ lightR, 1.2f, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT);
+ }else{
+ // braking
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 2, 120, 0, 0, 255,
+ lightL, 1.2f, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::RegisterCorona((uintptr)this + 3, 120, 0, 0, 255,
+ lightR, 1.2f, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
+ }
+ }else{
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 2, lightL, 50.0f*TheCamera.LODDistMultiplier, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 3, lightR, 50.0f*TheCamera.LODDistMultiplier, 0.0f);
+ }
+ }else{
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 2, lightL, 50.0f*TheCamera.LODDistMultiplier, 0.0f);
+ if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK)
+ CCoronas::UpdateCoronaCoors((uintptr)this + 3, lightR, 50.0f*TheCamera.LODDistMultiplier, 0.0f);
+ }
+ }
+ // end of lights
+ }
+
+ if (IsRealHeli())
+ CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_HELI);
+ else if ( GetModelIndex() == MI_RCBARON)
+ CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_RCPLANE);
+ else
+ CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_CAR);
+
+ DoSunGlare();
+
+ // Heli dust
+ if(IsRealHeli() && m_aWheelSpeed[1] > 0.1125f && GetPosition().z < 30.0f){
+ bool foundGround = false;
+ float waterZ = -1000.0f;
+ float groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, GetPosition().z, &foundGround);
+ if(!CWaterLevel::GetWaterLevel(GetPosition(), &waterZ, false))
+ waterZ = 0.0f;
+ groundZ = Max(groundZ, waterZ);
+ float rnd = (m_aWheelSpeed[1]-0.1125f)*((int)Max(16.0f-4.0f*CTimer::GetTimeStep(),2.0f))*400.0f/43.0f;
+ float radius = 10.0f;
+ if(GetModelIndex() == MI_RCGOBLIN || GetModelIndex() == MI_RCRAIDER)
+ radius = 3.0f;
+ if(GetPosition().z - groundZ < radius)
+ HeliDustGenerate(this, radius-(GetPosition().z - groundZ), groundZ, Ceil(rnd));
+ }
+
+ CMatrix mat;
+ CVector pos;
+
+ bool onlyFrontWheels = false;
+ if(IsRealHeli()){
+ // top rotor
+ m_aWheelRotation[1] += m_aWheelSpeed[1]*CTimer::GetTimeStep();
+ if(m_aWheelRotation[1] > TWOPI) m_aWheelRotation[1] -= TWOPI;
+ // rear rotor
+ m_aWheelRotation[3] += m_aWheelSpeed[1]*CTimer::GetTimeStep();
+ if(m_aWheelRotation[3] > TWOPI) m_aWheelRotation[3] -= TWOPI;
+ onlyFrontWheels = true;
+ }
+
+ CVector contactPoints[4]; // relative to model
+ CVector contactSpeeds[4]; // speed at contact points
+ CVector frontWheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f));
+ CVector rearWheelFwd = GetForward();
+ for(i = 0; i < 4; i++){
+ if (m_aWheelTimer[i] > 0.0f && (!onlyFrontWheels || i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT)) {
+ contactPoints[i] = m_aWheelColPoints[i].point - GetPosition();
+ contactSpeeds[i] = GetSpeed(contactPoints[i]);
+ if (i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT)
+ m_aWheelSpeed[i] = ProcessWheelRotation(m_aWheelState[i], frontWheelFwd, contactSpeeds[i], 0.5f*mi->m_wheelScale);
+ else
+ m_aWheelSpeed[i] = ProcessWheelRotation(m_aWheelState[i], rearWheelFwd, contactSpeeds[i], 0.5f*mi->m_wheelScale);
+ m_aWheelRotation[i] += m_aWheelSpeed[i];
+ }
+ }
+
+ RwRGBA hoverParticleCol = { 255, 255, 255, 32 };
+
+ // Rear right wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_REAR_RIGHT];
+ if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_REAR_RIGHT], 0.0f, 0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_RIGHT]));
+ else
+ mat.SetRotateX(m_aWheelRotation[CARWHEEL_REAR_RIGHT]);
+ if(GetStatus() == STATUS_PLAYER){
+ if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_RIGHT] < 1.0f &&
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB == SURFACE_WATER){
+ // hovering on water
+ mat.RotateY(-HALFPI);
+ if((CTimer::GetFrameCounter()+CARWHEEL_REAR_RIGHT) & 1){
+ CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point,
+ 0.5f*m_vecMoveSpeed+0.1f*GetRight(), nil, 0.4f, hoverParticleCol);
+ }else{
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point,
+ 0.3f*m_vecMoveSpeed+0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol,
+ CGeneral::GetRandomNumberInRange(0.0f, 90.0f),
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f), 1);
+ }
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) {
+ mat.RotateY(-HALFPI);
+#endif
+ }else{
+ // tilt wheel depending oh how much it presses on ground
+ float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale;
+ if(GetModelIndex() == MI_VOODOO)
+ groundOffset *= 0.6f;
+ mat.RotateY(Asin(clamp(-groundOffset, -1.0f, 1.0f)));
+ }
+ }
+ if(pHandling->Flags & HANDLING_FAT_REARW)
+ mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale);
+ else
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // Rear left wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_REAR_LEFT];
+ if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI+0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_LEFT]));
+ else
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI);
+ if(GetStatus() == STATUS_PLAYER){
+ if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_LEFT] < 1.0f &&
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB == SURFACE_WATER){
+ // hovering on water
+ mat.RotateY(HALFPI);
+ if((CTimer::GetFrameCounter()+CARWHEEL_REAR_LEFT) & 1){
+ CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_REAR_LEFT].point,
+ 0.5f*m_vecMoveSpeed-0.1f*GetRight(), nil, 0.4f, hoverParticleCol);
+ }else{
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_REAR_LEFT].point,
+ 0.3f*m_vecMoveSpeed-0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol,
+ CGeneral::GetRandomNumberInRange(0.0f, 90.0f),
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f), 1);
+ }
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) {
+ mat.RotateY(HALFPI);
+#endif
+ }else{
+ // tilt wheel depending oh how much it presses on ground
+ float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale;
+ if(GetModelIndex() == MI_VOODOO)
+ groundOffset *= 0.6f;
+ mat.RotateY(Asin(clamp(groundOffset, -1.0f, 1.0f)));
+ }
+ }
+ if(pHandling->Flags & HANDLING_FAT_REARW)
+ mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale);
+ else
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // Mid right wheel
+ if(m_aCarNodes[CAR_WHEEL_RM]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_REAR_RIGHT];
+ if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_REAR_RIGHT], 0.0f, 0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_RIGHT]));
+ else
+ mat.SetRotateX(m_aWheelRotation[CARWHEEL_REAR_RIGHT]);
+ if(GetStatus() == STATUS_PLAYER){
+ if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_RIGHT] < 1.0f &&
+ m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB == SURFACE_WATER){
+ // hovering on water
+ mat.RotateY(-HALFPI);
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) {
+ mat.RotateY(-HALFPI);
+#endif
+ }else{
+ // tilt wheel depending oh how much it presses on ground
+ float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale;
+ if(GetModelIndex() == MI_VOODOO)
+ groundOffset *= 0.6f;
+ mat.RotateY(Asin(clamp(-groundOffset, -1.0f, 1.0f)));
+ }
+ }
+ if(pHandling->Flags & HANDLING_FAT_REARW)
+ mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale);
+ else
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+
+ // Mid left wheel
+ if(m_aCarNodes[CAR_WHEEL_LM]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_REAR_LEFT];
+ if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI+0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_LEFT]));
+ else
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI);
+ if(GetStatus() == STATUS_PLAYER){
+ if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_LEFT] < 1.0f &&
+ m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB == SURFACE_WATER){
+ // hovering on water
+ mat.RotateY(HALFPI);
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) {
+ mat.RotateY(HALFPI);
+#endif
+ }else{
+ // tilt wheel depending oh how much it presses on ground
+ float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale;
+ if(GetModelIndex() == MI_VOODOO)
+ groundOffset *= 0.6f;
+ mat.RotateY(Asin(clamp(groundOffset, -1.0f, 1.0f)));
+ }
+ }
+ if(pHandling->Flags & HANDLING_FAT_REARW)
+ mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale);
+ else
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+
+ if(GetModelIndex() == MI_DODO){
+ // Front wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT];
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_RIGHT]));
+ else
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle);
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // Rotate propeller
+ if(m_aCarNodes[CAR_WINDSCREEN]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN]));
+ pos = mat.GetPosition();
+ mat.SetRotateY(m_fPropellerRotation);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ m_fPropellerRotation += m_fGasPedal != 0.0f ? TWOPI/13.0f : TWOPI/26.0f;
+ if(m_fPropellerRotation > TWOPI)
+ m_fPropellerRotation -= TWOPI;
+ }
+
+ // Rudder
+ if(Damage.GetDoorStatus(DOOR_BOOT) != DOOR_STATUS_MISSING && m_aCarNodes[CAR_BOOT]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT]));
+ pos = mat.GetPosition();
+ mat.SetRotate(0.0f, 0.0f, -m_fSteerAngle);
+ mat.Rotate(0.0f, Sin(m_fSteerAngle)*DEGTORAD(22.0f), 0.0f);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+
+ ProcessSwingingDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+ ProcessSwingingDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+ }else if(GetModelIndex() == MI_RHINO){
+ // Front right wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT];
+ // no damaged wheels or steering
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, 0.0f);
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // Front left wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_FRONT_LEFT];
+ // no damaged wheels or steering
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI);
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }else if(IsRealHeli()){
+ // Top rotor
+ if(m_aCarNodes[CAR_BONNET]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET]));
+ pos = mat.GetPosition();
+ mat.SetRotateZ(m_aWheelRotation[1]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+ // Blurred top rotor
+ if(m_aCarNodes[CAR_WINDSCREEN]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN]));
+ pos = mat.GetPosition();
+ mat.SetRotateZ(-m_aWheelRotation[1]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+ // Rear rotor
+ if(m_aCarNodes[CAR_BOOT]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT]));
+ pos = mat.GetPosition();
+ mat.SetRotateX(m_aWheelRotation[3]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+ // Blurred rear rotor
+ if(m_aCarNodes[CAR_BUMP_REAR]){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BUMP_REAR]));
+ pos = mat.GetPosition();
+ mat.SetRotateX(-m_aWheelRotation[3]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+ }else{
+ // Front right wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT];
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_RIGHT]));
+ else
+ mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle);
+ if(GetStatus() == STATUS_PLAYER){
+ if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_FRONT_RIGHT] < 1.0f &&
+ m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB == SURFACE_WATER){
+ // hovering on water
+ mat.RotateY(-HALFPI);
+ if((CTimer::GetFrameCounter()+CARWHEEL_FRONT_RIGHT) & 1){
+ CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point,
+ 0.5f*m_vecMoveSpeed+0.1f*GetRight(), nil, 0.4f, hoverParticleCol);
+ }else{
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point,
+ 0.3f*m_vecMoveSpeed+0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol,
+ CGeneral::GetRandomNumberInRange(0.0f, 90.0f),
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f), 1);
+ }
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) {
+ mat.RotateY(-HALFPI);
+#endif
+ }else{
+ // tilt wheel depending oh how much it presses on ground
+ float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale;
+ if(GetModelIndex() == MI_VOODOO)
+ groundOffset *= 0.6f;
+ mat.RotateY(Asin(clamp(-groundOffset, -1.0f, 1.0f)));
+ }
+ }
+ if(pHandling->Flags & HANDLING_NARROW_FRONTW)
+ mat.Scale(0.7f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale);
+ else
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // Front left wheel
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF]));
+ pos = mat.GetPosition();
+ pos.z = m_aWheelPosition[CARWHEEL_FRONT_LEFT];
+ if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST)
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI+m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_LEFT]));
+ else
+ mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI+m_fSteerAngle);
+ if(GetStatus() == STATUS_PLAYER){
+ if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_FRONT_LEFT] < 1.0f &&
+ m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB == SURFACE_WATER){
+ // hovering on water
+ mat.RotateY(HALFPI);
+ if((CTimer::GetFrameCounter()+CARWHEEL_FRONT_LEFT) & 1){
+ CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point,
+ 0.5f*m_vecMoveSpeed-0.1f*GetRight(), nil, 0.4f, hoverParticleCol);
+ }else{
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point,
+ 0.3f*m_vecMoveSpeed-0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol,
+ CGeneral::GetRandomNumberInRange(0.0f, 90.0f),
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f), 1);
+ }
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) {
+ mat.RotateY(HALFPI);
+#endif
+ }else{
+ // tilt wheel depending oh how much it presses on ground
+ float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale;
+ if(GetModelIndex() == MI_VOODOO)
+ groundOffset *= 0.6f;
+ mat.RotateY(Asin(clamp(groundOffset, -1.0f, 1.0f)));
+ }
+ }
+ if(pHandling->Flags & HANDLING_NARROW_FRONTW)
+ mat.Scale(0.7f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale);
+ else
+ mat.Scale(mi->m_wheelScale);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ ProcessSwingingDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+ ProcessSwingingDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+ ProcessSwingingDoor(CAR_DOOR_LR, DOOR_REAR_LEFT);
+ ProcessSwingingDoor(CAR_DOOR_RR, DOOR_REAR_RIGHT);
+ ProcessSwingingDoor(CAR_BONNET, DOOR_BONNET);
+ ProcessSwingingDoor(CAR_BOOT, DOOR_BOOT);
+ }
+
+ if((GetModelIndex() == MI_PHEONIX || GetModelIndex() == MI_BFINJECT) &&
+ GetStatus() == STATUS_PLAYER && m_aCarNodes[CAR_WING_LR]){
+ float rotation = 0.0f;
+
+ if(GetModelIndex() == MI_BFINJECT)
+ if(m_fPropellerRotation > TWOPI) m_fPropellerRotation -= TWOPI;
+
+ if(Abs(m_fGasPedal) > 0.0f){
+ if(GetModelIndex() == MI_BFINJECT){
+ m_fPropellerRotation += 0.2f*CTimer::GetTimeStep();
+ rotation = m_fPropellerRotation;
+ }else{
+ if(m_fPropellerRotation < 1.3f){
+ m_fPropellerRotation = Min(m_fPropellerRotation+0.1f*CTimer::GetTimeStep(), 1.3f);
+ rotation = m_fPropellerRotation;
+ }else{
+ float wave = Sin((CTimer::GetTimeInMilliseconds()%10000)/70.0f);
+ rotation = m_fPropellerRotation + 0.13*wave;
+ }
+ }
+ }else{
+ if(GetModelIndex() == MI_BFINJECT){
+ m_fPropellerRotation += 0.1f*CTimer::GetTimeStep();
+ rotation = m_fPropellerRotation;
+ }else{
+ if(m_fPropellerRotation > 0.0f){
+ m_fPropellerRotation = Max(m_fPropellerRotation-0.05f*CTimer::GetTimeStep(), 0.0f);
+ rotation = m_fPropellerRotation;
+ }
+ }
+ }
+
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WING_LR]));
+ pos = mat.GetPosition();
+ if(GetModelIndex() == MI_BFINJECT)
+ mat.SetRotateY(rotation);
+ else
+ mat.SetRotateX(rotation);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+}
+
+void
+CAutomobile::Render(void)
+{
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ mi->SetVehicleColour(m_currentColour1, m_currentColour2);
+
+ if(IsRealHeli()){
+ RpAtomic *atomic = nil;
+ int rotorAlpha = (1.5f - Min(1.7f*Max(m_aWheelSpeed[1],0.0f)/0.22f, 1.5f))*255.0f;
+ rotorAlpha = Min(rotorAlpha, 255);
+ int blurAlpha = Max(1.5f*m_aWheelSpeed[1]/0.22f - 0.4f, 0.0f)*150.0f;
+ blurAlpha = Min(blurAlpha, 150);
+
+ // Top rotor
+ if(m_aCarNodes[CAR_BONNET]){
+ RwFrameForAllObjects(m_aCarNodes[CAR_BONNET], GetCurrentAtomicObjectCB, &atomic);
+ if(atomic)
+ SetComponentAtomicAlpha(atomic, rotorAlpha);
+ }
+ atomic = nil;
+ // Rear rotor
+ if(m_aCarNodes[CAR_BOOT]){
+ RwFrameForAllObjects(m_aCarNodes[CAR_BOOT], GetCurrentAtomicObjectCB, &atomic);
+ if(atomic)
+ SetComponentAtomicAlpha(atomic, rotorAlpha);
+ }
+ atomic = nil;
+ // Blurred top rotor
+ if(m_aCarNodes[CAR_WINDSCREEN]){
+ RwFrameForAllObjects(m_aCarNodes[CAR_WINDSCREEN], GetCurrentAtomicObjectCB, &atomic);
+ if(atomic)
+ SetComponentAtomicAlpha(atomic, blurAlpha);
+ }
+ atomic = nil;
+ // Blurred rear rotor
+ if(m_aCarNodes[CAR_BUMP_REAR]){
+ RwFrameForAllObjects(m_aCarNodes[CAR_BUMP_REAR], GetCurrentAtomicObjectCB, &atomic);
+ if(atomic)
+ SetComponentAtomicAlpha(atomic, blurAlpha);
+ }
+ }
+
+ if(CVehicle::bWheelsOnlyCheat){
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]));
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]));
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]));
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]));
+ if(m_aCarNodes[CAR_WHEEL_RM])
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RM]));
+ if(m_aCarNodes[CAR_WHEEL_LM])
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LM]));
+ }else
+ CEntity::Render();
+}
+
+int32
+CAutomobile::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints)
+{
+ int i;
+ CColModel *colModel;
+
+ if(GetStatus() != STATUS_SIMPLE)
+ bVehicleColProcessed = true;
+
+ if(bUsingSpecialColModel)
+ colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel;
+ else
+ colModel = GetColModel();
+
+ int numWheelCollisions = 0;
+ float prevRatios[4] = { 0.0f, 0.0f, 0.0f, 0.0f};
+ for(i = 0; i < 4; i++)
+ prevRatios[i] = m_aSuspensionSpringRatio[i];
+
+ if(m_bIsVehicleBeingShifted || bSkipLineCol || ent->IsPed() ||
+ GetModelIndex() == MI_DODO && ent->IsVehicle())
+ colModel->numLines = 0;
+
+ int numCollisions = CCollision::ProcessColModels(GetMatrix(), *colModel,
+ ent->GetMatrix(), *ent->GetColModel(),
+ colpoints,
+ m_aWheelColPoints, m_aSuspensionSpringRatio);
+
+ // m_aSuspensionSpringRatio are now set to the point where the tyre touches ground.
+ // In ProcessControl these will be re-normalized to ignore the tyre radius.
+
+ if(colModel->numLines){
+ for(i = 0; i < 4; i++)
+ if(m_aSuspensionSpringRatio[i] < 1.0f && m_aSuspensionSpringRatio[i] < prevRatios[i]){
+ numWheelCollisions++;
+
+ // wheel is touching a physical
+ if(ent->IsVehicle() || ent->IsObject()){
+ CPhysical *phys = (CPhysical*)ent;
+
+ m_aGroundPhysical[i] = phys;
+ phys->RegisterReference((CEntity**)&m_aGroundPhysical[i]);
+ m_aGroundOffset[i] = m_aWheelColPoints[i].point - phys->GetPosition();
+ }
+
+ m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB;
+ if(ent->IsBuilding())
+ m_pCurGroundEntity = ent;
+ }
+ }else
+ colModel->numLines = 4;
+
+ if(numCollisions > 0 || numWheelCollisions > 0){
+ AddCollisionRecord(ent);
+ if(!ent->IsBuilding())
+ ((CPhysical*)ent)->AddCollisionRecord(this);
+
+ if(numCollisions > 0)
+ if(ent->IsBuilding() ||
+ ent->IsObject() && ((CPhysical*)ent)->bInfiniteMass)
+ bHasHitWall = true;
+ }
+
+ return numCollisions;
+}
+
+static int16 nLastControlInput;
+static float fMouseCentreRange = 0.35f;
+static float fMouseSteerSens = -0.0035f;
+static float fMouseCentreMult = 0.975f;
+
+void
+CAutomobile::ProcessControlInputs(uint8 pad)
+{
+ float speed = DotProduct(m_vecMoveSpeed, GetForward());
+
+ if(!CPad::GetPad(pad)->GetExitVehicle() ||
+ pDriver && pDriver->m_pVehicleAnim && (pDriver->m_pVehicleAnim->animId == ANIM_STD_ROLLOUT_LHS ||
+ pDriver->m_pVehicleAnim->animId == ANIM_STD_ROLLOUT_RHS))
+ bIsHandbrakeOn = !!CPad::GetPad(pad)->GetHandBrake();
+ else
+ bIsHandbrakeOn = true;
+
+ // Steer left/right
+ if(CCamera::m_bUseMouse3rdPerson && !CVehicle::m_bDisableMouseSteering){
+ if(CPad::GetPad(pad)->GetMouseX() != 0.0f){
+ m_fSteerInput += fMouseSteerSens*CPad::GetPad(pad)->GetMouseX();
+ nLastControlInput = 2;
+ if(Abs(m_fSteerInput) < fMouseCentreRange)
+ m_fSteerInput *= Pow(fMouseCentreMult, CTimer::GetTimeStep());
+ }else if(CPad::GetPad(pad)->GetSteeringLeftRight() || nLastControlInput != 2){
+ // mouse hasn't move, steer with pad like below
+ m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)*
+ 0.2f*CTimer::GetTimeStep();
+ nLastControlInput = 0;
+ }
+ }else{
+ m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)*
+ 0.2f*CTimer::GetTimeStep();
+ nLastControlInput = 0;
+ }
+ m_fSteerInput = clamp(m_fSteerInput, -1.0f, 1.0f);
+
+ // Accelerate/Brake
+ float acceleration = (CPad::GetPad(pad)->GetAccelerate() - CPad::GetPad(pad)->GetBrake())/255.0f;
+ if(GetModelIndex() == MI_DODO && acceleration < 0.0f)
+ acceleration *= 0.3f;
+ if(Abs(speed) < 0.01f){
+ // standing still, go into direction we want
+ if(CPad::GetPad(pad)->GetAccelerate() > 150.0f && CPad::GetPad(pad)->GetBrake() > 150.0f){
+ m_fGasPedal = CPad::GetPad(pad)->GetAccelerate()/255.0f;
+ m_fBrakePedal = CPad::GetPad(pad)->GetBrake()/255.0f;
+ m_doingBurnout = 1;
+ }else{
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }
+ }else{
+#if 1
+ // simpler than the code below
+ if(speed * acceleration < 0.0f){
+ // if opposite directions, have to brake first
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = Abs(acceleration);
+ }else{
+ // accelerating in same direction we were already going
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }
+#else
+ if(speed < 0.0f){
+ // moving backwards currently
+ if(acceleration < 0.0f){
+ // still go backwards
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }else{
+ // want to go forwards, so brake
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = acceleration;
+ }
+ }else{
+ // moving forwards currently
+ if(acceleration < 0.0f){
+ // want to go backwards, so brake
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = -acceleration;
+ }else{
+ // still go forwards
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }
+ }
+#endif
+ }
+
+ // Actually turn wheels
+ static float fValue; // why static?
+ if(m_fSteerInput < 0.0f)
+ fValue = -sq(m_fSteerInput);
+ else
+ fValue = sq(m_fSteerInput);
+ m_fSteerAngle = DEGTORAD(pHandling->fSteeringLock) * fValue;
+
+ if(bComedyControls){
+ int rnd = CGeneral::GetRandomNumber() % 10;
+ switch(m_comedyControlState){
+ case 0:
+ if(rnd < 2)
+ m_comedyControlState = 1;
+ else if(rnd < 4)
+ m_comedyControlState = 2;
+ break;
+ case 1:
+ m_fSteerAngle += 0.05f;
+ if(rnd < 2)
+ m_comedyControlState = 0;
+ break;
+ case 2:
+ m_fSteerAngle -= 0.05f;
+ if(rnd < 2)
+ m_comedyControlState = 0;
+ break;
+ }
+ }else
+ m_comedyControlState = 0;
+
+ // Brake if player isn't in control
+ // BUG: game always uses pad 0 here
+#ifdef FIX_BUGS
+ if(CPad::GetPad(pad)->ArePlayerControlsDisabled()){
+#else
+ if(CPad::GetPad(0)->ArePlayerControlsDisabled()){
+#endif
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ m_fGasPedal = 0.0f;
+
+ FindPlayerPed()->KeepAreaAroundPlayerClear();
+
+ // slow down car immediately
+ speed = m_vecMoveSpeed.Magnitude();
+ if(speed > 0.28f)
+ m_vecMoveSpeed *= 0.28f/speed;
+ }
+}
+
+void
+CAutomobile::FireTruckControl(void)
+{
+ if(this == FindPlayerVehicle()){
+ if(!CPad::GetPad(0)->GetCarGunFired())
+ return;
+#ifdef FREE_CAM
+ if (!CCamera::bFreeCam)
+#endif
+ {
+ m_fCarGunLR += CPad::GetPad(0)->GetCarGunLeftRight() * 0.00025f * CTimer::GetTimeStep();
+ m_fCarGunUD += CPad::GetPad(0)->GetCarGunUpDown() * 0.0001f * CTimer::GetTimeStep();
+ }
+ m_fCarGunUD = clamp(m_fCarGunUD, 0.05f, 0.3f);
+
+
+ CVector cannonPos(0.0f, 1.5f, 1.9f);
+ cannonPos = GetMatrix() * cannonPos;
+ CVector cannonDir(
+ Sin(m_fCarGunLR) * Cos(m_fCarGunUD),
+ Cos(m_fCarGunLR) * Cos(m_fCarGunUD),
+ Sin(m_fCarGunUD));
+ cannonDir = Multiply3x3(GetMatrix(), cannonDir);
+ cannonDir.z += (CGeneral::GetRandomNumber()&0xF)/1000.0f;
+ CWaterCannons::UpdateOne((uintptr)this, &cannonPos, &cannonDir);
+ }else if(GetStatus() == STATUS_PHYSICS){
+ CFire *fire = gFireManager.FindFurthestFire_NeverMindFireMen(GetPosition(), 10.0f, 35.0f);
+ if(fire == nil)
+ return;
+
+ // Target cannon onto fire
+ float targetAngle = CGeneral::GetATanOfXY(fire->m_vecPos.x-GetPosition().x, fire->m_vecPos.y-GetPosition().y);
+ float fwdAngle = CGeneral::GetATanOfXY(GetForward().x, GetForward().y);
+ float targetCannonAngle = fwdAngle - targetAngle;
+ float angleDelta = CTimer::GetTimeStep()*0.01f;
+ float cannonDelta = targetCannonAngle - m_fCarGunLR;
+ while(cannonDelta < PI) cannonDelta += TWOPI;
+ while(cannonDelta > PI) cannonDelta -= TWOPI;
+ if(Abs(cannonDelta) < angleDelta)
+ m_fCarGunLR = targetCannonAngle;
+ else if(cannonDelta > 0.0f)
+ m_fCarGunLR += angleDelta;
+ else
+ m_fCarGunLR -= angleDelta;
+
+ // Go up and down a bit
+ float upDown = Sin((float)(CTimer::GetTimeInMilliseconds() & 0xFFF)/0x1000 * TWOPI);
+ m_fCarGunUD = 0.2f + 0.2f*upDown;
+
+ // Spray water every once in a while
+ if((CTimer::GetTimeInMilliseconds()>>10) & 3){
+ CVector cannonPos(0.0f, 0.0f, 2.2f); // different position than player's firetruck!
+ cannonPos = GetMatrix() * cannonPos;
+ CVector cannonDir(
+ Sin(m_fCarGunLR) * Cos(m_fCarGunUD),
+ Cos(m_fCarGunLR) * Cos(m_fCarGunUD),
+ Sin(m_fCarGunUD));
+ cannonDir = Multiply3x3(GetMatrix(), cannonDir);
+ cannonDir.z += (CGeneral::GetRandomNumber()&0xF)/1000.0f;
+ CWaterCannons::UpdateOne((uintptr)this, &cannonPos, &cannonDir);
+ }
+ }
+}
+
+void
+CAutomobile::TankControl(void)
+{
+ int i;
+
+ // These coords are 1 unit higher then they should be relative to model center
+ CVector turrentBase(0.0f, -1.394f, 2.296f);
+ CVector gunEnd(0.0f, 1.813f, 2.979f);
+ CVector baseToEnd = gunEnd - turrentBase;
+
+ if(this != FindPlayerVehicle())
+ return;
+ if(CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING)
+ return;
+
+ // Rotate turret
+ float prevAngle = m_fCarGunLR;
+#ifdef FREE_CAM
+ if(!CCamera::bFreeCam)
+#endif
+ m_fCarGunLR -= CPad::GetPad(0)->GetCarGunLeftRight() * 0.00015f * CTimer::GetTimeStep();
+
+ if(m_fCarGunLR < 0.0f)
+ m_fCarGunLR += TWOPI;
+ if(m_fCarGunLR > TWOPI)
+ m_fCarGunLR -= TWOPI;
+ if(m_fCarGunLR != prevAngle)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TANK_TURRET_ROTATE, Abs(m_fCarGunLR - prevAngle));
+
+ // Shoot
+ if(CPad::GetPad(0)->CarGunJustDown() &&
+ CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeTankShotGun + 800){
+ CWorld::Players[CWorld::PlayerInFocus].m_nTimeTankShotGun = CTimer::GetTimeInMilliseconds();
+
+ // more like -sin(angle), cos(angle), i.e. rotated (0,1,0)
+ CVector turretDir = CVector(Sin(-m_fCarGunLR), Cos(-m_fCarGunLR), 0.0f);
+ turretDir = Multiply3x3(GetMatrix(), turretDir);
+
+ float c = Cos(m_fCarGunLR);
+ float s = Sin(m_fCarGunLR);
+ CVector rotatedEnd(
+ c*baseToEnd.x - s*baseToEnd.y,
+ s*baseToEnd.x + c*baseToEnd.y,
+ baseToEnd.z - 1.0f); // correct offset here
+ rotatedEnd += turrentBase;
+
+ CVector point1 = GetMatrix() * rotatedEnd;
+ CVector point2 = point1 + 60.0f*turretDir;
+ m_vecMoveSpeed -= 0.06f*turretDir;
+ m_vecMoveSpeed.z += 0.05f;
+
+ CWeapon::DoTankDoomAiming(FindPlayerVehicle(), FindPlayerPed(), &point1, &point2);
+ CColPoint colpoint;
+ CEntity *entity = nil;
+ CWorld::ProcessLineOfSight(point1, point2, colpoint, entity, true, true, true, true, true, true, false);
+ if(entity)
+ point2 = colpoint.point - 0.04f*(colpoint.point - point1);
+
+ CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_TANK_GRENADE, point2, 0);
+
+ // Add particles on the way to the explosion;
+ float shotDist = (point2 - point1).Magnitude();
+ int n = shotDist/4.0f;
+ RwRGBA black = { 0, 0, 0, 0 };
+ for(i = 0; i < n; i++){
+ float f = (float)i/n;
+ CParticle::AddParticle(PARTICLE_HELI_DUST,
+ point1 + f*(point2 - point1),
+ CVector(0.0f, 0.0f, 0.0f),
+ nil, 0.1f, black);
+ }
+
+ // More particles
+ CVector shotDir = point2 - point1;
+ shotDir.Normalise();
+ for(i = 0; i < 15; i++){
+ float f = i/15.0f;
+ CParticle::AddParticle(PARTICLE_GUNSMOKE2, point1,
+ shotDir*CGeneral::GetRandomNumberInRange(0.3f, 1.0f)*f,
+ nil, CGeneral::GetRandomNumberInRange(0.5f, 1.5f)*f, black);
+ }
+
+ // And some gun flashes near the gun
+ CVector flashPos = point1;
+ CVector nullDir(0.0f, 0.0f, 0.0f);
+ int lifeSpan = 250;
+ if(m_vecMoveSpeed.Magnitude() > 0.08f){
+ lifeSpan = 125;
+ flashPos.x += 5.0f*m_vecMoveSpeed.x;
+ flashPos.y += 5.0f*m_vecMoveSpeed.y;
+ }
+ CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.4f, black, 0, 0, 0, lifeSpan);
+ flashPos += 0.3f*shotDir;
+ CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.2f, black, 0, 0, 0, lifeSpan);
+ flashPos += 0.1f*shotDir;
+ CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.15f, black, 0, 0, 0, lifeSpan);
+ }
+}
+
+#define HYDRAULIC_UPPER_EXT (-0.16f)
+#define HYDRAULIC_LOWER_EXT (0.16f)
+
+void
+CAutomobile::HydraulicControl(void)
+{
+ int i;
+ float wheelPositions[4];
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ CColModel *normalColModel = mi->GetColModel();
+ float wheelRadius = 0.5f*mi->m_wheelScale;
+ CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus];
+ CColModel *specialColModel = &playerInfo->m_ColModel;
+
+ if(GetStatus() != STATUS_PLAYER){
+ // reset hydraulics for non-player cars
+
+ if(!bUsingSpecialColModel)
+ return;
+ if(specialColModel != nil) // this is always true
+ for(i = 0; i < 4; i++)
+ wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i];
+ for(i = 0; i < 4; i++){
+ m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit;
+ m_aSuspensionLineLength[i] = normalColModel->lines[i].p0.z - normalColModel->lines[i].p1.z;
+ m_aSuspensionSpringRatio[i] = (normalColModel->lines[i].p0.z - wheelPositions[i]) / m_aSuspensionLineLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+
+ if(m_hydraulicState == 0)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f);
+ else if(m_hydraulicState >= 100)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+
+ if(playerInfo->m_pVehicleEx == this)
+ playerInfo->m_pVehicleEx = nil;
+ bUsingSpecialColModel = false;
+ m_hydraulicState = 0;
+ return;
+ }
+
+ // Player car
+
+ float normalUpperLimit = pHandling->fSuspensionUpperLimit;
+ float normalLowerLimit = pHandling->fSuspensionLowerLimit;
+ float normalSpringLength = normalUpperLimit - normalLowerLimit;
+ float extendedUpperLimit = normalUpperLimit - 0.2f;
+ float extendedLowerLimit = normalLowerLimit - 0.2f;
+ float extendedSpringLength = extendedUpperLimit - extendedLowerLimit;
+
+ if(!bUsingSpecialColModel){
+ // Init special col model
+
+ if(playerInfo->m_pVehicleEx && playerInfo->m_pVehicleEx == this)
+ playerInfo->m_pVehicleEx->bUsingSpecialColModel = false;
+ playerInfo->m_pVehicleEx = this;
+ playerInfo->m_ColModel = *normalColModel;
+ bUsingSpecialColModel = true;
+ specialColModel = &playerInfo->m_ColModel;
+
+ if(m_fVelocityChangeForAudio > 0.1f)
+ m_hydraulicState = 20;
+ else{
+ m_hydraulicState = 0;
+ normalUpperLimit += HYDRAULIC_UPPER_EXT;
+ normalSpringLength = normalUpperLimit - (normalLowerLimit+HYDRAULIC_LOWER_EXT);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+ }
+
+ // Setup suspension
+ float normalLineLength = normalSpringLength + wheelRadius;
+ CVector pos;
+ for(i = 0; i < 4; i++){
+ wheelPositions[i] = normalColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i];
+ mi->GetWheelPosn(i, pos);
+ pos.z += normalUpperLimit;
+ specialColModel->lines[i].p0 = pos;
+ pos.z -= normalLineLength;
+ specialColModel->lines[i].p1 = pos;
+ m_aSuspensionSpringLength[i] = normalSpringLength;
+ m_aSuspensionLineLength[i] = normalLineLength;
+
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+ }
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+
+ // Adjust col model
+ mi->GetWheelPosn(0, pos);
+ float minz = pos.z + extendedLowerLimit - wheelRadius;
+ if(minz < specialColModel->boundingBox.min.z)
+ specialColModel->boundingBox.min.z = minz;
+ float radius = Max(specialColModel->boundingBox.min.Magnitude(), specialColModel->boundingBox.max.Magnitude());
+ if(specialColModel->boundingSphere.radius < radius)
+ specialColModel->boundingSphere.radius = radius;
+ return;
+ }
+
+ if(playerInfo->m_WBState != WBSTATE_PLAYING)
+ return;
+
+ bool setPrevRatio = false;
+ if(m_hydraulicState < 20 && m_fVelocityChangeForAudio > 0.2f){
+ if(m_hydraulicState == 0){
+ m_hydraulicState = 20;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f);
+ setPrevRatio = true;
+ }else{
+ m_hydraulicState++;
+ }
+ }else if(m_hydraulicState != 0){ // must always be true
+ if(m_hydraulicState < 21 && m_fVelocityChangeForAudio < 0.1f){
+ m_hydraulicState--;
+ if(m_hydraulicState == 0)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+ }
+ }
+
+ if(CPad::GetPad(0)->HornJustDown()){
+ // Switch between normal and extended
+
+ if(m_hydraulicState < 100)
+ m_hydraulicState = 100;
+ else{
+ if(m_fVelocityChangeForAudio > 0.1f)
+ m_hydraulicState = 20;
+ else
+ m_hydraulicState = 0;
+ }
+
+ if(m_hydraulicState < 100){
+ if(m_hydraulicState == 0){
+ normalUpperLimit += HYDRAULIC_UPPER_EXT;
+ normalLowerLimit += HYDRAULIC_LOWER_EXT;
+ normalSpringLength = normalUpperLimit - normalLowerLimit;
+ }
+
+ // Reset suspension to normal
+ float normalLineLength = normalSpringLength + wheelRadius;
+ CVector pos;
+ for(i = 0; i < 4; i++){
+ wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i];
+ mi->GetWheelPosn(i, pos);
+ pos.z += normalUpperLimit;
+ specialColModel->lines[i].p0 = pos;
+ pos.z -= normalLineLength;
+ specialColModel->lines[i].p1 = pos;
+ m_aSuspensionSpringLength[i] = normalSpringLength;
+ m_aSuspensionLineLength[i] = normalLineLength;
+
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+ }
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+ }else{
+ // Reset suspension to extended
+ float extendedLineLength = extendedSpringLength + wheelRadius;
+ CVector pos;
+ for(i = 0; i < 4; i++){
+ wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i];
+ mi->GetWheelPosn(i, pos);
+ pos.z += extendedUpperLimit;
+ specialColModel->lines[i].p0 = pos;
+ pos.z -= extendedLineLength;
+ specialColModel->lines[i].p1 = pos;
+ m_aSuspensionSpringLength[i] = extendedSpringLength;
+ m_aSuspensionLineLength[i] = extendedLineLength;
+
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+
+ setPrevRatio = true;
+ }
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+ }
+ }else{
+ float suspChange[4];
+ float maxDelta = 0.0f;
+ float rear = CPad::GetPad(0)->GetCarGunUpDown()/128.0f;
+ float front = -rear;
+ float right = CPad::GetPad(0)->GetCarGunLeftRight()/128.0f;
+ float left = -right;
+ suspChange[CARWHEEL_FRONT_LEFT] = Max(front+left, 0.0f);
+ suspChange[CARWHEEL_REAR_LEFT] = Max(rear+left, 0.0f);
+ suspChange[CARWHEEL_FRONT_RIGHT] = Max(front+right, 0.0f);
+ suspChange[CARWHEEL_REAR_RIGHT] = Max(rear+right, 0.0f);
+
+ if(m_hydraulicState < 100){
+ // Lowered, move wheels up
+
+ if(m_hydraulicState == 0){
+ normalUpperLimit += HYDRAULIC_UPPER_EXT;
+ normalLowerLimit += HYDRAULIC_LOWER_EXT;
+ normalSpringLength = normalUpperLimit - normalLowerLimit;
+ }
+
+ // Set suspension
+ CVector pos;
+ for(i = 0; i < 4; i++){
+ if(suspChange[i] > 1.0f)
+ suspChange[i] = 1.0f;
+
+ float upperLimit = suspChange[i]*(extendedUpperLimit-normalUpperLimit) + normalUpperLimit;
+ float springLength = suspChange[i]*(extendedSpringLength-normalSpringLength) + normalSpringLength;
+ float lineLength = springLength + wheelRadius;
+
+ wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i];
+ mi->GetWheelPosn(i, pos);
+ pos.z += upperLimit;
+ specialColModel->lines[i].p0 = pos;
+ pos.z -= lineLength;
+ if(Abs(pos.z - specialColModel->lines[i].p1.z) > Abs(maxDelta))
+ maxDelta = pos.z - specialColModel->lines[i].p1.z;
+ specialColModel->lines[i].p1 = pos;
+ m_aSuspensionSpringLength[i] = springLength;
+ m_aSuspensionLineLength[i] = lineLength;
+
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+ }
+ }else{
+ if(m_hydraulicState < 104)
+ m_hydraulicState++;
+
+ if(m_fVelocityChangeForAudio < 0.1f){
+ normalUpperLimit += HYDRAULIC_UPPER_EXT;
+ normalLowerLimit += HYDRAULIC_LOWER_EXT;
+ normalSpringLength = normalUpperLimit - normalLowerLimit;
+ }
+
+ // Set suspension
+ CVector pos;
+ for(i = 0; i < 4; i++){
+ if(suspChange[i] > 1.0f)
+ suspChange[i] = 1.0f;
+
+ float upperLimit = suspChange[i]*(normalUpperLimit-extendedUpperLimit) + extendedUpperLimit;
+ float springLength = suspChange[i]*(normalSpringLength-extendedSpringLength) + extendedSpringLength;
+ float lineLength = springLength + wheelRadius;
+
+ wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i];
+ mi->GetWheelPosn(i, pos);
+ pos.z += upperLimit;
+ specialColModel->lines[i].p0 = pos;
+ pos.z -= lineLength;
+ if(Abs(pos.z - specialColModel->lines[i].p1.z) > Abs(maxDelta))
+ maxDelta = pos.z - specialColModel->lines[i].p1.z;
+ specialColModel->lines[i].p1 = pos;
+ m_aSuspensionSpringLength[i] = springLength;
+ m_aSuspensionLineLength[i] = lineLength;
+
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+ }
+ }
+
+ float limitDiff = extendedLowerLimit - normalLowerLimit;
+ if(limitDiff != 0.0f && Abs(maxDelta/limitDiff) > 0.01f){
+ float f = (maxDelta + limitDiff)/2.0f/limitDiff;
+ f = clamp(f, 0.0f, 1.0f);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_3, f);
+ if(f < 0.4f || f > 0.6f)
+ setPrevRatio = true;
+ if(f < 0.25f)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f);
+ else if(f > 0.75f)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f);
+ }
+ }
+
+ if(setPrevRatio)
+ for(i = 0; i < 4; i++){
+ // wheel radius in relation to suspension line
+ float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i];
+ m_aSuspensionSpringRatioPrev[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius);
+ }
+}
+
+void
+CAutomobile::ProcessBuoyancy(void)
+{
+ int i;
+ CVector impulse, point;
+
+ if(mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)){
+ float timeStep = Max(CTimer::GetTimeStep(), 0.01f);
+ float impulseRatio = impulse.z / (GRAVITY * m_fMass * timeStep);
+ float waterResistance = Pow(1.0f - 0.05f*impulseRatio, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= waterResistance;
+ m_vecTurnSpeed *= waterResistance;
+
+ bool heliHitWaterHard = false;
+ if(IsRealHeli() && m_aWheelSpeed[1] > 0.15f){
+ if(GetModelIndex() == MI_SEASPAR){
+ if(impulseRatio > 3.0f){
+ m_aWheelSpeed[1] = 0.0f;
+ heliHitWaterHard = true;
+ }
+ }else{
+ float strength = 1.0f/Max(8.0f*impulseRatio, 1.0f);
+ ApplyMoveForce(-2.0f*impulse/strength);
+ ApplyTurnForce(-impulse/strength, point);
+ if(impulseRatio > 0.9f){
+ m_aWheelSpeed[1] = 0.0f;
+ heliHitWaterHard = true;
+ }else
+ return;
+ }
+ }
+
+ bTouchingWater = true;
+ ApplyMoveForce(impulse);
+ ApplyTurnForce(impulse, point);
+ CVector initialSpeed = m_vecMoveSpeed;
+
+ if(m_modelIndex == MI_SEASPAR && impulseRatio < 3.0f && (GetUp().z > -0.5f || impulseRatio < 0.6f) ||
+ CVehicle::bHoverCheat && GetStatus() == STATUS_PLAYER && GetUp().z > 0.1f){
+ bIsInWater = false;
+ bIsDrowning = false;
+ }else if(heliHitWaterHard || impulseRatio > 1.0f ||
+ impulseRatio > 0.6f && (m_aSuspensionSpringRatio[0] == 1.0f ||
+ m_aSuspensionSpringRatio[1] == 1.0f ||
+ m_aSuspensionSpringRatio[2] == 1.0f ||
+ m_aSuspensionSpringRatio[3] == 1.0f)){
+ bIsInWater = true;
+ bIsDrowning = true;
+ if(m_vecMoveSpeed.z < -0.1f)
+ m_vecMoveSpeed.z = -0.1f;
+
+ if(pDriver){
+ pDriver->bIsInWater = true;
+ if(pDriver->IsPlayer() || !bWaterTight)
+ pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0);
+ }
+ for(i = 0; i < m_nNumMaxPassengers; i++)
+ if(pPassengers[i]){
+ pPassengers[i]->bIsInWater = true;
+ if(pPassengers[i]->IsPlayer() || !bWaterTight)
+ pPassengers[i]->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0);
+ }
+ }else{
+ bIsInWater = false;
+ bIsDrowning = false;
+ }
+
+ static uint32 nGenerateRaindrops = 0;
+ static uint32 nGenerateWaterCircles = 0;
+
+ if(initialSpeed.z < -0.1f && impulse.z > 0.3f || heliHitWaterHard){
+ RwRGBA color;
+ color.red = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj())*0.45f*255;
+ color.green = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj())*0.45f*255;
+ color.blue = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj())*0.45f*255;
+ color.alpha = CGeneral::GetRandomNumberInRange(0, 32) + 128;
+ CVector target = CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.15f, 0.45f));
+ CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition(),
+ target, 0.0f, 75, color, true);
+
+ nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 300;
+ nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 60;
+
+ if(heliHitWaterHard){
+ CVector right = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f));
+ CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition() + right,
+ target, 0.0f, 75, color, true);
+ CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition() - right,
+ target, 0.0f, 75, color, true);
+ }
+
+ if(m_vecMoveSpeed.z < -0.2f)
+ m_vecMoveSpeed.z = -0.2f;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WATER_FALL, 0.0f);
+ }
+
+ if(nGenerateWaterCircles > 0 && nGenerateWaterCircles <= CTimer::GetTimeInMilliseconds()){
+ CVector pos = GetPosition();
+ float waterLevel = 0.0f;
+ if(CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false))
+ pos.z = waterLevel;
+ static RwRGBA black;
+ if(pos.z != 0.0f){
+ nGenerateWaterCircles = 0;
+ pos.z += 1.0f;
+ for(i = 0; i < 4; i++){
+ CVector p = pos;
+ p.x += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f);
+ p.y += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f);
+ CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW,
+ p, CVector(0.0f, 0.0f, 0.0f),
+ nil, 0.0f, black, 0, 0, 0, 0);
+ }
+ }
+ }
+
+ if(nGenerateRaindrops > 0 && nGenerateRaindrops <= CTimer::GetTimeInMilliseconds()){
+ CVector pos = GetPosition();
+ float waterLevel = 0.0f;
+ if(CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false))
+ pos.z = waterLevel;
+ static RwRGBA black;
+ if(pos.z >= 0.0f){
+ nGenerateRaindrops = 0;
+ pos.z += 0.5f;
+ CParticleObject::AddObject(POBJECT_SPLASHES_AROUND,
+ pos, CVector(0.0f, 0.0f, 0.0f), 6.5f, 2500, black, true);
+ }
+ }
+ }else{
+ bIsInWater = false;
+ bIsDrowning = false;
+ bTouchingWater = false;
+
+ static RwRGBA splashCol = {155, 155, 185, 196};
+ static RwRGBA smokeCol = {255, 255, 255, 255};
+
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatio[i] < 1.0f && m_aWheelColPoints[i].surfaceB == SURFACE_WATER){
+ CVector pos = m_aWheelColPoints[i].point + 0.3f*GetUp() - GetPosition();
+ CVector vSpeed = GetSpeed(pos);
+ vSpeed.z = 0.0f;
+ float fSpeed = vSpeed.MagnitudeSqr();
+ if(fSpeed > sq(0.05f)){
+ fSpeed = Sqrt(fSpeed);
+
+ float size = Min((fSpeed < 0.15f ? 0.25f : 0.75f)*fSpeed, 0.6f);
+ CVector right = 0.2f*fSpeed*GetRight() + 0.2f*vSpeed;
+
+ CParticle::AddParticle(PARTICLE_PED_SPLASH,
+ pos + GetPosition(), -0.5f*right,
+ nil, size, splashCol,
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f),
+ CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1, 0);
+
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ pos + GetPosition(), -0.6f*right,
+ nil, size, smokeCol, 0, 0, 0, 0);
+
+ if((CTimer::GetFrameCounter() & 0xF) == 0)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, 2000.0f*fSpeed);
+ }
+ }
+ }
+ }
+}
+
+void
+CAutomobile::DoDriveByShootings(void)
+{
+ CAnimBlendAssociation *anim = nil;
+ CPlayerInfo* playerInfo = ((CPlayerPed*)pDriver)->GetPlayerInfoForThisPlayerPed();
+ if (playerInfo && !playerInfo->m_bDriveByAllowed)
+ return;
+
+ CWeapon *weapon = pDriver->GetWeapon();
+ if(CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->m_nWeaponSlot != WEAPONSLOT_SUBMACHINEGUN)
+ return;
+
+ weapon->Update(pDriver->m_audioEntityId, nil);
+
+ bool lookingLeft = false;
+ bool lookingRight = false;
+ if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
+ TheCamera.m_bObbeCinematicCarCamOn){
+ if(CPad::GetPad(0)->GetLookLeft())
+ lookingLeft = true;
+ if(CPad::GetPad(0)->GetLookRight())
+ lookingRight = true;
+ }else{
+ if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft)
+ lookingLeft = true;
+ if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight)
+ lookingRight = true;
+ }
+
+ AnimationId rightAnim = ANIM_STD_CAR_DRIVEBY_RIGHT;
+ AnimationId leftAnim = ANIM_STD_CAR_DRIVEBY_LEFT;
+ if (pDriver->m_pMyVehicle->bLowVehicle) {
+ rightAnim = ANIM_STD_CAR_DRIVEBY_RIGHT_LO;
+ leftAnim = ANIM_STD_CAR_DRIVEBY_LEFT_LO;
+ }
+
+ if(lookingLeft || lookingRight){
+ if(lookingLeft){
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), rightAnim);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), leftAnim);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, leftAnim);
+ }else if(pDriver->m_pMyVehicle->pPassengers[0] == nil || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), leftAnim);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), rightAnim);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, rightAnim);
+ }
+
+ if (!anim || !anim->IsRunning()) {
+ if (CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer) {
+ weapon->FireFromCar(this, lookingLeft, true);
+ weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70;
+ }
+ }
+ }else{
+ weapon->Reload();
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), leftAnim);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), rightAnim);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ }
+
+ // TODO: what is this?
+ if(!lookingLeft && m_weaponDoorTimerLeft > 0.0f){
+ m_weaponDoorTimerLeft = Max(m_weaponDoorTimerLeft - CTimer::GetTimeStep()*0.1f, 0.0f);
+ ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_NUM, m_weaponDoorTimerLeft);
+ }
+ if(!lookingRight && m_weaponDoorTimerRight > 0.0f){
+ m_weaponDoorTimerRight = Max(m_weaponDoorTimerRight - CTimer::GetTimeStep()*0.1f, 0.0f);
+ ProcessOpenDoor(CAR_DOOR_RF, ANIM_STD_NUM, m_weaponDoorTimerRight);
+ }
+}
+
+void
+CAutomobile::DoHoverSuspensionRatios(void)
+{
+ int i;
+
+ if(GetUp().z < 0.1f)
+ return;
+
+ CColModel *colmodel = GetColModel();
+ for(i = 0; i < 4; i++){
+ float z, waterZ;
+ CVector upper = GetMatrix() * colmodel->lines[i].p0;
+ CVector lower = GetMatrix() * colmodel->lines[i].p1;
+ if(m_aSuspensionSpringRatio[i] < 1.0f)
+ z = m_aWheelColPoints[i].point.z;
+ else
+ z = -100.0f;
+ // see if touching water
+ if(CWaterLevel::GetWaterLevel(lower, &waterZ, false) &&
+ waterZ > z && lower.z-1.0f < waterZ){
+ // compress spring
+ if(lower.z < waterZ){
+ if(upper.z < waterZ)
+ m_aSuspensionSpringRatio[i] = 0.0f;
+ else
+ m_aSuspensionSpringRatio[i] = (upper.z - waterZ)/(upper.z - lower.z);
+ }else
+ m_aSuspensionSpringRatio[i] = 0.99999f;
+
+ m_aWheelColPoints[i].point.x = (lower.x - upper.x)*m_aSuspensionSpringRatio[i] + upper.x;
+ m_aWheelColPoints[i].point.y = (lower.y - upper.y)*m_aSuspensionSpringRatio[i] + upper.y;
+ m_aWheelColPoints[i].point.z = waterZ;
+ m_aWheelColPoints[i].normal = CVector(0.01f, 0.0f, 1.0f);
+ m_aWheelColPoints[i].surfaceB = SURFACE_WATER;
+ }
+ }
+}
+
+int32
+CAutomobile::RcbanditCheckHitWheels(void)
+{
+ int x, xmin, xmax;
+ int y, ymin, ymax;
+
+ xmin = CWorld::GetSectorIndexX(GetPosition().x - 2.0f);
+ if(xmin < 0) xmin = 0;
+ xmax = CWorld::GetSectorIndexX(GetPosition().x + 2.0f);
+ if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1;
+ ymin = CWorld::GetSectorIndexY(GetPosition().y - 2.0f);
+ if(ymin < 0) ymin = 0;
+ ymax = CWorld::GetSectorIndexY(GetPosition().y + 2.0f);
+ if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1;
+
+ CWorld::AdvanceCurrentScanCode();
+
+ for(y = ymin; y <= ymax; y++)
+ for(x = xmin; x <= xmax; x++){
+ CSector *s = CWorld::GetSector(x, y);
+ if(RcbanditCheck1CarWheels(s->m_lists[ENTITYLIST_VEHICLES]) ||
+ RcbanditCheck1CarWheels(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]))
+ return 1;
+ }
+ return 0;
+}
+
+int32
+CAutomobile::RcbanditCheck1CarWheels(CPtrList &list)
+{
+ static CMatrix matW2B;
+ int i;
+ CPtrNode *node;
+ CAutomobile *car;
+ CColModel *colModel = GetColModel();
+ CVehicleModelInfo *mi;
+
+ for(node = list.first; node; node = node->next){
+ car = (CAutomobile*)node->item;
+ if(this != car && car->IsCar() && car->GetModelIndex() != MI_RCBANDIT &&
+ car->m_scanCode != CWorld::GetCurrentScanCode()){
+ car->m_scanCode = CWorld::GetCurrentScanCode();
+
+ if(Abs(this->GetPosition().x - car->GetPosition().x) < 10.0f &&
+ Abs(this->GetPosition().y - car->GetPosition().y) < 10.0f){
+ mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(car->GetModelIndex());
+
+ for(i = 0; i < 4; i++){
+ if(car->m_aSuspensionSpringRatioPrev[i] < 1.0f || car->GetStatus() == STATUS_SIMPLE){
+ CVector wheelPos;
+ CColSphere sph;
+ mi->GetWheelPosn(i, wheelPos);
+ matW2B = Invert(GetMatrix());
+ sph.center = matW2B * (car->GetMatrix() * wheelPos);
+ sph.radius = mi->m_wheelScale*0.25f;
+ if(CCollision::TestSphereBox(sph, colModel->boundingBox))
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+void
+CAutomobile::PlaceOnRoadProperly(void)
+{
+ CColPoint point;
+ CEntity *entity;
+ CColModel *colModel = GetColModel();
+ float lenFwd, lenBack;
+ float frontZ, rearZ;
+
+ lenFwd = colModel->boundingBox.max.y;
+ lenBack = -colModel->boundingBox.min.y;
+
+ CVector front(GetPosition().x + GetForward().x*lenFwd,
+ GetPosition().y + GetForward().y*lenFwd,
+ GetPosition().z + 5.0f);
+ if(CWorld::ProcessVerticalLine(front, GetPosition().z - 5.0f, point, entity,
+ true, false, false, false, false, false, nil)){
+ frontZ = point.point.z;
+ m_pCurGroundEntity = entity;
+ }else{
+ frontZ = m_fMapObjectHeightAhead;
+ }
+
+ CVector rear(GetPosition().x - GetForward().x*lenBack,
+ GetPosition().y - GetForward().y*lenBack,
+ GetPosition().z + 5.0f);
+ if(CWorld::ProcessVerticalLine(rear, GetPosition().z - 5.0f, point, entity,
+ true, false, false, false, false, false, nil)){
+ rearZ = point.point.z;
+ m_pCurGroundEntity = entity;
+ }else{
+ rearZ = m_fMapObjectHeightBehind;
+ }
+
+ float len = lenFwd + lenBack;
+ float angle = Atan((frontZ - rearZ)/len);
+ float c = Cos(angle);
+ float s = Sin(angle);
+
+ GetMatrix().GetRight() = CVector((front.y - rear.y) / len, -(front.x - rear.x) / len, 0.0f);
+ GetMatrix().GetForward() = CVector(-c * GetRight().y, c * GetRight().x, s);
+ GetMatrix().GetUp() = CrossProduct(GetRight(), GetForward());
+ GetMatrix().GetPosition() = CVector((front.x + rear.x) / 2.0f, (front.y + rear.y) / 2.0f, (frontZ + rearZ) / 2.0f + GetHeightAboveRoad());
+}
+
+void
+CAutomobile::VehicleDamage(float impulse, uint16 damagedPiece)
+{
+ int i;
+ float damageMultiplier = 0.333f;
+
+ if(impulse == 0.0f){
+ impulse = m_fDamageImpulse;
+ damagedPiece = m_nDamagePieceType;
+ damageMultiplier = 1.0f;
+ }
+
+ if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f)
+ impulse *= 0.5f;
+
+ CVector pos(0.0f, 0.0f, 0.0f);
+
+ if(!bCanBeDamaged)
+ return;
+
+ if(m_pDamageEntity && m_pDamageEntity->IsPed() && ((CPed*)m_pDamageEntity)->bIsStanding){
+ float speed = ((CPed*)m_pDamageEntity)->m_vecAnimMoveDelta.y * DotProduct(GetForward(), m_vecDamageNormal);
+ if(speed < 0.0f)
+ impulse = Max(impulse + ((CPed*)m_pDamageEntity)->m_fMass * speed, 0.0f);
+ }
+
+ // damage flipped over car
+ if(GetUp().z < 0.0f && this != FindPlayerVehicle()){
+ if(bNotDamagedUpsideDown || GetStatus() == STATUS_PLAYER_REMOTE || bIsInWater)
+ return;
+ if(GetStatus() != STATUS_WRECKED)
+ m_fHealth = Max(m_fHealth - 4.0f*CTimer::GetTimeStep(), 0.0f);
+ }
+
+ float minImpulse = GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN ? 1.0f : 25.0f;
+ if(impulse > minImpulse && GetStatus() != STATUS_WRECKED){
+ if(bIsLawEnforcer &&
+ FindPlayerVehicle() && FindPlayerVehicle() == m_pDamageEntity &&
+ GetStatus() != STATUS_ABANDONED &&
+ FindPlayerVehicle()->m_vecMoveSpeed.Magnitude() >= m_vecMoveSpeed.Magnitude() &&
+ FindPlayerVehicle()->m_vecMoveSpeed.Magnitude() > 0.1f)
+ FindPlayerPed()->SetWantedLevelNoDrop(1);
+
+ if(GetStatus() == STATUS_PLAYER && impulse > 50.0f){
+ uint8 freq = Min(0.4f*impulse*2000.0f/m_fMass + 100.0f, 250.0f);
+ CPad::GetPad(0)->StartShake(40000/freq, freq);
+ }
+
+ if(GetStatus() != STATUS_PLAYER && bOnlyDamagedByPlayer){
+ if(m_pDamageEntity != FindPlayerPed() &&
+ m_pDamageEntity != FindPlayerVehicle())
+ return;
+ }
+
+ if(m_pDamageEntity && m_pDamageEntity->IsVehicle()){
+ m_nLastWeaponDamage = WEAPONTYPE_RAMMEDBYCAR;
+ m_pLastDamageEntity = m_pDamageEntity;
+ }
+
+ if(bCollisionProof)
+ return;
+
+ if(m_pDamageEntity){
+ if(m_pDamageEntity->IsBuilding() &&
+ DotProduct(m_vecDamageNormal, GetUp()) > 0.6f)
+ return;
+ }
+
+ int oldLightStatus[4];
+ for(i = 0; i < 4; i++)
+ oldLightStatus[i] = Damage.GetLightStatus((eLights)i);
+
+ if(GetUp().z > 0.0f || m_vecMoveSpeed.MagnitudeSqr() > 0.1f){
+ float impulseMult = bMoreResistantToDamage ? 0.5f : 4.0f;
+
+ switch(damagedPiece){
+ case CAR_PIECE_BUMP_FRONT:
+ GetComponentWorldPosition(CAR_BUMP_FRONT, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_BUMPER_FRONT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT);
+ if(m_aCarNodes[CAR_BONNET] && Damage.GetPanelStatus(VEHBUMPER_FRONT) == PANEL_STATUS_MISSING){
+ case CAR_PIECE_BONNET:
+ GetComponentWorldPosition(CAR_BONNET, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(GetModelIndex() != MI_DODO)
+ if(Damage.ApplyDamage(COMPONENT_DOOR_BONNET, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetDoorDamage(CAR_BONNET, DOOR_BONNET);
+ }
+ break;
+
+ case CAR_PIECE_BUMP_REAR:
+ GetComponentWorldPosition(CAR_BUMP_REAR, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_BUMPER_REAR, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR);
+ if(m_aCarNodes[CAR_BOOT] && Damage.GetPanelStatus(VEHBUMPER_REAR) == PANEL_STATUS_MISSING){
+ case CAR_PIECE_BOOT:
+ GetComponentWorldPosition(CAR_BOOT, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_DOOR_BOOT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetDoorDamage(CAR_BOOT, DOOR_BOOT);
+ }
+ break;
+
+ case CAR_PIECE_DOOR_LF:
+ GetComponentWorldPosition(CAR_DOOR_LF, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_DOOR_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+ break;
+ case CAR_PIECE_DOOR_RF:
+ GetComponentWorldPosition(CAR_DOOR_RF, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_DOOR_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+ break;
+ case CAR_PIECE_DOOR_LR:
+ GetComponentWorldPosition(CAR_DOOR_LR, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_DOOR_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT);
+ break;
+ case CAR_PIECE_DOOR_RR:
+ GetComponentWorldPosition(CAR_DOOR_RR, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_DOOR_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT);
+ break;
+
+ case CAR_PIECE_WING_LF:
+ GetComponentWorldPosition(CAR_WING_LF, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT);
+ break;
+ case CAR_PIECE_WING_RF:
+ GetComponentWorldPosition(CAR_WING_RF, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT);
+ break;
+ case CAR_PIECE_WING_LR:
+ GetComponentWorldPosition(CAR_WING_LR, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT);
+ break;
+ case CAR_PIECE_WING_RR:
+ GetComponentWorldPosition(CAR_WING_RR, pos);
+ dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier);
+ if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier))
+ SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT);
+ break;
+
+ case CAR_PIECE_WHEEL_LF:
+ case CAR_PIECE_WHEEL_LR:
+ case CAR_PIECE_WHEEL_RF:
+ case CAR_PIECE_WHEEL_RR:
+ break;
+
+ case CAR_PIECE_WINDSCREEN:
+ if(Damage.ApplyDamage(COMPONENT_PANEL_WINDSCREEN, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){
+ uint8 oldStatus = Damage.GetPanelStatus(VEHPANEL_WINDSCREEN);
+ SetPanelDamage(CAR_WINDSCREEN, VEHPANEL_WINDSCREEN);
+ if(oldStatus != Damage.GetPanelStatus(VEHPANEL_WINDSCREEN)){
+ // DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_WINDSHIELD_CRACK, 0.0f);
+ }
+ }
+ break;
+ }
+ }
+
+ float damage = (impulse-minImpulse)*pHandling->fCollisionDamageMultiplier*0.6f*damageMultiplier;
+
+ if(GetModelIndex() == MI_SECURICA && m_pDamageEntity && m_pDamageEntity->GetStatus() == STATUS_PLAYER)
+ damage *= 7.0f;
+
+ if(GetModelIndex() == MI_RCGOBLIN || GetModelIndex() == MI_RCRAIDER)
+ damage *= 30.0f;
+
+ if(damage > 0.0f){
+ if(damage > 5.0f &&
+ pDriver &&
+ m_pDamageEntity && m_pDamageEntity->IsVehicle() &&
+ (this != FindPlayerVehicle() || ((CVehicle*)m_pDamageEntity)->VehicleCreatedBy == MISSION_VEHICLE) &&
+ ((CVehicle*)m_pDamageEntity)->pDriver){
+ if(GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR)
+ pDriver->Say(SOUND_PED_CRASH_CAR);
+ else
+ pDriver->Say(SOUND_PED_CRASH_VEHICLE);
+ }
+
+ int oldHealth = m_fHealth;
+ if(this == FindPlayerVehicle())
+ m_fHealth -= bTakeLessDamage ? damage/6.0f : damage/2.0f;
+ else if(bTakeLessDamage)
+ m_fHealth -= damage/12.0f;
+ else if(m_pDamageEntity && m_pDamageEntity == FindPlayerVehicle())
+ m_fHealth -= damage/1.5f;
+ else
+ m_fHealth -= damage/4.0f;
+ if(m_fHealth <= 0.0f && oldHealth > 0)
+ m_fHealth = 1.0f;
+ }
+
+ // play sound if a light broke
+ for(i = 0; i < 4; i++)
+ if(oldLightStatus[i] != 1 && Damage.GetLightStatus((eLights)i) == 1){
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_LIGHT_BREAK, i); // BUG? i?
+ break;
+ }
+ }
+
+ if(m_fHealth < 250.0f){
+ // Car is on fire
+ if(Damage.GetEngineStatus() < ENGINE_STATUS_ON_FIRE){
+ // Set engine on fire and remember who did this
+ Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE);
+ m_fFireBlowUpTimer = 0.0f;
+ m_pSetOnFireEntity = m_pDamageEntity;
+ if(m_pSetOnFireEntity)
+ m_pSetOnFireEntity->RegisterReference(&m_pSetOnFireEntity);
+ }
+ }else{
+ if(GetModelIndex() == MI_BFINJECT){
+ if(m_fHealth < 400.0f)
+ Damage.SetEngineStatus(200);
+ else if(m_fHealth < 600.0f)
+ Damage.SetEngineStatus(100);
+ }
+ }
+}
+
+void
+CAutomobile::dmgDrawCarCollidingParticles(const CVector &pos, float amount)
+{
+ int i, n;
+
+ if(!GetIsOnScreen())
+ return;
+
+ // FindPlayerSpeed() unused
+
+ n = (int)amount/20;
+
+ for(i = 0; i < ((n+4)&0x1F); i++)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, pos,
+ CVector(CGeneral::GetRandomNumberInRange(-0.1f, 0.1f),
+ CGeneral::GetRandomNumberInRange(-0.1f, 0.1f),
+ 0.006f));
+
+ for(i = 0; i < n+2; i++)
+ CParticle::AddParticle(PARTICLE_CARCOLLISION_DUST,
+ CVector(CGeneral::GetRandomNumberInRange(-1.2f, 1.2f) + pos.x,
+ CGeneral::GetRandomNumberInRange(-1.2f, 1.2f) + pos.y,
+ pos.z),
+ CVector(0.0f, 0.0f, 0.0f), nil, 0.5f);
+
+ n = (int)amount/50 + 1;
+ for(i = 0; i < n; i++)
+ CParticle::AddParticle(PARTICLE_CAR_DEBRIS, pos,
+ CVector(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f),
+ CGeneral::GetRandomNumberInRange(-0.25f, 0.25f),
+ CGeneral::GetRandomNumberInRange(0.1f, 0.25f)),
+ nil,
+ CGeneral::GetRandomNumberInRange(0.02f, 0.08f),
+ CVehicleModelInfo::ms_vehicleColourTable[m_currentColour1],
+ CGeneral::GetRandomNumberInRange(-40.0f, 40.0f),
+ 0,
+ CGeneral::GetRandomNumberInRange(0.0f, 4.0f));
+}
+
+void
+CAutomobile::AddDamagedVehicleParticles(void)
+{
+ int i, n;
+
+ if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson())
+ return;
+ if(this != FindPlayerVehicle() && (CTimer::GetFrameCounter() + m_randomSeed) & 1)
+ return;
+ if(m_fHealth >= 650.0f)
+ return;
+
+ CVector direction = 0.85f*m_vecMoveSpeed;
+ CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_HEADLIGHTS];
+
+ switch(Damage.GetDoorStatus(DOOR_BONNET)){
+ case DOOR_STATUS_OK:
+ case DOOR_STATUS_SMASHED:
+ // Bonnet is still there, smoke comes out at the edge
+ damagePos += vecDAMAGE_ENGINE_POS_SMALL;
+ break;
+ case DOOR_STATUS_SWINGING:
+ case DOOR_STATUS_MISSING:
+ // Bonnet is gone, smoke comes out at the engine
+ damagePos += vecDAMAGE_ENGINE_POS_BIG;
+ break;
+ }
+
+ if(GetModelIndex() == MI_BFINJECT)
+ damagePos = CVector(0.3f, -1.5f, -0.1f);
+ else if(GetModelIndex() == MI_CADDY)
+ damagePos = CVector(0.6f, -1.0f, -0.25f);
+ else if(IsRealHeli()){
+ damagePos.x = 0.4f*GetColModel()->boundingBox.max.x;
+ damagePos.y = 0.2f*GetColModel()->boundingBox.min.y;
+ damagePos.z = 0.3f*GetColModel()->boundingBox.max.z;
+ }else
+ damagePos.z += 0.4f*(GetColModel()->boundingBox.max.z-damagePos.z) * DotProduct(GetForward(), m_vecMoveSpeed);
+ damagePos = GetMatrix()*damagePos;
+ damagePos.z += 0.15f;
+
+ bool electric = pHandling->Transmission.nEngineType == 'E';
+
+ if(electric && m_fHealth < 320.0f && m_fHealth > 1.0f){
+ direction = 0.85f*m_vecMoveSpeed;
+ direction += GetRight() * CGeneral::GetRandomNumberInRange(0.0f, 0.04f) * (1.0f - 2.0f*m_vecMoveSpeed.Magnitude());
+ direction.z += 0.001f;
+ n = (CGeneral::GetRandomNumber() & 7) + 2;
+ for(i = 0; i < n; i++)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, damagePos, direction);
+ if(((CTimer::GetFrameCounter() + m_randomSeed) & 7) == 0)
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, 0.8f*m_vecMoveSpeed, nil, 0.1f, 0, 0, 0, 1000);
+ }else if(electric && m_fHealth < 460.0f){
+ direction = 0.85f*m_vecMoveSpeed;
+ direction += GetRight() * CGeneral::GetRandomNumberInRange(0.0f, 0.04f) * (1.0f - 2.0f*m_vecMoveSpeed.Magnitude());
+ direction.z += 0.001f;
+ n = (CGeneral::GetRandomNumber() & 3) + 2;
+ for(i = 0; i < n; i++)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, damagePos, direction);
+ if(((CTimer::GetFrameCounter() + m_randomSeed) & 0xF) == 0)
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.8f*m_vecMoveSpeed, nil, 0.1f, 0, 0, 0, 1000);
+ }else if(m_fHealth < 250.0f){
+ // nothing
+ }else if(m_fHealth < 320.0f){
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, 0.8f*direction);
+ }else if(m_fHealth < 390.0f){
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, 0.75f*direction);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.85f*direction);
+ }else if(m_fHealth < 460.0f){
+ int rnd = CTimer::GetFrameCounter() + m_randomSeed;
+ if(rnd < 10 ||
+ rnd < 70 && rnd > 25 ||
+ rnd < 160 && rnd > 100 ||
+ rnd < 200 && rnd > 175 ||
+ rnd > 235)
+ return;
+ direction.z += 0.05f*Max(1.0f - 1.6f*m_vecMoveSpeed.Magnitude(), 0.0f);
+ if(electric){
+ // BUG. we had that case already
+ direction = 0.85f*m_vecMoveSpeed;
+ direction += GetRight() * CGeneral::GetRandomNumberInRange(0.0f, 0.04f) * (1.0f - 2.0f*m_vecMoveSpeed.Magnitude());
+ direction.z += 0.001f;
+ n = (CGeneral::GetRandomNumber() & 2) + 2;
+ for(i = 0; i < n; i++)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, damagePos, direction);
+ if(((CTimer::GetFrameCounter() + m_randomSeed) & 0xF) == 0)
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.8f*m_vecMoveSpeed, nil, 0.1f, 0, 0, 0, 1000);
+ }else{
+ if(TheCamera.GetLookDirection() != LOOKING_FORWARD)
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, 0.75f*direction);
+ else if(((CTimer::GetFrameCounter() + m_randomSeed) & 1) == 0)
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, 0.85f*m_vecMoveSpeed);
+ }
+ }else if(((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 0 ||
+ ((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 2){
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, 0.9f*direction);
+ }
+}
+
+int32
+CAutomobile::AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed)
+{
+ int i;
+ CVector dir;
+ static RwRGBA grassCol = { 8, 24, 8, 255 };
+ static RwRGBA gravelCol = { 64, 64, 64, 255 };
+ static RwRGBA mudCol = { 64, 32, 16, 255 };
+ static RwRGBA sandCol = { 170, 165, 140, 255 };
+ static RwRGBA waterCol = { 48, 48, 64, 0 };
+
+ if(!belowEffectSpeed &&
+ colpoint->surfaceB != SURFACE_SAND && colpoint->surfaceB != SURFACE_SAND_BEACH)
+ return 0;
+
+ switch(colpoint->surfaceB){
+ case SURFACE_GRASS:
+ dir.x = -0.05f*m_vecMoveSpeed.x;
+ dir.y = -0.05f*m_vecMoveSpeed.y;
+ for(i = 0; i < 4; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
+ CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil,
+ CGeneral::GetRandomNumberInRange(0.02f, 0.1f), grassCol);
+ }
+ return 0;
+ case SURFACE_GRAVEL:
+ dir.x = -0.05f*m_vecMoveSpeed.x;
+ dir.y = -0.05f*m_vecMoveSpeed.y;
+ for(i = 0; i < 4; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
+ CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil,
+ CGeneral::GetRandomNumberInRange(0.05f, 0.09f), gravelCol);
+ }
+ return 1;
+ case SURFACE_MUD_DRY:
+ dir.x = -0.05f*m_vecMoveSpeed.x;
+ dir.y = -0.05f*m_vecMoveSpeed.y;
+ for(i = 0; i < 4; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
+ CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil,
+ CGeneral::GetRandomNumberInRange(0.02f, 0.06f), mudCol);
+ }
+ return 0;
+ case SURFACE_SAND:
+ case SURFACE_SAND_BEACH:
+ if(CTimer::GetFrameCounter() & 2 ||
+ CGeneral::GetRandomNumberInRange(CWeather::WetRoads, 1.01f) > 0.5f)
+ return 0;
+ dir.x = 0.5f*m_vecMoveSpeed.x;
+ dir.y = 0.5f*m_vecMoveSpeed.y;
+ for(i = 0; i < 1; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.02f, 0.055f);
+ CParticle::AddParticle(PARTICLE_SAND, colpoint->point, dir, nil,
+ 2.0f*m_vecMoveSpeed.Magnitude(), sandCol);
+ }
+ return 0;
+ default:
+ if(CWeather::WetRoads > 0.01f){
+ if(CTimer::GetFrameCounter() & 1)
+ CParticle::AddParticle(
+ PARTICLE_WATERSPRAY,
+ colpoint->point + CVector(0.0f, 0.0f, 0.25f+0.25f),
+ CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.005f, 0.04f)),
+ nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.5f), waterCol);
+ return 0;
+ }
+ return 1;
+ }
+}
+
+void
+CAutomobile::GetComponentWorldPosition(int32 component, CVector &pos)
+{
+ if(m_aCarNodes[component] == nil){
+ printf("CarNode missing: %d %d\n", GetModelIndex(), component);
+ return;
+ }
+ RwMatrix *ltm = RwFrameGetLTM(m_aCarNodes[component]);
+ pos = *RwMatrixGetPos(ltm);
+}
+
+bool
+CAutomobile::IsComponentPresent(int32 comp)
+{
+ return m_aCarNodes[comp] != nil;
+}
+
+void
+CAutomobile::SetComponentRotation(int32 component, CVector rotation)
+{
+ CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+ CVector pos = mat.GetPosition();
+ // BUG: all these set the whole matrix
+ mat.SetRotateX(DEGTORAD(rotation.x));
+ mat.SetRotateY(DEGTORAD(rotation.y));
+ mat.SetRotateZ(DEGTORAD(rotation.z));
+ mat.Translate(pos);
+ mat.UpdateRW();
+}
+
+void
+CAutomobile::OpenDoor(int32 component, eDoors door, float openRatio)
+{
+ CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+ CVector pos = mat.GetPosition();
+ float axes[3] = { 0.0f, 0.0f, 0.0f };
+ float wasClosed = false;
+
+ if(Doors[door].IsClosed()){
+ // enable angle cull for closed doors
+ RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::ClearAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL);
+ wasClosed = true;
+ }
+
+ Doors[door].Open(openRatio);
+
+ if(wasClosed && Doors[door].RetAngleWhenClosed() != Doors[door].m_fAngle){
+ // door opened
+ HideAllComps();
+ // turn off angle cull for swinging door
+ RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_OPEN_BONNET + door, 0.0f);
+ }
+
+ if(!wasClosed && openRatio == 0.0f){
+ // door closed
+ if(Damage.GetDoorStatus(door) == DOOR_STATUS_SWINGING)
+ Damage.SetDoorStatus(door, DOOR_STATUS_OK); // huh?
+ ShowAllComps();
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_CLOSE_BONNET + door, 0.0f);
+ }
+
+ axes[Doors[door].m_nAxis] = Doors[door].m_fAngle;
+ mat.SetRotate(axes[0], axes[1], axes[2]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+}
+
+inline void ProcessDoorOpenAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end)
+{
+ if(time > start && time < end){
+ float ratio = (time - start)/(end - start);
+ if(car->Doors[door].GetAngleOpenRatio() < ratio)
+ car->OpenDoor(component, door, ratio);
+ }else if(time > end){
+ car->OpenDoor(component, door, 1.0f);
+ }
+}
+
+inline void ProcessDoorCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end)
+{
+ if(time > start && time < end){
+ float ratio = 1.0f - (time - start)/(end - start);
+ if(car->Doors[door].GetAngleOpenRatio() > ratio)
+ car->OpenDoor(component, door, ratio);
+ }else if(time > end){
+ car->OpenDoor(component, door, 0.0f);
+ }
+}
+
+inline void ProcessDoorOpenCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float mid, float end)
+{
+ if(time > start && time < mid){
+ // open
+ float ratio = (time - start)/(mid - start);
+ if(car->Doors[door].GetAngleOpenRatio() < ratio)
+ car->OpenDoor(component, door, ratio);
+ }else if(time > mid && time < end){
+ // close
+ float ratio = 1.0f - (time - mid)/(end - mid);
+ if(car->Doors[door].GetAngleOpenRatio() > ratio)
+ car->OpenDoor(component, door, ratio);
+ }else if(time > end){
+ car->OpenDoor(component, door, 0.0f);
+ }
+}
+
+void
+CAutomobile::ProcessOpenDoor(uint32 component, uint32 anim, float time)
+{
+ eDoors door;
+
+ switch(component){
+ case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; break;
+ case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; break;
+ case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; break;
+ case CAR_DOOR_LR: door = DOOR_REAR_LEFT; break;
+ default: assert(0);
+ }
+
+ if(IsDoorMissing(door))
+ return;
+
+ switch(anim){
+ case ANIM_STD_QUICKJACK:
+ case ANIM_STD_CAR_OPEN_DOOR_LHS:
+ case ANIM_STD_CAR_OPEN_DOOR_RHS:
+ ProcessDoorOpenAnimation(this, component, door, time, 0.41f, 0.89f);
+ break;
+ case ANIM_STD_CAR_CLOSE_DOOR_LHS:
+ case ANIM_STD_CAR_CLOSE_DOOR_LO_LHS:
+ case ANIM_STD_CAR_CLOSE_DOOR_RHS:
+ case ANIM_STD_CAR_CLOSE_DOOR_LO_RHS:
+ ProcessDoorCloseAnimation(this, component, door, time, 0.2f, 0.45f);
+ break;
+ case ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS:
+ case ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS:
+ ProcessDoorOpenCloseAnimation(this, component, door, time, 0.1f, 0.6f, 0.95f);
+ break;
+ case ANIM_STD_GETOUT_LHS:
+ case ANIM_STD_GETOUT_LO_LHS:
+ case ANIM_STD_GETOUT_RHS:
+ case ANIM_STD_GETOUT_LO_RHS:
+ ProcessDoorOpenAnimation(this, component, door, time, 0.06f, 0.43f);
+ break;
+ case ANIM_STD_CAR_CLOSE_LHS:
+ case ANIM_STD_CAR_CLOSE_RHS:
+ ProcessDoorCloseAnimation(this, component, door, time, 0.1f, 0.23f);
+ break;
+ case ANIM_STD_CAR_PULL_OUT_PED_RHS:
+ case ANIM_STD_CAR_PULL_OUT_PED_LO_RHS:
+ OpenDoor(component, door, 1.0f);
+ break;
+ case ANIM_STD_COACH_OPEN_LHS:
+ case ANIM_STD_COACH_OPEN_RHS:
+ ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f);
+ break;
+ case ANIM_STD_COACH_GET_OUT_LHS:
+ ProcessDoorOpenAnimation(this, component, door, time, 0.0f, 0.3f);
+ break;
+ case ANIM_STD_VAN_OPEN_DOOR_REAR_LHS:
+ case ANIM_STD_VAN_OPEN_DOOR_REAR_RHS:
+ ProcessDoorOpenAnimation(this, component, door, time, 0.37f, 0.55f);
+ break;
+ case ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS:
+ case ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS:
+ ProcessDoorCloseAnimation(this, component, door, time, 0.5f, 0.8f);
+ break;
+ case ANIM_STD_VAN_GET_OUT_REAR_LHS:
+ case ANIM_STD_VAN_GET_OUT_REAR_RHS:
+ ProcessDoorOpenAnimation(this, component, door, time, 0.5f, 0.6f);
+ break;
+ case ANIM_STD_NUM:
+ OpenDoor(component, door, time);
+ break;
+ }
+}
+
+bool
+CAutomobile::IsDoorReady(eDoors door)
+{
+ if(Doors[door].IsClosed() || IsDoorMissing(door))
+ return true;
+ int doorflag = 0;
+ switch(door){
+ case DOOR_FRONT_LEFT: doorflag = CAR_DOOR_FLAG_LF; break;
+ case DOOR_FRONT_RIGHT: doorflag = CAR_DOOR_FLAG_RF; break;
+ case DOOR_REAR_LEFT: doorflag = CAR_DOOR_FLAG_LR; break;
+ case DOOR_REAR_RIGHT: doorflag = CAR_DOOR_FLAG_RR; break;
+ default: break;
+ }
+ return (doorflag & m_nGettingInFlags) == 0;
+}
+
+bool
+CAutomobile::IsDoorFullyOpen(eDoors door)
+{
+ return Doors[door].IsFullyOpen() || IsDoorMissing(door);
+}
+
+bool
+CAutomobile::IsDoorClosed(eDoors door)
+{
+ return !!Doors[door].IsClosed();
+}
+
+bool
+CAutomobile::IsDoorMissing(eDoors door)
+{
+ return Damage.GetDoorStatus(door) == DOOR_STATUS_MISSING;
+}
+
+bool
+CAutomobile::IsDoorReady(uint32 door)
+{
+ switch(door){
+ case CAR_DOOR_RF: return IsDoorReady(DOOR_FRONT_RIGHT);
+ case CAR_DOOR_RR: return IsDoorReady(DOOR_REAR_RIGHT);
+ case CAR_DOOR_LF: return IsDoorReady(DOOR_FRONT_LEFT);
+ case CAR_DOOR_LR: return IsDoorReady(DOOR_REAR_LEFT);
+ default:
+ return false;
+ }
+}
+
+bool
+CAutomobile::IsDoorMissing(uint32 door)
+{
+ switch(door){
+ case CAR_DOOR_RF: return IsDoorMissing(DOOR_FRONT_RIGHT);
+ case CAR_DOOR_RR: return IsDoorMissing(DOOR_REAR_RIGHT);
+ case CAR_DOOR_LF: return IsDoorMissing(DOOR_FRONT_LEFT);
+ case CAR_DOOR_LR: return IsDoorMissing(DOOR_REAR_LEFT);
+ default:
+ return false;
+ }
+}
+
+bool
+CAutomobile::IsOpenTopCar(void)
+{
+ return GetModelIndex() == MI_STINGER ||
+ // component 0 is assumed to be a roof
+ GetModelIndex() == MI_COMET && m_aExtras[0] != 0 && m_aExtras[1] != 0 ||
+ GetModelIndex() == MI_STALLION && m_aExtras[0] != 0 && m_aExtras[1] != 0;
+}
+
+void
+CAutomobile::RemoveRefsToVehicle(CEntity *ent)
+{
+ int i;
+ for(i = 0; i < 4; i++)
+ if(m_aGroundPhysical[i] == ent)
+ m_aGroundPhysical[i] = nil;
+}
+
+void
+CAutomobile::BlowUpCar(CEntity *culprit)
+{
+ RpAtomic *atomic;
+
+ if(!bCanBeDamaged)
+ return;
+
+ if(culprit == FindPlayerPed() || culprit == FindPlayerVehicle()){
+ CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 20;
+ CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 10.0f;
+ CStats::PropertyDestroyed += CGeneral::GetRandomNumber()%6000 + 4000;
+ }
+
+ // explosion pushes vehicle up
+ m_vecMoveSpeed.z += 0.13f;
+ SetStatus(STATUS_WRECKED);
+ bRenderScorched = true;
+ m_nTimeOfDeath = CTimer::GetTimeInMilliseconds();
+ Damage.FuckCarCompletely();
+
+ if(GetModelIndex() != MI_RCBANDIT){
+ SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT);
+ SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR);
+ SetDoorDamage(CAR_BONNET, DOOR_BONNET);
+ SetDoorDamage(CAR_BOOT, DOOR_BOOT);
+ SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+ SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+ SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT);
+ SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT);
+ SpawnFlyingComponent(CAR_WHEEL_LF, COMPGROUP_WHEEL);
+ atomic = nil;
+ RwFrameForAllObjects(m_aCarNodes[CAR_WHEEL_LF], GetCurrentAtomicObjectCB, &atomic);
+ if(atomic)
+ RpAtomicSetFlags(atomic, 0);
+ }
+
+ m_fHealth = 0.0f;
+ m_nBombTimer = 0;
+ m_bombType = CARBOMB_NONE;
+
+ TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z);
+
+ KillPedsInVehicle();
+
+ bEngineOn = false;
+ bLightsOn = false;
+ m_bSirenOrAlarm = false;
+ bTaxiLight = false;
+ if(bIsAmbulanceOnDuty){
+ bIsAmbulanceOnDuty = false;
+ CCarCtrl::NumAmbulancesOnDuty--;
+ }
+ if(bIsFireTruckOnDuty){
+ bIsFireTruckOnDuty = false;
+ CCarCtrl::NumFiretrucksOnDuty--;
+ }
+ ChangeLawEnforcerState(false);
+
+ gFireManager.StartFire(this, culprit, 0.8f, true);
+ CDarkel::RegisterCarBlownUpByPlayer(this);
+ if(GetModelIndex() == MI_RCBANDIT)
+ CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR_QUICK, GetPosition(), 0);
+ else
+ CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0);
+}
+
+bool
+CAutomobile::SetUpWheelColModel(CColModel *colModel)
+{
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ CColModel *vehColModel = mi->GetColModel();
+
+ if(GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI ||
+ GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE)
+ return false;
+
+ colModel->boundingSphere = vehColModel->boundingSphere;
+ colModel->boundingBox = vehColModel->boundingBox;
+
+ CMatrix mat;
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF]));
+ colModel->spheres[0].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LF);
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB]));
+ colModel->spheres[1].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR);
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF]));
+ colModel->spheres[2].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RF);
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB]));
+ colModel->spheres[3].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR);
+
+ if(m_aCarNodes[CAR_WHEEL_LM] != nil && m_aCarNodes[CAR_WHEEL_RM] != nil){
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM]));
+ colModel->spheres[4].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR);
+ mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM]));
+ colModel->spheres[5].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR);
+ colModel->numSpheres = 6;
+ }else
+ colModel->numSpheres = 4;
+
+ return true;
+}
+
+void
+CAutomobile::BurstTyre(uint8 wheel, bool applyForces)
+{
+ if(GetModelIndex() == MI_RHINO || bTyresDontBurst)
+ return;
+
+ switch(wheel){
+ case CAR_PIECE_WHEEL_LF: wheel = CARWHEEL_FRONT_LEFT; break;
+ case CAR_PIECE_WHEEL_RF: wheel = CARWHEEL_FRONT_RIGHT; break;
+ case CAR_PIECE_WHEEL_LR: wheel = CARWHEEL_REAR_LEFT; break;
+ case CAR_PIECE_WHEEL_RR: wheel = CARWHEEL_REAR_RIGHT; break;
+ }
+
+ int status = Damage.GetWheelStatus(wheel);
+ if(status == WHEEL_STATUS_OK){
+ Damage.SetWheelStatus(wheel, WHEEL_STATUS_BURST);
+ CStats::TyresPopped++;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TYRE_POP, 0.0f);
+
+ if(GetStatus() == STATUS_SIMPLE){
+ SetStatus(STATUS_PHYSICS);
+ CCarCtrl::SwitchVehicleToRealPhysics(this);
+ }
+
+ if(applyForces){
+ ApplyMoveForce(GetRight() * m_fMass * CGeneral::GetRandomNumberInRange(-0.03f, 0.03f));
+ ApplyTurnForce(GetRight() * m_fTurnMass * CGeneral::GetRandomNumberInRange(-0.03f, 0.03f), GetForward());
+ }
+ }
+}
+
+bool
+CAutomobile::IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset)
+{
+ CColPoint colpoint;
+ CEntity *ent;
+ colpoint.point = CVector(0.0f, 0.0f, 0.0f);
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ CVector seatPos;
+ switch(component){
+ case CAR_DOOR_RF:
+ seatPos = mi->GetFrontSeatPosn();
+ break;
+ case CAR_DOOR_LF:
+ seatPos = mi->GetFrontSeatPosn();
+ seatPos.x = -seatPos.x;
+ break;
+ case CAR_DOOR_RR:
+ seatPos = mi->m_positions[CAR_POS_BACKSEAT];
+ break;
+ case CAR_DOOR_LR:
+ seatPos = mi->m_positions[CAR_POS_BACKSEAT];
+ seatPos.x = -seatPos.x;
+ break;
+ }
+ seatPos = GetMatrix() * seatPos;
+
+ CVector doorPos = CPed::GetPositionToOpenCarDoor(this, component);
+ if(doorOffset){
+ CVector off = *doorOffset;
+ if(component == CAR_DOOR_RF || component == CAR_DOOR_RR)
+ off.x = -off.x;
+ doorPos += Multiply3x3(GetMatrix(), off);
+ }
+
+ if(GetUp().z < 0.0f){
+ seatPos.z += 0.5f;
+ doorPos.z += 0.5f;
+ }
+
+ CVector dist = doorPos - seatPos;
+
+ // Removing that makes thiProcessEntityCollisions func. return false for van doors.
+ doorPos.z += 0.5f;
+ float length = dist.Magnitude();
+ CVector pedPos = seatPos + dist*((length+0.6f)/length);
+
+ if(!CWorld::GetIsLineOfSightClear(seatPos, pedPos, true, false, false, true, false, false))
+ return false;
+ if(CWorld::TestSphereAgainstWorld(doorPos, 0.6f, this, true, true, false, true, false, false))
+ return false;
+ if(CWorld::ProcessVerticalLine(doorPos, 1000.0f, colpoint, ent, true, false, false, true, false, false, nil))
+ if(colpoint.point.z > doorPos.z && colpoint.point.z < doorPos.z + 0.6f)
+ return false;
+ float upperZ = colpoint.point.z;
+ if(!CWorld::ProcessVerticalLine(doorPos, -1000.0f, colpoint, ent, true, false, false, true, false, false, nil))
+ return false;
+ if(upperZ != 0.0f && upperZ < colpoint.point.z)
+ return false;
+ return true;
+}
+
+float
+CAutomobile::GetHeightAboveRoad(void)
+{
+ return m_fHeightAboveRoad;
+}
+
+void
+CAutomobile::PlayCarHorn(void)
+{
+ int r;
+
+ if (IsAlarmOn() || m_nCarHornTimer != 0)
+ return;
+
+ if (m_nCarHornDelay) {
+ m_nCarHornDelay--;
+ return;
+ }
+
+ m_nCarHornDelay = (CGeneral::GetRandomNumber() & 0x7F) + 150;
+ r = m_nCarHornDelay & 7;
+ if(r < 2){
+ m_nCarHornTimer = 45;
+ }else if(r < 4){
+ if(pDriver)
+ pDriver->Say(SOUND_PED_ANNOYED_DRIVER);
+ m_nCarHornTimer = 45;
+ }else{
+ if(pDriver)
+ pDriver->Say(SOUND_PED_ANNOYED_DRIVER);
+ }
+}
+
+void
+CAutomobile::PlayHornIfNecessary(void)
+{
+ if(AutoPilot.m_bSlowedDownBecauseOfPeds ||
+ AutoPilot.m_bSlowedDownBecauseOfCars)
+ if(!HasCarStoppedBecauseOfLight())
+ PlayCarHorn();
+}
+
+void
+CAutomobile::ResetSuspension(void)
+{
+ int i;
+ for(i = 0; i < 4; i++){
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ m_aWheelTimer[i] = 0.0f;
+ m_aWheelRotation[i] = 0.0f;
+ m_aWheelState[i] = WHEEL_STATE_NORMAL;
+ }
+}
+
+void
+CAutomobile::SetupSuspensionLines(void)
+{
+ int i;
+ CVector posn;
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ CColModel *colModel = mi->GetColModel();
+
+ // Each suspension line starts at the uppermost wheel position
+ // and extends down to the lowermost point on the tyre
+ for(i = 0; i < 4; i++){
+ mi->GetWheelPosn(i, posn);
+ m_aWheelPosition[i] = posn.z;
+
+ // uppermost wheel position
+ posn.z += pHandling->fSuspensionUpperLimit;
+ colModel->lines[i].p0 = posn;
+
+ // lowermost wheel position
+ posn.z += pHandling->fSuspensionLowerLimit - pHandling->fSuspensionUpperLimit;
+ // lowest point on tyre
+ posn.z -= mi->m_wheelScale*0.5f;
+ colModel->lines[i].p1 = posn;
+
+ // this is length of the spring at rest
+ m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit;
+ m_aSuspensionLineLength[i] = colModel->lines[i].p0.z - colModel->lines[i].p1.z;
+ }
+
+ // Compress spring somewhat to get normal height on road
+ m_fHeightAboveRoad = m_aSuspensionSpringLength[0]*(1.0f - 1.0f/(4.0f*pHandling->fSuspensionForceLevel))
+ - colModel->lines[0].p0.z + mi->m_wheelScale*0.5f;
+ for(i = 0; i < 4; i++)
+ m_aWheelPosition[i] = mi->m_wheelScale*0.5f - m_fHeightAboveRoad;
+
+ // adjust col model to include suspension lines
+ if(colModel->boundingBox.min.z > colModel->lines[0].p1.z)
+ colModel->boundingBox.min.z = colModel->lines[0].p1.z;
+ float radius = Max(colModel->boundingBox.min.Magnitude(), colModel->boundingBox.max.Magnitude());
+ if(colModel->boundingSphere.radius < radius)
+ colModel->boundingSphere.radius = radius;
+
+ if(GetModelIndex() == MI_RCBANDIT){
+ colModel->boundingSphere.radius = 2.0f;
+ for(i = 0; i < colModel->numSpheres; i++)
+ colModel->spheres[i].radius = 0.3f;
+ }
+}
+
+// called on police cars
+void
+CAutomobile::ScanForCrimes(void)
+{
+ if(FindPlayerVehicle() && FindPlayerVehicle()->IsCar())
+ if(FindPlayerVehicle()->IsAlarmOn())
+ // if player's alarm is on, increase wanted level
+ if((FindPlayerVehicle()->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f))
+ CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetWantedLevelNoDrop(1);
+}
+
+void
+CAutomobile::BlowUpCarsInPath(void)
+{
+ int i;
+
+ if(m_vecMoveSpeed.Magnitude() > 0.1f && bTankDetonateCars)
+ for(i = 0; i < m_nCollisionRecords; i++)
+ if(m_aCollisionRecords[i] &&
+ m_aCollisionRecords[i]->IsVehicle() &&
+ m_aCollisionRecords[i]->GetModelIndex() != MI_RHINO &&
+ !m_aCollisionRecords[i]->bRenderScorched){
+ if(this == FindPlayerVehicle())
+ CEventList::RegisterEvent(EVENT_EXPLOSION, EVENT_ENTITY_VEHICLE, m_aCollisionRecords[i], FindPlayerPed(), 2000);
+ ((CVehicle*)m_aCollisionRecords[i])->BlowUpCar(this);
+ }
+}
+
+bool
+CAutomobile::HasCarStoppedBecauseOfLight(void)
+{
+ int i;
+
+ if(GetStatus() != STATUS_SIMPLE && GetStatus() != STATUS_PHYSICS)
+ return false;
+
+ if(AutoPilot.m_nCurrentRouteNode && AutoPilot.m_nNextRouteNode){
+ CPathNode *curnode = &ThePaths.m_pathNodes[AutoPilot.m_nCurrentRouteNode];
+ for(i = 0; i < curnode->numLinks; i++)
+ if(ThePaths.ConnectedNode(curnode->firstLink + i) == AutoPilot.m_nNextRouteNode)
+ break;
+ if(i < curnode->numLinks &&
+ ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3)
+ return true;
+ }
+
+ if(AutoPilot.m_nCurrentRouteNode && AutoPilot.m_nPrevRouteNode){
+ CPathNode *curnode = &ThePaths.m_pathNodes[AutoPilot.m_nCurrentRouteNode];
+ for(i = 0; i < curnode->numLinks; i++)
+ if(ThePaths.ConnectedNode(curnode->firstLink + i) == AutoPilot.m_nPrevRouteNode)
+ break;
+ if(i < curnode->numLinks &&
+ ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3)
+ return true;
+ }
+
+ return false;
+}
+
+void
+CPed::DeadPedMakesTyresBloody(void)
+{
+ int minX = CWorld::GetSectorIndexX(GetPosition().x - 2.0f);
+ if (minX < 0) minX = 0;
+ int minY = CWorld::GetSectorIndexY(GetPosition().y - 2.0f);
+ if (minY < 0) minY = 0;
+ int maxX = CWorld::GetSectorIndexX(GetPosition().x + 2.0f);
+ if (maxX > NUMSECTORS_X-1) maxX = NUMSECTORS_X-1;
+ int maxY = CWorld::GetSectorIndexY(GetPosition().y + 2.0f);
+ if (maxY > NUMSECTORS_Y-1) maxY = NUMSECTORS_Y-1;
+
+ CWorld::AdvanceCurrentScanCode();
+
+ for (int curY = minY; curY <= maxY; curY++) {
+ for (int curX = minX; curX <= maxX; curX++) {
+ CSector *sector = CWorld::GetSector(curX, curY);
+ MakeTyresMuddySectorList(sector->m_lists[ENTITYLIST_VEHICLES]);
+ MakeTyresMuddySectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
+ }
+ }
+}
+
+void
+CPed::MakeTyresMuddySectorList(CPtrList &list)
+{
+ CAutomobile *car = nil;
+ CBike *bike = nil;
+ for (CPtrNode *node = list.first; node; node = node->next) {
+ CVehicle *veh = (CVehicle*)node->item;
+ if (veh->m_scanCode != CWorld::GetCurrentScanCode()) {
+ veh->m_scanCode = CWorld::GetCurrentScanCode();
+
+ if (Abs(GetPosition().x - veh->GetPosition().x) < 10.0f && Abs(GetPosition().y - veh->GetPosition().y) < 10.0f) {
+ if (veh->IsCar()) {
+ bike = nil;
+ car = (CAutomobile*)veh;
+ } else if (veh->IsBike()) {
+ bike = (CBike*)veh;
+ car = nil;
+ }
+ if (veh->m_vecMoveSpeed.MagnitudeSqr2D() > 0.05f) {
+ if (car) {
+ for (int wheel = 0; wheel < 4; wheel++) {
+ if (!car->m_aWheelSkidmarkBloody[wheel] && car->m_aSuspensionSpringRatio[wheel] < 1.0f) {
+
+ CColModel* vehCol = car->GetModelInfo()->GetColModel();
+ CVector approxWheelOffset;
+ switch (wheel) {
+ case 0:
+ approxWheelOffset = CVector(-vehCol->boundingBox.max.x, vehCol->boundingBox.max.y, 0.0f);
+ break;
+ case 1:
+ approxWheelOffset = CVector(-vehCol->boundingBox.max.x, vehCol->boundingBox.min.y, 0.0f);
+ break;
+ case 2:
+ approxWheelOffset = CVector(vehCol->boundingBox.max.x, vehCol->boundingBox.max.y, 0.0f);
+ break;
+ case 3:
+ approxWheelOffset = CVector(vehCol->boundingBox.max.x, vehCol->boundingBox.min.y, 0.0f);
+ break;
+ default:
+ break;
+ }
+
+ // I hope so
+ CVector wheelPos = car->GetMatrix() * approxWheelOffset;
+ if (Abs(wheelPos.z - GetPosition().z) < 2.0f) {
+
+ if ((wheelPos - GetPosition()).MagnitudeSqr2D() < 1.0f) {
+ if (CGame::nastyGame) {
+ car->m_aWheelSkidmarkBloody[wheel] = true;
+ DMAudio.PlayOneShot(car->m_audioEntityId, SOUND_SPLATTER, 0.0f);
+ }
+ if (car->m_fMass > 500.f) {
+ car->ApplyMoveForce(CVector(0.0f, 0.0f, 50.0f * Min(1.0f, m_fMass * 0.001f)));
+
+ CVector vehAndWheelDist = wheelPos - car->GetPosition();
+ car->ApplyTurnForce(CVector(0.0f, 0.0f, 50.0f * Min(1.0f, m_fTurnMass * 0.0005f)), vehAndWheelDist);
+ if (car == FindPlayerVehicle()) {
+ CPad::GetPad(0)->StartShake(300, 70);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (bike) {
+ for (int wheel = 0; wheel < 2; wheel++) {
+ if (!bike->m_aWheelSkidmarkBloody[wheel] && bike->m_aSuspensionSpringRatio[wheel] < 1.0f) {
+
+ CColModel* vehCol = bike->GetModelInfo()->GetColModel();
+ CVector approxWheelOffset;
+ switch (wheel) {
+ case 0:
+ approxWheelOffset = CVector(0.0f, 0.8f * vehCol->boundingBox.max.y, 0.0f);
+ break;
+ case 1:
+ approxWheelOffset = CVector(0.0f, 0.8f * vehCol->boundingBox.min.y, 0.0f);
+ default:
+ break;
+ }
+
+ // I hope so
+ CVector wheelPos = bike->GetMatrix() * approxWheelOffset;
+ if (Abs(wheelPos.z - GetPosition().z) < 2.0f) {
+
+ if ((wheelPos - GetPosition()).MagnitudeSqr2D() < 1.0f) {
+ if (CGame::nastyGame) {
+ bike->m_aWheelSkidmarkBloody[wheel] = true;
+ DMAudio.PlayOneShot(bike->m_audioEntityId, SOUND_SPLATTER, 0.0f);
+ }
+ if (bike->m_fMass > 100.0f) {
+ bike->ApplyMoveForce(CVector(0.0f, 0.0f, 10.0f));
+
+ CVector vehAndWheelDist = wheelPos - bike->GetPosition();
+ bike->ApplyTurnForce(CVector(0.0f, 0.0f, 10.0f), vehAndWheelDist);
+
+ if (bike == FindPlayerVehicle()) {
+ CPad::GetPad(0)->StartShake(300, 70);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+CAutomobile::SetBusDoorTimer(uint32 timer, uint8 type)
+{
+ if(timer < 1000)
+ timer = 1000;
+ if(type == 0)
+ // open and close
+ m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds();
+ else
+ // only close
+ m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds() - 500;
+ m_nBusDoorTimerEnd = m_nBusDoorTimerStart + timer;
+}
+
+void
+CAutomobile::ProcessAutoBusDoors(void)
+{
+ if(CTimer::GetTimeInMilliseconds() < m_nBusDoorTimerEnd){
+ if(m_nBusDoorTimerEnd != 0 && CTimer::GetTimeInMilliseconds() > m_nBusDoorTimerEnd-500){
+ // close door
+ if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & CAR_DOOR_FLAG_LF) == 0){
+ if(IsDoorClosed(DOOR_FRONT_LEFT)){
+ m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds();
+ OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f);
+ }else{
+ OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT,
+ 1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f);
+ }
+ }
+
+ if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & CAR_DOOR_FLAG_RF) == 0){
+ if(IsDoorClosed(DOOR_FRONT_RIGHT)){
+ m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds();
+ OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f);
+ }else{
+ OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT,
+ 1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f);
+ }
+ }
+ }
+ }else{
+ // ended
+ if(m_nBusDoorTimerStart){
+ if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & CAR_DOOR_FLAG_LF) == 0)
+ OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f);
+ if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & CAR_DOOR_FLAG_RF) == 0)
+ OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f);
+ m_nBusDoorTimerStart = 0;
+ m_nBusDoorTimerEnd = 0;
+ }
+ }
+}
+
+void
+CAutomobile::ProcessSwingingDoor(int32 component, eDoors door)
+{
+ if(Damage.GetDoorStatus(door) != DOOR_STATUS_SWINGING)
+ return;
+
+ if (m_aCarNodes[component] == nil)
+ return;
+
+ CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+ CVector pos = mat.GetPosition();
+ float axes[3] = { 0.0f, 0.0f, 0.0f };
+
+ Doors[door].Process(this);
+ axes[Doors[door].m_nAxis] = Doors[door].m_fAngle;
+ mat.SetRotate(axes[0], axes[1], axes[2]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // make wind rip off bonnet
+ if(door == DOOR_BONNET && Doors[door].m_nDoorState == DOORST_OPEN &&
+ DotProduct(m_vecMoveSpeed, GetForward()) > 0.4f){
+#ifdef FIX_BUGS
+ CObject *comp = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_BONNET);
+#else
+ CObject *comp = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_DOOR);
+#endif
+ // make both doors invisible on car
+ SetComponentVisibility(m_aCarNodes[CAR_BONNET], ATOMIC_FLAG_NONE);
+ Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING);
+
+ if(comp){
+ if(CGeneral::GetRandomNumber() & 1)
+ comp->m_vecMoveSpeed = 0.4f*m_vecMoveSpeed + 0.1f*GetRight() + 0.5f*GetUp();
+ else
+ comp->m_vecMoveSpeed = 0.4f*m_vecMoveSpeed - 0.1f*GetRight() + 0.5f*GetUp();
+ comp->ApplyTurnForce(10.0f*GetUp(), GetForward());
+ }
+ }
+}
+
+void
+CAutomobile::Fix(void)
+{
+ int component;
+
+ Damage.ResetDamageStatus();
+
+ if(pHandling->Flags & HANDLING_NO_DOORS){
+ Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING);
+ Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING);
+ Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING);
+ Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING);
+ }
+
+ bIsDamaged = false;
+ RpClumpForAllAtomics((RpClump*)m_rwObject, CVehicleModelInfo::HideAllComponentsAtomicCB, (void*)ATOMIC_FLAG_DAM);
+
+ for(component = CAR_BUMP_FRONT; component < NUM_CAR_NODES; component++){
+ if(m_aCarNodes[component]){
+ CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+ mat.SetTranslate(mat.GetPosition());
+ mat.UpdateRW();
+ }
+ }
+
+ for(component = 0; component < 4; component++)
+ Damage.SetWheelStatus(component, WHEEL_STATUS_OK);
+
+ if(GetModelIndex() == MI_HUNTER){
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0);
+ }else if(IsRealHeli()){
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0);
+ RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0);
+ }
+}
+
+void
+CAutomobile::SetupDamageAfterLoad(void)
+{
+ if(m_aCarNodes[CAR_BUMP_FRONT])
+ SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT);
+ if(m_aCarNodes[CAR_BONNET])
+ SetDoorDamage(CAR_BONNET, DOOR_BONNET);
+ if(m_aCarNodes[CAR_BUMP_REAR])
+ SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR);
+ if(m_aCarNodes[CAR_BOOT])
+ SetDoorDamage(CAR_BOOT, DOOR_BOOT);
+ if(m_aCarNodes[CAR_DOOR_LF])
+ SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+ if(m_aCarNodes[CAR_DOOR_RF])
+ SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+ if(m_aCarNodes[CAR_DOOR_LR])
+ SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT);
+ if(m_aCarNodes[CAR_DOOR_RR])
+ SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT);
+ if(m_aCarNodes[CAR_WING_LF])
+ SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT);
+ if(m_aCarNodes[CAR_WING_RF])
+ SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT);
+ if(m_aCarNodes[CAR_WING_LR])
+ SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT);
+ if(m_aCarNodes[CAR_WING_RR])
+ SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT);
+}
+
+RwObject*
+GetCurrentAtomicObjectCB(RwObject *object, void *data)
+{
+ RpAtomic *atomic = (RpAtomic*)object;
+ assert(RwObjectGetType(object) == rpATOMIC);
+ if(RpAtomicGetFlags(atomic) & rpATOMICRENDER)
+ *(RpAtomic**)data = atomic;
+ return object;
+}
+
+CObject*
+CAutomobile::SpawnFlyingComponent(int32 component, uint32 type)
+{
+ RpAtomic *atomic;
+ RwFrame *frame;
+ RwMatrix *matrix;
+ CObject *obj;
+
+ if(CObject::nNoTempObjects >= NUMTEMPOBJECTS)
+ return nil;
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aCarNodes[component], GetCurrentAtomicObjectCB, &atomic);
+ if(atomic == nil)
+ return nil;
+
+ obj = new CObject();
+ if(obj == nil)
+ return nil;
+
+ if(component == CAR_WINDSCREEN){
+ obj->SetModelIndexNoCreate(MI_CAR_BONNET);
+ }else switch(type){
+ case COMPGROUP_BUMPER:
+ obj->SetModelIndexNoCreate(MI_CAR_BUMPER);
+ break;
+ case COMPGROUP_WHEEL:
+ obj->SetModelIndexNoCreate(MI_CAR_WHEEL);
+ break;
+ case COMPGROUP_DOOR:
+ obj->SetModelIndexNoCreate(MI_CAR_DOOR);
+ obj->SetCenterOfMass(0.0f, -0.5f, 0.0f);
+ obj->bDrawLast = true;
+ break;
+ case COMPGROUP_BONNET:
+ obj->SetModelIndexNoCreate(MI_CAR_BONNET);
+ obj->SetCenterOfMass(0.0f, 0.4f, 0.0f);
+ break;
+ case COMPGROUP_BOOT:
+ obj->SetModelIndexNoCreate(MI_CAR_BOOT);
+ obj->SetCenterOfMass(0.0f, -0.3f, 0.0f);
+ break;
+ case COMPGROUP_PANEL:
+ default:
+ obj->SetModelIndexNoCreate(MI_CAR_PANEL);
+ break;
+ }
+
+ // object needs base model
+ obj->RefModelInfo(GetModelIndex());
+
+ // create new atomic
+ matrix = RwFrameGetLTM(m_aCarNodes[component]);
+ frame = RwFrameCreate();
+ atomic = RpAtomicClone(atomic);
+ *RwFrameGetMatrix(frame) = *matrix;
+ RpAtomicSetFrame(atomic, frame);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ obj->AttachToRwObject((RwObject*)atomic);
+ obj->bDontStream = true;
+
+ // init object
+ obj->m_fMass = 10.0f;
+ obj->m_fTurnMass = 25.0f;
+ obj->m_fAirResistance = 0.97f;
+ obj->m_fElasticity = 0.1f;
+ obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f;
+ obj->ObjectCreatedBy = TEMP_OBJECT;
+ obj->SetIsStatic(false);
+ obj->bIsPickup = false;
+ obj->bUseVehicleColours = true;
+ obj->m_colour1 = m_currentColour1;
+ obj->m_colour2 = m_currentColour2;
+
+ // life time - the more objects the are, the shorter this one will live
+ CObject::nNoTempObjects++;
+ if(CObject::nNoTempObjects > 20)
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/5.0f;
+ else if(CObject::nNoTempObjects > 10)
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/2.0f;
+ else
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000;
+
+ obj->m_vecMoveSpeed = m_vecMoveSpeed;
+ if(obj->m_vecMoveSpeed.z > 0.0f){
+ obj->m_vecMoveSpeed.z *= 1.5f;
+ }else if(GetUp().z > 0.0f &&
+ (component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN)){
+ obj->m_vecMoveSpeed.z *= -1.5f;
+ obj->m_vecMoveSpeed.z += 0.04f;
+ }else{
+ obj->m_vecMoveSpeed.z *= 0.25f;
+ }
+ obj->m_vecMoveSpeed.x *= 0.75f;
+ obj->m_vecMoveSpeed.y *= 0.75f;
+
+ obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f;
+
+ // push component away from car
+ CVector dist = obj->GetPosition() - GetPosition();
+ dist.Normalise();
+ if(component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN){
+ // push these up some
+ dist += GetUp();
+ if(GetUp().z > 0.0f){
+ // simulate fast upward movement if going fast
+ float speed = CVector2D(m_vecMoveSpeed).Magnitude();
+ obj->GetMatrix().Translate(GetUp()*speed);
+ }
+ }
+ obj->ApplyMoveForce(dist);
+
+ if(type == COMPGROUP_WHEEL){
+ obj->m_fTurnMass = 5.0f;
+ obj->m_vecTurnSpeed.x = 0.5f;
+ obj->m_fAirResistance = 0.99f;
+ }
+
+ if(GetStatus() == STATUS_WRECKED && IsVisible() && DotProduct(dist, TheCamera.GetPosition() - GetPosition()) > -0.5f){
+ dist = TheCamera.GetPosition() - GetPosition();
+ dist.Normalise();
+ dist.z += 0.3f;
+ ApplyMoveForce(5.0f*dist);
+ }
+
+ if(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(),
+ this->GetMatrix(), *this->GetColModel(),
+ CWorld::m_aTempColPts, nil, nil) > 0)
+ obj->m_pCollidingEntity = this;
+
+ if(bRenderScorched)
+ obj->bRenderScorched = true;
+
+ CWorld::Add(obj);
+
+ return obj;
+}
+
+CObject*
+CAutomobile::RemoveBonnetInPedCollision(void)
+{
+ CObject *obj;
+
+ if(Damage.GetDoorStatus(DOOR_BONNET) == DOOR_STATUS_SWINGING &&
+ Doors[DOOR_BONNET].RetAngleWhenOpen()*0.4f < Doors[DOOR_BONNET].m_fAngle){
+#ifdef FIX_BUGS
+ obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_BONNET);
+#else
+ obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_DOOR);
+#endif
+ // make both doors invisible on car
+ SetComponentVisibility(m_aCarNodes[CAR_BONNET], ATOMIC_FLAG_NONE);
+ Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING);
+ return obj;
+ }
+ return nil;
+}
+
+void
+CAutomobile::SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents)
+{
+ int status = Damage.GetPanelStatus(panel);
+ if(m_aCarNodes[component] == nil)
+ return;
+ if(status == PANEL_STATUS_SMASHED1){
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_WINDSHIELD_CRACK, 0.0f);
+ // show damaged part
+ SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM);
+ }else if(status == PANEL_STATUS_MISSING){
+ if(!noFlyingComponents)
+ SpawnFlyingComponent(component, COMPGROUP_PANEL);
+ else
+ CGlass::CarWindscreenShatters(this, false);
+ // hide both
+ SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE);
+ }
+}
+
+void
+CAutomobile::SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents)
+{
+ int status = Damage.GetPanelStatus(panel);
+ if(m_aCarNodes[component] == nil){
+ printf("Trying to damage component %d of %s\n",
+ component, CModelInfo::GetModelInfo(GetModelIndex())->GetModelName());
+ return;
+ }
+ if(status == PANEL_STATUS_SMASHED1){
+ // show damaged part
+ SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM);
+ }else if(status == PANEL_STATUS_MISSING){
+ if(!noFlyingComponents)
+ SpawnFlyingComponent(component, COMPGROUP_BUMPER);
+ // hide both
+ SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE);
+ }
+}
+
+void
+CAutomobile::SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents)
+{
+ int status = Damage.GetDoorStatus(door);
+ if(m_aCarNodes[component] == nil){
+ printf("Trying to damage component %d of %s\n",
+ component, CModelInfo::GetModelInfo(GetModelIndex())->GetModelName());
+ return;
+ }
+
+ if(!CanDoorsBeDamaged() && status > DOOR_STATUS_SMASHED && door != DOOR_BONNET && door != DOOR_BOOT){
+ Damage.SetDoorStatus(door, DOOR_STATUS_SMASHED);
+ status = DOOR_STATUS_SMASHED;
+ }
+
+ if(door == DOOR_BOOT && status == DOOR_STATUS_SWINGING && pHandling->Flags & HANDLING_NOSWING_BOOT){
+ Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_MISSING);
+ status = DOOR_STATUS_MISSING;
+ }
+
+ switch(status){
+ case DOOR_STATUS_SMASHED:
+ // show damaged part
+ SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM);
+ break;
+ case DOOR_STATUS_SWINGING:
+ // turn off angle cull for swinging doors
+ RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL);
+ break;
+ case DOOR_STATUS_MISSING:
+ if(!noFlyingComponents){
+ if(door == DOOR_BONNET)
+ SpawnFlyingComponent(component, COMPGROUP_BONNET);
+ else if(door == DOOR_BOOT)
+ SpawnFlyingComponent(component, COMPGROUP_BOOT);
+ else
+ SpawnFlyingComponent(component, COMPGROUP_DOOR);
+ }
+ // hide both
+ SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE);
+ break;
+ }
+}
+
+
+static RwObject*
+SetVehicleAtomicVisibilityCB(RwObject *object, void *data)
+{
+ uint32 flags = (uint32)(uintptr)data;
+ RpAtomic *atomic = (RpAtomic*)object;
+ if((CVisibilityPlugins::GetAtomicId(atomic) & (ATOMIC_FLAG_OK|ATOMIC_FLAG_DAM)) == flags)
+ RpAtomicSetFlags(atomic, rpATOMICRENDER);
+ else
+ RpAtomicSetFlags(atomic, 0);
+ return object;
+}
+
+void
+CAutomobile::SetComponentVisibility(RwFrame *frame, uint32 flags)
+{
+ HideAllComps();
+ bIsDamaged = true;
+ RwFrameForAllObjects(frame, SetVehicleAtomicVisibilityCB, (void*)flags);
+}
+
+void
+CAutomobile::SetupModelNodes(void)
+{
+ int i;
+ for(i = 0; i < NUM_CAR_NODES; i++)
+ m_aCarNodes[i] = nil;
+ CClumpModelInfo::FillFrameArray(GetClump(), m_aCarNodes);
+}
+
+void
+CAutomobile::SetTaxiLight(bool light)
+{
+ bTaxiLight = light;
+}
+
+bool
+CAutomobile::GetAllWheelsOffGround(void)
+{
+ return m_nDriveWheelsOnGround == 0;
+}
+
+void
+CAutomobile::HideAllComps(void)
+{
+ // empty
+}
+
+void
+CAutomobile::ShowAllComps(void)
+{
+ // empty
+}
+
+void
+CAutomobile::ReduceHornCounter(void)
+{
+ if(m_nCarHornTimer != 0)
+ m_nCarHornTimer--;
+}
+
+void
+CAutomobile::SetAllTaxiLights(bool set)
+{
+ m_sAllTaxiLights = set;
+}
+
+void
+CAutomobile::TellHeliToGoToCoors(float x, float y, float z, uint8 speed)
+{
+ AutoPilot.m_nCarMission = MISSION_HELI_FLYTOCOORS;
+ AutoPilot.m_vecDestinationCoors.x = x;
+ AutoPilot.m_vecDestinationCoors.y = y;
+ AutoPilot.m_vecDestinationCoors.z = z;
+ AutoPilot.m_nCruiseSpeed = speed;
+ SetStatus(STATUS_PHYSICS);
+
+ if(m_fOrientation == 0.0f){
+ m_fOrientation = CGeneral::GetATanOfXY(GetForward().x, GetForward().y) + PI;
+ while(m_fOrientation > TWOPI) m_fOrientation -= TWOPI;
+ }
+}
+
+void
+CAutomobile::TellPlaneToGoToCoors(float x, float y, float z, uint8 speed)
+{
+ AutoPilot.m_nCarMission = MISSION_PLANE_FLYTOCOORS;
+ AutoPilot.m_vecDestinationCoors.x = x;
+ AutoPilot.m_vecDestinationCoors.y = y;
+ AutoPilot.m_vecDestinationCoors.z = z;
+ AutoPilot.m_nCruiseSpeed = speed;
+ SetStatus(STATUS_PHYSICS);
+
+ if(m_fOrientation == 0.0f)
+ m_fOrientation = CGeneral::GetATanOfXY(GetForward().x, GetForward().y);
+}
+
+void
+CAutomobile::PopBoot(void)
+{
+ switch(Damage.GetDoorStatus(DOOR_BOOT)){
+ case DOOR_STATUS_OK:
+ case DOOR_STATUS_SMASHED:
+ Doors[DOOR_BOOT].m_fAngle = Doors[DOOR_BOOT].m_fMinAngle;
+ CMatrix mat(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT]));
+ CVector pos = mat.GetPosition();
+ float axes[3] = { 0.0f, 0.0f, 0.0f };
+ axes[Doors[DOOR_BOOT].m_nAxis] = Doors[DOOR_BOOT].m_fAngle;
+ mat.SetRotate(axes[0], axes[1], axes[2]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+}
+
+void
+CAutomobile::PopBootUsingPhysics(void)
+{
+ switch(Damage.GetDoorStatus(DOOR_BOOT))
+ case DOOR_STATUS_OK:
+ case DOOR_STATUS_SMASHED:
+ Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_SWINGING);
+ Doors[DOOR_BOOT].m_fAngVel = -2.0f;
+}
+
+void
+CAutomobile::CloseAllDoors(void)
+{
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ if(!IsDoorMissing(DOOR_FRONT_LEFT))
+ OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f);
+ if(mi->m_numDoors > 1){
+ if(!IsDoorMissing(DOOR_FRONT_RIGHT))
+ OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f);
+ if(mi->m_numDoors > 2){
+ if(!IsDoorMissing(DOOR_REAR_LEFT))
+ OpenDoor(CAR_DOOR_LR, DOOR_REAR_LEFT, 0.0f);
+ if(!IsDoorMissing(DOOR_REAR_RIGHT))
+ OpenDoor(CAR_DOOR_RR, DOOR_REAR_RIGHT, 0.0f);
+ }
+ }
+}
+
+void
+CAutomobile::KnockPedOutCar(eWeaponType weapon, uint16 door, CPed *ped)
+{
+ AnimationId anim = ANIM_STD_KO_FRONT;
+ if(ped == nil)
+ return;
+
+ ped->m_vehDoor = door;
+ ped->SetPedState(PED_IDLE);
+ CAnimManager::BlendAnimation(ped->GetClump(), ped->m_animGroup, ANIM_STD_IDLE, 100.0f);
+ CPed::PedSetOutCarCB(nil, ped);
+ ped->SetMoveState(PEDMOVE_STILL);
+ if(GetUp().z < 0.0f)
+ ped->SetHeading(CGeneral::LimitRadianAngle(GetForward().Heading() + PI));
+ else
+ ped->SetHeading(GetForward().Heading());
+
+ switch(weapon){
+ case WEAPONTYPE_UNARMED:
+ case WEAPONTYPE_UNIDENTIFIED:
+ ped->m_vecMoveSpeed = m_vecMoveSpeed;
+ ped->m_pCollidingEntity = this;
+ anim = ANIM_STD_NUM;
+ break;
+
+ case WEAPONTYPE_BASEBALLBAT:
+ case WEAPONTYPE_RAMMEDBYCAR:
+ case WEAPONTYPE_FALL:
+ ped->m_vecMoveSpeed = m_vecMoveSpeed;
+ anim = ANIM_STD_SPINFORWARD_LEFT;
+ ApplyMoveForce(4.0f*GetUp() + 8.0f*GetRight());
+ break;
+ }
+
+ if(weapon != WEAPONTYPE_UNARMED){
+ ped->SetFall(1000, anim, 0);
+ ped->bIsStanding = false;
+ ped->m_headingRate = 0.0f;
+ }
+ ped->m_pMyVehicle = nil;
+}
+
+#ifdef COMPATIBLE_SAVES
+void
+CAutomobile::Save(uint8*& buf)
+{
+ CVehicle::Save(buf);
+ WriteSaveBuf<CDamageManager>(buf, Damage);
+ SkipSaveBuf(buf, 1500 - 672 - sizeof(CDamageManager));
+}
+
+void
+CAutomobile::Load(uint8*& buf)
+{
+ CVehicle::Load(buf);
+ Damage = ReadSaveBuf<CDamageManager>(buf);
+ SkipSaveBuf(buf, 1500 - 672 - sizeof(CDamageManager));
+ SetupDamageAfterLoad();
+}
+#endif
diff --git a/src/vehicles/Automobile.h b/src/vehicles/Automobile.h
new file mode 100644
index 0000000..16d8691
--- /dev/null
+++ b/src/vehicles/Automobile.h
@@ -0,0 +1,175 @@
+#pragma once
+
+#include "Vehicle.h"
+#include "DamageManager.h"
+#include "Door.h"
+#include "Skidmarks.h"
+
+class CObject;
+
+enum {
+ CARWHEEL_FRONT_LEFT,
+ CARWHEEL_REAR_LEFT,
+ CARWHEEL_FRONT_RIGHT,
+ CARWHEEL_REAR_RIGHT
+};
+
+
+class CAutomobile : public CVehicle
+{
+public:
+ CDamageManager Damage;
+ CDoor Doors[6];
+ RwFrame *m_aCarNodes[NUM_CAR_NODES];
+ CColPoint m_aWheelColPoints[4];
+ float m_aSuspensionSpringRatio[4];
+ float m_aSuspensionSpringRatioPrev[4];
+ float m_aWheelTimer[4]; // set to 4.0 when wheel is touching ground, then decremented
+ float m_auto_unused1;
+ eSkidmarkType m_aWheelSkidmarkType[4];
+ bool m_aWheelSkidmarkBloody[4];
+ bool m_aWheelSkidmarkUnk[4];
+ float m_aWheelRotation[4];
+ float m_aWheelPosition[4];
+ float m_aWheelSpeed[4];
+ uint8 m_auto_unused2;
+#if (defined GTA_PS2 && !defined FIX_BUGS)
+ uint8 m_bombType : 3;
+#endif
+ uint8 bTaxiLight : 1;
+ uint8 bFixedColour : 1;
+ uint8 bBigWheels : 1;
+ uint8 bWaterTight : 1; // no damage for non-player peds
+ uint8 bNotDamagedUpsideDown : 1;
+ uint8 bMoreResistantToDamage : 1;
+ uint8 bTankDetonateCars : 1;
+ uint8 bStuckInSand : 1;
+ uint8 bHeliDestroyed : 1;
+#if (defined GTA_PS2 && !defined FIX_BUGS)
+ CEntity* m_pBombRigger;
+#endif
+ int16 m_doingBurnout;
+ uint16 m_hydraulicState;
+ uint32 m_nBusDoorTimerEnd;
+ uint32 m_nBusDoorTimerStart;
+ float m_aSuspensionSpringLength[4];
+ float m_aSuspensionLineLength[4];
+ float m_fHeightAboveRoad;
+ float m_fTraction;
+ float m_fTireTemperature;
+ float m_fOrientation; // for heli and plane go-to
+ float m_fPlaneSteer; // related to the above
+ float m_fVelocityChangeForAudio;
+ float m_randomValues[6]; // used for what?
+ float m_fFireBlowUpTimer;
+ CPhysical *m_aGroundPhysical[4]; // physicals touching wheels
+ CVector m_aGroundOffset[4]; // from ground object to colpoint
+ CEntity *m_pSetOnFireEntity;
+ float m_weaponDoorTimerLeft; // still don't know what exactly this is
+ float m_weaponDoorTimerRight;
+ float m_fCarGunLR;
+ float m_fCarGunUD;
+ float m_fHeliOrientation;
+ float m_fPropellerRotation;
+ uint8 stuff4[4];
+ uint8 m_nWheelsOnGround;
+ uint8 m_nDriveWheelsOnGround;
+ uint8 m_nDriveWheelsOnGroundPrev;
+ float m_fGasPedalAudio;
+ tWheelState m_aWheelState[4];
+
+ static bool m_sAllTaxiLights;
+
+ CAutomobile(int32 id, uint8 CreatedBy);
+
+ // from CEntity
+ void SetModelIndex(uint32 id);
+ void ProcessControl(void);
+ void Teleport(CVector v);
+ void PreRender(void);
+ void Render(void);
+
+ // from CPhysical
+ int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints);
+
+ // from CVehicle
+ void ProcessControlInputs(uint8);
+ void GetComponentWorldPosition(int32 component, CVector &pos);
+ bool IsComponentPresent(int32 component);
+ void SetComponentRotation(int32 component, CVector rotation);
+ void OpenDoor(int32 component, eDoors door, float openRatio);
+ void ProcessOpenDoor(uint32, uint32, float);
+ bool IsDoorReady(eDoors door);
+ bool IsDoorFullyOpen(eDoors door);
+ bool IsDoorClosed(eDoors door);
+ bool IsDoorMissing(eDoors door);
+ bool IsDoorReady(uint32 door);
+ bool IsDoorMissing(uint32 door);
+ bool IsOpenTopCar(void);
+ void RemoveRefsToVehicle(CEntity *ent);
+ void BlowUpCar(CEntity *ent);
+ bool SetUpWheelColModel(CColModel *colModel);
+ void BurstTyre(uint8 tyre, bool applyForces);
+ bool IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset);
+ float GetHeightAboveRoad(void);
+ void PlayCarHorn(void);
+
+ void FireTruckControl(void);
+ void TankControl(void);
+ void HydraulicControl(void);
+ void VehicleDamage(float impulse, uint16 damagedPiece);
+ void ProcessBuoyancy(void);
+ void DoDriveByShootings(void);
+ void DoHoverSuspensionRatios(void);
+ int32 RcbanditCheckHitWheels(void);
+ int32 RcbanditCheck1CarWheels(CPtrList &list);
+ void PlaceOnRoadProperly(void);
+ void dmgDrawCarCollidingParticles(const CVector &pos, float amount);
+ void AddDamagedVehicleParticles(void);
+ int32 AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed);
+ void PlayHornIfNecessary(void);
+ void ResetSuspension(void);
+ void SetupSuspensionLines(void);
+ void ScanForCrimes(void);
+ void BlowUpCarsInPath(void);
+ bool HasCarStoppedBecauseOfLight(void);
+ void SetBusDoorTimer(uint32 timer, uint8 type);
+ void ProcessAutoBusDoors(void);
+ void ProcessSwingingDoor(int32 component, eDoors door);
+ void SetupDamageAfterLoad(void);
+ CObject *SpawnFlyingComponent(int32 component, uint32 type);
+ CObject *RemoveBonnetInPedCollision(void);
+ void SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents = false);
+ void SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents = false);
+ void SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents = false);
+
+ void TellHeliToGoToCoors(float x, float y, float z, uint8 speed);
+ void TellPlaneToGoToCoors(float x, float y, float z, uint8 speed);
+ void SetHeliOrientation(float orient) { m_fHeliOrientation = orient; }
+ void ClearHeliOrientation(void) { m_fHeliOrientation = -1.0f; }
+
+ void Fix(void);
+ void SetComponentVisibility(RwFrame *frame, uint32 flags);
+ void SetupModelNodes(void);
+ void SetTaxiLight(bool light);
+ bool GetAllWheelsOffGround(void);
+ void HideAllComps(void);
+ void ShowAllComps(void);
+ void ReduceHornCounter(void);
+
+ void PopBoot(void);
+ void PopBootUsingPhysics(void);
+ void CloseAllDoors(void);
+ void KnockPedOutCar(eWeaponType weapon, uint16 door, CPed *ped);
+
+#ifdef COMPATIBLE_SAVES
+ virtual void Save(uint8*& buf);
+ virtual void Load(uint8*& buf);
+#endif
+ static const uint32 nSaveStructSize;
+
+ static void SetAllTaxiLights(bool set);
+};
+
+extern CVector vecHunterGunPos;
+extern bool bAllCarCheat; \ No newline at end of file
diff --git a/src/vehicles/Bike.cpp b/src/vehicles/Bike.cpp
new file mode 100644
index 0000000..3e4c3a3
--- /dev/null
+++ b/src/vehicles/Bike.cpp
@@ -0,0 +1,2961 @@
+#include "common.h"
+#include "General.h"
+#include "Pad.h"
+#include "DMAudio.h"
+#include "Clock.h"
+#include "Timecycle.h"
+#include "ZoneCull.h"
+#include "Camera.h"
+#include "Darkel.h"
+#include "Rubbish.h"
+#include "Explosion.h"
+#include "Particle.h"
+#include "ParticleObject.h"
+#include "Shadows.h"
+#include "PointLights.h"
+#include "Coronas.h"
+#include "SpecialFX.h"
+#include "WaterLevel.h"
+#include "Floater.h"
+#include "World.h"
+#include "SurfaceTable.h"
+#include "Weather.h"
+#include "Record.h"
+#include "CarCtrl.h"
+#include "CarAI.h"
+#include "Script.h"
+#include "Stats.h"
+#include "Replay.h"
+#include "AnimManager.h"
+#include "RpAnimBlend.h"
+#include "AnimBlendAssociation.h"
+#include "Ped.h"
+#include "PlayerPed.h"
+#include "DamageManager.h"
+#include "Vehicle.h"
+#include "Automobile.h"
+#include "Bike.h"
+#include "Debug.h"
+
+const uint32 CBike::nSaveStructSize =
+#ifdef COMPATIBLE_SAVES
+ 1260;
+#else
+ sizeof(CBoat);
+#endif
+
+
+// TODO: maybe put this somewhere else
+inline void
+GetRelativeMatrix(RwMatrix *mat, RwFrame *frm, RwFrame *end)
+{
+ *mat = *RwFrameGetMatrix(frm);
+ frm = RwFrameGetParent(frm);
+ while(frm){
+ RwMatrixTransform(mat, RwFrameGetMatrix(frm), rwCOMBINEPOSTCONCAT);
+ frm = RwFrameGetParent(frm);
+ if(frm == end)
+ frm = nil;
+ }
+}
+
+#define FAKESUSPENSION (99999.992f)
+
+CBike::CBike(int32 id, uint8 CreatedBy)
+ : CVehicle(CreatedBy)
+{
+ int i;
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
+ switch(id){
+ case MI_ANGEL:
+ case MI_FREEWAY:
+ m_bikeAnimType = ASSOCGRP_BIKE_HARLEY;
+ break;
+ case MI_PIZZABOY:
+ case MI_FAGGIO:
+ m_bikeAnimType = ASSOCGRP_BIKE_VESPA;
+ break;
+ case MI_PCJ600:
+ m_bikeAnimType = ASSOCGRP_BIKE_STANDARD;
+ break;
+ case MI_SANCHEZ:
+ m_bikeAnimType = ASSOCGRP_BIKE_DIRT;
+ break;
+ default: assert(0 && "invalid bike model ID");
+ }
+ m_vehType = VEHICLE_TYPE_BIKE;
+
+ m_fFireBlowUpTimer = 0.0f;
+ m_doingBurnout = 0;
+ m_bike_flag01 = false;
+
+ SetModelIndex(id);
+
+ pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
+ pBikeHandling = mod_HandlingManager.GetBikePointer((tVehicleType)mi->m_handlingId);
+ pFlyingHandling = mod_HandlingManager.GetFlyingPointer((tVehicleType)mi->m_handlingId);
+
+ m_bike_unused1 = 20.0f;
+ m_bike_unused2 = 0;
+
+ mi->ChooseVehicleColour(m_currentColour1, m_currentColour2);
+
+ m_fRearForkLength = 0.0f;
+ m_fFrontForkY = 0.0;
+ m_fFrontForkZ = 0.0;
+ m_fFrontForkSlope = Tan(DEGTORAD(mi->m_bikeSteerAngle));
+
+ m_fMass = pHandling->fMass;
+ m_fTurnMass = pHandling->fTurnMass;
+ m_vecCentreOfMass = pHandling->CentreOfMass;
+ m_vecCentreOfMass.z = 0.1f;
+ m_fAirResistance = pHandling->Dimension.x*pHandling->Dimension.z/m_fMass;
+ m_fElasticity = 0.05f;
+ m_fBuoyancy = pHandling->fBuoyancy;
+
+ m_fSteerAngle = 0.0f;
+ m_fWheelAngle = 0.0f;
+ m_fLeanLRAngle = 0.0f;
+ m_fLeanLRAngle2 = 0.0f;
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 0.0f;
+ m_fLeanInput = 0.0f;
+ m_fPedLeanAmountLR = 0.0f;
+ m_fPedLeanAmountUD = 0.0f;
+ m_pSetOnFireEntity = nil;
+ m_pBombRigger = nil;
+ m_fGasPedalAudio = 0.0f;
+ m_bike_flag02 = false;
+ bWaterTight = false;
+ bIsBeingPickedUp = false;
+ bIsStanding = false;
+ bExtraSpeed = false;
+ bIsOnFire = false;
+ bWheelieCam = false;
+
+ m_fTireTemperature = 1.0f;
+ m_fBrakeDestabilization = 0.0f;
+ m_fVelocityChangeForAudio = 0;
+
+ for(i = 0; i < 2; i++){
+ m_aWheelRotation[i] = 0.0f;
+ m_aWheelSpeed[i] = 0.0f;
+ m_aWheelState[i] = WHEEL_STATE_NORMAL;
+ m_aWheelSkidmarkType[i] = SKIDMARK_NORMAL;
+ m_aWheelSkidmarkBloody[i] = false;
+ m_aWheelSkidmarkUnk[0] = false;
+ m_wheelStatus[i] = WHEEL_STATUS_OK;
+ }
+
+ for(i = 0; i < 4; i++){
+ m_aGroundPhysical[i] = nil;
+ m_aGroundOffset[i] = CVector(0.0f, 0.0f, 0.0f);
+ m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i] = 1.0f;
+ m_aWheelTimer[i] = 0.0f;
+ }
+
+ m_nWheelsOnGround = 0;
+ m_nDriveWheelsOnGround = 0;
+ m_nDriveWheelsOnGroundPrev = 0;
+ m_fHeightAboveRoad = 0.0f;
+ m_fTraction = 1.0f;
+
+ CColModel *colModel = mi->GetColModel();
+ if(colModel->lines == nil){
+ colModel->lines = (CColLine*)RwMalloc(4*sizeof(CColLine));
+ colModel->numLines = 4;
+ }
+ // BUG? this would make more sense in the if above
+ colModel->lines[0].p0.z = FAKESUSPENSION;
+
+ SetupSuspensionLines();
+
+ AutoPilot.m_nCarMission = MISSION_NONE;
+ AutoPilot.m_nTempAction = TEMPACT_NONE;
+ AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds();
+ AutoPilot.m_bStayInCurrentLevel = false;
+
+ SetStatus(STATUS_SIMPLE);
+ bUseCollisionRecords = true;
+ m_nNumPassengers = 0;
+ bIsVan = false;
+ bIsBus = false;
+ bIsBig = false;
+ bLowVehicle = false;
+ bPedPhysics = false;
+
+ bLeanMatrixClean = false;
+ m_leanMatrix = GetMatrix();
+}
+
+void
+CBike::SetModelIndex(uint32 id)
+{
+ CVehicle::SetModelIndex(id);
+ SetupModelNodes();
+}
+
+#define SAND_SLOWDOWN (0.02f)
+CVector vecTestResistance(0.9995f, 0.9f, 0.95f);
+float fDAxisX = 1.0f;
+float fDAxisXExtra = 100.0f;
+float fDAxisY = 1000.0f;
+float fInAirXRes = 0.98f;
+float fFlySpeedMult = -0.6f;
+
+#pragma optimize("", off) // a workaround for another compiler bug =P, original had optimize off for this function too though
+
+void
+CBike::ProcessControl(void)
+{
+ int i;
+ float wheelRot;
+ float acceleration = 0.0f;
+ bool bBalancedByRider = false;
+ bool bStuckInSand = false;
+ float brake = 0.0f;
+ CColModel *colModel = GetColModel();
+ float wheelScale = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_wheelScale;
+ bWarnedPeds = false;
+ bLeanMatrixClean = false;
+ m_doingBurnout = 0;
+ bExtraSpeed = false;
+ bRestingOnPhysical = false;
+
+ if(CReplay::IsPlayingBack())
+ return;
+
+ ProcessCarAlarm();
+
+ ActivateBombWhenEntered();
+
+ CRubbish::StirUp(this);
+
+ UpdateClumpAlpha();
+
+ AutoPilot.m_bSlowedDownBecauseOfCars = false;
+ AutoPilot.m_bSlowedDownBecauseOfPeds = false;
+
+ switch(GetStatus()){
+ case STATUS_PLAYER:
+ bBalancedByRider = true;
+ bIsBeingPickedUp = false;
+ if(FindPlayerPed()->GetPedState() != PED_EXIT_CAR && FindPlayerPed()->GetPedState() != PED_DRAG_FROM_CAR){
+ ProcessControlInputs(0);
+
+ if(m_fLeanInput < 0.0f){
+ m_vecCentreOfMass.y = pHandling->CentreOfMass.y + pBikeHandling->fLeanBakCOM*m_fLeanInput;
+ CVector com = m_vecCentreOfMass;
+#ifdef FIX_BUGS
+ // center of mass has to have world space orientation. unfortunately we can't do wheelies
+ // at high speed then, flipping y here is like riding south without this fix where wheelies work
+ com.y = -com.y;
+ com = Multiply3x3(GetMatrix(), com);
+#endif
+ if(m_fBrakePedal == 0.0f && !bIsHandbrakeOn || m_nWheelsOnGround == 0){
+ if(GetModelIndex() == MI_SANCHEZ){
+ float force = m_fLeanInput*m_fTurnMass*pBikeHandling->fLeanBackForce*Min(m_vecMoveSpeed.Magnitude(), 0.1f);
+ force *= 0.7f*m_fGasPedal + 0.3f;
+ ApplyTurnForce(-force*CTimer::GetTimeStep()*GetUp(), com+GetForward());
+ }else{
+ float force = m_fLeanInput*m_fTurnMass*pBikeHandling->fLeanBackForce*Min(m_vecMoveSpeed.Magnitude(), 0.1f);
+ force *= 0.5f*m_fGasPedal + 0.5f;
+ ApplyTurnForce(-force*CTimer::GetTimeStep()*GetUp(), com+GetForward());
+ }
+ }
+ }else{
+ m_vecCentreOfMass.y = pHandling->CentreOfMass.y + pBikeHandling->fLeanFwdCOM*m_fLeanInput;
+ CVector com = m_vecCentreOfMass;
+#ifdef FIX_BUGS
+ // see above
+ com.y = -com.y;
+ com = Multiply3x3(GetMatrix(), com);
+#endif
+ if(m_fBrakePedal < 0.0f || m_nWheelsOnGround == 0){
+ float force = m_fLeanInput*m_fTurnMass*pBikeHandling->fLeanFwdForce*Min(m_vecMoveSpeed.Magnitude(), 0.1f);
+ ApplyTurnForce(-force*CTimer::GetTimeStep()*GetUp(), com+GetForward());
+ }
+ }
+
+ PruneReferences();
+
+ if(GetStatus() == STATUS_PLAYER && !CRecordDataForChase::IsRecording())
+ DoDriveByShootings();
+
+ if(m_aSuspensionSpringRatio[0] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[0].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[1] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[1].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[2] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[2].surfaceB) == ADHESIVE_SAND ||
+ m_aSuspensionSpringRatio[3] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[3].surfaceB) == ADHESIVE_SAND){
+ CVector parallelSpeed = m_vecMoveSpeed - DotProduct(m_vecMoveSpeed, GetUp())*GetUp();
+ if(m_fGasPedal > 0.3f){
+ if(parallelSpeed.MagnitudeSqr() < SQR(0.3f))
+ bStuckInSand = true;
+ parallelSpeed -= DotProduct(parallelSpeed, GetForward())*GetForward();
+ }
+ ApplyMoveForce(parallelSpeed * -CTimer::GetTimeStep()*SAND_SLOWDOWN*m_fMass);
+ }
+ }
+ if(CPad::GetPad(0)->WeaponJustDown())
+ ActivateBomb();
+ break;
+
+ case STATUS_PLAYER_PLAYBACKFROMBUFFER:
+ bBalancedByRider = true;
+ break;
+
+ case STATUS_SIMPLE:
+ CCarAI::UpdateCarAI(this);
+ CPhysical::ProcessControl();
+ CCarCtrl::UpdateCarOnRails(this);
+
+ m_nWheelsOnGround = 2;
+ m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround;
+ m_nDriveWheelsOnGround = 2;
+
+ pHandling->Transmission.CalculateGearForSimpleCar(AutoPilot.m_fMaxTrafficSpeed/50.0f, m_nCurrentGear);
+
+ wheelRot = ProcessWheelRotation(WHEEL_STATE_NORMAL, GetForward(), m_vecMoveSpeed, 0.5f*wheelScale);
+ for(i = 0; i < 2; i++)
+ m_aWheelRotation[i] += wheelRot;
+
+ PlayHornIfNecessary();
+ ReduceHornCounter();
+ bVehicleColProcessed = false;
+ bAudioChangingGear = false;
+ bWheelieCam = false;
+ // that's all we do for simple vehicles
+ return;
+
+ case STATUS_PHYSICS:
+ CCarAI::UpdateCarAI(this);
+ CCarCtrl::SteerAICarWithPhysics(this);
+ PlayHornIfNecessary();
+
+ bBalancedByRider = true;
+ bWheelieCam = false;
+
+ if(bIsBeingCarJacked){
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ }else
+ bIsBeingPickedUp = false;
+ break;
+
+ case STATUS_ABANDONED:
+ m_fBrakePedal = 0.0f;
+ if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f) || bIsStanding)
+ bIsHandbrakeOn = true;
+ else
+ bIsHandbrakeOn = false;
+
+ m_fGasPedal = 0.0f;
+#ifdef FIX_BUGS
+ if(!IsAlarmOn())
+#endif
+ m_nCarHornTimer = 0;
+
+ bBalancedByRider = (pDriver || pPassengers[0] || bIsBeingCarJacked) && !bIsStanding;
+ m_fPedLeanAmountLR = 0.0f;
+ m_fPedLeanAmountUD = 0.0f;
+ bWheelieCam = false;
+
+ if(bIsBeingCarJacked){
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ }
+ break;
+
+ case STATUS_WRECKED:
+ m_fBrakePedal = 0.05f;
+ bIsHandbrakeOn = true;
+
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+#ifdef FIX_BUGS
+ if(!IsAlarmOn())
+#endif
+ m_nCarHornTimer = 0;
+
+ bBalancedByRider = false;
+ bWheelieCam = false;
+ m_fPedLeanAmountLR = 0.0f;
+ m_fPedLeanAmountUD = 0.0f;
+ break;
+
+ case STATUS_PLAYER_DISABLED:
+ if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f)){
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ }else{
+ m_fBrakePedal = 0.0f;
+ bIsHandbrakeOn = false;
+ }
+
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+#ifdef FIX_BUGS
+ if(!IsAlarmOn())
+#endif
+ m_nCarHornTimer = 0;
+
+ bBalancedByRider = true;
+ bWheelieCam = false;
+ break;
+ }
+
+ if(bIsStanding)
+ if(Abs(GetRight().z) > 0.35f || Abs(GetForward().z) > 0.5f)
+ bIsStanding = false;
+
+ if(bBalancedByRider || bIsBeingPickedUp || bIsStanding){
+ float fDx = fDAxisX;
+ CVector res = vecTestResistance;
+ CVector localTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix());
+
+ if(GetStatus() == STATUS_PLAYER){
+ if(m_aWheelTimer[BIKESUSP_F1] == 0.0f && m_aWheelTimer[BIKESUSP_F2] == 0.0f){
+ fDx = fDAxisXExtra;
+ if(!(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f) &&
+ GetForward().z > 0.0f)
+ res.x -= Min(0.25f*Abs(pBikeHandling->fWheelieAng-GetForward().z), 0.07f);
+ else
+ res.x = fInAirXRes;
+ }else if(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f){
+ fDx = fDAxisXExtra;
+ if(GetForward().z < 0.0f)
+ res.x *= Min(0.3f*Abs(pBikeHandling->fStoppieAng-GetForward().z), 0.1f) + 0.9f;
+ }
+ }
+
+ res.x *= 1.0f/(fDx*SQR(localTurnSpeed.x) + 1.0f);
+ res.y *= 1.0f/(fDAxisY*SQR(localTurnSpeed.y) + 1.0f);
+ res.x = Pow(res.x, CTimer::GetTimeStep());
+ res.y = Pow(res.y, CTimer::GetTimeStep());
+ float turnX = localTurnSpeed.x*(res.x - 1.0f);
+ float turnY = localTurnSpeed.y*(res.y - 1.0f);
+
+ res = -GetUp() * turnY * m_fTurnMass;
+ ApplyTurnForce(res, GetRight() + Multiply3x3(GetMatrix(), m_vecCentreOfMass));
+
+ res = GetUp() * turnX * m_fTurnMass;
+ ApplyTurnForce(res, GetForward() + Multiply3x3(GetMatrix(), m_vecCentreOfMass));
+
+ if(GetStatus() != STATUS_PLAYER)
+ m_vecCentreOfMass = pHandling->CentreOfMass;
+ }else{
+ m_vecCentreOfMass = pHandling->CentreOfMass;
+ m_vecCentreOfMass.z = pBikeHandling->fNoPlayerCOMz;
+ }
+
+ // Skip physics if object is found to have been static recently
+ bool skipPhysics = false;
+ if(!bIsStuck && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED) && !bIsBeingPickedUp){
+ bool makeStatic = false;
+ float moveSpeedLimit, turnSpeedLimit, distanceLimit;
+
+ if(!bVehicleColProcessed &&
+ m_vecMoveSpeed.IsZero() &&
+ // BUG? m_aSuspensionSpringRatioPrev[3] is checked twice in the game. also, why 3?
+ m_aSuspensionSpringRatioPrev[3] != 1.0f)
+ makeStatic = true;
+
+ if(GetStatus() == STATUS_WRECKED){
+ moveSpeedLimit = 0.006f;
+ turnSpeedLimit = 0.0015f;
+ distanceLimit = 0.015f;
+ }else{
+ moveSpeedLimit = 0.003f;
+ turnSpeedLimit = 0.0009f;
+ distanceLimit = 0.005f;
+ }
+
+ m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f;
+ m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f;
+
+ if(m_vecMoveSpeedAvg.MagnitudeSqr() <= sq(moveSpeedLimit*CTimer::GetTimeStep()) &&
+ m_vecTurnSpeedAvg.MagnitudeSqr() <= sq(turnSpeedLimit*CTimer::GetTimeStep()) &&
+ m_fDistanceTravelled < distanceLimit ||
+ makeStatic){
+ m_nStaticFrames++;
+
+ if(m_nStaticFrames > 10 || makeStatic)
+ if(!CCarCtrl::MapCouldMoveInThisArea(GetPosition().x, GetPosition().y)){
+ if(!makeStatic || m_nStaticFrames > 10)
+ m_nStaticFrames = 10;
+
+ skipPhysics = true;
+
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ }
+ }else
+ m_nStaticFrames = 0;
+ }
+
+ // Postpone
+ for(i = 0; i < 4; i++)
+ if(m_aGroundPhysical[i]){
+ bRestingOnPhysical = true;
+ if(!CWorld::bForceProcessControl && m_aGroundPhysical[i]->bIsInSafePosition){
+ bWasPostponed = true;
+ return;
+ }
+ }
+
+ if(bRestingOnPhysical){
+ skipPhysics = false;
+ m_nStaticFrames = 0;
+ }
+
+ VehicleDamage();
+
+ if(skipPhysics){
+ bHasContacted = false;
+ bIsInSafePosition = false;
+ bWasPostponed = false;
+ bHasHitWall = false;
+ m_nCollisionRecords = 0;
+ bHasCollided = false;
+ bVehicleColProcessed = false;
+ bAudioChangingGear = false;
+ m_nDamagePieceType = 0;
+ m_fDamageImpulse = 0.0f;
+ m_pDamageEntity = nil;
+ m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
+// missing. BUG?
+// m_fTireTemperature = 1.0f;
+
+ if(bIsStanding && m_fWheelAngle < DEGTORAD(20.0f))
+ m_fWheelAngle += DEGTORAD(1.0f)*CTimer::GetTimeStep();
+ if(bIsStanding){
+ float f = Pow(0.97f, CTimer::GetTimeStep());
+ m_fLeanLRAngle2 = m_fLeanLRAngle2*f - (Asin(clamp(GetRight().z,-1.0f,1.0f))+DEGTORAD(15.0f))*(1.0f-f);
+ m_fLeanLRAngle = m_fLeanLRAngle2;
+ }
+ }else{
+
+ // This has to be done if ProcessEntityCollision wasn't called
+ if(!bVehicleColProcessed){
+ CMatrix mat(GetMatrix());
+ bIsStuck = false;
+ bHasContacted = false;
+ bIsInSafePosition = false;
+ bWasPostponed = false;
+ bHasHitWall = false;
+ m_fDistanceTravelled = 0.0f;
+ m_bIsVehicleBeingShifted = false;
+ bSkipLineCol = false;
+ ApplyMoveSpeed();
+ ApplyTurnSpeed();
+ for(i = 0; CheckCollision() && i < 5; i++){
+ GetMatrix() = mat;
+ ApplyMoveSpeed();
+ ApplyTurnSpeed();
+ }
+ bIsInSafePosition = true;
+ bIsStuck = false;
+ }
+
+ if(!(bBalancedByRider || bIsBeingPickedUp || bIsStanding)){
+ if(GetRight().z < 0.0f){
+ if(m_fSteerAngle > -DEGTORAD(25.0f))
+ m_fSteerAngle -= DEGTORAD(0.5f)*CTimer::GetTimeStep();
+ }else{
+ if(m_fSteerAngle < DEGTORAD(25.0f))
+ m_fSteerAngle += DEGTORAD(0.5f)*CTimer::GetTimeStep();
+ }
+ }
+
+ // Lean forward speed up
+ float savedAirResistance = m_fAirResistance;
+ if(GetStatus() == STATUS_PLAYER && pDriver){
+ CAnimBlendAssociation *assoc = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_LEANF);
+ if(assoc && assoc->blendAmount > 0.5f &&
+ assoc->currentTime > 0.06f && assoc->currentTime < 0.14f){
+ m_fAirResistance *= 0.6f;
+ if(m_fGasPedal > 0.5f && DotProduct(m_vecMoveSpeed, GetForward()) > 0.25f){
+ ApplyMoveForce(0.2f*m_fMass*GRAVITY*CTimer::GetTimeStep()*GetForward());
+ bExtraSpeed = true;
+ }
+ }
+ }
+
+ CPhysical::ProcessControl();
+ m_fAirResistance = savedAirResistance;
+
+ ProcessBuoyancy();
+
+ // Rescale spring ratios, i.e. subtract wheel radius
+ for(i = 0; i < 4; i++){
+ // wheel radius in relation to suspension line
+ float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i];
+ // rescale such that 0.0 is fully compressed and 1.0 is fully extended
+ m_aSuspensionSpringRatio[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius);
+ }
+
+ int rnd = 0;
+ float fwdSpeed = Abs(DotProduct(m_vecMoveSpeed, GetForward()));
+ CVector contactPoints[4]; // relative to model
+ CVector contactSpeeds[4]; // speed at contact points
+ CVector springDirections[4]; // normalized, in model space
+
+ for(i = 0; i < 4; i++){
+ // Set spring under certain circumstances
+ if(m_wheelStatus[i/2] == WHEEL_STATUS_MISSING)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ else if(m_wheelStatus[i/2] == WHEEL_STATUS_BURST){
+ // wheel more bumpy the faster we are
+ if(i == BIKESUSP_F1 || i == BIKESUSP_R1)
+ rnd = CGeneral::GetRandomNumberInRange(0, (uint16)(40*fwdSpeed) + 98) < 100;
+ if(rnd){
+ m_aSuspensionSpringRatio[i] += 0.3f*(m_aSuspensionLineLength[i]-m_aSuspensionSpringLength[i])/m_aSuspensionSpringLength[i];
+ if(m_aSuspensionSpringRatio[i] > 1.0f)
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+ }
+
+ // get points and directions if spring is compressed
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ contactPoints[i] = m_aWheelColPoints[i].point - GetPosition();
+ springDirections[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1 - colModel->lines[i].p0);
+ springDirections[i].Normalise();
+ }
+ }
+
+ m_aWheelSkidmarkType[0] = m_aWheelSkidmarkType[1] = SKIDMARK_NORMAL;
+ m_aWheelSkidmarkUnk[0] = m_aWheelSkidmarkUnk[1] = false;
+
+ // Make springs push up vehicle
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatio[i] < 1.0f){
+ float bias = pHandling->fSuspensionBias;
+ if(i == BIKESUSP_R1 || i == BIKESUSP_R2)
+ bias = 1.0f - bias;
+
+ if(m_aWheelColPoints[i].normal.z > 0.35f)
+ ApplySpringCollisionAlt(pHandling->fSuspensionForceLevel,
+ springDirections[i], contactPoints[i],
+ m_aSuspensionSpringRatio[i], bias, m_aWheelColPoints[i].normal);
+ else
+ ApplySpringCollision(pHandling->fSuspensionForceLevel,
+ springDirections[i], contactPoints[i],
+ m_aSuspensionSpringRatio[i], bias);
+
+ if(m_aWheelColPoints[i].surfaceB == SURFACE_GRASS ||
+ m_aWheelColPoints[i].surfaceB == SURFACE_MUD_DRY){
+ if(i < 2)
+ m_aWheelSkidmarkType[0] = SKIDMARK_MUDDY;
+ else
+ m_aWheelSkidmarkType[1] = SKIDMARK_MUDDY;
+ }else if(m_aWheelColPoints[i].surfaceB == SURFACE_SAND ||
+ m_aWheelColPoints[i].surfaceB == SURFACE_SAND_BEACH){
+ if(i < 2){
+ m_aWheelSkidmarkType[0] = SKIDMARK_SANDY;
+ m_aWheelSkidmarkUnk[0] = true;
+ }else{
+ m_aWheelSkidmarkType[1] = SKIDMARK_SANDY;
+ m_aWheelSkidmarkUnk[1] = true;
+ }
+ }
+ }else{
+ contactPoints[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1);
+ }
+ }
+
+ // Get speed at contact points
+ for(i = 0; i < 4; i++){
+ contactSpeeds[i] = GetSpeed(contactPoints[i]);
+ if(m_aGroundPhysical[i]){
+ // subtract movement of physical we're standing on
+ contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]);
+#ifndef FIX_BUGS
+ // this shouldn't be reset because we still need it below
+ m_aGroundPhysical[i] = nil;
+#endif
+ }
+ }
+
+ CVector normal;
+ if(m_aSuspensionSpringRatio[0] < 1.0f || m_aSuspensionSpringRatio[1] < 1.0f){
+ normal = m_aSuspensionSpringRatio[0] < 1.0f ? m_aWheelColPoints[0].normal : m_aWheelColPoints[1].normal;
+ if(normal.z > 0.35f)
+ springDirections[0] = -normal;
+ normal = m_aSuspensionSpringRatio[1] < 1.0f ? m_aWheelColPoints[1].normal : m_aWheelColPoints[0].normal;
+ if(normal.z > 0.35f)
+ springDirections[1] = -normal;
+ }
+ if(m_aSuspensionSpringRatio[2] < 1.0f || m_aSuspensionSpringRatio[3] < 1.0f){
+ normal = m_aSuspensionSpringRatio[2] < 1.0f ? m_aWheelColPoints[2].normal : m_aWheelColPoints[3].normal;
+ if(normal.z > 0.35f)
+ springDirections[2] = -normal;
+ normal = m_aSuspensionSpringRatio[3] < 1.0f ? m_aWheelColPoints[3].normal : m_aWheelColPoints[2].normal;
+ if(normal.z > 0.35f)
+ springDirections[3] = -normal;
+ }
+
+ // game has dead code here if m_vecMoveSpeed.Magnitude() < 0.01f
+
+ // dampen springs
+ for(i = 0; i < 4; i++)
+ if(m_aSuspensionSpringRatio[i] < 1.0f)
+ ApplySpringDampening(pHandling->fSuspensionDampingLevel,
+ springDirections[i], contactPoints[i], contactSpeeds[i]);
+
+ // Get speed at contact points again
+ for(i = 0; i < 4; i++){
+ contactSpeeds[i] = GetSpeed(contactPoints[i]);
+ if(m_aGroundPhysical[i]){
+ // subtract movement of physical we're standing on
+ contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]);
+ m_aGroundPhysical[i] = nil;
+ }
+ }
+
+ bool gripCheat = true;
+ fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward());
+ if(!CVehicle::bCheat3)
+ gripCheat = false;
+ acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat);
+ acceleration /= m_fForceMultiplier;
+
+ brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep();
+ bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING);
+ float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias;
+ float brakeBiasRear = neutralHandling ? 1.0f : 2.0f*(1.0f-pHandling->fBrakeBias);
+ float tractionBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fTractionBias;
+ float tractionBiasRear = neutralHandling ? 1.0f : 2.0f-tractionBiasFront;
+
+ // Count how many wheels are touching the ground
+
+ m_nWheelsOnGround = 0;
+ m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround;
+ m_nDriveWheelsOnGround = 0;
+
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatio[i] < 1.0f)
+ m_aWheelTimer[i] = 4.0f;
+ else
+ m_aWheelTimer[i] = Max(m_aWheelTimer[i]-CTimer::GetTimeStep(), 0.0f);
+
+ if(m_aWheelTimer[i] > 0.0f){
+ m_nWheelsOnGround++;
+ if(i == BIKESUSP_R1 || i == BIKESUSP_R2)
+ m_nDriveWheelsOnGround = 1;
+ if(m_nWheelsOnGround == 1)
+ m_vecAvgSurfaceNormal = m_aWheelColPoints[i].normal;
+ else
+ m_vecAvgSurfaceNormal += m_aWheelColPoints[i].normal;
+ }
+ }
+
+ if(m_nWheelsOnGround == 0)
+ m_vecAvgSurfaceNormal = CVector(0.0f, 0.0f, 1.0f);
+ else{
+ m_vecAvgSurfaceNormal /= m_nWheelsOnGround;
+ if(DotProduct(m_vecAvgSurfaceNormal, GetUp()) < -0.5f)
+ m_vecAvgSurfaceNormal *= -1.0f;
+ }
+
+ // Find contact points for wheel processing
+ int frontLine = m_aSuspensionSpringRatio[BIKESUSP_F1] < m_aSuspensionSpringRatio[BIKESUSP_F2] ?
+ BIKESUSP_F1 : BIKESUSP_F2;
+ CVector frontContact(0.0f,
+ colModel->lines[BIKESUSP_F1].p0.y,
+ colModel->lines[BIKESUSP_F1].p0.z - m_aSuspensionSpringRatio[frontLine]*m_aSuspensionSpringLength[BIKESUSP_F1] - 0.5f*wheelScale);
+ frontContact = Multiply3x3(GetMatrix(), frontContact);
+
+ int rearLine = m_aSuspensionSpringRatio[BIKESUSP_R1] < m_aSuspensionSpringRatio[BIKESUSP_R2] ?
+ BIKESUSP_R1 : BIKESUSP_R2;
+ CVector rearContact(0.0f,
+ colModel->lines[BIKESUSP_R1].p0.y,
+ colModel->lines[BIKESUSP_R1].p0.z - m_aSuspensionSpringRatio[rearLine]*m_aSuspensionSpringLength[BIKESUSP_R1] - 0.5f*wheelScale);
+ rearContact = Multiply3x3(GetMatrix(), rearContact);
+
+ float traction = 0.004f * m_fTraction;
+ traction *= pHandling->fTractionMultiplier / 4.0f;
+
+ // Turn wheel
+ if(GetStatus() == STATUS_PLAYER || !bIsStanding || bIsBeingPickedUp){
+ if(Abs(m_vecMoveSpeed.x) < 0.01f && Abs(m_vecMoveSpeed.y) < 0.01f && m_fSteerAngle == 0.0f){
+ m_fWheelAngle *= Pow(0.96f, CTimer::GetTimeStep());
+ }else{
+ float f;
+ if(fwdSpeed > 0.01f && m_aWheelTimer[BIKESUSP_F1] > 0.0f && m_aWheelTimer[BIKESUSP_F2] > 0.0f && GetStatus() == STATUS_PLAYER){
+ CColPoint point;
+ point.surfaceA = SURFACE_WHEELBASE;
+ point.surfaceB = SURFACE_TARMAC;
+ float steer = CSurfaceTable::GetAdhesiveLimit(point)*4.0f*pBikeHandling->fSpeedSteer*traction;
+ if(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[rearLine].surfaceB) == ADHESIVE_LOOSE ||
+ CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[rearLine].surfaceB) == ADHESIVE_SAND)
+ steer *= pBikeHandling->fSlipSteer;
+ f = Asin(Min(steer/SQR(fwdSpeed), 1.0))/DEGTORAD(pHandling->fSteeringLock);
+ if(m_fSteerAngle < 0.0f && m_fLeanLRAngle < 0.0f ||
+ m_fSteerAngle > 0.0f && m_fLeanLRAngle > 0.0f)
+ f *= 2.0f;
+ f = Min(f, 1.0f);
+ }else{
+ f = 1.0f;
+ }
+ if(GetStatus() != STATUS_PLAYER)
+ f = 1.0f;
+ m_fWheelAngle = m_fSteerAngle*f;
+ }
+ }else if(m_fWheelAngle < DEGTORAD(20.0f))
+ m_fWheelAngle += DEGTORAD(1.5f)*CTimer::GetTimeStep();
+
+ static float fThrust;
+ static tWheelState WheelState[2];
+ CVector initialMoveSpeed = m_vecMoveSpeed;
+ bool rearWheelsFirst = !!(pHandling->Flags & HANDLING_REARWHEEL_1ST);
+
+ // Process front wheel - first try
+
+ if(!rearWheelsFirst){
+ if(m_aWheelTimer[BIKESUSP_F1] > 0.0f || m_aWheelTimer[BIKESUSP_F2] > 0.0f){
+ // Wheel on ground
+ eBikeWheelSpecial spec;
+ if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f)
+ spec = BIKE_WHEELSPEC_0;
+ else
+ spec = BIKE_WHEELSPEC_2;
+ CVector wheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fWheelAngle), Cos(m_fWheelAngle), 0.0f));
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[frontLine].normal)*m_aWheelColPoints[frontLine].normal;
+ wheelFwd.Normalise();
+ CVector wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[frontLine].normal);
+ wheelRight.Normalise();
+
+ fThrust = 0.0f;
+ m_aWheelColPoints[frontLine].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[frontLine])*traction;
+ float adhesionDestab = 1.0f;
+ if(m_fBrakeDestabilization > 0.0f)
+ switch(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[frontLine].surfaceB)){
+ case ADHESIVE_HARD:
+ case ADHESIVE_LOOSE:
+ adhesionDestab = 0.9f;
+ break;
+ case ADHESIVE_ROAD:
+ adhesionDestab = 0.7f;
+ break;
+ }
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[frontLine].surfaceB);
+ if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST)
+ adhesion *= 0.4f;
+ WheelState[BIKEWHEEL_FRONT] = m_aWheelState[BIKEWHEEL_FRONT];
+ CVector contactSpeed = GetSpeed(frontContact);
+ ProcessBikeWheel(wheelFwd, wheelRight,
+ contactSpeed, frontContact,
+ 2, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront, adhesionDestab,
+ BIKEWHEEL_FRONT,
+ &m_aWheelSpeed[BIKEWHEEL_FRONT],
+ &WheelState[BIKEWHEEL_FRONT],
+ spec,
+ m_wheelStatus[BIKEWHEEL_FRONT]);
+ if(bStuckInSand && (WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SPINNING || WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SKIDDING))
+ WheelState[BIKEWHEEL_FRONT] = WHEEL_STATE_NORMAL;
+ }else{
+ // Wheel in the air
+ m_aWheelSpeed[BIKEWHEEL_FRONT] *= 0.95f;
+ m_aWheelRotation[BIKEWHEEL_FRONT] += m_aWheelSpeed[BIKEWHEEL_FRONT];
+ }
+ }
+
+ // Process rear wheel
+
+ if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f){
+ // Wheel on ground
+ float rearBrake = brake;
+ float rearTraction = traction;
+
+ CVector wheelFwd = GetForward();
+ CVector wheelRight = GetRight();
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[rearLine].normal)*m_aWheelColPoints[rearLine].normal;
+ wheelFwd.Normalise();
+ wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[rearLine].normal);
+ wheelRight.Normalise();
+
+ if(bIsHandbrakeOn){
+#ifdef FIX_BUGS
+ // Not sure if this is needed, but brake usually has timestep as a factor
+ rearBrake = 20000.0f * CTimer::GetTimeStepFix();
+#else
+ rearBrake = 20000.0f;
+#endif
+ m_fTireTemperature = 1.0f;
+ }else if(m_doingBurnout){
+ rearBrake = 0.0f;
+ rearTraction = 0.0f;
+ ApplyTurnForce(contactPoints[BIKESUSP_R1], -0.0007f*m_fTurnMass*m_fSteerAngle*GetRight()*CTimer::GetTimeStep());
+ }else if(m_fTireTemperature < 1.0f && m_fGasPedal > 0.75f){
+ rearTraction *= m_fTireTemperature;
+ ApplyTurnForce(contactPoints[BIKESUSP_R1], (1.0f-m_fTireTemperature)*-0.0007f*m_fTurnMass*m_fSteerAngle*GetRight()*CTimer::GetTimeStep());
+ }
+
+ if(fThrust > 0.0f && brake > 0.0f)
+ brake = 0.0f; // only affects next front wheel. is this intended?
+ fThrust = acceleration;
+ m_aWheelColPoints[rearLine].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[rearLine])*rearTraction;
+ float adhesionDestab = 1.0f;
+ if(m_fBrakeDestabilization > 0.0f)
+ switch(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[rearLine].surfaceB)){
+ case ADHESIVE_HARD:
+ case ADHESIVE_LOOSE:
+ adhesionDestab = 0.9f;
+ break;
+ case ADHESIVE_ROAD:
+ adhesionDestab = 0.7f;
+ break;
+ }
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[rearLine].surfaceB);
+ if(m_wheelStatus[BIKEWHEEL_REAR] == WHEEL_STATUS_BURST)
+ adhesion *= 0.4f;
+ WheelState[BIKEWHEEL_REAR] = m_aWheelState[BIKEWHEEL_REAR];
+ CVector contactSpeed = GetSpeed(rearContact);
+ ProcessBikeWheel(wheelFwd, wheelRight,
+ contactSpeed, rearContact,
+ 2, fThrust,
+ rearBrake*brakeBiasRear,
+ adhesion*tractionBiasRear, adhesionDestab,
+ BIKEWHEEL_REAR,
+ &m_aWheelSpeed[BIKEWHEEL_REAR],
+ &WheelState[BIKEWHEEL_REAR],
+ BIKE_WHEELSPEC_1,
+ m_wheelStatus[BIKEWHEEL_REAR]);
+ if(bStuckInSand && (WheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING || WheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SKIDDING))
+ WheelState[BIKEWHEEL_REAR] = WHEEL_STATE_NORMAL;
+ }else{
+ // Wheel in the air
+ if(bIsHandbrakeOn)
+ m_aWheelSpeed[BIKEWHEEL_REAR] = 0.0f;
+ else{
+ if(acceleration > 0.0f){
+ if(m_aWheelSpeed[BIKEWHEEL_REAR] < 2.0f)
+ m_aWheelSpeed[BIKEWHEEL_REAR] -= 0.2f;
+ }else{
+ if(m_aWheelSpeed[BIKEWHEEL_REAR] > -2.0f)
+ m_aWheelSpeed[BIKEWHEEL_REAR] += 0.1f;
+ }
+ }
+ m_aWheelRotation[BIKEWHEEL_REAR] += m_aWheelSpeed[BIKEWHEEL_REAR];
+ }
+
+ if(m_doingBurnout && m_aWheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING){
+ m_fTireTemperature += 0.001f*CTimer::GetTimeStep();
+ if(m_fTireTemperature > 3.0f)
+ m_fTireTemperature = 3.0f;
+ }else if(m_fTireTemperature > 1.0f){
+ m_fTireTemperature = (m_fTireTemperature - 1.0f)*Pow(0.995f, CTimer::GetTimeStep()) + 1.0f;
+ }
+
+ // Process front wheel - second try
+
+ if(rearWheelsFirst){
+ if(m_aWheelTimer[BIKESUSP_F1] > 0.0f || m_aWheelTimer[BIKESUSP_F2] > 0.0f){
+ // Wheel on ground
+ eBikeWheelSpecial spec;
+ if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f)
+ spec = BIKE_WHEELSPEC_0;
+ else
+ spec = BIKE_WHEELSPEC_2;
+ CVector wheelFwd = GetMatrix() * CVector(-Sin(m_fWheelAngle), Cos(m_fWheelAngle), 0.0f);
+ wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[frontLine].normal)*m_aWheelColPoints[frontLine].normal;
+ wheelFwd.Normalise();
+ CVector wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[frontLine].normal);
+ wheelRight.Normalise();
+
+ fThrust = 0.0f;
+ m_aWheelColPoints[frontLine].surfaceA = SURFACE_WHEELBASE;
+ float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[frontLine])*traction;
+ float adhesionDestab = 1.0f;
+ if(m_fBrakeDestabilization > 0.0f)
+ switch(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[frontLine].surfaceB)){
+ case ADHESIVE_HARD:
+ case ADHESIVE_LOOSE:
+ adhesionDestab = 0.9f;
+ break;
+ case ADHESIVE_ROAD:
+ adhesionDestab = 0.7f;
+ break;
+ }
+ if(GetStatus() == STATUS_PLAYER)
+ adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[frontLine].surfaceB);
+ if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST)
+ adhesion *= 0.4f;
+ WheelState[BIKEWHEEL_FRONT] = m_aWheelState[BIKEWHEEL_FRONT];
+ CVector contactSpeed = GetSpeed(frontContact);
+ ProcessBikeWheel(wheelFwd, wheelRight,
+ contactSpeed, frontContact,
+ 2, fThrust,
+ brake*brakeBiasFront,
+ adhesion*tractionBiasFront, adhesionDestab,
+ BIKEWHEEL_FRONT,
+ &m_aWheelSpeed[BIKEWHEEL_FRONT],
+ &WheelState[BIKEWHEEL_FRONT],
+ spec,
+ m_wheelStatus[BIKEWHEEL_FRONT]);
+ if(bStuckInSand && (WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SPINNING || WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SKIDDING))
+ WheelState[BIKEWHEEL_FRONT] = WHEEL_STATE_NORMAL;
+ }else{
+ // Wheel in the air
+ m_aWheelSpeed[BIKEWHEEL_FRONT] *= 0.95f;
+ m_aWheelRotation[BIKEWHEEL_FRONT] += m_aWheelSpeed[BIKEWHEEL_FRONT];
+ }
+ }
+
+ // Process leaning
+ float idleAngle = 0.0f;
+ if(pDriver){
+ CAnimBlendAssociation *assoc = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_READY);
+ if(assoc)
+ idleAngle = DEGTORAD(10.0f) * assoc->blendAmount;
+ }
+ if(bBalancedByRider || bIsBeingPickedUp){
+ m_vecAvgSurfaceRight = CrossProduct(GetForward(), m_vecAvgSurfaceNormal);
+ m_vecAvgSurfaceRight.Normalise();
+ float lean;
+ if(m_nWheelsOnGround == 0)
+ lean = -(m_fSteerAngle/DEGTORAD(pHandling->fSteeringLock))*0.5f*GRAVITY*CTimer::GetTimeStep();
+ else
+ lean = DotProduct(m_vecMoveSpeed-initialMoveSpeed, m_vecAvgSurfaceRight);
+ lean /= GRAVITY*Max(CTimer::GetTimeStep(), 0.01f);
+ if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST)
+ lean = clamp(lean, -0.4f*pBikeHandling->fMaxLean, 0.4f*pBikeHandling->fMaxLean);
+ else
+ lean = clamp(lean, -pBikeHandling->fMaxLean, pBikeHandling->fMaxLean);
+ float f = Pow(pBikeHandling->fDesLean, CTimer::GetTimeStep());
+ m_fLeanLRAngle2 = (Asin(lean) - idleAngle)*(1.0f-f) + m_fLeanLRAngle2*f;
+ }else{
+ if(bIsStanding){
+ float f = Pow(0.97f, CTimer::GetTimeStep());
+ m_fLeanLRAngle2 = m_fLeanLRAngle2*f - (Asin(GetRight().z) + DEGTORAD(15.0f) + idleAngle)*(1.0f-f);
+ }else{
+ float f = Pow(0.95f, CTimer::GetTimeStep());
+ m_fLeanLRAngle2 = m_fLeanLRAngle2*f;
+ }
+ }
+ m_fLeanLRAngle = m_fLeanLRAngle2;
+
+ // Destabilize steering when braking
+ if((m_aSuspensionSpringRatio[BIKESUSP_F1] < 1.0f || m_aSuspensionSpringRatio[BIKESUSP_F2] < 1.0f) &&
+ m_fBrakePedal - m_fGasPedal > 0.9f &&
+ fwdSpeed > 0.02f &&
+ !bIsHandbrakeOn){
+ m_fBrakeDestabilization += CGeneral::GetRandomNumberInRange(0.5f, 1.0f)*0.2f*CTimer::GetTimeStep();
+ if(m_aSuspensionSpringRatio[BIKESUSP_R1] < 1.0f || m_aSuspensionSpringRatio[BIKESUSP_R2] < 1.0f){
+ // BUG: this clamp makes no sense and the arguments seem swapped too
+ ApplyTurnForce(contactPoints[BIKESUSP_R1],
+ m_fTurnMass*Sin(m_fBrakeDestabilization)*clamp(fwdSpeed, 0.5f, 0.2f)*0.013f*GetRight()*CTimer::GetTimeStep());
+ }else{
+ // BUG: this clamp makes no sense and the arguments seem swapped too
+ ApplyTurnForce(contactPoints[BIKESUSP_R1],
+ m_fTurnMass*Sin(m_fBrakeDestabilization)*clamp(fwdSpeed, 0.5f, 0.2f)*0.003f*GetRight()*CTimer::GetTimeStep());
+ }
+ }else
+ m_fBrakeDestabilization = 0.0f;
+
+ // Update wheel positions from suspension
+ float frontWheelPos = colModel->lines[frontLine].p0.z;
+ if(m_aSuspensionSpringRatio[frontLine] > 0.0f)
+ frontWheelPos -= m_aSuspensionSpringRatio[frontLine]*m_aSuspensionSpringLength[frontLine];
+ m_aWheelPosition[BIKEWHEEL_FRONT] += (frontWheelPos - m_aWheelPosition[BIKEWHEEL_FRONT])*0.75f;
+
+ float rearWheelPos = colModel->lines[rearLine].p0.z;
+ if(m_aSuspensionSpringRatio[rearLine] > 0.0f)
+ rearWheelPos -= m_aSuspensionSpringRatio[rearLine]*m_aSuspensionSpringLength[rearLine];
+ m_aWheelPosition[BIKEWHEEL_REAR] += (rearWheelPos - m_aWheelPosition[BIKEWHEEL_REAR])*0.75f;
+
+ for(i = 0; i < 2; i++)
+ m_aWheelState[i] = WheelState[i];
+ // never spin when moving backwards
+ if(m_fGasPedal < 0.0f && m_aWheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING)
+ m_aWheelState[BIKEWHEEL_REAR] = WHEEL_STATE_NORMAL;
+
+ // Process horn
+
+ if(GetStatus() != STATUS_PLAYER){
+#ifdef FIX_BUGS
+ if(!IsAlarmOn())
+#endif
+ ReduceHornCounter();
+ }else{
+#ifdef FIX_BUGS
+ if(!IsAlarmOn())
+#endif
+ {
+ if(Pads[0].GetHorn())
+ m_nCarHornTimer = 1;
+ else
+ m_nCarHornTimer = 0;
+ }
+ }
+ }
+
+ if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){
+ // Car is on fire
+
+ CVector damagePos, fireDir;
+
+ // move fire forward if in first person
+ if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()){
+ damagePos = CVector(0.0f, 1.2f, -0.4f);
+ fireDir = CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.01125f, 0.09f));
+ }else{
+ damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_BACKSEAT];
+ damagePos.z -= 0.3f;
+ fireDir = CGeneral::GetRandomNumberInRange(0.02025f, 0.09f) * GetRight();
+ fireDir -= CGeneral::GetRandomNumberInRange(0.02025f, 0.18f) * GetForward();
+ fireDir.z = CGeneral::GetRandomNumberInRange(0.00225f, 0.018f);
+ }
+
+ damagePos = GetMatrix()*damagePos;
+ CParticle::AddParticle(PARTICLE_CARFLAME, damagePos, fireDir,
+ nil, 0.9f);
+
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, CVector(0.0f, 0.0f, 0.0f), nil, 0.5f);
+
+ damagePos.x += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f),
+ damagePos.y += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f),
+ damagePos.z += CGeneral::GetRandomNumberInRange(0.5625f, 2.25f);
+ CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, damagePos, CVector(0.0f, 0.0f, 0.0f));
+
+ // Blow up car after 5 seconds
+ m_fFireBlowUpTimer += CTimer::GetTimeStepInMilliseconds();
+ if(m_fFireBlowUpTimer > 5000.0f)
+ BlowUpCar(m_pSetOnFireEntity);
+ }else
+ m_fFireBlowUpTimer = 0.0f;
+
+ ProcessDelayedExplosion();
+
+ // Find out how much to shake the pad depending on suspension and ground surface
+
+ float suspShake = 0.0f;
+ float surfShake = 0.0f;
+ float speedsq = m_vecMoveSpeed.MagnitudeSqr();
+ for(i = 0; i < 4; i++){
+ float suspChange = m_aSuspensionSpringRatioPrev[i] - m_aSuspensionSpringRatio[i];
+ if(suspChange > 0.3f && (i == BIKESUSP_F1 || i == BIKESUSP_R1) && speedsq > 0.04f){
+ if(GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PHYSICS){
+#ifdef FIX_BUGS
+ // only two wheels but 4 suspensions
+ if(m_wheelStatus[i/2] == WHEEL_STATUS_BURST)
+#else
+ if(m_wheelStatus[i] == WHEEL_STATUS_BURST)
+#endif
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP_2, suspChange);
+ else
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, suspChange);
+ if(suspChange > suspShake)
+ suspShake = suspChange;
+ }
+ }
+
+ if(this == FindPlayerVehicle()){
+ uint8 surf = m_aWheelColPoints[i].surfaceB;
+ if(surf == SURFACE_GRAVEL || surf == SURFACE_WATER || surf == SURFACE_HEDGE){
+ if(surfShake < 0.2f)
+ surfShake = 0.3f;
+ }else if(surf == SURFACE_MUD_DRY || surf == SURFACE_SAND || surf == SURFACE_SAND_BEACH){
+ if(surfShake < 0.1f)
+ surfShake = 0.2f;
+ }else if(surf == SURFACE_GRASS){
+ if(surfShake < 0.05f)
+ surfShake = 0.1f;
+ }
+
+// BUG: this only observes one of the wheels
+ TheCamera.m_bVehicleSuspenHigh = Abs(suspChange) > 0.05f;
+ }
+
+ m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i];
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ }
+
+ // Shake pad
+
+ if((suspShake > 0.0f || surfShake > 0.0f) && GetStatus() == STATUS_PLAYER){
+ float speed = m_vecMoveSpeed.MagnitudeSqr();
+ if(speed > sq(0.1f)){
+ speed = Sqrt(speed);
+ if(suspShake > 0.0f){
+ uint8 freq = Min(200.0f*suspShake*speed*2000.0f/m_fMass + 100.0f, 250.0f);
+ CPad::GetPad(0)->StartShake(20000.0f*CTimer::GetTimeStep()/freq, freq);
+ }else{
+ uint8 freq = Min(200.0f*surfShake*speed*2000.0f/m_fMass + 40.0f, 150.0f);
+ CPad::GetPad(0)->StartShake(5000.0f*CTimer::GetTimeStep()/freq, freq);
+ }
+ }
+ }
+
+ bVehicleColProcessed = false;
+ bAudioChangingGear = false;
+
+ if(!bWarnedPeds)
+ CCarCtrl::ScanForPedDanger(this);
+
+ if(bInfiniteMass){
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
+ }else if(!skipPhysics &&
+ (acceleration == 0.0f && brake == 0.0f || GetStatus() == STATUS_WRECKED)){
+ if(Abs(m_vecMoveSpeed.x) < 0.005f &&
+ Abs(m_vecMoveSpeed.y) < 0.005f &&
+ Abs(m_vecMoveSpeed.z) < 0.005f){
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed.z = 0.0f;
+ }
+ }
+
+ // Balance bike
+ if(bBalancedByRider || bIsBeingPickedUp || bIsStanding){
+ float onSideness = DotProduct(GetRight(), m_vecAvgSurfaceNormal);
+ onSideness = clamp(onSideness, -1.0f, 1.0f);
+ CVector worldCOM = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
+ // Keep bike upright
+ if(bBalancedByRider){
+ ApplyTurnForce(-0.07f*onSideness*m_fTurnMass*GetUp()*CTimer::GetTimeStep(), worldCOM+GetRight());
+ bIsStanding = false;
+ }else
+ ApplyTurnForce(-0.1f*onSideness*m_fTurnMass*GetUp()*CTimer::GetTimeStep(), worldCOM+GetRight());
+
+ // Wheelie/Stoppie stabilization
+ if(GetStatus() == STATUS_PLAYER){
+ if(m_aWheelTimer[BIKESUSP_F1] == 0.0f && m_aWheelTimer[BIKESUSP_F2] == 0.0f && GetForward().z > 0.0 &&
+ !(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f)){
+ // Wheelie
+ float wheelie = pBikeHandling->fWheelieAng - GetForward().z;
+ if(wheelie > 0.15f)
+ // below wheelie angle
+ wheelie = Max(0.3f - wheelie, 0.0f);
+ else if(wheelie < -0.08f)
+ // above wheelie angle
+ wheelie = Min(-0.15f - wheelie, 0.0f);
+ float wheelieStab = pBikeHandling->fWheelieStabMult * Min(m_vecMoveSpeed.Magnitude(), 0.1f) * wheelie;
+ ApplyTurnForce(0.5f*CTimer::GetTimeStep()*wheelieStab*m_fTurnMass*GetUp(), worldCOM+GetForward());
+ ApplyTurnForce(0.5f*CTimer::GetTimeStep()*m_fWheelAngle*pBikeHandling->fWheelieSteer*m_fTurnMass*GetRight(), worldCOM+GetForward());
+ }else if(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f && GetForward().z < 0.0 &&
+ !(m_aWheelTimer[BIKESUSP_F1] == 0.0f && m_aWheelTimer[BIKESUSP_F2] == 0.0f)){
+ // Stoppie
+ float stoppie = pBikeHandling->fStoppieAng - GetForward().z;
+ if(stoppie > 0.15f)
+ // below stoppie angle
+ stoppie = Max(0.3f - stoppie, 0.0f);
+ else if(stoppie < -0.15f)
+ // above stoppie angle
+ stoppie = Min(-0.3f - stoppie, 0.0f);
+ float speed = m_vecMoveSpeed.Magnitude();
+ float stoppieStab = pBikeHandling->fStoppieStabMult * Min(speed, 0.1f) * stoppie;
+ ApplyTurnForce(0.5f*CTimer::GetTimeStep()*stoppieStab*m_fTurnMass*GetUp(), worldCOM+GetForward());
+ ApplyTurnForce(0.5f*Min(5.0f*speed,1.0f)*CTimer::GetTimeStep()*m_fWheelAngle*pBikeHandling->fWheelieSteer*m_fTurnMass*GetRight(), worldCOM+GetForward());
+ }
+ }
+ }
+}
+
+#pragma optimize("", on)
+
+void
+CBike::Teleport(CVector pos)
+{
+ CWorld::Remove(this);
+
+ SetPosition(pos);
+ SetOrientation(0.0f, 0.0f, 0.0f);
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+
+ ResetSuspension();
+
+ CWorld::Add(this);
+}
+
+void
+CBike::PreRender(void)
+{
+ int i;
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ // Wheel particles
+
+ if(m_aWheelState[BIKEWHEEL_REAR] != WHEEL_STATE_NORMAL &&
+ m_aWheelColPoints[BIKESUSP_R2].surfaceB != SURFACE_WATER && m_aWheelTimer[BIKESUSP_R2] > 0.0f){
+ static float smokeSize = 0.2f;
+ CVector groundPos = m_aWheelColPoints[BIKESUSP_R2].point;
+ if(m_aSuspensionSpringRatioPrev[BIKESUSP_R1] < 1.0f)
+ groundPos = (groundPos + m_aWheelColPoints[BIKESUSP_R1].point)/2.0f;
+ groundPos += Sin(m_fLeanLRAngle) * 0.8f*GetColModel()->boundingBox.min.z * GetRight();
+ CParticle::AddParticle(PARTICLE_RUBBER_SMOKE,
+ groundPos + CVector(0.0f, 0.0f, 0.25f), CVector(0.0f, 0.0f, 0.0f),
+ nil, smokeSize);
+
+ CSkidmarks::RegisterOne((uintptr)this, groundPos, GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[BIKEWHEEL_REAR], &m_aWheelSkidmarkBloody[BIKEWHEEL_REAR]);
+
+ if(m_aWheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING &&
+ (CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[BIKESUSP_R2].surfaceB) == ADHESIVE_HARD ||
+ CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[BIKESUSP_R2].surfaceB) == ADHESIVE_ROAD)){
+ CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE,
+ groundPos + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.0f));
+ CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE,
+ groundPos + CVector(0.0f, 0.0f, 0.25f),
+ CVector(0.0f, 0.0f, 0.05f));
+ }
+ }else if(m_aWheelSkidmarkBloody[BIKEWHEEL_REAR] || m_aWheelSkidmarkUnk[BIKEWHEEL_REAR]){
+ CVector groundPos = m_aWheelColPoints[BIKESUSP_R2].point;
+ groundPos += Sin(m_fLeanLRAngle) * 0.8f*GetColModel()->boundingBox.min.z * GetRight();
+
+ CSkidmarks::RegisterOne((uintptr)this, groundPos, GetForward().x, GetForward().y,
+ m_aWheelSkidmarkType[BIKEWHEEL_REAR], &m_aWheelSkidmarkBloody[BIKEWHEEL_REAR]);
+ }
+
+ // Process lights
+
+ // Turn lights on/off
+ bool shouldLightsBeOn =
+ CClock::GetHours() > 20 ||
+ CClock::GetHours() > 19 && CClock::GetMinutes() > (m_randomSeed & 0x3F) ||
+ CClock::GetHours() < 7 ||
+ CClock::GetHours() < 8 && CClock::GetMinutes() < (m_randomSeed & 0x3F) ||
+ m_randomSeed/50000.0f < CWeather::Foggyness ||
+ m_randomSeed/50000.0f < CWeather::WetRoads;
+ if(shouldLightsBeOn != bLightsOn && GetStatus() != STATUS_WRECKED){
+ if(GetStatus() == STATUS_ABANDONED){
+ // Turn off lights on abandoned vehicles only when we they're far away
+ if(bLightsOn &&
+ Abs(TheCamera.GetPosition().x - GetPosition().x) + Abs(TheCamera.GetPosition().y - GetPosition().y) > 100.0f)
+ bLightsOn = false;
+ }else
+ bLightsOn = shouldLightsBeOn;
+ }
+
+ // Actually render the lights
+ bool alarmOn = false;
+ bool alarmOff = false;
+ if(IsAlarmOn()){
+ if(CTimer::GetTimeInMilliseconds() & 0x100)
+ alarmOn = true;
+ else
+ alarmOff = true;
+ }
+ if(bEngineOn && bLightsOn || alarmOn || alarmOff){
+ CalculateLeanMatrix();
+ CVector lookVector = GetPosition() - TheCamera.GetPosition();
+ float camDist = lookVector.Magnitude();
+ if(camDist != 0.0f)
+ lookVector *= 1.0f/camDist;
+ else
+ lookVector = CVector(1.0f, 0.0f, 0.0f);
+
+ // 1.0 if directly behind car, -1.0 if in front
+ float behindness = DotProduct(lookVector, GetForward());
+ // 0.0 if behind car, PI if in front
+ float angle = Abs(Acos(Abs(behindness)));
+
+ // Headlight
+
+ CMatrix mat;
+ CVector headLightPos = mi->m_positions[CAR_POS_HEADLIGHTS];
+ if(GetModelIndex() == 152){ // this is the bobcat in VC, but we don't want that effect anyway
+ mat.SetUnity();
+ mat.RotateZ(m_fWheelAngle);
+ mat = m_leanMatrix * mat;
+ }else
+ mat = m_leanMatrix;
+ CVector light = mat * headLightPos;
+ if(behindness < 0.0f){
+ // In front of bike
+ float intensity = -0.5f*behindness + 0.3f;
+ float size = 1.0f - behindness;
+
+ if(behindness < -0.97f && camDist < 30.0f){
+ // Directly in front and not too far away
+ if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){
+ CCoronas::RegisterCorona((uintptr)this + 6, 150, 150, 195, 255,
+ light, 1.2f, 45.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle);
+ }else{
+ CCoronas::RegisterCorona((uintptr)this + 6, 160, 160, 140, 255,
+ light, 1.2f, 45.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle);
+ }
+ }
+
+ if(alarmOff){
+ CCoronas::RegisterCorona((uintptr)this, 0, 0, 0, 0,
+ light, size, 0.0f,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){
+ CCoronas::RegisterCorona((uintptr)this + 1, 190*intensity, 190*intensity, 255*intensity, 255,
+ light, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ CCoronas::RegisterCorona((uintptr)this + 1, 210*intensity, 210*intensity, 195*intensity, 255,
+ light, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }
+ }
+ }else{
+ CCoronas::UpdateCoronaCoors((uintptr)this, light, 50.0f*TheCamera.LODDistMultiplier, angle);
+ }
+
+ // bright light
+ CBrightLights::RegisterOne(light, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT);
+
+ // Taillight
+
+ CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS];
+ light = m_leanMatrix * tailLightPos;
+
+ // Taillight corona
+ if(behindness > 0.0f){
+ // Behind car
+ float intensity = 0.4f*behindness + 0.4f;
+ float size = (behindness + 1.0f)/2.0f;
+
+ if(m_fGasPedal < 0.0f){
+ // reversing
+ // no lights in this case
+ }else{
+ if(m_fBrakePedal > 0.0f){
+ intensity += 0.4f;
+ size += 0.3f;
+ }
+
+ if(alarmOff){
+ CCoronas::RegisterCorona((uintptr)this + 14, 0, 0, 0, 0,
+ light, size, 0.0f,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }else{
+ CCoronas::RegisterCorona((uintptr)this + 14, 128*intensity, 0, 0, 255,
+ light, size, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle);
+ }
+ }
+ }else{
+ CCoronas::UpdateCoronaCoors((uintptr)this + 14, light, 50.0f*TheCamera.LODDistMultiplier, angle);
+ }
+
+ // bright light
+ CBrightLights::RegisterOne(light, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
+
+ // Light shadows
+ if(!alarmOff){
+ CVector pos = GetPosition();
+ CVector2D fwd(GetForward());
+ fwd.Normalise();
+ float f = headLightPos.y + 6.0f;
+ pos += CVector(f*fwd.x, f*fwd.y, 2.0f);
+ CShadows::StoreCarLightShadow(this, (uintptr)this + 22, gpShadowExplosionTex, &pos,
+ 7.0f*fwd.x, 7.0f*fwd.y, 3.5f*fwd.y, -3.5f*fwd.x, 45, 45, 45, 7.0f);
+
+ f = (tailLightPos.y - 2.5f) - (headLightPos.y + 6.0f);
+ pos += CVector(f*fwd.x, f*fwd.y, 0.0f);
+ CShadows::StoreCarLightShadow(this, (uintptr)this + 25, gpShadowExplosionTex, &pos,
+ 3.0f, 0.0f, 0.0f, -3.0f, 35, 0, 0, 4.0f);
+ }
+
+ if(this == FindPlayerVehicle() && !alarmOff){
+ CPointLights::AddLight(CPointLights::LIGHT_DIRECTIONAL, GetPosition(), GetForward(),
+ 20.0f, 1.0f, 1.0f, 1.0f,
+ FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.45f) ? CPointLights::FOG_NORMAL : CPointLights::FOG_NONE,
+ false);
+ CVector pos = GetPosition() - 4.0f*GetForward();
+ if(m_fBrakePedal > 0.0f)
+ CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f),
+ 10.0f, 1.0f, 0.0f, 0.0f,
+ CPointLights::FOG_NONE, false);
+ else
+ CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f),
+ 7.0f, 0.6f, 0.0f, 0.0f,
+ CPointLights::FOG_NONE, false);
+ }
+ }else if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED){
+ // Lights off
+ CalculateLeanMatrix();
+
+ CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS];
+ CVector light = m_leanMatrix * tailLightPos;
+
+ if(m_fBrakePedal > 0.0f || m_fGasPedal < 0.0f){
+ CVector lookVector = GetPosition() - TheCamera.GetPosition();
+ lookVector.Normalise();
+ float behindness = DotProduct(lookVector, GetForward());
+ if(behindness > 0.0f){
+ if(m_fGasPedal < 0.0f){
+ // reversing
+ // no lights in this case
+ }else{
+ // braking
+ CCoronas::RegisterCorona((uintptr)this + 14, 120, 0, 0, 255,
+ light, 1.2f, 50.0f*TheCamera.LODDistMultiplier,
+ CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ CBrightLights::RegisterOne(light, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR);
+ }
+ }else{
+ CCoronas::UpdateCoronaCoors((uintptr)this + 14, light, 50.0f*TheCamera.LODDistMultiplier, 0.0f);
+ }
+ }else{
+ CCoronas::UpdateCoronaCoors((uintptr)this + 14, light, 50.0f*TheCamera.LODDistMultiplier, 0.0f);
+ }
+ }
+
+
+ // Wheel particles
+
+ float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward())*180.0f;
+ int drawParticles = Abs(fwdSpeed) < 90.0f;
+ int susp = BIKESUSP_F1;
+ for(i = 0; i < 2; i++){
+ if(i == BIKEWHEEL_REAR)
+ susp = BIKESUSP_R1;
+
+ static float speedSq;
+ // Sparks for friction of burst wheels
+ if(m_wheelStatus[i] == WHEEL_STATUS_BURST && m_aSuspensionSpringRatioPrev[susp] < 1.0f &&
+ (speedSq = m_vecMoveSpeed.MagnitudeSqr(), speedSq > SQR(0.1f)) &&
+ m_aWheelColPoints[susp].surfaceB != SURFACE_GRASS &&
+ m_aWheelColPoints[susp].surfaceB != SURFACE_MUD_DRY &&
+ m_aWheelColPoints[susp].surfaceB != SURFACE_SAND &&
+ m_aWheelColPoints[susp].surfaceB != SURFACE_SAND_BEACH){
+ CVector normalSpeed = m_aWheelColPoints[susp].normal * DotProduct(m_aWheelColPoints[susp].normal, m_vecMoveSpeed);
+ CVector frictionSpeed = m_vecMoveSpeed - normalSpeed;
+ CVector sparkDir = 0.25f*frictionSpeed;
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir);
+
+ if(speedSq > 0.04f)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir);
+ if(speedSq > 0.16f){
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir);
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir);
+ }
+ }else if(m_aSuspensionSpringRatioPrev[i] < 1.0f &&
+ (fwdSpeed > 0.2f || m_aWheelState[i] == WHEEL_STATE_SPINNING)){
+ if(m_aWheelColPoints[susp].surfaceB == SURFACE_GRASS ||
+ m_aWheelColPoints[susp].surfaceB == SURFACE_MUD_DRY ||
+ m_aWheelColPoints[susp].surfaceB == SURFACE_SAND ||
+ m_aWheelColPoints[susp].surfaceB == SURFACE_SAND_BEACH)
+ AddWheelDirtAndWater(&m_aWheelColPoints[susp], drawParticles);
+ }
+ }
+
+ AddDamagedVehicleParticles();
+ CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_BIKE);
+
+ CMatrix mat;
+ CVector pos;
+ CColModel *colModel = mi->GetColModel();
+
+ // Wheel rotation
+ CVector frontWheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f));
+ CVector rearWheelFwd = GetForward();
+ if(m_aWheelTimer[BIKESUSP_F1] > 0.0f || m_aWheelTimer[BIKESUSP_F2] > 0.0f){
+ float springRatio = Min(m_aSuspensionSpringRatioPrev[BIKESUSP_F1], m_aSuspensionSpringRatioPrev[BIKESUSP_F2]);
+ CVector contactPoint(0.0f,
+ (colModel->lines[BIKESUSP_F1].p0.y - colModel->lines[BIKESUSP_F2].p0.y)/2.0f,
+ colModel->lines[BIKESUSP_F1].p0.z - m_aSuspensionSpringLength[BIKESUSP_F1]*springRatio - 0.5f*mi->m_wheelScale);
+ CVector contactSpeed = GetSpeed(contactPoint);
+ // Why is wheel state always normal?
+ m_aWheelSpeed[BIKEWHEEL_FRONT] = ProcessWheelRotation(WHEEL_STATE_NORMAL, frontWheelFwd, contactSpeed, 0.5f*mi->m_wheelScale);
+ m_aWheelRotation[BIKEWHEEL_FRONT] += m_aWheelSpeed[BIKEWHEEL_FRONT];
+ }
+ if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f){
+ float springRatio = Min(m_aSuspensionSpringRatioPrev[BIKESUSP_R1], m_aSuspensionSpringRatioPrev[BIKESUSP_R2]);
+ CVector contactPoint(0.0f,
+ (colModel->lines[BIKESUSP_R1].p0.y - colModel->lines[BIKESUSP_R2].p0.y)/2.0f,
+ colModel->lines[BIKESUSP_R1].p0.z - m_aSuspensionSpringLength[BIKESUSP_R1]*springRatio - 0.5f*mi->m_wheelScale);
+ CVector contactSpeed = GetSpeed(contactPoint);
+ m_aWheelSpeed[BIKEWHEEL_REAR] = ProcessWheelRotation(m_aWheelState[BIKEWHEEL_REAR], rearWheelFwd, contactSpeed, 0.5f*mi->m_wheelScale);
+ m_aWheelRotation[BIKEWHEEL_REAR] += m_aWheelSpeed[BIKEWHEEL_REAR];
+ }
+
+ // Front fork
+ if(m_aBikeNodes[BIKE_FORKS_FRONT]){
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_FORKS_FRONT]));
+ pos = mat.GetPosition();
+
+ RwMatrix rwrot;
+ // TODO: this looks like some weird ctor we don't have
+ CMatrix rot;
+ rot.m_attachment = &rwrot;
+ rot.SetUnity();
+ rot.UpdateRW();
+
+ // Make rotation matrix with front fork as axis
+ CVector forkAxis(0.0f, Sin(DEGTORAD(mi->m_bikeSteerAngle)), -Cos(DEGTORAD(mi->m_bikeSteerAngle)));
+ forkAxis.Normalise(); // as if that's not already the case
+ CQuaternion quat;
+ quat.Set(&forkAxis, -m_fWheelAngle);
+ quat.Get(rot.m_attachment);
+ rot.Update();
+
+ // Transform fork
+ mat.SetUnity();
+ mat = mat * rot;
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ if(m_aBikeNodes[BIKE_HANDLEBARS]){
+ // Transform handle
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_HANDLEBARS]));
+ pos = mat.GetPosition();
+ if(GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED){
+ mat.SetUnity();
+ mat = mat * rot;
+ mat.Translate(pos);
+ }else
+ mat.SetTranslate(mat.GetPosition());
+ mat.UpdateRW();
+ }
+ }
+
+ // Rear fork
+ if(m_aBikeNodes[BIKE_FORKS_REAR]){
+ float sine = (m_aWheelPosition[BIKEWHEEL_REAR] - m_aWheelBasePosition[BIKEWHEEL_REAR])/m_fRearForkLength;
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_FORKS_REAR]));
+ pos = mat.GetPosition();
+ mat.SetRotate(-Asin(sine), 0.0f, 0.0f);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+
+ // Front wheel
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_WHEEL_FRONT]));
+ pos.x = mat.GetPosition().x;
+ pos.z = m_aWheelPosition[BIKEWHEEL_FRONT] - m_fFrontForkZ;
+ float y = (colModel->lines[BIKESUSP_F1].p0.y+colModel->lines[BIKESUSP_F2].p0.y)/2.0f - m_fFrontForkY;
+ pos.y = y - (m_aWheelPosition[BIKEWHEEL_FRONT] - m_aWheelBasePosition[BIKEWHEEL_FRONT])*m_fFrontForkSlope;
+ if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST)
+ mat.SetRotate(m_aWheelRotation[BIKEWHEEL_FRONT], 0.0f, 0.05f*Sin(m_aWheelRotation[BIKEWHEEL_FRONT]));
+ else
+ mat.SetRotateX(m_aWheelRotation[BIKEWHEEL_FRONT]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ // and mudguard
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_MUDGUARD]));
+ mat.SetTranslateOnly(pos);
+ mat.UpdateRW();
+
+ // Rear wheel
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_WHEEL_REAR]));
+ pos = mat.GetPosition();
+ if(m_wheelStatus[BIKEWHEEL_REAR] == WHEEL_STATUS_BURST)
+ mat.SetRotate(m_aWheelRotation[BIKEWHEEL_REAR], 0.0f, 0.07f*Sin(m_aWheelRotation[BIKEWHEEL_REAR]));
+ else
+ mat.SetRotateX(m_aWheelRotation[BIKEWHEEL_REAR]);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ // Chassis
+ if(m_aBikeNodes[BIKE_CHASSIS]){
+ mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_CHASSIS]));
+ pos = mat.GetPosition();
+ pos.z = (1.0f - Cos(m_fLeanLRAngle)) * (0.9*colModel->boundingBox.min.z);
+ mat.SetRotateX(-0.05f*Abs(m_fLeanLRAngle));
+ mat.RotateY(m_fLeanLRAngle);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+
+ // Exhaust smoke
+ if(bEngineOn && !(pHandling->Flags & HANDLING_NO_EXHAUST) && fwdSpeed < 130.0f){
+ CVector exhaustPos = mi->m_positions[CAR_POS_EXHAUST];
+ CVector pos1, pos2, dir;
+
+ if(exhaustPos != CVector(0.0f, 0.0f, 0.0f)){
+ dir.z = 0.0f;
+ if(fwdSpeed < 10.0f){
+ CVector steerFwd(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f);
+ steerFwd = Multiply3x3(GetMatrix(), steerFwd);
+ float r = CGeneral::GetRandomNumberInRange(-0.06f, -0.03f);
+ dir.x = steerFwd.x * r;
+ dir.y = steerFwd.y * r;
+ }else{
+ dir.x = m_vecMoveSpeed.x;
+ dir.y = m_vecMoveSpeed.y;
+ }
+
+ bool dblExhaust = false;
+ pos1 = GetMatrix() * exhaustPos;
+ if(pHandling->Flags & HANDLING_DBL_EXHAUST){
+ dblExhaust = true;
+ pos2 = exhaustPos;
+ pos2.x = -pos2.x;
+ pos2 = GetMatrix() * pos2;
+ }
+
+ static float fumesLimit = 2.0f;
+ if(CGeneral::GetRandomNumberInRange(1.0f, 3.0f)*(m_fGasPedal+1.1f) > fumesLimit){
+ CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir);
+ if(dblExhaust)
+ CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir);
+
+ if(GetStatus() == STATUS_PLAYER && (CTimer::GetFrameCounter()&3) == 0 &&
+ CWeather::Rain == 0.0f){
+ CVector camDist = GetPosition() - TheCamera.GetPosition();
+ if(DotProduct(GetForward(), camDist) > 0.0f ||
+ TheCamera.GetLookDirection() == LOOKING_LEFT ||
+ TheCamera.GetLookDirection() == LOOKING_RIGHT){
+ if(dblExhaust)
+ pos1 = 0.5f*pos1 + 0.5f*pos2;
+
+ if(TheCamera.GetLookDirection() == LOOKING_LEFT ||
+ TheCamera.GetLookDirection() == LOOKING_RIGHT)
+ pos1 -= 0.2f*GetForward();
+
+ CParticle::AddParticle(PARTICLE_HEATHAZE, pos1, CVector(0.0f, 0.0f, 0.0f));
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+CBike::Render(void)
+{
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 3000;
+ mi->SetVehicleColour(m_currentColour1, m_currentColour2);
+ CEntity::Render();
+}
+
+int32
+CBike::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints)
+{
+ int i;
+ CColModel *colModel;
+
+ if(GetStatus() != STATUS_SIMPLE)
+ bVehicleColProcessed = true;
+
+ colModel = GetColModel();
+
+ int numWheelCollisions = 0;
+ float prevRatios[4] = { 0.0f, 0.0f, 0.0f, 0.0f};
+ for(i = 0; i < 4; i++)
+ prevRatios[i] = m_aSuspensionSpringRatio[i];
+
+ if(m_bIsVehicleBeingShifted || bSkipLineCol || ent->IsPed() ||
+ GetModelIndex() == MI_DODO && ent->IsVehicle())
+ colModel->numLines = 0;
+
+ int numCollisions = CCollision::ProcessColModels(GetMatrix(), *colModel,
+ ent->GetMatrix(), *ent->GetColModel(),
+ colpoints,
+ m_aWheelColPoints, m_aSuspensionSpringRatio);
+
+ // m_aSuspensionSpringRatio are now set to the point where the tyre touches ground.
+ // In ProcessControl these will be re-normalized to ignore the tyre radius.
+ if(colModel->numLines){
+ for(i = 0; i < 4; i++){
+ if(m_aSuspensionSpringRatio[i] < 1.0f && m_aSuspensionSpringRatio[i] < prevRatios[i]){
+ numWheelCollisions++;
+
+ // wheel is touching a physical
+ if(ent->IsVehicle() || ent->IsObject()){
+ CPhysical *phys = (CPhysical*)ent;
+
+ m_aGroundPhysical[i] = phys;
+ phys->RegisterReference((CEntity**)&m_aGroundPhysical[i]);
+ m_aGroundOffset[i] = m_aWheelColPoints[i].point - phys->GetPosition();
+ }
+
+ m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB;
+ if(ent->IsBuilding())
+ m_pCurGroundEntity = ent;
+ }
+ }
+ }else
+ colModel->numLines = 4;
+
+ if(numCollisions > 0 || numWheelCollisions > 0){
+ AddCollisionRecord(ent);
+ if(!ent->IsBuilding())
+ ((CPhysical*)ent)->AddCollisionRecord(this);
+
+ if(numCollisions > 0)
+ if(ent->IsBuilding() ||
+ ent->IsObject() && ((CPhysical*)ent)->bInfiniteMass)
+ bHasHitWall = true;
+ }
+
+ return numCollisions;
+}
+
+static int16 nLastControlInput;
+static float fMouseCentreRange = 0.35f;
+static float fMouseSteerSens = -0.0035f;
+static float fMouseCentreMult = 0.975f;
+
+void
+CBike::ProcessControlInputs(uint8 pad)
+{
+ float speed = DotProduct(m_vecMoveSpeed, GetForward());
+
+ if(CPad::GetPad(pad)->GetExitVehicle())
+ bIsHandbrakeOn = true;
+ else
+ bIsHandbrakeOn = !!CPad::GetPad(pad)->GetHandBrake();
+
+ // Steer left/right
+#ifdef FIX_BUGS
+ if(CCamera::m_bUseMouse3rdPerson && !CVehicle::m_bDisableMouseSteering){
+ if(CPad::GetPad(pad)->GetMouseX() != 0.0f){
+ m_fSteerInput += fMouseSteerSens*CPad::GetPad(pad)->GetMouseX();
+ nLastControlInput = 2;
+ if(Abs(m_fSteerInput) < fMouseCentreRange)
+ m_fSteerInput *= Pow(fMouseCentreMult, CTimer::GetTimeStep());
+ }else if(CPad::GetPad(pad)->GetSteeringLeftRight() || nLastControlInput != 2){
+ // mouse hasn't move, steer with pad like below
+ m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)*
+ 0.2f*CTimer::GetTimeStep();
+ nLastControlInput = 0;
+ }
+ }else
+#endif
+ {
+ m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)*
+ 0.2f*CTimer::GetTimeStep();
+ nLastControlInput = 0;
+ }
+ m_fSteerInput = clamp(m_fSteerInput, -1.0f, 1.0f);
+
+ // Lean forward/backward
+ float updown;
+#ifdef FREE_CAM
+ if (CCamera::bFreeCam) updown = CPad::IsAffectedByController ? -CPad::GetPad(pad)->GetSteeringUpDown()/128.0f : CPad::GetPad(pad)->GetCarGunUpDown()/128.0f;
+ else
+#endif
+ updown = -CPad::GetPad(pad)->GetSteeringUpDown()/128.0f + CPad::GetPad(pad)->GetCarGunUpDown()/128.0f;
+ m_fLeanInput += (updown - m_fLeanInput)*0.2f*CTimer::GetTimeStep();
+ m_fLeanInput = clamp(m_fLeanInput, -1.0f, 1.0f);
+
+ // Accelerate/Brake
+ float acceleration = (CPad::GetPad(pad)->GetAccelerate() - CPad::GetPad(pad)->GetBrake())/255.0f;
+ if(GetModelIndex() == MI_DODO && acceleration < 0.0f)
+ acceleration *= 0.3f;
+ if(Abs(speed) < 0.01f){
+ // standing still, go into direction we want
+ if(CPad::GetPad(pad)->GetAccelerate() > 150.0f && CPad::GetPad(pad)->GetBrake() > 150.0f){
+ m_fGasPedal = CPad::GetPad(pad)->GetAccelerate()/255.0f;
+ m_fBrakePedal = CPad::GetPad(pad)->GetBrake()/255.0f;
+ m_doingBurnout = 1;
+ }else{
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }
+ }else{
+#if 1
+ // simpler than the code below
+ if(speed * acceleration < 0.0f){
+ // if opposite directions, have to brake first
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = Abs(acceleration);
+ }else{
+ // accelerating in same direction we were already going
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }
+#else
+ if(speed < 0.0f){
+ // moving backwards currently
+ if(acceleration < 0.0f){
+ // still go backwards
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }else{
+ // want to go forwards, so brake
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = acceleration;
+ }
+ }else{
+ // moving forwards currently
+ if(acceleration < 0.0f){
+ // want to go backwards, so brake
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = -acceleration;
+ }else{
+ // still go forwards
+ m_fGasPedal = acceleration;
+ m_fBrakePedal = 0.0f;
+ }
+ }
+#endif
+ }
+
+ // Actually turn wheels
+ static float fValue; // why static?
+ if(m_fSteerInput < 0.0f)
+ fValue = -sq(m_fSteerInput);
+ else
+ fValue = sq(m_fSteerInput);
+ m_fSteerAngle = DEGTORAD(pHandling->fSteeringLock) * fValue;
+
+ if(bComedyControls){
+ if(((CTimer::GetTimeInMilliseconds() >> 10) & 0xF) < 12)
+ m_fGasPedal = 1.0f;
+ if((((CTimer::GetTimeInMilliseconds() >> 10)+6) & 0xF) < 12)
+ m_fBrakePedal = 0.0f;
+ bIsHandbrakeOn = false;
+ if(CTimer::GetTimeInMilliseconds() & 0x800)
+ m_fSteerAngle += 0.08f;
+ else
+ m_fSteerAngle -= 0.03f;
+ }
+
+ // Brake if player isn't in control
+ // BUG: game always uses pad 0 here
+ if(CPad::GetPad(pad)->ArePlayerControlsDisabled()){
+ m_fBrakePedal = 1.0f;
+ bIsHandbrakeOn = true;
+ m_fGasPedal = 0.0f;
+
+ FindPlayerPed()->KeepAreaAroundPlayerClear();
+
+ // slow down car immediately
+ speed = m_vecMoveSpeed.Magnitude();
+ if(speed > 0.28f)
+ m_vecMoveSpeed *= 0.28f/speed;
+ }
+}
+
+void
+CBike::ProcessBuoyancy(void)
+{
+ int i;
+ CVector impulse, point;
+
+ if(mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)){
+ bTouchingWater = true;
+ ApplyMoveForce(impulse);
+ ApplyTurnForce(impulse, point);
+
+ float timeStep = Max(CTimer::GetTimeStep(), 0.01f);
+ float impulseRatio = impulse.z / (GRAVITY * m_fMass * timeStep);
+ float waterResistance = Pow(1.0f - 0.05f*impulseRatio, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= waterResistance;
+ m_vecTurnSpeed *= waterResistance;
+
+ if(impulseRatio > 0.8f ||
+ impulseRatio > 0.4f && (m_aSuspensionSpringRatio[0] == 1.0f ||
+ m_aSuspensionSpringRatio[1] == 1.0f ||
+ m_aSuspensionSpringRatio[2] == 1.0f ||
+ m_aSuspensionSpringRatio[3] == 1.0f)){
+ bIsInWater = true;
+ bIsDrowning = true;
+ if(m_vecMoveSpeed.z < -0.1f)
+ m_vecMoveSpeed.z = -0.1f;
+
+ if(pDriver){
+ pDriver->bIsInWater = true;
+ if(pDriver->IsPlayer() || !bWaterTight){
+ if(m_aSuspensionSpringRatio[0] < 1.0f ||
+ m_aSuspensionSpringRatio[1] < 1.0f ||
+ m_aSuspensionSpringRatio[2] < 1.0f ||
+ m_aSuspensionSpringRatio[3] < 1.0f)
+ pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0);
+ else
+ KnockOffRider(WEAPONTYPE_DROWNING, 0, pDriver, false);
+ }
+ }
+ for(i = 0; i < m_nNumMaxPassengers; i++)
+ if(pPassengers[i]){
+ pPassengers[i]->bIsInWater = true;
+ if(pPassengers[i]->IsPlayer() || !bWaterTight){
+ if(m_aSuspensionSpringRatio[0] < 1.0f ||
+ m_aSuspensionSpringRatio[1] < 1.0f ||
+ m_aSuspensionSpringRatio[2] < 1.0f ||
+ m_aSuspensionSpringRatio[3] < 1.0f)
+ pPassengers[i]->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0);
+ else
+ KnockOffRider(WEAPONTYPE_DROWNING, 0, pPassengers[i], false);
+ }
+ }
+ }else{
+ bIsInWater = false;
+ bIsDrowning = false;
+ }
+ }else{
+ bIsInWater = false;
+ bIsDrowning = false;
+ bTouchingWater = false;
+ }
+}
+
+void
+CBike::DoDriveByShootings(void)
+{
+ CAnimBlendAssociation *anim;
+ CPlayerInfo* playerInfo = ((CPlayerPed*)pDriver)->GetPlayerInfoForThisPlayerPed();
+ if (playerInfo && !playerInfo->m_bDriveByAllowed)
+ return;
+
+ CWeapon *weapon = pDriver->GetWeapon();
+ if(CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->m_nWeaponSlot != 5)
+ return;
+
+ weapon->Update(pDriver->m_audioEntityId, nil);
+
+ bool lookingLeft = false;
+ bool lookingRight = false;
+ if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
+ TheCamera.m_bObbeCinematicCarCamOn){
+ if(CPad::GetPad(0)->GetLookLeft())
+ lookingLeft = true;
+ if(CPad::GetPad(0)->GetLookRight())
+ lookingRight = true;
+ }else{
+ if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft)
+ lookingLeft = true;
+ if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight)
+ lookingRight = true;
+ }
+
+ if(lookingLeft || lookingRight || CPad::GetPad(0)->GetCarGunFired()){
+ if(lookingLeft){
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), m_bikeAnimType, ANIM_BIKE_DRIVEBY_LHS);
+ }else if(lookingRight){
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), m_bikeAnimType, ANIM_BIKE_DRIVEBY_RHS);
+ }else{
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), m_bikeAnimType, ANIM_BIKE_DRIVEBY_FORWARD);
+ }
+
+ if (!anim || !anim->IsRunning()) {
+ if (CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer) {
+ weapon->FireFromCar(this, lookingLeft, lookingRight);
+ weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70;
+ }
+ }
+ }else{
+ weapon->Reload();
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ }
+}
+
+void
+CBike::VehicleDamage(void)
+{
+ float impulse = m_fDamageImpulse;
+ float colSpeed = 800.0f*impulse/m_fMass;
+ if(GetStatus() == STATUS_PLAYER)
+ colSpeed *= 0.65f;
+ else if(VehicleCreatedBy == MISSION_VEHICLE)
+ colSpeed *= 0.4f;
+
+ if(!bCanBeDamaged)
+ return;
+
+ if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f)
+ impulse *= 0.5f;
+
+ if(bIsStanding && impulse > 20.0f)
+ bIsStanding = false;
+
+ // Inflict damage on the driver and passenger
+ if(pDriver && pDriver->GetPedState() == PED_DRIVING && colSpeed > 10.0f){
+ float fwd = 0.6f;
+ if(Abs(DotProduct(m_vecDamageNormal, GetForward())) > 0.85f){
+ float u = Max(DotProduct(m_vecDamageNormal, CVector(0.0f, 0.0f, 1.0f)), 0.0f);
+ if(u < 0.85f)
+ u = 0.0f;
+ fwd += 7.0f * SQR(u);
+ }
+ float up = 0.05f;
+
+ if(GetModelIndex() == MI_SANCHEZ){
+ fwd *= 0.65f;
+ up *= 0.75f;
+ }
+
+ float total = fwd*Abs(DotProduct(m_vecDamageNormal, GetForward())) +
+ 0.45f*Abs(DotProduct(m_vecDamageNormal, GetRight())) +
+ up*Max(DotProduct(m_vecDamageNormal, GetUp()), 0.0f);
+ float damage = (total - 1.5f*Min(DotProduct(m_vecDamageNormal, GetUp()), 0.0f))*colSpeed;
+
+ if(pDriver->IsPlayer() && CCullZones::CamStairsForPlayer() && CCullZones::FindZoneWithStairsAttributeForPlayer())
+ damage = 0.0f;
+
+ if(damage > 75.0f){
+ int dir = -10;
+ if(pDriver){
+ dir = pDriver->GetLocalDirection(-m_vecDamageNormal);
+ if(pDriver->m_fHealth > 0.0f)
+ pDriver->InflictDamage(m_pDamageEntity, WEAPONTYPE_RAMMEDBYCAR, 0.05f*damage, PEDPIECE_TORSO, dir);
+ if(pDriver && pDriver->GetPedState() == PED_DRIVING)
+ KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, dir, pDriver, false);
+ }
+ if(pPassengers[0]){
+ dir = pPassengers[0]->GetLocalDirection(-m_vecDamageNormal);
+ if(pPassengers[0]->m_fHealth > 0.0f)
+ pPassengers[0]->InflictDamage(m_pDamageEntity, WEAPONTYPE_RAMMEDBYCAR, 0.05f*damage, PEDPIECE_TORSO, dir);
+ if(pPassengers[0] && pPassengers[0]->GetPedState() == PED_DRIVING)
+ KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, dir, pPassengers[0], false);
+ }
+ }
+ }
+
+ if(impulse > 25.0f && GetStatus() != STATUS_WRECKED){
+ float damage = (impulse-25.0f)*pHandling->fCollisionDamageMultiplier;
+ if(damage > 0.0f){
+ if(damage > 5.0f &&
+ pDriver &&
+ m_pDamageEntity && m_pDamageEntity->IsVehicle() &&
+ (this != FindPlayerVehicle() || ((CVehicle*)m_pDamageEntity)->VehicleCreatedBy == MISSION_VEHICLE) &&
+ ((CVehicle*)m_pDamageEntity)->pDriver)
+ pDriver->Say(SOUND_PED_CRASH_VEHICLE);
+
+ int oldHealth = m_fHealth;
+ if(this == FindPlayerVehicle())
+ m_fHealth -= bTakeLessDamage ? damage/6.0f : damage/2.0f;
+ else if(bTakeLessDamage)
+ m_fHealth -= damage/12.0f;
+ else if(m_pDamageEntity && m_pDamageEntity == FindPlayerVehicle())
+ m_fHealth -= damage/1.5f;
+ else
+ m_fHealth -= damage/4.0f;
+ if(m_fHealth <= 0.0f && oldHealth > 0)
+ m_fHealth = 1.0f;
+ }
+ }
+
+ if(m_fHealth < 250.0f){
+ // Car is on fire
+ if(!bIsOnFire){
+ // Set engine on fire and remember who did this
+ bIsOnFire = true;
+ m_fFireBlowUpTimer = 0.0f;
+ m_pSetOnFireEntity = m_pDamageEntity;
+ if(m_pSetOnFireEntity)
+ m_pSetOnFireEntity->RegisterReference(&m_pSetOnFireEntity);
+ }
+ }
+}
+
+void
+CBike::AddDamagedVehicleParticles(void)
+{
+ if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson())
+ return;
+ if(this != FindPlayerVehicle() && (CTimer::GetFrameCounter() + m_randomSeed) & 1)
+ return;
+ if(m_fHealth >= 650.0f)
+ return;
+
+ CVector direction = 0.5f*m_vecMoveSpeed;
+ CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->GetFrontSeatPosn();
+
+ damagePos.z -= 0.4f;
+ damagePos = GetMatrix()*damagePos;
+
+ CalculateLeanMatrix();
+
+ if(m_fHealth < 250.0f){
+ // fire, done in processControl
+ }else if(m_fHealth < 320.0f){
+ direction *= 0.2f;
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, direction + 0.02f*m_leanMatrix.GetRight());
+ }else if(m_fHealth < 390.0f){
+ if(((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 0 ||
+ ((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 2)
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction + 0.05f*m_leanMatrix.GetRight());
+ direction *= 0.3f;
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, direction + 0.04f*m_leanMatrix.GetRight());
+ }else if(m_fHealth < 460.0f){
+ int rnd = CTimer::GetFrameCounter() + m_randomSeed;
+ if(rnd < 10 ||
+ rnd < 70 && rnd > 25 ||
+ rnd < 160 && rnd > 100 ||
+ rnd < 200 && rnd > 175 ||
+ rnd > 235)
+ return;
+ direction.z += 0.05f;
+ if(TheCamera.GetLookDirection() != LOOKING_FORWARD){
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction + 0.08f*m_leanMatrix.GetRight(), nil, 0.1f, 0, 0, 0, 1000);
+ }else if(((CTimer::GetFrameCounter() + m_randomSeed) & 1) == 0){
+ direction = 0.8f*m_vecMoveSpeed;
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction + 0.07f*m_leanMatrix.GetRight(), nil, 0.1f, 0, 0, 0, 1000);
+ }
+ }else if(((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 0 ||
+ ((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 2){
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos + 0.06f*m_leanMatrix.GetRight(), direction);
+ }
+}
+
+int32
+CBike::AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed)
+{
+ int i;
+ CVector dir;
+ static float minSize = 0.02f;
+ static float maxSize = 0.04f;
+ static RwRGBA grassCol = { 8, 24, 8, 255 };
+ static RwRGBA gravelCol = { 64, 64, 64, 255 };
+ static RwRGBA mudCol = { 64, 32, 16, 255 };
+ static RwRGBA sandCol = { 170, 165, 140, 255 };
+ static RwRGBA waterCol = { 48, 48, 64, 0 };
+
+ if(!belowEffectSpeed &&
+ colpoint->surfaceB != SURFACE_SAND && colpoint->surfaceB != SURFACE_SAND_BEACH)
+ return 0;
+
+ switch(colpoint->surfaceB){
+ case SURFACE_GRASS:
+ dir.x = -0.05f*m_vecMoveSpeed.x;
+ dir.y = -0.05f*m_vecMoveSpeed.y;
+ for(i = 0; i < 4; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.04f);
+ CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil,
+ CGeneral::GetRandomNumberInRange(minSize, maxSize), grassCol);
+ }
+ return 0;
+ case SURFACE_GRAVEL:
+ dir.x = -0.05f*m_vecMoveSpeed.x;
+ dir.y = -0.05f*m_vecMoveSpeed.y;
+ for(i = 0; i < 4; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.04f);
+ CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil,
+ CGeneral::GetRandomNumberInRange(minSize, maxSize), gravelCol);
+ }
+ return 1;
+ case SURFACE_MUD_DRY:
+ dir.x = -0.05f*m_vecMoveSpeed.x;
+ dir.y = -0.05f*m_vecMoveSpeed.y;
+ for(i = 0; i < 4; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.04f);
+ CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil,
+ CGeneral::GetRandomNumberInRange(minSize, maxSize), mudCol);
+ }
+ return 0;
+ case SURFACE_SAND:
+ case SURFACE_SAND_BEACH:
+ if(CTimer::GetFrameCounter() & 2)
+ return 0;
+ dir.x = 0.75f*m_vecMoveSpeed.x;
+ dir.y = 0.75f*m_vecMoveSpeed.y;
+ for(i = 0; i < 1; i++){
+ dir.z = CGeneral::GetRandomNumberInRange(0.02f, 0.055f);
+ CParticle::AddParticle(PARTICLE_SAND, colpoint->point, dir, nil,
+ 0.8f*m_vecMoveSpeed.Magnitude(), sandCol);
+ }
+ return 0;
+ default:
+ if(CWeather::WetRoads > 0.01f){
+ CParticle::AddParticle(
+ PARTICLE_WATERSPRAY,
+ colpoint->point + CVector(0.0f, 0.0f, 0.25f+0.25f),
+ CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.005f, 0.04f)),
+ nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.5f), waterCol);
+ return 0;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+CBike::GetComponentWorldPosition(int32 component, CVector &pos)
+{
+ if(m_aBikeNodes[component] == nil){
+ printf("BikeNode missing: %d %d\n", GetModelIndex(), component);
+ return;
+ }
+ RwMatrix *ltm = RwFrameGetLTM(m_aBikeNodes[component]);
+ pos = *RwMatrixGetPos(ltm);
+}
+
+bool
+CBike::IsComponentPresent(int32 component)
+{
+ return m_aBikeNodes[component] != nil;
+}
+
+void
+CBike::SetComponentRotation(int32 component, CVector rotation)
+{
+ CMatrix mat(RwFrameGetMatrix(m_aBikeNodes[component]));
+ CVector pos = mat.GetPosition();
+ // BUG: all these set the whole matrix
+ mat.SetRotateX(DEGTORAD(rotation.x));
+ mat.SetRotateY(DEGTORAD(rotation.y));
+ mat.SetRotateZ(DEGTORAD(rotation.z));
+ mat.Translate(pos);
+ mat.UpdateRW();
+}
+
+bool
+CBike::IsDoorReady(eDoors door)
+{
+ return true;
+}
+
+bool
+CBike::IsDoorFullyOpen(eDoors door)
+{
+ return false;
+}
+
+bool
+CBike::IsDoorClosed(eDoors door)
+{
+ return false;
+}
+
+bool
+CBike::IsDoorMissing(eDoors door)
+{
+ return true;
+}
+
+void
+CBike::RemoveRefsToVehicle(CEntity *ent)
+{
+ int i;
+ for(i = 0; i < 4; i++)
+ if(m_aGroundPhysical[i] == ent)
+ m_aGroundPhysical[i] = nil;
+}
+
+void
+CBike::BlowUpCar(CEntity *culprit)
+{
+ if(!bCanBeDamaged)
+ return;
+
+#ifdef FIX_BUGS
+ // taken from CAutomobile. maybe tweak values?
+ if(culprit == FindPlayerPed() || culprit == FindPlayerVehicle()){
+ CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 20;
+ CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 10.0f;
+ CStats::PropertyDestroyed += CGeneral::GetRandomNumber()%6000 + 4000;
+ }
+#endif
+
+ // explosion pushes vehicle up
+ m_vecMoveSpeed.z += 0.13f;
+ SetStatus(STATUS_WRECKED);
+ bRenderScorched = true;
+
+ m_fHealth = 0.0f;
+ m_nBombTimer = 0;
+
+ TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z);
+
+ KillPedsInVehicle();
+
+ bEngineOn = false;
+ bLightsOn = false;
+ ChangeLawEnforcerState(false);
+
+ CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0);
+ CDarkel::RegisterCarBlownUpByPlayer(this);
+}
+
+bool
+CBike::SetUpWheelColModel(CColModel *colModel)
+{
+ RwMatrix *mat = RwMatrixCreate();
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ CColModel *vehColModel = mi->GetColModel();
+
+ colModel->boundingSphere = vehColModel->boundingSphere;
+ colModel->boundingBox = vehColModel->boundingBox;
+
+ GetRelativeMatrix(mat, m_aBikeNodes[BIKE_WHEEL_FRONT], m_aBikeNodes[BIKE_CHASSIS]);
+ colModel->spheres[0].Set(0.5f*mi->m_wheelScale, *RwMatrixGetPos(mat), SURFACE_RUBBER, CAR_PIECE_WHEEL_LF);
+ GetRelativeMatrix(mat, m_aBikeNodes[BIKE_WHEEL_REAR], m_aBikeNodes[BIKE_CHASSIS]);
+ colModel->spheres[1].Set(0.5f*mi->m_wheelScale, *RwMatrixGetPos(mat), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR);
+ colModel->numSpheres = 2;
+#ifdef FIX_BUGS
+ RwMatrixDestroy(mat);
+#endif
+ return true;
+}
+
+float fBikeBurstForceMult = 0.02f;
+float fBikeBurstFallSpeed = 0.3f;
+float fBikeBurstFallSpeedPlayer = 0.55f;
+
+void
+CBike::BurstTyre(uint8 wheel, bool applyForces)
+{
+ if(bTyresDontBurst)
+ return;
+
+ switch(wheel){
+ case CAR_PIECE_WHEEL_LF: wheel = BIKEWHEEL_FRONT; break;
+ case CAR_PIECE_WHEEL_LR: wheel = BIKEWHEEL_REAR; break;
+ }
+
+ if(m_wheelStatus[wheel] == WHEEL_STATUS_OK){
+ m_wheelStatus[wheel] = WHEEL_STATUS_BURST;
+#ifdef FIX_BUGS
+ CStats::TyresPopped++;
+#endif
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TYRE_POP, 0.0f);
+
+ if(GetStatus() == STATUS_SIMPLE){
+ SetStatus(STATUS_PHYSICS);
+ CCarCtrl::SwitchVehicleToRealPhysics(this);
+ }
+
+ if(applyForces){
+ ApplyMoveForce(GetRight() * m_fMass * CGeneral::GetRandomNumberInRange(-0.02f, 0.02f));
+ ApplyTurnForce(GetRight() * m_fTurnMass * CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), GetForward());
+ }
+
+ // This code checks piece types originally so it is never triggered
+ // as we have converted them to wheel indices above already.
+ if(pDriver){
+#ifdef FIX_SIGNIFICANT_BUGS
+ if(wheel == BIKEWHEEL_FRONT && (m_aSuspensionSpringRatioPrev[BIKESUSP_F1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_F2] < 1.0f) ||
+ wheel == BIKEWHEEL_REAR && (m_aSuspensionSpringRatioPrev[BIKESUSP_R1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_R2] < 1.0f)){
+#else
+ if(wheel == CAR_PIECE_WHEEL_LF && (m_aSuspensionSpringRatioPrev[BIKESUSP_F1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_F2] < 1.0f) ||
+ wheel == CAR_PIECE_WHEEL_LR && (m_aSuspensionSpringRatioPrev[BIKESUSP_R1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_R2] < 1.0f)){
+#endif
+ float speedSq = m_vecMoveSpeed.MagnitudeSqr();
+ if(speedSq > fBikeBurstFallSpeed &&
+ (GetStatus() != STATUS_PLAYER || speedSq > fBikeBurstFallSpeedPlayer)){
+#ifdef FIX_SIGNIFICANT_BUGS
+ if(wheel == BIKEWHEEL_FRONT){
+#else
+ if(wheel == CAR_PIECE_WHEEL_LF){
+#endif
+ KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, 0, pDriver, false);
+ if(pPassengers[0])
+ KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, 0, pPassengers[0], false);
+ }else
+ ApplyTurnForce(2.0f*fBikeBurstForceMult*m_fTurnMass*GetRight(), GetForward());
+ }
+ }
+ }
+ }
+}
+
+bool
+CBike::IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset)
+{
+ CColPoint colpoint;
+ CEntity *ent;
+ colpoint.point = CVector(0.0f, 0.0f, 0.0f);
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ CVector seatPos = mi->GetFrontSeatPosn();
+ if(component == CAR_DOOR_RR || component == CAR_DOOR_LR)
+ seatPos = mi->m_positions[CAR_POS_BACKSEAT];
+ if(component == CAR_DOOR_LF || component == CAR_DOOR_LR)
+ seatPos.x = -seatPos.x;
+ seatPos = GetMatrix() * seatPos;
+
+ CVector doorPos = CPed::GetPositionToOpenCarDoor(this, component);
+ if(doorOffset){
+ CVector off = *doorOffset;
+ if(component == CAR_DOOR_RF || component == CAR_DOOR_RR)
+ off.x = -off.x;
+ doorPos += Multiply3x3(GetMatrix(), off);
+ }
+
+ if(GetUp().z < 0.0f){
+ seatPos.z += 0.5f;
+ doorPos.z += 0.5f;
+ }
+
+ CVector dist = doorPos - seatPos;
+
+ // Removing that makes thiProcessEntityCollisions func. return false for van doors.
+ doorPos.z += 0.5f;
+ float length = dist.Magnitude();
+ CVector pedPos = seatPos + dist*((length+0.6f)/length);
+
+ if(!CWorld::GetIsLineOfSightClear(seatPos, pedPos, true, false, false, true, false, false))
+ return false;
+ if(CWorld::TestSphereAgainstWorld(doorPos, 0.6f, this, true, true, false, true, false, false))
+ return false;
+ if(CWorld::ProcessVerticalLine(doorPos, 1000.0f, colpoint, ent, true, false, false, true, false, false, nil))
+ if(colpoint.point.z > doorPos.z && colpoint.point.z < doorPos.z + 0.6f)
+ return false;
+ float upperZ = colpoint.point.z;
+ if(!CWorld::ProcessVerticalLine(doorPos, -1000.0f, colpoint, ent, true, false, false, true, false, false, nil))
+ return false;
+ if(upperZ != 0.0f && upperZ < colpoint.point.z)
+ return false;
+ return true;
+}
+
+float
+CBike::GetHeightAboveRoad(void)
+{
+ return m_fHeightAboveRoad;
+}
+
+void
+CBike::PlayCarHorn(void)
+{
+ int r;
+
+ if (IsAlarmOn() || m_nCarHornTimer != 0)
+ return;
+
+ if (m_nCarHornDelay) {
+ m_nCarHornDelay--;
+ return;
+ }
+
+ m_nCarHornDelay = (CGeneral::GetRandomNumber() & 0x7F) + 150;
+ r = m_nCarHornDelay & 7;
+ if(r < 2){
+ m_nCarHornTimer = 45;
+ }else if(r < 4){
+ if(pDriver)
+ pDriver->Say(SOUND_PED_ANNOYED_DRIVER);
+ m_nCarHornTimer = 45;
+ }else{
+ if(pDriver)
+ pDriver->Say(SOUND_PED_ANNOYED_DRIVER);
+ }
+}
+
+void
+CBike::KnockOffRider(eWeaponType weapon, uint8 direction, CPed *ped, bool bGetBackOn)
+{
+ AnimationId anim = ANIM_STD_KO_FRONT;
+ if(ped == nil)
+ return;
+
+ if(!ped->IsPlayer()){
+ if(bGetBackOn){
+ if(ped->m_pedStats->m_temper > ped->m_pedStats->m_fear &&
+ ped->CharCreatedBy != MISSION_CHAR && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE &&
+ FindPlayerPed()->m_carInObjective == ped->m_pMyVehicle &&
+ !CTheScripts::IsPlayerOnAMission())
+ ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle);
+ else if(ped->m_pedStats->m_temper > ped->m_pedStats->m_fear &&
+ ped->CharCreatedBy != MISSION_CHAR && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE &&
+ !CTheScripts::IsPlayerOnAMission())
+ ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle);
+ else if(ped->m_pedStats->m_temper <= ped->m_pedStats->m_fear &&
+ ped->CharCreatedBy != MISSION_CHAR && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE &&
+ !CTheScripts::IsPlayerOnAMission()){
+ ped->SetObjective(OBJECTIVE_WANDER, ped->m_pMyVehicle);
+ ped->m_nPathDir = CGeneral::GetRandomNumberInRange(0, 8);
+ }
+ }else if(ped->m_leader == nil){
+ if(pDriver == ped)
+ ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, this);
+ else
+ ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, this);
+ }
+ }
+
+ if(ped->IsPed()){
+ CAnimBlendAssociation *assoc;
+ for(assoc = RpAnimBlendClumpGetFirstAssociation(ped->GetClump(), ASSOC_DRIVING);
+ assoc;
+ assoc = RpAnimBlendGetNextAssociation(assoc))
+ assoc->flags |= ASSOC_DELETEFADEDOUT;
+ }
+
+ ped->SetPedState(PED_IDLE);
+ CAnimManager::BlendAnimation(ped->GetClump(), ped->m_animGroup, ANIM_STD_IDLE, 100.0f);
+ ped->m_vehDoor = CAR_DOOR_LF;
+ CPed::PedSetOutCarCB(nil, ped);
+ ped->SetMoveState(PEDMOVE_STILL);
+ if(GetUp().z < 0.0f)
+ ped->SetHeading(CGeneral::LimitRadianAngle(GetForward().Heading() + PI));
+ else
+ ped->SetHeading(GetForward().Heading());
+
+ switch(weapon){
+ case WEAPONTYPE_UNARMED:
+ case WEAPONTYPE_UNIDENTIFIED:
+ ped->m_vecMoveSpeed = m_vecMoveSpeed;
+ ped->m_pCollidingEntity = this;
+ anim = ANIM_STD_NUM;
+ break;
+
+ case WEAPONTYPE_BASEBALLBAT:
+ default:
+ switch(direction){
+ case 0:
+ anim = ANIM_STD_BIKE_FALLBACK;
+ ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.1f);
+ if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.3f))
+ ped->ApplyMoveForce(5.0f*GetUp() - 6.0f*GetForward());
+ ped->m_pCollidingEntity = this;
+ break;
+ case 1:
+ case 2:
+ if(m_vecMoveSpeed.MagnitudeSqr() > SQR(0.3f)){
+ anim = ANIM_STD_HIGHIMPACT_LEFT;
+ ped->m_vecMoveSpeed = 0.3f*m_vecMoveSpeed;
+ ped->ApplyMoveForce(5.0f*GetUp() + 6.0f*GetRight());
+ }else{
+ anim = ANIM_STD_SPINFORWARD_LEFT;
+ ped->m_vecMoveSpeed = m_vecMoveSpeed;
+ ped->ApplyMoveForce(4.0f*GetUp() + 8.0f*GetRight());
+ }
+ // BUG or is it intentionally missing?
+ //ped->m_pCollidingEntity = this;
+ break;
+ case 3:
+ if(m_vecMoveSpeed.MagnitudeSqr() > SQR(0.3f)){
+ anim = ANIM_STD_HIGHIMPACT_RIGHT;
+ ped->m_vecMoveSpeed = 0.3f*m_vecMoveSpeed;
+ ped->ApplyMoveForce(5.0f*GetUp() - 6.0f*GetRight());
+ }else{
+ anim = ANIM_STD_SPINFORWARD_RIGHT;
+ ped->m_vecMoveSpeed = m_vecMoveSpeed;
+ ped->ApplyMoveForce(4.0f*GetUp() - 8.0f*GetRight());
+ }
+ // BUG or is it intentionally missing?
+ //ped->m_pCollidingEntity = this;
+ break;
+ }
+ break;
+
+ case WEAPONTYPE_DROWNING:{
+ RwRGBA color;
+ anim = ANIM_STD_FALL;
+ ped->m_vecMoveSpeed = m_vecMoveSpeed*0.2f;
+ ped->m_vecMoveSpeed.z = 0.0f;
+ ped->m_pCollidingEntity = this;
+ color.red = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj())*0.45f*255;
+ color.green = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj())*0.45f*255;
+ color.blue = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj())*0.45f*255;
+ color.alpha = CGeneral::GetRandomNumberInRange(48, 96);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLASH, 0.0f);
+ CVector splashPos = ped->GetPosition() + 2.2f*ped->m_vecMoveSpeed;
+ float waterZ = 0.0f;
+ if(CWaterLevel::GetWaterLevel(splashPos, &waterZ, false))
+ splashPos.z = waterZ;
+ CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, splashPos, CVector(0.0f, 0.0f, 0.1f),
+ 0.0f, 200, color, true);
+ break;
+ }
+
+ case WEAPONTYPE_FALL: {
+ ped->m_vecMoveSpeed = ped->m_pMyVehicle->m_vecMoveSpeed;
+ float forceXY = -0.6*m_fDamageImpulse * ped->m_fMass / m_fMass;
+ ped->ApplyMoveForce(m_vecDamageNormal.x*forceXY, m_vecDamageNormal.y*forceXY,
+ CGeneral::GetRandomNumberInRange(3.0f, 7.0f));
+ ped->m_pCollidingEntity = this;
+ switch(direction){
+ case 0: anim = ANIM_STD_HIGHIMPACT_BACK; break;
+ case 1: anim = ANIM_STD_SPINFORWARD_RIGHT; break;
+ case 2: anim = ANIM_STD_BIKE_FALLBACK; break;
+ case 3: anim = ANIM_STD_SPINFORWARD_LEFT; break;
+ }
+ if(m_nWheelsOnGround == 0)
+ ped->bKnockedOffBike = true;
+ break;
+ }
+
+ case WEAPONTYPE_RAMMEDBYCAR: {
+ ped->m_vecMoveSpeed = ped->m_pMyVehicle->m_vecMoveSpeed;
+ static float minForceZ = 8.0f;
+ static float maxForceZ = 15.0f;
+ float forceXY = -0.6*m_fDamageImpulse * ped->m_fMass / m_fMass;
+ ped->ApplyMoveForce(m_vecDamageNormal.x*forceXY, m_vecDamageNormal.y*forceXY,
+ CGeneral::GetRandomNumberInRange(minForceZ, maxForceZ));
+ ped->m_pCollidingEntity = this;
+ switch(direction){
+ case 0: anim = ANIM_STD_HIGHIMPACT_BACK; break;
+ case 1: anim = ANIM_STD_SPINFORWARD_RIGHT; break;
+ case 2: anim = ANIM_STD_HIGHIMPACT_FRONT; break;
+ case 3: anim = ANIM_STD_SPINFORWARD_LEFT; break;
+ }
+ ped->bKnockedOffBike = true;
+ if(ped->IsPlayer())
+ ped->Say(SOUND_PED_DAMAGE);
+ break;
+ }
+ }
+
+ if(weapon == WEAPONTYPE_DROWNING){
+ ped->bIsStanding = false;
+ ped->bWasStanding = false;
+ ped->bIsInTheAir = true;
+ ped->bIsInWater = true;
+ ped->bTouchingWater = true;
+ CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_FALL, 4.0f);
+ }else if(weapon != WEAPONTYPE_UNARMED){
+ if(ped->m_fHealth > 0.0f)
+ ped->SetFall(1000, anim, 0);
+ else
+ ped->SetDie(anim);
+ ped->bIsStanding = false;
+ }
+
+ CEntity *ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CVector(0.0f, 0.0, 0.5f), 0.4f, nil, true, false, false, false, false, false);
+ if(ent == nil)
+ ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CVector(0.0f, 0.0, 0.8f), 0.4f, nil, true, false, false, false, false, false);
+ if(ent == nil)
+ ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CTimer::GetTimeStep()*ped->m_vecMoveSpeed+CVector(0.0f, 0.0, 0.5f), 0.4f, nil, true, false, false, false, false, false);
+ if(ent == nil)
+ ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CTimer::GetTimeStep()*ped->m_vecMoveSpeed+CVector(0.0f, 0.0, 0.8f), 0.4f, nil, true, false, false, false, false, false);
+ if(ent){
+ CColPoint point;
+ ent = nil;
+ if(CWorld::ProcessVerticalLine(ped->GetPosition(), ped->GetPosition().z-2.0f, point, ent, true, false, false, false, false, false, nil)){
+ if(ped->m_pMyVehicle == nil){
+ ped->m_pMyVehicle = this;
+ ped->PositionPedOutOfCollision();
+ ped->m_pMyVehicle = nil;
+ }else
+ ped->PositionPedOutOfCollision();
+ }else
+ ped->GetMatrix().Translate(CVector(0.0f, 0.0f, -2.0f));
+ ped->m_pCollidingEntity = ped->m_pMyVehicle;
+ ped->bKnockedOffBike = true;
+ ped->bHeadStuckInCollision = true;
+ }else if(weapon == WEAPONTYPE_RAMMEDBYCAR){
+ if(CWorld::TestSphereAgainstWorld(ped->GetPosition()+CVector(0.0f, 0.0, 1.3f), 0.6f, nil, true, false, false, false, false, false) == nil)
+ ped->GetMatrix().Translate(CVector(0.0f, 0.0f, 0.5f));
+ }
+ ped->m_pMyVehicle = nil;
+}
+
+void
+CBike::PlayHornIfNecessary(void)
+{
+ if(AutoPilot.m_bSlowedDownBecauseOfPeds ||
+ AutoPilot.m_bSlowedDownBecauseOfCars)
+ PlayCarHorn();
+}
+
+void
+CBike::ResetSuspension(void)
+{
+ int i;
+ for(i = 0; i < 2; i++){
+ m_aWheelRotation[i] = 0.0f;
+ m_aWheelState[i] = WHEEL_STATE_NORMAL;
+ }
+ for(i = 0; i < 4; i++){
+ m_aSuspensionSpringRatio[i] = 1.0f;
+ m_aWheelTimer[i] = 0.0f;
+ }
+}
+
+void
+CBike::SetupSuspensionLines(void)
+{
+ int i;
+ CVector posn;
+ float suspOffset = 0.0f;
+ RwFrame *node = nil;
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ CColModel *colModel = mi->GetColModel();
+ RwMatrix *mat = RwMatrixCreate();
+
+ bool initialized = colModel->lines[0].p0.z != FAKESUSPENSION;
+
+ for(i = 0; i < 4; i++){
+ if(initialized){
+ posn = colModel->lines[i].p0;
+ if(i < 2)
+ posn.z = m_aWheelBasePosition[0];
+ else
+ posn.z = m_aWheelBasePosition[1];
+ }else{
+ switch(i){
+ case BIKESUSP_F1:
+ node = m_aBikeNodes[BIKE_WHEEL_FRONT];
+ suspOffset = 0.25f*mi->m_wheelScale;
+ break;
+ case BIKESUSP_F2:
+ node = m_aBikeNodes[BIKE_WHEEL_FRONT];
+ suspOffset = -0.25f*mi->m_wheelScale;
+ break;
+ case BIKESUSP_R1:
+ node = m_aBikeNodes[BIKE_WHEEL_REAR];
+ suspOffset = 0.25f*mi->m_wheelScale;
+ break;
+ case BIKESUSP_R2:
+ node = m_aBikeNodes[BIKE_WHEEL_REAR];
+ suspOffset = -0.25f*mi->m_wheelScale;
+ break;
+ }
+
+ GetRelativeMatrix(mat, node, node);
+ posn = *RwMatrixGetPos(mat);
+ if(i == BIKESUSP_F1)
+ m_aWheelBasePosition[BIKEWHEEL_FRONT] = posn.z;
+ else if(i == BIKESUSP_R1){
+ m_aWheelBasePosition[BIKEWHEEL_REAR] = posn.z;
+
+ GetRelativeMatrix(mat, m_aBikeNodes[BIKE_FORKS_REAR], m_aBikeNodes[BIKE_FORKS_REAR]);
+ float dz = posn.z - RwMatrixGetPos(mat)->z;
+ float dy = posn.y - RwMatrixGetPos(mat)->y;
+ m_fRearForkLength = Sqrt(SQR(dy) + SQR(dz));
+ assert(m_fRearForkLength != 0.0f); // we want to divide by this
+ }
+ posn.y += suspOffset;
+ }
+
+ // uppermost wheel position
+ posn.z += pHandling->fSuspensionUpperLimit;
+ colModel->lines[i].p0 = posn;
+
+ // lowermost wheel position
+ posn.z += pHandling->fSuspensionLowerLimit - pHandling->fSuspensionUpperLimit;
+ // lowest point on tyre
+ posn.z -= mi->m_wheelScale*0.5f;
+ colModel->lines[i].p1 = posn;
+
+ // this is length of the spring at rest
+ m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit;
+ m_aSuspensionLineLength[i] = colModel->lines[i].p0.z - colModel->lines[i].p1.z;
+ }
+
+ if(!initialized){
+ GetRelativeMatrix(mat, m_aBikeNodes[BIKE_FORKS_FRONT], m_aBikeNodes[BIKE_FORKS_FRONT]);
+ m_fFrontForkY = RwMatrixGetPos(mat)->y;
+ m_fFrontForkZ = RwMatrixGetPos(mat)->z;
+ }
+
+ // Compress spring somewhat to get normal height on road
+ m_fHeightAboveRoad = m_aSuspensionSpringLength[0]*(1.0f - 1.0f/(4.0f*pHandling->fSuspensionForceLevel))
+ - colModel->lines[0].p0.z + mi->m_wheelScale*0.5f;
+ for(i = 0; i < 2; i++)
+ m_aWheelPosition[i] = mi->m_wheelScale*0.5f - m_fHeightAboveRoad;
+
+ // adjust col model to include suspension lines
+ if(colModel->boundingBox.min.z > colModel->lines[0].p1.z)
+ colModel->boundingBox.min.z = colModel->lines[0].p1.z;
+ float radius = Max(colModel->boundingBox.min.Magnitude(), colModel->boundingBox.max.Magnitude());
+ if(colModel->boundingSphere.radius < radius)
+ colModel->boundingSphere.radius = radius;
+
+#ifdef FIX_BUGS
+ RwMatrixDestroy(mat);
+#endif
+}
+
+void
+CBike::CalculateLeanMatrix(void)
+{
+ if(bLeanMatrixClean)
+ return;
+
+ CMatrix mat;
+ mat.SetRotateX(-0.05f*Abs(m_fLeanLRAngle));
+ mat.RotateY(m_fLeanLRAngle);
+ m_leanMatrix = GetMatrix();
+ m_leanMatrix = m_leanMatrix * mat;
+ // place wheel back on ground
+ m_leanMatrix.GetPosition() += GetUp()*(1.0f-Cos(m_fLeanLRAngle))*GetColModel()->boundingBox.min.z;
+ bLeanMatrixClean = true;
+}
+
+void
+CBike::GetCorrectedWorldDoorPosition(CVector &pos, CVector p1, CVector p2)
+{
+ CVector &fwd = GetForward();
+ CVector rightWorld = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
+ CVector upWorld = CrossProduct(rightWorld, fwd);
+ CColModel *colModel = GetColModel();
+ float onSide = DotProduct(GetUp(), rightWorld);
+ float diff = Max(colModel->boundingBox.max.z-colModel->boundingBox.max.x, 0.0f);
+ pos = CVector(0.0f, 0.0f, 0.0f);
+ float y = p2.y - p1.y;
+ float x = onSide*diff + p2.x + p1.x;
+ float z = p2.z - p1.z;
+ pos = x*rightWorld + y*fwd + z*upWorld + GetPosition();
+}
+
+void
+CBike::Fix(void)
+{
+ bIsDamaged = false;
+ bIsOnFire = false;
+ m_wheelStatus[0] = WHEEL_STATUS_OK;
+ m_wheelStatus[1] = WHEEL_STATUS_OK;
+}
+
+void
+CBike::SetupModelNodes(void)
+{
+ int i;
+ for(i = 0; i < BIKE_NUM_NODES; i++)
+ m_aBikeNodes[i] = nil;
+ CClumpModelInfo::FillFrameArray(GetClump(), m_aBikeNodes);
+}
+
+void
+CBike::ReduceHornCounter(void)
+{
+ if(m_nCarHornTimer != 0)
+ m_nCarHornTimer--;
+}
+
+#ifdef COMPATIBLE_SAVES
+void
+CBike::Save(uint8*& buf)
+{
+ CVehicle::Save(buf);
+ SkipSaveBuf(buf, 1260 - 672);
+}
+
+void
+CBike::Load(uint8*& buf)
+{
+ CVehicle::Load(buf);
+ SkipSaveBuf(buf, 1260 - 672);
+}
+#endif
diff --git a/src/vehicles/Bike.h b/src/vehicles/Bike.h
new file mode 100644
index 0000000..219d887
--- /dev/null
+++ b/src/vehicles/Bike.h
@@ -0,0 +1,172 @@
+#pragma once
+
+#include "Vehicle.h"
+#include "Skidmarks.h"
+#include "AnimManager.h"
+
+enum eBikeNodes {
+ BIKE_NODE_NONE,
+ BIKE_CHASSIS,
+ BIKE_FORKS_FRONT,
+ BIKE_FORKS_REAR,
+ BIKE_WHEEL_FRONT,
+ BIKE_WHEEL_REAR,
+ BIKE_MUDGUARD,
+ BIKE_HANDLEBARS,
+ BIKE_NUM_NODES
+};
+
+enum {
+ BIKEWHEEL_FRONT,
+ BIKEWHEEL_REAR,
+};
+
+enum {
+ BIKESUSP_F1,
+ BIKESUSP_F2,
+ BIKESUSP_R1,
+ BIKESUSP_R2,
+};
+
+class CBike : public CVehicle
+{
+public:
+ RwFrame *m_aBikeNodes[BIKE_NUM_NODES];
+ bool bLeanMatrixClean;
+ CMatrix m_leanMatrix;
+ CVector m_vecAvgSurfaceNormal;
+ CVector m_vecAvgSurfaceRight;
+ tBikeHandlingData *pBikeHandling;
+ AssocGroupId m_bikeAnimType;
+ uint8 m_wheelStatus[2];
+ CColPoint m_aWheelColPoints[4];
+ float m_aSuspensionSpringRatio[4];
+ float m_aSuspensionSpringRatioPrev[4];
+ float m_aWheelTimer[4];
+ float m_bike_unused1;
+ eSkidmarkType m_aWheelSkidmarkType[2];
+ bool m_aWheelSkidmarkBloody[2];
+ bool m_aWheelSkidmarkUnk[2];
+ float m_aWheelRotation[2];
+ float m_aWheelSpeed[2];
+ float m_aWheelPosition[2];
+ float m_aWheelBasePosition[2];
+ float m_aSuspensionSpringLength[4];
+ float m_aSuspensionLineLength[4];
+ float m_fHeightAboveRoad;
+ float m_fTraction;
+ float m_fRearForkLength;
+ float m_fFrontForkY;
+ float m_fFrontForkZ;
+ float m_fFrontForkSlope;
+ float m_fWheelAngle;
+ float m_fLeanLRAngle;
+ float m_fLeanLRAngle2;
+ float m_fLeanInput;
+ float m_fPedLeanAmountLR;
+ float m_fPedLeanAmountUD;
+ uint8 m_bike_unused2;
+ uint8 unused[3]; // looks like padding..but for what?
+ uint8 m_bike_flag01 : 1;
+ uint8 m_bike_flag02 : 1;
+ uint8 bWaterTight : 1;
+ uint8 bIsBeingPickedUp : 1;
+ uint8 bIsStanding : 1;
+ uint8 bExtraSpeed : 1; // leaning forward
+ uint8 bIsOnFire : 1;
+ uint8 bWheelieCam : 1;
+ int16 m_doingBurnout;
+ float m_fTireTemperature;
+ float m_fBrakeDestabilization;
+ float m_fVelocityChangeForAudio;
+ float m_fFireBlowUpTimer;
+ CPhysical *m_aGroundPhysical[4];
+ CVector m_aGroundOffset[4];
+ CEntity *m_pSetOnFireEntity;
+ uint8 m_nWheelsOnGround;
+ uint8 m_nDriveWheelsOnGround;
+ uint8 m_nDriveWheelsOnGroundPrev;
+ float m_fGasPedalAudio;
+ tWheelState m_aWheelState[2];
+
+ CBike(int32 id, uint8 CreatedBy);
+
+ // from CEntity
+ void SetModelIndex(uint32 id);
+ void ProcessControl(void);
+ void Teleport(CVector v);
+ void PreRender(void);
+ void Render(void);
+
+ // from CPhysical
+ int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints);
+
+ // from CVehicle
+ void ProcessControlInputs(uint8);
+ void GetComponentWorldPosition(int32 component, CVector &pos);
+ bool IsComponentPresent(int32 component);
+ void SetComponentRotation(int32 component, CVector rotation);
+ bool IsDoorReady(eDoors door);
+ bool IsDoorFullyOpen(eDoors door);
+ bool IsDoorClosed(eDoors door);
+ bool IsDoorMissing(eDoors door);
+ void RemoveRefsToVehicle(CEntity *ent);
+ void BlowUpCar(CEntity *ent);
+ bool SetUpWheelColModel(CColModel *colModel);
+ void BurstTyre(uint8 tyre, bool applyForces);
+ bool IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset);
+ float GetHeightAboveRoad(void);
+ void PlayCarHorn(void);
+
+ void KnockOffRider(eWeaponType weapon, uint8 direction, CPed *ped, bool bGetBackOn);
+ void VehicleDamage(void);
+ void ProcessBuoyancy(void);
+ void DoDriveByShootings(void);
+ void AddDamagedVehicleParticles(void);
+ int32 AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed);
+ void PlayHornIfNecessary(void);
+ void ResetSuspension(void);
+ void SetupSuspensionLines(void);
+ void CalculateLeanMatrix(void);
+ void GetCorrectedWorldDoorPosition(CVector &pos, CVector p1, CVector p2);
+
+ void Fix(void);
+ void SetupModelNodes(void);
+ void ReduceHornCounter(void);
+
+#ifdef COMPATIBLE_SAVES
+ virtual void Save(uint8*& buf);
+ virtual void Load(uint8*& buf);
+#endif
+ static const uint32 nSaveStructSize;
+};
+
+// These functions and function names are made up
+
+inline int8 GetBikeDoorFlag(int32 carnode) {
+ switch (carnode) {
+ case CAR_DOOR_RR:
+ case CAR_DOOR_LR:
+ return CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR;
+ case CAR_DOOR_RF:
+ case CAR_DOOR_LF:
+ return CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF;
+ default:
+ return CAR_DOOR_FLAG_UNKNOWN;
+ }
+}
+
+// for m_nGettingOutFlags
+inline int8 GetBikeDoorFlagInclJumpInFromFront(int32 carnode) {
+ switch (carnode) {
+ case CAR_DOOR_RR:
+ case CAR_DOOR_LR:
+ return CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR;
+ case CAR_DOOR_RF:
+ case CAR_DOOR_LF:
+ case CAR_WINDSCREEN:
+ return CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF;
+ default:
+ return CAR_DOOR_FLAG_UNKNOWN;
+ }
+} \ No newline at end of file
diff --git a/src/vehicles/Boat.cpp b/src/vehicles/Boat.cpp
new file mode 100644
index 0000000..0bc653c
--- /dev/null
+++ b/src/vehicles/Boat.cpp
@@ -0,0 +1,1488 @@
+#include "common.h"
+
+#include "main.h"
+#include "General.h"
+#include "Timecycle.h"
+#include "Weather.h"
+#include "HandlingMgr.h"
+#include "CarAI.h"
+#include "CarCtrl.h"
+#include "RwHelper.h"
+#include "ModelIndices.h"
+#include "VisibilityPlugins.h"
+#include "DMAudio.h"
+#include "Camera.h"
+#include "Darkel.h"
+#include "Explosion.h"
+#include "Particle.h"
+#include "ParticleObject.h"
+#include "WaterLevel.h"
+#include "Floater.h"
+#include "World.h"
+#include "Stats.h"
+#include "Pools.h"
+#include "Pad.h"
+#include "Boat.h"
+#include "AnimBlendAssociation.h"
+#include "RpAnimBlend.h"
+#include "Record.h"
+#include "Shadows.h"
+#include "Wanted.h"
+
+#define INVALID_ORIENTATION (-9999.99f)
+
+float CBoat::MAX_WAKE_LENGTH = 50.0f;
+float CBoat::MIN_WAKE_INTERVAL = 2.0f;
+float CBoat::WAKE_LIFETIME = 150.0f;
+
+float fShapeLength = 0.4f;
+float fShapeTime = 0.05f;
+float fRangeMult = 0.6f;
+float fTimeMult = 1.2f/CBoat::WAKE_LIFETIME;
+
+CBoat *CBoat::apFrameWakeGeneratingBoats[4];
+
+const uint32 CBoat::nSaveStructSize =
+#ifdef COMPATIBLE_SAVES
+ 1216;
+#else
+ sizeof(CBoat);
+#endif
+
+CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner)
+{
+ CVehicleModelInfo *minfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi);
+ m_vehType = VEHICLE_TYPE_BOAT;
+ m_fAccelerate = 0.0f;
+ m_fBrake = 0.0f;
+ m_fSteeringLeftRight = 0.0f;
+ m_nPadID = 0;
+ m_fMovingRotation = 0.0f;
+ m_fMovingSpeed = 0.0f;
+ m_skimmerThingTimer = 0.0f;
+ m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds();
+ SetModelIndex(mi);
+
+ pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)minfo->m_handlingId);
+ pFlyingHandling = mod_HandlingManager.GetFlyingPointer((tVehicleType)minfo->m_handlingId);
+ pBoatHandling = mod_HandlingManager.GetBoatPointer((tVehicleType)minfo->m_handlingId);
+ minfo->ChooseVehicleColour(m_currentColour1, m_currentColour2);
+
+ m_fMass = pHandling->fMass;
+ m_fTurnMass = pHandling->fTurnMass / 2.0f;
+ m_vecCentreOfMass = pHandling->CentreOfMass;
+ m_fAirResistance = pHandling->Dimension.x * pHandling->Dimension.z / m_fMass;
+ m_fElasticity = 0.1f;
+ m_fBuoyancy = pHandling->fBuoyancy;
+ m_fSteerAngle = 0.0f;
+ m_fGasPedal = 0.0f;
+ m_fBrakePedal = 0.0f;
+
+ m_boat_unused3 = false;
+
+ m_fVolumeUnderWater = 7.0f;
+ m_fPrevVolumeUnderWater = 7.0f;
+ m_vecBuoyancePoint = CVector(0.0f, 0.0f, 0.0f);
+
+ m_nDeltaVolumeUnderWater = 0;
+ bBoatInWater = true;
+ bPropellerInWater = true;
+
+ bIsInWater = true;
+
+ m_phys_unused1 = 0.0f;
+ m_boat_unused2 = 0;
+ m_bIsAnchored = true;
+ m_fOrientation = INVALID_ORIENTATION;
+ bTouchingWater = true;
+ m_fDamage = 0.0f;
+ m_pSetOnFireEntity = nil;
+ m_nNumWakePoints = 0;
+
+ for (int16 i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++)
+ m_afWakePointLifeTime[i] = 0.0f;
+
+ m_nAmmoInClip = 20;
+
+ if(GetModelIndex() == MI_MARQUIS)
+ m_boom.Init(-PI/10.0f, PI/10.0f, 0, 2);
+ else
+ m_boom.Init(-PI/5.0f, PI/5.0f, 0, 2);
+}
+
+void
+CBoat::SetModelIndex(uint32 id)
+{
+ CVehicle::SetModelIndex(id);
+ SetupModelNodes();
+}
+
+void
+CBoat::GetComponentWorldPosition(int32 component, CVector &pos)
+{
+ pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component]));
+}
+
+void
+CBoat::ProcessControl(void)
+{
+ bool onLand = m_fDamageImpulse > 0.0f && m_vecDamageNormal.z > 0.1f;
+
+ PruneWakeTrail();
+
+ if(bRenderScorched)
+ m_fBuoyancy *= 0.99f;
+
+#ifdef FIX_BUGS
+ if(FindPlayerPed() && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && GetModelIndex() == MI_PREDATOR){
+#else
+ if(FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && GetModelIndex() == MI_PREDATOR){
+#endif
+ CVehicle *playerVeh = FindPlayerVehicle();
+ if(playerVeh && playerVeh->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT &&
+ (AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY ||
+ AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE ||
+ AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_FARAWAY ||
+ AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE ||
+ AutoPilot.m_nCarMission == MISSION_ATTACKPLAYER) &&
+ CTimer::GetTimeInMilliseconds() > m_nPoliceShoutTimer){
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_VCPA_PLAYER_FOUND, 0.0f);
+ m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds() + 4500 + (CGeneral::GetRandomNumber()&0xFFF);
+ }
+ }
+
+ int r, g, b;
+ RwRGBA dropColor = { 0, 0, 0, 0 };
+ RwRGBA splashColor, jetColor;
+ r = 127.5f*(CTimeCycle::GetAmbientRed_Obj() + 0.5f*CTimeCycle::GetDirectionalRed());
+ g = 127.5f*(CTimeCycle::GetAmbientGreen_Obj() + 0.5f*CTimeCycle::GetDirectionalGreen());
+ b = 127.5f*(CTimeCycle::GetAmbientBlue_Obj() + 0.5f*CTimeCycle::GetDirectionalBlue());
+ r = clamp(r, 0, 255);
+ g = clamp(g, 0, 255);
+ b = clamp(b, 0, 255);
+ splashColor.red = r;
+ splashColor.green = g;
+ splashColor.blue = b;
+ splashColor.alpha = CGeneral::GetRandomNumberInRange(160, 196);
+
+ r = 229.5f*(CTimeCycle::GetAmbientRed() + 0.85f*CTimeCycle::GetDirectionalRed());
+ g = 229.5f*(CTimeCycle::GetAmbientGreen() + 0.85f*CTimeCycle::GetDirectionalGreen());
+ b = 229.5f*(CTimeCycle::GetAmbientBlue() + 0.85f*CTimeCycle::GetDirectionalBlue());
+ r = clamp(r, 0, 255);
+ g = clamp(g, 0, 255);
+ b = clamp(b, 0, 255);
+ jetColor.red = r;
+ jetColor.green = g;
+ jetColor.blue = b;
+ jetColor.alpha = CGeneral::GetRandomNumberInRange(196, 228);
+
+ CGeneral::GetRandomNumber(); // unused
+
+ UpdateClumpAlpha();
+ ProcessCarAlarm();
+
+ switch(GetStatus()){
+ case STATUS_PLAYER:
+ m_bIsAnchored = false;
+ m_fOrientation = INVALID_ORIENTATION;
+ ProcessControlInputs(0);
+ if(GetModelIndex() == MI_PREDATOR)
+ DoFixedMachineGuns();
+
+ if (!CRecordDataForChase::IsRecording())
+ DoDriveByShootings();
+ break;
+ case STATUS_SIMPLE:
+ m_bIsAnchored = false;
+ m_fOrientation = INVALID_ORIENTATION;
+ CCarAI::UpdateCarAI(this);
+ CPhysical::ProcessControl();
+ bBoatInWater = true;
+ bPropellerInWater = true;
+ bIsInWater = true;
+ return;
+ case STATUS_PHYSICS:
+ m_bIsAnchored = false;
+ m_fOrientation = INVALID_ORIENTATION;
+ CCarAI::UpdateCarAI(this);
+ CCarCtrl::SteerAICarWithPhysics(this);
+ break;
+ case STATUS_ABANDONED:
+ case STATUS_WRECKED:
+ bBoatInWater = true;
+ bPropellerInWater = true;
+ bIsInWater = true;
+ m_fSteerAngle = 0.0;
+ bIsHandbrakeOn = false;
+ m_fBrakePedal = 0.5f;
+ m_fGasPedal = 0.0f;
+ if((GetPosition() - FindPlayerCentreOfWorld_NoSniperShift()).Magnitude() > 150.0f){
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ return;
+ }
+ break;
+ default: break;
+ }
+
+ float collisionDamage = pHandling->fCollisionDamageMultiplier * m_fDamageImpulse;
+#ifdef FIX_BUGS
+ if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f)
+ collisionDamage *= 0.5f;
+ if (collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && !bCollisionProof) {
+#else
+ if(collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED){
+#endif
+ float prevHealth = m_fHealth;
+ if(prevHealth >= 250.0f){
+#ifndef FIX_BUGS
+ // if collisionDamage < 50 we actually increase health here...
+ if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f)
+ collisionDamage *= 0.5f;
+#endif
+ if(this == FindPlayerVehicle()){
+ if(bTakeLessDamage)
+ m_fHealth -= (collisionDamage-25.0f)/6.0f;
+ else
+ m_fHealth -= (collisionDamage-25.0f)/2.0f;
+ }else{
+ if(collisionDamage > 60.0f && pDriver)
+ pDriver->Say(SOUND_PED_ANNOYED_DRIVER);
+ if(bTakeLessDamage)
+ m_fHealth -= (collisionDamage-25.0f)/12.0f;
+ else
+ m_fHealth -= (collisionDamage-25.0f)/4.0f;
+ }
+
+ if(m_fHealth <= 0.0f && prevHealth > 0.0f){
+ m_fHealth = 1.0f;
+ m_pSetOnFireEntity = m_pDamageEntity;
+ }
+ }
+ }
+
+ // Damage particles
+ if(m_fHealth <= 460.0f && GetStatus() != STATUS_WRECKED &&
+ Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f &&
+ Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){
+ float speedSq = m_vecMoveSpeed.MagnitudeSqr();
+ CVector smokeDir = 0.8f*m_vecMoveSpeed;
+ CVector smokePos;
+ switch(GetModelIndex()){
+ case MI_SPEEDER:
+ smokePos = CVector(0.4f, -2.4f, 0.8f);
+ smokeDir += 0.05f*GetRight();
+ smokeDir.z += 0.2f*m_vecMoveSpeed.z;
+ break;
+ case MI_REEFER:
+ smokePos = CVector(2.0f, -1.0f, 0.5f);
+ smokeDir += 0.07f*GetRight();
+ break;
+ case MI_PREDATOR:
+ default:
+ smokePos = CVector(-1.5f, -0.5f, 1.2f);
+ smokeDir += -0.08f*GetRight();
+ break;
+ }
+
+ smokePos = GetMatrix() * smokePos;
+
+ // On fire
+ if(m_fHealth < 250.0f){
+ CParticle::AddParticle(PARTICLE_CARFLAME, smokePos,
+ CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(2.25f/200.0f, 0.09f)),
+ nil, 0.9f);
+ CVector smokePos2 = smokePos;
+ smokePos2.x += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f);
+ smokePos2.y += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f);
+ smokePos2.z += CGeneral::GetRandomNumberInRange(2.25f/4.0f, 2.25f);
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, smokePos2, CVector(0.0f, 0.0f, 0.0f));
+
+ m_fDamage += CTimer::GetTimeStepInMilliseconds();
+ if(m_fDamage > 5000.0f)
+ BlowUpCar(m_pSetOnFireEntity);
+ }
+
+ if(speedSq < 0.25f && (CTimer::GetFrameCounter() + m_randomSeed) & 1)
+ CParticle::AddParticle(PARTICLE_ENGINE_STEAM, smokePos, smokeDir);
+ if(speedSq < 0.25f && m_fHealth <= 390.0f)
+ CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, smokePos, 1.25f*smokeDir);
+ }
+
+ bool bSeparateTurnForce = bHasHitWall;
+ CPhysical::ProcessControl();
+
+ CVector buoyanceImpulse(0.0f, 0.0f, 0.0f);
+ CVector buoyancePoint(0.0f, 0.0f, 0.0f);
+ if(mod_Buoyancy.ProcessBuoyancyBoat(this, pHandling->fBuoyancy, &buoyancePoint, &buoyanceImpulse, bSeparateTurnForce)){
+ // Process boat in water
+ if(0.1f * m_fMass * GRAVITY*CTimer::GetTimeStep() < buoyanceImpulse.z){
+ bBoatInWater = true;
+ bIsInWater = true;
+ if (GetUp().z < -0.6f && Abs(GetMoveSpeed().x) < 0.05 && Abs(GetMoveSpeed().y) < 0.05) {
+ bIsDrowning = true;
+ if (pDriver){
+ pDriver->bTouchingWater = true;
+ pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0);
+ }
+ }
+ else
+ bIsDrowning = false;
+ }else{
+ bBoatInWater = false;
+ bIsInWater = false;
+ bIsDrowning = false;
+ }
+
+ m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater;
+ m_vecBuoyancePoint = buoyancePoint;
+ if(GetModelIndex() == MI_SKIMMER && GetUp().z < -0.5f && Abs(m_vecMoveSpeed.x) < 0.2f && Abs(m_vecMoveSpeed.y) < 0.2f)
+ ApplyMoveForce(0.03f*buoyanceImpulse);
+ else
+ ApplyMoveForce(buoyanceImpulse);
+ if(bSeparateTurnForce)
+ ApplyTurnForce(0.4f*buoyanceImpulse, buoyancePoint);
+
+ // TODO: what is this?
+ if(GetModelIndex() == MI_SKIMMER)
+ if(m_skimmerThingTimer != 0.0f ||
+ GetForward().z < -0.5f && GetUp().z > -0.5f && m_vecMoveSpeed.z < -0.15f &&
+ buoyanceImpulse.z > 0.01f*m_fMass * GRAVITY*CTimer::GetTimeStep() &&
+ buoyanceImpulse.z < 0.4f*m_fMass * GRAVITY*CTimer::GetTimeStep()){
+ float turnImpulse = -0.00017f*GetForward().z*buoyanceImpulse.z * m_fMass*CTimer::GetTimeStep();
+ ApplyTurnForce(turnImpulse*GetForward(), GetUp());
+ bBoatInWater = false;
+ //BUG? aren't we forgetting the timestep here?
+ float moveImpulse = -0.5f*DotProduct(m_vecMoveSpeed, GetForward()) * m_fMass;
+ ApplyMoveForce(moveImpulse*GetForward());
+ if(m_skimmerThingTimer == 0.0f)
+ m_skimmerThingTimer = CTimer::GetTimeInMilliseconds() + 300.0f;
+ else if(m_skimmerThingTimer < CTimer::GetTimeInMilliseconds())
+ m_skimmerThingTimer = 0.0f;
+ }
+
+ if(!onLand && bBoatInWater && GetUp().z > 0.0f){
+ float impulse = m_vecMoveSpeed.MagnitudeSqr()*pBoatHandling->fAqPlaneForce*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f;
+ if(GetModelIndex() == MI_SKIMMER)
+ impulse *= 1.0f + m_fGasPedal;
+ else if(m_fGasPedal > 0.05f)
+ impulse *= m_fGasPedal;
+ else
+ impulse = 0.0f;
+ impulse = Min(impulse, GRAVITY*pBoatHandling->fAqPlaneLimit*m_fMass*CTimer::GetTimeStep());
+ ApplyMoveForce(impulse*GetUp());
+ ApplyTurnForce(impulse*GetUp(), buoyancePoint - pBoatHandling->fAqPlaneOffset*GetForward());
+ }
+
+ // Handle boat moving forward
+ float fwdSpeed = 1.0f;
+ if(Abs(m_fGasPedal) > 0.05f || (fwdSpeed = m_vecMoveSpeed.Magnitude2D()) > 0.01f){
+ if(bBoatInWater && fwdSpeed > 0.05f)
+ AddWakePoint(GetPosition());
+
+ float steerFactor = 1.0f;
+ if(GetStatus() == STATUS_PLAYER){
+ float steerLoss = DotProduct(m_vecMoveSpeed, GetForward())*pHandling->fTractionBias;
+ if(CPad::GetPad(0)->GetHandBrake())
+ steerLoss *= 0.5f;
+ steerFactor -= steerLoss;
+ steerFactor = clamp(steerFactor, 0.0f, 1.0f);
+ }
+
+ CVector boundMin = GetColModel()->boundingBox.min;
+ CVector propeller(0.0f, boundMin.y*pBoatHandling->fThrustY, boundMin.z*pBoatHandling->fThrustZ);
+ propeller = Multiply3x3(GetMatrix(), propeller);
+ CVector propellerWorld = GetPosition() + propeller;
+
+ float steerSin = Sin(-m_fSteerAngle * steerFactor);
+ float steerCos = Cos(-m_fSteerAngle * steerFactor);
+ float waterLevel;
+ CWaterLevel::GetWaterLevel(propellerWorld, &waterLevel, true);
+ if(propellerWorld.z-0.5f < waterLevel){
+ float propellerDepth = waterLevel - (propellerWorld.z - 0.5f);
+ if(propellerDepth > 1.0f)
+ propellerDepth = 1.0f;
+ else
+ propellerDepth = SQR(propellerDepth);
+ bPropellerInWater = true;
+
+ bool bSlowAhead = false;
+ if(Abs(m_fGasPedal) > 0.01f && GetModelIndex() != MI_SKIMMER){
+ if(Abs(m_fGasPedal) < 0.05f)
+ bSlowAhead = true;
+
+ CVector forceDir = Multiply3x3(GetMatrix(), CVector(-steerSin, steerCos, -Abs(m_fSteerAngle)));
+ CVector force = propellerDepth * m_fGasPedal * 40.0f * pHandling->Transmission.fEngineAcceleration * pHandling->fMass * forceDir;
+ if(force.z > 0.2f)
+ force.z = SQR(1.2f - force.z) + 0.2f;
+ if(onLand){
+ if(m_fGasPedal < 0.0f){
+ force.x *= 5.0f;
+ force.y *= 5.0f;
+ }
+ if(force.z < 0.0f)
+ force.z = 0.0f;
+ ApplyMoveForce(force * CTimer::GetTimeStep());
+ }else{
+ ApplyMoveForce(force * CTimer::GetTimeStep());
+ ApplyTurnForce(force * CTimer::GetTimeStep(), propeller - pBoatHandling->fThrustAppZ*GetUp());
+ float rightForce = -DotProduct(GetRight(), force)*pHandling->fTractionMultiplier;
+ ApplyTurnForce(rightForce*GetRight() * CTimer::GetTimeStep(), GetUp());
+ }
+
+ // Spray some particles
+ CVector jetDir = -0.04f * force;
+ if(m_fGasPedal > 0.0f){
+ if(GetStatus() == STATUS_PLAYER){
+ CVector sternPos = GetColModel()->boundingBox.min;
+ sternPos.x = 0.0f;
+ sternPos.z = 0.0f;
+ sternPos = Multiply3x3(GetMatrix(), sternPos);
+
+ CVector wakePos = GetPosition() + sternPos;
+ // no actual particles for player...
+ }else if(IsVisible() && ((CTimer::GetFrameCounter() + m_randomSeed) & 1) &&
+ CVisibilityPlugins::GetDistanceSquaredFromCamera(&propellerWorld) < SQR(70.0f * TheCamera.GenerationDistMultiplier)){
+ jetDir.z = 0.015f;
+ jetDir.x *= 3.5f;
+ jetDir.y *= 3.5f;
+ propellerWorld.z += 0.5f;
+
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 1.25f, jetColor,
+ CGeneral::GetRandomNumberInRange(0, 5),
+ CGeneral::GetRandomNumberInRange(0, 90), 1, 500);
+
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.75f * jetDir, nil, 0.5f, splashColor,
+ CGeneral::GetRandomNumberInRange(0, 30),
+ CGeneral::GetRandomNumberInRange(0, 45), 3, 500);
+ }
+ }
+ }else
+ bSlowAhead = true;
+
+ if(!onLand && bSlowAhead){
+ float force = pHandling->fTractionLoss*DotProduct(m_vecMoveSpeed, GetForward());
+ force = Min(force, 0.01f*m_fTurnMass);
+ if(m_fGasPedal > 0.01f){
+ if(GetStatus() == STATUS_PLAYER)
+ force *= (0.55f - Abs(m_fGasPedal)) * 1.3f;
+ else
+ force *= (0.55f - Abs(m_fGasPedal)) * 2.5f;
+ }
+ if(m_fGasPedal < 0.0f && force > 0.0f || m_fGasPedal > 0.0f && force < 0.0f)
+ force *= -1.0f;
+ CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f));
+ ApplyMoveForce(propellerForce * CTimer::GetTimeStep());
+ ApplyTurnForce(propellerForce * CTimer::GetTimeStep(), propeller);
+ float rightForce = -steerSin * force * 0.75f/steerFactor * Max(CTimer::GetTimeStep(), 0.01f);
+ ApplyTurnForce(GetRight() * rightForce, GetUp());
+ }
+ }else
+ bPropellerInWater = false;
+
+ if(pHandling->fSuspensionBias != 0.0f){
+ CVector right = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f));
+ float rightSpeed = DotProduct(m_vecMoveSpeed, right);
+ float impulse = 0.1f*pHandling->fSuspensionBias * m_fMass * m_fVolumeUnderWater * rightSpeed * CTimer::GetTimeStep();
+ ApplyMoveForce(right - impulse * 0.3f * CVector(-right.y, right.x, 0.0f));
+ }
+
+ if(GetStatus() == STATUS_PLAYER && CPad::GetPad(0)->GetHandBrake()){
+ float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward());
+ if(fwdSpeed > 0.0f){
+ float impulse = -0.1f*pHandling->fSuspensionLowerLimit * m_fMass * m_fVolumeUnderWater * fwdSpeed * CTimer::GetTimeStep();
+ ApplyMoveForce(impulse * GetForward());
+ }
+ }
+ }
+
+ // Slow down or push down boat as it approaches the world limits
+ m_vecMoveSpeed.x = Min(m_vecMoveSpeed.x, -(GetPosition().x - (WORLD_MAX_X-100.0f))*0.01f); // east
+ m_vecMoveSpeed.x = Max(m_vecMoveSpeed.x, -(GetPosition().x - (WORLD_MIN_X+100.0f))*0.01f); // west
+ m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MAX_Y-100.0f))*0.01f); // north
+ m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MIN_Y+100.0f))*0.01f); // south
+
+ if(!onLand && bBoatInWater && !bSeparateTurnForce)
+ ApplyWaterResistance();
+
+ if((GetModelIndex() != MI_SKIMMER || m_skimmerThingTimer == 0.0f) && !bSeparateTurnForce){
+ // No idea what exactly is going on here besides drag in YZ
+ float fx = Pow(pBoatHandling->vecTurnRes.x, CTimer::GetTimeStep());
+ float fy = Pow(pBoatHandling->vecTurnRes.y, CTimer::GetTimeStep());
+ float fz = Pow(pBoatHandling->vecTurnRes.z, CTimer::GetTimeStep());
+ m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space
+ // TODO: figure this out
+ float magic = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx;
+ m_vecTurnSpeed.y *= fy;
+ m_vecTurnSpeed.z *= fz;
+ float forceUp = (magic - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass;
+ m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world
+ CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
+ ApplyTurnForce(forceUp*GetUp(), com + GetForward());
+ }
+
+ m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000;
+
+ // Falling into water
+ if(!onLand && bBoatInWater && GetUp().z > 0.0f){
+ float splashVol = m_nDeltaVolumeUnderWater*pBoatHandling->fWaveAudioMult;
+ if(splashVol > 200.0f)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, splashVol);
+
+ if(m_nDeltaVolumeUnderWater > 200){
+ float speedUp = m_vecMoveSpeed.MagnitudeSqr() * m_nDeltaVolumeUnderWater * 0.001f;
+ if(speedUp + m_vecMoveSpeed.z > pHandling->fBrakeDeceleration)
+ speedUp = pHandling->fBrakeDeceleration - m_vecMoveSpeed.z;
+ if(speedUp < 0.0f) speedUp = 0.0f;
+ float speedFwd = DotProduct(m_vecMoveSpeed, GetForward());
+ speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fBrakeBias;
+ CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp);
+ CVector splashImpulse = speed * m_fMass;
+ ApplyMoveForce(splashImpulse);
+ ApplyTurnForce(splashImpulse, buoyancePoint);
+ }
+ }
+
+ // Splashes
+ float speed = m_vecMoveSpeed.Magnitude();
+ if(speed > 0.05f && GetUp().x > 0.0f && !TheCamera.GetLookingForwardFirstPerson() && IsVisible() &&
+ (AutoPilot.m_nCarMission != MISSION_CRUISE || (CTimer::GetFrameCounter()&2) == 0)){
+ CVector splashPos, splashDir;
+ float splashSize, front, waterLevel;
+
+ switch(GetModelIndex()){
+ case MI_RIO:
+ splashSize = speed;
+ front = 0.9f * GetColModel()->boundingBox.max.y;
+ splashDir = -0.5f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir += 0.35f*speed*GetRight();
+ splashPos = GetPosition() + 1.85f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ case MI_SQUALO:
+ splashSize = speed;
+ front = 0.75f * GetColModel()->boundingBox.max.y;
+ splashDir = -0.125f * m_vecMoveSpeed;
+ splashDir.z += 0.15f*speed;
+ splashDir += 0.25f*speed*GetRight();
+ splashPos = GetPosition() + m_vecBuoyancePoint + 0.5f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ case MI_REEFER:
+ splashSize = speed;
+ front = 0.75f * GetColModel()->boundingBox.max.y;
+ splashDir = -0.5f * m_vecMoveSpeed;
+ splashDir.z += 0.15f*speed;
+ splashDir += 0.5f*speed*GetRight();
+ splashPos = GetPosition() + m_vecBuoyancePoint + 1.3f*GetRight() + front*GetForward();
+ break;
+ case MI_COASTG:
+ splashSize = 0.25f*speed;
+ front = 0.8f * GetColModel()->boundingBox.max.y;
+ splashDir = 0.165f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir += 0.15f*speed*GetRight();
+ splashPos = GetPosition() + 0.65f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ case MI_DINGHY:
+ splashSize = 0.25f*speed;
+ front = 0.9f * GetColModel()->boundingBox.max.y;
+ splashDir = 0.35f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir += 0.25f*speed*GetRight();
+ splashPos = GetPosition() + 0.6f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ default:
+ splashSize = speed;
+ front = 0.9f * GetColModel()->boundingBox.max.y;
+ splashDir = -0.5f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir += 0.35f*speed*GetRight();
+ splashPos = GetPosition() + m_vecBuoyancePoint + 0.5f*GetRight() + front*GetForward();
+ break;
+ }
+ if(splashSize > 0.75f) splashSize = 0.75f;
+ if(AutoPilot.m_nCarMission == MISSION_CRUISE)
+ splashDir *= 1.5f;
+ static float lifeMult = 1000.0f;
+ static float lifeBase = 300.0f;
+ splashDir.z += 0.0003f*m_nDeltaVolumeUnderWater;
+ CWaterLevel::GetWaterLevel(splashPos, &waterLevel, true);
+ if(splashPos.z-waterLevel < 3.0f &&
+ CVisibilityPlugins::GetDistanceSquaredFromCamera(&splashPos) < SQR(70.0f * TheCamera.GenerationDistMultiplier)){
+ splashPos.z = waterLevel + 0.1f;
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashPos, 0.75f*splashDir, nil, splashSize+0.1f, splashColor,
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f), CGeneral::GetRandomNumberInRange(0.0f, 90.0f),
+ 1, lifeBase + splashDir.z*lifeMult);
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, splashPos, splashDir, nil, splashSize, jetColor,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f),
+ 0, lifeBase + splashDir.z*lifeMult);
+ }
+
+ switch(GetModelIndex()){
+ case MI_RIO:
+ splashDir = -0.5f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir -= 0.35f*speed*GetRight();
+ splashPos = GetPosition() - 1.85f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ case MI_SQUALO:
+ splashDir = -0.125f * m_vecMoveSpeed;
+ splashDir.z += 0.15f*speed;
+ splashDir -= 0.25f*speed*GetRight();
+ splashPos = GetPosition() + m_vecBuoyancePoint - 0.5f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ case MI_REEFER:
+ splashDir = -0.5f * m_vecMoveSpeed;
+ splashDir.z += 0.15f*speed;
+ splashDir -= 0.5f*speed*GetRight();
+ splashPos = GetPosition() + m_vecBuoyancePoint - 1.3f*GetRight() + front*GetForward();
+ break;
+ case MI_COASTG:
+ splashDir = 0.165f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir -= 0.15f*speed*GetRight();
+ splashPos = GetPosition() - 0.65f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ case MI_DINGHY:
+ splashDir = 0.35f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir -= 0.25f*speed*GetRight();
+ splashPos = GetPosition() - 0.6f*GetRight() + front*GetForward();
+ splashPos.z += 0.5f;
+ break;
+ default:
+ splashDir = -0.5f * m_vecMoveSpeed;
+ splashDir.z += 0.25f*speed;
+ splashDir -= 0.35f*speed*GetRight();
+ splashPos = GetPosition() + m_vecBuoyancePoint - 0.5f*GetRight() + front*GetForward();
+ break;
+ }
+ if(AutoPilot.m_nCarMission == MISSION_CRUISE)
+ splashDir *= 1.5f;
+ splashDir.z += 0.0003f*m_nDeltaVolumeUnderWater;
+ CWaterLevel::GetWaterLevel(splashPos, &waterLevel, true);
+ if(splashPos.z-waterLevel < 3.0f &&
+ CVisibilityPlugins::GetDistanceSquaredFromCamera(&splashPos) < SQR(70.0f * TheCamera.GenerationDistMultiplier)){
+ splashPos.z = waterLevel + 0.1f;
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashPos, 0.75f*splashDir, nil, splashSize+0.1f, splashColor,
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f), CGeneral::GetRandomNumberInRange(0.0f, 90.0f),
+ 1, lifeBase + splashDir.z*lifeMult);
+ CParticle::AddParticle(PARTICLE_BOAT_SPLASH, splashPos, splashDir, nil, splashSize, jetColor,
+ CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f),
+ 0, lifeBase + splashDir.z*lifeMult);
+ }
+ }
+
+ // Spray waterdrops on screen
+ if(TheCamera.GetLookingForwardFirstPerson() && FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() &&
+ m_nDeltaVolumeUnderWater > 0 && numWaterDropOnScreen < 20){
+ CVector dropPos;
+ CVector dropDir(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), CGeneral::GetRandomNumberInRange(1.0f, 0.75f), 0.0f);
+
+ int frm = CGeneral::GetRandomNumber() & 1;
+ if(TheCamera.m_CameraAverageSpeed < 0.35f){
+ dropPos.x = CGeneral::GetRandomNumberInRange(50, (int)SCREEN_WIDTH-50);
+ dropPos.y = CGeneral::GetRandomNumberInRange(50, (int)SCREEN_HEIGHT-50);
+ }else{
+ dropPos.x = CGeneral::GetRandomNumberInRange(200, (int)SCREEN_WIDTH-200);
+ dropPos.y = CGeneral::GetRandomNumberInRange(150, (int)SCREEN_HEIGHT-150);
+ }
+ dropPos.z = 1.0f;
+
+ if(TheCamera.m_CameraAverageSpeed > 0.35f){
+ if((int)SCREEN_WIDTH / 2 < dropPos.x)
+ dropPos.x += CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f;
+ else
+ dropPos.x -= CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f;
+
+ if((int)SCREEN_HEIGHT / 2 < dropPos.y)
+ dropPos.y += CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f;
+ else
+ dropPos.y -= CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f;
+ }
+
+ if(CParticle::AddParticle(PARTICLE_WATERDROP, dropPos, dropDir, nil,
+ CGeneral::GetRandomNumberInRange(0.1f, 0.15f), dropColor, 0, 0, frm))
+ numWaterDropOnScreen++;
+ }
+
+ if(m_fPrevVolumeUnderWater == 0.0f && m_fVolumeUnderWater > 0.0f && GetModelIndex() == MI_SKIMMER){
+ CVector splashDir(0.0f, 0.0f, 0.25f*speed);
+ CVector splashPos = GetPosition();
+ float level;
+ CWaterLevel::GetWaterLevel(splashPos, &level, true);
+ splashPos.z = level;
+ CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, splashPos, splashDir, 0.0f, 65, splashColor, true);
+ }
+
+ m_fPrevVolumeUnderWater = m_fVolumeUnderWater;
+ }else{
+ bBoatInWater = false;
+ bIsInWater = false;
+#ifdef FIX_BUGS
+ bIsDrowning = false;
+#endif
+ }
+
+ if(m_modelIndex == MI_SKIMMER && CTimer::GetTimeStep() > 0.0f){
+ if(GetStatus() == STATUS_PLAYER){
+ if(m_fMovingSpeed < 0.22f)
+ m_fMovingSpeed += 0.001f*CTimer::GetTimeStep();
+ FlyingControl(FLIGHT_MODEL_SEAPLANE);
+ }else{
+ if(m_fMovingSpeed > 0.0005f*CTimer::GetTimeStep())
+ m_fMovingSpeed -= 0.0005f*CTimer::GetTimeStep();
+ else
+ m_fMovingSpeed = 0.0f;
+ }
+ }else if(bCheat8)
+ FlyingControl(FLIGHT_MODEL_PLANE);
+
+ if(m_bIsAnchored){
+ m_vecMoveSpeed.x = 0.0f;
+ m_vecMoveSpeed.y = 0.0f;
+
+ if(m_fOrientation == INVALID_ORIENTATION){
+ m_fOrientation = GetForward().Heading();
+ }else{
+ // is this some inlined CPlaceable method?
+ CVector pos = GetPosition();
+ GetMatrix().RotateZ(m_fOrientation - GetForward().Heading());
+ GetMatrix().SetTranslateOnly(pos);
+ }
+ }
+
+ ProcessDelayedExplosion();
+}
+
+void
+CBoat::ProcessControlInputs(uint8 pad)
+{
+ m_nPadID = pad;
+ if(m_nPadID > 3)
+ m_nPadID = 3;
+
+ m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f;
+ m_fBrake = clamp(m_fBrake, 0.0f, 1.0f);
+
+ if(m_fBrake < 0.05f){
+ m_fBrake = 0.0f;
+ m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f;
+ m_fAccelerate = clamp(m_fAccelerate, 0.0f, 1.0f);
+ }else
+ m_fAccelerate = -m_fBrake*0.3f;
+
+ m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f;
+ m_fSteeringLeftRight = clamp(m_fSteeringLeftRight, -1.0f, 1.0f);
+
+ float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight);
+ m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq);
+ m_fGasPedal = m_fAccelerate;
+}
+
+float fSeaPlaneWaterResistance = 30.0f;
+
+void
+CBoat::ApplyWaterResistance(void)
+{
+ // TODO: figure out how this works
+ float resistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass;
+ if(GetModelIndex() == MI_SKIMMER)
+ resistance *= fSeaPlaneWaterResistance;
+ float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward());
+ float magic = (SQR(fwdSpeed) + 0.05f) * resistance + 1.0f;
+ magic = Abs(magic);
+ float fx = Pow(pBoatHandling->vecMoveRes.x/magic, 0.5f*CTimer::GetTimeStep());
+ float fy = Pow(pBoatHandling->vecMoveRes.y/magic, 0.5f*CTimer::GetTimeStep());
+ float fz = Pow(pBoatHandling->vecMoveRes.z/magic, 0.5f*CTimer::GetTimeStep());
+
+ m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix()); // invert - to local space
+ m_vecMoveSpeed.x *= fx;
+ m_vecMoveSpeed.y *= fy;
+ m_vecMoveSpeed.z *= fz;
+ float force = (fy - 1.0f) * m_vecMoveSpeed.y * m_fMass;
+ m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed); // back to world
+
+ ApplyTurnForce(force*GetForward(), -GetUp());
+
+ if(m_vecMoveSpeed.z > 0.0f)
+ m_vecMoveSpeed.z *= fz;
+ else
+ m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz;
+}
+
+RwObject*
+GetBoatAtomicObjectCB(RwObject *object, void *data)
+{
+ RpAtomic *atomic = (RpAtomic*)object;
+ assert(RwObjectGetType(object) == rpATOMIC);
+ if(RpAtomicGetFlags(atomic) & rpATOMICRENDER)
+ *(RpAtomic**)data = atomic;
+ return object;
+
+
+}
+
+void
+CBoat::BlowUpCar(CEntity *culprit)
+{
+ RpAtomic *atomic;
+ RwFrame *frame;
+ RwMatrix *matrix;
+ CObject *obj;
+
+ if(!bCanBeDamaged)
+ return;
+
+ // explosion pushes vehicle up
+ m_vecMoveSpeed.z += 0.13f;
+ SetStatus(STATUS_WRECKED);
+ bRenderScorched = true;
+
+ m_fHealth = 0.0;
+ m_nBombTimer = 0;
+ TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z);
+
+ KillPedsInVehicle();
+
+ bEngineOn = false;
+ bLightsOn = false;
+ ChangeLawEnforcerState(false);
+
+ CExplosion::AddExplosion(this, culprit, EXPLOSION_BOAT, GetPosition(), 0);
+ CDarkel::RegisterCarBlownUpByPlayer(this);
+ if(m_aBoatNodes[BOAT_MOVING] == nil)
+ return;
+
+ // much like CAutomobile::SpawnFlyingComponent from here on
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
+ if(atomic == nil)
+ return;
+
+ obj = new CObject();
+ if(obj == nil)
+ return;
+
+ obj->SetModelIndexNoCreate(MI_CAR_WHEEL);
+
+ // object needs base model
+ obj->RefModelInfo(GetModelIndex());
+
+ // create new atomic
+ matrix = RwFrameGetLTM(m_aBoatNodes[BOAT_MOVING]);
+ frame = RwFrameCreate();
+ atomic = RpAtomicClone(atomic);
+ *RwFrameGetMatrix(frame) = *matrix;
+ RpAtomicSetFrame(atomic, frame);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ obj->AttachToRwObject((RwObject*)atomic);
+ obj->bDontStream = true;
+
+ // init object
+ obj->m_fMass = 10.0f;
+ obj->m_fTurnMass = 25.0f;
+ obj->m_fAirResistance = 0.99f;
+ obj->m_fElasticity = 0.1f;
+ obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f;
+ obj->ObjectCreatedBy = TEMP_OBJECT;
+ obj->SetIsStatic(false);
+ obj->bIsPickup = false;
+
+ // life time
+ CObject::nNoTempObjects++;
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000;
+
+ obj->m_vecMoveSpeed = m_vecMoveSpeed;
+ if(GetUp().z > 0.0f)
+ obj->m_vecMoveSpeed.z = 0.3f;
+ else
+ obj->m_vecMoveSpeed.z = 0.0f;
+
+ obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f;
+ obj->m_vecTurnSpeed.x = 0.5f;
+
+ // push component away from boat
+ CVector dist = obj->GetPosition() - GetPosition();
+ dist.Normalise();
+ if(GetUp().z > 0.0f)
+ dist += GetUp();
+ obj->GetMatrix().GetPosition() += dist;
+
+ CWorld::Add(obj);
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
+ if(atomic)
+ RpAtomicSetFlags(atomic, 0);
+}
+
+void
+CBoat::PreRender(void)
+{
+ CMatrix matrix;
+ CVector pos;
+ RpAtomic *atomic;
+
+ if(GetModelIndex() == MI_SKIMMER){
+ m_fMovingRotation += m_fMovingSpeed*CTimer::GetTimeStep();
+ if(m_fMovingRotation > TWOPI) m_fMovingRotation -= TWOPI;
+ int alpha = (1.0f - Min(2.0f*m_fMovingSpeed*8.0f/PI, 1.0f))*255.0f;
+ if(GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PLAYER_REMOTE || GetStatus() == STATUS_PLAYER_PLAYBACKFROMBUFFER){
+ if(m_aBoatNodes[BOAT_RUDDER]){
+ float sine = Sin(m_fSteerAngle);
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_RUDDER]));
+ pos = matrix.GetPosition();
+ matrix.SetRotate(0.0f, 0.0f, -m_fSteerAngle);
+ matrix.Rotate(0.0f, DEGTORAD(22.0f)*sine, 0.0f);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ if(m_aBoatNodes[BOAT_FLAP_LEFT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_LEFT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateX(-m_fSteerAngle);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ if(m_aBoatNodes[BOAT_FLAP_RIGHT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_RIGHT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateX(m_fSteerAngle);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ // FIX: Planes can also be controlled with GetCarGunUpDown
+#ifdef FIX_BUGS
+ static float steeringUpDown = 0.0f;
+#ifdef FREE_CAM
+ if(!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController))
+#endif
+ steeringUpDown += ((Abs(CPad::GetPad(0)->GetCarGunUpDown()) > 1.0f ? (-CPad::GetPad(0)->GetCarGunUpDown()/128.0f) : (-CPad::GetPad(0)->GetSteeringUpDown()/128.0f)) - steeringUpDown) * Min(1.f, CTimer::GetTimeStep()/5.f);
+#ifdef FREE_CAM
+ else
+ steeringUpDown = -CPad::GetPad(0)->GetSteeringUpDown()/128.0f;
+#endif
+#else
+ float steeringUpDown = -CPad::GetPad(0)->GetSteeringUpDown()/128.0f;
+#endif
+ if(m_aBoatNodes[BOAT_REARFLAP_LEFT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_LEFT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateX(steeringUpDown);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ if(m_aBoatNodes[BOAT_REARFLAP_RIGHT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_RIGHT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateX(steeringUpDown);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ }
+ if(m_aBoatNodes[BOAT_MOVING]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateY(m_fMovingRotation);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
+ if(atomic)
+ SetComponentAtomicAlpha(atomic, alpha);
+ }
+ if(m_aBoatNodes[BOAT_WINDSCREEN]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_WINDSCREEN]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateY(-m_fMovingRotation);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aBoatNodes[BOAT_WINDSCREEN], GetBoatAtomicObjectCB, &atomic);
+ if(atomic)
+ SetComponentAtomicAlpha(atomic, Max(150-alpha, 0));
+ }
+ CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_SEAPLANE);
+ }else if(GetModelIndex() == MI_COASTG || GetModelIndex() == MI_DINGHY || GetModelIndex() == MI_RIO ||
+ GetModelIndex() == MI_SQUALO || GetModelIndex() == MI_MARQUIS){
+ if(m_aBoatNodes[BOAT_RUDDER]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_RUDDER]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateZ(-m_fSteerAngle);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ if(m_aBoatNodes[BOAT_REARFLAP_LEFT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_LEFT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateZ(-m_fSteerAngle);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ if(m_aBoatNodes[BOAT_REARFLAP_RIGHT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_RIGHT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateZ(-m_fSteerAngle);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ }
+
+ if(GetModelIndex() == MI_RIO || GetModelIndex() == MI_MARQUIS){
+ float axes[3] = { 0.0f, 0.0f, 0.0f };
+ m_boom.Process(this);
+ axes[m_boom.m_nAxis] = m_boom.m_fAngle;
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_LEFT]));
+ pos = matrix.GetPosition();
+ matrix.SetRotate(axes[0], axes[1], axes[2]);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+
+ if(GetModelIndex() == MI_RIO){
+ // That little wind propeller
+ if(m_aBoatNodes[BOAT_FLAP_RIGHT]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_RIGHT]));
+ pos = matrix.GetPosition();
+
+ float flapHeading = matrix.GetForward().Heading();
+ float boatHeading = GetForward().Heading();
+ float rot = -DEGTORAD(45.0f) - (flapHeading + boatHeading);
+ // eh what?
+ rot = CGeneral::LimitRadianAngle(rot);
+ if(rot > HALFPI) rot = PI;
+ else if(rot < -HALFPI) rot = -PI;
+ rot = clamp(rot, -DEGTORAD(63.0f), DEGTORAD(63.0f));
+ m_fMovingSpeed += (0.008f * CWeather::Wind + 0.002f) * rot;
+ m_fMovingSpeed *= Pow(0.9985f, CTimer::GetTimeStep())/(500.0f*SQR(m_fMovingSpeed) + 1.0f);
+
+ matrix.SetRotateZ(flapHeading + m_fMovingSpeed*CTimer::GetTimeStep());
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+ }
+ if(m_aBoatNodes[BOAT_MOVING]){
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING]));
+ pos = matrix.GetPosition();
+ matrix.SetRotateY(m_fMovingRotation);
+ matrix.Translate(pos);
+ matrix.UpdateRW();
+
+ CVector wind = CVector(0.707f, 0.707f, 0.0f) * (CWeather::Wind + 0.15f)*0.4f;
+ m_fMovingRotation += (m_vecMoveSpeed + wind).Magnitude()*CTimer::GetTimeStep();
+ }
+ }else if(GetModelIndex() == MI_PREDATOR || GetModelIndex() == MI_REEFER){
+ if (m_aBoatNodes[BOAT_MOVING] != nil) {
+ matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING]));
+
+ CVector pos = matrix.GetPosition();
+ matrix.SetRotateZ(m_fMovingRotation);
+ matrix.Translate(pos);
+
+ matrix.UpdateRW();
+ if (CVehicle::bWheelsOnlyCheat) {
+ RpAtomicRender((RpAtomic*)GetFirstObject(m_aBoatNodes[BOAT_MOVING]));
+ }
+ }
+ m_fMovingRotation += 0.02f * CTimer::GetTimeStep();
+ }
+}
+
+RwIm3DVertex KeepWaterOutVertices[4];
+RwImVertexIndex KeepWaterOutIndices[6];
+
+void
+CBoat::Render()
+{
+ ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->SetVehicleColour(m_currentColour1, m_currentColour2);
+ m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 3000;
+ if (!CVehicle::bWheelsOnlyCheat)
+ CEntity::Render();
+#ifdef NEW_RENDERER
+ if(!gbNewRenderer)
+#endif
+ RenderWaterOutPolys(); // not separate function in VC
+}
+
+void
+CBoat::RenderWaterOutPolys(void)
+{
+ if(GetModelIndex() == MI_SKIMMER)
+ return;
+ KeepWaterOutIndices[0] = 0;
+ KeepWaterOutIndices[1] = 2;
+ KeepWaterOutIndices[2] = 1;
+ KeepWaterOutIndices[3] = 1;
+ KeepWaterOutIndices[4] = 2;
+ KeepWaterOutIndices[5] = 3;
+ RwIm3DVertexSetRGBA(&KeepWaterOutVertices[0], 255, 255, 255, 255);
+ RwIm3DVertexSetRGBA(&KeepWaterOutVertices[1], 255, 255, 255, 255);
+ RwIm3DVertexSetRGBA(&KeepWaterOutVertices[2], 255, 255, 255, 255);
+ RwIm3DVertexSetRGBA(&KeepWaterOutVertices[3], 255, 255, 255, 255);
+ switch (GetModelIndex()) {
+ case MI_RIO:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.3f, -1.016f, 0.51f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.3f, -1.016f, 0.51f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.3f, -2.832f, 0.51f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.3f, -2.832f, 0.51f);
+ break;
+ case MI_SQUALO:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.222f, 2.004f, 0.846f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.222f, 2.004f, 0.846f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.24f, -1.367f, 0.846f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.24f, -1.367f, 0.846f);
+ break;
+ case MI_SPEEDER:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.15f, 3.61f, 1.03f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.15f, 3.61f, 1.03f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.15f, 0.06f, 1.03f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.15f, 0.06f, 1.03f);
+ break;
+ case MI_REEFER:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.9f, 2.83f, 1.0f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.9f, 2.83f, 1.0f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.66f, -4.48f, 0.83f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.66f, -4.48f, 0.83f);
+ break;
+ case MI_PREDATOR:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.45f, 1.9f, 0.96f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.45f, 1.9f, 0.96f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.45f, -3.75f, 0.96f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.45f, -3.75f, 0.96f);
+ break;
+ case MI_TROPIC:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.886f, -2.347f, 0.787f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.886f, -2.347f, 0.787f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.886f, -4.67f, 0.842f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.886f, -4.67f, 0.842f);
+ break;
+ case MI_COASTG:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -0.663f, 3.565f, 0.382f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 0.663f, 3.565f, 0.382f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.087f, 0.83f, 0.381f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.087f, 0.83f, 0.381f);
+ break;
+ case MI_DINGHY:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -0.797f, 1.641f, 0.573f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 0.797f, 1.641f, 0.573f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -0.865f, -1.444f, 0.509f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 0.865f, -1.444f, 0.509f);
+ break;
+ case MI_MARQUIS:
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.246f, -1.373f, 0.787f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.246f, -1.373f, 0.787f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.023f, -5.322f, 0.787f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.023f, -5.322f, 0.787f);
+ break;
+ default:
+ return;
+ }
+ KeepWaterOutVertices[0].u = 0.0f;
+ KeepWaterOutVertices[0].v = 0.0f;
+ KeepWaterOutVertices[1].u = 1.0f;
+ KeepWaterOutVertices[1].v = 0.0f;
+ KeepWaterOutVertices[2].u = 0.0f;
+ KeepWaterOutVertices[2].v = 1.0f;
+ KeepWaterOutVertices[3].u = 1.0f;
+ KeepWaterOutVertices[3].v = 1.0f;
+#ifdef NEW_RENDERER
+ if(!gbNewRenderer)
+#endif
+{
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpWaterRaster);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
+}
+ if (!CVehicle::bWheelsOnlyCheat && RwIm3DTransform(KeepWaterOutVertices, 4, GetMatrix().m_attachment, rwIM3D_VERTEXUV)) {
+ RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, KeepWaterOutIndices, 6);
+ RwIm3DEnd();
+ }
+ bool drawAnotherRect = false;
+ if(GetModelIndex() == MI_COASTG){
+ drawAnotherRect = true;
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.087f, 0.831f, 0.381f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.087f, 0.831f, 0.381f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.097f, -2.977f, 0.381f);
+ RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.097f, -2.977f, 0.381f);
+ }
+ if(drawAnotherRect){
+ KeepWaterOutVertices[0].u = 0.0f;
+ KeepWaterOutVertices[0].v = 0.0f;
+ KeepWaterOutVertices[1].u = 1.0f;
+ KeepWaterOutVertices[1].v = 0.0f;
+ KeepWaterOutVertices[2].u = 0.0f;
+ KeepWaterOutVertices[2].v = 1.0f;
+ KeepWaterOutVertices[3].u = 1.0f;
+ KeepWaterOutVertices[3].v = 1.0f;
+ if (!CVehicle::bWheelsOnlyCheat && RwIm3DTransform(KeepWaterOutVertices, 4, GetMatrix().m_attachment, rwIM3D_VERTEXUV)) {
+ RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, KeepWaterOutIndices, 6);
+ RwIm3DEnd();
+ }
+ }
+#ifdef NEW_RENDERER
+ if(!gbNewRenderer)
+#endif
+{
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+}
+}
+
+void
+CBoat::Teleport(CVector v)
+{
+ CWorld::Remove(this);
+ SetPosition(v);
+ SetOrientation(0.0f, 0.0f, 0.0f);
+ SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ SetTurnSpeed(0.0f, 0.0f, 0.0f);
+ CWorld::Add(this);
+}
+
+// unused
+bool
+CBoat::IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats)
+{
+ uint8 numVerts = 0;
+
+ if ( apFrameWakeGeneratingBoats[0] == NULL )
+ return false;
+
+ for ( int32 i = 0; i < 4; i++ )
+ {
+ CBoat *pBoat = apFrameWakeGeneratingBoats[i];
+ if ( !pBoat )
+ break;
+
+ for ( int j = 0; j < pBoat->m_nNumWakePoints; j++ )
+ {
+ float fDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[j]) * fShapeTime + float(j) * fShapeLength + fSize;
+
+ if ( Abs(pBoat->m_avec2dWakePoints[j].x - sector.x) < fDist
+ && Abs(pBoat->m_avec2dWakePoints[i].y - sector.y) < fDist )
+ {
+ apBoats[numVerts] = pBoat;
+ numVerts = 1; // += ?
+ break;
+ }
+ }
+ }
+
+ return numVerts != 0;
+}
+
+// unused
+float
+CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat)
+{
+ for ( int i = 0; i < pBoat->m_nNumWakePoints; i++ )
+ {
+ float fMaxDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fShapeTime + float(i) * fShapeLength;
+
+ CVector2D vecDist = pBoat->m_avec2dWakePoints[i] - CVector2D(vecVertex);
+
+ float fDist = vecDist.MagnitudeSqr();
+
+ if ( fDist < SQR(fMaxDist) )
+ return 1.0f - Min(fRangeMult * Sqrt(fDist / SQR(fMaxDist)) + (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fTimeMult, 1.0f);
+ }
+
+ return 0.0f;
+}
+
+void
+CBoat::SetupModelNodes()
+{
+ int i;
+ for(i = 0; i < ARRAY_SIZE(m_aBoatNodes); i++)
+ m_aBoatNodes[i] = nil;
+ CClumpModelInfo::FillFrameArray(GetClump(), m_aBoatNodes);
+}
+
+void
+CBoat::FillBoatList()
+{
+ int16 frameId = 0;
+
+ apFrameWakeGeneratingBoats[0] = nil;
+ apFrameWakeGeneratingBoats[1] = nil;
+ apFrameWakeGeneratingBoats[2] = nil;
+ apFrameWakeGeneratingBoats[3] = nil;
+ CVector2D camPos = TheCamera.GetPosition();
+ CVector2D camFwd = TheCamera.GetForward();
+ float camDist = camFwd.Magnitude();
+ if(camDist > 0.0f)
+ camFwd /= camDist;
+ for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) {
+ CBoat *boat = (CBoat *)(CPools::GetVehiclePool()->GetSlot(i));
+ if (boat && boat->m_vehType == VEHICLE_TYPE_BOAT) {
+ if (boat->m_nNumWakePoints != 0) {
+ CVector2D camToBoat = CVector2D(boat->GetPosition()) - camPos;
+ float distToCam = DotProduct2D(camFwd, camToBoat);
+ if(distToCam > 100.0f || distToCam < -15.0f)
+ continue;
+ float distSq = camToBoat.MagnitudeSqr();
+ if(distSq > SQR(70.0f))
+ continue;
+ if (frameId >= ARRAY_SIZE(apFrameWakeGeneratingBoats)) {
+ float nearest = 999999.88f;
+ int16 frameId2 = -1;
+ for (int16 j = 0; j < ARRAY_SIZE(apFrameWakeGeneratingBoats); j++) {
+ float tmpDistSq = (CVector2D(apFrameWakeGeneratingBoats[j]->GetPosition()) - camPos).MagnitudeSqr();
+ if (tmpDistSq < nearest) {
+ nearest = tmpDistSq;
+ frameId2 = j;
+ }
+ }
+
+ if (frameId2 != -1 &&
+ (distSq < nearest || boat->GetStatus() == STATUS_PLAYER))
+ apFrameWakeGeneratingBoats[frameId2] = boat;
+ } else {
+ apFrameWakeGeneratingBoats[frameId++] = boat;
+ }
+ }
+ }
+ }
+}
+
+void
+CBoat::PruneWakeTrail(void)
+{
+ int i;
+
+ for(i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++){
+ if(m_afWakePointLifeTime[i] <= 0.0f)
+ break;
+ if(m_afWakePointLifeTime[i] <= CTimer::GetTimeStep()){
+ m_afWakePointLifeTime[i] = 0.0f;
+ break;
+ }
+ m_afWakePointLifeTime[i] -= CTimer::GetTimeStep();
+ }
+ m_nNumWakePoints = i;
+}
+
+void
+CBoat::AddWakePoint(CVector point)
+{
+ int i;
+ if(m_afWakePointLifeTime[0] > 0.0f){
+ if((CVector2D(GetPosition()) - m_avec2dWakePoints[0]).MagnitudeSqr() < SQR(2.0f)) {
+ if(GetStatus() == STATUS_PLAYER){
+ if(m_nNumWakePoints >= 31)
+ m_nNumWakePoints = 31;
+ }else if(VehicleCreatedBy == MISSION_VEHICLE){
+ if(m_nNumWakePoints >= 20)
+ m_nNumWakePoints = 20;
+ }else{
+ if(m_nNumWakePoints >= 15)
+ m_nNumWakePoints = 15;
+ }
+ for(i = m_nNumWakePoints; i != 0; i--){
+ m_avec2dWakePoints[i] = m_avec2dWakePoints[i-1];
+ m_afWakePointLifeTime[i] = m_afWakePointLifeTime[i-1];
+ }
+ m_avec2dWakePoints[0] = point;
+ m_afWakePointLifeTime[0] = 150.0f;
+ if(m_nNumWakePoints < ARRAY_SIZE(m_afWakePointLifeTime))
+ m_nNumWakePoints++;
+ }
+ }else{
+ m_avec2dWakePoints[0] = point;
+ m_afWakePointLifeTime[0] = 150.0f;
+ m_nNumWakePoints = 1;
+ }
+}
+
+void
+CBoat::DoDriveByShootings(void)
+{
+ CAnimBlendAssociation *anim = nil;
+ CPlayerInfo* playerInfo = ((CPlayerPed*)pDriver)->GetPlayerInfoForThisPlayerPed();
+ if (playerInfo && !playerInfo->m_bDriveByAllowed)
+ return;
+
+ CWeapon *weapon = pDriver->GetWeapon();
+ if(CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->m_nWeaponSlot != 5)
+ return;
+
+ weapon->Update(pDriver->m_audioEntityId, nil);
+
+ bool lookingLeft = false;
+ bool lookingRight = false;
+ if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
+ TheCamera.m_bObbeCinematicCarCamOn){
+ if(CPad::GetPad(0)->GetLookLeft())
+ lookingLeft = true;
+ if(CPad::GetPad(0)->GetLookRight())
+ lookingRight = true;
+ }else{
+ if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft)
+ lookingLeft = true;
+ if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight)
+ lookingRight = true;
+ }
+
+ if(lookingLeft || lookingRight){
+ if(lookingLeft){
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_LEFT);
+ }else if(pDriver->m_pMyVehicle->pPassengers[0] == nil || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT);
+ if(anim == nil || anim->blendDelta < 0.0f)
+ anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_RIGHT);
+ }
+
+ if (!anim || !anim->IsRunning()) {
+ if (CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer) {
+ weapon->FireFromCar(this, lookingLeft, true);
+ weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70;
+ }
+ }
+ }else{
+ weapon->Reload();
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT);
+ if(anim)
+ anim->blendDelta = -1000.0f;
+ }
+}
+
+#ifdef COMPATIBLE_SAVES
+void
+CBoat::Save(uint8*& buf)
+{
+ CVehicle::Save(buf);
+ SkipSaveBuf(buf, 1216 - 672);
+}
+
+void
+CBoat::Load(uint8*& buf)
+{
+ CVehicle::Load(buf);
+ SkipSaveBuf(buf, 1216 - 672);
+}
+#endif
diff --git a/src/vehicles/Boat.h b/src/vehicles/Boat.h
new file mode 100644
index 0000000..5d866c4
--- /dev/null
+++ b/src/vehicles/Boat.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "Vehicle.h"
+#include "Door.h"
+
+enum eBoatNodes
+{
+ BOAT_MOVING = 1,
+ BOAT_WINDSCREEN,
+ BOAT_RUDDER,
+ BOAT_FLAP_LEFT,
+ BOAT_FLAP_RIGHT,
+ BOAT_REARFLAP_LEFT,
+ BOAT_REARFLAP_RIGHT,
+ NUM_BOAT_NODES
+};
+
+class CBoat : public CVehicle
+{
+public:
+ float m_fMovingRotation;
+ float m_fMovingSpeed;
+ int32 m_boat_unused1;
+ RwFrame *m_aBoatNodes[NUM_BOAT_NODES];
+ CDoor m_boom;
+ tBoatHandlingData *pBoatHandling;
+ uint8 bBoatInWater : 1;
+ uint8 bPropellerInWater : 1;
+ bool m_bIsAnchored;
+ float m_fOrientation;
+ uint32 m_nPoliceShoutTimer;
+ int32 m_boat_unused2;
+ float m_fDamage;
+ CEntity *m_pSetOnFireEntity;
+ float m_skimmerThingTimer;
+ bool m_boat_unused3;
+ float m_fAccelerate;
+ float m_fBrake;
+ float m_fSteeringLeftRight;
+ uint8 m_nPadID;
+ int32 m_boat_unused4;
+ float m_fVolumeUnderWater;
+ CVector m_vecBuoyancePoint;
+ float m_fPrevVolumeUnderWater;
+ int16 m_nDeltaVolumeUnderWater;
+ uint16 m_nNumWakePoints;
+ CVector2D m_avec2dWakePoints[32];
+ float m_afWakePointLifeTime[32];
+
+ static float MAX_WAKE_LENGTH;
+ static float MIN_WAKE_INTERVAL;
+ static float WAKE_LIFETIME;
+
+ CBoat(int, uint8);
+
+ virtual void SetModelIndex(uint32 id);
+ virtual void ProcessControl();
+ virtual void Teleport(CVector v);
+ virtual void PreRender(void);
+ virtual void Render(void);
+ virtual void ProcessControlInputs(uint8);
+ virtual void GetComponentWorldPosition(int32 component, CVector &pos);
+ virtual bool IsComponentPresent(int32 component) { return true; }
+ virtual void BlowUpCar(CEntity *ent);
+
+ void RenderWaterOutPolys(void);
+ void ApplyWaterResistance(void);
+ void SetupModelNodes();
+ void PruneWakeTrail(void);
+ void AddWakePoint(CVector point);
+ void DoDriveByShootings(void);
+
+ static CBoat *apFrameWakeGeneratingBoats[4];
+
+ static bool IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats);
+ static float IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat);
+ static void FillBoatList(void);
+
+#ifdef COMPATIBLE_SAVES
+ virtual void Save(uint8*& buf);
+ virtual void Load(uint8*& buf);
+#endif
+ static const uint32 nSaveStructSize;
+
+}; \ No newline at end of file
diff --git a/src/vehicles/CarGen.cpp b/src/vehicles/CarGen.cpp
new file mode 100644
index 0000000..78e67d4
--- /dev/null
+++ b/src/vehicles/CarGen.cpp
@@ -0,0 +1,305 @@
+#include "common.h"
+
+#include "CarGen.h"
+
+#include "Automobile.h"
+#include "Bike.h"
+#include "Boat.h"
+#include "Camera.h"
+#include "CarCtrl.h"
+#include "CutsceneMgr.h"
+#include "General.h"
+#include "Pools.h"
+#include "Streaming.h"
+#include "Timer.h"
+#include "Vehicle.h"
+#include "VisibilityPlugins.h"
+#include "World.h"
+#include "Zones.h"
+#include "Occlusion.h"
+
+uint8 CTheCarGenerators::ProcessCounter;
+uint32 CTheCarGenerators::NumOfCarGenerators;
+CCarGenerator CTheCarGenerators::CarGeneratorArray[NUM_CARGENS];
+uint8 CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter;
+uint32 CTheCarGenerators::CurrentActiveCount;
+
+void CCarGenerator::SwitchOff()
+{
+#ifdef FIX_BUGS
+ if (m_nUsesRemaining != 0)
+#endif
+ {
+ m_nUsesRemaining = 0;
+ --CTheCarGenerators::CurrentActiveCount;
+ }
+}
+
+void CCarGenerator::SwitchOn()
+{
+ m_nUsesRemaining = UINT16_MAX;
+ m_nTimer = CalcNextGen();
+ ++CTheCarGenerators::CurrentActiveCount;
+}
+
+uint32 CCarGenerator::CalcNextGen()
+{
+ return CTimer::GetTimeInMilliseconds() + 4;
+}
+
+void CCarGenerator::DoInternalProcessing()
+{
+ int mi;
+ if (CCarCtrl::NumParkedCars >= 10)
+ return;
+ if (m_nModelIndex >= 0) {
+ if (CheckForBlockage(m_nModelIndex)) {
+ m_nTimer += 4;
+ return;
+ }
+ CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY);
+ mi = m_nModelIndex;
+ }
+ else {
+ mi = -m_nModelIndex;
+ if (m_nModelIndex == -1 || !CStreaming::HasModelLoaded(mi)) {
+ CZoneInfo pZone;
+ CVector pos = FindPlayerCoors();
+ CTheZones::GetZoneInfoForTimeOfDay(&pos, &pZone);
+ mi = CCarCtrl::ChooseCarModel(CCarCtrl::ChooseCarRating(&pZone));
+ if (mi < 0)
+ return;
+ m_nModelIndex = -mi;
+ m_nColor1 = -1;
+ m_nColor2 = -1;
+ }
+ if (CheckForBlockage(mi)) {
+ m_nTimer += 4;
+ return;
+ }
+ }
+ if (!CStreaming::HasModelLoaded(mi))
+ return;
+ CVehicle* pVehicle;
+
+ CVector pos;
+ if (CModelInfo::IsBoatModel(mi)){
+ CBoat* pBoat = new CBoat(mi, PARKED_VEHICLE);
+ pos = m_vecPos;
+ pVehicle = pBoat;
+ if (pos.z <= -100.0f)
+ pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y);
+ pBoat->bExtendedRange = true;
+ }else{
+ bool groundFound;
+ pos = m_vecPos;
+ if (pos.z > -100.0f){
+ pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &groundFound);
+ }else{
+ groundFound = false;
+ CColPoint cp;
+ CEntity* pEntity;
+ groundFound = CWorld::ProcessVerticalLine(CVector(pos.x, pos.y, 1000.0f), -1000.0f,
+ cp, pEntity, true, false, false, false, false, false, nil);
+ if (groundFound)
+ pos.z = cp.point.z;
+ }
+ if (!groundFound) {
+ debug("CCarGenerator::DoInternalProcessing - can't find ground z for new car x = %f y = %f \n", m_vecPos.x, m_vecPos.y);
+ return;
+ }
+ if (((CVehicleModelInfo*)CModelInfo::GetModelInfo(mi))->m_vehicleType == VEHICLE_TYPE_BIKE) {
+ CBike* pBike = new CBike(mi, PARKED_VEHICLE);
+ pBike->bIsStanding = true;
+ pVehicle = pBike;
+ }
+ else {
+ CAutomobile* pCar = new CAutomobile(mi, PARKED_VEHICLE);
+ pVehicle = pCar;
+ }
+ // pVehicle->GetDistanceFromCentreOfMassToBaseOfModel();
+ pVehicle->bLightsOn = false;
+ }
+ pVehicle->bIsStatic = false;
+ pVehicle->bEngineOn = false;
+ pos.z += pVehicle->GetDistanceFromCentreOfMassToBaseOfModel();
+ pVehicle->SetPosition(pos);
+ pVehicle->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle));
+ pVehicle->SetStatus(STATUS_ABANDONED);
+ pVehicle->m_nDoorLock = CARLOCK_UNLOCKED;
+ CWorld::Add(pVehicle);
+ if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm)
+ pVehicle->m_nAlarmState = -1;
+ if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock)
+ pVehicle->m_nDoorLock = CARLOCK_LOCKED;
+ if (m_nColor1 != -1 && m_nColor2 != -1) {
+ pVehicle->m_currentColour1 = m_nColor1;
+ pVehicle->m_currentColour2 = m_nColor2;
+ }
+ else if (m_nModelIndex < -1) {
+ m_nColor1 = pVehicle->m_currentColour1;
+ m_nColor2 = pVehicle->m_currentColour2;
+ }
+ CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0);
+ m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pVehicle);
+ /* I don't think this is a correct comparasion */
+#ifdef FIX_BUGS
+ if (m_nUsesRemaining < UINT16_MAX)
+ --m_nUsesRemaining;
+#else
+ if (m_nUsesRemaining < ~0)
+ --m_nUsesRemaining;
+#endif
+ m_nTimer = CalcNextGen();
+ if (m_nUsesRemaining == 0)
+ --CTheCarGenerators::CurrentActiveCount;
+}
+
+void CCarGenerator::Process()
+{
+ if (m_nVehicleHandle == -1 &&
+ (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter || CTimer::GetTimeInMilliseconds() >= m_nTimer) &&
+ m_nUsesRemaining != 0 && CheckIfWithinRangeOfAnyPlayers())
+ DoInternalProcessing();
+ if (m_nVehicleHandle == -1)
+ return;
+ CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_nVehicleHandle);
+ if (!pVehicle){
+ m_nVehicleHandle = -1;
+ return;
+ }
+ if (pVehicle->GetStatus() != STATUS_PLAYER)
+ return;
+ m_nTimer += 60000;
+ m_nVehicleHandle = -1;
+ m_bIsBlocking = true;
+ pVehicle->bExtendedRange = false;
+ if (m_nModelIndex < 0)
+ m_nModelIndex = -1;
+}
+
+void CCarGenerator::Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay)
+{
+ CMatrix m1, m2, m3; /* Unused but present on stack, so I'll leave them. */
+ m_vecPos = CVector(x, y, z);
+ m_fAngle = angle;
+ m_nModelIndex = mi;
+ m_nColor1 = color1;
+ m_nColor2 = color2;
+ m_bForceSpawn = force;
+ m_nAlarm = alarm;
+ m_nDoorlock = lock;
+ m_nMinDelay = min_delay;
+ m_nMaxDelay = max_delay;
+ m_nVehicleHandle = -1;
+ m_nTimer = CTimer::GetTimeInMilliseconds() + 1;
+ m_nUsesRemaining = 0;
+ m_bIsBlocking = false;
+}
+
+bool CCarGenerator::CheckForBlockage(int32 mi)
+{
+ int16 entities;
+ CEntity* pEntities[8];
+ CColModel* pColModel = CModelInfo::GetModelInfo(mi)->GetColModel();
+ CWorld::FindObjectsKindaColliding(CVector(m_vecPos), pColModel->boundingSphere.radius, 1, &entities, 8, pEntities, false, true, true, false, false);
+ for (int i = 0; i < entities; i++) {
+ if (m_vecPos.z + pColModel->boundingBox.min.z < pEntities[i]->GetPosition().z + pEntities[i]->GetColModel()->boundingBox.max.z + 1.0f &&
+ m_vecPos.z + pColModel->boundingBox.max.z > pEntities[i]->GetPosition().z + pEntities[i]->GetColModel()->boundingBox.min.z - 1.0f) {
+ m_bIsBlocking = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CCarGenerator::CheckIfWithinRangeOfAnyPlayers()
+{
+ CVector2D direction = FindPlayerCentreOfWorld(CWorld::PlayerInFocus) - m_vecPos;
+ float distance = direction.Magnitude();
+ float farclip = 110.0f * TheCamera.GenerationDistMultiplier;
+ float nearclip = farclip - 20.0f;
+ bool canBeRemoved = (m_nModelIndex > 0 && CModelInfo::IsBoatModel(m_nModelIndex) && 165.0f * TheCamera.GenerationDistMultiplier > distance &&
+ TheCamera.IsSphereVisible(m_vecPos, 0.0f) && !COcclusion::IsPositionOccluded(m_vecPos, 0.0f));
+ if (distance >= farclip && !canBeRemoved){
+ if (m_bIsBlocking)
+ m_bIsBlocking = false;
+ return false;
+ }
+ if (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter)
+ return true;
+ if (m_bIsBlocking)
+ return false;
+ if (distance < nearclip && !m_bForceSpawn)
+ return false;
+ return DotProduct2D(direction, FindPlayerSpeed()) <= 0;
+}
+
+void CTheCarGenerators::Process()
+{
+ if (FindPlayerTrain() || CCutsceneMgr::IsCutsceneProcessing())
+ return;
+ if (++CTheCarGenerators::ProcessCounter == 4)
+ CTheCarGenerators::ProcessCounter = 0;
+ for (uint32 i = ProcessCounter; i < NumOfCarGenerators; i += 4)
+ CTheCarGenerators::CarGeneratorArray[i].Process();
+ if (GenerateEvenIfPlayerIsCloseCounter)
+ GenerateEvenIfPlayerIsCloseCounter--;
+}
+
+int32 CTheCarGenerators::CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay)
+{
+ if (NumOfCarGenerators < NUM_CARGENS)
+ CarGeneratorArray[NumOfCarGenerators++].Setup(x, y, z, angle, mi, color1, color2, force, alarm, lock, min_delay, max_delay);
+ return NumOfCarGenerators - 1;
+}
+
+void CTheCarGenerators::Init()
+{
+ GenerateEvenIfPlayerIsCloseCounter = 0;
+ NumOfCarGenerators = 0;
+ ProcessCounter = 0;
+ CurrentActiveCount = 0;
+}
+
+void CTheCarGenerators::SaveAllCarGenerators(uint8 *buffer, uint32 *size)
+{
+ const uint32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16);
+ *size = sizeof(int) + nGeneralDataSize + sizeof(uint32) + sizeof(CarGeneratorArray) + SAVE_HEADER_SIZE;
+INITSAVEBUF
+ WriteSaveHeader(buffer, 'C','G','N','\0', *size - SAVE_HEADER_SIZE);
+
+ WriteSaveBuf(buffer, nGeneralDataSize);
+ WriteSaveBuf(buffer, NumOfCarGenerators);
+ WriteSaveBuf(buffer, CurrentActiveCount);
+ WriteSaveBuf(buffer, ProcessCounter);
+ WriteSaveBuf(buffer, GenerateEvenIfPlayerIsCloseCounter);
+ WriteSaveBuf(buffer, (int16)0); // alignment
+ WriteSaveBuf(buffer, (uint32)sizeof(CarGeneratorArray));
+ for (int i = 0; i < NUM_CARGENS; i++)
+ WriteSaveBuf(buffer, CarGeneratorArray[i]);
+VALIDATESAVEBUF(*size)
+}
+
+void CTheCarGenerators::LoadAllCarGenerators(uint8* buffer, uint32 size)
+{
+ NumOfCarGenerators = 0;
+ GenerateEvenIfPlayerIsCloseCounter = 0;
+ CurrentActiveCount = 0;
+ ProcessCounter = 0;
+
+ const int32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16);
+ Init();
+INITSAVEBUF
+ CheckSaveHeader(buffer, 'C','G','N','\0', size - SAVE_HEADER_SIZE);
+ assert(ReadSaveBuf<uint32>(buffer) == nGeneralDataSize);
+ NumOfCarGenerators = ReadSaveBuf<uint32>(buffer);
+ CurrentActiveCount = ReadSaveBuf<uint32>(buffer);
+ ProcessCounter = ReadSaveBuf<uint8>(buffer);
+ GenerateEvenIfPlayerIsCloseCounter = ReadSaveBuf<uint8>(buffer);
+ ReadSaveBuf<int16>(buffer); // alignment
+ assert(ReadSaveBuf<uint32>(buffer) == sizeof(CarGeneratorArray));
+ for (int i = 0; i < NUM_CARGENS; i++)
+ CarGeneratorArray[i] = ReadSaveBuf<CCarGenerator>(buffer);
+VALIDATESAVEBUF(size)
+}
diff --git a/src/vehicles/CarGen.h b/src/vehicles/CarGen.h
new file mode 100644
index 0000000..fccbee9
--- /dev/null
+++ b/src/vehicles/CarGen.h
@@ -0,0 +1,51 @@
+#pragma once
+#include "common.h"
+#include "config.h"
+
+enum {
+ CARGEN_MAXACTUALLIMIT = 100
+};
+
+class CCarGenerator
+{
+ int32 m_nModelIndex;
+ CVector m_vecPos;
+ float m_fAngle;
+ int16 m_nColor1;
+ int16 m_nColor2;
+ uint8 m_bForceSpawn;
+ uint8 m_nAlarm;
+ uint8 m_nDoorlock;
+ int16 m_nMinDelay;
+ int16 m_nMaxDelay;
+ uint32 m_nTimer;
+ int32 m_nVehicleHandle;
+ uint16 m_nUsesRemaining;
+ bool m_bIsBlocking;
+public:
+ void SwitchOff();
+ void SwitchOn();
+ uint32 CalcNextGen();
+ void DoInternalProcessing();
+ void Process();
+ void Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay);
+ bool CheckForBlockage(int32 mi);
+ bool CheckIfWithinRangeOfAnyPlayers();
+ void SetUsesRemaining(uint16 uses) { m_nUsesRemaining = uses; }
+};
+
+class CTheCarGenerators
+{
+public:
+ static uint8 ProcessCounter;
+ static uint32 NumOfCarGenerators;
+ static CCarGenerator CarGeneratorArray[NUM_CARGENS];
+ static uint8 GenerateEvenIfPlayerIsCloseCounter;
+ static uint32 CurrentActiveCount;
+
+ static void Process();
+ static int32 CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay);
+ static void Init();
+ static void SaveAllCarGenerators(uint8 *, uint32 *);
+ static void LoadAllCarGenerators(uint8 *, uint32);
+};
diff --git a/src/vehicles/Cranes.cpp b/src/vehicles/Cranes.cpp
new file mode 100644
index 0000000..b592686
--- /dev/null
+++ b/src/vehicles/Cranes.cpp
@@ -0,0 +1,659 @@
+#include "common.h"
+
+#include "Cranes.h"
+
+#include "Camera.h"
+#include "DMAudio.h"
+#include "Garages.h"
+#include "General.h"
+#include "Entity.h"
+#include "ModelIndices.h"
+#include "Replay.h"
+#include "Object.h"
+#include "World.h"
+
+#define MAX_DISTANCE_TO_FIND_CRANE (10.0f)
+#define CRANE_UPDATE_RADIUS (300.0f)
+#define CRANE_MOVEMENT_PROCESSING_RADIUS (150.0f)
+#define CRUSHER_Z (-0.951f)
+#define MILITARY_Z (10.7862f)
+#define DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE (5.0f)
+#define DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT (0.5f)
+#define CAR_REWARD_MILITARY_CRANE (1500)
+#define CAR_MOVING_SPEED_THRESHOLD (0.01f)
+#define CRANE_SLOWDOWN_MULTIPLIER (0.3f)
+
+#define OSCILLATION_SPEED (0.002f)
+#define CAR_ROTATION_SPEED (0.0035f)
+#define CRANE_MOVEMENT_SPEED (0.001f)
+#define HOOK_ANGLE_MOVEMENT_SPEED (0.004f)
+#define HOOK_OFFSET_MOVEMENT_SPEED (0.1f)
+#define HOOK_HEIGHT_MOVEMENT_SPEED (0.06f)
+
+#define MESSAGE_SHOW_DURATION (4000)
+
+#define MAX_DISTANCE (99999.9f)
+#define MIN_VALID_POSITION (-10000.0f)
+#define DEFAULT_OFFSET (20.0f)
+
+uint32 TimerForCamInterpolation;
+
+uint32 CCranes::CarsCollectedMilitaryCrane;
+int32 CCranes::NumCranes;
+CCrane CCranes::aCranes[NUM_CRANES];
+
+void CCranes::InitCranes(void)
+{
+ CarsCollectedMilitaryCrane = 0;
+ NumCranes = 0;
+ for (int i = 0; i < NUMSECTORS_X; i++) {
+ for (int j = 0; j < NUMSECTORS_Y; j++) {
+ for (CPtrNode* pNode = CWorld::GetSector(i, j)->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) {
+ CEntity* pEntity = (CEntity*)pNode->item;
+ if (MODELID_CRANE_1 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_2 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_3 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_4 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_5 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_6 == pEntity->GetModelIndex())
+ AddThisOneCrane(pEntity);
+ }
+ }
+ }
+ for (CPtrNode* pNode = CWorld::GetBigBuildingList(LEVEL_MAINLAND).first; pNode; pNode = pNode->next) {
+ CEntity* pEntity = (CEntity*)pNode->item;
+ if (MODELID_CRANE_1 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_2 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_3 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_4 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_5 == pEntity->GetModelIndex() ||
+ MODELID_CRANE_6 == pEntity->GetModelIndex())
+ AddThisOneCrane(pEntity);
+ }
+}
+
+void CCranes::AddThisOneCrane(CEntity* pEntity)
+{
+ pEntity->GetMatrix().ResetOrientation();
+ if (NumCranes >= NUM_CRANES)
+ return;
+ CCrane* pCrane = &aCranes[NumCranes];
+ pCrane->Init();
+ pCrane->m_pCraneEntity = (CBuilding*)pEntity;
+ pCrane->m_nCraneStatus = CCrane::NONE;
+ pCrane->m_fHookAngle = NumCranes; // lol wtf
+ while (pCrane->m_fHookAngle > TWOPI)
+ pCrane->m_fHookAngle -= TWOPI;
+ pCrane->m_fHookOffset = DEFAULT_OFFSET;
+ pCrane->m_fHookHeight = DEFAULT_OFFSET;
+ pCrane->m_nTimeForNextCheck = 0;
+ pCrane->m_nCraneState = CCrane::IDLE;
+ pCrane->m_bWasMilitaryCrane = false;
+ pCrane->m_bIsTop = (MODELID_CRANE_1 != pEntity->GetModelIndex());
+ pCrane->m_pHook = nil;
+ NumCranes++;
+}
+
+void CCranes::ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY)
+{
+ float fMinDistance = MAX_DISTANCE;
+ float X = fPosX, Y = fPosY;
+ if (X <= MIN_VALID_POSITION || Y <= MIN_VALID_POSITION) {
+ X = fDropOffX;
+ Y = fDropOffY;
+ }
+ int index = 0;
+ for (int i = 0; i < NumCranes; i++) {
+ float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
+ if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) {
+ fMinDistance = distance;
+ index = i;
+ }
+ }
+#ifdef FIX_BUGS // classic
+ if (fMinDistance == MAX_DISTANCE)
+ return;
+#endif
+ CCrane* pCrane = &aCranes[index];
+ pCrane->m_fPickupX1 = fInfX;
+ pCrane->m_fPickupX2 = fSupX;
+ pCrane->m_fPickupY1 = fInfY;
+ pCrane->m_fPickupY2 = fSupY;
+ pCrane->m_vecDropoffTarget.x = fDropOffX;
+ pCrane->m_vecDropoffTarget.y = fDropOffY;
+ pCrane->m_vecDropoffTarget.z = fDropOffZ;
+ pCrane->m_nCraneStatus = CCrane::ACTIVATED;
+ pCrane->m_pVehiclePickedUp = nil;
+ pCrane->m_nVehiclesCollected = 0;
+ pCrane->m_fDropoffHeading = fHeading;
+ pCrane->m_bIsCrusher = bIsCrusher;
+ pCrane->m_bIsMilitaryCrane = bIsMilitary;
+ bool military = true;
+ if (!bIsMilitary && !pCrane->m_bWasMilitaryCrane)
+ military = false;
+ pCrane->m_bWasMilitaryCrane = military;
+ pCrane->m_nTimeForNextCheck = 0;
+ pCrane->m_nCraneState = CCrane::IDLE;
+ float Z;
+ if (bIsCrusher)
+ Z = CRUSHER_Z;
+ else if (bIsMilitary)
+ Z = MILITARY_Z;
+ else
+ Z = CWorld::FindGroundZForCoord((fInfX + fSupX) / 2, (fInfY + fSupY) / 2);
+ pCrane->FindParametersForTarget((fInfX + fSupX) / 2, (fInfY + fSupY) / 2, Z, &pCrane->m_fPickupAngle, &pCrane->m_fPickupDistance, &pCrane->m_fPickupHeight);
+ pCrane->FindParametersForTarget(fDropOffX, fDropOffY, fDropOffZ, &pCrane->m_fDropoffAngle, &pCrane->m_fDropoffDistance, &pCrane->m_fDropoffHeight);
+}
+
+void CCranes::DeActivateCrane(float X, float Y)
+{
+ float fMinDistance = MAX_DISTANCE;
+ int index = 0;
+ for (int i = 0; i < NumCranes; i++) {
+ float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
+ if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) {
+ fMinDistance = distance;
+ index = i;
+ }
+ }
+#ifdef FIX_BUGS // classic
+ if (fMinDistance == MAX_DISTANCE)
+ return;
+#endif
+ aCranes[index].m_nCraneStatus = CCrane::DEACTIVATED;
+ aCranes[index].m_nCraneState = CCrane::IDLE;
+}
+
+bool CCranes::IsThisCarPickedUp(float X, float Y, CVehicle* pVehicle)
+{
+ int index = 0;
+ bool result = false;
+ for (int i = 0; i < NumCranes; i++) {
+ float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
+ if (distance < MAX_DISTANCE_TO_FIND_CRANE && aCranes[i].m_pVehiclePickedUp == pVehicle) {
+ if (aCranes[i].m_nCraneState == CCrane::LIFTING_TARGET || aCranes[i].m_nCraneState == CCrane::ROTATING_TARGET)
+ result = true;
+ }
+ }
+ return result;
+}
+
+void CCranes::UpdateCranes(void)
+{
+ for (int i = 0; i < NumCranes; i++) {
+ if (aCranes[i].m_bIsTop || aCranes[i].m_bIsCrusher ||
+ (TheCamera.GetPosition().x + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().x &&
+ TheCamera.GetPosition().x - CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().x &&
+ TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().y &&
+ TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().y))
+ aCranes[i].Update();
+ }
+}
+
+void CCrane::Update(void)
+{
+ if (CReplay::IsPlayingBack())
+ return;
+ if (((m_nCraneStatus == ACTIVATED || m_nCraneStatus == DEACTIVATED) &&
+ Abs(TheCamera.GetGameCamPosition().x - m_pCraneEntity->GetPosition().x) < CRANE_MOVEMENT_PROCESSING_RADIUS &&
+ Abs(TheCamera.GetGameCamPosition().y - m_pCraneEntity->GetPosition().y) < CRANE_MOVEMENT_PROCESSING_RADIUS) ||
+ m_nCraneState != IDLE) {
+ switch (m_nCraneState) {
+ case IDLE:
+ if (GoTowardsTarget(m_fPickupAngle, m_fPickupDistance, GetHeightToPickup()) &&
+ CTimer::GetTimeInMilliseconds() > m_nTimeForNextCheck) {
+ CWorld::AdvanceCurrentScanCode();
+#ifdef FIX_BUGS
+ int xstart = Max(0, CWorld::GetSectorIndexX(m_fPickupX1));
+ int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fPickupX2));
+ int ystart = Max(0, CWorld::GetSectorIndexY(m_fPickupY1));
+ int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fPickupY2));
+#else
+ int xstart = CWorld::GetSectorIndexX(m_fPickupX1);
+ int xend = CWorld::GetSectorIndexX(m_fPickupX2);
+ int ystart = CWorld::GetSectorIndexY(m_fPickupY1);
+ int yend = CWorld::GetSectorIndexY(m_fPickupY1);
+#endif
+ assert(xstart <= xend);
+ assert(ystart <= yend);
+ for (int i = xstart; i <= xend; i++) {
+ for (int j = ystart; j <= yend; j++) {
+ FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES]);
+ FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
+ }
+ }
+ }
+ break;
+ case GOING_TOWARDS_TARGET:
+ if (m_pVehiclePickedUp){
+ if (m_pVehiclePickedUp->GetPosition().x < m_fPickupX1 ||
+ m_pVehiclePickedUp->GetPosition().x > m_fPickupX2 ||
+ m_pVehiclePickedUp->GetPosition().y < m_fPickupY1 ||
+ m_pVehiclePickedUp->GetPosition().y > m_fPickupY2 ||
+ m_pVehiclePickedUp->pDriver ||
+ Abs(m_pVehiclePickedUp->GetMoveSpeed().x) > CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(m_pVehiclePickedUp->GetMoveSpeed().y) > CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(m_pVehiclePickedUp->GetMoveSpeed().z) > CAR_MOVING_SPEED_THRESHOLD ||
+ (FindPlayerPed()->GetPedState() == PED_ENTER_CAR
+#ifdef FIX_BUGS
+ || FindPlayerPed()->GetPedState() == PED_CARJACK
+#endif
+ ) && FindPlayerPed()->m_pSeekTarget == m_pVehiclePickedUp) {
+ m_pVehiclePickedUp = nil;
+ m_nCraneState = IDLE;
+ }
+ else {
+ float fAngle, fOffset, fHeight;
+ FindParametersForTarget(
+ m_pVehiclePickedUp->GetPosition().x,
+ m_pVehiclePickedUp->GetPosition().y,
+ m_pVehiclePickedUp->GetPosition().z + m_pVehiclePickedUp->GetColModel()->boundingBox.max.z,
+ &fAngle, &fOffset, &fHeight);
+ if (GoTowardsTarget(fAngle, fOffset, fHeight)) {
+ CVector distance = m_pVehiclePickedUp->GetPosition() - m_vecHookCurPos;
+ distance.z += m_pVehiclePickedUp->GetColModel()->boundingBox.max.z;
+ if (distance.MagnitudeSqr() < SQR(DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT)) {
+ m_nCraneState = GOING_TOWARDS_TARGET_ONLY_HEIGHT;
+ m_vecHookVelocity *= 0.4f;
+ m_pVehiclePickedUp->bLightsOn = false;
+ m_pVehiclePickedUp->bUsesCollision = false;
+ if (m_bIsCrusher)
+ m_pVehiclePickedUp->bCollisionProof = true;
+ }
+ }
+ }
+ }
+ else
+ m_nCraneState = IDLE;
+ break;
+ case LIFTING_TARGET:
+ RotateCarriedCarProperly();
+ if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoff(), CRANE_SLOWDOWN_MULTIPLIER))
+ m_nCraneState = ROTATING_TARGET;
+ if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) {
+ m_pVehiclePickedUp = nil;
+ m_nCraneState = IDLE;
+ }
+ break;
+ case GOING_TOWARDS_TARGET_ONLY_HEIGHT:
+ RotateCarriedCarProperly();
+ if (GoTowardsHeightTarget(GetHeightToPickupHeight(), CRANE_SLOWDOWN_MULTIPLIER))
+ m_nCraneState = LIFTING_TARGET;
+ TimerForCamInterpolation = CTimer::GetTimeInMilliseconds();
+ if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) {
+ m_pVehiclePickedUp = nil;
+ m_nCraneState = IDLE;
+ }
+ break;
+ case ROTATING_TARGET:
+ {
+ bool bRotateFinished = RotateCarriedCarProperly();
+ bool bMovementFinished = GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, m_fDropoffHeight, 0.3f);
+ if (bMovementFinished && bRotateFinished) {
+ float fDistanceFromPlayer = m_pVehiclePickedUp ? ((CVector2D)FindPlayerCoors() - (CVector2D)m_pVehiclePickedUp->GetPosition()).Magnitude() : 0.0f;
+ if (fDistanceFromPlayer > DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE || !m_bWasMilitaryCrane) {
+ m_nCraneState = DROPPING_TARGET;
+ if (m_pVehiclePickedUp) {
+ m_pVehiclePickedUp->bUsesCollision = true;
+ m_pVehiclePickedUp->m_nStaticFrames = 0;
+ ++m_nVehiclesCollected;
+ if (m_bIsMilitaryCrane) {
+ CCranes::RegisterCarForMilitaryCrane(m_pVehiclePickedUp->GetModelIndex());
+ if (!CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()) {
+ CWorld::Players[CWorld::PlayerInFocus].m_nMoney += CAR_REWARD_MILITARY_CRANE;
+ CGarages::TriggerMessage("GA_10", CAR_REWARD_MILITARY_CRANE, MESSAGE_SHOW_DURATION, -1);
+ }
+ CWorld::Remove(m_pVehiclePickedUp);
+ delete m_pVehiclePickedUp;
+ }
+ }
+ m_pVehiclePickedUp = nil;
+ }
+ }
+ break;
+ }
+ case DROPPING_TARGET:
+ if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoffHeight(), CRANE_SLOWDOWN_MULTIPLIER)) {
+ m_nCraneState = IDLE;
+ m_nTimeForNextCheck = CTimer::GetTimeInMilliseconds() + 10000;
+ }
+ break;
+ default:
+ break;
+ }
+ CVector vecHook;
+ CalcHookCoordinates(&vecHook.x, &vecHook.y, &vecHook.z);
+ m_vecHookVelocity += ((CVector2D)vecHook - (CVector2D)m_vecHookCurPos) * CTimer::GetTimeStep() * CRANE_MOVEMENT_SPEED;
+ m_vecHookVelocity *= Pow(0.98f, CTimer::GetTimeStep());
+ m_vecHookCurPos.x += m_vecHookVelocity.x * CTimer::GetTimeStep();
+ m_vecHookCurPos.y += m_vecHookVelocity.y * CTimer::GetTimeStep();
+ m_vecHookCurPos.z = vecHook.z;
+ switch (m_nCraneState) {
+ case LIFTING_TARGET:
+ case GOING_TOWARDS_TARGET_ONLY_HEIGHT:
+ case ROTATING_TARGET:
+ if (m_pVehiclePickedUp) {
+ m_pVehiclePickedUp->SetPosition(m_vecHookCurPos.x, m_vecHookCurPos.y, m_vecHookCurPos.z - m_pVehiclePickedUp->GetColModel()->boundingBox.max.z);
+ m_pVehiclePickedUp->SetMoveSpeed(0.0f, 0.0f, 0.0f);
+ CVector up(vecHook.x - m_vecHookCurPos.x, vecHook.y - m_vecHookCurPos.y, 20.0f);
+ up.Normalise();
+ m_pVehiclePickedUp->GetRight() = CrossProduct(m_pVehiclePickedUp->GetForward(), up);
+ m_pVehiclePickedUp->GetForward() = CrossProduct(up, m_pVehiclePickedUp->GetRight());
+ m_pVehiclePickedUp->GetUp() = up;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ int16 rnd = (m_pCraneEntity->m_randomSeed + (CTimer::GetTimeInMilliseconds() >> 11)) & 0xF;
+ // 16 options, lasting 2048 ms each
+ // a bit awkward: why there are 4 periods for -= and 6 for +=? is it a bug?
+ if (rnd < 4) {
+ m_fHookAngle -= OSCILLATION_SPEED * CTimer::GetTimeStep();
+ if (m_fHookAngle < 0.0f)
+ m_fHookAngle += TWOPI;
+ }
+ else if (rnd > 5 && rnd < 12) {
+ m_fHookAngle += OSCILLATION_SPEED * CTimer::GetTimeStep();
+ if (m_fHookAngle > TWOPI)
+ m_fHookAngle -= TWOPI;
+ }
+ CalcHookCoordinates(&m_vecHookCurPos.x, &m_vecHookCurPos.y, &m_vecHookCurPos.z);
+ m_vecHookVelocity.x = m_vecHookVelocity.y = 0.0f;
+ }
+ float fCos = Cos(m_fHookAngle);
+ float fSin = Sin(m_fHookAngle);
+ m_pCraneEntity->GetRight().x = fCos;
+ m_pCraneEntity->GetForward().y = fCos;
+ m_pCraneEntity->GetRight().y = fSin;
+ m_pCraneEntity->GetForward().x = -fSin;
+ m_pCraneEntity->GetMatrix().UpdateRW();
+ m_pCraneEntity->UpdateRwFrame();
+ SetHookMatrix();
+}
+
+bool CCrane::RotateCarriedCarProperly()
+{
+ if (m_fDropoffHeading <= 0.0f)
+ return true;
+ if (!m_pVehiclePickedUp)
+ return true;
+ float fAngleDelta = m_fDropoffHeading - CGeneral::GetATanOfXY(m_pVehiclePickedUp->GetForward().x, m_pVehiclePickedUp->GetForward().y);
+ while (fAngleDelta < -HALFPI)
+ fAngleDelta += PI;
+ while (fAngleDelta > HALFPI)
+ fAngleDelta -= PI;
+ float fDeltaThisFrame = CAR_ROTATION_SPEED * CTimer::GetTimeStep();
+ if (Abs(fAngleDelta) <= fDeltaThisFrame) // no rotation is actually applied?
+ return true;
+ m_pVehiclePickedUp->GetMatrix().RotateZ(fAngleDelta < 0 ? -fDeltaThisFrame : fDeltaThisFrame);
+ return false;
+}
+
+void CCrane::FindCarInSectorList(CPtrList* pList)
+{
+ CPtrNode* node;
+ for (node = pList->first; node; node = node->next) {
+ CVehicle* pVehicle = (CVehicle*)node->item;
+ if (pVehicle->m_scanCode == CWorld::GetCurrentScanCode())
+ continue;
+ pVehicle->m_scanCode = CWorld::GetCurrentScanCode();
+ if (pVehicle->GetPosition().x < m_fPickupX1 || pVehicle->GetPosition().x > m_fPickupX2 ||
+ pVehicle->GetPosition().y < m_fPickupY1 || pVehicle->GetPosition().y > m_fPickupY2)
+ continue;
+ if (pVehicle->pDriver)
+ continue;
+ if (Abs(pVehicle->GetMoveSpeed().x) >= CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(pVehicle->GetMoveSpeed().y) >= CAR_MOVING_SPEED_THRESHOLD ||
+ Abs(pVehicle->GetMoveSpeed().z) >= CAR_MOVING_SPEED_THRESHOLD)
+ continue;
+ if (!pVehicle->IsCar() || pVehicle->GetStatus() == STATUS_WRECKED || pVehicle->m_fHealth < 250.0f)
+ continue;
+ if (!DoesCranePickUpThisCarType(pVehicle->GetModelIndex()) ||
+ m_bIsMilitaryCrane && CCranes::DoesMilitaryCraneHaveThisOneAlready(pVehicle->GetModelIndex())) {
+ if (!pVehicle->bCraneMessageDone) {
+ pVehicle->bCraneMessageDone = true;
+ if (!m_bIsMilitaryCrane)
+ CGarages::TriggerMessage("CR_1", -1, MESSAGE_SHOW_DURATION, -1); // Crane cannot lift this vehicle.
+ else if (DoesCranePickUpThisCarType(pVehicle->GetModelIndex()))
+ CGarages::TriggerMessage("GA_20", -1, MESSAGE_SHOW_DURATION, -1); // We got more of these than we can shift. Sorry man, no deal.
+ else
+ CGarages::TriggerMessage("GA_19", -1, MESSAGE_SHOW_DURATION, -1); // We're not interested in that model.
+ }
+ }
+ else {
+ m_pVehiclePickedUp = pVehicle;
+ pVehicle->RegisterReference((CEntity**)&m_pVehiclePickedUp);
+ m_nCraneState = GOING_TOWARDS_TARGET;
+ }
+ }
+}
+
+bool CCrane::DoesCranePickUpThisCarType(uint32 mi)
+{
+ if (m_bIsCrusher) {
+ return mi != MI_FIRETRUCK &&
+ mi != MI_TRASH &&
+#ifdef FIX_BUGS
+ mi != MI_COACH &&
+#endif
+ mi != MI_SECURICA &&
+ mi != MI_BUS &&
+ mi != MI_DODO &&
+ mi != MI_RHINO;
+ }
+ if (m_bIsMilitaryCrane) {
+ return mi == MI_FIRETRUCK ||
+ mi == MI_AMBULAN ||
+ mi == MI_ENFORCER ||
+ mi == MI_FBIRANCH ||
+ mi == MI_RHINO ||
+ mi == MI_BARRACKS ||
+ mi == MI_POLICE;
+ }
+ return true;
+}
+
+bool CCranes::DoesMilitaryCraneHaveThisOneAlready(uint32 mi)
+{
+ switch (mi) {
+ case MI_FIRETRUCK: return (CarsCollectedMilitaryCrane & 1);
+ case MI_AMBULAN: return (CarsCollectedMilitaryCrane & 2);
+ case MI_ENFORCER: return (CarsCollectedMilitaryCrane & 4);
+ case MI_FBIRANCH: return (CarsCollectedMilitaryCrane & 8);
+ case MI_RHINO: return (CarsCollectedMilitaryCrane & 0x10);
+ case MI_BARRACKS: return (CarsCollectedMilitaryCrane & 0x20);
+ case MI_POLICE: return (CarsCollectedMilitaryCrane & 0x40);
+ default: break;
+ }
+ return false;
+}
+
+void CCranes::RegisterCarForMilitaryCrane(uint32 mi)
+{
+ switch (mi) {
+ case MI_FIRETRUCK: CarsCollectedMilitaryCrane |= 1; break;
+ case MI_AMBULAN: CarsCollectedMilitaryCrane |= 2; break;
+ case MI_ENFORCER: CarsCollectedMilitaryCrane |= 4; break;
+ case MI_FBIRANCH: CarsCollectedMilitaryCrane |= 8; break;
+ case MI_RHINO: CarsCollectedMilitaryCrane |= 0x10; break;
+ case MI_BARRACKS: CarsCollectedMilitaryCrane |= 0x20; break;
+ case MI_POLICE: CarsCollectedMilitaryCrane |= 0x40; break;
+ default: break;
+ }
+}
+
+bool CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()
+{
+ return (CarsCollectedMilitaryCrane & 0x7F) == 0x7F;
+}
+
+bool CCrane::GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier)
+{
+ bool bAngleMovementFinished, bOffsetMovementFinished, bHeightMovementFinished;
+ float fHookAngleDelta = fAngleToTarget - m_fHookAngle;
+ while (fHookAngleDelta > PI)
+ fHookAngleDelta -= TWOPI;
+ while (fHookAngleDelta < -PI)
+ fHookAngleDelta += TWOPI;
+ float fHookAngleChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_ANGLE_MOVEMENT_SPEED;
+ if (Abs(fHookAngleDelta) < fHookAngleChangeThisFrame) {
+ m_fHookAngle = fAngleToTarget;
+ bAngleMovementFinished = true;
+ } else {
+ if (fHookAngleDelta < 0.0f) {
+ m_fHookAngle -= fHookAngleChangeThisFrame;
+ if (m_fHookAngle < 0.0f)
+ m_fHookAngle += TWOPI;
+ }
+ else {
+ m_fHookAngle += fHookAngleChangeThisFrame;
+ if (m_fHookAngle > TWOPI)
+ m_fHookAngle -= TWOPI;
+ }
+ bAngleMovementFinished = false;
+ }
+ float fHookOffsetDelta = fDistanceToTarget - m_fHookOffset;
+ float fHookOffsetChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_OFFSET_MOVEMENT_SPEED;
+ if (Abs(fHookOffsetDelta) < fHookOffsetChangeThisFrame) {
+ m_fHookOffset = fDistanceToTarget;
+ bOffsetMovementFinished = true;
+ } else {
+ if (fHookOffsetDelta < 0.0f)
+ m_fHookOffset -= fHookOffsetChangeThisFrame;
+ else
+ m_fHookOffset += fHookOffsetChangeThisFrame;
+ bOffsetMovementFinished = false;
+ }
+ float fHookHeightDelta = fTargetHeight - m_fHookHeight;
+ float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED;
+ if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) {
+ m_fHookHeight = fTargetHeight;
+ bHeightMovementFinished = true;
+ } else {
+ if (fHookHeightDelta < 0.0f)
+ m_fHookHeight -= fHookHeightChangeThisFrame;
+ else
+ m_fHookHeight += fHookHeightChangeThisFrame;
+ bHeightMovementFinished = false;
+ }
+ return bAngleMovementFinished && bOffsetMovementFinished && bHeightMovementFinished;
+}
+
+bool CCrane::GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier)
+{
+ bool bHeightMovementFinished;
+ float fHookHeightDelta = fTargetHeight - m_fHookHeight;
+ float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED;
+ if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) {
+ m_fHookHeight = fTargetHeight;
+ bHeightMovementFinished = true;
+ } else {
+ if (fHookHeightDelta < 0.0f)
+ m_fHookHeight -= fHookHeightChangeThisFrame;
+ else
+ m_fHookHeight += fHookHeightChangeThisFrame;
+ bHeightMovementFinished = false;
+ }
+ return bHeightMovementFinished;
+}
+
+void CCrane::FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight)
+{
+ *pAngle = CGeneral::GetATanOfXY(X - m_pCraneEntity->GetPosition().x, Y - m_pCraneEntity->GetPosition().y);
+ *pDistance = ((CVector2D(X, Y) - (CVector2D)m_pCraneEntity->GetPosition())).Magnitude();
+ *pHeight = Z;
+}
+
+void CCrane::CalcHookCoordinates(float* pX, float* pY, float* pZ)
+{
+ *pX = Cos(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().x;
+ *pY = Sin(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().y;
+ *pZ = m_fHookHeight;
+}
+
+void CCrane::SetHookMatrix()
+{
+ if (m_pHook == nil)
+ return;
+ m_pHook->SetPosition(m_vecHookCurPos);
+ CVector up(m_vecHookInitPos.x - m_vecHookCurPos.x, m_vecHookInitPos.y - m_vecHookCurPos.y, 20.0f);
+ up.Normalise();
+ m_pHook->GetRight() = CrossProduct(CVector(0.0f, 1.0f, 0.0f), up);
+ m_pHook->GetForward() = CrossProduct(up, m_pHook->GetRight());
+ m_pHook->GetUp() = up;
+ m_pHook->SetOrientation(0.0f, 0.0f, -HALFPI);
+ m_pHook->GetMatrix().UpdateRW();
+ m_pHook->UpdateRwFrame();
+ CWorld::Remove(m_pHook);
+ CWorld::Add(m_pHook);
+}
+
+bool CCranes::IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle)
+{
+ for (int i = 0; i < NumCranes; i++) {
+ if (pVehicle == aCranes[i].m_pVehiclePickedUp) {
+ switch (aCranes[i].m_nCraneState) {
+ case CCrane::GOING_TOWARDS_TARGET_ONLY_HEIGHT:
+ case CCrane::LIFTING_TARGET:
+ case CCrane::ROTATING_TARGET:
+ return true;
+ default:
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+bool CCranes::IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle)
+{
+ for (int i = 0; i < NumCranes; i++) {
+ if (pVehicle == aCranes[i].m_pVehiclePickedUp)
+ return true;
+ }
+ return false;
+}
+
+void CCranes::Save(uint8* buf, uint32* size)
+{
+ INITSAVEBUF
+
+ *size = 2 * sizeof(uint32) + sizeof(aCranes);
+ WriteSaveBuf(buf, NumCranes);
+ WriteSaveBuf(buf, CarsCollectedMilitaryCrane);
+ for (int i = 0; i < NUM_CRANES; i++) {
+ CCrane *pCrane = WriteSaveBuf(buf, aCranes[i]);
+ if (pCrane->m_pCraneEntity != nil)
+ pCrane->m_pCraneEntity = (CBuilding*)(CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pCrane->m_pCraneEntity) + 1);
+ if (pCrane->m_pHook != nil)
+ pCrane->m_pHook = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pCrane->m_pHook) + 1);
+ if (pCrane->m_pVehiclePickedUp != nil)
+ pCrane->m_pVehiclePickedUp = (CVehicle*)(CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(pCrane->m_pVehiclePickedUp) + 1);
+ }
+
+ VALIDATESAVEBUF(*size);
+}
+
+void CCranes::Load(uint8* buf, uint32 size)
+{
+ INITSAVEBUF
+
+ NumCranes = ReadSaveBuf<int32>(buf);
+ CarsCollectedMilitaryCrane = ReadSaveBuf<uint32>(buf);
+ for (int i = 0; i < NUM_CRANES; i++)
+ aCranes[i] = ReadSaveBuf<CCrane>(buf);
+ for (int i = 0; i < NUM_CRANES; i++) {
+ CCrane *pCrane = &aCranes[i];
+ if (pCrane->m_pCraneEntity != nil)
+ pCrane->m_pCraneEntity = CPools::GetBuildingPool()->GetSlot((uintptr)pCrane->m_pCraneEntity - 1);
+ if (pCrane->m_pHook != nil)
+ pCrane->m_pHook = CPools::GetObjectPool()->GetSlot((uintptr)pCrane->m_pHook - 1);
+ if (pCrane->m_pVehiclePickedUp != nil)
+ pCrane->m_pVehiclePickedUp = CPools::GetVehiclePool()->GetSlot((uintptr)pCrane->m_pVehiclePickedUp - 1);
+ }
+
+ VALIDATESAVEBUF(size);
+}
diff --git a/src/vehicles/Cranes.h b/src/vehicles/Cranes.h
new file mode 100644
index 0000000..e842ed3
--- /dev/null
+++ b/src/vehicles/Cranes.h
@@ -0,0 +1,96 @@
+#pragma once
+#include "common.h"
+
+#include "World.h"
+
+class CVehicle;
+class CEntity;
+class CObject;
+class CBuilding;
+
+class CCrane
+{
+public:
+ enum CraneState {
+ IDLE = 0,
+ GOING_TOWARDS_TARGET = 1,
+ LIFTING_TARGET = 2,
+ GOING_TOWARDS_TARGET_ONLY_HEIGHT = 3,
+ ROTATING_TARGET = 4,
+ DROPPING_TARGET = 5
+ };
+ enum CraneStatus {
+ NONE = 0,
+ ACTIVATED = 1,
+ DEACTIVATED = 2
+ };
+ CBuilding *m_pCraneEntity;
+ CObject *m_pHook;
+ float m_fPickupX1;
+ float m_fPickupX2;
+ float m_fPickupY1;
+ float m_fPickupY2;
+ CVector m_vecDropoffTarget;
+ float m_fDropoffHeading;
+ float m_fPickupAngle;
+ float m_fDropoffAngle;
+ float m_fPickupDistance;
+ float m_fDropoffDistance;
+ float m_fPickupHeight;
+ float m_fDropoffHeight;
+ float m_fHookAngle;
+ float m_fHookOffset;
+ float m_fHookHeight;
+ CVector m_vecHookInitPos;
+ CVector m_vecHookCurPos;
+ CVector2D m_vecHookVelocity;
+ CVehicle *m_pVehiclePickedUp;
+ uint32 m_nTimeForNextCheck;
+ uint8 m_nCraneStatus;
+ uint8 m_nCraneState;
+ uint8 m_nVehiclesCollected;
+ bool m_bIsCrusher;
+ bool m_bIsMilitaryCrane;
+ bool m_bWasMilitaryCrane;
+ bool m_bIsTop;
+
+ void Init(void) { memset(this, 0, sizeof(*this)); }
+ void Update(void);
+ bool RotateCarriedCarProperly(void);
+ void FindCarInSectorList(CPtrList* pList);
+ bool DoesCranePickUpThisCarType(uint32 mi);
+ bool GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier = 1.0f);
+ bool GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier = 1.0f);
+ void FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight);
+ void CalcHookCoordinates(float* pX, float* pY, float* pZ);
+ void SetHookMatrix(void);
+
+ float GetHeightToPickup() { return 4.0f + m_fPickupHeight + (m_bIsCrusher ? 4.5f : 0.0f); };
+ float GetHeightToDropoff() { return m_bIsCrusher ? (2.0f + m_fDropoffHeight + 3.0f) : (2.0f + m_fDropoffHeight); }
+ float GetHeightToPickupHeight() { return m_fPickupHeight + (m_bIsCrusher ? 7.0f : 4.0f); }
+ float GetHeightToDropoffHeight() { return m_fDropoffHeight + (m_bIsCrusher ? 7.0f : 2.0f); }
+};
+
+VALIDATE_SIZE(CCrane, 128);
+
+class CCranes
+{
+public:
+ static void InitCranes(void);
+ static void AddThisOneCrane(CEntity* pCraneEntity);
+ static void ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY);
+ static void DeActivateCrane(float fX, float fY);
+ static bool IsThisCarPickedUp(float fX, float fY, CVehicle* pVehicle);
+ static void UpdateCranes(void);
+ static bool DoesMilitaryCraneHaveThisOneAlready(uint32 mi);
+ static void RegisterCarForMilitaryCrane(uint32 mi);
+ static bool HaveAllCarsBeenCollectedByMilitaryCrane(void);
+ static bool IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle);
+ static bool IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle);
+ static void Save(uint8* buf, uint32* size);
+ static void Load(uint8* buf, uint32 size); // out of class in III PC and later because of SecuROM
+
+ static uint32 CarsCollectedMilitaryCrane;
+ static int32 NumCranes;
+ static CCrane aCranes[NUM_CRANES];
+};
diff --git a/src/vehicles/DamageManager.cpp b/src/vehicles/DamageManager.cpp
new file mode 100644
index 0000000..8ba235b
--- /dev/null
+++ b/src/vehicles/DamageManager.cpp
@@ -0,0 +1,233 @@
+#include "common.h"
+
+#include "General.h"
+#include "Vehicle.h"
+#include "DamageManager.h"
+
+
+float G_aComponentDamage[] = { 2.5f, 1.25f, 3.2f, 1.4f, 2.5f, 2.8f, 0.5f };
+
+CDamageManager::CDamageManager(void)
+{
+ ResetDamageStatus();
+ m_fWheelDamageEffect = 0.5f;
+ field_18 = 1;
+}
+
+void
+CDamageManager::ResetDamageStatus(void)
+{
+ int i;
+ m_fWheelDamageEffect = 0.0f;
+ m_engineStatus = 0;
+ for(i = 0; i < ARRAY_SIZE(m_wheelStatus); i++) m_wheelStatus[i] = 0;
+ for(i = 0; i < ARRAY_SIZE(m_doorStatus); i++) m_doorStatus[i] = 0;
+ m_lightStatus = 0;
+ m_panelStatus = 0;
+}
+
+void
+CDamageManager::FuckCarCompletely(void)
+{
+ int i;
+
+ m_wheelStatus[0] = WHEEL_STATUS_MISSING;
+ // wheels 1-3 not reset?
+
+ for(i = 0; i < ARRAY_SIZE(m_doorStatus); i++)
+ m_doorStatus[i] = DOOR_STATUS_MISSING;
+
+ for(i = 0; i < 3; i++){
+#ifdef FIX_BUGS
+ ProgressPanelDamage(VEHBUMPER_FRONT);
+ ProgressPanelDamage(VEHBUMPER_REAR);
+#else
+ // this can't be right
+ ProgressPanelDamage(COMPONENT_BUMPER_FRONT);
+ ProgressPanelDamage(COMPONENT_BUMPER_REAR);
+#endif
+ }
+ // Why set to no damage?
+#ifndef FIX_BUGS
+ m_lightStatus = 0;
+ m_panelStatus = 0;
+#endif
+ SetEngineStatus(250);
+}
+
+bool
+CDamageManager::ApplyDamage(tComponent component, float damage, float unused)
+{
+ tComponentGroup group;
+ uint8 subComp;
+
+ GetComponentGroup(component, &group, &subComp);
+ damage *= G_aComponentDamage[group];
+ if(component == COMPONENT_PANEL_WINDSCREEN)
+ damage *= 0.6f;
+ if(damage > 150.0f){
+ switch(group){
+ case COMPGROUP_WHEEL:
+ ProgressWheelDamage(subComp);
+ break;
+ case COMPGROUP_DOOR:
+ case COMPGROUP_BOOT:
+ ProgressDoorDamage(subComp);
+ break;
+ case COMPGROUP_BONNET:
+ if(damage > 220.0f)
+ ProgressEngineDamage();
+ ProgressDoorDamage(subComp);
+ break;
+ case COMPGROUP_PANEL:
+ // so windscreen is a light?
+ SetLightStatus((eLights)subComp, 1);
+ // fall through
+ case COMPGROUP_BUMPER:
+ if(damage > 220.0f &&
+ (component == COMPONENT_PANEL_FRONT_LEFT ||
+ component == COMPONENT_PANEL_FRONT_RIGHT ||
+ component == COMPONENT_PANEL_WINDSCREEN))
+ ProgressEngineDamage();
+ ProgressPanelDamage(subComp);
+ break;
+ default: break;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool
+CDamageManager::GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *subComp)
+{
+ *subComp = -2; // ??
+
+ // This is done very strangely in the game, maybe an optimized switch?
+ if(component >= COMPONENT_PANEL_FRONT_LEFT){
+ if(component >= COMPONENT_BUMPER_FRONT)
+ *componentGroup = COMPGROUP_BUMPER;
+ else
+ *componentGroup = COMPGROUP_PANEL;
+ *subComp = component - COMPONENT_PANEL_FRONT_LEFT;
+ return true;
+ }else if(component >= COMPONENT_DOOR_BONNET){
+ if(component == COMPONENT_DOOR_BONNET)
+ *componentGroup = COMPGROUP_BONNET;
+ else if(component == COMPONENT_DOOR_BOOT)
+ *componentGroup = COMPGROUP_BOOT;
+ else
+ *componentGroup = COMPGROUP_DOOR;
+ *subComp = component - COMPONENT_DOOR_BONNET;
+ return true;
+ }else if(component >= COMPONENT_WHEEL_FRONT_LEFT){
+ *componentGroup = COMPGROUP_WHEEL;
+ *subComp = component - COMPONENT_WHEEL_FRONT_LEFT;
+ return true;
+ }else if(component >= COMPONENT_DEFAULT){
+ *componentGroup = COMPGROUP_DEFAULT;
+ *subComp = COMPONENT_DEFAULT;
+ return true;
+ }else
+ return false;
+}
+
+void
+CDamageManager::SetDoorStatus(int32 door, uint32 status)
+{
+ m_doorStatus[door] = status;
+}
+
+int32
+CDamageManager::GetDoorStatus(int32 door)
+{
+ return m_doorStatus[door];
+}
+
+bool
+CDamageManager::ProgressDoorDamage(uint8 door)
+{
+ int status = GetDoorStatus(door);
+ if(status == PANEL_STATUS_MISSING)
+ return false;
+ SetDoorStatus(door, status+1);
+ return true;
+}
+
+void
+CDamageManager::SetPanelStatus(int32 panel, uint32 status)
+{
+ m_panelStatus = dpb(status, panel*4, 4, m_panelStatus);
+}
+
+int32
+CDamageManager::GetPanelStatus(int32 panel)
+{
+ return ldb(panel*4, 4, m_panelStatus);
+}
+
+bool
+CDamageManager::ProgressPanelDamage(uint8 panel)
+{
+ int status = GetPanelStatus(panel);
+ if(status == DOOR_STATUS_MISSING)
+ return false;
+ SetPanelStatus(panel, status+1);
+ return true;
+}
+
+void
+CDamageManager::SetLightStatus(eLights light, uint32 status)
+{
+ m_lightStatus = dpb(status, light*2, 2, m_lightStatus);
+}
+
+int32
+CDamageManager::GetLightStatus(eLights light)
+{
+ return ldb(light*2, 2, m_lightStatus);
+}
+
+void
+CDamageManager::SetWheelStatus(int32 wheel, uint32 status)
+{
+ m_wheelStatus[wheel] = status;
+}
+
+int32
+CDamageManager::GetWheelStatus(int32 wheel)
+{
+ return m_wheelStatus[wheel];
+}
+
+bool
+CDamageManager::ProgressWheelDamage(uint8 wheel)
+{
+ int status = GetWheelStatus(wheel);
+ if(status == WHEEL_STATUS_MISSING)
+ return false;
+ SetWheelStatus(wheel, status+1);
+ return true;
+}
+
+void
+CDamageManager::SetEngineStatus(uint32 status)
+{
+ if(status > 250)
+ m_engineStatus = 250;
+ else
+ m_engineStatus = status;
+}
+
+int32
+CDamageManager::GetEngineStatus(void)
+{
+ return m_engineStatus;
+}
+
+bool
+CDamageManager::ProgressEngineDamage(void)
+{
+ // gone in VC
+ return false;
+}
diff --git a/src/vehicles/DamageManager.h b/src/vehicles/DamageManager.h
new file mode 100644
index 0000000..312006e
--- /dev/null
+++ b/src/vehicles/DamageManager.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include "common.h"
+
+// TODO: move some of this into Vehicle.h
+
+enum eEngineStatus
+{
+ ENGINE_STATUS_STEAM1 = 100,
+ ENGINE_STATUS_STEAM2 = 150,
+ ENGINE_STATUS_SMOKE = 200,
+ ENGINE_STATUS_ON_FIRE = 225
+};
+
+enum eDoorStatus
+{
+ DOOR_STATUS_OK,
+ DOOR_STATUS_SMASHED,
+ DOOR_STATUS_SWINGING,
+ DOOR_STATUS_MISSING
+};
+
+enum ePanelStatus
+{
+ PANEL_STATUS_OK,
+ PANEL_STATUS_SMASHED1,
+ PANEL_STATUS_SMASHED2,
+ PANEL_STATUS_MISSING,
+};
+
+enum eWheelStatus
+{
+ WHEEL_STATUS_OK,
+ WHEEL_STATUS_BURST,
+ WHEEL_STATUS_MISSING
+};
+
+enum eLightStatus
+{
+ LIGHT_STATUS_OK,
+ LIGHT_STATUS_BROKEN
+};
+
+enum tComponent
+{
+ COMPONENT_DEFAULT,
+ COMPONENT_WHEEL_FRONT_LEFT,
+ COMPONENT_WHEEL_FRONT_RIGHT,
+ COMPONENT_WHEEL_REAR_LEFT,
+ COMPONENT_WHEEL_REAR_RIGHT,
+ COMPONENT_DOOR_BONNET,
+ COMPONENT_DOOR_BOOT,
+ COMPONENT_DOOR_FRONT_LEFT,
+ COMPONENT_DOOR_FRONT_RIGHT,
+ COMPONENT_DOOR_REAR_LEFT,
+ COMPONENT_DOOR_REAR_RIGHT,
+ COMPONENT_PANEL_FRONT_LEFT,
+ COMPONENT_PANEL_FRONT_RIGHT,
+ COMPONENT_PANEL_REAR_LEFT,
+ COMPONENT_PANEL_REAR_RIGHT,
+ COMPONENT_PANEL_WINDSCREEN,
+ COMPONENT_BUMPER_FRONT,
+ COMPONENT_BUMPER_REAR,
+};
+
+enum tComponentGroup
+{
+ COMPGROUP_BUMPER,
+ COMPGROUP_WHEEL,
+ COMPGROUP_DOOR,
+ COMPGROUP_BONNET,
+ COMPGROUP_BOOT,
+ COMPGROUP_PANEL,
+ COMPGROUP_DEFAULT,
+};
+
+enum eLights;
+
+class CDamageManager
+{
+public:
+
+ float m_fWheelDamageEffect;
+ uint8 m_engineStatus;
+ uint8 m_wheelStatus[4];
+ uint8 m_doorStatus[6];
+ uint32 m_lightStatus;
+ uint32 m_panelStatus;
+ uint8 field_18;
+
+ CDamageManager(void);
+
+ void ResetDamageStatus(void);
+ void FuckCarCompletely(void);
+ bool ApplyDamage(tComponent component, float damage, float unused);
+ bool GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *foo);
+
+ void SetDoorStatus(int32 door, uint32 status);
+ int32 GetDoorStatus(int32 door);
+ bool ProgressDoorDamage(uint8 door);
+ void SetPanelStatus(int32 panel, uint32 status);
+ int32 GetPanelStatus(int32 panel);
+ bool ProgressPanelDamage(uint8 panel);
+ // needed for CReplay
+ static int32 GetPanelStatus(uint32 panelstatus, int32 panel) { return ldb(panel*4, 4, panelstatus); }
+ void SetLightStatus(eLights light, uint32 status);
+ int32 GetLightStatus(eLights light);
+ void SetWheelStatus(int32 wheel, uint32 status);
+ int32 GetWheelStatus(int32 wheel);
+ bool ProgressWheelDamage(uint8 wheel);
+ void SetEngineStatus(uint32 status);
+ int32 GetEngineStatus(void);
+ bool ProgressEngineDamage(void);
+};
+VALIDATE_SIZE(CDamageManager, 0x1C);
diff --git a/src/vehicles/Door.cpp b/src/vehicles/Door.cpp
new file mode 100644
index 0000000..c80965a
--- /dev/null
+++ b/src/vehicles/Door.cpp
@@ -0,0 +1,170 @@
+#include "common.h"
+
+#include "Vehicle.h"
+#include "Door.h"
+
+CDoor::CDoor(void)
+{
+ memset(this, 0, sizeof(*this));
+}
+
+void
+CDoor::Open(float ratio)
+{
+ float open;
+
+ m_fPrevAngle = m_fAngle;
+ open = RetAngleWhenOpen();
+ if(ratio < 1.0f){
+ m_fAngle = open*ratio;
+ if(m_fAngle == 0.0f)
+ m_fAngVel = 0.0f;
+ }else{
+ m_nDoorState = DOORST_OPEN;
+ m_fAngle = open;
+ }
+}
+
+void
+CDoor::Process(CVehicle *vehicle)
+{
+ static CVector vecOffset(1.0f, 0.0f, 0.0f);
+ CVector speed = vehicle->GetSpeed(vecOffset);
+ CVector vecSpeedDiff = speed - m_vecSpeed;
+ vecSpeedDiff = Multiply3x3(vecSpeedDiff, vehicle->GetMatrix());
+
+ // air resistance
+ float fSpeedDiff = 0.0f; // uninitialized in game
+ switch(m_nAxis){
+ case 0: // x-axis
+ if(m_nDirn)
+ fSpeedDiff = vecSpeedDiff.y + vecSpeedDiff.z;
+ else
+ fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.z);
+ break;
+
+ // we don't support y axis apparently?
+
+ case 2: // z-axis
+ if(m_nDirn)
+ fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.x);
+ else
+ fSpeedDiff = vecSpeedDiff.y - vecSpeedDiff.x;
+ break;
+ }
+ fSpeedDiff = clamp(fSpeedDiff, -0.2f, 0.2f);
+ if(Abs(fSpeedDiff) > 0.002f)
+ m_fAngVel += fSpeedDiff;
+ m_fAngVel *= 0.945f;
+ m_fAngVel = clamp(m_fAngVel, -0.3f, 0.3f);
+
+ m_fAngle += m_fAngVel;
+ m_nDoorState = DOORST_SWINGING;
+ if(m_fAngle > m_fMaxAngle){
+ m_fAngle = m_fMaxAngle;
+ m_fAngVel *= -0.8f;
+ m_nDoorState = DOORST_OPEN;
+ }
+ if(m_fAngle < m_fMinAngle){
+ m_fAngle = m_fMinAngle;
+ m_fAngVel *= -0.8f;
+ m_nDoorState = DOORST_CLOSED;
+ }
+ m_vecSpeed = speed;
+}
+
+float
+CDoor::RetAngleWhenClosed(void)
+{
+ if(Abs(m_fMaxAngle) < Abs(m_fMinAngle))
+ return m_fMaxAngle;
+ else
+ return m_fMinAngle;
+}
+
+float
+CDoor::RetAngleWhenOpen(void)
+{
+ if(Abs(m_fMaxAngle) < Abs(m_fMinAngle))
+ return m_fMinAngle;
+ else
+ return m_fMaxAngle;
+}
+
+float
+CDoor::GetAngleOpenRatio(void)
+{
+ float open = RetAngleWhenOpen();
+ if(open == 0.0f)
+ return 0.0f;
+ return m_fAngle/open;
+}
+
+bool
+CDoor::IsFullyOpen(void)
+{
+ // why -0.5? that's around 28 deg less than fully open
+ if(Abs(m_fAngle) < Abs(RetAngleWhenOpen()) - 0.5f)
+ return false;
+ return true;
+}
+
+bool
+CDoor::IsClosed(void)
+{
+ return m_fAngle == RetAngleWhenClosed();
+}
+
+
+CTrainDoor::CTrainDoor(void)
+{
+ memset(this, 0, sizeof(*this));
+}
+
+void
+CTrainDoor::Open(float ratio)
+{
+ float open;
+
+ m_fPrevPosn = m_fPosn;
+ open = RetTranslationWhenOpen();
+ if(ratio < 1.0f){
+ m_fPosn = open*ratio;
+ }else{
+ m_nDoorState = DOORST_OPEN;
+ m_fPosn = open;
+ }
+}
+
+float
+CTrainDoor::RetTranslationWhenClosed(void)
+{
+ if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn))
+ return m_fClosedPosn;
+ else
+ return m_fOpenPosn;
+}
+
+float
+CTrainDoor::RetTranslationWhenOpen(void)
+{
+ if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn))
+ return m_fOpenPosn;
+ else
+ return m_fClosedPosn;
+}
+
+bool
+CTrainDoor::IsFullyOpen(void)
+{
+ // 0.5f again...
+ if(Abs(m_fPosn) < Abs(RetTranslationWhenOpen()) - 0.5f)
+ return false;
+ return true;
+}
+
+bool
+CTrainDoor::IsClosed(void)
+{
+ return m_fPosn == RetTranslationWhenClosed();
+}
diff --git a/src/vehicles/Door.h b/src/vehicles/Door.h
new file mode 100644
index 0000000..567d326
--- /dev/null
+++ b/src/vehicles/Door.h
@@ -0,0 +1,69 @@
+#pragma once
+
+class CVehicle;
+
+enum eDoorState
+{
+ DOORST_SWINGING,
+ // actually wrong though,
+ // OPEN is really MAX_ANGLE and CLOSED is MIN_ANGLE
+ DOORST_OPEN,
+ DOORST_CLOSED
+};
+
+class CDoor
+{
+public:
+ float m_fMaxAngle;
+ float m_fMinAngle;
+ // direction of rotation for air resistance
+ int8 m_nDirn;
+ // axis in which this door rotates
+ int8 m_nAxis;
+ int8 m_nDoorState;
+ float m_fAngle;
+ float m_fPrevAngle;
+ float m_fAngVel;
+ CVector m_vecSpeed;
+
+ CDoor(void);
+ void Init(float minAngle, float maxAngle, int8 dir, int8 axis) {
+ m_fMinAngle = minAngle;
+ m_fMaxAngle = maxAngle;
+ m_nDirn = dir;
+ m_nAxis = axis;
+ }
+ void Open(float ratio);
+ void Process(CVehicle *veh);
+ float RetAngleWhenClosed(void); // dead
+ float RetAngleWhenOpen(void);
+ float GetAngleOpenRatio(void);
+ bool IsFullyOpen(void);
+ bool IsClosed(void); // dead
+};
+
+class CTrainDoor
+{
+public:
+ float m_fClosedPosn;
+ float m_fOpenPosn;
+ int8 m_nDirn;
+ int8 m_nDoorState; // same enum as above?
+ int8 m_nAxis;
+ float m_fPosn;
+ float m_fPrevPosn;
+ int field_14; // unused?
+
+ CTrainDoor(void);
+ void Init(float open, float closed, int8 dir, int8 axis) {
+ m_fOpenPosn = open;
+ m_fClosedPosn = closed;
+ m_nDirn = dir;
+ m_nAxis = axis;
+ }
+ bool IsClosed(void);
+ bool IsFullyOpen(void);
+ float RetTranslationWhenClosed(void);
+ float RetTranslationWhenOpen(void);
+ void Open(float ratio);
+};
diff --git a/src/vehicles/Floater.cpp b/src/vehicles/Floater.cpp
new file mode 100644
index 0000000..08688a3
--- /dev/null
+++ b/src/vehicles/Floater.cpp
@@ -0,0 +1,324 @@
+#include "common.h"
+
+#include "Timer.h"
+#include "WaterLevel.h"
+#include "ModelIndices.h"
+#include "Physical.h"
+#include "Vehicle.h"
+#include "Floater.h"
+
+cBuoyancy mod_Buoyancy;
+
+float fVolMultiplier = 1.0f;
+// amount of boat volume in bounding box
+// 1.0-volume is the empty space in the bbox
+float fBoatVolumeDistribution[9] = {
+ // rear
+ 0.75f, 0.9f, 0.75f,
+ 0.95f, 1.0f, 0.95f,
+ 0.4f, 0.7f, 0.4f
+ // bow
+};
+float fBoatVolumeDistributionCat[9] = {
+ 0.9f, 0.3f, 0.9f,
+ 1.0f, 0.5f, 1.0f,
+ 0.95f, 0.4f, 0.95f
+};
+float fBoatVolumeDistributionSail[9] = {
+ 0.55f, 0.95f, 0.55f,
+ 0.75f, 1.1f, 0.75f,
+ 0.3f, 0.8f, 0.3f
+};
+float fBoatVolumeDistributionDinghy[9] = {
+ 0.65f, 0.85f, 0.65f,
+ 0.85f, 1.1f, 0.85f,
+ 0.65f, 0.95f, 0.65f
+};
+float fBoatVolumeDistributionSpeed[9] = {
+ 0.7f, 0.9f, 0.7f,
+ 0.95f, 1.0f, 0.95f,
+ 0.6f, 0.7f, 0.6f
+};
+
+bool
+cBuoyancy::ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse)
+{
+ m_numSteps = 2.0f;
+
+ if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->bTouchingWater))
+ return false;
+ m_matrix = phys->GetMatrix();
+
+ PreCalcSetup(phys, buoyancy);
+ SimpleCalcBuoyancy();
+ float f = CalcBuoyancyForce(phys, point, impulse);
+ if(m_isBoat)
+ return true;
+ return f != 0.0f;
+}
+
+bool
+cBuoyancy::ProcessBuoyancyBoat(CVehicle *veh, float buoyancy, CVector *point, CVector *impulse, bool bNoTurnForce)
+{
+ m_numSteps = 2.0f;
+
+ if(!CWaterLevel::GetWaterLevel(veh->GetPosition(), &m_waterlevel, veh->bTouchingWater))
+ return false;
+ m_matrix = veh->GetMatrix();
+ PreCalcSetup(veh, buoyancy);
+
+
+ float x, y;
+ int ix, i;
+ tWaterLevel waterPosition;
+ CVector waterNormal;
+
+ // Floater is divided into 3x3 parts. Process and sum each of them
+ float volDiv = 1.0f/((m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f));
+ ix = 0;
+ for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){
+ i = ix;
+ for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){
+ CVector waterLevel(x, y, 0.0f);
+ FindWaterLevelNorm(m_positionZ, &waterLevel, &waterPosition, &waterNormal);
+ switch(veh->GetModelIndex()){
+ case MI_RIO:
+ fVolMultiplier = fBoatVolumeDistributionCat[i];
+ break;
+ case MI_SQUALO:
+ case MI_SPEEDER:
+ case MI_JETMAX:
+ fVolMultiplier = fBoatVolumeDistributionSpeed[i];
+ break;
+ case MI_COASTG:
+ case MI_DINGHY:
+ fVolMultiplier = fBoatVolumeDistributionDinghy[i];
+ break;
+ case MI_MARQUIS:
+ fVolMultiplier = fBoatVolumeDistributionSail[i];
+ break;
+ case MI_PREDATOR:
+ case MI_SKIMMER:
+ case MI_REEFER:
+ case MI_TROPIC:
+ default:
+ fVolMultiplier = fBoatVolumeDistribution[i];
+ break;
+ }
+ if(waterPosition != FLOATER_ABOVE_WATER){
+ float volume = SimpleSumBuoyancyData(waterLevel, waterPosition);
+ float upImpulse = volume * volDiv * buoyancy * CTimer::GetTimeStep();
+ CVector speed = veh->GetSpeed(Multiply3x3(veh->GetMatrix(), CVector(x, y, 0.0f)));
+ float damp = 1.0f - DotProduct(speed, waterNormal)*veh->pHandling->fSuspensionDampingLevel;
+ float finalImpulse = upImpulse*Max(damp, 0.0f);
+ impulse->z += finalImpulse;
+ if(!bNoTurnForce)
+ veh->ApplyTurnForce(finalImpulse*waterNormal, Multiply3x3(m_matrix, waterLevel));
+ }
+ i += 3;
+ }
+ ix++;
+ }
+
+ m_volumeUnderWater *= volDiv;
+
+ *point = Multiply3x3(m_matrix, m_impulsePoint);
+ return m_isBoat || m_haveVolume;
+
+}
+
+void
+cBuoyancy::PreCalcSetup(CPhysical *phys, float buoyancy)
+{
+ CColModel *colModel;
+
+ m_isBoat = phys->IsVehicle() && ((CVehicle*)phys)->IsBoat();
+ colModel = phys->GetColModel();
+ m_dimMin = colModel->boundingBox.min;
+ m_dimMax = colModel->boundingBox.max;
+
+ if(m_isBoat){
+ switch(phys->GetModelIndex()){
+ case MI_PREDATOR:
+ default:
+ m_dimMax.y *= 1.05f;
+ m_dimMin.y *= 0.9f;
+ break;
+ case MI_SPEEDER:
+ m_dimMax.y *= 1.25f;
+ m_dimMin.y *= 0.83f;
+ break;
+ case MI_REEFER:
+ m_dimMin.y *= 0.9f;
+ break;
+ case MI_RIO:
+ m_dimMax.y *= 0.9f;
+ m_dimMin.y *= 0.9f;
+ m_dimMax.z += 0.25f;
+ m_dimMin.z -= 0.2f;
+ break;
+ case MI_SQUALO:
+ m_dimMax.y *= 0.9f;
+ m_dimMin.y *= 0.9f;
+ break;
+ case MI_TROPIC:
+ m_dimMax.y *= 1.3f;
+ m_dimMin.y *= 0.82f;
+ m_dimMin.z -= 0.2f;
+ break;
+ case MI_SKIMMER:
+ m_dimMin.y = -m_dimMax.y;
+ m_dimMax.y *= 1.2f;
+ break;
+ case MI_COASTG:
+ m_dimMax.y *= 1.1f;
+ m_dimMin.y *= 0.9f;
+ m_dimMin.z -= 0.3f;
+ break;
+ case MI_DINGHY:
+ m_dimMax.y *= 1.3f;
+ m_dimMin.y *= 0.9f;
+ m_dimMin.z -= 0.2f;
+ break;
+ case MI_MARQUIS:
+ m_dimMax.y *= 1.3f;
+ m_dimMin.y *= 0.9f;
+ break;
+ case MI_JETMAX:
+ m_dimMin.y *= 0.9f;
+ break;
+ }
+ }
+
+ m_step = (m_dimMax - m_dimMin)/m_numSteps;
+
+ if(m_step.z > m_step.x && m_step.z > m_step.y){
+ m_stepRatio.x = m_step.x/m_step.z;
+ m_stepRatio.y = m_step.y/m_step.z;
+ m_stepRatio.z = 1.0f;
+ }else if(m_step.y > m_step.x && m_step.y > m_step.z){
+ m_stepRatio.x = m_step.x/m_step.y;
+ m_stepRatio.y = 1.0f;
+ m_stepRatio.z = m_step.z/m_step.y;
+ }else{
+ m_stepRatio.x = 1.0f;
+ m_stepRatio.y = m_step.y/m_step.x;
+ m_stepRatio.z = m_step.z/m_step.x;
+ }
+
+ m_haveVolume = false;
+ m_numPartialVolumes = 1.0f;
+ m_volumeUnderWater = 0.0f;
+ m_impulsePoint = CVector(0.0f, 0.0f, 0.0f);
+ m_position = phys->GetPosition();
+ m_positionZ = CVector(0.0f, 0.0f, m_position.z);
+ m_buoyancy = buoyancy;
+ m_waterlevel += m_waterLevelInc;
+}
+
+void
+cBuoyancy::SimpleCalcBuoyancy(void)
+{
+ float x, y;
+ tWaterLevel waterPosition;
+
+ // Floater is divided into 3x3 parts. Process and sum each of them
+ for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){
+ for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){
+ CVector waterLevel(x, y, 0.0f);
+ FindWaterLevel(m_positionZ, &waterLevel, &waterPosition);
+ fVolMultiplier = 1.0f;
+ if(waterPosition != FLOATER_ABOVE_WATER)
+ SimpleSumBuoyancyData(waterLevel, waterPosition);
+ }
+ }
+
+ m_volumeUnderWater /= (m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f);
+}
+
+float
+cBuoyancy::SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition)
+{
+ static float fThisVolume;
+ static CVector AverageOfWaterLevel;
+ static float fFraction;
+ static float fRemainingSlice;
+
+ float submerged = Abs(waterLevel.z - m_dimMin.z);
+ // subtract empty space from submerged volume
+ fThisVolume = submerged - (1.0f - fVolMultiplier);
+ if(fThisVolume < 0.0f)
+ return 0.0f;
+
+ if(m_isBoat){
+ fThisVolume *= fVolMultiplier;
+ fThisVolume = sq(fThisVolume);
+ }
+
+ m_volumeUnderWater += fThisVolume;
+
+ AverageOfWaterLevel.x = waterLevel.x * m_stepRatio.x;
+ AverageOfWaterLevel.y = waterLevel.y * m_stepRatio.y;
+ AverageOfWaterLevel.z = (waterLevel.z+m_dimMin.z)/2.0f * m_stepRatio.z;
+
+ if(m_flipAverage)
+ AverageOfWaterLevel = -AverageOfWaterLevel;
+
+ fFraction = 1.0f/m_numPartialVolumes;
+ fRemainingSlice = 1.0f - fFraction;
+ m_impulsePoint = m_impulsePoint*fRemainingSlice + AverageOfWaterLevel*fThisVolume*fFraction;
+ m_numPartialVolumes += 1.0f;
+ m_haveVolume = true;
+ return fThisVolume;
+}
+
+void
+cBuoyancy::FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition)
+{
+ *waterPosition = FLOATER_IN_WATER;
+ // waterLevel is a local x,y point
+ // m_position is the global position of our floater
+ // zpos is the global z coordinate of our floater
+ CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel);
+ CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z,
+ &waterLevel->z, true);
+ waterLevel->z -= xWaterLevel.z + zpos.z; // make local
+ if(waterLevel->z > m_dimMax.z){
+ waterLevel->z = m_dimMax.z;
+ *waterPosition = FLOATER_UNDER_WATER;
+ }else if(waterLevel->z < m_dimMin.z){
+ waterLevel->z = m_dimMin.z;
+ *waterPosition = FLOATER_ABOVE_WATER;
+ }
+}
+
+// Same as above but also get normal
+void
+cBuoyancy::FindWaterLevelNorm(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition, CVector *normal)
+{
+ *waterPosition = FLOATER_IN_WATER;
+ CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel);
+ CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z,
+ &waterLevel->z, true);
+ waterLevel->z -= xWaterLevel.z + zpos.z; // make local
+ if(waterLevel->z >= m_dimMin.z)
+ *normal = CWaterLevel::GetWaterNormal(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y);
+ if(waterLevel->z > m_dimMax.z){
+ waterLevel->z = m_dimMax.z;
+ *waterPosition = FLOATER_UNDER_WATER;
+ }else if(waterLevel->z < m_dimMin.z){
+ waterLevel->z = m_dimMin.z;
+ *waterPosition = FLOATER_ABOVE_WATER;
+ }
+}
+
+bool
+cBuoyancy::CalcBuoyancyForce(CPhysical *phys, CVector *point, CVector *impulse)
+{
+ if(!m_haveVolume)
+ return false;
+
+ *point = Multiply3x3(m_matrix, m_impulsePoint);
+ *impulse = CVector(0.0f, 0.0f, m_volumeUnderWater*m_buoyancy*CTimer::GetTimeStep());
+ return true;
+}
diff --git a/src/vehicles/Floater.h b/src/vehicles/Floater.h
new file mode 100644
index 0000000..91ab70a
--- /dev/null
+++ b/src/vehicles/Floater.h
@@ -0,0 +1,47 @@
+#pragma once
+
+class CPhysical;
+
+enum tWaterLevel
+{
+ FLOATER_ABOVE_WATER,
+ FLOATER_IN_WATER,
+ FLOATER_UNDER_WATER,
+};
+
+class cBuoyancy
+{
+public:
+ CVector m_position;
+ CMatrix m_matrix;
+ int m_field_54;
+ CVector m_positionZ;
+ float m_waterlevel;
+ float m_waterLevelInc;
+ float m_buoyancy;
+ CVector m_dimMax;
+ CVector m_dimMin;
+ float m_numPartialVolumes;
+ int m_field_8C;
+ int m_field_90;
+ int m_field_94;
+ bool m_haveVolume;
+ CVector m_step;
+ CVector m_stepRatio;
+ float m_numSteps;
+ bool m_flipAverage;
+ char m_field_B9;
+ bool m_isBoat;
+ float m_volumeUnderWater;
+ CVector m_impulsePoint;
+
+ bool ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse);
+ bool ProcessBuoyancyBoat(CVehicle *phys, float buoyancy, CVector *point, CVector *impulse, bool bNoTurnForce);
+ void PreCalcSetup(CPhysical *phys, float buoyancy);
+ void SimpleCalcBuoyancy(void);
+ float SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition);
+ void FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition);
+ void FindWaterLevelNorm(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition, CVector *normal);
+ bool CalcBuoyancyForce(CPhysical *phys, CVector *impulse, CVector *point);
+};
+extern cBuoyancy mod_Buoyancy;
diff --git a/src/vehicles/HandlingMgr.cpp b/src/vehicles/HandlingMgr.cpp
new file mode 100644
index 0000000..8438c5c
--- /dev/null
+++ b/src/vehicles/HandlingMgr.cpp
@@ -0,0 +1,434 @@
+#include "common.h"
+
+#include "main.h"
+#include "FileMgr.h"
+#include "Physical.h"
+#include "HandlingMgr.h"
+
+cHandlingDataMgr mod_HandlingManager;
+
+const char *HandlingFilename = "HANDLING.CFG";
+
+const char VehicleNames[NUMHANDLINGS][14] = {
+ "LANDSTAL",
+ "IDAHO",
+ "STINGER",
+ "LINERUN",
+ "PEREN",
+ "SENTINEL",
+ "PATRIOT",
+ "FIRETRUK",
+ "TRASH",
+ "STRETCH",
+ "MANANA",
+ "INFERNUS",
+ "PONY",
+ "MULE",
+ "CHEETAH",
+ "AMBULAN",
+ "FBICAR",
+ "MOONBEAM",
+ "ESPERANT",
+ "TAXI",
+ "KURUMA",
+ "BOBCAT",
+ "MRWHOOP",
+ "BFINJECT",
+ "POLICE",
+ "ENFORCER",
+ "SECURICA",
+ "BANSHEE",
+ "BUS",
+ "RHINO",
+ "BARRACKS",
+ "TRAIN",
+ "HELI",
+ "DODO",
+ "COACH",
+ "CABBIE",
+ "STALLION",
+ "RUMPO",
+ "RCBANDIT",
+ "MAFIA",
+ "AIRTRAIN",
+ "DEADDODO",
+ "FLATBED",
+ "YANKEE",
+ "GOLFCART",
+ "VOODOO",
+ "WASHING",
+ "CUBAN",
+ "ROMERO",
+ "PACKER",
+ "ADMIRAL",
+ "GANGBUR",
+ "ZEBRA",
+ "TOPFUN",
+ "GLENDALE",
+ "OCEANIC",
+ "HERMES",
+ "SABRE1",
+ "SABRETUR",
+ "PHEONIX",
+ "WALTON",
+ "REGINA",
+ "COMET",
+ "DELUXO",
+ "BURRITO",
+ "SPAND",
+ "BAGGAGE",
+ "KAUFMAN",
+ "RANCHER",
+ "FBIRANCH",
+ "VIRGO",
+ "GREENWOO",
+ "HOTRING",
+ "SANDKING",
+ "BLISTAC",
+ "BOXVILLE",
+ "BENSON",
+ "DESPERAD",
+ "LOVEFIST",
+ "BLOODRA",
+ "BLOODRB",
+ "BIKE",
+ "MOPED",
+ "DIRTBIKE",
+ "ANGEL",
+ "FREEWAY",
+ "PREDATOR",
+ "SPEEDER",
+ "REEFER",
+ "RIO",
+ "SQUALO",
+ "TROPIC",
+ "COASTGRD",
+ "DINGHY",
+ "MARQUIS",
+ "CUPBOAT",
+ "SEAPLANE",
+ "SPARROW",
+ "SEASPAR",
+ "MAVERICK",
+ "COASTMAV",
+ "POLMAV",
+ "HUNTER",
+ "RCBARON",
+ "RCGOBLIN",
+ "RCCOPTER"
+};
+
+cHandlingDataMgr::cHandlingDataMgr(void)
+{
+ memset(this, 0, sizeof(*this));
+}
+
+void
+cHandlingDataMgr::Initialise(void)
+{
+ LoadHandlingData();
+ field_0 = 0.1f;
+ fWheelFriction = 0.9f;
+ field_8 = 1.0f;
+ field_C = 0.8f;
+ field_10 = 0.98f;
+}
+
+void
+cHandlingDataMgr::LoadHandlingData(void)
+{
+ char *start, *end;
+ char line[201]; // weird value
+ char delim[4]; // not sure
+ char *word;
+ int field, handlingId;
+ int keepGoing;
+ tHandlingData *handling;
+ tFlyingHandlingData *flyingHandling;
+ tBoatHandlingData *boatHandling;
+ tBikeHandlingData *bikeHandling;
+
+ CFileMgr::SetDir("DATA");
+ CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r");
+ CFileMgr::SetDir("");
+
+ start = (char*)work_buff;
+ end = start+1;
+ handling = nil;
+ flyingHandling = nil;
+ boatHandling = nil;
+ bikeHandling = nil;
+ keepGoing = 1;
+
+ while(keepGoing){
+ // find end of line
+ while(*end != '\n') end++;
+
+ // get line
+ strncpy(line, start, end - start);
+ line[end - start] = '\0';
+ start = end+1;
+ end = start+1;
+
+ // yeah, this is kinda crappy
+ if(strcmp(line, ";the end") == 0)
+ keepGoing = 0;
+ else if(line[0] != ';'){
+ if(line[0] == '!'){
+ // Bike data
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0: break;
+ case 1:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ bikeHandling = GetBikePointer(handlingId);
+ bikeHandling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 2: bikeHandling->fLeanFwdCOM = atof(word); break;
+ case 3: bikeHandling->fLeanFwdForce = atof(word); break;
+ case 4: bikeHandling->fLeanBakCOM = atof(word); break;
+ case 5: bikeHandling->fLeanBackForce = atof(word); break;
+ case 6: bikeHandling->fMaxLean = atof(word); break;
+ case 7: bikeHandling->fFullAnimLean = atof(word); break;
+ case 8: bikeHandling->fDesLean = atof(word); break;
+ case 9: bikeHandling->fSpeedSteer = atof(word); break;
+ case 10: bikeHandling->fSlipSteer = atof(word); break;
+ case 11: bikeHandling->fNoPlayerCOMz = atof(word); break;
+ case 12: bikeHandling->fWheelieAng = atof(word); break;
+ case 13: bikeHandling->fStoppieAng = atof(word); break;
+ case 14: bikeHandling->fWheelieSteer = atof(word); break;
+ case 15: bikeHandling->fWheelieStabMult = atof(word); break;
+ case 16: bikeHandling->fStoppieStabMult = atof(word); break;
+ }
+ field++;
+ }
+ ConvertBikeDataToGameUnits(bikeHandling);
+ }else if(line[0] == '$'){
+ // Flying data
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0: break;
+ case 1:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ flyingHandling = GetFlyingPointer(handlingId);
+ flyingHandling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 2: flyingHandling->fThrust = atof(word); break;
+ case 3: flyingHandling->fThrustFallOff = atof(word); break;
+ case 4: flyingHandling->fYaw = atof(word); break;
+ case 5: flyingHandling->fYawStab = atof(word); break;
+ case 6: flyingHandling->fSideSlip = atof(word); break;
+ case 7: flyingHandling->fRoll = atof(word); break;
+ case 8: flyingHandling->fRollStab = atof(word); break;
+ case 9: flyingHandling->fPitch = atof(word); break;
+ case 10: flyingHandling->fPitchStab = atof(word); break;
+ case 11: flyingHandling->fFormLift = atof(word); break;
+ case 12: flyingHandling->fAttackLift = atof(word); break;
+ case 13: flyingHandling->fMoveRes = atof(word); break;
+ case 14: flyingHandling->vecTurnRes.x = atof(word); break;
+ case 15: flyingHandling->vecTurnRes.y = atof(word); break;
+ case 16: flyingHandling->vecTurnRes.z = atof(word); break;
+ case 17: flyingHandling->vecSpeedRes.x = atof(word); break;
+ case 18: flyingHandling->vecSpeedRes.y = atof(word); break;
+ case 19: flyingHandling->vecSpeedRes.z = atof(word); break;
+ }
+ field++;
+ }
+ }else if(line[0] == '%'){
+ // Boat data
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0: break;
+ case 1:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ boatHandling = GetBoatPointer(handlingId);
+ boatHandling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 2: boatHandling->fThrustY = atof(word); break;
+ case 3: boatHandling->fThrustZ = atof(word); break;
+ case 4: boatHandling->fThrustAppZ = atof(word); break;
+ case 5: boatHandling->fAqPlaneForce = atof(word); break;
+ case 6: boatHandling->fAqPlaneLimit = atof(word); break;
+ case 7: boatHandling->fAqPlaneOffset = atof(word); break;
+ case 8: boatHandling->fWaveAudioMult = atof(word); break;
+ case 9: boatHandling->vecMoveRes.x = atof(word); break;
+ case 10: boatHandling->vecMoveRes.y = atof(word); break;
+ case 11: boatHandling->vecMoveRes.z = atof(word); break;
+ case 12: boatHandling->vecTurnRes.x = atof(word); break;
+ case 13: boatHandling->vecTurnRes.y = atof(word); break;
+ case 14: boatHandling->vecTurnRes.z = atof(word); break;
+ case 15: boatHandling->fLook_L_R_BehindCamHeight = atof(word); break;
+ }
+ field++;
+ }
+ }else{
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ handling = &HandlingData[handlingId];
+ handling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 1: handling->fMass = atof(word); break;
+ case 2: handling->Dimension.x = atof(word); break;
+ case 3: handling->Dimension.y = atof(word); break;
+ case 4: handling->Dimension.z = atof(word); break;
+ case 5: handling->CentreOfMass.x = atof(word); break;
+ case 6: handling->CentreOfMass.y = atof(word); break;
+ case 7: handling->CentreOfMass.z = atof(word); break;
+ case 8: handling->nPercentSubmerged = atoi(word); break;
+ case 9: handling->fTractionMultiplier = atof(word); break;
+ case 10: handling->fTractionLoss = atof(word); break;
+ case 11: handling->fTractionBias = atof(word); break;
+ case 12: handling->Transmission.nNumberOfGears = atoi(word); break;
+ case 13: handling->Transmission.fMaxVelocity = atof(word); break;
+ case 14: handling->Transmission.fEngineAcceleration = atof(word) * 0.4; break;
+ case 15: handling->Transmission.nDriveType = word[0]; break;
+ case 16: handling->Transmission.nEngineType = word[0]; break;
+ case 17: handling->fBrakeDeceleration = atof(word); break;
+ case 18: handling->fBrakeBias = atof(word); break;
+ case 19: handling->bABS = !!atoi(word); break;
+ case 20: handling->fSteeringLock = atof(word); break;
+ case 21: handling->fSuspensionForceLevel = atof(word); break;
+ case 22: handling->fSuspensionDampingLevel = atof(word); break;
+ case 23: handling->fSeatOffsetDistance = atof(word); break;
+ case 24: handling->fCollisionDamageMultiplier = atof(word); break;
+ case 25: handling->nMonetaryValue = atoi(word); break;
+ case 26: handling->fSuspensionUpperLimit = atof(word); break;
+ case 27: handling->fSuspensionLowerLimit = atof(word); break;
+ case 28: handling->fSuspensionBias = atof(word); break;
+ case 29: handling->fSuspensionAntidiveMultiplier = atof(word); break;
+ case 30:
+ sscanf(word, "%x", &handling->Flags);
+ handling->Transmission.Flags = handling->Flags;
+ break;
+ case 31: handling->FrontLights = atoi(word); break;
+ case 32: handling->RearLights = atoi(word); break;
+ }
+ field++;
+ }
+ ConvertDataToGameUnits(handling);
+ }
+ }
+ }
+}
+
+int
+cHandlingDataMgr::FindExactWord(const char *word, const char *words, int wordLen, int numWords)
+{
+ int i;
+
+ for(i = 0; i < numWords; i++){
+ // BUG: the game does something really stupid here, it's fixed here
+ if(strncmp(word, words, wordLen) == 0)
+ return i;
+ words += wordLen;
+ }
+ return numWords;
+}
+
+
+void
+cHandlingDataMgr::ConvertDataToGameUnits(tHandlingData *handling)
+{
+ // convert distance to m, time to 1/50s
+ float velocity, a, b;
+
+ handling->Transmission.fEngineAcceleration *= 1.0f/(50.0f*50.0f);
+ handling->Transmission.fMaxVelocity *= 1000.0f/(60.0f*60.0f * 50.0f);
+ handling->fBrakeDeceleration *= 1.0f/(50.0f*50.0f);
+ handling->fTurnMass = (sq(handling->Dimension.x) + sq(handling->Dimension.y)) * handling->fMass / 12.0f;
+ if(handling->fTurnMass < 10.0f)
+ handling->fTurnMass *= 5.0f;
+ handling->fInvMass = 1.0f/handling->fMass;
+ handling->fCollisionDamageMultiplier *= 2000.0f/handling->fMass;
+ handling->fBuoyancy = 100.0f/handling->nPercentSubmerged * GRAVITY*handling->fMass;
+
+ // Don't quite understand this. What seems to be going on is that
+ // we calculate a drag (air resistance) deceleration for a given velocity and
+ // find the intersection between that and the max engine acceleration.
+ // at that point the car cannot accelerate any further and we've found the max velocity.
+ a = 0.0f;
+ b = 100.0f;
+ velocity = handling->Transmission.fMaxVelocity;
+ while(a < b && velocity > 0.0f){
+ velocity -= 0.01f;
+ // what's the 1/6?
+ a = handling->Transmission.fEngineAcceleration/6.0f;
+ // no density or drag coefficient here...
+ float a_drag = 0.5f*SQR(velocity) * handling->Dimension.x*handling->Dimension.z / handling->fMass;
+ // can't make sense of this... maybe v - v/(drag + 1) ? but that doesn't make so much sense either
+ b = -velocity * (1.0f/(a_drag + 1.0f) - 1.0f);
+ }
+
+ if(handling->nIdentifier == HANDLING_RCBANDIT){
+ handling->Transmission.fMaxCruiseVelocity = handling->Transmission.fMaxVelocity;
+ handling->Transmission.fMaxReverseVelocity = -handling->Transmission.fMaxVelocity;
+ }else if(handling->nIdentifier >= HANDLING_BIKE && handling->nIdentifier <= HANDLING_FREEWAY){
+ handling->Transmission.fMaxCruiseVelocity = velocity;
+ handling->Transmission.fMaxVelocity = velocity * 1.2f;
+ handling->Transmission.fMaxReverseVelocity = -0.05f;
+ }else{
+ handling->Transmission.fMaxCruiseVelocity = velocity;
+ handling->Transmission.fMaxVelocity = velocity * 1.2f;
+ handling->Transmission.fMaxReverseVelocity = -0.2f;
+ }
+
+ if(handling->Transmission.nDriveType == '4')
+ handling->Transmission.fEngineAcceleration /= 4.0f;
+ else
+ handling->Transmission.fEngineAcceleration /= 2.0f;
+
+ handling->Transmission.InitGearRatios();
+}
+
+void
+cHandlingDataMgr::ConvertBikeDataToGameUnits(tBikeHandlingData *handling)
+{
+ handling->fMaxLean = Sin(DEGTORAD(handling->fMaxLean));
+ handling->fFullAnimLean = DEGTORAD(handling->fFullAnimLean);
+ handling->fWheelieAng = Sin(DEGTORAD(handling->fWheelieAng));
+ handling->fStoppieAng = Sin(DEGTORAD(handling->fStoppieAng));
+}
+
+int32
+cHandlingDataMgr::GetHandlingId(const char *name)
+{
+ int i;
+ for(i = 0; i < NUMHANDLINGS; i++)
+ if(strncmp(VehicleNames[i], name, 14) == 0)
+ break;
+ return i;
+}
+
+tFlyingHandlingData*
+cHandlingDataMgr::GetFlyingPointer(uint8 id)
+{
+ if(id >= HANDLING_SEAPLANE && id <= HANDLING_RCCOPTER)
+ return &FlyingHandlingData[id-HANDLING_SEAPLANE];
+ return &FlyingHandlingData[0];
+}
+
+tBoatHandlingData*
+cHandlingDataMgr::GetBoatPointer(uint8 id)
+{
+ if(id >= HANDLING_PREDATOR && id <= HANDLING_SEAPLANE)
+ return &BoatHandlingData[id-HANDLING_PREDATOR];
+ return &BoatHandlingData[0];
+}
diff --git a/src/vehicles/HandlingMgr.h b/src/vehicles/HandlingMgr.h
new file mode 100644
index 0000000..8d290f7
--- /dev/null
+++ b/src/vehicles/HandlingMgr.h
@@ -0,0 +1,279 @@
+#pragma once
+
+#include "Transmission.h"
+
+enum tVehicleType
+{
+ HANDLING_LANDSTAL,
+ HANDLING_IDAHO,
+ HANDLING_STINGER,
+ HANDLING_LINERUN,
+ HANDLING_PEREN,
+ HANDLING_SENTINEL,
+ HANDLING_PATRIOT,
+ HANDLING_FIRETRUK,
+ HANDLING_TRASH,
+ HANDLING_STRETCH,
+ HANDLING_MANANA,
+ HANDLING_INFERNUS,
+ HANDLING_PONY,
+ HANDLING_MULE,
+ HANDLING_CHEETAH,
+ HANDLING_AMBULAN,
+ HANDLING_FBICAR,
+ HANDLING_MOONBEAM,
+ HANDLING_ESPERANT,
+ HANDLING_TAXI,
+ HANDLING_KURUMA,
+ HANDLING_BOBCAT,
+ HANDLING_MRWHOOP,
+ HANDLING_BFINJECT,
+ HANDLING_POLICE,
+ HANDLING_ENFORCER,
+ HANDLING_SECURICA,
+ HANDLING_BANSHEE,
+ HANDLING_BUS,
+ HANDLING_RHINO,
+ HANDLING_BARRACKS,
+ HANDLING_TRAIN,
+ HANDLING_HELI,
+ HANDLING_DODO,
+ HANDLING_COACH,
+ HANDLING_CABBIE,
+ HANDLING_STALLION,
+ HANDLING_RUMPO,
+ HANDLING_RCBANDIT,
+ HANDLING_MAFIA,
+ HANDLING_AIRTRAIN,
+ HANDLING_DEADDODO,
+ HANDLING_FLATBED,
+ HANDLING_YANKEE,
+ HANDLING_GOLFCART,
+ HANDLING_VOODOO,
+ HANDLING_WASHING,
+ HANDLING_CUBAN,
+ HANDLING_ROMERO,
+ HANDLING_PACKER,
+ HANDLING_ADMIRAL,
+ HANDLING_GANGBUR,
+ HANDLING_ZEBRA,
+ HANDLING_TOPFUN,
+ HANDLING_GLENDALE,
+ HANDLING_OCEANIC,
+ HANDLING_HERMES,
+ HANDLING_SABRE1,
+ HANDLING_SABRETUR,
+ HANDLING_PHEONIX,
+ HANDLING_WALTON,
+ HANDLING_REGINA,
+ HANDLING_COMET,
+ HANDLING_DELUXO,
+ HANDLING_BURRITO,
+ HANDLING_SPAND,
+ HANDLING_BAGGAGE,
+ HANDLING_KAUFMAN,
+ HANDLING_RANCHER,
+ HANDLING_FBIRANCH,
+ HANDLING_VIRGO,
+ HANDLING_GREENWOO,
+ HANDLING_HOTRING,
+ HANDLING_SANDKING,
+ HANDLING_BLISTAC,
+ HANDLING_BOXVILLE,
+ HANDLING_BENSON,
+ HANDLING_DESPERAD,
+ HANDLING_LOVEFIST,
+ HANDLING_BLOODRA,
+ HANDLING_BLOODRB,
+
+ HANDLING_BIKE,
+ HANDLING_MOPED,
+ HANDLING_DIRTBIKE,
+ HANDLING_ANGEL,
+ HANDLING_FREEWAY,
+
+ HANDLING_PREDATOR,
+ HANDLING_SPEEDER,
+ HANDLING_REEFER,
+ HANDLING_RIO,
+ HANDLING_SQUALO,
+ HANDLING_TROPIC,
+ HANDLING_COASTGRD,
+ HANDLING_DINGHY,
+ HANDLING_MARQUIS,
+ HANDLING_CUPBOAT,
+ HANDLING_SEAPLANE, // both boat and plane!
+ HANDLING_SPARROW,
+ HANDLING_SEASPAR,
+ HANDLING_MAVERICK,
+ HANDLING_COASTMAV,
+ HANDLING_POLMAV,
+ HANDLING_HUNTER,
+ HANDLING_RCBARON,
+ HANDLING_RCGOBLIN,
+ HANDLING_RCCOPTER,
+
+ NUMHANDLINGS,
+
+ NUMBIKEHANDLINGS = HANDLING_FREEWAY+1 - HANDLING_BIKE,
+ NUMFLYINGHANDLINGS = HANDLING_RCCOPTER+1 - HANDLING_SEAPLANE,
+ NUMBOATHANDLINGS = HANDLING_SEAPLANE+1 - HANDLING_PREDATOR,
+};
+
+enum tField // most likely a handling field enum, never used so :shrug:
+{
+
+};
+
+enum
+{
+ HANDLING_1G_BOOST = 1,
+ HANDLING_2G_BOOST = 2,
+ HANDLING_REV_BONNET = 4,
+ HANDLING_HANGING_BOOT = 8,
+ HANDLING_NO_DOORS = 0x10,
+ HANDLING_IS_VAN = 0x20,
+ HANDLING_IS_BUS = 0x40,
+ HANDLING_IS_LOW = 0x80,
+ HANDLING_DBL_EXHAUST = 0x100,
+ HANDLING_TAILGATE_BOOT = 0x200,
+ HANDLING_NOSWING_BOOT = 0x400,
+ HANDLING_NONPLAYER_STABILISER = 0x800,
+ HANDLING_NEUTRALHANDLING = 0x1000,
+ HANDLING_HAS_NO_ROOF = 0x2000,
+ HANDLING_IS_BIG = 0x4000,
+ HANDLING_HALOGEN_LIGHTS = 0x8000,
+ HANDLING_IS_BIKE = 0x10000,
+ HANDLING_IS_HELI = 0x20000,
+ HANDLING_IS_PLANE = 0x40000,
+ HANDLING_IS_BOAT = 0x80000,
+ HANDLING_NO_EXHAUST = 0x100000,
+ HANDLING_REARWHEEL_1ST = 0x200000,
+ HANDLING_HANDBRAKE_TYRE = 0x400000,
+ HANDLING_SIT_IN_BOAT = 0x800000,
+ HANDLING_FAT_REARW = 0x1000000,
+ HANDLING_NARROW_FRONTW = 0x2000000,
+ HANDLING_GOOD_INSAND = 0x4000000,
+ HANDLING_UNKNOWN = 0x8000000, // something for helis and planes
+};
+
+struct tHandlingData
+{
+ tVehicleType nIdentifier;
+ float fMass;
+ float fInvMass;
+ float fTurnMass;
+ CVector Dimension;
+ CVector CentreOfMass;
+ int8 nPercentSubmerged;
+ float fBuoyancy;
+ float fTractionMultiplier;
+ cTransmission Transmission;
+ float fBrakeDeceleration;
+ float fBrakeBias;
+ int8 bABS;
+ float fSteeringLock;
+ float fTractionLoss;
+ float fTractionBias;
+ float fUnused;
+ float fSuspensionForceLevel;
+ float fSuspensionDampingLevel;
+ float fSuspensionUpperLimit;
+ float fSuspensionLowerLimit;
+ float fSuspensionBias;
+ float fSuspensionAntidiveMultiplier;
+ float fCollisionDamageMultiplier;
+ uint32 Flags;
+ float fSeatOffsetDistance;
+ int32 nMonetaryValue;
+ int8 FrontLights;
+ int8 RearLights;
+};
+
+struct tBikeHandlingData
+{
+ tVehicleType nIdentifier;
+ float fLeanFwdCOM;
+ float fLeanFwdForce;
+ float fLeanBakCOM;
+ float fLeanBackForce;
+ float fMaxLean;
+ float fFullAnimLean;
+ float fDesLean;
+ float fSpeedSteer;
+ float fSlipSteer;
+ float fNoPlayerCOMz;
+ float fWheelieAng;
+ float fStoppieAng;
+ float fWheelieSteer;
+ float fWheelieStabMult;
+ float fStoppieStabMult;
+};
+
+struct tBoatHandlingData
+{
+ tVehicleType nIdentifier;
+ float fThrustY;
+ float fThrustZ;
+ float fThrustAppZ;
+ float fAqPlaneForce;
+ float fAqPlaneLimit;
+ float fAqPlaneOffset;
+ float fWaveAudioMult;
+ float fLook_L_R_BehindCamHeight;
+ CVector vecMoveRes;
+ CVector vecTurnRes;
+};
+
+struct tFlyingHandlingData
+{
+ tVehicleType nIdentifier;
+ float fThrust;
+ float fThrustFallOff;
+ float fYaw;
+ float fYawStab;
+ float fSideSlip;
+ float fRoll;
+ float fRollStab;
+ float fPitch;
+ float fPitchStab;
+ float fFormLift;
+ float fAttackLift;
+ float fMoveRes;
+ CVector vecTurnRes;
+ CVector vecSpeedRes;
+};
+
+class CVehicle;
+
+class cHandlingDataMgr
+{
+ float field_0; // unused it seems
+public:
+ float fWheelFriction; // wheel related
+private:
+ float field_8; //
+ float field_C; // unused it seems
+ float field_10; //
+ tHandlingData HandlingData[NUMHANDLINGS];
+ tBikeHandlingData BikeHandlingData[NUMBIKEHANDLINGS];
+ tFlyingHandlingData FlyingHandlingData[NUMFLYINGHANDLINGS];
+ tBoatHandlingData BoatHandlingData[NUMBOATHANDLINGS];
+
+public:
+ cHandlingDataMgr(void);
+ void Initialise(void);
+ void LoadHandlingData(void);
+ int FindExactWord(const char *word, const char *words, int wordLen, int numWords);
+ void ConvertDataToWorldUnits(tHandlingData *handling);
+ void ConvertDataToGameUnits(tHandlingData *handling);
+ void ConvertBikeDataToGameUnits(tBikeHandlingData *handling);
+ int32 GetHandlingId(const char *name);
+ tHandlingData *GetHandlingData(tVehicleType id) { return &HandlingData[id]; }
+ tBikeHandlingData *GetBikePointer(uint8 id) { return &BikeHandlingData[id-HANDLING_BIKE]; }
+ tFlyingHandlingData *GetFlyingPointer(uint8 id);
+ tBoatHandlingData *GetBoatPointer(uint8 id);
+ bool HasRearWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'F'; }
+ bool HasFrontWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'R'; }
+};
+extern cHandlingDataMgr mod_HandlingManager;
diff --git a/src/vehicles/Heli.cpp b/src/vehicles/Heli.cpp
new file mode 100644
index 0000000..bf14416
--- /dev/null
+++ b/src/vehicles/Heli.cpp
@@ -0,0 +1,1049 @@
+#include "common.h"
+#include "main.h"
+
+#include "General.h"
+#include "Darkel.h"
+#include "Stats.h"
+#include "SurfaceTable.h"
+#include "ModelIndices.h"
+#include "Streaming.h"
+#include "Camera.h"
+#include "VisibilityPlugins.h"
+#include "ZoneCull.h"
+#include "Particle.h"
+#include "Shadows.h"
+#include "Coronas.h"
+#include "Explosion.h"
+#include "WindModifiers.h"
+#include "Timecycle.h"
+#include "TempColModels.h"
+#include "World.h"
+#include "WaterLevel.h"
+#include "Population.h"
+#include "PlayerPed.h"
+#include "CopPed.h"
+#include "Wanted.h"
+#include "DMAudio.h"
+#include "Object.h"
+#include "HandlingMgr.h"
+#include "Ropes.h"
+#include "Heli.h"
+#ifdef FIX_BUGS
+#include "Replay.h"
+#endif
+
+enum
+{
+ HELI_STATUS_HOVER,
+ HELI_STATUS_CHASE_PLAYER,
+ HELI_STATUS_FLY_AWAY,
+ HELI_STATUS_SHOT_DOWN,
+ HELI_STATUS_HOVER2,
+};
+
+CHeli *CHeli::pHelis[NUM_HELIS];
+int16 CHeli::NumRandomHelis;
+uint32 CHeli::TestForNewRandomHelisTimer;
+bool CHeli::CatalinaHeliOn;
+bool CHeli::CatalinaHasBeenShotDown;
+bool CHeli::ScriptHeliOn;
+
+CHeli::CHeli(int32 id, uint8 CreatedBy)
+ : CVehicle(CreatedBy)
+{
+ int i;
+
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
+ m_vehType = VEHICLE_TYPE_HELI;
+ pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
+ SetModelIndex(id);
+ m_heliStatus = HELI_STATUS_HOVER;
+ m_pathState = 0;
+
+ m_fMass = 100000000.0f;
+ m_fTurnMass = 100000000.0f;
+ m_fAirResistance = 0.9994f;
+ m_fElasticity = 0.05f;
+
+ m_nHeliId = 0;
+ m_fRotorRotation = 0.0f;
+ m_nBulletDamage = 0;
+ m_fAngularSpeed = 0.0f;
+ m_fRotation = 0.0f;
+
+ m_numSwat = 4;
+
+ m_nSearchLightTimer = CTimer::GetTimeInMilliseconds();
+ for(i = 0; i < 6; i++){
+ m_aSearchLightHistoryX[i] = 0.0f;
+ m_aSearchLightHistoryY[i] = 0.0f;
+ }
+
+ for(i = 0; i < 8; i++)
+ m_fHeliDustZ[i] = -50.0f;
+
+ m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds();
+ SetStatus(STATUS_HELI);
+ m_bTestRight = true;
+ m_fTargetOffset = 0.0f;
+ m_fSearchLightX = m_fSearchLightY = 0.0f;
+
+ m_aSwatState[0] = m_aSwatState[1] = m_aSwatState[2] = m_aSwatState[3] = 0;
+
+ // BUG: not in game but gets initialized to CDCDCDCD in debug
+ m_nLastShotTime = 0;
+}
+
+void
+CHeli::SetModelIndex(uint32 id)
+{
+ int i;
+
+ CVehicle::SetModelIndex(id);
+ for(i = 0; i < NUM_HELI_NODES; i++)
+ m_aHeliNodes[i] = nil;
+ CClumpModelInfo::FillFrameArray(GetClump(), m_aHeliNodes);
+}
+
+static float CatalinaTargetX[7] = { -478.0, -677.0, -907.0, -1095.0, -1152.0, -1161.0, -1161.0 };
+static float CatalinaTargetY[7] = { 227.0, 206.0, 210.0, 242.0, 278.0, 341.0, 341.0 };
+static float CatalinaTargetZ[7] = { 77.0, 66.0, 60.0, 53.0, 51.0, 46.0, 30.0 };
+static float DamPathX[6] = { -1191.0, -1176.0, -1128.0, -1072.0, -1007.0, -971.0 };
+static float DamPathY[6] = { 350.0, 388.0, 429.0, 447.0, 449.0, 416.0 };
+static float DamPathZ[6] = { 42.0, 37.0, 28.0, 28.0, 31.0, 33.0 };
+static float ShortPathX[4] = { -974.0, -1036.0, -1112.0, -1173.0 };
+static float ShortPathY[4] = { 340.0, 312.0, 317.0, 294.0 };
+static float ShortPathZ[4] = { 41.0, 38.0, 32.0, 39.0 };
+static float LongPathX[7] = { -934.0, -905.0, -906.0, -1063.0, -1204.0, -1233.0, -1207.0 };
+static float LongPathY[7] = { 371.0, 362.0, 488.0, 548.0, 451.0, 346.0, 308.0 };
+static float LongPathZ[7] = { 57.0, 90.0, 105.0, 100.0, 81.0, 79.0, 70.0 };
+
+static int PathPoint;
+
+void
+CHeli::ProcessControl(void)
+{
+ int i;
+
+ if(gbModelViewer)
+ return;
+
+ CWindModifiers::RegisterOne(GetPosition(), 1);
+
+ // Find target
+ CVector target(0.0f, 0.0f, 0.0f);
+ CVector2D vTargetDist;
+ if(m_heliType == HELI_TYPE_CATALINA && m_heliStatus != HELI_STATUS_SHOT_DOWN){
+ switch(m_pathState){
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ target.x = CatalinaTargetX[m_pathState];
+ target.y = CatalinaTargetY[m_pathState];
+ target.z = CatalinaTargetZ[m_pathState];
+ if((target - GetPosition()).Magnitude() < 9.0f)
+ m_pathState++;
+ break;
+ case 6:
+ target.x = CatalinaTargetX[m_pathState];
+ target.y = CatalinaTargetY[m_pathState];
+ target.z = CatalinaTargetZ[m_pathState];
+ if(GetPosition().z > 31.55f)
+ break;
+ m_pathState = 7;
+ GetMatrix().GetPosition().z = 31.55f;
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ break;
+ case 7:
+ GetMatrix().GetPosition().z = 31.55f;
+ target = GetPosition();
+ break;
+
+
+ // Take off
+ case 8:
+ target.x = GetPosition().x;
+ target.y = GetPosition().y;
+ target.z = 74.0f;
+ if(GetPosition().z < 40.0f)
+ break;
+ PathPoint = 2;
+ m_pathState = 9;
+ break;
+ // Circle around dam
+ case 9:
+ target.x = DamPathX[PathPoint];
+ target.y = DamPathY[PathPoint];
+ target.z = DamPathZ[PathPoint];
+ if((target - GetPosition()).Magnitude() < 9.0f){
+ PathPoint++;
+ if(PathPoint >= 6){
+ m_pathState = 10;
+ PathPoint = 0;
+ }
+ }
+ break;
+ case 10:
+ target.x = ShortPathX[PathPoint];
+ target.y = ShortPathY[PathPoint];
+ target.z = ShortPathZ[PathPoint];
+ if((target - GetPosition()).Magnitude() < 9.0f){
+ PathPoint++;
+ if(PathPoint >= 3){
+ m_pathState = 9;
+ PathPoint = 1;
+ }
+ }
+ break;
+ // how do we get here?
+ case 11:
+ target.x = LongPathX[PathPoint];
+ target.y = LongPathY[PathPoint];
+ target.z = LongPathZ[PathPoint];
+ if((target - GetPosition()).Magnitude() < 9.0f){
+ PathPoint++;
+ if(PathPoint >= 7){
+ m_pathState = 9;
+ PathPoint = 0;
+ }
+ }
+ break;
+
+
+ // Fly away
+ case 12:
+ target.x = GetPosition().x;
+ target.y = GetPosition().y;
+ target.z = 200.0f;
+ break;
+ }
+
+ vTargetDist = target - GetPosition();
+ m_fTargetZ = target.z;
+ if(m_pathState == 6){
+ GetMatrix().GetPosition().x = GetMatrix().GetPosition().x*0.99f + target.x*0.01f;
+ GetMatrix().GetPosition().y = GetMatrix().GetPosition().y*0.99f + target.y*0.01f;
+ }
+ }else{
+ vTargetDist = FindPlayerCoors() - GetPosition();
+ m_fTargetZ = FindPlayerCoors().z;
+
+ // Heli flies away to (0, 0)
+ if(m_heliStatus == HELI_STATUS_FLY_AWAY && GetPosition().z > 20.0f){
+ vTargetDist.x = 0.0f - GetPosition().x;
+ vTargetDist.y = 0.0f - GetPosition().y;
+ }
+
+ float groundZ;
+ switch(m_heliStatus){
+ case HELI_STATUS_HOVER:
+ groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil);
+ m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f;
+ break;
+ case HELI_STATUS_SHOT_DOWN:
+ groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil);
+ m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f + m_fTargetOffset;
+ break;
+ case HELI_STATUS_HOVER2:
+ groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil);
+ m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f + m_fTargetOffset;
+ break;
+ default:
+ groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil);
+ m_fTargetZ = Max(groundZ, m_fTargetZ) + 12.0f;
+ break;
+ }
+
+ // Move up if too low
+ if(GetPosition().z - 2.0f < groundZ && m_heliStatus != HELI_STATUS_SHOT_DOWN)
+ m_vecMoveSpeed.z += CTimer::GetTimeStep()*0.01f;
+ m_vecMoveSpeed.z = clamp(m_vecMoveSpeed.z, -0.3f, 0.3f);
+ }
+
+ float fTargetDist = vTargetDist.Magnitude();
+
+ switch(m_heliStatus){
+ case HELI_STATUS_HOVER:
+ case HELI_STATUS_HOVER2:{
+ float targetHeight;
+ if(m_heliType == HELI_TYPE_CATALINA)
+ targetHeight = 8.0f;
+ else
+ targetHeight = 40.0f - m_nHeliId*10.0f;
+ if(fTargetDist > targetHeight)
+ m_heliStatus = HELI_STATUS_CHASE_PLAYER;
+ }
+ if(m_numSwat)
+ SendDownSwat();
+ break;
+ case HELI_STATUS_CHASE_PLAYER:{
+ float targetHeight;
+ if(m_heliType == HELI_TYPE_CATALINA)
+ targetHeight = 4.0f;
+ else
+ targetHeight = 30.0f - m_nHeliId*7.5f;
+ if(fTargetDist < 1.0f ||
+ fTargetDist < targetHeight && CWorld::GetIsLineOfSightClear(GetPosition(), FindPlayerCoors(), true, false, false, false, false, false))
+ m_heliStatus = HELI_STATUS_HOVER;
+ }
+ break;
+ }
+
+ // Find xy speed
+ float speed;
+ if(fTargetDist > 100.0f)
+ speed = 1.0f;
+ else if(fTargetDist > 75.0f)
+ speed = 0.7f;
+ else
+ speed = 0.4f;
+ if(m_heliStatus == HELI_STATUS_HOVER || m_heliStatus == HELI_STATUS_HOVER2 || m_heliStatus == HELI_STATUS_SHOT_DOWN)
+ speed = 0.0f;
+
+ if(fTargetDist != 0.0f)
+ vTargetDist /= fTargetDist;
+ else
+ vTargetDist.x = 1.0f;
+ CVector2D targetSpeed = vTargetDist * speed;
+
+ if(m_heliStatus == HELI_STATUS_HOVER2 || m_heliStatus == HELI_STATUS_SHOT_DOWN){
+ bool force = !!((CTimer::GetFrameCounter() + m_randomSeed) & 8);
+ if(m_bTestRight){
+ if(force || CWorld::TestSphereAgainstWorld(GetPosition() + 4.0f*GetRight(), 2.0f, this, true, false, false, false, false, false) == nil){
+ if(m_heliStatus == HELI_STATUS_SHOT_DOWN){
+ m_fTargetOffset -= CTimer::GetTimeStep()*0.05f;
+ targetSpeed.x -= -vTargetDist.x*0.15f;
+ targetSpeed.y -= vTargetDist.y*0.15f;
+ }else{
+ targetSpeed.x -= -vTargetDist.x*0.05f;
+ targetSpeed.y -= vTargetDist.y*0.05f;
+ }
+ }else{
+ m_bTestRight = false;
+ if(m_heliStatus == HELI_STATUS_HOVER2)
+ m_fTargetOffset += 5.0f;
+ else
+ m_fTargetOffset -= 5.0f;
+ }
+ }else{
+ if(force || CWorld::TestSphereAgainstWorld(GetPosition() - 4.0f*GetRight(), 2.0f, this, true, false, false, false, false, false) == nil){
+ if(m_heliStatus == HELI_STATUS_SHOT_DOWN){
+ m_fTargetOffset -= CTimer::GetTimeStep()*0.05f;
+ targetSpeed.x += -vTargetDist.x*0.15f;
+ targetSpeed.y += vTargetDist.y*0.15f;
+ }else{
+ targetSpeed.x += -vTargetDist.x*0.05f;
+ targetSpeed.y += vTargetDist.y*0.05f;
+ }
+ }else{
+ m_bTestRight = true;
+ if(m_heliStatus == HELI_STATUS_HOVER2)
+ m_fTargetOffset += 5.0f;
+ else
+ m_fTargetOffset -= 5.0f;
+ }
+ }
+
+ if(m_fTargetOffset > 30.0f)
+ m_fTargetOffset = 30.0f;
+
+ if(m_heliStatus == HELI_STATUS_SHOT_DOWN && force){
+ if(CWorld::TestSphereAgainstWorld(GetPosition() + 1.5f*GetForward(), 2.0f, this, true, false, false, false, false, false) ||
+ CWorld::TestSphereAgainstWorld(GetPosition() - 1.5f*GetForward(), 2.0f, this, true, false, false, false, false, false))
+ m_nExplosionTimer = CTimer::GetPreviousTimeInMilliseconds();
+ }
+ }else
+ if(m_fTargetOffset >= 2.0f)
+ m_fTargetOffset -= 2.0f;
+
+ CVector2D speedDir = targetSpeed - m_vecMoveSpeed;
+ float speedDiff = speedDir.Magnitude();
+ if(speedDiff != 0.0f)
+ speedDir /= speedDiff;
+ else
+ speedDir.x = 1.0f;
+ float speedInc = CTimer::GetTimeStep()*0.002f;
+ if(speedDiff < speedInc){
+ m_vecMoveSpeed.x = targetSpeed.x;
+ m_vecMoveSpeed.y = targetSpeed.y;
+ }else{
+ m_vecMoveSpeed.x += speedDir.x*speedInc;
+ m_vecMoveSpeed.y += speedDir.y*speedInc;
+ }
+ GetMatrix().GetPosition().x += m_vecMoveSpeed.x*CTimer::GetTimeStep();
+ GetMatrix().GetPosition().y += m_vecMoveSpeed.y*CTimer::GetTimeStep();
+
+ // Find z target
+ if(m_heliStatus == HELI_STATUS_FLY_AWAY)
+ m_fTargetZ = 1000.0f;
+ if((CTimer::GetTimeInMilliseconds() + 800*m_nHeliId) & 0x800)
+ m_fTargetZ += 2.0f;
+ m_fTargetZ += m_nHeliId*5.0f;
+
+ // Find z speed
+ float targetSpeedZ = (m_fTargetZ - GetPosition().z)*0.01f;
+ float speedDiffZ = targetSpeedZ - m_vecMoveSpeed.z;
+ float speedIncZ = CTimer::GetTimeStep()*0.001f;
+ if(m_heliStatus == HELI_STATUS_FLY_AWAY)
+ speedIncZ *= 1.5f;
+ if(Abs(speedDiffZ) < speedIncZ)
+ m_vecMoveSpeed.z = targetSpeedZ;
+ else if(speedDiffZ < 0.0f)
+ m_vecMoveSpeed.z -= speedIncZ;
+ else
+ m_vecMoveSpeed.z += speedIncZ*1.5f;
+ GetMatrix().GetPosition().z += m_vecMoveSpeed.z*CTimer::GetTimeStep();
+
+ // Find angular speed
+ float targetAngularSpeed;
+ m_fAngularSpeed *= Pow(0.995f, CTimer::GetTimeStep());
+ if(fTargetDist < 8.0f)
+ targetAngularSpeed = 0.0f;
+ else{
+ float rotationDiff = CGeneral::GetATanOfXY(vTargetDist.x, vTargetDist.y) - m_fRotation;
+ while(rotationDiff < -3.14f) rotationDiff += 6.28f;
+ while(rotationDiff > 3.14f) rotationDiff -= 6.28f;
+ if(Abs(rotationDiff) > 0.4f){
+ if(rotationDiff < 0.0f)
+ targetAngularSpeed = -0.2f;
+ else
+ targetAngularSpeed = 0.2f;
+ }else
+ targetAngularSpeed = 0.0f;
+ }
+ float angularSpeedDiff = targetAngularSpeed - m_fAngularSpeed;
+ float angularSpeedInc = CTimer::GetTimeStep()*0.0001f;
+ if(Abs(angularSpeedDiff) < angularSpeedInc)
+ m_fAngularSpeed = targetAngularSpeed;
+ else if(angularSpeedDiff < 0.0f)
+ m_fAngularSpeed -= angularSpeedInc;
+ else
+ m_fAngularSpeed += angularSpeedInc;
+ m_fRotation += m_fAngularSpeed * CTimer::GetTimeStep();
+
+ // Set matrix
+ CVector up(3.0f*m_vecMoveSpeed.x, 3.0f*m_vecMoveSpeed.y, 1.0f);
+ up.Normalise();
+ CVector fwd(-Cos(m_fRotation), -Sin(m_fRotation), 0.0f); // not really forward
+ CVector right = CrossProduct(up, fwd);
+ fwd = CrossProduct(up, right);
+ GetRight() = right;
+ GetForward() = fwd;
+ GetUp() = up;
+
+ // Search light and shooting
+ if(m_heliStatus == HELI_STATUS_FLY_AWAY || m_heliType == HELI_TYPE_CATALINA || CCullZones::PlayerNoRain())
+ m_fSearchLightIntensity = 0.0f;
+ else {
+ // Update search light history once every 1000ms
+ int timeDiff = CTimer::GetTimeInMilliseconds() - m_nSearchLightTimer;
+ while (timeDiff > 1000) {
+ for (i = 5; i > 0; i--) {
+ m_aSearchLightHistoryX[i] = m_aSearchLightHistoryX[i - 1];
+ m_aSearchLightHistoryY[i] = m_aSearchLightHistoryY[i - 1];
+ }
+ m_aSearchLightHistoryX[0] = FindPlayerCoors().x + FindPlayerSpeed().x * 50.0f * (m_nHeliId + 2);
+ m_aSearchLightHistoryY[0] = FindPlayerCoors().y + FindPlayerSpeed().y * 50.0f * (m_nHeliId + 2);
+
+ timeDiff -= 1000;
+ m_nSearchLightTimer += 1000;
+ }
+ assert(timeDiff <= 1000);
+ float f1 = timeDiff / 1000.0f;
+ float f2 = 1.0f - f1;
+ m_fSearchLightX = m_aSearchLightHistoryX[m_nHeliId + 2] * f2 + m_aSearchLightHistoryX[m_nHeliId + 2 - 1] * f1;
+ m_fSearchLightY = m_aSearchLightHistoryY[m_nHeliId + 2] * f2 + m_aSearchLightHistoryY[m_nHeliId + 2 - 1] * f1;
+
+ float searchLightDist = (CVector2D(m_fSearchLightX, m_fSearchLightY) - GetPosition()).Magnitude();
+ if (searchLightDist > 60.0f)
+ m_fSearchLightIntensity = 0.0f;
+ else if (searchLightDist < 40.0f)
+ m_fSearchLightIntensity = 1.0f;
+ else
+ m_fSearchLightIntensity = 1.0f - (40.0f - searchLightDist) / (60.0f-40.0f);
+
+ if (m_fSearchLightIntensity < 0.9f || sq(FindPlayerCoors().x - m_fSearchLightX) + sq(FindPlayerCoors().y - m_fSearchLightY) > sq(7.0f))
+ m_nShootTimer = CTimer::GetTimeInMilliseconds();
+ else if (CTimer::GetTimeInMilliseconds() > m_nPoliceShoutTimer) {
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_HELI_PLAYER_FOUND, 0.0f);
+ m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds() + 4500 + (CGeneral::GetRandomNumber() & 0xFFF);
+ }
+#ifdef FIX_BUGS
+ if (!CReplay::IsPlayingBack())
+#endif
+ {
+ // Shoot
+ int shootTimeout;
+ if (m_heliType == HELI_TYPE_RANDOM) {
+ switch (FindPlayerPed()->m_pWanted->GetWantedLevel()) {
+ case 0:
+ case 1:
+ case 2: shootTimeout = 999999; break;
+ case 3: shootTimeout = 10000; break;
+ case 4: shootTimeout = 5000; break;
+ case 5: shootTimeout = 3500; break;
+ case 6: shootTimeout = 2000; break;
+ }
+ if (CCullZones::NoPolice())
+ shootTimeout /= 2;
+ }
+ else
+ shootTimeout = 1500;
+
+ if (FindPlayerPed()->m_pWanted->IsIgnored())
+ m_nShootTimer = CTimer::GetTimeInMilliseconds();
+ else {
+ // Check if line of sight is clear
+ if (CTimer::GetTimeInMilliseconds() > m_nShootTimer + shootTimeout &&
+ CTimer::GetPreviousTimeInMilliseconds() <= m_nShootTimer + shootTimeout) {
+ if (CWorld::GetIsLineOfSightClear(GetPosition(), FindPlayerCoors(), true, false, false, false, false, false)) {
+ if (m_heliStatus == HELI_STATUS_HOVER2)
+ m_heliStatus = HELI_STATUS_HOVER;
+ }
+ else {
+ m_nShootTimer = CTimer::GetTimeInMilliseconds();
+ if (m_heliStatus == HELI_STATUS_HOVER)
+ m_heliStatus = HELI_STATUS_HOVER2;
+ }
+ }
+
+ // Shoot!
+ if (CTimer::GetTimeInMilliseconds() > m_nShootTimer + shootTimeout &&
+ CTimer::GetTimeInMilliseconds() > m_nLastShotTime + 200) {
+ CVector shotTarget = FindPlayerCoors();
+ // some inaccuracy
+ shotTarget.x += ((CGeneral::GetRandomNumber() & 0xFF) - 128) / 50.0f;
+ shotTarget.y += ((CGeneral::GetRandomNumber() & 0xFF) - 128) / 50.0f;
+ CVector direction = FindPlayerCoors() - GetPosition();
+ direction.Normalise();
+ shotTarget += 3.0f * direction;
+ CVector shotSource = GetPosition();
+ shotSource += 3.0f * direction;
+ FireOneInstantHitRound(&shotSource, &shotTarget, 20);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);
+ m_nLastShotTime = CTimer::GetTimeInMilliseconds();
+ }
+ }
+ }
+ }
+
+ // Process ropes
+ for(i = 0; i < 4; i++){
+ if(m_aSwatState[i] == 0)
+ continue;
+
+ m_aSwatState[i]--;
+ CRopes::RegisterRope((uintptr)this + i, GetMatrix()*FindSwatPositionRelativeToHeli(i), false);
+ if(m_aSwatState[i] == 0){
+ CVector speed = Multiply3x3(GetMatrix(), 0.05f*FindSwatPositionRelativeToHeli(i));
+ speed.z = 0.0f;
+ CRopes::SetSpeedOfTopNode((uintptr)this + i, speed);
+ }
+ }
+
+ RemoveAndAdd();
+ bIsInSafePosition = true;
+ GetMatrix().UpdateRW();
+ UpdateRwFrame();
+}
+
+void
+CHeli::PreRender(void)
+{
+ float radius = (GetPosition().z - FindPlayerCoors().z - 10.0f - 1.0f) * 0.3f + 10.0f;
+ HeliDustGenerate(this, radius, FindPlayerCoors().z, Max(16.0f - 4.0f*CTimer::GetTimeStep(), 2.0f));
+ CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_HELI);
+}
+
+void
+CHeli::Render(void)
+{
+ CMatrix mat;
+ CVector pos;
+
+ mat.Attach(RwFrameGetMatrix(m_aHeliNodes[HELI_TOPROTOR]));
+ pos = mat.GetPosition();
+ mat.SetRotateZ(m_fRotorRotation);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ m_fRotorRotation += 3.14f/6.5f;
+ if(m_fRotorRotation > 6.28f)
+ m_fRotorRotation -= 6.28f;
+
+ mat.Attach(RwFrameGetMatrix(m_aHeliNodes[HELI_BACKROTOR]));
+ pos = mat.GetPosition();
+ mat.SetRotateX(m_fRotorRotation);
+ mat.Translate(pos);
+ mat.UpdateRW();
+
+ CEntity::Render();
+}
+
+void
+CHeli::PreRenderAlways(void)
+{
+ CVector shadowPos(m_fSearchLightX, m_fSearchLightY, GetPosition().z);
+ if(m_fSearchLightIntensity > 0.0f){
+ CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &shadowPos,
+ 6.0f, 0.0f, 0.0f, -6.0f,
+ 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity,
+ 50.0f, true, 1.0f, nil, false);
+
+ CVector front = GetMatrix() * CVector(0.0f, 7.0f, 0.0f);
+ CVector toPlayer = FindPlayerCoors() - front;
+ toPlayer.Normalise();
+ float intensity = m_fSearchLightIntensity*sq(CTimeCycle::GetSpriteBrightness());
+ if(DotProduct(toPlayer, TheCamera.GetForward()) < -0.8f)
+ CCoronas::RegisterCorona((uintptr)this, 255*intensity, 255*intensity, 255*intensity, 255,
+ front, 10.0f, 60.0f, CCoronas::TYPE_STAR,
+ CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ else
+ CCoronas::RegisterCorona((uintptr)this, 200*intensity, 200*intensity, 200*intensity, 255,
+ front, 8.0f, 60.0f, CCoronas::TYPE_STAR,
+ CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ }
+
+ CVector back = GetMatrix() * CVector(0.0f, -9.0f, 0.0f);
+ if(CTimer::GetTimeInMilliseconds() & 0x100)
+ CCoronas::RegisterCorona((uintptr)this + 2, 255, 0, 0, 255,
+ back, 1.0f, 60.0f, CCoronas::TYPE_STAR,
+ CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+ else
+ CCoronas::RegisterCorona((uintptr)this + 2, 0, 0, 0, 255,
+ back, 1.0f, 60.0f, CCoronas::TYPE_STAR,
+ CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
+}
+
+RwObject*
+GetHeliAtomicObjectCB(RwObject *object, void *data)
+{
+ RpAtomic *atomic = (RpAtomic*)object;
+ assert(RwObjectGetType(object) == rpATOMIC);
+ if(RpAtomicGetFlags(atomic) & rpATOMICRENDER)
+ *(RpAtomic**)data = atomic;
+ return object;
+}
+
+CObject*
+CHeli::SpawnFlyingComponent(int32 component)
+{
+ RpAtomic *atomic;
+ RwFrame *frame;
+ RwMatrix *matrix;
+ CObject *obj;
+
+ if(m_aHeliNodes[component] == nil)
+ return nil;
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aHeliNodes[component], GetHeliAtomicObjectCB, &atomic);
+ if(atomic == nil)
+ return nil;
+
+ obj = new CObject;
+ if(obj == nil)
+ return nil;
+
+ obj->SetModelIndexNoCreate(MI_CAR_WHEEL);
+ // object needs base model
+ obj->RefModelInfo(GetModelIndex());
+
+ // create new atomic
+ matrix = RwFrameGetLTM(m_aHeliNodes[component]);
+ frame = RwFrameCreate();
+ atomic = RpAtomicClone(atomic);
+ *RwFrameGetMatrix(frame) = *matrix;
+ RpAtomicSetFrame(atomic, frame);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ obj->AttachToRwObject((RwObject*)atomic);
+ obj->bDontStream = true;
+
+ // init object
+ obj->m_fMass = 10.0f;
+ obj->m_fTurnMass = 25.0f;
+ obj->m_fAirResistance = 0.99f;
+ obj->m_fElasticity = 0.1f;
+ obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f;
+ obj->ObjectCreatedBy = TEMP_OBJECT;
+ obj->SetIsStatic(false);
+ obj->bIsPickup = false;
+
+ // life time
+ CObject::nNoTempObjects++;
+ if(component == HELI_TOPROTOR)
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 1000;
+ else
+ obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 3000;
+
+ obj->m_vecMoveSpeed = m_vecMoveSpeed;
+ if(obj->m_vecMoveSpeed.z > 0.0f)
+ obj->m_vecMoveSpeed.z = 0.3f;
+ else
+ obj->m_vecMoveSpeed.z = 0.0f;
+
+ obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f;
+
+ if(component == HELI_BACKROTOR)
+ obj->m_vecTurnSpeed.x = 0.5f;
+ else if(component == HELI_TOPROTOR || component == HELI_TOPKNOT)
+ obj->m_vecTurnSpeed.z = 0.5f;
+ else
+ obj->m_vecTurnSpeed.y = 0.5f;
+
+ obj->bRenderScorched = true;
+
+ CWorld::Add(obj);
+
+ atomic = nil;
+ RwFrameForAllObjects(m_aHeliNodes[component], GetHeliAtomicObjectCB, &atomic);
+ if(atomic)
+ RpAtomicSetFlags(atomic, 0);
+
+ return obj;
+}
+
+CVector
+CHeli::FindSwatPositionRelativeToHeli(int n)
+{
+ switch(n){
+ case 0: return CVector(-1.2f, -1.0f, -0.5f);
+ case 1: return CVector( 1.2f, -1.0f, -0.5f);
+ case 2: return CVector(-1.2f, 1.0f, -0.5f);
+ case 3: return CVector( 1.2f, 1.0f, -0.5f);
+ default: return CVector(0.0f, 0.0f, 0.0f);
+ }
+}
+
+bool
+CHeli::SendDownSwat(void)
+{
+ if(m_numSwat == 0 || !CStreaming::HasModelLoaded(MI_SWAT) ||
+ CGeneral::GetRandomNumber() & 0x7F || (GetPosition() - FindPlayerCoors()).Magnitude() > 50.0f)
+ return false;
+
+ CMatrix mat(GetMatrix());
+ CVector pos = Multiply3x3(mat, FindSwatPositionRelativeToHeli(m_numSwat-1)) + GetPosition();
+
+ float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, nil);
+ if(Abs(FindPlayerCoors().z - groundZ) < 2.5f && CRopes::RegisterRope((uintptr)this + m_numSwat-1, pos, false)){
+ CCopPed *swat = (CCopPed*)CPopulation::AddPed(PEDTYPE_COP, COP_HELI_SWAT, pos);
+ swat->bUsesCollision = false;
+ swat->m_pRopeEntity = this;
+ RegisterReference(&swat->m_pRopeEntity);
+ m_numSwat--;
+ swat->m_nRopeID = (uintptr)this + m_numSwat;
+ m_aSwatState[m_numSwat] = 255;
+ CAnimManager::BlendAnimation(swat->GetClump(), ASSOCGRP_STD, ANIM_STD_ABSEIL, 4.0f);
+ return true;
+ }
+ return false;
+}
+
+
+void
+CHeli::InitHelis(void)
+{
+ int i;
+
+ NumRandomHelis = 0;
+ TestForNewRandomHelisTimer = 0;
+ CatalinaHeliOn = false;
+ ScriptHeliOn = false;
+ for(i = 0; i < NUM_HELIS; i++)
+ pHelis[i] = nil;
+
+ ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHOPPER))->SetColModel(&CTempColModels::ms_colModelPed1);
+}
+
+CHeli*
+CHeli::GenerateHeli(bool catalina)
+{
+ CHeli *heli;
+ CVector heliPos;
+ int i;
+
+ if(catalina)
+ assert(0 && "can't create catalina's heli");
+ else
+ heli = new CHeli(MI_CHOPPER, PERMANENT_VEHICLE);
+
+ if(catalina)
+ heliPos = CVector(-224.0f, 201.0f, 83.0f);
+ else{
+ heliPos = FindPlayerCoors();
+ float angle = (float)(CGeneral::GetRandomNumber() & 0xFF)/0x100 * 6.28f;
+ heliPos.x += 250.0f*Sin(angle);
+ heliPos.y += 250.0f*Cos(angle);
+ if(heliPos.x < -2000.0f-400.0f || heliPos.x > 2000.0f-400.0f || heliPos.y < -2000.0f || heliPos.y > 2000.0f){
+ heliPos = FindPlayerCoors();
+ heliPos.x -= 250.0f*Sin(angle);
+ heliPos.y -= 250.0f*Cos(angle);
+ }
+ heliPos.z += 50.0f;
+ }
+ heli->GetMatrix().SetTranslate(heliPos);
+ if(catalina)
+ heli->GetMatrix().SetRotateZOnly(DEGTORAD(270.0f)); // game actually uses 3.14 here
+
+ heli->SetStatus(STATUS_ABANDONED);
+ heli->bIsLocked = true;
+
+ int id = -1;
+ bool found = false;
+ while(!found){
+ id++;
+ found = true;
+ for(i = 0; i < 4; i++)
+ if(pHelis[i] && pHelis[i]->m_nHeliId == id)
+ found = false;
+ }
+ heli->m_nHeliId = id;
+
+ CWorld::Add(heli);
+
+ return heli;
+}
+
+void
+CHeli::UpdateHelis(void)
+{
+ int i, j;
+
+ // Spawn new police helis
+ int numHelisRequired =
+#ifdef FIX_BUGS
+ CReplay::IsPlayingBack() ? 0 :
+#endif
+ FindPlayerPed()->m_pWanted->NumOfHelisRequired();
+ if(CCullZones::PlayerNoRain() || CGame::IsInInterior())
+ numHelisRequired = 0;
+ if(CStreaming::HasModelLoaded(MI_CHOPPER) && CTimer::GetTimeInMilliseconds() > TestForNewRandomHelisTimer){
+ // Spawn a police heli
+ TestForNewRandomHelisTimer = CTimer::GetTimeInMilliseconds() + 15000;
+ if(NumRandomHelis < numHelisRequired){
+ NumRandomHelis++;
+ CHeli *heli = GenerateHeli(false);
+ heli->m_heliType = HELI_TYPE_RANDOM;
+ if(pHelis[HELI_RANDOM0] == nil)
+ pHelis[HELI_RANDOM0] = heli;
+ else if(pHelis[HELI_RANDOM1] == nil)
+ pHelis[HELI_RANDOM1] = heli;
+ else
+ assert(0 && "too many helis");
+ }
+ }
+
+ // Handle script heli
+ if(ScriptHeliOn){
+ if(CStreaming::HasModelLoaded(MI_CHOPPER) && pHelis[HELI_SCRIPT] == nil){
+ pHelis[HELI_SCRIPT] = GenerateHeli(false);
+ pHelis[HELI_SCRIPT]->m_heliType = HELI_TYPE_SCRIPT;
+ }else
+ CStreaming::RequestModel(MI_CHOPPER, 0);
+ }else{
+ if(pHelis[HELI_SCRIPT])
+ pHelis[HELI_SCRIPT]->m_heliStatus = HELI_STATUS_FLY_AWAY;
+ }
+
+ // Delete helis that we no longer need
+ for(i = 0; i < NUM_HELIS; i++)
+ if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_FLY_AWAY && pHelis[i]->GetPosition().z > 150.0f){
+ CWorld::Remove(pHelis[i]);
+ delete pHelis[i];
+ pHelis[i] = nil;
+ if(i != HELI_SCRIPT && i != HELI_CATALINA)
+ NumRandomHelis--;
+ }
+
+ // Handle explosions
+ for(i = 0; i < NUM_HELIS; i++){
+ if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_SHOT_DOWN && CTimer::GetTimeInMilliseconds() > pHelis[i]->m_nExplosionTimer){
+ // Second part of explosion
+ static int nFrameGen;
+ CRGBA colors[8];
+
+ TheCamera.CamShake(0.7f, pHelis[i]->GetPosition().x, pHelis[i]->GetPosition().y, pHelis[i]->GetPosition().z);
+
+ colors[0] = CRGBA(0, 0, 0, 255);
+ colors[1] = CRGBA(224, 224, 224, 255);
+ colors[2] = CRGBA(0, 0, 0, 255);
+ colors[3] = CRGBA(0, 0, 0, 255);
+ colors[4] = CRGBA(66, 162, 252, 255);
+ colors[5] = CRGBA(0, 0, 0, 255);
+ colors[6] = CRGBA(0, 0, 0, 255);
+ colors[7] = CRGBA(0, 0, 0, 255);
+
+ CVector pos = pHelis[i]->GetPosition();
+ CVector dir;
+ for(j = 0; j < 40; j++){
+ dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
+ dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
+ dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f);
+ int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30);
+ if(CGeneral::GetRandomNumber() & 1)
+ rotSpeed = -rotSpeed;
+ int f = ++nFrameGen & 3;
+ CParticle::AddParticle(PARTICLE_HELI_DEBRIS, pos, dir,
+ nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f),
+ colors[nFrameGen&7], rotSpeed, 0, f, 0);
+ }
+
+ CExplosion::AddExplosion(nil, nil, EXPLOSION_HELI, pos, 0);
+
+ pHelis[i]->SpawnFlyingComponent(HELI_SKID_LEFT);
+ pHelis[i]->SpawnFlyingComponent(HELI_SKID_RIGHT);
+ pHelis[i]->SpawnFlyingComponent(HELI_TOPROTOR);
+
+ CDarkel::RegisterCarBlownUpByPlayer(pHelis[i]);
+ CWorld::Remove(pHelis[i]);
+ delete pHelis[i];
+ pHelis[i] = nil;
+ if(i != HELI_SCRIPT && i != HELI_CATALINA)
+ NumRandomHelis--;
+ if(i == HELI_CATALINA)
+ CatalinaHasBeenShotDown = true;
+
+ CStats::PeopleKilledByPlayer += 2;
+ CStats::PedsKilledOfThisType[PEDTYPE_COP] += 2;
+ CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 250;
+ pos = CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetPosition();
+ CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_SHOOT_HELI,
+ pos, i + 19843, false);
+
+ TestForNewRandomHelisTimer = CTimer::GetTimeInMilliseconds() + 50000;
+ }else if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_SHOT_DOWN && CTimer::GetTimeInMilliseconds()+7000 > pHelis[i]->m_nExplosionTimer){
+ // First part of explosion
+ if(CTimer::GetPreviousTimeInMilliseconds()+7000 < pHelis[i]->m_nExplosionTimer){
+ pHelis[i]->SpawnFlyingComponent(HELI_BACKROTOR);
+ pHelis[i]->SpawnFlyingComponent(HELI_TAIL);
+ pHelis[i]->m_fAngularSpeed *= -2.5f;
+ pHelis[i]->bRenderScorched = true;
+
+ TheCamera.CamShake(0.4f, pHelis[i]->GetPosition().x, pHelis[i]->GetPosition().y, pHelis[i]->GetPosition().z);
+
+ CVector pos = pHelis[i]->GetPosition() - 2.5f*pHelis[i]->GetForward();
+ CExplosion::AddExplosion(nil, nil, EXPLOSION_HELI2, pos, 0);
+ }else
+ pHelis[i]->m_fAngularSpeed *= 1.03f;
+ }
+ }
+
+ // Find police helis to remove
+ for(i = 0; i < 2; i++)
+ if(pHelis[i] && pHelis[i]->m_heliStatus != HELI_STATUS_FLY_AWAY){
+ if(numHelisRequired > 0)
+ numHelisRequired--;
+ else
+ pHelis[i]->m_heliStatus = HELI_STATUS_FLY_AWAY;
+ }
+
+ // Remove all helis if in a tunnel or under water
+ if(FindPlayerCoors().z < - 2.0f)
+ for(i = 0; i < NUM_HELIS; i++)
+ if(pHelis[i] && pHelis[i]->m_heliStatus != HELI_STATUS_SHOT_DOWN)
+ pHelis[i]->m_heliStatus = HELI_STATUS_FLY_AWAY;
+}
+
+void
+CHeli::SpecialHeliPreRender(void)
+{
+ int i;
+ for(i = 0; i < NUM_HELIS; i++)
+ if(pHelis[i])
+ pHelis[i]->PreRenderAlways();
+}
+
+bool
+CHeli::TestRocketCollision(CVector *rocketPos)
+{
+ int i;
+ bool hit = false;
+
+ for(i = 0; i < NUM_HELIS; i++){
+ if(pHelis[i] && !pHelis[i]->bExplosionProof && (*rocketPos - pHelis[i]->GetPosition()).MagnitudeSqr() < sq(8.0f)){
+ pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f;
+ pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN;
+ pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 10000;
+ hit = true;
+ }
+ }
+ return hit;
+}
+
+bool
+CHeli::TestBulletCollision(CVector *line0, CVector *line1, CVector *bulletPos, int32 damage)
+{
+ int i;
+ bool hit = false;
+
+ for(i = 0; i < NUM_HELIS; i++)
+ if(pHelis[i] && !pHelis[i]->bBulletProof && CCollision::DistToLine(line0, line1, &pHelis[i]->GetPosition()) < 5.0f){
+ // Find bullet position
+ float distToHeli = (pHelis[i]->GetPosition() - *line0).Magnitude();
+ CVector line = (*line1 - *line0);
+ float lineLength = line.Magnitude();
+ *bulletPos = *line0 + line*Max(1.0f, distToHeli-5.0f)/lineLength;
+
+ pHelis[i]->m_nBulletDamage += damage;
+
+ if(pHelis[i]->m_heliType == HELI_CATALINA && pHelis[i]->m_nBulletDamage > 400 ||
+ pHelis[i]->m_heliType != HELI_CATALINA && pHelis[i]->m_nBulletDamage > 700){
+ pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f;
+ pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN;
+ pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 10000;
+ }
+
+ hit = true;
+ }
+ return hit;
+}
+
+bool
+CHeli::TestSniperCollision(CVector *line0, CVector *line1)
+{
+ int i;
+ bool hit = false;
+
+ for(i = 0; i < NUM_HELIS; i++){
+ if(pHelis[i] && !pHelis[i]->bBulletProof) {
+ CVector pilotPos = pHelis[i]->GetMatrix() * CVector(-0.43f, 1.49f, 1.5f);
+ if(CCollision::DistToLine(line0, line1, &pilotPos) < 0.8f){
+ pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f;
+ pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN;
+ pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 9999999;
+ pHelis[i]->m_numSwat = 0;
+
+ hit = true;
+ }
+ }
+ }
+ return hit;
+}
+
+void CHeli::StartCatalinaFlyBy(void)
+{
+ CatalinaHeliOn = true;
+ CatalinaHasBeenShotDown = false;
+}
+
+void
+CHeli::RemoveCatalinaHeli(void)
+{
+ CatalinaHeliOn = false;
+ if(pHelis[HELI_CATALINA]){
+ CWorld::Remove(pHelis[HELI_CATALINA]);
+ delete pHelis[HELI_CATALINA];
+ pHelis[HELI_CATALINA] = nil;
+ }
+}
+
+CHeli *CHeli::FindPointerToCatalinasHeli(void) { return pHelis[HELI_CATALINA]; }
+void CHeli::CatalinaTakeOff(void) { pHelis[HELI_CATALINA]->m_pathState = 8; }
+void CHeli::MakeCatalinaHeliFlyAway(void) { pHelis[HELI_CATALINA]->m_pathState = 12; }
+bool CHeli::HasCatalinaBeenShotDown(void) { return CatalinaHasBeenShotDown; }
+
+void CHeli::ActivateHeli(bool activate) { ScriptHeliOn = activate; }
diff --git a/src/vehicles/Heli.h b/src/vehicles/Heli.h
new file mode 100644
index 0000000..c5ae08e
--- /dev/null
+++ b/src/vehicles/Heli.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "Vehicle.h"
+
+class CObject;
+
+enum eHeliNodes
+{
+ HELI_CHASSIS = 1,
+ HELI_TOPROTOR,
+ HELI_BACKROTOR,
+ HELI_TAIL,
+ HELI_TOPKNOT,
+ HELI_SKID_LEFT,
+ HELI_SKID_RIGHT,
+ NUM_HELI_NODES
+};
+
+enum
+{
+ HELI_RANDOM0,
+ HELI_RANDOM1,
+ HELI_SCRIPT,
+ HELI_CATALINA, // TODO 2 in VC
+ NUM_HELIS
+};
+
+enum
+{
+ HELI_TYPE_RANDOM,
+ HELI_TYPE_SCRIPT,
+ HELI_TYPE_CATALINA,
+};
+
+
+class CHeli : public CVehicle
+{
+public:
+ RwFrame *m_aHeliNodes[NUM_HELI_NODES];
+ int8 m_heliStatus;
+ float m_fSearchLightX;
+ float m_fSearchLightY;
+ uint32 m_nExplosionTimer;
+ float m_fRotation;
+ float m_fAngularSpeed;
+ float m_fTargetZ;
+ float m_fSearchLightIntensity;
+ int8 m_nHeliId;
+ int8 m_heliType;
+ int8 m_pathState;
+ int8 m_numSwat;
+ uint8 m_aSwatState[4];
+ float m_aSearchLightHistoryX[6];
+ float m_aSearchLightHistoryY[6];
+ uint32 m_nSearchLightTimer;
+ uint32 m_nShootTimer;
+ uint32 m_nLastShotTime;
+ uint32 m_nBulletDamage;
+ float m_fRotorRotation;
+ float m_fHeliDustZ[8];
+ uint32 m_nPoliceShoutTimer;
+ float m_fTargetOffset;
+ bool m_bTestRight;
+
+ static CHeli *pHelis[NUM_HELIS];
+ static int16 NumRandomHelis;
+ static uint32 TestForNewRandomHelisTimer;
+ static bool CatalinaHeliOn;
+ static bool CatalinaHasBeenShotDown;
+ static bool ScriptHeliOn;
+
+ CHeli(int32 id, uint8 CreatedBy);
+
+ // from CEntity
+ void SetModelIndex(uint32 id);
+ void ProcessControl(void);
+ void PreRender(void);
+ void Render(void);
+
+ void PreRenderAlways(void);
+ CObject *SpawnFlyingComponent(int32 component);
+ CVector FindSwatPositionRelativeToHeli(int n);
+ bool SendDownSwat(void);
+
+ static void InitHelis(void);
+ static CHeli *GenerateHeli(bool catalina); // out of class in III PC and later because of SecuROM
+ static void UpdateHelis(void);
+ static void SpecialHeliPreRender(void);
+ static bool TestRocketCollision(CVector *coors);
+ static bool TestBulletCollision(CVector *line0, CVector *line1, CVector *bulletPos, int32 damage);
+ static bool TestSniperCollision(CVector *line0, CVector *line1);
+
+ static void StartCatalinaFlyBy(void); // out of class in III PC and later because of SecuROM
+ static void RemoveCatalinaHeli(void);
+ static CHeli *FindPointerToCatalinasHeli(void);
+ static void CatalinaTakeOff(void);
+ static void MakeCatalinaHeliFlyAway(void);
+ static bool HasCatalinaBeenShotDown(void);
+
+ static void ActivateHeli(bool activate);
+};
diff --git a/src/vehicles/Plane.cpp b/src/vehicles/Plane.cpp
new file mode 100644
index 0000000..48f7b8c
--- /dev/null
+++ b/src/vehicles/Plane.cpp
@@ -0,0 +1,1069 @@
+#include "common.h"
+#include "main.h"
+
+#include "General.h"
+#include "CutsceneMgr.h"
+#include "ModelIndices.h"
+#include "FileMgr.h"
+#include "Streaming.h"
+#include "Replay.h"
+#include "Camera.h"
+#include "DMAudio.h"
+#include "Wanted.h"
+#include "Coronas.h"
+#include "Particle.h"
+#include "Explosion.h"
+#include "Fluff.h"
+#include "World.h"
+#include "HandlingMgr.h"
+#include "Heli.h"
+#include "Plane.h"
+#include "MemoryHeap.h"
+
+CPlaneNode *pPathNodes;
+CPlaneNode *pPath2Nodes;
+CPlaneNode *pPath3Nodes;
+CPlaneNode *pPath4Nodes;
+int32 NumPathNodes;
+int32 NumPath2Nodes;
+int32 NumPath3Nodes;
+int32 NumPath4Nodes;
+float TotalLengthOfFlightPath;
+float TotalLengthOfFlightPath2;
+float TotalLengthOfFlightPath3;
+float TotalLengthOfFlightPath4;
+float TotalDurationOfFlightPath;
+float TotalDurationOfFlightPath2;
+float TotalDurationOfFlightPath3;
+float TotalDurationOfFlightPath4;
+float LandingPoint;
+float TakeOffPoint;
+CPlaneInterpolationLine aPlaneLineBits[6];
+
+float PlanePathPosition[3];
+float OldPlanePathPosition[3];
+float PlanePathSpeed[3];
+float PlanePath2Position[5];
+float PlanePath3Position[4];
+float PlanePath2Speed[5];
+float PlanePath3Speed[4];
+
+
+enum
+{
+ CESNA_STATUS_NONE, // doesn't even exist
+ CESNA_STATUS_FLYING,
+ CESNA_STATUS_DESTROYED,
+ CESNA_STATUS_LANDED,
+};
+
+int32 CesnaMissionStatus;
+int32 CesnaMissionStartTime;
+CPlane *pDrugRunCesna;
+int32 DropOffCesnaMissionStatus;
+int32 DropOffCesnaMissionStartTime;
+CPlane *pDropOffCesna;
+
+CPlane::CPlane(int32 id, uint8 CreatedBy)
+ : CVehicle(CreatedBy)
+{
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
+ m_vehType = VEHICLE_TYPE_PLANE;
+ pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
+ SetModelIndex(id);
+
+ m_fMass = 100000000.0f;
+ m_fTurnMass = 100000000.0f;
+ m_fAirResistance = 0.9994f;
+ m_fElasticity = 0.05f;
+
+ bUsesCollision = false;
+ m_bHasBeenHit = false;
+ m_bIsDrugRunCesna = false;
+ m_bIsDropOffCesna = false;
+ m_bTempPlane = false;
+
+ SetStatus(STATUS_PLANE);
+ bIsBIGBuilding = true;
+ m_level = LEVEL_GENERIC;
+
+ m_isFarAway = false;
+#ifdef CPLANE_ROTORS
+ m_fRotorRotation = 0.0f;
+#endif
+}
+
+CPlane::~CPlane()
+{
+ DeleteRwObject();
+}
+
+void
+CPlane::SetModelIndex(uint32 id)
+{
+ CVehicle::SetModelIndex(id);
+#ifdef CPLANE_ROTORS
+ int i;
+ for(i = 0; i < NUM_PLANE_NODES; i++)
+ m_aPlaneNodes[i] = nil;
+ if(GetModelIndex() == MI_CHOPPER){
+ // This is surprisingly annoying...
+ RwFrame *heliNodes[NUM_HELI_NODES];
+ for(i = 0; i < NUM_HELI_NODES; i++)
+ heliNodes[i] = nil;
+ CClumpModelInfo::FillFrameArray(GetClump(), heliNodes);
+ m_aPlaneNodes[PLANE_TOPROTOR] = heliNodes[HELI_TOPROTOR];
+ m_aPlaneNodes[PLANE_BACKROTOR] = heliNodes[HELI_BACKROTOR];
+ }else
+ CClumpModelInfo::FillFrameArray(GetClump(), m_aPlaneNodes);
+#endif
+}
+
+void
+CPlane::DeleteRwObject(void)
+{
+ if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){
+ m_matrix.Detach();
+ if(RwObjectGetType(m_rwObject) == rpATOMIC){ // useless check
+ RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject);
+ RpAtomicDestroy((RpAtomic*)m_rwObject);
+ RwFrameDestroy(f);
+ }
+ m_rwObject = nil;
+ }
+ CEntity::DeleteRwObject();
+}
+
+// There's a LOT of copy and paste in here. Maybe this could be refactored somehow
+void
+CPlane::ProcessControl(void)
+{
+ int i;
+ CVector pos;
+
+ if(CReplay::IsPlayingBack())
+ return;
+
+ if(GetModelIndex() == MI_AIRTRAIN){
+ if(GetPosition().z > 100.0f)
+ CPlaneTrails::RegisterPoint(GetPosition(), m_nPlaneId);
+ }else if(GetModelIndex() == MI_DEADDODO)
+ CPlaneBanners::RegisterPoint(GetPosition(), m_nPlaneId);
+
+ // Explosion
+ if(m_bHasBeenHit){
+ // BUG: since this is all based on frames, you can skip the explosion processing when you go into the menu
+ if(GetModelIndex() == MI_AIRTRAIN){
+ int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit;
+ if(frm == 20){
+ static int nFrameGen;
+ CRGBA colors[8];
+
+ CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0);
+
+ colors[0] = CRGBA(0, 0, 0, 255);
+ colors[1] = CRGBA(224, 230, 238, 255);
+ colors[2] = CRGBA(224, 230, 238, 255);
+ colors[3] = CRGBA(0, 0, 0, 255);
+ colors[4] = CRGBA(224, 230, 238, 255);
+ colors[5] = CRGBA(0, 0, 0, 255);
+ colors[6] = CRGBA(0, 0, 0, 255);
+ colors[7] = CRGBA(224, 230, 238, 255);
+
+ CVector dir;
+ for(i = 0; i < 40; i++){
+ dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
+ dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
+ dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f);
+ int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30);
+ if(CGeneral::GetRandomNumber() & 1)
+ rotSpeed = -rotSpeed;
+ int f = ++nFrameGen & 3;
+ CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir,
+ nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f),
+ colors[nFrameGen&7], rotSpeed, 0, f, 0);
+ }
+ }
+ if(frm >= 40 && frm <= 80 && frm & 1){
+ if(frm & 1){
+ pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
+ pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
+ pos.y = frm - 40;
+ pos = GetMatrix() * pos;
+ }else{
+ pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
+ pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
+ pos.y = 40 - frm;
+ pos = GetMatrix() * pos;
+ }
+ CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0);
+ }
+ if(frm == 60)
+ bRenderScorched = true;
+ if(frm == 82){
+ TheCamera.SetFadeColour(255, 255, 255);
+ TheCamera.Fade(0.0f, FADE_OUT);
+ TheCamera.ProcessFade();
+ TheCamera.Fade(1.0f, FADE_IN);
+ FlagToDestroyWhenNextProcessed();
+ }
+ }else{
+ int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit;
+ if(frm == 20){
+ static int nFrameGen;
+ CRGBA colors[8];
+
+ CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0);
+
+ colors[0] = CRGBA(0, 0, 0, 255);
+ colors[1] = CRGBA(224, 230, 238, 255);
+ colors[2] = CRGBA(224, 230, 238, 255);
+ colors[3] = CRGBA(0, 0, 0, 255);
+ colors[4] = CRGBA(252, 66, 66, 255);
+ colors[5] = CRGBA(0, 0, 0, 255);
+ colors[6] = CRGBA(0, 0, 0, 255);
+ colors[7] = CRGBA(252, 66, 66, 255);
+
+ CVector dir;
+ for(i = 0; i < 40; i++){
+ dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
+ dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
+ dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f);
+ int rotSpeed = CGeneral::GetRandomNumberInRange(30.0f, 20.0f);
+ if(CGeneral::GetRandomNumber() & 1)
+ rotSpeed = -rotSpeed;
+ int f = ++nFrameGen & 3;
+ CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir,
+ nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f),
+ colors[nFrameGen&7], rotSpeed, 0, f, 0);
+ }
+ }
+ if(frm >= 40 && frm <= 60 && frm & 1){
+ if(frm & 1){
+ pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
+ pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
+ pos.y = (frm - 40)*0.3f;
+ pos = GetMatrix() * pos;
+ }else{
+ pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
+ pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
+ pos.y = (40 - frm)*0.3f;
+ pos = GetMatrix() * pos;
+ }
+ CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0);
+ }
+ if(frm == 30)
+ bRenderScorched = true;
+ if(frm == 62){
+ TheCamera.SetFadeColour(200, 200, 200);
+ TheCamera.Fade(0.0f, FADE_OUT);
+ TheCamera.ProcessFade();
+ TheCamera.Fade(1.0f, FADE_IN);
+ if(m_bIsDrugRunCesna){
+ CesnaMissionStatus = CESNA_STATUS_DESTROYED;
+ pDrugRunCesna = nil;
+ }
+ if(m_bIsDropOffCesna){
+ DropOffCesnaMissionStatus = CESNA_STATUS_DESTROYED;
+ pDropOffCesna = nil;
+ }
+ FlagToDestroyWhenNextProcessed();
+ }
+ }
+ }
+
+ // Update plane position and speed
+ if(GetModelIndex() == MI_AIRTRAIN || !m_isFarAway || ((CTimer::GetFrameCounter() + m_randomSeed) & 7) == 0){
+ if(GetModelIndex() == MI_AIRTRAIN){
+ float pathPositionRear = PlanePathPosition[m_nPlaneId] - 30.0f;
+ if(pathPositionRear < 0.0f)
+ pathPositionRear += TotalLengthOfFlightPath;
+ float pathPosition = pathPositionRear + 30.0f;
+
+ float pitch = 0.0f;
+ float distSinceTakeOff = pathPosition - TakeOffPoint;
+ if(distSinceTakeOff <= 0.0f && distSinceTakeOff > -70.0f){
+ // shortly before take off
+ pitch = 1.0f - distSinceTakeOff/-70.0f;
+ }else if(distSinceTakeOff >= 0.0f && distSinceTakeOff < 100.0f){
+ // shortly after take off
+ pitch = 1.0f - distSinceTakeOff/100.0f;
+ }
+
+ float distSinceLanding = pathPosition - LandingPoint;
+ if(distSinceLanding <= 0.0f && distSinceLanding > -200.0f){
+ // shortly before landing
+ pitch = 1.0f - distSinceLanding/-200.0f;
+ }else if(distSinceLanding >= 0.0f && distSinceLanding < 70.0f){
+ // shortly after landing
+ pitch = 1.0f - distSinceLanding/70.0f;
+ }
+
+
+ // Advance current node to appropriate position
+ float pos1, pos2;
+ int nextTrackNode = m_nCurPathNode + 1;
+ pos1 = pPathNodes[m_nCurPathNode].t;
+ if(nextTrackNode < NumPathNodes)
+ pos2 = pPathNodes[nextTrackNode].t;
+ else{
+ nextTrackNode = 0;
+ pos2 = TotalLengthOfFlightPath;
+ }
+ while(pathPositionRear < pos1 || pathPositionRear > pos2){
+ m_nCurPathNode = (m_nCurPathNode+1) % NumPathNodes;
+ nextTrackNode = m_nCurPathNode + 1;
+ pos1 = pPathNodes[m_nCurPathNode].t;
+ if(nextTrackNode < NumPathNodes)
+ pos2 = pPathNodes[nextTrackNode].t;
+ else{
+ nextTrackNode = 0;
+ pos2 = TotalLengthOfFlightPath;
+ }
+ }
+ bool bothOnGround = pPathNodes[m_nCurPathNode].bOnGround && pPathNodes[nextTrackNode].bOnGround;
+ if(PlanePathPosition[m_nPlaneId] >= LandingPoint && OldPlanePathPosition[m_nPlaneId] < LandingPoint)
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_PLANE_ON_GROUND, 0.0f);
+ float dist = pPathNodes[nextTrackNode].t - pPathNodes[m_nCurPathNode].t;
+ if(dist < 0.0f)
+ dist += TotalLengthOfFlightPath;
+ float f = (pathPositionRear - pPathNodes[m_nCurPathNode].t)/dist;
+ CVector posRear = (1.0f - f)*pPathNodes[m_nCurPathNode].p + f*pPathNodes[nextTrackNode].p;
+
+ // Same for the front
+ float pathPositionFront = pathPositionRear + 60.0f;
+ if(pathPositionFront > TotalLengthOfFlightPath)
+ pathPositionFront -= TotalLengthOfFlightPath;
+ int curPathNodeFront = m_nCurPathNode;
+ int nextPathNodeFront = curPathNodeFront + 1;
+ pos1 = pPathNodes[curPathNodeFront].t;
+ if(nextPathNodeFront < NumPathNodes)
+ pos2 = pPathNodes[nextPathNodeFront].t;
+ else{
+ nextPathNodeFront = 0;
+ pos2 = TotalLengthOfFlightPath;
+ }
+ while(pathPositionFront < pos1 || pathPositionFront > pos2){
+ curPathNodeFront = (curPathNodeFront+1) % NumPathNodes;
+ nextPathNodeFront = curPathNodeFront + 1;
+ pos1 = pPathNodes[curPathNodeFront].t;
+ if(nextPathNodeFront < NumPathNodes)
+ pos2 = pPathNodes[nextPathNodeFront].t;
+ else{
+ nextPathNodeFront = 0;
+ pos2 = TotalLengthOfFlightPath;
+ }
+ }
+ dist = pPathNodes[nextPathNodeFront].t - pPathNodes[curPathNodeFront].t;
+ if(dist < 0.0f)
+ dist += TotalLengthOfFlightPath;
+ f = (pathPositionFront - pPathNodes[curPathNodeFront].t)/dist;
+ CVector posFront = (1.0f - f)*pPathNodes[curPathNodeFront].p + f*pPathNodes[nextPathNodeFront].p;
+
+ // And for another point 60 units in front of the plane, used to calculate roll
+ float pathPositionFront2 = pathPositionFront + 60.0f;
+ if(pathPositionFront2 > TotalLengthOfFlightPath)
+ pathPositionFront2 -= TotalLengthOfFlightPath;
+ int curPathNodeFront2 = m_nCurPathNode;
+ int nextPathNodeFront2 = curPathNodeFront2 + 1;
+ pos1 = pPathNodes[curPathNodeFront2].t;
+ if(nextPathNodeFront2 < NumPathNodes)
+ pos2 = pPathNodes[nextPathNodeFront2].t;
+ else{
+ nextPathNodeFront2 = 0;
+ pos2 = TotalLengthOfFlightPath;
+ }
+ while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){
+ curPathNodeFront2 = (curPathNodeFront2+1) % NumPathNodes;
+ nextPathNodeFront2 = curPathNodeFront2 + 1;
+ pos1 = pPathNodes[curPathNodeFront2].t;
+ if(nextPathNodeFront2 < NumPathNodes)
+ pos2 = pPathNodes[nextPathNodeFront2].t;
+ else{
+ nextPathNodeFront2 = 0;
+ pos2 = TotalLengthOfFlightPath;
+ }
+ }
+ dist = pPathNodes[nextPathNodeFront2].t - pPathNodes[curPathNodeFront2].t;
+ if(dist < 0.0f)
+ dist += TotalLengthOfFlightPath;
+ f = (pathPositionFront2 - pPathNodes[curPathNodeFront2].t)/dist;
+ CVector posFront2 = (1.0f - f)*pPathNodes[curPathNodeFront2].p + f*pPathNodes[nextPathNodeFront2].p;
+
+ // Now set matrix
+ GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f);
+ GetMatrix().GetPosition().z += 4.3f;
+ CVector fwd = posFront - posRear;
+ fwd.Normalise();
+ if(pitch != 0.0f){
+ fwd.z += 0.4f*pitch;
+ fwd.Normalise();
+ }
+ CVector fwd2 = posFront2 - posRear;
+ fwd2.Normalise();
+ CVector roll = CrossProduct(fwd, fwd2);
+ CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
+ if(!bothOnGround)
+ right.z += 3.0f*roll.z;
+ right.Normalise();
+ CVector up = CrossProduct(right, fwd);
+ GetMatrix().GetRight() = right;
+ GetMatrix().GetUp() = up;
+ GetMatrix().GetForward() = fwd;
+ // Set speed
+ m_vecMoveSpeed = fwd*PlanePathSpeed[m_nPlaneId]/60.0f;
+ m_fSpeed = PlanePathSpeed[m_nPlaneId]/60.0f;
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+
+ m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f));
+ }else{
+ float planePathPosition;
+ float totalLengthOfFlightPath;
+ CPlaneNode *pathNodes;
+ float planePathSpeed;
+ int numPathNodes;
+
+ if(GetModelIndex() == MI_CHOPPER){
+ planePathPosition = PlanePath3Position[m_nPlaneId];
+ totalLengthOfFlightPath = TotalLengthOfFlightPath3;
+ pathNodes = pPath3Nodes;
+ planePathSpeed = PlanePath3Speed[m_nPlaneId];
+ numPathNodes = NumPath3Nodes;
+ }else{
+ planePathPosition = PlanePath2Position[m_nPlaneId];
+ totalLengthOfFlightPath = TotalLengthOfFlightPath2;
+ pathNodes = pPath2Nodes;
+ planePathSpeed = PlanePath2Speed[m_nPlaneId];
+ numPathNodes = NumPath2Nodes;
+ }
+
+ // Advance current node to appropriate position
+ float pathPositionRear = planePathPosition - 10.0f;
+ if(pathPositionRear < 0.0f)
+ pathPositionRear += totalLengthOfFlightPath;
+ float pos1, pos2;
+ int nextTrackNode = m_nCurPathNode + 1;
+ pos1 = pathNodes[m_nCurPathNode].t;
+ if(nextTrackNode < numPathNodes)
+ pos2 = pathNodes[nextTrackNode].t;
+ else{
+ nextTrackNode = 0;
+ pos2 = totalLengthOfFlightPath;
+ }
+ while(pathPositionRear < pos1 || pathPositionRear > pos2){
+ m_nCurPathNode = (m_nCurPathNode+1) % numPathNodes;
+ nextTrackNode = m_nCurPathNode + 1;
+ pos1 = pathNodes[m_nCurPathNode].t;
+ if(nextTrackNode < numPathNodes)
+ pos2 = pathNodes[nextTrackNode].t;
+ else{
+ nextTrackNode = 0;
+ pos2 = totalLengthOfFlightPath;
+ }
+ }
+ float dist = pathNodes[nextTrackNode].t - pathNodes[m_nCurPathNode].t;
+ if(dist < 0.0f)
+ dist += totalLengthOfFlightPath;
+ float f = (pathPositionRear - pathNodes[m_nCurPathNode].t)/dist;
+ CVector posRear = (1.0f - f)*pathNodes[m_nCurPathNode].p + f*pathNodes[nextTrackNode].p;
+
+ // Same for the front
+ float pathPositionFront = pathPositionRear + 20.0f;
+ if(pathPositionFront > totalLengthOfFlightPath)
+ pathPositionFront -= totalLengthOfFlightPath;
+ int curPathNodeFront = m_nCurPathNode;
+ int nextPathNodeFront = curPathNodeFront + 1;
+ pos1 = pathNodes[curPathNodeFront].t;
+ if(nextPathNodeFront < numPathNodes)
+ pos2 = pathNodes[nextPathNodeFront].t;
+ else{
+ nextPathNodeFront = 0;
+ pos2 = totalLengthOfFlightPath;
+ }
+ while(pathPositionFront < pos1 || pathPositionFront > pos2){
+ curPathNodeFront = (curPathNodeFront+1) % numPathNodes;
+ nextPathNodeFront = curPathNodeFront + 1;
+ pos1 = pathNodes[curPathNodeFront].t;
+ if(nextPathNodeFront < numPathNodes)
+ pos2 = pathNodes[nextPathNodeFront].t;
+ else{
+ nextPathNodeFront = 0;
+ pos2 = totalLengthOfFlightPath;
+ }
+ }
+ dist = pathNodes[nextPathNodeFront].t - pathNodes[curPathNodeFront].t;
+ if(dist < 0.0f)
+ dist += totalLengthOfFlightPath;
+ f = (pathPositionFront - pathNodes[curPathNodeFront].t)/dist;
+ CVector posFront = (1.0f - f)*pathNodes[curPathNodeFront].p + f*pathNodes[nextPathNodeFront].p;
+
+ // And for another point 30 units in front of the plane, used to calculate roll
+ float pathPositionFront2 = pathPositionFront + 30.0f;
+ if(pathPositionFront2 > totalLengthOfFlightPath)
+ pathPositionFront2 -= totalLengthOfFlightPath;
+ int curPathNodeFront2 = m_nCurPathNode;
+ int nextPathNodeFront2 = curPathNodeFront2 + 1;
+ pos1 = pathNodes[curPathNodeFront2].t;
+ if(nextPathNodeFront2 < numPathNodes)
+ pos2 = pathNodes[nextPathNodeFront2].t;
+ else{
+ nextPathNodeFront2 = 0;
+ pos2 = totalLengthOfFlightPath;
+ }
+ while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){
+ curPathNodeFront2 = (curPathNodeFront2+1) % numPathNodes;
+ nextPathNodeFront2 = curPathNodeFront2 + 1;
+ pos1 = pathNodes[curPathNodeFront2].t;
+ if(nextPathNodeFront2 < numPathNodes)
+ pos2 = pathNodes[nextPathNodeFront2].t;
+ else{
+ nextPathNodeFront2 = 0;
+ pos2 = totalLengthOfFlightPath;
+ }
+ }
+ dist = pathNodes[nextPathNodeFront2].t - pathNodes[curPathNodeFront2].t;
+ if(dist < 0.0f)
+ dist += totalLengthOfFlightPath;
+ f = (pathPositionFront2 - pathNodes[curPathNodeFront2].t)/dist;
+ CVector posFront2 = (1.0f - f)*pathNodes[curPathNodeFront2].p + f*pathNodes[nextPathNodeFront2].p;
+
+ // Now set matrix
+ GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f);
+ GetMatrix().GetPosition().z += 1.0f;
+ CVector fwd = posFront - posRear;
+ fwd.Normalise();
+ CVector fwd2 = posFront2 - posRear;
+ fwd2.Normalise();
+ CVector roll = CrossProduct(fwd, fwd2);
+ CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
+ right.z += 3.0f*roll.z;
+ right.Normalise();
+ CVector up = CrossProduct(right, fwd);
+ GetMatrix().GetRight() = right;
+ GetMatrix().GetUp() = up;
+ GetMatrix().GetForward() = fwd;
+
+ // Set speed
+ m_vecMoveSpeed = fwd*planePathSpeed/60.0f;
+ m_fSpeed = planePathSpeed/60.0f;
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+
+ m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f));
+ }
+ }
+
+ bIsInSafePosition = true;
+ GetMatrix().UpdateRW();
+ UpdateRwFrame();
+
+ // Handle streaming and such
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+ if(m_isFarAway){
+ // Switch to LOD model
+ if(m_rwObject && RwObjectGetType(m_rwObject) == rpCLUMP){
+ DeleteRwObject();
+ if(mi->m_planeLodId != -1){
+ PUSH_MEMID(MEMID_WORLD);
+ m_rwObject = CModelInfo::GetModelInfo(mi->m_planeLodId)->CreateInstance();
+ POP_MEMID();
+ if(m_rwObject)
+ m_matrix.AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)m_rwObject)));
+ }
+ }
+ }else if(CStreaming::HasModelLoaded(GetModelIndex())){
+ if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){
+ // Get rid of LOD model
+ m_matrix.Detach();
+ if(m_rwObject){ // useless check
+ if(RwObjectGetType(m_rwObject) == rpATOMIC){ // useless check
+ RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject);
+ RpAtomicDestroy((RpAtomic*)m_rwObject);
+ RwFrameDestroy(f);
+ }
+ m_rwObject = nil;
+ }
+ }
+ // Set high detail model
+ if(m_rwObject == nil){
+ int id = GetModelIndex();
+ m_modelIndex = -1;
+ SetModelIndex(id);
+ }
+ }else{
+ CStreaming::RequestModel(GetModelIndex(), STREAMFLAGS_DEPENDENCY);
+ }
+}
+
+void
+CPlane::PreRender(void)
+{
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ CVector lookVector = GetPosition() - TheCamera.GetPosition();
+ float camDist = lookVector.Magnitude();
+ if(camDist != 0.0f)
+ lookVector *= 1.0f/camDist;
+ else
+ lookVector = CVector(1.0f, 0.0f, 0.0f);
+ float behindness = DotProduct(lookVector, GetForward());
+
+ // Wing lights
+ if(behindness < 0.0f){
+ // in front of plane
+ CVector lightPos = mi->m_positions[PLANE_POS_LIGHT_RIGHT];
+ CVector lightR = GetMatrix() * lightPos;
+ CVector lightL = lightR;
+ lightL -= GetRight()*2.0f*lightPos.x;
+
+ float intensity = -0.6f*behindness + 0.4f;
+ float size = 1.0f - behindness;
+
+ if(behindness < -0.9f && camDist < 50.0f){
+ // directly in front
+ CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightL, size, 240.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightR, size, 240.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ }else{
+ CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightL, size, 240.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightR, size, 240.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ }
+ }
+
+ // Tail light
+ if(CTimer::GetTimeInMilliseconds() & 0x200){
+ CVector pos = GetMatrix() * mi->m_positions[PLANE_POS_LIGHT_TAIL];
+
+ CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255,
+ pos, 1.0f, 120.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ }
+
+#ifdef CPLANE_ROTORS
+ CMatrix mat;
+ CVector pos;
+ m_fRotorRotation += 3.14f/6.5f;
+ if(m_fRotorRotation > 6.28f)
+ m_fRotorRotation -= 6.28f;
+
+ if(m_aPlaneNodes[PLANE_TOPROTOR]){
+ mat.Attach(RwFrameGetMatrix(m_aPlaneNodes[PLANE_TOPROTOR]));
+ pos = mat.GetPosition();
+ mat.SetRotateZ(m_fRotorRotation);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+ if(m_aPlaneNodes[PLANE_BACKROTOR]){
+ mat.Attach(RwFrameGetMatrix(m_aPlaneNodes[PLANE_BACKROTOR]));
+ pos = mat.GetPosition();
+ mat.SetRotateX(m_fRotorRotation);
+ mat.Translate(pos);
+ mat.UpdateRW();
+ }
+#endif
+}
+
+void
+CPlane::Render(void)
+{
+ if(!CCutsceneMgr::IsRunning())
+ CEntity::Render();
+}
+
+#define CRUISE_SPEED (50.0f)
+#define TAXI_SPEED (5.0f)
+
+void
+CPlane::InitPlanes(void)
+{
+ int i;
+
+ CesnaMissionStatus = CESNA_STATUS_NONE;
+
+ // Jumbo
+ if(pPathNodes == nil){
+ pPathNodes = LoadPath("data\\paths\\flight.dat", NumPathNodes, TotalLengthOfFlightPath, true);
+
+ // Figure out which nodes are on ground
+ for(i = 0; i < NumPathNodes; i++){
+ if(pPathNodes[i].p.z < 14.0f){
+ pPathNodes[i].p.z = 14.0f;
+ pPathNodes[i].bOnGround = true;
+ }else
+ pPathNodes[i].bOnGround = false;
+ }
+
+ // Find lading and takeoff points
+ LandingPoint = -1.0f;
+ TakeOffPoint = -1.0f;
+ bool lastOnGround = pPathNodes[NumPathNodes-1].bOnGround;
+ for(i = 0; i < NumPathNodes; i++){
+ if(pPathNodes[i].bOnGround && !lastOnGround)
+ LandingPoint = pPathNodes[i].t;
+ else if(!pPathNodes[i].bOnGround && lastOnGround)
+ TakeOffPoint = pPathNodes[i].t;
+ lastOnGround = pPathNodes[i].bOnGround;
+ }
+
+ // Animation
+ float time = 0.0f;
+ float position = 0.0f;
+ // Start on ground with slow speed
+ aPlaneLineBits[0].type = 1;
+ aPlaneLineBits[0].time = time;
+ aPlaneLineBits[0].position = position;
+ aPlaneLineBits[0].speed = TAXI_SPEED;
+ aPlaneLineBits[0].acceleration = 0.0f;
+ float dist = (TakeOffPoint-500.0f) - position;
+ time += dist/TAXI_SPEED;
+ position += dist;
+
+ // Accelerate to take off
+ aPlaneLineBits[1].type = 2;
+ aPlaneLineBits[1].time = time;
+ aPlaneLineBits[1].position = position;
+ aPlaneLineBits[1].speed = TAXI_SPEED;
+ aPlaneLineBits[1].acceleration = 618.75f/500.0f;
+ time += 500.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f);
+ position += 500.0f;
+
+ // Fly at cruise speed
+ aPlaneLineBits[2].type = 1;
+ aPlaneLineBits[2].time = time;
+ aPlaneLineBits[2].position = position;
+ aPlaneLineBits[2].speed = CRUISE_SPEED;
+ aPlaneLineBits[2].acceleration = 0.0f;
+ dist = LandingPoint - TakeOffPoint;
+ time += dist/CRUISE_SPEED;
+ position += dist;
+
+ // Brake after landing
+ aPlaneLineBits[3].type = 2;
+ aPlaneLineBits[3].time = time;
+ aPlaneLineBits[3].position = position;
+ aPlaneLineBits[3].speed = CRUISE_SPEED;
+ aPlaneLineBits[3].acceleration = -618.75f/500.0f;
+ time += 500.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f);
+ position += 500.0f;
+
+ // Taxi
+ aPlaneLineBits[4].type = 1;
+ aPlaneLineBits[4].time = time;
+ aPlaneLineBits[4].position = position;
+ aPlaneLineBits[4].speed = TAXI_SPEED;
+ aPlaneLineBits[4].acceleration = 0.0f;
+ time += (TotalLengthOfFlightPath - position)/TAXI_SPEED;
+
+ // end
+ aPlaneLineBits[5].time = time;
+ TotalDurationOfFlightPath = time;
+ }
+
+ // Dodo
+ if(pPath2Nodes == nil){
+ pPath2Nodes = LoadPath("data\\paths\\flight2.dat", NumPath2Nodes, TotalLengthOfFlightPath2, true);
+ TotalDurationOfFlightPath2 = TotalLengthOfFlightPath2/CRUISE_SPEED;
+ }
+
+ // Heli
+ if(pPath3Nodes == nil){
+ pPath3Nodes = LoadPath("data\\paths\\flight3.dat", NumPath3Nodes, TotalLengthOfFlightPath3, false);
+ TotalDurationOfFlightPath3 = TotalLengthOfFlightPath3/CRUISE_SPEED;
+ }
+
+ CStreaming::LoadAllRequestedModels(false);
+ CStreaming::RequestModel(MI_AIRTRAIN, 0);
+ CStreaming::LoadAllRequestedModels(false);
+
+ // NB: 3 hardcoded also in CPlaneTrails
+ for(i = 0; i < 3; i++){
+ CPlane *plane = new CPlane(MI_AIRTRAIN, PERMANENT_VEHICLE);
+ plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ plane->SetStatus(STATUS_ABANDONED);
+ plane->bIsLocked = true;
+ plane->m_nPlaneId = i;
+ plane->m_nCurPathNode = 0;
+ CWorld::Add(plane);
+ }
+}
+
+void
+CPlane::Shutdown(void)
+{
+ delete[] pPathNodes;
+ delete[] pPath2Nodes;
+ delete[] pPath3Nodes;
+ delete[] pPath4Nodes;
+ pPathNodes = nil;
+ pPath2Nodes = nil;
+ pPath3Nodes = nil;
+ pPath4Nodes = nil;
+}
+
+CPlaneNode*
+CPlane::LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop)
+{
+ int bp, lp;
+ int i;
+
+ CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r");
+ *gString = '\0';
+ for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++)
+ gString[lp] = work_buff[bp];
+ bp++;
+ gString[lp] = '\0';
+ sscanf(gString, "%d", &numNodes);
+ CPlaneNode *nodes = new CPlaneNode[numNodes];
+
+ for(i = 0; i < numNodes; i++){
+ for(lp = 0; work_buff[bp] != '\n' && work_buff[bp] != '\0'; bp++, lp++)
+ gString[lp] = work_buff[bp];
+ bp++;
+ // BUG: game doesn't terminate string
+ gString[lp] = '\0';
+ sscanf(gString, "%f %f %f", &nodes[i].p.x, &nodes[i].p.y, &nodes[i].p.z);
+ }
+
+ // Calculate length of segments and path
+ totalLength = 0.0f;
+ for(i = 0; i < numNodes; i++){
+ nodes[i].t = totalLength;
+ float l = (nodes[(i+1) % numNodes].p - nodes[i].p).Magnitude2D();
+ if(!loop && i == numNodes-1)
+ l = 0.0f;
+ totalLength += l;
+ }
+
+ return nodes;
+}
+
+int32 LastTimeInPlane, LastTimeNotInPlane;
+bool bCesnasActivated;
+bool bHelisActivated;
+
+void
+CPlane::UpdatePlanes(void)
+{
+ int i, j;
+ uint32 time;
+ float t, deltaT;
+
+ if(CReplay::IsPlayingBack())
+ return;
+
+ // Jumbo jets
+ time = CTimer::GetTimeInMilliseconds();
+ for(i = 0; i < 3; i++){
+ t = TotalDurationOfFlightPath * (float)(time & 0x7FFFF)/0x80000;
+ // find current frame
+ for(j = 0; t > aPlaneLineBits[j+1].time; j++);
+
+ OldPlanePathPosition[i] = PlanePathPosition[i];
+ deltaT = t - aPlaneLineBits[j].time;
+ switch(aPlaneLineBits[j].type){
+ case 0: // standing still
+ PlanePathPosition[i] = aPlaneLineBits[j].position;
+ PlanePathSpeed[i] = 0.0f;
+ break;
+ case 1: // moving with constant speed
+ PlanePathPosition[i] = aPlaneLineBits[j].position + aPlaneLineBits[j].speed*deltaT;
+ PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000) * aPlaneLineBits[j].speed;
+ break;
+ case 2: // accelerating/braking
+ PlanePathPosition[i] = aPlaneLineBits[j].position + (aPlaneLineBits[j].speed + aPlaneLineBits[j].acceleration*deltaT)*deltaT;
+ PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000)*aPlaneLineBits[j].speed + 2.0f*aPlaneLineBits[j].acceleration*deltaT;
+ break;
+ }
+
+ // time offset for each plane
+ time += 0x80000/3;
+ }
+
+ time = CTimer::GetTimeInMilliseconds();
+
+ t = TotalDurationOfFlightPath2/0x80000;
+ PlanePath2Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t;
+ PlanePath2Position[1] = CRUISE_SPEED * ((time + 0x80000/5) & 0x7FFFF)*t;
+ PlanePath2Position[2] = CRUISE_SPEED * ((time + 0x80000/5*2) & 0x7FFFF)*t;
+ PlanePath2Position[3] = CRUISE_SPEED * ((time + 0x80000/5*3) & 0x7FFFF)*t;
+ PlanePath2Position[4] = CRUISE_SPEED * ((time + 0x80000/5*4) & 0x7FFFF)*t;
+ PlanePath2Speed[0] = CRUISE_SPEED*t;
+ PlanePath2Speed[1] = CRUISE_SPEED*t;
+ PlanePath2Speed[2] = CRUISE_SPEED*t;
+ PlanePath2Speed[3] = CRUISE_SPEED*t;
+ PlanePath2Speed[4] = CRUISE_SPEED*t;
+
+ t = TotalDurationOfFlightPath3/0x80000;
+ PlanePath3Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t;
+ PlanePath3Position[1] = CRUISE_SPEED * ((time + 0x80000/4) & 0x7FFFF)*t;
+ PlanePath3Position[2] = CRUISE_SPEED * ((time + 0x80000/4*2) & 0x7FFFF)*t;
+ PlanePath3Position[3] = CRUISE_SPEED * ((time + 0x80000/4*3) & 0x7FFFF)*t;
+ PlanePath3Speed[0] = CRUISE_SPEED*t;
+ PlanePath3Speed[1] = CRUISE_SPEED*t;
+ PlanePath3Speed[2] = CRUISE_SPEED*t;
+ PlanePath3Speed[3] = CRUISE_SPEED*t;
+
+ if(FindPlayerVehicle() && (FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI ||
+ FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE))
+ LastTimeInPlane = CTimer::GetTimeInMilliseconds();
+ else
+ LastTimeNotInPlane = CTimer::GetTimeInMilliseconds();
+
+ if(CTimer::GetTimeInMilliseconds() - LastTimeNotInPlane > 10000){
+ if(!bCesnasActivated){
+ if(CStreaming::HasModelLoaded(MI_DEADDODO)){
+ for(i = 0; i < 5; i++){
+ CPlane *plane = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE);
+ plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ plane->SetStatus(STATUS_ABANDONED);
+ plane->bIsLocked = true;
+ plane->m_nPlaneId = i;
+ plane->m_nCurPathNode = 0;
+ plane->m_bTempPlane = true;
+ CWorld::Add(plane);
+ }
+ bCesnasActivated = true;
+ }else
+ CStreaming::RequestModel(MI_DEADDODO, 0);
+ }
+
+ if(!bHelisActivated){
+ if(CStreaming::HasModelLoaded(MI_CHOPPER)){
+ for(i = 0; i < 4; i++){
+ CPlane *plane = new CPlane(MI_CHOPPER, PERMANENT_VEHICLE);
+ plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ plane->SetStatus(STATUS_ABANDONED);
+ plane->bIsLocked = true;
+ plane->m_nPlaneId = i;
+ plane->m_nCurPathNode = 0;
+ plane->m_bTempPlane = true;
+ CWorld::Add(plane);
+ }
+ bHelisActivated = true;
+ }else
+ CStreaming::RequestModel(MI_CHOPPER, 0);
+ }
+ }else if(CTimer::GetTimeInMilliseconds() - LastTimeInPlane > 10000)
+ RemoveTemporaryPlanes();
+}
+
+void
+CPlane::RemoveTemporaryPlanes(void)
+{
+ int i;
+ if(!bHelisActivated && !bCesnasActivated)
+ return;
+
+ i = CPools::GetVehiclePool()->GetSize();
+ while(--i >= 0){
+ CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i);
+ if(plane && plane->IsPlane() && plane->m_bTempPlane){
+ CWorld::Remove(plane);
+ delete plane;
+ }
+ }
+ bCesnasActivated = false;
+ bHelisActivated = false;
+}
+
+bool
+CPlane::TestRocketCollision(CVector *rocketPos)
+{
+ int i;
+
+ i = CPools::GetVehiclePool()->GetSize();
+ while(--i >= 0){
+ CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i);
+ if(plane &&
+#ifdef EXPLODING_AIRTRAIN
+ (plane->GetModelIndex() == MI_AIRTRAIN || plane->GetModelIndex() == MI_DEADDODO) &&
+#else
+ plane->GetModelIndex() != MI_AIRTRAIN && plane->GetModelIndex() == MI_DEADDODO && // strange check
+#endif
+ !plane->m_bHasBeenHit && (*rocketPos - plane->GetPosition()).Magnitude() < 25.0f){
+ plane->m_nFrameWhenHit = CTimer::GetFrameCounter();
+ plane->m_bHasBeenHit = true;
+ CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_DESTROYED_CESSNA,
+ plane->GetPosition(), i+1983, false);
+ return true;
+ }
+ }
+ return false;
+}
+
+// unused
+// BUG: not in CPlane in the game
+void
+CPlane::CreateIncomingCesna(void)
+{
+ if(CesnaMissionStatus == CESNA_STATUS_FLYING){
+ CWorld::Remove(pDrugRunCesna);
+ delete pDrugRunCesna;
+ pDrugRunCesna = nil;
+ }
+ pDrugRunCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE);
+ pDrugRunCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ pDrugRunCesna->SetStatus(STATUS_ABANDONED);
+ pDrugRunCesna->bIsLocked = true;
+ pDrugRunCesna->m_nPlaneId = 0;
+ pDrugRunCesna->m_nCurPathNode = 0;
+ pDrugRunCesna->m_bIsDrugRunCesna = true;
+ CWorld::Add(pDrugRunCesna);
+
+ CesnaMissionStatus = CESNA_STATUS_FLYING;
+ CesnaMissionStartTime = CTimer::GetTimeInMilliseconds();
+ printf("CPlane::CreateIncomingCesna(void)\n");
+}
+
+// unused
+void
+CPlane::CreateDropOffCesna(void)
+{
+ if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){
+ CWorld::Remove(pDropOffCesna);
+ delete pDropOffCesna;
+ pDropOffCesna = nil;
+ }
+ pDropOffCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE);
+ pDropOffCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ pDropOffCesna->SetStatus(STATUS_ABANDONED);
+ pDropOffCesna->bIsLocked = true;
+ pDropOffCesna->m_nPlaneId = 0;
+ pDropOffCesna->m_nCurPathNode = 0;
+ pDropOffCesna->m_bIsDropOffCesna = true;
+ CWorld::Add(pDropOffCesna);
+
+ DropOffCesnaMissionStatus = CESNA_STATUS_FLYING;
+ DropOffCesnaMissionStartTime = CTimer::GetTimeInMilliseconds();
+ printf("CPlane::CreateDropOffCesna(void)\n");
+}
+
+// all unused
+const CVector CPlane::FindDrugPlaneCoordinates(void) { return pDrugRunCesna->GetPosition(); }
+const CVector CPlane::FindDropOffCesnaCoordinates(void) { return pDropOffCesna->GetPosition(); }
+bool CPlane::HasCesnaLanded(void) { return CesnaMissionStatus == CESNA_STATUS_LANDED; }
+bool CPlane::HasCesnaBeenDestroyed(void) { return CesnaMissionStatus == CESNA_STATUS_DESTROYED; }
+bool CPlane::HasDropOffCesnaBeenShotDown(void) { return DropOffCesnaMissionStatus == CESNA_STATUS_DESTROYED; }
+
+void
+CPlane::Load(void)
+{
+ RemoveTemporaryPlanes();
+}
+
+void
+CPlane::Save(void)
+{
+ RemoveTemporaryPlanes();
+}
diff --git a/src/vehicles/Plane.h b/src/vehicles/Plane.h
new file mode 100644
index 0000000..c8f0204
--- /dev/null
+++ b/src/vehicles/Plane.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "Vehicle.h"
+
+enum ePlaneNodes
+{
+#ifdef CPLANE_ROTORS
+ // for heli
+ PLANE_TOPROTOR,
+ PLANE_BACKROTOR,
+#endif
+ PLANE_WHEEL_FRONT = 2,
+ PLANE_WHEEL_READ,
+ NUM_PLANE_NODES
+};
+
+struct CPlaneNode
+{
+ CVector p; // position
+ float t; // xy-distance from start on path
+ bool bOnGround; // i.e. not flying
+};
+
+struct CPlaneInterpolationLine
+{
+ uint8 type;
+ float time; // when does this keyframe start
+ // initial values at start of frame
+ float position;
+ float speed;
+ float acceleration;
+};
+
+class CPlane : public CVehicle
+{
+public:
+#ifdef CPLANE_ROTORS
+ RwFrame *m_aPlaneNodes[NUM_PLANE_NODES];
+ float m_fRotorRotation;
+#endif
+ int16 m_nPlaneId;
+ int16 m_isFarAway;
+ int16 m_nCurPathNode;
+ float m_fSpeed;
+ uint32 m_nFrameWhenHit;
+ bool m_bHasBeenHit;
+ bool m_bIsDrugRunCesna;
+ bool m_bIsDropOffCesna;
+ bool m_bTempPlane;
+
+ CPlane(int32 id, uint8 CreatedBy);
+ ~CPlane(void);
+
+ // from CEntity
+ void SetModelIndex(uint32 id);
+ void DeleteRwObject(void);
+ void ProcessControl(void);
+ void PreRender(void);
+ void Render(void);
+ void FlagToDestroyWhenNextProcessed() { bRemoveFromWorld = true; }
+
+ static void InitPlanes(void);
+ static void Shutdown(void);
+ static CPlaneNode *LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop);
+ static void RemoveTemporaryPlanes(void);
+ static void UpdatePlanes(void);
+ static bool TestRocketCollision(CVector *rocketPos);
+ static void CreateIncomingCesna(void);
+ static void CreateDropOffCesna(void);
+ static const CVector FindDrugPlaneCoordinates(void);
+ static const CVector FindDropOffCesnaCoordinates(void);
+ static bool HasCesnaLanded(void);
+ static bool HasCesnaBeenDestroyed(void);
+ static bool HasDropOffCesnaBeenShotDown(void);
+ static void Load(void);
+ static void Save(void);
+};
+
+extern float LandingPoint;
+extern float TakeOffPoint;
+extern float PlanePathPosition[3];
diff --git a/src/vehicles/Train.cpp b/src/vehicles/Train.cpp
new file mode 100644
index 0000000..5f0cf36
--- /dev/null
+++ b/src/vehicles/Train.cpp
@@ -0,0 +1,766 @@
+#include "common.h"
+#include "main.h"
+
+#include "Timer.h"
+#include "ModelIndices.h"
+#include "FileMgr.h"
+#include "Streaming.h"
+#include "Pad.h"
+#include "Camera.h"
+#include "Coronas.h"
+#include "World.h"
+#include "Ped.h"
+#include "DMAudio.h"
+#include "HandlingMgr.h"
+#include "Train.h"
+#include "AudioScriptObject.h"
+
+static CTrainNode* pTrackNodes;
+static int16 NumTrackNodes;
+static float StationDist[3] = { 873.0f, 1522.0f, 2481.0f };
+static float TotalLengthOfTrack;
+static float TotalDurationOfTrack;
+static CTrainInterpolationLine aLineBits[17];
+static float EngineTrackPosition[2];
+static float EngineTrackSpeed[2];
+
+static CTrainNode* pTrackNodes_S;
+static int16 NumTrackNodes_S;
+static float StationDist_S[4] = { 55.0f, 1388.0f, 2337.0f, 3989.0f };
+static float TotalLengthOfTrack_S;
+static float TotalDurationOfTrack_S;
+static CTrainInterpolationLine aLineBits_S[18];
+static float EngineTrackPosition_S[4];
+static float EngineTrackSpeed_S[4];
+
+CVector CTrain::aStationCoors[3];
+CVector CTrain::aStationCoors_S[4];
+
+static bool bTrainArrivalAnnounced[3] = {false, false, false};
+
+CTrain::CTrain(int32 id, uint8 CreatedBy)
+ : CVehicle(CreatedBy)
+{
+#ifdef GTA_TRAIN
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
+ m_vehType = VEHICLE_TYPE_TRAIN;
+ pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
+ SetModelIndex(id);
+
+ Doors[0].Init(0.8f, 0.0f, 1, 0);
+ Doors[1].Init(-0.8f, 0.0f, 0, 0);
+
+ m_fMass = 100000000.0f;
+ m_fTurnMass = 100000000.0f;
+ m_fAirResistance = 0.9994f;
+ m_fElasticity = 0.05f;
+
+ m_bProcessDoor = true;
+ m_bTrainStopping = false;
+ m_nTrackId = TRACK_ELTRAIN;
+ m_nNumMaxPassengers = 5;
+ m_nDoorTimer = CTimer::GetTimeInMilliseconds();
+ m_nDoorState = TRAIN_DOOR_CLOSED;
+
+ bUsesCollision = true;
+ SetStatus(STATUS_TRAIN_MOVING);
+
+#ifdef FIX_BUGS
+ m_isFarAway = true;
+#endif
+#else
+ assert(0 && "No trains in this game");
+#endif
+}
+
+void
+CTrain::SetModelIndex(uint32 id)
+{
+#ifdef GTA_TRAIN
+ int i;
+
+ CVehicle::SetModelIndex(id);
+ for(i = 0; i < NUM_TRAIN_NODES; i++)
+ m_aTrainNodes[i] = nil;
+ CClumpModelInfo::FillFrameArray(GetClump(), m_aTrainNodes);
+#endif
+}
+
+void
+CTrain::ProcessControl(void)
+{
+#ifdef GTA_TRAIN
+ if(gbModelViewer || m_isFarAway && (CTimer::GetFrameCounter() + m_nWagonId) & 0xF)
+ return;
+
+ CTrainNode *trackNodes;
+ int16 numTrackNodes;
+ float totalLengthOfTrack;
+ float *engineTrackPosition;
+ float *engineTrackSpeed;
+
+ if(m_nTrackId == TRACK_SUBWAY){
+ trackNodes = pTrackNodes_S;
+ numTrackNodes = NumTrackNodes_S;
+ totalLengthOfTrack = TotalLengthOfTrack_S;
+ engineTrackPosition = EngineTrackPosition_S;
+ engineTrackSpeed = EngineTrackSpeed_S;
+ }else{
+ trackNodes = pTrackNodes;
+ numTrackNodes = NumTrackNodes;
+ totalLengthOfTrack = TotalLengthOfTrack;
+ engineTrackPosition = EngineTrackPosition;
+ engineTrackSpeed = EngineTrackSpeed;
+ }
+
+ float trackPositionRear = engineTrackPosition[m_nWagonGroup] - m_fWagonPosition;
+ if(trackPositionRear < 0.0f)
+ trackPositionRear += totalLengthOfTrack;
+
+ // Advance current node to appropriate position
+ float pos1, pos2;
+ int nextTrackNode = m_nCurTrackNode + 1;
+ pos1 = trackNodes[m_nCurTrackNode].t;
+ if(nextTrackNode < numTrackNodes)
+ pos2 = trackNodes[nextTrackNode].t;
+ else{
+ nextTrackNode = 0;
+ pos2 = totalLengthOfTrack;
+ }
+ while(trackPositionRear < pos1 || trackPositionRear > pos2){
+ m_nCurTrackNode = (m_nCurTrackNode+1) % numTrackNodes;
+ nextTrackNode = m_nCurTrackNode + 1;
+ pos1 = trackNodes[m_nCurTrackNode].t;
+ if(nextTrackNode < numTrackNodes)
+ pos2 = trackNodes[nextTrackNode].t;
+ else{
+ nextTrackNode = 0;
+ pos2 = totalLengthOfTrack;
+ }
+ }
+ float dist = trackNodes[nextTrackNode].t - trackNodes[m_nCurTrackNode].t;
+ if(dist < 0.0f)
+ dist += totalLengthOfTrack;
+ float f = (trackPositionRear - trackNodes[m_nCurTrackNode].t)/dist;
+ CVector posRear = (1.0f - f)*trackNodes[m_nCurTrackNode].p + f*trackNodes[nextTrackNode].p;
+
+ // Now same again for the front
+ float trackPositionFront = trackPositionRear + 20.0f;
+ if(trackPositionFront > totalLengthOfTrack)
+ trackPositionFront -= totalLengthOfTrack;
+ int curTrackNodeFront = m_nCurTrackNode;
+ int nextTrackNodeFront = curTrackNodeFront + 1;
+ pos1 = trackNodes[curTrackNodeFront].t;
+ if(nextTrackNodeFront < numTrackNodes)
+ pos2 = trackNodes[nextTrackNodeFront].t;
+ else{
+ nextTrackNodeFront = 0;
+ pos2 = totalLengthOfTrack;
+ }
+ while(trackPositionFront < pos1 || trackPositionFront > pos2){
+ curTrackNodeFront = (curTrackNodeFront+1) % numTrackNodes;
+ nextTrackNodeFront = curTrackNodeFront + 1;
+ pos1 = trackNodes[curTrackNodeFront].t;
+ if(nextTrackNodeFront < numTrackNodes)
+ pos2 = trackNodes[nextTrackNodeFront].t;
+ else{
+ nextTrackNodeFront = 0;
+ pos2 = totalLengthOfTrack;
+ }
+ }
+ dist = trackNodes[nextTrackNodeFront].t - trackNodes[curTrackNodeFront].t;
+ if(dist < 0.0f)
+ dist += totalLengthOfTrack;
+ f = (trackPositionFront - trackNodes[curTrackNodeFront].t)/dist;
+ CVector posFront = (1.0f - f)*trackNodes[curTrackNodeFront].p + f*trackNodes[nextTrackNodeFront].p;
+
+ // Now set matrix
+ SetPosition((posRear + posFront)/2.0f);
+ CVector fwd = posFront - posRear;
+ fwd.Normalise();
+ CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
+ right.Normalise();
+ CVector up = CrossProduct(right, fwd);
+ GetRight() = right;
+ GetUp() = up;
+ GetForward() = fwd;
+
+ // Set speed
+ m_vecMoveSpeed = fwd*engineTrackSpeed[m_nWagonGroup]/60.0f;
+ m_fSpeed = engineTrackSpeed[m_nWagonGroup]/60.0f;
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+
+ if(engineTrackSpeed[m_nWagonGroup] > 0.001f){
+ SetStatus(STATUS_TRAIN_MOVING);
+ m_bTrainStopping = false;
+ m_bProcessDoor = true;
+ }else{
+ SetStatus(STATUS_TRAIN_NOT_MOVING);
+ m_bTrainStopping = true;
+ }
+
+ m_isFarAway = !((posFront - TheCamera.GetPosition()).Magnitude2D() < sq(250.0f));
+
+ if(m_fWagonPosition == 20.0f && m_fSpeed > 0.0001f)
+ if(Abs(TheCamera.GetPosition().z - GetPosition().z) < 15.0f)
+ CPad::GetPad(0)->StartShake_Train(GetPosition().x, GetPosition().y);
+
+ if(m_bProcessDoor)
+ switch(m_nDoorState){
+ case TRAIN_DOOR_CLOSED:
+ if(m_bTrainStopping){
+ m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000;
+ m_nDoorState = TRAIN_DOOR_OPENING;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_CLOSE, 0.0f);
+ }
+ break;
+
+ case TRAIN_DOOR_OPENING:
+ if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){
+ OpenTrainDoor(1.0f - (m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f);
+ }else{
+ OpenTrainDoor(1.0f);
+ m_nDoorState = TRAIN_DOOR_OPEN;
+ }
+ break;
+
+ case TRAIN_DOOR_OPEN:
+ if(!m_bTrainStopping){
+ m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000;
+ m_nDoorState = TRAIN_DOOR_CLOSING;
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_OPEN, 0.0f);
+ }
+ break;
+
+ case TRAIN_DOOR_CLOSING:
+ if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){
+ OpenTrainDoor((m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f);
+ }else{
+ OpenTrainDoor(0.0f);
+ m_nDoorState = TRAIN_DOOR_CLOSED;
+ m_bProcessDoor = false;
+ }
+ break;
+ }
+
+ GetMatrix().UpdateRW();
+ UpdateRwFrame();
+ RemoveAndAdd();
+
+ bIsStuck = false;
+ bIsInSafePosition = true;
+ bWasPostponed = false;
+
+ // request/remove model
+ if(m_isFarAway){
+ if(m_rwObject)
+ DeleteRwObject();
+ }else if(CStreaming::HasModelLoaded(MI_TRAIN)){
+ if(m_rwObject == nil){
+ m_modelIndex = -1;
+ SetModelIndex(MI_TRAIN);
+ }
+ }else{
+ if(FindPlayerCoors().z * GetPosition().z >= 0.0f)
+ CStreaming::RequestModel(MI_TRAIN, STREAMFLAGS_DEPENDENCY);
+ }
+
+ // Hit stuff
+ if(m_bIsFirstWagon && GetStatus()== STATUS_TRAIN_MOVING){
+ CVector front = GetPosition() + GetForward()*GetColModel()->boundingBox.max.y + m_vecMoveSpeed*CTimer::GetTimeStep();
+
+ int x, xmin, xmax;
+ int y, ymin, ymax;
+
+ xmin = CWorld::GetSectorIndexX(front.x - 3.0f);
+ if(xmin < 0) xmin = 0;
+ xmax = CWorld::GetSectorIndexX(front.x + 3.0f);
+ if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1;
+ ymin = CWorld::GetSectorIndexY(front.y - 3.0f);
+ if(ymin < 0) ymin = 0;
+ ymax = CWorld::GetSectorIndexY(front.y + 3.0f);
+ if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1;
+
+ CWorld::AdvanceCurrentScanCode();
+
+ for(y = ymin; y <= ymax; y++)
+ for(x = xmin; x <= xmax; x++){
+ CSector *s = CWorld::GetSector(x, y);
+ TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES]);
+ TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
+ TrainHitStuff(s->m_lists[ENTITYLIST_PEDS]);
+ TrainHitStuff(s->m_lists[ENTITYLIST_PEDS_OVERLAP]);
+ }
+ }
+#endif GTA_TRAIN
+}
+
+void
+CTrain::PreRender(void)
+{
+#ifdef GTA_TRAIN
+ CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+
+ if(m_bIsFirstWagon){
+ CVector lookVector = GetPosition() - TheCamera.GetPosition();
+ float camDist = lookVector.Magnitude();
+ if(camDist != 0.0f)
+ lookVector *= 1.0f/camDist;
+ else
+ lookVector = CVector(1.0f, 0.0f, 0.0f);
+ float behindness = DotProduct(lookVector, GetForward());
+
+ if(behindness < 0.0f){
+ // In front of train
+ CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_FRONT];
+ CVector lightR = GetMatrix() * lightPos;
+ CVector lightL = lightR;
+ lightL -= GetRight()*2.0f*lightPos.x;
+
+ float intensity = -0.4f*behindness + 0.2f;
+ float size = 1.0f - behindness;
+
+ if(behindness < -0.9f && camDist < 35.0f){
+ // directly in front
+ CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightL, size, 80.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightR, size, 80.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ }else{
+ CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightL, size, 80.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255,
+ lightR, size, 80.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ }
+ }
+ }
+
+ if(m_bIsLastWagon){
+ CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_REAR];
+ CVector lightR = GetMatrix() * lightPos;
+ CVector lightL = lightR;
+ lightL -= GetRight()*2.0f*lightPos.x;
+
+ CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255,
+ lightL, 1.0f, 80.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ CCoronas::RegisterCorona((uintptr)this + 13, 255, 0, 0, 255,
+ lightR, 1.0f, 80.0f,
+ CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
+ CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+ }
+#endif
+}
+
+void
+CTrain::Render(void)
+{
+#ifdef GTA_TRAIN
+ CEntity::Render();
+#endif
+}
+
+void
+CTrain::TrainHitStuff(CPtrList &list)
+{
+#ifdef GTA_TRAIN
+ CPtrNode *node;
+ CPhysical *phys;
+
+ for(node = list.first; node; node = node->next){
+ phys = (CPhysical*)node->item;
+ if(phys != this && Abs(this->GetPosition().z - phys->GetPosition().z) < 1.5f)
+ phys->bHitByTrain = true;
+ }
+#endif
+}
+
+void
+CTrain::AddPassenger(CPed *ped)
+{
+#ifdef GTA_TRAIN
+ int i = ped->m_vehDoor;
+ if((i == TRAIN_POS_LEFT_ENTRY || i == TRAIN_POS_MID_ENTRY || i == TRAIN_POS_RIGHT_ENTRY) && pPassengers[i] == nil){
+ pPassengers[i] = ped;
+ m_nNumPassengers++;
+ }else{
+ for(i = 0; i < 6; i++)
+ if(pPassengers[i] == nil){
+ pPassengers[i] = ped;
+ m_nNumPassengers++;
+ return;
+ }
+ }
+#endif
+}
+
+void
+CTrain::OpenTrainDoor(float ratio)
+{
+#ifdef GTA_TRAIN
+ if(m_rwObject == nil)
+ return;
+
+ CMatrix doorL(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_LHS]));
+ CMatrix doorR(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_RHS]));
+ CVector posL = doorL.GetPosition();
+ CVector posR = doorR.GetPosition();
+
+ bool isClosed = Doors[0].IsClosed(); // useless
+
+ Doors[0].Open(ratio);
+ Doors[1].Open(ratio);
+
+ if(isClosed)
+ Doors[0].RetTranslationWhenClosed(); // useless
+
+ posL.y = Doors[0].m_fPosn;
+ posR.y = Doors[1].m_fPosn;
+
+ doorL.SetTranslate(posL);
+ doorR.SetTranslate(posR);
+
+ doorL.UpdateRW();
+ doorR.UpdateRW();
+#endif
+}
+
+
+
+void
+CTrain::InitTrains(void)
+{
+#ifdef GTA_TRAIN
+ int i, j;
+ CTrain *train;
+
+ // El train
+ if(pTrackNodes == nil)
+ ReadAndInterpretTrackFile("data\\paths\\tracks.dat", &pTrackNodes, &NumTrackNodes, 3, StationDist,
+ &TotalLengthOfTrack, &TotalDurationOfTrack, aLineBits, false);
+ // Subway
+ if(pTrackNodes_S == nil)
+ ReadAndInterpretTrackFile("data\\paths\\tracks2.dat", &pTrackNodes_S, &NumTrackNodes_S, 4, StationDist_S,
+ &TotalLengthOfTrack_S, &TotalDurationOfTrack_S, aLineBits_S, true);
+
+ int trainId;
+ CStreaming::LoadAllRequestedModels(false);
+ if(CModelInfo::GetModelInfo("train", &trainId))
+ CStreaming::RequestModel(trainId, 0);
+ CStreaming::LoadAllRequestedModels(false);
+
+ // El-Train wagons
+ float wagonPositions[] = { 0.0f, 20.0f, 40.0f, 0.0f, 20.0f };
+ int8 firstWagon[] = { 1, 0, 0, 1, 0 };
+ int8 lastWagon[] = { 0, 0, 1, 0, 1 };
+ int16 wagonGroup[] = { 0, 0, 0, 1, 1 };
+ for(i = 0; i < 5; i++){
+ train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE);
+ train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ train->SetStatus(STATUS_ABANDONED);
+ train->bIsLocked = true;
+ train->m_fWagonPosition = wagonPositions[i];
+ train->m_bIsFirstWagon = firstWagon[i];
+ train->m_bIsLastWagon = lastWagon[i];
+ train->m_nWagonGroup = wagonGroup[i];
+ train->m_nWagonId = i;
+ train->m_nCurTrackNode = 0;
+ CWorld::Add(train);
+ }
+
+ // Subway wagons
+ float wagonPositions_S[] = { 0.0f, 20.0f, 0.0f, 20.0f, 0.0f, 20.0f, 0.0f, 20.0f };
+ int8 firstWagon_S[] = { 1, 0, 1, 0, 1, 0, 1, 0 };
+ int8 lastWagon_S[] = { 0, 1, 0, 1, 0, 1, 0, 1 };
+ int16 wagonGroup_S[] = { 0, 0, 1, 1, 2, 2, 3, 3 };
+ for(i = 0; i < 8; i++){
+ train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE);
+ train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
+ train->SetStatus(STATUS_ABANDONED);
+ train->bIsLocked = true;
+ train->m_fWagonPosition = wagonPositions_S[i];
+ train->m_bIsFirstWagon = firstWagon_S[i];
+ train->m_bIsLastWagon = lastWagon_S[i];
+ train->m_nWagonGroup = wagonGroup_S[i];
+ train->m_nWagonId = i;
+ train->m_nCurTrackNode = 0;
+ train->m_nTrackId = TRACK_SUBWAY;
+ CWorld::Add(train);
+ }
+
+ // This code is actually useless, it seems it was used for announcements once
+ for(i = 0; i < 3; i++){
+ for(j = 0; pTrackNodes[j].t < StationDist[i]; j++);
+ aStationCoors[i] = pTrackNodes[j].p;
+ }
+ for(i = 0; i < 4; i++){
+ for(j = 0; pTrackNodes_S[j].t < StationDist_S[i]; j++);
+ aStationCoors_S[i] = pTrackNodes_S[j].p;
+ }
+#endif
+}
+
+void
+CTrain::Shutdown(void)
+{
+#ifdef GTA_TRAIN
+ delete[] pTrackNodes;
+ delete[] pTrackNodes_S;
+ pTrackNodes = nil;
+ pTrackNodes_S = nil;
+#endif
+}
+
+void
+CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists,
+ float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail)
+{
+#ifdef GTA_TRAIN
+ bool readingFile = false;
+ int bp, lp;
+ int i, tmp;
+
+ if(*nodes == nil){
+ readingFile = true;
+
+ CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r");
+ *gString = '\0';
+ for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++)
+ gString[lp] = work_buff[bp];
+ bp++;
+ // BUG: game doesn't terminate string and uses numNodes in sscanf directly
+ gString[lp] = '\0';
+ sscanf(gString, "%d", &tmp);
+ *numNodes = tmp;
+ *nodes = new CTrainNode[*numNodes];
+
+ for(i = 0; i < *numNodes; i++){
+ *gString = '\0';
+ for(lp = 0; work_buff[bp] != '\n'; bp++, lp++)
+ gString[lp] = work_buff[bp];
+ bp++;
+ // BUG: game doesn't terminate string
+ gString[lp] = '\0';
+ sscanf(gString, "%f %f %f", &(*nodes)[i].p.x, &(*nodes)[i].p.y, &(*nodes)[i].p.z);
+ }
+
+ // Coordinates are of one of the rails, but we want the center
+ float toCenter = rightRail ? 0.9f : -0.9f;
+ CVector fwd;
+ for(i = 0; i < *numNodes; i++){
+ if(i == *numNodes-1)
+ fwd = (*nodes)[0].p - (*nodes)[i].p;
+ else
+ fwd = (*nodes)[i+1].p - (*nodes)[i].p;
+ CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
+ right.Normalise();
+ (*nodes)[i].p -= right*toCenter;
+ }
+ }
+
+ // Calculate length of segments and track
+ float t = 0.0f;
+ for(i = 0; i < *numNodes; i++){
+ (*nodes)[i].t = t;
+ t += ((*nodes)[(i+1) % (*numNodes)].p - (*nodes)[i].p).Magnitude2D();
+ }
+ *totalLength = t;
+
+ // Find correct z values
+ if(readingFile){
+ CColPoint colpoint;
+ CEntity *entity;
+ for(i = 0; i < *numNodes; i++){
+ CVector p = (*nodes)[i].p;
+ p.z += 1.0f;
+ if(CWorld::ProcessVerticalLine(p, p.z-0.5f, colpoint, entity, true, false, false, false, true, false, nil))
+ (*nodes)[i].p.z = colpoint.point.z;
+ (*nodes)[i].p.z += 0.2f;
+ }
+ }
+
+ // Create animation for stopping at stations
+ // TODO: figure out magic numbers?
+ float position = 0.0f;
+ float time = 0.0f;
+ int j = 0;
+ for(i = 0; i < numStations; i++){
+ // Start at full speed
+ interpLines[j].type = 1;
+ interpLines[j].time = time;
+ interpLines[j].position = position;
+ interpLines[j].speed = 15.0f;
+ interpLines[j].acceleration = 0.0f;
+ j++;
+ // distance to next keyframe
+ float dist = (stationDists[i]-40.0f) - position;
+ time += dist/15.0f;
+ position += dist;
+
+ // Now slow down 40 units before stop
+ interpLines[j].type = 2;
+ interpLines[j].time = time;
+ interpLines[j].position = position;
+ interpLines[j].speed = 15.0f;
+ interpLines[j].acceleration = -45.0f/32.0f;
+ j++;
+ time += 80.0f/15.0f;
+ position += 40.0f; // at station
+
+ // stopping
+ interpLines[j].type = 0;
+ interpLines[j].time = time;
+ interpLines[j].position = position;
+ interpLines[j].speed = 0.0f;
+ interpLines[j].acceleration = 0.0f;
+ j++;
+ time += 25.0f;
+
+ // accelerate again
+ interpLines[j].type = 2;
+ interpLines[j].time = time;
+ interpLines[j].position = position;
+ interpLines[j].speed = 0.0f;
+ interpLines[j].acceleration = 45.0f/32.0f;
+ j++;
+ time += 80.0f/15.0f;
+ position += 40.0f; // after station
+ }
+ // last keyframe
+ interpLines[j].type = 1;
+ interpLines[j].time = time;
+ interpLines[j].position = position;
+ interpLines[j].speed = 15.0f;
+ interpLines[j].acceleration = 0.0f;
+ j++;
+ *totalDuration = time + (*totalLength - position)/15.0f;
+
+ // end
+ interpLines[j].time = *totalDuration;
+#endif
+}
+
+void
+PlayAnnouncement(uint8 sound, uint8 station)
+{
+ // this was gone in a PC version but inlined on PS2
+ cAudioScriptObject *obj = new cAudioScriptObject;
+ obj->AudioId = sound;
+ obj->Posn = CTrain::aStationCoors[station];
+ obj->AudioEntity = AEHANDLE_NONE;
+ DMAudio.CreateOneShotScriptObject(obj);
+}
+
+void
+ProcessTrainAnnouncements(void)
+{
+#ifdef GTA_TRAIN
+ for (int i = 0; i < ARRAY_SIZE(StationDist); i++) {
+ for (int j = 0; j < ARRAY_SIZE(EngineTrackPosition); j++) {
+ if (!bTrainArrivalAnnounced[i]) {
+ float preDist = StationDist[i] - 100.0f;
+ if (preDist < 0.0f)
+ preDist += TotalLengthOfTrack;
+ if (EngineTrackPosition[j] > preDist && EngineTrackPosition[j] < StationDist[i]) {
+ bTrainArrivalAnnounced[i] = true;
+ PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1, i);
+ break;
+ }
+ } else {
+ float postDist = StationDist[i] + 10.0f;
+#ifdef FIX_BUGS
+ if (postDist > TotalLengthOfTrack)
+ postDist -= TotalLengthOfTrack;
+#else
+ if (postDist < 0.0f) // does this even make sense here?
+ postDist += TotalLengthOfTrack;
+#endif
+ if (EngineTrackPosition[j] > StationDist[i] && EngineTrackPosition[j] < postDist) {
+ bTrainArrivalAnnounced[i] = false;
+ PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2, i);
+ break;
+ }
+ }
+ }
+ }
+#endif
+}
+
+void
+CTrain::UpdateTrains(void)
+{
+#ifdef GTA_TRAIN
+ int i, j;
+ uint32 time;
+ float t, deltaT;
+
+ if(TheCamera.GetPosition().x > 200.0f && TheCamera.GetPosition().x < 1600.0f &&
+ TheCamera.GetPosition().y > -1000.0f && TheCamera.GetPosition().y < 500.0f){
+ // Update El-Train
+
+ time = CTimer::GetTimeInMilliseconds();
+ for(i = 0; i < 2; i++){
+ t = TotalDurationOfTrack * (float)(time & 0x1FFFF)/0x20000;
+ // find current frame
+ for(j = 0; t > aLineBits[j+1].time; j++);
+
+ deltaT = t - aLineBits[j].time;
+ switch(aLineBits[j].type){
+ case 0: // standing still
+ EngineTrackPosition[i] = aLineBits[j].position;
+ EngineTrackSpeed[i] = 0.0f;
+ break;
+ case 1: // moving with constant speed
+ EngineTrackPosition[i] = aLineBits[j].position + aLineBits[j].speed*deltaT;
+ EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000) * aLineBits[j].speed;
+ break;
+ case 2: // accelerating/braking
+ EngineTrackPosition[i] = aLineBits[j].position + (aLineBits[j].speed + aLineBits[j].acceleration*deltaT)*deltaT;
+ EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000)*aLineBits[j].speed + 2.0f*aLineBits[j].acceleration*deltaT;
+ break;
+ }
+
+ // time offset for each train
+ time += 0x20000/2;
+ }
+
+ ProcessTrainAnnouncements();
+ }
+
+ // Update Subway
+ time = CTimer::GetTimeInMilliseconds();
+ for(i = 0; i < 4; i++){
+ t = TotalDurationOfTrack_S * (float)(time & 0x3FFFF)/0x40000;
+ // find current frame
+ for(j = 0; t > aLineBits_S[j+1].time; j++);
+
+ deltaT = t - aLineBits_S[j].time;
+ switch(aLineBits_S[j].type){
+ case 0: // standing still
+ EngineTrackPosition_S[i] = aLineBits_S[j].position;
+ EngineTrackSpeed_S[i] = 0.0f;
+ break;
+ case 1: // moving with constant speed
+ EngineTrackPosition_S[i] = aLineBits_S[j].position + aLineBits_S[j].speed*deltaT;
+ EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000) * aLineBits_S[j].speed;
+ break;
+ case 2: // accelerating/braking
+ EngineTrackPosition_S[i] = aLineBits_S[j].position + (aLineBits_S[j].speed + aLineBits_S[j].acceleration*deltaT)*deltaT;
+ EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000)*aLineBits_S[j].speed + 2.0f*aLineBits_S[j].acceleration*deltaT;
+ break;
+ }
+
+ // time offset for each train
+ time += 0x40000/4;
+ }
+#endif
+}
diff --git a/src/vehicles/Train.h b/src/vehicles/Train.h
new file mode 100644
index 0000000..57cd28d
--- /dev/null
+++ b/src/vehicles/Train.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "Vehicle.h"
+#include "Door.h"
+
+enum
+{
+ TRACK_ELTRAIN,
+ TRACK_SUBWAY
+};
+
+enum
+{
+ TRAIN_DOOR_CLOSED,
+ TRAIN_DOOR_OPENING,
+ TRAIN_DOOR_OPEN,
+ TRAIN_DOOR_CLOSING
+};
+
+enum eTrainNodes
+{
+ TRAIN_DOOR_LHS = 1,
+ TRAIN_DOOR_RHS,
+ NUM_TRAIN_NODES
+};
+
+struct CTrainNode
+{
+ CVector p; // position
+ float t; // xy-distance from start on track
+};
+
+struct CTrainInterpolationLine
+{
+ uint8 type;
+ float time; // when does this keyframe start
+ // initial values at start of frame
+ float position;
+ float speed;
+ float acceleration;
+};
+
+class CTrain : public CVehicle
+{
+public:
+ // 0x288
+ float m_fWagonPosition;
+ int16 m_nWagonId;
+ int16 m_isFarAway; // don't update so often?
+ int16 m_nCurTrackNode;
+ int16 m_nWagonGroup;
+ float m_fSpeed;
+ bool m_bProcessDoor;
+ bool m_bTrainStopping;
+ bool m_bIsFirstWagon;
+ bool m_bIsLastWagon;
+ uint8 m_nTrackId; // or m_bUsesSubwayTracks?
+ uint32 m_nDoorTimer;
+ int16 m_nDoorState;
+ CTrainDoor Doors[2];
+ RwFrame *m_aTrainNodes[NUM_TRAIN_NODES];
+
+ // unused
+ static CVector aStationCoors[3];
+ static CVector aStationCoors_S[4];
+
+ CTrain(int32 id, uint8 CreatedBy);
+
+ // from CEntity
+ void SetModelIndex(uint32 id);
+ void ProcessControl(void);
+ void PreRender(void);
+ void Render(void);
+
+ void AddPassenger(CPed *ped);
+ void OpenTrainDoor(float ratio);
+ void TrainHitStuff(CPtrList &list);
+
+ static void InitTrains(void);
+ static void Shutdown(void);
+ static void ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists,
+ float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail);
+ static void UpdateTrains(void);
+};
diff --git a/src/vehicles/Transmission.cpp b/src/vehicles/Transmission.cpp
new file mode 100644
index 0000000..1aeabfe
--- /dev/null
+++ b/src/vehicles/Transmission.cpp
@@ -0,0 +1,130 @@
+#include "common.h"
+
+#include "Timer.h"
+#include "HandlingMgr.h"
+#include "Transmission.h"
+
+void
+cTransmission::InitGearRatios(void)
+{
+ static tGear *pGearRatio0 = nil;
+ static tGear *pGearRatio1 = nil;
+ int i;
+ float velocityDiff;
+
+ memset(Gears, 0, sizeof(Gears));
+
+ for(i = 1; i <= nNumberOfGears; i++){
+ pGearRatio0 = &Gears[i-1];
+ pGearRatio1 = &Gears[i];
+
+ pGearRatio1->fMaxVelocity = (float)i / nNumberOfGears * fMaxVelocity;
+
+ velocityDiff = pGearRatio1->fMaxVelocity - pGearRatio0->fMaxVelocity;
+
+ if(i >= nNumberOfGears){
+ pGearRatio1->fShiftUpVelocity = fMaxVelocity;
+ }else{
+ Gears[i+1].fShiftDownVelocity = velocityDiff*0.42f + pGearRatio0->fMaxVelocity;
+ pGearRatio1->fShiftUpVelocity = velocityDiff*0.6667f + pGearRatio0->fMaxVelocity;
+ }
+ }
+
+ // Reverse gear
+ Gears[0].fMaxVelocity = fMaxReverseVelocity;
+ Gears[0].fShiftUpVelocity = -0.01f;
+ Gears[0].fShiftDownVelocity = fMaxReverseVelocity;
+
+ Gears[1].fShiftDownVelocity = -0.01f;
+}
+
+void
+cTransmission::CalculateGearForSimpleCar(float speed, uint8 &gear)
+{
+ static tGear *pGearRatio;
+
+ pGearRatio = &Gears[gear];
+ fCurVelocity = speed;
+ if(speed > pGearRatio->fShiftUpVelocity)
+ gear++;
+ else if(speed < pGearRatio->fShiftDownVelocity){
+ if(gear - 1 < 0)
+ gear = 0;
+ else
+ gear--;
+ }
+}
+
+float
+cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat)
+{
+ static float fAcceleration = 0.0f;
+ static float fVelocity;
+ static float fCheat;
+ static tGear *pGearRatio;
+
+ fVelocity = velocity;
+ if(fVelocity < fMaxReverseVelocity){
+ fVelocity = fMaxReverseVelocity;
+ return 0.0f;
+ }
+ if(fVelocity > fMaxVelocity){
+ fVelocity = fMaxVelocity;
+ return 0.0f;
+ }
+ fCurVelocity = fVelocity;
+
+ assert(gear <= nNumberOfGears);
+
+ pGearRatio = &Gears[gear];
+ if(fVelocity > pGearRatio->fShiftUpVelocity){
+ if(gear != 0 || gasPedal > 0.0f){
+ gear++;
+ return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false);
+ }
+ }else if(fVelocity < pGearRatio->fShiftDownVelocity && gear != 0){
+ if(gear != 1 || gasPedal < 0.0f){
+ gear--;
+ return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false);
+ }
+ }
+
+ float speedMul, accelMul;
+
+ if(gear < 1){
+ // going reverse
+ accelMul = (Flags & HANDLING_2G_BOOST) ? 2.0f : 1.0f;
+ speedMul = -1.0f;
+ }else if(nNumberOfGears == 1){
+ accelMul = 1.0f;
+ speedMul = 1.0f;
+ }else{
+ // BUG or not? this is 1.0 normally but 0.0 in the highest gear
+ float f = 1.0f - (gear-1)/(nNumberOfGears-1);
+ speedMul = 3.0f*sq(f) + 1.0f;
+ // This is pretty ugly, could be written more clearly
+ if(Flags & HANDLING_2G_BOOST){
+ if(gear == 1)
+ accelMul = (Flags & HANDLING_1G_BOOST) ? 2.0f : 1.6f;
+ else if(gear == 2)
+ accelMul = 1.3f;
+ else
+ accelMul = 1.0f;
+ }else if(Flags & HANDLING_1G_BOOST && gear == 1){
+ accelMul = 2.0f;
+ }else
+ accelMul = 1.0f;
+ }
+
+ if(cheat)
+ fCheat = 1.2f;
+ else
+ fCheat = 1.0f;
+ float targetVelocity = Gears[gear].fMaxVelocity*speedMul*fCheat;
+ float accel = (targetVelocity - fVelocity) * (fEngineAcceleration*accelMul) / Abs(targetVelocity);
+ if(Abs(fVelocity) < Abs(Gears[gear].fMaxVelocity*fCheat))
+ fAcceleration = gasPedal * accel * CTimer::GetTimeStep();
+ else
+ fAcceleration = 0.0f;
+ return fAcceleration;
+}
diff --git a/src/vehicles/Transmission.h b/src/vehicles/Transmission.h
new file mode 100644
index 0000000..a3d1551
--- /dev/null
+++ b/src/vehicles/Transmission.h
@@ -0,0 +1,28 @@
+#pragma once
+
+struct tGear
+{
+ float fMaxVelocity;
+ float fShiftUpVelocity;
+ float fShiftDownVelocity;
+};
+
+class cTransmission
+{
+public:
+ // Gear 0 is reverse, 1-5 are forward
+ tGear Gears[6];
+ char nDriveType;
+ char nEngineType;
+ int8 nNumberOfGears;
+ uint8 Flags;
+ float fEngineAcceleration;
+ float fMaxVelocity;
+ float fMaxCruiseVelocity;
+ float fMaxReverseVelocity;
+ float fCurVelocity;
+
+ void InitGearRatios(void);
+ void CalculateGearForSimpleCar(float speed, uint8 &gear);
+ float CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat);
+};
diff --git a/src/vehicles/Vehicle.cpp b/src/vehicles/Vehicle.cpp
new file mode 100644
index 0000000..4e6c24e
--- /dev/null
+++ b/src/vehicles/Vehicle.cpp
@@ -0,0 +1,2517 @@
+#include "common.h"
+#include "main.h"
+
+#include "General.h"
+#include "Timer.h"
+#include "Pad.h"
+#include "Vehicle.h"
+#include "Bike.h"
+#include "Automobile.h"
+#include "Pools.h"
+#include "HandlingMgr.h"
+#include "CarCtrl.h"
+#include "Population.h"
+#include "ModelIndices.h"
+#include "World.h"
+#include "Lights.h"
+#include "PointLights.h"
+#include "Renderer.h"
+#include "VisibilityPlugins.h"
+#include "DMAudio.h"
+#include "Radar.h"
+#include "Fire.h"
+#include "Darkel.h"
+#include "Streaming.h"
+#include "Camera.h"
+#include "Stats.h"
+#include "Garages.h"
+#include "Wanted.h"
+#include "SurfaceTable.h"
+#include "Particle.h"
+#include "WaterLevel.h"
+#include "Timecycle.h"
+#include "Weather.h"
+#include "Coronas.h"
+
+bool CVehicle::bWheelsOnlyCheat;
+bool CVehicle::bAllDodosCheat;
+bool CVehicle::bCheat3;
+bool CVehicle::bCheat4;
+bool CVehicle::bCheat5;
+bool CVehicle::bCheat8;
+bool CVehicle::bCheat9;
+bool CVehicle::bCheat10;
+bool CVehicle::bHoverCheat;
+bool CVehicle::bAllTaxisHaveNitro;
+bool CVehicle::m_bDisableMouseSteering = true;
+bool CVehicle::bDisableRemoteDetonation;
+bool CVehicle::bDisableRemoteDetonationOnContact;
+#ifndef MASTER
+bool CVehicle::m_bDisplayHandlingInfo;
+#endif
+
+void *CVehicle::operator new(size_t sz) { return CPools::GetVehiclePool()->New(); }
+void *CVehicle::operator new(size_t sz, int handle) { return CPools::GetVehiclePool()->New(handle); }
+void CVehicle::operator delete(void *p, size_t sz) { CPools::GetVehiclePool()->Delete((CVehicle*)p); }
+void CVehicle::operator delete(void *p, int handle) { CPools::GetVehiclePool()->Delete((CVehicle*)p); }
+
+#ifdef FIX_BUGS
+// I think they meant that
+#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (MYRAND_MAX * 35 / 100)
+#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (MYRAND_MAX * 70 / 100)
+#else
+#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (35000)
+#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (70000)
+#endif
+#define DAMAGE_HEALTH_TO_FLEE_ALWAYS (200.0f)
+#define DAMAGE_HEALTH_TO_CATCH_FIRE (250.0f)
+
+
+CVehicle::CVehicle(uint8 CreatedBy)
+{
+ int i;
+
+ m_nCurrentGear = 1;
+ m_fChangeGearTime = 0.0f;
+ m_fSteerInput = 0.0f;
+ m_type = ENTITY_TYPE_VEHICLE;
+ VehicleCreatedBy = CreatedBy;
+ m_nRouteSeed = 0;
+ bIsLocked = false;
+ bIsLawEnforcer = false;
+ bIsAmbulanceOnDuty = false;
+ bIsFireTruckOnDuty = false;
+#ifdef FIX_BUGS
+ bIsHandbrakeOn = false;
+#endif
+ CCarCtrl::UpdateCarCount(this, false);
+ m_fHealth = 1000.0f;
+ bEngineOn = true;
+ bFreebies = true;
+ pDriver = nil;
+ m_nNumPassengers = 0;
+ m_nNumGettingIn = 0;
+ m_nGettingInFlags = 0;
+ m_nGettingOutFlags = 0;
+ m_nNumMaxPassengers = ARRAY_SIZE(pPassengers);
+ for(i = 0; i < m_nNumMaxPassengers; i++)
+ pPassengers[i] = nil;
+ m_nBombTimer = 0;
+ m_pBlowUpEntity = nil;
+ m_nPacManPickupsCarried = 0;
+ bComedyControls = false;
+ bCraneMessageDone = false;
+ bExtendedRange = false;
+ bTakeLessDamage = false;
+ bIsDamaged = false;
+ bFadeOut = false;
+ bIsBeingCarJacked = false;
+ m_nTimeOfDeath = 0;
+ m_pCarFire = nil;
+ bHasBeenOwnedByPlayer = false;
+ bCreateRoadBlockPeds = false;
+ bCanBeDamaged = true;
+ bUsingSpecialColModel = false;
+ bOccupantsHaveBeenGenerated = false;
+ bGunSwitchedOff = false;
+ m_nGunFiringTime = 0;
+ m_nTimeBlocked = 0;
+ bLightsOn = false;
+ bVehicleColProcessed = false;
+ m_numPedsUseItAsCover = 0;
+ bIsCarParkVehicle = false;
+ bHasAlreadyBeenRecorded = false;
+ m_bSirenOrAlarm = false;
+ m_nCarHornTimer = 0;
+ m_nCarHornPattern = 0;
+ m_nCarHornDelay = 0;
+ bPartOfConvoy = false;
+ bHeliMinimumTilt = false;
+ bAudioChangingGear = false;
+ bIsDrowning = false;
+ bTyresDontBurst = false;
+ bCreatedAsPoliceVehicle = false;
+ bRestingOnPhysical = false;
+ bParking = false;
+ bCanPark = CGeneral::GetRandomNumberInRange(0.0f, 1.0f) < 0.0f; // never true. probably doesn't work very well
+ bIsVan = false;
+ bIsBus = false;
+ bIsBig = false;
+ bLowVehicle = false;
+
+ m_bombType = CARBOMB_NONE;
+ bDriverLastFrame = false;
+ m_pBombRigger = nil;
+
+ m_nSetPieceExtendedRangeTime = 0;
+ m_nAlarmState = 0;
+ m_nDoorLock = CARLOCK_UNLOCKED;
+ m_nLastWeaponDamage = -1;
+ m_pLastDamageEntity = nil;
+ m_fMapObjectHeightAhead = m_fMapObjectHeightBehind = 0.0f;
+ m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, this);
+ if(m_audioEntityId >= 0)
+ DMAudio.SetEntityStatus(m_audioEntityId, true);
+ //m_nRadioStation = CGeneral::GetRandomNumber() % NUM_RADIOS;
+ switch(GetModelIndex()){
+ case MI_HUNTER:
+ case MI_ANGEL:
+ case MI_FREEWAY:
+ m_nRadioStation = V_ROCK;
+ break;
+ case MI_RCBARON:
+ case MI_RCBANDIT:
+ case MI_RCRAIDER:
+ case MI_RCGOBLIN:
+ case MI_TOPFUN:
+ case MI_CADDY:
+ case MI_BAGGAGE:
+ m_nRadioStation = RADIO_OFF;
+ break;
+ default:
+ m_nRadioStation = CGeneral::GetRandomNumber() % NUM_RADIOS;
+ break;
+ }
+ m_pCurGroundEntity = nil;
+ m_bRainAudioCounter = 0;
+ m_bRainSamplesCounter = 0;
+ m_comedyControlState = 0;
+ m_aCollPolys[0].valid = false;
+ m_aCollPolys[1].valid = false;
+ AutoPilot.m_nCarMission = MISSION_NONE;
+ AutoPilot.m_nTempAction = TEMPACT_NONE;
+ AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds();
+ AutoPilot.m_bStayInCurrentLevel = false;
+ AutoPilot.m_bIgnorePathfinding = false;
+ AutoPilot.m_nSwitchDistance = 20;
+}
+
+CVehicle::~CVehicle()
+{
+ m_nAlarmState = 0;
+ if (m_audioEntityId >= 0){
+ DMAudio.DestroyEntity(m_audioEntityId);
+ m_audioEntityId = -5;
+ }
+ CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(this));
+ if (pDriver)
+ pDriver->FlagToDestroyWhenNextProcessed();
+ for (int i = 0; i < m_nNumMaxPassengers; i++){
+ if (pPassengers[i])
+ pPassengers[i]->FlagToDestroyWhenNextProcessed();
+ }
+ if (m_pCarFire)
+ m_pCarFire->Extinguish();
+ CCarCtrl::UpdateCarCount(this, true);
+ if (bIsAmbulanceOnDuty){
+ CCarCtrl::NumAmbulancesOnDuty--;
+ bIsAmbulanceOnDuty = false;
+ }
+ if (bIsFireTruckOnDuty){
+ CCarCtrl::NumFiretrucksOnDuty--;
+ bIsFireTruckOnDuty = false;
+ }
+}
+
+void
+CVehicle::SetModelIndex(uint32 id)
+{
+ CEntity::SetModelIndex(id);
+ m_aExtras[0] = CVehicleModelInfo::ms_compsUsed[0];
+ m_aExtras[1] = CVehicleModelInfo::ms_compsUsed[1];
+ m_nNumMaxPassengers = CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(id);
+}
+
+bool
+CVehicle::SetupLighting(void)
+{
+ ActivateDirectional();
+ SetAmbientColoursForPedsCarsAndObjects();
+
+ if(bRenderScorched){
+ WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f);
+ }else{
+ CVector coors = GetPosition();
+ float lighting = CPointLights::GenerateLightsAffectingObject(&coors);
+ if(lighting != 1.0f){
+ SetAmbientAndDirectionalColours(lighting);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+CVehicle::RemoveLighting(bool reset)
+{
+ CRenderer::RemoveVehiclePedLights(this, reset);
+}
+
+bool
+CVehicle::IsClearToDriveAway(void)
+{
+ CColPoint point;
+ float length = GetColModel()->boundingBox.GetSize().y;
+ CEntity *ent = nil;
+ CVector front = GetForward() * (length*0.5f + 3.0f);
+ return !CWorld::ProcessLineOfSight(GetPosition() + front, GetPosition(),
+ point, ent, true, true, false, false, false, true, true) ||
+ ent == this;
+}
+
+float
+CVehicle::GetHeightAboveRoad(void)
+{
+ return -1.0f * GetColModel()->boundingBox.min.z;
+}
+
+void
+CVehicle::FlyingControl(eFlightModel flightModel)
+{
+ if(pFlyingHandling == nil)
+ return;
+
+ switch(flightModel){
+ case FLIGHT_MODEL_DODO:
+ {
+ // This seems pretty magic
+
+ // Move Left/Right
+ float moveSpeed = m_vecMoveSpeed.Magnitude();
+ float sideSpeed = DotProduct(m_vecMoveSpeed, GetRight());
+ float sideImpulse = -1.0f * sideSpeed / moveSpeed;
+ float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward());
+ float magic = m_vecMoveSpeed.MagnitudeSqr() * sq(fwdSpeed);
+ float turnImpulse = (sideImpulse*0.003f + m_fSteerAngle*0.001f) *
+ magic*m_fTurnMass*CTimer::GetTimeStep();
+ ApplyTurnForce(turnImpulse*GetRight(), -4.0f*GetForward());
+
+ float impulse = sideImpulse*0.2f *
+ magic*m_fMass*CTimer::GetTimeStep();
+ ApplyMoveForce(impulse*GetRight());
+ ApplyTurnForce(impulse*GetRight(), 2.0f*GetUp());
+
+
+ // Move Up/Down
+ moveSpeed = m_vecMoveSpeed.Magnitude();
+ float upSpeed = DotProduct(m_vecMoveSpeed, GetUp());
+ float upImpulse = -1.0f * upSpeed / moveSpeed;
+ turnImpulse = (upImpulse*0.002f + -CPad::GetPad(0)->GetSteeringUpDown()/128.0f*0.001f) *
+ magic*m_fTurnMass*CTimer::GetTimeStep();
+ ApplyTurnForce(turnImpulse*GetUp(), -4.0f*GetForward());
+
+ impulse = (upImpulse*3.5f + 0.5f)*0.05f *
+ magic*m_fMass*CTimer::GetTimeStep();
+ if(GRAVITY*m_fMass*CTimer::GetTimeStep() < impulse &&
+ GetPosition().z > 100.0f)
+ impulse = 0.9f*GRAVITY*m_fMass*CTimer::GetTimeStep();
+ CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
+ ApplyMoveForce(impulse*GetUp());
+ ApplyTurnForce(impulse*GetUp(), 2.0f*GetUp() + com);
+
+
+ m_vecTurnSpeed.y *= Pow(0.9f, CTimer::GetTimeStep());
+
+
+ moveSpeed = m_vecMoveSpeed.MagnitudeSqr();
+ if(moveSpeed > SQR(1.5f))
+ m_vecMoveSpeed *= 1.5f/Sqrt(moveSpeed);
+
+ float turnSpeed = m_vecTurnSpeed.MagnitudeSqr();
+ if(turnSpeed > SQR(0.2f))
+ m_vecTurnSpeed *= 0.2f/Sqrt(turnSpeed);
+ break;
+ }
+
+ case FLIGHT_MODEL_RCPLANE:
+ case FLIGHT_MODEL_SEAPLANE:
+ case FLIGHT_MODEL_PLANE_UNUSED:
+ case FLIGHT_MODEL_PLANE:
+ {
+ float fSteerLR = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f;
+ float fSteerUD = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f;
+ float fGunUD = Abs(CPad::GetPad(0)->GetCarGunUpDown());
+#ifdef FREE_CAM
+ if(!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController))
+#endif
+ if(fGunUD > 1.0f)
+ fSteerUD = -CPad::GetPad(0)->GetCarGunUpDown() / 128.0f;
+
+ float fSteerAngle = Atan2(fSteerUD, fSteerLR);
+ float fSteerMult = 1.0f;
+ if(fSteerAngle > -PI/4.0f && fSteerAngle <= PI/4.0f)
+ fSteerMult = 1.0f/Cos(fSteerAngle);
+ else if(fSteerAngle > PI/4.0f && fSteerAngle <= PI*3.0f/4.0f)
+ fSteerMult = 1.0f/Cos(fSteerAngle - HALFPI);
+ else if(fSteerAngle > PI*3.0f/4.0f)
+ fSteerMult = 1.0f/Cos(fSteerAngle - PI);
+ else if(fSteerAngle <= -PI*3.0f/4.0f)
+ fSteerMult = 1.0f/Cos(fSteerAngle + PI);
+ else if(fSteerAngle > -PI*3.0f/4.0f && fSteerAngle < -PI/4.0f)
+ fSteerMult = 1.0f/Cos(fSteerAngle + HALFPI);
+
+ fSteerLR *= fSteerMult;
+ fSteerUD *= -fSteerMult;
+
+ // thrust
+ float fThrust = pFlyingHandling->fThrust;
+ float fThrustFallOff = pFlyingHandling->fThrustFallOff;
+ float fThrustFallOffBack = pFlyingHandling->fThrustFallOff * 8.0f;
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ if (bAllDodosCheat && !IsRealPlane()) {
+ fThrust = pHandling->Transmission.fEngineAcceleration
+ * (pHandling->Transmission.nDriveType == '4' ? 4.0f : 2.0f);
+ fThrust = 5.0f * Max(fThrust, pFlyingHandling->fThrust); //tweak: (cars engines too weak to thrust car on air)
+ fThrustFallOff = Min(0.7f / pHandling->Transmission.fMaxVelocity, fThrustFallOff); //tweak: (use 0.7 instead of 1.0 to make cars 30% faster)
+ fThrustFallOffBack = -1.0f / pHandling->Transmission.fMaxReverseVelocity;
+ }
+#endif
+ float fForwSpeed = DotProduct(GetMoveSpeed(), GetForward());
+ CVector vecTail = GetColModel()->boundingBox.min.y * GetForward();
+ float fPedalState = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f;
+ float fThrustAccel;
+ if(fForwSpeed > 0.0f || fPedalState > 0.0f)
+ fThrustAccel = (fPedalState - fThrustFallOff * fForwSpeed) * fThrust;
+ else
+ fThrustAccel = Min(fPedalState - fThrustFallOffBack * fForwSpeed, 0.0f) * fThrust;
+ if(flightModel == FLIGHT_MODEL_PLANE_UNUSED)
+ fThrustAccel *= 0.3f;
+ else if(flightModel == FLIGHT_MODEL_PLANE)
+ fThrustAccel *= 0.1f;
+ ApplyMoveForce(fThrustAccel * GetForward() * m_fMass * CTimer::GetTimeStep());
+
+ // left/right
+ float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight());
+ float fSideSlipAccel = pFlyingHandling->fSideSlip * fSideSpeed * Abs(fSideSpeed);
+ ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep());
+
+ float fYaw = -DotProduct(GetSpeed(vecTail), GetRight());
+ float fYawAccel = pFlyingHandling->fYawStab * fYaw * Abs(fYaw) + pFlyingHandling->fYaw * fSteerLR * fForwSpeed;
+ ApplyTurnForce(fYawAccel * GetRight() * m_fTurnMass * CTimer::GetTimeStep(), vecTail);
+
+ float fRollAccel;
+ if (flightModel == FLIGHT_MODEL_RCPLANE) {
+ float fDirectionMultiplier = CPad::GetPad(0)->GetLookRight();
+ if (CPad::GetPad(0)->GetLookLeft())
+ fDirectionMultiplier = -1;
+ fRollAccel = (0.5f * fDirectionMultiplier + fSteerLR) * pFlyingHandling->fRoll;
+ }
+ else
+ fRollAccel = fSteerLR * pFlyingHandling->fRoll;
+ ApplyTurnForce(GetRight() * fRollAccel * fForwSpeed * m_fTurnMass * CTimer::GetTimeStep(), GetUp());
+
+ CVector vecFRight = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f));
+ CVector vecStabilise = (GetUp().z > 0.0f) ? vecFRight : -vecFRight;
+ float fStabiliseDirection = (GetRight().z > 0.0f) ? -1.0f : 1.0f;
+ float fStabiliseSpeed = pFlyingHandling->fRollStab * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z));
+ ApplyTurnForce(fStabiliseSpeed * m_fTurnMass * GetRight(), GetUp()); // no CTimer::GetTimeStep(), is it right?
+
+ // up/down
+ float fTail = -DotProduct(GetSpeed(vecTail), GetUp());
+ float fPitchAccel = pFlyingHandling->fPitchStab * fTail * Abs(fTail) + pFlyingHandling->fPitch * fSteerUD * fForwSpeed;
+ ApplyTurnForce(fPitchAccel * m_fTurnMass * GetUp() * CTimer::GetTimeStep(), vecTail);
+
+ float fLift = DotProduct(GetMoveSpeed(), GetUp()) / Max(0.01f, GetMoveSpeed().Magnitude()); //accel*angle
+ float fLiftAccel = (pFlyingHandling->fFormLift - pFlyingHandling->fAttackLift * fLift) * SQR(fForwSpeed);
+ float fLiftImpulse = fLiftAccel * m_fMass * CTimer::GetTimeStep();
+ if (GRAVITY * CTimer::GetTimeStep() * m_fMass < fLiftImpulse) {
+ if (flightModel == FLIGHT_MODEL_RCPLANE && GetPosition().z > 50.0f)
+ fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass;
+ else if (flightModel == FLIGHT_MODEL_SEAPLANE && GetPosition().z > 80.0f)
+ fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass;
+#ifdef BETTER_ALLCARSAREDODO_CHEAT
+ else if(bAllDodosCheat && GetPosition().z > 170.0f)
+ fLiftImpulse = CTimer::GetTimeStep() * 0.9f * GRAVITY * m_fMass;
+#endif
+ }
+ ApplyMoveForce(fLiftImpulse * GetUp());
+
+ CVector vecResistance;
+ vecResistance = pFlyingHandling->vecTurnRes;
+ float rX = Pow(vecResistance.x, CTimer::GetTimeStep());
+ float rY = Pow(vecResistance.y, CTimer::GetTimeStep());
+ float rZ = Pow(vecResistance.z, CTimer::GetTimeStep());
+ CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix());
+ vecTurnSpeed.x *= rX;
+ float fResistance = vecTurnSpeed.y * (1.0f / (pFlyingHandling->vecSpeedRes.y * SQR(vecTurnSpeed.y) + 1.0f)) * rY - vecTurnSpeed.y;
+ vecTurnSpeed.z *= rZ;
+ m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed);
+ ApplyTurnForce(-GetUp() * fResistance * m_fTurnMass, GetRight() + Multiply3x3(GetMatrix(), m_vecCentreOfMass));
+
+
+ float fMoveSpeed = m_vecMoveSpeed.MagnitudeSqr();
+ if(fMoveSpeed > SQR(1.5f))
+ m_vecMoveSpeed *= 1.5f/Sqrt(fMoveSpeed);
+
+ float fTurnSpeed = m_vecTurnSpeed.MagnitudeSqr();
+ if(fTurnSpeed > SQR(0.2f))
+ m_vecTurnSpeed *= 0.2f/Sqrt(fTurnSpeed);
+ break;
+ }
+ case FLIGHT_MODEL_RCHELI:
+ case FLIGHT_MODEL_HELI:
+ {
+#ifdef RESTORE_ALLCARSHELI_CHEAT
+ tFlyingHandlingData* flyingHandling = bAllCarCheat && !IsRealHeli() ? mod_HandlingManager.GetFlyingPointer(HANDLING_MAVERICK) : pFlyingHandling;
+#else
+ tFlyingHandlingData* flyingHandling = pFlyingHandling;
+#endif
+ float rm = Pow(flyingHandling->fMoveRes, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= rm;
+ if (GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE)
+ return;
+ float fUpSpeed = DotProduct(m_vecMoveSpeed, GetUp());
+ float fThrust = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f;
+ if(fThrust < 0.0f)
+ fThrust *= 2.0f;
+ if(flightModel == FLIGHT_MODEL_RCHELI){
+ fThrust = flyingHandling->fThrust * fThrust + 0.45f;
+ ApplyMoveForce(GRAVITY * CVector(0.0f, 0.0f, 0.5f) * m_fMass * CTimer::GetTimeStep());
+ }else
+ fThrust = flyingHandling->fThrust * fThrust + 0.95f;
+ fThrust -= flyingHandling->fThrustFallOff * fUpSpeed;
+ if(flightModel == FLIGHT_MODEL_RCHELI && GetPosition().z > 40.0f)
+ fThrust *= 10.0f/(GetPosition().z - 30.0f);
+ else if(GetPosition().z > 80.0f)
+ fThrust *= 10.0f/(GetPosition().z - 70.0f);
+ ApplyMoveForce(GRAVITY * GetUp() * fThrust * m_fMass * CTimer::GetTimeStep());
+
+ if (GetUp().z > 0.0f){
+ float upRight = clamp(GetRight().z, -flyingHandling->fFormLift, flyingHandling->fFormLift);
+ float upImpulseRight = -upRight * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep();
+ ApplyTurnForce(upImpulseRight * GetUp(), GetRight());
+
+ float upFwd = clamp(GetForward().z, -flyingHandling->fFormLift, flyingHandling->fFormLift);
+ float upImpulseFwd = -upFwd * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep();
+ ApplyTurnForce(upImpulseFwd * GetUp(), GetForward());
+ }else{
+ float upRight = GetRight().z < 0.0f ? -flyingHandling->fFormLift : flyingHandling->fFormLift;
+ float upImpulseRight = -upRight * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep();
+ ApplyTurnForce(upImpulseRight * GetUp(), GetRight());
+
+ float upFwd = GetForward().z < 0.0f ? -flyingHandling->fFormLift : flyingHandling->fFormLift;
+ float upImpulseFwd = -upFwd * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep();
+ ApplyTurnForce(upImpulseFwd * GetUp(), GetForward());
+ }
+
+ float fRoll, fPitch, fYaw;
+ if (bCheat5) {
+ fPitch = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f;
+ fRoll = CPad::GetPad(0)->GetLookLeft();
+ if (CPad::GetPad(0)->GetLookRight())
+ fRoll = -1.0f;
+ fYaw = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f;
+ } else {
+ fPitch = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f;
+ fRoll = -CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f;
+ fYaw = CPad::GetPad(0)->GetLookRight();
+ if (CPad::GetPad(0)->GetLookLeft())
+ fYaw = -1.0f;
+#ifdef FREE_CAM
+ if (!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController))
+#endif
+ if(Abs(CPad::GetPad(0)->GetCarGunLeftRight()) > 1.0f)
+ fYaw = CPad::GetPad(0)->GetCarGunLeftRight() / 128.0f;
+ }
+#ifdef FREE_CAM
+ if(!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController))
+#endif
+ if(Abs(CPad::GetPad(0)->GetCarGunUpDown()) > 1.0f)
+ fPitch = -CPad::GetPad(0)->GetCarGunUpDown() / 128.0f;
+ if (CPad::GetPad(0)->GetHorn()) {
+ fYaw = 0.0f;
+ fPitch = clamp(flyingHandling->fPitchStab * DotProduct(m_vecMoveSpeed, GetForward()), -200.0f, 1.3f);
+ fRoll = clamp(flyingHandling->fRollStab * DotProduct(m_vecMoveSpeed, GetRight()), -200.0f, 1.3f);
+ }
+ ApplyTurnForce(fPitch * GetUp() * flyingHandling->fPitch * m_fTurnMass * CTimer::GetTimeStep(), GetForward());
+ ApplyTurnForce(fRoll * GetUp() * flyingHandling->fRoll * m_fTurnMass * CTimer::GetTimeStep(), GetRight());
+
+ float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight());
+ float fSideSlipAccel = flyingHandling->fSideSlip * fSideSpeed * Abs(fSideSpeed);
+ ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep());
+ float fYawAccel = flyingHandling->fYawStab * fSideSpeed * Abs(fSideSpeed) + flyingHandling->fYaw * fYaw;
+ ApplyTurnForce(fYawAccel * GetRight() * m_fTurnMass * CTimer::GetTimeStep(), -GetForward());
+
+ ApplyTurnForce(fYaw * GetForward() * flyingHandling->fYaw * m_fTurnMass * CTimer::GetTimeStep(), GetRight());
+
+ float rX = Pow(flyingHandling->vecTurnRes.x, CTimer::GetTimeStep());
+ float rY = Pow(flyingHandling->vecTurnRes.y, CTimer::GetTimeStep());
+ float rZ = Pow(flyingHandling->vecTurnRes.z, CTimer::GetTimeStep());
+ CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix());
+ float fResistanceMultiplier = Pow(1.0f / (flyingHandling->vecSpeedRes.z * SQR(vecTurnSpeed.z) + 1.0f) * rZ, CTimer::GetTimeStep());
+ float fResistance = vecTurnSpeed.z * fResistanceMultiplier - vecTurnSpeed.z;
+ vecTurnSpeed.x *= rX;
+ vecTurnSpeed.y *= rY;
+ vecTurnSpeed.z *= fResistanceMultiplier;
+ m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed);
+ ApplyTurnForce(-GetRight() * fResistance * m_fTurnMass, GetForward() + Multiply3x3(GetMatrix(), m_vecCentreOfMass));
+ break;
+ }
+ }
+}
+
+static CColModel rotorColModel;
+static CColSphere rotorColSphere;
+float ROTOR_SEMI_THICKNESS = 0.05f;
+float ROTOR_TURN_SPEED = 0.2f;
+float ROTOR_DISGUARD_MULT = 0.3f;
+float ROTOR_COL_ELASTICITY = 1.0f;
+float ROTOR_COL_TURNMULT = -0.001f;
+float ROTOR_DEFAULT_DAMAGE = 100.0f;
+
+bool
+CVehicle::DoBladeCollision(CVector pos, CMatrix &matrix, int16 rotorType, float radius, float damageMult)
+{
+ CVector max(radius, radius, radius);
+ CVector min(-radius, -radius, -radius);
+
+ switch(rotorType){
+ case ROTOR_TOP:
+ case ROTOR_BOTTOM:
+ min.z = -ROTOR_SEMI_THICKNESS;
+ max.z = ROTOR_SEMI_THICKNESS;
+ break;
+ case ROTOR_FRONT:
+ case ROTOR_BACK:
+ min.y = -ROTOR_SEMI_THICKNESS;
+ max.y = ROTOR_SEMI_THICKNESS;
+ break;
+ case ROTOR_RIGHT:
+ case ROTOR_LEFT:
+ min.x = -ROTOR_SEMI_THICKNESS;
+ max.x = ROTOR_SEMI_THICKNESS;
+ break;
+ }
+
+ min += pos;
+ max += pos;
+ rotorColModel.boundingBox.Set(min, max);
+ rotorColModel.boundingSphere.Set(radius, pos);
+ rotorColSphere.Set(radius, pos, 0, 0);
+ rotorColModel.spheres = &rotorColSphere;
+ rotorColModel.numSpheres = 1;
+
+ pos = matrix * pos;
+ bool hadCollision = false;
+ int minX = CWorld::GetSectorIndexX(pos.x - radius);
+ if(minX <= 0) minX = 0;
+
+ int minY = CWorld::GetSectorIndexY(pos.y - radius);
+ if(minY <= 0) minY = 0;
+
+ int maxX = CWorld::GetSectorIndexX(pos.x + radius);
+#ifdef FIX_BUGS
+ if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1;
+#else
+ if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X;
+#endif
+
+ int maxY = CWorld::GetSectorIndexY(pos.y + radius);
+#ifdef FIX_BUGS
+ if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1;
+#else
+ if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y;
+#endif
+
+ CWorld::AdvanceCurrentScanCode();
+ for(int curY = minY; curY <= maxY; curY++) {
+ for(int curX = minX; curX <= maxX; curX++) {
+ CSector *sector = CWorld::GetSector(curX, curY);
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_BUILDINGS], rotorColModel, matrix, rotorType, damageMult))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], rotorColModel, matrix, rotorType, damageMult))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_VEHICLES], rotorColModel, matrix, rotorType, damageMult))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], rotorColModel, matrix, rotorType, damageMult))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_PEDS], rotorColModel, matrix, rotorType, 0.0f))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_PEDS_OVERLAP], rotorColModel, matrix, rotorType, 0.0f))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_OBJECTS], rotorColModel, matrix, rotorType, damageMult))
+ hadCollision = true;
+ if(BladeColSectorList(sector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], rotorColModel, matrix, rotorType, damageMult))
+ hadCollision = true;
+ }
+ }
+ rotorColModel.spheres = nil;
+ rotorColModel.numSpheres = 0;
+
+ return hadCollision;
+}
+
+bool
+CVehicle::BladeColSectorList(CPtrList &list, CColModel &rotorColModel, CMatrix &matrix, int16 rotorType, float damageMult)
+{
+ int i;
+ CVector axis;
+ CVector turnSpeed(0.0f, 0.0f, 0.0f);
+ switch(rotorType){
+ case ROTOR_TOP:
+ turnSpeed.z = -ROTOR_TURN_SPEED;
+ axis = -matrix.GetUp();
+ break;
+ case ROTOR_BOTTOM:
+ turnSpeed.z = ROTOR_TURN_SPEED;
+ axis = matrix.GetUp();
+ break;
+
+ case ROTOR_FRONT:
+ turnSpeed.y = -ROTOR_TURN_SPEED;
+ axis = -matrix.GetForward();
+ break;
+ case ROTOR_BACK:
+ turnSpeed.y = ROTOR_TURN_SPEED;
+ axis = matrix.GetForward();
+ break;
+
+ case ROTOR_RIGHT:
+ turnSpeed.x = -ROTOR_TURN_SPEED;
+ axis = -matrix.GetRight();
+ break;
+ case ROTOR_LEFT:
+ turnSpeed.x = ROTOR_TURN_SPEED;
+ axis = matrix.GetRight();
+ break;
+ }
+ turnSpeed = Multiply3x3(matrix, turnSpeed);
+ CVector center = rotorColModel.boundingSphere.center;
+ center = matrix*center;
+
+ for(CPtrNode *node = list.first; node; node = node->next) {
+ CEntity *entity = (CEntity *)node->item;
+ if(entity == (CEntity*)this ||
+ !entity->bUsesCollision ||
+ entity->m_scanCode == CWorld::GetCurrentScanCode())
+ continue;
+
+ entity->m_scanCode = CWorld::GetCurrentScanCode();
+
+ int numCollisions;
+ CColModel *entityCol;
+ if(entity->IsPed())
+ entityCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(entity->GetModelIndex()))->AnimatePedColModelSkinned(entity->GetClump());
+ else
+ entityCol = entity->GetColModel();
+ if(entityCol)
+ numCollisions = CCollision::ProcessColModels(matrix, rotorColModel, entity->GetMatrix(), *entityCol,
+ CWorld::m_aTempColPts, nil, nil);
+ else
+ numCollisions = 0;
+
+ if(numCollisions > 0 && entity->IsPed()){
+ CPed *ped = (CPed*)entity;
+ CVector2D dirToRotor = GetPosition() - entity->GetPosition();
+ dirToRotor.Normalise();
+ int localDir = ped->GetLocalDirection(dirToRotor);
+ if(ped->m_attachedTo == nil){
+ ped->bIsStanding = false;
+ ped->ApplyMoveForce(-5.0f*dirToRotor.x, -5.0f*dirToRotor.y, 5.0f);
+ }
+ ped->InflictDamage(this, WEAPONTYPE_RUNOVERBYCAR, 1000.0f, PEDPIECE_TORSO, localDir);
+
+ if(CGame::nastyGame && ped->GetIsOnScreen()){
+ for(i = 0; i < 16; i++)
+ CParticle::AddParticle(PARTICLE_BLOOD_SMALL, ped->GetPosition(), CVector(dirToRotor.x, dirToRotor.y, 1.0f) * 0.01f);
+ CParticle::AddParticle(PARTICLE_TEST, ped->GetPosition(), CVector(0.0f, 0.0f, 0.02f), nil, 0.1f);
+ CParticle::AddParticle(PARTICLE_TEST, ped->GetPosition()+CVector(0.0f, 0.0f, 0.2f), CVector(0.0f, 0.0f, -0.01f), nil, 0.1f);
+ }
+ }else if(numCollisions > 0 && entity->GetModelIndex() != MI_MISSILE){
+ float impulse = 0.0f;
+ bool hadCollision = false;
+ float savedElasticity = m_fElasticity;
+ m_fElasticity = ROTOR_COL_ELASTICITY;
+
+ for(i = 0; i < numCollisions; i++){
+ CVector colpos = CWorld::m_aTempColPts[i].point;
+ CVector localColpos = colpos - center;
+ float axisDir = DotProduct(axis, localColpos);
+ float colDir = DotProduct(CWorld::m_aTempColPts[i].normal, localColpos);
+
+ if(2.0f*ROTOR_SEMI_THICKNESS < Abs(axisDir) &&
+ ROTOR_DISGUARD_MULT*Abs(colDir) < Abs(axisDir))
+ continue;
+
+ hadCollision = true;
+ colpos -= axisDir*axis; // get rid of axis component
+
+ CVector tangentSpeed = CrossProduct(turnSpeed, colpos - center);
+
+ // Particles
+ for(int j = 0; j < 4; j++){
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpos, (tangentSpeed+m_vecMoveSpeed)/2.0f);
+ CParticle::AddParticle(PARTICLE_SPARK, colpos, 0.1f*CWorld::m_aTempColPts[i].normal);
+ }
+
+ // Apply Collision
+ if(IsCar()){
+ CAutomobile *heli = (CAutomobile*)this;
+ if(heli->m_aWheelSpeed[1] > 0.15f){
+ ApplyCollision(CWorld::m_aTempColPts[i], impulse);
+ ApplyTurnForce(m_fTurnMass*ROTOR_COL_TURNMULT*tangentSpeed, colpos - center);
+ heli->m_aWheelSpeed[1] = 0.15f;
+ }else if(heli->m_aWheelSpeed[1] < 0.075f && heli->m_aWheelSpeed[1] > 0.0f)
+ heli->m_aWheelSpeed[1] *= -1.0f;
+ }
+
+ float damageImpulse = damageMult * Max(impulse, ROTOR_DEFAULT_DAMAGE*m_fMass/3000.0f);
+ if(damageImpulse > m_fDamageImpulse)
+ SetDamagedPieceRecord(0, damageImpulse, entity, CWorld::m_aTempColPts[i].normal);
+
+ }
+
+ if(hadCollision && !entity->IsPed())
+ DMAudio.ReportCollision(this, entity, SURFACE_CAR_PANEL, SURFACE_TARMAC, 50.0f, 0.09f);
+ m_fElasticity = savedElasticity;
+ }
+ }
+ return false;
+}
+
+
+float fBurstSpeedMax = 0.3f;
+float fBurstTyreMod = 0.13f;
+
+void
+CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
+ int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus)
+{
+ // BUG: using statics here is probably a bad idea
+ static bool bAlreadySkidding = false; // this is never reset
+ static bool bBraking;
+ static bool bDriving;
+
+#ifdef FIX_SIGNIFICANT_BUGS
+ bAlreadySkidding = false;
+#endif
+
+ // how much force we want to apply in these axes
+ float fwd = 0.0f;
+ float right = 0.0f;
+
+ bBraking = brake != 0.0f;
+ if(bBraking)
+ thrust = 0.0f;
+ bDriving = thrust != 0.0f;
+
+ float contactSpeedFwd = DotProduct(wheelContactSpeed, wheelFwd);
+ float contactSpeedRight = DotProduct(wheelContactSpeed, wheelRight);
+
+ if(*wheelState != WHEEL_STATE_NORMAL)
+ bAlreadySkidding = true;
+ *wheelState = WHEEL_STATE_NORMAL;
+
+ adhesion *= CTimer::GetTimeStep();
+ if(bAlreadySkidding)
+ adhesion *= pHandling->fTractionLoss;
+
+ // moving sideways
+ if(contactSpeedRight != 0.0f){
+ // exert opposing force
+ right = -contactSpeedRight/wheelsOnGround;
+#ifdef FIX_BUGS
+ // contactSpeedRight is independent of framerate but right has timestep as a factor
+ // so we probably have to fix this
+ right *= CTimer::GetTimeStepFix();
+#endif
+
+ if(wheelStatus == WHEEL_STATUS_BURST){
+ float fwdspeed = Min(contactSpeedFwd, fBurstSpeedMax);
+ right += fwdspeed * CGeneral::GetRandomNumberInRange(-fBurstTyreMod, fBurstTyreMod);
+ }
+ }
+
+ if(bDriving){
+ fwd = thrust;
+
+ // limit sideways force (why?)
+ if(right > 0.0f){
+ if(right > adhesion)
+ right = adhesion;
+ }else{
+ if(right < -adhesion)
+ right = -adhesion;
+ }
+ }else if(contactSpeedFwd != 0.0f){
+ fwd = -contactSpeedFwd/wheelsOnGround;
+#ifdef FIX_BUGS
+ // contactSpeedFwd is independent of framerate but fwd has timestep as a factor
+ // so we probably have to fix this
+ fwd *= CTimer::GetTimeStepFix();
+#endif
+
+ if(!bBraking){
+ if(m_fGasPedal < 0.01f){
+ if(IsBike())
+ brake = 0.6f * mod_HandlingManager.fWheelFriction / (pHandling->fMass + 200.0f);
+ else if(pHandling->fMass < 500.0f)
+ brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass;
+ else if(GetModelIndex() == MI_RCBANDIT)
+ brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass;
+ else
+ brake = mod_HandlingManager.fWheelFriction / pHandling->fMass;
+#ifdef FIX_BUGS
+ brake *= CTimer::GetTimeStepFix();
+#endif
+ }
+ }
+
+ if(brake > adhesion){
+ if(Abs(contactSpeedFwd) > 0.005f)
+ *wheelState = WHEEL_STATE_FIXED;
+ }else {
+ if(fwd > 0.0f){
+ if(fwd > brake)
+ fwd = brake;
+ }else{
+ if(fwd < -brake)
+ fwd = -brake;
+ }
+ }
+ }
+
+ float speedSq = sq(right) + sq(fwd);
+ if(sq(adhesion) < speedSq){
+ if(*wheelState != WHEEL_STATE_FIXED){
+ if(bDriving && contactSpeedFwd < 0.2f)
+ *wheelState = WHEEL_STATE_SPINNING;
+ else
+ *wheelState = WHEEL_STATE_SKIDDING;
+ }
+
+ float l = Sqrt(speedSq);
+ float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss;
+ right *= adhesion * tractionLoss / l;
+ fwd *= adhesion * tractionLoss / l;
+ }
+
+ if(fwd != 0.0f || right != 0.0f){
+ CVector totalSpeed = fwd*wheelFwd + right*wheelRight;
+
+ CVector turnDirection = totalSpeed;
+ bool separateTurnForce = false; // BUG: not initialized on PC
+ if(pHandling->fSuspensionAntidiveMultiplier > 0.0f){
+ if(bBraking){
+ separateTurnForce = true;
+ turnDirection = totalSpeed - pHandling->fSuspensionAntidiveMultiplier*fwd*wheelFwd;
+ }else if(bDriving){
+ separateTurnForce = true;
+ turnDirection = totalSpeed - 0.5f*pHandling->fSuspensionAntidiveMultiplier*fwd*wheelFwd;
+ }
+ }
+
+ CVector direction = totalSpeed;
+
+ float speed = totalSpeed.Magnitude();
+ float turnSpeed;
+ if(separateTurnForce)
+ turnSpeed = turnDirection.Magnitude();
+ else
+ turnSpeed = speed;
+ direction.Normalise();
+ if(separateTurnForce)
+ turnDirection.Normalise();
+ else
+ turnDirection = direction;
+
+ float impulse = speed*m_fMass;
+ float turnImpulse = turnSpeed*GetMass(wheelContactPoint, turnDirection);
+
+ ApplyMoveForce(impulse * direction);
+ ApplyTurnForce(turnImpulse * turnDirection, wheelContactPoint);
+ }
+}
+
+float fBurstBikeSpeedMax = 0.12f;
+float fBurstBikeTyreMod = 0.05f;
+float fTweakBikeWheelTurnForce = 2.0f;
+
+void
+CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
+ int32 wheelsOnGround, float thrust, float brake, float adhesion, float destabTraction, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus)
+{
+ // BUG: using statics here is probably a bad idea
+ static bool bAlreadySkidding = false; // this is never reset
+ static bool bBraking;
+ static bool bDriving;
+ static bool bReversing;
+
+#ifdef FIX_SIGNIFICANT_BUGS
+ bAlreadySkidding = false;
+#endif
+
+ // how much force we want to apply in these axes
+ float fwd = 0.0f;
+ float right = 0.0f;
+
+ bBraking = brake != 0.0f;
+ if(bBraking)
+ thrust = 0.0f;
+ bDriving = thrust != 0.0f;
+ bReversing = thrust < 0.0f;
+
+ float contactSpeedFwd = DotProduct(wheelContactSpeed, wheelFwd);
+ float contactSpeedRight;
+
+ if(*wheelState != WHEEL_STATE_NORMAL)
+ bAlreadySkidding = true;
+ *wheelState = WHEEL_STATE_NORMAL;
+
+ adhesion *= CTimer::GetTimeStep();
+ if(bAlreadySkidding)
+ adhesion *= pHandling->fTractionLoss;
+
+ if(special == BIKE_WHEELSPEC_2 || special == BIKE_WHEELSPEC_3)
+ contactSpeedRight = 0.0f;
+ else
+ contactSpeedRight = DotProduct(wheelContactSpeed, wheelRight);
+
+ // moving sideways
+ if(contactSpeedRight != 0.0f){
+ // exert opposing force
+ right = -contactSpeedRight/wheelsOnGround;
+#ifdef FIX_BUGS
+ // contactSpeedRight is independent of framerate but right has timestep as a factor
+ // so we probably have to fix this
+ right *= CTimer::GetTimeStepFix();
+#endif
+
+ if(wheelStatus == WHEEL_STATUS_BURST){
+ float fwdspeed = Min(contactSpeedFwd, fBurstBikeSpeedMax);
+ right += fwdspeed * CGeneral::GetRandomNumberInRange(-fBurstBikeTyreMod, fBurstBikeTyreMod);
+ }
+ }
+
+ if(bDriving){
+ fwd = thrust;
+
+ // limit sideways force (why?)
+ if(right > 0.0f){
+ if(right > adhesion)
+ right = adhesion;
+ }else{
+ if(right < -adhesion)
+ right = -adhesion;
+ }
+ }else if(contactSpeedFwd != 0.0f){
+ fwd = -contactSpeedFwd/wheelsOnGround;
+#ifdef FIX_BUGS
+ // contactSpeedFwd is independent of framerate but fwd has timestep as a factor
+ // so we probably have to fix this
+ fwd *= CTimer::GetTimeStepFix();
+#endif
+
+ if(!bBraking){
+ if(m_fGasPedal < 0.01f){
+ if(IsBike())
+ brake = 0.6f * mod_HandlingManager.fWheelFriction / (pHandling->fMass + 200.0f);
+ else if(pHandling->fMass < 500.0f)
+ brake = mod_HandlingManager.fWheelFriction / m_fMass;
+ else if(GetModelIndex() == MI_RCBANDIT)
+ brake = 0.2f * mod_HandlingManager.fWheelFriction / m_fMass;
+ else
+ brake = mod_HandlingManager.fWheelFriction / m_fMass;
+#ifdef FIX_BUGS
+ brake *= CTimer::GetTimeStepFix();
+#endif
+ }
+ }
+
+ if(brake > adhesion){
+ if(Abs(contactSpeedFwd) > 0.005f)
+ *wheelState = WHEEL_STATE_FIXED;
+ }else {
+ if(fwd > 0.0f){
+ if(fwd > brake)
+ fwd = brake;
+ }else{
+ if(fwd < -brake)
+ fwd = -brake;
+ }
+ }
+ }
+
+ float speedSq = sq(right) + sq(fwd);
+ if(sq(adhesion) < speedSq){
+ if(*wheelState != WHEEL_STATE_FIXED){
+ if(bDriving && contactSpeedFwd < 0.2f)
+ *wheelState = WHEEL_STATE_SPINNING;
+ else
+ *wheelState = WHEEL_STATE_SKIDDING;
+ }
+
+ float l = Sqrt(speedSq);
+ float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss;
+ right *= adhesion * tractionLoss / l;
+ fwd *= adhesion * tractionLoss / l;
+
+ if(destabTraction < 1.0f)
+ right *= destabTraction;
+ }else if(destabTraction < 1.0f){
+ if(!bAlreadySkidding)
+ destabTraction *= pHandling->fTractionLoss;
+ if(sq(adhesion*destabTraction) < speedSq){
+ float l = Sqrt(speedSq);
+ right *= adhesion * destabTraction / l;
+ }
+ }
+
+ if(fwd != 0.0f || right != 0.0f){
+ CVector direction = fwd*wheelFwd + right*wheelRight;
+
+ float speed = direction.Magnitude();
+ direction.Normalise();
+
+ float impulse = speed*m_fMass;
+ float turnImpulse = speed*GetMass(wheelContactPoint, direction);
+ CVector vTurnImpulse = turnImpulse * direction;
+ ApplyMoveForce(impulse * direction);
+
+ float turnRight = DotProduct(vTurnImpulse, GetRight());
+ float contactRight = DotProduct(wheelContactPoint, GetRight());
+ float contactFwd = DotProduct(wheelContactPoint, GetForward());
+
+ if(wheelId != BIKEWHEEL_REAR || !bBraking && !bReversing)
+ ApplyTurnForce((vTurnImpulse - turnRight*GetRight()) * fTweakBikeWheelTurnForce,
+ wheelContactPoint - contactRight*GetRight());
+
+ ApplyTurnForce(turnRight*GetRight(), contactFwd*GetForward());
+ }
+}
+
+float
+CVehicle::ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius)
+{
+ float angularVelocity;
+ switch(state){
+ case WHEEL_STATE_SPINNING:
+ angularVelocity = -1.1f; // constant speed forward
+ break;
+ case WHEEL_STATE_FIXED:
+ angularVelocity = 0.0f; // not moving
+ break;
+ default:
+ angularVelocity = -DotProduct(fwd, speed) / radius; // forward speed
+ break;
+ }
+ return angularVelocity * CTimer::GetTimeStep();
+}
+
+int
+CVehicle::FindTyreNearestPoint(float x, float y)
+{
+ CVector pos = CVector(x - GetPosition().x, y - GetPosition().y, 0.0f);
+ float fwd = DotProduct(GetForward(), pos);
+ float right = DotProduct(GetRight(), pos);
+
+ int piece;
+ if(IsBike()){
+ piece = fwd > 0.0f ? CAR_PIECE_WHEEL_LF : CAR_PIECE_WHEEL_LR;
+ }else{
+ piece = fwd > 0.0f ?
+ right > 0.0f ? CAR_PIECE_WHEEL_RF : CAR_PIECE_WHEEL_LF :
+ right > 0.0f ? CAR_PIECE_WHEEL_RR : CAR_PIECE_WHEEL_LR;
+ }
+ return piece - CAR_PIECE_WHEEL_LF;
+}
+
+void
+CVehicle::InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage, CVector pos)
+{
+ if (!bCanBeDamaged)
+ return;
+ if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f)
+ damage *= 0.5f;
+ if (GetStatus() != STATUS_PLAYER && bOnlyDamagedByPlayer && (damagedBy != FindPlayerPed() && damagedBy != FindPlayerVehicle()))
+ return;
+
+ if(damage > 10.0f && (damagedBy == FindPlayerPed() || damagedBy == FindPlayerVehicle()) && GetStatus() != STATUS_WRECKED){
+ CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 2;
+ CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 1.0f;
+ CStats::PropertyDestroyed += CGeneral::GetRandomNumberInRange(5, 25);
+ }
+
+ bool bFrightensDriver = false;
+ switch (weaponType) {
+ case WEAPONTYPE_UNARMED:
+ case WEAPONTYPE_BRASSKNUCKLE:
+ case WEAPONTYPE_SCREWDRIVER:
+ case WEAPONTYPE_GOLFCLUB:
+ case WEAPONTYPE_NIGHTSTICK:
+ case WEAPONTYPE_KNIFE:
+ case WEAPONTYPE_BASEBALLBAT:
+ case WEAPONTYPE_HAMMER:
+ case WEAPONTYPE_CLEAVER:
+ case WEAPONTYPE_MACHETE:
+ case WEAPONTYPE_KATANA:
+ case WEAPONTYPE_CHAINSAW:
+ if (bMeleeProof)
+ return;
+ break;
+ case WEAPONTYPE_COLT45:
+ case WEAPONTYPE_PYTHON:
+ case WEAPONTYPE_SHOTGUN:
+ case WEAPONTYPE_SPAS12_SHOTGUN:
+ case WEAPONTYPE_STUBBY_SHOTGUN:
+ case WEAPONTYPE_TEC9:
+ case WEAPONTYPE_UZI:
+ case WEAPONTYPE_SILENCED_INGRAM:
+ case WEAPONTYPE_MP5:
+ case WEAPONTYPE_M4:
+ case WEAPONTYPE_RUGER:
+ case WEAPONTYPE_SNIPERRIFLE:
+ case WEAPONTYPE_LASERSCOPE:
+ case WEAPONTYPE_M60:
+ case WEAPONTYPE_MINIGUN:
+ case WEAPONTYPE_HELICANNON:
+ case WEAPONTYPE_UZI_DRIVEBY:
+ if (bBulletProof)
+ return;
+ bFrightensDriver = true;
+ break;
+ case WEAPONTYPE_GRENADE:
+ case WEAPONTYPE_MOLOTOV:
+ case WEAPONTYPE_ROCKET:
+ case WEAPONTYPE_EXPLOSION:
+ if (bExplosionProof)
+ return;
+ bFrightensDriver = true;
+ break;
+ case WEAPONTYPE_FLAMETHROWER:
+ if (bFireProof)
+ return;
+ break;
+ case WEAPONTYPE_RAMMEDBYCAR:
+ if (bCollisionProof)
+ return;
+ break;
+ default:
+ break;
+ }
+
+ if(bFrightensDriver && GetStatus() == STATUS_PLAYER && m_fHealth < 250.0f)
+ return;
+
+ // Pop tires
+ if(damagedBy && damagedBy->IsPed() && (IsCar() || IsBike())){
+ int accuracy = 0;
+ switch(weaponType){
+ case WEAPONTYPE_COLT45:
+ accuracy = 10;
+ break;
+ case WEAPONTYPE_PYTHON:
+ if(!((CPed*)damagedBy)->IsPlayer())
+ accuracy = 64;
+ break;
+ case WEAPONTYPE_SHOTGUN:
+ case WEAPONTYPE_STUBBY_SHOTGUN:
+ case WEAPONTYPE_M60:
+ case WEAPONTYPE_HELICANNON:
+ accuracy = 25;
+ break;
+ case WEAPONTYPE_TEC9:
+ case WEAPONTYPE_UZI:
+ case WEAPONTYPE_SILENCED_INGRAM:
+ case WEAPONTYPE_MP5:
+ case WEAPONTYPE_UZI_DRIVEBY:
+ accuracy = 15;
+ break;
+ case WEAPONTYPE_M4:
+ case WEAPONTYPE_RUGER:
+ if(!((CPed*)damagedBy)->IsPlayer())
+ accuracy = 15;
+ break;
+ }
+
+ if(((CPed*)damagedBy)->IsPlayer() && (CCamera::m_bUseMouse3rdPerson || TheCamera.Using1stPersonWeaponMode()))
+ accuracy = 0;
+
+ if(accuracy != 0 && !bTyresDontBurst && (CGeneral::GetRandomNumber()&0x7F) < accuracy){
+ if(IsBike())
+ BurstTyre(FindTyreNearestPoint(pos.x, pos.y) + CAR_PIECE_WHEEL_LF, false);
+ else if(GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR)
+ BurstTyre(FindTyreNearestPoint(pos.x, pos.y) + CAR_PIECE_WHEEL_LF, true);
+ }
+ }
+
+ if (m_fHealth > 0.0f) {
+ if (VehicleCreatedBy == RANDOM_VEHICLE && pDriver &&
+ (GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS) &&
+ AutoPilot.m_nCarMission == MISSION_CRUISE) {
+ if (m_randomSeed < DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE) {
+ CCarCtrl::SwitchVehicleToRealPhysics(this);
+ AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS;
+ AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * pHandling->Transmission.fMaxCruiseVelocity;
+ SetStatus(STATUS_PHYSICS);
+ }
+ }
+ m_nLastWeaponDamage = weaponType;
+ m_pLastDamageEntity = damagedBy;
+ float oldHealth = m_fHealth;
+ if (m_fHealth > damage) {
+ m_fHealth -= damage;
+ if (VehicleCreatedBy == RANDOM_VEHICLE && !IsBoat()){
+ switch (GetStatus()) {
+ case STATUS_SIMPLE:
+ case STATUS_PHYSICS:
+ if(AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_PLOUGH_THROUGH ||
+ CGeneral::GetRandomNumberInRange(0.0f, 1.0f) > 0.5f && AutoPilot.m_nCarMission == MISSION_CRUISE){
+ // Drive away like a maniac
+ if(pDriver && pDriver->m_objective != OBJECTIVE_LEAVE_CAR){
+ if(AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_PLOUGH_THROUGH)
+ AutoPilot.m_nCruiseSpeed *= 1.5f;
+ AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_PLOUGH_THROUGH;
+ }
+ }else{
+ // Leave vehicle
+ if (pDriver && pDriver->CharCreatedBy != MISSION_CHAR) {
+ SetStatus(STATUS_ABANDONED);
+ pDriver->bFleeAfterExitingCar = true;
+ pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this);
+ pDriver->Say(SOUND_PED_FLEE_SPRINT);
+ }
+ int time = 200;
+ for (int i = 0; i < m_nNumMaxPassengers; i++) {
+ if (pPassengers[i] &&
+ pPassengers[i]->m_objective != OBJECTIVE_LEAVE_CAR &&
+ pPassengers[i]->CharCreatedBy != MISSION_CHAR) {
+ pPassengers[i]->bFleeAfterExitingCar = true;
+ pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this);
+ pPassengers[i]->m_objectiveTimer = CTimer::GetTimeInMilliseconds() + time;
+ pPassengers[i]->Say(SOUND_PED_FLEE_SPRINT);
+ time += 200;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (oldHealth >= DAMAGE_HEALTH_TO_CATCH_FIRE && m_fHealth < DAMAGE_HEALTH_TO_CATCH_FIRE) {
+ if (IsCar()) {
+ CAutomobile* pThisCar = (CAutomobile*)this;
+ pThisCar->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE);
+ pThisCar->m_pSetOnFireEntity = damagedBy;
+ if (damagedBy)
+ damagedBy->RegisterReference((CEntity**)&pThisCar->m_pSetOnFireEntity);
+ }
+ }
+ }
+ else {
+ m_fHealth = 0.0f;
+ if (weaponType == WEAPONTYPE_EXPLOSION) {
+ // between 1000 and 3047. Also not very nice: can't be saved by respray or cheat
+ m_nBombTimer = 1000 + CGeneral::GetRandomNumber() & 0x7FF;
+ m_pBlowUpEntity = damagedBy;
+ if (damagedBy)
+ damagedBy->RegisterReference((CEntity**)&m_pBlowUpEntity);
+ }
+ else
+ BlowUpCar(damagedBy);
+ }
+ }
+#ifdef FIX_BUGS // removing dumb case when shooting police car in player's own garage gives wanted level
+ if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed() && damagedBy != nil && !bHasBeenOwnedByPlayer)
+#else
+ if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed())
+#endif
+ FindPlayerPed()->SetWantedLevelNoDrop(1);
+}
+
+void
+CVehicle::DoFixedMachineGuns(void)
+{
+ if(TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_FORWARD){
+ if(CPad::GetPad(0)->GetCarGunFired() && !bGunSwitchedOff){
+ FireFixedMachineGuns();
+ }else{
+ if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 1400)
+ m_nAmmoInClip = 20;
+ }
+ }
+}
+
+void
+CVehicle::FireFixedMachineGuns(void)
+{
+ if (CTimer::GetTimeInMilliseconds() <= m_nGunFiringTime + 150)
+ return;
+ CVector source, target;
+ float dx, dy, len;
+
+ dx = GetForward().x;
+ dy = GetForward().y;
+ len = Sqrt(SQR(dx) + SQR(dy));
+ if (len < 0.1f) len = 0.1f;
+ dx /= len;
+ dy /= len;
+
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds();
+
+ source = GetMatrix() * CVector(2.0f, 2.5f, 1.0f);
+ target = source + CVector(dx, dy, 0.0f) * 60.0f;
+ target += CVector(
+ ((CGeneral::GetRandomNumber() & 0xFF) - 128) * 0.015f,
+ ((CGeneral::GetRandomNumber() & 0xFF) - 128) * 0.015f,
+ ((CGeneral::GetRandomNumber() & 0xFF) - 128) * 0.02f);
+ CWeapon::DoTankDoomAiming(this, pDriver, &source, &target);
+ FireOneInstantHitRound(&source, &target, 15);
+
+ source = GetMatrix() * CVector(-2.0f, 2.5f, 1.0f);
+ target = source + CVector(dx, dy, 0.0f) * 60.0f;
+ target += CVector(
+ ((CGeneral::GetRandomNumber() & 0xFF) - 128) * 0.015f,
+ ((CGeneral::GetRandomNumber() & 0xFF) - 128) * 0.015f,
+ ((CGeneral::GetRandomNumber() & 0xFF) - 128) * 0.02f);
+ CWeapon::DoTankDoomAiming(this, pDriver, &source, &target);
+ FireOneInstantHitRound(&source, &target, 15);
+
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);
+
+ m_nAmmoInClip--;
+ if (m_nAmmoInClip == 0) {
+ m_nAmmoInClip = 20;
+ m_nGunFiringTime = CTimer::GetTimeInMilliseconds() + 1400;
+ }
+}
+
+void
+CVehicle::ActivateBomb(void)
+{
+ if(m_bombType == CARBOMB_TIMED){
+ m_bombType = CARBOMB_TIMEDACTIVE;
+ m_nBombTimer = 7000;
+ m_pBlowUpEntity = FindPlayerPed();
+ CGarages::TriggerMessage("GA_12", -1, 3000, -1);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_TIMED_ACTIVATED, 1.0f);
+ }else if(m_bombType == CARBOMB_ONIGNITION){
+ m_bombType = CARBOMB_ONIGNITIONACTIVE;
+ CGarages::TriggerMessage("GA_12", -1, 3000, -1);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_ONIGNITION_ACTIVATED, 1.0f);
+ }
+}
+
+void
+CVehicle::ActivateBombWhenEntered(void)
+{
+ if(pDriver){
+ if(!bDriverLastFrame && m_bombType == CARBOMB_ONIGNITIONACTIVE){
+ // If someone enters the car and there is a bomb, detonate
+ m_nBombTimer = 1000;
+ m_pBlowUpEntity = m_pBombRigger;
+ if(m_pBlowUpEntity)
+ m_pBlowUpEntity->RegisterReference((CEntity**)&m_pBlowUpEntity);
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_TICK, 1.0f);
+ }
+ bDriverLastFrame = true;
+ }else
+ bDriverLastFrame = false;
+}
+
+void
+CVehicle::ExtinguishCarFire(void)
+{
+ if(GetStatus() != STATUS_WRECKED)
+ m_fHealth = Max(m_fHealth, 300.0f);
+ if(m_pCarFire)
+ m_pCarFire->Extinguish();
+ if(IsCar()){
+ CAutomobile *car = (CAutomobile*)this;
+ if(car->Damage.GetEngineStatus() >= ENGINE_STATUS_ON_FIRE)
+ car->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE-10);
+ car->m_fFireBlowUpTimer = 0.0f;
+ }
+}
+
+bool
+CVehicle::ShufflePassengersToMakeSpace(void)
+{
+ if (m_nNumPassengers >= m_nNumMaxPassengers)
+ return false;
+ if (pPassengers[1] &&
+ !(m_nGettingInFlags & CAR_DOOR_FLAG_LR) &&
+ IsRoomForPedToLeaveCar(CAR_DOOR_LR, nil)) {
+ if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) {
+ pPassengers[2] = pPassengers[1];
+ pPassengers[1] = nil;
+ pPassengers[2]->m_vehDoor = CAR_DOOR_RR;
+ return true;
+ }
+ if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) {
+ pPassengers[0] = pPassengers[1];
+ pPassengers[1] = nil;
+ pPassengers[0]->m_vehDoor = CAR_DOOR_RF;
+ return true;
+ }
+ return false;
+ }
+ if (pPassengers[2] &&
+ !(m_nGettingInFlags & CAR_DOOR_FLAG_RR) &&
+ IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) {
+ if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) {
+ pPassengers[1] = pPassengers[2];
+ pPassengers[2] = nil;
+ pPassengers[1]->m_vehDoor = CAR_DOOR_LR;
+ return true;
+ }
+ if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) {
+ pPassengers[0] = pPassengers[2];
+ pPassengers[2] = nil;
+ pPassengers[0]->m_vehDoor = CAR_DOOR_RF;
+ return true;
+ }
+ return false;
+ }
+ if (pPassengers[0] &&
+ !(m_nGettingInFlags & CAR_DOOR_FLAG_RF) &&
+ IsRoomForPedToLeaveCar(CAR_DOOR_RF, nil)) {
+ if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) {
+ pPassengers[1] = pPassengers[0];
+ pPassengers[0] = nil;
+ pPassengers[1]->m_vehDoor = CAR_DOOR_LR;
+ return true;
+ }
+ if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) {
+ pPassengers[2] = pPassengers[0];
+ pPassengers[0] = nil;
+ pPassengers[2]->m_vehDoor = CAR_DOOR_RR;
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+void
+CVehicle::MakeNonDraggedPedsLeaveVehicle(CPed *ped1, CPed *ped2, CPlayerPed *&player, CCopPed *&cop)
+{
+ int i;
+ player = nil;
+ cop = nil;
+
+ if(ped1->IsPlayer() && ped2->m_nPedType == PEDTYPE_COP &&
+ ((CPlayerPed*)ped1)->m_pWanted->GetWantedLevel() > 0 &&
+ ped2->m_pedInObjective == ped1){
+ player = (CPlayerPed*)ped1;
+ cop = (CCopPed*)ped2;
+ return;
+ }
+
+ bool ped1IsDriver = ped1 == pDriver;
+
+ // Just what the hell is this weird code?
+ CPed *peds[9];
+ CPed *peds2[9];
+ int numPeds = 0;
+ int numPeds2 = 0;
+ for(i = 0; i < m_nNumMaxPassengers; i++){
+ CPed *p = pPassengers[i];
+ if(p && p != ped1 && !p->bStayInCarOnJack){
+ peds[numPeds++] = p;
+ // uhh what?
+ if(i < 1 && !ped1IsDriver)
+ continue;
+ peds2[numPeds2++] = p;
+ }
+ }
+
+ // So we're copying this array for no reason...
+ CPed *peds3[9];
+ int numPeds3 = 0;
+ for(i = 0; i < numPeds; i++){
+ if(peds[i]->IsPlayer() && ped2->m_nPedType == PEDTYPE_COP &&
+ ((CPlayerPed*)peds[i])->m_pWanted->GetWantedLevel() > 0 &&
+ ped2->m_pedInObjective == peds[i]){
+ player = (CPlayerPed*)peds[i];
+ cop = (CCopPed*)ped2;
+ return;
+ }
+ peds3[numPeds3++] = peds[i];
+ }
+
+ int time = 1800;
+ for(i = 0; i < numPeds3; i++){
+ peds3[i]->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + time;
+ peds3[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this);
+ time += CGeneral::GetRandomNumberInRange(300.0f, 600.0f);
+ }
+
+ if(IsCar() && numPeds2 > 0 && CGeneral::GetRandomTrueFalse())
+ for(i = 0; i < numPeds2; i++)
+ if(peds2[i]->IsFemale() || CGeneral::GetRandomTrueFalse()){
+ peds2[i]->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 10000;
+ peds2[i]->bHeldHostageInCar = true;
+ peds2[i]->bFleeAfterExitingCar = true;
+ }
+}
+
+void
+CVehicle::ProcessDelayedExplosion(void)
+{
+ if(m_nBombTimer == 0)
+ return;
+
+ int tick = CTimer::GetTimeStep()/60.0f*1000.0f;
+ int16 prev = m_nBombTimer;
+ if(tick > m_nBombTimer)
+ m_nBombTimer = 0;
+ else
+ m_nBombTimer -= tick;
+
+ if(IsCar() && ((CAutomobile*)this)->m_bombType == CARBOMB_TIMEDACTIVE && (m_nBombTimer & 0xFE00) != (prev & 0xFE00))
+ DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_BOMB_TICK, 0.0f);
+
+ if (m_nBombTimer != 0)
+ return;
+
+ BlowUpCar(m_pBlowUpEntity);
+}
+
+bool
+CVehicle::IsLawEnforcementVehicle(void)
+{
+ switch(GetModelIndex()){
+ case MI_POLICE:
+ case MI_ENFORCER:
+ case MI_PREDATOR:
+ case MI_RHINO:
+ case MI_BARRACKS:
+ case MI_FBIRANCH:
+ case MI_VICECHEE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+CVehicle::UsesSiren(void)
+{
+ switch(GetModelIndex()){
+ case MI_FIRETRUCK:
+ case MI_AMBULAN:
+ case MI_FBICAR:
+ case MI_MRWHOOP:
+ case MI_POLICE:
+ case MI_ENFORCER:
+ case MI_PREDATOR:
+ case MI_FBIRANCH:
+ case MI_VICECHEE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+CVehicle::IsVehicleNormal(void)
+{
+ if (!pDriver || m_nNumPassengers != 0 || GetStatus() == STATUS_WRECKED)
+ return false;
+ return GetModelInfo()->m_vehicleClass != -1;
+}
+
+bool
+CVehicle::CarHasRoof(void)
+{
+ if((pHandling->Flags & HANDLING_HAS_NO_ROOF) == 0)
+ return true;
+ // component 0 is assumed to be a roof
+ return m_aExtras[0] == 0 || m_aExtras[1] == 0;
+}
+
+bool
+CVehicle::IsUpsideDown(void)
+{
+ if(GetUp().z > -0.9f)
+ return false;
+ return true;
+}
+
+bool
+CVehicle::IsOnItsSide(void)
+{
+ if(GetRight().z < 0.8f && GetRight().z > -0.8f)
+ return false;
+ return true;
+}
+
+bool
+CVehicle::CanBeDeleted(void)
+{
+ int i;
+
+ if(m_nNumGettingIn || m_nGettingOutFlags)
+ return false;
+
+ if(pDriver){
+ // This looks like it was inlined
+ if(pDriver->CharCreatedBy == MISSION_CHAR)
+ return false;
+ if(pDriver->GetPedState() != PED_DRIVING &&
+ pDriver->GetPedState() != PED_DEAD)
+ return false;
+ }
+
+ for(i = 0; i < ARRAY_SIZE(pPassengers); i++){
+ // Same check as above
+ if(pPassengers[i]){
+ if(pPassengers[i]->CharCreatedBy == MISSION_CHAR)
+ return false;
+ if(pPassengers[i]->GetPedState() != PED_DRIVING &&
+ pPassengers[i]->GetPedState() != PED_DEAD)
+ return false;
+ }
+ // and then again... probably because something was inlined
+ if(pPassengers[i]){
+ if(pPassengers[i]->GetPedState() != PED_DRIVING &&
+ pPassengers[i]->GetPedState() != PED_DEAD)
+ return false;
+ }
+ }
+
+ switch(VehicleCreatedBy){
+ case RANDOM_VEHICLE: return true;
+ case MISSION_VEHICLE: return false;
+ case PARKED_VEHICLE: return true;
+ case PERMANENT_VEHICLE: return false;
+ }
+ return true;
+}
+
+bool
+CVehicle::CanPedOpenLocks(CPed *ped)
+{
+ if(m_nDoorLock == CARLOCK_LOCKED ||
+ m_nDoorLock == CARLOCK_LOCKED_INITIALLY ||
+ m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE ||
+ m_nDoorLock == CARLOCK_SKIP_SHUT_DOORS)
+ return false;
+ if(ped->IsPlayer() && m_nDoorLock == CARLOCK_LOCKOUT_PLAYER_ONLY)
+ return false;
+ return true;
+}
+
+bool
+CVehicle::CanDoorsBeDamaged(void)
+{
+ return m_nDoorLock == CARLOCK_NOT_USED ||
+ m_nDoorLock == CARLOCK_UNLOCKED ||
+ m_nDoorLock == CARLOCK_SKIP_SHUT_DOORS;
+}
+
+bool
+CVehicle::CanPedEnterCar(void)
+{
+ // can't enter when car is on side
+ if(IsBike() || GetUp().z > 0.1f || GetUp().z < -0.1f){
+ // also when car is moving too fast
+ if(m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f))
+ return false;
+ if(m_vecTurnSpeed.MagnitudeSqr() > sq(0.2f))
+ return false;
+ return true;
+ }
+ return false;
+}
+
+bool
+CVehicle::CanPedExitCar(bool jumpExit)
+{
+ CVector up = GetUp();
+ if(up.z > 0.1f || up.z < -0.1f){
+ if (IsBoat())
+ return true;
+ // can't exit when car is moving too fast
+ if(m_vecMoveSpeed.MagnitudeSqr() > 0.005f && !jumpExit)
+ return false;
+ // if car is slow enough, check turn speed
+ if(Abs(m_vecTurnSpeed.x) > 0.01f ||
+ Abs(m_vecTurnSpeed.y) > 0.01f ||
+ Abs(m_vecTurnSpeed.z) > 0.01f)
+ return false;
+ return true;
+ }else{
+ // What is this? just > replaced by >= ??
+
+ // can't exit when car is moving too fast
+ if(m_vecMoveSpeed.MagnitudeSqr() >= 0.005f)
+ return false;
+ // if car is slow enough, check turn speed
+ if(Abs(m_vecTurnSpeed.x) >= 0.01f ||
+ Abs(m_vecTurnSpeed.y) >= 0.01f ||
+ Abs(m_vecTurnSpeed.z) >= 0.01f)
+ return false;
+ return true;
+ }
+}
+
+bool
+CVehicle::CanPedJumpOutCar(void)
+{
+ if(GetUp().z < 0.3f)
+ return false;
+ float speed = m_vecMoveSpeed.MagnitudeSqr();
+ return speed < 0.1f || speed > 0.5f ? false : true;
+}
+
+bool
+CVehicle::CanPedJumpOffBike(void)
+{
+ if(pPassengers[0])
+ return false;
+ return m_vecMoveSpeed.MagnitudeSqr() < 0.07f ? false : true;
+}
+
+void
+CVehicle::ChangeLawEnforcerState(uint8 enable)
+{
+ if (enable) {
+ if (!bIsLawEnforcer) {
+ bIsLawEnforcer = true;
+ CCarCtrl::NumLawEnforcerCars++;
+ }
+ } else {
+ if (bIsLawEnforcer) {
+ bIsLawEnforcer = false;
+ CCarCtrl::NumLawEnforcerCars--;
+ }
+ }
+}
+
+CPed*
+CVehicle::SetUpDriver(void)
+{
+ if(pDriver)
+ return pDriver;
+ if(VehicleCreatedBy != RANDOM_VEHICLE)
+ return nil;
+
+ pDriver = CPopulation::AddPedInCar(this, true);
+ pDriver->m_pMyVehicle = this;
+ pDriver->m_pMyVehicle->RegisterReference((CEntity**)&pDriver->m_pMyVehicle);
+ pDriver->bInVehicle = true;
+ pDriver->SetPedState(PED_DRIVING);
+ if(bIsBus)
+ pDriver->bRenderPedInCar = false;
+ return pDriver;
+}
+
+CPed*
+CVehicle::SetupPassenger(int n)
+{
+ int i;
+
+ if(pPassengers[n])
+ return pPassengers[n];
+
+ if((IsTaxi() || IsLimo()) && n == 0)
+ pPassengers[0] = nil;
+ else{
+ CPed *passenger = CPopulation::AddPedInCar(this, false);
+ pPassengers[n] = passenger;
+ passenger->m_pMyVehicle = this;
+ passenger->m_pMyVehicle->RegisterReference((CEntity**)&pPassengers[n]->m_pMyVehicle);
+ passenger->bInVehicle = true;
+ passenger->SetPedState(PED_DRIVING);
+
+ if(passenger->m_nPedType == PEDTYPE_CIVMALE || passenger->m_nPedType == PEDTYPE_CIVFEMALE)
+ for(i = 0; i < n; i++)
+ if(pPassengers[i] && pPassengers[n] &&
+ (pPassengers[i]->m_nPedType == PEDTYPE_CIVMALE || pPassengers[i]->m_nPedType == PEDTYPE_CIVFEMALE) &&
+ passenger->GetModelIndex() == pPassengers[i]->GetModelIndex()){
+ pPassengers[n] = nil;
+ CPopulation::RemovePed(passenger);
+ }
+ }
+ if(bIsBus && pPassengers[n])
+ pPassengers[n]->bRenderPedInCar = false;
+ ++m_nNumPassengers;
+ return pPassengers[n];
+}
+
+void
+CVehicle::SetDriver(CPed *driver)
+{
+ pDriver = driver;
+ pDriver->RegisterReference((CEntity**)&pDriver);
+
+ if(bFreebies && driver == FindPlayerPed()){
+ bFreebies = false;
+ switch(GetModelIndex()){
+ case MI_AMBULAN:
+ FindPlayerPed()->m_fHealth = Max(FindPlayerPed()->m_fHealth, Min(FindPlayerPed()->m_fHealth + 20.0f, CWorld::Players[0].m_nMaxHealth));
+ break;
+
+ case MI_TAXI:
+ case MI_CABBIE:
+ case MI_ZEBRA:
+ case MI_KAUFMAN:
+ CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 12;
+ break;
+
+ case MI_POLICE:
+ CStreaming::RequestModel(MI_SHOTGUN, STREAMFLAGS_DONT_REMOVE);
+ bFreebies = true;
+ break;
+
+ case MI_ENFORCER:
+ driver->m_fArmour = Max(driver->m_fArmour, CWorld::Players[0].m_nMaxArmour);
+ break;
+
+ case MI_CADDY:
+ if(!(driver->IsPlayer() && ((CPlayerPed*)driver)->DoesPlayerWantNewWeapon(WEAPONTYPE_GOLFCLUB, true)))
+ CStreaming::RequestModel(MI_GOLFCLUB, STREAMFLAGS_DONT_REMOVE);
+ break;
+ }
+ }
+
+ if(IsBike())
+ ApplyMoveForce(-0.02f*driver->m_fMass * GetUp());
+ else
+ ApplyTurnForce(0.0f, 0.0f, -0.02f*driver->m_fMass,
+ driver->GetPosition().x - GetPosition().x,
+ driver->GetPosition().y - GetPosition().y,
+ 0.0f);
+}
+
+bool
+CVehicle::AddPassenger(CPed *passenger)
+{
+ int i;
+
+ if(IsBike())
+ ApplyTurnForce(-0.02f*passenger->m_fMass * GetUp(), -0.1f*GetForward());
+ else
+ ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass,
+ passenger->GetPosition().x - GetPosition().x,
+ passenger->GetPosition().y - GetPosition().y,
+ 0.0f);
+
+ for(i = 0; i < m_nNumMaxPassengers; i++)
+ if(pPassengers[i] == nil){
+ pPassengers[i] = passenger;
+ m_nNumPassengers++;
+ return true;
+ }
+ return false;
+}
+
+bool
+CVehicle::AddPassenger(CPed *passenger, uint8 n)
+{
+ if(bIsBus)
+ return AddPassenger(passenger);
+
+ if(IsBike())
+ ApplyTurnForce(-0.02f*passenger->m_fMass * GetUp(), -0.1f*GetForward());
+ else
+ ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass,
+ passenger->GetPosition().x - GetPosition().x,
+ passenger->GetPosition().y - GetPosition().y,
+ 0.0f);
+
+ if(n < m_nNumMaxPassengers && pPassengers[n] == nil){
+ pPassengers[n] = passenger;
+ m_nNumPassengers++;
+ return true;
+ }
+ return false;
+}
+
+void
+CVehicle::RemoveDriver(void)
+{
+#ifdef FIX_BUGS
+ if (GetStatus() != STATUS_WRECKED)
+#endif
+ SetStatus(STATUS_ABANDONED);
+ if(pDriver == FindPlayerPed()){
+ if(GetModelIndex() == MI_POLICE && CStreaming::HasModelLoaded(MI_SHOTGUN)){
+ if(bFreebies){
+ if(((CPlayerPed*)pDriver)->DoesPlayerWantNewWeapon(WEAPONTYPE_SHOTGUN, true))
+ pDriver->GiveWeapon(WEAPONTYPE_SHOTGUN, 5, true);
+ else
+ pDriver->GrantAmmo(WEAPONTYPE_SHOTGUN, 5);
+ bFreebies = false;
+ }
+ CStreaming::SetModelIsDeletable(MI_SHOTGUN);
+ }else if(GetModelIndex() == MI_CADDY && CStreaming::HasModelLoaded(MI_GOLFCLUB)){
+ if(((CPlayerPed*)pDriver)->DoesPlayerWantNewWeapon(WEAPONTYPE_GOLFCLUB, true))
+ pDriver->GiveWeapon(WEAPONTYPE_GOLFCLUB, 1, true);
+ CStreaming::SetModelIsDeletable(MI_GOLFCLUB);
+ }
+ }
+ pDriver = nil;
+}
+
+void
+CVehicle::RemovePassenger(CPed *p)
+{
+ if (IsTrain()){
+ for (int i = 0; i < ARRAY_SIZE(pPassengers); i++){
+ if (pPassengers[i] == p) {
+ pPassengers[i] = nil;
+ m_nNumPassengers--;
+ return;
+ }
+ }
+ return;
+ }
+ for (int i = 0; i < m_nNumMaxPassengers; i++){
+ if (pPassengers[i] == p){
+ pPassengers[i] = nil;
+ m_nNumPassengers--;
+ return;
+ }
+ }
+}
+
+bool
+CVehicle::IsDriver(CPed *ped)
+{
+ if(ped == nil)
+ return false;
+ return ped == pDriver;
+}
+
+bool
+CVehicle::IsDriver(int32 model)
+{
+ return pDriver && pDriver->GetModelIndex() == model;
+}
+
+bool
+CVehicle::IsPassenger(CPed *ped)
+{
+ int i;
+ if(ped == nil)
+ return false;
+ for(i = 0; i < 8; i++)
+ if(pPassengers[i] == ped)
+ return true;
+ return false;
+}
+
+bool
+CVehicle::IsPassenger(int32 model)
+{
+ int i;
+ for(i = 0; i < 8; i++)
+ if(pPassengers[i] && pPassengers[i]->GetModelIndex() == model)
+ return true;
+ return false;
+}
+
+void
+CVehicle::UpdatePassengerList(void)
+{
+ int i;
+ bool hasPassenger = false;
+ if(m_nNumPassengers)
+ for(i = 0; i < 8; i++)
+ if(pPassengers[i]){
+ hasPassenger = true;
+ break;
+ }
+ if(!hasPassenger)
+ m_nNumPassengers = 0;
+}
+
+void
+CVehicle::ProcessCarAlarm(void)
+{
+ uint32 step;
+
+ if(!IsAlarmOn())
+ return;
+
+ step = CTimer::GetTimeStepInMilliseconds();
+ if((uint16)m_nAlarmState < step){
+ m_nAlarmState = 0;
+ m_nCarHornTimer = 0;
+ }else
+ m_nAlarmState -= step;
+}
+
+bool
+CVehicle::IsSphereTouchingVehicle(float sx, float sy, float sz, float radius)
+{
+ float x, y, z;
+ // sphere relative to vehicle
+ CVector sph = CVector(sx, sy, sz) - GetPosition();
+ CColModel *colmodel = GetColModel();
+
+ x = DotProduct(sph, GetRight());
+ if(colmodel->boundingBox.min.x - radius > x ||
+ colmodel->boundingBox.max.x + radius < x)
+ return false;
+ y = DotProduct(sph, GetForward());
+ if(colmodel->boundingBox.min.y - radius > y ||
+ colmodel->boundingBox.max.y + radius < y)
+ return false;
+ z = DotProduct(sph, GetUp());
+ if(colmodel->boundingBox.min.z - radius > z ||
+ colmodel->boundingBox.max.z + radius < z)
+ return false;
+
+ return true;
+}
+
+RpMaterial*
+SetCompAlphaCB(RpMaterial *material, void *data)
+{
+ uint32 alpha = (uint32)(uintptr)data;
+ RwRGBA *col = (RwRGBA*)RpMaterialGetColor(material); // get rid of const
+ col->alpha = alpha;
+ return material;
+}
+
+void
+CVehicle::SetComponentAtomicAlpha(RpAtomic *atomic, int32 alpha)
+{
+ RpGeometry *geo = RpAtomicGetGeometry(atomic);
+ RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) | rpGEOMETRYMODULATEMATERIALCOLOR);
+ RpGeometryForAllMaterials(geo, SetCompAlphaCB, (void*)alpha);
+}
+
+void
+CVehicle::UpdateClumpAlpha(void)
+{
+ int clumpAlpha = CVisibilityPlugins::GetClumpAlpha((RpClump*)m_rwObject);
+ if(bFadeOut){
+ clumpAlpha -= 8;
+ if(clumpAlpha < 0)
+ clumpAlpha = 0;
+ }else if(clumpAlpha < 255){
+ clumpAlpha += 16;
+ if(clumpAlpha > 255)
+ clumpAlpha = 255;
+ }
+ CVisibilityPlugins::SetClumpAlpha((RpClump*)m_rwObject, clumpAlpha);
+}
+
+void
+CVehicle::HeliDustGenerate(CEntity *heli, float radius, float ground, int rnd)
+{
+ int i;
+ float angle;
+ CColPoint point;
+ CEntity *entity;
+ uint8 r, g, b;
+
+ if(heli == nil)
+ return;
+
+ uint8 surface = SURFACE_TARMAC;
+ int frm = CTimer::GetFrameCounter() & 7;
+ float testLowZ = ground - 10.0f;
+ float dustSize = 0.0f;
+ float baseSize = 1.0f;
+ float offset = 1.0f; // when heli is tilted
+ float particleZ = -101.0f;
+ int n = 0;
+
+ if(heli->GetModelIndex() == MI_RCGOBLIN || heli->GetModelIndex() == MI_RCRAIDER){
+ radius = 3.0f;
+ dustSize = 0.04f;
+ baseSize = 0.07f;
+ offset = 0.3f;
+ }
+
+ CVector heliPos = heli->GetPosition();
+
+ if(heli->IsVehicle() && ((CVehicle*)heli)->IsCar()){
+ heliPos.x -= (heliPos.z - ground)*heli->GetUp().x*offset*0.5f;
+ heliPos.y -= (heliPos.z - ground)*heli->GetUp().y*offset*0.5f;
+ }
+
+ float steamSize = 0.25f * radius * baseSize;
+ float splashSize = 0.3f * radius * baseSize;
+
+ i = 0;
+ for(i = 0; i < 32+rnd; i++){
+ angle = i * TWOPI/32.0f;
+ CVector pos(radius*Cos(angle), radius*Sin(angle), 0.0f);
+ CVector dir = CVector(pos.x, pos.y, 1.0f)*0.01f;
+ pos += heliPos;
+
+ if(i < 32 && i == 4*frm){
+ if(CWorld::ProcessVerticalLine(pos, testLowZ, point, entity, true, false, false, false, true, false, nil)){
+ n = rnd;
+ particleZ = point.point.z;
+ surface = point.surfaceB;
+ }else
+ n = 0;
+
+ float waterLevel = 0.0f;
+ if(CWaterLevel::GetWaterLevel(pos, &waterLevel, false) && waterLevel > particleZ){
+ surface = SURFACE_WATER;
+ n = rnd;
+ particleZ = waterLevel;
+ }
+ }
+
+ if(n){
+ pos.z = particleZ;
+ if(surface == SURFACE_WATER){
+ float red = (0.3*CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj())*255.0f/4.0f;
+ float green = (0.3*CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj())*255.0f/4.0f;
+ float blue = (0.3*CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj())*255.0f/4.0f;
+ r = clamp(red, 0.0f, 255.0f);
+ g = clamp(green, 0.0f, 255.0f);
+ b = clamp(blue, 0.0f, 255.0f);
+ RwRGBA col1 = { r, g, b, (RwUInt8)CGeneral::GetRandomNumberInRange(8, 32) };
+ RwRGBA col2 = { 255, 255, 255, 32 };
+
+ if(n&1)
+ CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, pos, dir, nil, steamSize, col2);
+ else
+ CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, dir, nil, splashSize, col1,
+ CGeneral::GetRandomNumberInRange(0.0f, 10.0f),
+ CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1);
+ }else{
+ switch(surface){
+ default:
+ case SURFACE_TARMAC:
+ r = 10;
+ g = 10;
+ b = 10;
+ break;
+ case SURFACE_GRASS:
+ r = 10;
+ g = 10;
+ b = 3;
+ break;
+ case SURFACE_GRAVEL:
+ r = 10;
+ g = 8;
+ b = 7;
+ break;
+ case SURFACE_MUD_DRY:
+ r = 10;
+ g = 6;
+ b = 3;
+ break;
+ case SURFACE_SAND:
+ case SURFACE_SAND_BEACH:
+ r = 10;
+ g = 10;
+ b = 7;
+ break;
+ }
+ RwRGBA col = { r, g, b, 32 };
+ if(heliPos.z - pos.z < 20.0f)
+ CParticle::AddParticle(PARTICLE_HELI_DUST, pos, dir, nil, dustSize, col);
+ }
+
+ n--;
+ }
+ }
+}
+
+#define GLARE_MIN_DIST (13.0f)
+#define GLARE_FULL_DIST (30.0f)
+#define GLARE_MIN_ANGLE (0.99f)
+#define GLARE_FULL_ANGLE (0.995f)
+
+void
+CVehicle::DoSunGlare(void)
+{
+ if(bRenderScorched || GetPosition().z < 0.0f ||
+ GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR || CWeather::SunGlare <= 0.0f)
+ return;
+
+ CVector camDir = TheCamera.GetPosition() - GetPosition();
+ float dist = camDir.Magnitude();
+ camDir *= 2.0f/dist;
+ CVector glareVec = camDir + CTimeCycle::GetSunDirection();
+ CVector localGlareVec;
+ localGlareVec.x = DotProduct(glareVec, GetRight());
+ localGlareVec.y = DotProduct(glareVec, GetForward());
+ localGlareVec.z = 0.0;
+ localGlareVec.Normalise();
+
+ CVector2D fwd2D = GetForward();
+ fwd2D.Normalise();
+ CVector2D camDir2D = camDir;
+ camDir2D.Normalise();
+ float fwdness = Abs(DotProduct2D(fwd2D, camDir2D));
+
+ // check angle
+ float strength;
+ if(fwdness > GLARE_FULL_ANGLE)
+ strength = 1.0f;
+ else if(fwdness > GLARE_MIN_ANGLE)
+ strength = (fwdness - GLARE_MIN_ANGLE)/(GLARE_FULL_ANGLE-GLARE_MIN_ANGLE);
+ else
+ return;
+ // check distance
+ if(dist > GLARE_FULL_DIST){
+ // no max distance
+ }else if(dist > GLARE_MIN_DIST)
+ strength *= (dist - GLARE_MIN_DIST)/(GLARE_FULL_DIST - GLARE_MIN_DIST);
+ else
+ return;
+
+ float intens = 0.8f * strength * CWeather::SunGlare;
+ int r = intens * (CTimeCycle::GetSunCoreRed() + 2*255)/3.0f;
+ int g = intens * (CTimeCycle::GetSunCoreGreen() + 2*255)/3.0f;
+ int b = intens * (CTimeCycle::GetSunCoreBlue() + 2*255)/3.0f;
+
+ CColModel *colmodel = GetColModel();
+ CCollision::CalculateTrianglePlanes(colmodel);
+
+ int i;
+ for(i = 0; i < colmodel->numTriangles-2; i += 2){
+ int a1 = colmodel->triangles[i].a;
+ int b1 = colmodel->triangles[i].b;
+ int c1 = colmodel->triangles[i].c;
+ int a2 = colmodel->triangles[i+1].a;
+ int b2 = colmodel->triangles[i+1].b;
+ int c2 = colmodel->triangles[i+1].c;
+ CVector vert1 = colmodel->vertices[a1].Get();
+ CVector vert4;
+ // Need an upward surface
+ if(vert1.z <= 0.0f)
+ continue;
+
+ // trying to find a quad here
+ int numTri2Verts = 0;
+ if(a2 != a1 && a2 != b1 && a2 != c1){
+ // a2 is not in tri1
+ numTri2Verts++;
+ vert4 = colmodel->vertices[a2].Get();
+ }
+ if(b2 != a1 && b2 != b1 && b2 != c1){
+ // b2 is not in tri1
+ numTri2Verts++;
+ vert4 = colmodel->vertices[b2].Get();
+ }
+ if(c2 != a1 && c2 != b1 && c2 != c1){
+ // c2 is not in tri1
+ numTri2Verts++;
+ vert4 = colmodel->vertices[c2].Get();
+ }
+ // Need exactly one vertex from tri2 for a quad with tri1
+ if(numTri2Verts != 1)
+ continue;
+
+ CVector mid = (vert1 + colmodel->vertices[b1].Get() + colmodel->vertices[c1].Get() + vert4)/4.0f;
+ float dy = mid.y - vert1.y;
+ float dx = mid.x - vert1.x;
+ float dist = 1.4f * Min(Abs(dx), Abs(dy));
+ if(dist > 0.6f){
+ CVector pos = GetMatrix() * (dist * localGlareVec + mid) + camDir;
+ CCoronas::RegisterCorona((uintptr)this + 27 + i,
+ r, g, b, 255,
+ pos, 0.9f*CWeather::SunGlare, 90.0f,
+ CCoronas::TYPE_STAR, CCoronas::FLARE_NONE,
+ CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF,
+ CCoronas::STREAK_OFF, 0.0f);
+ }
+ }
+}
+
+void
+CVehicle::KillPedsInVehicle(void)
+{
+ int i;
+ if(pDriver){
+ CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION);
+ if(pDriver->GetPedState() == PED_DRIVING){
+ pDriver->SetDead();
+ if(!pDriver->IsPlayer())
+ pDriver->FlagToDestroyWhenNextProcessed();
+ }else
+ pDriver->SetDie();
+ }
+ for(i = 0; i < m_nNumMaxPassengers; i++){
+ if(pPassengers[i]){
+ CDarkel::RegisterKillByPlayer(pPassengers[i], WEAPONTYPE_EXPLOSION);
+ if(pPassengers[i]->GetPedState() == PED_DRIVING){
+ pPassengers[i]->SetDead();
+ if(!pPassengers[i]->IsPlayer())
+ pPassengers[i]->FlagToDestroyWhenNextProcessed();
+ }else
+ pPassengers[i]->SetDie();
+ }
+ }
+}
+
+void
+DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle)
+{
+ if (pVehicle->pDriver) {
+ CDarkel::RegisterKillByPlayer(pVehicle->pDriver, WEAPONTYPE_UNIDENTIFIED);
+ pVehicle->pDriver->FlagToDestroyWhenNextProcessed();
+ }
+ for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) {
+ if (pVehicle->pPassengers[i]) {
+ CDarkel::RegisterKillByPlayer(pVehicle->pPassengers[i], WEAPONTYPE_UNIDENTIFIED);
+ pVehicle->pPassengers[i]->FlagToDestroyWhenNextProcessed();
+ }
+ }
+ CWorld::Remove(pVehicle);
+ delete pVehicle;
+}
+
+#ifdef COMPATIBLE_SAVES
+void
+CVehicle::Save(uint8*& buf)
+{
+ SkipSaveBuf(buf, 4);
+ WriteSaveBuf<float>(buf, GetRight().x);
+ WriteSaveBuf<float>(buf, GetRight().y);
+ WriteSaveBuf<float>(buf, GetRight().z);
+ SkipSaveBuf(buf, 4);
+ WriteSaveBuf<float>(buf, GetForward().x);
+ WriteSaveBuf<float>(buf, GetForward().y);
+ WriteSaveBuf<float>(buf, GetForward().z);
+ SkipSaveBuf(buf, 4);
+ WriteSaveBuf<float>(buf, GetUp().x);
+ WriteSaveBuf<float>(buf, GetUp().y);
+ WriteSaveBuf<float>(buf, GetUp().z);
+ SkipSaveBuf(buf, 4);
+ WriteSaveBuf<float>(buf, GetPosition().x);
+ WriteSaveBuf<float>(buf, GetPosition().y);
+ WriteSaveBuf<float>(buf, GetPosition().z);
+ SkipSaveBuf(buf, 16);
+ SaveEntityFlags(buf);
+ SkipSaveBuf(buf, 208);
+ AutoPilot.Save(buf);
+ WriteSaveBuf<int8>(buf, m_currentColour1);
+ WriteSaveBuf<int8>(buf, m_currentColour2);
+ SkipSaveBuf(buf, 2);
+ WriteSaveBuf<int16>(buf, m_nAlarmState);
+ SkipSaveBuf(buf, 42);
+ WriteSaveBuf<uint8>(buf, m_nNumMaxPassengers);
+ SkipSaveBuf(buf, 3);
+ WriteSaveBuf<float>(buf, field_1D0[0]);
+ WriteSaveBuf<float>(buf, field_1D0[1]);
+ WriteSaveBuf<float>(buf, field_1D0[2]);
+ WriteSaveBuf<float>(buf, field_1D0[3]);
+ SkipSaveBuf(buf, 8);
+ WriteSaveBuf<float>(buf, m_fSteerAngle);
+ WriteSaveBuf<float>(buf, m_fGasPedal);
+ WriteSaveBuf<float>(buf, m_fBrakePedal);
+ WriteSaveBuf<uint8>(buf, VehicleCreatedBy);
+ uint8 flags = 0;
+ if (bIsLawEnforcer) flags |= BIT(0);
+ if (bIsLocked) flags |= BIT(3);
+ if (bEngineOn) flags |= BIT(4);
+ if (bIsHandbrakeOn) flags |= BIT(5);
+ if (bLightsOn) flags |= BIT(6);
+ if (bFreebies) flags |= BIT(7);
+ WriteSaveBuf<uint8>(buf, flags);
+ SkipSaveBuf(buf, 10);
+ WriteSaveBuf<float>(buf, m_fHealth);
+ WriteSaveBuf<uint8>(buf, m_nCurrentGear);
+ SkipSaveBuf(buf, 3);
+ WriteSaveBuf<float>(buf, m_fChangeGearTime);
+ SkipSaveBuf(buf, 12);
+ WriteSaveBuf<uint32>(buf, m_nTimeOfDeath);
+ SkipSaveBuf(buf, 2);
+ WriteSaveBuf<int16>(buf, m_nBombTimer);
+ SkipSaveBuf(buf, 12);
+ WriteSaveBuf<int8>(buf, m_nDoorLock);
+ SkipSaveBuf(buf, 111);
+}
+
+void
+CVehicle::Load(uint8*& buf)
+{
+ CMatrix tmp;
+ SkipSaveBuf(buf, 4);
+ tmp.GetRight().x = ReadSaveBuf<float>(buf);
+ tmp.GetRight().y = ReadSaveBuf<float>(buf);
+ tmp.GetRight().z = ReadSaveBuf<float>(buf);
+ SkipSaveBuf(buf, 4);
+ tmp.GetForward().x = ReadSaveBuf<float>(buf);
+ tmp.GetForward().y = ReadSaveBuf<float>(buf);
+ tmp.GetForward().z = ReadSaveBuf<float>(buf);
+ SkipSaveBuf(buf, 4);
+ tmp.GetUp().x = ReadSaveBuf<float>(buf);
+ tmp.GetUp().y = ReadSaveBuf<float>(buf);
+ tmp.GetUp().z = ReadSaveBuf<float>(buf);
+ SkipSaveBuf(buf, 4);
+ tmp.GetPosition().x = ReadSaveBuf<float>(buf);
+ tmp.GetPosition().y = ReadSaveBuf<float>(buf);
+ tmp.GetPosition().z = ReadSaveBuf<float>(buf);
+ m_matrix = tmp;
+ SkipSaveBuf(buf, 16);
+ LoadEntityFlags(buf);
+ SkipSaveBuf(buf, 208);
+ AutoPilot.Load(buf);
+ m_currentColour1 = ReadSaveBuf<int8>(buf);
+ m_currentColour2 = ReadSaveBuf<int8>(buf);
+ SkipSaveBuf(buf, 2);
+ m_nAlarmState = ReadSaveBuf<int16>(buf);
+ SkipSaveBuf(buf, 42);
+ m_nNumMaxPassengers = ReadSaveBuf<int8>(buf);
+ SkipSaveBuf(buf, 3);
+ field_1D0[0] = ReadSaveBuf<float>(buf);
+ field_1D0[1] = ReadSaveBuf<float>(buf);
+ field_1D0[2] = ReadSaveBuf<float>(buf);
+ field_1D0[3] = ReadSaveBuf<float>(buf);
+ SkipSaveBuf(buf, 8);
+ m_fSteerAngle = ReadSaveBuf<float>(buf);
+ m_fGasPedal = ReadSaveBuf<float>(buf);
+ m_fBrakePedal = ReadSaveBuf<float>(buf);
+ VehicleCreatedBy = ReadSaveBuf<uint8>(buf);
+ uint8 flags = ReadSaveBuf<uint8>(buf);
+ bIsLawEnforcer = !!(flags & BIT(0));
+ bIsLocked = !!(flags & BIT(3));
+ bEngineOn = !!(flags & BIT(4));
+ bIsHandbrakeOn = !!(flags & BIT(5));
+ bLightsOn = !!(flags & BIT(6));
+ bFreebies = !!(flags & BIT(7));
+ SkipSaveBuf(buf, 10);
+ m_fHealth = ReadSaveBuf<float>(buf);
+ m_nCurrentGear = ReadSaveBuf<uint8>(buf);
+ SkipSaveBuf(buf, 3);
+ m_fChangeGearTime = ReadSaveBuf<float>(buf);
+ SkipSaveBuf(buf, 12);
+ m_nTimeOfDeath = ReadSaveBuf<uint32>(buf);
+ SkipSaveBuf(buf, 2);
+ m_nBombTimer = ReadSaveBuf<int16>(buf);
+ SkipSaveBuf(buf, 12);
+ m_nDoorLock = (eCarLock)ReadSaveBuf<int8>(buf);
+ SkipSaveBuf(buf, 111);
+}
+#endif
+
+eVehicleAppearance
+CVehicle::GetVehicleAppearance(void)
+{
+ uint32 flags = pHandling->Flags & 0xF0000;
+ if (flags == 0)
+ return VEHICLE_APPEARANCE_CAR;
+ if (flags == HANDLING_IS_BIKE)
+ return VEHICLE_APPEARANCE_BIKE;
+ if (flags == HANDLING_IS_HELI)
+ return VEHICLE_APPEARANCE_HELI;
+ if (flags == HANDLING_IS_PLANE)
+ return VEHICLE_APPEARANCE_PLANE;
+ if (flags == HANDLING_IS_BOAT)
+ return VEHICLE_APPEARANCE_BOAT;
+ return VEHICLE_APPEARANCE_NONE;
+}
+
+bool
+IsVehiclePointerValid(CVehicle* pVehicle)
+{
+ if (!pVehicle)
+ return false;
+ int index = CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(pVehicle);
+#ifdef FIX_BUGS
+ if (index < 0 || index >= NUMVEHICLES)
+#else
+ if (index < 0 || index > NUMVEHICLES)
+#endif
+ return false;
+ return pVehicle->m_vehType == VEHICLE_TYPE_PLANE || pVehicle->m_entryInfoList.first;
+}
diff --git a/src/vehicles/Vehicle.h b/src/vehicles/Vehicle.h
new file mode 100644
index 0000000..2fb2caf
--- /dev/null
+++ b/src/vehicles/Vehicle.h
@@ -0,0 +1,451 @@
+#pragma once
+
+#include "Physical.h"
+#include "AutoPilot.h"
+#include "ModelIndices.h"
+#include "AnimationId.h"
+#include "WeaponType.h"
+#include "Collision.h"
+#include "HandlingMgr.h"
+
+class CPed;
+class CPlayerPed;
+class CCopPed;
+class CFire;
+
+enum {
+ RANDOM_VEHICLE = 1,
+ MISSION_VEHICLE = 2,
+ PARKED_VEHICLE = 3,
+ PERMANENT_VEHICLE = 4,
+};
+
+enum eCarNodes
+{
+ CAR_WHEEL_RF = 1,
+ CAR_WHEEL_RM,
+ CAR_WHEEL_RB,
+ CAR_WHEEL_LF,
+ CAR_WHEEL_LM,
+ CAR_WHEEL_LB,
+ CAR_BUMP_FRONT,
+ CAR_BUMP_REAR,
+ CAR_WING_RF,
+ CAR_WING_RR,
+ CAR_DOOR_RF,
+ CAR_DOOR_RR,
+ CAR_WING_LF,
+ CAR_WING_LR,
+ CAR_DOOR_LF,
+ CAR_DOOR_LR,
+ CAR_BONNET,
+ CAR_BOOT,
+ CAR_WINDSCREEN,
+ NUM_CAR_NODES,
+};
+
+enum {
+ CAR_DOOR_FLAG_UNKNOWN = 0x0,
+ CAR_DOOR_FLAG_LF = 0x1,
+ CAR_DOOR_FLAG_LR = 0x2,
+ CAR_DOOR_FLAG_RF = 0x4,
+ CAR_DOOR_FLAG_RR = 0x8
+};
+
+enum eCarLock {
+ CARLOCK_NOT_USED,
+ CARLOCK_UNLOCKED,
+ CARLOCK_LOCKED,
+ CARLOCK_LOCKOUT_PLAYER_ONLY,
+ CARLOCK_LOCKED_PLAYER_INSIDE,
+ CARLOCK_LOCKED_INITIALLY,
+ CARLOCK_FORCE_SHUT_DOORS,
+ CARLOCK_SKIP_SHUT_DOORS
+};
+
+enum eBombType
+{
+ CARBOMB_NONE,
+ CARBOMB_TIMED,
+ CARBOMB_ONIGNITION,
+ CARBOMB_REMOTE,
+ CARBOMB_TIMEDACTIVE,
+ CARBOMB_ONIGNITIONACTIVE,
+};
+
+enum eDoors
+{
+ DOOR_BONNET = 0,
+ DOOR_BOOT,
+ DOOR_FRONT_LEFT,
+ DOOR_FRONT_RIGHT,
+ DOOR_REAR_LEFT,
+ DOOR_REAR_RIGHT
+};
+
+enum ePanels
+{
+ VEHPANEL_FRONT_LEFT,
+ VEHPANEL_FRONT_RIGHT,
+ VEHPANEL_REAR_LEFT,
+ VEHPANEL_REAR_RIGHT,
+ VEHPANEL_WINDSCREEN,
+ VEHBUMPER_FRONT,
+ VEHBUMPER_REAR,
+};
+
+enum eLights
+{
+ VEHLIGHT_FRONT_LEFT,
+ VEHLIGHT_FRONT_RIGHT,
+ VEHLIGHT_REAR_LEFT,
+ VEHLIGHT_REAR_RIGHT,
+};
+
+enum
+{
+ CAR_PIECE_BONNET = 1,
+ CAR_PIECE_BOOT,
+ CAR_PIECE_BUMP_FRONT,
+ CAR_PIECE_BUMP_REAR,
+ CAR_PIECE_DOOR_LF,
+ CAR_PIECE_DOOR_RF,
+ CAR_PIECE_DOOR_LR,
+ CAR_PIECE_DOOR_RR,
+ CAR_PIECE_WING_LF,
+ CAR_PIECE_WING_RF,
+ CAR_PIECE_WING_LR,
+ CAR_PIECE_WING_RR,
+ CAR_PIECE_WHEEL_LF,
+ CAR_PIECE_WHEEL_RF,
+ CAR_PIECE_WHEEL_LR,
+ CAR_PIECE_WHEEL_RR,
+ CAR_PIECE_WINDSCREEN,
+};
+
+enum tWheelState
+{
+ WHEEL_STATE_NORMAL, // standing still or rolling normally
+ WHEEL_STATE_SPINNING, // rotating but not moving
+ WHEEL_STATE_SKIDDING,
+ WHEEL_STATE_FIXED, // not rotating
+};
+
+enum eFlightModel
+{
+ FLIGHT_MODEL_DODO,
+ FLIGHT_MODEL_RCPLANE,
+ FLIGHT_MODEL_RCHELI,
+ FLIGHT_MODEL_SEAPLANE,
+ FLIGHT_MODEL_PLANE_UNUSED,
+ FLIGHT_MODEL_PLANE,
+ FLIGHT_MODEL_HELI
+};
+
+enum eVehicleAppearance
+{
+ VEHICLE_APPEARANCE_NONE,
+ VEHICLE_APPEARANCE_CAR,
+ VEHICLE_APPEARANCE_BIKE,
+ VEHICLE_APPEARANCE_HELI,
+ VEHICLE_APPEARANCE_BOAT,
+ VEHICLE_APPEARANCE_PLANE,
+};
+
+// TODO: what is this even?
+enum eBikeWheelSpecial
+{
+ BIKE_WHEELSPEC_0, // both wheels on ground
+ BIKE_WHEELSPEC_1, // rear wheel on ground
+ BIKE_WHEELSPEC_2, // only front wheel on ground
+ BIKE_WHEELSPEC_3, // can't happen
+};
+
+enum
+{
+ ROTOR_TOP = 3,
+ ROTOR_FRONT = 4,
+ ROTOR_RIGHT = 5,
+ ROTOR_LEFT = 7,
+ ROTOR_BACK = 8,
+ ROTOR_BOTTOM = 9,
+};
+
+class CVehicle : public CPhysical
+{
+public:
+ tHandlingData *pHandling;
+ tFlyingHandlingData *pFlyingHandling;
+ CAutoPilot AutoPilot;
+ uint8 m_currentColour1;
+ uint8 m_currentColour2;
+ int8 m_aExtras[2];
+ int16 m_nAlarmState;
+ int16 m_nRouteSeed;
+ CPed *pDriver;
+ CPed *pPassengers[8];
+ uint8 m_nNumPassengers;
+ int8 m_nNumGettingIn;
+ int8 m_nGettingInFlags;
+ int8 m_nGettingOutFlags;
+ uint8 m_nNumMaxPassengers;
+ float field_1D0[4];
+ CEntity *m_pCurGroundEntity;
+ CFire *m_pCarFire;
+ float m_fSteerAngle;
+ float m_fGasPedal;
+ float m_fBrakePedal;
+ uint8 VehicleCreatedBy;
+
+ // cf. https://github.com/DK22Pac/plugin-sdk/blob/master/plugin_sa/game_sa/CVehicle.h from R*
+ uint8 bIsLawEnforcer: 1; // Is this guy chasing the player at the moment
+ uint8 bIsAmbulanceOnDuty: 1; // Ambulance trying to get to an accident
+ uint8 bIsFireTruckOnDuty: 1; // Firetruck trying to get to a fire
+ uint8 bIsLocked: 1; // Is this guy locked by the script (cannot be removed)
+ uint8 bEngineOn: 1; // For sound purposes. Parked cars have their engines switched off (so do destroyed cars)
+ uint8 bIsHandbrakeOn: 1; // How's the handbrake doing ?
+ uint8 bLightsOn: 1; // Are the lights switched on ?
+ uint8 bFreebies: 1; // Any freebies left in this vehicle ?
+
+ uint8 bIsVan: 1; // Is this vehicle a van (doors at back of vehicle)
+ uint8 bIsBus: 1; // Is this vehicle a bus
+ uint8 bIsBig: 1; // Is this vehicle a bus
+ uint8 bLowVehicle: 1; // Need this for sporty type cars to use low getting-in/out anims
+ uint8 bComedyControls : 1; // Will make the car hard to control (hopefully in a funny way)
+ uint8 bWarnedPeds : 1; // Has scan and warn peds of danger been processed?
+ uint8 bCraneMessageDone : 1; // A crane message has been printed for this car allready
+ uint8 bExtendedRange : 1; // This vehicle needs to be a bit further away to get deleted
+
+ uint8 bTakeLessDamage : 1; // This vehicle is stronger (takes about 1/4 of damage)
+ uint8 bIsDamaged : 1; // This vehicle has been damaged and is displaying all its components
+ uint8 bHasBeenOwnedByPlayer : 1;// To work out whether stealing it is a crime
+ uint8 bFadeOut : 1; // Fade vehicle out
+ uint8 bIsBeingCarJacked : 1; // Fade vehicle out
+ uint8 bCreateRoadBlockPeds : 1; // If this vehicle gets close enough we will create peds (coppers or gang members) round it
+ uint8 bCanBeDamaged : 1; // Set to FALSE during cut scenes to avoid explosions
+ uint8 bUsingSpecialColModel : 1;// Is player vehicle using special collision model, stored in player strucure
+
+ uint8 bOccupantsHaveBeenGenerated : 1; // Is true if the occupants have already been generated. (Shouldn't happen again)
+ uint8 bGunSwitchedOff : 1; // Level designers can use this to switch off guns on boats
+ uint8 bVehicleColProcessed : 1;// Has ProcessEntityCollision been processed for this car?
+ uint8 bIsCarParkVehicle : 1; // Car has been created using the special CAR_PARK script command
+ uint8 bHasAlreadyBeenRecorded : 1; // Used for replays
+ uint8 bPartOfConvoy : 1;
+ uint8 bHeliMinimumTilt : 1; // This heli should have almost no tilt really
+ uint8 bAudioChangingGear : 1; // sounds like vehicle is changing gear
+
+ uint8 bIsDrowning : 1; // is vehicle occupants taking damage in water (i.e. vehicle is dead in water)
+ uint8 bTyresDontBurst : 1; // If this is set the tyres are invincible
+ uint8 bCreatedAsPoliceVehicle : 1;// True if this guy was created as a police vehicle (enforcer, policecar, miamivice car etc)
+ uint8 bRestingOnPhysical : 1; // Dont go static cause car is sitting on a physical object that might get removed
+ uint8 bParking : 1;
+ uint8 bCanPark : 1;
+#if (!defined GTA_PS2 || defined FIX_BUGS)
+ uint8 m_bombType : 3;
+#endif
+ uint8 bDriverLastFrame : 1;
+
+ int8 m_numPedsUseItAsCover;
+ uint8 m_nAmmoInClip; // Used to make the guns on boat do a reload (20 by default)
+ int8 m_nPacManPickupsCarried;
+ uint8 m_nRoadblockType;
+ float m_fHealth; // 1000.0f = full health. 250.0f = fire. 0 -> explode
+ uint8 m_nCurrentGear;
+ float m_fChangeGearTime;
+#if (!defined GTA_PS2 || defined FIX_BUGS)
+ CEntity* m_pBombRigger;
+#endif
+ uint32 m_nSetPieceExtendedRangeTime;
+ uint32 m_nGunFiringTime; // last time when gun on vehicle was fired (used on boats)
+ uint32 m_nTimeOfDeath;
+ uint16 m_nTimeBlocked;
+ int16 m_nBombTimer; // goes down with each frame
+ CEntity *m_pBlowUpEntity;
+ float m_fMapObjectHeightAhead; // front Z?
+ float m_fMapObjectHeightBehind; // rear Z?
+ eCarLock m_nDoorLock;
+ int8 m_nLastWeaponDamage; // see eWeaponType, -1 if no damage
+ CEntity *m_pLastDamageEntity;
+ uint8 m_nRadioStation;
+ uint8 m_bRainAudioCounter;
+ uint8 m_bRainSamplesCounter;
+ uint32 m_nCarHornTimer;
+ uint8 m_nCarHornPattern;
+ bool m_bSirenOrAlarm;
+ uint8 m_nCarHornDelay;
+ int8 m_comedyControlState;
+ CStoredCollPoly m_aCollPolys[2]; // poly which is under front/rear part of car
+ float m_fSteerInput;
+ uint8 m_vehType;
+
+ static void *operator new(size_t);
+ static void *operator new(size_t sz, int slot);
+ static void operator delete(void*, size_t);
+ static void operator delete(void*, int);
+
+ CVehicle(void) {} // FAKE
+ CVehicle(uint8 CreatedBy);
+ ~CVehicle(void);
+ // from CEntity
+ void SetModelIndex(uint32 id);
+ bool SetupLighting(void);
+ void RemoveLighting(bool);
+ void FlagToDestroyWhenNextProcessed(void) {}
+
+ virtual void ProcessControlInputs(uint8) {}
+ virtual void GetComponentWorldPosition(int32 component, CVector &pos) {}
+ virtual bool IsComponentPresent(int32 component) { return false; }
+ virtual void SetComponentRotation(int32 component, CVector rotation) {}
+ virtual void OpenDoor(int32, eDoors door, float) {}
+ virtual void ProcessOpenDoor(uint32, uint32, float) {}
+ virtual bool IsDoorReady(eDoors door) { return false; }
+ virtual bool IsDoorFullyOpen(eDoors door) { return false; }
+ virtual bool IsDoorClosed(eDoors door) { return false; }
+ virtual bool IsDoorMissing(eDoors door) { return false; }
+ virtual bool IsDoorReady(uint32 door) { return false; }
+ virtual bool IsDoorMissing(uint32 door) { return false; }
+ virtual bool IsOpenTopCar(void) { return false; }
+ virtual void RemoveRefsToVehicle(CEntity *ent) {}
+ virtual void BlowUpCar(CEntity *ent) {}
+ virtual bool SetUpWheelColModel(CColModel *colModel) { return false; }
+ virtual void BurstTyre(uint8 tyre, bool applyForces) {}
+ virtual bool IsRoomForPedToLeaveCar(uint32 component, CVector *forcedDoorPos) { return false; }
+ virtual bool IsClearToDriveAway(void);
+ virtual float GetHeightAboveRoad(void);
+ virtual void PlayCarHorn(void) {}
+#ifdef COMPATIBLE_SAVES
+ virtual void Save(uint8*& buf);
+ virtual void Load(uint8*& buf);
+#endif
+
+ eVehicleAppearance GetVehicleAppearance(void);
+ bool IsCar(void) { return m_vehType == VEHICLE_TYPE_CAR; }
+ bool IsBoat(void) { return m_vehType == VEHICLE_TYPE_BOAT; }
+ bool IsTrain(void) { return m_vehType == VEHICLE_TYPE_TRAIN; }
+ bool IsHeli(void) { return m_vehType == VEHICLE_TYPE_HELI; }
+ bool IsPlane(void) { return m_vehType == VEHICLE_TYPE_PLANE; }
+ bool IsBike(void) { return m_vehType == VEHICLE_TYPE_BIKE; }
+
+ void FlyingControl(eFlightModel flightModel);
+ bool DoBladeCollision(CVector pos, CMatrix &matrix, int16 rotorType, float radius, float damageMult);
+ bool BladeColSectorList(CPtrList &list, CColModel &rotorColModel, CMatrix &matrix, int16 rotorType, float damageMult);
+
+ void ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
+ int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus);
+ void ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
+ int32 wheelsOnGround, float thrust, float brake, float adhesion, float destabTraction, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus);
+ void ExtinguishCarFire(void);
+ void ProcessDelayedExplosion(void);
+ float ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius);
+ int FindTyreNearestPoint(float x, float y);
+ bool IsLawEnforcementVehicle(void);
+ void ChangeLawEnforcerState(uint8 enable);
+ bool UsesSiren(void);
+ bool IsVehicleNormal(void);
+ bool CarHasRoof(void);
+ bool IsUpsideDown(void);
+ bool IsOnItsSide(void);
+ bool CanBeDeleted(void);
+ bool CanPedOpenLocks(CPed *ped);
+ bool CanDoorsBeDamaged(void);
+ bool CanPedEnterCar(void);
+ bool CanPedExitCar(bool jumpExit);
+ bool CanPedJumpOutCar(void);
+ bool CanPedJumpOffBike(void);
+ // do these two actually return something?
+ CPed *SetUpDriver(void);
+ CPed *SetupPassenger(int n);
+ void SetDriver(CPed *driver);
+ bool AddPassenger(CPed *passenger);
+ bool AddPassenger(CPed *passenger, uint8 n);
+ void RemovePassenger(CPed *passenger);
+ void RemoveDriver(void);
+ bool IsDriver(CPed *ped);
+ bool IsDriver(int32 model);
+ bool IsPassenger(CPed *ped);
+ bool IsPassenger(int32 model);
+ void UpdatePassengerList(void);
+ void ProcessCarAlarm(void);
+ bool IsSphereTouchingVehicle(float sx, float sy, float sz, float radius);
+ bool ShufflePassengersToMakeSpace(void);
+ void MakeNonDraggedPedsLeaveVehicle(CPed *ped1, CPed *ped2, CPlayerPed *&player, CCopPed *&cop);
+ void InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage, CVector pos = CVector(0.0f, 0.0f, 0.0f));
+ void DoFixedMachineGuns(void);
+ void FireFixedMachineGuns(void);
+ void ActivateBomb(void);
+ void ActivateBombWhenEntered(void);
+ void KillPedsInVehicle(void);
+
+ void SetComponentAtomicAlpha(RpAtomic *atomic, int32 alpha);
+ void UpdateClumpAlpha(void);
+
+ static void HeliDustGenerate(CEntity *heli, float radius, float ground, int rnd);
+ void DoSunGlare(void);
+
+ bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1 && GetStatus() != STATUS_WRECKED; }
+ CVehicleModelInfo* GetModelInfo() { return (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); }
+ bool IsTaxi(void) { return GetModelIndex() == MI_TAXI || GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_ZEBRA || GetModelIndex() == MI_KAUFMAN; }
+ bool IsLimo(void) { return GetModelIndex() == MI_STRETCH || GetModelIndex() == MI_LOVEFIST; }
+ bool IsRealHeli(void) { return !!(pHandling->Flags & HANDLING_IS_HELI); }
+ bool IsRealPlane(void) { return !!(pHandling->Flags & HANDLING_IS_PLANE); }
+
+ static bool bWheelsOnlyCheat;
+ static bool bAllDodosCheat;
+ static bool bCheat3;
+ static bool bCheat4;
+ static bool bCheat5;
+ static bool bCheat8;
+ static bool bCheat9;
+ static bool bCheat10;
+ static bool bHoverCheat;
+ static bool bAllTaxisHaveNitro;
+ static bool m_bDisableMouseSteering;
+ static bool bDisableRemoteDetonation;
+ static bool bDisableRemoteDetonationOnContact;
+#ifndef MASTER
+ static bool m_bDisplayHandlingInfo;
+#endif
+};
+
+void DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle);
+bool IsVehiclePointerValid(CVehicle* pVehicle);
+
+// Names of functions below are made up by us.
+
+// Used in III and VC.
+inline int8 GetCarDoorFlag(int32 carnode) {
+ switch (carnode) {
+ case CAR_DOOR_LF:
+ return CAR_DOOR_FLAG_LF;
+ case CAR_DOOR_LR:
+ return CAR_DOOR_FLAG_LR;
+ case CAR_DOOR_RF:
+ return CAR_DOOR_FLAG_RF;
+ case CAR_DOOR_RR:
+ return CAR_DOOR_FLAG_RR;
+ default:
+ return CAR_DOOR_FLAG_UNKNOWN;
+ }
+}
+
+// VC. Accounts the case numMaxPassengers == 0, only for m_nGettingInFlags.
+inline int8 GetEnterCarDoorFlag(int32 carnode, uint8 numMaxPassengers) {
+ switch (carnode) {
+ case CAR_DOOR_RF:
+ return CAR_DOOR_FLAG_RF;
+ case CAR_DOOR_RR:
+ return CAR_DOOR_FLAG_RR;
+ case CAR_DOOR_LF:
+ if (numMaxPassengers != 0)
+ return CAR_DOOR_FLAG_LF;
+ else
+ return CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR;
+ case CAR_DOOR_LR:
+ if (numMaxPassengers != 0)
+ return CAR_DOOR_FLAG_LR;
+ else
+ return CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR;
+ default:
+ return CAR_DOOR_FLAG_UNKNOWN;
+ }
+}