summaryrefslogtreecommitdiffstats
path: root/src/entities
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/entities/Building.cpp7
-rw-r--r--src/entities/Building.h15
-rw-r--r--src/entities/CutsceneHead.cpp2
-rw-r--r--src/entities/CutsceneHead.h10
-rw-r--r--src/entities/CutsceneObject.cpp2
-rw-r--r--src/entities/CutsceneObject.h9
-rw-r--r--src/entities/Entity.cpp391
-rw-r--r--src/entities/Entity.h146
-rw-r--r--src/entities/Object.cpp9
-rw-r--r--src/entities/Object.h50
-rw-r--r--src/entities/Ped.h35
-rw-r--r--src/entities/Physical.cpp916
-rw-r--r--src/entities/Physical.h137
-rw-r--r--src/entities/Treadable.cpp7
-rw-r--r--src/entities/Treadable.h16
-rw-r--r--src/entities/Vehicle.h21
16 files changed, 1773 insertions, 0 deletions
diff --git a/src/entities/Building.cpp b/src/entities/Building.cpp
new file mode 100644
index 00000000..e8f19b01
--- /dev/null
+++ b/src/entities/Building.cpp
@@ -0,0 +1,7 @@
+#include "common.h"
+#include "rpworld.h"
+#include "Building.h"
+#include "Pools.h"
+
+void *CBuilding::operator new(size_t sz) { return CPools::GetBuildingPool()->New(); }
+void CBuilding::operator delete(void *p, size_t sz) { CPools::GetBuildingPool()->Delete((CBuilding*)p); }
diff --git a/src/entities/Building.h b/src/entities/Building.h
new file mode 100644
index 00000000..d33aaa4f
--- /dev/null
+++ b/src/entities/Building.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "Entity.h"
+
+class CBuilding : public CEntity
+{
+public:
+ // TODO: ReplaceWithNewModel
+ // TODO: ctor
+ static void *operator new(size_t);
+ static void operator delete(void*, size_t);
+
+ virtual bool GetIsATreadable(void) { return false; }
+};
+static_assert(sizeof(CBuilding) == 0x64, "CBuilding: error");
diff --git a/src/entities/CutsceneHead.cpp b/src/entities/CutsceneHead.cpp
new file mode 100644
index 00000000..6a8874f5
--- /dev/null
+++ b/src/entities/CutsceneHead.cpp
@@ -0,0 +1,2 @@
+#include "common.h"
+#include "CutsceneHead.h"
diff --git a/src/entities/CutsceneHead.h b/src/entities/CutsceneHead.h
new file mode 100644
index 00000000..5784ffc9
--- /dev/null
+++ b/src/entities/CutsceneHead.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "CutsceneObject.h"
+
+class CCutsceneHead : public CCutsceneObject
+{
+public:
+ RwFrame *m_pHeadNode;
+};
+static_assert(sizeof(CCutsceneHead) == 0x19C, "CCutsceneHead: error");
diff --git a/src/entities/CutsceneObject.cpp b/src/entities/CutsceneObject.cpp
new file mode 100644
index 00000000..6aa0f4b3
--- /dev/null
+++ b/src/entities/CutsceneObject.cpp
@@ -0,0 +1,2 @@
+#include "common.h"
+#include "CutsceneObject.h"
diff --git a/src/entities/CutsceneObject.h b/src/entities/CutsceneObject.h
new file mode 100644
index 00000000..c5cbf83f
--- /dev/null
+++ b/src/entities/CutsceneObject.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Object.h"
+
+class CCutsceneObject : public CObject
+{
+public:
+};
+static_assert(sizeof(CCutsceneObject) == 0x198, "CCutsceneObject: error");
diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp
new file mode 100644
index 00000000..11fa9ab2
--- /dev/null
+++ b/src/entities/Entity.cpp
@@ -0,0 +1,391 @@
+#include "common.h"
+#include "rpworld.h"
+#include "Placeable.h"
+#include "Entity.h"
+#include "Lights.h"
+#include "World.h"
+#include "Camera.h"
+#include "References.h"
+#include "TxdStore.h"
+#include "Zones.h"
+#include "patcher.h"
+
+int gBuildings;
+
+void
+CEntity::GetBoundCentre(CVector &out)
+{
+ out = m_matrix * CModelInfo::GetModelInfo(m_modelIndex)->GetColModel()->boundingSphere.center;
+};
+
+bool
+CEntity::GetIsTouching(CVector const &center, float radius)
+{
+ return sq(GetBoundRadius()+radius) > (GetBoundCentre()-center).MagnitudeSqr();
+}
+
+bool
+CEntity::GetIsOnScreen(void)
+{
+ return TheCamera.IsSphereVisible(GetBoundCentre(), GetBoundRadius(),
+ &TheCamera.GetCameraMatrix());
+}
+
+bool
+CEntity::GetIsOnScreenComplex(void)
+{
+ RwV3d boundBox[8];
+
+ if(TheCamera.IsPointVisible(GetBoundCentre(), &TheCamera.GetCameraMatrix()))
+ return true;
+
+ CRect rect = GetBoundRect();
+ CColModel *colmodel = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel();
+ float z = GetPosition().z;
+ float minz = z + colmodel->boundingBox.min.z;
+ float maxz = z + colmodel->boundingBox.max.z;
+ boundBox[0].x = rect.left;
+ boundBox[0].y = rect.top;
+ boundBox[0].z = minz;
+ boundBox[1].x = rect.left;
+ boundBox[1].y = rect.bottom;
+ boundBox[1].z = minz;
+ boundBox[2].x = rect.right;
+ boundBox[2].y = rect.top;
+ boundBox[2].z = minz;
+ boundBox[3].x = rect.right;
+ boundBox[3].y = rect.bottom;
+ boundBox[3].z = minz;
+ boundBox[4].x = rect.left;
+ boundBox[4].y = rect.top;
+ boundBox[4].z = maxz;
+ boundBox[5].x = rect.left;
+ boundBox[5].y = rect.bottom;
+ boundBox[5].z = maxz;
+ boundBox[6].x = rect.right;
+ boundBox[6].y = rect.top;
+ boundBox[6].z = maxz;
+ boundBox[7].x = rect.right;
+ boundBox[7].y = rect.bottom;
+ boundBox[7].z = maxz;
+
+ return TheCamera.IsBoxVisible(boundBox, &TheCamera.GetCameraMatrix());
+}
+
+void
+CEntity::Add(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES];
+ break;
+ }else switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP];
+ break;
+ }
+ list->InsertItem(this);
+ }
+}
+
+void
+CEntity::Remove(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES];
+ break;
+ }else switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP];
+ break;
+ }
+ list->RemoveItem(this);
+ }
+}
+
+void
+CEntity::CreateRwObject(void)
+{
+ CBaseModelInfo *mi;
+
+ mi = CModelInfo::GetModelInfo(m_modelIndex);
+ m_rwObject = mi->CreateInstance();
+ if(m_rwObject){
+ if(IsBuilding())
+ gBuildings++;
+ if(RwObjectGetType(m_rwObject) == rpATOMIC)
+ m_matrix.AttachRW(RwFrameGetMatrix(RpAtomicGetFrame(m_rwObject)), false);
+ else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+ m_matrix.AttachRW(RwFrameGetMatrix(RpClumpGetFrame(m_rwObject)), false);
+ mi->AddRef();
+ }
+}
+
+void
+CEntity::DeleteRwObject(void)
+{
+ RwFrame *f;
+
+ m_matrix.Detach();
+ if(m_rwObject){
+ if(RwObjectGetType(m_rwObject) == rpATOMIC){
+ f = RpAtomicGetFrame(m_rwObject);
+ RpAtomicDestroy((RpAtomic*)m_rwObject);
+ RwFrameDestroy(f);
+ }else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+ RpClumpDestroy((RpClump*)m_rwObject);
+ m_rwObject = nil;
+ CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef();
+ if(IsBuilding())
+ gBuildings--;
+ }
+}
+
+void
+CEntity::UpdateRwFrame(void)
+{
+ if(m_rwObject){
+ if(RwObjectGetType(m_rwObject) == rpATOMIC)
+ RwFrameUpdateObjects(RpAtomicGetFrame(m_rwObject));
+ else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+ RwFrameUpdateObjects(RpClumpGetFrame(m_rwObject));
+ }
+}
+
+void
+CEntity::SetupBigBuilding(void)
+{
+ CSimpleModelInfo *mi;
+
+ mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_modelIndex);
+ bIsBIGBuilding = true;
+ m_flagC20 = true;
+ bUsesCollision = false;
+ m_level = CTheZones::GetLevelFromPosition(GetPosition());
+ if(m_level == LEVEL_NONE){
+ if(mi->GetTxdSlot() != CTxdStore::FindTxdSlot("generic")){
+ mi->SetTexDictionary("generic");
+ printf("%d:%s txd has been set to generic\n", m_modelIndex, mi->GetName());
+ }
+ }
+ if(mi->m_lodDistances[0] > 2000.0f)
+ m_level = LEVEL_NONE;
+}
+
+CRect
+CEntity::GetBoundRect(void)
+{
+ CRect rect;
+ CVector v;
+ CColModel *col = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel();
+
+ rect.ContainPoint(m_matrix * col->boundingBox.min);
+ rect.ContainPoint(m_matrix * col->boundingBox.max);
+
+ v = col->boundingBox.min;
+ v.x = col->boundingBox.max.x;
+ rect.ContainPoint(m_matrix * v);
+
+ v = col->boundingBox.max;
+ v.x = col->boundingBox.min.x;
+ rect.ContainPoint(m_matrix * v);
+
+ return rect;
+}
+
+void
+CEntity::PreRender(void)
+{
+}
+
+void
+CEntity::Render(void)
+{
+ if(m_rwObject){
+ bImBeingRendered = true;
+ if(RwObjectGetType(m_rwObject) == rpATOMIC)
+ RpAtomicRender((RpAtomic*)m_rwObject);
+ else
+ RpClumpRender((RpClump*)m_rwObject);
+ bImBeingRendered = false;
+ }
+}
+
+bool
+CEntity::SetupLighting(void)
+{
+ DeActivateDirectional();
+ SetAmbientColours();
+ return false;
+}
+
+void
+CEntity::RegisterReference(CEntity **pent)
+{
+ if(IsBuilding())
+ return;
+ CReference *ref;
+ // check if already registered
+ for(ref = m_pFirstReference; ref; ref = ref->next)
+ if(ref->pentity == pent)
+ return;
+ // have to allocate new reference
+ ref = CReferences::pEmptyList;
+ if(ref){
+ CReferences::pEmptyList = ref->next;
+
+ ref->pentity = pent;
+ ref->next = m_pFirstReference;
+ m_pFirstReference = ref;
+ return;
+ }
+ return;
+}
+
+// Clear all references to this entity
+void
+CEntity::ResolveReferences(void)
+{
+ CReference *ref;
+ // clear pointers to this entity
+ for(ref = m_pFirstReference; ref; ref = ref->next)
+ if(*ref->pentity == this)
+ *ref->pentity = nil;
+ // free list
+ if(m_pFirstReference){
+ for(ref = m_pFirstReference; ref->next; ref = ref->next)
+ ;
+ ref->next = CReferences::pEmptyList;
+ CReferences::pEmptyList = ref;
+ m_pFirstReference = nil;
+ }
+}
+
+// Free all references that no longer point to this entity
+void
+CEntity::PruneReferences(void)
+{
+ CReference *ref, *next, **lastnextp;
+ lastnextp = &m_pFirstReference;
+ for(ref = m_pFirstReference; ref; ref = next){
+ next = ref->next;
+ if(*ref->pentity == this)
+ lastnextp = &ref->next;
+ else{
+ *lastnextp = ref->next;
+ ref->next = CReferences::pEmptyList;
+ CReferences::pEmptyList = ref;
+ }
+ }
+}
+
+STARTPATCHES
+ InjectHook(0x4742C0, (void (CEntity::*)(CVector&))&CEntity::GetBoundCentre, PATCH_JUMP);
+ InjectHook(0x474310, &CEntity::GetBoundRadius, PATCH_JUMP);
+ InjectHook(0x474C10, &CEntity::GetIsTouching, PATCH_JUMP);
+ InjectHook(0x474CC0, &CEntity::GetIsOnScreen, PATCH_JUMP);
+ InjectHook(0x474D20, &CEntity::GetIsOnScreenComplex, PATCH_JUMP);
+ InjectHook(0x474CA0, &CEntity::IsVisible, PATCH_JUMP);
+ InjectHook(0x474330, &CEntity::UpdateRwFrame, PATCH_JUMP);
+ InjectHook(0x4755E0, &CEntity::SetupBigBuilding, PATCH_JUMP);
+ InjectHook(0x4A7480, &CEntity::RegisterReference, PATCH_JUMP);
+ InjectHook(0x4A74E0, &CEntity::ResolveReferences, PATCH_JUMP);
+ InjectHook(0x4A7530, &CEntity::PruneReferences, PATCH_JUMP);
+
+ InjectHook(0x475080, &CEntity::Add_, PATCH_JUMP);
+ InjectHook(0x475310, &CEntity::Remove_, PATCH_JUMP);
+ InjectHook(0x473EA0, &CEntity::CreateRwObject_, PATCH_JUMP);
+ InjectHook(0x473F90, &CEntity::DeleteRwObject_, PATCH_JUMP);
+ InjectHook(0x474000, &CEntity::GetBoundRect_, PATCH_JUMP);
+ InjectHook(0x474BD0, &CEntity::Render_, PATCH_JUMP);
+ InjectHook(0x4A7C60, &CEntity::SetupLighting_, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/entities/Entity.h b/src/entities/Entity.h
new file mode 100644
index 00000000..8bcd7348
--- /dev/null
+++ b/src/entities/Entity.h
@@ -0,0 +1,146 @@
+#pragma once
+
+#include "ModelInfo.h"
+#include "Placeable.h"
+
+struct CReference;
+
+enum eEntityType
+{
+ ENTITY_TYPE_NOTHING = 0,
+ ENTITY_TYPE_BUILDING,
+ ENTITY_TYPE_VEHICLE,
+ ENTITY_TYPE_PED,
+ ENTITY_TYPE_OBJECT,
+ ENTITY_TYPE_DUMMY,
+ ENTITY_TYPE_6,
+ ENTITY_TYPE_7,
+};
+
+enum eEntityStatus
+{
+ // from SA MTA! let's hope they didn't change from III
+ STATUS_PLAYER = 0,
+ STATUS_PLAYER_PLAYBACKFROMBUFFER,
+ STATUS_SIMPLE,
+ STATUS_PHYSICS,
+ STATUS_ABANDONED,
+ STATUS_WRECKED,
+ STATUS_TRAIN_MOVING,
+ STATUS_TRAIN_NOT_MOVING,
+ STATUS_HELI,
+ STATUS_PLANE,
+ STATUS_PLAYER_REMOTE,
+ STATUS_PLAYER_DISABLED,
+ //STATUS_TRAILER,
+ //STATUS_SIMPLE_TRAILER
+};
+
+class CEntity : public CPlaceable
+{
+public:
+ RwObject *m_rwObject;
+ uint32 m_type : 3;
+ uint32 m_status : 5;
+
+ // flagsA
+ uint32 bUsesCollision : 1;
+ uint32 bCollisionProcessed : 1;
+ uint32 bIsStatic : 1;
+ uint32 bHasContacted : 1;
+ uint32 bPedPhysics : 1;
+ uint32 bIsStuck : 1;
+ uint32 bIsInSafePosition : 1;
+ uint32 bUseCollisionRecords : 1;
+
+ // flagsB
+ uint32 bWasPostponed : 1;
+ uint32 m_flagB2 : 1; // explosion proof?
+ uint32 bIsVisible : 1;
+ uint32 bHasCollided : 1; //
+ uint32 bRenderScorched : 1;
+ uint32 m_flagB20 : 1; // bFlashing?
+ uint32 bIsBIGBuilding : 1;
+ // VC inserts one more flag here: if drawdist <= 2000
+ uint32 bRenderDamaged : 1;
+
+ // flagsC
+ uint32 m_flagC1 : 1; // bullet proof?
+ uint32 m_flagC2 : 1; // fire proof?
+ uint32 m_flagC4 : 1; // collision proof?
+ uint32 m_flagC8 : 1; // melee proof?
+ uint32 m_flagC10 : 1; // bOnlyDamagedByPlayer?
+ uint32 m_flagC20 : 1;
+ uint32 m_bZoneCulled : 1;
+ uint32 m_bZoneCulled2 : 1; // only treadables+10m
+
+ // flagsD
+ uint32 bRemoveFromWorld : 1;
+ uint32 bHasHitWall : 1;
+ uint32 bImBeingRendered : 1;
+ uint32 m_flagD8 : 1;
+ uint32 m_flagD10 : 1;
+ uint32 bDrawLast : 1;
+ uint32 m_flagD40 : 1;
+ uint32 m_flagD80 : 1;
+
+ // flagsE
+ uint32 bDistanceFade : 1;
+ uint32 m_flagE2 : 1;
+
+ uint16 m_scanCode;
+ int16 m_randomSeed;
+ int16 m_modelIndex;
+ uint16 m_level; // int16
+ CReference *m_pFirstReference;
+
+ virtual void Add(void);
+ virtual void Remove(void);
+ virtual void SetModelIndex(uint32 i) { m_modelIndex = i; CreateRwObject(); }
+ virtual void SetModelIndexNoCreate(uint32 i) { m_modelIndex = i; }
+ virtual void CreateRwObject(void);
+ virtual void DeleteRwObject(void);
+ virtual CRect GetBoundRect(void);
+ virtual void ProcessControl(void) {}
+ virtual void ProcessCollision(void) {}
+ virtual void ProcessShift(void) {}
+ virtual void Teleport(CVector v) {}
+ virtual void PreRender(void);
+ virtual void Render(void);
+ virtual bool SetupLighting(void);
+ virtual void RemoveLighting(bool) {}
+ virtual void FlagToDestroyWhenNextProcessed(void) {}
+
+ bool IsBuilding(void) { return m_type == ENTITY_TYPE_BUILDING; }
+ bool IsVehicle(void) { return m_type == ENTITY_TYPE_VEHICLE; }
+ bool IsPed(void) { return m_type == ENTITY_TYPE_PED; }
+ bool IsObject(void) { return m_type == ENTITY_TYPE_OBJECT; }
+ bool IsDummy(void) { return m_type == ENTITY_TYPE_DUMMY; }
+
+ void GetBoundCentre(CVector &out);
+ CVector GetBoundCentre(void) { CVector v; GetBoundCentre(v); return v; }
+ float GetBoundRadius(void) { return CModelInfo::GetModelInfo(m_modelIndex)->GetColModel()->boundingSphere.radius; }
+ bool GetIsTouching(CVector const &center, float r);
+ bool GetIsOnScreen(void);
+ bool GetIsOnScreenComplex(void);
+ bool IsVisible(void) { return m_rwObject && bIsVisible && GetIsOnScreen(); }
+ bool IsVisibleComplex(void) { return m_rwObject && bIsVisible && GetIsOnScreenComplex(); }
+ int GetModelIndex(void) { return m_modelIndex; }
+ void UpdateRwFrame(void);
+ void SetupBigBuilding(void);
+
+ void RegisterReference(CEntity **pent);
+ void ResolveReferences(void);
+ void PruneReferences(void);
+
+
+ // to make patching virtual functions possible
+ void Add_(void) { CEntity::Add(); }
+ void Remove_(void) { CEntity::Remove(); }
+ void CreateRwObject_(void) { CEntity::CreateRwObject(); }
+ void DeleteRwObject_(void) { CEntity::DeleteRwObject(); }
+ CRect GetBoundRect_(void) { return CEntity::GetBoundRect(); }
+ void Render_(void) { CEntity::Render(); }
+ bool SetupLighting_(void) { return CEntity::SetupLighting(); }
+};
+static_assert(sizeof(CEntity) == 0x64, "CEntity: error");
diff --git a/src/entities/Object.cpp b/src/entities/Object.cpp
new file mode 100644
index 00000000..8ce1250f
--- /dev/null
+++ b/src/entities/Object.cpp
@@ -0,0 +1,9 @@
+#include "common.h"
+#include "patcher.h"
+#include "Object.h"
+#include "Pools.h"
+
+void *CObject::operator new(size_t sz) { return CPools::GetObjectPool()->New(); }
+void CObject::operator delete(void *p, size_t sz) { CPools::GetObjectPool()->Delete((CObject*)p); }
+
+WRAPPER void CObject::ObjectDamage(float amount) { EAXJMP(0x4BB240); }
diff --git a/src/entities/Object.h b/src/entities/Object.h
new file mode 100644
index 00000000..6992b92d
--- /dev/null
+++ b/src/entities/Object.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "Physical.h"
+
+enum {
+ GAME_OBJECT = 1,
+ MISSION_OBJECT = 2,
+ TEMP_OBJECT = 3,
+};
+
+class CObject : public CPhysical
+{
+public:
+ CMatrix m_objectMatrix;
+ float m_fUprootLimit;
+ int8 ObjectCreatedBy;
+// int8 m_nObjectFlags;
+ int8 m_obj_flag1 : 1;
+ int8 m_obj_flag2 : 1;
+ int8 m_obj_flag4 : 1;
+ int8 m_obj_flag8 : 1;
+ int8 m_obj_flag10 : 1;
+ int8 bHasBeenDamaged : 1;
+ int8 m_obj_flag40 : 1;
+ int8 m_obj_flag80 : 1;
+ int8 field_172;
+ int8 field_173;
+ float m_fCollisionDamageMultiplier;
+ int8 m_nCollisionDamageEffect;
+ int8 m_bSpecialCollisionResponseCases;
+ int8 m_bCameraToAvoidThisObject;
+ int8 field_17B;
+ int8 field_17C;
+ int8 field_17D;
+ int8 field_17E;
+ int8 field_17F;
+ int32 m_nEndOfLifeTime;
+ int16 m_nRefModelIndex;
+ int8 field_186;
+ int8 field_187;
+ CEntity *m_pCurSurface;
+ CEntity *field_18C;
+ int8 m_colour1, m_colour2;
+
+ static void *operator new(size_t);
+ static void operator delete(void*, size_t);
+
+ void ObjectDamage(float amount);
+};
+static_assert(sizeof(CObject) == 0x198, "CObject: error");
diff --git a/src/entities/Ped.h b/src/entities/Ped.h
new file mode 100644
index 00000000..fd71b616
--- /dev/null
+++ b/src/entities/Ped.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "Physical.h"
+
+enum PedAction
+{
+ PED_PASSENGER = 44,
+};
+
+class CVehicle;
+
+class CPed : public CPhysical
+{
+public:
+ // 0x128
+ uint8 stuff1[252];
+ int32 m_nPedState;
+ uint8 stuff2[196];
+ CEntity *m_pCurrentPhysSurface;
+ CVector m_vecOffsetFromPhysSurface;
+ CEntity *m_pCurSurface;
+ uint8 stuff3[16];
+ CVehicle *m_pMyVehicle;
+ bool bInVehicle;
+ uint8 stuff4[23];
+ int32 m_nPedType;
+ uint8 stuff5[528];
+
+ bool IsPlayer(void) { return m_nPedType == 0 || m_nPedType== 1 || m_nPedType == 2 || m_nPedType == 3; }
+};
+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(sizeof(CPed) == 0x540, "CPed: error");
diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp
new file mode 100644
index 00000000..f235cb42
--- /dev/null
+++ b/src/entities/Physical.cpp
@@ -0,0 +1,916 @@
+#include "common.h"
+#include "patcher.h"
+#include "World.h"
+#include "Timer.h"
+#include "ModelIndices.h"
+#include "Vehicle.h"
+#include "Ped.h"
+#include "Object.h"
+#include "Glass.h"
+#include "ParticleObject.h"
+#include "Particle.h"
+#include "SurfaceTable.h"
+#include "Physical.h"
+
+void
+CPhysical::Add(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ default:
+ assert(0);
+ }else switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ default:
+ assert(0);
+ }
+ CPtrNode *node = list->InsertItem(this);
+ assert(node);
+ m_entryInfoList.InsertItem(list, node, s);
+ }
+}
+
+void
+CPhysical::Remove(void)
+{
+ CEntryInfoNode *node, *next;
+ for(node = m_entryInfoList.first; node; node = next){
+ next = node->next;
+ node->list->DeleteNode(node->listnode);
+ m_entryInfoList.DeleteNode(node);
+ }
+}
+
+void
+CPhysical::RemoveAndAdd(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ // we'll try to recycle nodes from here
+ CEntryInfoNode *next = m_entryInfoList.first;
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ }else switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ }
+ if(next){
+ // If we still have old nodes, use them
+ next->list->RemoveNode(next->listnode);
+ list->InsertNode(next->listnode);
+ next->list = list;
+ next->sector = s;
+ next = next->next;
+ }else{
+ CPtrNode *node = list->InsertItem(this);
+ m_entryInfoList.InsertItem(list, node, s);
+ }
+ }
+
+ // Remove old nodes we no longer need
+ CEntryInfoNode *node;
+ for(node = next; node; node = next){
+ next = node->next;
+ node->list->DeleteNode(node->listnode);
+ m_entryInfoList.DeleteNode(node);
+ }
+}
+
+CRect
+CPhysical::GetBoundRect(void)
+{
+ CVector center;
+ float radius;
+ GetBoundCentre(center);
+ radius = GetBoundRadius();
+ return CRect(center.x-radius, center.y-radius, center.x+radius, center.y+radius);
+}
+
+void
+CPhysical::AddToMovingList(void)
+{
+ m_movingListNode = CWorld::GetMovingEntityList().InsertItem(this);
+}
+
+void
+CPhysical::RemoveFromMovingList(void)
+{
+ if(m_movingListNode){
+ CWorld::GetMovingEntityList().DeleteNode(m_movingListNode);
+ m_movingListNode = nil;
+ }
+}
+
+
+/*
+ * Some quantities (german in parens):
+ *
+ * acceleration: distance/time^2: a
+ * velocity: distance/time: v (GTA: speed)
+ * momentum (impuls): velocity*mass: p
+ * impulse (kraftstoss): delta momentum, force*time: J
+ *
+ * angular equivalents:
+ * velocity -> angular velocity (GTA: turn speed)
+ * momentum -> angular momentum (drehimpuls): L = r cross p
+ * force -> torque (drehmoment): tau = r cross F
+ * mass -> moment of inertia, angular mass (drehmoment, drehmasse): I = L/omega (GTA: turn mass)
+ */
+
+CVector
+CPhysical::GetSpeed(const CVector &r)
+{
+ return m_vecMoveSpeed + m_vecMoveFriction + CrossProduct(m_vecTurnFriction + m_vecTurnSpeed, r);
+}
+
+void
+CPhysical::ApplyMoveSpeed(void)
+{
+ GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep();
+}
+
+void
+CPhysical::ApplyTurnSpeed(void)
+{
+ // Move the coordinate axes by their speed
+ // Note that this denormalizes the matrix
+ CVector turnvec = m_vecTurnSpeed*CTimer::GetTimeStep();
+ GetRight() += CrossProduct(turnvec, GetRight());
+ GetForward() += CrossProduct(turnvec, GetForward());
+ GetUp() += CrossProduct(turnvec, GetUp());
+}
+
+void
+CPhysical::ApplyMoveForce(float jx, float jy, float jz)
+{
+ m_vecMoveSpeed += CVector(jx, jy, jz)*(1.0f/m_fMass);
+}
+
+void
+CPhysical::ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz)
+{
+ CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
+ CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz));
+ m_vecTurnSpeed += turnimpulse*(1.0f/m_fTurnMass);
+}
+
+void
+CPhysical::ApplyFrictionMoveForce(float jx, float jy, float jz)
+{
+ m_vecMoveFriction += CVector(jx, jy, jz)*(1.0f/m_fMass);
+}
+
+void
+CPhysical::ApplyFrictionTurnForce(float jx, float jy, float jz, float px, float py, float pz)
+{
+ CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
+ CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz));
+ m_vecTurnFriction += turnimpulse*(1.0f/m_fTurnMass);
+}
+
+void
+CPhysical::ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3)
+{
+ if(1.0f - f2 <= 0.0f)
+ return;
+ float step = min(CTimer::GetTimeStep(), 3.0f);
+ float strength = -0.008f*m_fMass*2.0f*step * f1 * (1.0f-f2) * f3;
+ ApplyMoveForce(v.x*strength, v.y*strength, v.z*strength);
+ ApplyTurnForce(v.x*strength, v.y*strength, v.z*strength, p.x, p.y, p.z);
+}
+
+void
+CPhysical::ApplyGravity(void)
+{
+ if(bAffectedByGravity)
+ m_vecMoveSpeed.z -= 0.008f * CTimer::GetTimeStep();
+}
+
+void
+CPhysical::ApplyFriction(void)
+{
+ m_vecMoveSpeed += m_vecMoveFriction;
+ m_vecTurnSpeed += m_vecTurnFriction;
+ m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
+}
+
+void
+CPhysical::ApplyAirResistance(void)
+{
+ if(m_fAirResistance > 0.1f){
+ float f = powf(m_fAirResistance, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= f;
+ m_vecTurnSpeed *= f;
+ }else{
+ float f = powf(1.0f/(m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr() + 1.0f), CTimer::GetTimeStep());
+ m_vecMoveSpeed *= f;
+ m_vecTurnSpeed *= 0.99f;
+ }
+}
+
+
+bool
+CPhysical::ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB)
+{
+ float eA, eB;
+ CPhysical *A = this;
+ CObject *Bobj = (CObject*)B;
+
+ bool ispedcontactA = false;
+ bool ispedcontactB = false;
+
+ float timestepA;
+ if(B->bPedPhysics){
+ timestepA = 10.0f;
+ if(B->IsPed() && ((CPed*)B)->m_pCurrentPhysSurface == A)
+ ispedcontactA = true;
+ }else
+ timestepA = A->m_phy_flagA1 ? 2.0f : 1.0f;
+
+ float timestepB;
+ if(A->bPedPhysics){
+ if(A->IsPed() && ((CPed*)A)->IsPlayer() && B->IsVehicle() &&
+ (B->m_status == STATUS_ABANDONED || B->m_status == STATUS_WRECKED || A->bHasHitWall))
+ timestepB = 2200.0f / B->m_fMass;
+ else
+ timestepB = 10.0f;
+
+ if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B)
+ ispedcontactB = true;
+ }else
+ timestepB = B->m_phy_flagA1 ? 2.0f : 1.0f;
+
+ float speedA, speedB;
+ if(B->bIsStatic){
+ if(A->bPedPhysics){
+ speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ if(speedA < 0.0f){
+ if(B->IsObject()){
+ impulseA = -speedA * A->m_fMass;
+ impulseB = impulseA;
+ if(impulseA > Bobj->m_fUprootLimit){
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false);
+ else if(!B->bInfiniteMass)
+ B->bIsStatic = false;
+ }else{
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToSoftCollision(B, impulseA);
+ if(!A->bInfiniteMass)
+ A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA);
+ return true;
+ }
+ }else if(!B->bInfiniteMass)
+ B->bIsStatic = false;
+
+ if(B->bInfiniteMass){
+ impulseA = -speedA * A->m_fMass;
+ impulseB = 0.0f;
+ if(!A->bInfiniteMass)
+ A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA);
+ return true;
+ }
+ }
+ }else{
+ CVector pointposA = colpoint.point - A->GetPosition();
+ speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
+ if(speedA < 0.0f){
+ if(B->IsObject()){
+ if(A->bHasHitWall)
+ eA = -1.0f;
+ else
+ eA = -(1.0f + A->m_fElasticity);
+ impulseA = eA * speedA * A->GetMass(pointposA, colpoint.normal);
+ impulseB = impulseA;
+
+ if(Bobj->m_nCollisionDamageEffect && impulseA > 20.0f){
+ Bobj->ObjectDamage(impulseA);
+ if(!B->bUsesCollision){
+ if(!A->bInfiniteMass){
+ A->ApplyMoveForce(colpoint.normal*0.2f*impulseA);
+ A->ApplyTurnForce(colpoint.normal*0.2f*impulseA, pointposA);
+ }
+ return false;
+ }
+ }
+
+ if((impulseA > Bobj->m_fUprootLimit || A->bIsStuck) &&
+ !B->bInfiniteMass){
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false);
+ else
+ B->bIsStatic = false;
+ int16 model = B->GetModelIndex();
+ if(model == MI_FIRE_HYDRANT && !Bobj->bHasBeenDamaged){
+ CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, B->GetPosition() - CVector(0.0f, 0.0f, 0.5f), true);
+ Bobj->bHasBeenDamaged = true;
+ }else if(B->IsObject() && model != MI_EXPLODINGBARREL && model != MI_PETROLPUMP)
+ Bobj->bHasBeenDamaged = true;
+ }else{
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToSoftCollision(B, impulseA);
+ CVector f = colpoint.normal * impulseA;
+ if(A->IsVehicle() && colpoint.normal.z < 0.7f)
+ f.z *= 0.3f;
+ if(!A->bInfiniteMass){
+ A->ApplyMoveForce(f);
+ if(!A->IsVehicle() || !CWorld::bNoMoreCollisionTorque)
+ A->ApplyTurnForce(f, pointposA);
+ }
+ return true;
+ }
+ }else if(!B->bInfiniteMass)
+ B->bIsStatic = false;
+ }
+ }
+
+ if(B->bIsStatic)
+ return false;
+ if(!B->bInfiniteMass)
+ B->AddToMovingList();
+ }
+
+ // B is not static
+
+ if(A->bPedPhysics && B->bPedPhysics){
+ // negative if A is moving towards B
+ speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ // positive if B is moving towards A
+ // not interested in how much B moves into A apparently?
+ // only interested in cases where A collided into B
+ speedB = max(0.0f, DotProduct(B->m_vecMoveSpeed, colpoint.normal));
+ // A has moved into B
+ if(speedA < speedB){
+ if(!A->bHasHitWall)
+ speedB -= (speedA - speedB) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (speedB-speedA) * A->m_fMass * timestepA;
+ if(!A->bInfiniteMass)
+ A->ApplyMoveForce(colpoint.normal*(impulseA/timestepA));
+ return true;
+ }
+ }else if(A->bPedPhysics){
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal);
+
+ float a = A->m_fMass*timestepA;
+ float b = B->GetMassTime(pointposB, colpoint.normal, timestepB);
+ float speedSum = (b*speedB + a*speedA)/(a + b);
+ if(speedA < speedSum){
+ if(A->bHasHitWall)
+ eA = speedSum;
+ else
+ eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ if(B->bHasHitWall)
+ eB = speedSum;
+ else
+ eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (eA - speedA) * a;
+ impulseB = -(eB - speedB) * b;
+ CVector fA = colpoint.normal*(impulseA/timestepA);
+ CVector fB = colpoint.normal*(-impulseB/timestepB);
+ if(!A->bInfiniteMass){
+ if(fA.z < 0.0f) fA.z = 0.0f;
+ if(ispedcontactB){
+ fA.x *= 2.0f;
+ fA.y *= 2.0f;
+ }
+ A->ApplyMoveForce(fA);
+ }
+ if(!B->bInfiniteMass && !ispedcontactB){
+ B->ApplyMoveForce(fB);
+ B->ApplyTurnForce(fB, pointposB);
+ }
+ return true;
+ }
+ }else if(B->bPedPhysics){
+ CVector pointposA = colpoint.point - A->GetPosition();
+ speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
+ speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
+
+ float a = A->GetMassTime(pointposA, colpoint.normal, timestepA);
+ float b = B->m_fMass*timestepB;
+ float speedSum = (b*speedB + a*speedA)/(a + b);
+ if(speedA < speedSum){
+ if(A->bHasHitWall)
+ eA = speedSum;
+ else
+ eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ if(B->bHasHitWall)
+ eB = speedSum;
+ else
+ eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (eA - speedA) * a;
+ impulseB = -(eB - speedB) * b;
+ CVector fA = colpoint.normal*(impulseA/timestepA);
+ CVector fB = colpoint.normal*(-impulseB/timestepB);
+ if(!A->bInfiniteMass && !ispedcontactA){
+ if(fA.z < 0.0f) fA.z = 0.0f;
+ A->ApplyMoveForce(fA);
+ A->ApplyTurnForce(fA, pointposA);
+ }
+ if(!B->bInfiniteMass){
+ if(fB.z < 0.0f){
+ fB.z = 0.0f;
+ if(fabs(speedA) < 0.01f)
+ fB *= 0.5f;
+ }
+ if(ispedcontactA){
+ fB.x *= 2.0f;
+ fB.y *= 2.0f;
+ }
+ B->ApplyMoveForce(fB);
+ }
+ return true;
+ }
+ }else{
+ CVector pointposA = colpoint.point - A->GetPosition();
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
+ speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal);
+ float a = A->GetMassTime(pointposA, colpoint.normal, timestepA);
+ float b = B->GetMassTime(pointposB, colpoint.normal, timestepB);
+ float speedSum = (b*speedB + a*speedA)/(a + b);
+ if(speedA < speedSum){
+ if(A->bHasHitWall)
+ eA = speedSum;
+ else
+ eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ if(B->bHasHitWall)
+ eB = speedSum;
+ else
+ eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (eA - speedA) * a;
+ impulseB = -(eB - speedB) * b;
+ CVector fA = colpoint.normal*(impulseA/timestepA);
+ CVector fB = colpoint.normal*(-impulseB/timestepB);
+ if(A->IsVehicle() && !A->bHasHitWall){
+ fA.x *= 1.4f;
+ fA.y *= 1.4f;
+ if(colpoint.normal.z < 0.7f)
+ fA.z *= 0.3f;
+ if(A->m_status == STATUS_PLAYER)
+ pointposA *= 0.8f;
+ if(CWorld::bNoMoreCollisionTorque){
+ A->ApplyFrictionMoveForce(fA*-0.3f);
+ A->ApplyFrictionTurnForce(fA*-0.3f, pointposA);
+ }
+ }
+ if(B->IsVehicle() && !B->bHasHitWall){
+ fB.x *= 1.4f;
+ fB.y *= 1.4f;
+ if(colpoint.normal.z < 0.7f)
+ fB.z *= 0.3f;
+ if(B->m_status == STATUS_PLAYER)
+ pointposB *= 0.8f;
+ if(CWorld::bNoMoreCollisionTorque){
+ // BUG: the game actually uses A here, but this can't be right
+ B->ApplyFrictionMoveForce(fB*-0.3f);
+ B->ApplyFrictionTurnForce(fB*-0.3f, pointposB);
+ }
+ }
+ if(!A->bInfiniteMass){
+ A->ApplyMoveForce(fA);
+ A->ApplyTurnForce(fA, pointposA);
+ }
+ if(!B->bInfiniteMass){
+ if(B->bIsInSafePosition)
+ B->UnsetIsInSafePosition();
+ B->ApplyMoveForce(fB);
+ B->ApplyTurnForce(fB, pointposB);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed)
+{
+ float normalSpeed;
+ float e;
+ CVector speed;
+ CVector vImpulse;
+
+ if(bPedPhysics){
+ normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal);
+ if(normalSpeed < 0.0f){
+ impulse = -normalSpeed * m_fMass;
+ ApplyMoveForce(colpoint.normal * impulse);
+ return true;
+ }
+ }else{
+ CVector pointpos = colpoint.point - GetPosition();
+ speed = GetSpeed(pointpos);
+ normalSpeed = DotProduct(speed, colpoint.normal);
+ if(normalSpeed < 0.0f){
+ float minspeed = 0.0104f * CTimer::GetTimeStep();
+ if((IsObject() || IsVehicle() && GetUp().z < -0.3f) &&
+ !bHasContacted &&
+ fabs(m_vecMoveSpeed.x) < minspeed &&
+ fabs(m_vecMoveSpeed.y) < minspeed &&
+ fabs(m_vecMoveSpeed.z) < minspeed*2.0f)
+ e = -1.0f;
+ else
+ e = -(m_fElasticity + 1.0f);
+ impulse = normalSpeed * e * GetMass(pointpos, colpoint.normal);
+
+ // ApplyMoveForce
+ vImpulse = colpoint.normal*impulse;
+ if(IsVehicle() &&
+ (!bHasHitWall ||
+ !(m_vecMoveSpeed.MagnitudeSqr() > 0.1 || !(B->IsBuilding() || ((CPhysical*)B)->bInfiniteMass))))
+ moveSpeed += vImpulse * 1.2f * (1.0f/m_fMass);
+ else
+ moveSpeed += vImpulse * (1.0f/m_fMass);
+
+ // ApplyTurnForce
+ CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
+ CVector turnimpulse = CrossProduct(pointpos-com, vImpulse);
+ turnSpeed += turnimpulse*(1.0f/m_fTurnMass);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
+{
+ CVector speedA, speedB;
+ float normalSpeedA, normalSpeedB;
+ CVector vOtherSpeedA, vOtherSpeedB;
+ float fOtherSpeedA, fOtherSpeedB;
+ float speedSum;
+ CVector frictionDir;
+ float impulseA, impulseB;
+ float impulseLimit;
+ CPhysical *A = this;
+
+ if(A->bPedPhysics && B->bPedPhysics){
+ normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
+ vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ speedSum = (B->m_fMass*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(B->m_fMass + A->m_fMass);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
+ impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit; // BUG: game has A's clamp again here, but this can't be right
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ return true;
+ }
+ }else if(A->bPedPhysics){
+ if(B->IsVehicle())
+ return false;
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedB = B->GetSpeed(pointposB);
+
+ normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ normalSpeedB = DotProduct(speedB, colpoint.normal);
+ vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = speedB - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ float massB = B->GetMass(pointposB, frictionDir);
+ speedSum = (massB*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(massB + A->m_fMass);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
+ impulseB = (speedSum - fOtherSpeedB) * massB;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit;
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB);
+ return true;
+ }
+ }else if(B->bPedPhysics){
+ if(A->IsVehicle())
+ return false;
+ CVector pointposA = colpoint.point - A->GetPosition();
+ speedA = A->GetSpeed(pointposA);
+
+ normalSpeedA = DotProduct(speedA, colpoint.normal);
+ normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
+ vOtherSpeedA = speedA - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ float massA = A->GetMass(pointposA, frictionDir);
+ speedSum = (B->m_fMass*fOtherSpeedB + massA*fOtherSpeedA)/(B->m_fMass + massA);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * massA;
+ impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit;
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ return true;
+ }
+ }else{
+ CVector pointposA = colpoint.point - A->GetPosition();
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedA = A->GetSpeed(pointposA);
+ speedB = B->GetSpeed(pointposB);
+
+ normalSpeedA = DotProduct(speedA, colpoint.normal);
+ normalSpeedB = DotProduct(speedB, colpoint.normal);
+ vOtherSpeedA = speedA - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = speedB - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ float massA = A->GetMass(pointposA, frictionDir);
+ float massB = B->GetMass(pointposB, frictionDir);
+ speedSum = (massB*fOtherSpeedB + massA*fOtherSpeedA)/(massB + massA);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * massA;
+ impulseB = (speedSum - fOtherSpeedB) * massB;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit;
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint)
+{
+ CVector speed;
+ float normalSpeed;
+ CVector vOtherSpeed;
+ float fOtherSpeed;
+ CVector frictionDir;
+ float fImpulse;
+ float impulseLimit;
+
+ if(bPedPhysics){
+ normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal);
+ vOtherSpeed = m_vecMoveSpeed - colpoint.normal*normalSpeed;
+
+ fOtherSpeed = vOtherSpeed.Magnitude();
+ if(fOtherSpeed > 0.0f){
+ frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
+ // not really impulse but speed
+ // maybe use ApplyFrictionMoveForce instead?
+ fImpulse = -fOtherSpeed;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass;
+ if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
+ CVector vImpulse = frictionDir*fImpulse;
+ m_vecMoveFriction += CVector(vImpulse.x, vImpulse.y, 0.0f);
+ return true;
+ }
+ }else{
+ CVector pointpos = colpoint.point - GetPosition();
+ speed = GetSpeed(pointpos);
+ normalSpeed = DotProduct(speed, colpoint.normal);
+ vOtherSpeed = speed - colpoint.normal*normalSpeed;
+
+ fOtherSpeed = vOtherSpeed.Magnitude();
+ if(fOtherSpeed > 0.0f){
+ frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
+ fImpulse = -fOtherSpeed * m_fMass;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5f;
+ if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
+ ApplyFrictionMoveForce(frictionDir*fImpulse);
+ ApplyFrictionTurnForce(frictionDir*fImpulse, pointpos);
+
+ if(fOtherSpeed > 0.1f &&
+ colpoint.surfaceB != SURFACE_2 && colpoint.surfaceB != SURFACE_4 &&
+ CSurfaceTable::GetAdhesionGroup(colpoint.surfaceA) == ADHESIVE_HARD){
+ CVector v = frictionDir * fOtherSpeed * 0.25f;
+ for(int i = 0; i < 4; i++)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpoint.point, v);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void
+CPhysical::AddCollisionRecord(CEntity *ent)
+{
+ AddCollisionRecord_Treadable(ent);
+ this->bHasCollided = true;
+ ent->bHasCollided = true;
+ if(IsVehicle() && ent->IsVehicle()){
+ if(((CVehicle*)this)->m_nAlarmState == -1)
+ ((CVehicle*)this)->m_nAlarmState = 15000;
+ if(((CVehicle*)ent)->m_nAlarmState == -1)
+ ((CVehicle*)ent)->m_nAlarmState = 15000;
+ }
+ if(bUseCollisionRecords){
+ int i;
+ for(i = 0; i < m_nCollisionRecords; i++)
+ if(m_aCollisionRecords[i] == ent)
+ return;
+ if(m_nCollisionRecords < PHYSICAL_MAX_COLLISIONRECORDS)
+ m_aCollisionRecords[m_nCollisionRecords++] = ent;
+ m_nLastTimeCollided = CTimer::GetTimeInMilliseconds();
+ }
+}
+
+void
+CPhysical::AddCollisionRecord_Treadable(CEntity *ent)
+{
+ if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){
+ CTreadable *t = (CTreadable*)ent;
+ if(t->m_nodeIndicesPeds[0] >= 0 ||
+ t->m_nodeIndicesPeds[1] >= 0 ||
+ t->m_nodeIndicesPeds[2] >= 0 ||
+ t->m_nodeIndicesPeds[3] >= 0)
+ m_pedTreadable = t;
+ if(t->m_nodeIndicesCars[0] >= 0 ||
+ t->m_nodeIndicesCars[1] >= 0 ||
+ t->m_nodeIndicesCars[2] >= 0 ||
+ t->m_nodeIndicesCars[3] >= 0)
+ m_carTreadable = t;
+ }
+}
+
+bool
+CPhysical::GetHasCollidedWith(CEntity *ent)
+{
+ int i;
+ if(bUseCollisionRecords)
+ for(i = 0; i < m_nCollisionRecords; i++)
+ if(m_aCollisionRecords[i] == ent)
+ return true;
+ return false;
+}
+
+void
+CPhysical::ProcessControl(void)
+{
+ if(!IsPed())
+ m_phy_flagA8 = false;
+ bHasContacted = false;
+ bIsInSafePosition = false;
+ bWasPostponed = false;
+ bHasHitWall = false;
+
+ if(m_status == STATUS_SIMPLE)
+ return;
+
+ m_nCollisionRecords = 0;
+ bHasCollided = false;
+ m_nCollisionPieceType = 0;
+ m_fCollisionImpulse = 0.0f;
+ m_pCollidingEntity = nil;
+
+ if(!bIsStuck){
+ if(IsObject() ||
+ IsPed() && !bPedPhysics){
+ m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f;
+ m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f;
+ float step = CTimer::GetTimeStep() * 0.003;
+ if(m_vecMoveSpeedAvg.MagnitudeSqr() < step*step &&
+ m_vecTurnSpeedAvg.MagnitudeSqr() < step*step){
+ m_nStaticFrames++;
+ if(m_nStaticFrames > 10){
+ m_nStaticFrames = 10;
+ bIsStatic = true;
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecMoveFriction = m_vecMoveSpeed;
+ m_vecTurnFriction = m_vecTurnSpeed;
+ return;
+ }
+ }else
+ m_nStaticFrames = 0;
+ }
+ }
+ ApplyGravity();
+ ApplyFriction();
+ ApplyAirResistance();
+}
+
+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(0x4958F0, &CPhysical::AddToMovingList, PATCH_JUMP);
+ InjectHook(0x495940, &CPhysical::RemoveFromMovingList, PATCH_JUMP);
+
+ InjectHook(0x497180, &CPhysical::AddCollisionRecord, PATCH_JUMP);
+ InjectHook(0x4970C0, &CPhysical::AddCollisionRecord_Treadable, PATCH_JUMP);
+ InjectHook(0x497240, &CPhysical::GetHasCollidedWith, PATCH_JUMP);
+
+#define F3 float, float, float
+ InjectHook(0x495B10, &CPhysical::ApplyMoveSpeed, PATCH_JUMP);
+ InjectHook(0x497280, &CPhysical::ApplyTurnSpeed, PATCH_JUMP);
+ InjectHook(0x4959A0, (void (CPhysical::*)(F3))&CPhysical::ApplyMoveForce, PATCH_JUMP);
+ InjectHook(0x495A10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyTurnForce, PATCH_JUMP);
+ InjectHook(0x495D90, (void (CPhysical::*)(F3))&CPhysical::ApplyFrictionMoveForce, PATCH_JUMP);
+ InjectHook(0x495E10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyFrictionTurnForce, PATCH_JUMP);
+ InjectHook(0x499890, &CPhysical::ApplySpringCollision, PATCH_JUMP);
+ InjectHook(0x495B50, &CPhysical::ApplyGravity, PATCH_JUMP);
+ InjectHook(0x495B80, (void (CPhysical::*)(void))&CPhysical::ApplyFriction, PATCH_JUMP);
+ InjectHook(0x495C20, &CPhysical::ApplyAirResistance, PATCH_JUMP);
+
+ InjectHook(0x4973A0, &CPhysical::ApplyCollision, PATCH_JUMP);
+ InjectHook(0x4992A0, &CPhysical::ApplyCollisionAlt, PATCH_JUMP);
+ InjectHook(0x499BE0, (bool (CPhysical::*)(float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP);
+ InjectHook(0x49A180, (bool (CPhysical::*)(CPhysical*, float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/entities/Physical.h b/src/entities/Physical.h
new file mode 100644
index 00000000..681ab5c8
--- /dev/null
+++ b/src/entities/Physical.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "Lists.h"
+#include "Entity.h"
+#include "Treadable.h"
+
+enum {
+ PHYSICAL_MAX_COLLISIONRECORDS = 6
+};
+
+class CPhysical : public CEntity
+{
+public:
+ // The not properly indented fields haven't been checked properly yet
+
+ int uAudioEntityId;
+ float unk1;
+ CTreadable *m_carTreadable;
+ CTreadable *m_pedTreadable;
+ uint32 m_nLastTimeCollided;
+ CVector m_vecMoveSpeed; // velocity
+ CVector m_vecTurnSpeed; // angular velocity
+ CVector m_vecMoveFriction;
+ CVector m_vecTurnFriction;
+ CVector m_vecMoveSpeedAvg;
+ CVector m_vecTurnSpeedAvg;
+ float m_fMass;
+ float m_fTurnMass; // moment of inertia
+ float fForceMultiplier;
+ float m_fAirResistance;
+ float m_fElasticity;
+ float fPercentSubmerged;
+ CVector m_vecCentreOfMass;
+ CEntryInfoList m_entryInfoList;
+ CPtrNode *m_movingListNode;
+
+ char field_EC;
+ uint8 m_nStaticFrames;
+ uint8 m_nCollisionRecords;
+ char 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;
+
+ uint8 m_phy_flagA1 : 1;
+ uint8 bAffectedByGravity : 1;
+ uint8 bInfiniteMass : 1;
+ uint8 m_phy_flagA8 : 1;
+ uint8 m_phy_flagA10 : 1;
+ uint8 m_phy_flagA20 : 1;
+ uint8 m_phy_flagA40 : 1;
+ uint8 m_phy_flagA80 : 1;
+
+ uint8 m_phy_flagB1 : 1;
+ uint8 m_phy_flagB2 : 1;
+ uint8 m_phy_flagB4 : 1;
+ uint8 m_phy_flagB8 : 1;
+ uint8 m_phy_flagB10 : 1;
+ uint8 m_phy_flagB20 : 1;
+ uint8 m_phy_flagB40 : 1;
+ uint8 m_phy_flagB80 : 1;
+
+ char byteLastCollType;
+ char byteZoneLevel;
+ int16 pad;
+
+
+ // from CEntity
+ void Add(void);
+ void Remove(void);
+ CRect GetBoundRect(void);
+ void ProcessControl(void);
+
+ void RemoveAndAdd(void);
+ void AddToMovingList(void);
+ void RemoveFromMovingList(void);
+
+ // 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)); }
+ float GetMass(const CVector &pos, const CVector &dir) {
+ return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/m_fTurnMass +
+ 1.0f/m_fMass);
+ }
+ float GetMassTime(const CVector &pos, const CVector &dir, float t) {
+ return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/(m_fTurnMass*t) +
+ 1.0f/(m_fMass*t));
+ }
+ void UnsetIsInSafePosition(void) {
+ m_vecMoveSpeed *= -1.0f;
+ m_vecTurnSpeed *= -1.0f;
+ ApplyTurnSpeed();
+ ApplyMoveSpeed();
+ m_vecMoveSpeed *= -1.0f;
+ m_vecTurnSpeed *= -1.0f;
+ bIsInSafePosition = false;
+ }
+
+ void ApplyMoveSpeed(void);
+ void ApplyTurnSpeed(void);
+ // Force actually means Impulse here
+ void ApplyMoveForce(float jx, float jy, float jz);
+ void ApplyMoveForce(const CVector &j) { ApplyMoveForce(j.x, j.y, j.z); }
+ // v(x,y,z) is direction of force, p(x,y,z) is point relative to model center where force is applied
+ void ApplyTurnForce(float jx, float jy, float jz, float rx, float ry, float rz);
+ // v is direction of force, p is point relative to model center where force is applied
+ void ApplyTurnForce(const CVector &j, const CVector &p) { ApplyTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); }
+ void ApplyFrictionMoveForce(float jx, float jy, float jz);
+ void ApplyFrictionMoveForce(const CVector &j) { ApplyFrictionMoveForce(j.x, j.y, j.z); }
+ void ApplyFrictionTurnForce(float jx, float jy, float jz, float rx, float ry, float rz);
+ void ApplyFrictionTurnForce(const CVector &j, const CVector &p) { ApplyFrictionTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); }
+ void ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3);
+ void ApplyGravity(void);
+ void ApplyFriction(void);
+ void ApplyAirResistance(void);
+ bool ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB);
+ bool ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed);
+ bool ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint);
+ bool ApplyFriction(float adhesiveLimit, CColPoint &colpoint);
+
+ void AddCollisionRecord(CEntity *ent);
+ void AddCollisionRecord_Treadable(CEntity *ent);
+ bool GetHasCollidedWith(CEntity *ent);
+
+ // 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(); }
+};
+static_assert(sizeof(CPhysical) == 0x128, "CPhysical: error");
diff --git a/src/entities/Treadable.cpp b/src/entities/Treadable.cpp
new file mode 100644
index 00000000..e2eca36a
--- /dev/null
+++ b/src/entities/Treadable.cpp
@@ -0,0 +1,7 @@
+#include "common.h"
+#include "rpworld.h"
+#include "Treadable.h"
+#include "Pools.h"
+
+void *CTreadable::operator new(size_t sz) { return CPools::GetTreadablePool()->New(); }
+void CTreadable::operator delete(void *p, size_t sz) { CPools::GetTreadablePool()->Delete((CTreadable*)p); }
diff --git a/src/entities/Treadable.h b/src/entities/Treadable.h
new file mode 100644
index 00000000..df5c9ee0
--- /dev/null
+++ b/src/entities/Treadable.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "Building.h"
+
+class CTreadable : public CBuilding
+{
+public:
+ static void *operator new(size_t);
+ static void operator delete(void*, size_t);
+
+ int16 m_nodeIndicesCars[12];
+ int16 m_nodeIndicesPeds[12];
+
+ virtual bool GetIsATreadable(void) { return true; }
+};
+static_assert(sizeof(CTreadable) == 0x94, "CTreadable: error");
diff --git a/src/entities/Vehicle.h b/src/entities/Vehicle.h
new file mode 100644
index 00000000..598b4a57
--- /dev/null
+++ b/src/entities/Vehicle.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "Physical.h"
+
+class CPed;
+
+class CVehicle : public CPhysical
+{
+public:
+ // 0x128
+ uint8 stuff1[120];
+ int16 m_nAlarmState;
+ CPed *pDriver;
+ CPed *pPassengers[8];
+ uint8 stuff2[24];
+ CEntity *m_pCurSurface;
+ uint8 stuff3[160];
+ int32 m_vehType;
+};
+static_assert(sizeof(CVehicle) == 0x288, "CVehicle: error");
+static_assert(offsetof(CVehicle, m_pCurSurface) == 0x1E0, "CVehicle: error");