diff --git a/src/Timer.h b/src/Timer.h index a96574ce..e92fe9e1 100644 --- a/src/Timer.h +++ b/src/Timer.h @@ -14,6 +14,7 @@ class CTimer static bool &m_CodePause; public: static float GetTimeStep(void) { return ms_fTimeStep; } + static void SetTimeStep(float ts) { ms_fTimeStep = ts; } static uint32 GetFrameCounter(void) { return m_FrameCounter; } static uint32 GetTimeInMilliseconds(void) { return m_snTimeInMilliseconds; } }; diff --git a/src/audio/DMAudio.cpp b/src/audio/DMAudio.cpp index cc8d4e8b..1ab5a52f 100644 --- a/src/audio/DMAudio.cpp +++ b/src/audio/DMAudio.cpp @@ -2,4 +2,6 @@ #include "patcher.h" #include "DMAudio.h" -WRAPPER void cDMAudio::ReportCollision(CEntity *A, CEntity *B, uint8 surfA, uint8 surfB, float impulse, float speed) { EAXJMP(0x161684); } +cDMAudio &DMAudio = *(cDMAudio*)0x95CDBE; + +WRAPPER void cDMAudio::ReportCollision(CEntity *A, CEntity *B, uint8 surfA, uint8 surfB, float impulse, float speed) { EAXJMP(0x57CBE0); } diff --git a/src/audio/DMAudio.h b/src/audio/DMAudio.h index 8d49be27..140c6493 100644 --- a/src/audio/DMAudio.h +++ b/src/audio/DMAudio.h @@ -5,5 +5,6 @@ class CEntity; class cDMAudio { public: - static void ReportCollision(CEntity *A, CEntity *B, uint8 surfA, uint8 surfB, float impulse, float speed); + void ReportCollision(CEntity *A, CEntity *B, uint8 surfA, uint8 surfB, float impulse, float speed); }; +extern cDMAudio &DMAudio; diff --git a/src/entities/Automobile.h b/src/entities/Automobile.h new file mode 100644 index 00000000..06c0e742 --- /dev/null +++ b/src/entities/Automobile.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Vehicle.h" + +class CAutomobile : public CVehicle +{ +public: + // 0x228 + uint8 stuff1[484]; + float m_afWheelSuspDist[4]; + uint8 stuff2[300]; +}; +static_assert(sizeof(CAutomobile) == 0x5A8, "CAutomobile: error"); +static_assert(offsetof(CAutomobile, m_afWheelSuspDist) == 0x46C, "CAutomobile: error"); diff --git a/src/entities/Object.h b/src/entities/Object.h index 5d648e07..e0b8dd91 100644 --- a/src/entities/Object.h +++ b/src/entities/Object.h @@ -41,7 +41,7 @@ public: int8 field_186; int8 field_187; CEntity *m_pCurSurface; - CVehicle *m_pCollidingVehicle; + CEntity *m_pCollidingEntity; int8 m_colour1, m_colour2; static void *operator new(size_t); diff --git a/src/entities/Ped.cpp b/src/entities/Ped.cpp new file mode 100644 index 00000000..2d97545e --- /dev/null +++ b/src/entities/Ped.cpp @@ -0,0 +1,9 @@ +#include "common.h" +#include "patcher.h" +#include "Ped.h" +#include "Pools.h" + +//void *CPed::operator new(size_t sz) { return CPools::GetPedPool()->New(); } +//void CPed::operator delete(void *p, size_t sz) { CPools::GetPedPool()->Delete((CPed*)p); } + +WRAPPER void CPed::KillPedWithCar(CVehicle *veh, float impulse) { EAXJMP(0x4EC430); } diff --git a/src/entities/Ped.h b/src/entities/Ped.h index fe448a94..4236340e 100644 --- a/src/entities/Ped.h +++ b/src/entities/Ped.h @@ -13,7 +13,81 @@ class CPed : public CPhysical { public: // 0x128 - uint8 stuff1[252]; + CStoredCollPoly m_collPoly; + float m_fCollisionSpeed; + uint8 m_ped_flagA1 : 1; + uint8 m_ped_flagA2 : 1; + uint8 m_ped_flagA4 : 1; + uint8 m_ped_flagA8 : 1; + uint8 m_ped_flagA10 : 1; + uint8 m_ped_flagA20 : 1; + uint8 m_ped_flagA40 : 1; + uint8 m_ped_flagA80 : 1; + uint8 m_ped_flagB1 : 1; + uint8 m_ped_flagB2 : 1; + uint8 m_ped_flagB4 : 1; + uint8 m_ped_flagB8 : 1; + uint8 m_ped_flagB10 : 1; + uint8 m_ped_flagB20 : 1; + uint8 m_ped_flagB40 : 1; + uint8 m_ped_flagB80 : 1; + uint8 m_ped_flagC1 : 1; + uint8 m_ped_flagC2 : 1; + uint8 m_ped_flagC4 : 1; + uint8 m_ped_flagC8 : 1; + uint8 m_ped_flagC10 : 1; + uint8 m_ped_flagC20 : 1; + uint8 m_ped_flagC40 : 1; + uint8 m_ped_flagC80 : 1; + uint8 m_ped_flagD1 : 1; + uint8 m_ped_flagD2 : 1; + uint8 m_ped_flagD4 : 1; + uint8 m_ped_flagD8 : 1; + uint8 m_ped_flagD10 : 1; + uint8 m_ped_flagD20 : 1; + uint8 m_ped_flagD40 : 1; + uint8 m_ped_flagD80 : 1; + uint8 m_ped_flagE1 : 1; + uint8 m_ped_flagE2 : 1; + uint8 m_ped_flagE4 : 1; + uint8 m_ped_flagE8 : 1; + uint8 m_ped_flagE10 : 1; + uint8 m_ped_flagE20 : 1; + uint8 m_ped_flagE40 : 1; + uint8 m_ped_flagE80 : 1; + uint8 m_ped_flagF1 : 1; + uint8 m_ped_flagF2 : 1; + uint8 m_ped_flagF4 : 1; + uint8 m_ped_flagF8 : 1; + uint8 m_ped_flagF10 : 1; + uint8 m_ped_flagF20 : 1; + uint8 m_ped_flagF40 : 1; + uint8 m_ped_flagF80 : 1; + uint8 m_ped_flagG1 : 1; + uint8 m_ped_flagG2 : 1; + uint8 m_ped_flagG4 : 1; + uint8 m_ped_flagG8 : 1; + uint8 m_ped_flagG10 : 1; + uint8 m_ped_flagG20 : 1; + uint8 m_ped_flagG40 : 1; + uint8 m_ped_flagG80 : 1; + uint8 m_ped_flagH1 : 1; + uint8 m_ped_flagH2 : 1; + uint8 m_ped_flagH4 : 1; + uint8 m_ped_flagH8 : 1; + uint8 m_ped_flagH10 : 1; + uint8 m_ped_flagH20 : 1; + uint8 m_ped_flagH40 : 1; + uint8 m_ped_flagH80 : 1; + uint8 m_ped_flagI1 : 1; + uint8 m_ped_flagI2 : 1; + uint8 m_ped_flagI4 : 1; + uint8 m_ped_flagI8 : 1; + uint8 m_ped_flagI10 : 1; + uint8 m_ped_flagI20 : 1; + uint8 m_ped_flagI40 : 1; + uint8 m_ped_flagI80 : 1; + uint8 stuff1[199]; int32 m_nPedState; uint8 stuff2[196]; CEntity *m_pCurrentPhysSurface; @@ -26,14 +100,18 @@ public: int32 m_nPedType; uint8 stuff5[28]; - CVehicle *m_pCollidingVehicle; + CEntity *m_pCollidingEntity; uint8 stuff6[496]; +// static void *operator new(size_t); +// static void operator delete(void*, size_t); + bool IsPlayer(void) { return m_nPedType == 0 || m_nPedType== 1 || m_nPedType == 2 || m_nPedType == 3; } + void KillPedWithCar(CVehicle *veh, float impulse); }; static_assert(offsetof(CPed, m_nPedState) == 0x224, "CPed: error"); static_assert(offsetof(CPed, m_pCurSurface) == 0x2FC, "CPed: error"); static_assert(offsetof(CPed, m_pMyVehicle) == 0x310, "CPed: error"); static_assert(offsetof(CPed, m_nPedType) == 0x32C, "CPed: error"); -static_assert(offsetof(CPed, m_pCollidingVehicle) == 0x34C, "CPed: error"); +static_assert(offsetof(CPed, m_pCollidingEntity) == 0x34C, "CPed: error"); static_assert(sizeof(CPed) == 0x540, "CPed: error"); diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp index 947ac47c..dada5072 100644 --- a/src/entities/Physical.cpp +++ b/src/entities/Physical.cpp @@ -12,6 +12,7 @@ #include "SurfaceTable.h" #include "CarCtrl.h" #include "DMAudio.h" +#include "Automobile.h" #include "Physical.h" void @@ -176,11 +177,11 @@ CPhysical::RemoveFromMovingList(void) void CPhysical::SetDamagedPieceRecord(uint16 piece, float impulse, CEntity *entity, CVector dir) { - m_nCollisionPieceType = piece; - m_fCollisionImpulse = impulse; - m_pCollidingEntity = entity; - entity->RegisterReference(&m_pCollidingEntity); - m_vecCollisionDirection = dir; + m_nDamagePieceType = piece; + m_fDamageImpulse = impulse; + m_pDamageEntity = entity; + entity->RegisterReference(&m_pDamageEntity); + m_vecDamageNormal = dir; } void @@ -282,9 +283,9 @@ CPhysical::ProcessControl(void) m_nCollisionRecords = 0; bHasCollided = false; - m_nCollisionPieceType = 0; - m_fCollisionImpulse = 0.0f; - m_pCollidingEntity = nil; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; if(!bIsStuck){ if(IsObject() || @@ -927,18 +928,6 @@ CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint) return false; } - -// ProcessCollision calls -// CheckCollision -// CheckCollision_SimpleCar -// CheckCollision calls -// ProcessCollisionSectorList -// CheckCollision_SimpleCar -// ProcessCollisionSectorList_SimpleCar -// ProcessShift calls -// ProcessCollisionSectorList -// ProcessShiftSectorList - bool CPhysical::ProcessShiftSectorList(CPtrList *lists) { @@ -996,21 +985,22 @@ CPhysical::ProcessShiftSectorList(CPtrList *lists) B->GetUp().z < 0.66f && IsTrafficLight(B->GetModelIndex())) skipShift = true; +// TODO: maybe flip some ifs here else if(A->IsObject() && B->IsVehicle()){ CObject *Aobj = (CObject*)A; if(Aobj->ObjectCreatedBy != TEMP_OBJECT && !Aobj->bHasBeenDamaged && Aobj->bIsStatic){ - if(Aobj->m_pCollidingVehicle == B) - Aobj->m_pCollidingVehicle = nil; - }else if(Aobj->m_pCollidingVehicle != B){ + if(Aobj->m_pCollidingEntity == B) + Aobj->m_pCollidingEntity = nil; + }else if(Aobj->m_pCollidingEntity != B){ CMatrix inv; CVector size = CModelInfo::GetModelInfo(A->GetModelIndex())->GetColModel()->boundingBox.GetSize(); size = A->GetMatrix() * size; if(size.z < B->GetPosition().z || (Invert(B->GetMatrix(), inv) * size).z < 0.0f){ skipShift = true; - Aobj->m_pCollidingVehicle = (CVehicle*)B; + Aobj->m_pCollidingEntity = B; } } }else if(B->IsObject() && A->IsVehicle()){ @@ -1018,24 +1008,22 @@ CPhysical::ProcessShiftSectorList(CPtrList *lists) if(Bobj->ObjectCreatedBy != TEMP_OBJECT && !Bobj->bHasBeenDamaged && Bobj->bIsStatic){ - if(Bobj->m_pCollidingVehicle == A) - Bobj->m_pCollidingVehicle = nil; - }else if(Bobj->m_pCollidingVehicle != A){ + if(Bobj->m_pCollidingEntity == A) + Bobj->m_pCollidingEntity = nil; + }else if(Bobj->m_pCollidingEntity != A){ CMatrix inv; CVector size = CModelInfo::GetModelInfo(B->GetModelIndex())->GetColModel()->boundingBox.GetSize(); size = B->GetMatrix() * size; if(size.z < A->GetPosition().z || - (Invert(A->GetMatrix(), inv) * size).z < 0.0f){ + (Invert(A->GetMatrix(), inv) * size).z < 0.0f) skipShift = true; - Bobj->m_pCollidingVehicle = (CVehicle*)A; - } } }else if(IsBodyPart(A->GetModelIndex()) && B->IsPed()) skipShift = true; else if(A->IsPed() && IsBodyPart(B->GetModelIndex())) skipShift = true; - else if(A->IsPed() && ((CPed*)A)->m_pCollidingVehicle == B || - B->IsPed() && ((CPed*)B)->m_pCollidingVehicle == A || + else if(A->IsPed() && ((CPed*)A)->m_pCollidingEntity == B || + B->IsPed() && ((CPed*)B)->m_pCollidingEntity == A || A->GetModelIndex() == MI_RCBANDIT && B->IsVehicle() || B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle())) skipShift = true; @@ -1154,16 +1142,16 @@ collision: if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) continue; - if(impulseA > A->m_fCollisionImpulse) + if(impulseA > A->m_fDamageImpulse) A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); - if(impulseB > B->m_fCollisionImpulse) - A->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); - cDMAudio::ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); } }else if(A->bHasContacted){ CVector savedMoveFriction = A->m_vecMoveFriction; @@ -1176,16 +1164,16 @@ collision: if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) continue; - if(impulseA > A->m_fCollisionImpulse) + if(impulseA > A->m_fDamageImpulse) A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); - if(impulseB > B->m_fCollisionImpulse) - A->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); - cDMAudio::ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ A->bHasContacted = true; @@ -1209,16 +1197,16 @@ collision: if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) continue; - if(impulseA > A->m_fCollisionImpulse) + if(impulseA > A->m_fDamageImpulse) A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); - if(impulseB > B->m_fCollisionImpulse) - A->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); - cDMAudio::ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ A->bHasContacted = true; @@ -1236,16 +1224,16 @@ collision: if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) continue; - if(impulseA > A->m_fCollisionImpulse) + if(impulseA > A->m_fDamageImpulse) A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); - if(impulseB > B->m_fCollisionImpulse) - A->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); - cDMAudio::ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ A->bHasContacted = true; @@ -1263,12 +1251,602 @@ collision: return true; } +bool +CPhysical::ProcessCollisionSectorList(CPtrList *lists) +{ + static CColPoint aColPoints[32]; + float radius; + CVector center; + CPtrList *list; + CPhysical *A, *B; + CObject *Aobj, *Bobj; + CPed *Aped, *Bped; + int numCollisions; + int numResponses; + int i, j; + bool skipCollision, altcollision; + float impulseA = -1.0f; + float impulseB = -1.0f; + + A = (CPhysical*)this; + Aobj = (CObject*)A; + Aped = (CPed*)A; + + radius = A->GetBoundRadius(); + A->GetBoundCentre(center); + + for(j = 0; j <= ENTITYLIST_PEDS_OVERLAP; j++){ + list = &lists[j]; + + CPtrNode *listnode; + for(listnode = list->first; listnode; listnode = listnode->next){ + B = (CPhysical*)listnode->item; + Bobj = (CObject*)B; + Bped = (CPed*)B; + + bool isTouching = true; + if(B == A || + B->m_scanCode == CWorld::GetCurrentScanCode() || + !B->bUsesCollision || + !(isTouching = B->GetIsTouching(center, radius))){ + if(!isTouching){ + if(A->IsObject() && Aobj->m_pCollidingEntity == B) + Aobj->m_pCollidingEntity = nil; + else if(B->IsObject() && Bobj->m_pCollidingEntity == A) + Bobj->m_pCollidingEntity = nil; + else if(A->IsPed() && Aped->m_pCollidingEntity == B) + Aped->m_pCollidingEntity = nil; + else if(B->IsPed() && Bped->m_pCollidingEntity == A) + Bped->m_pCollidingEntity = nil; + } + continue; + } + + A->m_phy_flagA80 = false; + skipCollision = false; + altcollision = false; + + if(B->IsBuilding()) + skipCollision = false; + else if(IsTrafficLight(A->GetModelIndex()) && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f){ + skipCollision = true; + A->m_phy_flagA80 = true; + Aobj->m_pCollidingEntity = B; + }else if((A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f && + IsTrafficLight(B->GetModelIndex())){ + skipCollision = true; + A->m_phy_flagA80 = true; + Bobj->m_pCollidingEntity = A; + }else if(A->IsObject() && B->IsVehicle()){ + if(A->GetModelIndex() == MI_CAR_BUMPER || A->GetModelIndex() == MI_FILES) + skipCollision = true; + else if(Aobj->ObjectCreatedBy == TEMP_OBJECT || + Aobj->bHasBeenDamaged || + !Aobj->bIsStatic){ + if(Aobj->m_pCollidingEntity == B) + skipCollision = true; + else{ + CMatrix inv; + CVector size = CModelInfo::GetModelInfo(A->GetModelIndex())->GetColModel()->boundingBox.GetSize(); + size = A->GetMatrix() * size; + if(size.z < B->GetPosition().z || + (Invert(B->GetMatrix(), inv) * size).z < 0.0f){ + skipCollision = true; + Aobj->m_pCollidingEntity = B; + } + } + } + }else if(B->IsObject() && A->IsVehicle()){ + if(B->GetModelIndex() == MI_CAR_BUMPER || B->GetModelIndex() == MI_FILES) + skipCollision = true; + else if(Bobj->ObjectCreatedBy == TEMP_OBJECT || + Bobj->bHasBeenDamaged || + !Bobj->bIsStatic){ + if(Bobj->m_pCollidingEntity == A) + skipCollision = true; + else{ + CMatrix inv; + CVector size = CModelInfo::GetModelInfo(B->GetModelIndex())->GetColModel()->boundingBox.GetSize(); + size = B->GetMatrix() * size; + if(size.z < A->GetPosition().z || + (Invert(A->GetMatrix(), inv) * size).z < 0.0f){ + skipCollision = true; + } + } + } + }else if(IsBodyPart(A->GetModelIndex()) && B->IsPed()){ + skipCollision = true; + }else if(A->IsPed() && IsBodyPart(B->GetModelIndex())){ + skipCollision = true; + A->m_phy_flagA80 = true; + }else if(A->IsPed() && Aped->m_pCollidingEntity == B){ + skipCollision = true; + if(!Aped->m_ped_flagH1) + A->m_phy_flagA80 = true; + }else if(B->IsPed() && Bped->m_pCollidingEntity == A){ + skipCollision = true; + A->m_phy_flagA80 = true; + }else if(A->GetModelIndex() == MI_RCBANDIT && (B->IsPed() || B->IsVehicle()) || + B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle())){ + skipCollision = true; + A->m_phy_flagA80 = true; + }else if(A->IsPed() && B->IsObject() && Bobj->m_fUprootLimit > 0.0f) + altcollision = true; + + + if(!A->bUsesCollision || skipCollision){ + B->m_scanCode = CWorld::GetCurrentScanCode(); + A->ProcessEntityCollision(B, aColPoints); + }else if(B->IsBuilding() || B->bIsStuck || B->bInfiniteMass || altcollision){ + + // This is the case where B doesn't move + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions <= 0) + continue; + + CVector moveSpeed = { 0.0f, 0.0f, 0.0f }; + CVector turnSpeed = { 0.0f, 0.0f, 0.0f }; + numResponses = 0; + if(A->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float imp = impulseA; + if(A->IsVehicle() && A->GetUp().z < -0.6f && + fabs(A->m_vecMoveSpeed.x) < 0.05f && + fabs(A->m_vecMoveSpeed.y) < 0.05f) + imp *= 0.1f; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, imp, max(turnSpeedDiff, moveSpeedDiff)); + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float imp = impulseA; + if(A->IsVehicle() && A->GetUp().z < -0.6f && + fabs(A->m_vecMoveSpeed.x) < 0.05f && + fabs(A->m_vecMoveSpeed.y) < 0.05f) + imp *= 0.1f; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, imp, max(turnSpeedDiff, moveSpeedDiff)); + + float adhesion = CSurfaceTable::GetAdhesiveLimit(aColPoints[i]) / numCollisions; + + if(A->GetModelIndex() == MI_RCBANDIT) + adhesion *= 0.2f; + else if(IsBoatModel(A->GetModelIndex())){ + if(aColPoints[i].normal.z > 0.6f){ + if(CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_LOOSE) + adhesion *= 3.0f; + }else + adhesion = 0.0f; + }else if(A->IsVehicle()){ + if(A->m_status == STATUS_WRECKED) + adhesion *= 3.0f; + else if(A->GetUp().z > 0.3f) + adhesion = 0.0f; + else + adhesion *= max(5.0f, 0.03f*impulseA + 1.0f); + } + + if(A->ApplyFriction(adhesion, aColPoints[i])) + A->bHasContacted = true; + } + } + + if(numResponses){ + m_vecMoveSpeed += moveSpeed / numResponses; + m_vecTurnSpeed += turnSpeed / numResponses; + if(!CWorld::bNoMoreCollisionTorque && + A->m_status == STATUS_PLAYER && A->IsVehicle() && + fabs(A->m_vecMoveSpeed.x) > 0.2f && + fabs(A->m_vecMoveSpeed.y) > 0.2f){ + A->m_vecMoveFriction.x += moveSpeed.x * -0.3f / numCollisions; + A->m_vecMoveFriction.y += moveSpeed.y * -0.3f / numCollisions; + A->m_vecTurnFriction += turnSpeed * -0.3f / numCollisions; + } + return true; + } + }else{ + + // B can move + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions <= 0) + continue; + + float maxImpulseA = 0.0f; + float maxImpulseB = 0.0f; + if(A->bHasContacted && B->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + } + }else if(A->bHasContacted){ + CVector savedMoveFriction = A->m_vecMoveFriction; + CVector savedTurnFriction = A->m_vecTurnFriction; + A->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + A->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + A->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!A->bHasContacted){ + A->bHasContacted = true; + A->m_vecMoveFriction = savedMoveFriction; + A->m_vecTurnFriction = savedTurnFriction; + } + }else if(B->bHasContacted){ + CVector savedMoveFriction = B->m_vecMoveFriction; + CVector savedTurnFriction = B->m_vecTurnFriction; + B->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + B->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + B->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!B->bHasContacted){ + B->bHasContacted = true; + B->m_vecMoveFriction = savedMoveFriction; + B->m_vecTurnFriction = savedTurnFriction; + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + } + + if(B->IsPed() && A->IsVehicle() && + (!Bped->IsPlayer() || B->bHasHitWall && A->m_vecMoveSpeed.MagnitudeSqr() > 0.0025f)) + Bped->KillPedWithCar((CVehicle*)A, maxImpulseB); + else if(B->GetModelIndex() == MI_TRAIN && A->IsPed() && + (!Aped->IsPlayer() || A->bHasHitWall)) + Aped->KillPedWithCar((CVehicle*)B, maxImpulseA*2.0f); + else if(B->IsObject() && B->bUsesCollision && A->IsVehicle()){ + if(Bobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Bobj->ObjectDamage(maxImpulseB); + }else if(A->IsObject() && A->bUsesCollision && B->IsVehicle()){ + // BUG? not impulseA? + if(Aobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Aobj->ObjectDamage(maxImpulseB); + } + + if(B->m_status == STATUS_SIMPLE){ + B->m_status = STATUS_PHYSICS; + if(B->IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)B); + } + + return true; + } + + } + } + + return false; +} + +bool +CPhysical::CheckCollision(void) +{ + CEntryInfoNode *node; + + bCollisionProcessed = false; + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)) + return true; + return false; +} + +bool +CPhysical::CheckCollision_SimpleCar(void) +{ + CEntryInfoNode *node; + + bCollisionProcessed = false; + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList_SimpleCar(node->sector->m_lists)) + return true; + return false; +} + +void +CPhysical::ProcessShift(void) +{ + m_fDistanceTravelled = 0.0f; + if(m_status == STATUS_SIMPLE){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + }else{ + CMatrix matrix(GetMatrix()); + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + + CWorld::AdvanceCurrentScanCode(); + + if(IsVehicle()) + field_EF = true; + + CEntryInfoNode *node; + bool hasshifted = false; // whatever that means... + for(node = m_entryInfoList.first; node; node = node->next) + hasshifted |= ProcessShiftSectorList(node->sector->m_lists); + field_EF = false; + if(hasshifted){ + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)){ + GetMatrix() = matrix; + return; + } + } + bIsStuck = false; + bIsInSafePosition = true; + m_fDistanceTravelled = (GetPosition() - *matrix.GetPosition()).Magnitude(); + RemoveAndAdd(); + } +} + +// x is the number of units (m) we would like to step +#define NUMSTEPS(x) ceil(sqrt(distSq) * (1.0f/(x))) + +void +CPhysical::ProcessCollision(void) +{ + int i; + CPed *ped = (CPed*)this; + + m_fDistanceTravelled = 0.0f; + field_EF = 0; + m_phy_flagA80 = false; + + if(!bUsesCollision){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + return; + } + + if(m_status == STATUS_SIMPLE){ + if(CheckCollision_SimpleCar() && m_status == STATUS_SIMPLE){ + m_status = STATUS_PHYSICS; + if(IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)this); + } + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + return; + } + + // Save current state + CMatrix savedMatrix(GetMatrix()); + float savedTimeStep = CTimer::GetTimeStep(); + + int8 n = 1; // The number of steps we divide the time step into + float step = 0.0f; // divided time step + float distSq = GetDistanceSq(); + + if(IsPed() && (distSq >= sq(0.2f) || ped->IsPlayer())){ + if(ped->IsPlayer()) + n = min(NUMSTEPS(0.2f), 2.0); + else + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + }else if(IsVehicle() && distSq >= sq(0.4f)){ + if(m_status == STATUS_PLAYER) + n = NUMSTEPS(0.2f); + else + n = distSq > 0.32f ? NUMSTEPS(0.3f) : NUMSTEPS(0.4f); + step = savedTimeStep / n; + }else if(IsObject()){ + int responsecase = ((CObject*)this)->m_bSpecialCollisionResponseCases; + if(responsecase == 1){ + CVector speedUp = { 0.0f, 0.0f, 0.0f }; + CVector speedDown = { 0.0f, 0.0f, 0.0f }; + speedUp.z = GetBoundRadius(); + speedDown.z = -speedUp.z; + speedUp = Multiply3x3(GetMatrix(), speedUp); + speedDown = Multiply3x3(GetMatrix(), speedDown); + speedUp = GetSpeed(speedUp); + speedDown = GetSpeed(speedDown); + distSq = max(speedUp.MagnitudeSqr(), speedDown.MagnitudeSqr()) * sq(CTimer::GetTimeStep()); + if(distSq >= sq(0.3f)){ + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + } + }else if(responsecase == 5){ + if(distSq >= 0.009f){ + n = NUMSTEPS(0.09f); + step = savedTimeStep / n; + } + }else if(responsecase == 2 || responsecase == 4){ + if(distSq >= sq(0.15f)){ + n = NUMSTEPS(0.15f); + step = savedTimeStep / n; + } + }else{ + if(distSq >= sq(0.3f)){ + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + } + } + } + + for(i = 1; i < n; i++){ + CTimer::SetTimeStep(i * step); + ApplyMoveSpeed(); + ApplyTurnSpeed(); + // TODO: get rid of copy paste? + if(CheckCollision()){ + if(IsPed() && m_vecMoveSpeed.z == 0.0f && + !ped->m_ped_flagA2 && + ped->m_ped_flagA1) + savedMatrix.GetPosition()->z = GetPosition().z; + GetMatrix() = savedMatrix; + CTimer::SetTimeStep(savedTimeStep); + return; + } + if(IsPed() && m_vecMoveSpeed.z == 0.0f && + !ped->m_ped_flagA2 && + ped->m_ped_flagA1) + savedMatrix.GetPosition()->z = GetPosition().z; + GetMatrix() = savedMatrix; + CTimer::SetTimeStep(savedTimeStep); + if(IsVehicle()){ + CVehicle *veh = (CVehicle*)this; + if(veh->m_vehType == VEHICLE_TYPE_CAR){ + CAutomobile *car = (CAutomobile*)this; + car->m_afWheelSuspDist[0] = 1.0f; + car->m_afWheelSuspDist[1] = 1.0f; + car->m_afWheelSuspDist[2] = 1.0f; + car->m_afWheelSuspDist[3] = 1.0f; + }else if(veh->m_vehType == VEHICLE_TYPE_BIKE){ + assert(0 && "TODO - but unused"); + } + } + } + + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + field_EF = 0; + m_phy_flagA80 = false; + if(!m_vecMoveSpeed.IsZero() || + !m_vecTurnSpeed.IsZero() || + m_phy_flagA40 || + m_status == STATUS_PLAYER || IsPed() && ped->IsPlayer()){ + if(IsVehicle()) + ((CVehicle*)this)->m_veh_flagD4 = true; + if(CheckCollision()){ + GetMatrix() = savedMatrix; + return; + } + } + m_phy_flagA40 = false; + m_fDistanceTravelled = (GetPosition() - *savedMatrix.GetPosition()).Magnitude(); + m_phy_flagA80 = false; + + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); +} + STARTPATCHES InjectHook(0x4951F0, &CPhysical::Add_, PATCH_JUMP); InjectHook(0x4954B0, &CPhysical::Remove_, PATCH_JUMP); InjectHook(0x495540, &CPhysical::RemoveAndAdd, PATCH_JUMP); InjectHook(0x495F10, &CPhysical::ProcessControl_, PATCH_JUMP); + InjectHook(0x496F10, &CPhysical::ProcessShift_, PATCH_JUMP); + InjectHook(0x4961A0, &CPhysical::ProcessCollision_, PATCH_JUMP); InjectHook(0x49F790, &CPhysical::ProcessEntityCollision_, PATCH_JUMP); InjectHook(0x4958F0, &CPhysical::AddToMovingList, PATCH_JUMP); InjectHook(0x495940, &CPhysical::RemoveFromMovingList, PATCH_JUMP); @@ -1296,4 +1874,7 @@ STARTPATCHES InjectHook(0x49DA10, &CPhysical::ProcessShiftSectorList, PATCH_JUMP); InjectHook(0x49E790, &CPhysical::ProcessCollisionSectorList_SimpleCar, PATCH_JUMP); + InjectHook(0x49B620, &CPhysical::ProcessCollisionSectorList, PATCH_JUMP); + InjectHook(0x496E50, &CPhysical::CheckCollision, PATCH_JUMP); + InjectHook(0x496EB0, &CPhysical::CheckCollision_SimpleCar, PATCH_JUMP); ENDPATCHES diff --git a/src/entities/Physical.h b/src/entities/Physical.h index 574238ab..06ae3166 100644 --- a/src/entities/Physical.h +++ b/src/entities/Physical.h @@ -1,6 +1,7 @@ #pragma once #include "Lists.h" +#include "Timer.h" #include "Entity.h" #include "Treadable.h" @@ -37,16 +38,16 @@ public: char field_EC; uint8 m_nStaticFrames; uint8 m_nCollisionRecords; - char field_EF; + bool field_EF; CEntity *m_aCollisionRecords[PHYSICAL_MAX_COLLISIONRECORDS]; float m_fDistanceTravelled; // damaged piece - float m_fCollisionImpulse; - CEntity *m_pCollidingEntity; - CVector m_vecCollisionDirection; - int16 m_nCollisionPieceType; + float m_fDamageImpulse; + CEntity *m_pDamageEntity; + CVector m_vecDamageNormal; + int16 m_nDamagePieceType; uint8 m_phy_flagA1 : 1; uint8 bAffectedByGravity : 1; @@ -88,6 +89,7 @@ public: bool GetHasCollidedWith(CEntity *ent); void RemoveRefsToEntity(CEntity *ent); + float GetDistanceSq(void) { return m_vecMoveSpeed.MagnitudeSqr() * sq(CTimer::GetTimeStep()); } // get speed of point p relative to entity center CVector GetSpeed(const CVector &r); CVector GetSpeed(void) { return GetSpeed(CVector(0.0f, 0.0f, 0.0f)); } @@ -133,12 +135,19 @@ public: bool ProcessShiftSectorList(CPtrList *ptrlists); bool ProcessCollisionSectorList_SimpleCar(CPtrList *lists); + bool ProcessCollisionSectorList(CPtrList *lists); + bool CheckCollision(void); + bool CheckCollision_SimpleCar(void); + void ProcessShift(void); + void ProcessCollision(void); // to make patching virtual functions possible void Add_(void) { CPhysical::Add(); } void Remove_(void) { CPhysical::Remove(); } CRect GetBoundRect_(void) { return CPhysical::GetBoundRect(); } void ProcessControl_(void) { CPhysical::ProcessControl(); } + void ProcessShift_(void) { CPhysical::ProcessShift(); } + void ProcessCollision_(void) { CPhysical::ProcessCollision(); } int32 ProcessEntityCollision_(CEntity *ent, CColPoint *point) { return CPhysical::ProcessEntityCollision(ent, point); } }; static_assert(sizeof(CPhysical) == 0x128, "CPhysical: error"); diff --git a/src/entities/Vehicle.h b/src/entities/Vehicle.h index 1a43e075..c62605c9 100644 --- a/src/entities/Vehicle.h +++ b/src/entities/Vehicle.h @@ -14,7 +14,40 @@ public: CPed *pPassengers[8]; uint8 stuff2[24]; CEntity *m_pCurSurface; - uint8 stuff3[160]; + uint8 stuff3[17]; + uint8 m_veh_flagA1 : 1; + uint8 m_veh_flagA2 : 1; + uint8 m_veh_flagA4 : 1; + uint8 m_veh_flagA8 : 1; + uint8 m_veh_flagA10 : 1; + uint8 m_veh_flagA20 : 1; + uint8 m_veh_flagA40 : 1; + uint8 m_veh_flagA80 : 1; + uint8 m_veh_flagB1 : 1; + uint8 m_veh_flagB2 : 1; + uint8 m_veh_flagB4 : 1; + uint8 m_veh_flagB8 : 1; + uint8 m_veh_flagB10 : 1; + uint8 m_veh_flagB20 : 1; + uint8 m_veh_flagB40 : 1; + uint8 m_veh_flagB80 : 1; + uint8 m_veh_flagC1 : 1; + uint8 m_veh_flagC2 : 1; + uint8 m_veh_flagC4 : 1; + uint8 m_veh_flagC8 : 1; + uint8 m_veh_flagC10 : 1; + uint8 m_veh_flagC20 : 1; + uint8 m_veh_flagC40 : 1; + uint8 m_veh_flagC80 : 1; + uint8 m_veh_flagD1 : 1; + uint8 m_veh_flagD2 : 1; + uint8 m_veh_flagD4 : 1; + uint8 m_veh_flagD8 : 1; + uint8 m_veh_flagD10 : 1; + uint8 m_veh_flagD20 : 1; + uint8 m_veh_flagD40 : 1; + uint8 m_veh_flagD80 : 1; + uint8 stuff4[139]; int32 m_vehType; bool IsCar(void) { return m_vehType == VEHICLE_TYPE_CAR; } diff --git a/src/math/Vector.h b/src/math/Vector.h index 98c3f0ec..fbc59832 100644 --- a/src/math/Vector.h +++ b/src/math/Vector.h @@ -64,6 +64,7 @@ public: this->z /= t; return *this; } + bool IsZero(void) { return x == 0.0f && y == 0.0f && z == 0.0f; } }; inline float diff --git a/src/modelinfo/ModelIndices.h b/src/modelinfo/ModelIndices.h index f008543e..d64cf939 100644 --- a/src/modelinfo/ModelIndices.h +++ b/src/modelinfo/ModelIndices.h @@ -203,9 +203,14 @@ enum MI_FATFEMALE01, MI_FATFEMALE02, + MI_PREDATOR = 120, MI_RHINO = 122, + MI_TRAIN = 124, MI_COACH = 127, MI_RCBANDIT = 131, + MI_SPEEDER = 142, + MI_REEFER = 143, + MI_GHOST = 150, MI_CAR_DOOR = 190, MI_CAR_BUMPER, @@ -248,3 +253,13 @@ IsBodyPart(int16 id) { return id == MI_BODYPARTA || id == MI_BODYPARTB; } + +// This is bad and should perhaps not be used +inline bool +IsBoatModel(int16 id) +{ + return id == MI_PREDATOR || + id == MI_REEFER || + id == MI_SPEEDER || + id == MI_GHOST; +}