#include "common.h" #include "patcher.h" #include "General.h" #include "Pad.h" #include "ModelIndices.h" #include "VisibilityPlugins.h" #include "DMAudio.h" #include "Camera.h" #include "Darkel.h" #include "Fire.h" #include "Explosion.h" #include "World.h" #include "SurfaceTable.h" #include "HandlingMgr.h" #include "CarCtrl.h" #include "PathFind.h" #include "Ped.h" #include "PlayerPed.h" #include "Object.h" #include "Automobile.h" RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data); bool &CAutomobile::m_sAllTaxiLights = *(bool*)0x95CD21; WRAPPER CAutomobile* CAutomobile::ctor(int, uint8) { EAXJMP(0x52C6B0); } CAutomobile::CAutomobile(int mi, uint8 CreatedBy) { ctor(mi, CreatedBy); } void CAutomobile::SetModelIndex(uint32 id) { CVehicle::SetModelIndex(id); SetupModelNodes(); } WRAPPER void CAutomobile::ProcessControl(void) { EAXJMP(0x531470); } void CAutomobile::Teleport(CVector pos) { CWorld::Remove(this); GetPosition() = 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); } WRAPPER void CAutomobile::PreRender(void) { EAXJMP(0x535B40); } WRAPPER void CAutomobile::Render(void) { EAXJMP(0x539EA0); } int32 CAutomobile::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) { int i; CColModel *colModel; if(m_status != STATUS_SIMPLE) bVehicleColProcessed = true; if(m_veh_flagC80) 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]; 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(field_EF || m_phy_flagA80 || GetModelIndex() == MI_DODO && (ent->m_status == STATUS_PHYSICS || ent->m_status == STATUS_SIMPLE)){ // don't do line collision for(i = 0; i < 4; i++) m_aSuspensionSpringRatio[i] = prevRatios[i]; }else{ 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(); if(phys->GetModelIndex() == MI_BODYCAST && m_status == STATUS_PLAYER){ // damage body cast float speed = m_vecMoveSpeed.MagnitudeSqr(); if(speed > 0.1f){ CObject::nBodyCastHealth -= 0.1f*m_fMass*speed; DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_BODYCAST_HIT, 0.0f); } // move body cast if(phys->bIsStatic){ phys->bIsStatic = false; phys->m_nStaticFrames = 0; phys->ApplyMoveForce(m_vecMoveSpeed / speed); phys->AddToMovingList(); } } } m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB; if(ent->IsBuilding()) m_pCurGroundEntity = ent; } } 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()) bIsHandbrakeOn = true; else bIsHandbrakeOn = !!CPad::GetPad(pad)->GetHandBrake(); // Steer left/right if(CCamera::m_bUseMouse3rdPerson && !CVehicle::m_bDisableMouseSteering){ if(CPad::GetPad(pad)->GetMouseX() != 0.0f){ m_fSteerRatio += fMouseSteerSens*CPad::GetPad(pad)->GetMouseX(); nLastControlInput = 2; if(Abs(m_fSteerRatio) < fMouseCentreRange) m_fSteerRatio *= Pow(fMouseCentreMult, CTimer::GetTimeStep()); }else if(CPad::GetPad(pad)->GetSteeringLeftRight() || nLastControlInput != 2){ // mouse hasn't move, steer with pad like below m_fSteerRatio += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerRatio)* 0.2f*CTimer::GetTimeStep(); nLastControlInput = 0; } }else{ m_fSteerRatio += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerRatio)* 0.2f*CTimer::GetTimeStep(); nLastControlInput = 0; } m_fSteerRatio = clamp(m_fSteerRatio, -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 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_fSteerRatio < 0.0f) fValue = -sq(m_fSteerRatio); else fValue = sq(m_fSteerRatio); m_fSteerAngle = DEGTORAD(m_handling->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 if(CPad::GetPad(pad)->DisablePlayerControls){ 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::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_CAR_QJACK: case ANIM_CAR_OPEN_LHS: case ANIM_CAR_OPEN_RHS: ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f); break; case ANIM_CAR_CLOSEDOOR_LHS: case ANIM_CAR_CLOSEDOOR_LOW_LHS: case ANIM_CAR_CLOSEDOOR_RHS: case ANIM_CAR_CLOSEDOOR_LOW_RHS: ProcessDoorCloseAnimation(this, component, door, time, 0.2f, 0.63f); break; case ANIM_CAR_ROLLDOOR: case ANIM_CAR_ROLLDOOR_LOW: ProcessDoorOpenCloseAnimation(this, component, door, time, 0.1f, 0.6f, 0.95f); break; break; case ANIM_CAR_GETOUT_LHS: case ANIM_CAR_GETOUT_LOW_LHS: case ANIM_CAR_GETOUT_RHS: case ANIM_CAR_GETOUT_LOW_RHS: ProcessDoorOpenAnimation(this, component, door, time, 0.06f, 0.43f); break; case ANIM_CAR_CLOSE_LHS: case ANIM_CAR_CLOSE_RHS: ProcessDoorCloseAnimation(this, component, door, time, 0.1f, 0.23f); break; case ANIM_CAR_PULLOUT_RHS: case ANIM_CAR_PULLOUT_LOW_RHS: OpenDoor(component, door, 1.0f); case ANIM_COACH_OPEN_L: case ANIM_COACH_OPEN_R: ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f); break; case ANIM_COACH_OUT_L: ProcessDoorOpenAnimation(this, component, door, time, 0.0f, 0.3f); break; case ANIM_VAN_OPEN_L: case ANIM_VAN_OPEN: ProcessDoorOpenAnimation(this, component, door, time, 0.37f, 0.55f); break; case ANIM_VAN_CLOSE_L: case ANIM_VAN_CLOSE: ProcessDoorCloseAnimation(this, component, door, time, 0.5f, 0.8f); break; case ANIM_VAN_GETOUT_L: case ANIM_VAN_GETOUT: ProcessDoorOpenAnimation(this, component, door, time, 0.5f, 0.6f); break; case NUM_ANIMS: OpenDoor(component, door, time); break; } } bool CAutomobile::IsDoorReady(eDoors door) { if(Doors[door].IsClosed() || IsDoorMissing(door)) return true; int doorflag = 0; // TODO: enum? switch(door){ case DOOR_FRONT_LEFT: doorflag = 1; break; case DOOR_FRONT_RIGHT: doorflag = 4; break; case DOOR_REAR_LEFT: doorflag = 2; break; case DOOR_REAR_RIGHT: doorflag = 8; 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; } 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) { int i; RpAtomic *atomic; if(!bCanBeDamaged) return; // explosion pushes vehicle up m_vecMoveSpeed.z += 0.13f; m_status = 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); RwFrameForAllObjects(m_aCarNodes[CAR_WHEEL_LF], GetCurrentAtomicObjectCB, &atomic); if(atomic) RpAtomicSetFlags(atomic, 0); } m_fHealth = 0.0f; m_nBombTimer = 0; m_auto_flagA7 = 0; TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z); // kill driver and passengers if(pDriver){ CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION); if(pDriver->GetPedState() == PED_DRIVING){ pDriver->SetDead(); if(!pDriver->IsPlayer()) pDriver->FlagToDestroyWhenNextProcessed(); }else pDriver->SetDie(ANIM_KO_SHOT_FRONT1, 4.0f, 0.0f); } 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(ANIM_KO_SHOT_FRONT1, 4.0f, 0.0f); } } 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, 1); // TODO CDarkel::RegisterCarBlownUpByPlayer(this); if(GetModelIndex() == MI_RCBANDIT) CExplosion::AddExplosion(this, culprit, EXPLOSION_4, GetPosition(), 0); // TODO else CExplosion::AddExplosion(this, culprit, EXPLOSION_3, GetPosition(), 0); // TODO } bool CAutomobile::SetUpWheelColModel(CColModel *colModel) { CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); CColModel *vehColModel = mi->GetColModel(); 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, mat.GetPosition(), SURFACE_TIRE, CAR_PIECE_WHEEL_LF); mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); colModel->spheres[1].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_TIRE, CAR_PIECE_WHEEL_LR); mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); colModel->spheres[2].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_TIRE, CAR_PIECE_WHEEL_RF); mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); colModel->spheres[3].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_TIRE, 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, mat.GetPosition(), SURFACE_TIRE, CAR_PIECE_WHEEL_RF); mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); colModel->spheres[5].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_TIRE, CAR_PIECE_WHEEL_RR); colModel->numSpheres = 6; }else colModel->numSpheres = 4; return true; } // this probably isn't used in III yet void CAutomobile::BurstTyre(uint8 wheel) { switch(wheel){ case CAR_PIECE_WHEEL_LF: wheel = VEHWHEEL_FRONT_LEFT; break; case CAR_PIECE_WHEEL_LR: wheel = VEHWHEEL_REAR_LEFT; break; case CAR_PIECE_WHEEL_RF: wheel = VEHWHEEL_FRONT_RIGHT; break; case CAR_PIECE_WHEEL_RR: wheel = VEHWHEEL_REAR_RIGHT; break; } int status = Damage.GetWheelStatus(wheel); if(status == WHEEL_STATUS_OK){ Damage.SetWheelStatus(wheel, WHEEL_STATUS_BURST); if(m_status == STATUS_SIMPLE){ m_status = STATUS_PHYSICS; CCarCtrl::SwitchVehicleToRealPhysics(this); } ApplyMoveForce(GetRight() * CGeneral::GetRandomNumberInRange(-0.3f, 0.3f)); ApplyTurnForce(GetRight() * CGeneral::GetRandomNumberInRange(-0.3f, 0.3f), GetForward()); } } WRAPPER bool CAutomobile::IsRoomForPedToLeaveCar(uint32, CVector *) { EAXJMP(0x53C5B0); } float CAutomobile::GetHeightAboveRoad(void) { return m_fHeightAboveRoad; } void CAutomobile::PlayCarHorn(void) { int r; if(m_nCarHornTimer != 0) return; r = CGeneral::GetRandomNumber() & 7; if(r < 2){ m_nCarHornTimer = 45; }else if(r < 4){ if(pDriver) pDriver->Say(SOUND_PED_CAR_COLLISION); m_nCarHornTimer = 45; }else{ if(pDriver) pDriver->Say(SOUND_PED_CAR_COLLISION); } } void CAutomobile::PlayHornIfNecessary(void) { if(m_autoPilot.m_flag2 || m_autoPilot.m_flag1) if(!HasCarStoppedBecauseOfLight()) PlayCarHorn(); } void CAutomobile::ResetSuspension(void) { int i; for(i = 0; i < 4; i++){ m_aSuspensionSpringRatio[i] = 1.0f; m_aWheelSkidThing[i] = 0.0f; m_aWheelRotation[i] = 0.0f; m_aWheelState[i] = 0; // TODO: enum? } } 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 += m_handling->fSuspensionUpperLimit; colModel->lines[i].p0 = posn; // lowermost wheel position posn.z += m_handling->fSuspensionLowerLimit - m_handling->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] = m_handling->fSuspensionUpperLimit - m_handling->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 = -(colModel->lines[0].p0.z + (colModel->lines[0].p1.z - colModel->lines[0].p0.z)* (1.0f - 1.0f/(8.0f*m_handling->fSuspensionForceLevel))); 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()->m_nAlarmState != -1) // 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) 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) ((CVehicle*)m_aCollisionRecords[i])->BlowUpCar(this); } bool CAutomobile::HasCarStoppedBecauseOfLight(void) { int i; if(m_status != STATUS_SIMPLE && m_status != STATUS_PHYSICS) return false; if(m_autoPilot.m_nCurrentRouteNode && m_autoPilot.m_nNextRouteNode){ CPathNode *curnode = &ThePaths.m_pathNodes[m_autoPilot.m_nCurrentRouteNode]; for(i = 0; i < curnode->numLinks; i++) if(ThePaths.m_connections[curnode->firstLink + i] == m_autoPilot.m_nNextRouteNode) break; if(i < curnode->numLinks && ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) // TODO return true; } if(m_autoPilot.m_nCurrentRouteNode && m_autoPilot.m_nPrevRouteNode){ CPathNode *curnode = &ThePaths.m_pathNodes[m_autoPilot.m_nCurrentRouteNode]; for(i = 0; i < curnode->numLinks; i++) if(ThePaths.m_connections[curnode->firstLink + i] == m_autoPilot.m_nPrevRouteNode) break; if(i < curnode->numLinks && ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) // TODO return true; } return false; } 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 & 1) == 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 & 4) == 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 & 1) == 0) OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f); if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & 4) == 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; 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(); } void CAutomobile::Fix(void) { int component; Damage.ResetDamageStatus(); if(m_handling->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(); } } } 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; } CColPoint aTempPedColPts[32]; // this name doesn't make any sense 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); 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); // 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->bIsStatic = true; 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).MagnitudeSqr(); obj->GetPosition() += 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(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(), this->GetMatrix(), *this->GetColModel(), aTempPedColPts, 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){ // BUG? why not COMPGROUP_BONNET? obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_DOOR); // 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){ // show damaged part SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); }else if(status == PANEL_STATUS_MISSING){ if(!noFlyingComponents) SpawnFlyingComponent(component, COMPGROUP_PANEL); // 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())->GetName()); 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())->GetName()); return; } if(door == DOOR_BOOT && status == DOOR_STATUS_SWINGING && m_handling->Flags & HANDLING_NOSWING_BOOT){ Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_MISSING); status = DOOR_STATUS_MISSING; } if(status == DOOR_STATUS_SMASHED){ // show damaged part SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); }else if(status == DOOR_STATUS_SWINGING){ // turn off angle cull for swinging doors RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); }else if(status == 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); } } 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((RpClump*)m_rwObject, m_aCarNodes); } void CAutomobile::SetTaxiLight(bool light) { bTaxiLight = light; } bool CAutomobile::GetAllWheelsOffGround(void) { return m_nWheelsOnGround == 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; } class CAutomobile_ : public CAutomobile { public: void dtor() { CAutomobile::~CAutomobile(); } void SetModelIndex_(uint32 id) { CAutomobile::SetModelIndex(id); } void ProcessControl_(void) { CAutomobile::ProcessControl(); } void Teleport_(CVector v) { CAutomobile::Teleport(v); } void PreRender_(void) { CAutomobile::PreRender(); } void Render_(void) { CAutomobile::Render(); } int32 ProcessEntityCollision_(CEntity *ent, CColPoint *colpoints){ return CAutomobile::ProcessEntityCollision(ent, colpoints); } void ProcessControlInputs_(uint8 pad) { CAutomobile::ProcessControlInputs(pad); } void GetComponentWorldPosition_(int32 component, CVector &pos) { CAutomobile::GetComponentWorldPosition(component, pos); } bool IsComponentPresent_(int32 component) { return CAutomobile::IsComponentPresent(component); } void SetComponentRotation_(int32 component, CVector rotation) { CAutomobile::SetComponentRotation(component, rotation); } void OpenDoor_(int32 component, eDoors door, float ratio) { CAutomobile::OpenDoor(component, door, ratio); } void ProcessOpenDoor_(uint32 component, uint32 anim, float time) { CAutomobile::ProcessOpenDoor(component, anim, time); } bool IsDoorReady_(eDoors door) { return CAutomobile::IsDoorReady(door); } bool IsDoorFullyOpen_(eDoors door) { return CAutomobile::IsDoorFullyOpen(door); } bool IsDoorClosed_(eDoors door) { return CAutomobile::IsDoorClosed(door); } bool IsDoorMissing_(eDoors door) { return CAutomobile::IsDoorMissing(door); } void RemoveRefsToVehicle_(CEntity *ent) { CAutomobile::RemoveRefsToVehicle(ent); } void BlowUpCar_(CEntity *ent) { CAutomobile::BlowUpCar(ent); } bool SetUpWheelColModel_(CColModel *colModel) { return CAutomobile::SetUpWheelColModel(colModel); } void BurstTyre_(uint8 tyre) { CAutomobile::BurstTyre(tyre); } bool IsRoomForPedToLeaveCar_(uint32 door, CVector *pos) { return CAutomobile::IsRoomForPedToLeaveCar(door, pos); } float GetHeightAboveRoad_(void) { return CAutomobile::GetHeightAboveRoad(); } void PlayCarHorn_(void) { CAutomobile::PlayCarHorn(); } }; STARTPATCHES InjectHook(0x52D170, &CAutomobile_::dtor, PATCH_JUMP); InjectHook(0x52D190, &CAutomobile_::SetModelIndex_, PATCH_JUMP); InjectHook(0x535180, &CAutomobile_::Teleport_, PATCH_JUMP); InjectHook(0x53B270, &CAutomobile_::ProcessEntityCollision_, PATCH_JUMP); InjectHook(0x53B660, &CAutomobile_::ProcessControlInputs_, PATCH_JUMP); InjectHook(0x52E5F0, &CAutomobile_::GetComponentWorldPosition_, PATCH_JUMP); InjectHook(0x52E660, &CAutomobile_::IsComponentPresent_, PATCH_JUMP); InjectHook(0x52E680, &CAutomobile_::SetComponentRotation_, PATCH_JUMP); InjectHook(0x52E750, &CAutomobile_::OpenDoor_, PATCH_JUMP); InjectHook(0x52EF10, &CAutomobile_::IsDoorReady_, PATCH_JUMP); InjectHook(0x52EF90, &CAutomobile_::IsDoorFullyOpen_, PATCH_JUMP); InjectHook(0x52EFD0, &CAutomobile_::IsDoorClosed_, PATCH_JUMP); InjectHook(0x52F000, &CAutomobile_::IsDoorMissing_, PATCH_JUMP); InjectHook(0x53BF40, &CAutomobile_::RemoveRefsToVehicle_, PATCH_JUMP); InjectHook(0x53BC60, &CAutomobile_::BlowUpCar_, PATCH_JUMP); InjectHook(0x53BF70, &CAutomobile_::SetUpWheelColModel_, PATCH_JUMP); InjectHook(0x53C0E0, &CAutomobile_::BurstTyre_, PATCH_JUMP); InjectHook(0x437690, &CAutomobile_::GetHeightAboveRoad_, PATCH_JUMP); InjectHook(0x53C450, &CAutomobile_::PlayCarHorn_, PATCH_JUMP); InjectHook(0x5353A0, &CAutomobile::ResetSuspension, PATCH_JUMP); InjectHook(0x52D210, &CAutomobile::SetupSuspensionLines, PATCH_JUMP); InjectHook(0x53E000, &CAutomobile::BlowUpCarsInPath, PATCH_JUMP); InjectHook(0x42E220, &CAutomobile::HasCarStoppedBecauseOfLight, PATCH_JUMP); InjectHook(0x53D320, &CAutomobile::SetBusDoorTimer, PATCH_JUMP); InjectHook(0x53D370, &CAutomobile::ProcessAutoBusDoors, PATCH_JUMP); InjectHook(0x535250, &CAutomobile::ProcessSwingingDoor, PATCH_JUMP); InjectHook(0x53C240, &CAutomobile::Fix, PATCH_JUMP); InjectHook(0x53C310, &CAutomobile::SetupDamageAfterLoad, PATCH_JUMP); InjectHook(0x530300, &CAutomobile::SpawnFlyingComponent, PATCH_JUMP); InjectHook(0x535320, &CAutomobile::RemoveBonnetInPedCollision, PATCH_JUMP); InjectHook(0x5301A0, &CAutomobile::SetPanelDamage, PATCH_JUMP); InjectHook(0x530120, &CAutomobile::SetBumperDamage, PATCH_JUMP); InjectHook(0x530200, &CAutomobile::SetDoorDamage, PATCH_JUMP); InjectHook(0x5300E0, &CAutomobile::SetComponentVisibility, PATCH_JUMP); InjectHook(0x52D1B0, &CAutomobile::SetupModelNodes, PATCH_JUMP); InjectHook(0x53C420, &CAutomobile::SetTaxiLight, PATCH_JUMP); InjectHook(0x53BC40, &CAutomobile::GetAllWheelsOffGround, PATCH_JUMP); InjectHook(0x5308C0, &CAutomobile::ReduceHornCounter, PATCH_JUMP); InjectHook(0x53C440, &CAutomobile::SetAllTaxiLights, PATCH_JUMP); ENDPATCHES