From 600bf0351476a5a21aabb5429132ddf7f52ac0b9 Mon Sep 17 00:00:00 2001 From: aap Date: Wed, 15 May 2019 16:52:37 +0200 Subject: first commit --- src/entities/Building.cpp | 7 + src/entities/Building.h | 15 + src/entities/CutsceneHead.cpp | 2 + src/entities/CutsceneHead.h | 10 + src/entities/CutsceneObject.cpp | 2 + src/entities/CutsceneObject.h | 9 + src/entities/Entity.cpp | 391 +++++++++++++++++ src/entities/Entity.h | 146 +++++++ src/entities/Object.cpp | 9 + src/entities/Object.h | 50 +++ src/entities/Ped.h | 35 ++ src/entities/Physical.cpp | 916 ++++++++++++++++++++++++++++++++++++++++ src/entities/Physical.h | 137 ++++++ src/entities/Treadable.cpp | 7 + src/entities/Treadable.h | 16 + src/entities/Vehicle.h | 21 + 16 files changed, 1773 insertions(+) create mode 100644 src/entities/Building.cpp create mode 100644 src/entities/Building.h create mode 100644 src/entities/CutsceneHead.cpp create mode 100644 src/entities/CutsceneHead.h create mode 100644 src/entities/CutsceneObject.cpp create mode 100644 src/entities/CutsceneObject.h create mode 100644 src/entities/Entity.cpp create mode 100644 src/entities/Entity.h create mode 100644 src/entities/Object.cpp create mode 100644 src/entities/Object.h create mode 100644 src/entities/Ped.h create mode 100644 src/entities/Physical.cpp create mode 100644 src/entities/Physical.h create mode 100644 src/entities/Treadable.cpp create mode 100644 src/entities/Treadable.h create mode 100644 src/entities/Vehicle.h (limited to 'src/entities') 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 ¢er, 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 ¢er, 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"); -- cgit v1.2.3